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

108 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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` 追蹤操作者
```python
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 層複雜性
```python
# 預設過濾
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:**
```sql
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
```sql
-- 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 邏輯簡單,影響極小 |
| 權限變更頻繁觸發 | 僅記錄實際變更 |