KB-1864

02 — Approval / Authorization State Machine (hardened, build-ready)

22 min read Revision 1
one-roof-governanceaxisauthorizationproposalhardeningbuild-readydesign-onlyread-only2026-06-02state-machine

02 — Approval / Authorization State Machine (hardened, build-ready)

Package: one-roof-axis-auth-proposal-operational-hardening-build-ready-design-2026-06-02 Mode: DESIGN ONLY · READ-ONLY · NO COMMIT · NO MUTATION Hardens: prior …-2026-06-01/02-authorization-model-technical-vs-sovereign.md (the L0–L4 ladder sketch). Closes GPT review gap #1: "Authorization L0–L4 needs hardening into exact state machine, required records, anti-forgery proof, TTL/revocation, and verifier algorithm." Companion: doc 03 (the CREATE-TABLE-level substrate). This doc is the behaviour; doc 03 is the storage.


2.0 The problem this state machine solves ("one gate for two jobs")

The legacy master gate M-1 = "≥1 row in os_proposal_approvals scoping this step". Live evidence (doc 01 §1.4; Phase-1 index doc 114 §114.3): os_proposal_approvals is a Directus human e-signature collection in a sales module — 0 rows, columns signature_text/signature_image/first_name/last_name/email/ip_address/esignature_agreement/proposal. It captures a real person physically e-signing. An agent writing it = forging the sovereign's signature.

Using it as the generic COMMIT key conflates two different jobs:

  1. Technical build authorization — "may this reversible engineering step be committed?" (risk × reversibility; agent-or-human; per-step; revocable).
  2. Sovereign approval — "does the President personally assent to this irreversible/constitutional act?" (one human, legally binding, durable).

The hardened model separates them across five levels L0–L4 and gives each a distinct, verifiable, non-forgeable truth record. The state machine below governs the lifecycle of any authorization request through those levels.


2.1 The L0–L4 ladder (hardened)

Levels are assigned by risk × reversibility, not by who runs the step. (An agent and a human running the same low-risk reversible step are both L0.)

Level Trigger (risk × reversibility) Who may REQUEST APPROVER of record Agent may approve? Human approval mandatory? Sovereign e-sign mandatory? Truth store (live mechanism)
L0 Machine low-risk low risk and fully reversible and action in governed allowlist agent / human / DOT automatic (governed allowlist) Yes (allowlist lane only) No No approval_requests(auto) + apr_approvals(approver_type='system'). Live: fn_auto_approve_add lane — after the P5 hardening (allowlist + fail-safe deny), not the current action='add' trap
L1 Owner-approved technical medium risk, reversible (e.g. add_field) agent / human exactly one named human technical owner, approver ≠ proposer No Yes (1 human) No approval_requests + apr_approvals (1 human approve), quorum medium
L2 Council-approved governance high risk, governed decision (ownership, policy, vocabulary, law-adjacent) agent / human council quorum per Điều 32 (high ⇒ ≥1 president + ≥2 ai_council) No (vote only, never self-decide) Yes (quorum) No (unless escalated) approval_requests + apr_approvals quorum (fn_apr_quorum_check PASS) + governance_audit_log
L3 High-risk build / deploy (COMMITs substrate) high risk, commits/alters substrate (DDL, governed rows, deploy) agent / human (agent may prepare draft) L2 council quorum + named build owner, recorded in a build-auth row (granted_by ≠ build agent) No (may prepare draft; cannot set active) Yes (via the L2 quorum behind it) No — unless a sovereignty trigger flips requires_sovereign_esign=true governance_build_authorization (NEW, doc 03) backed by L2
L4 Sovereign e-sign irreversible / constitutional / authority-model change / enact_nrm/amend_law / blast-radius over governed ceiling human (President) the President, personally Never n/a Yes human e-sign collection — interim os_proposal_approvals (sovereign-only, agent-forbidden); target a purpose-built governance sovereign e-sign collection (doc 12 patch)

