KB-27A9

05 — Axis Storage Build-Ready Design (PG-first, CREATE-TABLE level)

15 min read Revision 1
one-roof-governanceaxisauthorizationproposalhardeningbuild-readydesign-onlyread-only2026-06-02axis-storage

05 — Axis Storage Build-Ready Design (PG-first, CREATE-TABLE level)

Package: one-roof-axis-auth-proposal-operational-hardening-build-ready-design-2026-06-02 Mode: DESIGN ONLY · READ-ONLY · NO COMMIT · NO MUTATION · paper DDL only Hardens: prior …-2026-06-01/04-axis-storage-model-pg-first.md. Closes GPT review gap #2 (storage half): exact CREATE-TABLE-level design for axis_registry + axis_assignment, compatible with taxonomy/taxonomy_facets/entity_labels/iu_relation/KG tables. Substrate names: SB-AXIS-1 = axis_registry, SB-AXIS-2 = axis_assignment.


5.0 Storage principle & the 3 zones

PG is truth and runtime. Three storage zones (NĐ-36-01 MT4 model — see §5.7 citation flag):

  1. Approved / born — official truth; in taxonomy(active) / axis_registry / entity_labels(approved) / IUs; UI-visible.
  2. Candidate — proposed, under review; in axis_assignment(zone='candidate') / taxonomy(status='candidate'); AI-visible, NOT official UI.
  3. Quarantine — missing provenance / failed input gate; in axis_assignment(zone='quarantine'); never promoted, governed as input-quality.

Only two new tables (plus the registry-generalization of the existing envelope). Everything else reuses live substrate.


5.1 axis_registry (SB-AXIS-1) — column-level design (paper DDL)

Generalizes taxonomy_facets; each existing facet projects to one axis_registry row of axis_family='label' (lossless — §5.4).

Column Type Null Default Constraint / notes
axis_code text NOT NULL PK, e.g. AX-TOPIC.
axis_name text NOT NULL Display name.
axis_family text NOT NULL CHECK ∈ (structural,label,semantic,pivot,system).
axis_kind text NOT NULL CHECK ∈ (deterministic,uncertain,system). Drives behaviour (doc 04 §4.2).
owner_scope_ref text NOT NULL governance_responsibility_scope (SB-2). Owner-per-scope.
scope text NOT NULL 'global' CHECK ∈ (global,domain-local,object-local).
vocabulary_source text NULL Where node values come from (e.g. taxonomy:FAC-08, information_unit, computed).
node_kind text NOT NULL CHECK ∈ (taxonomy_node,information_unit,born_entity). Polymorphic node home.
relation_store text NOT NULL CHECK ∈ (iu_relation,universal_edges,taxonomy_parent,entity_relations,none).
assignment_store text NOT NULL CHECK ∈ (entity_labels,iu_metadata_tag,axis_assignment,iu_parent_ref,intrinsic).
grouping_policy_ref text NULL Grouping/DOT policy row (Điều 35).
coverage_rule_ref text NOT NULL → coverage rule row (confidence threshold, cardinality, coverage expectation). No literal thresholds.
issue_path_ref text NOT NULL event_type_registry (event_domain='governance') issue family for this axis.
projection_view text NULL Named view the UI reads (doc 10).
lifecycle_status text NOT NULL 'register' CHECK ∈ (birth,register,active,deprecate,retire).
cardinality text NOT NULL 'multiple' CHECK ∈ (single,multiple). Carried from taxonomy_facets.cardinality.
max_labels_per_entity integer NOT NULL 0 0 = not operationalized (matches FAC-08 today).
created_by / created_at text / timestamptz NOT NULL now() Provenance.

Constraints: PK(axis_code); CHECK enums above; FK-soft owner_scope_ref/coverage_rule_ref/issue_path_ref (soft text refs until SB-2/SB-11 exist, to avoid ordering deadlock; hardened to FK when those tables land). Index on (axis_family,axis_kind), (lifecycle_status).

Born: an axis_registry row of lifecycle_status='active' is a born governed object (Birth row + owner + coverage).


5.2 axis_assignment (SB-AXIS-2) — column-level design (paper DDL)

The confidence/zone/evidence-bearing assignment table for uncertain axes. (Deterministic axes use intrinsic stores and write no axis_assignment row.)

