Microcosm's calibration hierarchy (microcosm#855, schema 8, merged 2026-09-11) completes every target's hierarchy from the Chronicle facts it selects and now requires each fact to carry human labels for every dimension and every dimension value. The consumer artifact does not carry them, so tools/generate_uk_target_references.py fails on every real feed, old and new alike:
LedgerHierarchyMetadataError: Ledger target reference 'obr.income_tax': Chronicle fact
'ledger.aggregate_fact.v2:d928e7da0fbfe088ca1a30d7' dimension 'obr.efo_line' requires exactly one
non-empty label, got [].
Microcosm will take a fallback in the meantime; this issue is the durable fix: Chronicle owns the labels, so the artifact should carry them.
What the consumer reads
From microcosm/build/ledger_targets.py (_validate_chronicle_hierarchy_labels, _inherited_dimensions, _dimension_label, _dimension_value_label), per fact:
label — already present; must be non-empty.
dimension_labels — a mapping dimension_id -> label covering every key of dimensions and, when layout.groupby_dimension is set, that id too (Microcosm folds layout.groupby_dimension: layout.groupby_value_id into the dimension set when the key is absent from dimensions).
dimension_value_labels — a nested mapping dimension_id -> value_id -> label, where value_id is the string form of the dimension value (str(value); booleans lower-cased; lists and mappings JSON-serialised with sorted keys and no spaces).
layout.groupby_dimension_label — accepted as the label of the groupby dimension in place of a dimension_labels entry; layout.groupby_value_label (already emitted) is accepted as the value label of the groupby value.
Labels must be identical across every fact a target selects; conflicting labels are refused.
Examples of what is missing today: the OBR EFO rows carry layout.groupby_dimension: obr.efo_line and layout.groupby_value_label: "Income tax (gross of tax credits)" but no label for obr.efo_line itself; the UC crosses from #260 carry dimensions: {carer_entitlement: "No", payment_indicator: "Yes"} and layout.groupby_dimension: dwp.carer_entitlement with no label for carer_entitlement, payment_indicator or dwp.carer_entitlement, and no per-dimension value labels (the row label "No, Yes" is the combined groupby value).
Ask
- Emit
dimension_labels, dimension_value_labels and layout.groupby_dimension_label on every consumer fact.
- Source the labels from Chronicle-owned declarations, not from identifiers: the Stat-Xplore field labels the archived responses already carry ("Carer Entitlement", "Payment Indicator", "Housing Entitlement - Tenure"), the recode item labels for values ("Yes", "No", "Social Rented Sector"), publisher line labels for the OBR / HMRC tables, and a package-level
dimension_labels block in source_package.yaml for anything the parser cannot recover, with a bundle check that every dimension id and value in a package has exactly one label.
- Version it:
consumer_fact.v1 declares additionalProperties: false, so the additive keys need consumer_fact.v2 (epoch.py already reserves chronicle.consumer_fact.v2). Microcosm pins consumer_fact_schema_sha256 and the accepted schema-version list in uk/national_chronicle_feed.json, so the pin moves in the same re-pin that adopts the new artifact; the facts, keys and values must not change.
- Cover the UK suite completely (every
groupby_dimension and every dimensions key in the bundle), since Microcosm validates every selected fact, not only the new ones.
Context
- Consumer-side mechanism and the interim fallback: microcosm issue to follow from the draft
uk-855-generator-regression-issue-draft.md (microcosm#855, #854).
- Blocked consumers today: microcosm#882's element bindings (branch
uk-uc-elements-882, on the 9f8b77af artifact), microcosm#904 and #906 once they rebase onto main.
- Not asked: changes to fact values, keys, periods, geographies or the record-set ids.
Microcosm's calibration hierarchy (microcosm#855, schema 8, merged 2026-09-11) completes every target's hierarchy from the Chronicle facts it selects and now requires each fact to carry human labels for every dimension and every dimension value. The consumer artifact does not carry them, so
tools/generate_uk_target_references.pyfails on every real feed, old and new alike:Microcosm will take a fallback in the meantime; this issue is the durable fix: Chronicle owns the labels, so the artifact should carry them.
What the consumer reads
From
microcosm/build/ledger_targets.py(_validate_chronicle_hierarchy_labels,_inherited_dimensions,_dimension_label,_dimension_value_label), per fact:label— already present; must be non-empty.dimension_labels— a mappingdimension_id -> labelcovering every key ofdimensionsand, whenlayout.groupby_dimensionis set, that id too (Microcosm foldslayout.groupby_dimension: layout.groupby_value_idinto the dimension set when the key is absent fromdimensions).dimension_value_labels— a nested mappingdimension_id -> value_id -> label, wherevalue_idis the string form of the dimension value (str(value); booleans lower-cased; lists and mappings JSON-serialised with sorted keys and no spaces).layout.groupby_dimension_label— accepted as the label of the groupby dimension in place of adimension_labelsentry;layout.groupby_value_label(already emitted) is accepted as the value label of the groupby value.Labels must be identical across every fact a target selects; conflicting labels are refused.
Examples of what is missing today: the OBR EFO rows carry
layout.groupby_dimension: obr.efo_lineandlayout.groupby_value_label: "Income tax (gross of tax credits)"but no label forobr.efo_lineitself; the UC crosses from #260 carrydimensions: {carer_entitlement: "No", payment_indicator: "Yes"}andlayout.groupby_dimension: dwp.carer_entitlementwith no label forcarer_entitlement,payment_indicatorordwp.carer_entitlement, and no per-dimension value labels (the row label "No, Yes" is the combined groupby value).Ask
dimension_labels,dimension_value_labelsandlayout.groupby_dimension_labelon every consumer fact.dimension_labelsblock insource_package.yamlfor anything the parser cannot recover, with a bundle check that every dimension id and value in a package has exactly one label.consumer_fact.v1declaresadditionalProperties: false, so the additive keys needconsumer_fact.v2(epoch.py already reserveschronicle.consumer_fact.v2). Microcosm pinsconsumer_fact_schema_sha256and the accepted schema-version list inuk/national_chronicle_feed.json, so the pin moves in the same re-pin that adopts the new artifact; the facts, keys and values must not change.groupby_dimensionand everydimensionskey in the bundle), since Microcosm validates every selected fact, not only the new ones.Context
uk-855-generator-regression-issue-draft.md(microcosm#855, #854).uk-uc-elements-882, on the9f8b77afartifact), microcosm#904 and #906 once they rebase onto main.