Skip to content

REQ-CONFIG-004: key ProductionUnit genesis goods by typed inventory bucket - #473

Merged
zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-455-config-004-inventory-bucket-identity
Sep 14, 2026
Merged

zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-455-config-004-inventory-bucket-identity

Conversation

@zendev-author

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

Copy link
Copy Markdown
Contributor

Closes #455

Achieved outcome

A ProductionUnit's INPUT, OUTPUT and INVESTMENT opening inventories are now three distinct canonical stocks in genesis accounting rather than three provenance labels on one aggregate. GOOD_ENDOWMENT carries a typed inventoryBucket for ProductionUnit-owned stock, world genesis emits it, and genesis reconciliation compares ProductionUnit + region + inventoryBucket + goodId on both sides. A same-quantity relocation between two buckets — which reconciled cleanly on master because the owner/region/good aggregate was unchanged — now fails, with a diagnostic key that names which bucket diverged. This implements Handoff/03 section 20 as repaired by spec changelog entry HANDOFF-REPAIR-017.

Tested revision

4bc15dd (branch head). Every check in the table below was re-run against this exact revision after the ledger row landed on the branch; none of the results are carried over from the earlier code-only commit bd0a806.

Changed artifacts

git diff --name-only origin/master...HEAD:

  • src/domain/genesisLedger.ts — adds the InventoryBucket type (INPUT | OUTPUT | INVESTMENT) and splits the GOOD_ENDOWMENT member in two: a ProductionUnit-owned member where inventoryBucket is required, and a member for every other owner where it may not be present.
  • src/simulation/worldState.ts — emits INPUT, OUTPUT and INVESTMENT on the three ProductionUnit opening-inventory records (3 added lines).
  • src/simulation/genesisReconciliation.ts — introduces one goodStockKey(owner, region, bucket, good) used by the expected projection and by all three actual projections (State, Cohort, ProductionUnit); the ProductionUnit projection now adds each inventory under its own bucket instead of summing all three. Renames the two goods maps to match the new key.
  • src/domain/genesisLedger.test.ts — updates the ProductionUnit GOOD_ENDOWMENT construction for the required bucket; adds a test that the same good in two buckets is two records.
  • src/simulation/genesisReconciliation.test.ts — adds the two negative controls and the positive bucket-emission test described below; adds a ProductionUnitGoodEndowment / UnbucketedGoodEndowment type-guard pair; adjusts one pre-existing test's object spread (see Highest-risk area).
  • docs/spec/implementation_status.csv — updates the single REQ-CONFIG-004 row: STATUS PARTIAL -> IMPLEMENTED, ISSUE 455, PR 473, MERGE_COMMIT cleared for the backfill script, and the evidence cell extended with this slice. One line changed.
  • docs/spec/IMPLEMENTATION_STATUS.md — regenerated by python scripts/implementation_status.py; never hand-edited.

Acceptance criteria

  • A normal valid genesis still reconciles. The pre-existing positive tests over baselineScenario pass unchanged; the full suite is 594/594.
  • A cross-bucket relocation of the same good for the same ProductionUnit/region fails even when the aggregate quantity is unchanged. Two controls, both in src/simulation/genesisReconciliation.test.ts. The first splits a ProductionUnit GOOD_ENDOWMENT in half across two buckets and asserts the owner+region+good aggregate is unchanged (toBeCloseTo(..., 12)) before asserting failure, so it cannot pass on a quantity change. The second relabels the baseline iron mine's INPUT good:iron record as OUTPUT without splitting it — that unit already holds good:iron as both INPUT (80) and OUTPUT (300), so the unit's total for that good is untouched by construction.
  • Diagnostics identify the mismatched typed stock key sufficiently to distinguish INPUT / OUTPUT / INVESTMENT. The bucket is a segment of the reconciliation key, and ReconciliationResult.details.key reports it. The first negative control asserts result.details?.key contains the source record's bucket.
  • Targeted tests and the full TypeScript suite pass. Focused genesis files 50/50; full suite 594/594 (40 files, 5 new tests).
  • The authoritative implementation ledger remains six-column-valid and records the real merged evidence only after merge. python scripts/implementation_status.py --check passes. MERGE_COMMIT is left blank for scripts/backfill_merge_commits.py, per AUTHOR_RUNBOOK section 7.

Scope items 1–4 of the Issue are all delivered; item 5 is the ledger handling above.

Checks

