Repository navigation
REQ-CONFIG-005: reject inputsPerBatch keys the DefinitionPack does not declare - #510
Conversation
…t declare Handoff/03 section 21 fails configuration validation fast on unknown Good IDs, and section 19 step 1 runs that validation before genesis constructs anything. validateDefinitionPack() enforced that membership rule for the good-keyed map investmentGoodsPerCapitalUnit but not for the sibling inputsPerBatch, over the same declaredGoodKeys set in the same function. An undeclared input Good whose coefficient was finite and strictly positive therefore passed step 1 and reached world construction. inputsPerBatch keys are now checked against declaredGoodKeys before the coefficient bound, with a diagnostic naming the recipe and the unknown Good, matching the sibling check's wording. An empty map and a declared good with a strictly positive coefficient remain valid. Regressions: three direct validateDefinitionPack() cases plus a genesis-level file running the reproduction end to end through buildInitialWorld() on the baseline scenario. The shared test fixture minimalPack() declared no goods at all while minimalRecipe() referenced good-2, so it now declares the goods its recipe names; that makes the pack well-formed under both reference checks instead of accidentally exercising them. Negative control: with the membership check removed and the tests in place, exactly the three undeclared-Good rejections fail and the other 70 tests in those files pass; restoring it returns 73/73. Refs #508 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updates the single existing REQ-CONFIG-005 row rather than appending a second, as section 7 of the AUTHOR runbook requires: PR repointed to #510, MERGE_COMMIT cleared because it cannot be known from inside the pull request that carries the row, ISSUE set to the issue this slice closes, and EVIDENCE extended to name every contributing pull request (#76, #88, #502, #510). STATUS stays IMPLEMENTED: the repair strictly increases coverage of the invariant and asserts nothing new. The evidence cell says outright what the row does not cover - outputGoodId is still unchecked by validateDefinitionPack() - and names Issue #509, so the record does not read as total coverage. Refs #508 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verdict: ACCEPTHead revision judged: 1. Every required check is measured green at the head revisionRead from the checks tab and re-read from the commit status/check-runs API at
2. Independent re-execution of both runtimes at the head revisionChecked out
The legacy .NET suite is green on a canonical-TypeScript-only change, so 3. Every acceptance criterion of #508 is met, with the evidence I observed
The claimed requirement ID is implemented, not merely mentioned: the membership check is in the authoritative step-1 validator on the genesis path, proved by an end-to-end genesis regression, not only by a unit test against the validator. 4. The diff is confined to the declared scopeFive paths, each inside #508's
No path under 5. No invariant and no test was weakenedMeasured, not assumed. On I examined the change the body flags as highest-risk — The guard's ordering (membership before coefficient bound) matches the sibling 6. No secret, credential, personal data or local machine path
7. The handoff record is completeAll nine elements of the AGENTS.md handoff are present, and the evidence separates measured from assumed honestly — the "Not checked" and "Assumptions and unknowns" sections mark the unmeasured One correction to the record. The On the decision the author invited a reviewer to overrule: leaving the Merging with |
Updates the single REQ-CONFIG-005 row rather than adding a second one: PR becomes #512, ISSUE becomes #509 to stay paired with it, MERGE_COMMIT is cleared for scripts/backfill_merge_commits.py, and EVIDENCE names every contributing pull request (#76, #88, #502, #510 and now #512). STATUS stays IMPLEMENTED. The change strictly increases coverage and asserts nothing new; the gap the previous evidence named as unmeasured is now measured and closed. IMPLEMENTATION_STATUS.md is regenerated by scripts/implementation_status.py, not hand-edited. Refs #509 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…not declare (#512) * REQ-CONFIG-005: reject a recipe outputGoodId the DefinitionPack does not declare `validateDefinitionPack()` built `declaredGoodKeys` once per pack and consulted it for both good-keyed recipe maps — `investmentGoodsPerCapitalUnit` (#502) and `inputsPerBatch` (#510) — but never for the recipe's one scalar Good reference, `outputGoodId`. Handoff/03 section 21 fails configuration validation fast on "unknown Good/Recipe/Event/Metric IDs" and section 24 invariant 3 requires every ID to be reference-valid, so a recipe producing a Good the pack does not declare should not survive world-genesis step 1. Nothing downstream rejected it either. Unlike an undeclared investment good, which `resolveCapitalGoodsPerCapitalUnit()` at least drops, `outputGoodId` has no canonical reader at all — production is M4 — so outside tests it appears only in the type declaration and the baseline fixtures. The invalid reference reached the constructed world untouched. This is a latent fail-fast gap rather than an observable production defect, and the repair closes it at the authoritative validator. The check runs before the `outputPerBatch` bound and throws a diagnostic naming the recipe and the unknown Good, matching the two sibling reference checks. Negative control: with the guard removed and the tests in place, exactly the four rejection regressions fail and the other 68 tests in those files pass (4 failed | 68 passed of 72); restoring the guard returns 72/72. Refs #509 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ledger: record REQ-CONFIG-005 outputGoodId evidence for PR #512 Updates the single REQ-CONFIG-005 row rather than adding a second one: PR becomes #512, ISSUE becomes #509 to stay paired with it, MERGE_COMMIT is cleared for scripts/backfill_merge_commits.py, and EVIDENCE names every contributing pull request (#76, #88, #502, #510 and now #512). STATUS stays IMPLEMENTED. The change strictly increases coverage and asserts nothing new; the gap the previous evidence named as unmeasured is now measured and closed. IMPLEMENTATION_STATUS.md is regenerated by scripts/implementation_status.py, not hand-edited. Refs #509 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>
Closes #508
Achieved outcome
validateDefinitionPack()now rejects aRecipeDefinition.inputsPerBatchkey that namesa Good the
DefinitionPackdoes not declare, so the REQ-CONFIG-005 fail-fast referenceinvariant holds for recipe inputs as well as for
investmentGoodsPerCapitalUnit. Beforethis change
inputsPerBatchwas checked only for a finite, strictly positive coefficient,so an undeclared input Good with a well-formed coefficient passed world-genesis step 1 and
reached canonical world construction — although the function already built
declaredGoodKeysand the sibling map's check consulted it, in the same loop over the sameset. Handoff/03 section 21 fails configuration validation fast on "unknown Good/Recipe/Event/
Metric IDs" and section 19 step 1 runs that validation before construction; that is now true
for this field.
Tested revision
3c5a980f38fc92eb900654fc831172e9d0b8c3c3— the head ofclaude/issue-508-inputs-per-batch-undeclared-good. Every check in the table below wasre-run at this exact revision after the ledger commit landed on the branch, so no outcome
here describes a superseded head. If the branch moves after this text, the evidence is
stale and must be re-measured.
Changed artifacts
Every path in
git diff --name-only origin/master...HEAD:src/config/validation.ts— the repair. TheinputsPerBatchloop now testsdeclaredGoodKeys.has(goodKey)before the coefficient bound and throws naming the recipeand the unknown Good, worded exactly as the
investmentGoodsPerCapitalUnitcheck is. Theexisting strictly-positive coefficient rule and the valid empty map are untouched; the
function's doc comment records the added rule.
src/config/validation.test.ts— three direct regressions underinputsPerBatch good references (REQ-CONFIG-005): an undeclared key alongside a declaredone, an undeclared key whose coefficient is well-formed, and the acceptance case of two
declared keys. The shared
minimalPack()fixture declaredgoods: {}whileminimalRecipe()referencedgood-2, so every pack built from it would now throw on areference error unrelated to what each test asserts; it declares
good-1andgood-2instead, and
packWithTools()spreads those goods rather than replacing them. ThegoodDefinition()helper moved up one scope to be shared; it is otherwise unchanged.src/simulation/genesisInputGoodReferences.test.ts— new. Runs the Issue's ownreproduction end to end: the baseline pack with
recipe:tools-craft's inputs given anundeclared Good, then
buildInitialWorld()on the otherwise-valid baseline scenario.Plus three acceptance cases — the unmodified pack, the baseline inputs, and an empty map.
docs/spec/implementation_status.csv— updates the single existingREQ-CONFIG-005rowrather than appending a second:
PRrepointed to this pull request,MERGE_COMMITcleared because it cannot be known from inside the pull request that carries the row, and
EVIDENCEextended to name every contributing pull request and the gap this repair doesnot close.
docs/spec/IMPLEMENTATION_STATUS.md— regenerated byscripts/implementation_status.py,never edited by hand.
Acceptance criteria
inputsPerBatchcontains a Good key absent fromDefinitionPack.goodsis rejected byvalidateDefinitionPack()with a diagnostic naming the recipe and the unknown Good.src/config/validation.test.ts,rejects an input keyed by a good the pack does not declareandrejects an undeclared input good even when its coefficient is well-formed. Both assert on the thrown message, which names both the recipe and the good.buildInitialWorld(), before canonical world construction continues.src/simulation/genesisInputGoodReferences.test.ts,throws before world construction for an input keyed by an undeclared good.inputsPerBatchmap remains valid.accepts a strictly positive coefficient keyed by a declared goodand the pre-existingaccepts empty inputsPerBatch mapin the validator suite;still accepts the baseline inputs when every key names a declared goodandstill accepts an empty inputs mapat genesis. The unmodified baseline pack is proved to still build a world.declaredGoodKeys.hasguard deleted and the tests in place, exactly three tests failed — the two validator rejections and the genesis rejection — and 70 passed, including every acceptance case and all 12 of theinvestmentGoodsPerCapitalUnittests. Restoring the guard returned 73/73 across the same files. The control isolates this guard: nothing else in the diff changes an outcome.Checks
npm cinpm run typechecktsc --noEmit, no outputnpm testnpm run buildvite build,dist/canonical.jsemitteddotnet restoredotnet build --configuration Release --no-restoredotnet test --configuration Release --no-buildpython scripts/implementation_status.py --checkIMPLEMENTATION_STATUS.md matches 30 ledger row(s) over 49 registry row(s)python scripts/scope_guard.py(asci.ymlinvokes it)every one of 5 changed path(s) is named in the handoffpython scripts/status_lint.py --self 51030 ledger row(s) agree with the merged pull requestsOutcome is exactly one of
passed,failed,not_run,unavailable.Not checked
this diff touches
TradeCraftSimulation*; the gates ran because AGENTS.md requires bothsuites on every pull request, and they passed. Residual risk: none identified.
pack can reach one — rejection happens in genesis step 1. What a tick would have done
with an undeclared input Good is therefore unmeasured and stays unmeasured. Residual
risk: none for this change; it is the reason the acceptance criteria stop at genesis.
RecipeDefinition.outputGoodIdwas not repaired and its downstream behavior was notmeasured. See Assumptions, and Issue REQ-CONFIG-005: RecipeDefinition.outputGoodId accepts a Good the DefinitionPack does not declare #509.
Assumptions and unknowns
validateDefinitionPack()consultsdeclaredGoodKeysin exactly two places after this change —inputsPerBatchandinvestmentGoodsPerCapitalUnit.recipe.outputGoodIdis read nowhere in that function,and outside tests it appears only in the type declaration and the baseline fixtures. So
the step-1 validator still accepts a recipe that produces a Good the pack does not
declare.
rejects such an output, and what a production tick does with it. The REQ-CONFIG-005: inputsPerBatch accepts undeclared Good IDs #508 Non-goals
exclude
outputGoodId, so measuring it here would have been a scope increase. It isfiled as Issue REQ-CONFIG-005: RecipeDefinition.outputGoodId accepts a Good the DefinitionPack does not declare #509 (
status:needs-triage) with the gap stated as measured and theunmeasured part marked as such.
keeps
STATUS=IMPLEMENTEDrather than being downgraded toPARTIAL. This repairstrictly increases coverage of the invariant and asserts nothing new about the row, and
a downgrade of a row in an already-released milestone on the strength of an unmeasured
slice would be a claim I cannot fully support. Instead the
EVIDENCEcell names theoutputGoodIdgap and Issue REQ-CONFIG-005: RecipeDefinition.outputGoodId accepts a Good the DefinitionPack does not declare #509 outright, so the record does not read as completecoverage. If the ACCEPTOR reads the requirement statement as unsatisfiable while any
Good reference is unchecked,
PARTIALis the correct status and I will make that edit.GoodIdkey ininputsPerBatchis comparable bystring equality to a key of
DefinitionPack.goods. This is how the siblinginvestmentGoodsPerCapitalUnitcheck has worked since PR REQ-CONFIG-005: validate investmentGoodsPerCapitalUnit instead of silently dropping it #502, and the baseline packbuilds a world under the new check, which exercises it over real data.
Highest-risk area for review
The
minimalPack()fixture change insrc/config/validation.test.ts. It is the onlyedit in this diff that alters the input of tests it does not own — roughly forty existing
validateDefinitionPackcases now run against a pack that declares two goods instead ofnone. That is required (their recipe references
good-2, which the new rule wouldotherwise reject first, masking each test's real assertion), but it is exactly the kind of
change that can make an unrelated test pass for a new reason. Worth confirming: every one
of those cases asserts on a specific thrown diagnostic or on
not.toThrow(), none of themdepends on
goodsbeing empty, and the negative-control run shows the pre-existingfailures and passes are unchanged by anything but the guard itself.
Second, the guard's ordering: the membership test precedes the coefficient test, so a key
that is both undeclared and carries a bad coefficient now reports the reference error. No
existing test asserted the other ordering, and this matches the sibling check.
Remaining gate
None from this run. The pull request is complete and every required check passed on the
tested revision. It needs an ACCEPTOR verdict, which is not mine to give — I do not
approve and do not merge.
Issue #509 is filed for the
outputGoodIdgap and is not a gate on this pull request.🤖 Generated with Claude Code