06 — Generic Axis Registration & Auto-Scale (Workstream D)
06 — Generic Axis Registration & Auto-Scale (Workstream D)
Goal: a new axis becomes visible by registry/config, not new code. Prevent a hardcoded "topic axis" special case (topic is only the pilot).
The one missing object: axis_registry (M-DEF-9)
Verified absent live. It is the ground-truth inventory of axes the coverage scanner reconciles against; its absence is itself the critical inventory_gap. It generalizes the live taxonomy_facets (which is a proto-registry for label axes only).
Axis registration contract (paper; additive; owner-gated)
axis_registry (governed registry object; born via birth_registry; owner relational)
axis_code text PRIMARY KEY -- AX-TOPIC, AX-CONTAINMENT, AX-RECON, AX-PIVOT-*, AX-<future>
axis_name text NOT NULL
axis_family text NOT NULL -- structural | label | semantic | pivot | system
axis_kind text NOT NULL -- deterministic | uncertain | system
node_source text NOT NULL -- node_table or node_view (e.g. taxonomy WHERE facet_id=8)
node_filter jsonb NULL -- predicate selecting this axis's nodes
relation_source text NOT NULL -- relation_table/view (universal_edges | taxonomy_parent | iu_relation)
root_rule jsonb NOT NULL -- predicate for Layer-1 roots (e.g. status=active AND parent IS NULL)
child_relation_rule jsonb NOT NULL -- how children are derived (parent_id | edge kind broader/narrower)
lifecycle_field text NOT NULL -- status column driving candidate→active→retired
owner_scope_ref text NOT NULL -- → governance_responsibility_scope (relational owner)
grouping_policy_ref text NULL -- → rp_grouping_policy row
count_pivot_codes text[] NULL -- declared/generated pivots (PIV-320..)
substrate_resolver text NOT NULL -- fn_<axis>_node_substrate(code)
projection_view text NULL -- the RP surface companion that renders it
issue_path_ref text NOT NULL -- → event_type_registry domain (orphan/phantom/island)
created_by, created_at, ...
taxonomy_facets rows project in as axis_family='label'. A future axis is a row, declaring which existing stores hold its nodes/relations/assignments and which view projects it — no schema change, no fixed array.
Generic axis_assignment (the second, only other new object)
Generalizes the IU-only iu_metadata_tag to any entity↔axis-node membership with confidence/evidence/zone/lifecycle — so documents and workflows can carry topic assignments, not just IUs.
axis_assignment
id uuid PK, axis_code text → axis_registry, node_code text, entity_kind text, entity_ref text,
confidence numeric, evidence jsonb, zone text (approved|candidate|quarantine),
provenance text (PROV-AI|HUMAN|DOT), assigned_by, assigned_at, lifecycle_status text
Until built, topic↔IU uses live iu_metadata_tag; topic↔doc uses knowledge_documents.tags.
How a new axis becomes visible (the pipeline, all data)
- Register — insert an
axis_registryrow (APR-gated; can change classification/counting truth →assign_axis_owner/register_axisaction type). - Birth —
axis_registryrow + its nodes getbirth_registryentries (before that = birth-orphan). - Add relation rules — declare
relation_source+child_relation_rule(reuseuniversal_edges/taxonomy.parent_id/iu_relation). - Generate/validate pivots — emit or validate
count_pivot_codesagainst the livepivot_definitionsengine (which already readsFROMthe declarednode_source). - Show in Registries-Pivot — the graph-aware companion surface (doc 07) reads
axis_registry+ the declaredprojection_view. No Nuxt change, no new code per axis.
STOP conditions (detected like orphans; no silent fallback)
| Condition | Issue type | Severity |
|---|---|---|
axis registered but node_source resolver missing/unreadable |
axis_node_resolver_missing |
critical |
node exists but no birth_registry entry |
axis_node_birth_orphan |
critical |
relation exists but no lifecycle_field |
axis_relation_no_lifecycle |
high |
root_rule missing |
axis_root_rule_missing |
critical (blocks Layer-1) |
child_count > grouping ceiling but no grouping_policy_ref |
axis_grouping_island |
high |
a thing functions as an axis (pivot group-by, tag namespace, facet) but is not in axis_registry |
axis_unregistered |
critical |
axis active but not in governance_object_ownership |
axis_owner_gap |
high |
These reuse the same six-layer coverage scanner as object orphans — no per-axis detection code.
Why topic is only a pilot (no special case)
AX-TOPIC is registered with: node_source = taxonomy WHERE facet_id=8, relation_source = universal_edges ∪ taxonomy.parent_id, root_rule = status='active' AND no active parent, child_relation_rule = parent_id | edge(broader/narrower), lifecycle_field = status, substrate_resolver = fn_topic_node_substrate, count_pivot_codes = {PIV-320..332}. Any next axis (e.g. AX-EXPERTISE over FAC-01, AX-DOC-TYPE over knowledge_documents.category, a future business-process axis) is the same row template — the law, scanner, and surface do not change. This satisfies the macro's "auto-scale without hardcoding each axis."
Compliance statement (no fixed axis arrays)
The runtime depends only on (a) the axis predicate, (b) axis_registry (data), (c) coverage reconciliation. No enumerated axis list exists in code/SQL/Nuxt. A hardcoded axis list in the UI = hardcode_violation.
Workstream D completion: future axes are added by registry/config; topic is the first registered axis, not a coded special case.