0079. Commands and events are files; publish writes to a core outbox; M6 builds it
0079. Commands and events are files; publish writes to a core outbox; M6 builds it
Amendment, 2026-10-11 (the outbox is a permanent log). In item 4 below, the outbox is core, and it is a permanent, ordered log of the events, not the table the transactional-outbox pattern usually describes, which is emptied after delivery. It replaces Hyper’s event table (DOM-06, DOM-18), and Hyper reads the whole log back, in order, from any cursor (
events.read,PROTOCOL.mdsection 8, in the Hyper specification repository, branchfeat/protocol-phases, commitccc00f9), so a row is never deleted once delivered. The decisions below do not change.
Status
Accepted. Amends ADR-0023 (the outbox is not after 1.0), ADR-0053 (no after-commit step), ADR-0072 (M6 builds domain events and the outbox) and ADR-0075 (the afterCommit seam is not the route for after-commit effects).
Rows: DOM-17, DOM-18, DOM-19, DOM-02 (the publish half; publishes is in ADR-0077), DOM-05, DOM-06, DOM-25. None is open.
Date
2026-10-11
Deciders
operator (Saulo Vallory), 2026-10-11 (DOM-02, DOM-05, DOM-06 at 06:40 and 07:07; the rest the same day).
Context
Hyper’s engine keeps an event table by hand and delivers events from application code. ADR-0023 put jobs, workflows and a provisional outbox after 1.0, and ADR-0053 listed an after-commit step as planned. The study (D7 to D9, D15, D16) asked what a command and an event are, how they are declared and raised, and where delivery lives.
Decision
commandandeventdeclarations live in their own files, and an action names the commands it answers withhandles=[...](DOM-17). A command is a serialised request to run a write action; a query is a serialised read request.- Declaring and raising are separate syntax (DOM-19). An action raises an event with
publish :task-completed. publishwrites the event to the outbox, in the action’s transaction, and an event bus delivers it from there (DOM-02, DOM-06). A surface lists what it sends out withpublishes(ADR-0077).- The outbox is core (DOM-06). It is a permanent, ordered log of the events, not a table emptied after delivery (see the amendment at the top). It comes with domain events in M6 (DOM-18), so ADR-0023’s “after 1.0” no longer covers it. Jobs and workflows stay after 1.0 (ARC-14).
- HA’s hand-written event table is dropped (DOM-18). HA keeps
@meshfw/jsonrpcwith a hand-written method table (ADR-0077). - There is no
after-commitstep, and theafterCommitseam is not used for it either (DOM-25). An effect after commit is apublishand a listener. - Handler words (DOM-05): a handler that updates a read-model row is
apply; a handler that runs commands isprocess.
An illustration. The files, handles= and publish are ruled; the lines inside them are the study’s sketch:
event :task-completed
&taskId
&workspaceId
entity :Task
actions
update :complete handles=[:complete-task]
do
publish :task-completed
Options considered
The study’s D7 (a command in core): (a) generated only, as Command and Query unions with a dispatch() per surface; (b) a command declaration per entity action. The ruling is closer to (b): commands are declared in their own files and an action names them with handles=. D8 (domain events): the form, (a) an events section and an emit step, (b) an emits= option on the action line, © application code on seams only; the timing, (i) after 1.0 with Hyper keeping its table, (ii) before the gate. The ruling is before the gate (M6) and, per DOM-19, declaring and raising are separate syntax. D9 (seam payloads now): (a) reserve an events field on afterWrite and afterCommit; (b) declared events get their own seam later; © decide later. (a) was not taken: with events delivered from the outbox, the seams need no events field (a consequence, not a ruling; the payloads are settled in M6’s design). D15 (outbox): (a) an extension; (b) core (chosen). D16 (after-commit): (a) drop the after-commit step; (b) keep it for one-off side effects (the ruling is (a), and the afterCommit seam is not the route either).
Trade-off analysis
An outbox written in the action’s transaction means an event exists if and only if the write committed, which an after-commit step cannot promise. Delivery from an outbox is usually at least once, so a listener should tolerate repeats; that is a design assumption of this record, not a ruling. Core owns the table, so the Hyper port does not carry its own. The cost is a core table and a bus contract in M6, which the roadmap size does not yet reflect.
Consequences
- ADR-0023 is amended: the outbox is not deferred.
- ADR-0053 is amended with ADR-0082: no
after-commitstep. - ADR-0075: its note that Hyper’s typed events are an application table is replaced by the outbox. The
afterCommitseam stays as built; it is not the route for effects after commit (DOM-25). - The roadmap’s M6 becomes “domain events and the outbox”, and HA drops its event table. The roadmap size for M6 is unchanged here; it is probably too small.
applyalso names state folding in the study’s event-sourcing sketch. ADR-0081 leaves that to the extension, which may use another word.
Action items
- M6: the event and command files,
publish, the outbox table and the bus contract. - Revisit the M6 size.