KB-681B

18 — SB-1/SB-2 Live PG Evidence (read-only, zero mutation, 2026-06-01)

23 min read Revision 1
one-roof-governanceimplementation-indexsb-1sb-2live-pg-evidenceread-onlyapr-action-typesgovernance-relationsgovernance-object-ownershipapproval-spinequorumtriggersregister-before-emitno-mutation2026-06-01

18 — SB-1 / SB-2 Live PG Evidence (read-only)

Track: T3 (SB-1) + T4 (SB-2) foundation. Mutation footprint: read-only PG (query_pg, READ ONLY role, statement_timeout 5s, hard LIMIT 500); zero PG/Directus/Qdrant/Nuxt/law/approval mutation. KB write = this doc only. DB: directus on contabo VPS, container postgres. As of: 2026-06-01. Purpose: Capture the exact live substrate for SB-1 (governance APR action-types) and SB-2 (object/axis ownership edge), plus the integration substrate the designs (docs 16/17) and readiness verdict (doc 20) reconcile against. Live evidence wins over older reports (house-law §1). Re-verify read-only before relying on any count. Controlling inputs: index docs 00/01/03/04/05/06; concept canon 01/02/03; house law prompt-muc-tieu-mo-for-claude-code.md.


0. Method & provenance

All rows below are from live read-only SELECT against directus. pg_schema MCP is broken for this role (AmbiguousParameter: could not determine data type of parameter $1 on the table_schema = $1 clause) → schema introspected via information_schema.columns / pg_constraint / pg_proc / pg_trigger with explicit casts. information_schema.triggers is empty for the read-only role (privilege artifact, per prior sessions) → triggers read from pg_trigger. No statement timed out; no row set was truncated except where noted.


1. SB-1 — Governance APR action-types (vocabulary gap CONFIRMED)

1.1 apr_action_types — full contents (6 rows)

PK = action_code. risk_level CHECK ∈ {low, medium, high}. status CHECK ∈ {active, deprecated, retired}.

action_code handler_ref risk status _dot_origin
add_field dot-apr-execute:add_field medium active MIGRATION
amend_law unimplemented high active MIGRATION
create_item dot-apr-execute:create low active MIGRATION
enact_nrm unimplemented high active S178-Fix21-…
patch_ops_code dot-apr-execute:patch_ops high active MIGRATION
update_item dot-apr-execute:update low active MIGRATION

Columns: action_code, description, handler_ref, risk_level, status, _dot_origin, created_at, retired_at.

Gap: none of assign_governance_owner / grant_governance_exception / delegate_authority / assign_axis_owner exists. Pre-flight collision check: 0 of the 4 proposed codes present. SB-1 confirmed OPEN.

Load-bearing pattern discovered: two action-types (amend_law, enact_nrm) are registered but reservedhandler_ref='unimplemented', status='active', risk='high'. They are the live precedent for "vocabulary exists, execution fail-closed" (see §4.3).

1.2 apr_action_types triggers (4)

trigger function effect
trg_apr_action_types_no_rename fn_prevent_action_code_rename_with_history action_code is immutable once it has history
trg_birth_apr_action_types fn_birth_registry_auto INSERT auto-registers the row in the birth registry (becomes a governed object, not an orphan)
trg_desc_guard_apr_action_types fn_description_birth_guard guards description on insert
trg_desc_provenance_apr_action_types fn_auto_label_provenance auto-labels provenance

⇒ Registering an action-type is not a bare insert; it fires birth-registration + description-guard + provenance. A rehearsal must account for these (doc 19).


2. SB-2 — Object/axis ownership edge (un-expressible CONFIRMED)

2.1 governance_relations — constraints + contents

Columns: id, source_type, source_code, target_type, target_code, relation_type, is_contract, discovery_source, enforcement_type, enforcement_ref, status, last_verified_at.

Constraints (verbatim pg_get_constraintdef):

  • chk_relations_source_typeCHECK (source_type IN ('law','agency'))
  • chk_relations_target_typeCHECK (target_type IN ('law','agency'))
  • chk_relations_discoveryCHECK (discovery_source IN ('pg_catalog','manual','dot_scan'))
  • uq_relations_source_target_typeUNIQUE (source_code, target_code, relation_type)
  • governance_relations_pkeyPRIMARY KEY (id)
  • No FK on source_code/target_code (codes are CHECK-typed free-text, not FK-enforced against registry/law).

