Skip to content

Model

Bounded contexts, aggregates, vertical slices.

The shape under every CORA feature: a BC owns a slice of the domain, an aggregate owns a consistency boundary inside it, a vertical slice owns one command or query end-to-end. Get these three right and adding a feature stops being a refactor.

Bounded contexts

CORA is a set of bounded contexts (BCs) organised into tracks. Each BC owns its model, language, and API surface. Each aggregate inside a BC owns one consistency boundary.

Status legend: Active = aggregate is shipping and listed under Modules; Planned = scoped, not yet implemented.

Track BC Aggregates Status
Foundation access actor Active
Foundation equipment family, asset Active
Track A (episodic procedures) recipe capability, method, practice, plan Active
Track A run run Active
Track A campaign campaign Active
Track B (continuous operations) supply supply Active
Track B operation procedure Active
Track C (trust topology) trust zone, conduit, surface, policy Active
Governance safety clearance Active
Governance caution caution Active
Governance calibration calibration Active
Decisions and agents decision decision Active
Decisions and agents agent agent Active
Independent subject subject Active
Independent data dataset Active
Decisions and agents strategy strategy Planned
Independent budget budget Planned

Fifteen BCs and 22 aggregates ship today; two more BCs are reserved with single planned aggregates. Tracks group BCs by the lens they take on operations: Foundation owns the shared facts every other track refers to, Track A is the batch-shaped recipe ladder, Track B is the always-on resource and procedure side, Track C is the trust topology that gates the others, Governance owns the formal and informal operator controls, Decisions and agents own the audit and configuration of consequential choices, and Independent covers what doesn't sit on any single track.

Aggregates

The unit of consistency inside a BC: state, invariants, events. Every aggregate is a stream in the event store, identified by (stream_type, stream_id) and folded from its events into an in-memory state per command. The same shape repeats across BCs: a state.py carries the fields and invariants, an events.py carries the closed union of events, and an evolver.py folds events back into state. See Reference/Modeling for the rules.

Vertical slices

One folder per command or query, holding everything the slice needs:

features/<verb>_<aggregate>/
├── command         input shape
├── decider         pure rule: (state, command) -> events
├── handler         shell: wires core to ports
└── adapters        one per surface (HTTP API, agent tool, ...)

Independently readable, testable, deletable. For the in-repo file layout, see Reference/Layout.

What each piece does

  • Command. Immutable input shape, one per slice. Captures the caller's intent. Structurally validated at the adapter, semantically validated by the decider.
  • Functional core. Decider takes a command and current state, returns events. Evolver folds events back into state. Both pure, no I/O.
  • Imperative shell. Handler wires the core to side-effect ports (clock, IDs, event store, authorize, idempotency). Real ports in production, fakes in tests.
  • Thin adapters. One per surface, translates protocol-specific input into the same handler call. Schema validation only, no business rules.

Query slices follow the same shape with query in place of command and no decider, since there's no state change. Single-record reads fold the stream; list and filter reads hit a projection. See Reference/Patterns.