Repository navigation
REQ-CONFIG-004: key ProductionUnit genesis goods by typed inventory bucket - #473
Conversation
…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>
Note on the check rollup — one superseded
|
There was a problem hiding this comment.
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
baselineScenarioreconciliation 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: collapsingserializeInventoryBucket
(src/simulation/genesisReconciliation.ts:72) to always return the unbucketed sentinel
restoresmaster's aggregate behaviour, andgenesisReconciliation.test.tsthen 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'sINPUT good:iron
asOUTPUTwhere 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 ofgoodStockKey(genesisReconciliation.ts:82) and surfaces through
ReconciliationResult.details.key; the first control asserts
result.details?.keycontains 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.csvchanges exactly one line (1 / 1in--numstat):
REQ-CONFIG-004PARTIAL->IMPLEMENTED,ISSUE455,PR473,MERGE_COMMITleft
blank forscripts/backfill_merge_commits.py.IMPLEMENTATION_STATUS.mdis 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.
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_ENDOWMENTcarries a typedinventoryBucketfor ProductionUnit-owned stock, world genesis emits it, and genesis reconciliation comparesProductionUnit + region + inventoryBucket + goodIdon both sides. A same-quantity relocation between two buckets — which reconciled cleanly onmasterbecause 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 entryHANDOFF-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 commitbd0a806.Changed artifacts
git diff --name-only origin/master...HEAD:src/domain/genesisLedger.ts— adds theInventoryBuckettype (INPUT | OUTPUT | INVESTMENT) and splits theGOOD_ENDOWMENTmember in two: a ProductionUnit-owned member whereinventoryBucketis required, and a member for every other owner where it may not be present.src/simulation/worldState.ts— emitsINPUT,OUTPUTandINVESTMENTon the three ProductionUnit opening-inventory records (3 added lines).src/simulation/genesisReconciliation.ts— introduces onegoodStockKey(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 ProductionUnitGOOD_ENDOWMENTconstruction 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 aProductionUnitGoodEndowment/UnbucketedGoodEndowmenttype-guard pair; adjusts one pre-existing test's object spread (see Highest-risk area).docs/spec/implementation_status.csv— updates the singleREQ-CONFIG-004row:STATUSPARTIAL->IMPLEMENTED,ISSUE455,PR473,MERGE_COMMITcleared for the backfill script, and the evidence cell extended with this slice. One line changed.docs/spec/IMPLEMENTATION_STATUS.md— regenerated bypython scripts/implementation_status.py; never hand-edited.Acceptance criteria
baselineScenariopass unchanged; the full suite is 594/594.src/simulation/genesisReconciliation.test.ts. The first splits a ProductionUnitGOOD_ENDOWMENTin 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'sINPUTgood:ironrecord asOUTPUTwithout splitting it — that unit already holdsgood:ironas bothINPUT(80) andOUTPUT(300), so the unit's total for that good is untouched by construction.ReconciliationResult.details.keyreports it. The first negative control assertsresult.details?.keycontains the source record's bucket.python scripts/implementation_status.py --checkpasses.MERGE_COMMITis left blank forscripts/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
npm cinpm run typechecktsc --noEmit, no outputnpm testnpm run builddist/canonical.jsbuiltpython scripts/implementation_status.py --checkdotnet restoredotnet build --configuration Release --no-restoredotnet test --configuration Release --no-buildREQ-MIGRATION-003maintained)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
serializeInventoryBucketalways return the unbucketed sentinel, reproducingmaster'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
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.Assumptions and unknowns
inventoryBucketfor ProductionUnit-ownedGOOD_ENDOWMENTand makes it part of stock identity; it declares the field optional on the sharedGenesisRecordshape.householdInventoryand a State onepublicInventory— neither is an INPUT/OUTPUT/INVESTMENT container, so a bucket on such a record could only be fabricated. If the reviewer reads section 20 as permitting an ignored bucket on other owners, the union should collapse back to one member with an optional field; the reconciliation change stands either way.REQ-CONFIG-004is promoted fromPARTIALtoIMPLEMENTED. This follows the ledger's own trail: thePARTIALrationale written by PRs REQ-CONFIG-004: attribute cohort opening money/goods/population to the cohort, not its Clan #459/REQ-CONFIG-004: reconcile genesis capital per capital good #461/REQ-CONFIG-004: key good genesis reconciliation by region identity #467/REQ-CONFIG-004: key resource genesis reconciliation by typed region/good identity #469/REQ-CONFIG-004: key cohort population genesis reconciliation by typed region identity #471 names an explicit list of typed-identity defects, and each of those PRs closed one and re-stated the remainder. PR REQ-CONFIG-004: key cohort population genesis reconciliation by typed region identity #471's cell names REQ-CONFIG-004: preserve ProductionUnit GOOD_ENDOWMENT inventory-bucket identity #455 as the sole remaining one. No otherREQ-CONFIG-004Issue is open. Unknown: whether a typed-identity dimension exists that nobody has yet named — I can assert only that none is recorded.Highest-risk area for review
The two-member
GOOD_ENDOWMENTunion insrc/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 elementrrather than the narrowedcohortGoodRecord, 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 aGOODfailure) — 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 onowner.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
IMPLEMENTEDon 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 toPARTIALnaming whatever defect they identify.MERGE_COMMITstays blank for the backfill script, as the runbook requires.🤖 Generated with Claude Code