The 8 live edges (all source_type='agency' → target_type='law', all status='active', discovery_source='manual'):

id source_code relation_type target_code
4 GOV-COUNCIL owner NRM-LAW-37
5 GOV-COUNCIL approver_tbox NRM-LAW-39
6 GOV-KG-SYS owner NRM-LAW-39
7 GOV-KG-SYS executor_abox NRM-LAW-39
8 GOV-DOT owner NRM-LAW-35
9 GOV-DOT owner NRM-LAW-36
10 GOV-NRM-SYS owner NRM-LAW-38
11 GOV-SIV owner NRM-LAW-31

Gap: source_type/target_type admit only law/agency. An object (e.g. a pivot, a collection, an IU class) or an axis can be neither a source nor a target. Ownership of an object/axis is therefore un-expressible in this table. SB-2 confirmed OPEN.

2.2 The proposed governance_object_ownership table does NOT exist

to_regclass('public.governance_object_ownership')null. to_regclass('public.governance_responsibility_scope')null. Both candidate substrate objects are genuinely absent (greenfield-additive, no migration risk to anything).

2.3 Triggers on governance_relations (2)

trg_birth_governance_relations (fn_birth_registry_auto_id), trg_relations_validate (fn_relations_validate). SB-2's design deliberately does not touch governance_relations, so these are untouched by the recommended path.


3. Ownership is RELATIONAL — the existing legs (why SB-2 complements, not duplicates)

System-wide no table carries owner_gov_code (concept M-DEF-2 reuse decision). Ownership is resolved from existing substrate. The legs that exist today:

  1. governance_registry (9 rows) — the agencies/mothers. PK = code. CHECK status ∈ {active, draft, retired}. FK created_by_law → normative_registry(code), FK domain → dot_domains(code).

    code gov_type domain status created_by_law
    GOV-COUNCIL council governance active NRM-LAW-37
    GOV-SIV system monitoring.integrity active NRM-LAW-31
    GOV-DOT system monitoring.dot active NRM-LAW-35
    GOV-KG-SYS system kg active NRM-LAW-39
    GOV-NRM-SYS system normative active NRM-LAW-38
    GOV-MOIT/MOT/MOUT/MOW factory (gov_group=mother) assembly.* draft NRM-LAW-07

    No GOV-IU (OP-B). GOV-MOUT (render owner, C-5) is draft. The 6 OP-B owner-default agencies (COUNCIL, SIV, DOT, KG-SYS, NRM-SYS, MOUT) are all present (pre-flight: owner_agencies_present=6).

  2. law_jurisdiction (43 rows) — law → domain → coverage_type (primary/secondary/reference). E.g. NRM-LAW-37 → governance/primary, NRM-LAW-32-V1P1 → governance/secondary, NRM-LAW-31 → monitoring/primary, NRM-LAW-24 → classification/primary, NRM-LAW-26 → pivot/primary, NRM-LAW-28 → collection/secondary.

  3. governance_relations (8 rows) — agency ↔ law edges (§2.1).

Missing fourth leg = SB-2: object/axis → owner-agency. The new table is the leg that does not exist; it does not restate the other three.

3.1 Object-class vocabulary source (no-hardcode anchor for object_type)

meta_catalog (169 rows) is the registry/object inventory. entity_type distribution (top): cms_block(26), os_crm(24), website_content(14), business_support(11), governance_infra(10), ai_support(7), website_page(6), collection(5), catalog(4), workflow/workflow_step(2 each), … plus dot_coverage, label_facet, approval_request, system_issue, etc. Columns include code, entity_type, source_model, registry_collection, record_count, actual_count, orphan_count, composition_level, identity_class, layer. ⇒ object_type/object_ref for SB-2 can resolve against a governed registry (meta_catalog and the per-class registries it indexes), not a hardcoded enum.


4. The Điều 32 approval spine (SB-1 wiring point) — exact live behaviour

4.1 approval_requests (211 rows) — the APR

Status distribution: applied=176, expired=19, rejected=14, approved=2. Key columns + constraints:

  • proposed_action_codeFK fk_apr_action_type → apr_action_types(action_code) ON UPDATE/DELETE RESTRICTthis is the SB-1 integration point: adding the 4 action-types as rows makes them immediately referenceable as proposed_action_code, with zero code change.
  • request_type_code → FK fk_apr_request_type → apr_request_types(request_code).
  • action CHECK ∈ {add, modify, delete, review} (default add).
  • status CHECK ∈ {pending, approved, applied, rejected, expired}.
  • priority CHECK ∈ {critical, high, normal, low}.
  • target_collection NOT NULL (chk_apr_target_collection).
  • jsonb carriers: current_state, proposed_action, alternative_actions; source_context (json) carries proposer/created_by.

