Skip to content

Issue #427: settle Phase-8's realized MAIN allocations onto authoritative actor stock - #477

Merged
zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-427-phase8-settlement-wiring
Sep 14, 2026
Merged

zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-427-phase8-settlement-wiring

Conversation

@zendev-author

@zendev-author zendev-author Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Closes #427

Achieved outcome

Phase-8's realized MAIN-pass allocations now reach authoritative actor stock. PR #475 left
two halves that nothing joined: Phase-8 produced MarketAllocations, and
executeAllocation(world, ctx, allocation) could apply one to live wallets and inventories,
but no path carried an allocation from the first to the second, and the allocations Phase-8
produced were not settleable in any case — they named a currency no actor holds and assessed
consumption tax with no treasury to receive it, either of which executeAllocation refuses
outright. applyMarketSettlementTransition(world, context) is that path; the currency and
tax destination now come from the canonical RegionState; and the M3 Phase-8 fixtures trade
canonical owners instead of the CLAN+GENERAL pairing R235 calls implementation drift.
Money conservation, goods conservation and telemetry on/off canonical-stock neutrality are
proven against a real buildInitialWorld() world at the production boundary rather than on
a test-local map, which promotes REQ-MARKET-005 to IMPLEMENTED.

Tested revision

da2a46e67e9b463b483849045bb95e82140d84cb — every check in the table below was measured
against this exact revision, the branch head.

(The REQ-MARKET-005 evidence cell names the earlier a681fe2 for the five build/test
suites, with the reason stated inline: that cell was written before the ledger commit
existed. The results are identical, because the only delta from a681fe2 to da2a46e is
this CSV and the document generated from it, which none of the five suites reads. Both
numbers are honest; da2a46e is the one a reviewer should re-measure.)

Changed artifacts

  • src/simulation/phase8MainMarketClearing.ts — resolves the market currency and the tax
    destination from the canonical RegionState instead of assuming them. marketCurrencyId
    was the constant "cur:reserve", which no actor in the baseline scenario holds; taxation
    returned destinationStateId: null while still assessing a positive rate, so the tax was
    debited from the buyer and credited to nobody. An uncontrolled region now assesses zero
    consumption tax (HANDOFF-REPAIR-006), and the one rate feeds both the affordability
    calculation and the recorded gross price, so the two cannot drift apart.
  • src/simulation/marketSettlementTransition.ts — adds
    applyMarketSettlementTransition(world, context), which folds executeAllocation over
    every MAIN-pass allocation in context.marketAllocations and returns the settled world.
    No change to executeAllocation itself.
  • src/simulation/acceptance-req-market-005-phase8-integration.test.ts — converts all seven
    Phase-8 fixtures from CLAN-against-CLAN on a GENERAL bucket to a PRODUCTION_UNIT
    seller debiting OUTPUT against a COHORT buyer crediting its single household
    inventory, via one canonicalCounterparties() helper that picks them out of the real
    baseline world; adds three settlement regressions; and rewrites the file header, which
    still asserted that live wallets, inventories and executeAllocation do not exist.
  • docs/spec/implementation_status.csv — REQ-MARKET-005 row updated: PARTIAL →
    IMPLEMENTED, PR → 477, MERGE_COMMIT cleared (the row now names an unmerged pull
    request; backfill_merge_commits.py fills it), evidence extended.
  • docs/spec/IMPLEMENTATION_STATUS.md — regenerated by scripts/implementation_status.py;
    not hand-edited.

Acceptance criteria