Level-assignment is computed, not declared. approval_requests carries a computed sovereignty_required (see §2.5 escalation triggers); the level is a function of apr_action_types.risk_level for the row's proposed_action_code plus the reversibility/blast-radius of proposed_action. No hardcoded per-object level lists (Điều 37 no-hardcode).


2.2 The unified authorization lifecycle state machine

One state machine covers all levels; higher levels add states (L0/L1/L2 stop at APPROVED/APPLIED; L3 adds the grant → consume segment; L4 adds the sovereign e-sign dependency).

                          ┌─────────────┐
                          │   DRAFTED   │  (agent/human authors approval_requests row;
                          └──────┬──────┘   action='review' — NEVER 'add'; status='pending')
                                 │ submit
                                 ▼
                          ┌─────────────┐    reject (any reviewer) / quorum-fail
                          │   PENDING   │───────────────────────────────┐
                          │ (under      │                                │
                          │  review)    │◄──── vote (apr_approvals)      │
                          └──────┬──────┘                                ▼
              quorum/owner PASS  │                              ┌────────────────┐
              (fn_apr_quorum_    │                              │    REJECTED    │ (terminal)
               check on          ▼                              └────────────────┘
               pending→approved) ┌─────────────┐
                                 │  APPROVED   │  L0/L1/L2 decisions END here (the *decision*)
                                 └──────┬──────┘
                                        │  L3 only: build owner grants a scoped authorization
                                        │  (granted_by ≠ build agent)  → governance_build_authorization
                                        ▼
            sovereignty trigger?  ┌─────────────┐   no e-sign within TTL / revoked
        ┌───────yes──────────────│   GRANTED   │──────────────────────────┐
        ▼                        │ (status=    │                          │
 ┌──────────────┐  e-sign        │  active,    │◄── revoke (granted_by/   │
 │ AWAITING_    │  valid         │ commit_     │     council, anytime)    │
 │ SOVEREIGN    │───────────────►│ allowed=t)  │                          │
 │ (L4 e-sign   │                └──────┬──────┘                          │
 │  referenced) │                       │ COMMIT (verifier PASS;          │
 └──────────────┘                       │ single-use; consumed_at set)    ▼
                                         ▼                          ┌────────────┐
                                  ┌─────────────┐                   │  EXPIRED   │ (terminal)
                                  │  CONSUMED   │ (terminal,        │  REVOKED   │ (terminal)
                                  │  one COMMIT │  audit-complete)  └────────────┘
                                  └─────────────┘

Transition table (guards · actor · records written · anti-forgery check)

# From → To Guard Actor Records written Anti-forgery invariant enforced
T1 ∅ → DRAFTED proposed_action_code exists in apr_action_types; action='review' (≠'add') requester (agent ok) approval_requests(status='pending') INV-2 (no auto-approve bypass)
T2 DRAFTED → PENDING implicit (row is pending) system
T3 PENDING → REJECTED any reviewer casts reject reviewer apr_approvals(decision='reject') INV-3 (reject is sticky)
T4 PENDING → APPROVED quorum for risk_level met; proposer ∉ approvers owner/council apr_approvals(approve…); approval_requests.status='approved' INV-1 (no self-approval), INV-4 (quorum recomputed by trigger)
T5 APPROVED → GRANTED (L3) build owner issues build-auth; granted_by ≠ build agent; backing quorum PASS; rollback plan present build owner (not the build agent) governance_build_authorization(status='active', commit_allowed=true) INV-5 (granter ≠ executor), INV-6 (grant inert without backing quorum)
T5a GRANTED → AWAITING_SOVEREIGN (L4 needed) sovereignty_required=true system governance_build_authorization.requires_sovereign_esign=true INV-7 (sovereign-only e-sign)
T5b AWAITING_SOVEREIGN → GRANTED valid L4 e-sign row referenced President (out-of-band) …sovereign_esign_ref set; valid_sovereign_esign() true INV-7, INV-8 (agent may only READ e-sign)
T6 GRANTED → CONSUMED verifier PASS at COMMIT; single-use build agent (under grant) governance_build_authorization.consumed_at/by; registry_changelog; governance_audit_log INV-9 (single-use), INV-10 (verifier reads v_build_auth_valid, not raw status)
T7 GRANTED → EXPIRED now() > expires_at and not consumed system status='expired' (lazy/by sweep) INV-11 (TTL)
T8 GRANTED/AWAITING → REVOKED revoke issued granter / council revoked_at/by/reason; status='revoked' INV-12 (revocable anytime before consume)