4.2 apr_request_types (14 rows) — the "why" vocabulary

request_code (PK) + default_action_code (maps a request to a default action). E.g. fix_repair_dot → patch_ops_code. Most have default_action_code=null. Active codes include: accuracy_drift, birth_orphan, fix_repair_dot, general, new_dot, other, process_improvement, reclassify, retire, rule_change, schema_add, schema_delete, schema_modify, user_feedback. No governance-ownership request type exists (candidate reuse: rule_change/reclassify/general, or a new governed request type — secondary to SB-1).

4.3 The three approval-gate functions (verbatim logic) — decisive for SB-1

fn_auto_approve_add (BEFORE trigger):

IF NEW.action = 'add' AND NEW.status = 'pending' THEN
  NEW.status := 'approved';
  NEW.reviewed_by := 'system_auto_approve';
  ...

Any APR with action='add' is machine-approved on insert.

fn_apr_quorum_check (fires only on transition pending → approved):

IF NOT (NEW.status='approved' AND OLD.status='pending') THEN RETURN NEW; END IF;
... reads risk_level FROM apr_action_types WHERE action_code = NEW.proposed_action_code;
IF v_risk IS NULL THEN RETURN NEW; END IF;
-- any reject blocks; self-approve (proposer == approver) prohibited
IF v_risk='high'   THEN require president>=1 AND ai_council>=2
ELSIF v_risk='medium' THEN require president>=1
ELSIF v_risk='low'    THEN require approves_total>=1

Quorum is data-driven by apr_action_types.risk_level (the no-hardcode anchor); high = ≥1 president + ≥2 ai_council.

fn_apr_block_unimplemented_handler:

SELECT handler_ref INTO handler FROM apr_action_types WHERE action_code = NEW.proposed_action_code;
IF handler = 'unimplemented' THEN RAISE EXCEPTION '... Reserve-only, cannot execute.'; END IF;

⇒ Any APR referencing an action-type whose handler_ref='unimplemented' is blocked (fail-closed).

⚠️ The auto-approve bypass (failure mode to design around): fn_auto_approve_add is a BEFORE trigger that sets status:='approved' on INSERT. fn_apr_quorum_check requires OLD.status='pending', which does not exist on INSERT. Therefore an action='add' APR is auto-approved without quorum. Governance APRs must therefore use action ∈ {review, modify, delete} (never add) to force the pending → approved path that triggers quorum. (Hardening fn_auto_approve_add to exclude high-risk action-types is a T11 handler change, not in scope here.)

4.4 apr_approvals (42 rows) — the quorum vote spine

Columns: id, apr_id (FK→approval_requests.id ON DELETE RESTRICT), approver, approver_type, decision, rationale, created_at. CHECK approver_type ∈ {human, ai_council}, decision ∈ {approve, reject, abstain}, UNIQUE (apr_id, approver) (one vote per approver). Live high-risk pattern observed: president (human) + 2× ai_council (gpt/gemini) all approve (apr_id 211/219/220/232) — matches the fn_apr_quorum_check high rule. All 42 are S178 DOT-repair; zero governance/IU/axis/coverage approvals ever.

4.5 Quorum config table

nrm_approval_rules (3 rows) is keyed by document level, not action-type/risk:

doc_level approval_authority approval_method
1–2 council council_review
3–4 leadership apr_dieu32
5–6 department department_head

⇒ There is no per-action-type quorum config table. Per-action quorum lives in fn_apr_quorum_check keyed off risk_level. SB-1 must rely on risk_level (data) for quorum; the doc-level model in nrm_approval_rules is the law-approval path (relevant to enactment H-1, not to governance action execution).

4.6 Sovereign sign-off + commit-forbidden evidence

os_proposal_approvals = 0 (Directus e-signature collection: signature_text/image, proposal, email, …). No sovereign sign-off ever ⇒ COMMIT_FORBIDDEN for any live governance binding (SB-6 / H-2). admin_fallback_log = 22 (all S178). governance_audit_log = 1 (stale).


