- 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>
2.8 KiB
2.8 KiB
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 邏輯簡單,影響極小 |
| 權限變更頻繁觸發 | 僅記錄實際變更 |