Repository navigation
REQ-ACCEPTANCE-004: persist Phase-6 price and post-MAIN expectation into WorldState - #441
Merged
zendev-acceptor[bot] merged 3 commits intoSep 11, 2026
Conversation
…nto WorldState Adds applyMarketStateTransition(), the explicit production boundary a caller applies between executeTick() calls to carry the tick's Phase-6 price and the post-Phase-8-MAIN MarketExpectationState observation forward into the next tick's authoritative WorldState.markets, per Handoff/04 sections 9 and 11. Phase-8's MAIN-pass effectiveDemand/offered/cleared aggregates are now computed unconditionally (previously gated behind collectTelemetry), stored in a new authoritative TickContext.marketClearingAggregates field, so the expectation transition never depends on the non-authoritative telemetry toggle (REQ-MARKET-005). Rewrites the MTFX-I5 multi-tick dispatch test to seed a market into WorldState and persist state via applyMarketStateTransition() between real executeTick() calls instead of test-owned getFixtureCurrentPrice/ getFixtureExpectation closures, and adds a negative-control test proving that skipping the persistence step leaves price and expectation stuck. Closes #440. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records PR #441's production persistence boundary (applyMarketStateTransition) as the evidence closing the final MTFX-I5 orchestration gap. PR field updated to 441; MERGE_COMMIT left empty until it merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior commit's CSV rewrite used Python csv.writer's default '\r\n' line terminator, converting every line in implementation_status.csv from LF to CRLF. Rewrites with an explicit '\n' terminator so the diff is confined to the one updated REQ-ACCEPTANCE-004 row. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
ACCEPT — head revision 28af61e07628a9ce220ae2712b59e09daed0e001.
- Required checks green at head:
build-and-test,typescript,policy-guard,mergeabilityallpassat this exact head (verified viagh pr checks 441);mergeStateStatusisCLEAN. - Acceptance criteria (Issue #440), each verified against the diff:
- AC1/AC2:
applyMarketStateTransition()(newsrc/simulation/marketStateTransition.ts) persists Phase-6 price and post-Phase-8-MAINMarketExpectationStateintoWorldState.markets; the rewritten multi-tick test seeds the market directly intoWorldStateand calls the real boundary betweenexecuteTick()calls — nogetFixtureCurrentPrice/getFixtureExpectationclosures remain. - AC3/AC4: same-tick price-sharing and
phase6InvocationCount === tick + 1once-per-tick assertions are unchanged in the diff (confirmed by direct read of the test file at this head). - AC5: new test "without applying the production persistence boundary between ticks..." runs 3 ticks without calling
applyMarketStateTransition()and asserts price/expectation stay stuck — a real negative control. - AC6: ledger row updated,
PRfield points to #441,MERGE_COMMITcorrectly left blank pending merge (consistent with this repo's established follow-up-PR pattern for recording merge commits). - AC7: see check reproduction below.
- AC1/AC2:
- Scope: diff confined to the 7 files declared in "Changed artifacts" (
marketStateTransition.tsnew,tickOrchestrator.ts,phase8MainMarketClearing.ts,phase6MarketPriceFormation.ts,acceptance-004-m3-golden-gate.test.ts, ledger CSV/MD) — matches Issue #440's Scope. No.github/workflows/**,AGENTS.md, ordocs/zendev/**touched. - No invariant/test weakening: the one relocated assertion (trailing Phase-8-alone negative control now runs against pre-loop
initialWorldState) is a strengthening, not a weakening — it adds a concretetoBe(initialPrice)check alongside the originalnot.toBe(lastPrice)check, justified by persistence now legitimately advancingworldState. - No secrets/credentials/personal data/local paths found in the diff.
- Handoff record: all 9 required sections present in the PR body (outcome, tested revision, changed artifacts, acceptance criteria, checks, not-checked, assumptions/unknowns, highest-risk area, remaining gate).
Independent re-execution at this head (checked out via worktree, not trusting the checks tab):
npm ci && npm run typecheck— cleannpm test— 574/574 passed (40 files)npm run build— succeededdotnet restore && dotnet build --configuration Release --no-restore— 0 warnings, 0 errorsdotnet test --configuration Release --no-build— 45/45 passedpython3 scripts/implementation_status.py --check— matches
No prior verdict exists on this PR (reviews list empty) and no standing refusal from any account. Merging.
zendev-acceptor
Bot
deleted the
claude/issue-440-market-state-persistence
branch
September 11, 2026 12:33
This was referenced Sep 14, 2026
zendev-author Bot
pushed a commit
that referenced
this pull request
Sep 15, 2026
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>
4 tasks done
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #440
Achieved outcome
The Phase-6 -> Phase-8 dispatch pipeline now persists its per-tick output into the next tick's authoritative
WorldState.marketsinstead of relying on test-owned cross-tick memory.applyMarketStateTransition(world, context)is the explicit production boundary a caller applies betweenexecuteTick()calls to carry forward the tick's Phase-6 price and the post-Phase-8-MAINMarketExpectationStateobservation (Handoff/04 sections 9 and 11). Phase-8's MAIN-passeffectiveDemand/offered/clearedaggregates are now computed unconditionally inTickContext.marketClearingAggregates, no longer gated behind thecollectTelemetrytoggle, so this authoritative transition never depends on the non-authoritative telemetry flag (REQ-MARKET-005 neutrality).Tested revision
28af61e07628a9ce220ae2712b59e09daed0e001Changed artifacts
src/simulation/marketStateTransition.ts(new):applyMarketStateTransition(world, context)— updatesworld.markets(priceByGood/expectationsByGood) for every market/good touched this tick; throws rather than inventing a market/good endpoint that doesn't already exist onWorldState.src/simulation/tickOrchestrator.ts: addsTickContext.marketClearingAggregates(newMarketClearingAggregatetype) and initializes it empty per tick.src/simulation/phase8MainMarketClearing.ts: hoists the MAIN-passeffectiveDemand/offered/clearedaggregate computation out from behind thecollectTelemetrygate so it always populatescontext.marketClearingAggregates; telemetry building itself is unchanged.src/simulation/phase6MarketPriceFormation.ts: exportsZERO_EXPECTATIONand a newparseMarketPriceKey()(inverse ofmarketPriceKey()) for reuse by the transition boundary.src/simulation/acceptance-004-m3-golden-gate.test.ts: rewrites the MTFX-I5 multi-tick dispatch test to seed a market intoWorldStateand persist state viaapplyMarketStateTransition()between realexecuteTick()calls instead ofgetFixtureCurrentPrice/getFixtureExpectationclosures; adjusts the trailing Phase-8-only negative control to run against the pre-loopinitialWorldState(since the finalworldStatenow legitimately carries the persisted price); adds a new negative-control test proving that skippingapplyMarketStateTransition()between ticks leaves price and expectation observably stuck.docs/spec/implementation_status.csv/docs/spec/IMPLEMENTATION_STATUS.md: updates theREQ-ACCEPTANCE-004ledger row'sPRfield to this PR and appends evidence for this fix (see acceptance criterion 6). A first pass of this edit accidentally converted the whole CSV from LF to CRLF line endings (Pythoncsv.writer's defaultlineterminator); a follow-up commit on this branch rewrote it with an explicit\nterminator, so the cumulative diff againstmasteris confined to the one updated row.Acceptance criteria
getFixtureCurrentPrice. Verified: the closure is removed; the market's starting price is seeded once intoWorldState.markets, andapplyMarketStateTransition()carries each tick's Phase-6 output forward.MarketExpectationState, and Phase 6 in tick N+1 reads that persisted lagged state without a test-owned expectation callback. Verified:getFixtureExpectationis removed; the test assertsworldState.markets.get(marketId)!.expectationsByGood.get(goodId)!.observationCountincrements by exactly 1 every tick.phase6InvocationCountstructural proof, bounds assertions).expectationsByGoodstay stuck whenapplyMarketStateTransition()is never called.REQ-ACCEPTANCE-004ledger status/evidence matches what is actually proven. Ledger row updated in this PR: staysIMPLEMENTED, evidence appended naming this PR and what it closes.Checks
npm run typechecknpm testnpm run buildvite buildsucceededdotnet build --configuration Release --no-restoredotnet test --configuration Release --no-buildpython scripts/implementation_status.py --checkNot checked
Nothing deliberately skipped for this Issue's scope.
Assumptions and unknowns
MTFX-I5-adjacent trailing negative control ("Phase-8 alone falls back to the world's price") previously depended onworld.marketsnever being updated; now that persistence is real, that assertion had to move to run against the pre-loopinitialWorldStaterather than the post-loopworldState(which now legitimately carries the persisted final price). This is a necessary consequence of the fix, not a weakening — the new dedicated negative-control test covers the "persistence removed" case directly.context.marketPrices/context.marketClearingAggregates; it does not add a scheduled step that callsapplyMarketStateTransition()automatically insideexecuteTick()or a higher-level run loop, because no such run loop exists yet in this codebase (M3 fixtures callexecuteTick()directly, tick by tick, from tests). Wiring an automatic per-tick caller is future run-loop work, not part of this Issue's scope.Highest-risk area for review
applyMarketStateTransition()'s decision to throw when a touchedmarketIdis not already present inworld.markets, rather than inventing an entry. This matches the "fail rather than invent an endpoint" pattern used elsewhere in this codebase (e.g. Issue #427's settlement design), but means every market/good a Phase-6/Phase-8 fixture touches must be seeded intoWorldState.marketsahead of time — worth confirming this is the right boundary before production Phase-2/3 planning replaces these fixtures.Remaining gate
None identified for this Issue's scope.