5. Interim governed-exception store reality (SB-1 / M-DEF-6 / C-2)

admin_fallback_log (22 rows) — the C-2 "interim exception store" default. Columns: id, dot_code (NOT NULL), reason (NOT NULL), backup_path (NOT NULL), patch_diff (NOT NULL), verify_evidence (jsonb NOT NULL), approved_by (default 'president'), session_code (NOT NULL), status (default 'applied'), retroactive_apr_id (FK→approval_requests), retroactive_deadline (default now()+24h), created_at. CHECK status ∈ {applied, retroactive_documented, audit_overdue, rolled_back}. Trigger trg_auto_apr_on_fallback (fn_auto_apr_on_fallback) auto-creates a retroactive APR on insert.

Mismatch finding (improves on the C-2 default): admin_fallback_log is DOT-repair-shaped — it forces dot_code, backup_path, patch_diff NOT NULL and its status vocabulary + auto-retroactive-APR are for emergency DOT fixes. It does not carry the M-DEF-6 11 governed-exception fields (exception_type, scope, accountable_owner, reason, risk, approval_ref, expiry, review_cadence, rollback_ref, replacement_plan, issue_on_expiry) and would force them into verify_evidence jsonb (lossy, unqueryable, semantic drift). See doc 16 §6 for the recommended resolution (governed-exception payload carried in the APR proposed_action jsonb under grant_governance_exception, with admin_fallback_log used only for genuine break-glass, not as the canonical exception register).


6. Audit + event substrate (integration, register-before-emit)

6.1 Audit paths

  • governance_audit_log (1 stale row): columns id, relation_id (FK→governance_relations.id), checked_at, checked_by, result, detail(json). The single row has relation_id=null, checked_by='codex-v6-alignment-review-2026-04-24', result='knowledge_reports_created' — not a real governance audit. Relation-scoped only → it cannot audit an object-ownership row in a new table (no object FK). ⇒ SB-2 ownership-change audit must use registry_changelog + event_outbox, not governance_audit_log.
  • registry_changelog (68,323 rows): generic entity-keyed audit — id, code, timestamp, entity_type, entity_code, entity_name, action, collection_name, changed_by, alert_level (NOT NULL), alert_detail, resolved (NOT NULL), resolved_by, resolved_at. Ideal reuse target for SB-1/SB-2 changes (entity_type='governance_object_ownership', entity_code=<object_ref>, action='assign_owner', changed_by=<DOT/apr>).

6.2 Event substrate (Điều 45, register-before-emit)

  • event_type_registry (40 rows): PK-ish (event_domain, event_type); columns event_domain, event_type, event_stream, delivery_lane, default_severity, description, active, created_at. Domains: iu(16 active), mother(9, 0 active), piece(6), staging(5), system(4/3 active). The only governance-named types are mother.governance.blocked / mother.governance.unblocked, both active=false. No governance / coverage / integrity / axis / owner / exception event domain exists (SB-4). ⇒ any SB-1/SB-2 event must be registered here first, under a GOV-SIV-owned governance/integrity domain, before emit.
  • event_outbox (Điều 45 emit, signal-not-data): id(uuid), event_domain, event_type, event_stream, delivery_lane (default immediate), event_severity, event_subject_table (NOT NULL), event_subject_ref, canonical_address (NOT NULL), actor_ref (NOT NULL), source_system (NOT NULL), correlation_id, payload_classification (default 'safe_metadata'), safe_payload (jsonb default '{}'), occurred_at, created_at. ⇒ governance events emit with event_subject_table='governance_object_ownership' (or apr_action_types), payload_classification='safe_metadata', no sensitive payload.

6.3 Issue substrate (SB-4)

system_issues.issue_type is free-text (no CHECK). Top types: template_gap(183,378 — anti-spam scale proof: coalesce_key+occurrence_count machinery already in use), null(5,033), thiếu_quan_hệ(606 — orphan), silent_fail(546), collection_onboarding_gap(345), dot_bug(170), kb_pg_sync_drift(86), hardcode_violation(11), thiếu_mã_định_danh(9), sai_lệch_dữ_liệu(2 — drift), apr_phantom_applied(1), … No governance/coverage/owner/anarchy/island/exception issue_type exists. Reuse thiếu_quan_hệ (orphan/OWNER_GAP) and sai_lệch_dữ_liệu (GOVERNANCE_SCHEMA_DRIFT); register new governance issue types under SB-4 (T7).