L0 fast-path: for an allowlisted low-risk reversible action, T1→T4 collapses into a single INSERT that lands status='approved' with apr_approvals(approver_type='system')only if the action is in the governed allowlist and risk_level='low' (the P5-hardened fn_auto_approve_add; see INV-2). There is no L0 grant/consume segment because L0 does not COMMIT substrate.


2.3 Required records per level (the "what must exist" contract)

A verifier (human or agent) can confirm authorization only by the presence of these exact records. Absence = not authorized = COMMIT_FORBIDDEN.

Level approval_requests apr_approvals governance_build_authorization e-sign collection Verifier reads
L0 1 row, status='approved', action in allowlist, risk_level='low' 1 row approver_type='system' allowlist membership + the auto row
L1 1 row, status='approved', proposed_action_code risk='medium' ≥1 approve, approver ≠ proposer status='approved' + approver≠proposer
L2 1 row, status='approved', risk='high' quorum: ≥1 president + ≥2 ai_council approve, 0 reject, proposer ∉ approvers fn_apr_quorum_check PASS
L3 1 row (the L2 request) the L2 quorum 1 row status='active', commit_allowed=true, in TTL, consumed_at IS NULL, granted_by≠agent v_build_auth_valid (doc 03)
L4 1 row the L2 quorum 1 row, requires_sovereign_esign=true, sovereign_esign_ref set 1 sovereign-signed row scoping this act (signature fields populated, registered sovereign identity) valid_sovereign_esign(sovereign_esign_ref) (READ only)

2.4 Anti-forgery proof (invariants and why each forgery attempt fails)

The model is "non-forgeable" iff every path to a COMMIT requires a record that a single agent cannot fabricate. Below, each invariant pairs the mechanism with the attack it defeats.

  • INV-1 No self-approval. fn_apr_quorum_check derives the proposer from source_context and excludes them from the approver set. Attack: agent proposes and approves its own L2 request → fails: its own vote is not counted toward quorum; quorum unreachable alone.
  • INV-2 No auto-approve bypass. Every governance/build APR uses action='review'. Attack: set action='add' so fn_auto_approve_add (BEFORE INSERT) flips it to approved, skipping quorum (the live "dangerous default" trap, doc 01 §1.3). Defeated by: mandatory action≠'add' convention plus the P5 hardening that restricts auto-approve to (allowlisted AND risk_level='low') with fail-safe deny. A high-risk action can never enter the auto lane.
  • INV-3 Reject is sticky. Any reject blocks the pending→approved transition for that request.
  • INV-4 Quorum recomputed at the gate. Quorum is enforced by a BEFORE UPDATE trigger reading live apr_approvals + apr_action_types.risk_level, not by a writable status column. Attack: directly UPDATE approval_requests SET status='approved'fails: the trigger recomputes and RAISES if quorum absent.
  • INV-5 Granter ≠ executor. governance_build_authorization.granted_by must differ from the identity that consumes the grant. Attack: build agent grants itself L3 → fails: verifier rejects granted_by = consuming agent.
  • INV-6 Grant inert without backing quorum. Validity is recomputed from the backing L2 quorum via v_build_auth_valid (doc 03 §3.2), never asserted by the row's own status. Attack: INSERT a governance_build_authorization row with status='active', commit_allowed=true but no real approved request behind it → fails: v_build_auth_valid joins to approval_requests/apr_approvals, finds no passing quorum, returns the row as invalid; the verifier consults the view, never the raw status. (Hardening rule: NO consumer path may read governance_build_authorization.status directly to decide a COMMIT — all consumers MUST use v_build_auth_valid / fn_build_commit_allowed. doc 03 §3.4 makes this a tested invariant.)
  • INV-7 Sovereign e-sign is human-only. L4 rows are written only by the President through the Directus e-sign flow, out-of-band. The constraint is governance, not transport: even though no DB constraint can tell a forged signature from a real one, the governance rule forbids any agent from writing the collection, and the read channel an agent has (query_pg) is read-only.
  • INV-8 Agent reads, never writes, e-sign. Build/runtime agents may only valid_sovereign_esign()-check (SELECT) an e-sign row; writing one is forbidden and is the single act the model never delegates (see bootstrap paradox §2.7).
  • INV-9 Single-use. consumed_at is set in the same COMMIT txn that performs the step; a consumed grant fails the verifier thereafter. Attack: replay one grant across many COMMITs → fails: consumed_at IS NOT NULL ⇒ invalid.
  • INV-10 Verifier uses the view. The COMMIT-time check calls fn_build_commit_allowed / v_build_auth_valid, which recompute everything; the raw status flag is advisory display only.
  • INV-11 TTL. Grants carry expires_at (default 24–72h); past it the grant is invalid even if active.
  • INV-12 Revocable. A granter or the council may set revoked_at on any un-consumed grant; revoked ⇒ invalid immediately.

