Skip to content

Walkthrough: The Pull-Replenishment Round Trip

This walkthrough traces FLOW-0001 — The Pull-Replenishment Round Trip — a kanban card’s scan, demand collation, order commitment, dock receipt, and distribution back to the waiting card — to the calls that implement each leg. Four modules participate: Kanban Cards, Demand, Orders, and (on paper only) Receiving, with Inventory Items providing the pool that Leg 3 births. Coverage is uneven by leg: Leg 2 (composing and committing the order) is solid and fully tested; Leg 3 (the delivery is received) is the flow’s biggest gap, because the Receiving module the narrative leans on for the capture and the freeze does not exist yet — everything downstream of that freeze is real, but callable only as independent operations rather than as effects of a receipt.

See the walkthroughs index for the shared call shape and the built-vs-designed legend used throughout.

Leg 1 — The card signals and demand collates

Section titled “Leg 1 — The card signals and demand collates”
#Business stepAPI callWhat changesRefusals you can hitEvidence
1.1A kanban card exists on an item, hanging at the bin’s reorder pointPOST /v1/kanban/kanban-cardKanban Cards: new card, status = AVAILABLEUnknown item eId → 404/400 (not asserted in the suite)a-kanban-lifecycle.ui-compat.test.ts:36-38
1.2Sam empties the front bin and scans the card at the reorder pointPOST /v1/kanban/kanban-card/{eId}/event/requestKanban Cards: status AVAILABLE → REQUESTING; card enters the details/requesting bucketSee Fidelity notes — the illegal-transition surface is weaker than the narrative impliesa-kanban-lifecycle.ui-compat.test.ts:45-58; S-5 finding :113-129
1.3The cycle’s signal becomes eligible for collation (the narrative draws this as a notification)— (see Fidelity notes)——Absence: CardCycleId.kt:8 (a doc comment only); the sole kanban→demand listener is the withdrawal listener at DemandMembershipSagas.kt:59
1.4Eligible signals are collated into a demand item (Open)POST /v1/operations/demand/demand-item/create (or .../create-with-cycle)Demand: new demand item, status = OPEN, demandQty seeded, membership empty or holding the named cardRRetiredRef — the referenced item is retired. RForeignCycle (403) — the member card doesn’t resolve in this tenantdemand-item.functional.test.ts:44-57
1.5The demand item’s advisory total is adjusted before it goes outPOST /v1/operations/demand/demand-item/adjust-qty (or .../reset-qty)Demand: demandQty set. status unchangedRFrozen — no longer OPEN. RDemandClosed — not livedemand-item.functional.test.ts:60-78
1.6The demand item becomes eligible for an orderPOST /v1/operations/demand/demand-item/releaseDemand: status OPEN → RELEASEDRBadState — repeated release. RDemandCloseddemand-item.functional.test.ts:81-100

Leg 2 — The buyer composes and commits the order

