Skip to content

Port the post-freeze childcare / Tax-Free Childcare inputs and targets, re-pin the Chronicle feed, and activate the DfT bus targets (#834, #789) - #874

Draft
juaristi22 wants to merge 35 commits into
mainfrom
uk-childcare-tfc-834
Draft

Port the post-freeze childcare / Tax-Free Childcare inputs and targets, re-pin the Chronicle feed, and activate the DfT bus targets (#834, #789)#874
juaristi22 wants to merge 35 commits into
mainfrom
uk-childcare-tfc-834

Conversation

@juaristi22

@juaristi22 juaristi22 commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #834 and #789 (WS-E of #145, epic #665). Plan: repos/uk-834-childcare-tfc-port-plan.md (approved 2026-09-04, re-based on main 96faa5d5); Codex implemented increments I0–I2 under the approved-plan protocol (deviations listed below), followed by the licensed lane (L0–L5) whose receipts are in experiments/834-childcare-tfc-receipts.md. Stacked on #849 (A11: the v2/v3 artifact loader this feed needs): #849's 22 commits ride in this diff until it merges, after which this branch rebases and the diff is #834's own. Base is main so CI runs on the merge ref. Draft — María reviews everything at the end; never merge without her go.

What the issue asked for, re-measured against main

Item State on main This PR
tax_free_childcare_spend_routed_share (uk-data #472) Absent; policyengine-uk ≥2.93 reads it and defaults to 1.0 (the un-routed spend the June cut shipped). Generic assign_period_constant op in frs_person_draws, reading a new stochastic entry of take_up_contract.json at the build year (2024-04-01 → 0.593; series 2017–2025 from HMRC's account-activity ODS, Chronicle package pinned by sha). Engine floor ≥2.93, lock 2.94.0.
Five childcare targets (uk-data #473/#474, pe-uk #1830/#1834) None on the surface. hmrc.tfc.government_top_up, hmrc.tfc.children_with_used_accounts, dfe.funded_childcare.working_parent_children_2_to_4, dfe.funded_childcare.early_learning_2_year_olds, dfe.funded_childcare.universal_only_children — declared once, resolved by period (Part E): the issue's five numbers at 2024, the FY2025-26 / Jan-2025 counterparts at the 2025 calibration year.
Take-up rates re-fit (uk-data #473) June rates. L5: re-fit at the 2024 base on the L2 column twin with the repaired fitter (A6). Fitted on the L2 column twin with an expected-count objective (see Deviations): extended 0.812 → 0.6054, universal 0.563 → 0.4539 landed; TFC (0.997, at its ceiling: with spend routed at 0.593 the two HMRC rows conflict) and targeted (1.0, a ~157-child eligible base) held at 0.88 / 0.597 for María's ruling (plan A22/A23).
DfT bus targets (#789, Chronicle #202/#231) Facts absent from the pinned feed. dft.bus_fare_receipts.england and dft.bus_net_support.england active at household grain; London / outside-London / UK rows declared with model-side filters and signed-excluded until the regional measure lands (target_reference_signed_exclusions.json).
Chronicle feed pin 226358e7 feed (v1 schema); 12 references broken on the current feed. Re-pinned to chronicle main 6fb700e (128 717 rows, facts sha 6ae49d7d…, manifest sha dcda51d6…, v2 schema via #849); contract repaired by declaration only (Part A / plan §5c).

Contract repairs carried by the re-pin (I0)

The previous feed pin (226358e7, schema v1) broke 12 references on the current Chronicle main and moved 101 values. The repair is declaration-only: UC household rows re-bound to the dwp.uc_households family-type/children/youngest-child series with dimensions (exact key set) + dimension_values (subset) selectors and 2025 calendar-year averages (April–December, the months the feed carries); the caseload row keeps DWP deductions Table 1 total_units with a tightened selector (A16; chronicle#247 filed for the Stat-Xplore family-type Total row); the two CGT totals bound to Table 1's Total line (Table 5 duplicates excluded by source_measure_id); the stale PIPR deferral retired and the available local rent rows activated on the same temporal basis (A18); the runner now requires --ledger-manifest-sha256 and refuses a feed that is not the committed pin unless --allow-unpinned-feed is recorded. One I0 defect surfaced by the L4-pre calibration is fixed in this PR's second commit: the Scotland youngest-child row keeps its household-grain binding (the measure provider has no categorical household-to-benefit-unit broadcast). Receipts Part A; docs/uk-chronicle-feed-repin.md.

Surfaces moved

Lane A: assign_period_constant operation kind (schema branch, allow-list, sources.yaml + byte-lockstep source_stages.json); frs_person_draws artifact pin of the take-up contract (its sha folds into the node key) and build-year lockstep check; take_up_contract.json (new stochastic entry; hours entry frozen_by_adjudication; four programs entries with corrected citations, two re-fitted with fitting_receipt); loader validation of entity; H2 parity fixture regenerated (twice: the column, then the contract sha); coverage manifest; degenerate_reviewed_exclusions.json (+1); spine_swap_signed_differences.json (net-new column; re-fit mechanism_change with L5 magnitudes); contract digests; roster pins. Lane B: uk_population_targets.json (+7 active rows, +6 signed-held bus rows, Scotland row grain); difference value operation with value_operands; expected_member_count cardinality guard; observation_basis; entity_name/period_type/period_value/layout_groupby_value_id selector keys; target_reference_signed_exclusions.json (new register, declared in the country package); regenerated national/local references, membership and compile-parity receipts (415 active / 7 no fact / 8 signed on feed 6ae49d7d…); test fixtures from Chronicle rows only (uk_bus05i_fy2025.json, feed rows appended). Fitter: tools/fit_uk_childcare_takeup.py rewritten (tool version 3). Engine: policyengine-uk>=2.93, lock 2.94.0.

Deviations from the plan

  • Fitter objective (plan D6 said identity-keyed draws). The build keys each take-up flag on the spine's int64 benunit id at the frs_take_up stage and later stages clone and re-key rows, so no re-drawn realisation can sit on the persisted one (66% flag agreement re-hashing final ids; chance 51%), and the draw objective is a step function whose 1e-2 finite-difference step flipped no unit on the targeted row's 157-child base (first fit left it untouched). The fitter now takes two engine runs with the flags forced on and fits each row's expectation (linear in its own rate, bilinear with the extended rate through the engine's mutual exclusions): smooth, exact for the build, ceilings reported. Plan A24; receipts Part C L5.
  • Two rates held rather than landed (plan A6 said the four land). TFC and targeted come out at their ceilings (0.997 / 1.0); landing 1.0 would make the persisted flag constant (degenerate-gate refusal) and assert a 100% take-up the evidence does not support. Held at 0.88 / 0.597 with the full fitted vector in the receipts; María's rulings A22/A23.
  • A21 (CGT vintage after the re-pin): ruled 2026-09-05 — facts bound as published, the OBR miss signed on the stack register under UK CGT: translate the 2024-25 gains outturn to the 2025 calibration year (forestalling year; OBR CGT +44.2% signed on the stack register) #875 (window to 2026-10-05); the with-children deferral clock (2026-09-30) is unchanged.
  • Codex implemented I0–I2 with zero deviations from the plan text; the two I0 row defects above were found by the licensed runs and fixed by hand.

Verification

  • Hermetic funnel on the I1+I2 tree (María's UK + machinery set, test_contract.py, the graph package incl. test_h2_uk_spine_parity), JUnit-counted from this branch's venv with policyengine-uk 2.94.0: 2 893 tests, 2 failures, 27 skipped — the two failures are the documented licensed-cache regeneration tests (test_cached_reference_regeneration…, test_cached_candidate_regeneration…: committed evidence minted under 2.89.0 vs the installed engine, Make the UK eFRS parity re-pin one reviewed command; hold every identity mirror in lockstep (companion to #747) #749's lane), failing on main the same way. Post-port targeted set 468 passed / 1 skipped. After the L5 landing: graph package + digest-sensitive set 463 tests, the same single licensed-cache failure. ruff check clean on every touched file; tools/ci_test_groups.py --verify ok; tools/build_uk_release_input_coverage_manifest.py --check ok. CI is authoritative. Merged main c1b83241 (Portable authenticated worker identity: semantic identity plus audit-only aliases for the primary-QRF worker binding (fixes #863) #871, portable worker identity) on 2026-09-05: two conflicts (a root journal kept at main's version; the build tests' conftest keeps both Accept chronicle-era Chronicle artifact schema ids and fact-key domains; CHRONICLE_* env names (chronicle#143 consumer side) #849's Logbook isolation and Portable authenticated worker identity: semantic identity plus audit-only aliases for the primary-QRF worker binding (fixes #863) #871's worker-identity priming), the primary-QRF worker identity's approved uv.lock digest moved to the merged tree's (this branch's engine floor bump changed the lock) with no derived identity re-pin needed (spec-engine identity, legacy adapter, re-emission, field-usage and inventory-coverage suites green), and the national membership report regenerated after the uk-data citation fix. Post-merge set: 1 027 tests on the identity, pin, UK contract, graph and data-contract files with only the documented licensed-cache failure. Full-tree funnel on the L5 tree (every shard's tests, 8 233 tests): 19 failures — 13 US tests that need policyengine-us (this venv carries the uk extra only; the same files fail on a clean origin/main checkout in the same venv), the same two licensed-cache tests, and four of mine, fixed in the last commit: the live-tree guard found the incumbent package name in the new signed-exclusions register (now uk-data#230), and the monetary-binding receipt contract needed the two new reference fields (value_operands, expected_member_count) — test_monetary_targets and the calibrate shard's monetary tests green after the fix.
  • Composition branch uk-publication-stack-834 (ca050866, 8c8f60d9, 9b71e0f8, 9ab60650 — register port, required artifact, seam clock, signed CGT deferral — on this branch's head): the register port's 406-test set green (pins, spec bundles, source stages, terminal gates, battery bindings, gate battery, release certification, test_contract.py).

Licensed lane (receipts Parts B–D; raw JSON licensed-side)

  • L0 / L1 (before-receipts): spine-n read at 2.94.0 and an engine-only twin at the floor bump: benunit and time_period byte-equal, the engine bump re-draws 37 imputed household columns through its predictors; the five childcare rows read 1.07 / 1.02 / 1.28 / 0.60 / 1.10 at 2024 with the routed share absent.
  • L2 (column twin) / L3 (round trip): 28 stages, 15/15 spine gates; the only between-twin difference is the new column (0.593 for every person, read back by the engine) and its consequences — 1 414 TFC families' net income moves, so the WAS/LCFS/ETB imputations re-draw for 654 / 1 196 / 1 027 households (≈80% of them TFC families), the regional property rescale and the energy IPF ripple to every owner / household at median relative 3e-4 – 1e-3; no cash, weight or benefit-unit column moves; column totals within ±0.9%. The routed share moves only the TFC spend row (1.07 → 0.70); the four count rows are identical to three decimals.
  • L5 (re-fit): three fits (draw objective at eps 1e-2 and 0.05, then the expected-count objective, converged: loss 0.080); ceilings at a take-up of one — spend 0.81, children 1.14, extended 1.63, targeted 0.79, universal 1.58. Landed extended 0.6054 / universal 0.4539; rebuilt twin realises 0.999 / 1.087 (expectations 0.986 / 1.000); the two moved flags signed (max |Δshare| 0.207 / 0.110), everything else as L2.
  • L4 (composition, uk-publication-stack-834): on the rebuilt twin, family_equal as L4-pre: loss 0.352 → 0.0235, 90.0% of 371 targets within 10%, ESS 5 868. The seven new rows all fit within 0.5% (targeted from 0.77× at design weights to 0.997). The pre-existing surface is where L4-pre left it to the second decimal: the same seven uk_target_fit breaches (OBR CGT +44.2% — A21; the four UC with-children cells −29…−39% signed on the stack's register to 2026-09-30; the two state-pension SPI bands +35% / +25%), UC caseload −13.4%. The port neither helps nor harms the Port UK compact source stages and 2024-25 refresh from uk-data #145 residuals. Terminal battery with the ported register (run 5, after María's A21 ruling): the four UC with-children cells and OBR CGT (+44.2%, signed 2026-09-05 → 2026-10-05 under UK CGT: translate the 2024-25 gains outturn to the 2025 calibration year (forestalling year; OBR CGT +44.2% signed on the stack register) #875, the 2024-25 → 2025 translation of the forestalling-year gains) defer under their in-force entries; uk_target_fit still fails on the two state-pension SPI bands (+34.6% / +25.3%) and reports the private-pension 100–150k deferral stale (+24.7%, back inside the bound). Those are the stack's open register questions, not Port post-freeze UK childcare/TFC spine inputs and calibration contract #834 rows.
  • Bus (UK bus fare and subsidy consumer targets from Chronicle facts #789, Part D): on the L2 twin at design weights England fares 0.569× and net support 0.712× the FY2025 facts (E9 measured 0.56× / 0.71×); London 0.276×, outside London 0.760× / 0.969× — the Adjudicate the incumbent's four post-calibration aggregate scalers #790 gradient, which is why those rows are declared with filters but signed-held.

UK-specificity note

The routed share, the DfE/HMRC taxonomies and the DfT geographies are UK; assign_period_constant, difference, expected_member_count and observation_basis are generic surface machinery.

Not in this PR

The publication-stack target-fit register itself, including the new CGT entry (lives on uk-publication-stack-834, the composition branch, not for merge on its own); the #875 translation; the expected-count objective's extension to the four would_claim_* build draws (they stay identity-keyed by design); pointer comments on #789/#790 and closing #153 (each on María's go); the London / outside-London bus measure (#790's gradient); chronicle#247 (Stat-Xplore Total row); the CGT year (A21).

🤖 Generated with Claude Code

MaxGhenis and others added 30 commits September 2, 2026 10:46
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>
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>
policyengine-uk 2.93.0 adds the person-level tax_free_childcare_spend_routed_share
input and 2.92.2 applies the Tax-Free Childcare rate to the gross payment; the
port of uk-data #473/#474 needs both. 2.94.0 is the release the free-childcare
dashboard was measured on. The frame and data shards' uk extras had drifted
back to >=2.88 after #811; all three shards now agree on >=2.93.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tract they compile against (#834, #789 I0)

The committed national surface was generated on a Chronicle feed (226358e7, 107,550
rows) the local census no longer pinned (33ca98a, 108,112 rows); on the pinned feed
19 references failed to compile and the calibration runner refused to run. Both
surfaces now pin one feed built from Chronicle main 6fb700e (128,717 rows, facts
6ae49d7d…, manifest dcda51d6…, artifact schema v2), and the runner refuses any other
feed unless --allow-unpinned-feed is passed and recorded.

What the re-pin surfaced and how the contract answers it:
- The #239 UC crosses share source_concept and groupby_dimension with the base
  family-type and number-of-children tables; the composition rows now pin their
  exact dimension key set, and the generator no longer reads a `dimensions` list as
  a sum trigger.
- The caseload row keeps binding DWP UC deductions statistics Table 1 (measure
  total_units, month groupby): its April–December 2025 calendar-year average is
  6,758,889, the value #735 recorded; the Stat-Xplore family-type sum runs 1.45%
  above it (chronicle#247 asks for the cube's Total row).
- The CGT totals pin Table 1; Table 5's UK rows carry the same concepts.
- Every dwp_universal_credit row is a calendar-year average (one temporal basis):
  the 100 payment-band rows move from the May-2025 snapshot to the 2025 mean, and
  the Scotland youngest-child row is re-bound at benefit-unit grain.
- ons.population.scotland_households_3plus_children is signed out (its selector
  reaches person-level mid-year population rows; #736).
- The local private-rent deferral private_rent_pipr_after_target_period is stale
  since #231 extended PIPR to 2023–2026: the 314 rows bind the 2025 calendar-year
  average (12 members each). Local active references 19,618 -> 19,932.
- The nine scotgov council-tax bands restore themselves (#231's measure-id fix).

Regenerated with the repository tools: national and local references and
membership, the local census and validation levels, both compile-parity receipts,
the feed-row fixture. A hermetic test regenerates the surfaces from the pinned
identity and skips only when the feed is absent. docs/uk-chronicle-feed-repin.md
records the procedure.

Codex implemented increment I0 of the #834 plan under the approved-plan protocol;
reviewed section by section, source_repo restored to the canonical
PolicyEngine/chronicle, census regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…us targets (#834, #789)

Lane A (D1–D6): a generic `assign_period_constant` source operation gives
`frs_person_draws` the HMRC-derived `tax_free_childcare_spend_routed_share`
from a new `stochastic` entry of the take-up contract at the build year
(2024-04-01 → 0.593; 2017–2025 series from the March-2026 account-activity
ODS, Chronicle package and sha pinned), with the stage's node key folding the
contract sha, a build-year lockstep check against the installed engine, the
degenerate-column and swap-register entries, and the H2 fixture regenerated.
The contract's TFC citation is corrected, the targeted rationale added, and
the extended-hours distribution moved to `frozen_by_adjudication`. The
childcare fitter is rewritten: targets compiled from the contract and the
pinned feed, four rates, hours frozen, identity-keyed draws, `--eps`
recorded in the receipt.

Lane B (D8–D13): five childcare rows declared once and resolved by period
(`hmrc.tfc.*` at fiscal-year government/person grain; the three DfE rows with
the `sum` cardinality guard, the `difference` operation and the observation
basis), the two England bus rows active at household grain and the London,
outside-London and UK rows declared with model-side filters but signed on the
new `target_reference_signed_exclusions.json`; national references,
membership, local references and the compile-parity receipts regenerated on
feed `6ae49d7d…` (415 active / 7 no fact / 8 signed). Publisher fixtures for
the tests are Chronicle rows (`uk_bus05i_fy2025.json`), never hand-copied.

Two I0 defects fixed here: `dwp.uc.scotland_households_child_under_1` keeps
its household-grain binding (the measure provider has no categorical
household-to-benefit-unit broadcast, so the benefit-unit re-bind refused at
calibration), and the held London / outside-London bus rows carry their
region filters.

Receipts: `experiments/834-childcare-tfc-receipts.md` Parts C–E (L2 column
twin, L3 round trip, L5 fit diagnosis, bus re-measurement, both-basis table).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd land the extended and universal rates (#834 L5)

The fitter no longer draws. The build keys each take-up flag on the spine's
int64 benunit id at the frs_take_up stage and later stages clone and re-key
rows, so no re-drawn realisation can sit on the persisted one (re-hashing
the final ids agrees with the persisted universal flag on 66% of rows,
chance being 51%), and the draw objective was a step function whose
finite-difference step flipped no unit on the targeted row's ~157-child
base. Two engine runs with the flags forced on (extended on, then off) now
give each row's weighted per-row contributions; each target's expectation is
linear in its own rate and bilinear with the extended rate through the
engine's mutual exclusions (targeted and universal need a family that is not
extended-eligible). The objective is smooth, exact for the build, and the
receipt reports each row's ceiling at a take-up of one. Unit test pins the
bilinear form; `--eps` is recorded in the receipt.

Fitted on the L2 column twin at the 2024 base year (Jan-2025 vintage override
on the extended row, routed share 0.593 from the contract, hours frozen):
extended 0.812 → 0.6054 and universal 0.563 → 0.4539 land as 2024-04-06
entries with a `fitting_receipt`. `tax_free_childcare` (0.997, at its
ceiling: with spend routed the two HMRC rows conflict) and
`targeted_childcare` (1.0, the eligible base reaches 0.85 of the count) are
held at 0.88 / 0.597 for adjudication (plan A22/A23); a rate of 1.0 would
also make the persisted flag constant. Receipts Part C L5 carry the three
fits, the probes and the rebuilt twin's magnitudes; the swap register signs
the two moved flags as `childcare-take-up-refit-834`; the H2 graph parity
fixture is regenerated (the take-up stages' node keys fold the contract sha)
and the contract test's pinned rates follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Run 1 is the weighting-sensitivity receipt (per-target uniform weights);
run 2 the family-equal composition, equal to L4-pre to the second decimal
with the seven new rows binding within 0.5%; run 4 reproduces it on a
payload-identical rebuild with the seam supplying the exclusion clock, so
the ported target-fit register renders its verdict: the four UC
with-children cells defer, OBR CGT and the two state-pension bands fail
unsigned, the private-pension band entry reads stale. The runner's
--build-record-json is the seam's own output; the note records how run 2
overwrote the twin's spine sidecar and why the twin was rebuilt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…w reference fields in monetary-binding receipts (#834)

The live-tree guard refuses the incumbent package name: the signed
target-reference exclusions register now cites uk-data#230 and the uk-data
PR as every other live file does. The monetary binding's activated-reference
receipt is an exact key set; LedgerTargetReference gained value_operands and
expected_member_count for the multi-member childcare rows, so the receipt
contract and its hermetic fixture carry them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juaristi22 and others added 5 commits September 5, 2026 15:01
# Conflicts:
#	out.md
#	packages/microcosm-build/tests/conftest.py
María ruled A21 as option 1 on 2026-09-05: the facts stay bound as published
and obr.capital_gains_tax@2025 is signed on the publication stack's register
under microcosm#875 (window to 2026-10-05). Run 5 reproduces run 4 and the
battery now defers five cells; the two state-pension bands and the stale
private-pension band entry remain the stack's open questions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fix (#834)

The membership report embeds the signed-exclusion rationale, so the
citation fix in the register moved its digest; the references themselves
are byte-identical. The packaged-report test carries the uk-data wording.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dentity (#834)

Main's #871 attests the workspace lock; this branch's policyengine-uk floor
bump (>=2.93, lock 2.94.0) changed uv.lock, so the merged tree's digest is
approved. No derived identity moved: the spec-engine identity, legacy
adapter, re-emission, field-usage and inventory-coverage suites pass on the
approved digest without a re-pin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…834)

The provenance receipt read policyengine-uk's version unconditionally, so
the hermetic determinism test with an injected runner failed in CI's
no-engine lanes (fast spine-uk, wheels) while every engine lane passed.
The version is recorded as absent when the distribution is missing; a
real fit still needs the engine for its basis runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

Port post-freeze UK childcare/TFC spine inputs and calibration contract

2 participants