Forgery-resistance summary: to force a COMMIT an actor needs either (a) a real passing L2 quorum it did not solely cast (INV-1/4/6), or (b) for sovereign acts, a real human signature it physically cannot produce (INV-7/8). No single agent can manufacture either. The model is non-forgeable under the stated governance + read-only-transport assumptions.


2.5 TTL / expiry / revocation / escalation semantics

  • TTL (L3): granted_at + window (expires_at, default 24–72h, set per risk_level). After expiry → EXPIRED (terminal); a new step needs a new request+grant. L1 carries a configured apply window (apply within N days of reviewed_at). L2 decisions don't expire (the decision is durable) but their L3 grant does. L4 e-sign is a durable legal record but its authority is scoped to one named act; reuse for another act is invalid.
  • Single-use: consumed_at/consumed_by set at COMMIT; one grant ⇒ one COMMIT.
  • Revocation: revoked_at/by/reason may be set by the granter or council on any active/awaiting grant before consume. Revocation is immediate (verifier rejects). No cascade is needed because a grant maps to exactly one un-started step; if a multi-step plan must be cancelled, each step's grant is revoked individually (per-step authorization, no blanket grant).
  • Escalation L3→L4 (sovereignty triggers, governed rows not literals): enact_nrm/amend_law/any law status change; irreversible/destructive op; change to the authority model itself (editing the L0 allowlist top tier or quorum rules); blast radius beyond the governed ceiling (touches birth_registry truth, or > N production objects). When any trigger is true, approval_requests.sovereignty_required=truerequires_sovereign_esign=true; commit_allowed is not honored until sovereign_esign_ref resolves valid.

2.6 The COMMIT-time verifier algorithm

Every build/runtime agent runs this before any COMMIT (design-only; the PG function is specified in doc 03 §3.4):

function authorize_commit(step_name, action_code, agent_identity) -> {ALLOW | DENY, reason}:
    level := classify_level(action_code, proposed_action)      # risk × reversibility
    switch level:
      L0:  return ALLOW iff action_code ∈ governed_allowlist
                          AND risk_level(action_code) = 'low'
                          AND handler_implemented(action_code)        # else DENY (fail-safe)
      L1:  r := latest approval_requests for step
           return ALLOW iff r.status='approved'
                          AND exists apr_approvals(r) decision='approve' AND approver≠proposer
      L2:  return ALLOW iff fn_apr_quorum_check_passes(r)             # recomputed, not a flag
      L3:  v := SELECT 1 FROM v_build_auth_valid                      # the ONLY source of truth
                  WHERE step_name=:step_name
                    AND commit_allowed = true
                    AND granted_by <> :agent_identity                # INV-5
           return ALLOW iff v exists                                  # view already enforces
                                                                      # quorum/TTL/consumed/revoked/esign
      L4:  return ALLOW iff (L3 ALLOW) AND valid_sovereign_esign(grant.sovereign_esign_ref)
    default: return DENY                                              # fail-closed

