開発計画書 §4.3.1 Sprint 4 App。audit_logs の改ざん検知(tamper-evidence)をハッシュチェーンで完成させる。記録基盤は Sprint 3(#734 / #736)で landing 済。
方式は ADR-0038 で確定(#758、merged)。Sprint 3 landing 済の AuditLogPersistenceAdapter.computeChainHash は真の連鎖でない(再帰なし / 保護列 3 列のみ / 前行ハッシュ格納)ことが判明したため、本 Issue で正しい連鎖に作り直す。
確定方式(ADR-0038)
- 連鎖単位: テナント単位 + プラットフォーム連鎖(
chain_key = tenant_id、横断は予約値 0)
- ハッシュ: 自レコードハッシュ
hash_n = HMAC_SHA256(key, canonical(自行の全不変列) ‖ hash_{n-1})、鍵は Parameter Store / KMS、hash_key_id で解決
- 直列化:
chain_heads テーブル + 行ロック(全表スキャン廃止)
- 順序の正本:
chain_seq 列
- 保管削除両立:
audit_anchors 追記専用チェックポイント(B-03 と両立)
- 検証: B-05 日次バッチ(ShedLock)、不整合は fail-open でアラート
段階 PR(密結合のため #751 内で段階実装)
- Flyway:
audit_logs に chain_seq / hash_key_id 追加、chain_heads / audit_anchors 新設(命名規約準拠)。dev シードは再シード/再計算(後方互換移行なし、プレ本番)
- 連鎖計算改修:
AuditLogPersistenceAdapter を自レコード HMAC + chain_heads 行ロックへ全面改修、computeChainHash 置換・findFirstByOrderByIdDesc 廃止、HMAC 鍵を Parameter Store からロード
- B-05 検証バッチ + アンカー追記(ShedLock、ADR-0037 /
NotificationBatchScheduler 同様)、不整合アラート + 構造化ログ
- テスト: 連鎖連結 / 改ざん注入(改変・削除・並べ替え・末尾)検出 / 保管削除後のチェックポイント起点検証 / 並行 INSERT 非分岐、カバレッジ 80% 以上
依存: 2←1 / 3←1,2 / 4←3。段階 PR は Refs #751、最終 PR のみ Closes #751。
受け入れ条件
関連: ADR-0038 / ADR-0013 / ADR-0020 / ADR-0037 / #758 / #734 / #736
実装 pin(2026-06-25 確定)
- canonical(row): 固定キー順の正準 JSON(ADR-0038 §3.2 追補)、
payload_utf8 ‖ 前ハッシュhex を HMAC、NULL→JSON null、detail はネスト object
- chain_heads 初回行:
INSERT ... ON DUPLICATE KEY UPDATE(no-op)で存在保証 → SELECT ... FOR UPDATE(audit TX 内)。chain_key=0 は Flyway で seed
- インデックス:
audit_logs に KEY idx_al_chain (chain_key, chain_seq)
- dev 既存行: プレ本番のため Flyway で
audit_logs truncate(後方互換 backfill なし)
- B-05 不整合: ERROR 構造化ログ + OTel メトリクス → CloudWatch アラーム(ADR-0029)。監査行としては記録しない、fail-open
- HMAC 鍵: Parameter Store SecureString(key-id
v1)+ ECS task-role IAM ssm:GetParameter(ARN scoped、Terraform 規約=同一 PR)。test は property 注入
- B-03 アンカー整合(ADR-0038 §3.6)は B-03 削除バッチ側の責務。本 Issue では検証が retained アンカー起点で成立することをテストで担保
開発計画書 §4.3.1 Sprint 4 App。audit_logs の改ざん検知(tamper-evidence)をハッシュチェーンで完成させる。記録基盤は Sprint 3(#734 / #736)で landing 済。
方式は ADR-0038 で確定(#758、merged)。Sprint 3 landing 済の
AuditLogPersistenceAdapter.computeChainHashは真の連鎖でない(再帰なし / 保護列 3 列のみ / 前行ハッシュ格納)ことが判明したため、本 Issue で正しい連鎖に作り直す。確定方式(ADR-0038)
chain_key = tenant_id、横断は予約値0)hash_n = HMAC_SHA256(key, canonical(自行の全不変列) ‖ hash_{n-1})、鍵は Parameter Store / KMS、hash_key_idで解決chain_headsテーブル + 行ロック(全表スキャン廃止)chain_seq列audit_anchors追記専用チェックポイント(B-03 と両立)段階 PR(密結合のため #751 内で段階実装)
audit_logsにchain_seq/hash_key_id追加、chain_heads/audit_anchors新設(命名規約準拠)。dev シードは再シード/再計算(後方互換移行なし、プレ本番)AuditLogPersistenceAdapterを自レコード HMAC +chain_heads行ロックへ全面改修、computeChainHash置換・findFirstByOrderByIdDesc廃止、HMAC 鍵を Parameter Store からロードNotificationBatchScheduler同様)、不整合アラート + 構造化ログ依存: 2←1 / 3←1,2 / 4←3。段階 PR は
Refs #751、最終 PR のみCloses #751。受け入れ条件
chain_seq/hash_key_id追加 +chain_heads/audit_anchors新設chain_key単位で連鎖が正しく連結(並行 INSERT で分岐しない)audit_anchorsチェックポイントで保管削除(B-03)後も検証可能関連: ADR-0038 / ADR-0013 / ADR-0020 / ADR-0037 / #758 / #734 / #736
実装 pin(2026-06-25 確定)
payload_utf8 ‖ 前ハッシュhexを HMAC、NULL→JSON null、detail はネスト objectINSERT ... ON DUPLICATE KEY UPDATE(no-op)で存在保証 →SELECT ... FOR UPDATE(audit TX 内)。chain_key=0は Flyway で seedaudit_logsにKEY idx_al_chain (chain_key, chain_seq)audit_logstruncate(後方互換 backfill なし)v1)+ ECS task-role IAMssm:GetParameter(ARN scoped、Terraform 規約=同一 PR)。test は property 注入