Detailed design: close five R-series regressions from Opus 4.7 audit
Independent regression audit (Opus 4.7) verified P1×4 + P2×9 fixes were closed but found that three of the P2 fixes had introduced new baseline violations and two had minor errors. This commit closes all five. R-series fixes (in system-detailed-design.md): - R1-01 §10.2: L2 Safety source no longer names a fictional PermissionEngine.current_profile() method. The L2 row now describes the active permission profile sources (~/.air/permissions.yaml + project permission config) without inventing a contract method, honoring DD §0 "no new public contracts." - R1-02 §5.4 Table A: agent.started projection no longer claims a two-step "starting → running" update within a single event commit (which would violate event atomicity). The row now matches event-registry §3: a single status (starting or running) at emission time, with the follow-up transition handled by WorkerManager per the state machine in §20.4. - R1-03 §18.4 + §5.4 Table B: removed the non-baseline phase: intent | committed payload-field extension from memory.promoted and debug.record.created. Outbox semantics now follow the baseline model: owning store performs the external write first, then ingests a single durable completion event whose payload matches event-registry §3 exactly. Any future intent/commit split must go through an ADR plus payload version bump. - R2-01 §7.5: corrected workspace responsibility cross-reference from "overview §10.5" (Direct mode) to "overview §10.3" (Scheduler state machine, which actually covers workspace assignment and merge). - R2-02 §3: resolved self-contradiction in the contracts file-set decision. The 16 files are now stated as mandatory with default inlining of all overview §4 symbol groups for V1.0.0 Alpha; any future split is explicitly out of scope until an ADR is added. Also adds opus4.7详细设计与UML审查.md documenting the regression findings, baseline-evidence trails, and recommended fixes that drove these changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
369
AirPlan/docs/architecture/opus4.7详细设计与UML审查.md
Normal file
369
AirPlan/docs/architecture/opus4.7详细设计与UML审查.md
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
# Opus 4.7 系统详细设计与 UML 类图回归审查
|
||||||
|
|
||||||
|
Date: 2026-06-01
|
||||||
|
Reviewer: Claude Opus 4.7 (independent regression audit)
|
||||||
|
Scope: 修复后回归审查 — 验证 P1×4 + P2×9 修复是否真正闭合,且未在修复过程中引入新偏差
|
||||||
|
Inputs:
|
||||||
|
- 详细设计:`system-detailed-design.md`(2264 行,23 章节,commit `543743b`)
|
||||||
|
- 受冻结基线:22 份基线 + 已冻结概要设计
|
||||||
|
- 此前审查:DeepSeek / MIMO 2.5 Pro / GPT-5.5 Pro / Opus 4.8 四轮审查 + 汇总
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 审查方法
|
||||||
|
|
||||||
|
本次为**回归审查**,不重复覆盖性扫描,而是:
|
||||||
|
|
||||||
|
1. **P1×4 修复验证**:检查每项修复是否真正落地、文字是否清晰、是否与基线一致
|
||||||
|
2. **P2×9 修复验证**:同上,并特别检查"修复过程中是否引入新的偏离基线条款"
|
||||||
|
3. **跨文档一致性**:DD 修复后是否仍与 contracts、event-registry、runtime-semantics、scope-escalation 等基线一致
|
||||||
|
4. **新发现**:审查过程中浮出的、之前轮次未发现的新问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 总览结论
|
||||||
|
|
||||||
|
| 维度 | 状态 |
|
||||||
|
|---|---|
|
||||||
|
| P1×4 修复闭合性 | ✅ 4/4 已闭合 |
|
||||||
|
| P2×9 修复闭合性 | ⚠ 9/9 文字已落地,但 3 项引入新基线偏离 |
|
||||||
|
| 新增 P0 | 0 |
|
||||||
|
| 新增 P1(修复引入的回归) | **3** |
|
||||||
|
| 新增 P2 | 2 |
|
||||||
|
| 覆盖性 | ✅ 维持 100% |
|
||||||
|
| 架构合理性 | ✅ 维持,无循环依赖 |
|
||||||
|
| 综合判定 | **CONDITIONAL PASS(次轮)** — 需收束 3 项 P1 回归 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. P1×4 修复验证
|
||||||
|
|
||||||
|
### 3.1 P1-01: Worker exit code 语义冲突 — **✅ 已闭合**
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| DD §8.1 与 `baselineV1.md §8` 对齐 | ✓ 完全一致(6 个 code 含义逐字对应)|
|
||||||
|
| overview §11 与 baselineV1 对齐 | ✓(commit `8d9c420`/`453df09` 中验证)|
|
||||||
|
| 任务结果与 exit code 解耦说明 | ✓ DD §8.1 末尾"Design decision"明确:任务 status 走 `WorkerResult.status`,exit code 仅表达进程协议层语义 |
|
||||||
|
| 表格格式与 baselineV1 一致 | ✓ |
|
||||||
|
|
||||||
|
**结论**:完全闭合。无残留歧义。
|
||||||
|
|
||||||
|
### 3.2 P1-02: PromptLayerLevel 枚举与 L0-L9 不对齐 — **✅ 已闭合**
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| `interface-contracts-v1.md §16` 增加 `"safety"` 枚举值 | ✓ |
|
||||||
|
| 11 个枚举值(含 `system_debug`)映射到 L0-L9 + 系统调试 | ✓ |
|
||||||
|
| DD §10.2 表 1(枚举 → L# → loader)正确显示 11 行 | ✓ |
|
||||||
|
| `system_debug` 注释"applied within L9 when present" | ✓ 与 prompt-layering-v1 §2 注脚对齐 |
|
||||||
|
|
||||||
|
**结论**:完全闭合。L0-L9 全部有显式枚举对应。
|
||||||
|
|
||||||
|
### 3.3 P1-03: EventStore.project() 错误处理未定义 — **✅ 已闭合**
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| DD §5.3 包含"Error handling for `project()`"块 | ✓ |
|
||||||
|
| 异常 → 事务回滚 → `EventBus.publish()` 不触发 | ✓ |
|
||||||
|
| 返回 `AirError{kind: "system_error"}` | ✓ |
|
||||||
|
| FK-off 不一致触发 `SessionStore.referential_check()` | ✓ |
|
||||||
|
|
||||||
|
**结论**:完全闭合,并与 §4.3、§18.3 形成闭环。
|
||||||
|
|
||||||
|
### 3.4 P1-04: PromptLayerLoader 接口不完整 — **✅ 已闭合**
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| DD §10.2 第二张表(L2/L4/L6/L7/L8/L9 → ContextAssembler 内部装配)| ✓ |
|
||||||
|
| 显式说明 PromptLayerLoader 只覆盖 L0/L1/L3/L5 | ✓ |
|
||||||
|
| Runtime roles(main/architecture/scheduler)不走 `load_role()` 的设计决策 | ✓ |
|
||||||
|
| 与 §2"Worker AgentType vs. runtime roles"块互为印证 | ✓ |
|
||||||
|
|
||||||
|
**⚠ 次级发现**:L2 Safety 来源写为 `PermissionEngine.current_profile()`,但 `interface-contracts-v1.md §13` 的 `PermissionEngine` 接口**只声明 `evaluate()` 与 `record()` 两个方法**。`current_profile()` 是基线之外的方法引用 → 见 §5.3 新发现 R1-01。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. P2×9 修复验证
|
||||||
|
|
||||||
|
| ID | 文字落地 | 内容质量 | 新偏离基线 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| P2-01 Architecture gate 序列图 | ✓ §19.4 | ✓ 完整 | 无 |
|
||||||
|
| P2-02 CLI catalog 命令类归属 | ✓ §17 | ✓ 11 个 Command 类 | 无 |
|
||||||
|
| P2-03 agent.started 投影措辞 | ✓ §5.4 表 A | ⚠ | **是** → R1-02 |
|
||||||
|
| P2-04 WorkspaceManager 职责矩阵 | ✓ §7.5 | ✓ | ⚠ 引用错误 → R2-01 |
|
||||||
|
| P2-05 §5.4 拆分两张表 | ✓ §5.4 | ✓ Table A/B 分离清晰 | 无 |
|
||||||
|
| P2-06 memory.promoted 两相 | ✓ §18.4 | ⚠ | **是** → R1-03 |
|
||||||
|
| P2-07 docs task 闭合 | ✓ §8.3 | ✓ | 无 |
|
||||||
|
| P2-08 AgentType vs runtime roles | ✓ §2 | ✓ | 无 |
|
||||||
|
| P2-09 contracts 文件集冻结 | ✓ §3 | ✓ | ⚠ → R2-02 |
|
||||||
|
|
||||||
|
整体:**9/9 文字落地,但 3 项(P2-03 / P2-04 / P2-06 / P2-09)在修复过程中引入新偏离**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 新发现(回归引入)
|
||||||
|
|
||||||
|
### 5.1 [R1-01] P1-04 副作用:`PermissionEngine.current_profile()` 不在基线 — **P1**
|
||||||
|
|
||||||
|
**位置**:DD §10.2 "Design decision — Layer loading responsibility" 表,L2 Safety 来源
|
||||||
|
|
||||||
|
**问题**:
|
||||||
|
```text
|
||||||
|
L2 Safety → PermissionEngine.current_profile() + ~/.air/permissions.yaml + project permissions
|
||||||
|
```
|
||||||
|
|
||||||
|
但 `interface-contracts-v1.md §13` 的 `PermissionEngine` 接口仅声明:
|
||||||
|
```ts
|
||||||
|
export interface PermissionEngine {
|
||||||
|
evaluate(context: PermissionRequestContext): Promise<PermissionDecision>
|
||||||
|
record(decision: PermissionDecision, context: PermissionRequestContext): Promise<PermissionRecordResult>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`current_profile()` 是详细设计层引入的方法名,没有对应的契约方法。这违反了 DD §0 "does not introduce new public contracts" 的约束。
|
||||||
|
|
||||||
|
**根因**:P1-04 修复时为给 L2 Safety 找一个明确来源,引用了一个并不存在的方法名。
|
||||||
|
|
||||||
|
**建议处理**:
|
||||||
|
- 选项 A:把来源改为"`PermissionEngine` 内部状态(实现细节)+ `~/.air/permissions.yaml` 文件 + 项目权限配置",避免点名一个不存在的方法
|
||||||
|
- 选项 B:在 `PermissionEngine` 契约中加 `current_profile()` 方法 → 需要 ADR 并修改 contracts §13(违反"基线冻结"约束)
|
||||||
|
- **推荐**:选项 A
|
||||||
|
|
||||||
|
**预计工作量**:5 分钟(文字调整)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2 [R1-02] P2-03 副作用:`agent.started` 引入"同事件内分阶段更新"概念,超出基线 — **P1**
|
||||||
|
|
||||||
|
**位置**:DD §5.4 Table A 中 `agent.started` 行
|
||||||
|
|
||||||
|
**当前文字**:
|
||||||
|
```text
|
||||||
|
insert `agents` row with `status='starting'` upon spawn intent, then update to
|
||||||
|
`status='running'` on the same event's commit when WorkerProcess handshake has
|
||||||
|
succeeded (`workers` ack); domain row carries final state at commit time
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:这段话描述了"同一个事件提交过程中先 starting 再 running"的语义,事件投影在一次事务内对同一行做 insert+update。
|
||||||
|
|
||||||
|
但 `event-registry-v1.md §3` 对 `agent.started` 的定义是:
|
||||||
|
```text
|
||||||
|
Domain update: insert `agents` row with `status = running` or `starting`.
|
||||||
|
```
|
||||||
|
|
||||||
|
即基线明确"插入时 row 的 `status` 等于 `running` 或 `starting` 其一",**不是两步**。状态从 `starting` 到 `running` 的过渡由独立事件 / 后续状态机决定(参见 DD §20.4 中 `starting ──ready──▶ running`,但 baseline 并未对应 "ready" 持久事件)。
|
||||||
|
|
||||||
|
把握手成功"在同一事件提交时"反映到 row 上,事实上无法实现 — 因为 `agent.started` 事件已经在 `WorkerManager.spawn` 完成 handshake **之前或之后**就已经被持久化。
|
||||||
|
|
||||||
|
**根因**:P2-03 旧文字"insert agents (starting/running)"措辞被认为"含糊",修复时反而引入了一个**违反事件原子性**的描述。
|
||||||
|
|
||||||
|
**建议处理**:恢复为符合基线的清晰版本:
|
||||||
|
```text
|
||||||
|
| `agent.started` | insert `agents` row with `status = 'starting'` (initial spawn)
|
||||||
|
or `status = 'running'` (if handshake completed before the event is emitted);
|
||||||
|
final transition `starting → running` is recorded via the implicit state
|
||||||
|
machine in §20.4 without a dedicated durable event |
|
||||||
|
```
|
||||||
|
|
||||||
|
**预计工作量**:5 分钟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3 [R1-03] P2-06 副作用:`memory.promoted` 引入 `phase` 字段,扩展基线 payload — **P1**
|
||||||
|
|
||||||
|
**位置**:DD §18.4 "memory.promoted two-phase semantics" 表
|
||||||
|
|
||||||
|
**当前文字**:
|
||||||
|
```text
|
||||||
|
| Phase | Payload marker |
|
||||||
|
| Intent | phase: "intent", no artifact_ref/memory_id yet |
|
||||||
|
| Completion | phase: "committed", includes artifact_ref / memory_id |
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:`event-registry-v1.md §3` 的 `MemoryPromotedPayload` 定义为:
|
||||||
|
```ts
|
||||||
|
interface MemoryPromotedPayload {
|
||||||
|
candidate_id: string
|
||||||
|
target_ref: string
|
||||||
|
promoted_by: "user" | "curator" | "system"
|
||||||
|
summary: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**没有 `phase` 字段**。P2-06 修复引入的"intent vs committed"两相是 DD 层对 outbox 模型的额外扩展,事实上在基线中是通过**单一 `memory.promoted` 事件 + 上下游推断**完成的(runtime-semantics §6.4 直接说"session event records completed promotion and target ref",即单事件即为完成态)。
|
||||||
|
|
||||||
|
此外,DD §18.4 也对 `debug.record.created` 同样应用了 `phase` 字段("the same two-phase pattern (intent → committed) with payload field `phase`"),同样不在 `event-registry-v1.md` 的 `DebugRecordCreatedPayload`中。
|
||||||
|
|
||||||
|
**根因**:GPT-5.5 Pro 的原 P2-06 关注点是"outbox intent/completion 阶段不清"。修复时为了在 DD 层明确区分,新增了 `phase` 字段。但**契约层从未声明这个字段**,且 baseline `runtime-semantics §6.4` 实际上认为 `memory.promoted` 是单事件、完成态语义。
|
||||||
|
|
||||||
|
**建议处理**:去掉 `phase` 字段引入,改用"两个事件"或"前置 candidate + 单完成事件"叙述:
|
||||||
|
|
||||||
|
```text
|
||||||
|
**`memory.promoted` outbox flow**(不引入新 payload 字段):
|
||||||
|
|
||||||
|
1. `memory.candidate.created` 已持久化(已存在事件)
|
||||||
|
2. 决定 promote 后,业务侧执行外部 store 写入
|
||||||
|
3. 写入成功后发出 `memory.promoted` —— 这就是基线说的"completed promotion"
|
||||||
|
4. 写入失败:发出 `memory.archived` 或回退到一个新的 candidate;运行时不发未完成的 `memory.promoted`
|
||||||
|
|
||||||
|
如果未来需要严格的 outbox intent,应通过 ADR 引入新事件类型,而非给现有事件增加 payload 字段。
|
||||||
|
```
|
||||||
|
|
||||||
|
对 `debug.record.created`:基线 runtime-semantics §6.3 说"intent/completion event",但实际 payload 是单一 schema;建议同样不引入 `phase`,而是依赖 `debug.record.created` + `debug.record.failed`(如果未来加入)形成 outbox。
|
||||||
|
|
||||||
|
**预计工作量**:15 分钟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.4 [R2-01] P2-04 副作用:DD §7.5 引用 "overview §10.5" 不正确 — **P2**
|
||||||
|
|
||||||
|
**位置**:DD §7.5 "Responsibility split — Scheduler vs. WorkspaceManager"
|
||||||
|
|
||||||
|
**当前文字**:`**Responsibility split — Scheduler vs. WorkspaceManager** (overview §10.5, scheduler-state-machine §4/§MERGING)`
|
||||||
|
|
||||||
|
**问题**:`system-overview-design.md §10.5` 是 "Direct mode",不包含 workspace 职责描述。workspaces 描述实际位于 overview §10.3 (Scheduler state machine, 含 "workspace assignment", "workspace merge and conflict handling")。
|
||||||
|
|
||||||
|
**根因**:P2-04 修复时引用错章节号。
|
||||||
|
|
||||||
|
**建议处理**:把 `(overview §10.5, scheduler-state-machine §4/§MERGING)` 改为 `(overview §10.3, scheduler-state-machine §4/§MERGING)`。
|
||||||
|
|
||||||
|
**预计工作量**:1 分钟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.5 [R2-02] P2-09 副作用:file-set 决策中存在 self-contradicting 描述 — **P2**
|
||||||
|
|
||||||
|
**位置**:DD §3 "Frozen file-set decision (P2-09)" 块
|
||||||
|
|
||||||
|
**当前文字**:
|
||||||
|
```text
|
||||||
|
For V1.0.0 Alpha, the canonical contracts package file set is exactly the 16 files listed above
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:
|
||||||
|
|
||||||
|
1. 上方 ASCII 列表中实际上是 **15 个文件**(ids/error/event/runtime/ipc/task/worker-result/tool/permission/artifact/evidence/project/provider/ui/capability/platform = 16?让我数一遍:ids, error, event, runtime, ipc, task, worker-result, tool, permission, artifact, evidence, project, provider, ui, capability, platform = 16),数对了。
|
||||||
|
|
||||||
|
2. 但紧接着的 file-mapping 表说"context.ts symbols → runtime.ts + dedicated context.ts only if needed (otherwise inlined in runtime.ts)"以及"dedicated doctor.ts only if file size warrants split"等条目,**实际允许额外文件**。这与"exactly the 16 files listed above"自相矛盾。
|
||||||
|
|
||||||
|
3. "no new `.ts` files are added in the contracts package without an ADR" 是好的硬约束,但表中的"only if needed"放宽了它。
|
||||||
|
|
||||||
|
**根因**:P2-09 修复想同时表达"主体冻结" + "极少数情况可拆分",但语言模糊。
|
||||||
|
|
||||||
|
**建议处理**:明确两层:
|
||||||
|
- 冻结:16 个**必有**文件
|
||||||
|
- 可选:`context.ts` / `doctor.ts` / `knowledge.ts` / `diagnostics.ts` 在出现明确大小/分层需求时**可以追加**,追加同时需要更新 code-view §3 + ADR
|
||||||
|
|
||||||
|
或更严格:彻底冻结到 16 个,不预留追加口子。
|
||||||
|
|
||||||
|
**预计工作量**:5 分钟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 重新验证未受影响的领域
|
||||||
|
|
||||||
|
| 维度 | 状态 |
|
||||||
|
|---|---|
|
||||||
|
| 22 份基线契约覆盖 | ✅ 100% |
|
||||||
|
| 55 个 durable + 7 个 ephemeral 事件覆盖 | ✅ 100% |
|
||||||
|
| 19 张 DB 表的 Repository 覆盖 | ✅ 100% |
|
||||||
|
| 6 个状态机覆盖 | ✅ 100%(main agent, scheduler, task, agent, workspace, capability)|
|
||||||
|
| 10 条 forbidden edge | ✅ §2 显式继承,禁止路径无破口 |
|
||||||
|
| 8 个 UML 类图 | ✅ 全部存在(22.1-22.8)|
|
||||||
|
| 5 个序列图(含 P2-01 新增 gate)| ✅ 5/5 |
|
||||||
|
| 5 项 traceability 矩阵 | ✅ 5/5 |
|
||||||
|
| 依赖方向 | ✅ 无循环 |
|
||||||
|
| 事务语义 | ✅ project + publish 时序正确 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. UML 类图回归审查
|
||||||
|
|
||||||
|
**8 个 Mermaid 类图**全部存在且语法可解析,无新偏差。需要补充指出:
|
||||||
|
|
||||||
|
- **§22.1 EventSource.kind**:枚举 `"main"|"architecture_designer"|"scheduler"|"agent"|"tool"|"system"` 现与 §2 "runtime role" 表协同(P2-08 修复正确指向 `event.ts`)。✓
|
||||||
|
- **§22.3 Scheduler vs WorkspaceManager 关系**:图中 `Scheduler --> WorkspaceManager` 已暗示策略/机制层级,但**未引入 P2-04 矩阵中的"Scheduler 拥有 conflict resolution policy"语义**。建议是否在图旁加注 "Scheduler owns policy; WorkspaceManager owns mechanism"。
|
||||||
|
- **§22.5 WorkerRole 继承**:5 个 Role 类全部出现;P2-07 引入的 `docs` TaskType 通过复用 `ExecutorRole`,类图无需新增节点 — ✓ 与文字一致。
|
||||||
|
- **§22.7 ContextAssembler vs PromptLayerLoader**:图中 `ContextAssembler --> PromptLayerLoader` 是单向依赖,符合 P1-04 / P2-08 决策。建议补充 `ContextAssembler --> PermissionEngine`(用于 L2 Safety 加载)— 但若采纳 R1-01 选项 A,此依赖也应消失。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 门禁判定
|
||||||
|
|
||||||
|
| 条件 | 状态 |
|
||||||
|
|---|---|
|
||||||
|
| P0 = 0 | **PASS** |
|
||||||
|
| 契约覆盖 100% | **PASS** |
|
||||||
|
| 事件覆盖 100% | **PASS** |
|
||||||
|
| DB Schema 覆盖 100% | **PASS** |
|
||||||
|
| 状态机覆盖 100% | **PASS** |
|
||||||
|
| 禁止路径全执行 | **PASS** |
|
||||||
|
| 架构无循环依赖 | **PASS** |
|
||||||
|
| 职责分离无泄漏 | **PASS** |
|
||||||
|
| 此前 4 处 P1 已闭合 | **PASS** |
|
||||||
|
| 此前 9 处 P2 文字已落地 | **PASS** |
|
||||||
|
| 修复未引入新偏离基线 | **FAIL**(3 处 P1 回归 + 2 处 P2 回归) |
|
||||||
|
|
||||||
|
**门禁结果:CONDITIONAL PASS(次轮)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 修复优先级建议
|
||||||
|
|
||||||
|
| 优先级 | Finding | 建议处理 | 工作量 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **P1** | R1-01 `PermissionEngine.current_profile()` 不在契约 | 改写 L2 Safety 来源描述 | 5 min |
|
||||||
|
| **P1** | R1-02 `agent.started` 同事件内 starting→running 违反事件原子性 | 恢复为基线允许的"starting OR running" | 5 min |
|
||||||
|
| **P1** | R1-03 `memory.promoted` 引入未冻结 `phase` payload 字段 | 移除 phase 字段,回归基线 outbox 单事件叙述 | 15 min |
|
||||||
|
| **P2** | R2-01 §7.5 "overview §10.5" 引用错误 | 改为 §10.3 | 1 min |
|
||||||
|
| **P2** | R2-02 §3 file-set 表内 self-contradicting | 明确"16 必有 + 可选追加(需 ADR)"或彻底冻结 | 5 min |
|
||||||
|
|
||||||
|
**总预计工作量:30 分钟**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 多模型审查累积视角
|
||||||
|
|
||||||
|
| 轮次 | 模型 | 主要贡献 | 累积 P0 | 累积 P1 | 累积 P2 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| R1 | DeepSeek | 全量覆盖性 | 0 | 0 | 3 |
|
||||||
|
| R2 | MIMO 2.5 Pro | 内在一致性 | 0 | 2 | 5 |
|
||||||
|
| R3 | GPT-5.5 Pro | 基线冲突检测 | 0 | 2 (新) | 4 (新) |
|
||||||
|
| R4 | Opus 4.8 | 验证 + 根因 + 建议 | 0 | 2 (确认) | 5 (确认) |
|
||||||
|
| R5 | Opus 4.7(本次回归) | 修复闭合性 + 回归引入检测 | 0 | **3 (新回归)** | **2 (新回归)** |
|
||||||
|
|
||||||
|
**累积说明**:
|
||||||
|
- R1–R4 发现的 P1×4 + P2×9 已全部文字闭合(commit `453df09` + `543743b`)
|
||||||
|
- R5 发现修复过程引入新偏离基线的 3 P1 + 2 P2,需要再迭代一次
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 最终结论
|
||||||
|
|
||||||
|
### 11.1 修复有效性
|
||||||
|
|
||||||
|
- **P1×4 修复**:4/4 全部文字闭合,质量良好。其中 P1-04 副作用引入 R1-01。
|
||||||
|
- **P2×9 修复**:9/9 全部文字闭合,但 P2-03 / P2-06 / P2-09 引入新偏差,P2-04 含小引用错误。
|
||||||
|
|
||||||
|
### 11.2 详细设计当前状态
|
||||||
|
|
||||||
|
- **从覆盖性 / 架构合理性 / 状态机一致性看**:详细设计仍然 PASS
|
||||||
|
- **从契约/事件 payload 不变性看**:当前版本存在 **3 处契约/payload 越界引用**(R1-01 ~ R1-03)
|
||||||
|
|
||||||
|
### 11.3 建议
|
||||||
|
|
||||||
|
**短迭代修复(30 分钟以内)** 收束 3 P1 + 2 P2,然后可以正式冻结详细设计、进入实现阶段。
|
||||||
|
|
||||||
|
不建议在不解决 R1-01 ~ R1-03 的情况下开始实现:
|
||||||
|
- R1-01 会让 ContextAssembler 实现时找不到 `current_profile()` 方法
|
||||||
|
- R1-02 会让 EventStore 实现者误以为 `agent.started` 投影需要做两次 status 更新
|
||||||
|
- R1-03 会让消息处理代码尝试读不存在的 `event.payload.phase` 字段
|
||||||
|
|
||||||
|
### 11.4 最终判定
|
||||||
|
|
||||||
|
**CONDITIONAL PASS (R5)** — 详细设计相比 R4 后版本仍然满足覆盖性与结构正确性,但修复过程引入了 3 处契约 / payload 越界条款,需要在冻结/实现前 30 分钟内修正。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
End of regression audit.
|
||||||
@@ -156,24 +156,28 @@ platform.ts → cross-platform tier enums referenced by Doctor (from cross-p
|
|||||||
above; the binding requirement is that the exported symbol set equals the contract set. This design
|
above; the binding requirement is that the exported symbol set equals the contract set. This design
|
||||||
keeps the code-view §3 list as canonical and treats overview §4 as the superset note.
|
keeps the code-view §3 list as canonical and treats overview §4 as the superset note.
|
||||||
|
|
||||||
**Frozen file-set decision (P2-09)**: For V1.0.0 Alpha, the canonical contracts package file set is
|
**Frozen file-set decision (P2-09)**: For V1.0.0 Alpha, the canonical contracts package has exactly
|
||||||
exactly the 16 files listed above (frozen by code-view §3). Overview §4 symbol groups that do not
|
**16 mandatory files** (frozen by code-view §3); see the list above. Overview §4 symbol groups that
|
||||||
have a matching dedicated file are merged into the existing files per the mapping below; no new
|
do not have a matching dedicated file are merged into one of these 16 files per the mapping below.
|
||||||
`.ts` files are added in the contracts package without an ADR.
|
The default for V1.0.0 Alpha is: **all overview §4 symbol groups are inlined into the 16
|
||||||
|
mandatory files** (i.e. the "default home" column).
|
||||||
|
|
||||||
| Overview §4 symbol group | Canonical file (code-view §3) |
|
| Overview §4 symbol group | Default home (mandatory, no ADR needed) |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `storage.ts` symbols (TransactionManager, Repository facades) | merged into `task.ts` (TransactionManager) and per-domain files |
|
| `storage.ts` symbols (TransactionManager, Repository facades) | merged into `task.ts` (TransactionManager) and per-domain files |
|
||||||
| `scheduler.ts` symbols (SchedulerWavePlan, SchedulerRunResult) | `task.ts` |
|
| `scheduler.ts` symbols (SchedulerWavePlan, SchedulerRunResult) | `task.ts` |
|
||||||
| `workers.ts` symbols (WorkerRole, WorkerRuntime, IPC payloads) | `ipc.ts` + `worker-result.ts` |
|
| `workers.ts` symbols (WorkerRole, WorkerRuntime, IPC payloads) | `ipc.ts` + `worker-result.ts` |
|
||||||
| `context.ts` symbols (PromptLayer, PromptLayerLoader, ContextAssembler) | `runtime.ts` (`ContextPack`) + dedicated `context.ts` only if needed (otherwise inlined in `runtime.ts`) |
|
| `context.ts` symbols (PromptLayer, PromptLayerLoader, ContextAssembler) | `runtime.ts` (`ContextPack` and prompt-layer types are co-located) |
|
||||||
| `projection.ts` symbols | `ui.ts` |
|
| `projection.ts` symbols | `ui.ts` |
|
||||||
| `doctor.ts` symbols (DoctorService, DoctorRunInput/Output) | `platform.ts` (cross-platform tier types) + dedicated `doctor.ts` only if file size warrants split |
|
| `doctor.ts` symbols (DoctorService, DoctorRunInput/Output) | `platform.ts` (cross-platform tier + doctor types co-located) |
|
||||||
| `knowledge.ts` symbols (DebugKnowledgeStore, LearnedMemoryStore) | inlined into `artifact.ts` / dedicated file only if needed |
|
| `knowledge.ts` symbols (DebugKnowledgeStore, LearnedMemoryStore) | `artifact.ts` |
|
||||||
| `diagnostics.ts` symbols (Diagnostic, semantic_signature types) | inlined into `tool.ts` / dedicated file only if needed |
|
| `diagnostics.ts` symbols (Diagnostic, semantic_signature types) | `tool.ts` |
|
||||||
|
|
||||||
The barrel `index.ts` exports the full union. Any future split into additional files requires an ADR
|
The barrel `index.ts` exports the full union. **No new `.ts` files** are added to the contracts
|
||||||
under `docs/architecture/adr/` and a synchronized update to code-view §3.
|
package for V1.0.0 Alpha. Any future split (for example extracting a dedicated `context.ts`,
|
||||||
|
`doctor.ts`, `knowledge.ts`, or `diagnostics.ts`) is **out of scope for V1.0.0 Alpha** and requires
|
||||||
|
an ADR under `docs/architecture/adr/` plus a synchronized update to code-view §3 before it may be
|
||||||
|
introduced in a later version.
|
||||||
|
|
||||||
## 4. Storage and Repositories
|
## 4. Storage and Repositories
|
||||||
|
|
||||||
@@ -376,7 +380,7 @@ post-commit outbox/compensation work explicit, the map is split into two tables.
|
|||||||
| `assistant.message.started` | upsert `message_drafts` (status=streaming) |
|
| `assistant.message.started` | upsert `message_drafts` (status=streaming) |
|
||||||
| `assistant.message.created` | insert `messages` + delete matching `message_drafts` |
|
| `assistant.message.created` | insert `messages` + delete matching `message_drafts` |
|
||||||
| `assistant.message.failed` | `message_drafts.status=error` or failure artifact ref |
|
| `assistant.message.failed` | `message_drafts.status=error` or failure artifact ref |
|
||||||
| `agent.started` | insert `agents` row with `status='starting'` upon spawn intent, then update to `status='running'` on the same event's commit when WorkerProcess handshake has succeeded (`workers` ack); domain row carries final state at commit time |
|
| `agent.started` | insert `agents` row with `status = 'starting'` when emitted at spawn (handshake not yet acknowledged) or `status = 'running'` when emitted after the WorkerProcess handshake has completed; the event carries exactly one terminal-of-emission status. The follow-up `starting → running` transition (without a dedicated durable event) is recorded by `WorkerManager` updating the row directly per the state machine in §20.4 |
|
||||||
| `agent.completed/failed/lost/cancelled` | update `agents.status` |
|
| `agent.completed/failed/lost/cancelled` | update `agents.status` |
|
||||||
| `task.created` | insert `tasks` (+ optional `task_dependencies`) |
|
| `task.created` | insert `tasks` (+ optional `task_dependencies`) |
|
||||||
| `task.started` | `tasks.status=running`, set started/agent/workspace; insert `task_attempts` |
|
| `task.started` | `tasks.status=running`, set started/agent/workspace; insert `task_attempts` |
|
||||||
@@ -414,28 +418,29 @@ post-commit outbox/compensation work explicit, the map is split into two tables.
|
|||||||
| `memory.candidate.created` | append |
|
| `memory.candidate.created` | append |
|
||||||
| `memory.archived` | append (mark memory inactive in session-side mirror if any) |
|
| `memory.archived` | append (mark memory inactive in session-side mirror if any) |
|
||||||
|
|
||||||
**Table B — Projection + post-commit outbox/compensation (cross-DB or external write):**
|
**Table B — Projection paired with prior owner-side external write (cross-DB):**
|
||||||
|
|
||||||
These events project a session-side intent row inside the same transaction, then drive a
|
For these events the **external write happens first** in the owning store; the durable
|
||||||
follow-up external write via the outbox model (§18.4, runtime-semantics §6.3-§6.4). The
|
session event is then ingested by the owning service to record the completed cross-store
|
||||||
external write is performed by the *owning service* after EventStore.commit, never inside
|
transition. `project(event, tx)` only writes session-DB rows; the cross-DB pair is
|
||||||
`project(event, tx)`.
|
eventually consistent (§18.4, runtime-semantics §6.3-§6.4).
|
||||||
|
|
||||||
| Event type | Projection step (in transaction) | Outbox step (post-commit) | Owner |
|
| Event type | Projection step (in session-DB transaction) | External write (already done by owner before event is ingested) | Owner |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `memory.promoted` | append durable event recording promotion intent | write `rules/`, `skills/`, or `learned-memory.db` row | `LearnedMemoryStore` / `RuleStore` |
|
| `memory.promoted` | append durable event (event log row); domain side has no dedicated table — `MemoryRepository` is project-DB only | write `rules/`, `skills/`, or `learned-memory.db` row | `LearnedMemoryStore` / rules subsystem |
|
||||||
| `memory.archived` (when external mirror exists) | append durable event | mark external memory inactive | `LearnedMemoryStore` |
|
| `memory.archived` | append durable event | mark memory inactive in external store | `LearnedMemoryStore` |
|
||||||
| `debug.record.created` | append durable session event | insert/update row in `debug-records.db` | `DebugKnowledgeStore` |
|
| `debug.record.created` | append durable event | insert/update row in `debug-records.db` | `DebugKnowledgeStore` |
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- `project(event, tx)` itself never opens external DBs or files; it only writes
|
- `project(event, tx)` never opens external DBs or files; it only writes
|
||||||
`events_session.db` rows (events, drafts, domain projections).
|
`events_session.db` rows (events, drafts, session domain projections).
|
||||||
- After successful commit, `EventBus.publish(event)` fires; outbox-aware subscribers
|
- The owning service performs its external write **before** ingesting the durable event.
|
||||||
(`LearnedMemoryStore`, `DebugKnowledgeStore`, ...) then perform the external write and
|
This makes the session-DB event the "we observed the external write succeeded" record.
|
||||||
emit a completion event (`memory.promoted` completion artifact, `debug.record.created`
|
- If the external write fails, the owning service does **not** ingest the success event.
|
||||||
status update) per §18.4.
|
See §18.4 for failure modes (`task.failed` carrying `AirError`, candidate re-queue).
|
||||||
- On restart, recovery scans pending outbox intents in the session DB and retries
|
- On restart, recovery cross-checks for upstream session rows (e.g. `memory.candidate.created`)
|
||||||
external writes (runtime-semantics §6.4).
|
without a matching downstream event (`memory.promoted`) and re-queues work
|
||||||
|
(runtime-semantics §6.4).
|
||||||
|
|
||||||
### 5.5 EventBus
|
### 5.5 EventBus
|
||||||
|
|
||||||
@@ -598,7 +603,7 @@ Strategies (db-schema §16, scheduler-state-machine §MERGING): `main` (no merge
|
|||||||
GC retention follows overview §15 (active until merge/cancel; merged 7d; abandoned 3d; cleaned keeps
|
GC retention follows overview §15 (active until merge/cancel; merged 7d; abandoned 3d; cleaned keeps
|
||||||
DB row).
|
DB row).
|
||||||
|
|
||||||
**Responsibility split — Scheduler vs. WorkspaceManager** (overview §10.5, scheduler-state-machine §4/§MERGING):
|
**Responsibility split — Scheduler vs. WorkspaceManager** (overview §10.3, scheduler-state-machine §4/§MERGING):
|
||||||
|
|
||||||
| Responsibility | Owner |
|
| Responsibility | Owner |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -922,7 +927,7 @@ configuration or resource loading (L0, L1, L3, L5). The remaining layers are ass
|
|||||||
|
|
||||||
| Layer | Source | Assembled by |
|
| Layer | Source | Assembled by |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| L2 Safety | `PermissionEngine.current_profile()` + `~/.air/permissions.yaml` + project permissions | `ContextAssembler` |
|
| L2 Safety | Active permission profile (`~/.air/permissions.yaml` + project permission config; same source the `PermissionEngine` implementation reads internally — no new contract method) | `ContextAssembler` |
|
||||||
| L4 Architecture | `TaskSpec.context_refs.arc_ref` → load from plan/ADR/C4 docs | `ContextAssembler` |
|
| L4 Architecture | `TaskSpec.context_refs.arc_ref` → load from plan/ADR/C4 docs | `ContextAssembler` |
|
||||||
| L6 Evidence | `TaskSpec.context_refs.artifacts` + `EvidenceStore.list_for_task()` | `ContextAssembler` |
|
| L6 Evidence | `TaskSpec.context_refs.artifacts` + `EvidenceStore.list_for_task()` | `ContextAssembler` |
|
||||||
| L7 Conversation | `SessionStore.messages.list_by_session()` (recent N messages) | `ContextAssembler` |
|
| L7 Conversation | `SessionStore.messages.list_by_session()` (recent N messages) | `ContextAssembler` |
|
||||||
@@ -1322,40 +1327,61 @@ developer log and either re-parented or archived (runtime-semantics §5).
|
|||||||
### 18.4 Outbox / compensation for cross-DB writes
|
### 18.4 Outbox / compensation for cross-DB writes
|
||||||
|
|
||||||
Writes to project-level DBs (`debug-records.db`, `learned-memory.db`) or external files follow the
|
Writes to project-level DBs (`debug-records.db`, `learned-memory.db`) or external files follow the
|
||||||
outbox model (runtime-semantics §6.3-§6.4, overview §8.3):
|
outbox model (runtime-semantics §6.3-§6.4, overview §8.3). The general pattern is:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
1. Insert durable session event recording intent/request (e.g. memory.promoted intent payload,
|
1. Owning service (LearnedMemoryStore, DebugKnowledgeStore, ...) performs the external
|
||||||
debug.record.created intent payload). Domain row in session DB marks status="pending_external".
|
DB/file write through its own transaction. Each owning store is single-writer.
|
||||||
2. Commit the session-side transaction (events_session.db).
|
2. On success: the owning service ingests a single durable session event whose payload is
|
||||||
3. After commit, EventBus delivers the event to the owning service (LearnedMemoryStore /
|
already the completion form fixed by event-registry §3 (e.g. memory.promoted with
|
||||||
DebugKnowledgeStore). The service performs the external DB/file operation.
|
target_ref populated, debug.record.created with debug_record_id populated). The
|
||||||
4. On success, the owning service emits a durable completion event (memory.promoted completion
|
EventStore commit makes the cross-DB pair eventually consistent.
|
||||||
payload with artifact_ref, debug.record.created status="committed") that updates the
|
3. On external failure: the owning service does NOT emit the success event. It either
|
||||||
session-side row from pending_external to committed.
|
emits a task.failed carrying the AirError (so Scheduler/Doctor can decide), or queues
|
||||||
5. On external failure, the service emits a durable failure event; recovery retries from step 3
|
a fresh upstream candidate (e.g. memory.candidate.created) for retry.
|
||||||
based on the pending intent row.
|
4. On restart: recovery cross-checks session-side "upstream" rows (memory.candidate.created
|
||||||
6. On restart, recovery scans pending external intents and resumes step 3.
|
without a matching memory.promoted, or pending debug-task tasks) against external store
|
||||||
|
state. Stale candidates are re-queued; orphan external rows are surfaced through Doctor.
|
||||||
```
|
```
|
||||||
|
|
||||||
**`memory.promoted` two-phase semantics** (event-registry §3, runtime-semantics §6.3):
|
No event payload is extended with intent/commit phase markers; the existing payload
|
||||||
|
schemas in `event-registry-v1.md §3` are honored as-is. Any future change to add an
|
||||||
|
intent-phase event would require an ADR plus a new event type or payload version bump
|
||||||
|
(event-registry §2 rule 7).
|
||||||
|
|
||||||
The single `memory.promoted` event type carries two semantically distinct phases distinguished by
|
**`memory.promoted` outbox semantics** (event-registry §3 `MemoryPromotedPayload`, runtime-semantics §6.4):
|
||||||
its payload:
|
|
||||||
|
|
||||||
| Phase | Payload marker | Meaning | When emitted |
|
Per `event-registry-v1.md §3`, `memory.promoted` payload is fixed to:
|
||||||
|---|---|---|---|
|
`{ candidate_id, target_ref, promoted_by, summary }` — there is no `phase` field
|
||||||
| Intent | `phase: "intent"`, no `artifact_ref`/`memory_id` yet | The system has decided to promote a candidate; external write is pending | Step 1 above, inside session-side transaction |
|
and no separate intent/completion variants. Per `runtime-semantics-v1.md §6.4`,
|
||||||
| Completion | `phase: "committed"`, includes `artifact_ref` / `memory_id`, `target_store` | External store now holds the promoted memory | Step 4 above, after external write succeeds |
|
`memory.promoted` records the **completed** promotion (target_ref already populated).
|
||||||
|
|
||||||
The intent event makes the promotion durable even if the runtime crashes before the external
|
The outbox sequence is therefore:
|
||||||
write. The completion event closes the outbox loop and exposes the resulting memory ID/artifact
|
|
||||||
to downstream readers. If the external write fails permanently, a `memory.candidate.created`
|
|
||||||
follow-up may re-queue the promotion, or a Doctor task surfaces it for user attention; the
|
|
||||||
intent event itself remains in the log for audit.
|
|
||||||
|
|
||||||
`debug.record.created` follows the same two-phase pattern (intent → committed) with payload
|
| Step | Event / action | DB |
|
||||||
field `phase`.
|
|---|---|---|
|
||||||
|
| 1 | `memory.candidate.created` (durable, already in registry) | session DB |
|
||||||
|
| 2 | Owning service (`ExperienceMiner` / curator) approves the candidate and writes the external store (`learned-memory.db` row, `.air/shared/rules` file, or skill file) | external DB / file |
|
||||||
|
| 3 | On success, emit `memory.promoted` with `target_ref` pointing at the just-written external row/file | session DB |
|
||||||
|
| 4 | On write failure: do **not** emit `memory.promoted`. Either re-queue via a fresh `memory.candidate.created`, or emit a `task.failed` carrying the `AirError` so Scheduler / Doctor can surface it to the user |
|
||||||
|
| 5 | On restart: recovery uses session DB candidates without matching promotion events to detect work in flight (runtime-semantics §6.4) |
|
||||||
|
|
||||||
|
`memory.archived` likewise records a completed archival (no `phase` field; payload
|
||||||
|
is fixed by event-registry §3 `MemoryArchivedPayload`).
|
||||||
|
|
||||||
|
**`debug.record.created` outbox semantics** (event-registry §3 `DebugRecordCreatedPayload`, runtime-semantics §6.3):
|
||||||
|
|
||||||
|
The payload is a single fixed schema with `debug_record_id` already assigned by
|
||||||
|
the owning `DebugKnowledgeStore`. Per `runtime-semantics §6.3` the flow is:
|
||||||
|
|
||||||
|
1. Owning `DebugKnowledgeStore` performs the `debug-records.db` insert/update.
|
||||||
|
2. On success, emit `debug.record.created` referencing the new `debug_record_id`.
|
||||||
|
3. On `debug-records.db` write failure: emit a `task.failed` (or future
|
||||||
|
`debug.record.failed`, gated by ADR) carrying the `AirError`; do **not** emit
|
||||||
|
`debug.record.created`.
|
||||||
|
|
||||||
|
No `phase` payload field is introduced in either event. Any future intent/commit
|
||||||
|
split must go through an ADR + payload version bump (event-registry §2 rule 7).
|
||||||
|
|
||||||
### 18.5 Security invariants
|
### 18.5 Security invariants
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user