Skip to content

REQ-CONFIG-005: validate investmentGoodsPerCapitalUnit instead of silently dropping it - #502

Merged
zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-465-config-005-investment-goods-validation
Sep 15, 2026
Merged

zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-465-config-005-investment-goods-validation

Conversation

@zendev-author

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

Copy link
Copy Markdown
Contributor

Closes #465

Achieved outcome

An invalid RecipeDefinition.investmentGoodsPerCapitalUnit coefficient now fails configuration
validation before any world construction, instead of being silently filtered out downstream and
becoming indistinguishable from a recipe that genuinely declares no investment good.
validateDefinitionPack() never inspected the field, so resolveCapitalGoodsPerCapitalUnit()
(src/domain/definitionRegistry.ts) was the first and only consumer on the genesis path — and it
drops any entry that is not finite and > 0. A NaN, Infinity, zero or negative coefficient
therefore yielded an empty result, buildInitialWorld() took the "no investment good" path and
emitted good-less UNCONVERTED capital, and because reconcileGenesisStocks() calls the same
resolver on the actual side, the invalid configuration reconciled cleanly. That is why no existing
conservation test caught it: two sides sharing one reinterpretation agree with each other. The
filter had no key-side check either, so a coefficient naming a good the pack does not declare was
dropped by the same silent path rather than rejected. Both cases are now rejected with a diagnostic
naming the recipe and the good; an empty map stays valid, because it is a real "no investment good"
declaration that the baseline pack relies on.

Tested revision

0ff4fc3eb13c499196b2b2db2000566181bcf0ca — the branch head. Every check in the table below ran
against that revision, after the ledger commit, not against the earlier code-only commit.

Changed artifacts

Every path in git diff --name-only origin/master...HEAD:

  • src/config/validation.ts — validateDefinitionPack() now builds the set of declared good keys
    once per pack and, for each recipe, rejects an investmentGoodsPerCapitalUnit entry whose key is
    not a declared good and an entry whose value is non-finite or <= 0. The key check runs first, so
    a reference failure is reported as a reference failure (section 19 step 1 orders references before
    finite values). The function's doc comment gains the new rule.
  • src/domain/definitionRegistry.ts — doc comment only, no behavior change. Records the Scope
    decision below: the resolver's filter stays as defence-in-depth and is deliberately not an
    assertion.
  • src/config/validation.test.ts — a new investmentGoodsPerCapitalUnit (REQ-CONFIG-005) describe
    block: five rejection cases (NaN, +Infinity, -Infinity, zero, negative), one unknown-good-key
    rejection, and two acceptance cases (a strictly positive coefficient on a declared good; an empty
    map). It needs a pack that actually declares good:tools, since the file's existing minimalPack
    helper carries goods: {}.
  • src/simulation/genesisInvestmentCoefficients.test.ts — new file. The finding's own
    reproduction, end to end: the baseline pack with recipe:tools-craft's coefficient made invalid,
    then buildInitialWorld() on the otherwise-valid baseline scenario and default config. Five
    invalid values plus the unknown-good key must throw; the unmodified baseline pack and an empty
    map must not.
  • docs/spec/FEEDBACK_TO_RESEARCHER.md — the dated two-part entry required by the Issue (appended,
    nothing rewritten).
  • docs/spec/implementation_status.csv — the existing REQ-CONFIG-005 row updated in place.
  • docs/spec/IMPLEMENTATION_STATUS.md — regenerated by python scripts/implementation_status.py;
    not hand-edited.

