diff --git a/docs/specs/v0.6.0/00-release-scope.md b/docs/specs/v0.6.0/00-release-scope.md new file mode 100644 index 0000000..8cc5373 --- /dev/null +++ b/docs/specs/v0.6.0/00-release-scope.md @@ -0,0 +1,118 @@ +# v0.6.0 release scope + +Status: Confirmed + +## 1. Release objective + +v0.6.0 面向单组织私有化部署,目标是让一个小型 SOC 团队完成稳定的告警分诊、案件调查、响应执行和日常运维。该版本以核心功能闭环为优先,不以 SaaS、多租户或大规模集群为目标。 + +## 2. Supported deployment + +- 唯一正式支持的部署拓扑是单主机 Docker Compose。 +- 不承诺 Kubernetes、横向扩容、多节点高可用或自动故障切换。 +- 每种后台 Worker 正式支持一个实例;重复实例必须按 Worker Health Spec 拒绝启动。 +- 数据库降级不受支持。回滚依赖升级前备份恢复。 + +## 3. Upgrade contract + +- 必须支持从 v0.5.2 直接升级到 v0.6.0。 +- 升级必须保留所有业务数据、用户、LDAP 配置、集成配置、API Key、附件和自定义脚本。 +- 所有数据库变更必须提供 Django migration。 +- migration 必须适用于已有数据,不得要求清空数据库。 +- 升级流程继续先运行 migration,再启动应用服务和 Worker。 +- 不承诺从 v0.5.1 或更早版本直接升级,也不提供 v0.6.0 到 v0.5.2 的数据库 downgrade。 + +## 4. Capacity baseline + +正式验收使用现有 `generate_perf_data --scale medium` 数据规模: + +| Resource | Baseline | +| --- | ---: | +| Users | 20 | +| Cases | 10,000 | +| Alerts | 100,000 | +| Artifacts | 50,000 | +| Alert-artifact links | 300,000 | +| Enrichments | 30,000 | +| Playbook runs | 10,000 | +| Knowledge records | 2,000 | +| Audit logs | 100,000 | + +`large` 和 `extreme` 数据档位可用于开发压测,但不属于 v0.6.0 响应时间承诺。 + +## 5. Official compatibility matrix + +| Area | Officially supported | +| --- | --- | +| SIEM | Splunk, ELK | +| LLM | OpenAI-compatible Chat Completions endpoints | +| Threat intelligence | AlienVault OTX, OpenCTI | +| Authentication | Local account, LDAP | +| Object storage | 当前 Compose 分发所配置的 S3-compatible storage | +| Cache/stream | 当前 Compose 分发所配置的 Redis | + +代码枚举或 UI 示例中出现其他厂商名称,不代表官方连接器或兼容承诺。 + +## 6. Roles + +v0.6.0 保持三个固定角色: + +| Role | Meaning | +| --- | --- | +| Admin | 系统管理和全部业务写操作 | +| User / Analyst | 分析师业务写操作 | +| Viewer | 只读访问 | + +不实现自定义角色、权限编辑器或团队级数据隔离。每个新功能必须在自己的 Spec 中定义三种角色的具体权限。 + +## 7. Language + +- 正式 UI 只支持英文。 +- 项目用户文档保持中英文版本,先完成中文再同步英文。 +- v0.6.0 不引入前端 i18n 框架。 + +## 8. API compatibility + +- v0.6.0 允许破坏性修改现有前端 API 和 `/api/agent/v1/`。 +- 不创建 Agent API v2 作为兼容层。 +- 不要求旧 CLI 或旧插件拒绝连接,也不维护其兼容性。 +- 新 API 仍应有明确的 DRF schema,避免无意的响应漂移。 + +## 9. Confirmed functional domains + +1. Case 批量分诊。 +2. 案件合并。 +3. Playbook 执行可观测性和控制。 +4. Custom Variables。 +5. Worker Health。 +6. Integration Health。 +7. SLA 管理,待详细讨论。 +8. AI 质量评估,待详细讨论。 +9. 抑制规则,待详细讨论。 +10. Operations 页面整合,待详细讨论。 + +SLA、AI 质量评估和抑制规则都是 v0.6.0 正式发布的阻断项,但必须限制为最小可用闭环。 + +## 10. Explicit exclusions + +- 多租户、组织/Workspace 隔离。 +- Kubernetes 和高可用部署。 +- OIDC、SAML 或其他 SSO。 +- 自定义角色。 +- Jira、ServiceNow 等专用连接器。 +- 可视化或表单式 Playbook 编排器。 +- Playbook 中途人工审批。 +- 通用 HTTP/Webhook Connector 或统一厂商动作抽象。 +- 全站 UI 国际化。 +- 旧 CLI/插件兼容层。 +- 自动 Unmerge。 + +## 11. Cross-domain invariants + +- merged source Case 默认不得进入 Dashboard、SLA、案件数量和正常列表统计。 +- Case 单条编辑和批量编辑必须调用同一服务端状态机。 +- Playbook、Module 和 Worker 错误不得向 API 暴露原始凭据、响应正文或 traceback。 +- Admin 管理动作按各 Spec 写 AuditLog;高频健康遥测不写 AuditLog。 +- 所有时间使用 timezone-aware UTC 存储,前端按浏览器时区显示。 +- 所有列表型 API 必须分页;动态 Stage 数量不设上限,因此 Stage API 尤其不得全量返回。 +- 业务写操作不得通过前端限制替代服务端权限和状态校验。 diff --git a/docs/specs/v0.6.0/01-bulk-case-triage.md b/docs/specs/v0.6.0/01-bulk-case-triage.md new file mode 100644 index 0000000..e7ac7e9 --- /dev/null +++ b/docs/specs/v0.6.0/01-bulk-case-triage.md @@ -0,0 +1,323 @@ +# Bulk Case Triage + +Status: Confirmed + +## 1. Purpose + +允许分析师在 Case 列表中明确勾选一组 Case,一次修改相同的分诊字段。该功能优化重复人工操作,不替代案件合并、抑制规则、AI 分析或 Playbook。 + +## 2. Scope + +### Included + +- 仅 Case 支持批量分诊。 +- 一次可组合修改 Assignee、Status、Severity、Verdict。 +- 最多 100 个显式 Case ID。 +- 支持跨分页选择。 +- 同步执行并返回逐 Case 成功或失败结果。 +- 允许部分成功。 + +### Excluded + +- Alert 批量分诊。 +- Priority 和 Tags 批量修改。 +- 对当前筛选结果全部执行。 +- 后台批量任务。 +- 服务端幂等键。 +- updated_at 乐观锁。 +- 批次级 AuditLog 实体。 + +## 3. Permission matrix + +| Action | Admin | User | Viewer | +| --- | --- | --- | --- | +| Select cases | Yes | Yes | No | +| Bulk update any case | Yes | Yes | No | +| Bulk assign to any valid user | Yes | Yes | No | +| Bulk close | Yes | Yes | No | + +权限必须在服务端检查。User 不受 assignee 所有权限制。 + +## 4. Shared Case state machine + +单条 PATCH 与 bulk triage 必须调用同一个 domain service,不得分别维护状态逻辑。 + +### Allowed transitions + +| Current | Allowed next | +| --- | --- | +| New | In Progress, Closed | +| In Progress | On Hold, Resolved, Closed | +| On Hold | In Progress, Resolved, Closed | +| Resolved | In Progress, Closed | +| Closed | In Progress | + +相同状态写入可以作为无变化字段跳过,不应产生状态错误。 + +### Transition side effects + +- Case 第一次离开 `New` 时设置 `acknowledged_time=now()`。 +- `acknowledged_time` 设置后永久保留,Reopen 不重置。 +- 进入 `Closed` 时设置 `closed_time=now()`。 +- 进入 `Closed` 必须在最终状态上存在非空 Verdict,并要求本次请求提供 disposition note。 +- `Closed → In Progress` 是 Reopen: + - 清空 `closed_time`。 + - 清空 `verdict`。 + - 保留 Summary、acknowledged_time 和历史审计。 +- 其他离开 Closed 的路径不存在。 + +状态机应作为可复用服务,例如 `apps.cases.services.transition_case()`;Serializer 和 bulk endpoint 都调用它。 + +## 5. Editable field semantics + +### Assignee + +- 字段未包含:保持不变。 +- 提供有效用户 ID:设置新 assignee。 +- 显式 `null`:取消分配。 +- 不允许设置不存在或不可用的用户。 + +### Status + +- 字段未包含:保持不变。 +- 必须是 CaseStatus 枚举。 +- 必须满足共享状态机。 +- Status 不允许清空或设为 null。 + +### Severity + +- 字段未包含:保持不变。 +- 必须是 CaseSeverity 枚举。 +- 不使用空字符串表达清空;使用 `Unknown`。 + +### Verdict + +- 字段未包含:保持不变。 +- 必须是 CaseVerdict 枚举。 +- 非 Closed Case 可以显式设为 null/空值以清空 Verdict。 +- 请求结束后的 Case 若为 Closed,Verdict 必须非空。 +- 不允许只清空 Closed Case 的 Verdict。 + +### Multi-field evaluation + +校验应基于本次请求全部字段应用后的最终 Case 状态,而不是按 JSON 字段顺序执行。例如同一次请求可以把 New Case 设为 Closed 并提供 Verdict。 + +## 6. Bulk close note + +进入 Closed 时 `reason` 必填。该文本同时用于审计和 Summary 追加。 + +Summary 追加格式: + +```markdown + +### Bulk disposition · 2026-07-28 10:44 UTC · alice + +Confirmed as false positive after campaign review. +``` + +规则: + +- 保留现有 Summary。 +- 现有 Summary 与新段落之间插入空行。 +- 标题时间使用 UTC 的稳定格式。 +- actor 使用当前用户名。 +- 对每个成功关闭的 Case 追加相同说明。 +- 普通分配、Severity、Status 或 Verdict 修改的 reason 可选;如果提供,只写审计,不追加 Summary。 + +## 7. API + +### Endpoint + +`POST /api/cases/bulk-triage/` + +这是 Case 专用 collection action,不实现通用 bulk PATCH。 + +### Request + +```json +{ + "case_ids": [ + "6387d62e-4ed6-45b4-b83a-522cd7b5e845", + "b109ce5c-956a-42d5-b53c-bc883d19dbfc" + ], + "changes": { + "assignee": "e0602519-1cd0-4750-80af-aac269a98722", + "status": "In Progress", + "severity": "High", + "verdict": "Suspicious" + }, + "reason": "Campaign triage" +} +``` + +Validation: + +- `case_ids` 必须是非空数组。 +- 去重后最多 100 个。 +- ID 必须是合法 UUID;请求结构中的非法 UUID 是请求级 400,而不存在的合法 UUID 是逐项失败。 +- `changes` 至少包含一个允许字段。 +- 不接受 Priority、Tags 或其他字段。 +- `reason` 去除首尾空白后校验。 +- 如果请求的最终目标状态是 Closed,reason 必填。 + +### Success and partial success response + +只要请求结构有效,返回 HTTP 200: + +```json +{ + "operation_id": "a143bc8b-df7b-40d8-b0ee-e5df4e7efdc4", + "requested": 2, + "succeeded_count": 1, + "failed_count": 1, + "succeeded": [ + { + "id": "6387d62e-4ed6-45b4-b83a-522cd7b5e845", + "case_id": "case_000123", + "updated_at": "2026-07-28T02:44:00Z" + } + ], + "failed": [ + { + "id": "b109ce5c-956a-42d5-b53c-bc883d19dbfc", + "case_id": "case_000124", + "code": "invalid_transition", + "detail": "Case cannot transition from New to Resolved." + } + ] +} +``` + +允许的安全失败码至少包括: + +- `not_found` +- `permission_denied` +- `invalid_transition` +- `invalid_final_state` +- `invalid_assignee` +- `update_failed` + +不得在 `detail` 返回 traceback 或数据库错误。 + +### Request-level errors + +以下返回 400,不处理任何 Case: + +- 空 case_ids。 +- 超过 100 个 ID。 +- 非法 UUID。 +- changes 为空。 +- 未知字段。 +- 非法枚举值。 +- 目标 Closed 但 reason 缺失。 + +401/403 使用现有认证和权限响应。 + +## 8. Transaction and failure behavior + +- 整批不使用一个全局原子事务。 +- 每个 Case 在自己的短事务中读取、应用状态机、保存并写 AuditLog。 +- 一个 Case 失败不得回滚已成功 Case。 +- 使用 last-write-wins,不比较客户端看到的 updated_at。 +- 在事务中读取最新 Case;只覆盖请求明确包含的字段。 +- 前端请求进行中禁用提交按钮,平台不提供服务端 idempotency key。 + +## 9. Notifications + +只有 assignee 实际变化时产生 assignment notification。 + +- 按最终 assignee 分组。 +- 每位接收人一次批量请求最多收到一条 Inbox 消息。 +- 消息包含成功分配给该用户的 Case 数量和可打开的 Case ID 列表。 +- 失败 Case 不进入通知。 +- 取消分配不发送通知。 +- 如果 assignee 未改变,不发送通知。 +- 通知在对应 Case 事务成功后统一生成;通知失败不得把已完成业务更新伪装为失败,应按现有通知错误策略显式记录。 + +## 10. Audit + +每个成功 Case 写一条现有 `updated` AuditLog: + +```json +{ + "action": "updated", + "changes": { + "status": {"from": "New", "to": "In Progress"}, + "severity": {"from": "Medium", "to": "High"} + }, + "metadata": { + "source": "bulk_triage", + "operation_id": "a143bc8b-df7b-40d8-b0ee-e5df4e7efdc4", + "reason": "Campaign triage" + } +} +``` + +- 后端为每次请求生成一个 UUID operation_id。 +- 同一请求的所有成功 Case 共享 operation_id。 +- 不创建单独的批次 AuditLog。 +- 无实际字段变化的 Case 可以视为成功,但不得写空 changes 审计;响应应包含 `unchanged: true`。 +- 失败 Case 不写业务更新审计。 + +## 11. Frontend + +### Selection + +- 仅 Case 主列表启用批量分诊。 +- 翻页保留选择。 +- Search、普通 Filter 或 Advanced Filter 发生变化时清空选择。 +- 不支持“Select all filtered results”。 +- 最多选择 100 条;达到上限后其他 checkbox disabled,并显示上限提示。 +- Toolbar 始终显示选中数量和 Clear selection。 + +### Bulk triage modal + +- 用户点击 Bulk Triage 打开 Modal,不立即执行。 +- 每个字段有独立“修改此字段”开关;未启用字段不进入 changes。 +- Assignee 支持选择用户和 Unassigned。 +- Status、Severity、Verdict 使用现有选项与 Tag 表现。 +- reason 默认可选;Status 选择 Closed 后立即变为必填并解释会追加到 Summary。 +- 显示选中数量、字段变更预览和关闭副作用。 +- 提交期间禁用所有操作。 + +### Result handling + +- 成功项从 selection 移除。 +- 失败项保持选中。 +- 显示成功/失败汇总。 +- 失败列表显示 Case ID、失败码对应的用户可读说明。 +- 不自动重试。 +- 刷新当前列表数据,但不得因刷新清除失败 selection。 + +## 12. Backend implementation surfaces + +预期涉及: + +- `backend/apps/cases/services.py`:共享状态机和字段应用。 +- `backend/apps/cases/serializers.py`:bulk request/response serializer。 +- `backend/apps/cases/views.py`:collection action。 +- `backend/apps/inbox/notifications.py`:汇总 assignment notification。 +- `frontend/src/components/DataTable.tsx`:受控跨页 selection 和自定义 bulk action context。 +- Case resource 页面:Bulk Triage modal 和结果展示。 + +不得把 Case 业务状态机写进通用 DataTable。 + +## 13. Acceptance criteria + +1. Admin/User 可对 1–100 个 Case 组合修改四个允许字段。 +2. Viewer 看不到操作并且 API 返回 403。 +3. 跨页选择保留,筛选变化清空。 +4. 非法状态转换只失败对应 Case,其他 Case 成功。 +5. Closed 必须有最终 Verdict 和 reason。 +6. Bulk close 正确追加带时间和 actor 的 Markdown Summary。 +7. Reopen 清空 closed_time/verdict,保留 acknowledged_time/Summary。 +8. assignment notification 按用户汇总。 +9. 每个成功 Case 有 source、operation_id 和 changes 审计。 +10. 请求级错误不产生任何 Case 更新。 +11. medium 数据集下 100 条同步请求可在正式验收设定的超时内完成。 + +## 14. Known tradeoffs + +- last-write-wins 可能覆盖并发编辑,这是已接受行为。 +- 没有服务端 idempotency,客户端网络重试可能重复追加 close note;前端必须避免自动重试 POST。 +- 部分成功使一次 operation_id 不代表全量成功;响应和逐 Case 审计是事实来源。 diff --git a/docs/specs/v0.6.0/02-case-merge.md b/docs/specs/v0.6.0/02-case-merge.md new file mode 100644 index 0000000..a9517b6 --- /dev/null +++ b/docs/specs/v0.6.0/02-case-merge.md @@ -0,0 +1,424 @@ +# Case Merge + +Status: Confirmed + +## 1. Purpose + +允许 Admin 将最多 20 个重复源 Case 原子地合并到一个明确目标 Case。业务对象迁移到目标,源 Case 永久保留为只读历史入口,确保 correlation routing、调查证据和审计可追溯。 + +## 2. Scope + +### Included + +- 多源到单目标合并。 +- 服务端预检。 +- 原子执行和行锁复检。 +- operation_id 幂等。 +- 关联 Alert、Enrichment、Comment、Playbook 和 AI Job 迁移。 +- merged source 只读 API 和 UI。 +- 后续 correlation_uid 路由到最终目标。 +- 合并关系扁平化。 + +### Excluded + +- 自动重复检测。 +- 自动选择目标。 +- Unmerge。 +- 非 Admin 合并。 +- 自动合并字段、Tags、Summary、AI 报告或 Knowledge。 +- 活动任务自动取消。 + +## 3. Permission matrix + +| Action | Admin | User | Viewer | +| --- | --- | --- | --- | +| View merged source | Yes | Yes | Yes | +| View Merged Sources tab | Yes | Yes | Yes | +| Run merge preflight | Yes | No | No | +| Execute merge | Yes | No | No | +| Edit merged source | No | No | No | + +User 可以普通编辑 Verdict 为 Duplicate,但不能迁移关系。 + +## 4. Data model + +### Case additions + +```python +merged_into = models.ForeignKey( + "self", + null=True, + blank=True, + on_delete=models.PROTECT, + related_name="merged_sources", +) +``` + +约束: + +- `merged_into_id != id`。 +- merged source 的 merged_into 必须直接指向最终目标。 +- 最终目标的 merged_into 必须为空。 +- 应为 `merged_into` 建索引。 +- 使用 PROTECT 防止目标删除;业务 API 本身也不应允许删除有历史关系的 Case。 + +### CaseMergeOperation + +目标模型字段: + +| Field | Notes | +| --- | --- | +| id | UUID primary key | +| operation_id | UUID, unique, client supplied | +| target | FK Case, PROTECT | +| actor | nullable FK User, SET_NULL | +| reason | required text | +| source_snapshot | JSON list of explicit and flattened source metadata | +| target_changes | JSON of explicit target edits | +| moved_counts | JSON counters | +| result | JSON stable idempotent response | +| created_at | immutable timestamp | + +Operation 创建后不可修改或删除。`source_snapshot` 至少保存源 UUID、readable ID、title、原 status、原 verdict、原 assignee、原 merged_into 和本次是否显式选择。 + +不单独创建可变 join table;Case.merged_into 是当前关系来源,Operation 是历史事实。 + +## 5. Eligibility + +### Target + +- 必须存在。 +- 必须是未合并最终 Case,即 `merged_into IS NULL`。 +- 不能同时出现在 source_ids。 +- Status 不能是 Closed;用户必须先显式 Reopen 到 In Progress。 +- 可以已经拥有 merged sources。 + +### Explicit sources + +- 1–20 个唯一 ID。 +- 必须存在。 +- 可以是任何 Status,包括 Closed。 +- 必须 `merged_into IS NULL`,即已经作为 source 的 Case 不能再次显式合并。 +- 不能等于 target。 +- 可以本身拥有历史 merged sources;执行时这些 inbound source 一并扁平化到新目标。 + +### Active job blocker + +任一显式 source 或其需要迁移的任务存在以下状态时阻止整次合并: + +- Playbook Pending 或 Running。 +- CaseAnalysisJob Pending 或 Running。 + +系统不自动取消任务。用户必须等待结束或通过对应合法操作取消后重试。 + +## 6. Preflight API + +### Endpoint + +`POST /api/cases/merge/preflight/` + +### Request + +```json +{ + "target_id": "ef235eb5-a0d8-48d2-a568-741909ce2e06", + "source_ids": [ + "87cc7cf4-66df-4f4e-bb96-66c1155c0219", + "aa3d9963-8338-4401-b92d-3eeaa9fcbfde" + ] +} +``` + +### Response + +预检返回: + +- target 摘要及可编辑字段。 +- 每个 source 的字段对比。 +- source Tags 差异。 +- 将迁移的 Alert、Case-level Enrichment、Comment、Playbook、AI Job 数量。 +- 保留在 source 的 Knowledge、Summary 和 AI report 标记。 +- active job blocker。 +- target Closed、source already merged、循环或其他 blocker。 +- 需要扁平化重新指向的历史 source 数量。 +- 不可撤销提示。 + +示例: + +```json +{ + "eligible": true, + "target": { + "id": "ef235eb5-a0d8-48d2-a568-741909ce2e06", + "case_id": "case_000100", + "status": "In Progress" + }, + "sources": [], + "move_counts": { + "alerts": 17, + "case_enrichments": 4, + "comments": 8, + "playbooks": 3, + "analysis_jobs": 2 + }, + "retained_counts": { + "knowledge": 1, + "source_reports": 2 + }, + "flattened_source_count": 3, + "blockers": [], + "warnings": ["This merge cannot be undone."] +} +``` + +Preflight 不获取长期锁,不保证执行时仍可合并。执行必须复检。 + +## 7. Merge API + +### Endpoint + +`POST /api/cases/merge/` + +### Request + +```json +{ + "operation_id": "a00384a1-bca5-471f-95e2-02fc8f419a3a", + "target_id": "ef235eb5-a0d8-48d2-a568-741909ce2e06", + "source_ids": [ + "87cc7cf4-66df-4f4e-bb96-66c1155c0219" + ], + "reason": "Duplicate cases generated by the same campaign", + "target_changes": { + "severity": "High", + "tags": ["phishing", "campaign-2026-07"] + } +} +``` + +规则: + +- operation_id 必填 UUID。 +- reason trim 后必填。 +- target_changes 可选,只允许 Case 正常可编辑业务字段。 +- target_changes 必须通过共享 Case serializer/state machine;不能在 merge 中把 target 设为 Closed。 +- target 默认字段全部保留,source 永不自动覆盖 target。 +- Tags 只有在 target_changes 明确给出时改变,不自动并集。 + +### Idempotency + +- operation_id unique。 +- 已存在相同 operation_id 时,不再次执行;返回 Operation 保存的原始 result。 +- 如果相同 operation_id 携带不同 target/source/reason,也返回原 result,不重新解释请求;可以附 `replayed: true`。 +- Operation 与业务迁移在同一数据库事务提交。 + +### Success response + +返回目标摘要、显式 source、扁平化 source、moved_counts、保留项和 operation_id。 + +### Failure + +- 结构错误返回 400。 +- 权限错误返回 403。 +- target/source 不存在返回 404 或统一 400;实现必须保持 schema 一致。 +- 执行时状态变化、active job 或已合并冲突返回 409。 +- 任一失败整次事务回滚。 +- 不返回原始数据库异常。 + +## 8. Execution algorithm + +在一个 `transaction.atomic()` 中: + +1. 按稳定 UUID 顺序 `select_for_update()` 锁定 target 和显式 sources,避免死锁。 +2. 查找显式 source 已有的 inbound merged sources,并锁定这些 Case。 +3. 重新校验 target、source、merged relation 和活动任务。 +4. 应用显式 target_changes。 +5. 保存 Operation 初始事实。 +6. 迁移业务关系。 +7. 把所有显式 source 和其 inbound source 的 `merged_into` 设为 target,形成一层关系。 +8. 显式 source 设置 Closed、Duplicate、closed_time;保留 Summary、AI 字段和 acknowledged_time。 +9. 写 target 和每个显式 source 的 merge AuditLog。 +10. 保存 moved_counts 和稳定 result。 +11. transaction commit 后发送汇总通知。 + +任何步骤失败全部回滚。 + +## 9. Relationship migration + +| Relation | Behavior | +| --- | --- | +| Alert.case | 全部改为 target | +| Alert.artifacts | 不变 | +| Alert-level Enrichment | 不变,随 Alert 可见 | +| Artifact-level Enrichment | 不变 | +| Case-level Enrichment | case_id 改为 target;不去重 | +| Completed/Failed Playbook | case_id 改为 target | +| Completed/Failed CaseAnalysisJob | case_id 改为 target | +| Pending/Running jobs | 阻止合并 | +| Comment on source Case | content object 改为 target | +| Comment reply tree | 保持 parent/mentions/attachments | +| Knowledge.case | 保持 source | +| Existing AuditLog | 保持原 object_id | +| Source Summary/report | 保持 source | + +### Comment origin + +评论迁移后必须能显示来源。推荐在 Comment 增加 nullable `origin_case`,或创建等价不可变 metadata。不得修改评论正文来注入来源文本。 + +## 10. Source final state + +显式 source: + +- `status = Closed` +- `verdict = Duplicate` +- `closed_time = merge time` +- `merged_into = target` +- 保留 title、description、summary、tags、AI 字段、acknowledged_time 和 correlation_uid。 +- 记录合并前 status/verdict 到 Operation snapshot 和 AuditLog。 + +历史 inbound source 已经是 merged source,只更新 merged_into 到最终 target,不重写其原 merge 时间或历史。 + +## 11. Correlation routing + +Alert ingestion 按 correlation_uid 找到 Case 后: + +1. 如果 Case.merged_into 为空,正常使用。 +2. 如果不为空,使用 merged_into 最终 target。 +3. 数据约束要求最多一跳;服务仍可防御性解析并检测循环。 +4. source 保留 correlation_uid,作为目标的历史 alias。 + +这项逻辑必须放在统一 Case correlation service,不能只修某个 Module。 + +## 12. Merged source API behavior + +### Read + +- GET list 默认排除 `merged_into IS NOT NULL`。 +- 提供 `include_merged=true` Admin/User/Viewer 筛选。 +- 精确 Case ID 搜索必须能找到 merged source。 +- GET detail 返回 200,并包含: + - `is_merged: true` + - target UUID/readable ID/title + - merge actor/time/reason + - 原只读字段和历史 Summary/report。 + +### Write + +对 merged source 的所有业务写操作返回 409: + +- PATCH/PUT/DELETE Case。 +- 添加/删除评论。 +- 启动 Playbook 或 AI 分析。 +- 新建 Case-level Enrichment/Knowledge。 +- 任何其他以 Case 为目标的 mutation。 + +409 响应包含安全目标摘要: + +```json +{ + "code": "case_merged", + "detail": "This case was merged and is read-only.", + "merged_into": { + "id": "...", + "case_id": "case_000100" + } +} +``` + +## 13. Visibility and metrics + +默认排除 merged source: + +- Case 主列表。 +- Dashboard Case 数量、状态分布和 SLA。 +- 任何“当前案件”统计。 +- Assignee 活跃工作量。 + +保留: + +- Audit 搜索。 +- 精确 ID 搜索。 +- `include_merged` 筛选。 +- target 的 Merged Sources tab。 + +## 14. Audit + +现有历史 AuditLog 永不迁移或改写。 + +本次合并: + +- target 一条 `merged` AuditLog。 +- 每个显式 source 一条 `merged` AuditLog。 +- 共享 operation_id。 +- metadata 包含 target、显式 sources、actor、reason、moved_counts、flattened count。 +- source changes 包含 status/verdict/merged_into 的 before/after。 +- 不为每个 Alert 外键变化生成独立业务审计,避免日志爆炸;迁移数量由 merge event 覆盖。 + +## 15. Notifications + +通知目标: + +- target 当前 assignee。 +- 每个显式 source 合并前 assignee。 +- 去重用户。 +- null assignee 忽略。 + +每个用户只收到一条 Inbox 消息,包含 target、source 数量、source IDs 和 reason。通知在 commit 后发送,通知失败不回滚合并。 + +## 16. Frontend + +### Entry + +- Case 列表 Admin 批量操作增加 Merge。 +- Detail 页面 Admin 可发起“Merge into another case”。 +- 非 Admin 不显示入口。 + +### Modal + +1. 选择或确认最多 20 个 source。 +2. 通过 Case ID/title 搜索明确选择任意有效 target。 +3. 调用 preflight。 +4. 展示 target/source 字段对比、Tags 差异、迁移数量、保留数据和 blocker。 +5. 允许手动编辑 target 字段。 +6. reason 必填。 +7. 显示不可撤销警告。 +8. 生成 operation_id 后提交;网络重试复用同一 operation_id。 + +### Merged source + +- Detail 顶部显示明显 Warning banner。 +- 提供 Open target case。 +- 所有编辑控件、评论、Playbook 等 mutation 隐藏或 disabled。 + +### Target + +新增 `Merged Sources` tab: + +- source Case ID、title。 +- 合并前 status/verdict。 +- merge time、actor、reason。 +- 打开只读 source。 +- 使用分页。 + +## 17. Acceptance criteria + +1. Admin 可把 1–20 个 source 原子合并到 target。 +2. User/Viewer 无法 preflight 或 merge。 +3. Closed target 被拒绝,Closed source 可合并。 +4. active Playbook/AI Job 阻止整次操作。 +5. 所有规定关系迁移或保留正确。 +6. 任一迁移异常整次回滚。 +7. 重复 operation_id 不产生重复迁移、审计或通知。 +8. 现有审计 object identity 不变。 +9. merged source GET 可读、所有 mutation 409。 +10. 后续 correlation_uid Alert 路由到最终 target。 +11. 二次合并扁平化,不形成多跳。 +12. 默认列表/Dashboard/SLA 排除 merged source。 +13. Merged Sources tab 可追溯原 Summary 和 AI report。 + +## 18. Known tradeoffs + +- Merge 不可撤销;误操作只能人工修复或恢复备份。 +- Knowledge 不迁移,target 与 source 可能分别保留知识。 +- target 字段默认胜出,不自动选择“更严重”的值。 +- 合并后的评论失去 source 详情页独立讨论入口,但通过 origin metadata 可追溯。 diff --git a/docs/specs/v0.6.0/03-playbook-execution.md b/docs/specs/v0.6.0/03-playbook-execution.md new file mode 100644 index 0000000..c264368 --- /dev/null +++ b/docs/specs/v0.6.0/03-playbook-execution.md @@ -0,0 +1,388 @@ +# Playbook Execution + +Status: Confirmed + +## 1. Purpose + +保留低心智成本的 Python `run()` 编程模型,同时增加可持久化的结构化执行阶段、明确时间、取消、完整重试、崩溃恢复和只读运行历史。 + +该设计不是工作流引擎。Stage 是观测事件,不是独立调度、恢复或重试的 Step。 + +## 2. Authoring model + +Playbook 继续由 Python 代码定义: + +```python +class Playbook(BasePlaybook): + NAME = "Contain Endpoint" + DESC = "Contain the endpoint associated with the case." + TAGS = ["EDR", "Response"] + RISK_LEVEL = "High" + + def run(self): + with self.stage("collect", "Collect endpoint context") as stage: + endpoints = collect_endpoints(self.case) + stage.summary = f"Collected {len(endpoints)} endpoint(s)." + + for endpoint in endpoints: + with self.stage("contain", f"Contain {endpoint.hostname}") as stage: + contain(endpoint) + stage.summary = "Containment request accepted." + + return f"Contained {len(endpoints)} endpoint(s)." +``` + +规则: + +- `run()` 仍是唯一执行入口。 +- Stage 完全可选;现有 v0.5.2 Playbook 无需修改即可运行。 +- Stage 不保存 Python 返回值或跨阶段输入。 +- Stage 不独立调度、不暂停、不恢复、不单步重试。 +- `run()` 未捕获异常导致整个 Run Failed。 +- 开发者可以在 Stage 内捕获可容忍错误,并显式写安全 summary。 +- Playbook 可直接使用 `httpx` 或厂商 SDK;平台不提供通用 Connector。 + +## 3. Explicit exclusions + +- 可视化/表单式编排器。 +- DAG、分支、并行 Step。 +- 中途人工审批;点击 Run 即授权整个 Playbook。 +- Running hard cancel 或 cooperative cancel。 +- 单步 retry/resume。 +- 源码、hash 或定义版本锁定。 +- 结构化 input schema。 +- HTTP/Webhook Connection profile。 +- 自动轮询或 WebSocket 进度。 + +## 4. Definition metadata + +定义扫描继续读取: + +- `NAME` +- `DESC` +- `TAGS` +- 新增 `RISK_LEVEL` + +风险枚举: + +- Low +- Medium +- High +- Critical + +默认 Low。风险只用于 UI 展示,不改变权限、确认或执行流程。 + +Run 不保存 DESC/TAGS/RISK_LEVEL 快照。历史页面按 name 解析当前定义: + +- 定义仍存在:展示当前 metadata。 +- 定义已删除或改名:只展示 Run 保存的 name,并标记 definition unavailable。 + +Pending/Retry 执行时始终加载当前最新 Python 代码。 + +## 5. Run model + +现有 `Playbook` 记录继续作为 Run,可考虑重命名 Python 类但不要求修改 db_table。 + +新增/调整字段: + +| Field | Type | Semantics | +| --- | --- | --- | +| job_status | enum | Pending/Running/Success/Failed/Cancelled | +| job_id | string/UUID | 当前执行标识 | +| retry_of | nullable self FK | Failed Run 的重试来源 | +| started_at | nullable datetime | claim 成功时间 | +| finished_at | nullable datetime | terminal 时间 | +| cancelled_by | nullable FK User | Pending cancel actor | +| remark | text | 终态安全摘要 | + +保留: + +- case +- name +- user +- user_input +- created_at/updated_at + +### Status transitions + +| Current | Allowed next | +| --- | --- | +| Pending | Running, Cancelled | +| Running | Success, Failed | +| Success | none | +| Failed | none; Retry creates new Run | +| Cancelled | none | + +不允许直接修改 job_status。所有状态变化通过 domain service。 + +### Timing + +- Pending 创建时 started_at/finished_at 为空。 +- Pending→Running 设置 started_at。 +- Running→Success/Failed 设置 finished_at。 +- Pending→Cancelled 设置 finished_at,不设置 started_at。 +- duration_seconds 由 started_at 和 finished_at 计算;Running 使用 now-started_at。 + +## 6. Stage model + +建议模型 `PlaybookStage`: + +| Field | Type | Notes | +| --- | --- | --- | +| id | UUID | primary key | +| playbook_run | FK | CASCADE at DB level, though Run API cannot delete | +| sequence | positive bigint | per Run append order | +| key | string | developer supplied, may repeat | +| label | string | human-readable | +| status | enum | Running/Success/Failed | +| summary | text | explicit safe summary | +| error_type | string | sanitized exception class | +| error_message | text | sanitized length-limited message | +| started_at | datetime | context enter | +| finished_at | nullable datetime | context exit | +| duration_ms | nullable bigint | derived/persisted | + +Constraints/indexes: + +- unique `(playbook_run, sequence)`. +- index `(playbook_run, sequence)`. +- index `(playbook_run, status)`. +- key/label 长度必须有限,但 Stage 数量不限。 + +### Stage context behavior + +进入 `with self.stage(key, label)`: + +1. 原子分配下一个 sequence。 +2. 创建 Running Stage。 +3. 返回可设置 `summary` 的 context object。 + +正常退出: + +1. 保存显式 summary。 +2. 设置 Success、finished_at、duration。 + +异常退出: + +1. 设置 Failed。 +2. 保存异常类型。 +3. 保存经过敏感字段过滤和长度限制的安全错误。 +4. 完整 traceback 只进入 Worker log。 +5. 原异常继续抛出,使 Run Failed。 + +Stage key/label 可以重复,支持循环动态生成。Stage 是扁平 sequence,不支持 parent。 + +### Output safety + +- 不自动 `str()` 或 JSON serialize 任意函数输出。 +- 不自动保存 HTTP response、LLM output、SIEM records 或变量值。 +- summary 由自定义代码显式提供。 +- error_message 使用统一 sanitizer,至少屏蔽 password/token/api_key/secret/authorization 等值。 +- API 不返回 traceback。 + +## 7. Queue and Worker behavior + +### Supported topology + +- 正式支持一个 Playbook Worker。 +- 全局 FIFO,按 created_at/id claim。 +- 不按 Case Severity 或用户优先级排序。 + +### Per-Case concurrency + +同一 Case 最多一个 Running Run: + +- claim 时跳过已经存在 Running Run 的 Case。 +- 同 Case 的其他 Pending 保持队列。 +- 不同 Case 在未来多 Worker 实现中可并行,但 v0.6.0 不支持多 Worker。 + +### Duplicate launch + +Run endpoint 不提供 idempotency。重复请求可以创建多条 Pending Run,这是已接受行为。 + +### Worker loss + +Playbook Worker 使用 Worker Health 心跳。检测到前一实例丢失后: + +- 遗留 Running Run 标记 Failed。 +- 遗留 Running Stage 标记 Failed。 +- remark 使用固定安全文本,说明 Worker stopped before completion。 +- 不自动重置 Pending 或重跑。 +- 用户检查后手动 Retry。 + +实现可在 Worker 成功获取 singleton lease 后执行 orphan recovery。不得仅按运行时长把合法长任务判失败。 + +## 8. Cancellation + +只有 Pending 可取消。 + +`POST /api/playbooks/{id}/cancel/` + +- Admin/User 可取消任意 Pending Run。 +- Viewer 403。 +- 非 Pending 返回 409。 +- 设置 Cancelled、finished_at、cancelled_by 和安全 remark。 +- 写 AuditLog。 +- 不发送 completion notification。 + +Running 无取消入口。Playbook 中每个外部调用必须自行设置超时。 + +## 9. Retry + +`POST /api/playbooks/{id}/retry/` + +- 仅 Failed Run。 +- Admin/User 可重试任意 Failed Run。 +- Viewer 403。 +- 创建新的 Pending Run。 +- `retry_of` 指向原 Run。 +- 复制 case、name、user_input。 +- 新 Run 的 user 是执行 Retry 的当前用户。 +- 使用当前 Case 数据和最新 Playbook 代码。 +- 原 Run/Stage 不修改。 +- 写 retry AuditLog,关联新旧 Run。 +- Retry 不受幂等保护;重复点击可能创建多个 Run。 + +## 10. Launch + +`POST /api/playbooks/run/` + +- Admin/User 可运行,Viewer 403。 +- 任何未合并 Case 均可运行,包括 Closed。 +- merged source 返回 409 和 target 摘要。 +- name 必须能在当前定义扫描中找到。 +- user_input 是可选自由文本。 +- 点击 Run 直接创建 Pending,不增加确认。 +- risk level 只展示。 + +## 11. API shape + +Playbook Run 资源改为 read-only: + +- GET list。 +- GET retrieve。 +- GET definitions。 +- POST run。 +- POST cancel。 +- POST retry。 +- GET stages(detail action 或独立 nested endpoint)。 + +禁止: + +- 普通 POST create。 +- PUT/PATCH。 +- DELETE。 + +Stage API: + +- 只读。 +- 必须按 sequence 分页。 +- 支持 status 筛选可选,但不得一次返回无限 Stage。 + +### Run response additions + +```json +{ + "id": "...", + "playbook_id": "playbook_000123", + "job_status": "Running", + "retry_of": null, + "started_at": "2026-07-30T08:00:00Z", + "finished_at": null, + "duration_seconds": 42, + "current_stage": { + "sequence": 8, + "key": "contain", + "label": "Contain endpoint-7", + "status": "Running" + }, + "stage_count": 8, + "definition": { + "available": true, + "risk_level": "High", + "tags": ["EDR", "Response"] + } +} +``` + +## 12. Remark semantics + +| Terminal state | Remark | +| --- | --- | +| Success | `str(run() return value)`,经长度限制和安全处理 | +| Failed | 固定安全摘要,可引用失败 Stage label/sequence | +| Cancelled | 固定文本并记录取消 actor | + +过程日志不得拼接到 remark。原始异常只进服务器日志。 + +## 13. Notifications + +遵循发起用户现有 `notify_on_playbook_completion` 偏好: + +- Success 通知。 +- Failed 通知。 +- Cancelled 不通知。 +- Stage 状态变化不通知。 +- Retry 新 Run 按新发起用户偏好处理。 + +## 14. Audit + +只记录用户动作: + +- launch +- cancel +- retry + +Worker 自动状态变化不写全局 AuditLog,因为 Run/Stage 已是状态事实。 + +Audit metadata 不包含 user_input 全文、Stage summary 或任何 Secret。 + +## 15. Frontend + +### Definition selection + +- 显示 name、description、tags、risk level。 +- 点击 Run 直接排队。 +- 保留自由文本 user_input。 + +### Run list/detail + +- 状态、Case、发起人、时间、duration、retry relation。 +- Pending 显示 Cancel。 +- Failed 显示 Retry。 +- Run/Stage 无 Delete/Edit。 +- Stage 使用分页的扁平时间线或表格。 +- 页面不自动轮询、不使用 WebSocket;提供 Refresh。 +- definition 删除后显示 unavailable,而不是报页面错误。 + +## 16. Migration + +- 现有四状态数据直接保留。 +- 新 Cancelled 只用于 v0.6.0 后记录。 +- 已有 Success/Failed Run 的 started_at 可为空,不伪造历史时间。 +- 旧 Running Run 在升级后由首次 Worker recovery 处理。 +- retry_of、timing、cancel actor 均 nullable。 + +## 17. Acceptance criteria + +1. v0.5.2 旧 Playbook 不修改即可运行。 +2. 可选 Stage 正确保存动态重复 key 和 sequence。 +3. Stage 异常导致 Stage/Run Failed,API 不泄露 traceback。 +4. Pending 可取消,Running 不可取消。 +5. Failed Retry 创建新 Run并保留原历史。 +6. Run/Stage 所有普通 mutation/delete 被拒绝。 +7. 同一 Case 不同时 Running 两个 Run。 +8. FIFO claim 可预测。 +9. Worker 崩溃后遗留 Running 标记 Failed且不自动重跑。 +10. Closed Case 可运行,merged source 409。 +11. Success/Failed 通知符合用户偏好。 +12. Stage 数量大时 API 正确分页。 + +## 18. Known tradeoffs + +- 最新代码执行使 Pending Run 语义可能在排队期间变化。 +- 不保存 metadata 快照,历史 risk/tags 会随定义变化。 +- 重复 launch/retry 可产生重复外部副作用。 +- Running 不可取消。 +- 动态无限 Stage 可能产生大量数据,开发者需自律;平台只通过分页保护读取。 +- 直接 httpx 调用的重试、幂等和 Secret 安全由自定义代码负责。 diff --git a/docs/specs/v0.6.0/04-custom-variables.md b/docs/specs/v0.6.0/04-custom-variables.md new file mode 100644 index 0000000..3e33b1f --- /dev/null +++ b/docs/specs/v0.6.0/04-custom-variables.md @@ -0,0 +1,316 @@ +# Custom Variables + +Status: Confirmed + +## 1. Purpose + +提供一个由 Admin 通过 UI 管理、由自定义 Playbook 和 Agentic Module 在后端运行时读取的字符串变量仓库。它类似受控的数据库版环境变量,但不会修改真实 `os.environ`,也不影响 Django 全局配置。 + +典型用途: + +- 外部系统 base URL。 +- API token、username/password。 +- tenant/project ID。 +- 自定义 JSON 字符串。 +- Module 或 Playbook 的运行参数。 + +## 2. Scope + +### Consumers + +- `BasePlaybook.get_variable(key)` +- `BaseModule.get_variable(key)` + +### Not consumers + +- Agent API。 +- CLI 或插件。 +- 前端运行时。 +- Django template。 +- 其他任意后端模块。 +- 系统 Runtime Settings。 + +### Excluded + +- 写入真实进程环境变量。 +- 全局配置覆盖。 +- per-Playbook/per-Module scope。 +- 变量继承或 override。 +- value 类型系统。 +- `.env` 导入导出。 +- REQUIRED_VARIABLES 声明。 +- Secret 加密、版本历史或自动泄露防护。 + +## 3. Data model + +建议模型 `CustomVariable`: + +| Field | Type | Rules | +| --- | --- | --- | +| id | UUID | primary key | +| key | CharField(128) | unique, immutable | +| value | TextField | non-empty, max 65,536 UTF-8 bytes | +| is_secret | Boolean | controls API masking only | +| description | TextField | optional | +| enabled | Boolean | default true | +| created_at | DateTime | auto | +| updated_at | DateTime | auto | + +### Key validation + +Regex: + +```text +[A-Z][A-Z0-9_]{0,127} +``` + +- 全局唯一。 +- 大小写敏感,但合法输入只有大写。 +- 创建后不可改名。 +- 修改 key 的 PATCH/PUT 返回 validation error。 + +### Value validation + +- 必须至少一个字符。 +- 以 UTF-8 bytes 计算,最多 65,536 bytes。 +- 可包含换行。 +- 不支持 binary。 +- 不做 trim;空格可能是有意值,但零长度禁止。 +- 更新 Secret 时省略 value 表示保留旧值。 +- 显式 `""` 始终拒绝。 + +## 4. Storage security + +已确认: + +- Secret 和非 Secret 均以 PostgreSQL 明文保存。 +- 数据库管理员、数据库泄露和未加密备份可以读取 Secret。 +- `is_secret` 只控制 API/UI 显示与审计,不提供 cryptographic protection。 +- 用户文档必须明确该限制。 + +不得在 AuditLog、普通 API response、异常文本或日志中复制 value。 + +## 5. Runtime API + +Base class helper: + +```python +def get_variable(self, key): + ... +``` + +返回: + +- Enabled 且存在:原始 `str`。 +- 不存在:`None`。 +- Disabled:`None`。 +- 已删除:`None`。 + +行为: + +- 每次调用都查询 PostgreSQL。 +- 不做 per-run、per-message 或 Worker 全局缓存。 +- Admin 在 Playbook/Module 运行中修改、Disabled 或删除变量,下一次读取立即看到变化。 +- 不抛 missing variable 异常。 +- 不支持 default 参数作为平台约定;自定义代码自行使用 `or` 或显式 None 处理。 +- 不返回 is_secret/description 等 metadata。 +- 不记录 read audit 或 usage relation。 + +建议共享 service 位于 `apps.settings.custom_variables` 或等价窄模块,BasePlaybook/BaseModule 只做代理,避免重复 ORM 代码。 + +## 6. Permissions + +| Action | Admin | User | Viewer | Playbook/Module Worker | +| --- | --- | --- | --- | --- | +| List metadata | Yes | No | No | No | +| Read non-secret value via Admin API | Yes | No | No | N/A | +| Reveal secret | Yes | No | No | N/A | +| Create/update/delete | Yes | No | No | No | +| get_variable runtime | No direct API | No direct API | No direct API | Yes | + +Worker 读取不根据发起 Playbook 的用户角色过滤。User 发起的 Playbook 仍可读取所有 Custom Variables,因此 Admin 必须只安装可信自定义代码。 + +## 7. Admin API + +建议资源: + +`/api/settings/custom-variables/` + +### List/retrieve default representation + +非 Secret: + +```json +{ + "id": "...", + "key": "EDR_BASE_URL", + "value": "https://edr.internal.example", + "is_secret": false, + "description": "Production EDR API", + "enabled": true +} +``` + +Secret: + +```json +{ + "id": "...", + "key": "EDR_API_TOKEN", + "value": "", + "value_configured": true, + "is_secret": true, + "description": "Production EDR token", + "enabled": true +} +``` + +### Reveal + +`POST /api/settings/custom-variables/{id}/reveal/` + +- 仅 active Admin session。 +- 不要求重新输入本地或 LDAP 密码。 +- 返回一次明文 value。 +- 每次请求写 AuditLog。 +- 不应支持 bulk reveal。 +- response 应使用 no-store cache headers。 + +### Update + +- key 不可改。 +- Secret update 未包含 value:保留旧值。 +- Secret→non-secret:API 需要显式确认字段,例如 `confirm_secret_exposure=true`。 +- 没有确认返回 400。 +- non-secret→secret 正常允许。 +- 所有 changes 审计不包含 value。 + +### Delete + +- 允许硬删除。 +- UI 必须二次确认。 +- API 不负责扫描 Python 代码引用。 +- 删除后运行时返回 None。 +- 删除 AuditLog 保存 key、description、is_secret、enabled,不保存 value。 + +## 8. Audit + +写 AuditLog: + +- create +- update +- enable/disable(作为 update) +- reveal +- delete + +不写: + +- get_variable runtime read。 +- Admin 普通 list/retrieve。 + +Secret 和非 Secret value 均不得进入 changes。可使用: + +```json +{ + "changes": { + "value": {"from": "***", "to": "***"}, + "enabled": {"from": true, "to": false} + }, + "metadata": { + "key": "EDR_API_TOKEN", + "value_changed": true + } +} +``` + +不保留旧 value,不支持 rollback。 + +## 9. Frontend + +位置:System Settings 中新增 `Custom Variables`。 + +### List + +- Key。 +- Description。 +- Secret 标记。 +- Enabled。 +- Value configured。 +- Updated time。 +- Edit/Delete actions。 +- Secret 默认不可见。 + +### Create/edit modal + +- Key 创建时可编辑,编辑时只读。 +- Value 使用 multiline input;Secret 使用 password input。 +- is_secret switch。 +- enabled switch。 +- description。 +- value byte limit 提示。 +- 编辑 Secret 时 value 留空表示不修改,UI 必须明确说明。 + +### Reveal + +- Secret 行显示 Reveal。 +- active Admin session 直接调用。 +- 明文只显示在临时 Modal。 +- Modal 关闭后从前端 state 清除。 +- 不复制到列表 state、URL、localStorage 或 console。 + +### Secret downgrade + +从 Secret 改为普通变量时显示额外确认: + +> This value will become visible in normal Admin API responses and UI. + +## 10. Interaction with custom code + +示例: + +```python +class Playbook(BasePlaybook): + def run(self): + base_url = self.get_variable("EDR_BASE_URL") + token = self.get_variable("EDR_API_TOKEN") + if not base_url or not token: + raise ValueError("EDR custom variables are not configured.") +``` + +自定义代码责任: + +- 检查 None。 +- 解析 JSON/boolean/number。 +- 不把 Secret 写入 log、Stage summary、remark、Enrichment 或异常。 +- 为 HTTP 调用设置 timeout、TLS、proxy、重试和幂等。 + +平台不静态分析 key,也不展示“变量被哪些脚本使用”。 + +## 11. Migration and backup + +- 新表 migration,无旧数据迁移。 +- v0.5.2 现有 LLM/SIEM/LDAP/TI Secret 不自动复制到 Custom Variables。 +- 备份/恢复自然包含明文 value。 +- Compose 文档需提醒保护数据库备份。 + +## 12. Acceptance criteria + +1. 只有 Admin 能访问资源和 Reveal。 +2. key regex、唯一和不可改名规则生效。 +3. 空 value 和超过 65,536 bytes 被拒绝。 +4. Secret 默认 response 不含明文。 +5. Admin Reveal 返回明文并写不含 value 的审计。 +6. Secret→普通无确认被拒绝。 +7. Playbook 和 Module 每次读取当前数据库值。 +8. Disabled/missing/deleted 返回 None。 +9. Agent API、CLI 和普通用户没有读取端点。 +10. CRUD AuditLog 永不包含 old/new value。 +11. 删除无需引用检查且立即影响运行时读取。 + +## 13. Known tradeoffs + +- PostgreSQL 和备份保存明文 Secret。 +- active Admin session 无需重新认证即可 Reveal。 +- User 发起的可信 Playbook 可以间接使用全部 Secret。 +- 平台无法阻止恶意或错误自定义代码泄露 Secret。 +- 每次 ORM 查询增加少量开销,这是为即时一致性接受的成本。 diff --git a/docs/specs/v0.6.0/05-worker-health.md b/docs/specs/v0.6.0/05-worker-health.md new file mode 100644 index 0000000..93881fd --- /dev/null +++ b/docs/specs/v0.6.0/05-worker-health.md @@ -0,0 +1,362 @@ +# Worker Health + +Status: Confirmed + +## 1. Purpose + +让 Admin 不依赖 Docker CLI 或日志文件,就能判断后台 Worker 是否存活、正在做什么、是否失败以及业务积压情况。 + +v0.6.0 最终监控六个逻辑 Worker: + +1. Agentic Module。 +2. Case Analysis。 +3. Playbook。 +4. ELK Action。 +5. Dashboard Cache。 +6. Integration Health。 + +监控粒度是逻辑进程类型,不是每个 Module/Playbook definition,也不是 Docker container ID。 + +## 2. Architecture + +Worker Health 基础设施集成进 `apps.common.worker_runner.run_worker()`。 + +每个 Worker: + +- 启动时获取 Redis singleton lease。 +- 启动 daemon heartbeat thread。 +- 主线程在 iteration 边界更新执行状态与计数。 +- 优雅退出时释放 lease并留下安全退出原因。 + +Admin API 从 Redis 读取当前状态,并按 Worker 类型附加 backlog diagnostics。 + +## 3. Redis keys + +建议: + +```text +worker-health:v1:{worker_type}:lease +worker-health:v1:{worker_type}:state +worker-health:v1:{worker_type}:last-exit +``` + +### Lease + +- value 至少包含随机 instance_id。 +- TTL 30 秒。 +- heartbeat 每 10 秒 compare-and-refresh,只有 owner instance_id 可续租。 +- 第二个同类型 Worker 发现有效 lease 后拒绝启动。 +- lease 过期后新实例可获取。 +- 不允许 last-writer-wins 覆盖。 + +### State + +state 可与 lease 同一个 JSON key,也可独立;必须保证 owner 才能更新。 + +建议字段: + +| Field | Meaning | +| --- | --- | +| worker_type | stable logical identifier | +| instance_id | random UUID per process | +| hostname | safe host/container hostname | +| state | Starting/Idle/Running/Degraded | +| started_at | process start | +| heartbeat_at | last heartbeat | +| iteration_started_at | current/last iteration start | +| last_iteration_success_at | any successful iteration, including idle | +| last_processed_at | last iteration that processed work | +| last_failure_at | last failed/partial-failed iteration | +| last_duration_ms | last iteration duration | +| consecutive_failures | reset on successful iteration | +| iteration_count | process-lifetime count | +| processed_iteration_count | iterations with processed=true | +| success_count | successful iterations | +| failure_count | failed or partial-failed iterations | +| last_message | safe WorkerIterationResult message | +| last_error | normalized safe error | +| log_role | log file role | + +PID、完整 command、环境变量和 Secret 不通过 API 暴露。 + +### Last exit + +优雅退出时记录短 TTL 或非租约型安全信息: + +- instance_id +- exited_at +- reason=`graceful` + +意外退出没有 last-exit update,API 通过 lease expired 判定。 + +## 4. Heartbeat + +- daemon thread 每 10 秒刷新 lease。 +- 主线程运行长任务或 sleep 时仍持续刷新。 +- heartbeat Redis 写失败应记录安全日志;如果长期失败 lease 会过期。 +- heartbeat thread 不改变业务 iteration 状态。 +- Running 不设最大时长,也没有 Stalled。 +- 只要 heartbeat 存活,任意长任务保持 Running。 + +线程退出规则: + +- Worker 正常 SIGTERM/KeyboardInterrupt 时停止 heartbeat。 +- 释放仅属于自身 instance_id 的 lease。 +- 不能删除新实例已取得的 lease。 + +## 5. Singleton startup + +Worker 启动: + +1. 生成 instance_id。 +2. 原子 SET NX 获取 lease。 +3. 失败则抛 CommandError 并退出,Compose 可记录重启失败。 +4. 成功写 Starting state。 +5. 启动 heartbeat thread。 +6. 进入主循环。 + +这适用于所有六个正式 Worker。`--once` 是运维命令,不应长期占用 singleton lease;如果它会与正式 Worker 并行影响同一数据,需要显式决定是否短暂获取 lease,默认建议获取以避免并发。 + +## 6. States + +API 状态: + +| State | Definition | +| --- | --- | +| Starting | live lease exists, first iteration not completed | +| Idle | live lease, last iteration successful, currently no work | +| Running | live lease, iteration currently executing | +| Degraded | live lease, latest iteration has any failure | +| Down | expected Worker has no live lease | + +### Never reported + +系统知道六种 expected worker type。没有 lease 且没有 state/last-exit 时: + +- state=Down +- reason=`never_reported` +- UI 显示 Never reported + +### Graceful and expired + +- 正常退出:Down / graceful。 +- 意外崩溃或进程被杀:30 秒后 Down / heartbeat_expired。 +- Redis 本身不可用不是 Unknown/Down;整个 Worker Health API 返回 503。 + +### Degraded recovery + +- 一次完整失败立即 Degraded。 +- `WorkerIterationResult.failure_count > 0` 的部分失败也立即 Degraded。 +- consecutive_failures 增加。 +- 下一次无失败的 iteration 清零并恢复 Idle 或完成后的健康状态。 + +## 7. WorkerIterationResult contract + +扩展当前 dataclass: + +```python +@dataclass(frozen=True) +class WorkerIterationResult: + processed: bool = False + message: str = "" + failure_count: int = 0 +``` + +语义: + +- `processed` 表示至少完成一个业务工作项。 +- `failure_count` 表示 iteration 内被捕获的业务失败数。 +- 抛异常是完整 iteration failure。 +- `message` 必须是安全摘要。 + +### Time semantics + +- 正常 idle polling 更新 `last_iteration_success_at`。 +- 只有 `processed=True` 更新 `last_processed_at`。 +- partial failure 不更新 success timestamp;是否同时有 processed 由结果如实记录。 + +## 8. Backlog diagnostics + +不得强制统一为 queue_depth。 + +### Playbook + +- Pending Run count。 +- oldest Pending age。 +- Running count。 + +### Case Analysis + +- Pending job count。 +- oldest eligible scheduled job age。 +- Running count。 + +### Module + +复用 Redis Stream health: + +- stream length。 +- consumer group pending。 +- consumer count。 +- last-delivered-id。 +- 可计算的 lag。 +- 每个 Module definition 仍在现有 Custom 页面展示;Worker Health 可给汇总和跳转。 + +### ELK Action + +外部索引完整积压不可可靠得知,只显示: + +- last poll time。 +- last poll actions/sent/skipped。 +- last successful processed time。 +- current configured interval。 + +### Dashboard Cache + +- 24h/7d/30d 各缓存 age。 +- generated/refreshed time。 +- configured interval。 +- cache missing 标记。 + +### Integration Health + +- enabled target count。 +- Healthy/Unhealthy/Disabled counts。 +- current serial check progress(如果可安全获得)。 + +Backlog 查询失败不应使 heartbeat 消失。API 行级 diagnostics 可返回 safe warning。 + +## 9. Error safety + +`last_error` 只包含: + +- exception type。 +- 标准 reason code 或固定安全摘要。 +- failure time。 +- log role。 + +不包含: + +- 原始 exception message。 +- traceback。 +- HTTP response。 +- SIEM query/event。 +- username、password、token、API key。 + +完整排查信息留在 Worker 日志。 + +## 10. API + +建议: + +`GET /api/settings/operations/workers/` + +仅 Admin。 + +正常返回六行: + +```json +{ + "results": [ + { + "worker_type": "playbook", + "display_name": "Playbook Worker", + "state": "Idle", + "reason": "", + "instance_id": "...", + "hostname": "asp-worker-playbook", + "started_at": "...", + "heartbeat_at": "...", + "last_iteration_success_at": "...", + "last_processed_at": "...", + "last_failure_at": null, + "last_duration_ms": 8, + "consecutive_failures": 0, + "counters": {}, + "last_message": "", + "last_error": null, + "log_role": "agentic-playbook-worker", + "backlog": { + "pending_count": 0, + "oldest_pending_age_seconds": null, + "running_count": 0 + } + } + ] +} +``` + +Redis health storage 无法访问: + +- HTTP 503。 +- 固定信息:`Worker health monitoring is unavailable.` +- 不返回六个伪造 Down 状态。 + +API 不提供: + +- Restart。 +- Run Now。 +- Stop。 +- Tail/download logs。 +- 历史趋势。 + +## 11. Permissions and audit + +- 仅 Admin 可访问 API/UI。 +- User/Viewer 403。 +- Health read 不写 AuditLog。 +- heartbeat/state change 不写 AuditLog。 +- 没有 Worker health notification。 + +## 12. Frontend + +最终位置:`System Settings → Operations → Worker Health`。 + +行为: + +- 每 10 秒自动刷新。 +- 提供手动 Refresh。 +- 503 显示页面级 monitoring unavailable,不显示全红 Down。 +- 状态 Tag:Starting、Idle、Running、Degraded、Down。 +- 每行显示心跳、最后处理、最后失败、错误摘要、积压和 log reference。 +- Worker-specific backlog 使用不同 detail 展示,不强行同列 JSON。 +- 不提供任何 mutation 按钮。 +- 可展示运维命令文本,但 Operations Center TODO 尚未确认具体命令 UX。 + +## 13. History and retention + +- Redis 只保存当前状态。 +- 计数从进程启动开始。 +- Worker 重启后计数重置。 +- 不提供 uptime 百分比、趋势图或历史事件。 +- 日志是历史排查来源。 + +## 14. Compose changes + +- 新增 Integration Health Worker 服务。 +- 六个 Worker 使用明确 command 和 log role。 +- 不挂载 Docker socket。 +- 不通过 Web app 重启容器。 +- Worker singleton 冲突应在日志中明确显示。 + +## 15. Acceptance criteria + +1. 六类 Worker 正常显示 Starting→Idle/Running。 +2. 第二个同类型实例拒绝启动。 +3. heartbeat 10 秒、30 秒过期行为正确。 +4. 长 Running 任务保持 heartbeat,不被判 Stalled/Down。 +5. 进程被强杀后约 30 秒显示 Down/heartbeat expired。 +6. 优雅退出显示 Down/graceful。 +7. 第一次 iteration/partial failure 立即 Degraded,后续成功恢复。 +8. idle success 与 actual processed 时间分离。 +9. Redis 不可用 API 503。 +10. 错误 API 不泄露原始异常或 Secret。 +11. 每类 backlog 数据符合专属 schema。 +12. User/Viewer 403,Admin 页面每 10 秒刷新。 + +## 16. Known tradeoffs + +- Redis 故障时无法独立判断 Worker 健康。 +- 当前状态和计数不会跨进程重启保留。 +- daemon thread 增加少量线程复杂度,但解决了长任务 liveness。 +- Running 永不按时长 Stalled,业务死循环只要 heartbeat thread 活着就仍显示 Running。 diff --git a/docs/specs/v0.6.0/06-integration-health.md b/docs/specs/v0.6.0/06-integration-health.md new file mode 100644 index 0000000..4a3b793 --- /dev/null +++ b/docs/specs/v0.6.0/06-integration-health.md @@ -0,0 +1,355 @@ +# Integration Health + +Status: Confirmed + +## 1. Purpose + +通过独立定时 Worker 主动验证正式支持的外部集成和关键依赖,并在 Admin Operations 页面展示每个配置实例的当前状态。 + +该功能表达“最近一次轻量检查是否成功”,不保证所有业务查询、数据权限或具体动作都可用。 + +## 2. Targets + +定时检查: + +- Splunk singleton config。 +- ELK singleton config。 +- 每个 enabled OpenAI-compatible LLM Provider。 +- AlienVault OTX。 +- OpenCTI。 +- LDAP。 +- Redis。 +- S3-compatible object storage。 + +不检查 PostgreSQL;Web/API 本身已依赖 PostgreSQL。 + +大量 EnrichmentProvider 枚举值不等于可检查的官方集成。 + +## 3. Dedicated Worker + +新增 `run_integration_health_worker`: + +- 使用公共 `run_worker()`。 +- 作为第六类 singleton Worker 出现在 Worker Health。 +- 固定每 5 分钟检查一次。 +- 串行检查目标。 +- 单目标超时 10 秒。 +- 单项失败后继续其他目标。 +- iteration 中任一失败使 Integration Health Worker 本轮 `failure_count > 0`,因此 Worker 状态 Degraded。 + +页面加载不直接 fan-out 外部请求。 + +## 4. States + +持久状态只有: + +- Healthy +- Unhealthy +- Disabled + +附加 reason 表达细节: + +| Situation | State | Reason | +| --- | --- | --- | +| enabled, never checked | Unhealthy | never_checked | +| check running | 保持原状态 | transient `checking=true` | +| last check succeeded | Healthy | ok | +| last check failed | Unhealthy | normalized failure code | +| config disabled | Disabled | disabled | +| config incomplete/not configured | Disabled | not_configured | + +不使用 Unknown/Degraded/Checking 作为持久 state。 + +未配置和 Disabled 是中性状态,不算健康也不算故障。页面不计算整体 platform/integration status。 + +## 5. Data model + +建议模型 `IntegrationHealth`: + +| Field | Type | Notes | +| --- | --- | --- | +| id | UUID | primary key | +| integration_type | string enum | splunk/elk/llm/otx/opencti/ldap/redis/storage | +| config_object_id | nullable string | LLM Provider UUID;singleton/dependency 使用稳定 sentinel | +| display_name | string | safe display label | +| state | enum | Healthy/Unhealthy/Disabled | +| reason_code | string | normalized | +| message | string | fixed safe message | +| checking_started_at | nullable datetime | transient check marker | +| last_checked_at | nullable datetime | any completed check | +| last_success_at | nullable datetime | successful check | +| last_failure_at | nullable datetime | failed check | +| duration_ms | nullable integer | latest check | +| consecutive_failures | nonnegative integer | reset on success | +| updated_at | datetime | auto | + +Constraints: + +- unique `(integration_type, config_object_id)`,null/sentinel 处理必须稳定。 +- index `(state, integration_type)`。 +- 不保存完整 config、URL、username、Secret、response body。 + +只保存当前状态,不保存历史行或趋势。 + +## 6. Configuration synchronization + +### Create + +- 新 enabled config:创建 Unhealthy/never_checked。 +- 新 disabled config:创建 Disabled/disabled。 + +### Update + +相关配置字段改变: + +- Enabled:立即 Unhealthy/configuration_changed。 +- Disabled:Disabled/disabled。 +- 不保留旧 Healthy 到下一个检查。 + +现有 Settings 保存逻辑应在同一 transaction commit 后同步 health 状态。 + +### Delete + +- 多实例配置(例如 LLM Provider)删除时同步硬删除 health row。 +- singleton config 不删除;Disabled 时保留 Disabled row。 +- 配置 CRUD 自身继续由现有 AuditLog 追溯。 + +## 7. Check depth by target + +### LLM + +- 对每个 enabled Provider 发最小 Chat Completions 请求。 +- 验证 base URL、认证、model 和实际推理路径。 +- 限制最大 token,接受每 5 分钟极低但非零费用。 +- 不保存 prompt/response content。 + +### Splunk + +- 使用轻量认证管理/信息接口。 +- 不执行 SPL。 +- 不依赖 index、time field 或数据存在。 + +### ELK + +- 使用集群 info/health 或等价轻量认证接口。 +- 不搜索用户 index。 +- 不将 yellow 等集群业务状态自动扩展为复杂 Degraded;检查器应按能否满足现有连接需求返回 Healthy/Unhealthy,并在 reason code 中规范化。 + +### LDAP + +- 连接服务器。 +- 使用配置的 bind DN/password 进行 service bind。 +- 验证 search base 可访问。 +- 不保存或使用专用普通用户测试密码。 +- 没有 bind DN 时执行当前支持模式下可行的基础连接/search check。 + +### OTX/OpenCTI + +- 调用轻量身份/账户 endpoint。 +- 验证 Token。 +- 不执行 IOC 搜索。 + +### Redis + +- PING。 +- 读取安全的基础 INFO:版本、内存、连接状态。 +- 不写测试 key。 +- 不重复 Module stream backlog diagnostics。 + +### Object storage + +- 验证 endpoint、凭据和目标 bucket metadata/list 权限。 +- 不写入或删除 probe object。 + +## 8. Check service contract + +统一内部结果: + +```python +@dataclass(frozen=True) +class IntegrationCheckResult: + success: bool + reason_code: str + message: str + duration_ms: int +``` + +规范 reason_code 至少包括: + +- ok +- never_checked +- configuration_changed +- not_configured +- disabled +- authentication_failed +- timeout +- tls_error +- connection_refused +- dns_error +- invalid_response +- permission_denied +- service_unavailable +- unknown_error + +message 必须来自固定映射,不直接使用 exception message。 + +## 9. State update rules + +检查开始: + +- 设置 checking_started_at。 +- 不改变当前 state。 + +成功: + +- state=Healthy。 +- reason_code=ok。 +- last_checked_at/last_success_at=now。 +- consecutive_failures=0。 +- 清空 checking_started_at。 + +失败: + +- 第一次失败立即 state=Unhealthy。 +- last_checked_at/last_failure_at=now。 +- consecutive_failures += 1。 +- 保存 normalized reason/message。 +- 清空 checking_started_at。 + +Worker 崩溃遗留 checking_started_at 时,下次 Worker 可覆盖;持久 state 仍是上一次结果。 + +## 10. Manual Settings tests + +保留现有各配置页 Test: + +- 用于测试未保存或已保存配置。 +- 已保存配置的 Test 结果更新对应 IntegrationHealth。 +- 未保存临时配置的 Test 不更新 health。 +- Manual Test 继续写现有 AuditLog。 +- Unified Integration Health 页面不提供 Check Now。 + +普通业务调用的成功/失败永不更新 IntegrationHealth,避免查询输入、限流或业务权限错误污染集成状态。 + +## 11. API + +建议: + +`GET /api/settings/operations/integrations/` + +仅 Admin。 + +返回每个目标/配置实例的独立状态: + +```json +{ + "results": [ + { + "integration_type": "llm", + "config_object_id": "...", + "display_name": "OpenAI Production", + "state": "Healthy", + "reason_code": "ok", + "message": "The provider responded successfully.", + "checking": false, + "checking_started_at": null, + "last_checked_at": "...", + "last_success_at": "...", + "last_failure_at": null, + "duration_ms": 821, + "consecutive_failures": 0, + "settings_route": "/system/llm-providers/..." + } + ] +} +``` + +- 不返回 overall status。 +- 不返回 Secret、完整 endpoint、username 或 response preview。 +- API 只读取 PostgreSQL,不执行检查。 +- Redis target 的健康状态也是定时检查结果;与 Worker Health Redis 503 是不同语义。 + +## 12. Error safety + +UI/API 只显示: + +- normalized reason code。 +- 固定安全 message。 +- check time/duration。 + +禁止: + +- 原始 exception。 +- traceback。 +- response body。 +- URL query。 +- username。 +- API key/token/password。 + +完整异常进入 Integration Health Worker log,仍需遵循现有日志 Secret 规范。 + +## 13. Permissions and audit + +- 仅 Admin 可看 API/UI。 +- User/Viewer 403。 +- Scheduled check 不写 AuditLog。 +- 定时状态变化不写 AuditLog。 +- Manual saved-config Test 写现有 AuditLog。 +- 不发送 Inbox/Webhook 健康通知。 + +## 14. Frontend + +位置:`System Settings → Operations → Integration Health`。 + +- 与 Worker Health 独立表格/API。 +- 每 30 秒读取 PostgreSQL 当前状态。 +- 提供页面手动 Refresh,但不触发外部检查。 +- 展示 integration type/name/state/reason/last check/duration/consecutive failures。 +- Disabled/Not configured 可见且使用中性样式。 +- enabled never checked 显示 Unhealthy + Never checked。 +- checking 时保留原 state Tag并显示 Checking 辅助标记。 +- 行可跳转对应 Settings 页面。 +- 不显示 overall status。 +- 不提供 Check Now。 + +## 15. Worker Health interaction + +Integration Health Worker 自身: + +- 使用第六个 Worker type。 +- heartbeat/lease 遵循 Worker Health Spec。 +- 本轮任一集成失败时 WorkerIterationResult.failure_count > 0,Worker Health 显示 Degraded。 +- Integration 表显示具体失败目标。 +- Redis health target 失败可能同时导致 Worker 心跳不可用;此时 Worker Health API 可能 503,但 PostgreSQL 中最后 Integration Health 结果仍可显示。 + +## 16. Migration + +- 创建 IntegrationHealth 表。 +- 为现有配置生成初始行: + - enabled/configured → Unhealthy/never_checked。 + - disabled/incomplete → Disabled。 +- 不在 migration 中访问外部服务。 +- 添加 Integration Health Worker Compose service。 +- 更新 log role mapping。 + +## 17. Acceptance criteria + +1. Worker 每 5 分钟串行检查所有 eligible targets。 +2. 每项超时 10 秒,失败不阻止后续目标。 +3. 各目标使用已确认的轻量检查深度。 +4. enabled 首次检查前 Unhealthy/never_checked。 +5. 一次失败立即 Unhealthy,一次成功立即恢复。 +6. config 修改立即 configuration_changed。 +7. saved-config Test 更新状态,unsaved Test 不更新。 +8. 业务请求不更新状态。 +9. 删除 LLM Provider 同步删除 health row。 +10. API 不执行外部请求、不返回敏感信息。 +11. Admin-only 和 30 秒 UI 刷新正确。 +12. Worker 自身出现在 Worker Health。 + +## 18. Known tradeoffs + +- 没有历史趋势和整体状态。 +- 没有统一 Check Now。 +- LLM 检查产生持续小额调用成本。 +- 一次瞬时失败立即 Unhealthy,可能产生短暂抖动,但当前不发送通知。 +- 轻量管理接口 Healthy 不等于所有索引、模型能力或业务查询均正常。 diff --git a/docs/specs/v0.6.0/07-sla-management.md b/docs/specs/v0.6.0/07-sla-management.md new file mode 100644 index 0000000..b51c0c6 --- /dev/null +++ b/docs/specs/v0.6.0/07-sla-management.md @@ -0,0 +1,391 @@ +# SLA Management + +Status: Confirmed + +## 1. Purpose + +为升级后新建的 Case 建立按 Severity 配置的 TTD、TTA、TTR 时限,向负责人提示即将超时和已经超时的工作,并在 Case 和 Dashboard 中提供与现有平均时间指标完全一致的达标统计。 + +## 2. Terminology and formulas + +单个 Case 使用不带 Mean 前缀的名称: + +| Per-Case | Formula | Dashboard aggregate | +| --- | --- | --- | +| TTD, Time to Detect | earliest valid Alert.first_seen_time → Case.created_at | MTTD | +| TTA, Time to Acknowledge | Case.created_at → Case.acknowledged_time | MTTA | +| TTR, Time to Resolve | Case.acknowledged_time → Case.closed_time | MTTR | + +规则: + +- `M` 仅表示多个样本的 Mean。 +- Dashboard 现有 MTTD/MTTA/MTTR 公式必须与上表一致。 +- created_at→closed_time 可以作为总耗时展示,但没有独立 SLA target。 +- 全部使用 24×7 elapsed seconds,不使用工作时间、节假日或暂停时钟。 + +## 3. Policy + +每个 CaseSeverity 一行全局策略: + +| Severity | TTD | TTA | TTR | +| --- | ---: | ---: | ---: | +| Critical | 300s | 900s | 14,400s | +| High | 900s | 1,800s | 28,800s | +| Medium | 1,800s | 7,200s | 86,400s | +| Low | 7,200s | 28,800s | 259,200s | +| Informational | 28,800s | 86,400s | 604,800s | +| Unknown | 3,600s | 14,400s | 172,800s | + +- v0.6.0 中 SLA 始终启用。 +- 六行和三项目标都必填。 +- 每个目标为整数 seconds,范围 60 秒到 365 天。 +- 不要求 TTD ≤ TTA ≤ TTR,因为三者覆盖不同阶段。 +- 不按 Category、Tag、Assignee 或业务组配置。 + +### Policy model + +建议 `SlaPolicy`: + +- severity,unique。 +- ttd_target_seconds。 +- tta_target_seconds。 +- ttr_target_seconds。 +- created_at/updated_at。 + +Settings API 一次提交全部六行,并在一个事务中全部保存。任一值非法则全部拒绝。 + +## 4. Snapshot semantics + +每个阶段开始时快照当时 Severity 和 target: + +- TTD:Case 创建时。 +- TTA:Case 创建时。 +- TTR:首次 acknowledged_time 设置时。 + +之后修改 Case Severity 不改变已经开始或完成的阶段。 + +策略修改只影响未来创建的阶段快照: + +- 新 Case 的 TTD/TTA 使用新策略。 +- 旧但尚未 acknowledge 的 Case,其 TTR 在未来开始时使用新策略。 +- 不批量重算已有快照。 + +## 5. CaseSla model + +每个参与 SLA 的 Case 一条 OneToOne `CaseSla`,不用 JSON 或通用 metric child table。 + +每项指标明确保存: + +- severity_snapshot。 +- target_seconds。 +- started_at。 +- ended_at。 +- deadline_at。 +- elapsed_seconds。 + +补充字段: + +- case OneToOne。 +- created_at/updated_at。 + +建议索引: + +- tta_deadline_at,配合 acknowledged_time/null 或等价 active marker。 +- ttr_deadline_at,配合 closed_time/null。 +- CaseSla.case unique。 + +状态不周期写入数据库,按快照、当前时间和完成时间动态计算。 + +## 6. Metric states + +通用状态: + +- Pending。 +- Warning。 +- Met。 +- Breached。 +- Not applicable。 + +未完成指标: + +- elapsed < 80% target:Pending。 +- 80% ≤ elapsed < 100%:Warning。 +- elapsed ≥ target:Breached。 + +已完成指标: + +- elapsed ≤ target:Met。 +- elapsed > target:Breached。 +- 完成后超时仍保持 Breached,不增加 Completed late。 + +### TTD subset + +TTD 在被观察时已经完成,因此只可能: + +- Met。 +- Breached。 +- Not applicable。 + +TTD 不进入 Pending 或 Warning。 + +### TTR before acknowledgement + +尚未 acknowledge: + +- UI state 显示 Pending。 +- started/deadline/elapsed 为空。 +- 标注 `Starts after acknowledgement`。 +- SLA Worker 不扫描 Warning/Breach。 + +不计算 Case overall SLA 状态。TTD、TTA、TTR 始终独立展示和筛选。 + +## 7. TTD behavior + +### Valid source + +使用当前 Case 下最早的有效 Alert.first_seen_time,要求: + +- 非空。 +- `first_seen_time <= Case.created_at`。 + +没有有效时间时: + +- TTD=Not applicable。 +- 不进入达标率分母。 +- 不使用 Alert.created_at、Case.created_at 或 0 秒兜底。 + +### Recalculation + +以下情况重算 TTD start、elapsed 和 result: + +- Alert 创建并关联 Case。 +- Alert.first_seen_time 修改。 +- Alert 移动到其他 Case。 +- Case merge 迁移 Alert。 + +TTD target 和 Severity snapshot 不变。更早 Alert 可能使 Met 变为 Breached。 + +## 8. TTA behavior + +- Case 创建时立即启动。 +- deadline=created_at + snapshotted target。 +- Case 第一次离开 New 时设置 acknowledged_time 并结束。 +- acknowledged_time 永久保留,Reopen 不重置。 +- On Hold 不暂停。 + +## 9. TTR behavior + +- 首次 acknowledged_time 设置时启动并快照当时 Severity/target。 +- deadline=acknowledged_time + target。 +- closed_time 设置时结束。 +- On Hold 不暂停。 +- Closed→In Progress Reopen 清空 closed_time 后,从原 acknowledged_time 恢复同一时钟。 +- 再次 Closed 后使用新的 closed_time 作为当前最终结果。 +- 原关闭历史只通过 AuditLog 保留。 + +### Direct New→Closed + +状态机同时设置 acknowledged_time 和 closed_time: + +- TTA=created_at→transition timestamp。 +- TTR=0 秒,Met。 + +## 10. Upgrade boundary + +- 只有 SLA migration/feature 启用后新建的 Case 创建 CaseSla。 +- v0.5.2 已存在 Case 不回填,不展示 SLA 状态、不筛选、不通知、不进入达标率。 +- 旧 Case 继续进入现有 MTTD/MTTA/MTTR mean,只要满足原查询条件。 +- Dashboard 必须显示 mean 和 compliance 各自 sample count,因为样本集合不同。 + +## 11. Case merge + +- merged source 排除 SLA、达标率、active count 和通知。 +- target 保留自己的 TTA/TTR snapshot 和时钟。 +- Alert 迁入后 target 重算 TTD。 +- target 不继承 source target、Severity snapshot、状态或通知记录。 + +## 12. Notifications + +### Recipient + +- 只通知当前 Assignee。 +- 未分配 Case 不通知。 +- Admin 不作为 fallback recipient。 +- 用户不能关闭 SLA 通知。 + +### Events + +- TTA Warning。 +- TTA Breached。 +- TTR Warning。 +- TTR Breached。 +- TTD Breached。 +- 不发送 TTD Warning。 +- 不发送 Met、恢复或完成通知。 + +Warning 和 Breached 分别通知。同一 recipient 最多各一次。Worker 首次看到已 Breached 时只发 Breached,不补发 Warning。 + +### Reassignment + +按 CaseSla + metric + state + recipient 去重。当前状态已经 Warning/Breached 后重新分配,新 Assignee 在下一次扫描收到一次当前状态通知;原 Assignee不收到撤销消息。 + +### Notification storage + +`CaseSlaNotification`: + +- case_sla FK。 +- metric enum TTD/TTA/TTR。 +- state enum Warning/Breached。 +- recipient nullable FK User,删除用户后 SET_NULL。 +- sent_at。 +- unique(case_sla, metric, state, recipient);nullable recipient 的历史处理需保证不会影响实际去重。 + +通知记录不保存消息 Secret 或完整 Case 内容。 + +## 13. SLA Worker + +新增单实例 `run_sla_worker`: + +- 每 60 秒扫描。 +- 接入 Worker Health,成为第 7 个 Worker。 +- 只负责发现需通知状态并去重发送。 +- API 状态仍动态计算,不依赖 Worker 更新状态。 +- 对适用且未完成的 TTA/TTR 使用 deadline 索引扫描。 +- 对 TTD Breached 和 reassignment 使用未通知查询。 +- 一个通知失败不得吞掉;按 WorkerIterationResult.failure_count 进入 Degraded。 +- 不发送外部 Webhook 或邮件。 + +## 14. API + +Case list/detail 增加嵌套 SLA: + +```json +{ + "sla": { + "applicable": true, + "ttd": { + "state": "Met", + "severity": "High", + "target_seconds": 900, + "elapsed_seconds": 420, + "started_at": "...", + "ended_at": "...", + "deadline_at": "..." + }, + "tta": {}, + "ttr": { + "state": "Pending", + "started": false + } + } +} +``` + +旧 Case: + +```json +{"sla": {"applicable": false}} +``` + +Case list 支持每项 state filter 和 deadline ordering。不得用 Python 全量计算后分页;查询必须可在数据库层筛选。 + +Settings SLA API: + +- Admin GET。 +- Admin 原子 PUT/PATCH 全部六行。 +- User/Viewer 403。 +- seconds 为唯一 API 单位。 + +## 15. Frontend + +### Case list + +- TTA state 默认显示。 +- TTR state 默认显示。 +- TTD state 默认隐藏但可选。 +- 三项分别筛选。 +- TTA/TTR 支持 deadline 排序。 +- 不显示 overall Tag。 + +### Case detail + +SLA 区块分别显示: + +- TTD/TTA/TTR。 +- state。 +- target。 +- elapsed。 +- start/end/deadline。 +- Severity snapshot。 +- TTR 未开始提示。 + +### Settings + +`System Settings → SLA`: + +- 六个 Severity 行。 +- 三个目标列。 +- UI 使用分钟/小时可读输入,提交转换为精确 seconds。 +- 一次 Save 全部原子提交。 +- 仅 Admin。 + +## 16. Dashboard + +保留现有: + +- MTTD mean/sample。 +- MTTA mean/sample。 +- MTTR mean/sample。 + +新增: + +- TTD compliance rate/sample。 +- TTA compliance rate/sample。 +- TTR compliance rate/sample。 +- 当前 TTA Warning count。 +- 当前 TTA Breached count。 +- 当前 TTR Warning count。 +- 当前 TTR Breached count。 + +取样: + +- TTD compliance:Case.created_at 在窗口。 +- TTA compliance:acknowledged_time 在窗口。 +- TTR compliance:closed_time 在窗口。 +- 当前 Warning/Breached:所有活动、未合并且有 CaseSla 的 Case,不受创建时间窗口限制。 + +TTD 没有当前 Warning/Breach count。 + +## 17. Audit + +- SLA policy 修改写 AuditLog,记录六行三项目标的 before/after seconds。 +- 时间推移导致的 Pending/Warning/Breached/Met 不写 Case AuditLog。 +- SLA Worker 扫描不写 AuditLog。 +- CaseSlaNotification 提供发送历史。 + +## 18. Acceptance criteria + +1. 单案 TTD/TTA/TTR 与 Dashboard MTTD/MTTA/MTTR公式一致。 +2. 六个 Severity 默认值与范围正确,Admin 原子保存。 +3. 每阶段按当时 Severity 快照,后续修改不追溯。 +4. TTD 缺失时间为 NA,Alert 变化可重算。 +5. TTA first-exit-New 结束,TTR acknowledge 开始。 +6. On Hold 不暂停,Reopen 从原 acknowledge 继续。 +7. Direct close 的 TTR=0 Met。 +8. 80% Warning、100% Breached,完成后 late 仍 Breached。 +9. 旧 Case 无 SLA,新 Case有 SLA。 +10. merged source 排除,target 仅重算 TTD。 +11. SLA Worker 每分钟运行并进入 Worker Health。 +12. 仅当前 Assignee 强制接收去重 Warning/Breach。 +13. 新 Assignee 可收到当前状态,未分配不通知。 +14. Case list/detail、Dashboard 和 Settings 行为符合 Spec。 +15. medium 数据规模下 active deadline 扫描使用索引,不全表 Python 计算。 + +## 19. Known tradeoffs + +- 旧 Case 不进入达标率,升级初期样本较少。 +- On Hold 持续计时,不反映净工作时间。 +- 重开 Case 会持续拉长同一 TTR。 +- Severity 变化不追溯,单 Case 不同阶段可能使用不同策略版本。 +- TTD 可因迟到或合并 Alert 从 Met 变为 Breached。 +- 未分配 Case 不产生 SLA 通知。 diff --git a/docs/specs/v0.6.0/README.md b/docs/specs/v0.6.0/README.md new file mode 100644 index 0000000..bb7b363 --- /dev/null +++ b/docs/specs/v0.6.0/README.md @@ -0,0 +1,36 @@ +# ASP v0.6.0 specification index + +本目录是 v0.6.0 的跨会话实施依据。已完成讨论的功能使用实施级 Spec 固化;尚未完成讨论的功能只记录 TODO 和待决策问题,不得把 TODO 中的推荐项当作已经确认的需求。 + +## 已确认 Spec + +| 文档 | 状态 | 内容 | +| --- | --- | --- | +| [00-release-scope.md](00-release-scope.md) | Confirmed | 版本目标、部署边界、兼容矩阵、容量、权限与排除项 | +| [01-bulk-case-triage.md](01-bulk-case-triage.md) | Confirmed | Case 批量分诊、共享状态机、通知与审计 | +| [02-case-merge.md](02-case-merge.md) | Confirmed | 多源案件合并、数据迁移、只读源案件与幂等 | +| [03-playbook-execution.md](03-playbook-execution.md) | Confirmed | Playbook Run、结构化 Stage、取消、重试与 Worker 语义 | +| [04-custom-variables.md](04-custom-variables.md) | Confirmed | Playbook/Module 可读取的 UI 管理变量 | +| [05-worker-health.md](05-worker-health.md) | Confirmed | Redis 心跳、Worker 状态、积压指标与 Admin API | +| [06-integration-health.md](06-integration-health.md) | Confirmed | 定时集成检查、当前状态持久化与安全错误 | +| [07-sla-management.md](07-sla-management.md) | Confirmed | TTD/TTA/TTR 时限、Severity 策略、通知和 Dashboard 达标率 | + +## 待讨论 + +[TODO-remaining-domains.md](TODO-remaining-domains.md) 记录 AI 质量评估、抑制规则、Operations 页面整合和版本验收。继续讨论时应逐项把决定写回独立 Spec。 + +## 实施顺序 + +1. 先完成 Case 状态机,再实现批量分诊和案件合并。 +2. 完成 Playbook Run/Stage 后实现 Custom Variables。 +3. 完成通用 Worker Health 基础设施,再接入六类 Worker。 +4. 完成 Integration Health 数据层和 Worker,再实现 Operations 页面。 +5. 待剩余三个业务域定稿后,统一补齐 v0.6.0 验收规范。 + +## Spec 使用规则 + +- `Confirmed` 表示产品决策已确认,实施不得自行改变行为。 +- Spec 中的模型名和 URL 是目标设计;若代码库已有命名约束冲突,可以做等价调整,但外部行为必须一致。 +- 每项功能必须同时覆盖后端、前端、权限、审计、迁移和失败行为。 +- v0.6.0 允许破坏性 API 调整,不需要兼容旧 CLI 或插件。 +- 不得把本目录复制到 `asp-doc` 作为用户文档;用户文档应在功能实现定型后另行编写。 diff --git a/docs/specs/v0.6.0/TODO-remaining-domains.md b/docs/specs/v0.6.0/TODO-remaining-domains.md new file mode 100644 index 0000000..ea5e3a5 --- /dev/null +++ b/docs/specs/v0.6.0/TODO-remaining-domains.md @@ -0,0 +1,114 @@ +# v0.6.0 remaining domain TODO + +Status: Not discussed + +本文件用于在另一台电脑或新会话中继续产品讨论。以下内容仅是讨论清单,不是已确认需求。 + +## TODO 1: AI quality evaluation + +Release blocking: Yes + +Goal: 用人工最终判断评估 AI 严重度、置信度、影响、优先级和 Verdict 的质量,形成可解释的反馈闭环。 + +需要逐项决定: + +- 评估对象是所有 AI 字段,还是先只做 Verdict/Severity。 +- 何时生成评价记录:字段修改、Case Close 或定时快照。 +- AI 输出与人工输出为空时如何处理。 +- 一次 Case 多次 AI 分析如何选择被评估版本。 +- 是否必须保存 model、provider、prompt slug/version 和 analysis job。 +- 指标使用 agreement rate、confusion matrix、precision/recall 还是更简单统计。 +- Verdict 多分类如何归并,Unknown/Insufficient Data 是否排除。 +- 分析师是否提供显式 thumbs up/down 和错误原因。 +- 是否允许抽样复核和争议标记。 +- 时间窗口、Provider、Model、Playbook/Trigger、Case category 等筛选维度。 +- 数据保留、历史不可变性和模型配置删除后的展示。 +- Dashboard/独立页面的信息架构。 +- 权限和匿名化要求。 +- 是否将反馈用于自动 prompt 优化;建议 v0.6.0 排除自动学习。 +- 审计、迁移和验收案例。 + +输出目标:`08-ai-quality-evaluation.md`。 + +## TODO 2: suppression rules + +Release blocking: Yes + +Goal: 在告警进入 Case 流程前降低已知噪声,同时保留可审计的命中记录,避免简单删除安全数据。 + +需要逐项决定: + +- Rule 匹配对象是原始 Webhook、标准化 Alert 字段还是 Module 输出。 +- 可匹配字段白名单以及 text/exact/list/CIDR/regex 运算符。 +- 多条件 AND/OR 能力和是否允许嵌套。 +- Rule 作用域:全局、Module、rule_id、产品或数据源。 +- 动作是 drop、mark suppressed、route to existing Case 还是不创建 Case。 +- 被抑制事件是否持久化;存储多少原始数据。 +- Rule 优先级、first-match 或 all-match。 +- Enabled、有效期、自动过期和命中计数。 +- 模拟/预览如何在历史样本上运行,是否必须先模拟再启用。 +- 如何防止一条宽泛 Rule 隐藏大量真实告警。 +- Admin/User/Viewer 权限;建议至少创建/修改仅 Admin。 +- 命中 AuditLog、Rule 变更审计和 Secret/PII 脱敏。 +- Module correlation 与 merged source routing 的执行顺序。 +- 列表、详情、命中历史和筛选 UX。 +- 数据库索引、性能目标和验收案例。 + +输出目标:`09-suppression-rules.md`。 + +## TODO 3: Operations Center + +Release blocking: Depends on final scope + +已确认的固定边界: + +- 位于 `System Settings → Operations`。 +- 包含 Worker Health 和 Integration Health 两个独立区块。 +- 仅 Admin 可访问。 +- Worker 每 10 秒刷新,Integration 每 30 秒刷新。 +- 不提供 Worker Restart、Run Now、日志读取或健康通知。 +- Integration 不提供统一 Check Now;保留各 Settings 页的 Test。 +- 不计算整体 Integration Health。 + +仍需决定: + +- 页面布局、过滤、排序、状态颜色和移动端是否考虑。 +- Worker backlog 的不同数据结构如何展示。 +- 如何从 Integration 行跳转到对应 Settings。 +- Redis 监控不可用时页面局部还是整体错误。 +- Operations 导航徽标是否显示异常数量。 +- 是否显示 Compose 修复命令,以及具体安全文本。 +- 空状态、加载状态和权限拒绝 UX。 +- API 聚合还是前端调用两个独立 API。 +- 前端组件复用和验收案例。 + +输出目标:`10-operations-center.md`。 + +## TODO 4: v0.6.0 acceptance + +Release blocking: Yes + +需要在所有功能 Spec 完成后定义: + +- 从 v0.5.2 生产备份副本升级的完整演练。 +- 全新 Docker Compose 安装。 +- medium 数据规模下的关键 API/页面性能阈值。 +- Admin/User/Viewer 权限矩阵回归。 +- Case 单条和批量状态机一致性。 +- 部分成功批量分诊、原子合并、幂等重试和 merged source 路由。 +- Playbook Pending/Running/终态、Cancel、Retry、Stage 和 Worker 崩溃恢复。 +- Custom Variables 的 Secret masking、Reveal audit 和 Module/Playbook 读取。 +- Worker Redis 故障、重复实例、优雅退出和心跳过期。 +- 所有正式集成的 Healthy/Unhealthy/Disabled 行为。 +- SLA、AI 质量评估和抑制规则端到端场景。 +- 备份、恢复和回滚演练。 +- 文档、release notes、已知限制和功能冻结条件。 +- P0/P1 缺陷门槛和 RC 观察周期。 + +输出目标:`11-release-acceptance.md`。 + +## Suggested continuation prompt + +在新电脑上可从下面的提示继续: + +> 阅读 `docs/specs/v0.6.0/README.md` 和 `TODO-remaining-domains.md`。已确认 Spec 不要重新讨论。从 AI quality evaluation 开始,一次只问一个决策问题,每个问题给出推荐答案;确认完成后依次处理 suppression rules、Operations Center 和 release acceptance。