Replace upsert effects with create, update and remove, and embed bookings in creates
Decision
Replace each upsert effect with separate create, update and remove effects. A create for an accommodation or a journey leg may embed its booking, and applying it writes the planning record and the linked reservation together, so one user action is one modification. Storage keeps plan and reservation apart, with the accommodation under its destination and the reservation at trip level, because the discrepancy check depends on that split.
Derive each entity's id as a UUID v5 of the creating modification's id and a slot counted per kind. Replay reproduces the ids, and two branches forked from the same point give their new entities different ids, so the version diff (ADR-0048) never has to reconcile them.
A journey leg names its endpoints the way every other effect names a destination, and applying the effect resolves them. When a transport document lacks what a leg needs, the model writes a bare transport reservation, and the discrepancy check shows it to the user to attach.
Why
Booking effects were shaped when the only callers were the app's forms and the MCP tools, and both know the trip before they write. A model reading bookings out of a dropped confirmation email doesn't. An upsert decided between create and update by whether its id already existed, and marking an accommodation as booked took two writes joined by a client-minted id. A leg's endpoints had to be stored identifiers, which a model can't produce, so importing a transport booking from a document was impossible.
Rejected alternatives
- One upsert per kind. A model reading a document can't know whether an id it made up collides with an existing one.
- Two effects linked by an id the client mints. That allows a state where only one of them landed.
- An effect pointing at a sibling by its position in the batch. Applying one effect would then reach into others.
- One counter per modification for ids. Reordering effects inside a modification would change every later id.
- Having the model guess missing structure. Better to show the gap than invent an answer.
Consequences
Direct edits produce one effect per modification. Batches come from parsed sentences and extracted documents, so anyone auditing only the command layer will think grouping is dead weight, and be wrong. Replay marks a modification unapplied if any of its effects misses its target, which is coarse and will misreport as batches grow. Three effects per kind lengthen the apply pipeline, and every future kind pays that. The accommodation and transport booking shapes look similar enough to merge, but merging them would couple two domains for little gain.