Repository navigation
Technical Design 2A
中文 · Application Backend & Product Interface | Technical Design 1 | Technical Design 2 | Technical Design 3
本文补齐 Technical Design 1 与 Technical Design 2 之间此前没有独立 owner 的 Application Backend / Product Interface。TD1 已冻结 M1–M3 Data Plane,TD2 已冻结 M4–M8 Task / Context / Perception / Capability / Policy / Decision Runtime;当前实现进一步证明,中间仍需要一层稳定的 application composition:用户的一次产品操作如何进入已有 domain runtime、哪些步骤同步完成、哪些步骤创建 durable TaskRun、HTTP 返回什么、前端读取什么、长任务如何继续更新。
本设计不修改 M1–M8 ownership,不重做 Agent Runtime,也不把 HTTP / UI 语义下沉到 domain package。本文只冻结 Presentation → Application → Domain Runtime → Infrastructure 四层边界,并定义第一版 Product API、Use Case、Read Model、错误、幂等、SSE、健康检查与测试 contract。
Product 使用 FastAPI/REST/OpenAPI/SSE,apps.application 组合能力 owner;领域包不依赖 Product DTO。账户、会话、多轮问答、Case 继续调查、不可变逐回合 Decision、来源与任务审计均已实现。推荐/关注与反馈、事件库、资料库和公开研报是当前正式产品读取面。
Slice A/B 已完成;Slice C 的问答、会话、exact retrieval reuse 与 pre-context failed retrieval attempt 已落地。Slice D 的幂等、cancel、revision conflict 和 activity 已完成;独立通用 /resume 尚未实现,session follow-up 与 runtime wake 不是该接口。Slice E 的 ProductEvent/SSE、Last-Event-ID 与终态通知已实现。Slice F 已有来源/系统、证据、情报、事件、资料与公开/私人 HTML 读取。Slice G 有 transport/owner/contract 回归;本轮未重新做真实 PostgreSQL/Redis 集成及收费模型端到端验收。
Question 的未校验模型草稿可以经 SSE 展示,但正式结果只能来自验证后的 Decision。事实 proposal 被拒绝时,当前 use case 可返回零事实、零引用的有效边界答复(evidence_validation_failed);真正调用失败仍返回 error。不能沿用“所有证据拒绝都会产生失败 Task”的旧描述。
Product 命令幂等为 migration 0032,独立失败检索记录为 0033,活动反馈为 0035,公开研报/自动编研为 0036/0037。当前源码 migration head 为 20261015_0037;这不表示每个部署已执行升级。
历史正式 Product/session QA 已进入冻结 CompetitionReport;当前能力验收与扩展分母见 验收台账。Workbench 旧 HTTP/静态控制台已退役,Product-safe DTO 承接必要观察面;A2A 保持独立 transport contract。
以下问题清单记录 2026-09-27 的设计动因;当前实现状态与 Slice 验收以本页顶部及第 29 节为准,不作为未完成列表。
2026-09-27 的 apps/api/main.py 曾负责 lifespan、session factory、router 和 dev/test Workbench;当前旧 Workbench 挂载已撤下。apps/worker 负责 Celery worker / scheduler;TD2 Investigation / Enrichment 的真实执行已经进入统一 TaskRun / ExecutionRun / Role Runtime,而不是由 HTTP request 持有长任务。
顶层 apps/ 下已经存在一批 composition module:
- task_admission.py:把 Enrichment / Investigation compiler 与 Runtime Policy 组合成统一 TaskContractService;
- investigation_runtime.py:组合 ModelProvider、ContextMaterializer、PerceptionRuntime、Delegation、Budget、Execution;
- decision_runtime.py:组合 M6 DecisionService 与 M4 DecisionCommit / Continuation gate;
- enrichment_runtime.py、perception_execution.py、investigation_delegation.py、watch_runtime.py:完成对应 runtime wiring。
这些模块已经承担“把多个 domain package 组合成可运行能力”的职责。2A 不重新制造另一套 Runtime,只在其上增加产品 Use Case owner。
当时 apps/api/routes/workbench.py 约 700 行,同时承担:
- Pydantic request / response model;
- 直接 SQL aggregation 和 list query;
- Case / EvidenceNeed 创建;
- Task admission;
- ContextManifest 构造;
- Budget / ExecutionEnvelope 创建;
- TaskRun queue transition;
- Celery enrichment trigger;
- M6 DecisionRuntime wiring;
- citation source assembly;
- HTTPException 映射。
这些代码多数没有绕过 TD1/TD2 domain gate,因此不是“业务规则写错了”;问题是 Application Use Case 没有独立 owner,导致 route 被迫同时承担 transport、query composition 与 workflow orchestration。随着 Product API 增加,继续沿这个模式扩展会让 route 成为新的 god layer。
现有 apps/web 明确是 operator/test frontend。它故意展示:
- TaskRun / TaskContract / ContextManifest;
- TaskEvent;
- Case / InvestigationState / EvidenceNeed;
- M7 trajectory / snapshot coordinates;
- raw Decision runtime output。
这些信息对工程复验有价值,不应为了“产品化”全部隐藏。本文只规定:Product Frontend 不依赖这些 internal schema;Runtime Workbench 可以继续存在并直接观察内部状态,且不承诺 public compatibility。
GET /api/v1/vulnerabilities/{cve_id} 已经通过 packages.intelligence.knowledge.read.get_vulnerability_by_cve 读取 Evidence-rich KnowledgeObjectView,并保持 read-only。该 route 是当前最接近 Product API 的稳定 read seam,应保留,不因 2A 重构而打断。
系统产品路径固定为四层:
Presentation
HTTP / SSE / OpenAPI / A2A transport adapter
validation / serialization / request context / public error
↓
Application
Use Case / Command / Query / Read Model / ProductEvent
product workflow / sync-async boundary / idempotency
↓
Domain Runtime
TD1 Evidence/Knowledge + TD2 Task/State/Agent/Decision
business truth / runtime state / policy / capability
↓
Infrastructure
PostgreSQL / Redis / Celery / S3 / Model / External Provider
physical persistence / delivery / provider implementation
Presentation 持有 HTTP method/path、query/path/body validation、request/response serialization、authentication bootstrap、request correlation、OpenAPI、SSE framing 与 public error mapping。
Presentation 不持有:
- 漏洞、Incident、Evidence 的业务判断;
- EvidenceNeed 创建/关闭条件;
- Task state transition;
- Capability / Policy 规则;
- Knowledge / Case 的数据库查询策略;
- Agent planning。
Application 持有“一个稳定产品行为如何编排现有 domain runtime”。
例如 StartInvestigation 负责:
resolve target
→ build TaskIntent
→ Task admission
→ create/bind Case
→ bind ContextManifest
→ create TaskRun / Budget / Execution
→ queue run
→ return InvestigationView
其中每一步的业务 invariant 仍归原 domain owner;Application 只持有顺序、产品语义、sync/async boundary、public idempotency 和 read model projection。
TD1 / TD2 owner 不变:
- Knowledge / Evidence:M1–M3;
- InvestigationState / EvidenceNeed:M4;
- InvestigationRole / Perception:M5;
- DecisionResult:M6;
- Replay / Evaluation:M7;
- Capability / Policy / Budget / Sandbox / Telemetry:共享 runtime / M8。
Application 可以调用这些 port,但不能复制其规则。
Infrastructure 持有 SQLAlchemy/PostgreSQL、Redis、Celery、S3、ModelProvider、external source、sandbox substrate 和网络实现。Infrastructure exception 先翻译成可归属的 failure,再进入 Application / Presentation;原始 SDK exception 不成为 public API contract。
Presentation
↓
Application
↓
Domain Port
↓
Infrastructure Port
↓
Infrastructure Implementation
禁止:
- domain import FastAPI / HTTP status;
- repository 返回 HTTP response;
- Product route 直接写 ORM query;
- Product frontend 依赖 ORM row / TaskEvent / StatePatch;
- Application 重写 M4/M5/M6 invariant;
- provider error 原样泄漏到客户端。
目标:稳定、可版本化、可供正式前端和外部 client 使用。
要求:
- stable DTO / read model;
- OpenAPI;
- 统一 error contract;
- idempotency;
- pagination;
- authorization seam;
- request / task trace linkage;
- sync / async 明确。
原 /api/v1/workbench/* 和静态 operator console 不再注册。必要的来源、任务、Case、上下文和模型观察由账户校验后的 Product read models 承接;开发诊断使用能力 owner、脚本与测试。退役 transport 未带走任何 domain authority,不再规划其逐步抽取或长期公开支持。
路径 /api/v1/a2a/* 保持协议适配职责。A2A Task / Artifact / Message / Stream semantics 与 Product Investigation / Decision UI 不共用 DTO。A2A inbound message 如果未来进入真实执行,仍必须通过 authenticated principal、TaskIntent normalization 和 shared Task admission。
Application 不建立单一 ApplicationService god object。每个稳定产品行为对应独立 command/query handler;输入是 typed request,输出是 public view 或 operation reference。
| Use Case | 类型 | Domain Owner | 输出 |
|---|---|---|---|
| GetVulnerability | Query | TD1 Knowledge / Projection | VulnerabilityView |
| SearchIntelligence | Query | TD1 Retrieval | SearchResultPage |
| GetIncident | Query | TD1 Incident | IncidentView |
| GetEvidence | Query | TD1 Evidence | EvidenceView |
| GetDocument | Query | TD1 Document | DocumentView |
普通 GET 只读取当前世界,不在 miss 时隐式触发 enrichment、资产扫描或外部搜索。
| Use Case | 类型 | Domain Owner | 输出 |
|---|---|---|---|
| AskQuestion | Command-like Query | Task admission + M6 | QuestionResultView |
| GetDecision | Query | M6 / M4 | DecisionView |
AskQuestion 是 Product API 的 routing point:
Question
→ TaskIntent / admission
→ ExecutionProfile
DIRECT
→ current Knowledge / M4
→ M6
→ synchronous Decision
RETRIEVE
→ bounded Perception
→ M6
→ synchronous Decision when interactive budget allows
VERIFY / INVESTIGATE
→ durable Case + TaskRun
→ 202 Accepted
WATCH
→ durable Case
→ 202 Accepted / waiting
| Use Case | 类型 | Domain Owner | 输出 |
|---|---|---|---|
| StartInvestigation | Command | M4/M5 + Task Runtime | InvestigationView |
| GetInvestigation | Query | M4 read | InvestigationView |
| ListInvestigations | Query | Application read projection | InvestigationPage |
| CancelInvestigation | Command | Task lifecycle | OperationView |
| ResumeInvestigation | Command | Task lifecycle | OperationView |
| GetInvestigationActivity | Query | TaskEvent / Trajectory projection | ActivityPage |
| StreamInvestigation | Stream | ProductEvent projection | SSE |
Product Web 通过 ListInvestigations(origin_scope=product, cursor) 连续读取账户可见的 Case 页面,并把“展开已载入”与“载入更早调查”分开。深链接先由 GetInvestigation 执行 owner check;若该 Case 不在当前列表页面,读取成功后仍把它放进可见索引。对应实现:2e131cc。
旧 Workbench 的 create_case + create_run 曾是两步操作;当前 Product Use Case 已组合成 StartInvestigation,同时继续调用相同 CaseService、InvestigationStateService、TaskContractService、BudgetGovernor、ExecutionRunService 和 TaskRun storage gate。
| Use Case | 类型 | Domain Owner | 输出 |
|---|---|---|---|
| ListSources | Query | M1 | SourcePage |
| GetSourceStatus | Query | M1/M8 | SourceStatusView |
| GetSystemOverview | Query | Application aggregate | SystemOverviewView |
| GetHealth | Query | M8 / process | HealthView |
source enable/disable、credential mutation、policy revision 暂不进入公开 Product API;这些属于未来 admin/control plane。
不要求引入完整 CQRS 基础设施,但所有 Product Use Case 在语义上区分 Command 与 Query。
Query 只读取 projection / read port,不产生不可见副作用。页面刷新、详情读取、列表读取不能改变 Task / Case / Enrichment state。
Command 创建或改变系统状态,包括:
- ask 触发长调查;
- start investigation;
- cancel / resume;
- 显式 refresh / re-investigate;
- 未来 human review。
每个 Command 必须定义:
- authorization;
- idempotency;
- expected revision;
- sync / async;
- public failure code;
- audit / trace。
禁止:
- GET vulnerability miss 后自动启动外部采集;
- GET investigation 因 stale 自动创建新 TaskRun;
- 前端刷新触发 enrichment;
- 列表查询改变 Case status。
所有 Product route 共用统一 edge pipeline:
HTTP Request
→ Request ID / Trace Context
→ Authentication Bootstrap
→ Rate / Payload Guard
→ Path / Query / Body Validation
→ RequestContext
→ Route Adapter
→ Application Use Case
→ Public DTO
→ HTTP Response
Exception
→ Global Error Mapper
→ ProblemDetail
RequestContext
request_id
trace_id?
principal
client_id?
idempotency_key?
request_started_at
api_version
RequestContext 属于 HTTP/Application edge,不替代 TD2 ExecutionEnvelope。创建 TaskRun 后建立 request_id → task_run_id / execution_id trace linkage,Agent 后续 episode 不依赖原 HTTP connection。
Pydantic request model 负责 transport shape。Domain validation 仍由 domain owner 执行:
- 字符串是否像 CVE:boundary normalization;
- 某 TaskKind 是否允许 admission:Task Runtime;
- EvidenceNeed transition 是否有效:M4;
- Capability 是否允许:Policy;
- citation 是否支持结论:M6。
服务 HTTP / OpenAPI / SSE / frontend。
服务 TD1/TD2 invariant,例如 Observation、Claim、Relation、TaskContract、InvestigationState、DecisionResult。
服务 SQLAlchemy / Redis / S3 / migration / index。
Public DTO
↕ mapper
Application / Domain
↕ repository mapper
Persistence Model
Product API 禁止:
- response_model 直接绑定 ORM model;
- 原样 dump ContextManifest / PolicyDecision / CapabilityBinding;
- storage migration 无意改变 public JSON;
- frontend 根据内部 event / table field 推断产品状态。
旧 diagnostic schema 不作为 Product DTO;退役的 Workbench 不再提供该读取面。
Read Model 只定义前端真正需要的稳定语义,不复制全部 domain object。第一版可以由现有 read port 即时装配;只有 benchmark 证明需要时再物化独立 projection。
当前 GET /api/v1/vulnerabilities/{cve_id} 的 KnowledgeObjectView 保持兼容。Product frontend 若需要更稳定的聚合字段,在 Application 层增加 VulnerabilityView mapper,不要求立即破坏现有 route。
至少覆盖:
- identity / canonical id;
- summary / severity;
- affected / fixed information;
- exploit / incident relation(可用时);
- conflict / unknown;
- evidence summary;
- revision / freshness。
至少覆盖:
- evidence id;
- source / source class / authority;
- published / fetched time;
- locator;
- excerpt / artifact media type;
- revision。
内部 S3 URI、filesystem path、credential 不直接暴露。
至少覆盖:
- case_id / revision;
- status / execution profile;
- target / goal;
- confirmed findings;
- conflicts;
- unknowns;
- open EvidenceNeed summary;
- current activity;
- latest decision;
- terminal reason;
- timestamps。
Progress 不使用模型自由生成的百分比。如果没有可靠 denominator,使用 phase、open/closed EvidenceNeed 和 Task terminal state。
至少覆盖:
- decision_id;
- answer;
- fact / inference / recommendation;
- citation;
- conflict / unknown / assumption;
- stop reason;
- created_at。
当前 DecisionView 另提供可选的 report_paragraphs,用于普通产品问答与已完成调查的自然段落研究报告。同步 Product Question 和持久调查终局的 DecisionRole 均显式启用报告提示词:在同一次结构化研判中先确定 answer_payload 与结论,再用问题的语言写出完整句子;段落只能引用该 Decision 已验证结论中的 EvidenceRef。前端优先显示段落和逐段证据,结构化字段留在可展开的审计层。正式 QA 的评分、回放及证据约束仍以结构化 DecisionResult 为准,不以报告措辞改写正确性;提示词版本从 decision-model-v2 变为 decision-model-v4,新评测须使用新的 DeploymentRevision,历史指标不能直接冒充当前版本。历史结果没有段落时保持兼容读取,以结论为主、字段可展开。
至少覆盖:
- source id / class / role;
- enabled / schedule state;
- health;
- last attempt / success;
- next due;
- degraded reason。
面向 Product Overview 聚合:
- source health summary;
- recent intelligence;
- active investigation;
- recent decision;
- runtime degraded component;
- generated_at。
该 view 是 Application projection,不成为 canonical truth。
比赛需要展示 Agent 自主调查过程,但 Product Frontend 不应直接 dump TaskEvent、PromptAssembly 或完整 trajectory。Application 提供脱敏 RuntimeActivityView:
RuntimeActivityView
activity_id
case_id
seq
actor_role
activity_type
summary
evidence_need?
selected_capability?
child_task_ref?
result_summary?
status
started_at?
finished_at?
允许的 activity type:
task_started
evidence_need_selected
retrieval_planned
capability_selected
external_observation
child_task_started
child_task_completed
state_updated
decision_ready
waiting
completed
failed
不暴露:
- 模型私有推理文本;
- 完整 prompt;
- secret / credential;
- raw PolicyDecision;
- 未经 redaction 的 tool output;
- sandbox filesystem path。
内部 TaskEvent / ContextManifest 保留在 owner storage 和授权审计投影中。Product AGENTS 的 Task 详情读取经 owner check 的 AgentTaskDetail 投影:只展示 ContextManifest 的版本、引用数量与可追溯坐标、每次 ModelAttempt 的模型/状态/提供方用量/预算结算,以及已有的预算、事件、CapabilityInvocation;证据与对象引用跳转到 INTELLIGENCE read。页面不接收完整 prompt、模型私有推理文本、secret 或 raw PolicyDecision。对应实现:d8ddfed。
Public namespace 继续使用 /api/v1。
GET /api/v1/vulnerabilities/{cve_id} # 保留现有 read seam
GET /api/v1/intelligence/search
GET /api/v1/incidents/{incident_id}
GET /api/v1/evidence/{evidence_id}
POST /api/v1/questions
GET /api/v1/decisions/{decision_id}
QuestionResultView:
request_id
mode = completed | accepted
execution_profile
decision? # completed
investigation? # accepted
DIRECT / bounded RETRIEVE 可以 200;VERIFY / INVESTIGATE / WATCH 返回 202。
POST /api/v1/investigations
GET /api/v1/investigations
GET /api/v1/investigations/{case_id}
POST /api/v1/investigations/{case_id}/cancel
POST /api/v1/investigations/{case_id}/resume
GET /api/v1/investigations/{case_id}/activity
GET /api/v1/investigations/{case_id}/events
events 使用 SSE。
GET /api/v1/sources
GET /api/v1/sources/{source_id}
GET /api/v1/system/overview
GET /health/live
GET /health/ready
GET /health
旧 /api/v1/workbench/* 已退役;Product Use Case 复用 domain composition,transport contract 独立。
HTTP latency contract 与 Agent execution duration 分离。
优先同步:
- exact / current projection read;
- 普通 structured / lexical read;
- 已有 Evidence World 上的 DIRECT;
- 满足 interactive budget 的 bounded RETRIEVE;
- status / evidence / read model。
Requirements 的普通 QA ≤5s 目标由这条 fast path 承担。
以下 workload 默认异步:
- 需要新 external evidence 的 VERIFY;
- INVESTIGATE;
- WATCH;
- sandbox / browser / long provider chain;
- 超过 interactive budget 的任务。
返回:
202 Accepted
Location: /api/v1/investigations/{case_id}
客户端以后通过 stable resource ID + GET/SSE 继续,不暴露 Celery task id 作为 Product contract。
内部 TaskEvent 是 runtime protocol,ProductEvent 是产品协议。Application 增加投影器:
TaskEvent / M4 state change / M6 Decision
→ ProductEventProjector
→ ProductEvent
→ SSE
ProductEvent:
event_id
stream_id
seq
type
resource_revision?
occurred_at
data
第一版事件:
- investigation.started
- investigation.status_changed
- investigation.progress
- investigation.finding_added
- investigation.conflict_changed
- investigation.evidence_need_changed
- investigation.decision_ready
- investigation.waiting
- investigation.completed
- investigation.failed
- investigation.canceled
SSE:
GET /api/v1/investigations/{case_id}/events
Accept: text/event-stream
Last-Event-ID: ...
Redis Streams 只承担内部实时 delivery;SSE reconnect 不能依赖 Redis 尚未淘汰的 transient message,必要状态从 durable TaskEvent / current read model 恢复。
第一版选择 SSE,因为主要是 server → client 状态更新。只有未来 computer-use、协同操作或高频双向控制出现后才重新评估 WebSocket。
Product Question 流式合同:POST /api/v1/questions/stream 在同一请求内发送真实模型 model_delta,可选发送 provider reasoning;这些字节只是未验证草稿。React 会把已经收到的报告自然段按字符呈现,正式答案仍只来自通过 M6 证据不变量并持久化的 DecisionResult,到达 result 时立即替换草稿。完整会话默认请求 90 秒交互预算,服务端允许 1–120 秒;这只是执行上界,不人为延迟答案。终态 error 结束该幂等命令,重试生成新键。若事实 proposal 被证据门拒绝,当前 Question use case 提交零事实、零引用的边界 Decision,标记 evidence_validation_failed,并替换未校验草稿;真正执行失败仍返回 error 和可用的 TaskRun 坐标。不持久化被拒绝 proposal 为事实。未知异常只返回通用错误并在服务端按 request_id 记日志,不把内部异常或原始模型输出暴露给客户端。
可能无界增长的列表默认 cursor pagination:
- intelligence search;
- incidents;
- investigations;
- activity;
- source runs;
- evidence list。
GET ...?limit=50&cursor=...
Page[T]
items[]
next_cursor?
has_more
Cursor 是 opaque token。Filter 和 sort 使用 allowlist,不把任意数据库 column 暴露成 public query language。
Product 分页按本 contract 实现;旧 diagnostic limit-only list 不作为当前产品契约。
当前 Product path 仍有简单 HTTPException(detail=...);2A 将正式 Product API 错误统一为 RFC 9457 风格 ProblemDetail,并增加稳定 code、request_id、retryable。
ProblemDetail
type
title
status
detail
code
instance?
request_id
retryable
context?
第一版 public code:
- validation_error
- authentication_required
- permission_denied
- resource_not_found
- revision_conflict
- idempotency_conflict
- task_not_cancelable
- task_not_resumable
- provider_unavailable
- capability_unavailable
- budget_exhausted
- deadline_exceeded
- rate_limited
- dependency_unavailable
- internal_error
HTTP mapping:
| Failure | HTTP |
|---|---|
| transport validation | 422 |
| unauthenticated | 401 |
| unauthorized | 403 |
| missing resource | 404 |
| revision / lifecycle / idempotency conflict | 409 |
| rate limit | 429 |
| async accepted | 202 |
| temporary dependency/provider unavailable | 503 |
| unknown server failure | 500 |
Evidence unknown 与 provider unavailable 必须区分。Public body 不返回 traceback、SQL、internal path、secret、raw SDK exception。
Product route 使用统一 mapper;已退役的 Workbench 不保留第二套错误合同。
旧 Workbench 的随机 trigger_ref 不适合作为客户端重试合同;Product Command 已使用 client idempotency key。
以下 endpoint 第一版支持 Idempotency-Key:
- POST /api/v1/questions
- POST /api/v1/investigations
- POST /api/v1/investigations/{case_id}/cancel
- POST /api/v1/investigations/{case_id}/resume
Application 持有 IdempotencyRecord:
principal_scope
operation
key
request_digest
response_ref
status
created_at
expires_at?
相同 scope + key + 相同 request digest 返回既有结果;相同 key + 不同 request digest 返回 409 idempotency_conflict。
这不替代 TD1/TD2 内部 Observation/Event/Task idempotency,只处理 public command retry。
2026-10-09 实现进度:product_command_records(migration 20261009_0032)现在按账户、操作、客户端键保存归一化请求摘要与既有结果坐标。POST /questions、POST /questions/stream、POST /investigations 和 POST /investigations/{case_id}/cancel 已接入;同键同请求不重复调用模型、写会话回合或建立 Case,同键不同请求返回 409 idempotency_conflict。Question SSE 重放只返回既有终态结果,不伪造先前流式 token。Case/会话回合/最终响应与命令记录在一次事务中提交;同步模型调用之前的 TaskRun 是独立 durable checkpoint,进程在中途退出后 pending 键 fail closed,避免再次执行。失败模型调用把键标为 failed,下一次明确重试需使用新键。Web 对同一未完成表单请求复用键,并在成功后清理。独立 /resume Product route 仍未建立:现在的用户追问通过 Question session/ContinueInvestigationUseCase,运行时等待恢复由 Task Runtime 持有,不能把它们描述成已有的通用手工 resume。
需要基于当前状态发出的 command 携带 expected revision。Transport 可以使用 If-Match 或 body expected_revision,Application 转换成 domain revision check。
适用:
- cancel / resume;
- future human review;
- 基于 Case state 的修改动作。
revision mismatch 返回 409 revision_conflict,不静默覆盖新状态。
2026-10-09 实现进度:Product Investigation GET/POST 返回 quoted revision ETag;取消命令必须携带 If-Match(quoted 或整数形式),Application 在锁定 Case 后与 current_revision 对照。浏览器从所展示的 Case revision 发送条件写;失败返回 409 revision_conflict,旧页面不能覆盖较新的 Case 状态。会话追问的继续调查路径仍由现有 active-run 与 M4 revision gate 控制,独立 Product /resume 尚未提供。
正式 Product Frontend 把状态分成三类。
API / SSE 持有权威版本:
- vulnerability / incident / evidence;
- investigation;
- decision;
- source / overview。
只属于前端:
- 当前 tab;
- filter / sort;
- panel 展开状态;
- draft question;
- pagination cursor history;
- transient notification。
只属于后端:
- TaskRun;
- InvestigationState;
- EvidenceNeed;
- ContextManifest;
- PolicyDecision;
- Budget;
- CapabilityInvocation;
- Trajectory。
Product Frontend 页面刷新后通过 case_id / decision_id 恢复,不把 Agent durable state 存到 localStorage。
当前 apps/web 是正式 Product Web;Task/Context 通过授权和裁剪后的审计 DTO 呈现。
FastAPI 生成的 OpenAPI 作为 Product API 的机器可读 contract,至少覆盖:
- path / method;
- request / response schema;
- error schema;
- auth scheme;
- pagination;
- status code;
- example;
- SSE endpoint 说明。
前端类型优先从 OpenAPI 生成或校验,不维护平行手写 DTO。
CI 增加 Product API contract gate:
- OpenAPI 可以生成;
- schema ref 可解析;
- public response 不泄漏 persistence/internal runtime model;
- breaking change 必须显式进入 API version review。
OpenAPI 记录现有 Product 与独立 A2A routes;旧 Workbench routes 不再注册。
2026-10-08 的 Product 实现已将 principal 绑定到真实账户会话;完整契约见产品账户、会话与个性化。旧的固定 local principal 仅属于历史数据,不自动归属新账户。
Presentation:
- 从 HttpOnly Cookie 对应的服务端会话解析身份;
- 构造 RequestContext principal;
- 处理 401、Origin/CSRF 与跨标签账户变化。
Application / Domain:
- 资源级 access;
- command permission;
- TD2 Policy / Capability execution authorization。
前端隐藏按钮不能替代 authorization。X-Principal 不能作为 Product 身份来源;会话、Case、Decision、Task、SSE 和 A2A 读取必须在服务端按 durable owner 判定。公共 Evidence/Knowledge 与明确纯系统来源的只读 Case 引用另设边界,不泄漏私有 Task 或会话。
退役的 Workbench 不进入 Product 路由或导航。Nginx Basic Auth 可作为外围保护,但不能代替应用账户。生产 Cookie 需要 HTTPS、精确允许的浏览器 Origin 和可撤销的服务端 session。
当前只有 /health/live。第一版补:
只表示 process 活着。外部 provider 失败不影响 liveness。
表示 instance 是否能承接核心请求。至少检查:
- PostgreSQL;
- 必要 migration;
- required runtime configuration。
optional source / asset / model provider 不应默认让整个 API unready。
聚合:
overall = healthy | degraded | unhealthy
components[]
component
status
checked_at
detail_code?
典型语义:
- PostgreSQL down → unhealthy;
- Redis Task Event delivery degraded,但 direct read 可用 → degraded;
- 单个 source 403 → degraded;
- optional model provider 未配置且系统可 lexical-only → degraded / disabled;
- optional provider 无 credential → disabled,不伪装成核心系统故障。
HTTP edge 创建 request_id / trace context。创建 TaskRun 后形成:
request_id
→ task_run_id
→ execution_id
→ capability_invocation_id
→ evidence / decision
Structured log 至少包含:
- request_id;
- route / use_case;
- case_id / task_run_id;
- status;
- latency;
- public error code;
- trace_id;
- 可记录时的 principal_id。
Audit 与 debug log 分离。Cancel / Resume / future human review 记录谁对哪个 revision 做了什么动作。Secret / credential 不进入日志。
跨运行的 ModelAttempt、token / context 评测归因留给 TD3;单次 Task 的已持久化用量与 context 坐标由 Product read projection 呈现。
Application 表达 workflow intent,但不持有跨所有 subsystem 的超级事务。
保持:
- TD1 KnowledgeChange + outbox 的既有 transaction;
- TaskRun / TaskEvent 的既有 durable boundary;
- M4 State transition / DecisionCommit 的既有 transaction;
- S3 / Redis / Celery 使用各自可靠性 contract。
跨 subsystem workflow 依靠 durable state + idempotency + event / retry / compensation,不引入分布式数据库事务。
StartInvestigation 的产品逻辑顺序:
admit TaskContract
→ create/bind Case
→ open/bind EvidenceNeed when required
→ bind Context
→ create TaskRun / Budget / Execution
→ transition queued
→ dispatch
→ return InvestigationView
该编排已由 Application Use Case 持有,routes 只负责 transport。
Product route 不直接查询 ORM。Application Query 调用:
1.已有 domain read service;或 2.明确的 Application read repository / query service。
当前 overview、task、case 查询由 Application read projection 持有;SQL 不复制到 Product route。
Repository 持有:
- persistence query;
- mapping;
- pagination;
- transaction-local data access。
Repository 不持有:
- 产品策略;
- task admission;
- policy;
- “CVSS 高就自动调查”一类业务动作;
- provider fallback policy。
现状已经存在 apps/*.py composition module,不进行大规模移动。第一阶段只新增 Product Application 子层:
apps/
├── api/
│ ├── main.py
│ ├── routes/
│ │ ├── knowledge.py # existing compatibility read
│ │ ├── a2a.py # interoperability
│ │ ├── questions.py # Product API
│ │ ├── investigations.py # Product API
│ │ ├── sources.py # Product API
│ │ └── health.py
│ ├── dependencies.py
│ └── errors.py
│
├── application/
│ ├── commands/
│ │ ├── ask_question.py
│ │ ├── start_investigation.py
│ │ ├── cancel_investigation.py
│ │ └── resume_investigation.py
│ ├── queries/
│ │ ├── intelligence.py
│ │ ├── investigations.py
│ │ ├── decisions.py
│ │ ├── sources.py
│ │ └── overview.py
│ ├── views/
│ ├── events/
│ ├── idempotency/
│ └── errors.py
│
├── task_admission.py # existing composition
├── decision_runtime.py # existing composition
├── investigation_runtime.py # existing composition
├── enrichment_runtime.py
├── perception_execution.py
├── investigation_delegation.py
└── watch_runtime.py
Application Use Case 可以依赖现有 apps-level composition module;后续如果这些 module 自然形成稳定 application runtime package,再统一移动。当前不为了目录纯度制造大范围 churn。
packages/ 下的 TD1/TD2 owner 不因 2A 重排。
现有 tests/test_architecture_dependencies.py 已把 packages 之间的依赖方向变成 executable invariant,并明确禁止 package code 反向 import apps。将 Product Application 放在 apps/application 下可以保持这一约束:Application 作为顶层 composition 层允许组合多个 package,而任何 domain package 都不需要认识 Product Use Case。若未来把 Application 下沉到 packages/application,则必须重新设计依赖矩阵,不能仅为目录命名修改 architecture test。
迁移已完成:Product routes 调用 Application use cases/read models,旧 diagnostic routes 已撤下。Task/Context/Model/Evidence 通过授权后的产品 DTO 可读;内部 raw schema 不成为产品合同。当前不存在等待继续重构的 workbench.py。
当前 apps/web 已是四入口 Product Web,现有 surface 对应如下:
| Product View | Backend View |
|---|---|
| Overview | SystemOverviewView |
| Intelligence Search | SearchResultPage |
| Vulnerability Detail | VulnerabilityView + EvidenceView |
| Incident Detail | IncidentView |
| Investigation | InvestigationView + Activity + SSE |
| Ask / QA | QuestionResultView + DecisionView |
| Operations | SourceStatusView + HealthView |
Product Frontend 不直接消费 TaskContract、原始 ContextManifest、StatePatch、PolicyDecision、CapabilityBinding 或 Budget row;AGENTS/QUESTIONS 使用 Application 层经过授权和裁剪的审计投影展示 context refs、model attempts 与预算状态。
任务执行展示使用 Product-safe RuntimeActivityView 与授权审计;更深调试使用 owner 脚本和日志。
apps/api/tests 验证 Product routes 的权限、Case/EvidenceNeed、policy-admitted TaskRun、幂等和 SSE;不依赖旧 Workbench。
2A 增加四类测试。
用 fake / test domain port 验证编排:
- DIRECT 不创建 TaskRun;
- VERIFY / INVESTIGATE 创建 durable Investigation;
- duplicate Idempotency-Key 不重复创建;
- stale revision 返回 conflict;
- cancel/resume 只通过 Task lifecycle owner。
通过 ASGITransport 验证:
- validation;
- status code;
- ProblemDetail;
- request_id;
- DTO serialization;
- sync / async response。
使用真实 PostgreSQL / Redis / object-store test profile 验证:
- StartInvestigation durable path;
- idempotency;
- cursor;
- SSE reconnect;
- health degraded state。
SQLite component test 可以继续承担快速 HTTP contract 检查,但不替代依赖 PostgreSQL transaction / locking 语义的 integration test。
第一版固定:
Lookup vulnerability
→ Ask direct question
→ synchronous Decision
→ Ask verification question
→ 202 Investigation
→ observe Activity / SSE
→ terminal Decision
→ open Citation / Evidence
以及一个 degraded flow:
Start Investigation
→ provider unavailable
→ explicit degraded / partial state
→ retry or fallback
→ terminal result
E2E 验证产品 contract,不重复 M7 的模型质量 benchmark。
- /health/live:进程级 smoke;
- /health/ready:必要 dependency readiness;
- /health:聚合 degraded semantics;
- Product API:稳定 contract;
- Workbench:内部 runtime observability;
- M7:Task / Agent replay 与质量评测。
不使用 Workbench test 数量代替 Product API completeness,也不使用 Product E2E 代替 M7 benchmark。
2A 以减少当前后端 coding friction 为目标,不要求一次重构完。
- RequestContext/request ID、共享 session、错误映射、health/live/ready、OpenAPI。
- Investigation use cases/read models、统一 admission 与 domain owner 组合。
- 同步 Question / 异步 Investigation、不可变 Decision、会话/目标/context carry。
- exact-query/session reuse、durable RetrievalInvocation、pre-context failed attempt。
- 同 Case 串行追问、active-Case read、Product/session evaluation runner。
- 暂缓:semantic/non-identical-query reuse;不属于已交付 exact reuse,见验收台账 O09。
- Cancel、Idempotency-Key、revision conflict、Activity。
- session continuation 与 runtime wait/wake 各有 owner。
-
未实现:通用独立 Product
/resume,见 O08;不能用上述 continuation 代替。
- Task/State → ProductEvent、SSE、Last-Event-ID reconnect、终态 Decision 通知。
- Question draft stream 与正式验证结果分离;重放仅返回持久终态。
- 来源/系统、证据、对象/漏洞、事件、资料库、关注/推荐、账户任务审计。
- 公开摘要/HTML 和账户所属私人 HTML;读取时重新校验可见性。
- API/访问控制/幂等/报告/前端 contract 回归。
- 待当前部署验收:真实 DB/队列集成、收费模型及更广自然问题端到端效果,见 O01/O03。
逐项测试入口、限制与下一步统一见 当前验收台账。旧 Workbench 已退役,不作为当前交付入口。
每个新增 Product endpoint 必须回答:
- Query 还是 Command?
- Use Case owner 在哪里?
- 是否有隐藏副作用?
- sync 还是 async?
- 是否需要 Idempotency-Key?
- 是否需要 expected revision?
- DTO 是否泄漏 ORM / TaskEvent / ContextManifest?
- collection 是否有 pagination / bound?
- failure 是否有稳定 public code?
- request_id 是否能追到 TaskRun / Execution? 11.长任务为什么用 SSE / 为什么不需要 SSE? 12.页面刷新后是否只凭 stable resource ID 恢复? 13.是否存在 component / integration test?
- OpenAPI 是否同步?
2A 不重新拥有 Source、Acquisition、Evidence、Knowledge、Incident、Projection、Retrieval、Artifact、M1–M3 reliability。
2A 不重新拥有 TaskIntent、TaskContract、TaskRun、ContextManifest、InvestigationState、EvidenceNeed、Perception、Skill、Capability、Policy、Budget、Sandbox、Agent loop、DecisionResult、Replay、Telemetry seam。
仅新增:
Product Use Case
Product Command / Query
Public DTO / Read Model
Product Error Contract
Public Idempotency
ProductEvent / SSE
Product Health / Overview
Frontend server-state boundary
2A 的目标不是“再加一层抽象”,而是消除三类已经出现的耦合。
Route 负责 transport,Use Case 负责产品编排,TD1/TD2 Runtime 持有业务 invariant。
正式前端读取 Product View;内部 Task / Context / Event 留在 Workbench 或脱敏 RuntimeActivityView。
跨多个 domain service 的稳定产品动作不能长期散落在 route、worker 和前端。
最终稳定链路:
HTTP / SSE
→ Application Use Case
→ TD1 / TD2 Domain Port
→ Infrastructure
Domain State / Result
→ Application Read Model
→ Public DTO
→ Product Frontend
长任务:
Product Command
→ durable Case / TaskRun
→ 202 Accepted
→ internal TaskEvent / State Change
→ ProductEvent projection
→ SSE
→ frontend refreshes stable Read Model
内部工程诊断通过 owner 脚本、测试与授权审计读取 Task/Context/State/Replay;退役 Workbench 不再是一条可访问的产品入口。
以下资料用于补充 application/backend engineering practice,不覆盖 SecFusionAgent 自身 ownership:
- goldbergyoni/nodebestpractices https://github.com/goldbergyoni/nodebestpractices
- futurice/backend-best-practices https://github.com/futurice/backend-best-practices
- psenger/Best-Practices-For-Rest-API https://github.com/psenger/Best-Practices-For-Rest-API
- khasky/backend-architecture-playbook https://github.com/khasky/backend-architecture-playbook
- RFC 9457 — Problem Details for HTTP APIs https://www.rfc-editor.org/rfc/rfc9457
World 异构材料投影保留各来源的真实标题、日期、正文、身份与 Evidence 坐标;首屏 story 与 Hot/健康/formation 分开读取。Hot 每页 24 条且 offset 有界,resident_total 独立报告;完整 CVE 可查整个驻留池,不能把缓存视为持久事实。
资料库按公开 Source/Artifact、材料质量和当前修订筛选,保留 as_of 页坐标和词法多标签分类。事件库读取 durable incident 与证据绑定的编目时间线;候选信号与确认、工具里程碑与攻击事件分别表达。
Product Document excerpt 只在 Source 明确 public、选中 Observation 至少有一个 Artifact 且所有 Artifact 明确 public 时返回,最多 1,200 字符;否则连同 chunk/locator 隐藏。Evidence 详情的来源片段与对象级 EvidenceLink 不冒充逐句事实证明。
自然调查请求根据稳定目标或会话上下文进入 InvestigationRole;元数据不满足安全调查正文需求。Perception 的 lexical/dense/inspect 读取绑定确切 DocumentRevision Observation 的现有 EvidenceLink,不借用新版引用。已支持但未满足完整 EvidenceNeed 的发现可经 StatePatch Gate 保留,Need 继续开放。
M5 规划同时看来源 Percepts 与未重复的状态拒绝反馈。委派子任务失败/阻塞/取消后父任务得到明确反馈,可以转向其他授权来源或有界停止;不把失败委派当成功证据,也不等待已经终态的子任务。
正常完成或受控 BLOCKED episode 通过独立 DecisionRole 形成阶段报告;FAILED/CANCELLED 不伪装为成功。M6 新发现缺口时先经 M4 接受 continuation,再以新 revision 提交报告;未知或开放 Need 保持 Case waiting。每个会话回合绑定自己的 immutable Decision,后续 Case current decision 不覆盖旧答案。
当前各项实现、测试和未完成范围见 当前验收台账。原 2026-10-08/11 的在线调试细节已移至 联调历史,不再和当前操作合同混排。
信息质量与 HTML 情报报告 冻结本轮补充合同:WORLD 仍为无模型读取,但过滤错误页、正文污染并保留真实日期语义;公开编研使用独立 M4 tentative → M6 Decision → 审校 → publication 写入流程。私人 HTML 沿用账户权限,公开目录只读通过审校且来源当前仍公开、修订未变化的发布记录。模型输出自然段与有界章节,受控 renderer 生成 HTML;生成不改变权限。具体 prompt、幂等、UTC 窗口、失效及验证边界见该页。实现迁移为 20261015_0036(公开研报)与 20261015_0037(自动编研任务),代码已合入默认 main。
当前产品与验收
需求与设计
- 需求基线 · EN
- 技术设计 1 · EN
- 技术设计 2 · EN
- 技术设计 2A · Application Backend
- 技术设计 3 · Measurement & Evaluation
- 架构图 1:中文 · EN
- 架构图 2:中文 · EN
- 项目网站
专题
- 01 数据源与处理 · EN
- 02 CTI 基线与复用 · EN
- 03 规范知识与政策 · EN
- 04 模型数据与应用安全 · EN
- 05 事件与新闻情报 · EN
来源