Skip to content

Fold seven entity-drift lineages into their economy/aggregate siblings - #344

Merged
MaxGhenis merged 6 commits into
codex/thesis-ledger-factsfrom
feat/catalog-entity-fold-d353
Oct 12, 2026
Merged

MaxGhenis merged 6 commits into
codex/thesis-ledger-factsfrom
feat/catalog-entity-fold-d353

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Implements Max's ruling d353 (2026-09-27): seven series whose hand-recorded May 2026 rows carry a bespoke entity, while every resolver print carries economy/aggregate, fold into their economy/aggregate lineage. Companion PRs: ThesisInstitute/thesis#342 (merged: stages the fold seed in the resolver's catalog regeneration, accepts the new catalog key) and ThesisInstitute/thesis#346 (re-stamps three docket pins and adds their BLS API templates; merges after this PR and the next resolver run).

The defect

Catalog identity is (concept, geography, entity). Alias inheritance never crosses an entity slice, and the curated-merge recipe restores the split on regeneration (reproduced 2026-09-22). So seven series held two observed lineages each (the "entity-drift families" in docs/catalog-curation-backlog.md). In Thesis, a docket entry that declares a source binding must find its source id in exactly one candidate lineage, which held back BLS API templates for the unemployment rate and CPI headline and core.

The fix

