Epoch prep: accept chronicle-era fact domains and schema ids; emit unchanged (#143, mechanism 1) - #228
Merged
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 068e054145aca02942896cf41fd89dc1aedc16aa)
(cherry picked from commit 273235a8d87d0d9f1cffc322473369ef82aa7814)
(cherry picked from commit f0ec75179ce72a22c9298c042ba4fd62da1e5576)
(cherry picked from commit 052aaed09265edcc9ba61e540389b64ba4188a7c)
(cherry picked from commit 3dabeeac5866f44596c8da7a0ca4a497c555af46)
(cherry picked from commit 906d35cc194d836a85edac20aa6f5384a8acf918)
(cherry picked from commit ccc10346d00e0642a3bac613a7af559657691d82)
…t copying, report malformed lineage keys as a contract issue, state the build-id prefix form
…r schema is pinned, refuse non-string lineage keys inside the epoch pair, report a malformed build id instead of raising
… canonicalize consumer-artifact rows to the emit epoch, accept epoch strings - build_consumer_artifact and build_chronicle_db validate their emit epoch and explicit build id before rmtree/unlink, so a refused replace=True call leaves the existing artifact or database byte-identical. - build_consumer_artifact writes every row canonicalized to the emit epoch; a mixed-epoch input now produces the same bytes as Ledger-named input and every emitted row validates against the pinned v1 schema bytes alone. - _require_ledger_emit coerces through Epoch() so the string "ledger" is accepted everywhere and "chronicle"/unknown strings raise ValueError at the boundary instead of AttributeError. - README documents the dual-epoch acceptance contract. Suite: 804 passed, 1 skipped.
MaxGhenis
added a commit
that referenced
this pull request
Sep 4, 2026
This was referenced Sep 4, 2026
MaxGhenis
added a commit
that referenced
this pull request
Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Mechanism 1, step 1 of #143 (Max's migration spec, 2026-08-20): dual-domain acceptance, emit unchanged. Fact identity migrates by epoch, never in place. This PR makes every Chronicle reader and validator accept both the ledger-era identifiers and their chronicle-era successors, while everything Chronicle emits stays ledger-named until a later, consumer-gated cutover flips one default.
chronicle/epoch.py— the single registry of frozen ledger identifiers and their successors: 15 hash domains (ledger.aggregate_fact.v2→chronicle.aggregate_fact.v3, …,ledger.fact.v1→chronicle.fact.v2,ledger.source_cell.v1→chronicle.source_cell.v2, …) and the schema ids (ledger.bundle.v1→chronicle.bundle.v2,ledger.consumer_fact.v1→chronicle.consumer_fact.v2,ledger.relational.v1→chronicle.relational.v2,ledger.source_package.v1→chronicle.source_package.v2,policyengine_ledger.consumer_artifact.v2→policyengine_chronicle.consumer_artifact.v3,policyengine_ledger.approved_agents.v1→policyengine_chronicle.approved_agents.v2, …). Canonical payloads and digest suffixes are identical across epochs; only the domain or schema string changes.EMIT_EPOCH = Epoch.LEDGERis the one default a future cutover changes.consumer_contract.py,core.py), artifact and schema pins (policyengine_chronicle/consumer.py,schema.py), bundle ingestion (bundle.py), the relational load path (database.py), source-package readers and lineage (source_package.py,sources/cells.py,sources/rows.py,sources/offline_fetch.py), suite build (suite.py), and governance validation. Key verification recomputes under the epoch declared by the key's own prefix; a key or schema id outside both accepted forms is rejected with an error naming both.sources/admin_packages.pyand the Statbel builder, are pinned to the registry's ledger default). Chronicle-era output exists only behind an explicit epoch argument, to exercise acceptance before the gated flip.Consumer side: PolicyEngine/microcosm#849 accepts the same pairs (including
policyengine_ledger.consumer_artifact.v2today and.v3after the flip). The emit flip is a separate PR gated on microcosm and the Thesis-side validators (brier append gate,pin_ledger.py/verify_custody.py) accepting both epochs.Chronicle Governance
ledger-contract-maintainer(identity, provenance, and consumer contracts). Itsallowed_pathspredate this work and cover onlychronicle/core.py,chronicle/consumer_contract.py,policyengine_chronicle/**and two test files; the readers listed above andchronicle/epoch.pyare outside that list and are disclosed here rather than the manifest being widened silently (same gap flagged on Register raw microdata releases as source artifacts: licence/access fields, hash-only registration, first UK registrations (#221) #227; follow-up to extend the globs).783 passed, 1 skipped;ruff checkclean; changed Python files formatted; wheel build and clean-venv import smoke passed; a GitNexus impact audit drove additional validator and lineage fixes. Goldens byte-identical (asserted).ledger-contract: PASS — identity changes preserve provenance; digests unchanged; no Microcosm responsibility moves into Chronicle.ledger-boundary: PASS — no reconciliation, aging, imputation, activation, or solver construction; no microdata content.Tests
New and extended:
test_chronicle_core.py(+304),test_chronicle_consumer_contract.py,test_chronicle_consumer.py,test_policyengine_chronicle_schema.py,test_chronicle_bundle.py,test_chronicle_database.py,test_chronicle_suite.py,test_chronicle_source_package.py,test_chronicle_source_cells.py,test_chronicle_offline_fetch.py,test_chronicle_artifacts.py,test_belgium_targets.py,test_chronicle_governance.py— chronicle-era rows and artifacts accepted end to end, mixed-epoch artifacts accepted, unknown domains rejected naming both forms, and the property that a chronicle-era key hashes the same canonical payload as its ledger-era key.Authored by a Sol lane (no network); pushed and opened from the dispatching session.
🤖 Generated with Claude Code