Column Type Null Default Constraint / notes
id uuid NOT NULL gen_random_uuid() PK.
axis_code text NOT NULL FK → axis_registry.axis_code.
entity_kind text NOT NULL CHECK ∈ (information_unit,born_entity,collection).
entity_collection text NOT NULL The collection name (e.g. information_unit). Replaces the prior single entity_ref — see §5.3 key reconciliation.
entity_code text NOT NULL The entity's code (matches entity_labels.entity_code / birth_registry.entity_code).
entity_uuid uuid NULL Optional native id for IU rows (information_unit.id).
node_ref text NOT NULL FK → taxonomy.code (the axis node).
zone text NOT NULL 'candidate' CHECK ∈ (approved,candidate,quarantine). The governance state.
match_score numeric(5,4) NULL Raw matcher score (similarity). Distinct from confidence (NĐ-36-01 MT4: never collapse score into approval state).
confidence numeric(5,4) NULL Calibrated confidence; < coverage_rule threshold ⇒ stays candidate.
evidence jsonb NULL [{kind, ref, weight}].
provenance jsonb NULL {source, source_authority, extraction_method, resolved_by, ts}. NULL ⇒ zone forced quarantine (CHECK or promote-guard).
assigned_by text NOT NULL CHECK ∈ (dot,agent,human,rule).
rule_ref integer NULL FK → label_rules.id (when rule-derived).
review_decision_ref text NULL approval_requests.code / doc_reviews (when reviewed).
valid_time tstzrange NOT NULL tstzrange(now(),NULL) Bitemporal validity (matches iu_relation/universal_edges).
revision integer NOT NULL 1 Bumped on supersession.
superseded_by uuid NULL Self-FK to the replacing row.
status text NOT NULL 'active' CHECK ∈ (active,superseded,retracted).
created_by / created_at text / timestamptz NOT NULL now()

Constraints/indexes:

  • PK(id); FK(axis_code)→axis_registry; FK(node_ref)→taxonomy(code); FK(rule_ref)→label_rules(id); self-FK(superseded_by).
  • CHECK provenance IS NOT NULL OR zone='quarantine' (provenance-or-quarantine).
  • CHECK zone='approved' only set by the promotion txn (enforced operationally + a guard: agents may not write zone='approved', doc 02/06).
  • Partial UNIQUE uq_axis_assignment_active: UNIQUE (axis_code, entity_collection, entity_code, node_ref) WHERE status='active' — one active assignment per (axis,entity,node).
  • For single-cardinality axes, an additional partial-unique on (axis_code,entity_collection,entity_code) WHERE status='active' AND zone='approved' (one approved node per entity per single-axis).
  • Indexes: (axis_code,zone), (entity_collection,entity_code), GIN on provenance/evidence.

5.3 Key reconciliation — the canonical_address-NULL fix (named inconsistency)

Live fact (doc 01 §1.5): birth_registry.canonical_address is NULL in all 1,069,055 rows; information_unit.canonical_address exists but is unreliable as a universal key. The prior doc 04 wrote axis_assignment.entity_ref = 'canonical_address/code/uuid'inconsistent with the live key.

Hardening: replace the single entity_ref with entity_collection + entity_code (+ optional entity_uuid), exactly matching the live addressing used by entity_labels.entity_code and birth_registry(collection_name, entity_code). The canonical candidate key is therefore:

candidate_key := entity_collection || ':' || entity_code      -- NOT canonical_address

This aligns axis_assignment with the SB-10 candidate key COALESCE(canonical_address, collection_name||':'||entity_code) (which, since canonical_address is always NULL, evaluates to collection_name:entity_code). One key, used by assignment, coverage, and promotion. No design anywhere keys off canonical_address.


5.4 Compatibility — taxonomy_facets/taxonomy/entity_labels (build-ready)

Live table Relationship to new tables Migration / compatibility
taxonomy_facets (10) each facet → one axis_registry row (axis_family='label', assignment_store='entity_labels' for deterministic, axis_assignment for uncertain) lossless projection, additive: taxonomy_facets is kept; axis_registry references it via vocabulary_source='taxonomy:FAC-NN'. No ALTER of taxonomy_facets.
taxonomy (58) the node store for label/semantic axes; node_ref FKs here unchanged; topic candidates added as status='candidate' rows under the ladder.
entity_labels (787,723) the materialized approved projection for label axes kept as-is (no ALTER). For uncertain axes (topic), axis_assignment is the SoT and entity_labels is reconciled from it on approval (§5.5). For deterministic label facets, entity_labels+label_rules remain authoritative; no axis_assignment rows.
iu_metadata_tag (confidence-bearing) overlaps axis_assignment fold-in over time (a patch, doc 12): new uncertain-axis tags go to axis_assignment; existing iu_metadata_tag rows read-compat via a union view until migrated. No big-bang migration.
label_rules (37) deterministic rule engine unchanged; axis_assignment.rule_ref FKs here when a candidate is rule-derived.

