[Domain] Order timeline and state-change audit - #65
Conversation
Admin order detail now shows a chronological timeline (state transitions with who/when, fulfillment, cancellation with reason, reconciliation resolved, notes), and every state change is durably audited going forward (admin-UX Increment 1, timeline slice — the last slice of the order-detail build). - Domain: OrderEvent + OrderStore.listEventsForOrder port read; getOrderTimeline use-case merging the audited state-change spine with the order's derived artifacts (created/notes/fulfillment/cancellation/reconciliation) into one chronological view (stable same-timestamp tie-break). - Adapters: migration 0014_order_events (append-only, portable DDL); the event INSERT rides the shared #flipAndEnqueue transaction so it is atomic with the guarded flip and records nothing on a replay/lost race. Fake mirrors it. - Service: GET /admin/orders/:id/timeline (read-only, internal-token guarded). - Plugin: read-only Timeline section on the order detail (sandbox-clean). Verification: full suite green against Postgres (1405 tests) incl. exactly-one audit event under a concurrent-flip race; plugin verified in the workerd sandbox. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XhEjVemVDUk62ohJ5nkFYx
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
VERDICT: APPROVE — no blocking issues (posted as comment; self-authored PR blocks a formal review).
|
|
Verdict: Approve (posting as comment — GitHub rejects self-authored review) Verified independently in the worktree against Postgres (127.0.0.1:55432): full suite green (138 files / 1405 tests, matches PR claim), Findings:
No blocking issues found. |
What
The order timeline / audit vertical slice (admin-UX Increment 1, slice 5 — the last of the order-detail build). The admin order detail page now shows a chronological timeline of everything that happened to an order — state transitions (with who/when), fulfillment recorded, cancellation with reason, reconciliation resolved, and notes — and every state change is durably audited going forward.
Design
Choke-point audit capture. After #63/#64, ALL order state flips (
transition/markPaid/markFailed/expire/recordFulfillment/cancelOrder) route through ONE shared primitive —#flipAndEnqueue(a guardedWHERE state=:fromStateUPDATE + the outbox INSERT in one transaction). This slice extends THAT primitive (and the in-memory fake's equivalent) to also INSERT one append-onlyorder_eventsrow IN the same transaction. So every state change self-audits with no behavior change to the flips themselves, and — because the event is written only after the guarded flip matched a row — a replayed or lost-race flip is a 0-row miss that records NO event (audit never double-counts a replay; falls straight out of the choke-point design, and is tested under a Postgres race).Merge vs. write, per artifact.
order_eventsis kept lean — it is ONLY the state-change spine (the history nothing else recorded before). Everything that already carries its own durable timestamp is merged at read time, never double-written:order_eventsorder.createdAtorder_notesorder.fulfillmentorder.cancellationorder.reconciliationResolutionThe fulfillment/cancel flips also stamp their recorder/canceller as the state-change event's
actor(the who this domain knows); bare transitions have no modeled actor (null).Graceful degradation. Orders whose transitions predate this migration have no
order_eventsrows. The timeline read-model degrades: their creation moment, notes, and any recorded fulfillment/cancellation/resolution still populate the view, and astateChangesAuditedflag (false) lets the surface say the state-change history is partial. Events are recorded from this release onward.Layers
[Domain]—OrderEvent/OrderEventKindtypes +OrderStore.listEventsForOrder; puregetOrderTimelineuse-case (merge + stable same-timestamp tie-break:atASC → kind rank → insertion order).[Adapters]— forward-only migration0014_order_events(append-only, portable text DDL,(order_id, at, id)index); the event INSERT rides#flipAndEnqueue's transaction; fake mirrors it.[Service]—GET /admin/orders/:id/timeline(read-only, internal-token guarded; structured entries on the wire, no money, no PII beyond the existing detail).[Plugin]— read-only Timeline section (when/what/who/detail table), honest partial-history caption, independent degradation; sandbox-clean (Block Kit only).Verification
127.0.0.1:55432): 138 files / 1405 tests, incl. the neworderTimelineContracton fake + sqlite + pg.markPaid(new); extended the fulfillment concurrent-record race to assert exactly oneprocessing → shippedevent.pnpm lint/pnpm typecheck/pnpm format:checkclean; changeset added (minor: domain/store-postgres/service/plugin).🤖 Generated with Claude Code
https://claude.ai/code/session_01XhEjVemVDUk62ohJ5nkFYx