23. Storage Discipline

Storage class · Append-only · Set-once · Forbidden · Runtime-only

5 Storage Classes

ClassBadge특성예시
M-mutM-mut일반 mutable column — UPDATE 자유users.first_name, vault_accounts.name, addresses.deactivated_at
M-cacheM-cachederived value 의 cache — canonical source 따로 존재asset_wallets.balance_cached, transactions.state_cached
A-rowA-rowappend-only — UPDATE / DELETE 금지audit_events, lifecycle_events, signing_events, ledger_entries
A-setA-setset-once column — NULL → value 한 번만verdict, tx_hash, finalized_at, raw_payload_cbor, sgx_mrenclave
A-chainA-chainhash chain — prev/current_hash 가 정합되어야 함audit_events.prev_hash + current_hash
R-onlyR-onlyruntime-only — DB 영속화 금지, 메모리만signing ceremony partial share, ephemeral nonce
F-bidF-bidforbidden — 어떤 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 code
(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
Figure 11. Append-only enforcement — application code 가 evolve 하더라도 DB 자체가 거절해야 institutional-grade.

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_hash
  • approval_requests.finalized_at, approval_decisions.mobile_device_sig
  • signing_events.partial_sig_hash, signing_events.mrenclave_at_signing
  • broadcast_attempts.signed_payload, broadcast_attempts.tx_hash
  • policies.active_at, policy_change_log.mobile_app_approval_sigs
  • recovery_events.completed_at, deposit_observations.cleared_at
  • users.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

  1. 잘못된 row 는 그대로 둠
  2. 새 reversing row 를 INSERT (반대 부호 amount + reverses_entry_id FK + 정정 사유)
  3. balance 는 SUM 이므로 자동 net-zero
  4. Audit 시 두 row 모두 보임 — 사실의 일부