No change to docs/spec/mirror/**.

Acceptance criteria

  • 1. A pack with NaN, +Infinity, -Infinity, 0 or a negative coefficient is rejected,
    with a diagnostic naming the recipe and the good, before any world construction.
    Five cases in
    validation.test.ts assert on the thrown message
    (RecipeDefinition "recipe-1": investmentGoodsPerCapitalUnit["good:tools"] … strictly positive);
    five more in genesisInvestmentCoefficients.test.ts assert the same through buildInitialWorld(),
    which is what makes "before any world construction" load-bearing rather than assumed.
  • 2. A coefficient keyed by a good the pack does not declare is rejected, naming the recipe
    and the unknown good.
    One test at each level, asserting on
    investmentGoodsPerCapitalUnit["good:unobtainium"] references a Good the DefinitionPack does not declare.
  • 3. The reproduction in the finding throws rather than producing reconciling UNCONVERTED
    capital.
    genesisInvestmentCoefficients.test.ts runs exactly the stated reproduction —
    baseline pack, recipe:tools-craft.investmentGoodsPerCapitalUnit set to { "good:tools": NaN },
    buildInitialWorld() on the otherwise-valid baseline scenario/config.
  • 4. The valid baseline pack, including the legitimately empty map at
    baselineDefinitionPack.ts:121, still passes.
    Directly asserted (accepts the unmodified baseline definition pack; still accepts an empty investment map), and indirectly by the whole
    suite: the baseline pack is built by many existing tests and all 671 pass.
  • 5. Negative controls confirmed sensitive. src/config/validation.ts was restored to its
    master content with the new tests in place. Result: 12 failed | 54 passed (66). The twelve
    failures are exactly the twelve new rejection tests (six in each file), each
    AssertionError: expected [Function] to throw an error. The four new acceptance tests passed
    under the revert, as they should — they assert the repair does not over-reject. Restoring the
    repair returns 66/66.
  • 6. FEEDBACK_TO_RESEARCHER.md carries the dated two-part entry referencing
    REQ-CONFIG-005.
    Entry ## 2026-09-15 — REQ-CONFIG-005 — …, in the file's documented
    Observed/Problem/Proposal/Impact shape.

Checks

Check Outcome Evidence
npm ci passed 0 vulnerabilities
npm run typecheck passed tsc --noEmit, clean
npm test passed 671 passed (671), 46 files passed (46)
npm run build passed vite build, dist/canonical.js emitted
python scripts/implementation_status.py --check passed matches 30 ledger rows over 49 registry rows
Negative control (criterion 5) passed 12 failed / 54 passed under the revert; 66/66 restored
dotnet restore passed both projects restored
dotnet build --configuration Release --no-restore passed Build succeeded, 0 warnings, 0 errors
dotnet test --configuration Release --no-build passed Failed: 0, Passed: 45, Total: 45 (REQ-MIGRATION-003 maintained)

All outcomes above are measured, at 0ff4fc3. Nothing is promoted.

Not checked

  • The rest of the required CI checks (policy-guard, scope-guard, status_lint,
    mergeability, machine-pr-guard): not_run locally — they are forge-side and run on this pull
    request. scope-guard is the one most likely to bite; the Changed artifacts section above lists
    every path from git diff --name-only origin/master...HEAD verbatim, which is what it compares
    against. Residual risk: low, and self-correcting — a body edit re-runs it without a new commit.
  • Runtime behavior of a valid non-empty coefficient. Unchanged and not re-measured beyond the
    existing suite. The repair only rejects values that never reached production code, because the
    resolver already discarded them.
  • Any pack outside this repository. not_run — none exists. See the assumption below.

Assumptions and unknowns

  1. The strictly-positive bound is an inference, not a quoted rule. This is the substantive
    unknown, and it is why the Issue mandates researcher feedback. 06 - Handoff/03 …:490 (section
    16A) enumerates recipe validation field by field and omits this field; 06 - Handoff/05 …:62-78
    annotates every sibling numeric field with an explicit bound and annotates this one with prose
    only. The inference is strong — 05 …:463 divides by the coefficient, so zero is a
    divide-by-zero and negative makes the minimum meaningless, the same argument
    HANDOFF-REPAIR-015 used to fix inputsPerBatch — but it is an inference. Filed as part 1 of
    the feedback entry.
  2. An empty map is unconditionally valid. The specification's only annotation, "at least one
    good for capital-forming recipes"
    , is unmeasurable: "capital-forming recipe" is defined nowhere,
    and the baseline's own investmentGoodsPerCapitalUnit: {} must stay valid (criterion 4). Filed
    as part 2 of the feedback entry. If the researcher answers that a predicate exists, the missing
    check is a follow-up, not a regression.
  3. Established fact, not assumption: this change rejects only values
    resolveCapitalGoodsPerCapitalUnit() already discarded silently. No pack that is valid today
    changes outcome — the whole suite passing, baseline pack included, is the evidence.
  4. Decision (also recorded as a comment on REQ-CONFIG-005: validateDefinitionPack never validates investmentGoodsPerCapitalUnit, so a non-finite or non-positive coefficient is silently reinterpreted as "no investment good" #465): the resolver's filter stays as
    defence-in-depth and does not become an assertion. Validation is authoritative on the genesis
    path, so the filter can no longer discard anything there; but the resolver is reachable from a
    registry assembled without validateDefinitionPack() (src/domain/definitionRegistry.test.ts
    does exactly that), and the emitting and reconciling sides both call it — loosening one side only
    is precisely the drift the shared resolver exists to prevent. Raising there would also move the
    diagnostic away from the validator that can name the offending pack. Not recorded as an ADR: it
    is cheap to reverse, being one filter in one function.

Highest-risk area for review

src/config/validation.ts, the key-existence check specifically. It is the one part that can reject
a pack that previously loaded, because it consults definitionPack.goods — state outside the recipe
being validated. Two things worth a reviewer's eye: (a) goods is keyed by GoodId and the
coefficient map is Readonly<Record<GoodId, number>>, so the comparison is string-to-string at
runtime and correct only as long as both are keyed by the same canonical good key — the baseline
pack's "good:tools" on both sides is the evidence, and packWithTools in the tests is the
isolated case; (b) the sibling inputsPerBatch validation immediately above has no such
key check. That asymmetry is deliberate — acceptance criterion 2 scopes the key check to this field,
and widening it to inputsPerBatch would be scope creep on a different requirement — but it is the
kind of inconsistency worth a second opinion. I have not filed it as an Issue because I could not
establish from the slice I am permitted to read whether inputsPerBatch keys are constrained to
declared goods anywhere; a reviewer who knows should say so.

Remaining gate

None mandatory. Every acceptance criterion is met and evidenced, both suites are green at the tested
revision, the ledger row is updated in place (one row per identifier; REQ-CONFIG-003's row
untouched; MERGE_COMMIT left empty for scripts/backfill_merge_commits.py), and the researcher
feedback travels in this pull request.

Two non-blocking follow-ups, both already routed and neither a gate on this merge: the two
specification questions above are in FEEDBACK_TO_RESEARCHER.md awaiting the researcher, and the
inputsPerBatch key-check asymmetry noted under Highest-risk area is an open question for review,
not discovered work I am withholding.

🤖 Generated with Claude Code

github-actions Bot and others added 2 commits September 15, 2026 06:46
…ently dropping it

validateDefinitionPack() never inspected RecipeDefinition.investmentGoodsPerCapitalUnit.
resolveCapitalGoodsPerCapitalUnit() was the first consumer on the genesis path and it
filters out any entry that is not finite and > 0, so a NaN, Infinity, zero or negative
coefficient produced an empty result — indistinguishable from a recipe that genuinely
declares no investment good. Because buildInitialWorld() and reconcileGenesisStocks()
both resolve through that same filter, the invalid configuration reconciled cleanly:
two sides sharing one reinterpretation agree. The filter had no key-side check either,
so a coefficient naming an undeclared good was dropped by the same silent path.

validateDefinitionPack() now rejects, before any world construction, a coefficient that
is non-finite or <= 0 and a coefficient keyed by a good the pack does not declare, with
a diagnostic naming the recipe and the good. An empty map stays valid: it is a real
"no investment good" declaration, and the baseline pack relies on it.

The resolver's filter is kept as defence-in-depth rather than promoted to an assertion —
validation is now authoritative on the genesis path, but the resolver is also reachable
from a registry assembled without it, and both the emitting and reconciling side call it,
so loosening one side only would let them disagree. Its doc comment now says so.

Files a two-part researcher feedback entry: the specification states no per-coefficient
bound for this field (the strictly-positive rule is an inference from the divide in
05 ...:463, the same argument HANDOFF-REPAIR-015 used for inputsPerBatch), and its only
prose annotation, "at least one good for capital-forming recipes", is unmeasurable
because "capital-forming recipe" is defined nowhere.

Closes #465

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updates the existing REQ-CONFIG-005 row rather than adding a second one, per
AUTHOR_RUNBOOK.md section 7. STATUS stays IMPLEMENTED; ISSUE/PR move to the
contributing pair for this slice and EVIDENCE now names #76, #88 and #502.
MERGE_COMMIT is cleared for backfill_merge_commits.py.

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

zendev-author Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

AUTHOR note: the earlier policy-guard failure at this head is superseded, not outstanding

statusCheckRollup lists two policy-guard entries for head 0ff4fc3, one FAILURE and one
SUCCESS. Recording which is which so no run has to re-derive it.

  • Run 34938518679,
    06:47:14Z — failure. Cause, from its log:
    scope-guard: 7 changed path(s) are not named anywhere in the pull request body. I opened this
    pull request with a placeholder body in order to learn its number, because AUTHOR_RUNBOOK.md
    section 7 requires the ledger row to name this pull request and says to open it first when the
    number is not yet known. The placeholder named no paths, so scope-guard correctly refused it.
  • Run 34938587190,
    06:48:07Z — success, all three jobs (policy-guard, typescript, build-and-test). Same head,
    after the body was filled in with every path from git diff --name-only origin/master...HEAD.

No commit was made between the two runs; the body edit re-ran the check, which is the remedy the
runbook names for exactly this case. Current state at 0ff4fc3: mergeable: MERGEABLE,
mergeStateStatus: CLEAN, every check green.

The failure was real and is reported as failed for the run that produced it — it is not promoted
to passed. It is simply no longer the current result for that check at this head.

@zendev-acceptor

Copy link
Copy Markdown
Contributor

ACCEPTOR verdict: ACCEPT — head 0ff4fc3eb13c499196b2b2db2000566181bcf0ca

Reviewed at head 0ff4fc3eb13c499196b2b2db2000566181bcf0ca (base bd74d83, which is an ancestor of
the head — the branch is not behind). Issue #465. The six ACCEPT conditions, each as observed by
this run rather than as reported by the body:

1. Every required check is measured green at the head revision

Latest conclusion per check name, all at head 0ff4fc3:

Check Conclusion Run
policy-guard SUCCESS (06:48:17Z) 34938587190
typescript SUCCESS (06:48:36Z) 34938587190
build-and-test SUCCESS (06:48:43Z) 34938587190
mergeability SUCCESS Mergeability run 34938513188 (push)
write-mergeability-status SUCCESS ×2 34938513188, 34938518688

Nothing is pending, skipped, neutral or unknown.

The earlier policy-guard FAILURE at this same head was checked rather than taken on the
author's word: gh api .../actions/runs/34938518679 and .../34938587190 both report
head_sha: 0ff4fc3eb13c499196b2b2db2000566181bcf0ca, run_attempt: 1, created 06:47:14Z and
06:48:07Z respectively. The success is the later run at the same head, so it is the current
result for that check name, which is also what the rollup resolves to. No commit separates them —
git log bd74d83..0ff4fc3 is two commits, e00272f then 0ff4fc3, both before either run. The
failure is recorded here as failed for the run that produced it; it is not promoted.

mergeable: MERGEABLE, mergeStateStatus: CLEAN, and git merge-base --is-ancestor origin/master 0ff4fc3 succeeds — the branch merges cleanly and is current with the base, which are the two
separate conditions.

Independent re-execution at 0ff4fc3 (detached checkout of the head), both runtimes:

Command Outcome Evidence
npm ci passed clean install
npm run typecheck passed tsc --noEmit clean
npm test passed Test Files 46 passed (46); Tests 671 passed (671)
npm run build passed dist/canonical.js emitted
dotnet restore passed both projects
dotnet build --configuration Release --no-restore passed 0 errors
dotnet test --configuration Release --no-build passed Failed: 0, Passed: 45, Total: 45 — REQ-MIGRATION-003 maintained
python scripts/implementation_status.py --check passed matches 30 ledger rows over 49 registry rows

2. Every acceptance criterion is met, with observed evidence

  1. Invalid values rejected before world construction — met. src/config/validation.ts:229-236
    rejects a non-finite or <= 0 coefficient inside validateDefinitionPack(). Proven at two
    levels: five cases in src/config/validation.test.ts:876-887, and the same five through
    buildInitialWorld() in src/simulation/genesisInvestmentCoefficients.test.ts:47-64. The
    second is what makes "before any world construction" evidence rather than assertion.
  2. Unknown good key rejected — met. validation.ts:222-226 checks declaredGoodKeys first, so
    a reference failure reports as a reference failure (section 19 step 1 order). Tested at both
    levels (validation.test.ts:889-894, genesisInvestmentCoefficients.test.ts:66-74).
  3. The finding's reproduction throws — met. genesisInvestmentCoefficients.test.ts runs exactly
    the stated reproduction: baseline pack, recipe:tools-craft.investmentGoodsPerCapitalUnit set to
    the invalid value, buildInitialWorld() on the otherwise-valid baseline scenario and default
    config. It throws instead of emitting good-less reconciling UNCONVERTED capital.
  4. The valid baseline pack still passes — met. Asserted directly (accepts the unmodified baseline definition pack, still accepts an empty investment map) and indirectly by the whole
    suite: 671/671 pass at this head in my own run.
  5. Negative controls confirmed sensitive — met, and re-measured by this run, not accepted on
    report.
    I restored src/config/validation.ts to its bd74d83 content with the new tests in
    place and ran both files: Tests 12 failed | 54 passed (66), the twelve failures being exactly
    the twelve new rejection cases, each AssertionError: expected [Function] to throw an error. The
    four new acceptance tests passed under the revert, as they should. The working tree was restored
    to 0ff4fc3 afterwards (git status --porcelain empty); no commit was made and nothing was
    pushed to the branch.
  6. Dated two-part researcher feedback — met. docs/spec/FEEDBACK_TO_RESEARCHER.md gains
    ## 2026-09-15 — REQ-CONFIG-005 — … in the file's Observed/Problem/Proposal/Impact shape,
    appended with nothing rewritten (+64 −0). Part 1 is the unstated per-coefficient bound; part 2
    is the unmeasurable "at least one good for capital-forming recipes".

The claimed requirement identifier is implemented, not merely mentioned. REQ-CONFIG-005 in
REQUIREMENTS_REGISTRY.csv:16 is "Invalid references, non-finite values and out-of-range
configuration fail fast with useful diagnostics; do not silently coerce"
— an invariant. The diff
adds exactly that: a reference check, a finite/range check, and a diagnostic naming recipe, field,
key and value. The ledger row is updated in place (ISSUE 465, PR 502, MERGE_COMMIT empty for
backfill_merge_commits.py, EVIDENCE naming #76, #88 and #502), REQ-CONFIG-003's row is
untouched, no second REQ-CONFIG-005 row was added, and IMPLEMENTATION_STATUS.md is regenerated
rather than hand-edited — the --check run above is the proof.

3. The diff is confined to the declared scope

Seven paths, every one inside the Issue's Scope: src/config/validation.ts (the required rule),
src/config/validation.test.ts and src/simulation/genesisInvestmentCoefficients.test.ts (criteria
1-5), src/domain/definitionRegistry.ts (doc comment only, +12 −0, no behavior change — the
Decision the Scope required, also recorded as a comment on #465),
docs/spec/FEEDBACK_TO_RESEARCHER.md, docs/spec/implementation_status.csv and its generated
docs/spec/IMPLEMENTATION_STATUS.md. Nothing touches docs/spec/mirror/**, and nothing touches
.github/workflows/**, AGENTS.md or docs/zendev/** — so no policy path is mixed with product
code and no automated authority is widened by this merge. Non-goals are respected: opening
quantities, recipes, the baseline pack and reconciliation keying are unchanged.

4. No invariant and no test was weakened

Both test files are additions only — validation.test.ts +59 −0, genesisInvestmentCoefficients.test.ts
+83 −0 (new) — and no other test file is in the diff. No deletion, no skip, no loosened
assertion; the suite goes up, and the discovered count rises from 659 to 671. No conservation
invariant is relaxed: the change adds a refusal, it does not widen what passes. The one behavioral
risk is the opposite direction — the new key-existence check can reject a pack that previously
loaded — and it is bounded by the fact that resolveCapitalGoodsPerCapitalUnit() already discarded
every value now rejected, so no pack that is valid today changes outcome. The full suite, baseline
pack included, is the evidence.

On the highest-risk area the author nominated: the key comparison is string-to-string between
Object.keys(definitionPack.goods) and the coefficient map's keys, and both are canonical
good:* keys in the baseline pack and in packWithTools. The noted asymmetry — inputsPerBatch
carries no equivalent key check — is real but correctly left alone: widening it is a different
requirement, and inventing it here would be the scope creep this runbook refuses. It is a fair
question to route to a follow-up Issue rather than a defect in this change.

5. No secret, credential, or personal data is present

policy-guard is green at this head, and reading the diff myself found no token, credential or
local machine path — the only paths are repository-relative.

6. The handoff record is complete

All nine elements are present and, more to the point, honest: the checks table separates what was
measured from what was not_run and says why, the strictly-positive bound is labelled an inference
rather than a quoted rule, and the empty-map reading is labelled an assumption with the
specification tension stated. Both are routed to the researcher in this same pull request, and
neither blocks: the adopted reading rejects only values the resolver already discarded.

Merging

No standing CHANGES_REQUESTED exists on this pull request from this identity or any other, so the
merge is executable and this is not a verdict I cannot carry out. Squash-merging and deleting the
branch.

@zendev-acceptor
zendev-acceptor Bot merged commit 5c0fa64 into master Sep 15, 2026
8 of 9 checks passed
@zendev-acceptor
zendev-acceptor Bot deleted the claude/issue-465-config-005-investment-goods-validation branch September 15, 2026 06:54
zendev-author Bot pushed a commit that referenced this pull request Sep 15, 2026
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>
zendev-acceptor Bot pushed a commit that referenced this pull request Sep 15, 2026
…t declare (#510)

* REQ-CONFIG-005: reject inputsPerBatch keys the DefinitionPack does not 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>

* ledger: record PR #510 against REQ-CONFIG-005

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>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
zendev-author Bot pushed a commit that referenced this pull request Sep 15, 2026
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>
zendev-acceptor Bot pushed a commit that referenced this pull request Sep 15, 2026
…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>
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