# ARCHITECTURE 本文件记录 `dsh-outcome-loop` 的架构决策与实现规则。权威行为约束见根目录 `AGENTS.md`(全局开发指导);本文档与代码冲突时,以 `AGENTS.md` 与代码为准并更新本文档。 ## 1. 分层与依赖方向 ```text consumers (命令/投影) ──只调用──▶ service (ctx.outcomeLoop) │ ┌───────────────────┼─────────────────────┐ ▼ ▼ ▼ verification persistence dsh 适配层 (engine/policy/ (storage-domain (observer/replay/ registry/adapters) sidecar) token/feedback) │ │ │ └───────── 纯 domain(无 DSH/IO/时钟)─────────┘ ``` 规则(`AGENTS.md` §9): - `domain/` 是纯 TypeScript:不访问文件、网络、时钟或 DSH;所有时间以 Unix epoch ms 传入; - `dsh/` 只负责把 DSH 类型与事件转换为领域输入; - `persistence/` 负责 schema、CAS、幂等、队列与修复; - `verification/` 只通过显式 provider 接口访问外部事实; - `export/` 是唯一允许构造可分享数据集的路径; - UI、命令与模型工具不包含领域真相,只调用 service; - 不允许循环依赖;不允许 domain import adapter。 ## 2. 数据流 ### 2.1 会话观察(`dsh/observer.ts` + `dsh/registry.ts`) - 热路径只订阅 `session/event`(Cordis post-commit 通知,签名 `(session, event)`); - 每个事件经 `dsh/events.ts` 归一化为 0..n 个 `SessionFact`:常数大小、无正文复制(只存 digest/计数/seq/退出码); - 事实写入 per-session 内存 `FactRegistry`(可重建的派生索引),并按 `(sessionId, seq)` 去重; - 有契约的 session 同步推进持久化 cursor(`session_cursors` 表)。 ### 2.2 重放(spec §8.3) - durable `session/event` 只发布新 append;constructor seed 不重发; - 因此历史通过快照填充恢复:插件启动时扫描 `ctx.sessions.list()`,并在 `session/created`(resume/fork)时重新填充; - 可选 `sessionPersistence.inspect()` 用于冷会话读取(best-effort,缺失时核心功能照常); - 重复投递由 high-water seq 去重;seq gap 不允许猜测 —— 无权威日志可读时,该 session 不产生事实(criterion 保守为 `unknown`)。 ### 2.3 冷会话回放(spec §8.3 规则 5) 验证时若契约 session 尚无事实日志且挂载了可选 `sessionPersistence`,`service.verify` 先 best-effort 回放权威日志(`inspect()`)再验证;无该服务时该 session 不产生事实,criterion 保守 `unknown`。 ### 2.4 结构化测试报告(beta.3) - 事件归一化对测试命令输出做 TAP 解析(内存中完成,只存计数)——`test-report` 验收使用真实 passed/failed/skipped 计数而非退出码代理; - active 路径:`junit` 框架读取 workspace 内 `reportPath` 的 JUnit XML;`tap` 框架运行 `command` 并解析其 TAP 输出(仍受四重策略门约束); - 解析器纯函数、无依赖(`verification/adapters/tap.ts`、`junit.ts`)。 ### 2.5 验证(`verification/engine.ts`) 1. 被动适配器从事实日志 + 先前证据行计算每个 criterion 的初步判定(spec §12.2:观察不到足够事实 → `unknown`,**绝不**为拿标签自动重跑命令); 2. 仅当所有策略门(部署 `autoRun` + 契约 `verificationPolicy.autoRun` + scope `allowActiveVerification` + verifier 白名单 + 绝对 workspaceRoot)都打开时,才运行 active verifier(spec §12.3:argv+cwd、无 shell 拼接、env allowlist、timeout、output cap、AbortSignal、默认只读、默认无网络); 3. 匹配事实持久化为不可变 Evidence 行(确定性 id → 幂等); 4. 用 freshness(contract revision / verifier version / workspace epoch / maxAge)筛选当前证据;冲突证据 → `inconclusive`(规则 6); 5. 聚合(`domain/reducer.ts` 规则 1–9)→ 持久化 `VerificationRun`。 标签强度由 `evidenceLabelStrength()` 从实际证据行的 strength 计算:strong(机械确定性)/ medium(用户确认)/ weak(仅 judge,本版本无 judge)/ unknown。 ### 2.6 导出(`export/`) preview(`previewExport`)→ 用户批准 digest(`exportJsonl` 重算并比对,内容变化即 `export-approval-invalid`)→ 写入。导出记录由权威 records 派生,永不反向修改账本。 ## 3. 存储设计(spec §8.4) - domain `outcome_loop` v1,表:`contracts` / `evidence` / `verification_runs` / `dispositions` / `session_cursors` / `exports`; - 权威 record 先写、派生 index 后写;所有派生 index 可由权威 records 重建(`repair.ts` 在启动时清理孤儿 + 可选保留窗口裁剪 `retention.evidenceMaxAgeMs`,默认 0 不裁剪); - 单进程内 storage-domain 的单写链提供每域串行化;跨进程 CAS 不做宣称(见 SECURITY.md §多进程); - 返回给调用方的对象一律 detached + frozen。 ## 4. 为什么不在 session log 里写 outcome - outcome 含可删除、可修订的用户 disposition 与导出资格; - 证据摘要可能敏感,不应自动进入 session telemetry; - 任务级聚合不是模型历史的一部分; - 用户需要独立删除与迁移。 因此默认 sidecar;未来如需 session event,只能保存最小、非敏感、不可变关联指针。 ## 5. 已做/未做的产品决策(spec §24 状态) | 决策 | 状态 | | --- | --- | | Task Contract 首个创建入口 | 已定:人类命令 `/outcome new` + Host API(保守默认) | | MVP 是否包含 active verifier | 已实现但**默认关闭**;全部策略门打开才运行 | | outcome Web UI 与核心包同 release | 提供可选 projection consumer(headless 安全),无独立 UI 页面 | | sidecar 默认保留期与删除交互 | 不设默认保留期;`/outcome delete --yes` 显式删除 | | 发布方式 | 先 GitHub tag + tarball(`dsh plugin add`),npm 发布待定 | | 首批兼容 DSH 范围 | `0.1.0-rc.7`(见 COMPATIBILITY.md) | | Windows 支持 | 不在首个矩阵;路径代码已做跨平台防护,未宣称支持 | | dsh-code-reference 集成 | 独立安装、单向可选(§7 设计) | | export v1 是否允许消息正文 | 不允许;`privacy.content_included` 恒为 false | | 企业策略文件信任机制 | 未实现;仓库内配置**永不**授予命令/网络权限 | ## 6. 已知限制(诚实声明) - 被动 command 匹配基于工具名 + `command` 参数摘要;模型以非常规方式执行同一命令时可能不匹配 → `unknown`,不会伪造 pass; - 任意 bash 写入不跟踪为 workspace 变更(只有显式写文件工具 bump epoch)——相关 criterion 依赖 active verification 或 import 提供 file-state 证据; - 冷 session 且无 `sessionPersistence` 时,历史事实不可重建 → 证据缺失 → `unknown`; - `session/event` 热路径不做磁盘 IO;写入经 per-session 队列异步完成。 ## 7. 契约文件(`outcome-loop.contract.v1`) `/outcome import` / `export-contract` 使用版本化 JSON 文件(`src/export/contract.ts`)。文件是用户显式指向的输入:不自动发现、不隐式信任,逐字段 zod 校验;导入契约沿用保守策略默认(`autoRun: false`、`private-only`)。注意:契约文件包含 explicit goal 文本(用户自有数据),与**导出记录**(默认最小化、只含 digest)是两回事。 ## 8. 企业策略(beta.4,ADR-0006) `enforceEnterprisePolicy`(纯函数,`domain/aggregate.ts`)在 `createContract`/`reviseContract` 处强制部署配置中的 `enterprise` 段(仅 `mode: 'enterprise'`)。策略来源**只有**部署配置;workspace 文件永不读取为策略。verifier 白名单、必需 criterion 种类、最小 criterion 数均可强制。 ## 9. 校准与成本(§15、§8.6) - `dsh/calibration.ts`:decision 证据 × 实际结果的相关性(observation 六分类),纯描述性; - `/outcome cost --summary`:跨契约 token 聚合,仅校准用途;价格永不硬编码(`test/privacy` 断言无价格字面量)。 ## 10. Skill 候选(beta.5,§21.7.5) `dsh/skills.ts` 从用户自己的账本聚合主题与验收模式,输出 `SkillsReport`(主题行 + 候选建议)。候选仅在「某主题 ≥2 个通过契约共享同一验收种类」时出现,且永远只是**展示给人工**——不自动应用、不修改 skill、不把单次成功轨迹固化为规则(§22;测试断言输出文案包含 "never auto-applied")。 ## 11. 贡献模式(beta.5,ADR-0005) 独立消费者 `outcome-loop-contribute`:默认未安装、默认禁用;启用后 `/contribute` 提供 preview → approve(确定性脱敏门阻断任何敏感命中)→ 数据集目录(版本化 consent manifest + records/summary);revoke = 删除目录。**无上传通道**;`cordis.patch.yml` 不含该行(见 README 手动安装示例)。 ## 12. 与 dsh-code-reference 的可选集成 outcome-loop 暴露 `recordDecisionEvidence()`(source: `'dsh-code-reference'` 等,`decision` 证据类型)作为通用 decision-evidence 入口;code-reference 或桥接插件可主动提交 PriorDecisionEvidence(决策 id、strategy、predictedMatch/effort、policyDigest)。`decision` 证据分类 internal、永不参与验证判定(`impliesVerdict` 返回 unknown)——它是用户的校准数据,不是成功因果。集成方不得 import 本包内部文件;启发式相似度**不**等于真实复用收益;候选仓库完整代码/README 永不保存。