Skip to content

Let an enriched docket placeholder be enriched again - #360

Merged
MaxGhenis merged 6 commits into
codex/thesis-ledger-factsfrom
fix/placeholder-second-enrichment
Oct 18, 2026
Merged

MaxGhenis merged 6 commits into
codex/thesis-ledger-factsfrom
fix/placeholder-second-enrichment

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The fold #363 has merged (5a6ef17d), and this branch is current with it (f9e0f893). At 10bd12e2 the full catalog suite passes 235/235 and --check is current. Review rounds 1–3 and their fixes are logged in the PR comments; round 4 (a Sol delta review of 10bd12e2) is running.

Why

Thesis resolve run 38962977031 refused usaspending.dod.prime_award_obligations.fy2026.registered_query_snapshot:

identity (…, ["country","0100000US","current"], ["economy","aggregate"]) would mint uuid a2649fd9-…, which the registry already binds to (…, [null,null,null], [null,null])

The registry history for a2649fd9:

  • line 196 mints it with geography null and entity null;
  • line 249 retires that;
  • line 250 is a succeeds-mint to {country, 0100000US, vintage null}. That hop happened when the docket entry gained country: US.

The catalog row is still docket-only. The FY2026 snapshot window closes 2026-10-22.

_plan_enrichment required registry.uuid_owner[row_uuid] == prior_own_key. uuid_owner records the first owner and never moves, so every second enrichment hop was refused, and the row fell through to the UUID-reuse refusal.

What

scripts/build_series_catalog.py, _plan_enrichment:

  • Dropped: the uuid_owner conjunct and its local.
  • Added: prior_own_key not in registry.consumed (review round 1, blocking). A key hands over at most once. A placeholder that handed over and later returned as a later hop's successor is live and bound correctly, but it is still consumed, and the grammar refuses it as a predecessor. Without this check the builder planned a pair that staging rejected; with it, the builder refuses at plan time, as base did.
  • Added: prior["concept"] == canon_key[0] (review round 1, LOW, pre-existing). A docket entry matched to a placeholder through a curated alias planned a cross-concept succeeds that died in staging. It now refuses at plan time. Observed buckets always take the prior's concept, so only that docket path changes.
  • The contract is now stated in the comment. The row must be enrichment-shaped, of the same concept, live, bound to row_uuid, and not consumed. These are exactly the retire + succeeds preconditions of UuidRegistry.
  • The mint-path refusal now says "already knows (first bound to …)". The old "already binds to " was false whenever the first owner had handed over.

tests/test_build_series_catalog.py: 15 new tests. docs/catalog-curation-backlog.md: a dated bullet. The committed ledger is unchanged.