Section titled “Leg 2 — The buyer composes and commits the order”
#Business stepAPI callWhat changesRefusals you can hitEvidence
2.1David reviews the demand queueGET /v1/operations/demand/demand-item/for?item=&station=&asOf=Nothing (read)—demand-item-client.ts:173-178
2.2Create (Draft) with the supplierPOST /v1/procurement/order/order/createOrders: new order, status = DRAFT, priority = UNDEFINED. Under mutation-mode: PROPAGATE, even a name-only supplier is linked at rest to a found-or-created VENDOR roleRRetiredRef (403) — a linked reference names a retired vendor. RForeignRef (403) — doesn’t resolve to a live VENDOR roleorder.functional.test.ts:22-35; RRetiredRef :136-162
2.3Choose or override the supplier while DraftPOST /v1/procurement/order/order/update-supplier (revert: .../reset-to-supplier)Orders: supplier binding replaced; reset drops per-order overrides, keeps the referenceRForeignRef, RRetiredRef, RFrozen (post-submit), ROrderClosedorder.functional.test.ts:38-63, 66-81
2.4Add lines from the demand queuePOST /v1/procurement/order/order-line/add-line (mode: FROM_DEMAND; other arms FROM_ITEM, FREE_FORM)Orders / order-line: new line, lineStatus = L_OPEN, demand set, quantity set, received keyed zero. The item pin is server-derived, never client-suppliedRForeignRef — dangling demand reference. RDemandIneligible — not RELEASED. RDemandHeld — already held by a live line. RFrozen — parent SUBMITTEDorder-line.functional.test.ts:28-54
2.5Attach a further demand item to an existing linePOST /v1/procurement/order/order-line/attach-demand (undo: .../detach-demand)Orders / order-line: demand set grows (or shrinks on detach)RDemandHeld, RDemandIneligible, RNotAttached on a detach of an unattached demandorder-line.functional.test.ts:136-170
2.6Adjust a line’s quantity while composingPOST /v1/procurement/order/order-line/update-lineOrders / order-line: quantity set; item pin untouchedRFrozen (SUBMITTED parent), RLineClosedorder-line.functional.test.ts:111-133
2.7Remove a line while composingPOST /v1/procurement/order/order-line/remove-lineOrders / order-line: line tombstones — GET by id 404s, drops from by-orderRFrozen, RLineClosedorder-line.functional.test.ts:284-303
2.8Assign the orderPOST /v1/procurement/order/order/update-detailsOrders: the priority / assignee / notes cluster (SET semantics — an absent field clears). See Fidelity notes on the assigneeRFrozen post-submit, ROrderClosedorder.functional.test.ts:110-133; assignee typing order-client.ts:58-60
2.9Internal notes on the draftPOST /v1/procurement/order/order/annotateOrders: internalNotes replaced wholesale (SET, not append); one ANNOTATE occurrence per actROrderClosed on a terminal orderorder.functional.test.ts:84-107
2.10Submit — the order freezes; every serviced demand item starts productionPOST /v1/procurement/order/order/submitOrders: status DRAFT → SUBMITTED; structure, supplier, header details freeze. Demand: every serviced demand item RELEASED → IN_PROCESS, each minting its own holding poolRNoLines, RNoSupplier, RDemandIneligible, RRetiredRef (403, re-checked at the commitment point)order-submit-settle.functional.test.ts:24-34; refusals at :36-46, 48-57, 59-74, 102-116
2.11Verify the commitment frozePOST .../order/update-supplier or .../update-details on the SUBMITTED orderNothing — refusedRFrozen on bothorder-submit-settle.functional.test.ts:77-88, 91-99
2.12The committed order goes to the supplier (Vera)— (physical step)Nothing in the system—Narrative only — no transport exists in this build

Leg 3 — The delivery is received, inventory is born, distribution reaches the card

Section titled “Leg 3 — The delivery is received, inventory is born, distribution reaches the card”
#Business stepAPI callWhat changesRefusals you can hitEvidence
3.1Vera ships; the delivery arrives at the dock with its packing slip— (physical step)——Narrative only
3.2Keisha creates a receiver at the dock and adds lines from the orderdesigned — Receiving——Absence: no receiving/ package, route, or test anywhere in the tree
3.3Receive — the freeze: accepted/rejected counts, reason, captured facts, and attribution actuals in one actdesigned — Receiving——Same absence
3.4The accepted goods are born as an inventory pool located at the dockPOST /v1/resources/inventory/inventory-pool/create — the effect exists standalone, but nothing births it from a receiptInventory Items: new pool; content keyed-zero or seed. No location or custody field on the wireNone at genesis; later ROverdraw, RIncomparable, RForeignPoolinventory-pool.functional.test.ts:21-31
3.5Record the production delivery — material lands in the demand item’s holding poolPOST /v1/operations/demand/demand-item/record-productionDemand: delivery pool folded into the holding pool; a ProductionDelivery row reified, status = CREATED; demand item stays IN_PROCESS; ProductionRecorded firesRTargetNotInProcess, RWrongItem, RForeignRef (403)demand-item.functional.test.ts:148-192
3.6Delivery announced (notification); Orders accrues to the line’s received amounts— (system notification — wired). Manual repair route: POST /v1/procurement/order/order-line/record-receiptOrders / order-line: received accrues pairwise; listener stamps notificationId = the delivery’s eId for idempotencyRNoDemand — free-form line. RLineClosedSubscription Module.kt:98-100; accrual order-receipt.functional.test.ts:26-43
3.7A delivery is revoked (the reversing entry)POST /v1/operations/demand/demand-item/revoke-delivery; order side reverses via listener or POST .../order-line/reverse-receiptDemand: ProductionDelivery CREATED → REVOKED (terminal). Orders: received decrements pairwiseRDeliveryClosed — not livedemand-item.functional.test.ts:194-216; order-receipt.functional.test.ts:46-59
3.8Distribute to the waiting card — its cycle loads the poolPOST /v1/operations/demand/demand-item/distributeDemand: holding pool content allocated to member card cycles; named fills cycles’ processing completes firstRBadAllocation — outside live membership. RBadState — not IN_PROCESSRefusal demand-item.functional.test.ts:235-252; happy arc untested, see Fidelity notes
3.9The demand item’s trip endsPOST /v1/operations/demand/demand-item/completeDemand: status IN_PROCESS → COMPLETERUndistributed — holding pool still has content. RBadStatedemand-item.functional.test.ts:218-233
3.10The line’s received amounts make Close line available; the order closesPOST /v1/procurement/order/order-line/close-line, then POST /v1/procurement/order/order/closeOrders: lineStatus L_OPEN → L_CLOSED, then order status SUBMITTED → CLOSEDRLineClosed — second close. RLinesOpen — order still has a live L_OPEN lineorder-submit-settle.functional.test.ts:119-131, 134-145

