ConceptsEvent model

Event model

Flightdeck’s events are in-process, today — internal events never cross a network. EventBridge/SQS exist only inside edges/ (see Topology). The contract shape is deliberately the same shape an out-of-process bus would use, so promoting a module to its own service later doesn’t require redesigning its events.

Registration — one line, impossible to half-register

Every event is defined once in contracts/src/events/<domain>.ts and rolled up into a single central registry, EVENTS, in contracts/src/events/index.ts. If an event name isn’t in EVENTS, it cannot be published and cannot be subscribed to — the registry is the whole contract surface, checked at both publish and subscribe time.

Schema evolution is additive within a version; a breaking payload change ships as a new .v2 definition. A shipped payload is never mutated in place.

The envelope

Every event carries a tenant_refrequired, not optional — and a payload that is self-contained: a consumer must never need to call back into the producer to make use of an event. This is what lets consumers stay decoupled even though dispatch is in-process today.

The dispatcher

app/src/dispatcher.ts is a small in-process pub/sub:

  • Modules register consumers at boot (dispatcher.register(moduleName, handlers)); registering after boot throws — the dispatcher is sealed once startup completes.
  • Subscribing to an event name that isn’t in the central EVENTS registry throws immediately, at boot — not at first dispatch.
  • Publishing an unregistered event name throws.
  • Consumers run after the publishing transaction. A consumer failure is logged and surfaced — never silently swallowed.

Built-must-be-wired, applied to events

A registered event with zero consumers, or a module that calls publish on an event nobody imports, is exactly the kind of orphan the conventions checker looks for. Registering an event is not the same as shipping it — the consumer’s infra.ts/dispatcher registration is the step that actually makes it fire.

Adding a new event — the four steps

  1. Define the payload type in contracts/src/events/<domain>.ts.
  2. Add its line to the central EVENTS object in contracts/src/events/index.ts.
  3. Publish it from the owning module via the injected bus.publish.
  4. Wire the consumer(s) — a module registers its handler with the dispatcher at construction time. A registered-but-unconsumed event is an orphan; a consumer with no upstream publisher is a dead scaffold. Both fail the conventions check.