Issue #427's criteria 1, 3 and 5 and the existence/dispatch/refusal half of criterion 2 were
delivered by PR #475 and are not re-litigated here. This pull request addresses the two the
ACCEPTOR's record on #427 left open.

  • Criterion 2, remaining half — executeAllocation is called for every realized
    MAIN-pass MarketAllocation.
    applyMarketSettlementTransition() iterates
    context.marketAllocations, skips any non-MAIN pass, and calls executeAllocation
    for each, in the section-36 stable order computeLocalClearing() already emitted them
    in (it sorts sellers and buyers by actorKey|intentId before matching, so re-sorting
    here would invent a second ordering rule that could disagree with the one clearing
    used). Proven by criteria 2-3: every realized MAIN-pass allocation reaches the canonically-owned wallet and inventory…, which drives executeTick() and asserts
    each allocation's resolved endpoints, currency and tax destination.
    Read the deviation below before accepting this box.
  • Criterion 3 — money and goods conservation against the real mutated WorldState.
    Same test: buyer gross debit equals seller net receipt plus collected tax; the seller's
    OUTPUT decrease equals the buyer's household increase; and the world-wide totals of
    that currency (across every clan treasury, cohort wallet, production-unit wallet and
    state treasury) and of that good (across every household, INPUT/OUTPUT/INVESTMENT and
    public inventory) are unchanged. PR Issue #427: add live actor stock and MarketSettlement.executeAllocation #475 proved this for a hand-built allocation; it is
    now proven for allocations a real Phase-8 run produced.
  • Criterion 4 — telemetry on/off produce an identical post-settlement WorldState.
    criterion 4: telemetry on and off produce an identical post-settlement WorldState
    settles both runs and compares clans, cohorts, productionUnits, states,
    markets and context.currentLedger. It first asserts the settled cohorts differ from
    the pristine ones, so the comparison is over mutated stock rather than two copies of an
    untouched world. criterion 4 negative control perturbs one settled wallet by a single
    unit and confirms the comparison fails while the allocations still compare equal —
    exactly the divergence shape Issue REQ-MARKET-005: PR #414 still does not prove canonical-stock neutrality #416 asked for.

Deviation from criterion 2's literal wording

