remove unused spec

This commit is contained in:
funnywolf
2026-08-01 15:10:32 +08:00
parent ddee6cb2f7
commit 5f1660c2e4
17 changed files with 0 additions and 3718 deletions
@@ -1,331 +0,0 @@
# API Documentation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add maintainable Swagger/OpenAPI documentation for the full backend HTTP API and add lightweight documentation-site guidance for HTTP and realtime integration.
**Architecture:** Use `drf-spectacular` to generate OpenAPI 3 schema from Django REST Framework views and serializers, served by the backend at `/api/schema/`, `/api/docs/`, and `/api/redoc/`. Use sidecar-packaged Swagger UI/Redoc assets for self-hosted deployments. Keep WebSocket protocol documentation in VitePress because OpenAPI does not describe websocket message flows.
**Tech Stack:** Django, Django REST Framework, SimpleJWT, custom API key auth, drf-spectacular, drf-spectacular-sidecar, VitePress.
---
### Task 1: Add OpenAPI dependencies and backend settings
**Files:**
- Modify: `backend/pyproject.toml`
- Modify: `backend/asp/settings.py`
- [ ] **Step 1: Add dependencies**
Add these dependencies to `backend/pyproject.toml`:
```toml
"drf-spectacular>=0.29.0",
"drf-spectacular-sidecar>=2026.1.1",
```
- [ ] **Step 2: Configure installed apps and DRF schema class**
Update `backend/asp/settings.py`:
```python
INSTALLED_APPS = [
# Third party
"rest_framework",
"rest_framework_simplejwt",
"drf_spectacular",
"drf_spectacular_sidecar",
"corsheaders",
]
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
```
- [ ] **Step 3: Add OpenAPI settings**
Add `SPECTACULAR_SETTINGS` in `backend/asp/settings.py`:
```python
SPECTACULAR_SETTINGS = {
"TITLE": "Agentic SOC Platform API",
"DESCRIPTION": "HTTP API for Agentic SOC Platform. External integrations should prefer API keys for automation.",
"VERSION": "0.5.0",
"SERVE_INCLUDE_SCHEMA": False,
"SWAGGER_UI_DIST": "SIDECAR",
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
"COMPONENT_SPLIT_REQUEST": True,
"SECURITY": [
{"bearerAuth": []},
{"apiKeyAuth": []},
],
}
```
- [ ] **Step 4: Sync dependencies**
Run:
```powershell
Set-Location -Path 'C:\Code\agentic-soc-platform\backend'
uv sync
```
Expected: dependencies resolve and install without errors.
---
### Task 2: Add schema views and authentication extensions
**Files:**
- Create: `backend/apps/common/openapi.py`
- Modify: `backend/asp/urls.py`
- [ ] **Step 1: Define authentication extensions**
Create `backend/apps/common/openapi.py`:
```python
from drf_spectacular.extensions import OpenApiAuthenticationExtension
class ApiKeyAuthenticationScheme(OpenApiAuthenticationExtension):
target_class = "apps.accounts.authentication.ApiKeyAuthentication"
name = "apiKeyAuth"
def get_security_definition(self, auto_schema):
return {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"description": "Use the format: Api-Key <key>",
}
```
- [ ] **Step 2: Ensure extensions load**
Import the module from `backend/apps/common/apps.py` inside `ready()`:
```python
class CommonConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.common"
def ready(self):
from . import openapi # noqa: F401
```
- [ ] **Step 3: Register schema routes**
Update `backend/asp/urls.py`:
```python
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
]
```
- [ ] **Step 4: Validate schema route import**
Run:
```powershell
Set-Location -Path 'C:\Code\agentic-soc-platform\backend'
.\.venv\Scripts\python.exe manage.py check
```
Expected: `System check identified no issues`.
---
### Task 3: Add schema tags and minimal APIView annotations
**Files:**
- Modify: `backend/apps/common/openapi.py`
- Modify: `backend/apps/common/views.py`
- Modify: APIView-heavy modules as needed: `backend/apps/agent_api/views.py`, `backend/apps/settings/views.py`, `backend/apps/settings/custom_views.py`, `backend/apps/dashboard/views.py`, `backend/apps/webhook/views.py`, `backend/apps/attachments/views.py`, `backend/apps/preferences/views.py`
- [ ] **Step 1: Add preprocessing hook for business tags**
Add a hook in `backend/apps/common/openapi.py`:
```python
def assign_business_tags(endpoints):
tag_map = (
("/api/auth/", "Auth"),
("/api/auth/users", "Users"),
("/api/auth/api-keys", "API Keys"),
("/api/cases", "Cases"),
("/api/alerts", "Alerts"),
("/api/artifacts", "Artifacts"),
("/api/comments", "Comments"),
("/api/attachments", "Attachments"),
("/api/settings", "Settings"),
("/api/dashboard", "Dashboard"),
("/api/agent/v1", "Agent API"),
("/api/webhook", "Webhooks"),
("/api/user-table-preferences", "Preferences"),
("/api/saved-table-filters", "Preferences"),
("/api/health", "System"),
("/api/metadata", "Metadata"),
)
for path, path_regex, method, callback in endpoints:
for prefix, tag in tag_map:
if path.startswith(prefix):
callback.cls._spectacular_annotation = getattr(callback.cls, "_spectacular_annotation", {})
callback.cls._spectacular_annotation["tags"] = [tag]
break
return endpoints
```
- [ ] **Step 2: Wire preprocessing hook**
Add to `SPECTACULAR_SETTINGS`:
```python
"PREPROCESSING_HOOKS": [
"apps.common.openapi.assign_business_tags",
],
```
- [ ] **Step 3: Annotate APIViews that lack serializers**
For high-warning APIViews, use `extend_schema` with `OpenApiTypes.OBJECT` where exact schemas are not yet serializer-backed:
```python
from drf_spectacular.utils import OpenApiResponse, extend_schema
from drf_spectacular.types import OpenApiTypes
@extend_schema(
responses={200: OpenApiResponse(response=OpenApiTypes.OBJECT)},
)
def get(self, request):
...
```
- [ ] **Step 4: Generate schema**
Run:
```powershell
Set-Location -Path 'C:\Code\agentic-soc-platform\backend'
.\.venv\Scripts\python.exe manage.py spectacular --file $env:TEMP\asp-openapi.yaml
```
Expected: command exits successfully. Warnings are acceptable in the first implementation phase.
---
### Task 4: Add docs-site HTTP API and realtime guide
**Files:**
- Create: `asp-doc/docs/zh/asp/integrations/api/index.md`
- Create: `asp-doc/docs/en/asp/integrations/api/index.md`
- Modify: `asp-doc/docs/.vitepress/config/zh.ts`
- Modify: `asp-doc/docs/.vitepress/config/en.ts`
- [ ] **Step 1: Add Chinese guide**
Create `asp-doc/docs/zh/asp/integrations/api/index.md`:
```markdown
# API 集成
ASP 后端提供实时生成的 OpenAPI 文档,用于外部系统集成和调试。
## 文档入口
- Swagger UI: `/api/docs/`
- Redoc: `/api/redoc/`
- OpenAPI Schema: `/api/schema/`
## 认证
自动化集成推荐使用 API Key
```http
Authorization: Api-Key <key>
```
交互式用户也可以使用 JWT
```http
Authorization: Bearer <access_token>
```
## Realtime API
WebSocket 地址为 `/ws/events/`。连接时携带访问令牌,连接成功后服务端发送 `realtime.connected`
客户端可以发送:
- `comments.subscribe`
- `comments.unsubscribe`
服务端可能返回:
- `comments.subscribed`
- `comments.unsubscribed`
- `realtime.error`
```
- [ ] **Step 2: Add English guide**
Create `asp-doc/docs/en/asp/integrations/api/index.md` with equivalent English content.
- [ ] **Step 3: Link guide in sidebars**
Add API page under Integrations in both VitePress configs:
```ts
{text: 'API', link: 'api/'},
```
---
### Task 5: Validate and commit
**Files:**
- All files above
- [ ] **Step 1: Backend checks**
Run:
```powershell
Set-Location -Path 'C:\Code\agentic-soc-platform\backend'
.\.venv\Scripts\python.exe manage.py check
.\.venv\Scripts\python.exe manage.py spectacular --file $env:TEMP\asp-openapi.yaml
```
Expected: `check` passes and schema generation exits successfully.
- [ ] **Step 2: Documentation check**
Do not run VitePress build unless explicitly requested. Inspect the sidebar and guide files for valid links and consistent zh/en content.
- [ ] **Step 3: Commit**
Commit backend and docs changes:
```powershell
Set-Location -Path 'C:\Code\agentic-soc-platform'
git add backend asp-doc docs/superpowers/plans/2026-07-22-api-documentation.md
git commit -m "feat: add API documentation endpoints" -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
```
---
## Self-review
- Spec coverage: backend Swagger/OpenAPI endpoints, local UI assets, JWT/API key auth docs, websocket docs-site guidance, no generated schema artifact, and non-breaking response behavior are covered.
- Placeholder scan: no TODO/TBD placeholders remain.
- Type consistency: route names, dependency names, and settings keys match drf-spectacular conventions.
@@ -1,289 +0,0 @@
# Dashboard Design
## Status
Approved for implementation planning on 2026-06-25.
## Goal
Create a new Dashboard page that presents the platform as a Cyber Command Center: a dark, high-impact security posture view suitable as a public-facing first impression, while still using real platform data only.
The dashboard should show valuable SOC state and security posture signals, not mock or fabricated telemetry. It should use the Ant Design family for the visual system, with Ant Design Charts for charts where practical.
## Non-goals
- Do not replace the current `/` default route to Cases.
- Do not create synthetic demo records or hard-coded metric values.
- Do not turn the dashboard into a table-heavy SOC queue.
- Do not add real-time streaming in the first version.
## Existing data sources
The first version uses existing backend models:
- `Case`: status, severity, priority, confidence, impact, verdict, category, assignee, timestamps, AI assessment fields, and linked alert/playbook counts.
- `Alert`: severity, confidence, impact, status, risk level, product category/vendor/name, MITRE tactic/technique, first/last seen time, artifacts, and linked case.
- `Artifact`: type, name, role, value, and linked alerts.
- `Enrichment`: type, provider, linked case/alert/artifact.
- `Playbook`: job status, name, user, linked case, timestamps.
- `Knowledge`: source, tags, linked case.
- `AuditLog`: recent create/update/delete activity.
## Navigation and routing
Add a new `/dashboard` frontend route and a Dashboard item in the main sidebar. The route is available to authenticated users.
Keep the current root behavior unchanged: `/` still redirects to `/cases`.
## Page shape
The page is presentation-first and should feel more like a security operations screen than an admin report. It uses the existing dark theme and sidebar, then adds a denser cyber visual layer inside the content area:
1. Hero strip with title, selected time window, last refreshed time, and manual refresh.
2. Core posture band with Active Risk Index, open critical cases, critical/high alerts, automation success rate, MTTD, MTTA, and MTTR.
3. Threat landscape charts: alert trend, severity distribution, product/category distribution, and MITRE tactic distribution.
4. Automation and intelligence panels: playbook status, enrichment coverage, knowledge extraction signal.
5. Risk focus area: top risk artifacts as visual cards and latest high-severity highlights as an event stream.
Avoid traditional tables on this page. Lists should be styled as timeline/event-stream cards, not `Table`.
## Time windows
The page supports three windows:
- 24h
- 7d
- 30d
Default window: 7d.
The user switches windows with an Ant Design `Segmented` control. Data refresh is manual through a `Refresh` action. There is no automatic polling in the first version.
## Backend API
Add a backend dashboard aggregation endpoint:
```text
GET /api/dashboard/overview/?window=24h|7d|30d
```
The endpoint should be authenticated and read-only. It should aggregate with Django ORM queries on existing tables. Use a dedicated `apps.dashboard` Django app because dashboard aggregation is a business feature, not generic metadata.
No database migration is expected because the dashboard does not add new models.
### Response shape
The response is grouped by frontend module:
```json
{
"window": "7d",
"generated_at": "2026-06-25T13:00:00Z",
"summary": {
"active_risk_index": 73,
"open_cases": 12,
"open_critical_cases": 3,
"critical_high_alerts": 28,
"running_playbooks": 2,
"failed_playbooks": 1,
"automation_success_rate": 82.5
},
"mean_times": {
"mttd": {"seconds": 3600, "sample_count": 8},
"mtta": {"seconds": 5400, "sample_count": 6},
"mttr": {"seconds": 43200, "sample_count": 4}
},
"alert_trend": [],
"severity_distribution": [],
"case_status_mix": [],
"product_category_distribution": [],
"mitre_tactics": [],
"automation": [],
"coverage": {},
"top_risk_artifacts": [],
"recent_highlights": []
}
```
Arrays should contain simple `{label, value}` or `{time, label, value}` objects where possible, so the frontend stays display-focused.
## Metric definitions
### Active Risk Index
Active Risk Index is a normalized risk-pressure visualization derived from existing records. It is not an external security rating.
Use only records in the selected window and open operational state:
- Cases with status `New`, `In Progress`, or `On Hold`.
- Alerts with status `New` or `In Progress`.
- Playbooks with status `Running` or `Failed`.
Severity weights:
- Critical: 10
- High: 6
- Medium: 3
- Low: 1
- Informational/Info: 0.5
- Unknown/empty/Other: 0
Formula:
```text
raw_score =
sum(open_case_severity_weight * 2)
+ sum(active_alert_severity_weight)
+ failed_playbook_count * 4
+ running_playbook_count * 1
active_risk_index = min(100, round(raw_score))
```
The UI tooltip must explain that this is a weighted risk-pressure index based on current platform records.
### MTTD
Mean Time To Detect.
Base records: cases created in the selected window.
For each case, use:
```text
case.created_at - first_alert_seen_time
```
`first_alert_seen_time` is the earliest non-empty `Alert.first_seen_time` for the case.
Include only non-negative durations where both timestamps exist. Return average seconds and sample count. If the sample count is zero, return `seconds: null` and show `N/A`.
### MTTA
Mean Time To Acknowledge.
Base records: cases acknowledged in the selected window.
For each case, use:
```text
case.acknowledged_time - case.created_at
```
Include only non-negative durations where both timestamps exist. Return average seconds and sample count. If the sample count is zero, return `seconds: null` and show `N/A`.
### MTTR
Mean Time To Resolve.
Base records: cases closed in the selected window.
For each case, use:
```text
case.closed_time - case.acknowledged_time
```
Include only non-negative durations where both timestamps exist. Return average seconds and sample count. If the sample count is zero, return `seconds: null` and show `N/A`.
## Visual components
Use Ant Design components for layout and controls:
- `Card`
- `Statistic`
- `Segmented`
- `Tooltip`
- `Badge`
- `Tag`
- `Progress`
- `Skeleton`
- `Alert`
- `Button`
Use Ant Design Charts for chart rendering:
- Gauge or circular progress for Active Risk Index.
- Area or Line chart for alert trend.
- Pie or Rose chart for severity distribution.
- Column chart for product category distribution.
- Column or compact heat-strip component for MITRE tactics.
- Donut or stacked status visualization for playbook automation status.
The page can include custom CSS for cyber presentation: subtle gradients, glow borders, grid backgrounds, and compact event cards. Keep it scoped to the dashboard page.
## Empty, loading, and error states
Loading state:
- Preserve the full dashboard layout.
- Use Skeleton cards and chart placeholders.
Empty state:
- Do not use plain default `Empty`.
- Use a custom cyber-style empty panel such as "No telemetry in this window" or "Signal quiet".
- Do not fabricate fallback data.
Error state:
- Keep the dashboard shell visible.
- Show a low-profile module-level error or corner notice.
- Keep the manual Refresh action available.
- Avoid large blocking error pages because the dashboard is presentation-oriented.
## Frontend data flow
Add a dashboard API helper that calls `/dashboard/overview/`.
`Dashboard.tsx` owns:
- selected window state,
- loading/error state,
- fetched dashboard payload,
- manual refresh,
- formatting seconds into readable durations,
- rendering module components.
Break visual modules into these small components so each component has a single display responsibility:
- `PostureMetricCard`
- `MeanTimeMetricCard`
- `ThreatTrendChart`
- `DistributionChart`
- `AutomationPanel`
- `RiskArtifactCard`
- `SecurityHighlightStream`
These components should receive plain data props and not fetch directly.
## Permissions
Dashboard is available to authenticated users. It does not require admin permission.
The endpoint should use the same authentication behavior as other platform APIs.
## Testing and validation
Backend validation:
- The dashboard endpoint returns HTTP 200 for authenticated users.
- `window=24h`, `window=7d`, and `window=30d` return the same response shape.
- Invalid `window` returns a clear 400 response.
- MTTD, MTTA, and MTTR ignore incomplete or negative durations and include sample counts.
- Empty datasets return zero counts, empty arrays, and `null` mean-time seconds rather than failing.
Frontend validation:
- Dashboard route renders for authenticated users.
- Time-window switching requests the matching backend window.
- Loading, empty, and error states render without breaking the layout.
- `N/A` is shown for MTTD/MTTA/MTTR when sample count is zero.
- Existing root route and Cases page behavior remain unchanged.
Manual visual validation:
- Dashboard fits the existing dark shell.
- The first screen feels promotional and high-impact.
- The page avoids table-heavy layout.
- Charts and KPI cards remain readable on common laptop widths.
@@ -1,102 +0,0 @@
# asf-doc 中文文档重构设计
## 背景
`asf-doc/docs/zh` 仍保留较多旧版本 ASP 框架、插件和模块叙事。当前项目已经转向以 ASP 工作台为核心的产品形态,文档需要按当前 backend、frontend 和 ClaudeCode 插件能力重新组织。
旧版本说明、迁移说明和兼容说明不再保留。无法从旧内容确认映射关系时,以当前代码为准;业务含义不确定时向用户确认。
## 目标
- 重构中文文档信息架构,使其贴合当前 ASP 产品工作台。
- 删除不属于新结构的旧中文文档文件。
- 为每个新章节提供简洁、当前态、可用的首版内容。
- 保留 Release / 更新日志栏目,但它只作为版本记录,不承担当前功能说明。
- ClaudeCode 插件保留在集成章节;MCP 只作为 ClaudeCode 连接机制说明,不作为独立主线。
## 非目标
- 不同步英文文档。
- 不编写旧版本迁移或兼容说明。
- 不修改 backend/frontend 业务代码。
- 不把 asp-marketplace 作为本次文档主线;它未来会单独发布为 GitHub 项目,本次影响仅限 ClaudeCode 插件说明。
- 不在第一阶段补大量截图、长篇示例或完整 API 手册。
## 信息架构
中文文档主线调整为产品工作台优先:
1. 概览
- 什么是 Agentic SOC Platform
- 产品架构与核心工作流
- 术语表:Case、Alert、Artifact、Enrichment、Knowledge、Playbook、Audit、Inbox
2. 快速开始
- 部署
- 首次登录
- 基础配置
- 连接 LLM、SIEM、威胁情报、LDAP
3. 工作台功能
- Dashboard
- Case
- Alert
- Artifact
- Enrichment
- Knowledge
- Playbook
- Inbox / 通知
- Audit Log / 审计
4. 系统设置
- 用户与权限
- API Key
- LLM Provider
- SIEMSplunk / ELK
- 威胁情报:AlienVault OTX
- LDAP
- Agentic Runtime
5. 集成
- WebhookSplunk / Kibana 告警接入
- ClaudeCode 插件:安装、能力边界、可用 Skills / Agents
6. 开发扩展
- 数据模型与 API 约定
- Playbook 扩展
- 当前 `backend/modules``backend/playbooks` 中仍存在的真实示例
7. 更新日志
- 保留 Release 页面和入口
- 不用历史版本说明替代当前功能说明
## 内容规则
- 简洁优先,每页只写当前用户需要知道的内容。
- 当前功能说明以 `backend/apps``frontend/src` 和 ClaudeCode 插件现状为准。
- 首页 hero/features 同步当前产品定位,避免旧版本宣传口径。
- 旧的 `feature/``background/``modules/``playbooks/``integrations/` 中不符合新结构的页面删除或迁移。
- 真实 backend modules/playbooks 示例可迁移到开发扩展章节,但不沿用旧叙事。
## 实施方案
- 主工作区:`asf-doc/docs/zh`
- 配置入口:`asf-doc/docs/.vitepress/config/zh.ts`
- 新路径采用小写英文目录,优先使用稳定路径,例如 `overview/``quick-start/``workspace/``settings/``integrations/``development/``release/`
- 先调整导航和目录,再写首版内容,最后删除未进入新结构的旧文件。
- 如果现有 Release 路径可用,保留现有路径,避免无意义重命名。
## 校验
- 使用 asf-doc 已有 package scripts 验证 VitePress 文档构建或链接。
- 不运行 frontend 产品 build。
- 若发现文档链接、导航路径或业务含义不确定,停止并向用户确认。
## 分阶段执行
第一阶段:
- 完成 zh 信息架构重构。
- 删除旧中文文档文件。
- 写入每个新章节的简洁首版内容。
- 保留 Release 栏目。
第二阶段:
- 按功能页补操作步骤、截图、字段解释和典型场景。
- 根据 ClaudeCode 插件独立仓库发布情况,同步安装链接和 marketplace 说明。
- 深化 Playbook/module 开发示例。
@@ -1,167 +0,0 @@
# ASP 单机 Docker Compose 分发设计
## 背景
ASP 当前由 Django 后端、Vite 前端、PostgreSQL、Redis、S3 兼容对象存储和多个后台 worker 组成。项目已有依赖服务的 Compose 示例,但还没有应用级 Dockerfile、完整 Compose 分发包和一键部署流程。
目标用户优先定位为单机或小团队私有化部署:一台服务器上尽量少步骤启动,并能保留必要的定制开发能力。
## 决策
ASP 首选分发形态采用版本化 Docker Compose 应用包。每个 release 发布镜像和一个 `asp-compose-<version>.tar.gz`,用户通过 `.env``custom/` 和持久化 volume 管理本地配置、数据和定制代码。
不采用 All-in-one 单容器作为主路径,因为 ASP 包含前端、HTTP API、ASGI/MCP、多个 worker 和状态服务,单容器会降低排障、升级和定制开发的可维护性。原生安装包可以作为未来补充,但不是单机部署首选。
## 目标
- 用户在单机环境中通过 Docker Compose 完成部署。
- 产品镜像和用户定制内容分离,升级时不要求用户修改产品源码。
- Module、Playbook、SIEM YAML 和额外 Python 依赖是分发包的一等能力。
- 支持管理员手动刷新/校验 Module、Playbook 和 SIEM YAML 定义。
- 初始化、升级和健康检查有明确命令,失败时显式报错。
- 默认路径适合联网安装;代理和自定义安装源通过原生 `uv pip install` 参数支持。
## 非目标
- 不在本设计中覆盖 Kubernetes/Helm 部署。
- 不把用户定制代码打进官方镜像作为默认流程。
- 不支持文件监听式自动热加载;刷新/校验由管理员显式触发。
- 不承诺额外依赖包、已导入 helper module 或 Python 包升级的无重启热替换;这类变更仍要求重启相关容器。
- 不为直接修改产品源码提供升级兼容承诺。
## 容器架构
单机 Compose 包含以下服务:
| 服务 | 作用 |
| --- | --- |
| `asp-frontend` | Nginx 托管 Vite build 产物,并反向代理 `/api/``/api/mcp`。 |
| `asp-web` | Django HTTP API、Admin、普通业务接口。 |
| `asp-asgi` | Django ASGI/MCP,服务 `/api/mcp`。 |
| `asp-worker-module` | 执行 `run_agentic_module_worker`。 |
| `asp-worker-case-analysis` | 执行 `run_agentic_case_analysis_worker`。 |
| `asp-worker-playbook` | 执行 `run_agentic_playbook_worker`。 |
| `asp-worker-elk-action` | 执行 `run_elk_action_worker`。 |
| `asp-migrate` | 一次性迁移/初始化服务,不常驻。 |
| `asp-custom-deps` | 一次性安装用户自定义 Python 依赖,不常驻。 |
| `postgres` | 默认内置 PostgreSQL。 |
| `redis-stack` | 默认内置 Redis/Redis Stack。 |
| `rustfs` | 默认内置 S3 兼容对象存储。 |
应用容器尽量无状态;PostgreSQL、Redis、RustFS 和 custom dependency volume 持久化。前端是单一入口,后端 HTTP、ASGI/MCP 和 worker 独立运行,便于重启和排障。
## 发布包结构
每个版本发布一个 Compose 包:
```text
asp-compose/
compose.yaml
.env.example
README.md
scripts/
init.sh
upgrade.sh
doctor.sh
install-custom-deps.sh
custom/
modules/
playbooks/
data/siem/
requirements.txt
```
用户只编辑 `.env``custom/``compose.yaml` 随 release 维护,升级时可替换。
## 定制开发
定制开发通过宿主机 `custom/` 目录进入容器:
- `custom/modules/*.py`:用户 Module。
- `custom/playbooks/*.py`:用户 Playbook。
- `custom/data/siem/*.yaml`:用户 SIEM YAML。
- `custom/requirements.txt`:用户 Module/Playbook 需要的额外 Python 包。
backend、ASGI 和 worker 容器挂载 `custom/`。运行时默认只从 `custom/modules` 加载 Module,只从 `custom/data/siem` 加载 SIEM YAMLPlaybook 从产品内置目录和 `custom/playbooks` 加载,custom 可追加或覆盖内置 Playbook。
ASP 提供管理员触发的刷新/校验能力,用于重新扫描 Module、Playbook 和 SIEM YAML,并返回已加载定义、来源路径和加载错误。该操作写入审计日志。纯脚本定义或 YAML 变更可通过刷新/校验确认,并在 worker 下一轮处理或下一次 Playbook 列表/执行时生效。
如果变更涉及 `custom/requirements.txt`、额外 Python 包升级或被普通 `import` 导入的 helper module,用户需要重新安装依赖并重启相关容器,例如:
```bash
docker compose restart asp-worker-module asp-worker-playbook asp-asgi
```
## 自定义 Python 依赖
额外 Python 包不写入产品镜像的 `.venv`,也不修改官方 site-packages。分发包提供一次性服务 `asp-custom-deps`
```bash
docker compose run --rm asp-custom-deps [uv pip install 参数]
```
该服务内部执行:
```bash
uv pip install --target /opt/asp/custom-packages -r /app/custom/requirements.txt "$@"
```
`/opt/asp/custom-packages` 使用独立 named volume 持久化。backend、ASGI 和 worker 通过 `PYTHONPATH=/opt/asp/custom-packages:/app/custom` 加载这些依赖。
用户指定安装源时直接传 `uv pip install` 参数:
```bash
docker compose run --rm asp-custom-deps --index-url https://pypi.tuna.tsinghua.edu.cn/simple
```
代理使用标准环境变量传递:
```bash
HTTP_PROXY=http://proxy.example:8080 \
HTTPS_PROXY=http://proxy.example:8080 \
docker compose run --rm asp-custom-deps --index-url https://pypi.org/simple
```
这种方式不要求用户重打官方镜像;如果后续需要把客户定制固化为交付镜像,可以再提供 `Dockerfile.custom` 作为高级流程。
## 首次部署流程
1. 解压 `asp-compose-<version>.tar.gz`
2. 复制 `.env.example``.env`,配置域名、端口、密码、`DJANGO_SECRET_KEY`、对象存储参数。
3. 执行 `docker compose pull`
4. 如有额外 Python 包,执行 `docker compose run --rm asp-custom-deps [uv pip install 参数]`
5. 执行 `docker compose run --rm asp-migrate`,完成数据库迁移、静态资源准备和对象存储 bucket 初始化。
6. 执行 `docker compose up -d`
7. 创建管理员账号,并执行 `scripts/doctor.sh` 检查运行状态。
## 升级流程
升级遵循产品版本和用户定制分离:
1. 备份 PostgreSQL、RustFS 数据和 `.env``custom/`
2. 替换 release 包或更新镜像 tag。
3. 执行 `docker compose pull`
4.`custom/requirements.txt` 有变化,重跑 `asp-custom-deps`
5. 执行 `docker compose run --rm asp-migrate`
6. 执行 `docker compose up -d`
7. 执行 `scripts/doctor.sh`
官方兼容边界是公开的 Module、Playbook、Base API、SIEM YAML 格式和配置变量。用户直接修改产品源码不作为升级兼容路径。
## 错误处理与健康检查
- `asp-migrate` 失败时返回非 0,不继续伪装成功。
- `asp-custom-deps` 安装失败时返回非 0,不修改业务容器启动逻辑。
- Module/Playbook 加载失败应在对应 worker 日志和刷新/校验结果中输出文件名、类名和异常。
- 每个 worker 独立容器输出日志,便于区分 Module、Case Analysis、Playbook 和 ELK Action 问题。
- Compose healthcheck 覆盖 PostgreSQL、Redis、RustFS、backend API 和 frontend。
- `scripts/doctor.sh` 检查容器健康、数据库连接、Redis Stream、S3 bucket、MCP `/api/mcp`、custom 目录可读、custom packages 可导入和 custom 脚本可扫描。
## 发布验证
每个 release 至少验证三条路径:
1. 全新部署:空 volume、默认配置、创建管理员账号后可登录。
2. 带定制部署:提供 custom Module/Playbook/SIEM YAML 和 `custom/requirements.txt`,依赖安装后刷新/校验通过,worker 能加载。
3. 升级部署:旧版本数据、`.env``custom/` 保留,迁移后核心功能可用。
@@ -1,134 +0,0 @@
# ASP custom 运行目录设计
## 背景
ASP 需要把本地开发、测试和单机 Compose 生产部署的定制机制统一起来。当前已经引入 `custom/` 作为 Module、Playbook、SIEM YAML 和额外 Python 依赖的运行入口,但仍存在两个问题:
- `backend/examples` 会让示例与真实运行目录分离,开发者需要复制文件才能测试。
- 内置 Module、SIEM YAML 和自定义 Playbook 示例的生产边界需要更清晰,避免测试内容进入生产默认运行路径。
## 决策
`backend/custom` 作为源码开发环境的 canonical custom 目录;Compose 发布包中的 `custom/` 使用相同目录结构,但默认只提供空模板,不包含测试样例。Docker 镜像不包含源码 `backend/custom`,生产运行时只读取挂载的发布包 `custom/`
## 目标
- 删除 `backend/examples` 概念。
- 让本地开发直接使用 `backend/custom` 验证 Module、SIEM YAML、custom Playbook、custom Prompt 和 `requirements.txt`
- 让生产发布包保留空 `custom/` 模板,避免默认加载测试 Module 或测试 SIEM schema。
- 保留官方 Playbook 的产品能力,同时提供一个 custom LLM Playbook 示例用于验证扩展机制。
- 更新文档和 `asp-marketplace` 中对应 Skills,使路径说明一致。
## 非目标
- 不把 custom 测试内容打进 Docker 镜像。
- 不让发布包默认加载测试 Module、测试 SIEM YAML 或测试 custom Playbook。
- 不改变官方 Playbook 的运行模型,除 `cmdb_enrichment` 迁出为 custom 示例外。
## 目录结构
源码开发目录:
```text
backend/custom/
modules/
aws_iam_privilege_escalation_attach_user_policy.py
edr_vssadmin_delete_shadows.py
mail_user_report_phishing.py
data/
modules/
aws_iam_privilege_escalation_attach_user_policy/raw_alert_*.json
edr_vssadmin_delete_shadows/raw_alert_*.json
mail_user_report_phishing/raw_alert_*.json
siem/
siem-aws-cloudtrail.yaml
siem-host-events.yaml
siem-network-traffic.yaml
playbooks/
case_summary/System_en.md
case_summary/System_zh.md
playbooks/
cmdb_enrichment.py
case_summary.py
requirements.txt
```
Compose 发布包目录保持同构,但默认为空模板:
```text
custom/
modules/
data/
modules/
siem/
playbooks/
playbooks/
requirements.txt
```
## 运行加载规则
- Module 只加载 `custom/modules/*.py`
- SIEM YAML 只加载 `custom/data/siem/*.yaml`
- 官方 Playbook 保留:
- `backend/playbooks/investigation.py`
- `backend/playbooks/knowledge_extraction.py`
- `backend/playbooks/threat_intelligence_enrichment.py`
- 自定义 Playbook 加载 `custom/playbooks/*.py`,可追加或覆盖同名官方 Playbook。
- `backend/data` 只保留产品运行时 Prompt,不再放 Module 或 SIEM 示例数据。
## 自定义 Playbook Prompt
新增一个 custom LLM Playbook 示例:
```text
backend/custom/playbooks/case_summary.py
backend/custom/data/playbooks/case_summary/System_en.md
backend/custom/data/playbooks/case_summary/System_zh.md
```
提供通用 Prompt 读取能力,供 custom Playbook 调用,例如:
```python
self.read_prompt("System")
```
读取路径为:
```text
custom/data/playbooks/<playbook_slug>/System_<prompt_language>.md
```
如果 Prompt 文件不存在,Playbook 执行失败并把明确错误写入 `remark`,不使用空 Prompt 或静默 fallback。
`case_summary.py` 读取 Case 及其关联 Alert、Artifact、Enrichment 的摘要上下文,调用当前 LLM Provider,生成并写回 `case.summary`,返回执行摘要。它用于验证 custom Playbook 加载、custom Prompt 加载、LLM 调用和 Case 写回。
## 错误处理与刷新校验
`Refresh / Validate` 需要覆盖:
- Module:扫描 `custom/modules/*.py`,返回 `name``stream_name``path` 和加载错误。
- SIEM YAML:扫描 `custom/data/siem/*.yaml`,返回 `name``backend``path` 和加载错误。
- Playbook:扫描官方目录和 `custom/playbooks/*.py`,返回 `source``name``tags``path` 和加载错误。
- Prompt:至少校验 `case_summary``System_en.md``System_zh.md` 是否存在。
加载错误只影响对应文件,不阻塞 ASP 启动。如果 `custom/requirements.txt` 或 helper module 变更,仍要求重新安装依赖并重启相关容器。
## 验证标准
本地源码环境:
- `refresh_custom_definitions()` 应返回 `modules=3``siem=3``playbooks=5`,其中 Playbook 为 3 个官方 + 2 个 custom。
- `case_summary` 可在已有 Case 上运行,并写回 `summary`
生产 Compose 空模板:
- `refresh_custom_definitions()` 应返回 `modules=0``siem=0``playbooks=3`
-`custom/requirements.txt` 不触发依赖安装。
文档与 marketplace
- `asf-doc` 中 Module、SIEM YAML、custom Playbook 的路径全部使用 `custom/...`
- `asp-marketplace` 中 Module Creator、SIEM Index YAML、Playbook 相关 Skill 全部使用新目录。
- 不再出现 `backend/examples``backend/modules``backend/data/siem` 作为运行或生成路径。
@@ -1,157 +0,0 @@
# GitHub CI/CD 发布设计
## 背景
ASP 最终会在 GitHub 上创建仓库并发布。项目当前已经具备 backend/frontend Dockerfile、Docker Compose 分发包、`deploy/package-asp-compose.sh` 打包脚本、uv 管理的 Django 后端、pnpm/Vite 前端,以及 GitHub Container Registry 可直接承载镜像。
`asf-doc` 是独立 GitHub 仓库,并已配置 Cloudflare Pages,因此主仓库 CI/CD 不负责构建或发布文档站。
## 决策
主仓库使用 GitHub Actions、GitHub Container Registry、GitHub Releases、GitHub Advanced Security/CodeQL/Dependabot 等 GitHub 工具链。发布主路径为推送 Git tag,例如 `v0.2.0`
## 目标
- PR 和主分支 push 自动运行质量检查。
- 推送 `v*` tag 自动构建并发布 backend/frontend 镜像。
- 自动生成 `asp-compose-<version>.tar.gz` 并上传到 GitHub Release。
- Release 失败时不生成半成品 Release。
- 第一阶段保持流程简单,预留 SBOM、镜像签名和漏洞门禁的扩展空间。
## 非目标
- 不在主仓库构建 `asf-doc` 或发布 Cloudflare Pages。
- 不实现自动在线升级器。
- 不在第一阶段把全部安全扫描结果作为 Release 阻断。
- 不引入 GitHub 之外的 CI/CD 平台。
## Workflow 分层
### `ci.yml`
触发:
- `pull_request`
- `push` 到主分支
职责:
- Backend
- 安装 uv。
- `uv sync --frozen`
- `python manage.py check`
- 运行已有 Django tests。
- Frontend
- 启用 Corepack/pnpm。
- `pnpm install --frozen-lockfile`
- `pnpm exec eslint .`
- `pnpm exec tsc -b`
- `pnpm build`
- Compose package
- 校验 `deploy/asp-compose/compose.yaml` 可解析。
- dry-run 生成 `asp-compose-<version>.tar.gz`
- 校验 tar.gz 中的 `custom/` 是空模板,不包含源码 `backend/custom` 测试样例。
PR 必须通过 `ci.yml` 才能合并。
### `docker.yml`
触发:
- `push` tag `v*`
- `workflow_dispatch` 用于维护者手动重跑
职责:
- 使用 Docker Buildx 构建:
- `backend/Dockerfile`
- `frontend/Dockerfile`
- 推送到 GHCR
- `ghcr.io/<owner>/<repo>/asp-backend:<version>`
- `ghcr.io/<owner>/<repo>/asp-frontend:<version>`
- 可选 `latest`,只指向最新稳定 tag。
PR 阶段可以只执行 Docker build,不 push。
### `release.yml`
触发:
- `push` tag `v*`
职责:
- 等待或依赖镜像构建成功。
- 运行打包脚本生成 `asp-compose-<version>.tar.gz`
- 确认 `.env.example` 中镜像 tag 指向当前版本。
- 创建 GitHub Release。
- 上传 `asp-compose-<version>.tar.gz`
- 使用 GitHub auto-generated release notes,后续可替换为 Release Drafter。
如果镜像构建、打包或校验失败,则不创建 Release。
### `security.yml` 与 GitHub 安全能力
第一阶段启用:
- CodeQL。
- Secret scanning 和 push protection。
- Dependabot
- GitHub Actions。
- Docker。
- npm/pnpm。
Python/uv 依赖更新先不强行自动化;后续可以增加定期 workflow 执行 `uv lock --upgrade` 并开 PR。
## Release 产物
Tag `v0.2.0` 对应:
```text
ghcr.io/<owner>/<repo>/asp-backend:0.2.0
ghcr.io/<owner>/<repo>/asp-frontend:0.2.0
asp-compose-0.2.0.tar.gz
```
Release asset 保留版本号。虽然 GitHub Release 页面本身有 tag,但用户下载到本地后常会保留多个版本,版本号能避免文件覆盖和混淆。
## 用户升级流程
用户升级时:
1. 下载新版本 `asp-compose-<version>.tar.gz`
2. 备份 PostgreSQL、RustFS、`.env``custom/``certs/``logs/`
3. 解压新包。
4. 复制旧 `.env``custom/``certs/``logs/` 到新目录。
5. 执行 `scripts/upgrade.sh`
用户如果希望固定部署路径,可以把解压后的目录重命名为 `asp-compose`,但 Release asset 仍保持版本化命名。
## 权限与安全
Workflow 使用最小权限:
- CI`contents: read`
- Docker publish`contents: read``packages: write`
- Release`contents: write`、必要时 `packages: read`
镜像推送和 Release 创建默认使用 `GITHUB_TOKEN`。发布 tag 应只允许维护者创建;主分支开启 branch protection,并要求 `ci.yml` 通过。
后续增强项:
- Trivy 镜像扫描。
- SBOM 生成和上传。
- cosign keyless 签名。
- GitHub Environments 和 Release approval。
这些增强项不阻塞第一阶段上线。
## 验证标准
- PR 修改 backend/frontend/deploy 时,`ci.yml` 能准确失败或通过。
- 推送 `v*` tag 后,GHCR 出现两个版本镜像。
- GitHub Release 出现 `asp-compose-<version>.tar.gz`
- tar.gz 中 `compose.yaml``.env.example``scripts/`、空模板 `custom/``logs/``certs/` 完整。
- tar.gz 中不包含源码开发的 `backend/custom` 测试样例。
- 使用 Release tar.gz 后能执行 `scripts/init.sh``scripts/upgrade.sh`
@@ -1,104 +0,0 @@
# Inbox message 资源链接展示设计
## 背景
Inbox message 中的资源链接当前会在部分资源上显示内部 `id`/UUID,而不是业务可读 ID,例如 `alert_000001``enrichment_000001``playbook_000001``knowledge_000001`。这不是 alert 单点问题,而是所有使用 readable id 的资源在 message 链接展示上的通用身份处理问题。
当前代码里资源身份被拆成两类字段:
- `object_id`:用于 GenericForeignKey 和详情接口加载记录的内部主键。
- `resource_label`:用于 message 链接展示的业务可读标签。
前端 `InboxDrawer` 的链接文字使用 `resource_label || object_id`。因此只要后端没有返回正确的 `resource_label`,内部主键就会泄漏到 message 链接文字上。
## 根因
现有逻辑只部分统一了 label 生成,没有统一 record identity 的完整生命周期。
`label_for_content_object()` 可以从对象上按字段优先级读取 `case_id``alert_id``artifact_id``enrichment_id``playbook_id``knowledge_id` 等 readable id,但它只是一个展示 label helper。`create_inbox_message()` 只有在调用方传入 `content_object` 时才会自动生成 `resource_key``resource_label`;如果调用链只传 `content_type + object_id`,或历史消息已经存了空 label/错误 label,最终序列化和前端 fallback 仍可能显示内部 `object_id`
回复消息还会复制父消息模型字段里的 `resource_label`。如果父消息的持久化 label 已经是空值或内部 id,回复也会继承错误展示。
## 决策
保留 `object_id` 作为内部主键,继续用于打开详情页和 GenericForeignKey 查询;不要把详情路由和 ViewSet lookup 改成 readable id。
新增统一的 record identity 解析逻辑,输入可以是 `content_object`,也可以是 `content_type + object_id`,输出统一为:
- `resource_key`:前端资源 key,例如 `alerts``enrichments``playbooks``knowledge`
- `object_id`:内部主键字符串,用于打开详情。
- `resource_label`:业务可读 ID,优先使用资源的 readable id 字段。
前端不再把内部 `object_id` 作为链接文字兜底。后端无法解析 label 时,前端显示中性占位文案,例如 `related record`,避免泄漏 UUID/id。
## 范围
覆盖以下资源:
- Case: `case_id`
- Alert: `alert_id`
- Artifact: `artifact_id`
- Enrichment: `enrichment_id`
- Playbook: `playbook_id`
- Knowledge: `knowledge_id`
- User: `username`
不改变资源详情接口 lookup 方式,不新增数据库字段,不迁移主键,不改变 comments/audit 使用 `object_id` 查询当前记录的设计。
## 组件设计
### 后端 identity helper
在 inbox 资源链接相关逻辑中集中定义资源身份解析:
1. 根据 `content_object` 直接读取模型和主键。
2. 如果只有 `content_type + object_id`,先通过 GenericForeignKey 等价逻辑解析对象。
3. 根据模型名映射 `resource_key`
4. 根据模型名映射 readable id 字段,生成 `resource_label`
5. 如果记录不存在或已删除,保留已有 `resource_label`,但不把内部 `object_id` 当作展示 label。
### Inbox 创建链路
`create_inbox_message()` 在保存前统一调用 identity helper
- 新消息传入 `content_object` 时,自动补齐 `content_type``object_id``resource_key``resource_label`
- 新消息只传 `content_type + object_id` 时,也解析对象并补齐 `resource_key``resource_label`
- 调用方显式传入 `resource_label` 时,仍优先使用 helper 解析出的 readable label;只有对象不可解析时才保留调用方 label。
`send_system_message()``send_user_message()`、comment mention 通知都继续调用 `create_inbox_message()`,不各自重复 label 规则。
### Inbox 序列化链路
`InboxMessageSerializer.get_resource_label()` 使用同一个 identity helper 动态生成展示 label。这样可以修复历史 message 的 API 展示,即使数据库里的 `resource_label` 为空或已经存成内部 id,只要目标记录仍存在,API 也返回 readable id。
回复消息创建时不要直接复制父消息模型字段里的旧 `resource_label` 作为最终可信值,而是交给 `create_inbox_message()` 重新解析。
### 前端展示链路
`InboxDrawer.RecordLink` 保持使用 `object_id` 打开详情,因为后端 ViewSet 当前 lookup 都是内部主键。
链接文字改为只信任 `resource_label`;没有 label 时显示 `related record`,不显示 `object_id`。这保证未来某个资源解析失败时,UI 不会再次暴露内部 id。
## 数据流
1. 用户在资源详情评论中 mention 其他用户,前端提交 `content_type` 和当前记录内部 `id`
2. 后端创建 Comment,随后创建 InboxMessage。
3. InboxMessage 创建前统一解析 record identity,保存内部 `object_id` 和 readable `resource_label`
4. Inbox API 序列化时再次通过 helper 计算展示 label,修复历史数据和边界情况。
5. 前端 message 链接显示 `resource_label`,点击时仍用 `resource_key + object_id` 打开详情。
## 错误处理
- 目标记录已删除或 `content_type/object_id` 无法解析时,不抛出影响 inbox 列表的异常。
- API 保留已有 `resource_label`;如果没有可用 label,返回空字符串。
- 前端显示 `related record`,点击仍可尝试打开原 `object_id`;如果详情接口返回 404,沿用现有的 “Record not found or has been deleted” 提示。
## 验证
覆盖 case、alert、artifact、enrichment、playbook、knowledge 六类资源的 message 链接展示:
1. 新建 comment mention 后,Inbox API 的 `object_id` 是内部主键,`resource_label` 是对应 readable id。
2. 历史 message 即使持久化 `resource_label` 为空,API 仍能在目标记录存在时返回 readable id。
3. 回复 message 不继承错误 label,而是重新解析父消息目标资源。
4. 前端 Inbox 链接文字不再显示 UUID/id,点击仍能打开正确详情。
@@ -1,153 +0,0 @@
# Playbook 与 Case 分配通知偏好设计
## 背景
ASP 已有 Inbox 站内信能力,支持系统消息、用户消息、未读计数和资源跳转。当前 Comment mention 会写入 Inbox,但 Playbook 执行完成和 Case 分配不会主动提醒相关用户。
本次目标是在不新增通知通道的前提下,让用户可以控制两类站内通知:
- Playbook 运行完成后通知触发该 Playbook 的用户。
- 用户被分配 Case 后通知新的负责人。
## 决策
采用 User 模型布尔字段保存个人通知偏好,并复用现有 Inbox system message 发送通知。
新增字段:
- `notify_on_playbook_completion`:Playbook 成功或失败完成后是否通知触发用户,默认开启。
- `notify_on_case_assignment`:Case 分配给当前用户时是否通知,默认开启。
偏好仅由用户本人在个人中心配置。管理员用户管理页面不新增代改入口。
## 目标
- Playbook 状态变为 `Success``Failed` 后,按触发用户偏好发送 Inbox 通知。
- Case `assignee` 从空或其他用户变为新用户后,按新负责人偏好发送 Inbox 通知。
- 个人中心新增 `Settings` 标签页,通知偏好作为其中一个设置区块。
- 通知失败不影响 Playbook 状态落库或 Case 分配保存,但必须记录错误日志。
- 更新 `asf-doc` 中对应用户文档,说明通知偏好和触发规则。
## 非目标
- 不新增邮件、Webhook、浏览器推送或实时 Toast 通知。
- 不让管理员代用户配置通知偏好。
- 不在取消分配、重复保存同一负责人时发送 Case 分配通知。
- 不在用户把 Case 分配给自己时发送 Case 分配通知。
- 不新增通用通知规则引擎或独立偏好表。
## 后端设计
### 用户偏好
`accounts.User` 上新增两个布尔字段,默认值为 `True`。迁移后现有用户也保持默认开启。
`UserSerializer` 返回两个字段,使登录、刷新用户资料和个人中心都能拿到当前偏好。
`UserProfileSerializer` 允许当前用户通过 `/auth/profile/` 更新这两个字段。管理员使用的 `UserAdminUpdateSerializer` 不包含这两个字段。
### 通知事件封装
新增一个小型事件通知模块,例如 `apps.inbox.notifications`,集中处理:
- 偏好判断。
- 用户是否存在、是否活跃。
- 自分配跳过逻辑。
- Inbox 文案。
- `metadata.source` 和相关上下文。
- 发送失败时的结构化日志。
通知仍通过 `apps.inbox.services.send_system_message()` 创建,使用 `content_object` 关联目标记录:
- Playbook 完成通知关联 `Playbook``metadata.source = "playbook_completion"`
- Case 分配通知关联 `Case``metadata.source = "case_assignment"`
### Playbook 完成通知
触发点在 Playbook worker 将状态写成终态之后:
- 成功路径:`mark_playbook_success()` 保存 `Success``remark` 后触发。
- 失败路径:`mark_playbook_failed()` 保存 `Failed` 和错误 remark 后触发。
发送条件:
- `playbook.user` 存在。
- 用户仍为 active。
- 用户开启 `notify_on_playbook_completion`
- 状态为 `Success``Failed`
通知内容包含 Playbook 名称、状态、关联 Case 标识和 remark 摘要,并链接到 Playbook 详情。
### Case 分配通知
触发点在 Case 更新保存后。保存前记录旧 `assignee_id`,保存后比较新值。
发送条件:
- 新 `assignee_id` 非空。
- 新 `assignee_id` 与旧值不同。
- 操作者不是新 assignee。
- 新 assignee 仍为 active。
- 新 assignee 开启 `notify_on_case_assignment`
通知内容包含 Case 标识、标题和分配操作者,并链接到 Case 详情。
## 前端设计
个人中心新增 `Settings` 标签页。该标签页先包含一个 `Notification Preferences` 区块,后续可承载其他个人设置。
区块内包含两个 Ant Design `Switch`
- `Notify me when my Playbook runs finish`
- `Notify me when a Case is assigned to me`
打开个人中心时使用 auth store 中的当前用户初始化表单。保存时 PATCH `/auth/profile/`,成功后刷新 auth store 并提示保存成功。
## 错误处理
通知发送是 best effort
- Playbook 状态落库和 Case 分配保存是核心操作,通知失败不回滚这些操作。
- 通知失败必须记录错误日志,日志包含事件类型、目标记录 ID 和接收用户 ID。
- 偏好关闭、用户不存在、用户禁用、无触发用户、自分配等是正常跳过路径,不记录错误。
## 数据迁移
新增 accounts migration
- 添加 `notify_on_playbook_completion = models.BooleanField(default=True)`
- 添加 `notify_on_case_assignment = models.BooleanField(default=True)`
不需要数据回填脚本,字段默认值覆盖现有用户。
## 文档
实现完成后更新 `asf-doc`
- 先更新中文文档。
- 说明个人中心 `Settings` 中的通知偏好。
- 说明 Playbook 完成通知覆盖成功和失败。
- 说明 Case 分配通知只通知新负责人,取消分配、重复分配和自分配不会通知。
- 中文定稿后同步英文文档。
## 验证标准
后端:
- 迁移文件生成并可应用。
- Playbook 成功完成后,开启偏好的触发用户收到 Inbox system message。
- Playbook 失败完成后,开启偏好的触发用户收到 Inbox system message。
- 关闭 Playbook 完成通知后,不再收到 Playbook 完成消息。
- Case 分配给其他用户后,新负责人收到 Inbox system message。
- 关闭 Case 分配通知后,新负责人不再收到消息。
- 取消分配、重复保存同一 assignee、自分配不产生通知。
前端:
- 个人中心出现 `Settings` 标签页。
- 两个通知开关能反映当前用户偏好。
- 保存后刷新当前登录用户状态。
文档:
- `asf-doc` 中文和英文文档均描述该功能。
@@ -1,293 +0,0 @@
# Custom Definitions Console 设计
## 背景
Custom Definitions 是 ASP 扩展框架的管理入口,用于让管理员理解当前运行环境中加载了哪些自定义能力,以及这些能力是否能被后端正确解析。现有入口位于 System Settings 的 Runtime Tab 内,只有一个聚合的 `Refresh / Validate` 区块,信息密度低,也把 Custom 功能和 Runtime 配置混在一起。
本次设计将 Custom Definitions 提升为独立的 admin-only 控制台,聚焦三个可管理对象:
- Modules
- Playbooks
- SIEM YAML
Prompt 文件不再作为 Custom Definitions 的管理对象。Playbook 可以选择把 prompt 写在代码中,也可以自行使用 `BasePlaybook.read_prompt()` 读取文件;框架不应要求或校验 playbook prompt 文件。
## 决策
采用 **Custom Console** 方案:
- 前端新增独立 `/custom` 页面和侧边栏 `Custom` 入口。
- 页面仅 admin 可见,分为 `Modules``Playbooks``SIEM YAML` 三个 tabs。
- 每个 tab 独立加载、刷新和校验自己的 definition。
- Modules 增加只读 Redis Stream inspection,用于辅助调试输入数据。
- Playbooks 和 SIEM YAML 以展示、校验为主,不提供创建和编辑。
- 删除 Custom Definitions 链路中的 prompt 扫描、计数和缺失校验。
## 目标
- 让管理员可以从独立入口查看所有已加载 definition。Playbook 需要区分 `official` / `custom` 来源;Modules 和 SIEM YAML 没有 official 来源概念,不展示 source。
- 将验证功能按 tab 拆分,避免一个聚合结果难以定位问题。
- 让 Module 页面展示对应 Redis Stream 的基础运行信息,并支持只读查看最近消息或指定消息。
- 让 Playbook 页面展示可运行定义,但不在 Custom 页面直接执行 playbook。
- 让 SIEM YAML 页面展示 index schema 和 fields,方便确认 YAML 是否符合预期。
- 从 Runtime 设置页移除 Custom Definitions 区块,使 Runtime 只负责运行配置。
## 非目标
- 不做文件创建、编辑、删除或在线 YAML 编辑器。
- 不提供 Module 投递测试消息、立即消费消息、run once 或删除 stream/message。
- 不在 Custom 页面运行 PlaybookPlaybook 测试继续通过 Case 的 Run Playbook 入口完成。
- 不新增 URL 过滤、URL 打开指定记录或跨页面 deep link 能力;Playbook 运行记录跳转留到后续单独设计。
- 不新增数据库模型或迁移。
- 不新增 Prompts tab,不校验 prompt 文件是否存在。
## 权限与导航
`/custom` 使用与 System Settings 相同的 admin 权限边界:
- admin 用户在侧边栏看到 `Custom`,位置在 `Knowledge``Setting` 之间。
- 非 admin 用户不显示入口;直接访问 `/custom` 时重定向到 `/cases`
- 页面 breadcrumb 显示 `Custom`
`RuntimeSettings` 删除现有 Custom Definitions 区块,只保留 Prompt Language 和 Stream Maxlen 等 Runtime 配置。
## 后端 API
新增 admin-only API,路径不挂在 Runtime 下:
| Method | Path | 用途 |
| --- | --- | --- |
| `GET` | `/custom/modules/` | 自动加载 Module definitions 和 stream health,不写审计 |
| `POST` | `/custom/modules/` | 手动 Refresh / Validate Modules,写审计 |
| `GET` | `/custom/playbooks/` | 自动加载 Playbook definitions,不写审计 |
| `POST` | `/custom/playbooks/` | 手动 Refresh / Validate Playbooks,写审计 |
| `GET` | `/custom/siem/` | 自动加载 SIEM YAML definitions,不写审计 |
| `POST` | `/custom/siem/` | 手动 Refresh / Validate SIEM YAML,并 reload registry cache,写审计 |
| `GET` | `/custom/modules/stream/messages/` | 读取指定 stream 最近消息,默认 5 条,最大 20 条 |
| `GET` | `/custom/modules/stream/message/` | 按 stream name 和 message id 读取单条消息 |
`POST` 审计沿用现有 RuntimeConfig 单例作为审计目标,metadata 至少包含:
- `section`
- `success`
- `counts`
GET 自动加载不写审计,避免用户打开页面导致审计噪声。
## 数据契约
### Modules
Module section 返回:
- `items`
- `errors`
- `counts`
- `success`
每个 item 包含:
- `name`
- `description`
- `path`
- `stream_name`
- `thread_num`
- `stream_health`
`stream_health` 包含:
- `available`
- `length`
- `first_id`
- `last_id`
- `groups`
- `warning`
Redis 不可用或 stream 不存在时,definition scan 仍成功;仅在 `stream_health.warning` 中体现原因。
### Playbooks
Playbook section 返回:
- `items`
- `errors`
- `counts`
- `success`
每个 item 包含:
- `name`
- `description`
- `tags`
- `source`
- `path`
Custom Definitions 不再返回 `prompts` section、prompt counts 或 prompt errors。`REQUIRED_PROMPTS` 扫描从 refresh/validate 链路删除。
### SIEM YAML
SIEM section 返回:
- `items`
- `errors`
- `counts`
- `success`
每个 item 包含:
- `name`
- `backend`
- `description`
- `path`
- `field_count`
- `key_field_count`
- `fields`
每个 field 包含:
- `name`
- `type`
- `description`
- `is_key_field`
- `sample_values`
## 前端设计
新增 `CustomDefinitions` 页面。页面使用 Ant Design `Tabs`,包含三个 tab,不设置 Overview。
### Modules tab
Toolbar
- `Refresh / Validate`
- `Reload`
- search
主表字段:
- Module name
- description
- stream_name
- thread_num
- stream length
- last message id
- path
点击行打开详情抽屉,展示:
- definition 基础信息
- stream health
- 最近消息 JSON viewer,默认 5 条,最多 20 条
- 按 message id 读取单条消息
所有 stream 操作只读,不提供写入、消费或删除。
### Playbooks tab
Toolbar
- `Refresh / Validate`
- `Reload`
- source filter
- tag filter
- search
主表字段:
- Playbook name
- source
- tags
- description
- path
点击行打开详情抽屉,展示完整描述、tags 和路径。
动作:
- Playbook 运行记录跳转留到后续单独设计,本次不实现 URL 过滤或 deep link。
### SIEM YAML tab
Toolbar
- `Refresh / Validate`
- `Reload`
- backend filter
- search
主表字段:
- index name
- backend
- description
- field count
- key field count
- path
点击行打开详情抽屉,展示 fields 表:
- name
- type
- key field
- description
- sample values
不提供 YAML 编辑、新建或 live query preview。
## 错误处理
- Definition scan 按文件收集错误,一个坏文件不阻断同类其他文件展示。
- 每个 tab 只展示本 section 的 errors。
- Redis stream health 失败降级为 warning,不影响 Module definition 列表。
- Stream message 读取失败时,前端显示 toast,并在抽屉内显示错误状态。
- SIEM YAML parse/validation 错误显示文件路径和异常信息。
- 删除 prompt 扫描后,prompt 文件缺失不再算 Custom Definitions validation error。
## 代码边界
后端复用现有 loader
- `apps.agentic.runtime.module.scan_module_definitions`
- `apps.agentic.services.playbooks.scan_playbook_definitions`
- `integrations.siem.registry.scan_registry_configs`
需要拆出 section 级 service,避免一个聚合函数继续承载所有逻辑。旧的 Runtime nested refresh view 可以删除或替换为新 API。
保留 `BasePlaybook.prompt_path()``BasePlaybook.read_prompt()` 作为可选 helper;删除 Custom Definitions 中对 `REQUIRED_PROMPTS` 的扫描和错误计数。
前端新增页面组件时优先复用 Ant Design Table、Drawer、Tag、Alert、Input.Search 和现有 JSON viewer。不要为第一版引入自定义复杂布局。
## 文档
更新文档时先更新中文,再同步英文:
- Runtime 文档移除 Custom Definitions 区块。
- 新增或调整 Custom Definitions 文档,说明新入口、三个 tabs、Refresh / Validate 行为和 Module stream inspection。
- 说明 Prompt 文件不是 Custom Definitions 管理对象;文件 prompt 是 Playbook 可选实现方式。
## 验证标准
后端:
- 非 admin 访问 `/custom/*` 被拒绝。
- `GET /custom/modules/` 返回 definitions 和 stream health,不写 audit log。
- `POST /custom/modules/` 返回同样结构并写 section 审计。
- Module stream messages 默认 5 条,最大限制为 20 条。
- Redis 不可用时 Module definitions 仍返回,stream health 显示 warning。
- `GET/POST /custom/playbooks/` 不返回 prompts section,也不因 prompt 文件缺失报错。
- `GET/POST /custom/siem/` 返回 fields、field_count 和 key_field_count。
- `POST /custom/siem/` 会 reload SIEM registry cache。
前端:
- admin 侧边栏显示 `Custom`,非 admin 不显示。
- `/custom` 页面只有 Modules、Playbooks、SIEM YAML 三个 tabs。
- Runtime 页不再显示 Custom Definitions。
- 每个 tab 可单独 Refresh / Validate 并展示本 section errors。
- Modules 详情抽屉可读取最近 stream 消息和指定 message id。
- Playbooks tab 只展示,不直接执行 playbook。
- SIEM YAML 详情抽屉展示 fields 表。
文档:
- 中文和英文文档都不再描述 Prompt 作为 Custom Definitions 的校验对象。
@@ -1,204 +0,0 @@
# Custom Examples 文档设计
## 背景
当前 `定制开发` 文档已经分别说明了 Mock 数据、SIEM YAML、Module 开发和 Playbook 开发,但源码仓库里有一组示例文件本来是可以配合使用的:
- `backend/mock/siem/` 负责生成 SIEM 风格的模拟日志。
- `backend/custom/data/siem/` 提供描述这些模拟索引的 SIEM YAML。
- `backend/custom/modules/` 提供将 raw alert 转换为 ASP Case / Alert / Artifact 的 Module 示例。
- `backend/custom/data/modules/` 提供这些 Module 可使用的 raw alert 样本。
- `backend/custom/playbooks/` 提供两个 custom Playbook 示例。
- `backend/custom/data/playbooks/` 提供 Case Summary Playbook 使用的 prompt 文件。
新文档需要把这些关系讲清楚,让用户理解它们不是互相孤立的代码样例,而是一套可复用的定制开发演示资产,覆盖自定义日志说明、SIEM 查询、告警接入、Case 生成和 Case 后续自动化处理。
## 目标
1. 在 `Development / 定制开发` 下新增一个清晰的示例入口。
2. 说明 Mock SIEM 日志、SIEM YAML、Module、raw alert 样本和 custom Playbook 之间的关系。
3. 为用户提供的位置记录 Mock 日志对应的 Splunk SPL 和 ELK ES|QL 告警查询。
4. 保留现有 Mock Data、SIEM YAML、Module 开发、Playbook 开发页面的单项指南职责。
5. 从现有页面增加指向新示例页的交叉链接。
6. 同时说明源码仓库使用方式,以及如何把示例复制到 Compose 部署包的 `custom/` 目录中使用。
7. 中英文文档结构保持一致。
## 非目标
1. 不修改后端示例代码。
2. 不新增截图或图片占位符。
3. 不声称每个 SIEM YAML 都有对应 Module。
4. 不声称 Mail Module 来自 SIEM Mock 日志生成器。
5. 除非明确要求,不运行 VitePress build。
## 页面结构
`Development / 定制开发` 侧边栏下新增 Custom Examples 区域:
```text
custom-examples/
custom-examples/siem-module-flow/
custom-examples/playbooks/
```
推荐位置:放在 Custom Console 之后、单项开发指南之前;或者作为一个小的示例组,靠近 Module Development、Playbook Development、SIEM YAML 和 Mock Data。
整体 `定制开发` 栏目的信息架构应调整为:
- `定制开发总览`:解释扩展模型和推荐阅读路径。
- `Custom Console`:解释运行时观察和校验。
- `Module 开发``Playbook 开发``SIEM YAML``Mock 数据`:继续作为单项指南。
- `Custom Examples`:作为 cookbook / 端到端示例区域,解释这些单项能力如何组合使用。
这样可以把概念、接口和开发规范保留在现有页面,把跨模块的组合示例集中到新页面,避免内容重复和割裂。
### `custom-examples/`
职责:示例总览页。
内容:
- 说明源码树中的 `backend/custom/` 是测试/示例资产集合。
- 说明发布包中的 `custom/` 默认是空模板。
- 说明用户可以把需要的示例复制到 Compose 部署环境中运行。
- 展示整体关系:
- Mock SIEM 日志生成测试日志数据。
- SIEM YAML 描述索引和字段。
- Module 消费 Redis Stream raw alert 并创建 ASP 资源。
- Playbook 在 Case 生成后继续执行自动化处理。
### `custom-examples/siem-module-flow/`
职责:说明 Mock 日志、SIEM YAML、raw alert 和 Module 如何配合。
必须包含的关系表:
| 资产 | 角色 | 关系 |
| --- | --- | --- |
| `siem-host-events.yaml` | SIEM 索引说明 | 对应 Host mock events,支撑 EDR vssadmin 场景。 |
| `edr_vssadmin_delete_shadows.py` | Module | 将类似 vssadmin 的 raw alert 转换为勒索调查 Case。 |
| `siem-aws-cloudtrail.yaml` | SIEM 索引说明 | 对应 CloudTrail mock events,支撑 AttachUserPolicy 场景。 |
| `aws_iam_privilege_escalation_attach_user_policy.py` | Module | 将类似 AttachUserPolicy 的 raw alert 转换为 IAM 权限提升 Case。 |
| `siem-network-traffic.yaml` | SIEM 索引说明 | 支撑网络流量和暴力破解 mock 日志,可用于 SIEM 查询和规则编写示例;当前没有专属 Module。 |
| `mail_user_report_phishing.py` | Module | 使用 `backend/custom/data/modules/` 中的 raw alert 样本;它不是由 SIEM Mock 日志生成器产生的。 |
还需要说明:
- 源码仓库路径。
- 复制到 Compose 部署包后的路径。
- 如果有可用规则,展示每个场景的 Splunk SPL 和 ELK ES|QL 告警查询。
- 在 Custom Console 中执行 Refresh / Validate 的方式。
- Module 需要运行哪个 Worker。
- 预期输出:Case、Alert、Artifact、Enrichment,以及在适用场景下触发 AI analysis。
如果用户提供具体告警查询,应放在对应场景附近:
- Host vssadmin 场景:查找 `vssadmin.exe delete shadows` 的 Splunk SPL / ELK ES|QL。
- AWS AttachUserPolicy 场景:查找高风险 `AttachUserPolicy` 行为的 Splunk SPL / ELK ES|QL。
- Network traffic / brute-force 场景:查找可疑网络或认证模式的 Splunk SPL / ELK ES|QL。
需要明确:这些规则用于从 Mock SIEM 日志中筛选证据。如果该场景有对应 Module,那么告警/action 集成最终写入的 Redis Stream 名称必须和 Module 的 `STREAM_NAME` 一致。
用户已提供的 Network brute-force / failed-then-success 登录场景 Splunk SPL
```spl
index=siem-network-traffic event.category=authentication (event.action=login_failed OR
event.action=login_success)
| search
[search index=siem-network-traffic event.category=authentication event.action=login_failed
| stats count AS failed_count BY source.ip, user.name
| where failed_count >= 5
| join source.ip, user.name
[search index=siem-network-traffic event.category=authentication event.action=login_success
| stats count AS success_count BY source.ip, user.name]
| fields source.ip, user.name]
```
用户已提供的 AWS AttachUserPolicy 高风险成功授权场景 ELK ES|QL:
```esql
FROM siem-aws-cloudtrail
| WHERE event.action == "AttachUserPolicy"
| WHERE event.risk_score > 80
| WHERE event.outcome == "success"
| WHERE
requestParameters.policyArn IN
(
"arn:aws:iam::aws:policy/AdministratorAccess",
"arn:aws:iam::aws:policy/IAMFullAccess"
)
| SORT @timestamp DESC
```
用户已提供并整理的 Host vssadmin delete shadows 场景 ELK ES|QL
```esql
FROM siem-host-events
| WHERE process.name == "vssadmin.exe"
| WHERE risk_score >= 80
| WHERE process.command_line LIKE "*delete*shadows*"
| SORT @timestamp DESC
```
### `custom-examples/playbooks/`
职责:介绍两个 custom Playbook 示例。
必须包含:
- `case_summary.py`
- 读取 `custom/data/playbooks/case_summary/System_zh.md``System_en.md`
- 按 Runtime 中的 Prompt Language 选择提示词。
- 调用 LLM,并把结果写回 Case Summary 字段。
- `cmdb_enrichment.py`
- 遍历 Case 关联的 Artifact。
- 通过集成层查询 CMDB 上下文。
- 将结果写入 Artifact Enrichment。
还需要说明:
- 源码仓库路径。
- 复制到 Compose 部署包后的路径。
- 在 Custom Console 中执行 Refresh / Validate 的方式。
- 需要运行的 Worker。
- 在 ASP UI 中查看结果的位置。
## 现有页面更新
需要从以下页面增加简短入口链接:
- `development/`
- `development/custom-console/`
- `development/mock-data/`
- `development/siem-yaml/`
- `development/module-examples/`
- `development/playbook/`
每个页面继续保持当前职责,只把端到端组合示例引导到 Custom Examples。
具体更新:
- `定制开发总览`:在推荐阅读顺序中,把 Custom Examples 放到单项开发指南之后。
- `Custom Console`:链接到 Custom Examples,说明这里可以了解运行时加载的示例如何组合。
- `Mock 数据`:明确 SIEM Mock 日志可以配合示例 SIEM YAML、示例查询和示例 Module 使用。
- `SIEM YAML`:链接到 SIEM + Module 示例链路,展示具体索引 / 查询 / Module 的关系。
- `Module 开发`:链接到 SIEM + Module 示例链路,展示完整 raw alert 到 Case 的示例。
- `Playbook 开发`:链接到 Custom Playbook 示例页,说明两个 custom Playbook。
## 语言流程
1. 先写中文页面。
2. 中文结构稳定后,再创建对应英文页面。
3. zh/en 的标题和页面结构保持一致。
## 验证
手工验证应覆盖:
1. 中文和英文侧边栏都包含新页面。
2. 所有新页面都有 zh/en 两个版本。
3. 现有页面能链接到新总览页或详情页。
4. 新页面能链接回 Mock Data、SIEM YAML、Module Development、Playbook Development 和 Custom Console。
5. 页面没有声称 `siem-network-traffic` 有专属 Module。
6. 页面没有声称 Mail Module 来自 SIEM Mock 日志。
7. 除非明确要求,不运行 VitePress build。
@@ -1,192 +0,0 @@
# 定制开发文档信息架构调整设计
## 背景
当前 `集成 / Integrations` 中包含 Webhook 和 ELK Index Action,但这两个页面的实际职责不是“外部能力集成展示”,而是把 SIEM 告警送入 ASP 的 Redis Stream
```text
SIEM Alert / Rule
-> Webhook 或 ELK Index Action
-> Redis Stream
-> Module
-> Case / Alert / Artifact
```
因此它们更接近 `定制开发 / Development` 主线中的“告警接入层”。如果继续放在 `Integrations`,会让读者误以为它们和 MCP、ClaudeCode 插件属于同一类集成能力,也会让 `定制开发` 栏目的链路不完整。
同时,当前 `定制开发` 栏目虽然已经有 Environment Setup、Custom Console、Module、Playbook、SIEM YAML、Mock Data、Custom Examples 等页面,但整体顺序仍偏文件类型和功能点罗列,没有清晰呈现“从日志源到 Case,再到自动化处理”的完整脉络。
## 目标
1. 将 Webhook 和 ELK Index Action 从 `Integrations` 移动到 `Development`
2. 让 `Integrations` 只保留 MCP 和 ClaudeCode Plugin 这类外部 Agent / Harness 集成。
3. 将 `Development` 重组为一条更清晰的定制开发链路。
4. 明确 Webhook / ELK Index Action 的职责是把 SIEM 告警写入 Redis Stream,供 Module 消费。
5. 保持中英文文档结构一致。
6. 删除旧 Integrations 页面,不保留跳转页。
## 非目标
1. 不修改后端 API 或运行逻辑。
2. 不修改 Webhook / ELK Index Action 的功能行为。
3. 不新增图片或图片占位符。
4. 不运行 VitePress build,除非明确要求。
## 新导航结构
### Integrations / 集成
只保留:
```text
Overview
MCP
ClaudeCode Plugin
```
`Integrations` 总览页应只描述外部 Agent、协议、工具生态相关集成。Webhook 和 ELK Index Action 从这里移除。
### Development / 定制开发
按定制开发链路组织:
```text
Overview
Environment Setup
Mock Data
Alert Ingestion
Overview
Splunk Webhook
Kibana Webhook
ELK Index Action
SIEM YAML
Module Development
Playbook Development
Custom Console
Custom Examples
Overview
SIEM + Module Flow
Custom Playbooks
```
推荐理解路径:
1. `Environment Setup`:准备开发环境。
2. `Mock Data`:生成工作台数据或 SIEM 测试日志。
3. `Alert Ingestion`:将 SIEM 告警写入 Redis Stream。
4. `SIEM YAML`:描述日志索引和字段,让 Agent / MCP 能理解日志。
5. `Module Development`:消费 Stream raw alert,生成 Case / Alert / Artifact。
6. `Playbook Development`:在 Case 上继续做调查、富化、摘要或知识提取。
7. `Custom Console`:观察和校验运行时加载状态。
8. `Custom Examples`:用端到端示例串起前面的能力。
## 页面迁移
删除旧页面:
```text
integrations/webhook/index.md
integrations/webhook/splunk/index.md
integrations/webhook/elk/index.md
integrations/elk-index-action/index.md
```
新增 / 移动到:
```text
development/alert-ingestion/index.md
development/alert-ingestion/splunk-webhook/index.md
development/alert-ingestion/kibana-webhook/index.md
development/alert-ingestion/elk-index-action/index.md
```
英文文档做同样迁移。
## 页面职责
### `development/alert-ingestion/`
职责:告警接入总览。
需要说明:
- WebhookSIEM 直接 POST 到 ASP Webhook,适合 SIEM 能访问 ASP API 的环境。
- ELK Index ActionKibana 先把 Action 写入 Elasticsearch 索引,ASP Worker 再轮询读取,适合无法直接 POST 或社区版能力受限的环境。
- 两种方式最终都会写入 Redis Stream。
- Stream 名称必须和后续 Module 的 `STREAM_NAME` 对齐。
- Alert Ingestion 是 Module 的上游,不负责直接生成 Case。
### `development/alert-ingestion/splunk-webhook/`
职责:Splunk Alert 直接 POST 到 ASP。
保留原有内容,并强化:
- `search_name` 会作为 Redis Stream 名称。
- `result` 是写入 Stream 的 raw alert。
- Splunk Alert 名称要和 Module `STREAM_NAME` 对齐。
- 链接到 Module Development 和 Custom Console。
### `development/alert-ingestion/kibana-webhook/`
职责:Kibana Rule 通过 Webhook connector 直接 POST 到 ASP。
保留原有内容,并强化:
- `rule.name` 会作为 Redis Stream 名称。
- `context.hits` 会逐条写入 Stream。
- Rule 名称要和 Module `STREAM_NAME` 对齐。
- 链接到 Module Development 和 Custom Console。
### `development/alert-ingestion/elk-index-action/`
职责:Kibana Rule 通过 Index connector 先写入 ElasticsearchASP 再轮询读取。
保留原有内容,并强化:
- Action Index 和轮询参数在 SIEM 设置中配置。
- `run_elk_action_worker` 持续读取 Action Index。
- 读取结果会转换为 Kibana webhook payload,再写入 Redis Stream。
- 链接到 SIEM 设置、Module Development、Custom Console。
## 现有页面更新
需要更新:
- `development/index.md`
- 重写总览中的数据流和推荐阅读顺序。
- 明确 Alert Ingestion 在 Mock Data 和 SIEM YAML / Module 之间。
- `development/mock-data/`
- 链接到 Alert Ingestion,说明 Mock SIEM 日志可以作为告警规则输入。
- `development/siem-yaml/`
- 链接到 Alert Ingestion 和 Custom Examples。
- `development/module-examples/`
- 明确 Module 的上游是 Webhook / ELK Index Action 写入的 Redis Stream。
- `development/custom-examples/`
- 将 Webhook / ELK Index Action 链接改到新路径。
- `settings/siem/`
- 如果有指向旧 ELK Index Action 文档的链接,改到新路径。
- `integrations/index.md`
- 移除 Webhook 和 ELK Index Action。
- 只保留 MCP 和 ClaudeCode Plugin。
## 链接策略
按用户确认:
- 旧 Integrations 页面直接删除。
- 不保留 redirect / stub 页面。
- 所有文档内部链接必须更新到新路径。
- 若外部已有旧链接,后续可以通过站点层 redirects 再处理,但本次不做。
## 验证
手工验证应覆盖:
1. zh/en 侧边栏中 Integrations 不再包含 Webhook / ELK Index Action。
2. zh/en 侧边栏中 Development 包含 Alert Ingestion 分组。
3. 新路径下 zh/en 页面都存在。
4. 旧路径下 Webhook / ELK Index Action 页面已删除。
5. 文档中不再出现指向旧 `integrations/webhook``integrations/elk-index-action` 的相对链接。
6. Development 总览能读出清晰链路:Mock Data -> Alert Ingestion -> SIEM YAML -> Module -> Playbook -> Custom Console -> Custom Examples。
7. 除非明确要求,不运行 VitePress build。
@@ -1,171 +0,0 @@
# Record Sharing Design
## Context
The platform already has authenticated detail routes for every supported record resource:
- `/cases/:rowId`
- `/alerts/:rowId`
- `/artifacts/:rowId`
- `/enrichments/:rowId`
- `/playbooks/:rowId`
- `/knowledge/:rowId`
`App.tsx` routes these URLs to `ResourceDetailRoute`, which loads the relevant `ResourceConfig` and renders the existing `RecordDetailModal`. The detail modal fetches the record through the configured detail endpoint, such as `/api/cases/{id}/`, and displays the normal Basic view and tabs.
This means record sharing does not need a new detail page or a backend share-token system for the first version. The missing pieces are a visible copy-link action and preserving the requested deep link across authentication redirects.
## Goals
1. Let users copy a URL for any record type that already has a detail route.
2. Require normal authentication and existing read permissions; a share link must not grant new access.
3. Reuse the existing detail modal presentation.
4. Open shared links on the Basic tab.
5. Keep current list-page behavior unchanged: opening a record from a list does not update the address bar.
6. Preserve the destination URL when unauthenticated users log in, including after token-expiry redirects.
## Non-goals
1. No public links.
2. No share tokens, expiry, revocation, or per-link ACLs.
3. No database schema changes.
4. No new full-page record detail layout.
5. No restoration of detail sub-tabs such as Case Alerts or Investigation.
## Recommended approach
Use a generic authenticated deep-link design.
The share button copies an absolute URL built from the current origin, the resource key, and the row ID:
```text
{origin}/{resourceKey}/{rowId}
```
Examples:
```text
https://example.local/cases/123
https://example.local/alerts/456
```
The copied link is only a deep link into the existing application. When another user opens it, the current authentication and permission model decides whether the record can be read.
## Components
### Record share URL helper
Add a small frontend helper, for example `frontend/src/utils/recordShare.ts`.
Responsibilities:
- Maintain the allow-list of resource keys that have detail routes.
- Build a path from `{ resourceKey, rowId }`.
- Build an absolute URL from `window.location.origin`.
- Encode `rowId` safely for URL path usage.
Keeping URL construction outside `RecordDetailModal` prevents routing details from leaking into the modal and makes future URL shape changes localized.
### `RecordDetailModal`
Add a Share action to the existing modal header.
Responsibilities:
- Show the action only when `rowId` is available and the resource is shareable.
- Copy the generated absolute URL to the clipboard.
- Show a success or failure message using the existing Ant Design message pattern.
- Leave the current address bar unchanged when the modal was opened from a list page.
This makes sharing work for both list-opened modals and direct-route modals.
### Protected route redirect
Update `ProtectedRoute` in `App.tsx` so unauthenticated users are redirected to login with the requested location:
```text
/login?next={encoded current pathname + search + hash}
```
The login page should validate `next` before navigating. Only same-origin relative paths that start with `/` should be accepted. Invalid values fall back to `/`.
### Login page
After a successful login, `Login.tsx` should navigate to the validated `next` path instead of always navigating to `/`.
### Axios 401 interceptor
When an API response returns 401, the interceptor should clear auth state and redirect to:
```text
/login?next={encoded current pathname + search + hash}
```
This preserves the current deep link when a token expires while the user is already on a record URL.
## Data flow
### Copying a link
1. User opens a record detail modal.
2. User clicks Share.
3. The modal calls the share URL helper with `config.key` and `rowId`.
4. The helper returns an absolute URL such as `https://host/cases/123`.
5. The modal writes the URL to the clipboard and shows a confirmation.
### Opening a shared link while authenticated
1. Browser opens `/cases/123`.
2. `App.tsx` matches the `cases/:rowId` route.
3. `ProtectedRoute` allows access because a token is present.
4. `ResourceDetailRoute` loads the `cases` resource config.
5. `RecordDetailModal` opens and fetches `/api/cases/123/`.
6. The Basic tab is displayed.
### Opening a shared link while unauthenticated
1. Browser opens `/cases/123`.
2. `ProtectedRoute` redirects to `/login?next=%2Fcases%2F123`.
3. User logs in.
4. `Login.tsx` validates and navigates to `/cases/123`.
5. The existing authenticated shared-link flow runs.
## Error handling
The first version should reuse the existing detail-loading behavior where possible.
- Missing record: keep the current 404 behavior that warns the user and closes the modal.
- Unauthorized or insufficient permission: show a clear permission message, then close the modal or leave the normal empty state.
- Clipboard failure: show an error message and do not change application state.
- Invalid `next`: ignore it and navigate to `/`.
The design must not silently grant access or bypass backend permissions.
## Complexity assessment
Overall complexity is low to medium.
Low-complexity parts:
- Existing detail routes already support direct record URLs.
- Existing `ResourceDetailRoute` already adapts route params into `RecordDetailModal`.
- Existing backend detail APIs already enforce authentication.
- No database migration is required.
Medium-risk parts:
- Login redirect handling must avoid open redirects.
- The 401 interceptor must preserve useful deep links without causing redirect loops.
- The share helper must only generate routes for resources that are actually routable.
## Validation
Manual validation should cover:
1. An authenticated user opens `/cases/{id}` and sees the detail modal.
2. An unauthenticated user opens `/cases/{id}`, logs in, and returns to the same record.
3. A user opens a record from a list, clicks Share, and the copied URL opens the same record in a new tab.
4. A token-expired user on a record URL is redirected to login and then back to that record.
5. Unsupported resource keys do not produce share URLs.
Frontend build validation is not required unless explicitly requested.
@@ -1,155 +0,0 @@
# v0.4.0 Release Design
## Context
The project is preparing release `v0.4.0` with the title:
```text
I always have a choice
```
The current repository already contains release automation:
- `.github/workflows/release.yml` listens for pushed tags matching `v*`.
- `.github/workflows/docker.yml` builds and pushes backend and frontend images to GHCR.
- `deploy/package-asp-compose.sh` creates the downloadable Docker Compose package.
- `deploy/release-docs.json` maps release versions to public documentation slugs.
The documentation repository is stored as the `asf-doc` submodule. Current docs contain a partial Chinese `0.4.0 - Less Is More` draft and navigation entries pointing to `0_4_0_Less_Is_More`. The English release page for `0.4.0` does not exist yet.
The current local branch is `dev` tracking `origin/dev`. The main repository has an unrelated dirty `TODO.md`; release work must not include it.
## Goals
1. Prepare release notes for `0.4.0 - I always have a choice`.
2. Keep Chinese and English release documentation structurally consistent.
3. Update release documentation slug references so GitHub Release links point to the correct page.
4. Use the existing `v*` release workflow without expanding the automation scope.
5. Build the final release from a tag named `v0.4.0`.
6. Avoid including unrelated local changes.
## Non-goals
1. Do not redesign the release workflow.
2. Do not add support for unprefixed release tags.
3. Do not introduce a new changelog generator.
4. Do not run the VitePress docs build unless explicitly requested.
5. Do not force-push or rewrite an already-pushed release tag without a separate decision.
## Release materials
### Documentation repository (`asf-doc`)
Rename the current `0.4.0` release page slug from:
```text
0_4_0_Less_Is_More
```
to:
```text
0_4_0_I_always_have_a_choice
```
Required documentation updates:
- `docs/zh/release/0_4_0_I_always_have_a_choice/index.md`
- `docs/en/release/0_4_0_I_always_have_a_choice/index.md`
- `docs/.vitepress/config/zh.ts`
- `docs/.vitepress/config/en.ts`
The Chinese release page should be drafted first, then the English page should mirror the same structure and content.
### Main repository
Update `deploy/release-docs.json` so version `0.4.0` maps to the new slug:
```json
"0.4.0": "0_4_0_I_always_have_a_choice"
```
After the `asf-doc` release documentation is committed, update and commit the submodule pointer in the main repository.
## Release notes content
Release notes should be derived from commits between `0.3.0` and the final release commit.
Use these sections:
1. **New Features**
- Custom Console and runtime custom-definition management.
- Tags preview/settings support.
- Authenticated record share links.
- Dashboard and workspace experience improvements.
2. **Improvements**
- Inbox notification and resource-label refinements.
- Runtime settings cleanup and naming consistency.
- User-management safety improvements.
- Activity feed pagination and detail-view usability improvements.
3. **Deployment and Release Engineering**
- Docker Compose package improvements.
- CI and release workflow readiness.
- GHCR image and compose package release path.
4. **Developer Notes**
- Explain the theme behind "I always have a choice": ASP should make platform behavior configurable and extensible without locking users into a single workflow.
- Describe the move toward low-cost customization through Custom Console, custom module/playbook definitions, SIEM YAML, and runtime refresh/validation.
- Describe why authenticated deep links and visual tag previews improve day-to-day analyst workflows.
## Release automation
Keep `.github/workflows/release.yml` unchanged.
The release is triggered by pushing an annotated tag:
```text
v0.4.0
```
The release workflow will:
1. Parse `version=0.4.0` from the tag.
2. Read the release docs slug from `deploy/release-docs.json`.
3. Build and push:
- `ghcr.io/<owner>/<repo>/asp-backend:0.4.0`
- `ghcr.io/<owner>/<repo>/asp-frontend:0.4.0`
- `latest` tags for both images.
4. Build `asp-compose-0.4.0.tar.gz`.
5. Create the GitHub Release with the compose archive and release notes link.
The previous `0.3.0` tag is unprefixed, but this release should use `v0.4.0` because the existing workflow only listens for `v*`.
## Validation plan
Before pushing the release tag:
1. Confirm `v0.4.0` does not already exist locally or remotely.
2. Confirm `deploy/release-docs.json` resolves `0.4.0` to the new slug.
3. Confirm both Chinese and English release pages exist at the mapped slug.
4. Confirm the docs navigation points to the new title and slug.
5. Confirm `asf-doc` is committed and the main repository submodule pointer is committed.
6. Confirm the main repository has no unexpected dirty files included in release commits.
7. Run a compose package validation using the existing package script and archive checks.
Do not run the VitePress docs build unless explicitly requested.
## Publish sequence
1. Commit the `asf-doc` release documentation changes.
2. Commit the main repository release mapping and submodule pointer changes.
3. Push the `asf-doc` commit.
4. Push the main repository release-preparation commit.
5. Create annotated tag `v0.4.0` with message `v0.4.0 - I always have a choice`.
6. Push tag `v0.4.0` to trigger the Release workflow.
7. Watch the Release workflow until it succeeds or fails.
## Failure handling
- If validation fails before the tag is pushed, fix the release-preparation commits before creating the tag.
- If the Release workflow fails after the tag is pushed, inspect the failed job logs first.
- Prefer rerunning the failed workflow after fixing transient infrastructure issues.
- If the tag points to the wrong commit or release inputs are wrong, pause and decide whether to delete and recreate the tag. Do not force-rewrite the release tag silently.
- If GitHub Release creation succeeds but docs are wrong, fix docs in `asf-doc` and update the release body manually or through a follow-up automation change.
@@ -1,225 +0,0 @@
# 用户表格偏好后端存储设计
## 背景
当前前端 `DataTable` 将用户表格偏好保存在浏览器 `localStorage`
- `asp:<tableKey>:savedFilters`:高级筛选弹窗中保存的筛选方案。
- `asp:<tableKey>:columnSettings`:列显示状态和列顺序。
- `asp:<tableKey>:pageSize`:每页行数。
这些配置只存在单个浏览器中,换设备、换浏览器或清理缓存后都会丢失。本次目标是将这些用户偏好改为后端持久化,并让 Saved filters 支持私有和共享两种可见性。
## 决策
采用专用后端资源保存表格偏好:
- 表格级用户偏好单独建模,按 `user + table_key` 保存 `page_size``column_settings`
- Saved filters 单独建模,支持 `private``shared` 可见性。
- 共享 Saved filters 对所有登录用户可见,但只有创建者或管理员可以修改和删除。
- 不迁移旧的 `localStorage` 数据。上线后后端无配置时使用默认表格配置。
## 目标
- 用户的列显示、列顺序、pageSize 存在后端,并按用户和表格隔离。
- Saved filters 存在后端,私有筛选只对创建者可见。
- 共享 Saved filters 对所有登录用户可见。
- 共享 Saved filters 只有创建者或管理员可更新和删除。
- 前端不再读取或写入旧的表格偏好 `localStorage` key。
- 后端接口失败时不影响表格主数据查询。
## 非目标
- 不迁移浏览器中已有的 `localStorage` 表格配置。
- 不恢复当前未保存的搜索、快速筛选、排序、当前页码。
- 不做团队、角色、项目级筛选共享范围。
- 不新增筛选模板市场、收藏、复制等扩展能力。
- 不把列设置做成共享配置。
## 后端设计
### 模块位置
新增 `apps.preferences` 模块承载表格偏好资源。该模块只依赖当前登录用户和 DRF 权限,不嵌入 `User` 的 profile JSON,避免让账号模型继续承担 UI 偏好存储职责。
### `UserTablePreference`
字段:
- `user`:外键到当前用户。
- `table_key`:前端传入的表格标识。
- `page_size`:每页行数,允许空;为空时前端使用默认值 20。
- `column_settings`JSON,结构为 `{ "visible": string[], "order": string[] }`
- `created_at``updated_at`
约束:
- `user + table_key` 唯一。
- `table_key` 必填并限制最大长度。
- `column_settings.visible``column_settings.order` 必须是字符串数组。
- `page_size` 只允许现有前端支持的值:20、50、100。
### `SavedTableFilter`
字段:
- `owner`:创建用户。
- `table_key`:适用的表格标识。
- `name`:筛选名称。
- `state`JSON,沿用前端 `SavedTableFilter.state`,当前只保存 `{ "quick": {}, "advanced": [...] }`
- `visibility``private``shared`
- `created_at``updated_at`
约束:
- `table_key``name` 必填并限制最大长度。
- `visibility` 只能是 `private``shared`
- `state.quick` 必须是对象,`state.advanced` 必须是数组。
- 同一 owner、同一 table_key 下不强制唯一名称,避免误伤现有前端“Save as”行为。
### 权限
- 所有接口都要求登录。
- `UserTablePreference` 只能读取和修改当前用户自己的记录。
- 私有 Saved filters 只有 owner 可见、可改、可删。
- 共享 Saved filters 所有登录用户可见。
- 共享 Saved filters 只有 owner 或 admin 可改、可删。
- 创建共享 Saved filter 不限制普通用户。
## API 设计
### 表格偏好
`GET /api/user-table-preferences/<table_key>/`
返回当前用户在该表格的偏好。没有记录时返回默认空配置:
```json
{
"table_key": "cases",
"page_size": null,
"column_settings": null
}
```
`PATCH /api/user-table-preferences/<table_key>/`
局部更新当前用户在该表格的偏好。请求可以只包含其中一个字段:
```json
{
"page_size": 50,
"column_settings": {
"visible": ["id", "title", "status"],
"order": ["id", "title", "status", "severity"]
}
}
```
### Saved filters
`GET /api/saved-table-filters/?table_key=<table_key>`
返回当前用户可见的筛选方案:自己的私有筛选和全部共享筛选。
`POST /api/saved-table-filters/`
创建私有或共享筛选:
```json
{
"table_key": "cases",
"name": "High severity open cases",
"visibility": "shared",
"state": {
"quick": {},
"advanced": []
}
}
```
`PATCH /api/saved-table-filters/<id>/`
更新名称、状态或可见性。权限按 owner/admin 校验。
`DELETE /api/saved-table-filters/<id>/`
删除筛选。权限按 owner/admin 校验。
## 前端设计
### `DataTable`
`DataTable` 挂载时根据 `resolvedTableKey` 加载后端偏好:
- 后端返回 `page_size` 时作为初始 pageSize,否则使用 20。
- 后端返回有效 `column_settings` 时按现有 `readColumnSettings` 的归一化逻辑处理:过滤不存在的列、补齐新列、保留 locked columns。
- 后端没有偏好或加载失败时使用默认列配置和默认 pageSize。
用户修改列显示、列顺序、pageSize 时,前端调用偏好接口保存。保存失败时保留当前界面状态,但提示用户保存失败,避免误以为配置已经跨设备同步。
### `TableFilterModal`
弹窗打开时通过 `GET /api/saved-table-filters/?table_key=...` 加载 Saved filters,不再读取 `localStorage`
操作映射:
- `Save as``POST /api/saved-table-filters/`
- `Update``PATCH /api/saved-table-filters/<id>/`
- `Delete``DELETE /api/saved-table-filters/<id>/`
- `Load`:只更新当前弹窗草稿,不立即修改后端。
- `Search`:应用当前草稿到表格状态,不自动保存。
Saved filters 列表需要显示私有/共享状态。共享筛选如果当前用户不是 owner 且不是 admin,更新和删除按钮应禁用或隐藏。
### 旧 `localStorage`
实现后不再读取和写入以下 key
- `asp:<tableKey>:savedFilters`
- `asp:<tableKey>:columnSettings`
- `asp:<tableKey>:columns`
- `asp:<tableKey>:pageSize`
旧 key 不需要主动清理。
## 错误处理
- 表格偏好加载失败:表格继续使用默认配置,提示一次加载失败。
- 表格偏好保存失败:保留当前 UI 状态,提示保存失败。
- Saved filters 加载失败:弹窗显示空列表,并提示加载失败。
- Saved filters 创建、更新、删除失败:不更新本地列表,提示对应操作失败。
- 后端校验失败返回 400,权限失败返回 403,不存在返回 404。
## 数据迁移
新增数据库迁移:
- 创建 `user_table_preferences` 表。
- 创建 `saved_table_filters` 表。
- 添加必要索引:
- `user_table_preferences(user_id, table_key)` 唯一索引。
- `saved_table_filters(table_key, visibility)` 查询索引。
- `saved_table_filters(owner_id, table_key)` 查询索引。
不需要从浏览器 `localStorage` 回填数据。
## 验证标准
后端:
- 迁移文件生成并可应用。
- 用户只能读取和修改自己的表格偏好。
- 私有 Saved filter 只对 owner 可见。
- 共享 Saved filter 对其他登录用户可见。
- 共享 Saved filter 只有 owner 或 admin 可修改和删除。
- 非法 `page_size`、非法 `column_settings`、非法 `state` 返回 400。
前端:
- 新用户进入表格时使用默认列配置和 pageSize 20。
- 修改列显示、列顺序、pageSize 后刷新页面仍保持配置。
- 同一用户换浏览器后能看到后端保存的配置。
- 不同用户的列设置和 pageSize 互不影响。
- 私有 Saved filter 只在创建用户下可见。
- 共享 Saved filter 在其他用户下可见,但非 owner 非 admin 不能更新或删除。
@@ -1,177 +0,0 @@
# WebSocket Message and Inbox Design
## Context
The current Inbox and comment flows use REST APIs for writes and cursor-based reads. `InboxDrawer` also polls unread count every 60 seconds. This design replaces polling with a WebSocket realtime channel while keeping REST as the durable source of truth for creation, pagination, manual refresh, and reconnect recovery.
The scope covers both:
- Inbox user/system messages, including unread count, new messages, replies, deletes, mark-read, and mark-all-read state.
- Resource comments in `DiscussionThread`, including new comments and deleted comments for the currently viewed record.
## Chosen approach
Use Django Channels with `channels-redis`.
Reasons:
- The backend already runs an ASGI service and uses Redis.
- Channels keeps authentication, ORM access, serializers, permissions, and transaction handling inside the Django ecosystem.
- User-level Inbox groups and record-level comment groups map naturally to Channels groups.
- REST endpoints can remain stable, reducing migration risk.
Rejected alternatives:
- Handwritten Starlette WebSocket handling would avoid dependencies but would duplicate JWT auth, connection management, group subscription, and Django permission integration.
- SSE would be simpler for one-way events but is not the requested WebSocket direction and is less flexible for dynamic subscribe/unsubscribe.
## Architecture
Add a dedicated realtime module, such as `apps.realtime`, responsible for:
- WebSocket consumers.
- Event type definitions and payload shape.
- Broadcast helpers used by Inbox and Comments.
- Subscription permission checks.
`apps.inbox` and `apps.comments` remain owners of their domain writes. They emit realtime events only after successful database commits. They do not manage socket connections directly.
Expose a single WebSocket endpoint:
```text
/ws/events/
```
ASGI should route `/ws/events/` to Channels while preserving the existing Django HTTP app and the existing `/api/mcp` ASGI mount.
Redis is used as the Channels channel layer through the existing Redis configuration.
## Groups and subscriptions
Each authenticated connection automatically joins its Inbox group:
```text
inbox.user.<user_id>
```
The frontend can subscribe and unsubscribe to resource comment groups as the user opens or leaves a record detail view:
```text
comments.<content_type>.<object_id>
```
Inbox events are only sent to the affected user's group. Comment events are sent only to the matching resource group.
WebSocket messages are not used for domain writes. Creating comments, sending messages, deleting messages, and marking messages read continue to use existing REST endpoints.
## Event envelope
All server-to-client events use a common envelope:
```json
{
"type": "inbox.message_created",
"event_id": "uuid-or-stable-id",
"occurred_at": "2026-06-29T03:13:11Z",
"actor_id": 1,
"payload": {}
}
```
`event_id` lets the frontend deduplicate events. `occurred_at` helps decide whether reconnect recovery needs a REST refresh. `payload` contains a typed body for each event.
## Inbox events
The Inbox realtime channel supports:
- `inbox.message_created`: carries a complete serialized `InboxMessage`.
- `inbox.message_deleted`: carries `message_id`.
- `inbox.message_read`: carries `message_id` and `read_at`.
- `inbox.all_read`: carries `read_at` and enough state for the frontend to mark loaded items as read.
- `inbox.unread_count_changed`: carries the latest unread count and is the authoritative badge value.
The frontend should still call `fetchInboxUnreadCount()` once after login or reconnect. After that, `inbox.unread_count_changed` updates the badge. When the drawer is open, message events update the loaded list in place. Manual refresh remains available.
## Comment events
The comment realtime channel supports:
- `comment.created`: carries a complete serialized `RecordComment`.
- `comment.deleted`: carries `comment_id`.
`DiscussionThread` continues to use REST for initial load, cursor pagination, and manual refresh. While mounted, it subscribes to its current `content_type` and `object_id`; on unmount or record switch, it unsubscribes.
If search is active, the component still applies local filtering to the current loaded list after realtime updates.
## Consistency and transactions
Broadcasts must be scheduled with `transaction.on_commit()` so clients only receive events after the corresponding database state is committed.
For events that include serialized objects, serialization should happen after commit or from a freshly loaded object so payloads match what REST readers can retrieve.
The client treats REST as the source of truth. WebSocket updates are incremental hints that keep already-loaded UI state fresh.
## Authentication and reconnect behavior
The frontend authenticates the WebSocket connection with the current JWT. Because browser WebSocket APIs cannot set arbitrary headers, the implementation can use either a query token or a supported subprotocol-based token exchange. The design does not require HTTPS/WSS as a functional prerequisite, though production deployments may still choose WSS.
If authentication fails, the server closes the socket with a clear close code. The frontend should surface login expiration through existing auth behavior.
Reconnect behavior is hybrid:
- Initial data and pagination remain REST-based.
- While disconnected, UI keeps existing data and can show a non-blocking realtime connection warning.
- On reconnect, the frontend refreshes unread count and any currently open Inbox/comment view via REST, then resumes incremental events.
- Event handlers deduplicate by `event_id` and entity id to avoid double-inserting the current user's own writes.
## Frontend integration
Add a global realtime connection layer, for example `RealtimeProvider` plus `useRealtime`.
Responsibilities:
- Connect after login and disconnect on logout.
- Maintain connection status.
- Send heartbeat or respond to server ping if needed.
- Reconnect with backoff.
- Dispatch typed events to subscribers.
- Track comment subscriptions for mounted `DiscussionThread` instances.
`InboxDrawer` changes:
- Remove the 60-second unread polling timer.
- Load unread count once after login/reconnect.
- Use realtime unread-count events for the badge.
- Use realtime message events to update currently loaded rows.
- Keep manual refresh and REST pagination.
`DiscussionThread` changes:
- Subscribe to the current record group while mounted.
- Apply `comment.created` and `comment.deleted` events to loaded comments.
- Keep REST for create/delete, initial load, pagination, search, and manual refresh.
## Deployment and documentation
Add the Channels dependencies and route `/ws/events/` to the ASGI service. Deployment documentation only needs to mention that `/ws/` must be proxied to the ASGI service; it does not need to describe the internal Channels or Redis details.
No database migration is expected because the design does not require new model fields.
## Validation plan
Backend validation:
- WebSocket rejects unauthenticated or invalid JWT connections.
- Authenticated connections join only their user Inbox group.
- Comment subscribe/unsubscribe targets the requested record group.
- Inbox create, reply, delete, mark-read, and mark-all-read emit events after commit.
- Comment create and delete emit events only to the matching record group.
Frontend validation:
- Inbox badge updates without polling.
- Open Inbox list receives new, deleted, and read-state updates.
- Comment thread receives new and deleted comments for the subscribed record only.
- Reconnect triggers REST refresh for unread count and currently open data.
- Duplicate events do not create duplicate rows.
@@ -1,368 +0,0 @@
# MCP 评论附件访问设计
## 背景
当前系统已经有评论和附件能力:
- `Comment` 通过 `content_type + object_id` 关联任意业务记录。
- `Comment.attachments` 通过多对多关系关联 `Attachment`
- `Attachment` 使用 `access_key` 作为不可猜测的公开下载 key,并通过现有下载端点提供文件下载。
- REST 评论接口支持正文或附件至少一个;创建评论时通过 `attachment_ids` 关联已上传附件。
- REST 附件上传接口使用 multipart/form-data;全局 DRF 认证已经支持 `Authorization: Api-Key <key>`,因此 API key 用户可以通过 HTTP multipart 上传附件。
当前 MCP 侧存在缺口:
- `add_comment` 只能写入正文,不能关联附件、回复父评论或提及用户。
- MCP 的 `serialize_comment` 只返回 `id/body/author/created_at`,不会返回附件元数据。
- MCP 没有单独的 `list_comments` 工具;当前只有 `list_cases(include_related=True)` 会隐式返回 case comments,且其他可评论资源无法通过 MCP 读回评论。
- 文件内容不应默认进入 MCP tool 响应或模型上下文,否则会造成大文件 token 成本、二进制损坏和敏感内容扩散风险。
## 目标
- MCP 用户可以在现有记录查询工具中显式获取评论元数据。
- MCP 评论元数据包含附件列表,附件列表提供统一的 `file_key`、文件名、大小、内容类型和下载 URL。
- MCP 用户可以通过统一的 `get_file(file_key)` 获取文件下载信息。
- MCP 用户可以通过 `add_comment` 添加带附件、回复父评论、提及用户的评论。
- MCP 不通过 tool 参数上传文件内容,避免大文件进入模型上下文。
- 自定义 playbook 不新增封装;用户可继续用 Django ORM 和现有模型/服务直接处理评论附件。
## 非目标
- 不新增通用 MCP `list_comments` 工具。
- 不让 MCP `get_file` 默认返回文件内容、base64 或文本。
- 不新增 MCP base64 文件上传工具。
- 不改变附件下载端点的公开访问模型。
- 不改变 `/api/attachments/` 的上传权限。
- 不为自定义 playbook 增加 `BasePlaybook` helper、SDK 或文档。
- 不新增 enrichment 的 MCP 查询工具;enrichment comments 本次不通过 MCP 读取。
- 不新增数据库模型或字段。
## 选定方案
采用“现有记录查询工具显式包含评论 + HTTP multipart 上传 + `access_key` 作为统一 `file_key`”。
理由:
- `Attachment` 已经是统一文件存储表,未来 case、alert 等业务文件字段也应引用 `Attachment`,不需要再设计额外 namespace key。
- `access_key` 已经是当前公开下载机制的稳定外部 key,可直接作为 MCP `file_key`
- HTTP multipart 上传让文件字节绕开 LLM 和 MCP tool 参数;tool 调用只传小体积元数据和 `file_key`
- 评论读取挂到现有 `list_*`/search 工具,符合“不新增单独 list_comments”的方向。
- 文件内容默认不返回,符合大文件和任意文件类型场景。
## 备选方案和取舍
### 新增 `list_comments(target_id)`
优点是评论读取入口统一,能覆盖所有 `add_comment` 支持的 target,包括 enrichment。缺点是增加新的 MCP 工具,与“通过对应 list 获取评论”的现有使用方式不一致。本次不采用。
### MCP tool 直接上传 base64 文件
优点是所有动作都发生在 MCP 内。缺点是文件内容会进入 tool 参数和上下文,大文件成本高,二进制文件也容易被错误处理。本次不采用。
### `get_file` 返回 inline base64/text
优点是模型可直接读取小文件。缺点是默认行为容易把大文件或敏感文件带入上下文,也不适合任意文件类型。本次不采用。后续如果有明确需求,可单独设计受大小限制的 `read_text_file(file_key, max_bytes=...)`
### 为 playbook 增加 helper
优点是降低自定义 playbook 作者理解 ContentType 和 Attachment ORM 的门槛。缺点是当前 playbook 本来就是后端 Python 代码,已能直接使用 Django ORM、`Attachment.file.open()``create_record_comment()`,新增封装不是必要条件。本次不采用。
## 数据标识
`file_key` 是 MCP 对外暴露的统一文件引用字段。
本次规定:
```text
file_key = str(Attachment.access_key)
```
MCP 响应不暴露 `Attachment.id`。调用方不应依赖数据库 id。
未来如果 case、alert、artifact 等资源增加文件字段,这些字段也应引用 `Attachment`,并继续返回同样的 `file_key`
## MCP 评论读取设计
### 支持工具
以下现有 MCP 工具新增 `include_comments``comments_limit` 参数:
- `list_cases`
- `list_alerts`
- `list_artifacts`
- `list_playbooks`
- `search_knowledge`
`create_enrichment` 不变。由于 MCP 当前没有 `list_enrichments``get_enrichment`enrichment comments 本次不提供 MCP 读取入口。
### 参数行为
```text
include_comments: bool = false
comments_limit: int = 20
```
规则:
- comments 只由 `include_comments` 控制。
- `include_related` 不再隐式返回 comments,包括 case。
- `include_comments=false` 时响应不包含 `comments` 字段。
- `comments_limit` 默认 20,最大 50,最小 1。
- 每个记录取最新 N 条评论,再按创建时间正序返回。
这会改变现有 `list_cases(include_related=True)` 的隐式 comments 行为。新行为更明确,也避免默认返回过多评论和附件元数据。
### Comment 响应字段
MCP comment 使用精简字段:
```json
{
"id": 123,
"body": "Please review the attached evidence.",
"author": "alice",
"created_at": "2026-07-01T12:00:00+00:00",
"updated_at": "2026-07-01T12:00:00+00:00",
"parent_id": null,
"attachments": [
{
"file_key": "6f2c5d7e-31c6-4f48-9e3c-6d9b5f92c457",
"filename": "evidence.zip",
"size": 1048576,
"content_type": "application/zip",
"download_url": "https://asp.example.com/api/attachments/6f2c5d7e-31c6-4f48-9e3c-6d9b5f92c457/download/"
}
]
}
```
不包含 REST UI 字段,例如 `can_delete`、avatar、`mentioned_users``parent_body`
### Attachment 响应字段
附件元数据只返回外部字段:
- `file_key``Attachment.access_key` 字符串。
- `filename`:原始文件名。
- `size`:文件大小,单位 bytes。
- `content_type`:根据文件名推断的 MIME type;无法推断时使用 `application/octet-stream`
- `download_url`:现有公开下载 URL。
`download_url` 优先返回绝对 URL。实现应从当前 MCP HTTP 请求的 scheme 和 host 推断 base URL;如果无法推断,则回退到相对路径 `/api/attachments/<access_key>/download/`
## MCP 文件工具设计
新增 MCP 工具:
```text
get_file(file_key)
```
用途是获取文件下载信息,而不是读取文件内容。
MCP 服务运行在后端,不能可靠地直接把文件写入用户客户端本地磁盘。`get_file(file_key)` 的“一次调用”目标是返回可直接用于浏览器、curl 或客户端脚本下载的 URL;真正的文件字节通过普通 HTTP 下载,不进入 MCP tool 返回值。
返回:
```json
{
"file_key": "6f2c5d7e-31c6-4f48-9e3c-6d9b5f92c457",
"filename": "evidence.zip",
"size": 1048576,
"content_type": "application/zip",
"download_url": "https://asp.example.com/api/attachments/6f2c5d7e-31c6-4f48-9e3c-6d9b5f92c457/download/"
}
```
行为:
- `file_key` 必须匹配现有 `Attachment.access_key`
- 文件不存在时返回 MCP tool 错误。
- 不返回 `content``base64``text` 或文件 bytes。
- 下载继续复用现有 public access_key 下载端点,不要求额外认证。
## MCP 添加评论设计
扩展现有 MCP `add_comment`
```text
add_comment(
target_id,
body="",
file_keys=None,
parent_id=None,
mentions=None,
ctx=None
)
```
### Target
`target_id` 沿用现有规则:
- `case_...`
- `alert_...`
- `artifact_...`
- `enrichment_...`
- `knowledge_...`
- `playbook_...`
找不到 target 时返回 MCP tool 错误。
### Body 和附件校验
`body``file_keys` 至少提供一个:
- 支持纯正文评论。
- 支持纯附件评论。
- 支持正文 + 附件评论。
如果 `body` 为空且 `file_keys` 为空,返回错误。
### File keys
`file_keys` 是一个或多个 `Attachment.access_key` 字符串。
输入可支持列表,也可兼容现有 MCP 参数风格中的 JSON 数组字符串或逗号分隔字符串。每个 key 必须能找到对应 `Attachment`,否则整体失败并返回错误。
权限规则:
- 任何有效 `access_key` 都可被关联到评论。
- 不要求附件由当前用户上传。
这与现有 REST 评论创建的宽松模型保持一致:REST 通过 `attachment_ids` 关联附件时也不校验上传者归属。
### Parent
`parent_id` 可选。
如果提供:
- 必须找到对应 `Comment`
- 父评论必须属于同一个 `target_id` 对应的 `content_type + object_id`
- 不允许跨记录回复。
### Mentions
`mentions` 可选,表示被提及用户。
输入规则:
- 以 username 为主要格式。
- 兼容数字用户 id。
- 可支持列表、JSON 数组字符串或逗号分隔字符串。
- 任意 mention 无法解析时,整体失败并返回错误。
- 不静默忽略无效 mention。
### 写权限
MCP `add_comment` 写权限对齐 REST
- 只有 admin/user 角色可以创建评论。
- viewer 不能通过 MCP 创建评论或关联附件。
- 未认证或 API key 无效仍按现有 MCP 认证失败处理。
实现上应复用现有业务角色判断,例如 `is_business_writer(user)`
`add_comment` 成功后返回同一套 MCP comment 精简结构,包括 `attachments` 元数据;不返回附件内容。
## 文件上传流程
MCP 不提供上传文件内容的 tool。
推荐流程:
1. 客户端或用户脚本使用 HTTP multipart 上传文件:
```text
POST /api/attachments/
Authorization: Api-Key <key>
Content-Type: multipart/form-data
```
2. 上传响应中拿到 `access_key`
3. 将该 `access_key` 作为 MCP `file_key` 传给 `add_comment(file_keys=[...])`
4. 后续通过 `get_file(file_key)` 或评论附件元数据获取下载 URL。
`/api/attachments/` 上传权限保持现状:已认证用户可以上传。是否能把附件挂到评论由 `add_comment` 的写权限控制。
## 自定义 playbook 设计
本次不改自定义 playbook 接口。
用户在 playbook 中已经可以用 Python 代码直接实现:
- 用 `ContentType.objects.get_for_model(record)``Comment.objects.filter(...)` 查询记录评论。
- 用 `comment.attachments.all()` 获取附件列表。
- 用 `attachment.file.open("rb")` 读取任意文件类型的 bytes。
- 用 `create_record_comment(..., attachments=[...])` 创建带附件评论。
因此不新增 `BasePlaybook` 方法、不新增 service functions、不新增 playbook 文档。
## 错误处理
- 无效 `target_id`:返回明确错误,说明支持的前缀。
- 记录不存在:返回 `Record not found` 风格错误。
- 无效 `file_key`:返回文件不存在或无效 file key 错误。
- 无效 `parent_id`:返回父评论不存在或不属于同一 target 的错误。
- 无效 mention:返回无法解析的 username/id。
- viewer 调用 `add_comment`:返回权限错误。
- `get_file` 找不到附件:返回 MCP tool 错误。
- 下载 URL 指向的文件对象不存在时,现有下载端点继续返回 404。
不添加宽泛 try/except 或静默跳过无效输入;错误应显式暴露给 MCP 调用方。
## 安全和隐私
- 文件内容不会默认进入 MCP tool 响应。
- public download URL 继续依赖不可猜测 `access_key`,符合当前系统行为。
- MCP 写评论对齐 REST 角色权限,避免 viewer 通过 MCP 绕过 UI/REST 写权限。
- `download_url` 可能被模型上下文或日志保存;这是选择复用现有 public access_key URL 的已接受风险。
- 关联附件时不做上传者归属限制,保持与现有 REST 行为一致。
## 后端实现边界
预期改动集中在:
- `apps.mcp.serializers`
- 增加 attachment 元数据序列化。
- 扩展 comment 序列化。
- 支持按记录加载 comments,并应用 `comments_limit` 和排序规则。
- 支持根据 MCP request 构造绝对下载 URL,失败回退相对路径。
- `apps.mcp.tools`
- 新增 `get_file(file_key)`
- 为 `list_cases/list_alerts/list_artifacts/list_playbooks/search_knowledge` 增加 `include_comments/comments_limit`
- 扩展 `add_comment` 参数和校验。
- 增加 writer 权限校验。
- 注册新增 MCP tool。
不需要数据库迁移。
## 兼容性
- REST 评论和附件接口保持兼容。
- REST 附件下载 URL 保持现状。
- MCP `add_comment(target_id, body)` 继续可用。
- MCP `list_cases(include_related=True)` 不再因为 `include_related` 隐式返回 comments;调用方需要显式传 `include_comments=true`
- MCP comment 返回字段会新增 `updated_at``parent_id``attachments`
## 验证标准
后端测试应覆盖:
- `get_file` 对有效 `file_key` 返回文件元数据和下载 URL。
- `get_file` 对无效 `file_key` 返回错误。
- comment 序列化返回附件外部字段,不返回数据库附件 id。
- `include_comments=false` 时各工具不返回 comments。
- `include_comments=true` 时各工具返回最新 N 条 comments,并按时间正序展示。
- `comments_limit` 默认 20,最大 50。
- `list_cases(include_related=True, include_comments=false)` 不返回 comments。
- `add_comment` 支持正文评论、纯附件评论、正文 + 附件评论。
- `add_comment` 在 body 和 file_keys 都为空时报错。
- `add_comment(file_keys=...)` 能通过 `access_key` 找到并关联附件。
- `add_comment` 对无效 file_key 报错且不创建评论。
- `add_comment(parent_id=...)` 只允许同一 target 的父评论。
- `add_comment(mentions=...)` 支持 username 和数字 id;无效 mention 报错。
- viewer API key 调用 `add_comment` 被拒绝。
- admin/user API key 调用 `add_comment` 成功。
- `/api/attachments/` 继续支持 `Authorization: Api-Key <key>` multipart 上传。
@@ -1,496 +0,0 @@
# ASP CLI Agent 集成架构设计
## 状态
已确认。
## 背景
ASP 当前通过后端 MCP endpoint 向 Claude Code plugin 的 agents 和 skills 暴露能力。MCP 的优点是工具签名、参数和调用协议由框架处理,接入成本低;主要问题是工具列表和描述会固定进入上下文,缺少命令行天然具备的渐进式 help。ASP 的目标用户是安全工程师和 SOC 分析师,对 CLI 接受度较高,因此新架构将 CLI 作为 Agent 和人类共同使用的主集成面。
现有 `/api/mcp` 先保留兼容窗口。新能力以 CLI 和 Agent Operations API 为主路径,等 CLI 能力、文档和 marketplace skills 稳定后,再将 MCP 标记为 deprecated。
## 目标
- 提供符合主流 Agent/平台 CLI 习惯的 `asp` 命令。
- 通过分层命令和 help 支持渐进式能力发现。
- 首发使用 Python 实现,支持 `pipx install` 和一行 bootstrap 安装。
- 覆盖当前 MCP 暴露的能力,并为后续新增 operation 留出发版期扩展机制。
- 所有命令支持人类可读输出和稳定 JSON 输出。
- 复用成熟 CLI/HTTP/渲染库,避免手写底层框架。
- 保持 CLI 和后端运行时解耦,CLI 可独立安装。
## 非目标
- 不做运行时动态命令发现。服务端新增业务命令后,通过 CLI 发版暴露。
- 不把 CLI 做成后端 Django 管理命令,也不要求 CLI 在后端源码环境运行。
- 不把所有能力压到单一 `operation run``/run` RPC。
- 不按 MCP 函数名设计主命令;MCP 名只作为迁移映射和 alias 记录。
- 不让文件内容默认进入 CLI 输出或 Agent 上下文。
## 选定方案
采用“静态主流 CLI + build-time operation spec + Agent Operations API”。
- CLI 使用 Python Typer + Rich + httpx + Pydantic。
- CLI 业务命令是静态分层命令,随 CLI 发版。
- 后端新增版本化 Agent Operations API,提供适合 CLI/Agent 的稳定 schema。
- 后端维护发版期 operation specCLI 包内携带对应 spec snapshot。
- marketplace 新增 CLI 版 skills,与 MCP 版并行,稳定后切默认入口。
这个方案接近 `gh``docker``terraform` 等主流 CLI 的静态命令树模式,同时保留类似 AWS CLI 的 model/spec-driven 契约管理思想。相比服务端动态命令发现,它牺牲“服务端新增命令无需 CLI 发版”的便利,换取更稳定的 help、completion、测试和安装体验。
## 架构组件
### ASP CLI package
CLI 作为当前 monorepo 中的独立 package 维护,发布为 PyPI 包。
首发安装方式:
```bash
pipx install asp-cli
```
同时提供 PowerShell 和 bash bootstrap 一行命令,用于检测 Python/pipx 并安装 CLI。CLI 包不 import Django app、models 或 serializers;后端依赖不会进入 CLI 安装环境。
CLI 静态内置:
- `auth`
- `config`
- `doctor`
- `completion`
- 各业务命令组
CLI 使用包内 operation spec snapshot 提供命令说明、examples、兼容测试和文档生成输入。
### Backend Agent Operations API
后端新增 `/api/agent/v1/...` API 层,定位是给 Agent/CLI 使用的稳定接口。
API 原则:
- 版本化。
- 领域化。
- 可写 OpenAPI / operation spec。
- 复用现有 service、ORM、permission 和 audit 机制。
- 使用 Agent 专用 serializer/schema,不直接暴露 UI REST 字段。
- 不提供单一 `/run` RPC。
示例 endpoint 形态:
```text
GET /api/agent/v1/version
GET /api/agent/v1/cases/
GET /api/agent/v1/cases/{case_id}/
PATCH /api/agent/v1/cases/{case_id}/ai-analysis/
POST /api/agent/v1/comments/
GET /api/agent/v1/files/{file_key}/
POST /api/agent/v1/files/
POST /api/agent/v1/siem/search/keyword/
POST /api/agent/v1/threat-intel/query/
POST /api/agent/v1/cmdb/lookup/
```
具体 URL 可在实现时细化,但不得退化为 UI REST 的不稳定透传。
### Operation spec
operation spec 是 CLI、Agent API、文档和 skills 的发版期契约源。
每个 operation 至少包含:
- operation id,例如 `case.list`
- CLI path,例如 `case list`
- HTTP method 和 endpoint。
- 参数 schema。
- 输出 schema。
- 权限要求。
- capability 要求。
- examples。
- deprecated aliases,例如旧 MCP 工具名 `list_cases`
- 最低 CLI/API 版本要求。
CLI 不运行时动态拉取业务命令。服务端通过 `/api/agent/v1/version` 返回 `api_version``min_cli_version` 和 capabilitiesCLI 用于兼容检查。CLI 新、服务端旧时,对不支持的 operation 明确报错;服务端要求更高 CLI 时,CLI 直接提示升级。
## 命令树
主命令树:
```text
asp auth login|status|logout
asp config get|set|list
asp doctor
asp completion powershell|bash|zsh
asp case list|show|update-ai
asp alert list|show
asp artifact list|show
asp enrichment create
asp knowledge search|show|update
asp playbook template list
asp playbook list|show|run
asp comment list|add
asp file upload|info|download|read-text
asp siem schema list|show
asp siem search keyword
asp siem query adaptive|spl|esql
asp siem fields discover
asp ti query
asp cmdb lookup
asp dev stream head|read
```
命名规则:
- 安全行业常用短名作为主命令,例如 `siem``ti``cmdb`
- 长名通过 alias 或 help 提供,例如 `threat-intel` alias 到 `ti`
- MCP 函数名不作为主 CLI UX。
### 当前 MCP 能力映射
| MCP 工具 | CLI 命令 |
| --- | --- |
| `list_cases` | `asp case list`, `asp case show` |
| `update_case` | `asp case update-ai` |
| `get_file` | `asp file info`, `asp file download`, `asp file read-text` |
| `add_comment` | `asp comment add` |
| `list_alerts` | `asp alert list`, `asp alert show` |
| `list_artifacts` | `asp artifact list`, `asp artifact show` |
| `create_enrichment` | `asp enrichment create` |
| `list_playbook_templates` | `asp playbook template list` |
| `execute_playbook` | `asp playbook run` |
| `list_playbooks` | `asp playbook list`, `asp playbook show` |
| `update_knowledge` | `asp knowledge update` |
| `search_knowledge` | `asp knowledge search` |
| `read_stream_message_by_id` | `asp dev stream read` |
| `read_stream_head` | `asp dev stream head` |
| `ti_query` | `asp ti query` |
| `cmdb_lookup` | `asp cmdb lookup` |
| `siem_explore_schema` | `asp siem schema list`, `asp siem schema show` |
| `siem_keyword_search` | `asp siem search keyword` |
| `siem_adaptive_query` | `asp siem query adaptive` |
| `siem_discover_index_fields` | `asp siem fields discover` |
| `siem_execute_spl` | `asp siem query spl` |
| `siem_execute_esql` | `asp siem query esql` |
CLI 可以比 MCP 更完整。首版设计包含 `comment list``file upload``file download``file read-text`,因为这些能力适合 CLI,但不适合 MCP tool 参数直接传输文件内容。
## Help 和命令发现
采用分层渐进式 help
- `asp --help`:只显示全局选项和命令组。
- `asp case --help`:显示 case 子命令和常见流程。
- `asp case list --help`:显示完整参数、枚举、输出说明和 examples。
所有业务命令支持 `--output human|json`。Agent/skill 文档必须使用 `--output json`,避免解析 human 表格。
CLI 提供 shell completion
```bash
asp completion powershell
asp completion bash
asp completion zsh
```
`asp auth login` 成功后给出下一步建议,例如运行 `asp doctor` 和一个只读 list 命令。
## 配置和认证
`asp auth login` 是主认证入口:
```bash
asp auth login --api-url https://asp.example.com --api-key asp_xxx
```
该命令默认将 base URL 和 API key 写入 settings。后续命令自动使用该配置,不需要再次认证。
配置范围:
- 全局个人配置。
- 当前仓库 local `.asp/settings.json`
local 配置查找规则:
- 从当前目录向上查找最近的 `.asp/settings.json`
- 不越过 git repository root。
配置优先级:
```text
explicit CLI flags > environment variables > local .asp/settings.json > global settings
```
环境变量仅作为 CI、容器或高级临时覆盖通道,日常文档主推 settings。
API key 明文保存在 settings 中。实现写入配置时尽量收紧文件权限;文档说明明文配置的行为和适用场景,`auth login` 成功路径不输出风险警告。
`asp auth status` 输出当前配置来源、base URL、认证用户和 key 状态,不显示完整 API key。`asp auth logout` 删除当前 scope 的认证配置。
## 输入契约
输入规则:
- 简单参数用 flags。
- 列表用重复 flag,兼容逗号分隔。
- 复杂对象支持 `--data-json``--data-file``--stdin`
- 长文本支持 `--body``--body-file`,后续可支持 `--editor`
示例:
```bash
asp case list --status New --severity High --limit 20
asp enrichment create case_000001 --name ti --data-file enrichment.json
asp comment add case_000001 --body-file note.md --file-key 6f2c...
```
## 输出契约
默认输出为 human,面向人类阅读:
- list/search 使用紧凑表格。
- show/detail 使用分区详情。
- 写操作输出变更摘要。
- SIEM/TI/CMDB 输出关键命中和分析摘要。
完整数据通过 JSON 输出:
```bash
asp case list --output json
```
成功 JSON 统一 envelope
```json
{
"data": {},
"meta": {
"operation": "case.list",
"request_id": "req_...",
"pagination": null
}
}
```
失败 JSON 统一 envelope
```json
{
"error": {
"code": "not_found",
"message": "Case not found: case_000001",
"details": {}
},
"meta": {
"operation": "case.show",
"request_id": "req_..."
}
}
```
CLI 支持可选 `--query`,使用 JMESPath 对 JSON `data` 做客户端筛选:
```bash
asp case list --output json --query "data[].case_id"
```
## 分页和大结果
list/search 默认有界,避免一次拉取过多数据。
分页采用无状态 cursor。服务端不保存客户端翻页 session,cursor 是客户端携带的不透明 token。
JSON `meta.pagination` 示例:
```json
{
"pagination": {
"next_cursor": "opaque-token",
"has_more": true
}
}
```
CLI 支持:
- `--cursor`:继续下一页。
- `--limit`:控制返回数量。
- `--page-size`:控制单次请求大小。
- `--all`:显式自动翻页。
- `--max-items`:限制自动翻页最大数量。
SIEM 查询必须要求时间范围和 limit。若底层 SIEM 后端支持稳定 cursor/search_after,再提供 cursor;否则返回有界结果并在 meta 中说明限制。
## 错误、exit code 和日志
错误类型使用稳定 error code 和 exit code,至少区分:
- 参数错误。
- 认证失败。
- 权限不足。
- 资源不存在。
- 冲突。
- 版本不兼容。
- 网络错误。
- 服务端错误。
human 模式输出简短可行动错误。`--verbose` 才显示请求方法、URL path、HTTP status、request id 和耗时。
日志规则:
- 默认不写详细日志。
- `--verbose` 输出脱敏诊断信息。
- `--log-file` 显式写本地日志。
- `--debug-http` 仍强制脱敏。
- Authorization、API key 和敏感参数不得出现在日志中。
## 权限和写操作安全
认证继续使用 ASP User API Key。
权限规则:
- 读操作要求 authenticated。
- 写操作复用现有 business writer 规则。
- operation spec 标注 required permission 和 required capability。
- 后端写操作继续使用现有 audit 机制。
明确写命令不做二次确认,保证 Agent/skill 可无交互执行:
- `asp comment add`
- `asp case update-ai`
- `asp enrichment create`
- `asp playbook run`
未来 destructive 或 bulk 命令必须要求 `--yes`,并优先支持 `--dry-run`。JSON/CI 模式下不弹交互 prompt;缺少 `--yes` 时返回标准错误。
## 文件能力
CLI 文件命令:
- `asp file upload <path>`
- `asp file info <file_key>`
- `asp file download <file_key> --output-path <path>`
- `asp file read-text <file_key> --max-bytes <n>`
默认不输出文件 bytes、base64 或大文本。`read-text` 必须显式调用,并受大小和内容类型限制。
comment 附件继续使用 `file_key` 引用。CLI 上传本地文件后返回 `file_key`,可直接传给 `asp comment add --file-key ...`
## `asp doctor`
`asp doctor` 是只读诊断命令,支持 human 和 JSON 输出。
检查内容:
- 当前配置来源。
- base URL 连通性。
- TLS/代理基础错误。
- API key 是否有效。
- 当前用户和角色。
- 服务端 API version。
- CLI version。
- 版本兼容。
- 服务端 capabilities,例如 SIEM、TI、CMDB。
`doctor` 不修改配置,不执行写操作。
## Marketplace skills 迁移
迁移策略:
1. 新增 CLI 版 skillsmetadata 标记依赖 ASP CLI。
2. MCP 版 skills 保留兼容窗口。
3. CLI 版 skills 一律使用 `--output json`
4. CLI 版稳定后,marketplace 默认入口切到 CLI。
5. MCP 版标记 deprecated,后续再移除。
同一个 skill 不同时兼容 MCP 和 CLI,避免分支逻辑复杂化。CLI 版 skill 应直接写最优 CLI 命令,不围绕 MCP 历史函数名设计。
## 文档
文档由两部分组成:
- 从 operation spec 生成命令/API 参考,包括参数、schema、examples 和输出结构。
- 手写指南和 SOP,包括安装、认证、配置、SOC 调查流程、Claude Code skills 使用。
asf-doc 修改遵循项目规则:先更新 zh 文档,zh 定稿后再同步 en 文档。
## 测试策略
后端测试:
- Agent API endpoint tests。
- 权限 tests。
- schema/envelope tests。
- cursor 分页 tests。
- 错误码 tests。
spec 测试:
- operation spec 结构校验。
- CLI command 覆盖检查。
- deprecated alias 映射检查。
- server `min_cli_version` 兼容检查。
CLI 测试:
- Typer 命令解析测试。
- httpx mock 集成测试。
- `--output json` contract tests。
- 关键 human 输出 snapshot tests。
- 配置优先级 tests。
- 脱敏日志 tests。
Marketplace skill 检查:
- 命令示例静态检查。
- JSON 输出契约引用检查。
## 实施阶段
### Phase 1: Foundation
- CLI package skeleton。
- `auth login/status/logout`
- global/local settings。
- `doctor`
- `--output human|json`
- JSON envelope。
- 标准错误和 exit code。
- operation spec 基础结构。
- Agent API `/version` 和基础认证。
### Phase 2: Core SOC
- `case`
- `comment`
- `file`
- `enrichment`
- `knowledge`
- `playbook`
- 对应 Agent API 和 serializers。
### Phase 3: Investigation integrations
- `siem`
- `ti`
- `cmdb`
- 对应 Agent API,覆盖当前 MCP 的 SIEM/TI/CMDB 能力。
### Phase 4: Advanced and migration
- `dev stream`
- shell completion polish。
- generated command reference。
- CLI 版 marketplace skills。
- MCP deprecation 文档。
## 设计结论
ASP CLI 将成为新的 Agent 主集成面。后端提供稳定的 Agent Operations APICLI 提供主流静态命令树和渐进式 help,operation spec 负责发版期契约同步。该方案优先保证主流 CLI 体验、低运行时复杂度、可测试性和长期可维护性,同时保留 MCP 兼容窗口降低迁移风险。