Repository navigation
REQ-ACCEPTANCE-004: wire the real per-tick Phase-6 -> Phase-8 dispatch pipeline for MTFX-I5 - #438
Conversation
…h pipeline for MTFX-I5 Adds createPhase6Handler (src/simulation/phase6MarketPriceFormation.ts), a PhaseHandler that reprices via repriceGoodInPhase6() only when context.phase === 6 and writes the result into a new TickContext.marketPrices field. createPhase8Handler now reads that same field (falling back to world.markets when absent, so existing callers are unaffected), and a new composePhaseHandlers() utility lets both handlers run through the real executeTick() phase-0..15 loop as one dispatch pipeline. A new test in acceptance-004-m3-golden-gate.test.ts's MTFX-I5 describe block runs that composed pipeline across multiple ticks and proves, against the real dispatch (not a hand-rolled loop): repricing executes exactly once per tick, Phase-8 clearing settles every allocation at exactly the price Phase 6 produced that same tick, and price stays within configured bounds throughout -- closing the once-per-tick orchestration-timing half of MTFX-I5 that was left outstanding by PR #435/#436. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MTFX-I5's once-per-tick orchestration-timing half was the last outstanding acceptance artifact; PR #438 closes it, so every enumerated MTFX-T1..T6/ I1..I6 item and the local-shortage golden scenario are now proven. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
ACCEPT — head revision 5099b13346a2327938aa3fddc30a672293643ffe
-
Every required check measured green at this head:
gh pr checks 438shows build-and-test, typescript, policy-guard, mergeability, and write-mergeability-status allpassat this exact SHA. (Note: this head had two CI runs — the first failedpolicy-guard/scope-guard because the PR body's "Changed artifacts" list omitted the two ledger files; the PR body was corrected without changing the code, and the second run is the one GitHub's rollup andgh pr checkstreat as authoritative. Verified viagh api .../commits/<sha>/check-runsthat this is a same-SHA re-run, not a stale check.)mergeable=MERGEABLE,mergeStateStatus=CLEAN(themergeabilitystatus's "(blocked)" suffix is GitHub's rawmergeable_state, meaning "pending required review," not a conflict — confirmed by readingscripts/mergeability.py). -
Acceptance criteria met, with evidence I observed: Issue #268's criteria 1-8 were already satisfied by prior merged PRs on this issue; this PR closes the one remaining gap — MTFX-I5's once-per-tick orchestration-timing half. I read
phase6MarketPriceFormation.ts,phase8MainMarketClearing.ts, andtickOrchestrator.ts'sexecuteTick/executePhaseto confirm the phase loop runs phase 0-15 exactly once each per tick and setscontext.phasebefore dispatch, so the new test's phase-6-guarded invocation counter is a structural proof of "at most once per tick" against the real dispatch, not a hand-rolled loop. I independently checked out the head at5099b13in a worktree and ran the full verification suite myself:npm ci,npm run typecheck(clean),npm test(573/573, 40 files),npm run build(succeeded),dotnet build --configuration Release(0 warnings/errors),dotnet test --configuration Release(45/45) — all match the PR body's claims. Also ranpython scripts/implementation_status.py --checkandpython scripts/status_lint.py --self 438 --base mastermyself: both pass. -
Diff confined to declared scope: all 7 changed files (
docs/spec/IMPLEMENTATION_STATUS.md,docs/spec/implementation_status.csv,src/simulation/acceptance-004-m3-golden-gate.test.ts,src/simulation/index.ts,src/simulation/phase6MarketPriceFormation.ts,src/simulation/phase8MainMarketClearing.ts,src/simulation/tickOrchestrator.ts) are named under "Changed artifacts" and fall under Issue #268's scope of proving the M3 golden-gate acceptance suite; the small amount of new production wiring (createPhase6Handler,composePhaseHandlers) is the minimum needed to exercise a real per-tick dispatch boundary, consistent with the precedent set by prior PRs on this same issue (#426, #436). No.github/workflows/**,AGENTS.md, ordocs/zendev/**touched. -
No invariant or test weakened: diff to the test file is purely additive (147 additions / 6 deletions, the deletions being a docstring update);
phase8MainMarketClearing.ts's lookup-order change preserves the priorworld.marketsfallback for every existing caller. -
No secret, credential, or personal data present: reviewed the full diff; none found.
-
Handoff record complete: PR body states outcome, Issue/tested revision, changed artifacts, acceptance criteria with status, checks, not-checked items, assumptions/unknowns, highest-risk area, and remaining gate (none).
Merging via squash and deleting the branch.
Closes #268
Achieved outcome
The once-per-tick orchestration-timing half of MTFX-I5 ("Price changes at most once per tick and stays within configured bounds", Handoff/04 §38), left outstanding by PR #435/#436, is now proven against a real production dispatch pipeline instead of only against isolated function calls or a hand-rolled test loop.
createPhase6Handler(new filesrc/simulation/phase6MarketPriceFormation.ts) is aPhaseHandlerthat callsrepriceGoodInPhase6()only whencontext.phase === 6and writes the resulting price into a newTickContext.marketPricesfield (keyed"marketId|goodId").createPhase8Handlernow reads that same field when present, falling back toworld.marketswhen absent so every existing caller/test is unaffected. A newcomposePhaseHandlers()utility intickOrchestrator.tslets both handlers run through the realexecuteTick()phase-0..15 loop as one composed dispatch pipeline, the single-handler shapeexecuteTickalready accepts.With MTFX-I5's last gap closed, every enumerated MTFX-T1..T6 / MTFX-I1..I6 acceptance artifact and the local-shortage golden scenario (Handoff/04 §40 scenario A) are now proven, so this PR also promotes the
REQ-ACCEPTANCE-004ledger row fromPARTIALtoIMPLEMENTED.Tested revision
5099b13346a2327938aa3fddc30a672293643ffeChanged artifacts
src/simulation/phase6MarketPriceFormation.ts(new) —createPhase6Handler,marketPriceKey,Phase6PriceConfig.src/simulation/tickOrchestrator.ts— addsTickContext.marketPrices(initialized empty per tick), andcomposePhaseHandlers().src/simulation/phase8MainMarketClearing.ts—createPhase8Handlerreadscontext.marketPricesfor the market/good before falling back toworld.markets.src/simulation/index.ts— exportscomposePhaseHandlersalongside the othertickOrchestratorexports.src/simulation/acceptance-004-m3-golden-gate.test.ts— new test in the MTFX-I5describeblock driving the composed Phase-6/Phase-8 pipeline throughexecuteTick()across 5 ticks; corrects the file's header comment, which previously said this half was not observable.docs/spec/implementation_status.csv—REQ-ACCEPTANCE-004row:STATUSPARTIAL→IMPLEMENTED,PR→ 438,MERGE_COMMITleft empty (unmerged),EVIDENCEappended describing this slice.docs/spec/IMPLEMENTATION_STATUS.md— regenerated from the CSV viapython scripts/implementation_status.py(never hand-edited).Acceptance criteria
Issue #268's own criteria (1-8) were already met by prior PRs on this Issue; this PR closes the one item the ledger still listed as outstanding for
REQ-ACCEPTANCE-004: MTFX-I5's once-per-tick orchestration-timing half.executeTick()dispatch — proven by a fixture-intents call counter gated insidecreatePhase6Handler'scontext.phase === 6guard, asserted equal to the tick index after each of 5executeTick()calls.result.context.marketAllocations[*].sellerNetUnitPricevsresult.context.marketPrices.get(key).context.marketPricesdefaults to empty and every existing caller ofcreatePhase8Handlerfalls back to its priorworld.marketsprice exactly as before).REQ-ACCEPTANCE-004ledger row reflects the actual state (IMPLEMENTED, evidence naming this PR and every prior contributing PR);python scripts/implementation_status.py --checkandpython scripts/status_lint.py --repo drevendev/trade_simulation --self 438 --base masterboth pass.Checks
npm run typecheck5099b13npm test5099b13npm run buildvite buildsucceeded, at5099b13dotnet build --configuration Releasedotnet test --configuration Releasepython scripts/implementation_status.py --checkpython scripts/status_lint.py --self 438 --base masterNot checked
WorldStatemutation (MarketSettlement.executeAllocation, Handoff/04 §35) — that boundary still does not exist in this codebase (tracked separately on Issue Add live WorldState wallet/inventory state and implement MarketSettlement.executeAllocation(world, ctx, allocation) #427) and is out of scope here. The new handlers use the same fixture-intent injection conventioncreatePhase8Handleralready established.MarketExpectationState/price persistence in realWorldStateis not implemented by this PR; the new test supplies price/expectation via fixture closures across its own local loop variable, the same convention the existing golden-scenario test uses, not viaWorldStatemutation.Assumptions and unknowns
Phase6PriceConfig'sminimumPrice/maximumPriceare fixture-supplied, matching every existing MTFX-T2/I5 test in this file: there is no canonicalSimulationConfig.marketsfield owning per-good price bounds yet (confirmed by readingsrc/config/simulationConfig.ts), so this is consistent with, not a regression from, current practice.IMPLEMENTEDrather than leftPARTIAL. If the ACCEPTOR finds a gap in that reasoning, the correct remedy is to hold the row atPARTIALand name the remaining gap, not to re-litigate the already-merged prior slices.Highest-risk area for review
phase8MainMarketClearing.ts's one-line lookup-order change (context.marketPricesbeforeworld.markets): it's the crux of "Phase 7/8 use the resulting Phase-6 price" actually being true through the real dispatch rather than by convention, and it is depended on by every existingcreatePhase8Handlercaller falling through to the same?? 10default as before whenmarketPriceshas no entry for that key.Remaining gate
None known for this slice.