Why not just extend entity_labels? entity_labels has no confidence/evidence/zone/valid_time/provenance columns and is 787k rows at scale; adding nullable governance columns to it (ALTER) is both a migration risk and would mix approved truth with candidates. Keeping it as the approved projection and adding axis_assignment as the candidate/SoT layer is additive and reversible (the prior doc 04 recommendation, now hardened).


5.5 The reconcile path axis_assignmententity_labels (concrete mechanism)

GPT "key claim to verify": the reconcile must be concrete. Promotion txn (doc 16) for a topic assignment:

-- inside the L3-authorized promotion transaction:
UPDATE axis_assignment SET zone='approved', review_decision_ref=:apr,
       valid_time = tstzrange(lower(valid_time), NULL)
 WHERE id = :assignment_id;                                  -- candidate → approved
INSERT INTO entity_labels (entity_code, label_code, assigned_by, rule_id, assigned_at)
VALUES (:entity_code, :node_ref, 'axis_promote', NULL, now())
ON CONFLICT (entity_code, label_code) DO NOTHING;            -- materialize approved projection
-- retraction reverses: zone='retracted' + DELETE/expire the entity_labels row

A daily reconcile detector (doc 09) flags drift: an entity_labels row with no backing axis_assignment(zone='approved') for an uncertain axis (label_orphan), or an approved assignment missing its entity_labels projection (projection_lag). Deterministic facets are exempt (entity_labels is their SoT).


5.6 Relations, multi-parent, and graph (reuse, no new relation table)

  • Containment / multi-parent: iu_relation(relation_type='contains') is a graph (an IU may have multiple parents); iu.parent_or_container_ref names the primary parent; iu_tree_path is the closure projection (tree). Graph = truth, tree = projection (doc 07).
  • KG / cross-collection: universal_edges (+ v_kg_edges_all) carries typed weighted edges with confidence/valid_time/provenance already.
  • entity_relations (decreed for NĐ-36-01 soft relations, unbuilt): do NOT build now. Interim, soft relations ride universal_edges (with edge_subtype). axis_registry.relation_store='entity_relations' is a declared-but-deferred option; building it is a future step (doc 12 Class 4) only if universal_edges proves insufficient. Confidence/evidence/provenance for relations are already native to iu_relation/universal_edges — no new columns needed.

5.7 Confidence / evidence / provenance / ruleset / snapshot storage (where each lives)

Dimension Storage
match_score (raw) vs confidence (calibrated) separate columns on axis_assignment (uncertain) / iu_metadata_tag / universal_edges.confidence (relations)
evidence jsonb on axis_assignment/iu_relation/universal_edges
provenance jsonb on axis_assignment/iu_relation/universal_edges (NULL ⇒ quarantine)
ruleset_version SB-12 governance_ruleset (canonical hash over enabled measurement_registry(142) ⊕ profile/axis/scope)
snapshot / fingerprint SB-12 reuse of evolution_snapshots (per-group fingerprint in metrics jsonb)
coverage verdict (decaying) SB-10 governance_candidate_state, keyed (candidate_key, snapshot, ruleset_version, scan_time) — no checked-forever boolean

Citation flag (NĐ-36-01): the 3-zone (approved/candidate/quarantine) and score-vs-state separation are attributed in prior docs to "NĐ-36-01 MT4". State recovery (doc 00) found no distinct decree "NĐ-36-01" in the KB; the closest is Điều 36 (referenced by Điều 37) and the soft-relation/alias model. The design stands on its own (it is sound regardless of citation), but the citation must be verified before any law cross-reference is written (doc 12 patch L3).


5.8 Open decisions

  • O-STORE-1: iu_metadata_tag fold-in timing (read-compat union view vs migrate).
  • O-STORE-2: whether AX-EXPERTISE/AX-AUDIENCE adopt axis_assignment or stay entity_labels-native (doc 04 O-AXIS-4).
  • O-STORE-3: FAC-08 cardinality/max (doc 07) — affects the single-axis partial-unique.
  • O-STORE-4: build entity_relations now or defer (recommend defer; reuse universal_edges).

Forbidden-compliance: paper DDL only; nothing created/altered; no row written; read-only audit only.

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-axis-auth-proposal-operational-hardening-build-ready-design-2026-06-02/05-axis-storage-build-ready-design.md