Leg 4 — The card returns and replenishment re-arms

Section titled “Leg 4 — The card returns and replenishment re-arms”
#Business stepAPI callWhat changesRefusals you can hitEvidence
4.1Keisha restocks the shelf and returns the card to its position— (physical step)——Narrative only
4.2The card’s processing completes and it is received backPOST /v1/kanban/kanban-card/{eId}/event/complete-processing then .../event/receiveKanban Cards: status IN_PROCESS → READY → FULFILLED; card leaves details/requesting, appears in details/FULFILLEDIllegal transitions weakly enforced — see 1.2kanban-lifecycle.acceptance.test.ts:24-37
4.3The FULFILLED card is un-marked back into circulationPOST /v1/kanban/kanban-card/{eId}/event/unmarkKanban Cards: status transition — captured as a pinned baseline, not assertedNot asserteda-kanban-lifecycle.ui-compat.test.ts:96-111 (S-4)
4.4Sam draws from the restocked bin — the cycle reads in usePOST /v1/kanban/kanban-card/{eId}/event/useKanban Cards: status moves to the in-use readingNot covered by any testEvent declared kanban-client.ts:9-20; no test exercises it
4.5The front bin empties again — the cycle reads depletedPOST /v1/kanban/kanban-card/{eId}/event/depleteKanban Cards: status moves to the depleted readingNot coveredEvent declared kanban-client.ts:9-20; no test exercises it
4.6Re-trigger demand — the next lap begins on the same cardPOST /v1/kanban/kanban-card/{eId}/event/request (the restart shape)Kanban Cards: status returns to REQUESTING; a re-request from REQUESTED rolls the card back rather than refusingNone — the restart returns 200a-kanban-lifecycle.ui-compat.test.ts:61-75 (S-2)
#Business stepAPI callWhat changesRefusals you can hitEvidence
A1.1While the order is still a Draft, David abandons itPOST /v1/procurement/order/order/cancelOrders: status DRAFT → CANCELED (terminal). Every attached demand item returns to the queue still RELEASED — a rival line can attach it immediatelyROrderClosed — re-cancel on an already-terminal orderorder.functional.test.ts:165-176; hold-dies-with-the-order arc order-lifecycle.acceptance.test.ts:93-124
A1.2Cancel is unavailable once SubmittedPOST .../order/cancel on a SUBMITTED orderNothing — refused (by design)Not directly witnessed — see Fidelity notesReason codes OrderReason.kt:38, 73; no test asserts the SUBMITTED case
A1.3The canceled order is deletedPOST /v1/procurement/order/order/deleteOrders: order tombstones — GET by id 404sRNotTerminal — still live. ROrderClosed afterwardsorder.functional.test.ts:179-187, 189-200