scripts/build_series_catalog.py reads a curated seed, ledger/seeds/entity_folds.json (chronicle_entity_folds_v1). Each fold names a concept, geography, absorbed entity, survivor entity, absorbed_names (the absorbed row's concept and aliases) and a note.

  • Before identities are grouped, current rows of the fold's concept and geography that carry the absorbed entity and are spelled by one of the absorbed names are read (as copies) as carrying the survivor entity. The observation file is never changed; the immutable prefix is untouched.
  • The absorbed row's names join the survivor's aliases; its binding retires with the --remint-note; the survivor keeps its UUID. The catalog commits to the seed as entity_folds_sha256 (absent without a seed; generator version stays 3).
  • Every seed edit (new, retargeted or undone fold) is an identity change that needs --allow-remint --remint-note. Deleting the seed under a catalog that declares it is refused.
  • Undo: mark a fold "undone": true. The absorbed identity revives with its original UUID, spellings it had absorbed earlier rejoin it, and exactly its declared names move back off the survivor. Deleting a fold entry is refused while git HEAD holds it.
  • Refusals: absorbed_names must equal the absorbed row's names when the fold is made; a name already held in the survivor's slice is refused at fold time; while a fold stands the survivor must hold every absorbed name alone; a fold must name an identity the registry has bound and a survivor with a committed observed row; a folded row's own entity is still validated. A fold matching no current row is inert.

Regenerated artifacts

Before (d7e45bc) After
Catalog series 228 221
Observed series 184 177
Sum of observation_count 233 233
Registry lines 276 283 (seven retired, append-only)
Mints, supersedes 0, 0
stripped_segments, suspect_segments unchanged

Survivors keep their UUIDs: db2f3857, 70ca2ecf, e5e402d5, 1d271205, 8c999902, 9668ecdb, dbfd5050. Retired: 8dbbd54f, 3e796803, d85c3c7f, e8e6e43e, d0212a1e, 978252a9, d5634fc4. Replaying the fold from d7e45bc's catalog and registry with the committed seed reproduces both files byte for byte. Marking all seven folds undone reproduces the pre-fold catalog exactly, apart from the seed and registry digests.

Invariants (stated and tested)

  • Conservation: the observed series' observation_count sums to current_assertion_rows before and after any fold (Hypothesis property over any split of one series across two entities).
  • No fold mints or supersedes; survivors keep their UUIDs; each absorbed binding retires exactly once.
  • Determinism: regeneration with the same inputs is byte-identical.
  • Exact reversibility: marking a fold undone restores the pre-fold catalog apart from the two digests.
  • The observation journal is never written.
  • Appends after a fold (new prints under either entity, superseding corrections of versioned absorbed rows) regenerate with the registry unchanged.

Also in this PR

Review

Independent GPT-6.1 Sol reviews, rounds 3 to 7 (round 1 and 2 reviewed the proposal). Rounds 3 to 5 requested changes, each answered in a commit: legacy-correction claim narrowed, retarget ceremony, exact undo, entity validation, undo scoped to the explicit marker, absorbed names declared. Round 6 approved at 38716ee with minors; 0e1dc63 answers them and the round-7 delta review approved it. Reports and responses: ~/chief-of-staff/state/chronicle-lineage/reviews/ on Max's machine.

Locally at 0e1dc63: 198 tests pass, ruff 0.14.10 clean, --check green, doctests pass.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 12, 2026 09:51
Max's ruling d353 (2026-09-27): the hand-recorded May 2026 rows of seven
series carry a different entity from every resolver print of the same
series, so the catalog kept two lineages for each. Alias inheritance never
crosses an entity slice, and the curated-merge recipe restores the split on
regeneration (reproduced 2026-09-22).

- build_series_catalog.py reads a curated seed, ledger/seeds/entity_folds.json
  (chronicle_entity_folds_v1). Before identities are grouped, current rows of
  a fold's concept and geography carrying the absorbed entity are read as
  carrying the survivor entity; the observation file is never changed. The
  absorbed row's names join the survivor's aliases, its binding retires with
  the --remint-note, and the catalog commits to the seed as
  entity_folds_sha256 (absent without a seed).
- Once a committed catalog declares the digest, regenerating without the seed
  is a hard error. A fold that matches no current row is inert; a fold must
  name an absorbed identity the registry has bound and a survivor the
  committed catalog holds.
- The default seed path is seeds/entity_folds.json beside the catalog, so the
  Thesis resolver's staged regeneration and temporary test repos resolve it
  without new flags.
- Regenerated with --allow-remint: 228 -> 221 series, seven retire lines, no
  mint, no supersede; observation_count still sums to the 233 current rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Behaviour: two drift lineages fold into one, the survivor keeps its UUID,
  the absorbed binding retires with the note, the catalog commits to the
  seed, and regeneration is byte-identical. A first fold is refused without
  --allow-remint and pending under --check.
- Ratchet: deleting the seed under a catalog that declares it is a hard
  error. Emptying a fold revives the absorbed UUID. A fold matching no
  current row is inert. Alias-spelled absorbed rows fold too.
- Refusals: closed seed schema (twelve cases), strict JSON, an absorbed
  identity the registry never bound, a survivor with no committed row.
- Property (Hypothesis, any split of one series across two entities,
  overlapping months allowed): observations are conserved, nothing is minted
  or superseded, exactly the absorbed UUID retires, the survivor spans every
  period, regeneration is byte-identical, and an emptied fold revives exactly
  the absorbed UUID.
- docs/catalog-curation-backlog.md: the seven entity-drift families move from
  blocked to folded, with the survivors, the retired bindings, the two
  unfolded sibling pairs and how to undo a fold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 12, 2026 12:37
Answers the independent review of a803f08.

- Any change to the entity-fold seed is an identity change and needs
  --allow-remint --remint-note, even when it plans no registry event:
  retargeting a fold's survivor moved observations between two existing
  lineages with no ceremony (three-entity regression added).
- Undoing a fold now restores earlier same-dimension spelling merges. A row
  that matches nothing in its own slice rejoins a retired identity of its own
  entity whose names live on another slice's row, and those names move back
  off that row. Undoing all seven folds on the committed artifacts reproduces
  the pre-fold catalog exactly apart from the seed and registry digests;
  before this, the us.frb and us.census spellings revived as separate
  identities. The property test and an alias-bearing regression assert the
  exact round trip.
- A folded row's own entity is validated before the fold replaces it.
- The strict-JSON seed test checks duplicate keys, NaN and Infinity
  separately.
- The docstring says what the fold guarantees about corrections: an inert
  fold never fails. Superseding a pre-versioning row is refused by the
  builder whatever the folds say (a pre-existing limitation #306 addresses);
  a test shows a correction superseding a versioned absorbed row, with either
  entity, regenerates with the registry unchanged. A second test appends new
  prints of a folded series under both entities with the registry unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers the independent review of 9c9313b.

- The rule that lets a row rejoin a retired identity through names held in
  another entity slice now applies only to folds marked "undone": true. At
  9c9313b it applied on every run, so an ordinary append of a new spelling
  could revive an unrelated retired identity without the ceremony (the
  review's append-only replay; now a regression test that fails on the old
  generator and passes here).
- Folds are undone by the marker, never by deletion: removing an entry that
  git HEAD still holds is refused, because the entry is the record the
  builder needs to hand absorbed spellings back.
- Documented limits: a curated alias no current row spells stays on the
  survivor after an undo (regression added), and the seed ratchet compares
  with the committed catalog header, which is trusted like every generated
  artifact.
- Marking all seven committed folds undone still reproduces the pre-fold
  catalog exactly apart from the seed and registry digests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers the independent review of d3d23ff.

- Each fold now declares absorbed_names, the absorbed row's concept and
  aliases, checked for equality against that row while it is committed.
  Folding matches only those spellings; at d3d23ff the matcher used every
  survivor alias, so a new absorbed-entity row spelled by a survivor-only
  alias was silently captured (regression added). Replaying the fold from the
  pre-fold state with the new seed reproduces the committed catalog and
  registry byte for byte; an incomplete absorbed_names is refused.
- Undo moves exactly the declared names back, so curated aliases no row
  spells now round-trip too (the regression asserts the exact pre-fold
  catalog).
- The seed comment and the missing-seed error say to mark a fold undone, not
  to remove it; the docstring sentence that lost words is whole again; the
  round-4 regression retires its lineage by an appended superseding
  correction instead of rewriting the journal.
- Regenerated with the ceremony for the new seed digest; the registry is
  unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers the minors of the approving review of 38716ee.

- At fold time, an absorbed name already held by a row in the survivor's
  slice is refused: shared ownership made the undo inexact.
- While a fold stands, the survivor must hold every absorbed name alone; a
  hand edit that drops one from the survivor or gives it to another series is
  refused instead of routing folded rows there.
- The docstring's field list names absorbed_names and the undone marker, and
  the property test's description says the fold is marked undone.

The committed catalog, a fresh fold from d7e45bc and a full marked undo all
reproduce as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit to ThesisInstitute/thesis that referenced this pull request Oct 12, 2026
…alog

#344's seed now declares each fold's absorbed names, which changes the
catalog's entity_folds_sha256 and nothing else. The fixture is its catalog at
0e1dc63b byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 12, 2026 19:37
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merging at 0e1dc63 under Max's ruling d353 (2026-09-27: economy/aggregate survives, all seven entity-drift pairs, generator fold).

  • CI: all required checks pass at this head (Append gate, Trusted base append gate, Arch checks; Arch bundle drift also passes).
  • Independent review (GPT-6.1 Sol, subfleet run --task review --tier hard): rounds 3 to 5 requested changes, each answered in a commit; round 6 approved at 38716ee with minors; 0e1dc63 answers them and the round-7 delta review approved it (no blocker or major).
  • Verified locally: 198 tests, ruff 0.14.10, --check green; a fresh fold from d7e45bc reproduces the committed catalog and registry byte for byte; marking all seven folds undone reproduces the pre-fold catalog apart from the two digests.
  • Next: the first Thesis resolver run after this merge walks the site's ledger pin; then Give the three folded BLS series their templates and re-stamped pins ThesisInstitute/thesis#346 (re-stamped pins, BLS API templates) is refreshed, re-tested and merged.

@MaxGhenis
MaxGhenis merged commit 65822f4 into codex/thesis-ledger-facts Oct 12, 2026
8 checks passed
MaxGhenis added a commit that referenced this pull request Oct 12, 2026
…period-invariant

Conflicts: build_catalog keeps both plan keys (entity_folds_applied from
#344, strips from this branch); test_committed_catalog_is_current_and_valid
keeps the reviewed-strip audit in place of #344's one-time re-pin of the
exact spelling list. The committed catalog now declares
entity_folds_sha256, so _committed_build, the ABS CPI regression and the
next-period test pass bsc.ENTITY_FOLDS, and the independent replay resolves
a row an active fold absorbs (fold geography and absorbed entity, concept
among the declared absorbed_names, read from the seed) at the survivor
entity. The folds change no strip: the reviewed fixture is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis deleted the feat/catalog-entity-fold-d353 branch October 16, 2026 18:05
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.

1 participant