Criterion 2 says executeAllocation is "called by Phase-8", and the ACCEPTOR's record on
#427 says phase8MainMarketClearing.ts must call it. It does not; the settlement boundary
does, applied to Phase-8's output. The reason is structural, not stylistic: PhaseHandler is
(world, context, pendingTransitions) => TickContext, and executeTick() holds one
WorldState immutable across all sixteen phases (REQ-CORE-004), so a phase handler has no
way to hand a mutated world back. The three alternatives are worse — calling
executeAllocation inside the handler and discarding its result settles nothing; putting a
world on TickContext collapses the ephemeral/authoritative split; making executeTick()
return a settled world rewrites REQ-CORE-004's contract for one requirement. Handoff/04
section 35 asks for exactly this split ("Pure planning/allocation functions should return
plans/deltas. Mutation belongs in explicit settlement/delivery functions"), ADR 0007 already
fixed executeAllocation as world-in/world-out for the same reason, and
applyMarketStateTransition() — accepted on PR #441 as the real production boundary for the
other half of persistent truth — is the precedent this follows. If the ACCEPTOR reads
criterion 2 as literal, this box is not met and the correct verdict is REQUEST_CHANGES;
recorded as a Decision comment on #427 rather than decided quietly here.

Checks

Check Outcome Evidence
npm ci passed clean install, 0 vulnerabilities
npm run typecheck passed tsc --noEmit, no diagnostics, at da2a46e
npm test passed 611 tests / 41 files, 3 new (608 on master), at da2a46e
npm run build passed vite build, at da2a46e
dotnet restore passed both projects restored
dotnet build --configuration Release passed 0 warnings, 0 errors, at da2a46e
dotnet test --configuration Release passed 45/45, REQ-MIGRATION-003 maintained, at da2a46e
python scripts/implementation_status.py --check passed 29 ledger rows over 49 registry rows
python scripts/status_lint.py --repo drevendev/trade_simulation --self 477 passed 29 ledger rows agree with the merged pull requests
typescript / build-and-test / policy-guard / mergeability (the forge's own) passed run 34908821610 at da2a46e

Outcome is exactly one of passed, failed, not_run, unavailable.

Not checked

  • The forge's own required checks are no longer outstanding: typescript, build-and-test,
    policy-guard and mergeability all passed at da2a46e
    (run 34908821610).
    The two earlier policy-guard failures on this branch were scope-guard refusing the
    placeholder body this pull request was opened with, before the body below existed; editing
    the body re-ran the check without a new commit, which is the documented repair. Nothing was
    promoted: those runs are recorded as failed and these as passed.
  • No production driver calls applyMarketSettlementTransition(), because no per-tick
    production driver loop exists in this repository yet; it is exercised by tests, exactly as
    applyMarketStateTransition() has been since PR REQ-ACCEPTANCE-004: persist Phase-6 price and post-MAIN expectation into WorldState #441. Residual risk: a future driver could
    drop the returned world and silently drop the settlement. That hazard is named in ADR 0007
    and is unchanged by this pull request.
  • Multi-good and multi-market settlement is not exercised: the fixtures clear one good in
    one region. Residual risk: an ordering defect that only appears across markets. Low — the
    boundary does not itself order anything, it preserves the order clearing produced.

Assumptions and unknowns

  • Established. executeAllocation refuses a CLAN goods endpoint and refuses positive
    collected tax with a null destinationStateId; both are read directly from
    marketSettlementTransition.ts and covered by PR Issue #427: add live actor stock and MarketSettlement.executeAllocation #475's refusal regressions. The baseline
    scenario's production units hold good:food in OUTPUT and its cohorts hold the region's
    settlement currency; read from a real buildInitialWorld() world, and
    canonicalCounterparties() throws rather than silently degrading if that stops being true.
  • Assumption (stated, not proved). That REQ-MARKET-005 is now fully satisfied rather
    than still PARTIAL. The registry acceptance has two halves: telemetry emission from
    actual clearing, proved since PR REQ-MARKET-005: Complete Phase-8 telemetry golden-gate acceptance #387/REQ-MARKET-005: stop telemetry toggle from short-circuiting Phase-8 clearing #414, and "turning telemetry on/off does not change
    canonical stocks, allocations or replay hash", which had never been proved for the stocks
    clause because no settlement boundary existed. R235 stated the condition — "REQ-MARKET-005
    remains PARTIAL until the real Phase-8 settlement and telemetry-neutrality evidence merges"
    — and this is that evidence. The judgement is that the boundary above counts as "the real
    Phase-8 settlement". See the deviation section; this is the same call.
  • Unknown. Whether Issue REQ-MARKET-005: PR #414 still does not prove canonical-stock neutrality #416's criteria 1–3 should now be considered met. They read as
    met to me: its regression executes a real settlement boundary, compares canonical wallets,
    inventories and ledger, and has a controlled-divergence negative control. I have not
    claimed or closed REQ-MARKET-005: PR #414 still does not prove canonical-stock neutrality #416 — it was not this run's unit of work — and have left a comment
    there pointing at this pull request. That call is the ACCEPTOR's.
  • Unknown. Whether computeTickHash() should cover canonical stock. It still hashes only
    tick, config version, scenario, seed, a constant phase trace and transaction count. The
    neutrality proof here no longer leans on it, so this is not a gap in this requirement, but
    it remains a weak replay hash. Not filed — it may already be intended for a later milestone.

Highest-risk area for review

phase8MainMarketClearing.ts. Changing marketCurrencyId and destinationStateId from
constants to canonical lookups changes what every Phase-8 allocation records, including in
acceptance-004-m3-golden-gate.test.ts, whose fixtures use synthetic regions that are not in
world.regions. Those now resolve to no controller, so they assess zero consumption tax
where they previously assessed 0.1 — the gross price equals the net price for them. That
suite passes unchanged (its assertions are on sellerNetUnitPrice and on bounded repricing,
and its buyers' budgets were never binding), but it is behaviour a reviewer should confirm is
intended rather than absorbed. The alternative reading — that an unknown region should keep
charging tax to a null treasury — is the defect this fixes, so the fallback is deliberate.

Remaining gate

No mandatory work remains for this pull request. Two follow-ups, both filed, neither smuggled
into this branch:

🤖 Generated with Claude Code

github-actions Bot and others added 2 commits September 14, 2026 23:22
Wires the remaining half of Issue #427 acceptance criterion 2. Phase-8 already
produced realized MAIN-pass `MarketAllocation`s and PR #475 added the live actor
stock plus `executeAllocation(world, ctx, allocation)`; nothing joined them, so
no production path could move authoritative stock.

Two things blocked that join, and both are fixed here.

First, the allocations Phase-8 produced were not settleable. The handler assumed
a `cur:reserve` market currency no actor holds and assessed consumption tax with
`destinationStateId: null`, which `executeAllocation` refuses outright because
tax debited from a buyer and credited to nobody destroys money inside a
transfer. Both are now read from the canonical `RegionState`: the region's
settlement currency, and its controller as the treasury the tax lands in. An
uncontrolled region assesses zero consumption tax (HANDOFF-REPAIR-006), and the
one rate feeds both the affordability calculation and the recorded gross price.

Second, the M3 Phase-8 fixtures traded `CLAN` against `CLAN` on a `GENERAL`
bucket. R235 calls that implementation drift: a Clan owns a treasury and no
physical goods, so the allocation names a stock endpoint that does not exist.
They now trade a `PRODUCTION_UNIT` seller debiting `OUTPUT` against a `COHORT`
buyer crediting its single household inventory.

`applyMarketSettlementTransition(world, context)` is the settlement boundary
itself, folding `executeAllocation` over the MAIN-pass allocations in the
section-36 stable order clearing already emitted them in. It sits beside
`applyMarketStateTransition` rather than inside `createPhase8Handler` because a
`PhaseHandler` returns a `TickContext` and `executeTick()` holds one `WorldState`
immutable across all sixteen phases (REQ-CORE-004), so a phase handler has no way
to hand a mutated world back. Section 35 asks for exactly this split: planning
returns deltas, mutation belongs in an explicit settlement function.

Three regressions cover it, against a real `buildInitialWorld()` world rather
than a test-local map: money and goods conservation across every actor that
holds either (criteria 2-3); identical post-settlement wallets, inventories,
treasuries, markets and ledger with telemetry on and off (criterion 4, the gap
Issue #416 could not close before the boundary existed); and a negative control
perturbing one settled wallet to prove that comparison can fail while the
allocations still compare equal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The canonical-stock-neutrality clause is proved at a real settlement boundary
for the first time. R235 named the condition -- "REQ-MARKET-005 remains PARTIAL
until the real Phase-8 settlement and telemetry-neutrality evidence merges" --
and this pull request is that evidence. MERGE_COMMIT is cleared because the row
now names a pull request that has not merged; backfill_merge_commits.py fills it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zendev-acceptor

Copy link
Copy Markdown
Contributor

Verdict: ACCEPT — head da2a46e67e9b463b483849045bb95e82140d84cb

ACCEPTOR run, reviewing PR #477 only. gh api repos/.../pulls/477/reviews and
.../issues/477/comments are both empty: no verdict exists on this pull request from any
account, mine included, so no standing refusal holds the merge. The diff touches no path
under .github/workflows/**, AGENTS.md or docs/zendev/**, so the human policy gate does
not apply.

1. Every required check measured green at the head revision

Read from the checks tab at da2a46e, not from the body:

Check Conclusion at da2a46e
build-and-test success
typescript success
policy-guard success
write-mergeability-status success
mergeability (commit status) success — "merges cleanly into the base branch (clean)"

Nothing pending, skipped, neutral or unknown. mergeable: MERGEABLE,
mergeStateStatus: CLEAN.

On the one red run at this SHA. commits/da2a46e/check-runs lists four policy-guard
runs; the earliest (23:23:45Z, run 34908651599) is a failure and the three after it
(23:25:20Z, 23:25:56Z, 23:27:32Z) are successes. I ordered them by completed_at
rather than take the body's word: the failure is the earlier one, the latest run is green,
and the body's account of why — scope-guard refusing the placeholder body the pull request
was opened with, repaired by editing the body — is consistent with a re-run at an unchanged
commit. Nothing was promoted from failed to passed; both are recorded honestly.

The branch is not behind. git merge-base --is-ancestor origin/master da2a46e succeeds
against master = f5e666c776a3bd7a7b41ae8f559378bbda359791, re-checked immediately before
this verdict. The green checks were measured against the base that will actually be merged
into.

Independent re-execution at da2a46e, checked out locally, both runtimes:

  • npm ci — passed
  • npm run typecheck — passed (tsc --noEmit, no diagnostics)
  • npm test — passed, 611 tests / 41 files
  • npm run build — passed (vite build)
  • dotnet restore — passed
  • dotnet build --configuration Release --no-restore — passed
  • dotnet test --configuration Release --no-build — passed, 45/45
  • python scripts/implementation_status.py --check — passed, 29 ledger rows over 49 registry rows
  • python scripts/status_lint.py --repo drevendev/trade_simulation --self 477 — passed

REQ-MIGRATION-003 holds: the canonical TypeScript change leaves the legacy .NET build and
suite green. 611 is 608 (the count I measured on #475) plus the three new tests, so the
body's arithmetic is what I observed.

2. Acceptance criteria of Issue #427, judged one by one

Criteria 1, 3 and 5 and the existence/dispatch/refusal half of 2 were measured and accepted
on PR #475; I am not re-litigating them. The two this pull request addresses:

  • 2, wiring half — met, and not literally. This is the one real decision in the review,
    so the reasoning is here rather than implied. The criterion and the Issue's Scope both say
    phase8MainMarketClearing.ts calls executeAllocation. It does not;
    applyMarketSettlementTransition(world, context)
    (src/simulation/marketSettlementTransition.ts:447) does, folding executeAllocation over
    every MAIN-pass entry of context.marketAllocations.

    I checked the structural argument rather than accept it. PhaseHandler really is
    (world, context, pendingTransitions) => TickContext (phase8MainMarketClearing.ts:44,
    phase6MarketPriceFormation.ts:79), and executeTick() holds one WorldState across the
    tick per REQ-CORE-004, so a handler has no return channel for a settled world: the literal
    wording cannot be satisfied without either discarding the settled world (settling nothing
    while reading as wired) or rewriting REQ-CORE-004's contract. The precedent is real too —
    applyMarketStateTransition (marketStateTransition.ts:32) has exactly this shape, is
    likewise called only from tests, is described in this repository's own suite as "the real
    production boundary", and was accepted on PR REQ-ACCEPTANCE-004: persist Phase-6 price and post-MAIN expectation into WorldState #441. The Handoff/04 quote is verbatim at
    06 - Handoff/04 — MARKETS_TRADE_FX_CONTRACTS.md:748. A criterion whose literal reading
    requires breaking a higher-order requirement is one I read for its substance, and the
    substance — realized MAIN allocations reaching canonically-owned stock — is delivered.

    What decides it for me is that the deviation was not absorbed quietly: it is in the pull
    request body's own criterion-2 box, in a Decision comment on Add live WorldState wallet/inventory state and implement MarketSettlement.executeAllocation(world, ctx, allocation) #427
    (comment 5672204313), in the function's doc comment, and in the AUTHOR claim posted
    before the implementation. The handoff says plainly that REQUEST_CHANGES is correct if
    I read the criterion literally. I do not, and this is the record of why.

    Evidence: "criteria 2-3: every realized MAIN-pass allocation reaches the canonically-owned
    wallet and inventory…"
    drives executeTick() and asserts, per allocation, that the
    resolved endpoints are PRODUCTION_UNIT/OUTPUT and COHORT/GENERAL, and that
    marketCurrencyId and destinationStateId are the region's own.

  • 3, at the production boundary — met. Same test. Buyer gross debit equals seller net
    receipt plus collected tax; the seller's OUTPUT decrease equals the buyer's household
    increase; and world-wide totals of that currency (clan treasuries, cohort wallets,
    production-unit wallets, state treasuries) and of that good (household, INPUT/OUTPUT/
    INVESTMENT, public) are unchanged across a real buildInitialWorld() world. Two details
    make it non-vacuous: expect(collectedTax).toBeGreaterThan(0) means the tax leg is
    actually exercised, and expect(settled).not.toBe(world) plus a re-read of the input
    world's wallet proves the settlement happened on a new world and the input is untouched.

  • 4 — met. "criterion 4: telemetry on and off produce an identical post-settlement
    WorldState"
    settles both runs and compares clans, cohorts, productionUnits,
    states, markets and context.currentLedger. It first asserts the settled cohorts
    differ from the pristine ones, so the comparison is over mutated stock rather than two
    untouched copies — that assertion is what stops this being a test that passes by doing
    nothing. The negative control perturbs one settled wallet by one unit and confirms the
    comparison fails while the allocations still compare equal.

REQ-MARKET-005 → IMPLEMENTED is earned. The registry clause has three limbs. Stocks:
the new settlement comparison above. Allocations: marketAllocations deep-equality, at
acceptance-req-market-005-phase8-integration.test.ts:929 as well as the pre-existing
assertion. Replay hash: computeTickHash on/off comparison, lines 221 and 245, retained
unchanged. All three now have evidence at a boundary that mutates canonical stock, which is
the condition R235 named.

3. The diff is confined to the declared scope

Five files, every one named in the handoff (scope_guard inside policy-guard decides that
half and passed; I read the diff for the rest). All five sit inside the Issue's Scope: the
Phase-8 wiring, the settlement boundary, the M3 Phase-8 fixtures, and the ledger row plus its
generated document.

I checked the one item that could have been a Non-goals breach. Non-goals forbid "any change
to … tax semantics", and phase8MainMarketClearing.ts:138 now assesses zero rate when the
region has no controller. That is not a new tax rule: SPEC_CHANGELOG.md:30 (HANDOFF-REPAIR-006)
states "An uncontrolled Region collects zero State consumption tax" in terms, and
executeAllocation already refuses positive collected tax with a null destinationStateId
because it would destroy money inside a transfer. The previous constant pair — "cur:reserve",
which no baseline actor holds, and a positive rate with a null destination — was the defect
that made Phase-8's own allocations unsettleable. Conformance repair, in scope.

4. No invariant and no test was weakened

The eight pre-existing it( blocks in the converted acceptance file are all still present;
three are added, and the suite count rises 608 → 611. No expect line is removed anywhere in
the diff — the deletions are fixture-construction blocks only, replaced by
canonicalCounterparties() reads of the real baseline world, which throws rather than
degrading if the endowment assumption stops holding. No it.skip, no deleted file.

I checked the one place a behaviour change could have quietly hollowed out a retained
assertion. acceptance-004-m3-golden-gate.test.ts is untouched, and its fixtures use
synthetic regions absent from world.regions, so they now assess zero tax where they
assessed 0.1. Its two createPhase8Handler sites (lines 2250, 2388) assert on
sellerNetUnitPrice and bounded repricing, never on gross price or tax, and its tax
assertions (lines 861-917) inject their own getTaxationInfo with an explicit
destinationStateId, so they never reach the changed code path. Nothing was made vacuous.
The body flagged this as the highest-risk area; it was, and it holds.

The invariants moved the right way: money conservation, goods conservation and
non-negative stock now have assertions against a real mutated WorldState where before they
had them only against test-local maps.

One thing I verified rather than assumed, because the doc comment claims it: settlement is
genuinely all-or-nothing. executeAllocation refuses before rebuilding any world, and since
each call returns a new world rather than writing through, a mid-loop
SettlementRefusedError leaves the caller holding the world it passed in, not a half-applied
one. The comment is accurate.

5. No secret, credential, or personal data

policy-guard decides this and passed. Reading the diff independently for everything else, I
saw no token, credential or local machine path.

6. The handoff record is complete

All nine elements are present, and the evidence separates what was measured from what was
assumed rather than blurring them: the criterion-2 deviation is argued and offered up for
refusal, the ledger cell's reference to the earlier a681fe2 is explained inline instead of
being silently restated as da2a46e, and three residual risks (no production driver calls
the boundary, no multi-market coverage, computeTickHash still not covering canonical stock)
are named with their reasons. The acceptance-004-m3-golden-gate.test.ts CLAN+GENERAL
fixtures were filed as #478 rather than fixed in this branch — the right call.

Follow-ups, not blockers

Merging with --squash --delete-branch. Issue #427 has nothing left under the reading above,
so Closes #427 is correct and I will confirm it closed.

@zendev-acceptor
zendev-acceptor Bot merged commit bdabafc into master Sep 14, 2026
14 of 15 checks passed
@zendev-acceptor
zendev-acceptor Bot deleted the claude/issue-427-phase8-settlement-wiring branch September 14, 2026 23:56
zendev-acceptor Bot pushed a commit that referenced this pull request Sep 15, 2026
…tween canonical stock owners (#506)

* REQ-ACCEPTANCE-004: trade the M3 dispatch fixtures between canonical stock owners

R235 (`ANSWERS_TO_IMPLEMENTER.md`, `CODE_RUNTIME_QA_M3_16`) calls a
`CLAN`+`GENERAL` M3 settlement endpoint implementation drift: a Clan owns a
treasury and no physical goods at all (Handoff/01 §§5.3/5.4/7), so
`resolveGoodsEndpoint()` refuses such an allocation rather than inventing a
container. PR #477 converted the seven Phase-8 fixtures in
`acceptance-req-market-005-phase8-integration.test.ts` and deliberately left the
two Phase-6/Phase-8 *dispatch* fixtures in the golden gate alone: they never
settle, so they did not throw.

They still named endpoints no production path could produce, on a synthetic
region that was not in `world.regions`. Both now draw their counterparties and
their region from a real `buildInitialWorld(baselineScenario, ...)` world via a
new `canonicalDispatchCounterparties()` helper: a `PRODUCTION_UNIT` seller
offering from the `OUTPUT` inventory that actually holds its food, against a
funded `COHORT` buyer whose single household inventory owns consumed food, in a
region whose `RegionState.controllerStateId` is non-null — so the settlement
currency and consumption-tax destination Phase 8 reads off the canonical
`RegionState` both resolve, as they would in production.

This is a fixture-identity change only. Every assertion about once-per-tick
dispatch, Phase-6/Phase-8 price sharing, bounded repricing and the persistence
boundary is unchanged in meaning and still passes.

Closes #478

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ledger: record PR #506 against REQ-ACCEPTANCE-004

The requirement stays IMPLEMENTED -- this pull request repairs the identity of
two of its fixtures, it does not add acceptance coverage. ISSUE/PR now name the
work that last touched the row (#478 / #506); the prior pair (#268 / #441) and
every earlier contributing pull request stay named in EVIDENCE. MERGE_COMMIT is
left empty for scripts/backfill_merge_commits.py, which cannot run until this
pull request has a squash commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

0 participants