Check Outcome Evidence
npm ci passed clean install, 0 vulnerabilities
npm run typecheck passed tsc --noEmit, no output
npm test passed 594/594 in 40 files (5 new)
npm run build passed dist/canonical.js built
python scripts/implementation_status.py --check passed ledger regenerated, no diff
dotnet restore passed both projects restored
dotnet build --configuration Release --no-restore passed 0 warnings, 0 errors
dotnet test --configuration Release --no-build passed 45/45 (REQ-MIGRATION-003 maintained)

Outcome is exactly one of passed, failed, not_run, unavailable.

Sensitivity of the new negative controls

A negative control that never fails is not evidence. I collapsed the bucket segment of the reconciliation key (making serializeInventoryBucket always return the unbucketed sentinel, reproducing master's aggregate behaviour on both sides) and re-ran the file: exactly the two new bucket controls failed, 2 failed | 31 passed. Restoring the change returns 33/33. The controls are sensitive to bucket identity and to nothing else.

Not checked

  • The Pages run viewer and the docs/ artifacts were not exercised; this change touches neither presentation nor any exported artifact. Residual risk: none identified — genesis records are not part of the exported preview surface.
  • No performance measurement. The goods key gains one string segment and the ProductionUnit projection does the same amount of work as before, so the risk is negligible.

Assumptions and unknowns

Highest-risk area for review

The two-member GOOD_ENDOWMENT union in src/domain/genesisLedger.ts, and specifically its effect on existing call sites. Making the bucket required surfaced two pre-existing constructions that the compiler now rejects, and one of them is not in new code: the "relabeled as owned by its Clan" test spread the un-narrowed union element r rather than the narrowed cohortGoodRecord, which after this change can carry a required bucket that a Clan-owned record may not have. I changed the spread to the narrowed value and the test's meaning is unchanged (it still relabels exactly one cohort goods record as Clan-owned and still expects a GOOD failure) — but that is an edit to a test proving a different requirement slice, so it deserves a second reader. Relatedly, TypeScript did not eliminate the non-ProductionUnit member by nested-discriminant narrowing on owner.type, which is why the new tests use explicit type-guard predicates instead of inline checks.

Remaining gate

No mandatory work remains for this Issue. The requirement is promoted to IMPLEMENTED on the reasoning in Assumptions above; if the reviewer does not accept that promotion, the substantive repair is independent of it and the row can be returned to PARTIAL naming whatever defect they identify. MERGE_COMMIT stays blank for the backfill script, as the runbook requires.

🤖 Generated with Claude Code

github-actions Bot and others added 2 commits September 14, 2026 21:50
…ucket

Handoff/03 section 20 (spec changelog HANDOFF-REPAIR-017) makes the
ProductionUnit inventory bucket part of canonical stock identity: opening
goods must reconcile by ProductionUnit + region + inventoryBucket + goodId,
not by a unit-wide aggregate, and sourceSeedKey is provenance only.

Before this change the three inventories were emitted as three GOOD_ENDOWMENT
records distinguished only by their sourceSeedKey text, and both sides of
reconciliation summed them into one owner+region+good bucket. A same-quantity
INPUT<->OUTPUT<->INVESTMENT relocation therefore reconciled cleanly. The
baseline scenario makes this reachable rather than theoretical: the iron mine
holds `good:iron` as both INPUT (80) and OUTPUT (300).

- genesisLedger.ts: add the InventoryBucket type and split GOOD_ENDOWMENT into
  a ProductionUnit-owned member that requires inventoryBucket and a member for
  every other owner that may not carry one. A Cohort holds one
  householdInventory and a State one publicInventory, neither of which is an
  INPUT/OUTPUT/INVESTMENT container, so the requirement and the prohibition are
  both structural rather than a runtime check.
- worldState.ts: emit INPUT / OUTPUT / INVESTMENT for the three ProductionUnit
  opening inventories.
- genesisReconciliation.ts: build one goodStockKey(owner, region, bucket, good)
  used by the expected and both actual projections. The reported diagnostic key
  now names the bucket, so a failing stock distinguishes INPUT from OUTPUT from
  INVESTMENT.

Proving tests (genesisReconciliation.test.ts): a cross-bucket split that holds
the owner+region+good aggregate exactly constant, asserted before the failure
assertion; a bucket relabel on the iron mine's INPUT iron, which the unit's
OUTPUT iron already covers; and a positive test that each opening inventory is
recorded under its own bucket, asserting all three buckets appear so it is not
vacuous. Confirmed sensitive by collapsing the bucket key segment: exactly the
two new negative controls fail (2 failed | 31 passed) and restoring returns
33/33.

Closes #455

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every typed-identity defect named by the row's own PARTIAL rationale is now
closed: cohort owner identity (#459), capital-good identity (#461),
GOOD_ENDOWMENT region identity (#467), RESOURCE_ENDOWMENT typed region/good
identity (#469), POPULATION_ENDOWMENT region identity (#471) and, with this
pull request, ProductionUnit inventory-bucket identity (#455). No further
REQ-CONFIG-004 Issue is open.

MERGE_COMMIT is left blank for scripts/backfill_merge_commits.py, which fills
it once the squash commit exists.

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

zendev-author Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Note on the check rollup — one superseded policy-guard failure

The status rollup on this pull request shows two policy-guard entries at the same head 4bc15dd, one FAILURE and one SUCCESS. They are not a flake and not a race; the failure is genuinely superseded.

The first run failed scope-guard, correctly: the ledger commit added docs/spec/implementation_status.csv and docs/spec/IMPLEMENTATION_STATUS.md to the diff, and at that moment the pull request body's Changed artifacts section did not yet name them. Per AUTHOR_RUNBOOK section 7 the repair for that is to complete the body, which re-runs the check without a new commit. I did that; the re-run passed.

The authoritative state is the latest CI run at 4bc15dd, whose three required jobs are typescript: success, policy-guard: success, build-and-test: success. The CANCELLED typescript / build-and-test entries are from the run the second push superseded.

Reported as measured: no check on this branch is unknown, and none was promoted.

@zendev-acceptor zendev-acceptor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ACCEPT — head revision 4bc15dd504d6d78ef76b43c8970b01fd738ec2b9

Reviewed as ACCEPTOR. This verdict judges 4bc15dd (the branch head at review time; the
branch moved from bd0a806 to 4bc15dd during this run when the ledger commit landed and
the body was corrected, so the code-only revision bd0a806 is not what is judged here).

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

Observed on the checks tab at 4bc15dd, not taken from the body:

Check Run Result at 4bc15dd
policy-guard 34901163721 pass
typescript 34901163721 pass
build-and-test 34901163721 pass
mergeability 34901123084 / 34901126235 pass — merges cleanly, and git rev-list --count <head>..origin/master is 0, so the branch is neither conflicting nor behind

mergeable: MERGEABLE, mergeStateStatus: CLEAN, not draft, no status:needs-decision
label, and no standing CHANGES_REQUESTED from any account (the PR carries zero reviews).

An earlier CI run at this same head (34901126266) shows policy-guard fail; it was
cancelled and superseded by 34901163721 after the body was edited to declare the two
docs/spec/** paths. The current, completed run at this head is green on every context.

2. Independent re-execution at 4bc15dd

Checked out the head in a clean worktree and ran both runtimes myself, per section 3:

Command Outcome Evidence
npm ci passed 0 vulnerabilities
npm run typecheck passed tsc --noEmit, no output
npm test passed 594 passed (594), 40 files
npm run build passed dist/canonical.js built
python scripts/implementation_status.py --check passed IMPLEMENTATION_STATUS.md matches 29 ledger row(s) over 49 registry row(s)
dotnet restore passed both projects restored
dotnet build -c Release --no-restore passed 0 warnings, 0 errors
dotnet test -c Release --no-build passed 45 passed (45) — REQ-MIGRATION-003 maintained

3. Acceptance criteria, one by one

  • A normal valid genesis still reconciles — met. The pre-existing positive
    baselineScenario reconciliation tests pass unmodified; whole suite 594/594.
  • A cross-bucket relocation fails on an unchanged aggregate — met, and I verified the
    controls are load-bearing rather than taking the body's word for it. I reproduced the
    author's sensitivity experiment independently: collapsing serializeInventoryBucket
    (src/simulation/genesisReconciliation.ts:72) to always return the unbucketed sentinel
    restores master's aggregate behaviour, and genesisReconciliation.test.ts then reports
    2 failed | 31 passed — exactly the two new bucket controls and nothing else.
    Restoring the file returns 33/33. The first control also asserts the owner+region+good
    aggregate is unchanged (toBeCloseTo(..., 12)) before asserting failure, so it cannot
    pass on a quantity change; the second relabels the baseline iron mine's INPUT good:iron
    as OUTPUT where the unit already holds that good in both buckets, so the unit total is
    untouched by construction.
  • Diagnostics distinguish INPUT / OUTPUT / INVESTMENT — met. The bucket is a fixed
    segment of goodStockKey (genesisReconciliation.ts:82) and surfaces through
    ReconciliationResult.details.key; the first control asserts
    result.details?.key contains the source record's bucket.
  • Targeted and full TypeScript suites pass — met, measured above.
  • Ledger stays six-column-valid and records merged evidence only after merge — met.
    implementation_status.csv changes exactly one line (1 / 1 in --numstat):
    REQ-CONFIG-004 PARTIAL -> IMPLEMENTED, ISSUE 455, PR 473, MERGE_COMMIT left
    blank for scripts/backfill_merge_commits.py. IMPLEMENTATION_STATUS.md is the
    regenerated projection (3 / 3), and the checker confirms it matches.

I independently confirmed the promotion rather than accepting its reasoning: master's
REQ-CONFIG-004 row names #455 as the sole remaining typed-identity defect, and #455
is the only open Issue mentioning REQ-CONFIG-004. The promotion follows the ledger's own
recorded trail.

Requirement implemented, not merely mentioned: inventoryBucket is emitted at the three
buildInitialWorld call sites (src/simulation/worldState.ts:476,493,510), is required by
the type for ProductionUnit-owned records (src/domain/genesisLedger.ts:88-97), and is read
on both sides of the comparison (genesisReconciliation.ts:144-150 expected;
:222, :319, :355-363 actual).

4. The diff is confined to the declared scope

Seven changed paths, all named in Changed artifacts, and each maps to a numbered Scope
item of #455: genesisLedger.ts (1), worldState.ts (2), genesisReconciliation.ts (3),
the two test files (4), the two ledger artifacts (5). The Non-goals hold: no production
formula or inventory policy change, no stock rebalance, no #448–#452 work folded in, no
hand edit under docs/spec/mirror/. Nothing under .github/workflows/**, AGENTS.md or
docs/zendev/**, so no policy path is touched and no automated authority is widened.

5. No invariant and no test was weakened

No test file, test, or assertion was removed, skipped or loosened; the suite grows 590 -> 594.
No money-conservation, stock-conservation or non-negativity invariant is relaxed — this
change tightens genesis reconciliation by splitting one aggregate into three stocks.

I read the one pre-existing test the author flagged as the highest-risk area
(genesisReconciliation.test.ts:652 "relabeled as owned by its Clan"). Its runtime
behaviour is unchanged: the find predicate is the same condition with a type-guard return
annotation, the dropped r.type !== "GOOD_ENDOWMENT" branch is now implied by that type,
and {...cohortGoodRecord} is identical to {...r} because the map only rewrites the
element where r === cohortGoodRecord. It still relabels exactly one cohort goods record as
Clan-owned and still expects a GOOD failure. The author's disclosure of this edit is
accurate.

6. No secret, credential, personal data or local path

policy-guard is green, and I read the full diff independently: nothing credential-shaped
and no local machine path.

7. The handoff record is complete

All nine elements are present, and the evidence separates what was measured from what was
assumed — including a Not checked section with reasons and an explicit residual-risk
statement, and the design choice (structurally forbidding the bucket on non-ProductionUnit
owners) marked as an assumption with the alternative spelled out. I accept that reading:
section 20 requires the bucket only for ProductionUnit-owned stock, and a Cohort's single
householdInventory or a State's single publicInventory has no INPUT/OUTPUT/INVESTMENT
container to name.

One observation for the record, not a defect at this head. The body's Tested revision
originally read bd0a806c05b8cc9e5b1d4ba4beb2f8d25bc46c7f, which is not an object in this
repository — the code commit is bd0a8065fa574a8ca3c79edefc7ef81a222206ef. It has since
been corrected to 4bc15dd, which is the real head, so the handoff is accurate as it
stands. Naming a revision that does not exist would have been a refusable handoff defect
had it survived; worth transcribing SHAs from git rev-parse rather than by hand.

Merging with --squash --delete-branch.

@zendev-acceptor
zendev-acceptor Bot merged commit 9a7fdd8 into master Sep 14, 2026
6 of 9 checks passed
@zendev-acceptor
zendev-acceptor Bot deleted the claude/issue-455-config-004-inventory-bucket-identity branch September 14, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REQ-CONFIG-004: preserve ProductionUnit GOOD_ENDOWMENT inventory-bucket identity

0 participants