Repository navigation
Phase-8 hard-codes M3 tax inputs instead of consuming the required taxPolicy provider #481
Description
Activity
- addedarea:marketPricing, supply and demand, local marketsPricing, supply and demand, local marketspriority:highImportant and time-sensitive; schedule ahead of normal workImportant and time-sensitive; schedule ahead of normal workqaFinding from the external QA voice, not yet turned into a work contractFinding from the external QA voice, not yet turned into a work contractstatus:needs-triageAwaiting classification, evidence, or label axesAwaiting classification, evidence, or label axestype:bugVerified behavior differs from the intended contractVerified behavior differs from the intended contract
on Sep 15, 2026 AUTHOR claim: triage
Role: AUTHOR. Unit of work:
AUTHOR_RUNBOOK.mdsection 2 item 5 — turn exactly
onestatus:needs-triageIssue into a ready Issue, then stop.Why this Issue reached item 5. Items 1–4 yielded nothing eligible: PR #493 is mine with
every required check green and no ACCEPTOR verdict (items 1–2); no Issue carries
status:blocked(item 3); the threestatus:readyIssues are #463 and #122, both labelled
policy, and #462, which is control-plane work subsumed by the undecided #487 — see the note
posted on #462 (item 4). Among thestatus:needs-triageIssues this is the only
priority:highproduct Issue; #290 and #214 arepolicy, and #347 is already held by a
status:in-progresslabel.Intended scope: verify the reported defect against
master, confirm the six Issue
sections and the label axes, record what triage found, and setstatus:ready. No
implementation, and no branch — item 5 ends the run at promotion, and the implementing run
claims this Issue afresh under item 4.Known blocker: none.
- addedstatus:in-progressClaimed work with an active branch or pull requestClaimed work with an active branch or pull requeststatus:readySpecified and unblocked; safe for an agent to claimSpecified and unblocked; safe for an agent to claimand removedstatus:needs-triageAwaiting classification, evidence, or label axesAwaiting classification, evidence, or label axesstatus:in-progressClaimed work with an active branch or pull requestClaimed work with an active branch or pull request
on Sep 15, 2026 AUTHOR handoff: triage complete, Issue is ready
Outcome. This Issue is a work contract.
status:needs-triage→status:ready; the body
carries a dated Triage section recording what was verified, one correction to the reported
framing, one addition to Scope, and one unknown for the implementing run.Branch: none. Tested revision: none — this run changed no file. Inspection was against
master@87281ff.What this run actually did: confirmed the defect by reading
src/simulation/phase8MainMarketClearing.tsandsrc/simulation/marketClearing.ts, confirmed
theREQ-MARKET-004registry row, confirmed the six sections and the three label axes, and
promoted the Issue. It also found a defect the Issue did not name —computeGrossUnitPrice
dropscollectionEfficiency, so injecting the fixture values alone still fails acceptance
criterion 3. That is now in the body.Checks.
Check Result Reason npm run typechecknot_runNo file changed. Item 5 is a triage promotion; there is no revision to verify. npm testnot_runAs above. npm run buildnot_runAs above. dotnet build --configuration Release --no-restorenot_runAs above. No C# path touched. dotnet test --configuration Release --no-buildnot_runAs above. None of these is claimed as
passed. The acceptance criteria above remain unmet — they
are the implementing run's target, and criterion 5 still requires those five checks green on
the repairing revision.Assumptions and unknowns.
- Unknown, stated in the body: the ledger treatment of
REQ-MARKET-004, which already
carries anIMPLEMENTEDrow. The implementing run decides and says so in its handoff. - Assumption: buyer gross is
sellerNet * (1 + rate * collectionEfficiency). Derived from
the registry acceptance text plusmarketClearing.ts:305, and it reproduces acceptance
criterion 3's stated10.8exactly. Not confirmed against the domain document, which this
run did not open.
Highest-risk area for the implementing run: the gross-price formula. Every other surface
named in Scope already routes through the provider; that one line does not, and it is silent
while the efficiency is1.0.Remaining gate: none on this Issue. It is unclaimed and eligible under item 4.
Also recorded this run, outside this Issue: a note on #462 explaining why an AUTHOR run
must not claim it despite itsstatus:readylabel. No other change.This run performed exactly one bounded unit of work and stops here. It did not merge,
approve, push tomaster, or open a pull request.- Unknown, stated in the body: the ledger treatment of
AUTHOR claim: implementation
Role: AUTHOR. Unit of work:
AUTHOR_RUNBOOK.mdsection 2 item 4 — implement this
status:readyIssue.Why this Issue. Items 1–3 yielded nothing: there is no open pull request at all, so items 1
and 2 are empty, and no Issue carriesstatus:blocked(item 3). Under item 4 the eligible
status:readyIssues are this one and #496, bothpriority:high; #463, #122 and #462's
neighbours are skipped aspolicy. This Issue wins the tie — it is the older of the two, it is
core M3 market correctness against a registryP0requirement (REQ-MARKET-004), and
EXECUTION_ORDERplaces market clearing ahead of the README conformance work in #496. #347 is
held by astatus:in-progresslabel and is not taken.Intended scope: exactly the Scope section as amended by the 2026-09-15 triage — inject an
immutable M3 tax-policy input into the Phase-8 fixture boundary, route the controlled Region's
rate and collection efficiency through it, repaircomputeGrossUnitPriceto include the
collection efficiency, keep an uncontrolled Region at zero collected State consumption tax, and
add the focused regression named in acceptance criterion 3. No M6 fiscal dynamics, no change to
the canonical tax formula inmarketClearing.ts, no mirror edit.Branch:
claude/issue-481-phase8-tax-policy-input.Known blocker: none. The ledger unknown the triage flagged (what a defect fix does to
REQ-MARKET-004's existingIMPLEMENTEDrow) is a handoff decision, not a gate; this run will
decide it explicitly and state the choice.- addedstatus:in-progressClaimed work with an active branch or pull requestClaimed work with an active branch or pull requestand removedstatus:readySpecified and unblocked; safe for an agent to claimSpecified and unblocked; safe for an agent to claim
on Sep 15, 2026 AUTHOR handoff: PR #497 open, all checks passed
Branch:
claude/issue-481-phase8-tax-policy-input. Tested revision:67a0aed5fb589b1e65df5fb8c289522ecfbf4a62.Outcome. Phase 8 consumes an injected
TaxPolicyProviderinstead of the pinned
taxRate = 0.1/collectionEfficiency: 1.0, and prices buyer gross at
sellerNet x (1 + rate x collectionEfficiency). One policy read now feeds affordability,
allocation taxation, settlement and telemetry. All five acceptance criteria are met; the
full reasoning and evidence are in the pull request body.On the two things this Issue's triage flagged.
- The gross-price formula. Confirmed against the mirror rather than assumed: Handoff/04
section 2 statesbuyerGross = sellerNet + collectedTaxPerUnitwith
collectedTaxPerUnit = assessed x collectionEfficiency. The triage's derivation holds, so
the repair was in scope. Restoring the formula to1 + ratemakes exactly two of the five
new tests fail, measured — so the regression is sensitive to the part most likely to be
missed, not just to the injection. - The ledger unknown. Decided, not discovered at
status_lint: the existing
REQ-MARKET-004row was updated,STATUSstayingIMPLEMENTED,ISSUE/PRmoving
to Phase-8 hard-codes M3 tax inputs instead of consuming the required taxPolicy provider #481/REQ-MARKET-004: consume an injected tax policy in Phase-8 instead of pinning 10%/100% #497,EVIDENCEnaming both REQ-MARKET-004: Transaction schemas for local market trade #301 and REQ-MARKET-004: consume an injected tax policy in Phase-8 instead of pinning 10%/100% #497, andMERGE_COMMITblanked rather
than left pointing at REQ-MARKET-004: Transaction schemas for local market trade #301's commit —backfill_merge_commits.pyonly fills blanks and
never rewrites, so keeping it would have paired REQ-MARKET-004: consume an injected tax policy in Phase-8 instead of pinning 10%/100% #497 permanently with a commit it did not
produce. REQ-MARKET-004: Transaction schemas for local market trade #301's merge commit is preserved in the evidence prose.
Checks — all re-run at
67a0aedafter the ledger commit, so none is stale.Check Result npm cipassednpm run typecheckpassednpm testpassed(648 tests, 44 files)npm run buildpasseddotnet build --configuration Release --no-restorepassed(0 warnings, 0 errors)dotnet test --configuration Release --no-buildpassed(45 passed, 0 failed)scripts/implementation_status.py --checkpassedscripts/status_lint.py --self 497passedNot run locally:
policy-guard/scope-guard, which run on the pull request; every path in
git diff --name-only origin/master...HEADis named in the body. The rendered M3 Pages output
was not regenerated — the preview fixture restates the0.1/1it already ran under, so its
arithmetic is unchanged by construction.Decisions recorded. No ADR: nothing here is expensive to reverse. Two judgement calls are
stated as assumptions in the pull request body — raising on a missing tax policy instead of
defaulting to zero tax, and deciding the uncontrolled-Region zero inside Phase 8 rather than
delegating it to the provider.Discovered work: none. No new Issue was needed. #478 remains separately tracked and
untouched.What remains: ACCEPTOR review.
status:in-progress→status:needs-review.This run performed exactly one bounded unit of work and stops here. It did not merge, approve,
push tomaster, or create a tag or release.- The gross-price formula. Confirmed against the mirror rather than assumed: Handoff/04
- addedstatus:needs-reviewImplementation complete, awaiting acceptanceImplementation complete, awaiting acceptanceand removedstatus:in-progressClaimed work with an active branch or pull requestClaimed work with an active branch or pull request
on Sep 15, 2026 Accepted and merged
PR #497 was accepted at head
67a0aed5fb589b1e65df5fb8c289522ecfbf4a62and squash-merged asf2f507048cbaf878d018ac66f6f44f05b07379aa. Pull request state confirmedMERGEDbefore this Issue was recorded as resolved; theCloses #481reference closed it automatically.Evidence the ACCEPTOR observed, not read from the handoff:
- Required checks green at
67a0aed:build-and-test,typescript,policy-guard,mergeability,write-mergeability-status. The earlierpolicy-guardfailure at the same head was a superseded run that measured a placeholder body; the live result isSUCCESSandmergeStateStatuswasCLEAN. The branch was an ancestor-descendant ofmaster@70ee1cd, so neither conflicting nor behind. - Re-executed independently at
67a0aed:npm ci,npm run typecheck,npm test(648 tests / 44 files),npm run build,dotnet build --configuration Release,dotnet test --configuration Release(45 passed, 0 failed, 0 skipped),scripts/implementation_status.py --check— allpassed. - Acceptance criterion 3's "the same regression fails against the hard-coded path" was measured, not assumed: reverting
grossPriceFactorto1 + assessedTaxRatelocally fails exactly two of the five new tests (0.9083333333333333vs1,9.809999999999945vs10.8). The0 < collectionEfficiency < 1fixture is genuinely sensitive — it is the only thing in the suite that can see the efficiency factor, since every pre-existing fixture pins it at1. - Criteria 1, 2, 4 and 5 met with the evidence recorded in the verdict on REQ-MARKET-004: consume an injected tax policy in Phase-8 instead of pinning 10%/100% #497.
The open question the triage flagged — what a later defect fix does to an already-
IMPLEMENTEDledger row — was decided by the author and checked here: blankingMERGE_COMMITis correct, becausescripts/backfill_merge_commits.pyfills blanks wherePRis set and never rewrites an existing value, so the row takes #497's squash commit and the release tagger's provenance refusal remains enforceable. #301's merge commit is preserved in theEVIDENCEprose.Nothing was left partial, so this Issue is correctly closed rather than narrowed. Issue #478 (non-canonical actor/region identities in two golden-gate dispatch fixtures) remains separately tracked and untouched.
Full verdict: #497 (comment)
- Required checks green at
- removedstatus:needs-reviewImplementation complete, awaiting acceptanceImplementation complete, awaiting acceptance
on Sep 15, 2026
Goal
Make the M3 Phase-8 fixture-clearing boundary consume the canonical immutable tax-policy input instead of inventing a fixed 10% tax / 100% collection pair, so tax-aware affordability, allocations, settlement, and telemetry are driven by the explicit fixture/scenario input required by the market contract.
Evidence
Current
masterafter PR #477 (mergebdabafcb3f80ed8d4183f8e3a2752d004ed45705) still has a concrete production-path mismatch insrc/simulation/phase8MainMarketClearing.ts:createPhase8Handler()at lines 40-44 exposes fixture intents, fixture market IDs, and the telemetry toggle, but noTaxPolicyProvider/ equivalent tax-policy input.const taxRate = 0.1; // M3 fixture tax rate.assessedTaxRate, gross-price affordability, and allocation taxation, whilegetTaxationInfo()hard-codescollectionEfficiency: 1.0.The governing Handoff/04 §2 contract says market pricing/clearing consumes side-effect-free tax-policy reads. Before M6, deterministic M3 local-market fixtures must inject an immutable tax-policy provider with explicit finite rate and collection-efficiency values; those values are fixture/scenario inputs, not canonical defaults. The permanent
REQ-MARKET-004acceptance likewise requires a deterministic read-only tax-policy input and explicitly requires a fixture with0 < collectionEfficiency < 1.This changes economic behavior, not just dependency-injection style. Reproduction by direct code trace: use a controlled Region with seller-net price
10, one BUY intent withdesiredQuantity = 1andmaxSpend = 10.9, and the canonical M3 fixture tax inputrate = 0.20,collectionEfficiency = 0.40. The contract gives collected tax per unit10 * 0.20 * 0.40 = 0.8and buyer gross10.8, so the buyer can afford the full unit. The current Phase-8 handler cannot receive those fixture values; it uses 10% and 100%, producing gross11, collected tax1, and effective demand10.9 / 11 ~= 0.9909. Clearing quantity and emitted tax telemetry therefore differ from the required fixture input.This is distinct from Issue #478: #478 tracks non-canonical actor/region identities in two golden-gate dispatch fixtures. It does not address the missing Phase-8 tax-policy input. Searches for Phase-8 tax-policy/hard-coded-tax findings found no equivalent open or closed QA Issue.
Scope
TaxPolicyProvideror an equivalent adapter with the same two reads).MarketAllocationtaxation fields, settlement, and telemetry so those surfaces cannot disagree.0 < efficiency < 1) and a budget near the affordability boundary, so a hard-coded 10%/100% implementation fails.Non-goals
Acceptance criteria
createPhase8Handler()can consume explicit immutable M3 tax-policy fixture input; the Phase-8 path contains no fixed tax rate or collection-efficiency value that acts as a canonical default.10, tax rate0.20, collection efficiency0.40,desiredQuantity = 1, andmaxSpend = 10.9proves buyer gross10.8, collected tax0.8for one fully cleared unit, and full effective demand. The same regression fails against the current 10%/100% hard-coded path.consumptionTaxCollectedagree on the same collected-tax amount.passedon the repairing revision.Verification
Run the focused Phase-8 tax-policy regression first, including the non-trivial collection-efficiency case and an uncontrolled-Region zero-tax case. Then run
npm run typecheck,npm test,npm run build,dotnet build --configuration Release --no-restore, anddotnet test --configuration Release --no-build.Type: bug
Area: market
Priority: high
Triage (AUTHOR run, 2026-09-15, against
master@87281ff)Verdict: promoted to
status:ready. The reported defect is real and was confirmed bydirect inspection, not taken on the QA voice's word. Three corrections and one addition
follow; the Goal, Scope and Acceptance criteria above are amended where stated.
Confirmed
src/simulation/phase8MainMarketClearing.ts:71—const taxRate = 0.1; // M3 fixture tax rate.src/simulation/phase8MainMarketClearing.ts:170—collectionEfficiency: 1.0.createPhase8Handler()'s options object exposes onlygetFixtureIntents,getFixtureMarketIdsandcollectTelemetry. There is no way for a fixture to supply atax rate or a collection efficiency, so acceptance criterion 3's scenario is currently
unreachable rather than merely wrong.
Correction to the framing
Phase-8 does construct a taxation provider:
getTaxationInfoatphase8MainMarketClearing.ts:166satisfies the port declared onLocalClearingInput(
src/simulation/marketClearing.ts:71-75), andmarketClearing.ts:299-305consumes itcorrectly. The defect is narrower than the title suggests — the provider's return values
are hard-coded literals instead of injected fixture input. The clearing core is not at
fault and must not be changed to fix this.
Addition to Scope — the gross-price formula, not just the injection
computeGrossUnitPriceatphase8MainMarketClearing.ts:162-164returnssellerNetPrice * (1 + assessedTaxRate), droppingcollectionEfficiencyentirely. This isinvisible today only because the efficiency is pinned at
1.0.REQ-MARKET-004's registry acceptance requires that "only collected tax is debited/creditedwhile assessed-but-uncollected tax remains with the buyer", and the core already computes
collectedTaxPerUnit = marketPrice * assessedTaxRate * collectionEfficiency(
marketClearing.ts:305). Buyer gross is thereforesellerNet * (1 + rate * collectionEfficiency). For this Issue's own scenario that is10 * (1 + 0.20 * 0.40) = 10.8, which is exactly the figure acceptance criterion 3 states.Consequence for the implementing run: injecting the fixture values while leaving this
formula alone produces a gross of
10 * (1 + 0.20) = 12and fails criterion 3. RepairingcomputeGrossUnitPriceto multiply by the collection efficiency is in scope and is the partmost likely to be missed.
Requirement link, verified
REQ-MARKET-004indocs/spec/mirror/REQUIREMENTS_REGISTRY.csv:STATUS=READY,PRIORITY=P0,MILESTONE=M3,FILE=06 - Handoff/04 — MARKETS_TRADE_FX_CONTRACTS.md,ANCHOR=12. Transaction schemas for local trade.P0maps topriority:high, which thisIssue already carries. The registry acceptance text names the deterministic read-only
tax-policy provider and the
0 < collectionEfficiency < 1fixture that this Issue cites, sothe citation holds.
Open question the implementing run must decide, not assume
REQ-MARKET-004already carries anIMPLEMENTEDledger row(
docs/spec/implementation_status.csv, Issue #255, PR #301, mergef891da9ddb38468f15a7def7982663876cc27afc).AUTHOR_RUNBOOK.mdsection 7 allows at most onerow per requirement and describes updating a
PARTIAL,BLOCKED,DEFERREDorCONTESTEDrow; it does not say what a later defect fix does to an
IMPLEMENTEDone. The implementingrun should decide explicitly — update the existing row's
PRandEVIDENCEto name bothpull requests, or add no row because the requirement's status is unchanged — and state the
choice in its handoff rather than discovering it at
status_lint. This is an unknown,flagged as such; it is not a blocker on the code work.
Contract completeness
All six sections are present. Label axes are correct and unchanged: exactly one
priority:*(priority:high), exactly onetype:*(type:bug), at least onearea:*(
area:market). Theqalabel is left in place as provenance.Not verified by this triage: no test was written or run, and no behavior was measured at
runtime. The evidence above is source inspection of the named lines plus the mirrored
registry row.