ADDED Requirements
Requirement: 身分動作 append-only 稽核寫入
Summary: 系統 MUST 提供 append-only 稽核寫入介面;本 change 涵蓋身分相關事件(bootstrap / 密碼變更 / 鎖定 / 解鎖 / 強制改密 / 策略變更);每筆 record SHALL 含時間、操作者、動作、對象、結果、來源 IP。
The system MUST provide an append-only audit log writer accessible to all capabilities that perform write actions. For this change, the auth capability MUST emit audit records for: bootstrap_admin / password_changed / password_policy_update / account_locked / account_auto_unlocked / account_unlocked_admin / login_blocked_locked / force_password_change_set / password_expired_forced_change. Each record MUST contain a strictly-ordered timestamp, actor identity, action enum, target (when applicable), result, and source IP.
Scenario: Bootstrap 寫稽核紀錄
- GIVEN 平台正在執行 bootstrap、Admin 帳號剛建立成功
- WHEN Bootstrap transaction commit
- THEN 系統 MUST 在 audit-log 寫一筆 record,欄位包含:
timestamp = transaction commit 時間(UTC ISO 8601 with millisecond)
actor = system_init
action = bootstrap_admin
target = 新建 Admin 的 username
result = success
source_ip = 提交 bootstrap 表單的 client IP
- AND 該 record SHALL 與 Admin 帳號建立 in the same DB transaction(同進同退)
Scenario: 密碼策略變更寫稽核 with before/after diff
- GIVEN Admin 修改了密碼策略(最少字數 12 → 16)
- WHEN 變更儲存成功
- THEN 系統 MUST 寫 audit-log record,
action = password_policy_update
- AND record body SHALL 包含
before = {min_length: 12, ...} 與 after = {min_length: 16, ...} 差異
- AND
actor SHALL = 執行變更的 Admin username
Scenario: 失敗的身分動作也寫稽核
- GIVEN 使用者
alice 嘗試登入時帳號已鎖定
- WHEN 登入請求被拒絕
- THEN 系統 MUST 寫一筆
action = login_blocked_locked 的 record
- AND
result SHALL = denied
- AND
source_ip SHALL = 失敗請求來源 IP
Scenario: 稽核寫入失敗時動作必須 rollback
- GIVEN 任何身分動作(如 bootstrap / 密碼變更)正在進行
- WHEN audit-log 寫入失敗(如 disk full / DB error)
- THEN 整個動作 transaction MUST rollback
- AND 使用者 SHALL 看到「儲存失敗 — 請聯絡支援」訊息
- AND 系統 SHALL NOT 留下「動作成功但稽核遺失」的狀態
Edge Cases
- 高頻動作(未來 EN02 webhook 大量寫稽核):本 change 不涵蓋,但 schema 預留
severity / category 欄位以支援未來分類查詢
- 系統時鐘倒退:以 monotonic clock + sequence number 保證 record 嚴格排序,不依賴 wall-clock
- Audit-log 介面被未授權呼叫:寫入 API 僅供應用層內部呼叫,無外部 HTTP endpoint(防止外部偽造)
- 對象 user 已刪除:record 仍保留原 username 字串(不做 FK),避免歷史紀錄因 user 刪除而失意義
- Bootstrap 時
actor = system_init 是 reserved sentinel:任何真實 user 不可使用此 username
Requirement: 稽核紀錄不可竄改且保留至少 5 年
Summary: Audit-log 表 MUST 以 DB 層 grant 限制為 INSERT only(無 UPDATE/DELETE 權限);應用層 SHALL NOT 提供修改介面;保留期間至少 5 年。
The audit-log table MUST be protected by DB-level grants that permit only INSERT (no UPDATE or DELETE for the application role). The application layer MUST NOT expose any modification or deletion API for audit records. Audit records MUST be retained for at least 5 years from creation, satisfying financial-industry compliance baselines.
Scenario: DB 層拒絕 UPDATE / DELETE
- GIVEN 應用程式以正常角色連 DB
- WHEN 嘗試對
audit_log 表執行 UPDATE 或 DELETE
- THEN DB MUST 回應權限錯誤
- AND 應用層 SHALL NOT 對外提供任何修改 API
Scenario: 應用層讀取介面僅 SELECT
- GIVEN EN08 Epic 未來會提供 audit 查詢 UI
- WHEN 任何讀取 API 被呼叫
- THEN 該 API MUST 只執行 SELECT
- AND SHALL NOT 暴露 UPDATE / DELETE / TRUNCATE 路徑
Scenario: 保留期間至少 5 年
- GIVEN Audit record 建立於 4 年 364 天前
- WHEN 系統執行 retention job
- THEN 該 record MUST 仍存在
- AND 系統 SHALL NOT 刪除任何 < 5 年的 record
- AND > 5 年的 record SHALL 可由 Admin 明確操作匯出後封存(不自動刪除,由 V2 archive 機制處理)
Edge Cases
- DB superuser 仍有 UPDATE 權限(無法以 grant 完全擋):install guide 標示「audit 完整性依賴 DB 運維責任」;V2+ 可加 hash chain 提升可證明性,本 V1 不做
- 5 年儲存量估算:以平均日 1000 筆 audit × 5 年 ≈ 1.8M record,標準 RDBMS 表單可支撐;分區策略留 V2
- 法規變更要求 > 5 年(如金管會某些業務要求 7 年):保留期間 MUST 可由 install-time config 調整,但 SHALL NOT 低於 5 年下限
- 災難復原情境:audit-log MUST 隨 DB backup 一起備份;恢復後 record 完整性與時間序 MUST 保持
- Multi-region / replica:本 V1 單機部署不涉及 replication conflict;V2+ 多地端時 audit-log MUST 走 single-writer 模式