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 step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| 1.1 | A kanban card exists on an item, hanging at the bin’s reorder point | POST /v1/kanban/kanban-card | Kanban Cards: new card, status = AVAILABLE | Unknown item eId → 404/400 (not asserted in the suite) | a-kanban-lifecycle.ui-compat.test.ts:36-38 |
| 1.2 | Sam empties the front bin and scans the card at the reorder point | POST /v1/kanban/kanban-card/{eId}/event/request | Kanban Cards: status AVAILABLE → REQUESTING; card enters the details/requesting bucket | See Fidelity notes — the illegal-transition surface is weaker than the narrative implies | a-kanban-lifecycle.ui-compat.test.ts:45-58; S-5 finding :113-129 |
| 1.3 | The 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.4 | Eligible 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 card | RRetiredRef — the referenced item is retired. RForeignCycle (403) — the member card doesn’t resolve in this tenant | demand-item.functional.test.ts:44-57 |
| 1.5 | The demand item’s advisory total is adjusted before it goes out | POST /v1/operations/demand/demand-item/adjust-qty (or .../reset-qty) | Demand: demandQty set. status unchanged | RFrozen — no longer OPEN. RDemandClosed — not live | demand-item.functional.test.ts:60-78 |
| 1.6 | The demand item becomes eligible for an order | POST /v1/operations/demand/demand-item/release | Demand: status OPEN → RELEASED | RBadState — repeated release. RDemandClosed | demand-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 step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| 2.1 | David reviews the demand queue | GET /v1/operations/demand/demand-item/for?item=&station=&asOf= | Nothing (read) | — | demand-item-client.ts:173-178 |
| 2.2 | Create (Draft) with the supplier | POST /v1/procurement/order/order/create | Orders: 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 role | RRetiredRef (403) — a linked reference names a retired vendor. RForeignRef (403) — doesn’t resolve to a live VENDOR role | order.functional.test.ts:22-35; RRetiredRef :136-162 |
| 2.3 | Choose or override the supplier while Draft | POST /v1/procurement/order/order/update-supplier (revert: .../reset-to-supplier) | Orders: supplier binding replaced; reset drops per-order overrides, keeps the reference | RForeignRef, RRetiredRef, RFrozen (post-submit), ROrderClosed | order.functional.test.ts:38-63, 66-81 |
| 2.4 | Add lines from the demand queue | POST /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-supplied | RForeignRef — dangling demand reference. RDemandIneligible — not RELEASED. RDemandHeld — already held by a live line. RFrozen — parent SUBMITTED | order-line.functional.test.ts:28-54 |
| 2.5 | Attach a further demand item to an existing line | POST /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 demand | order-line.functional.test.ts:136-170 |
| 2.6 | Adjust a line’s quantity while composing | POST /v1/procurement/order/order-line/update-line | Orders / order-line: quantity set; item pin untouched | RFrozen (SUBMITTED parent), RLineClosed | order-line.functional.test.ts:111-133 |
| 2.7 | Remove a line while composing | POST /v1/procurement/order/order-line/remove-line | Orders / order-line: line tombstones — GET by id 404s, drops from by-order | RFrozen, RLineClosed | order-line.functional.test.ts:284-303 |
| 2.8 | Assign the order | POST /v1/procurement/order/order/update-details | Orders: the priority / assignee / notes cluster (SET semantics — an absent field clears). See Fidelity notes on the assignee | RFrozen post-submit, ROrderClosed | order.functional.test.ts:110-133; assignee typing order-client.ts:58-60 |
| 2.9 | Internal notes on the draft | POST /v1/procurement/order/order/annotate | Orders: internalNotes replaced wholesale (SET, not append); one ANNOTATE occurrence per act | ROrderClosed on a terminal order | order.functional.test.ts:84-107 |
| 2.10 | Submit — the order freezes; every serviced demand item starts production | POST /v1/procurement/order/order/submit | Orders: status DRAFT → SUBMITTED; structure, supplier, header details freeze. Demand: every serviced demand item RELEASED → IN_PROCESS, each minting its own holding pool | RNoLines, 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.11 | Verify the commitment froze | POST .../order/update-supplier or .../update-details on the SUBMITTED order | Nothing — refused | RFrozen on both | order-submit-settle.functional.test.ts:77-88, 91-99 |
| 2.12 | The 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 step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| 3.1 | Vera ships; the delivery arrives at the dock with its packing slip | — (physical step) | — | — | Narrative only |
| 3.2 | Keisha creates a receiver at the dock and adds lines from the order | designed — Receiving | — | — | Absence: no receiving/ package, route, or test anywhere in the tree |
| 3.3 | Receive — the freeze: accepted/rejected counts, reason, captured facts, and attribution actuals in one act | designed — Receiving | — | — | Same absence |
| 3.4 | The accepted goods are born as an inventory pool located at the dock | POST /v1/resources/inventory/inventory-pool/create — the effect exists standalone, but nothing births it from a receipt | Inventory Items: new pool; content keyed-zero or seed. No location or custody field on the wire | None at genesis; later ROverdraw, RIncomparable, RForeignPool | inventory-pool.functional.test.ts:21-31 |
| 3.5 | Record the production delivery — material lands in the demand item’s holding pool | POST /v1/operations/demand/demand-item/record-production | Demand: delivery pool folded into the holding pool; a ProductionDelivery row reified, status = CREATED; demand item stays IN_PROCESS; ProductionRecorded fires | RTargetNotInProcess, RWrongItem, RForeignRef (403) | demand-item.functional.test.ts:148-192 |
| 3.6 | Delivery announced (notification); Orders accrues to the line’s received amounts | — (system notification — wired). Manual repair route: POST /v1/procurement/order/order-line/record-receipt | Orders / order-line: received accrues pairwise; listener stamps notificationId = the delivery’s eId for idempotency | RNoDemand — free-form line. RLineClosed | Subscription Module.kt:98-100; accrual order-receipt.functional.test.ts:26-43 |
| 3.7 | A delivery is revoked (the reversing entry) | POST /v1/operations/demand/demand-item/revoke-delivery; order side reverses via listener or POST .../order-line/reverse-receipt | Demand: ProductionDelivery CREATED → REVOKED (terminal). Orders: received decrements pairwise | RDeliveryClosed — not live | demand-item.functional.test.ts:194-216; order-receipt.functional.test.ts:46-59 |
| 3.8 | Distribute to the waiting card — its cycle loads the pool | POST /v1/operations/demand/demand-item/distribute | Demand: holding pool content allocated to member card cycles; named fills cycles’ processing completes first | RBadAllocation — outside live membership. RBadState — not IN_PROCESS | Refusal demand-item.functional.test.ts:235-252; happy arc untested, see Fidelity notes |
| 3.9 | The demand item’s trip ends | POST /v1/operations/demand/demand-item/complete | Demand: status IN_PROCESS → COMPLETE | RUndistributed — holding pool still has content. RBadState | demand-item.functional.test.ts:218-233 |
| 3.10 | The line’s received amounts make Close line available; the order closes | POST /v1/procurement/order/order-line/close-line, then POST /v1/procurement/order/order/close | Orders: lineStatus L_OPEN → L_CLOSED, then order status SUBMITTED → CLOSED | RLineClosed — second close. RLinesOpen — order still has a live L_OPEN line | order-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 step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| 4.1 | Keisha restocks the shelf and returns the card to its position | — (physical step) | — | — | Narrative only |
| 4.2 | The card’s processing completes and it is received back | POST /v1/kanban/kanban-card/{eId}/event/complete-processing then .../event/receive | Kanban Cards: status IN_PROCESS → READY → FULFILLED; card leaves details/requesting, appears in details/FULFILLED | Illegal transitions weakly enforced — see 1.2 | kanban-lifecycle.acceptance.test.ts:24-37 |
| 4.3 | The FULFILLED card is un-marked back into circulation | POST /v1/kanban/kanban-card/{eId}/event/unmark | Kanban Cards: status transition — captured as a pinned baseline, not asserted | Not asserted | a-kanban-lifecycle.ui-compat.test.ts:96-111 (S-4) |
| 4.4 | Sam draws from the restocked bin — the cycle reads in use | POST /v1/kanban/kanban-card/{eId}/event/use | Kanban Cards: status moves to the in-use reading | Not covered by any test | Event declared kanban-client.ts:9-20; no test exercises it |
| 4.5 | The front bin empties again — the cycle reads depleted | POST /v1/kanban/kanban-card/{eId}/event/deplete | Kanban Cards: status moves to the depleted reading | Not covered | Event declared kanban-client.ts:9-20; no test exercises it |
| 4.6 | Re-trigger demand — the next lap begins on the same card | POST /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 refusing | None — the restart returns 200 | a-kanban-lifecycle.ui-compat.test.ts:61-75 (S-2) |
Alternate paths
Section titled “Alternate paths”The draft order is canceled
Section titled “The draft order is canceled”| # | Business step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| A1.1 | While the order is still a Draft, David abandons it | POST /v1/procurement/order/order/cancel | Orders: status DRAFT → CANCELED (terminal). Every attached demand item returns to the queue still RELEASED — a rival line can attach it immediately | ROrderClosed — re-cancel on an already-terminal order | order.functional.test.ts:165-176; hold-dies-with-the-order arc order-lifecycle.acceptance.test.ts:93-124 |
| A1.2 | Cancel is unavailable once Submitted | POST .../order/cancel on a SUBMITTED order | Nothing — refused (by design) | Not directly witnessed — see Fidelity notes | Reason codes OrderReason.kt:38, 73; no test asserts the SUBMITTED case |
| A1.3 | The canceled order is deleted | POST /v1/procurement/order/order/delete | Orders: order tombstones — GET by id 404s | RNotTerminal — still live. ROrderClosed afterwards | order.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 step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| A2.1 | Keisha counts what arrived and records accepted 8 / rejected 2 with the reason | designed — Receiving | — | — | Same absence as Leg 3 steps 3.2–3.3 |
| A2.2 | Only the accepted goods are born as the inventory pool | POST /v1/resources/inventory/inventory-pool/create with {seed:{each:8}} — reachable, but nothing derives the 8 from an accept/reject split | Inventory Items: a pool of eight | — | inventory-pool.functional.test.ts:21-31 |
The card is withdrawn mid-lap
Section titled “The card is withdrawn mid-lap”| # | Business step | API call | What changes | Refusals you can hit | Evidence |
|---|---|---|---|---|---|
| A3.1 | Irene withdraws the card from circulation | POST /v1/kanban/kanban-card/{eId}/event/withdraw | Kanban Cards: the lap ends from any live state; the card has no live cycle | Not covered by any test | Event declared kanban-client.ts:9-20; no test |
| A3.2 | The 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 quantity | RFrozen, RNotAttached, RCycleLive are folded as benign no-ops by the listener; anything else propagates | DemandMembershipSagas.kt:59 (subscription), :334-359 (listener body) |
| A3.3 | Manual repair of the same reconciliation | POST /v1/operations/demand/demand-item/detach-withdrawn | Demand: same as A3.2 | RNotAttached, RCycleLive, RFrozen | DemandService.kt:82-83 |
| A3.4 | A 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 gated | RHasCards guards a cancel that still has attached cycles | DemandMembershipSagas.kt:126-136, 196-215, 287 |
Fidelity notes
Section titled “Fidelity notes”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
requestevent 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 explicitcreate/create-with-cyclecall 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/receiveon 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), andwithdraw(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 manualdetach-withdrawnroute’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.
assigneeis a plain string in the order payload with no Staff-module resolution, unlike the vendor and item pins that are real.
Read next
Section titled “Read next”- FLOW-0001 — The Pull-Replenishment Round Trip — the narrative this walkthrough traces.
- Kanban Cards, Demand, Orders, Receiving, Inventory Items — the module pages that specify each leg’s behavior.
Copyright: © Arda Systems 2025-2026, All rights reserved