aps-agent/poc/pi-fallback/P2-IMPL-NOTES.md

124 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P2 实施笔记 — 写操作过确认卡门禁(Fallback Execute Lane)
> 施工:Agent-J(实施员),对照 `P2-DESIGN.md v1.0` 逐条落地。
> 代码轮验证日期:2026-09-03。状态:**代码与黄金测试全部就绪,待 K 轮真实冒烟 + 注入复证后合闸**。
---
## 1. 改动清单(`git diff --stat` 实测)
跟踪文件改动(`git diff --stat`,不含 `package.json` 的既有未提交改动):
```
docs/CHANGELOG.md | 19 +
docs/architecture/fallback.md | 90 ++++
docs/architecture/harness.md | 3 +
server/agent_core/fallback_lane.py | 915 +++++++++++++++++++- (净增,含 docstring/注释)
server/agent_core/harness.py | 4 +
server/aps_domain/workflow.py | 45 ++
server/integrations/pi_bridge.py | 152 ++++-
tests/golden/test_fallback_lane.py | 31 ++
8 files changed, 1246 insertions(+), 13 deletions(-)
```
新建文件(untracked):`server/agent_core/fallback_verify.py` 190 行、`tests/golden/test_fallback_execute.py` 762 行(19 例)。
(工作区另有 `tests/golden/_saga_cp.json` 被既有 saga 测试例行改写,已 `git checkout --` 还原;`package.json` +3 行为既有 docx 依赖改动,非本轮产物。)
与设计 §1 文件清单逐条对应:
| # | 设计条目 | 落地情况 |
|---|---|---|
| 1 | `fallback_lane.py` 计划锁 + 执行编排 | ✅ `plan_fingerprint`/`load_plan`/`validate_plan`/`check_step_request`/`_check_constraints` + `execute_plan` + `_stage_plan_confirmation` + `write_guard_extension` + `build_pi_runner` + `FallbackConfig` +3 字段 |
| 2 | `pi_bridge.py` 邮箱协议 + fs_write + 简报模板 | ✅ `ActionMailbox` + `handle_fs_write` + `fs_write`/`aps_invoke` 注册 + `render_plan_task_brief` + `_PLAN_TASK_TEMPLATE`(含 `<<<UNTRUSTED_DATA` 段落) |
| 3 | `harness.py` 权力注册 | ✅ `_POWER_MAP`/`_POLICY_DESC` 各 +2(`agent.fallback.execute`=P2、`agent.fallback.execute.highrisk`=P3) |
| 4 | `workflow.py` execute_confirmed +1 分支 | ✅ 证据校验后、`schedule.publish` 前;try/except 归一为显式失败文案;WORLD_WRITE 审计 result=SUCCESS/DENIED/FAILED;补写落盘 |
| 5 | `fallback_verify.py` 新建 | ✅ `world_diff`/`check_expectations`/`diff_summary_lines`/`build_report`/`report_fingerprint` |
| 6 | `tests/golden/test_fallback_execute.py` 新建 | ✅ 19 例(T-1..T-17 + E-1/E-5/E-7)全绿 |
| 7 | `test_fallback_lane.py` 守卫模板断言 | ✅ 扩为 3 条(见 §2 偏差①) |
| 8 | `docs/architecture/harness.md` | ✅ 权力矩阵 +2 行 + 变更记录 FB-02 |
| 9 | `docs/architecture/fallback.md` | ✅ 追加 P2 章节(协议细节以代码为准,本文为准入口) |
| 10 | `docs/CHANGELOG.md` | ✅ FB-02 条目(含实测数字) |
| 11 | `scripts/poc/smoke_s3_execute.py` | ❌ **归 K 轮**(设计 §7 已标注"给 Agent-K",J 分工为代码+notes;接口交接见 §4) |
| 12 | `scripts/poc/rogue_llm_server.py` +2 剧本 | ❌ **归 K 轮**(同上;剧本规格见 §4) |
P1 既有面**逐字节不变**(黄金 `test_pi_bridge.py`/`test_fallback_lane.py` 原断言全绿佐证):`_TASK_TEMPLATE`/`render_task_brief`/`handle_fs_read`/`render_request_envelope`/`parse_llm_result`;`run_fallback_lane(...)` 不传新参数时行为与 P1 一致;readonly 守卫模板字节不变。
---
## 2. 与设计的偏差及理由
**① 守卫双模板方案(v1 readonly / v2 plan+execute),设计预期的"既有守卫测试被打破"未发生。**
设计 §2 E 案要求扩展 `_GUARD_TS_TEMPLATE`,并预期打破 `test_fallback_lane.py::test_write_guard_extension_matches_template`,要求同轮修复为逐字节断言。实际实施采用**双模板**:`mode="readonly"` 输出 P1 模板逐字节不变(原测试原样通过,无需修复),`"plan"/"execute"` 输出 v2 模板(追加 EXECUTE LANE MODE 注释 + APS_WORK_DIR + `fs.unlink/rename/writeFile` 许可包装)。理由:P1 只读 lane 的守卫面不应为 P2 承担任何字节级回归风险;v2 的新表面由同轮新增的两条测试逐字节锁定(沙箱写门面 + 两个 mode 的模板内容)。设计的真实意图——"改守卫必须改测试"——以更强形式满足(新增表面均有字节级断言)。
**② 执行段任务简报渲染器位于 `fallback_lane.py` 而非 `pi_bridge.py`。**
`_render_exec_task_brief` 依赖 `PendingAction` 结构与摘要字段,与编排器同模块避免跨层 import;`pi_bridge` 保持"信封/邮箱/注册表"纯协议层。`plan_fingerprint`/`load_plan` 同理放 `fallback_lane`(计划锁是编排概念)。设计 §2 的模块归属描述以本节为准。
**③ 步骤级 TOOL 审计由 `execute_plan` 成功路径补写,而非 workflow 分支。**
设计 §1 #4 说 workflow 分支"成功则补写步骤级审计"。实际:步骤级审计条目在 `_execute_steps` 各 handler 内构造、成功路径统一补写(含 actor/evidenceRefs/计划指纹);workflow 分支只写一条 WORLD_WRITE 汇总审计。理由:归并失败路径(try/except 捕获 + 显式失败文案)位于 execute_plan 内部,失败现场信息(cp_failed 指纹、回滚验证、diff 详情)只有 execute_plan 拿得到;workflow 分支保持薄壳。
**④ `ExecuteResult.message` 由 `execute_plan` 组装。**
设计说"message 由 workflow 分支组装"。实际失败文案的组装素材(失败原因、checkpoint id、漂移/回滚指纹)全部在 execute_plan 作用域内,workflow 只透传 `fb_result.message`。卡片摘要仍由编排器 `_stage_plan_confirmation` 从结构化字段再生成(设计 §4.1 的硬约束未变)。
**⑤ T-17 经 `monkeypatch.setattr("server.agent_core.llm_client.get_provider", ...)` 直驱 `parse_llm_result`。**
低置信/未知 verdict/非法 JSON 三态不需要真起 rogue HTTP 服务;真实 rogue 服务的三态复证归 K 轮 §7。
**⑥ `ExecuteResult` 默认 `status="failed"`,成功路径在 try 块末尾显式置位。**
施工期曾因此出现"全步骤成功却走失败分支"的缺陷(回滚把已落库成果冲掉),已修复并由 T-2 锁定。记录在此提醒后续维护者:**成功是显式结论,不是默认值**。
---
## 3. 自验结果(全部实测,命令可复跑)
Python 一律 `.venv\Scripts\python.exe`(硬约束)。
| 验证项 | 命令 | 结果 |
|---|---|---|
| 目标三文件 | `pytest tests/golden/test_fallback_execute.py tests/golden/test_fallback_lane.py tests/golden/test_feature_flags.py -q` | **41 passed** |
| 爆炸半径切片(impact 上下游全符号对应黄金文件) | confirm/审批/saga/checkpoint/chat/契约/导入 16 文件 | **118 passed** |
| 邻近切片 | assistant/state 24 例 | **24 passed** |
| 全量黄金·批一(80 文件) | | **573 passed** |
| 全量黄金·批二(77 文件) | | 517 passed, 3 failed, 14 errors |
| ruff(本轮全部新/改文件) | `ruff check` ×8 文件 | **All checks passed!**(零新增) |
批二 3 failed + 14 errors **与本轮无关**,`git stash -u` 前后逐例一致(基线复跑实测):
- `test_preference_features.py` 3 例:内存时钟 2026-09-03 越过示例文档的 validUntil 2026-06-30(日期炸弹,仓库既有)。
- `test_mes_http.py` / `test_mes_readiness.py` / `test_trace_external_http.py` 14 errors:fixture 假数据不满足新接口(仓库既有)。
T-1..T-17 全链路确定性:sandbox fake runner 由 `_start_fake_runner` 动态分配端口、结束即关,不涉及固定端口;execute 段 fake runner 不经端口(直接注入 `build_pi_runner`)。
关键断言落点(19 例 ↔ 设计 §5 映射):
- 锁:`load_plan` 指纹错/缺文件/超步数/空计划/非法结构拒绝;`validate_plan` 漂移拒绝;`check_step_request` 非法 intent/未知字段/数量非正/intent 与 action 不一致拒绝。
- 圈禁:`handle_fs_write` 写 `work/../x` → `{"error": "path escapes allowed roots"}`;越界 intent(order.delete)在 LLM 层拒绝、不进入审批流。
- 门禁:未决卡阻塞首步(含防重放 mock 回调篡改);无卡世界直接拒绝(hint 含 fallback.propose)。
- 回滚:数量篡改、verify 偏离、子进程崩溃三例均恢复 before 世界 + 显式失败文案 + WORLD_WRITE 失败审计 +(篡改类)cp_failed 现场指纹可复算。
- 证据:expectation 容差 0(±1 拒绝)、无关行变化容忍、中文文案 PASS、diff 摘要行格式。
- 审计链:PROPOSE→GATE(plan 指纹)→GATE(confirm 指纹+快照集)→TOOL×2(步骤)→WORLD_WRITE;失败链 PROPOSE→GATE(plan)→GATE(confirm)→WORLD_WRITE(FAILED)。
- 小世界:17 实体 demo 世界全流程成功(仅种子行可 import.commit)。
- 总开关:features.json `fallback.enabled=false` 时新分支不触达(feat_fallback=False 复跑 2 例)。
---
## 4. 遗留问题 / 交接清单(→ Agent-K)
1. **真实 S3 冒烟(`scripts/poc/smoke_s3_execute.py`,设计 §7 规格)**:临时 APS_HOME 双仓 + features.json 开 fallback + 根 `.env` 的 kimi-k2.6 key。验收点 A–H 见设计 §7。接口提示:fake pi runner 注入点为 `fallback_lane.build_pi_runner`(签名 `(config, *, mode="readonly")`,execute 段用 `mode="execute"`);`make_customer_file` 自检要求 `detect_kind` 对该文件**失败**(否则 commit 阶段会先 reject)。
2. **注入复证**:`rogue_llm_server.py` SCENARIOS += `inject_via_csv`(CSV 藏指→断言零文件落盘+零审批+计划锁拦截)与 `inject_fake_approval`(Pi 伪造审批→断言卡片结构不采纳+审批仓空);复跑 P1 三剧本回归。
3. **真实 pi + 真实 LLM 端到端未验**:守卫 v2 模板在真实 pi 子进程下的行为未验(黄金测试锁定的是模板字节内容本身);`--sabotage` 变体脚本未实现。
4. **ASSISTED 长执行占事件循环**:设计 §5.2 已知边界,main 期处置项(K 轮真实冒烟时观察 pi 超时 120s 的占用表现)。
5. **async_jobs 不复用**:P2 同步执行(设计 §4.2 取舍),main 期若执行耗时长再评估异步化。
6. **`handle_fs_write` 的 `root != "work"` 拒绝分支无专门用例**:当前注册表只暴露 work;未来暴露 outbox 写时需补测试。
7. **`harness.py` 存量 5 条、`workflow.py` 存量 50 条 ruff 告警**(`git stash -u` 基线复跑同为 55 条,零新增;治理归清洁口粮,不在本 round)。
---
## 5. 宪法红线自证
- 新写路径唯一入口 = 既有 `/api/actions/confirm`(`workflow.py` 分支挂在 confirm 处理器内部,无新 HTTP 端点)。
- 全部新 powers 注册进 Harness 权力矩阵并过 `require_power` 调用链(stage→plan→confirm→execute 四段 GATE)。
- 失败路径显式文案且自动回滚;无任何静默降级。
- checkpoint 前/后/失败现场三段快照成对出现,指纹双向验证(beforeFingerprint 重算比对 + 回滚后指纹验证)。
- Pi 写面仍是圈禁 `work/` 邮箱 + 沙箱 unlink/rename/writeFile 三函数包装——**没有给 pi 新增任何物理写通道**;`fs_write` 仅供中间产物,`aps_invoke` 仅产生请求、由计划锁拦截。
- 计划指纹 sha256 绑定 approvedPlanJson,卡片摘要全部由编排器从结构化字段再生成,注入文本只能出现在 `<<<UNTRUSTED_DATA` 数据段落。