18 — SB-1/SB-2 Live PG Evidence (read-only, zero mutation, 2026-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:directuson contabo VPS, containerpostgres. 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 lawprompt-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 reserved — handler_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_type→CHECK (source_type IN ('law','agency'))chk_relations_target_type→CHECK (target_type IN ('law','agency'))chk_relations_discovery→CHECK (discovery_source IN ('pg_catalog','manual','dot_scan'))uq_relations_source_target_type→UNIQUE (source_code, target_code, relation_type)governance_relations_pkey→PRIMARY 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:
-
governance_registry(9 rows) — the agencies/mothers. PK =code. CHECKstatus ∈ {active, draft, retired}. FKcreated_by_law → normative_registry(code), FKdomain → 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). -
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. -
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_code→ FKfk_apr_action_type → apr_action_types(action_code)ON UPDATE/DELETE RESTRICT — this is the SB-1 integration point: adding the 4 action-types as rows makes them immediately referenceable asproposed_action_code, with zero code change.request_type_code→ FKfk_apr_request_type → apr_request_types(request_code).actionCHECK ∈{add, modify, delete, review}(defaultadd).statusCHECK ∈{pending, approved, applied, rejected, expired}.priorityCHECK ∈{critical, high, normal, low}.target_collectionNOT NULL (chk_apr_target_collection).- jsonb carriers:
current_state,proposed_action,alternative_actions;source_context(json) carriesproposer/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): columnsid, relation_id (FK→governance_relations.id), checked_at, checked_by, result, detail(json). The single row hasrelation_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 useregistry_changelog+event_outbox, notgovernance_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); columnsevent_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 aremother.governance.blocked/mother.governance.unblocked, bothactive=false. Nogovernance/coverage/integrity/axis/owner/exceptionevent domain exists (SB-4). ⇒ any SB-1/SB-2 event must be registered here first, under a GOV-SIV-ownedgovernance/integritydomain, 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 withevent_subject_table='governance_object_ownership'(orapr_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 includeid(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: noGOV-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'sassign_axis_ownerand 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 ingroup_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.