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