KB-4F11

27 — Auto-Approve (action='add') Bypass Hardening Risk Note (design/analysis-only, no mutation, 2026-06-01)

14 min read Revision 1
one-roof-governanceimplementation-indexauto-approve-bypassfn-auto-approve-addaction-addquorum-bypassfn-apr-quorum-checktrigger-timingbefore-insertdefault-trapaction-review-conventionhardeningauto-approvable-allowlistno-mutationt11-gatedrisk-note2026-06-01

27 — Auto-Approve (action='add') Bypass Hardening Risk Note

Path: knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/ Doc: 27. Track: Branch E. Builds on doc 16 §3.2, doc 18 §4.3, GPT review (auto-approve risk flag). Status: RISK ANALYSIS + HARDENING DESIGN ONLY. NO MUTATION. No code is changed, no trigger altered, no data written, no APR created. query_pg is read-only and AST-rejects DDL/DML; all "tests" below are author-mode BEGIN..ROLLBACK rehearsal plans for an operator, never executed here. Evidence base: re-verified read-only against DB directus (contabo postgres) on 2026-06-01 — full function source and trigger definitions in §1.


1. Live behavior evidence (re-verified verbatim, 2026-06-01)

1.1 The auto-approve function — fn_auto_approve_add

CREATE OR REPLACE FUNCTION public.fn_auto_approve_add() RETURNS trigger AS $$
BEGIN
  IF NEW.action = 'add' AND NEW.status = 'pending' THEN
    NEW.status     := 'approved';
    NEW.reviewed_by := 'system_auto_approve';
    NEW.reviewed_at := NOW();
    NEW.review_note := 'Auto-approved: action=add (low risk, reversible)';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

1.2 The trigger wiring on approval_requests (6 triggers, verbatim)