6.4 DOT coverage (SB-8)

dot_coverage_required (11 rows): domain, operation, tier. Already contains a governance.approval family: propose(A), execute(B), health(A) — the existing approval/apply DOT coverage the SB-1 action-types extend. Also birth.orphan/scan(A), birth.register/health(A), collection/*, monitoring.dot/*. No governance.coverage/classification/pivot/axis/iu rows yet (feeds T6, out of SB-1/SB-2 scope).


7. IU / axis substrate (context for assign_axis_owner + future-axis; SB-3/OP-B boundary)

  • information_unit (219 rows; 219 live; conformance_status='open' on all 219): columns include id(uuid), canonical_address, unit_kind, lifecycle_status, owner_ref (text, NOT NULL — FREE-TEXT, not FK), conformance_status (NOT NULL), identity_profile(jsonb), parent_or_container_ref. distinct owner_ref = 21 (21 ungoverned owner strings); distinct unit_kind = 2 (of 9). ⇒ OP-B: no GOV-IU, owner free-text, all conformance open. IU owner-binding is HELD.
  • iu_three_axis_envelope (216 rows): columns hardcode exactly 3 axis families — axis_a_doc_code/axis_a_sort_order/axis_a_section_code, axis_b_tags(jsonb)/axis_b_tags_by_source(jsonb), axis_c_parent_id/axis_c_depth/axis_c_ancestors[]/axis_c_ancestor_addresses[]. A 4th axis ⇒ ALTER TABLE. SB-3 confirmed: open-axis is concept-true, substrate-false at IU. Boundary note: SB-1's assign_axis_owner and SB-2's axis-ownership operate at the axis-as-governed-object (ownership) level and are independent of SB-3 (how axis values are stored). Owning an axis does not require the generic axis-value store; it requires only the SB-2 ownership row + a future Axis-Registry axis_code. SB-1/SB-2 therefore do not unblock or depend on SB-3.
  • pivot_definitions (37 rows): code, source_object, filter_spec, group_spec(jsonb — the grouping axis), metric_spec, registry_group, parent_code, is_active, …. Grouping dimensions live in group_spec; the future Axis Registry enumerates them. (Registries-Pivot integration = T9.)

8. Counts snapshot (read-only, 2026-06-01)

object count object count
apr_action_types 6 governance_registry 9
apr_request_types 14 governance_relations 8
approval_requests 211 governance_object_ownership absent
apr_approvals 42 governance_audit_log 1 (stale)
os_proposal_approvals 0 admin_fallback_log 22
nrm_approval_rules 3 registry_changelog 68,323
event_type_registry 40 (gov domain: absent) normative_registry 47
dot_coverage_required 11 law_jurisdiction 43
information_unit 219 (all conformance=open) iu_three_axis_envelope 216
meta_catalog 169 pivot_definitions 37
dot_tools 309

9. Feasibility assessment (per blocker)

SB-1 (action-types) SB-2 (object/axis ownership)
Gap real? YES — 4 codes absent (0 collisions) YES — relations CHECK ∈ {law,agency}; object/axis un-expressible; target table absent
Integration point exists? YES — approval_requests.proposed_action_code FK to apr_action_types; quorum keyed to risk_level; fail-closed via handler_ref='unimplemented' YES — owner-agency FK target = governance_registry(code); audit via registry_changelog; object vocab via meta_catalog
Additive (no migration risk)? YES — INSERT rows only; reuses reserved-action pattern (amend_law/enact_nrm) YES — new table + new reference table; does not widen the relations CHECK; the 8 live edges untouched
No-hardcode anchor? quorum=risk_level (data); handler=handler_ref (data) object_type/scope/owner resolve to registries (meta_catalog / scope ref / governance_registry)
Live-apply blocker? machine auto-approve bypass (action='add') + COMMIT_FORBIDDEN (os_proposal_approvals=0) + C-2 ruling COMMIT_FORBIDDEN + C-1 ruling + handler to write the row
Design now? / Implement now? Design YES (T3) / Implement NO Design YES (T4) / Implement NO

Channel reality: query_pg is read-only and AST-rejects DDL/DML, so a live BEGIN..ROLLBACK rehearsal cannot run through the available tool; see doc 19 for the author-mode paste-ready rehearsal + static additivity proof + operator-handoff posture.

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/18-sb1-sb2-live-pg-evidence.md