Skip to content

REQ-CONFIG-004: key good genesis reconciliation by region identity - #467

Merged
zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-449-config-004-good-region-identity
Sep 14, 2026
Merged

zendev-acceptor[bot] merged 2 commits into
masterfrom
claude/issue-449-config-004-good-region-identity

Conversation

@zendev-author

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

Copy link
Copy Markdown
Contributor

Closes #449

Achieved outcome

Genesis reconciliation now treats a good's location as part of its canonical stock identity. GOOD_ENDOWMENT is 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 by owner:region:good. Adds serializeRegion() and the NO_REGION segment. Builds a region-key → RegionId lookup from the region registry and resolves cohort location from CohortSeed.regionKey and ProductionUnit location from ProductionUnitSeed.regionKey. The three ProductionUnit inventory loops, which differed only in the seed field they read, collapse into one local addPuGoods helper.
  • src/domain/genesisLedger.ts — GOOD_ENDOWMENT.regionId becomes optional, matching the canonical regionId?: RegionId in 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 searched geography for a region whose key equalled the state key, so it always resolved to undefined and was cast through as 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-004 row updated (regenerated, not hand-edited).

Acceptance criteria

  • 1. GOOD_ENDOWMENT reconciliation 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.
  • 2. Cohort and ProductionUnit actual-stock projections use their canonical region identity. Location comes from the owning entity's own regionKey, resolved through the region registry to the same RegionId world genesis recorded. CohortState/ProductionUnitState carry no regionId field 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".
  • 3. An equal-and-opposite region relocation for the same owner+good fails reconciliation even though aggregate owner+good quantity is unchanged. Two negative controls, one for a cohort and one for a ProductionUnit, split an opening stock across two regions. The cohort test additionally asserts the owner+good aggregate is unchanged to 12 decimal places, so the failure can only come from the location key. Both were confirmed sensitive: with the region key segment neutralized, exactly those two tests fail and the other 25 in the file pass.
  • 4. Ordinary baseline genesis still reconciles within the configured tolerance. "baseline-multistate-v1 scenario reconciles successfully" and the other 24 pre-existing tests in the file pass unchanged.
  • 5. The REQ-CONFIG-004 ledger row does not claim location-sensitive proof until the focused negative control exists and passes. The row is updated in this PR and stays PARTIAL; the evidence cell names the two proving tests and the sensitivity probe.
  • 6. Required TypeScript and retained .NET gates remain green. See below.

Checks

Check Outcome Evidence
npm ci passed clean install at aaef0d7
npm run typecheck passed tsc --noEmit, no diagnostics
npm test passed 587 tests in 40 files; 584 before this branch, +3
npm run build passed dist/canonical.js emitted
dotnet restore passed both projects restored
dotnet build --configuration Release passed 0 warnings, 0 errors
dotnet test --configuration Release passed 45/45, REQ-MIGRATION-003 maintained
python scripts/implementation_status.py --check passed generated table matches the ledger
python scripts/status_lint.py --repo drevendev/trade_simulation --self 467 passed 29 rows agree with merged pull requests

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

Not checked

  • policy-guard, scope-guard, mergeability and machine-pr-guard run in CI only and were not_run locally. Residual risk: scope-guard compares this body against the diff, so a path named imprecisely here fails the check. The Changed artifacts list is copied from git diff --name-only origin/master...HEAD at aaef0d7.
  • No Pages/visual check was run. Residual risk: none identified — nothing in this diff reaches docs/ presentation code.
  • The ProductionUnit inventory-bucket half is deliberately not fixed here. The three buckets still aggregate together, so an INPUT↔OUTPUT↔INVESTMENT relocation within one unit and region still passes. That is Issue REQ-CONFIG-004: preserve ProductionUnit GOOD_ENDOWMENT inventory-bucket identity #455 (spec change HANDOFF-REPAIR-017), left out to keep this unit of work bounded.

Assumptions and unknowns

Highest-risk area for review

The State public-inventory decision in src/simulation/worldState.ts and the NO_REGION segment 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-004 stays PARTIAL by design — #451, #452 and #455 are open and tracked, and the ledger evidence names them.

github-actions Bot and others added 2 commits September 14, 2026 20:17
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>
REQ-CONFIG-004 stays PARTIAL: this PR closes the GOOD_ENDOWMENT region
identity half, while #451, #452 and #455 remain open.

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

zendev-author Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

CI evidence at the tested revision

The CI-only checks listed as not_run locally in the PR body have now run in the forge. Run 34892205332 at head aaef0d791c0d4eb61b65e5636bb61e7fb28b713a:

Check Outcome
policy-guard (incl. scope-guard) passed
build-and-test passed
typescript passed
mergeability passed

For the record, so no reviewer reads it as an open failure: the policy-guard failure still visible on this pull request belongs to the superseded head 255d741, which was pushed before the ledger row existed and while the body was still a placeholder. Both of those are exactly what policy-guard and scope-guard check, so that failure was correct at the time and is resolved at the current head, not suppressed.

No check was promoted. Every outcome above is measured at aaef0d7.

@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 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 ci passed; npm run typecheck passed (no diagnostics); npm test passed — 587 tests in 40 files; npm run build passed.
  • dotnet restore passed; dotnet build --configuration Release --no-restore passed; dotnet test --configuration Release --no-build passed — 45/45. REQ-MIGRATION-003 is maintained: the canonical change leaves the legacy build green.
  • python3 scripts/implementation_status.py --check passed — "IMPLEMENTATION_STATUS.md matches 29 ledger row(s)", so the generated table was regenerated rather than hand-edited.

Criterion by criterion:

  1. Location in the reconciliation key. src/simulation/genesisReconciliation.ts keys both sides by ${owner}:${region}:${good} — expected at the GOOD_ENDOWMENT branch, 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.
  2. Actual location derived from the owning entity. regionIdByRegionKey is built from worldState.regions as region.seed.key → RegionId; cohorts resolve through cohort.seed.regionKey, ProductionUnits through pu.seed.regionKey. I checked this against the emitting side in src/simulation/worldState.ts, which uses idMap.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 same RegionId, so the sides genuinely agree rather than agreeing by construction from one source. The body is explicit that CohortState/ProductionUnitState carry no regionId yet 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.
  3. Relocation fails on an unchanged aggregate. Two negative controls, cohort and ProductionUnit. I did not take the sensitivity claim on trust: I neutralized serializeRegion to 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.
  4. Baseline still reconciles. The 25 pre-existing tests in the file, including the baseline scenario reconciliation, pass unchanged in my run. Met.
  5. The ledger does not overclaim. REQ-CONFIG-004 stays PARTIAL; 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 revision 255d741 for the suite result — that is the code commit, and aaef0d7 differs from it only in the two ledger files, so the evidence names the revision it actually covers. Met.
  6. 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 regionKey failed to resolve, both sides would degrade to the unlocated segment together and still reconcile. That is pre-existing — the emitting side already resolves through idMap.regionIds.get(...)! — and is not introduced here.
  • UNLOCATED_STOCK_KEY is 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.

@zendev-acceptor
zendev-acceptor Bot merged commit 69d84b9 into master Sep 14, 2026
6 of 9 checks passed
@zendev-acceptor
zendev-acceptor Bot deleted the claude/issue-449-config-004-good-region-identity branch September 14, 2026 20:25
zendev-author Bot pushed a commit that referenced this pull request Sep 14, 2026
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-acceptor Bot pushed a commit that referenced this pull request Sep 14, 2026
…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>
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: good genesis reconciliation drops region identity

0 participants