Trigger Timing / event When-clause Function
trg_approval_auto_code BEFORE INSERT fn_approval_auto_code (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')

1.3 The quorum function — fn_apr_quorum_check (key guard, verbatim)

-- Only fire on transition pending -> approved
IF NOT (NEW.status = 'approved' AND OLD.status = 'pending') THEN RETURN NEW; END IF;
... v_risk := risk_level FROM apr_action_types WHERE action_code = NEW.proposed_action_code;
-- any reject blocks; self-approve (proposer==approver) prohibited;
-- high => president>=1 AND ai_council>=2 ; medium => president>=1 ; low => total>=1

1.4 Column facts

  • approval_requests.action CHECK ∈ {add, modify, delete, review} with DEFAULT 'add'.
  • approval_requests.status CHECK ∈ {pending, approved, applied, rejected, expired}.
  • proposed_action_code FK → apr_action_types(action_code).

2. Exact bypass mechanics (the trigger interaction)

The quorum gate (fn_apr_quorum_check) is wired as a BEFORE UPDATE trigger that only fires on the pending → approved status transition. The auto-approve (fn_auto_approve_add) is a BEFORE INSERT trigger that sets NEW.status := 'approved' inside the INSERT itself.

Therefore, for an APR submitted with action='add':

  1. The row is inserted already at status='approved' (set by the BEFORE INSERT trigger).
  2. There is no pending → approved UPDATE — the row never sat at pending. So trg_apr_quorum_check never fires.
  3. Result: the APR reaches approved with zero quorum votes, no apr_approvals rows, and no self-approve checkreviewed_by='system_auto_approve'.

The handler_ref='unimplemented' block (trg_apr_block_unimplemented) fires only on the later → applied UPDATE — so in Phase A it still prevents execution of a reserved-handler action even if the APR was auto-approved. But it does not prevent the approval-without-quorum itself, and it offers no protection once Phase B flips the handler to a real ref.

The dangerous value is the default. action defaults to 'add'. An APR inserted without explicitly setting action is auto-approved. The safe path (action ∈ {review, modify, delete}) must be explicitly chosen; forgetting it lands on the bypass.


3. Risk scenarios

ID Scenario Mechanism Severity
R1 — default trap A governance APR is inserted without explicitly setting action → defaults to 'add' → auto-approved on INSERT, no quorum. column DEFAULT 'add' + BEFORE INSERT auto-approve high (latent; the unsafe path is the default)
R2 — Phase-B unquorumed execution After Phase B flips a governance action-type's handler_ref to a real handler, an action='add' governance APR auto-approves (no quorum) and can be applied → an owner-assignment / exception / delegation executes without president+2-council quorum. bypass + implemented handler critical
R3 — approval-record pollution Even in Phase A, action='add' governance APRs appear approved by system_auto_approve with zero apr_approvals votes — polluting the audit trail and potentially satisfying naive "is it approved?" checks downstream. bypass produces status='approved' high
R4 — self-approve evasion The self-approve prohibition lives only in fn_apr_quorum_check (the UPDATE path). An action='add' APR skips it → a proposer could self-grant. bypass skips quorum fn high

These are exactly why SB-1 (doc 16) flagged the bypass and why governance APRs must not use action='add'.


4. Why governance actions must NOT use action='add'

Governance action-types (assign_governance_owner, grant_governance_exception, delegate_authority, assign_axis_owner) are all risk_level='high' — they require president + ≥2 ai_council quorum and a self-approve prohibition. That enforcement lives only on the pending → approved UPDATE path. action='add' short-circuits that path entirely (approved on INSERT). Submitting a governance change as action='add' would therefore grant the change with no quorum, no self-approve check, and (Phase B) executable — the precise opposite of the high-risk routing the design depends on. Governance APRs must traverse pending → approved so the quorum trigger fires.


  • 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 — so it is not a no-hardcode violation.
  • The dot_governance_gap_propose DOT (doc 25 §6.3) sets action='review' by contract and is paired-tested to refuse action='add'.
  • Until the substrate hardening (§6) ships, the two independent Phase-A safeties are: (a) the action≠'add' convention (forces the quorum path), and (b) handler_ref='unimplemented' (blocks execution at the → applied step). Note (a) protects the approval; (b) protects only the apply — so (a) is the load-bearing one for R1/R3/R4.

6. Future hardening options (substrate; T11-gated; design only)

Ordered by recommendation. Each is proposed, not applied.

  • H-OPT-1 — risk-aware auto-approve guard (primary). Modify fn_auto_approve_add to auto-approve only low-risk, non-governance actions. Proposed predicate:
    -- inside fn_auto_approve_add, before flipping to 'approved':IF NEW.action = 'add' AND NEW.status = 'pending' THEN   SELECT risk_level INTO v_risk FROM apr_action_types WHERE action_code = NEW.proposed_action_code;   IF NEW.proposed_action_code IS NULL OR v_risk = 'low' THEN      NEW.status := 'approved'; NEW.reviewed_by := 'system_auto_approve'; ...   END IF;   -- high/medium-risk actions are NEVER auto-approved, even with action='add'END IF;
    
    Closes R2 and most of R1/R3/R4 for governance (and patch_ops_code/amend_law/enact_nrm, which are also high/medium).
  • H-OPT-2 — fail-safe column default. Change approval_requests.action default from 'add' to 'review' (or drop the default → force an explicit choice). Closes the default trap (R1) at the source. (A surgical-but-semantic change — requires council awareness because it alters submission ergonomics for all APR producers; not a §4G silent drift patch.)
  • H-OPT-3 — defensive block trigger (belt-and-suspenders). Add a BEFORE INSERT trigger that RAISES if a governance-family proposed_action_code is submitted with action='add' — fail-closed at INSERT regardless of the auto-approve logic.
  • H-OPT-4 — data-driven auto-approvable allowlist (cleanest, no-hardcode). Add apr_action_types.auto_approvable boolean DEFAULT false; gate auto-approve on … AND (SELECT auto_approvable FROM apr_action_types WHERE action_code = NEW.proposed_action_code). Makes "what may auto-approve" data in the same registry that drives quorum — no risk-tier literal in code, future-proof for new action-types. (Recommended pairing: H-OPT-4 as the mechanism + H-OPT-2 as the fail-safe default.)
  • H-OPT-5 — (assessed, not recommended) per-action-type quorum config table. See §7.

All of the above are DDL/function changes → forbidden in this macro; each requires its own council awareness, a BEGIN..ROLLBACK rehearsal, and the H-1 enact path before any commit.


7. Per-action-type quorum config — assessment (is it warranted?)

Not warranted. Quorum is already data-driven off apr_action_types.risk_level (fn_apr_quorum_check), and the design sets every governance action-type to risk_level='high' (council may pick medium for display-only axes). A separate per-action quorum table would add a parallel config surface (a mini-island) without adding expressive power the risk_level model lacks. Recommendation: keep the risk_level-driven quorum model unchanged. The real gap is not the quorum model — it is the auto-approve bypass, which H-OPT-1/H-OPT-4 close directly.


8. Tests (author-mode BEGIN..ROLLBACK rehearsal plan — NOT executed)

To be run by the operator in a transaction that is always rolled back (doc 19 style; query_pg cannot run these — they are author-mode). All assert behavior, commit nothing.

Test Setup Expected (current) Expected (after H-OPT-1/4)
T-1 Insert governance APR action='review', proposed_action_code='assign_governance_owner' row stays status='pending' unchanged
T-2 Then UPDATE … SET status='approved' with no apr_approvals votes fn_apr_quorum_check RAISES (high needs president+2 council) unchanged
T-3 Insert governance APR action='add' (demonstrate bypass) auto-approved on INSERT, no quorum (BUG) NOT auto-approved (risk='high') → stays pending
T-4 Insert governance APR with no action (default trap) defaults to 'add' → auto-approved (BUG) with H-OPT-2: defaults to 'review'pending; with H-OPT-1: not auto-approved
T-5 Approved governance APR, UPDATE status='applied' while handler_ref='unimplemented' fn_apr_block_unimplemented_handler RAISES (Phase-A safety holds) unchanged
T-6 Proposer self-approves via action='add' bypass evades self-approve check (BUG) blocked (never auto-approved)

Each rehearsal: BEGIN; … assertions …; ROLLBACK; — entry/exit row counts identical; zero net mutation.


9. No-mutation attestation & gates

  • No mutation occurred. This doc is read-only analysis + design. No function/trigger/column/row was changed. The function and trigger source above were obtained via read-only SELECT pg_get_functiondef(...) / pg_get_triggerdef(...) as context_pack_readonly.
  • Gates: any hardening (H-OPT-1..4) is a substrate change → NO-GO until a T11 build authorization + council awareness (H-OPT-2 changes submission ergonomics) + BEGIN..ROLLBACK rehearsal + H-1 enact path. No gate may be self-approved.
  • Interim protection in force regardless: the action='review' convention (§5) + handler_ref='unimplemented' (Phase A) keep governance safe until the hardening ships; the hardening is the durable fix for Phase B (R2).

10. Verdict

Auto-approve hardening risk note: COMPLETE (Branch E). Live behavior is re-verified verbatim (function source + exact trigger timing: BEFORE INSERT auto-approve vs BEFORE UPDATE quorum); the bypass mechanics, four risk scenarios (incl. the critical Phase-B execution risk R2 and the default-trap R1), the reason governance must avoid action='add', the interim action='review' convention, five hardening options (recommended: H-OPT-4 allowlist + H-OPT-2 fail-safe default, with H-OPT-1 as the direct risk-aware guard), the per-action-quorum assessment (not warranted), and an author-mode BEGIN..ROLLBACK test plan are all specified. No mutation, no code change, no APR. Next: doc 28 (bundle integration/readiness).

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/27-auto-approve-hardening-risk-note.md