Skip to content

[Docs] ADR-0019: commerce aggregates are one storage document each, and the SQL guards they replace - #254

Merged
vedanshujain merged 1 commit into
feat/in-process-commercefrom
docs/adr-commerce-aggregates
Sep 13, 2026
Merged

vedanshujain merged 1 commit into
feat/in-process-commercefrom
docs/adr-commerce-aggregates

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

What

ADR-0019 records the document model the commerce adapters implement on plugin storage: the
inventory model as actually built, the design the remaining adapters must satisfy, and — because
the Postgres adapter is deleted later in this work order — a prose snapshot of every SQL guard
that model replaces, each mapped to the document write that now carries the invariant and the
suite that proves it. This is the ninth increment of work order 02. Documentation only.

What the record decides

  • The aggregate-per-document rule and the two-tier write strategy it implies.
  • The inventory reserve as a durable-claim two-step, with the ordering rule that makes it
    atomicity rather than bookkeeping, the bounded ring for movements, the one accepted residual,
    and the sweeper contract that reaps it.
  • The collection layout with its declared indexes, treated as a read contract.
  • The decisions taken where plugin storage cannot express what the SQL could: prefix-only order
    search, a denormalised customer key served through an in predicate, a single due-at field for
    the email lease, per-customer coupon claims with compensation instead of a unique constraint, a
    live-sku claim document, and the refund capacity lifecycle folded into one CAS.
  • The contention budget as numbers — attempt ceiling, backoff bounds, ring size — with an explicit
    line between what is asserted in a test and what is only measured.

The snapshot

Section 7 is a 17-subsection prose snapshot (7.1–7.17) covering inventory, orders, carts, coupons,
product commerce, settings, entitlements, addresses, sessions, credentials, shipping and tax
rules, notes, payment events and reporting. Every quoted predicate was machine-checked against the
current Postgres adapter — 113 checks, 0 failures — and every number from the built model (section
2) was checked against the storage adapter's own source — 48 checks, 0 failures. Obligations for
adapters not yet built are written in future tense, each tagged with its owning increment. Two
known gaps are recorded as follow-ups outside this work order: the port docblock does not yet
document the retryable contention outcome, and the in-memory inventory fake disagrees with the
port on null-expiry adoption.

Deliberate staleness

ADR-0018's body still says ADR-0019 is "to be written" — left as-is under the folder's
amend-don't-rewrite rule; the README index is corrected instead.

Length

826 lines, justified by the census and full-snapshot scope this record covers — it is written to
survive the Postgres adapter's deletion later in this work order.

Verification

Lint, typecheck and format all clean. All 13 tables in the document render with consistent column
counts. Hygiene grep over the diff is clean. All markdown links resolve. The adr/README.md diff
is limited to the new 0019 entry, the 0018 correction, and a Queued-list annotation.

Review

Two independent reviews over four rounds; the first three requested changes on snapshot
completeness, factual characterisation of the SQL, and tense. Both approve. An independent check
re-verified sampled citations against the current adapter.

🤖 Generated with Claude Code

https://claude.ai/code/session_011NjdC8awspUte5wML6eY2X

…nd the SQL guards they replace

Records the document model the commerce adapters are built on: an invariant
spanning two facts lives in ONE document; a coupling spanning two aggregates is
made idempotently completable by any replayer and swept. `updateIf` for
contended pure-counter writes, `compareAndSet` with bounded jittered retry for
everything multi-field.

Inventory is described as built, not as planned: a durable per-key claim, a
reverse index written before the hold, then the guarded decrement and the hold
in one compare-and-set — a two-step with one healed crash window and one
mitigated one, plus two early out-of-stock exits that differ in whether they
consume the key. The terminal-answer-before-prune ordering rule, the bounded
movement ring with its accepted residual and the sweeper contract that closes
it, and the contention budget are recorded with the numbers the code asserts,
distinguished from the numbers that are only measured and logged.

Five couplings needed a ruling because the naive translation is wrong, and each
is recorded as design with its owning increment: coupon redemption inverts the
claim order because there is no transaction to roll a refused bump back; the
email lease and the orders customer filter each denormalise an OR into one
indexed field; live-sku uniqueness across products and variants becomes a claim
document, replacing two partial unique indexes; refunds keep a four-state
capacity lifecycle arbitrated only on the reserve path.

Because the Kysely stores are deleted later in this effort, section 7 snapshots
the guard semantics of every statement the design replaces — predicate, the
invariant it holds, the document write that now holds it, and the suite that
proves it, saying so where a suite must still be written. Places where the plan
disagreed with the code are corrected in favour of the code, including the
refund row lock (a real timestamp write, not `FOR UPDATE` and not a
self-assignment), the outbox write (a do-nothing conflict, not an upsert), the
batch methods (two take id sets, not four), the lost finalizer (it echoes the
winner rather than rolling back work), and order creation (only the items insert
is multi-row). Two hazards are named rather than inherited silently: the login
throttle has no unique constraint to fall back on, and cross-customer address
isolation must become an explicit ownership check.

ADR-0013, 0016 and 0017 are unchanged and respected; nothing is amended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011NjdC8awspUte5wML6eY2X
@vedanshujain
vedanshujain merged commit 6e50d7d into feat/in-process-commerce Sep 13, 2026
2 checks passed
@vedanshujain
vedanshujain deleted the docs/adr-commerce-aggregates branch September 13, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant