15. Approval State Machine (TAP)
Stage 10 Policy 명세 — 3 action · first-match · Unanimous-Veto · 3-tier governance3 Action Semantics (★ Stage 10)
how-policies-work.md, p.1 — Policy rule 의 action 은 3 종.
| Action | 의미 | State 전이 |
|---|---|---|
| Allow | 자동 approve → designated signer 로 forward | SUBMITTED → QUEUED (PENDING_AUTHORIZATION 우회) |
| Approved by | 지정 user / user group 이 approve / deny. 모든 associated role 이 authorized approver 여야 함 | SUBMITTED → PENDING_AUTHORIZATION (collecting) |
| Block | 자동 차단 | SUBMITTED → BLOCKED (violated rule number 표시) |
First-Match Principle
- Policy 를 top-to-bottom scan
- 첫 match 에서 scan 중단 + action 실행
- 모든 Policy 의 마지막 rule = block-all (default, 삭제 불가) → default-deny architecture
Rule Ordering (most restrictive first)
- Time-period based rule 먼저 (cumulative limit 가 single-tx rule 보다 앞)
- Overlap 시 더 strict 한 rule 먼저 (예: $10M+ 2-approval rule 이 $1M+ 1-approval 보다 앞)
Approval State Machine
stateDiagram-v2
[*] --> EVALUATING : tx submitted
EVALUATING --> AUTO_APPROVED : action = Allow
EVALUATING --> COLLECTING : action = Approved by
EVALUATING --> BLOCKED : action = Block (rule violation)
EVALUATING --> BLOCKED : no rule match (default block-all)
COLLECTING --> APPROVED : threshold met
COLLECTING --> REJECTED : 1 reject (Unanimous-Veto)
COLLECTING --> EXPIRED : 2h timeout (PENDING_AUTHORIZATION)
COLLECTING --> EXPIRED : 7-day timeout (Add user flow)
AUTO_APPROVED --> [*]
APPROVED --> [*]
REJECTED --> [*]
BLOCKED --> [*]
EXPIRED --> [*]
Figure 7. Approval state machine — Allow auto-approve / Approved by collecting / Block fail-fast. Unanimous-Veto: 1 reject = 즉시 REJECTED.
Approvers Unanimous-Veto Rule (★)
transaction-lifecycle.md, p.6 직접 인용: "If at least one person chooses to reject a transaction, the transaction is rejected."
Approval group 의 N명 중 1명 reject = 즉시 fail. 다수결 아님.
3-Tier Governance (★ Stage 10 spine)
Admin Quorum (workspace-level default)
└── Approval Group (action-level 위임, 12 actions)
└── Policy "Approved by" sub-quorum (rule-level 위임, user group 기반 N-of-M)
how-policies-work.md, p.4: "you can also define approval quorums within a group as part of the Approved by action... requires any two members of a user group called Management to approve."
Default Policy Rules (5)
모든 신규 workspace 자동 적용 (about-policies.md, p.4):
- Transfer Policy 1: Allow NFT transfer → whitelisted address
- Transfer Policy 2: Allow 모든 USD amount, 모든 asset → whitelisted address
- Contract Call Policy: Allow contract call → whitelisted smart contract
- Approve Policy: Allow Web3 Approve tx
- All Policies (마지막, 삭제 불가): Block any tx not explicitly allowed
→ Custom Policy 도입 = default 즉시 삭제 (one-way replacement). Custom 전부 삭제 시 default 복원.
DB Schema
CREATE TABLE approval_requests (
id BINARY(16) PRIMARY KEY,
workspace_id BINARY(16) NOT NULL,
subject_type ENUM('transaction', 'add_user', 'edit_user', 'policy_change',
'admin_quorum_change', 'whitelist_address') NOT NULL,
subject_id BINARY(16) NOT NULL,
matched_rule_id BINARY(16), -- first-match rule
action ENUM('allow', 'approved_by', 'block') NOT NULL,
state ENUM('EVALUATING', 'COLLECTING', 'AUTO_APPROVED',
'APPROVED', 'REJECTED', 'BLOCKED', 'EXPIRED') NOT NULL,
threshold INT, -- N of M
collected_approvals INT NOT NULL DEFAULT 0,
created_at DATETIME(6) NOT NULL,
expires_at DATETIME(6) NOT NULL, -- 2h (tx) / 7-day (add_user)
finalized_at DATETIME(6), -- ★ set-once
KEY (workspace_id, state),
KEY (expires_at)
);
CREATE TABLE approval_decisions (
id BINARY(16) PRIMARY KEY,
request_id BINARY(16) NOT NULL,
approver_user_id BINARY(16) NOT NULL,
decision ENUM('approve', 'reject') NOT NULL,
mobile_device_sig BLOB NOT NULL, -- ★ set-once, append-only
decided_at DATETIME(6) NOT NULL,
UNIQUE KEY (request_id, approver_user_id) -- 1 user = 1 decision per request
);
Sticky Terminal · Set-once
approval_requests.state 가 APPROVED / REJECTED / BLOCKED / EXPIRED 도달 시 더 이상 전이 불가 (BEFORE UPDATE trigger).
approval_decisions 의 mobile_device_sig 는 set-once + append-only.