Invariants (all executed)

  1. Chain continuity (Hypothesis, through main).

    • Every chain the builder can reach: geography-free → country, geography-free → observed, country → observed, and all three. Any country, vintage ∈ {current, 2020, null}, two entities.
    • Each hop writes exactly one retired + succeeds pair handing over the key it retired. No --allow-remint.
    • One UUID per lineage, and exactly one live binding holds it, at the row's identity. Bystanders are untouched.
    • --check passes and a rerun is byte-identical.
  2. The owner conjunct only refused chains. In any admitted history, a live binding whose key is not the UUID's first owner was set by a succeeds event.

  3. Plan-time guard ⇔ registry grammar (Hypothesis differential). Take any admitted history, including keys that are live but consumed, plus any docket-only row and any observation.

    • The builder plans retire + succeeds exactly when UuidRegistry accepts that pair.
    • Everything it plans stages and agrees with its catalog.
    • A refusal is a plan-time diagnostic.
    • A row never takes a UUID another key holds.
  4. Stale rows stay refused by the live-binding conditions. Six cases after a chain, each refused with artifacts byte-untouched:

    • superseded;
    • retired;
    • handed over;
    • reclaimed;
    • chain root;
    • another series' UUID.

    This also covers Enrichment demands the live binding; stale-UUID staging test #153's existing tests.

  5. Regressions:

    • the production replay (registry lines 196/249/250 → exactly the pair thesis#362 admits);
    • the chain through main;
    • review round 1's lifecycle through main (B live and consumed → refused, with and without --allow-remint);
    • the cross-concept docket alias.

Intended non-violation: the second hop's retire note still reads "docket placeholder enriched by first observed identity". thesis#362 pins that string.

Verification

  • pytest tests/test_build_series_catalog.py -q at this head: 233 pass. The 2 failures are the base's own (dd929cba fails them alone; Fold the 2026-10-17 DoD first prints: review fy2026 strips, move the anchor #363 fixes them).
  • Simulated merge onto Fold the 2026-10-17 DoD first prints: review fy2026 strips, move the anchor #363 (9a2bb393): 235 passed.
  • --check: current, 221 series. ruff: clean.
  • Counterfactuals (5 Hypothesis seeds each):
    • owner conjunct restored: the regressions, the chain property and the differential fail;
    • consumed check removed: the lifecycle regression and the differential fail;
    • concept check removed: the alias test fails.
    • The stale-row tests pass in every variant.
  • Production replay on the ledger at dd929cba plus the synthetic FY2026 dod row:
    • base generator: reproduces the refusal;
    • this head: writes retire(US/vintage-null, a2649fd9) + succeeds(US/current economy, a2649fd9), byte-identical to the diagnosed tail;
    • --check passes, and only the dod row changes.

Review

  • Round 1 (GPT-6.1 Sol, adb19015): CHANGES. One blocking finding (live-but-consumed predecessor) and one LOW (cross-concept docket alias). Both are fixed in ac8652da, with deterministic regressions and generator coverage for the class.
  • Round 2 (independent Opus 5.5, ac8652da): MERGE. Its non-blocking L1, L3 and N1 are fixed in 5be9ff8a.
  • Round 3 (Sol, 5be9ff8a): CHANGES on the consumed-refusal wording. Fixed in 10bd12e2, which states only the cause.
  • Round 4 (Sol, 10bd12e2): running.

axiom: n/a: catalog-builder identity bookkeeping, no policy encoding

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 16, 2026 18:58
_plan_enrichment required uuid_owner[row_uuid] == the row's own key.
uuid_owner records FIRST ownership and never moves, so a placeholder
that was itself the successor of an earlier enrichment (geography-free
-> geography-only) never owns its UUID, and its observation fell to the
mint path and was refused as UUID reuse. This blocked
usaspending.dod.prime_award_obligations (a2649fd9) from taking its
FY2026 observation (Thesis run 38962977031).

Drop that conjunct. The live-binding conditions that remain
(enrichment-shaped, the row's key live, and bound to row_uuid now) are
the contract and still refuse stale, superseded, retired, handed-over
and reclaimed rows. The mint-path refusal now says "first bound to",
since uuid_owner is not the current binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regressions:
- the production state (registry lines 196/249/250 for a2649fd9): the
  geography-only placeholder takes its first print as exactly the
  retire + succeeds pair Thesis's append guard admits;
- the chain through main (docket entry learns a country, then the first
  print lands): no ceremony, one UUID, --check after each hop;
- six stale rows after a chain (superseded, retired, handed over,
  reclaimed, chain root, another series' UUID) still refused at plan time
  by the live-binding conditions alone, artifacts byte-untouched.

Properties (Hypothesis):
- every builder-reachable chain, any country, vintage and entity, keeps
  one UUID, writes one retire + succeeds pair per hop, passes --check
  and is byte-idempotent;
- in any grammar-admitted history, a live binding its key did not first
  own was established by succeeds (why the owner conjunct only ever
  refused chains);
- differential: the builder plans an enrichment exactly when
  UuidRegistry accepts the pair, everything it plans stages and agrees
  with its catalog, and refusals are plan-time diagnostics.

Counterfactually verified: with the owner conjunct restored, both
regressions, the chain property and the differential property fail
(the differential on 5/5 seeds, shrinking to the dod shape); the
stale-row tests pass either way.

The mint-path diagnostic now says "already knows (first bound to …)",
so test_mint_never_reuses_a_bound_uuid matches that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #360 found a state the live binding misses. A
placeholder B hands its lineage to C, then (docket loses and regains its
country) takes a fresh UUID back from a coarser placeholder A. B is live
and bound to that UUID but still consumed, and the grammar never lets a
predecessor hand over twice. Without the dropped owner conjunct the
builder planned retire(B) + C succeeds B, which staging then rejected.
_plan_enrichment now also requires prior_own_key not in
registry.consumed, so it refuses at plan time again (as base did).

The review's LOW finding, pre-existing: a docket entry matched to a
placeholder through a curated alias keyed the docket row under the alias,
so the planned succeeds crossed concepts and died in staging. Enrichment
now also requires the prior's concept to equal the canonical one.
Observed buckets always take the prior's concept, so only that docket
path changes, from a staging failure to a plan-time refusal.

Tests:
- the review's lifecycle through main (seven events, B live and
  consumed), refused with and without --allow-remint, artifacts
  byte-untouched;
- the cross-concept docket alias, refused at plan time;
- the registry generator gains a "return" move (mint a coarser
  placeholder and hop it onto a retired key that already handed over),
  and retired hop targets and consumed rows are weighted up, so the
  differential property reaches live-but-consumed keys.

Counterfactuals: without the consumed check, the lifecycle regression
and the differential property fail on 5/5 seeds; without the concept
check, the alias regression fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 18, 2026 14:09
Round-2 review of #360 (LOW L1, L3; NIT N1):

- L1 (new in this PR): a docket entry matching a placeholder through a
  curated alias was refused with the UUID-reuse message, whose remedy
  (curated alias + --allow-remint) cannot help.
- L3 (pre-existing): a placeholder that already handed over once was
  refused with "superseding UUIDs must be new", blaming a remint nobody
  made.

When the placeholder holds the row's UUID live but either hand-over rule
fails, _plan_enrichment now raises a diagnostic naming the cause: lineage
never crosses concepts (name the docket entry by the placeholder's own
concept), or a placeholder hands over at most once (delete its catalog
row and rerun with --allow-remint). Both callers already refused every
such row, since the UUID is registered, so only the message changes.

Tests exercise each suggested curation: renaming the docket entry
enriches cleanly, and deleting the consumed placeholder's row then
--allow-remint revives the observed identity's own UUID and retires the
placeholder, with --check passing. _event_kind now reads planned events
too (absent markers there are None, not missing).

N1: the backlog bullet and docstring said the live binding alone is the
contract; they now state the hand-over rules too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in dd929cb and 86da023 (append lane) and the #363 fold, so the branch is current with its strict-protected base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delta review of 5be9ff8 (CHANGES; F1 blocking, F2 and F3 non-blocking):
the refusal for a placeholder that already handed over prescribed a
recipe (delete its catalog row, rerun with --allow-remint) and promised
outcomes that do not always hold:

- F1: if the returning observation is spelled by a curated alias held
  only on the placeholder row, deleting the row loses that mapping; the
  rerun mints a separate alias-named identity and leaves the placeholder
  live.
- F2: an observed identity that is itself consumed reclaims a fresh UUID
  rather than reviving its retired one.
- F3: a returned placeholder can later supersede its UUID while staying
  consumed, so "holds this uuid as the successor of a later enrichment"
  can be false.

The message now states only the cause: the placeholder already handed a
lineage over once and remains consumed, so it cannot hand this one over
and the lineage needs explicit curation. Refusal behaviour is unchanged
(the review found zero raise-versus-plan differences over 478,632
old/new input pairs). The lifecycle test keeps demonstrating one
curation that works for its simple lineage, labelled as such, and the
backlog bullet records the caveats instead of a recipe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Review log (durable copies in ~/chief-of-staff/state/chronicle-lineage/dod-second-enrichment/)

Round 1: GPT-6.1 Sol, at adb19015. Verdict: CHANGES.

  • Blocking: a placeholder that is live but already handed over (consumed) passed the guard.
  • LOW: a docket-alias match planned a cross-concept succeeds.
  • Fixed in ac8652da: both guard conditions added, with a deterministic regression for each and generator coverage of the consumed class.

Round 2: independent Opus 5.5, at ac8652da. Codex lanes were on operator hold, so it fell back per policy. Verdict: MERGE.

  • Derivation: every grammar precondition of retire and succeeds, derived from the guard and its callers.
  • Exhaustive differential at both call sites: 17,094 states, 205,128 builds, 0 guard divergences. Base 8c4a0f86 shows 74, which is the chain bug this PR fixes. With the consumed check removed there are 62 plans that staging rejects.
  • Counterfactuals: reproduce on 5/5 seeds. The differential reaches the decisive class about 8 times as often as before the generator steering.
  • Non-blocking:
    • L1: the alias refusal gave a misleading remedy. I introduced this in ac8652da.
    • L2: a pre-existing, fail-safe invalid revive in the remint fallback. It is outside this diff and has been proposed as a separate task.
    • L3: the consumed refusal blamed a supersede.
    • N1: stale doc text.
  • Fixed in 5be9ff8a: L1, L3 and N1. The concept and consumed cases now get dedicated plan-time diagnostics, and the doc and docstring state the hand-over rules.

Round 3: GPT-6.1 Sol delta review of 5be9ff8a. Verdict: CHANGES.

  • Behaviour: refusal behaviour is identical to before; 478,632 old/new input pairs, 0 raise-versus-plan differences.
  • F1 (blocking): the consumed diagnostic's recipe (delete the placeholder row, then --allow-remint) loses aliases held only on that row.
  • F2: the recipe's promise to revive the retired binding is false when the observed identity is itself consumed; it reclaims instead.
  • F3: the provenance clause can be false after a later supersede.
  • Fixed in 10bd12e2: the message now states only the cause; the backlog bullet records the caveats.

Fold dependency: #363 is merged (5a6ef17d). f9e0f893 merges the new base cleanly, and the full file passes 235/235 at 10bd12e2 with --check current.

Round 4: Sol delta review of 10bd12e2 is running.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Round 4: GPT-6.1 Sol delta review of 10bd12e2. Verdict: MERGE, with no findings left (blocking or not).

  • F1–F3 resolved: the reviewer executed the alias-loss, fresh-reclaim and superseded-consumed scenarios, and each outcome agrees with the revised message and the caveats in the doc.
  • Behaviour unchanged: across 8,204 old/new input pairs, no outcome, catalog or plan differs.
  • Merge reconstruction: matches the tree exactly, with no conflicts.
  • Production DoD replay: writes the expected 659-byte tail exactly.
  • Gates at 10bd12e2: 235 passed, ruff clean, --check current (221 series), ledger diff empty.

Report: ~/chief-of-staff/state/chronicle-lineage/dod-second-enrichment/review-360-r4.md.

@MaxGhenis
MaxGhenis merged commit 3dc5c9f into codex/thesis-ledger-facts Oct 18, 2026
8 checks passed
@MaxGhenis
MaxGhenis deleted the fix/placeholder-second-enrichment branch October 18, 2026 18:54
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