架构
V1 部署说明。 下方的实体列表命名对若干表使用了遗留的 "case_*" 前缀;实际的 V1 schema 名称为:
cases、investigation_runs、investigation_events、investigation_iocs、investigation_assets、investigation_links、investigation_outbox、proposals。为向后兼容,cases表名保持不变,但每个调查的子表都统一使用investigation_*前缀。其中,cases / investigation_runs / investigation_events 表由当前的编排器(orchestrator)使用;proposals与investigation_outbox已存在于 schema 中,但消费它们的执行器(executor)一侧仍在路线图上。请将本页理解为架构意图;确切的 schema 请查阅src/soctalk/core/ir/models.py。
1. 核心实体
最小化的形态。完整的列清单存在于迁移文件中;此处仅列出承载关键作用的字段。
alerts raw ingest from adapter; AI-triaged
cases investigation unit; one run at a time
case_runs a single AI execution span against a case
case_events ordered event inbox per case (immutable)
proposals AI-proposed actions awaiting human gate
execution_log append-only audit of all meaningful actions
notes markdown / evidence blocks
iocs typed artifacts; carry external_context
case_iocs, case_assets bridge tables
case_links related-case edges (shared IOC / asset / rule)
case_outbox outbound work for executors and exports每一行承载内容的记录都带有 tenant_id、visibility 和 created_at。RLS 按租户维度生效。
2. 可见性模型
分类(枚举):
mssp_only default; internal reasoning, raw tool output, hypotheses
customer_safe approved for customer view
system lifecycle and state-change events, always visible
tool_output classified per-tool at registration time规则:
visibility是每一行用户可见记录上的一列(消息、笔记、 proposals、tool_output 记录、时间线条目、事实面板字段)。- 插入时的默认值为
mssp_only。提升为customer_safe是一个 显式操作。 - 客户门户查询在 RLS 策略层过滤,而非在 渲染层。客户查看者会话即使通过原始 SQL 也无法读取
mssp_only行。 - Proposals 具有字段级可见性:
{action, outcome}可以是customer_safe,而{rationale, blast_radius}保持mssp_only。 渲染为两个投影。 - 每一次可见性提升都会产生一条带有执行者 与理由的
execution_log条目。
默认拒绝提升(Default-deny-promotion):策略可以降低可见性,但在没有 经授权主体的显式操作前不得提升。
3. Run 生命周期
状态:
active run consuming events and taking steps
waiting_on_gate a proposal is pending; run does not mutate state
halted_budget budget exceeded; requires analyst resume
paused analyst-paused
completed case closed
failed unrecoverable error; requires analyst resume or restart转换:
active → waiting_on_gate on proposal created (status = proposed)
waiting_on_gate → active on proposal approved/rejected (new event)
active → halted_budget on budget exceeded
halted_budget → active on analyst resume (grants new budget)
active → paused on analyst pause
paused → active on analyst resume
active → completed on case close
* → failed on uncaught error, preserved for diagnosis不变式:
- 每个 case 至多有一个处于
active | waiting_on_gate | halted_budget | paused状态的 run。通过case_runs(case_id) WHERE status IN (...)上的部分唯一索引强制执行。 - run 上的预算计数器:
tokens_used、dollars_used、tool_calls_used、wall_clock_ms。在服务端强制执行;在 75% 时 软告警,在 100% 时硬性停止。 - 处于
waiting_on_gate的 run 不处理收件箱事件,除了 闸门解析事件(proposal.approved / .rejected)。
4. 事件收件箱、排序、合并与幂等性
一个 case 的所有传入工作都落入 case_events:
event_id uuid PK
case_id FK
run_id FK nullable
seq bigint, case-scoped monotonic (sequence)
kind enum (alert_ingested, tool_result,
proposal_approved, proposal_rejected,
analyst_message, analyst_correction,
budget_warning, external_signal, ...)
payload jsonb
causation_event_id uuid nullable (which event caused this one)
correlation_id uuid (spans a causally-related fan-out)
idempotency_key text unique per case
created_at timestamptz规则:
seq在插入时由 case 作用域的序列发放。消费者严格 按seq顺序读取。idempotency_key在每个case_id内唯一。重复插入会被 静默丢弃(返回已存在的行)。- 合并:在插入前,匹配
(case_id, kind, payload.signature, window)的事件会合并为单行。签名是 随 kind 而定的(alert:IOC + 规则 + 资产的指纹;tool_result: tool_id + 参数哈希)。 causation_event_id为重放(replay)链接因 → 果。correlation_id将来自单个外部触发或分析师操作的事件分组。- 事件是不可变的。更新以后续事件的形式表达。
突发示例:5 分钟内 100 条相似的主机告警合并为一条 alert_ingested 事件,携带 asset_ids: [...] 列表。run 只处理它一次。
5. Proposal 生命周期与执行契约
状态:
draft being composed by the AI
proposed submitted to human gate
approved human approved (with typed reason if required)
rejected human rejected (reason required)
executing outbox picked up; executor running
executed action complete, result recorded
rolled_back post-execution reversal (rare, analyst-initiated)
failed executor error幂等性:
proposal.idempotency_key = sha256(case_id || action_type ||
canonical_json(params))在活动窗口内(默认 15 分钟)的重复 proposals 会在插入时被 拒绝。保证 AI 即使在重跑(re-run)下也不会重复触发。
闸门行为:
- 在
proposed时:run 转换为waiting_on_gate。 - 在
approved时:向case_outbox插入一行,带kind = 'execute_proposal'、idempotency_key = proposal.idempotency_key。 向case_events发出proposal_approved。run 恢复。 - 在
rejected时:向case_events发出带理由的proposal_rejected。run 恢复。不产生 outbox 行。
执行:
- 独立的执行器(executor)worker 消费
case_outbox并执行 操作。 - 成功时:向
case_events记录execute_proposal_result, 将 proposal 更新为executed,写入execution_log条目。 - 失败时:记录错误,将 proposal 更新为
failed,写入execution_log条目。run 可以提议重试。 - 通过
idempotency_key实现恰好一次(exactly-once):具有重复键的 outbox 行 会被拒绝。执行器 worker 以租约(lease)领取行(例如,FOR UPDATE SKIP LOCKED)。
AI run 不会内联执行副作用。所有内容都 经由 outbox。
6. 执行日志 schema 与不变式
仅追加(Append-only),与会话分离:
log_id uuid PK
case_id FK
run_id FK nullable
actor_kind enum (ai, human, system, executor)
actor_id text
kind enum (tool_call, proposal_state_change,
approval, override, visibility_promotion,
correction_applied, policy_bound,
export_emitted, ...)
subject_type enum (case, proposal, ioc, asset, note, ...)
subject_id text
before jsonb nullable
after jsonb nullable
versions jsonb (model_id, prompt_version, template_version,
policy_version at time of action)
ts timestamptz default now()不变式:
- 不允许应用角色执行 UPDATE 或 DELETE。仅允许 INSERT + SELECT。 在 Postgres 角色授权层强制执行。
- 每一次 proposal 状态变更、每一次工具调用、每一次批准、 分析师对 AI 决策的每一次覆盖、每一次可见性变更、 每一次纠正、每一次 outbox 分派都会写入一行。
versions捕获产生该操作的技术栈。这是可复现性 与事后校准(post-hoc calibration)所必需的。- 会话是事件子集的渲染视图;它不是 审计。销毁或压实(compacting)会话不会销毁审计。
7. 事实面板权威性与纠正流程
结构化的 case 状态(假设、IOC、资产、时间线摘要、 置信度、活动指令)是 case_events 之上的 reducer 输出。 它绝不由会话直接变更。
规则:
- 会话消息不写入结构化状态。
- AI 对结构化状态的更新通过 AI 发出的事件进行 (
hypothesis_updated、ioc_added、asset_linked)。 - 分析师在事实面板中的编辑会发出
analyst_correction事件。 reducer 应用它们。AI 将该纠正作为下一个 收件箱事件消费,并从纠正后的状态重新推理。 - 事实面板与
case_events最终一致。系统维护一个 物化投影(表或视图);读取可以 直接命中它。 - 禁止对执行日志进行直接纠正;纠正 以新事件加上一个指向被纠正事件的指针来表达。
8. 工具能力分类法
每个工具在注册时都带有一个能力类别、一个默认审批 策略和一个成本模型。
能力类别:
read_local inspect SocTalk state only
read_external_silent no target footprint (feeds, cached intel, vector)
read_external_attributed trace at target (SIEM query, EDR read)
write_sandbox footprint without target mutation (detonation)
write_external target state change (block, isolate, notify)每个类别的默认审批策略:
read_local → autonomous
read_external_silent → autonomous
read_external_attributed → analyst_approve
write_sandbox → analyst_approve
write_external → typed_reason每个工具的成本模型:{tokens_est, dollars_est, wall_ms_est, footprint}。 run 预算跟踪其总和。
9. 策略优先级
策略按此顺序合并,靠后的覆盖靠前的:
1. install default (shipped in chart, read-only in v1)
2. tenant override (MSSP sets per customer)
3. case template (phishing, ransomware, etc.)
4. case-local override (set for this one case by analyst)对每个策略键(工具审批、自动关闭、可见性提升、 响应模板、预算),有效值是定义它的最深作用域。
不变式:
- 在 install 作用域下,可见性提升绝不会默认设置为
permissive。默认是“需要显式提升”。 - 租户策略不能覆盖 install 级别的硬性上限(例如,
max_tokens_per_case)。 - case 本地覆盖的作用域限定于该 case,不会延续到 未来的 case。
10. 自动关闭 / 重开语义
针对高置信度误报的自动关闭:
Trigger:
AI assessment = fp, confidence ≥ policy.auto_close_threshold
AND policy.auto_close_enabled is true for the tenant
AND no active directive prevents auto-close
Action:
case.status = 'auto_closed_fp'
case.reopen_window_until = now() + policy.reopen_window
case.reopen_signature = {
ioc_fingerprints: [...],
asset_ids: [...],
time_window: {start, end}
}
run transitions to completed
execution_log row written重开:
Trigger:
new case_events row with kind ∈ {alert_ingested, external_signal}
whose signature intersects a case's reopen_signature
where case.status = 'auto_closed_fp'
AND now() < case.reopen_window_until
Action:
case.status = 'active'
emit reopened event into case_events
new run created
execution_log row written
conversation receives a system message noting the reopen熔断开关(Kill switch):
- 每个租户的
IntegrationConfig.auto_close_enabled(默认:开)。 - 每种 case 类型的
CaseTemplate.auto_close_disabled。
11. TheHive 导出契约(基于 outbox,单向)
当租户启用了 thehive_export_enabled 时,将 cases、IOC 和选定的笔记 向外镜像到 TheHive。绝不接受入站变更。
Outbox 行(在 case_outbox 中):
id uuid PK
kind 'export.thehive.case' | 'export.thehive.ioc' | ...
external_system 'thehive'
external_ref TheHive object id (filled on first successful mirror)
object_type case | ioc | note
object_id internal subject id
idempotency_key sha256(object_type || object_id || state_hash)
payload jsonb
export_status pending | in_flight | succeeded | failed | skipped
attempts int
last_error text nullable
next_attempt_at timestamptz
created_at, updated_at规则:
- 被镜像对象上的状态变更会入队一行带有 全新
idempotency_key的导出行(其中纳入了状态哈希)。 - Worker 以
FOR UPDATE SKIP LOCKED领取。成功时,记录external_ref(按需在 TheHive 一侧创建或更新)并 写入 execution_log。 - 来自 TheHive 的入站 webhook 仅对只读 仪表盘 case 接受(非 v1)。任何接受入站状态的尝试都会被 显式拒绝并记录。
- 无对账循环(reconciliation loop)——TheHive 是下游镜像,真相之源 是 SocTalk。
- 失败的导出以指数退避重试直至上限;永久性 失败会在集成健康面板上显现。
12. 强制测试与不变式
测试套件(单元 + 集成)必须覆盖:
- 执行日志不可变性。 从应用角色对
execution_log执行 UPDATE 和 DELETE 会在 Postgres 层失败。 - 每个 case 单个活动 run。 并发尝试创建 第二个活动 run 会因唯一约束违反而失败。
- Proposal 幂等性。 在窗口内提交两个具有相同 幂等键的 proposals:第二个会被拒绝。
- 闸门暂停行为。 带有
proposedproposal 的 run 不会 从其收件箱消费非闸门事件。 - Outbox 恰好一次。 两个 worker 领取同一个 outbox 行 会导致一个成功、一个空操作(no-op)。
- 可见性强制。 客户查看者会话即使使用原始 SQL,也无法从任何表 select
mssp_only行。 - 可见性提升已记录。 每一次从
mssp_only到customer_safe的提升都会产生一条execution_log行。 - 纠正流程。 分析师纠正事件会产生一个新事件, reducer 应用它;事实面板投影反映该 纠正。
- 自动关闭重开。 在窗口内匹配某个 reopen_signature 的事件 会重开该 case 并启动一个新 run。
- TheHive 导出幂等性。 对状态未变化的对象 重跑导出是一个空操作(相同 idempotency_key)。
- 工具审批策略。 没有 typed_reason 批准的
write_external工具调用无法到达执行器。 - 策略优先级。 对同一策略键,case 本地覆盖优先于租户, 租户优先于 install。
13. 本规范范围之外
- 组件模型、可视化行为、命令栏解析 → 会话 UI 工作流。
- 战役关联、评分、跨租户机制 → 战役(campaigns)工作流。
- 提示词库、LLM 工具注册表内容、模型版本策略 → 待我们着手时的独立 LLM 运行时工作流(LLM runtime)。
