Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/catalog-curation-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ were closed on 2026-08-09 by chronicle#153:
replay rejects the hand-forged plan before any write reaches disk, and
the builder end-to-end refuses the stale catalog, leaving both
artifacts untouched.
- 2026-10-16: #153 added the live-binding check but kept the first-owner
conjunct (`uuid_owner[row_uuid] == prior_own_key`) alongside it. Because
the first owner never moves, that conjunct refused every second
enrichment hop. A placeholder that had already been enriched once (a
geography-free placeholder enriched to geography-only when its docket
entry learned a country) could not take its first print.
`usaspending.dod.prime_award_obligations` (a2649fd9) hit this in Thesis
run 38962977031. The conjunct is gone (#360). The contract is now the
live binding plus the grammar's hand-over rules: the placeholder's key
is live and bound to the row's UUID, it is the same concept as the
identity it hands over to, and it has not handed over before. A key that
handed over and later returned as the successor of another hop is live
again but stays consumed. When the binding holds but either hand-over
rule fails, the builder refuses at plan time and names the cause. The
cross-concept refusal names its remedy (call the docket entry by the
placeholder's concept). The consumed refusal prescribes none, because
the right curation depends on the lineage. Deleting the placeholder's
catalog row and rerunning with --allow-remint resolves the simple case,
but it loses curated aliases held only on that row, and an observed
identity that is itself consumed reclaims a fresh UUID instead of
reviving. Tests pin the chain, the stale-row refusals, both hand-over
refusals, the cross-concept remedy and the simple consumed curation. A
property test checks that the builder plans an enrichment exactly when
the registry grammar admits the retire + succeeds pair.

## Country geography on Colorado-scoped concepts, and one raw series id (2026-09-04)

Expand Down
69 changes: 55 additions & 14 deletions scripts/build_series_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2129,14 +2129,21 @@ def _plan_enrichment(
entity: dict | None,
row_uuid: str,
) -> bool:
"""Plan a retire + succeeds pair when ``prior`` is a genuine
placeholder whose live binding owns ``row_uuid``; True if planned.
"""Plan a retire + succeeds pair when ``prior`` is a docket
placeholder that holds ``row_uuid`` live and may hand it over to
``canon_key``; True if planned.

A placeholder that holds ``row_uuid`` live but may not hand it over
(``canon_key`` is another concept, or the placeholder already
handed over once) raises a diagnostic naming that cause. Every
caller would refuse such a row anyway, since the UUID is
registered, but with a UUID-reuse or remint message that misstates
why.
"""
prior_own_key = UuidRegistry.entry_key(prior)
owner_key = registry.uuid_owner.get(uuid_module.UUID(row_uuid).int)
prior_geo = prior.get("geography") or None
geo = geography or {}
enrichment_shaped = (
placeholder_shaped = (
prior.get("status") == "docket-only"
and prior.get("entity") is None
and (
Expand All @@ -2149,23 +2156,56 @@ def _plan_enrichment(
)
)
)
# uuid_owner records FIRST ownership and never moves, so alone it
# would accept a stale catalog row still carrying a UUID its key
# has since superseded away from. The contract is about the LIVE
# binding: demand it be row_uuid itself. Staging replay would
# reject such a plan anyway; this fails at plan time instead.
# The contract is the LIVE binding plus the grammar's hand-over
# rules. The row's own key must be live and bound to row_uuid right
# now: that refuses a stale row still carrying a UUID its key has
# since superseded away from, and a key that was retired, handed
# over or reclaimed. Staging replay would reject each of these
# plans anyway; this fails at plan time instead. Historical
# ownership (uuid_owner) is not part of the contract: it records
# FIRST ownership and never moves, so a placeholder that was itself
# the successor of an earlier enrichment never owns its UUID and
# could never be enriched again.
if not (
enrichment_shaped
and owner_key == prior_own_key
placeholder_shaped
and registry.is_live(prior_own_key)
and registry.binding(prior_own_key) == row_uuid
):
return False
# Lineage never crosses concepts. Observed buckets always take the
# prior's concept; a docket entry matched through a curated alias
# can name another one, and the grammar refuses that succeeds.
if prior.get("concept") != canon_key[0]:
raise SystemExit(
f"identity {canon_key} matches docket placeholder "
f"{prior_own_key} (uuid {row_uuid}) only through the "
f"curated alias {canon_key[0]!r}; lineage never crosses "
"concepts, so the placeholder cannot be enriched into it — "
f"name the docket entry {prior['concept']!r}, the "
"placeholder's own concept"
)
# A key hands over at most once. One that handed over and later
# returned as the successor of another hop is live again, but it
# stays consumed and the grammar refuses it as a predecessor. How
# to resolve that depends on the lineage (curated aliases, whether
# the observed identity has a binding of its own), so the
# diagnostic names the cause and prescribes no recipe.
if prior_own_key in registry.consumed:
raise SystemExit(
f"identity {canon_key} would take uuid {row_uuid} from docket "
f"placeholder {prior_own_key}, which already handed a lineage "
"over once and remains consumed; a placeholder hands over at "
"most once, so it cannot hand this one over — this lineage "
"needs explicit curation"
)
# Docket-placeholder enrichment: the binding MOVES to the observed
# identity via an explicit retire + succeeds pair. UUID continuity
# is preserved, so no ceremony flag is needed — this holds for the
# first enrichment and for a re-enrichment after a withdraw/reclaim
# cycle (the grammar accepts succeeds on a retired successor key).
# first enrichment, for a chain of them (a geography-free
# placeholder enriched to a geography-only one, then to the
# observed identity), and for a re-enrichment after a
# withdraw/reclaim cycle (the grammar accepts succeeds on a retired
# successor key).
plan["enrich_retires"].append(
dict(
_registry_event(
Expand Down Expand Up @@ -2285,7 +2325,8 @@ def resolve_uuid(
return row_uuid
raise SystemExit(
f"identity {canon_key} would mint uuid {row_uuid}, which "
f"the registry already binds to {owner_key} — UUID reuse "
f"the registry already knows (first bound to {owner_key}) "
"— UUID reuse "
"requires an explicit ceremony (merge via curated alias + "
"--allow-remint, or placeholder enrichment)"
)
Expand Down
Loading
Loading