Repository navigation
REQ-CONFIG-004: key good genesis reconciliation by region identity - #467
zendev-acceptor[bot] merged 2 commits into
Conversation
GOOD_ENDOWMENT records carry a regionId, but genesis reconciliation keyed both the expected ledger projection and every actual projection by owner + good alone, so the location was discarded before comparison. A same-owner, same-good relocation between two regions left the key unchanged and passed as long as the aggregate quantity was unchanged. Handoff/03 section 20 makes location part of canonical stock identity: reconciliation must compare owner + region + good rather than an owner-wide aggregate. Key goods accordingly, deriving the actual location from the owning entity's own region (CohortSeed.regionKey / ProductionUnitSeed.regionKey, resolved through the region registry) rather than from an aggregate container. A State's public inventory is held by the State itself, not by any one of the regions it controls, so it is not region-bound: GOOD_ENDOWMENT.regionId becomes optional to match the canonical `regionId?: RegionId` declaration, world genesis stops fabricating a placeholder region for it (the lookup matched region keys against a state key and always resolved to undefined), and both sides of the comparison carry an explicit unlocated segment. The three ProductionUnit inventory buckets still aggregate together here; typed INPUT/OUTPUT/INVESTMENT bucket identity is Issue #455. Closes #449 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI evidence at the tested revisionThe CI-only checks listed as
For the record, so no reviewer reads it as an open failure: the No check was promoted. Every outcome above is measured at |
There was a problem hiding this comment.
Verdict: ACCEPT
Head revision judged: aaef0d791c0d4eb61b65e5636bb61e7fb28b713a. No prior verdict existed on this pull request from any account, so no standing refusal held the merge.
1. Every required check is measured green at the head revision
Read from the checks tab at aaef0d7, latest conclusion per check:
| Check | Conclusion at aaef0d7 |
|---|---|
policy-guard (incl. scope-guard, machine-pr-guard) |
success — run 34892205332 |
build-and-test |
success — same run |
typescript |
success — same run |
mergeability |
success — "merges cleanly into the base branch" |
Noted for the record: an earlier workflow run at this same SHA (34892117226, 20:18:42Z) recorded policy-guard as failure, and its build-and-test/typescript jobs as cancelled. That run predates a pull request body edit, which is the input scope_guard reads; the superseding run at 20:19:51Z is green on all three. Latest conclusion per required check at the head revision is success, and nothing is pending, skipped, neutral or unknown.
mergeability checked before anything else, per section 3: mergeable: MERGEABLE, mergeStateStatus: CLEAN, and git log pr467..origin/master is empty — the branch is neither conflicting nor behind, so the green checks were measured against the base that will actually land.
2. Every acceptance criterion is met, with observed evidence
Independent re-execution at aaef0d7 (section 3 — deliberate, not a restatement of CI):
npm cipassed;npm run typecheckpassed (no diagnostics);npm testpassed — 587 tests in 40 files;npm run buildpassed.dotnet restorepassed;dotnet build --configuration Release --no-restorepassed;dotnet test --configuration Release --no-buildpassed — 45/45.REQ-MIGRATION-003is maintained: the canonical change leaves the legacy build green.python3 scripts/implementation_status.py --checkpassed — "IMPLEMENTATION_STATUS.md matches 29 ledger row(s)", so the generated table was regenerated rather than hand-edited.
Criterion by criterion:
- Location in the reconciliation key.
src/simulation/genesisReconciliation.tskeys both sides by${owner}:${region}:${good}— expected at theGOOD_ENDOWMENTbranch, actual in the State, cohort and ProductionUnit projections. Both the expected-key sweep and the actual-key orphan sweep were updated to the new map, so a key present on only one side still fails rather than being silently dropped. Met. - Actual location derived from the owning entity.
regionIdByRegionKeyis built fromworldState.regionsasregion.seed.key → RegionId; cohorts resolve throughcohort.seed.regionKey, ProductionUnits throughpu.seed.regionKey. I checked this against the emitting side insrc/simulation/worldState.ts, which usesidMap.regionIds.get(cohortSeed.regionKey)/…get(puSeed.regionKey), and against region construction at line 253 (idMap.regionIds.get(regionSeed.key)) — the two lookups resolve the same seed key to the sameRegionId, so the sides genuinely agree rather than agreeing by construction from one source. The body is explicit thatCohortState/ProductionUnitStatecarry noregionIdyet and that the seed is the canonical source available today; the Issue's Scope anticipates exactly that ("or the equivalent resolved field once #427/#448 repair the live state shape"). Met. - Relocation fails on an unchanged aggregate. Two negative controls, cohort and ProductionUnit. I did not take the sensitivity claim on trust: I neutralized
serializeRegionto return the unlocated segment unconditionally in my working tree and re-ran the file — exactly those two tests failed and the other 25 passed, then I reverted. The controls are sensitive to the region segment and to nothing else. Met. - Baseline still reconciles. The 25 pre-existing tests in the file, including the baseline scenario reconciliation, pass unchanged in my run. Met.
- The ledger does not overclaim.
REQ-CONFIG-004staysPARTIAL; the new evidence names the two proving tests, the sensitivity probe, and the three siblings still open (#451, #452, #455) with the reason the requirement is not yet provable at full typed-identity granularity. The row names revision255d741for the suite result — that is the code commit, andaaef0d7differs from it only in the two ledger files, so the evidence names the revision it actually covers. Met. - Gates green. As measured above. Met.
Claimed requirement ID is implemented, not merely mentioned: the REQ-CONFIG-004 location half is the code change, and the row's PARTIAL status is what the code supports.
3. The diff is confined to the declared scope
Six files, every one named in Changed artifacts: src/simulation/genesisReconciliation.ts, src/domain/genesisLedger.ts, src/simulation/worldState.ts, src/simulation/genesisReconciliation.test.ts, docs/spec/implementation_status.csv, docs/spec/IMPLEMENTATION_STATUS.md. All four non-ledger paths are reconciliation/identity repair, which is what the Issue's Scope allows. No Non-goal is touched: no economic quantity, phase order, price formula, generic WorldState inventory layer, ClanState goods, or specification-mirror edit. No .github/workflows/**, AGENTS.md or docs/zendev/** path appears, so no policy-widening change is in play and this is not a human-decision class.
4. No invariant and no test was weakened
The test file is +110/−0 — three tests added, none removed, skipped, or loosened. The suite count rises 584 → 587. No it.skip, no narrowed assertion. The change strengthens an identity invariant rather than relaxing one: reconciliation now distinguishes cases it previously could not, and the expectedGoodsByOwnerRegionGoodId/actualGoodsByOwnerRegionGoodId split preserves the orphan-key sweep in both directions.
5. No secret, credential, or personal data
policy-guard is green, and my own read of the full diff found no token, credential or local machine path. The one removed line was a placeholder region lookup, not a secret.
6. The handoff record is complete
All nine required elements are present, and the evidence separates what was measured from what was assumed rather than asserting uniformly. Two things in particular earn that reading: the not_run row for the CI-only checks is honest about residual risk instead of promoted to passed, and the Highest-risk area names the State public-inventory decision as the contestable one — including the sharp observation that because NO_REGION is applied identically on both sides, a wrong answer there would keep reconciliation green while proving less than it appears to.
I agree with that framing and record my own reading of it: this is not a behavior change. On master the placeholder searched geography for a region whose key equalled the state key, which never matched, so regionId was already undefined at runtime and cast through as any. Making regionId?: RegionId optional makes the type honest about what the code already did, and State goods are reconciled at exactly the granularity they were before — STATE:x:NO_REGION:good is the same partition as the old STATE:x:good. So this pull request neither adds nor removes State-side coverage; whether a State's public inventory ought to be region-bound is a scenario-schema question, correctly left outside this Issue. The deliberate exclusion of the ProductionUnit inventory-bucket half is likewise declared and tracked as #455 rather than quietly scoped in.
Two observations recorded for a later run, neither of which blocks:
- If a cohort's or unit's
regionKeyfailed to resolve, both sides would degrade to the unlocated segment together and still reconcile. That is pre-existing — the emitting side already resolves throughidMap.regionIds.get(...)!— and is not introduced here. UNLOCATED_STOCK_KEYis a literal"NO_REGION"in the same colon-delimited namespace as serialized IDs, which is the delimiter convention the file already uses throughout.
Gates I did not re-derive
policy-guard decides policy/product path mixing, credential shapes, and whether every changed path is named in the handoff. I read its result rather than re-deriving it, per section 2. Reading the diff for everything else is mine, and I did.
Merging now via the protected path with --squash --delete-branch.
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>
…ucket (#473) * REQ-CONFIG-004: key ProductionUnit genesis goods by typed inventory bucket 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> * ledger: record REQ-CONFIG-004 as IMPLEMENTED via PR #473 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> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Closes #449
Achieved outcome
Genesis reconciliation now treats a good's location as part of its canonical stock identity.
GOOD_ENDOWMENTis keyed by owner + region + good on both sides of the comparison instead of owner + good, so moving the same owner's same good from one region to another no longer passes reconciliation on an unchanged aggregate quantity. The actual-stock projections derive location from the owning entity's own region rather than from an aggregate container that happens to hold the good.Tested revision
aaef0d791c0d4eb61b65e5636bb61e7fb28b713a— every check below was re-run at this exact revision after the ledger commit landed, so none of the evidence is stale.Changed artifacts
src/simulation/genesisReconciliation.ts— goods keyed byowner:region:good. AddsserializeRegion()and theNO_REGIONsegment. Builds a region-key →RegionIdlookup from the region registry and resolves cohort location fromCohortSeed.regionKeyand ProductionUnit location fromProductionUnitSeed.regionKey. The three ProductionUnit inventory loops, which differed only in the seed field they read, collapse into one localaddPuGoodshelper.src/domain/genesisLedger.ts—GOOD_ENDOWMENT.regionIdbecomes optional, matching the canonicalregionId?: RegionIdin Handoff/03 section 20, with a doc comment stating when it is absent.src/simulation/worldState.ts— removes the placeholder that fabricated a region for a State's public inventory. It searchedgeographyfor a region whose key equalled the state key, so it always resolved toundefinedand was cast throughas any; State public inventory now records no region at all.src/simulation/genesisReconciliation.test.ts— three tests added (one positive identity assertion, two negative controls).docs/spec/implementation_status.csv,docs/spec/IMPLEMENTATION_STATUS.md—REQ-CONFIG-004row updated (regenerated, not hand-edited).Acceptance criteria
GOOD_ENDOWMENTreconciliation includes canonical location identity for region-bound actor stocks in addition to owner and good. The expected projection keys${owner}:${region}:${good}; every actual projection matches. Handoff/03 section 20 is explicit that reconciliation compares owner + region + good rather than an owner-wide aggregate.regionKey, resolved through the region registry to the sameRegionIdworld genesis recorded.CohortState/ProductionUnitStatecarry noregionIdfield today (Issue Add live WorldState wallet/inventory state and implement MarketSettlement.executeAllocation(world, ctx, allocation) #427 is where the live state shape gains one), so the entity's seed is the canonical source available now. Asserted by "records cohort and ProductionUnit good endowments at the owning entity's own region".REQ-CONFIG-004ledger row does not claim location-sensitive proof until the focused negative control exists and passes. The row is updated in this PR and staysPARTIAL; the evidence cell names the two proving tests and the sensitivity probe.Checks
npm ciaaef0d7npm run typechecktsc --noEmit, no diagnosticsnpm testnpm run builddist/canonical.jsemitteddotnet restoredotnet build --configuration Releasedotnet test --configuration Releasepython scripts/implementation_status.py --checkpython scripts/status_lint.py --repo drevendev/trade_simulation --self 467Outcome is exactly one of
passed,failed,not_run,unavailable.Not checked
policy-guard,scope-guard,mergeabilityandmachine-pr-guardrun in CI only and were not_run locally. Residual risk:scope-guardcompares this body against the diff, so a path named imprecisely here fails the check. The Changed artifacts list is copied fromgit diff --name-only origin/master...HEADataaef0d7.docs/presentation code.Assumptions and unknowns
regionIdand reconciles owner-bound. Grounds: a State controls many regions and the canonical record declaresregionId?optional, so there is no non-arbitrary region to name. This is not a behavior change — the previous code'sregionIdwas alreadyundefinedat runtime — but it does make the type honest, and a reviewer who holds that public inventory is region-bound would want it seeded per region instead, which is a scenario-schema change well outside this Issue.baselineScenariothose namespaces (region:*vs state keys) do not overlap. It also returned a seed key rather than a typedRegionId.REQ-CONFIG-004's remaining siblings (REQ-CONFIG-004: resource genesis reconciliation ignores typed region/good identity #451, REQ-CONFIG-004: population genesis reconciliation drops typed region identity #452, REQ-CONFIG-004: preserve ProductionUnit GOOD_ENDOWMENT inventory-bucket identity #455) will want a shared identity-key helper. Three key builders now follow the same owner/region/good shape; I did not extract one, because REQ-CONFIG-004: resource genesis reconciliation ignores typed region/good identity #451 and REQ-CONFIG-004: population genesis reconciliation drops typed region identity #452 may key differently and a premature abstraction would be harder to undo than to add.Highest-risk area for review
The State public-inventory decision in
src/simulation/worldState.tsand theNO_REGIONsegment it implies. If a State's public inventory should in fact be region-bound, every State goods stock is currently reconciled at the wrong granularity — and because the segment is applied identically on both sides, reconciliation would stay green while proving less than it appears to. The two negative controls do not cover this case: they exercise cohort and ProductionUnit stocks, which are unambiguously region-bound.Remaining gate
No mandatory work remains in this unit.
REQ-CONFIG-004staysPARTIALby design — #451, #452 and #455 are open and tracked, and the ledger evidence names them.