Accept chronicle-era Chronicle artifact schema ids and fact-key domains; CHRONICLE_* env names (chronicle#143 consumer side) - #849
Accept chronicle-era Chronicle artifact schema ids and fact-key domains; CHRONICLE_* env names (chronicle#143 consumer side)#849MaxGhenis wants to merge 23 commits into
Conversation
chronicle#143 consumer side: microcosm must accept both ledger-era and chronicle-era Chronicle schema ids and hash domains before Chronicle flips emit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… dual-read Chronicle (formerly Ledger) introduces chronicle-era hash domains and chronicle-named schema ids for newly emitted rows at a declared cutover; ledger-era ids stay valid history forever (PolicyEngine/chronicle#143). Microcosm is a consumer, so it must accept both eras before Chronicle flips emit. microcosm.build.chronicle_epoch is the single place that knows which identity belongs to which era. Epoch detection is structural — the namespace segment of a key's domain — rather than a lookup in a frozen list of domain strings, so a chronicle-era family whose version number the spec has not yet declared still resolves, and no validator hard-codes one epoch. Fact keys stay opaque: Microcosm never recomputes a Chronicle digest, so comparison stays exact string equality and only the 'is this a Chronicle key?' question goes dual-era. The consumer-artifact loader now tests manifest schema_version for membership in both eras instead of equality with one, validates a per-row schema id when a feed carries one (Chronicle-published rows never have), and records the observed id, its epoch, and the feed's fact-key epochs in provenance() so a release manifest witnesses which era actually resolved its targets. microcosm.build.chronicle_env is the dual-read window for the operational env names: CHRONICLE_URL/KEY/API_KEY/EXPORT_KEY preferred, POPULACE_LEDGER_* still honored with one DeprecationWarning per process per name. LEDGER_HMRC_BANDS, LEDGER_ONS_*_BANDS and LEDGER_US_SOURCE_COVERAGE_CONTRACT_COMMIT only look like env vars to a grep — they are data constants, so they take chronicle-era aliases instead. Nothing renames on disk: emitted ids, diagnostic field names, populace_* key namespaces, goldens and fixtures stay frozen at v1 (microcosm#639). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Covers the epoch module itself, the loader under each schema id, a mixed-epoch feed straddling the cutover, mixed-epoch target compilation and a chronicle-era reference pin, and the two producer surfaces: the minted US trade artifact declares the ledger-era id but loads under either, and the congressional-district vintage translation mints byte-identical microcosm.derived_fact keys whether its source rows are ledger-era or chronicle-era — otherwise cutover day would silently re-identify every derived fact a target is pinned to. Also documents the env dual-read in logbook/README.md and adds the towncrier fragment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dual acceptance must widen what loads and narrow nothing. The loader as committed added a per-row schema_version check that rejected any id outside the two chronicle#143 names — a validation main never performed, on a field main never read. Real feeds do not honor that set. The pinned US fiscal-refresh feed consumer_facts_buildn_v9_4.jsonl, which the release path loads through this loader, declares 'arch.consumer_fact.v1' on 37,006 of its first 200,000 rows and 'ledger.consumer_fact.v1' on 399 of them; test_uk_firm_generation builds the same arch-namespace rows. The check therefore failed the build closed on its own pinned input, and PR CI could not see it: the feed is gated data outside PR CI. Verified by loading an arch-namespace row through load_ledger_consumer_artifact — ValueError before, loads after. The id is now carried and reported through fact_schema_versions, which provenance records beside the artifact schema id and the fact-key epochs, so a release manifest still witnesses exactly which producers wrote its rows. Epoch witnessing was also reading only the four key fields targets resolve a fact by. Published rows carry nine, plus two key lists: the captured UK feed fixture has observed_measure_key, source_release_key, source_series_key, universe_constraint_set_key and lineage.source_row_keys as well. Chronicle moves families independently — the spec declares v3 only for the aggregate and semantic families — so a straddling row with a ledger-era aggregate key and a chronicle-era source-release key was witnessed as pure ledger-era. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…scribe The map documented six families and said it recorded what this repo's feeds carry. The captured UK feed fixture carries ten, and the pinned US feed carries eleven: observed_measure, source_release, source_series, universe_constraint_set and source_row were all missing. Nothing consults the map at runtime, which is precisely how such a list rots, so a test now pins it to the fixture. The completed map also shows why detection cannot be a lookup in it: the observed version numbers differ by family, with fact and source_cell at v1 where the rest are at v2. 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>
|
Review (Fable, main): the epoch half is right and lands as specified — structural epoch detection, membership over both manifest ids, per-row ids carried not gated, Microcosm-minted namespaces outside both eras, provenance witnessing the observed era, no on-disk renames. Approving that half as is. One correction before merge, and it is my brief's fault, not the implementation's: Please rename the preferred spellings to |
POPULACE_LEDGER_URL/_KEY/_API_KEY/_EXPORT_KEY are Logbook store credentials (microcosm#632), not Chronicle fact-store ones. Naming the preferred spellings CHRONICLE_* would recreate the exact collision microcosm#632 renamed Ledger away from. Per PR #849 review: rename chronicle_env.py to logbook_env.py, its symbols to LOGBOOK_*/logbook_env*, and every caller (logbook.py, tools/logbook.py, build/__init__.py exports, and the two module-path comments in firm_generation.py/source_coverage.py that named the old module). Legacy POPULACE_LEDGER_* names and the once-per-process DeprecationWarning behavior are unchanged; the warning text now cites microcosm#632 instead of chronicle#143. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The changelog fragment, logbook/README.md, and the branch journal all described CHRONICLE_URL/CHRONICLE_KEY and the env dual-read window as part of the chronicle#143 epoch migration. It is a separate Logbook naming cleanup (microcosm#632) riding along on this branch: split the changelog sentence into its own paragraph, update the README's live-store section to LOGBOOK_URL/LOGBOOK_KEY, and append a dated correction note to the journal recording why and what changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
logbook_env's import in build/__init__.py sorted alphabetically after the rename, and tools/logbook.py's two dual-read imports needed reordering and reformatting to match. 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>
…-*.md only Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The findings turn on which schema ids Chronicle actually emits, so the identities are recorded here with the file and line they were read from on chronicle origin/main rather than restated from the PR body. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…istry
The accepted set was {policyengine_ledger.consumer_artifact.v1,
policyengine_chronicle.consumer_artifact.v2}, which is one generation off at
both ends. Chronicle's main emits policyengine_ledger.consumer_artifact.v2
today (policyengine_chronicle/consumer.py:30, README line 299), so the loader
rejected every artifact Chronicle currently publishes, and the chronicle-era
successor of that id is .v3, not .v2. The v1 spelling is Microcosm's own
minted artifact and stays accepted as the history it is.
Epoch now comes from a declaration keyed by (namespace, family, version)
rather than from the namespace segment of the key. Reading the namespace told
us where a key claimed to come from, not that Chronicle issued it, so
chronicle.source_cell.v3 and chronicle.anything.vN were being witnessed as
Chronicle-issued identity. The ledger-era half of the table is transcribed
from Chronicle's own hash-domain constants; the chronicle-era half is each
one's successor under the rename rule, which puts source_cell at v2 where
aggregate_fact is at v3. An undeclared spelling in a Chronicle namespace is
reported as 'undeclared' in provenance, with the domain named, instead of
being folded into an era nobody declared it for.
The membership predicates are total over JSON. A manifest whose schema_version
is a list or an object raised TypeError out of a frozenset membership test,
bypassing the loader's own unsupported-schema message.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The per-module delenv lists named POPULACE_LEDGER_* only, because they were
written before the LOGBOOK_* spelling existed. A shell exporting the
preferred names — a developer who just ran an export, a runner where the
variables are set for a later job — walked straight past them:
logbook._remote_config() returns ('https://example.invalid', 'x', 'x') under
LOGBOOK_URL/LOGBOOK_KEY alone, and the append path would then POST to it.
The store is append-only, so a row that lands cannot be retracted.
An autouse conftest fixture now clears both generations of every variable in
the dual-read window, taken from the window itself so a variable added there
cannot leave a hole here, plus POPULACE_LOGBOOK_PREV_ROW_DIGEST. The second,
independent guard replaces the Logbook urlopen with one that raises —
on every module currently holding it, since tools/logbook.py binds the name
at import, and on the opener it delegates to, which closes the path for any
importer this does not find. allow_logbook_network restores the real
callables for a test that means it; nothing in the suite asks for it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t alias microcosm.build re-exported the reader function logbook_env under the same name as its module. 'import a.b as c' returns the attribute when the package has one, so 'import microcosm.build.logbook_env as env' handed back the function and every env.LOGBOOK_URL_ENV after it raised AttributeError. The barrel now re-exports only logbook_env_names, which collides with nothing; the function is imported from its own module, as logbook.py and tools/logbook.py already do. CHRONICLE_US_SOURCE_COVERAGE_CONTRACT_COMMIT was added beside the ledger-era pin but never re-exported, so it was importable from source_coverage and not from the us_runtime barrel its sibling reaches — an alias only half the callers could use. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
_ledger_provenance rebuilt the block from the artifact field by field, which is how it came to record facts_sha256, fact_row_count and manifest_sha256 but not schema_epoch, fact_key_epochs or fact_schema_versions. The loader had already worked out which era resolved the targets; the UK run reported only the hashes, so a signed diagnostics file could not say whether a cutover-window feed had straddled the rename. It now delegates to LedgerConsumerArtifact.provenance() and keeps only the manifest sub-block UK-shaped, because that one feeds the run's identity digest and is deliberately narrow. A stand-in without the shared method still records what it carries rather than fabricating the rest. Coverage is a real mixed-epoch artifact through run_uk_calibration: a Chronicle-main manifest id over ledger-era and chronicle-era rows plus one undeclared Chronicle-namespace key, asserted in the build record and in the diagnostics 'build' block. The delegation itself is pinned as a property — every shared field reaches the UK block — rather than as a list that would go stale the next time the shared block grows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
'logbook export --remote --help' named POPULACE_LEDGER_URL alone, so an operator configuring from the help text set up the exact spelling the dual-read window exists to retire, then got a deprecation warning for following the instructions. The preferred LOGBOOK_* names come first and the ledger-era ones are named as the fallback they are, since an operator whose environment predates the rename still has to recognise what is being asked for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The lane journal is history once written, but four of its claims read as current design and are now wrong: the two-id accepted set, the uniform v2-to-v3 domain bump, CHRONICLE_* env names, and structural epoch detection. Each is annotated in place with the correction and its date so the file cannot mislead a later reader, and the changelog fragments now describe the behavior that is actually on the branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both said chronicle#143 declares the v3 spelling for exactly two fact families, which the issue does not: it lists 'dual-hash window or v3 domains' as open options. The import_entry_facts header also named the chronicle-era artifact id as .v2 and did not say which of the ledger-era ids this module emits, which is the distinction the whole finding turned on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both microcosm-build suites are outcome-identical, which is the guarantee the Logbook isolation finding asked for. The two red tests are recorded with the evidence that they reproduce at origin/main, because the earlier lane reported this suite as all green and on a machine holding the pinned feed it is not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Consumer side of the Ledger→Chronicle epoch migration
(PolicyEngine/chronicle#143).
Chronicle will introduce chronicle-era hash domains (
chronicle.aggregate_fact.v3,chronicle.semantic_fact.v3, …) and chronicle-named schema ids(
policyengine_chronicle.consumer_artifact.v2,chronicle.consumer_fact.v2) fornewly emitted rows at a declared cutover; ledger-era ids stay valid history
forever. Dual-domain acceptance has to land in every validator before the emit
flip, so Microcosm accepts both eras starting now. The acceptance half on
Chronicle's side is landing in a parallel lane.
What changed
microcosm.build.chronicle_epoch— one place that knows which identity is which era.Three rules, encoded rather than asserted:
payload, so it never needs to know how the domain string feeds the hash.
Comparison stays exact string equality — correct in both eras. What goes
dual-era is the question "is this a Chronicle fact key?".
namespace segment of
<namespace>.<family>.v<n>:<digest>— not a lookup in afrozen list of domain strings. chronicle#143 declares the
v3spelling onlyfor the aggregate and semantic fact families; the version numbers the other
families will take are not declared yet, and this module deliberately does not
guess them.
chronicle.source_cell.v7:…resolves anyway.namespaces (
microcosm.derived_fact.*,populace_us_trade.*), sit outsideboth eras, and must not move when a source row crosses the cutover.
Only identity strings the spec names explicitly are pinned as literals.
Loading accepts both eras and records which one arrived. The consumer-artifact
manifest's
schema_versionis a membership test over both eras.provenance()now carries
schema_epoch,fact_key_epochs, andfact_schema_versionsalongside the verbatim observed
schema_version, so a release manifest witnesseswhich era actually resolved its targets and flags a mixed-epoch feed instead of
silently reporting one era.
Acceptance widened; nothing narrowed. Only the manifest id is gated. The
per-row
schema_versionis carried and reported, never gated — real feeds stamprows from namespaces belonging to neither era, and gating it would fail the build
closed on its own pinned input. The pinned US fiscal-refresh feed
consumer_facts_buildn_v9_4.jsonldeclaresarch.consumer_fact.v1on 37,006 ofits 37,405 rows and
ledger.consumer_fact.v1on the other 399;test_uk_firm_generation.py:584builds rows in that samearch.*namespace.Epoch witnessing likewise reads every Chronicle key a published row carries
(nine single-key fields plus two lineage key lists in the captured feed fixture),
not just the four identifiers targets resolve by — Chronicle moves families
independently, so a row can straddle the cutover with a ledger-era aggregate key
and a chronicle-era source-release key.
microcosm.build.logbook_env— the Logbook env dual-read window. This is aseparate Logbook naming cleanup (microcosm#632) riding along on this branch, not
part of the chronicle#143 epoch migration above:
POPULACE_LEDGER_URL/_KEY/_API_KEY/_EXPORT_KEYare the Logbook store's own credentials(Supabase
logbookschema,logbook_writer/logbook_exporterroles), notChronicle names — naming the preferred spellings
CHRONICLE_*would haverecreated the exact collision the Logbook rename fixed.
LOGBOOK_*preferred,the ledger-era name honored with one
DeprecationWarningper process per name(a build loop reads these repeatedly; one warning, not a storm). One helper,
one table; adding a variable to the window is one row.
Nothing renames on disk or in artifacts. Emitted schema ids,
populace_*andmicrocosm.*key namespaces, diagnostic field names (ledger_aggregate_fact_key,ledger_member_fact_keys,ledger_fact_pin,ledger_commit), H5 attributes,goldens, and fixtures all stay frozen at v1 (#639). No golden was regenerated.
Audit
I audited every site that compares, parses, or mints a Chronicle fact key or
schema id —
ledger_targets.pyandcongressional_district_vintage.pyread infull, plus a repo-wide sweep for
ledger.<family>.v<n>,policyengine_ledger,consumer_artifact,consumer_fact,aggregate_fact_key,semantic_fact_key,source_cell,dimension_set,concept_alignment,av2:, and everyos.environread — across 8 surfaces with an adversarial verification pass onevery candidate.
Result: exactly three hard-coded epoch literals exist in non-test source, all
in the two files below. Everything else either carries keys opaquely or mints into
a Microcosm-owned namespace. Every other candidate site was refuted on
verification.
ledger_artifact.py:58CONSUMER_ARTIFACT_SCHEMA_VERSION = "policyengine_ledger.consumer_artifact.v1"LEDGER_CONSUMER_ARTIFACT_SCHEMA_VERSION, and it names only what Microcosm emits. Acceptance is the set, not this.ledger_artifact.py:187schema_version != CONSUMER_ARTIFACT_SCHEMA_VERSION→ raiseis_accepted_consumer_artifact_schema_version(...)— membership over both eras; the error names both.ledger_artifact.py:307fact_schema_versions; gating it would rejectarch.consumer_fact.v1, which the pinned release feed stamps on 37,006 rows.ledger_artifact.py:160-163provenance()recordedschema_versiononlyschema_epoch,fact_key_epochs, andfact_schema_versions; the observed id is still recorded verbatim.import_entry_facts.py:164"policyengine_ledger.consumer_artifact.v1"literalwrite_consumer_artifact(…, schema_version=…)(:612) takes the declared id as an argument, membership-checked over both eras; the default is unchanged, so emitted bytes do not move.import_entry_facts.py:165"ledger.consumer_fact.v1"literalimport_entry_facts.py:890populace_us_trade.<kind>.v1:<digest>congressional_district_vintage.py:399, 402, 636, 639microcosm.derived_fact.*.v1/microcosm.semantic_fact.*.v1_translated_fact_key) and the proxy's(source_record_id, geography, value)— never its Chronicle key — so a chronicle-era source mints byte-identical derived keys. Pinned by test; otherwise cutover day would silently re-identify every derived fact a target is pinned to.ledger_targets.py(all 2203 lines)_fact_key/_str_atand compared verbatim;_ledger_fact_indexbuckets a row under all five key slots, so a pin in either epoch resolves. Left alone, covered by new mixed-epoch tests.logbook.py:839, 840, 843os.environ.get("POPULACE_LEDGER_{URL,KEY,API_KEY}")logbook_env(LOGBOOK_{URL,KEY,API_KEY}_ENV)dual-read.logbook.py:110LEDGER_API_KEY_ENV = "POPULACE_LEDGER_API_KEY"= LEGACY_API_KEY_ENV; the name stays exported for callers and messages.tools/logbook.py:260-262os.environ.get("POPULACE_LEDGER_{URL,EXPORT_KEY,API_KEY}")logbook_env(...); the "remote export requires …" error now names both spellings viadescribe_logbook_env.tools/logbook.py:63-64REMOTE_{EXPORT_KEY,API_KEY}_ENVliteralsfirm_generation.py:168-170LEDGER_{ONS_TURNOVER,ONS_EMPLOYMENT,HMRC}_BANDSsource_coverage.py:41LEDGER_US_SOURCE_COVERAGE_CONTRACT_COMMITledger_commitdiagnostic field it feeds is frozen.Two names from the brief that grep resolves differently, recorded so the next
reader does not go looking:
LEDGER_TARGETSis not an environment variable. The only matches are theDIRECT_LEDGER_TARGETS/INDICATOR_LEDGER_TARGETSmodule dicts inus_runtime/fiscal_targets.py— mapping tables, no epoch, untouched.POPULACE_LEDGER_*, not bareLEDGER_*; there areexactly four (
URL,KEY,API_KEY,EXPORT_KEY) and they are the ones thedual-read window covers.
Also verified absent, so nothing needed regenerating: no golden under
packages/*/tests/golden/**contains a Chronicle domain string, and no H5attribute name in the repo embeds
ledger.Tests
New files (both engine-free;
tools/ci_test_groups.py --verifyplaces them inrest/shared-spec, alongsidetest_ledger_artifact.py, and reportsok):test_chronicle_epoch.py(38 cases) — key parsing and epoch resolution forboth eras, chronicle-era families with undeclared version numbers, Microcosm
namespaces resolving to neither era, malformed keys returning
Noneratherthan raising, row/feed epoch reporting across
source_cell_keysandconcept_alignment_key, a synthetic consumer artifact loaded under eachschema id, a mixed-epoch feed whose provenance witnesses both eras, bare
feeds, rejection of an unknown manifest id naming both accepted spellings,
a per-row id outside both eras still loading and being reported verbatim,
every straddling key field witnessed for its own epoch, and an inventory check
that grounds the witnessed field list in the captured feed fixture rather than
in a guess.
test_logbook_env.py(16 cases) — preferred name wins silently, legacyname honored with a warning naming both spellings, the warning fires once per
process across repeated reads, per-name independence, empty-as-unset, and
logbook._remote_config()reading both eras.Added to existing suites:
test_ledger_targets.py— a mixed-epoch feed compiles to targets whose namesand metadata carry each row's own key verbatim, and a reference pinned to a
chronicle-era key resolves.
test_us_congressional_district_vintage.py— chronicle-era source rows mintbyte-identical
microcosm.derived_fact.*keys on both the vintage-translationand state-total-proxy paths.
test_us_trade_facts.py— the minted artifact still declares the ledger-era id,loads under either, and the fact bytes are identical when only the declaration
moves; a bogus declaration is refused.
uv sync --all-packages --extra us·uv run pytest packages/microcosm-build -q·
uv run ruff check .— all green; full output in the lane report. Nopublish, promotion, or release action was taken.
Review fixes (round 2)
Eight findings from the Fable+Sol gate, all addressed. The identity half of
the original PR was wrong about which ids exist, so the correction is
structural rather than cosmetic — everything above this section that
describes a two-id accepted set or namespace-based epoch detection is
superseded here.
The ids were one generation off at both ends
Read from
PolicyEngine/chronicleatorigin/mainthis pass, not restated:policyengine_ledger.consumer_artifact.v2policyengine_chronicle/consumer.py:30, README line 299policyengine_ledger.consumer_artifact.v1import_entry_facts.py:164policyengine_chronicle.consumer_artifact.v3ledger.consumer_fact.v1chronicle/consumer_contract.py:28chronicle.consumer_fact.v2The accepted set was
{policyengine_ledger.consumer_artifact.v1, policyengine_chronicle.consumer_artifact.v2}. That rejects every artifactChronicle publishes right now, and names a
.v2chronicle-era id that willnever exist. All three artifact ids are accepted now, and a test loads a
manifest built field for field from
build_consumer_artifacton chroniclemain.There is no
origin/epoch-dual-domainbranch on chronicle(
git ls-remote --heads origin | grep -i epoch→ empty), so the chronicle-erasuccessors are declared here under the rename rule the spec states — same
family, new namespace, version bumped by one — with the two the consumer
migration pins spelled out. That rule puts
source_cellatledger.source_cell.v1→chronicle.source_cell.v2, a version behind thederived families'
v2→v3.Epoch is now a declaration, not a parse
DECLARED_IDENTITIESinchronicle_epoch.pyis an explicit table of literalidentity strings;
DECLARED_IDENTITY_EPOCHSkeys it by(namespace, family, version)→ epoch. The ledger-era half is transcribedfrom Chronicle's own hash-domain constants (
chronicle/core.py,chronicle/sources/*.py,chronicle/consumer_contract.py), with the file itcame from noted per block.
The previous structural rule read the namespace segment and answered
"chronicle" for
chronicle.<anything>.vN. That reports where a key claimsto be from as though it were issued identity — the opposite of what pinning a
fact key is for.
chronicle.source_cell.v3andchronicle.some_family_nobody_declared.v11are now labelledundeclared:parsed, reported in provenance with the domain named
(
undeclared_fact_key_domains), never counted as an era. The tests thatcertified those spellings as valid are corrected.
Two prose claims went with it. chronicle#143 does not "declare the
v3spelling for the aggregate and semantic fact families" — it lists
"dual-hash window or v3 domains" as open options. That sentence is gone from
the module, the tests, and (annotated in place, dated) the lane journal.
Tests could reach the live Logbook
_remote_config()returns('https://example.invalid', 'x', 'x')underLOGBOOK_URL+LOGBOOK_KEYalone. The per-moduledelenvlists named onlyPOPULACE_LEDGER_*— they predate theLOGBOOK_*spelling — so acredentialed developer or runner shell walked straight past them into the
append path of an append-only store.
packages/microcosm-build/tests/conftest.pynow carries an autouse fixturewith two independent guards: every variable in the dual-read window is
cleared in both generations (read from the window itself, so adding one there
cannot leave a hole here) plus
POPULACE_LOGBOOK_PREV_ROW_DIGEST; and theLogbook
urlopenis replaced with one that raises — on every module holdingit, since
tools/logbook.pybinds the name at import, and on the opener itdelegates to, which closes the path for any importer the sweep does not find.
allow_logbook_networkrestores the real callables for a test that means it.Nothing in the suite asks for it.
Proof, with the polluted environment the finding describes:
The rest
uk_runtime/calibration_run.py::_ledger_provenancerebuilt the block field by field and so recorded the hashes but not
schema_epoch/fact_key_epochs/fact_schema_versions. It delegates toLedgerConsumerArtifact.provenance()now, keeping only the manifestsub-block UK-shaped (it feeds the run's identity digest). Covered by a real
mixed-epoch artifact through
run_uk_calibration, asserted in the buildrecord and the signed diagnostics
buildblock, plus a property test thatevery shared field reaches the UK block rather than a list that would go
stale.
import microcosm.build.logbook_env as envbound the function: thebarrel re-exported it under its own module's name, and
import a.b as cprefers an existing attribute. The barrel now re-exports only
logbook_env_names; regression test included.CHRONICLE_US_SOURCE_COVERAGE_CONTRACT_COMMITnow reaches themicrocosm.build.us_runtimebarrel like its ledger-era sibling.schema_version. A JSON list or object raisedTypeError: unhashable typeout of the frozenset membership test, bypassingthe loader's own message. The predicates are total over JSON; the loader
raises the documented unsupported-schema
ValueError. Parametrized overlist, object, empty list, empty object, int, bool, and null.
tools/logbook.py --remotehelp advertisedPOPULACE_LEDGER_URLalone,so an operator configuring from
--helpset up the spelling the dual-readwindow exists to retire and then got warned for it.
LOGBOOK_*first,ledger-era names given as the fallback they are.
Round-2 verification
The polluted-environment run is outcome-identical to the clean one, which
is the guarantee finding 2 asked for. It exports
LOGBOOK_URL,LOGBOOK_KEY,LOGBOOK_API_KEY,LOGBOOK_EXPORT_KEYand all fourPOPULACE_LEDGER_*names.
The 2 failures are pre-existing and not this PR's. Both are
test_release_target_parity.py::TestRegeneration, and both fail identicallyat this branch's previous head (
1f78847c) and atorigin/main(
47c74225), verified this session in a throwaway worktree of each:They run only where the pinned feed exists —
DEFAULT_FEED_PATHis~/PolicyEngine/_buildh-runtime/inputs/consumer_facts_buildn_v9_4.jsonl,outside the repo — and
_feed_or_skipskips them everywhere else, so they donot run in PR CI. Nothing in this PR touches
ledger_targets.py,fiscal_targets.py,tools/build_us_target_parity_manifest.py, or thattest. Recording it because the earlier lane reported this suite as "all
green", which on a machine holding that feed it is not.
uv run ruff format --check .reports 116 files as reformat candidates onthis branch and on its merge base — ruff 0.15.16's formatter against a
repo pinned only to
ruff>=0.8, whose CI lint lane runsruff checkand notruff format --check. All seven source files this PR touches are cleanbefore and after. Reformatting the other 116 is not this PR's change to make.
tools/ci_test_groups.py --verifyplaces the newtest_logbook_isolation.pyin
fast: rest/engine: shared-spec, alongside every othertest_logbook_*file, and reports
verification=ok.No golden was regenerated, nothing renamed on disk, and no publish,
promotion, or release action was taken.
🤖 Generated with Claude Code