Skip to content

Walkthrough: Commitments That Cannot Be Silently Edited

This walkthrough traces FLOW-0003 — Commitments That Cannot Be Silently Edited — an order’s free draft composition, the real four-check submission floor, the hard freeze at Submit, and closure only by deliberate acts — to the Orders module calls that implement it, with Business Affiliate providing the vendor pin and Demand supplying the readiness check Submit relies on. This is the best-covered flow in the catalog: 26 of its 27 narrative steps have a direct, tested API counterpart, and the one exception is the out-of-band step of the order physically reaching the supplier.

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

#Business stepAPI callWhat changesRefusals you can hitEvidence
1Create the orderPOST /v1/procurement/order/order/createOrders: new order, status = DRAFT, priority = UNDEFINED, internalNotes = []. Under mutation-mode: PROPAGATE, even a name-only supplier is linked at restRRetiredRef (403) at genesis. RForeignRef (403)order.functional.test.ts:22-35; RRetiredRef :159-161
2Add lines freely — three armsPOST /v1/procurement/order/order-line/add-line (FROM_DEMAND / FROM_ITEM / FREE_FORM)Orders / order-line: new line, lineStatus = L_OPEN, quantity set, received keyed zero, demand set (FROM_DEMAND only). Item pin is server-derivedRForeignRef, RDemandIneligible, RDemandHeldorder-line.functional.test.ts:28-54, 56-76, 78-92
3Edit a line’s quantityPOST /v1/procurement/order/order-line/update-lineOrders / order-line: quantity set; item pin untouchedRFrozen (post-submit), RLineClosedorder-line.functional.test.ts:111-133
4Remove a linePOST /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
5Change the supplier freely while DraftPOST /v1/procurement/order/order/update-supplierOrders: the whole supplier binding is replacedRForeignRef (403), RRetiredRef (403), ROrderClosedorder.functional.test.ts:38-50; RForeignRef :52-63; RRetiredRef on an existing order :156-158
6Drop per-order supplier overrides, keep the referencePOST /v1/procurement/order/order/reset-to-supplierOrders: supplier.overrides cleared; supplier.reference preservedRFrozen, ROrderClosedorder.functional.test.ts:66-81
7Set the header details (priority, assignee, notes)POST /v1/procurement/order/order/update-detailsOrders: priority / assignee / notes cluster — SET semantics, an absent field clearsRFrozen post-submit, ROrderClosedorder.functional.test.ts:110-133
8Keep internal notesPOST /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

The narrative states four checks. The API has four distinct refusals, each with its own stable code, and each leaves the draft’s content intact.