Part of the delivery is rejected at the dock

Section titled “Part of the delivery is rejected at the dock”
#Business stepAPI callWhat changesRefusals you can hitEvidence
A2.1Keisha counts what arrived and records accepted 8 / rejected 2 with the reasondesigned — Receiving——Same absence as Leg 3 steps 3.2–3.3
A2.2Only the accepted goods are born as the inventory poolPOST /v1/resources/inventory/inventory-pool/create with {seed:{each:8}} — reachable, but nothing derives the 8 from an accept/reject splitInventory Items: a pool of eight—inventory-pool.functional.test.ts:21-31
#Business stepAPI callWhat changesRefusals you can hitEvidence
A3.1Irene withdraws the card from circulationPOST /v1/kanban/kanban-card/{eId}/event/withdrawKanban Cards: the lap ends from any live state; the card has no live cycleNot covered by any testEvent declared kanban-client.ts:9-20; no test
A3.2The card’s trip ended (notification) — Demand hears it— (system notification — wired)Demand: the ended cycle’s signal detaches from membership; intent decrements by the withdrawn card’s genesis quantityRFrozen, RNotAttached, RCycleLive are folded as benign no-ops by the listener; anything else propagatesDemandMembershipSagas.kt:59 (subscription), :334-359 (listener body)
A3.3Manual repair of the same reconciliationPOST /v1/operations/demand/demand-item/detach-withdrawnDemand: same as A3.2RNotAttached, RCycleLive, RFrozenDemandService.kt:82-83
A3.4A demand item left empty with nothing else expected cancels itself— (system — the R7 composite inside DetachWithdrawn; no separate call)Demand: status → CANCELED when the withdrawal emptied membership and drained the intent; self-cancel is gatedRHasCards guards a cancel that still has attached cyclesDemandMembershipSagas.kt:126-136, 196-215, 287

What the tests witness today versus what the narrative claims:

  • The dominant gap is Leg 3’s capture and freeze (steps 3.2–3.3). The Receiving module is design-only — no Kotlin package, no route, no api-test. Everything downstream of the freeze (pool birth, production delivery, accrual, distribution) is real, but callable independently rather than as an effect of a receipt.
  • Step 1.3’s collation notification does not exist. No listener couples a card’s request event to demand collation — the only kanban→demand listener in the build is the withdrawal listener used in the alternates (A3.2), which reacts to a card leaving its trip, not entering one. Demand items are born by an explicit create / create-with-cycle call from a composing caller; the narrative’s “which signals group together is the composing caller’s decision” is faithful to the API, but the automatic-eligibility notification it draws alongside that claim is not.
  • The Leg 3 “not yet wired” caveat in the narrative page is now false, and this walkthrough already reflects the fix. The delivery-announcement wire (step 3.6) is live end to end — emission, subscription, idempotency, and a self-heal probe — which is why it’s marked as a wired system notification above rather than a gap.
  • Kanban’s illegal-transition refusals (step 1.2) are weaker than the narrative implies. event/receive on a fresh AVAILABLE card returns 200 and lands FULFILLED rather than refusing; the suite records this as a pinned baseline rather than asserting it as a guard.
  • use, deplete (4.4–4.5), and withdraw (A3.1) have zero test coverage — declared on the client, never exercised. A3.1’s downstream reconciliation (A3.2–A3.4) is therefore witnessed only from the demand side, via the manual detach-withdrawn route’s guard refusals.
  • Distribute (3.8) is covered only at the guard level. The happy distribute-over-live-members arc needs a full CardCycle fixture and is a documented register delta rather than a finished test.
  • Cancel-on-SUBMITTED (A1.2) is untested. The only cancel refusal witnessed anywhere in the suite is a re-cancel of an already-CANCELED order — nothing asserts that canceling a SUBMITTED order refuses, which is the narrative’s actual claim.
  • The assignee “pin” (2.8) has no API counterpart. assignee is a plain string in the order payload with no Staff-module resolution, unlike the vendor and item pins that are real.