Skip to content

Epoch prep: accept chronicle-era fact domains and schema ids; emit unchanged (#143, mechanism 1) - #228

Merged
MaxGhenis merged 21 commits into
mainfrom
epoch-dual-domain
Sep 4, 2026
Merged

Epoch prep: accept chronicle-era fact domains and schema ids; emit unchanged (#143, mechanism 1)#228
MaxGhenis merged 21 commits into
mainfrom
epoch-dual-domain

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

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.v2chronicle.aggregate_fact.v3, …, ledger.fact.v1chronicle.fact.v2, ledger.source_cell.v1chronicle.source_cell.v2, …) and the schema ids (ledger.bundle.v1chronicle.bundle.v2, ledger.consumer_fact.v1chronicle.consumer_fact.v2, ledger.relational.v1chronicle.relational.v2, ledger.source_package.v1chronicle.source_package.v2, policyengine_ledger.consumer_artifact.v2policyengine_chronicle.consumer_artifact.v3, policyengine_ledger.approved_agents.v1policyengine_chronicle.approved_agents.v2, …). Canonical payloads and digest suffixes are identical across epochs; only the domain or schema string changes. EMIT_EPOCH = Epoch.LEDGER is the one default a future cutover changes.
  • Readers accept both epochs: fact-identity builders and verifiers (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.
  • Emitters unchanged: package scaffolds, relational and build artifacts, consumer artifacts all still emit ledger-era ids by default (the two package generators, sources/admin_packages.py and 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.
  • Nothing else moves: no source values, reference periods, publisher provenance, fixture or golden bytes, frozen schemas, witnessed releases, CLI/env/bucket/Supabase names, or consumer-owned contracts. The retired profile-bearing consumer artifact and resolved-target/target-profile contracts stay retired (no successor ids minted for them).

Consumer side: PolicyEngine/microcosm#849 accepts the same pairs (including policyengine_ledger.consumer_artifact.v2 today and .v3 after 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

  • Approved Chronicle agent role: ledger-contract-maintainer (identity, provenance, and consumer contracts). Its allowed_paths predate this work and cover only chronicle/core.py, chronicle/consumer_contract.py, policyengine_chronicle/** and two test files; the readers listed above and chronicle/epoch.py are 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).
  • Deterministic checks run: full suite 783 passed, 1 skipped; ruff check clean; 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).
  • LLM judge verdicts:
    • 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

MaxGhenis and others added 10 commits September 2, 2026 10:46
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>
MaxGhenis and others added 11 commits September 3, 2026 13:02
(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.
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