KB-3647

06 — Generic Axis Registration & Auto-Scale (Workstream D)

6 min read Revision 1
registries-pivotaxis-registryautoscaleno-hardcodem-def-92026-06-03

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)

  1. Register — insert an axis_registry row (APR-gated; can change classification/counting truth → assign_axis_owner/register_axis action type).
  2. Birthaxis_registry row + its nodes get birth_registry entries (before that = birth-orphan).
  3. Add relation rules — declare relation_source + child_relation_rule (reuse universal_edges/taxonomy.parent_id/iu_relation).
  4. Generate/validate pivots — emit or validate count_pivot_codes against the live pivot_definitions engine (which already reads FROM the declared node_source).
  5. Show in Registries-Pivot — the graph-aware companion surface (doc 07) reads axis_registry + the declared projection_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.

Back to Knowledge Hub knowledge/dev/reports/architecture/information-piece-topic-axis-registries-pivot-design-2026-06-03/06-generic-axis-registration-and-autoscale.md