23. Storage Discipline
Storage class · Append-only · Set-once · Forbidden · Runtime-only5 Storage Classes
| Class | Badge | 특성 | 예시 |
|---|---|---|---|
| M-mut | M-mut | 일반 mutable column — UPDATE 자유 | users.first_name, vault_accounts.name, addresses.deactivated_at |
| M-cache | M-cache | derived value 의 cache — canonical source 따로 존재 | asset_wallets.balance_cached, transactions.state_cached |
| A-row | A-row | append-only — UPDATE / DELETE 금지 | audit_events, lifecycle_events, signing_events, ledger_entries |
| A-set | A-set | set-once column — NULL → value 한 번만 | verdict, tx_hash, finalized_at, raw_payload_cbor, sgx_mrenclave |
| A-chain | A-chain | hash chain — prev/current_hash 가 정합되어야 함 | audit_events.prev_hash + current_hash |
| R-only | R-only | runtime-only — DB 영속화 금지, 메모리만 | signing ceremony partial share, ephemeral nonce |
| F-bid | F-bid | forbidden — 어떤 DB·log·file 에도 저장 금지 | private key, MPC share, recovery passphrase, HSM PIN, master KEK plaintext, API credential plaintext |
Append-only Enforcement (4-layer defense)
graph TB CODE["Application codeFigure 11. Append-only enforcement — application code 가 evolve 하더라도 DB 자체가 거절해야 institutional-grade.
(UPDATE / DELETE 시도)"] TRIG["DB trigger
prevent_mutation"] SCHEMA["Schema constraint
(set-once columns)"] PRIV["DB privilege
(no UPDATE/DELETE permission)"] SUPER["Superuser
(직접 SQL 시 alert)"] ALOG["audit_events log
(superuser activity)"] CODE -->|차단됨| TRIG CODE -->|차단됨| SCHEMA CODE -->|차단됨| PRIV SUPER -->|emergency bypass| PRIV SUPER -.alert + audit.-> ALOG classDef code fill:#eaf2fc,stroke:#2858a4 classDef enforce fill:#e6f0e8,stroke:#2a5a36 classDef warn fill:#fdeaea,stroke:#a44 class CODE code class TRIG,SCHEMA,PRIV enforce class SUPER,ALOG warn
Trigger Pattern (MySQL)
DELIMITER //
CREATE TRIGGER audit_events_no_update
BEFORE UPDATE ON audit_events
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'audit_events is append-only';
END //
CREATE TRIGGER audit_events_no_delete
BEFORE DELETE ON audit_events
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'audit_events is append-only';
END //
DELIMITER ;
같은 패턴을 signing_events, lifecycle_events, ledger_entries, approval_decisions, policy_change_log, chain_events, broadcast_attempts, confirmations 등에 일괄 적용.
Set-once Column Trigger
DELIMITER //
-- transactions.tx_hash 는 NULL → value 한 번만
CREATE TRIGGER transactions_tx_hash_setonce
BEFORE UPDATE ON transactions
FOR EACH ROW
BEGIN
IF OLD.tx_hash IS NOT NULL AND NEW.tx_hash != OLD.tx_hash THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'tx_hash is set-once';
END IF;
END //
DELIMITER ;
주요 Set-once Column 목록
transactions.raw_payload_cbor,transactions.tx_hashapproval_requests.finalized_at,approval_decisions.mobile_device_sigsigning_events.partial_sig_hash,signing_events.mrenclave_at_signingbroadcast_attempts.signed_payload,broadcast_attempts.tx_hashpolicies.active_at,policy_change_log.mobile_app_approval_sigsrecovery_events.completed_at,deposit_observations.cleared_atusers.activated_at,workspace_keys_backups.destroyed_at
Forbidden Storage — 절대 컬럼 X
DB · log · file 어디에도 저장 금지
- private key (chain private key)
- MPC share material (full share, partial share)
- recovery passphrase (plaintext / hash 모두 금지)
- HSM PIN
- TEE sealing key
- DCAP attestation private key
- Master KEK plaintext
- RSA private key (Auto-passphrase / DR 의 customer-side key)
- API credential plaintext (반드시 HSM-wrapped)
- TOTP shared secret plaintext
- SSO client_secret plaintext
Runtime-only Context
- Signing ceremony 의 partial share — 메모리만, 디스크 영속화 금지
- Ephemeral nonce (MPC-CMP 의 round-specific)
- Access token (6h) 의 in-memory cache
- Activation token 의 in-memory state (7-day window 안)
정정의 유일한 경로 = Reversing Entry
- 잘못된 row 는 그대로 둠
- 새 reversing row 를 INSERT (반대 부호 amount +
reverses_entry_idFK + 정정 사유) - balance 는 SUM 이므로 자동 net-zero
- Audit 시 두 row 모두 보임 — 사실의 일부