Files
PROJECT-CONTORL/openspec/changes/archive/2025-12-29-fix-audit-trail/design.md
beabigegg 10db2c9d1f feat: implement audit trail alignment (soft delete & permission audit)
- Task Soft Delete:
  - Add is_deleted, deleted_at, deleted_by fields to Task model
  - Convert DELETE to soft delete with cascade to subtasks
  - Add include_deleted query param (admin only)
  - Add POST /api/tasks/{id}/restore endpoint
  - Exclude deleted tasks from subtask_count

- Permission Change Audit:
  - Add user.role_change event (high sensitivity)
  - Add user.admin_change event (critical, triggers alert)
  - Add PATCH /api/users/{id}/admin endpoint
  - Add role.permission_change event type

- Append-Only Enforcement:
  - Add DB triggers for audit_logs immutability (manual for production)
  - Migration 008 with graceful trigger failure handling

- Tests: 11 new soft delete tests (153 total passing)
- OpenSpec: fix-audit-trail archived, fix-realtime-notifications & fix-weekly-report proposals added

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 06:58:30 +08:00

2.8 KiB
Raw Blame History

Context

audit-trail spec 定義了軟刪除、權限變更記錄、append-only 日誌等需求,但現行實作未完全對齊。

Goals / Non-Goals

Goals:

  • 任務刪除改為軟刪除,保留資料可追溯
  • 所有權限變更記錄至 audit log
  • 確保 audit_logs 表不可被修改或刪除

Non-Goals:

  • 不實作任務還原 UI僅提供 API
  • 不變更現有 checksum 計算邏輯

Decisions

1. 軟刪除欄位設計

Decision: 使用 is_deleted + deleted_at + deleted_by

Rationale:

  • is_deleted 簡化查詢過濾
  • deleted_at 提供時間資訊
  • deleted_by 追蹤操作者
is_deleted = Column(Boolean, default=False, nullable=False)
deleted_at = Column(DateTime, nullable=True)
deleted_by = Column(String(36), ForeignKey("pjctrl_users.id"), nullable=True)

2. 查詢過濾策略

Decision: API 層過濾,非 ORM 層

Rationale:

  • 保持彈性,部分查詢需要包含已刪除項目
  • 避免 ORM 層複雜性
# 預設過濾
query = query.filter(Task.is_deleted == False)

# 管理員可查看已刪除
if include_deleted and current_user.is_system_admin:
    query = query.filter()  # 不過濾

3. Append-Only 實作

Decision: 使用 MySQL/PostgreSQL trigger

MySQL:

DELIMITER //
CREATE TRIGGER prevent_audit_update BEFORE UPDATE ON pjctrl_audit_logs
FOR EACH ROW BEGIN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Audit logs are immutable';
END//

CREATE TRIGGER prevent_audit_delete BEFORE DELETE ON pjctrl_audit_logs
FOR EACH ROW BEGIN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Audit logs are immutable';
END//
DELIMITER ;

4. 權限變更事件

Decision: 記錄以下變更類型

事件 event_type sensitivity
角色指派 user.role_change high
角色權限更新 role.permission_change critical
系統管理員變更 user.admin_change critical

Data Model Changes

-- Task 表新增欄位
ALTER TABLE pjctrl_tasks ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE pjctrl_tasks ADD COLUMN deleted_at DATETIME NULL;
ALTER TABLE pjctrl_tasks ADD COLUMN deleted_by VARCHAR(36) NULL;
ALTER TABLE pjctrl_tasks ADD INDEX idx_task_deleted (is_deleted);

API Changes

# 現有 API 行為變更
DELETE /api/tasks/{id}  -> 軟刪除,回傳 200 而非 204
GET /api/projects/{id}/tasks -> 預設排除 is_deleted=true

# 新增 API
POST /api/tasks/{id}/restore -> 還原已刪除任務(需權限)
GET /api/projects/{id}/tasks?include_deleted=true -> 管理員可查看全部

Risks / Trade-offs

Risk Mitigation
軟刪除增加資料量 定期歸檔/清理策略
DB trigger 影響效能 trigger 邏輯簡單,影響極小
權限變更頻繁觸發 僅記錄實際變更