#Business stepAPI callWhat changesRefusals you can hitEvidence
9Submit with a name-only supplier → refused; a linked vendor is requiredPOST /v1/procurement/order/order/submitNothing — the draft keeps its contentSee Fidelity notes — this refusal is not reachable as the narrative frames itorder-submit-settle.functional.test.ts:59-74 (comment at :61-65); link-at-rest behavior order.functional.test.ts:29-33
10Submit an order with no lines → refusedPOST .../order/submitNothingRNoLinesorder-submit-settle.functional.test.ts:48-57
11Submit with a vendor linked but retired since → refused; re-checked at the commitmentPOST .../order/submitNothing — the draft keeps its contentRRetiredRef (403)order-submit-settle.functional.test.ts:102-116
12Submit with a serviced demand item not ready to start → refusedPOST .../order/submitNothingRDemandIneligibleorder-submit-settle.functional.test.ts:36-46
13Submit — floor satisfiedPOST .../order/submitOrders: status DRAFT → SUBMITTED; structure, supplier, header details, and assignee freeze on their pins. Demand: every serviced demand item RELEASED → IN_PROCESS, each minting its own holding pool—order-submit-settle.functional.test.ts:24-34; arc shared/arcs.ts:116-126
14The committed order goes to Vera— (physical step)Nothing in the system—No transport exists in this build
#Business stepAPI callWhat changesRefusals you can hitEvidence
15Change a line’s quantity after submit → refused; the structure frozePOST /v1/procurement/order/order-line/update-line (and add-line / remove-line) on a SUBMITTED parentNothingRFrozenorder-line.functional.test.ts:306-315
16Edit the header details after submit → refusedPOST /v1/procurement/order/order/update-detailsNothingRFrozen (the headerDetailFrozen law)order-submit-settle.functional.test.ts:91-99
17Change the supplier after submit → refusedPOST /v1/procurement/order/order/update-supplierNothingRFrozen (the supplierBindingFrozen law)order-submit-settle.functional.test.ts:77-88
18Record the supplier’s answer on a line → recorded; answers accrete against the frozen promisePOST /v1/procurement/order/order-line/record-acknowledgmentOrders / order-line: confirmation:{disposition, confirmedQuantity?} recorded; the note rides the occurrence envelopeRBadState — acknowledgment on a line whose parent is still DRAFT. RLineClosedorder-submit-settle.functional.test.ts:148-176
19Receipts accrue against the linesPOST /v1/procurement/order/order-line/record-receipt (manual) — the C/NOTIF listener drives the same kind internallyOrders / order-line: received accrues pairwise; reversal via reverse-receipt decrementsRNoDemand — free-form line has no received tracking. RLineClosedorder-receipt.functional.test.ts:26-43, 46-59, 62-82
20Notes accretePOST /v1/procurement/order/order-line/annotate-lineOrders / order-line: note lands on the ANNOTATE_LINE occurrence envelope; no payload field changesRLineClosedorder-line.functional.test.ts:284-303 (annotate leg at :293-297)
21Read the order → Confirmed / Receiving are computed views, not stored statesGET /v1/procurement/order/order/{eId}/readings?asOf=Nothing (read). Returns {confirmed, receiving, reading}; payload.status never leaves SUBMITTED404 on a missing orderorder-submit-settle.functional.test.ts:179-195; also order-lifecycle.acceptance.test.ts:40-43
#Business stepAPI callWhat changesRefusals you can hitEvidence
22Close the order → refused; a line is still openPOST /v1/procurement/order/order/closeNothingRLinesOpenorder-submit-settle.functional.test.ts:119-131
23Close the outstanding line short — an explicit act, never automaticPOST /v1/procurement/order/order-line/close-lineOrders / order-line: lineStatus L_OPEN → L_CLOSED. The shortfall is simply left recorded — nothing reconciles itRLineClosed — second closeorder-submit-settle.functional.test.ts:134-145
24Close the order → Closed, finalPOST /v1/procurement/order/order/closeOrders: status SUBMITTED → CLOSED (terminal)— (succeeds once every line is closed)order-submit-settle.functional.test.ts:126-130; full arc order-lifecycle.acceptance.test.ts:24-51
25Cancel exists only while the order is a DraftPOST /v1/procurement/order/order/cancelOrders: DRAFT → CANCELED (terminal). Every attached demand item returns to the queue untouched, still RELEASEDROrderClosed — see Fidelity notes (gap: only witnessed as a re-cancel)order.functional.test.ts:165-176; demand-release arc order-lifecycle.acceptance.test.ts:93-124
26A terminal order is deletedPOST /v1/procurement/order/order/deleteOrders: order tombstones — GET .../{eId} 404s; every further mutation refusesRNotTerminal — still live. ROrderClosed afterwardsorder.functional.test.ts:179-187, 189-200
27The record shows what was refused, not just what happenedGET /v1/procurement/order/order/{eId}/log?page=&size= (line equivalent: .../order-line/{eId}/log)Nothing (read). Returns occurrences most recent first, carrying both COMMITTED and REFUSED rows—order.functional.test.ts:203-222

What the tests witness today versus what the narrative claims:

  • The “name-only supplier” refusal (step 9) is not reachable as drawn. Under mutation-mode: PROPAGATE, a name-only supplier is linked at rest, at create time, to a found-or-created VENDOR role — so it carries a real eId / affiliateEId and submits without complaint. The reachable RNoSupplier case is an order with no binding at all. The narrative’s claim (“a name alone can compose a draft but never a commitment”) is true of the design intent, not of the current API surface.
  • The assignee (step 7, frozen at step 13) doesn’t freeze on a pin, because there is no pin. assignee is a plain string with no Staff-module resolution. It does freeze — update-details refuses RFrozen post-submit — but as text, not as a version pin the way the vendor and item references are.
  • The RECEIVING reading (step 21) is reachable, contrary to a stale comment in the acceptance suite claiming the record-receipt route isn’t on the T0 surface. The route is present and covered elsewhere, but no test yet drives readings all the way to RECEIVING — the one reading in the narrative’s “computed views” claim with no direct witness.
  • Refusal HTTP status is disposition-driven, not per-reason. PRECONDITION reasons render 409; POLICY reasons render 403 — which is why RRetiredRef and RForeignRef are asserted with an explicit 403 in the tests while RFrozen, RNoLines, and RLinesOpen take the default.
  • Step 14 is the only step with no API counterpart in this flow. Every other narrative step maps to a real, tested call — the reason this is the best-covered of the four walkthroughs.
  • Cancel-on-SUBMITTED (step 25’s actual claim) is not directly witnessed. The suite’s only cancel refusal is a re-cancel of an already-CANCELED order — nothing asserts that canceling a SUBMITTED order refuses.