Series catalog: canonical registry for the thesis fact ledger - #128
Series catalog: canonical registry for the thesis fact ledger#128MaxGhenis wants to merge 8 commits into
Conversation
One row per series family mined from official_observations.jsonl (period
tokens stripped to {P}; release vintages and concept spellings never
merged mechanically), seeded with docket-only rows for Thesis docket
series not yet observed. UUIDs mint once and persist across regeneration,
keyed by concept. Regenerate with scripts/build_series_catalog.py;
--check verifies currency.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Responds to the 2026-08-01 adversarial review (all six findings): - identity key now (concept, geography, entity) per the fact-identity ADR (FNS state error rates split into per-state rows; Eurostat flash/final separate); unit/cadence conflicts are hard errors, never modal picks - token grammar covers every live spelling the review found (underscore dates, abbreviated months, week_ending, month ranges, after_* rate states) plus a semantic pass deriving tokens from each row's own period; surviving year-bearing segments are flagged in suspect_segments (now 0) - UUIDs inherit by identity key, then by unique concept/alias match, so upstream renames and hand merges keep identity; curated aliases persist across regeneration; ambiguous matches and UUID collisions are hard errors; --check validates UUID syntax/version/uniqueness - the docket seed is committed (ledger/seeds/) and digest-bound in the header, so bare --check covers the full input set; docket rows without a declared country carry null geography rather than a fabricated one - regression tests from the review's token table + rename/merge/collision cases; CI runs the seeded --check and the test file Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An observed spelling that inherits identity through a curated alias or a unique prior concept/alias match now keeps the prior row's canonical concept — curation owns naming; observed spellings fold into aliases. Buckets landing on one canonical identity merge; inheriting two different prior UUIDs is a hard error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
An adversarial sol review of the first commit returned BLOCK with six findings; the two follow-ups respond to all of them. Review highlights and dispositions:
Regression tests ( 🤖 Generated with Claude Code |
Focused re-review — series-catalog v2Reviewed range: The seed, current period spellings, FNS geography split, BoE merge, M3 normalization, observed-row unit/cadence rejection, and ordinary stale-catalog CI check all improved. Those passes do not offset the identity failures below. Ranked findings1. [CRITICAL] The commits wholesale remint the registry, and
|
| Catalog identity | Raw/seed evidence | Result |
|---|---|---|
BoE Bank Rate (ledger/series_catalog.json:1619-1650) |
raw ledger/official_observations.jsonl:39-40 |
Correct GB/government/bank-rate identity; both spellings merge, count 2. |
Census M3 orders (ledger/series_catalog.json:1805-1835) |
raw :157; seed ledger/seeds/thesis_docket_series.json:546-568 |
Correct US/economy identity; dated concept and docket seed heal to one observed row. |
Census M3 shipments (ledger/series_catalog.json:1838-1868) |
raw :158; seed :571-592 |
Correct US/economy identity; one observed row. |
FNS national (ledger/series_catalog.json:2845-2873) |
raw ledger/official_observations.jsonl:6,103 |
Correct US-country/household identity; FY2024+FY2025, count 2. |
FNS California (ledger/series_catalog.json:2995-3024) |
raw :54 |
Exact state ID/name and household entity. |
FNS District of Columbia (ledger/series_catalog.json:3115-3144) |
raw :58 |
Exact state-level DC ID/name and household entity. |
FNS Guam (ledger/series_catalog.json:4405-4434) |
raw :61 |
Faithfully retains the raw state-level Guam ID/entity. |
Eurostat May final (ledger/series_catalog.json:2312-2341) |
raw :33 |
Correct EA/household/HICP-all-items identity. |
Eurostat June flash (ledger/series_catalog.json:2344-2374) |
raw :134 |
Correct EA21/economy identity; no longer collapsed with May final. |
Old DOL initial claims (ledger/series_catalog.json:2181-2210) |
raw :18 |
Faithful US/ui_initial_claimant/month metadata. |
Standard weekly initial claims (ledger/series_catalog.json:5520-5551) |
raw :105-106,144,148,162 |
Correct US/ui_claimant/week-ending identity, count 5. |
Second old initial-claims spelling (ledger/series_catalog.json:5552-5584) |
raw :44 |
Period token strips, but row remains separate despite aliasing the first old-DOL concept. |
FNS now splits correctly: 55 observations become 54 geographic identities — one national identity with two periods plus 53 state-level jurisdiction identities. The raw and catalog geography/entity sets agree exactly.
The initial-claims three-row split reflects inconsistent raw metadata rather than three cleanly distinct economic concepts:
- raw row 18:
dol.eta..., entity roleui_initial_claimant, period typemonth; - raw row 44:
us.dol..., the sameui_initial_claimant/monthdimensions, and an alias back to the first concept; - raw rows 105-106, 144, 148, 162:
us.dol..., roleui_claimant, properweek_endingcadence.
The year-bearing token defect is fixed, and future observations matching each normalized bucket will not mint a UUID per week. But the existing semantic duplication is not healed: rows 1 and 2 still have separate UUIDs even though one aliases the other's canonical concept.
Six prior dispositions
| Prior issue | Re-review result |
|---|---|
| 1. Untracked seed / bare check | Partial pass. Seed is tracked, hashed, and bare check uses it; a byte change fails. Missing seed plus regenerated reduced catalog still passes. |
| 2. Period spellings | Qualified pass. All live spellings normalize; BoE and M3 heal and suspects are zero. Initial claims remains three rows, and false-positive stripping is still possible/unflagged. |
| 3. Rename/curation/collision | Fail. The commits remint every UUID; auto aliases can merge/flip concepts; geography can move; parsed-equivalent UUID collisions pass; current alias-linked duplicates persist. |
| 4. Concept-only geography collapse | Partial pass. Current FNS and Eurostat rows are correctly split, but fallback ignores dimensions and geography vintage is absent from the key. |
| 5. Modal unit/cadence | Pass. _modal is gone and synthetic observation unit and cadence conflicts both hard-fail through _sole (scripts/build_series_catalog.py:257-266,402-403). |
| 6. Provenance / no fabricated default geography | Pass narrowly. Both digests match exact bytes; seed-only undeclared countries remain null; UUID state is correctly described as catalog state rather than input derivation. |
CI and validation
The CI step is wired correctly and unconditional for pushes and pull requests: it runs bare --check and the focused tests with no error suppression (.github/workflows/ci.yml:37-40). An ordinary stale derived field, one-byte seed change, observation change, or missing seed against the current 201-row catalog exits 1 and fails the step.
Its boundary is material: because the catalog under check supplies UUID/alias/canonical state, valid UUID remints and persisted alias changes are considered current. Both the 6ab4fbe and HEAD UUID-disjoint catalogs pass the HEAD checker. Missing seed plus a consistently regenerated 155-row catalog also passes. Therefore CI is a derived-data freshness check, not an identity-continuity or required-seed check.
Validation record:
python3 scripts/build_series_catalog.py --check PASS (201)
pytest tests/test_build_series_catalog.py -q PASS (34)
python3 -m doctest scripts/build_series_catalog.py PASS
ruff check script + focused tests PASS
git diff --check 9b4329e..HEAD PASS
one-byte seed change vs current catalog FAIL as stale (correct)
missing seed vs current catalog FAIL as stale (correct)
missing seed + regenerated 155-row catalog INCORRECT PASS
uppercase/hyphenless/braced duplicate UUID INCORRECT PASS
HEAD checker against 6ab4fbe UUID catalog INCORRECT PASS (201)
current FNS split PASS (54 identities / 55 observations)
current UUID syntax/version/parsed uniqueness PASS (201)
final worktree CLEAN
Merge gate
Do not merge until, at minimum:
- The surviving UUID for every pre-existing/merged identity is explicitly curated and the catalog is rebuilt without wholesale reminting; continuity must be checked against the prior committed registry, not only against itself.
- Alias fallback is scoped to compatible identity dimensions (with an explicit docket-placeholder enrichment rule), and derived source concepts are not treated as curated synonyms without provenance or review.
- UUIDs are required to use canonical text and uniqueness is keyed by parsed UUID value.
- Geography boundary vintage participates in identity and required seed absence is a hard error.
- Period normalization either reserves date-shaped segments by contract or flags mismatches/false-positive candidates.
Final recommendation: BLOCK.
Answers the second adversarial review (BLOCK) point by point: 1. Identity continuity is now checked against a prior committed registry, not the catalog itself: ledger/series_uuid_registry.jsonl is an append-only minting ledger (one line per binding; supersedes chains per identity). The builder inherits UUIDs from the registry, --check proves catalog/registry agreement plus registry append-only vs git HEAD, and CI proves append-only vs the PR base. All 201 committed UUIDs are preserved (bootstrap = the 2859ecb catalog's bindings, 201/201 carried forward). A write that would change or drop an existing identity's UUID refuses without --allow-remint --remint-note, which records a supersede event. 2. Concept/alias inheritance is scoped to the same (geography, entity); the only exception is docket-placeholder enrichment (docket-only row, entity unknown, geography absent or equal on level/id). 3. measure.source_concept values are provenance (new per-row source_concepts field), never aliases and never identity. The two docket links that machine labels carried (BLS AHE private, StatCan EI beneficiaries) are now explicit curated aliases. 4. UUID uniqueness keys on the parsed 128-bit value and canonical lowercase text is required everywhere (catalog and registry). 5. A missing docket seed is a hard error in every mode. 6. Geography boundary vintage joins the identity key per the fact-identity ADR. 7. Date-shaped segments strip only when they denote the row's own period (direct spelling or overlapping calendar window); disjoint or impossible tokens stay in the identity and are flagged as suspects. Also fixes a latent v2 bug where MONTHS_ABBREV carried 13 entries, shifting derived abbreviations for October-December. Regenerated artifact: 201 series (155 observed, 46 docket-only), suspects=0, ambiguous_aliases=0, minted=0, superseded=0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disposition — series-catalog v3 (
|
Self-found follow-up to v3: deleting the committed catalog and rebuilding from the registry alone loses curated naming/alias memory, so a previously renamed identity would re-key away from its registry binding and fresh-mint a UUID while the old binding goes dormant — a silent remint that survives catalog/registry agreement AND the append-only checks (the registry only grows). Not reachable today (canonical == derived concept for all 201 rows) but opens after the first rename curation. The builder now refuses to rebuild when the prior catalog has no rows while the registry holds bindings, absent --allow-remint --remint-note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
v3 addendum ( |
…it, push gate The first run of the third adversarial review crashed before its REPORT but surfaced four reproducible findings; all fixed: 1. Dimension keys were lossy pipe-joins, so crafted geography/entity values could collide across fields (level "a|b" + id "c" vs "a" + "b|c") and None was indistinguishable from "None". Keys are now JSON-encoded (injective, sortable). No identity moved; the only catalog byte effect besides the new header field is the two Eurostat rows swapping into natural EA < EA21 order. 2. The bare-registry rebuild gate only fired on a zero-row catalog: a PARTIAL deletion still silently re-minted renamed identities. The registry now tracks binding liveness with explicit retire/revive events: dropping a row appends a noted retire line (gated by --allow-remint), a returning identity revives its ORIGINAL uuid automatically, and --check enforces the reverse agreement direction — every live binding's uuid must appear on some catalog row. 3. Window-overlap stripping was silent; a statute/cohort/edition label spelling a window that overlaps the row's own period is mechanically indistinguishable from a period label, so every overlap-based strip is now reported in the catalog's overlap_stripped_segments audit list (currently exactly the eight live segments). 4. The CI registry append-only gate ran only on pull requests; direct pushes now verify against github.event.before, and an unfetchable before-sha (history rewrite) fails loudly instead of skipping. 66 tests; ruff, doctest, --check, byte-idempotence all green; uuids/identities unchanged 201/201. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Third-review attempt 1 crashed mid-run (harness stall on a full-suite pytest invocation) after surfacing four reproducible findings; all four are fixed in
Also correcting a disposition count: the one-time migration removed 82 machine alias instances, not 85 as written. State at |
Third adversarial review (sol, ultra) — against
|
…full strip audit All nine findings from the third review (BLOCK, CRITICAL) addressed: 1. [CRITICAL] Ordinary mints can no longer reuse ANY bound UUID (parsed 128-bit ownership map at registry load + build-time refusal), so re-keyed identities cannot swap or inherit foreign UUIDs; forged registry mint lines sharing a UUID are rejected at load. The one sanctioned reuse is a retire + succeeds event pair (docket placeholder enrichment), and live bindings' UUIDs must be unique. Liveness/agreement are now IDENTITY-AWARE bijections: each live binding needs a catalog row at exactly its identity with exactly its UUID, both directions. 2. [HIGH] The registry-introduction commit cannot be gated by append-only (no prior registry exists), so the reviewed 201-binding identity->uuid map is pinned as a sha256 anchor in the test suite; changing any binding must edit the anchor in the same diff. New- branch pushes (all-zero before-sha) now fall back to verifying against the canonical branch's registry instead of skipping. 3. [HIGH] Docket name claiming is dimension-scoped: an entry declaring a country is only claimed by rows in that country, duplicate docket series ids are hard errors. 4. [HIGH] Placeholder enrichment refuses a declared, conflicting geography vintage. 5. [HIGH] The strip audit now covers EVERY stripped spelling (stripped_segments replaces overlap_stripped_segments; 27 live entries), and malformed periods (month 13, impossible week dates) are hard errors instead of token factories. 6. [MEDIUM] A remint of a returning retired identity stages revive THEN supersede, and stage() revalidates the entire registry before any bytes are written. 7. [MEDIUM] Registry lines parse with duplicate-member rejection. 8. [MEDIUM] Registry bytes must be LF-only and newline-terminated; .gitattributes pins eol for the catalog, registry, and seed. 9. [LOW] Notes must contain alphanumeric content; no-op supersedes (uuid == supersedes) are rejected. 81 tests; ruff, doctest, --check, byte-idempotence green; identity map unchanged (201/201, minted=0, superseded=0; registry still exactly the 201 bootstrap mints). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The review-context-*.md files are per-session adversarial-review briefing materials (prior review texts + instructions), not repo content; an over-broad git add swept them into the previous commit. Ignore the pattern so it cannot recur. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disposition — third review answered (
|
Adds
ledger/series_catalog.json— the canonical series registry the observation file never had — plus its generator.Why: ThesisInstitute/thesis#112. Thesis's docket (75 series) has been acting as a second identity system; bill metrics could only wire to those 75. The catalog makes the ledger the single identity home: one row per series family with a UUID minted exactly once, so downstream consumers (docket, bill mappers, permalinks) reference series by UUID/concept instead of minting their own.
What's in a row:
uuid, canonicalconcept,family_patterns(period tokens{P}-stripped from observed concepts:fy2026,2026-05,may_2026,q1_2026/2026_q1,week_…), modalunit/cadence/geography/entity,aliases(observed concept spellings + source labels likeUNRATE),rid_patterns(source_record_id families, for grading linkage), first/last observed period, count,status(observed|docket-only).Deliberate non-moves: release vintages (
first_print,third_estimate) and concept-spelling drift (abs.cpi.all_groups.yoyvsabs.cpi_indicator.allgroups.yoy) are never merged mechanically — those are curation judgments done by hand-merging rows (survivor keeps its UUID, absorbed spellings become aliases).Determinism: regeneration is byte-identical for the same inputs; UUIDs persist keyed by concept;
--checkmode verifies currency (CI can adopt it);observations_sha256binds the catalog to the exact observation bytes it was built from. Families differing only in{P}placement merge (same series, two period formattings) — the 168 rows' 107 raw concepts collapse to 93 observed series + 48 docket-only = 141.Consumer side (lands in thesis after this):
pin_ledger.pypins the catalog alongside the observations; every docket entry gainsledger: {uuid, concept}; a build-time containment test enforces docket ⊆ catalog; the bill mapper matches hints against catalog ∪ docket.🤖 Generated with Claude Code