Properties: fail-closed (unknown ⇒ DENY); idempotent/read-only (the verifier itself writes nothing — consumed_at is written by the COMMIT txn, not the check); single source for L3 (v_build_auth_valid, never raw status).


2.7 Redefined M-1, os_proposal_approvals, and the bootstrap paradox

Redefined M-1 (per-step): M-1 is satisfied for a step iff fn_build_commit_allowed(step_name, agent) = ALLOW, i.e. a valid governance_build_authorization exists for this step (via v_build_auth_valid) and, if requires_sovereign_esign, a valid L4 e-sign is referenced. M-1 is no longer "≥1 row in os_proposal_approvals".

os_proposal_approvals = L4 only. It remains the sovereign e-sign surface, agent-forbidden, sovereign-only. After M-1 redefinition, no L3 step depends on it. (Target: stand up a purpose-built governance sovereign e-sign collection and return the sales module to sales — doc 12 patch P3; a council L2/L4 decision.)

Bootstrap paradox (stated honestly, not hidden — GPT acceptance criterion #14). The authorization model itself is a change to the authority model, which is an L4 sovereignty trigger. Therefore standing up SB-0 (governance_build_authorization + view + action-type rows) is itself a one-time L2 council + L4 sovereign-ratified constitutional act. The new model cannot remove from the President the single act of e-signing its own adoption. This is a decision dependency, not an engineering gap: once ratified and built, every subsequent reversible build step (SB-12…SB-1, axis substrate) is L3-authorized via SB-0 without further sovereign e-sign (unless individually escalated).


2.8 Worked traces

  1. L0 — create_item (low, reversible, allowlisted): T1 INSERT lands approved + apr_approvals(system). Verifier: allowlist∧low∧handler ⇒ ALLOW. No grant/consume. No human, no sovereign.
  2. L1 — add_field (medium): T1 draft (action='review') → T2 pending → human technical owner approves (approver≠proposer) → T4 APPROVED. Verifier L1 ⇒ ALLOW. No sovereign.
  3. L3 — authorize building SB-12 governance_ruleset: T1 draft proposed_action_code='authorize_build_step' (risk=high, action='review') → council quorum (≥1 president + ≥2 ai_council) → T4 APPROVED → build owner issues governance_build_authorization for step_name='SB-12', scope={objects_allowed:[governance_ruleset], txn_strategy:'single small txn', channel:'psql workflow_admin'}, commit_allowed=true, expires_at=now+48h, granted_by=<owner>≠build agent → GRANTED. Build agent runs verifier (v_build_auth_valid) ⇒ ALLOW → COMMIT sets consumed_at → CONSUMED. No sovereign (SB-12 is reversible, below ceiling).
  4. L4 — enact_nrm (law enactment): L2 quorum approves → grant created with requires_sovereign_esign=true → AWAITING_SOVEREIGN → President e-signs out-of-band → valid_sovereign_esign true → GRANTED → COMMIT → CONSUMED. Agent only ever READ the e-sign.

2.9 Audit / provenance / forbidden-compliance

  • Provenance: every transition writes to apr_approvals (votes), governance_build_authorization (grant/consume/revoke), registry_changelog + governance_audit_log (the COMMIT), and emits a governance-domain event (after SB-11; register-before-emit, Điều 45).
  • Rollback: each grant carries rollback_plan_ref; post-consume rollback uses the change-set reverse + grant revoke + runbook (doc 16).
  • Forbidden-compliance (this doc): design-only; no row created in any approval/e-sign table; no quorum cast; no grant issued; no law/event/DOT mutation; os_proposal_approvals neither read-for-write nor written.
Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-axis-auth-proposal-operational-hardening-build-ready-design-2026-06-02/02-approval-authorization-state-machine.md