84 — SB-1 Fail-Closed / Trigger / Quorum Analysis (live-verified, no APR created, 2026-06-01)
84 — SB-1 Fail-Closed / Trigger / Quorum Analysis
Branch B (mission §5). Tier: live read-only verification of the approval-path triggers/functions + design analysis. Mutation footprint: ZERO —
pg_get_functiondef/pg_get_triggerdefread-only viaquery_pg; noapproval_requests/apr_approvalsrow was created (mission §14). Trigger/quorum behaviour is proven from authoritative live source, not by inserting an APR. Base: doc 16 §2–3, doc 27 (re-verified verbatim 2026-06-01). This doc freshens that evidence for the final bundle and ties it to the SB-1 rehearsal (doc 83).
84.1 The existing auto-approve path (live, verbatim)
approval_requests has 6 triggers (re-verified 2026-06-01):
| Trigger | Timing / event | When-clause | Function |
|---|---|---|---|
trg_approval_auto_code |
BEFORE INSERT | — | fn_approval_auto_code |
trg_apr_auto_approve |
BEFORE INSERT | — | fn_auto_approve_add ← the bypass |
trg_apr_quorum_check |
BEFORE UPDATE OF status | WHEN new.status='approved' AND old.status='pending' |
fn_apr_quorum_check |
trg_apr_block_unimplemented |
BEFORE UPDATE | WHEN new.status='applied' AND old.status<>'applied' |
fn_apr_block_unimplemented_handler |
trg_apr_lifecycle |
BEFORE UPDATE OF status | — | fn_enforce_apr_lifecycle |
trg_birth_approval_requests |
AFTER INSERT | — | fn_birth_registry_auto('code') |
fn_auto_approve_add (doc 27 §1.1, verbatim): on NEW.action='add' AND NEW.status='pending' it sets NEW.status:='approved', reviewed_by:='system_auto_approve' inside the INSERT. approval_requests.action CHECK ∈ {add, modify, delete, review} with DEFAULT 'add'.
fn_apr_block_unimplemented_handler (re-verified live 2026-06-01, verbatim):
IF NEW.proposed_action_code IS NULL THEN RETURN NEW; END IF;
SELECT handler_ref INTO handler FROM apr_action_types WHERE action_code = NEW.proposed_action_code;
IF handler = 'unimplemented' THEN
RAISE EXCEPTION 'Action % has handler_ref=unimplemented. Reserve-only, cannot execute.', NEW.proposed_action_code;
END IF;
RETURN NEW;
84.2 Why action='add' is unsafe (the bypass mechanics)
The quorum gate (fn_apr_quorum_check) fires only on the pending → approved UPDATE transition. fn_auto_approve_add sets status:='approved' on INSERT — so an action='add' APR is inserted already approved; it never sits at pending; therefore the pending→approved UPDATE never happens and trg_apr_quorum_check never fires. Result: approved with zero quorum votes, no apr_approvals rows, and no self-approve check. The handler_ref='unimplemented' block fires only on the later →applied UPDATE, so in Phase A it still prevents execution, but it does not prevent approval-without-quorum, and it offers no protection once Phase B flips a handler.
The dangerous value is the default. action defaults to 'add'; an APR inserted without explicitly setting action is auto-approved. The four governance action-types are all risk_level='high' (proven in doc 83: all 4 inserted rows were high) → they require president + ≥2 ai_council quorum + a self-approve prohibition, all of which live only on the pending→approved path. Submitting a governance change as action='add' would grant it with no quorum, no self-approve check, and (Phase B) executable — the precise opposite of the intended high-risk routing.
Risk scenarios (doc 27 §3): R1 default-trap (high), R2 Phase-B unquorumed execution (critical), R3 approval-record pollution (high), R4 self-approve evasion (high).
84.3 Safe action convention (load-bearing Phase-A safety)
Every governance APR is submitted with action='review' (explicitly; modify/delete also acceptable — anything except add). This is a fixed protocol constant of the Điều-32 contract (like an HTTP method), not a discovered datum → not a no-hardcode violation. The dot_governance_gap_propose DOT (doc 25) sets action='review' by contract and is paired-tested to refuse action='add'. Two independent Phase-A safeties: (a) action≠'add' (forces the quorum path — protects the approval; load-bearing for R1/R3/R4) and (b) handler_ref='unimplemented' (blocks execution at →applied — protects the apply).
84.4 Phase A cannot execute ownership changes (fail-closed proof)
In Phase A all four governance action-types carry handler_ref='unimplemented' (proven additive in doc 83). Any APR that reaches the →applied transition with one of these proposed_action_codes is RAISEd by fn_apr_block_unimplemented_handler (Reserve-only, cannot execute.). This is identical to how amend_law and enact_nrm sit live today (high/unimplemented). Therefore registering the SB-1 vocabulary (Phase A) creates ZERO apply capability — no owner row, no exception, no delegation, no axis-owner can be written by these action-types until Phase B builds a real handler and flips handler_ref (itself an APR).
84.5 Phase B remains NO-GO
Flipping any governance handler_ref from 'unimplemented' to a real ref requires ALL of: (a) C-2 council ratification (recorded, not GPT-delegated); (b) the handler exists as a governed DOT writing the canonical SB-2 target (governance_object_ownership), not a private table; (c) SB-2 built (owner-write target exists); (d) sovereign sign-off path — os_proposal_approvals currently 0 ⇒ COMMIT_FORBIDDEN (M-1); (e) dot_coverage_required rows for the new propose/apply ops. No gate may be self-approved. The mutating apply DOT (dot_governance_assignment_apply) is G-APPLY = do-not-build until A-9 (H-1/H-2/SB-6) sovereign sign-off exists.
84.6 No self-approval / no event emit / no hidden handler activation
- No self-approval: the prohibition lives in
fn_apr_quorum_check(proposer==approver blocked). Because governance APRs useaction≠'add'they traversepending→approved, so the check fires. (Theaction='add'evasion R4 is closed by the convention + future H-OPT-1/4.) - No event emit: this mission registered no
event_type_registrygovernance row and emitted nothing —event_outboxgovernance = 0 pre/post (doc 91). SB-1 emits (governance.owner.assigned, …) are register-before-emit, deferred to T7 build. - No hidden handler activation: the rehearsal's in-txn trigger DDL (the F-83-1 fix) was fully reversed by ROLLBACK (doc 83 §83.3); no
handler_refwas flipped;apr_action_typesis byte-for-byte at entry state (doc 91).
84.7 Hardening recommendations (T11-gated, design-only — carried from doc 27)
Unchanged and reaffirmed: H-OPT-4 (data-driven apr_action_types.auto_approvable boolean DEFAULT false allowlist gating fn_auto_approve_add) as the cleanest no-hardcode mechanism, paired with H-OPT-2 (change approval_requests.action default from 'add' to 'review', or drop the default). H-OPT-1 (risk-aware guard: never auto-approve risk∈{high,medium}) is the direct risk-aware fix. Per-action quorum config table = not warranted (quorum already data-driven off risk_level). All are DDL/function changes → forbidden in this macro; each needs its own council awareness + BEGIN..ROLLBACK rehearsal + H-1 enact path before any commit.
84.8 Verdict
Branch B = COMPLETE. The auto-approve path, the action='add' bypass mechanics (BEFORE-INSERT approve vs BEFORE-UPDATE quorum), the safe action='review' convention, Phase-A non-executability (handler_ref='unimplemented' → fn_apr_block_unimplemented_handler RAISE), Phase-B NO-GO gating, and the no-self-approval / no-emit / no-hidden-handler guarantees are all verified against live source (2026-06-01) and tied to the doc-83 rehearsal. No APR row was created; no mutation occurred.