Decision 0018 — Inbound Cardinality, Receipt And Reversal Authority¶
- Status:
ACCEPTED - Authority:
RATIONALE_ONLY - Date:
2026-08-26 - Source baseline:
qa@49fdcb61963a9e475bb61ec3d6d48b5790329adb - Requires:
0017-tenant-runtime-authority.md - Implementation handoff:
../tasks/NS2-019.md
Context¶
Current contracts accept many commercial lines and many fulfillment items, and the MySQL writer validates that a fulfillment item references a line in its selected commercial order. The active inbound path is still single-line in practice:
- the UI creates one item and executes only
detail.items[0]; - receipt policy rejects an inbound order unless its total line count is exactly one;
UQ_NS2_LOT_SOURCE_ITEMpermits only one active lot/receipt per fulfillment line;- receipt finalization marks the entire order complete after one line;
- Scale ticket selection is order-level, no-scale eligibility is unresolved, and receipt reversal has a capability but no command/writer;
- the receipt transaction already creates Lot and Ledger together, while Warehouse Map already owns UUID-backed placement separately.
The accepted database has multi-line records, immutable ledger rows and active placement identity. The missing contract is cardinality and orchestration, not a missing generic workflow platform.
Decision¶
Cardinality And Conservation¶
Commercial Purchase 1 -> many Commercial Lines
Commercial Line 1 -> many Inbound Fulfillment Lines
Inbound Fulfillment Order 1 -> many Fulfillment Lines
Fulfillment Line 1 -> many Receipt Events
Receipt Event 1 -> 1 Inventory Lot -> many immutable Ledger Entries
Inventory Lot 1 -> 0 or 1 active Warehouse Map Placement
Each fulfillment line references exactly one commercial line from the selected purchase. Summed active fulfillment quantity for a commercial line cannot exceed ordered quantity. Summed finalized receipt quantity cannot exceed the fulfillment line. Receipt net weight follows the user-entered physical evidence; the previous fulfillment weight is not an upper bound. A duplicate command UUID with the same normalized payload is idempotent; a different payload conflicts.
ordered = open fulfillment + received - reversed and
received = lot opening ledger + receipt corrections + receipt reversals are binding readbacks.
Quantity over receipt is rejected in Phase 2; this does not impose a planned-weight cap.
2026-09-10 direct user clarification (NS3-101/104): user-entered weight is authoritative; do not reject it merely for exceeding the previous line weight. Do not add mandatory planned/actual fields, a tolerance gate or a new schema for this purpose. Existing quantity, evidence, ledger and correction/reversal contracts remain.
Typed Inbound Policy¶
The existing policy/Runtime owner adds two code-owned typed settings:
INBOUND_SCALE_POLICY:REQUIREDorCERTIFIED_NO_SCALE_ALLOWED;INBOUND_PARTIAL_RECEIPT_POLICY:ALLOWEDorFULL_ONLY.
The two acceptance profiles are semantic policy combinations, not customer names or numeric IDs:
- scale required + partial allowed;
- certified no-scale allowed + full only.
Missing, invalid or unavailable policy blocks the command. No-scale requires explicit certified quantity/net-weight evidence, reference and reason; planned fulfillment weight is not physical proof.
Scale Work And Evidence¶
No durable queue or new package is added. The Scale work queue is a read projection of open inbound fulfillment lines plus effective policy. A fulfillment line is the work owner. The existing Scale ticket lifecycle is the weigh session; its completed gross/tare result is the immutable evidence projection. The ticket gains an exact fulfillment-line UUID relationship so an order-level ticket cannot satisfy the wrong line.
Scale correction/void stays owned by @ctc/scale. Receipt finalization locks and validates the
chosen completed evidence in the same MySQL transaction but does not mutate or delete it.
Receipt Transaction Owner¶
@ctc/inventory owns the finalize/correct/reverse command contract and conservation policy.
@ctc/db-mysql implements one transaction per command and owns numeric compatibility, locks,
schema checks and authoritative UUID reread.
Finalize locks tenant, fulfillment line, prior receipt totals and optional Scale evidence; inserts an immutable receipt event, creates one lot, appends its opening ledger entry and updates line/order status from reconciled totals. The order becomes complete only when every active line is complete.
The route remains authentication -> effective capability -> parse -> command -> response. React selects a line and typed evidence; it owns no business invariant or SQL.
Placement¶
Warehouse Map remains the sole placement effect owner. Finalize creates the lot as HOLD when it is
unplaced. The existing UUID-targeted Warehouse Map placement command creates/updates the active
placement and only then opens the lot. Placement failure leaves a truthful retryable HOLD lot; it
does not roll back a committed physical receipt or fabricate success.
Correction And Reversal¶
Correction never rewrites history. It appends a reasoned receipt correction and compensating ledger delta under optimistic version/conservation checks. Reversal marks the receipt event reversed, appends the exact negative ledger delta, closes/releases active placement, reopens fulfillment line/order totals and retains Scale evidence.
An active or progressed downstream allocation blocks receipt correction/reversal until its owning allocation reversal completes. Lots and ledger rows are never deleted. Unknown commit results are recovered only by authoritative receipt UUID reread and normalized payload comparison.
Capability And Exposure¶
Existing inventory.receipt.finalize and inventory.receipt.reverse capabilities remain the action
gates, independent from menus. Module/policy readiness gates execute server-side. The existing
Inbound Fulfillment surface becomes line-aware; no new Product surface is approved.
Alternatives¶
Alternative 1 — Patch The Current One-Line Scale-Required Flow¶
Keep one lot per source line, remove only the UI items[0] assumption and continue completing an
order from the first receipt. This is the smallest initial diff but cannot support partial receipts,
multi-line completion, no-scale policy or reversal. Rejected because it preserves the blocking data
model and causes a second rewrite.
Alternative 2 — Add A Workflow Service, Queue And Event Bus¶
Create generic workflow records and asynchronous events across commercial, fulfillment, scale, inventory and placement. This increases packages, dependencies, failure states and unknown-result coordination before any immediate consumer needs them. Rejected as speculative infrastructure.
Alternative 3 — Compose Existing Owners With One Inventory Command¶
Keep commercial intent, fulfillment work, Scale evidence, inventory conservation and Warehouse Map placement in their current semantic owners. Add only the receipt event/cardinality data required by partial receipt and reversal, with one MySQL transaction per command and authoritative reread. Accepted as the smallest boundary that satisfies every required invariant.
Consequences¶
- NS2-019 must remove every inbound business use of
items[0]and make the UI line-aware. - The one-active-lot-per-source-line index must be replaced by receipt-event identity and explicit active-total constraints; migration requires exact preflight/readback/rollback.
- Typed inbound policy becomes part of effective Runtime and Admin readiness diagnostics.
- Receipt and placement remain separate commands with truthful HOLD recovery between them.
- No new package, service, queue, event bus, Product surface, customer backend or architecture exception is approved.
Expected implementation structure: existing module roots, Web route/UI/composition, db-mysql adapter, one forward/rollback migration and focused tests. Public/domain/application identities remain UUID.
Revisit¶
Revisit only if measured throughput requires an asynchronous queue, one receipt must span multiple fulfillment lines, regulatory evidence requires a separate weigh-session record, or Warehouse Map moves behind an external service. Any revisit requires a new accepted ADR and migration plan.
Rollback before implementation is to supersede this decision. After implementation, run the paired schema rollback only after proving zero multi-receipt/correction/reversal rows and restoring the previous one-line caller boundary; never drop receipt or ledger evidence opportunistically.
---¶
Source:
new-system/docs/decisions/0018-inbound-authority.md— ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.