777 lines
57 KiB
Markdown
777 lines
57 KiB
Markdown
# P3-DESIGN — Pi Agent 兜底能力 · 高风险场景 S4/S6/S7 + 白名单治理(文件级详细设计)
|
||
|
||
> 设计员:Agent-L · 日期:2026-09-03 · 协调板:`GOAL-P3.md`
|
||
> 实施员(Agent-M)照本文件施工;验证员(Agent-N)按 §8 测试矩阵与 §9 冒烟规格执行。
|
||
> 前置已读并源码核实:GOAL-P3.md、docs/architecture/fallback.md、
|
||
> P2-DESIGN.md / P2-IMPL-NOTES.md / P2-VALIDATION.md、
|
||
> fallback_lane.py(1735 行:propose_reply 745 / _PLAN_SCENARIOS 1013 / validate_plan 1075 /
|
||
> check_step_request 1218 / execute_plan 1514 / _stage_plan_confirmation 1686)、
|
||
> fallback_verify.py(190 行)、pi_bridge.py(416 行:TOOL_REGISTRY 38 / PiBridge 98 /
|
||
> ActionMailbox 307 / render_plan_task_brief 406)、
|
||
> harness.py(878 行:world_fingerprint 102 / _POWER_MAP 143 / _GLOBAL_APPROVAL_ROLES 372 /
|
||
> APS_APPROVAL_ROLE_POLICIES 370 / _can_initiate_current 112 / stage_confirmation 632
|
||
> (requiredApprovals=2 if P3,683 行)/ consume_execution_grant 810)、
|
||
> approval_store.py decide 395-464(P3 双人 SOD + executionGrant)、
|
||
> server/auth/(context.py IdentityContext.roles 自由字符串元组;middleware.py 中间件绑定;
|
||
> providers.py 207-218 JMS roles 缺省 planner)、feature_flags.py(fail-open + _DEFAULT_OFF_KEYS
|
||
> 语义)、workflow.py(execute_confirmed fallback 分支 577-621 / assistant.reply 兜底接线 2920-2931 /
|
||
> _flex_simulate_due 1598 / scenario.compare 分支 2517 / mes.dispatch 分支 1003-1023)、
|
||
> flex.py(simulate_due 684 / compare_sort_modes 789,均深拷贝沙盒不改主干)、
|
||
> mes.py(_get_active_client 20 / mes_connection_status 28 / apply_dispatch 1203 消费
|
||
> execution_grant 1250 / apply_report 1408)、mes_http.py(MesHttpError 五码 /
|
||
> readiness(probe) 234 / fetch_work_order 341 只读)、wms_events.py、
|
||
> automation.py(Gear/Gate 机制,与 S6 无关——见 §0.13)、audit_alerts.py(仅审计链告警)、
|
||
> tests/golden/test_approval_role_policies.py / test_harness_p3.py / test_fallback_execute.py、
|
||
> PROJECT_OPERATING_RULES.md(§红线 9-16)。
|
||
|
||
---
|
||
|
||
## 0. 现状关键事实(设计依据,均已源码核实)
|
||
|
||
1. **P3 双人审批与执行授权是既有机制**(harness.py:683 + approval_store.py:417-464):
|
||
`power == "P3"` 的 action 出卡自动 `requiredApprovals=2`,第二次批准必须不同用户
|
||
(SOD_DENIED),终审后签发一次性 `executionGrant`(绑定 confirmId/action/paramsHash,
|
||
`consume_execution_grant` 消费,harness.py:810)。mes.dispatch 是唯一在用的 P3 先例
|
||
(workflow.py:1003-1023 → mes.apply_dispatch 1250 行消费 grant)。**P3 白名单放行的
|
||
highrisk 意图直接继承这套双人+授权语义,一行新机制不造。**
|
||
2. **角色体系是自由字符串 + env 配置三层解析**(harness.py:372-375/453-475):
|
||
roles 来自 auth provider(JMS 用户 roles codes,缺省 `JMS_AUTH_DEFAULT_ROLE=planner`),
|
||
审批能力分 initiate/approve/history 三 capability,解析顺序 =
|
||
action 级规则(`APS_APPROVAL_ROLE_POLICIES` JSON env)→ power 级规则 → 全局 env
|
||
(`APS_APPROVAL_INITIATOR_ROLES` / `APS_APPROVAL_ROLES` 等,默认
|
||
`planner,approver,admin`)。**"运维"不需要新权限模型:它就是一个角色字符串(建议
|
||
`ops`),action 级策略把 highrisk 意图的 initiate/approve 圈到 `ops,admin` 即可,
|
||
纯配置零代码**(§4.2)。stage 期门禁既有:stage_confirmation:653
|
||
`_can_initiate_current` 不满足即 PermissionError。
|
||
3. **匿名/测试身份**:`get_identity()` 无 HTTP 上下文返回 roles=("system",),
|
||
`_current_identity_has_approval_role` 对 system 放行(harness.py:486)——黄金测试沿用
|
||
test_approval_role_policies.py 的 `bind_identity(IdentityContext(roles=(...)))` 模式。
|
||
4. **feature_flags 语义对照**(feature_flags.py:8-12):features.json 是 fail-open
|
||
「可用性/可见性」开关(损坏回退默认全开、fallback 键例外默认关),**显式声明"不是权限
|
||
边界"**。P3 白名单语义必须相反:**fail-closed,任何配置问题 → 全拒**,且它是权限边界
|
||
的一部分。两者文件分离、加载器分离、文档各写各的语义(§2.1),绝不复用同一加载函数
|
||
(避免语义混淆——GOAL 硬约束)。
|
||
5. **fallback 核心符号的外部调用方只有两处**(rg 实测):workflow.py:583
|
||
(execute_plan)与 workflow.py:2927(propose_reply)。`FALLBACK_EXECUTABLE_INTENTS` /
|
||
`validate_plan` / `check_step_request` / `plan_fingerprint` 无模块外调用——P3 对计划锁
|
||
的扩展爆炸半径天然收敛(§9)。
|
||
6. **沙盒意图已有成熟先例**:flex.simulate_due(flex.py:684,深拷贝世界试排,P0)、
|
||
flex.compare(789,三模式沙盒对比,P0)、scenario.compare(scenario.py,沙盒三策略
|
||
并行试排,P1,workflow.py:2517 分支写 ALGO_RUN 审计后 `store.save()`——**注意:save
|
||
只落审计,业务世界未变**)、scenario.sensitivity(P1 同形态)。S4 = Pi 编排这些既有
|
||
沙盒意图 + 围墙内临时脚本,产物草稿(§5)。
|
||
7. **world_fingerprint 排除 append-only 键**(harness.py:102-118,_VOLATILE_WORLD_KEYS
|
||
含 auditEvents/logs/mesSyncJournal)——**沙盒执行「零主干写」的验证器断言可以直接用
|
||
前后指纹相等**(审计自然增长不误报),S4 断言与 S6 漂移检测都建立在这上面。
|
||
8. **MES 故障点的全部显式信号**(mes_http.py):MesHttpError 五码
|
||
(TIMEOUT/CONNECT_FAILED/UPSTREAM_ERROR/AUTH_FAILED/NOT_CONFIGURED)、
|
||
`status()` 返回 connected=False 不抛错、`readiness(probe=True)` 轻量探测 connectivity
|
||
ok/failed。客户端切换点唯一:mes.py:20 `_get_active_client()`(MES_HTTP_BASE_URL 有则
|
||
HTTP 否则 stub)——**S6 冒烟的「断连」= 指向不可达地址的 MES_HTTP_BASE_URL +
|
||
reset_http_mes_client()**,「恢复」= 重置回 stub,零产品代码改动(§9.1)。
|
||
9. **MES 外部状态只读回流口已有**:`HttpMesClient.fetch_work_order(external_wo_id)`
|
||
(mes_http.py:341,GET 轮询,只读)+ MockMesClient 同接口——S6 恢复后对账的读侧
|
||
(P0)不需要新 HTTP 面(§6.4)。
|
||
10. **补录写入口已有**:`mes.apply_report(store, wo_id, progress_pct, finish, actor)`
|
||
(mes.py:1408,P1 `mes.report`,workflow.py:2485-2515 既有批量报工先例——逐笔循环
|
||
+ 逐笔 try/except + 汇总文案)。S6 补录 = 编排既有 mes.report,不造新写通道。
|
||
11. **计划锁现状**(P2 基线):`_PLAN_SCENARIOS=("S1","S2","S3","S9")`(fallback_lane.py:
|
||
1013);validate_plan:1101 **硬性拒绝 power ∉ {P1,P2} 的意图**(P3 意图整计划拒绝);
|
||
check_step_request:1231 同款(白名单 + power ∈ {P1,P2})。**这两处是 P3 白名单放行的
|
||
唯一物理闸口**——改成「power 分级 + 白名单裁决」双闸(§3.3),放行面完全由白名单
|
||
文件决定,代码里没有第二个 P3 放行路径。
|
||
12. **P3 确认卡零前端改动**(P2-DESIGN §0.6 结论延续):ConfirmCard 只读
|
||
confirmId/title/summary/power 四 props;双人审批的 PARTIALLY_APPROVED /
|
||
needsSecondConfirm 流程在 app.py confirm 端点与审批仓内闭环,前端确认卡组件不变。
|
||
S6 逐笔多卡 = 多个 `confirm-card` 块在同一 AgentReply.blocks 里依次下发,前端逐张
|
||
渲染(既有能力,S6 冒烟需实测确认呈现顺序)。
|
||
13. **automation.py / audit_alerts.py 与 S6 触发无关**(精读结论):automation.py 是
|
||
Gear/受控自动化升级机制(规则触发业务动作),audit_alerts.py 只聚合审计链完整性告警;
|
||
**现状没有任何「集成故障告警」子系统**——S6 的故障信号来源只能是 ① 用户在对话里
|
||
陈述(触发兜底)+ ② 编排器在 propose 时主动探测 mes status/readiness 注入 inbox
|
||
(§6.2)。不新造告警子系统(GOAL 边界外)。
|
||
14. **K 轮真实冒烟实测教训(P2-VALIDATION §5,直接吸收进本设计)**:
|
||
① 出卡审计落链改变世界指纹——stage 后必须 `refresh_confirmation_world_fingerprint`
|
||
对齐「卡片就绪时刻」(P2 已修,P3 新出卡路径照做);
|
||
② `contextPolicies` 簿记键会被 chat 管线事后物化——出卡前 `setdefault`(P2 已修);
|
||
③ artifactRef 路线对真实 Pi 物理不可用(无 sha256 工具)——**P3 计划一律 params 内联
|
||
frozen,不依赖 artifact 指纹路线**(S6 补录清单是编排器生成的结构化文件,不是 Pi 算的
|
||
sha256);
|
||
④ ASSISTED 真实模型端到端未验——**P3 三场景全部只开放 frozen 步**(§3.4),ASSISTED
|
||
仍限 P1/P2 意图。
|
||
15. **P2 确认卡的 beforeFingerprint 漂移比对在 execute_plan 步骤 2**(fail closed),
|
||
S6 补录卡沿用;S4 沙盒卡不需要漂移保护(不写主干)但仍冻结指纹做「执行期间主干未变」
|
||
的副作用断言(§5.3)。
|
||
|
||
---
|
||
|
||
## 1. 变更清单表
|
||
|
||
| # | 文件 | 新建/修改 | 改什么(函数级) | 为什么 | 风险 |
|
||
|---|------|----------|------------------|--------|------|
|
||
| 1 | `server/agent_core/fallback_highrisk.py` | **新建**(~330 行) | 全部,见 §2/§4:白名单加载器(fail-closed)`load_highrisk_whitelist` / 场景裁决 `scenario_grant` / 意图裁决 `intent_allowed` / 角色判定 `current_identity_roles` · `is_ops_identity` · `roles_for_scenario` / 白名单文档校验 `validate_whitelist_doc`(改白名单出卡前复用) | GOAL 交付 1/5 的治理主体 | 新模块,零存量影响 |
|
||
| 2 | `server/agent_core/fallback_lane.py` | 修改(+~420 行) | 见 §3:`_PLAN_SCENARIOS` 扩为 7 场景;`validate_plan` power 闸改「白名单裁决」;`check_step_request` 同步;`propose_reply` 加角色闸 + 场景提示注入简报;`_stage_plan_confirmation` 分流(P2 单卡 / S6 逐笔多卡 / S4 沙盒卡);`execute_plan` 加 S4 沙盒分支与 S6/S7 步执行器登记;`FallbackConfig` +3 字段。**不改 P1/P2 已有函数签名与默认路径语义** | GOAL 交付 1/2/3/4/5 编排 | MED(本模块是 P1/P2 自建,外部调用方仅 workflow.py 两处,§9.4) |
|
||
| 3 | `server/integrations/pi_bridge.py` | 修改(+~160 行) | `PiBridge.export_integration_status()`(MES 状态/readiness 注入 inbox);`PiBridge.export_ops_diagnostics()`(日志尾部 + 配置脱敏快照注入 inbox);简报模板 +S4/S6/S7 段落(可用沙盒意图/补录协议/运维边界声明);TOOL_REGISTRY **不加新工具**(读面仍只有 fs_read 消费 inbox——围墙不扩) | GOAL 交付 3/4 的 Pi 读面 | LOW(纯追加;P1/P2 五键注册表查询路径不变) |
|
||
| 4 | `server/agent_core/harness.py` | 修改(+4 行) | `_POWER_MAP` += `"agent.fallback.policy.update": "P2"`、`"agent.fallback.ops.config.apply": "P3"`;`_POLICY_DESC` 同步两条。**不动任何函数** | GOAL 交付 1(改白名单登记为 P2 管理动作)/4(S7 改配置 P3) | LOW(P1/P2 同款纯追加,§9.1) |
|
||
| 5 | `server/aps_domain/workflow.py` | 修改(+~90 行) | `execute_confirmed` 追加两分支:`agent.fallback.policy.update`(原子写白名单文件 + 重载校验 + 备份,§2.4)与 `agent.fallback.ops.config.apply`(原子写 features.json 类配置 + 回读校验,§7.3)。**propose 接线点(2920 分支)不动** | GOAL 交付 1/4 执行通道 | MED(热路径纯增量分支,§9.2) |
|
||
| 6 | `tests/golden/test_fallback_highrisk.py` | **新建**(~520 行) | §8 测试矩阵 21 例 | GOAL 交付 6 | 新文件 |
|
||
| 7 | `tests/golden/test_fallback_execute.py` | 修改(±20 行) | T-3 语义更新:P3 意图未入白名单仍整计划拒绝(断言文案对齐新闸);补一例「白名单放行后 P3 意图过校验」的反向断言(或迁入新文件——M 轮自定,以新文件为先) | 白名单闸替换硬拒闸的同轮修复 | 低 |
|
||
| 8 | `docs/architecture/harness.md` | 修改 | 权力矩阵 += 2 行(文档同步铁律) | GOAL 交付 8 | 低 |
|
||
| 9 | `docs/architecture/fallback.md` | 修改 | 增 P3 章节:白名单 fail-closed/角色矩阵/S4/S6/S7/审计回放 | GOAL 交付 8 | 低 |
|
||
| 10 | `docs/CHANGELOG.md` | 修改 | FB-03 条目 | GOAL 交付 8 | 低 |
|
||
| 11 | `poc/pi-fallback/smoke/smoke_s6_reconcile.py` | **新建** | §9.1 冒烟脚本(临时 APS_HOME + MES 断连/恢复) | GOAL 交付 7 | poc 内,零产品影响 |
|
||
| 12 | `poc/pi-fallback/smoke/smoke_s7_ops.py` | **新建** | §9.2 冒烟脚本(运维只读诊断) | GOAL 交付 7 | poc 内,零产品影响 |
|
||
|
||
**明确不改**:`gateway/app.py`(confirm/双人审批通道原样复用)、`contracts.py`、
|
||
`shared/schemas/*`、`apps/web/**`(§0.12 零前端改动)、`feature_flags.py`(§0.4 语义分离)、
|
||
`state/**`(只用公开 API)、`intent.py`、`assistant.py`、`automation.py`/`audit_alerts.py`
|
||
(§0.13)、`mes.py`/`mes_http.py`/`wms_events.py`(编排既有函数,一行不改)、
|
||
`tool_runtime.py`、`async_jobs.py`、`apps/desktop/sidecar.cjs`。
|
||
|
||
---
|
||
|
||
## 2. P3 白名单设计(GOAL 交付 1 + 前置问题 4)
|
||
|
||
### 2.1 文件形态与 fail-closed 语义
|
||
|
||
**默认路径**:`path_under_data("fallback-highrisk.json")`(与 features.json 同目录并列),
|
||
环境变量覆盖:`APS_FALLBACK_HIGHRISK_PATH`。
|
||
|
||
**Schema(whitelistVersion=1)**:
|
||
|
||
```json
|
||
{
|
||
"whitelistVersion": 1,
|
||
"updatedAt": "2026-09-04T10:00:00",
|
||
"updatedBy": "admin-1",
|
||
"scenarios": {
|
||
"S4": {
|
||
"enabled": true,
|
||
"intents": ["flex.simulate_due", "flex.compare", "scenario.compare", "scenario.sensitivity"],
|
||
"roles": ["planner", "admin"]
|
||
},
|
||
"S6": {
|
||
"enabled": true,
|
||
"intents": ["mes.report"],
|
||
"roles": ["planner", "admin"],
|
||
"maxItemsPerRun": 20
|
||
},
|
||
"S7": {
|
||
"enabled": true,
|
||
"intents": ["agent.fallback.ops.config.apply", "agent.fallback.policy.update"],
|
||
"roles": ["ops", "admin"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
字段规则(`validate_whitelist_doc` 逐条强制):
|
||
|
||
| 字段 | 规则 |
|
||
|------|------|
|
||
| `whitelistVersion` | 必须 == 1 |
|
||
| `scenarios` | 对象;键只允许 ∈ {"S4","S6","S7"}——**未知场景键 → 整个文件判损坏(全拒)**,不做「忽略未知键」(与 features.json 的 unknown 忽略相反,白名单里任何无法解释的内容都按攻击面处理) |
|
||
| `scenarios.<S>.enabled` | 必须 bool |
|
||
| `scenarios.<S>.intents` | 字符串数组;每个 intent 必须在 harness `_POWER_MAP` 登记(未登记 → 文件判损坏全拒——白名单不能引用不存在的动作,防止「先放行后登记」的倒置) |
|
||
| `scenarios.<S>.roles` | 非空字符串数组(小写归一);该场景可见/可发起/可执行的角色集合 |
|
||
| `scenarios.<S>.maxItemsPerRun` | 可选正整数(仅 S6 用,缺省 20) |
|
||
|
||
**fail-closed 裁决表(`load_highrisk_whitelist` 的全部出口)**:
|
||
|
||
| 情况 | 结果 | 表现 |
|
||
|------|------|------|
|
||
| 文件缺失 | 全拒 | `{"ok": False, "error": "白名单文件缺失(fail-closed 默认全拒)", "grants": {}}` |
|
||
| JSON 解析失败 / 顶层非对象 / version 非法 | 全拒 | error 显式带原因 |
|
||
| 未知场景键 / 未知字段 / 类型错误 | 全拒 | error 显式带 offending key |
|
||
| intent 未在 _POWER_MAP 登记 | 全拒 | error 显式带 intent 名 |
|
||
| 任何异常(含 IO) | 全拒 | error 归并(与 fallback_feature_enabled 的「宁可误关」同款哲学) |
|
||
| 全过 | 按文件放行 | `grants` = 归一化后的场景表 |
|
||
|
||
**加载结果每次调用现读文件(不缓存)**——白名单修改经确认卡落盘后下一次裁决即生效,
|
||
无热更新机制(配置文件量级,读盘成本可忽略;黄金测试改文件即改裁决,确定性)。
|
||
返回结构同时携带 `path`/`sha256`(裁决时文件指纹),出卡审计 rationale 记录
|
||
`whitelistSha256`——**执行时重算比对,出卡后白名单被改 → 执行拒绝**(§3.5,防「出卡时
|
||
合法、执行前收窄/扩权」的窗口攻击)。
|
||
|
||
### 2.2 与 features.json 的语义隔离(GOAL 硬约束的落实)
|
||
|
||
| 维度 | features.json | fallback-highrisk.json |
|
||
|------|--------------|------------------------|
|
||
| 语义 | 可用性/可见性(**不是权限边界**) | **权限边界的一部分**(P3 放行唯一依据) |
|
||
| 损坏回退 | fail-open(默认全开,fallback 键默认关) | **fail-closed(全拒)** |
|
||
| 未知键 | 忽略并列入 unknown | **判损坏全拒** |
|
||
| 加载器 | feature_flags.load_feature_flags | fallback_highrisk.load_highrisk_whitelist(新建,互不 import) |
|
||
| 修改通道 | 手工编辑文件(现状) | **只能经 `agent.fallback.policy.update` 确认卡**(§2.4) |
|
||
|
||
### 2.3 粒度取舍(前置问题 4 的回答)
|
||
|
||
**结论:意图级为骨架 + 场景级为聚合开关 + 角色级为第三维,三层同文件表达。**
|
||
|
||
- **为什么不是纯场景级**:S6 的补录意图(mes.report,P1)与 S7 的改配置(P3)权力等级
|
||
不同、风险不同、角色不同;场景级整开会把「S7 开了」变成「运维场景里一切 P3 动作都开了」,
|
||
违背最小放行。
|
||
- **为什么不是纯意图级**:计划的 `scenario` 字段(P2 schema 既有)提供了语义归属——
|
||
同一个 mes.report 在 S6(断连补录)放行,不等于在 S3(日常导入)里也该被 Pi 编排;
|
||
脱离场景的意图级白名单会让「意图全局放行」悄悄扩大 Pi 在普通对话里的编排面。
|
||
场景级开关同时是审计叙事单位(白名单文件读起来就是一张「场景治理表」)。
|
||
- **代价如实登记**:场景 × 意图的二维表意味着新增场景或新意图要同时动两处(_POWER_MAP
|
||
登记 + 白名单放行),流程变长——这是有意的「放行摩擦」,与白名单的治理定位一致。
|
||
|
||
### 2.4 改白名单 = P2 管理动作(`agent.fallback.policy.update`)
|
||
|
||
**链路**(全部复用既有通道,零新机制):
|
||
|
||
```
|
||
发起人(任何有 initiate 角色的用户,建议现场管理员操作)在对话说
|
||
「把 mes.report 加进 S6 白名单」→ 意图未登记 → fallback 兜底
|
||
→ 编排器识别 policy 请求(简报声明 + Pi 产出 policy 计划,见下)
|
||
→ 编排器从「当前白名单 + 计划 diff」在服务端再生成完整新白名单文档
|
||
(Pi 只产出 diff 声明 {scenario, addIntents[], removeIntents[], enabled?},
|
||
文档本体由编排器计算——卡片内容的机器再生成原则,P2-DESIGN §6.2 同款)
|
||
→ validate_whitelist_doc(新文档) 全过才出卡(非法新文档拒绝出卡 + 显式原因)
|
||
→ stage_confirmation("agent.fallback.policy.update",
|
||
params={"document": 新文档, "documentSha256": sha256(canonical),
|
||
"beforeSha256": 当前文件 sha256 | null, "diff": 结构化 diff})
|
||
→ P2 单卡确认 → execute_confirmed 新分支:
|
||
① 重算 beforeSha256 与当前文件比对(漂移 → 拒绝,fail closed)
|
||
② 写 tmp 文件 → validate_whitelist_doc(读回) → os.replace 原子替换
|
||
③ 旧文件备份 <path>.bak-<ts>(保留最近 5 份,供人工回滚)
|
||
④ WORLD_WRITE 审计(target=FALLBACK_POLICY,rationale 含前后 sha256 + diff + approver)
|
||
```
|
||
|
||
- **手工直接编辑文件不被禁止但可见**:裁决器每次现读 + 出卡/执行双端 sha256 比对,
|
||
任何绕开确认卡的手改都会使在途卡片执行拒绝,并在下一次裁决的 `updatedBy/updatedAt`
|
||
与审计链对不上——治理台可查(文档如实写明「文件层手改是破窗行为,审计可发现」)。
|
||
- **P3 白名单里的 `agent.fallback.policy.update` 自身**(S7.intents 含它时):运维场景下
|
||
Pi 编排改白名单同样走上面的卡——「改白名单的卡」本身不享受任何豁免(自指无特权)。
|
||
|
||
---
|
||
|
||
## 3. 计划锁的 P3 扩展(GOAL 交付 1 的执行点)
|
||
|
||
### 3.1 场景开放集
|
||
|
||
`_PLAN_SCENARIOS = ("S1","S2","S3","S9","S4","S6","S7")`——validate_plan 的场景校验不变,
|
||
新场景能不能过卡完全由后续闸口决定。
|
||
|
||
### 3.2 计划 schema 增量(planVersion 仍为 1,向后兼容)
|
||
|
||
- `scenario` 新值 S4/S6/S7;
|
||
- 步骤 `intent` 可为白名单放行的 P0/P3 意图(见 §3.3 闸口);
|
||
- S6 计划的 steps 一律 frozen + params 内联(§0.14③:artifact 指纹路线对真实 Pi 不可用,
|
||
P3 全面弃用 artifactRef——validate_plan 对 S4/S6/S7 场景的 artifactRef 显式拒绝,
|
||
报错文案指明「P3 场景只接受 params 内联」);
|
||
- S4 计划 steps 是「沙盒步骤」(§5.2,`mode` 仍填 "frozen",语义 = 编排器直调沙盒函数)。
|
||
|
||
### 3.3 闸口改造:硬拒 → 白名单裁决(两处的唯一放行路径)
|
||
|
||
**validate_plan(出卡闸)改造后的意图检查**(替换 fallback_lane.py:1099-1104):
|
||
|
||
```python
|
||
intent = str(step.get("intent") or "")
|
||
scenario = plan.get("scenario")
|
||
power = power_of(intent)
|
||
if scenario in ("S4", "S6", "S7"):
|
||
# P3 场景:意图必须 ① 在 FALLBACK_EXECUTABLE_INTENTS 或 FALLBACK_P3_INTENTS 登记
|
||
# ② 经白名单 scenario_grant 裁决放行(含 enabled/intents 双条件)
|
||
# ③ power 与该场景允许集匹配(S4 只许 P0/P1 沙盒;S6 只许 P1/P2;
|
||
# S7 只许白名单内 P2/P3)
|
||
highrisk.check_scenario_step(scenario, intent, power, whitelist) # 不过 → PlanError
|
||
else:
|
||
# P2 语义逐字节保持:登记 + power ∈ {P1,P2}
|
||
if intent not in FALLBACK_EXECUTABLE_INTENTS or power not in ("P1", "P2"):
|
||
raise PlanError(...)
|
||
```
|
||
|
||
`highrisk.check_scenario_step` 内部:whitelist 非 ok → PlanError(fail-closed 全拒);
|
||
场景未 enabled → PlanError(「S6 未在白名单启用」);intent ∉ 场景 intents → PlanError
|
||
(「意图 mes.dispatch 未列入 S6 白名单放行集」);power 越场景允许集 → PlanError。
|
||
|
||
**check_step_request(执行闸,ASSISTED 邮箱比对)同步改造**:同样的裁决函数,偏离类型
|
||
沿用 `plan_deviation:tool`。**但 §3.4 决定 P3 场景不开放 ASSISTED**,该闸的改造仅为
|
||
防御纵深(邮箱里出现 P3 意图请求时给出白名单裁决而非硬编码 P1/P2 拒)。
|
||
|
||
### 3.4 P3 场景只开放 frozen 步(明确取舍)
|
||
|
||
S4 沙盒步骤参数在提议段即可全量确定(产品/数量/策略名);S6 补录逐笔冻结;S7 配置变更
|
||
冻结文档——**三个场景都没有「执行期才知道下一步参数」的正当需求**,而 ASSISTED 是 K 轮
|
||
唯一未真实端到端验证的路径(§0.14④)。决定:S4/S6/S7 的 step.mode 只允许 "frozen",
|
||
出现 "assisted" → 出卡拒绝(显式文案)。收益:P3 全部执行 = 编排器确定性直执,
|
||
**Pi 在执行段不在环**——高风险场景拿到计划锁的最强形态(P2-DESIGN §2.1 的设计理由
|
||
原样成立)。代价:未来若出现真需自适应的 P3 场景,需单独设计轮。
|
||
|
||
### 3.5 出卡/执行双端白名单指纹
|
||
|
||
- 出卡:`params.whitelistSha256` = 裁决时白名单文件 sha256(随计划冻结进卡);
|
||
- 执行:execute_plan 步骤 1.5(指纹重算之后、漂移比对之前)重算当前白名单 sha256,
|
||
不等 → `denied`(「白名单在审批窗口内已变更,本次未执行任何变更,请重新发起」)。
|
||
P2 场景卡无此字段(None)→ 跳过(向后兼容,T-1..T-17 不受影响)。
|
||
|
||
---
|
||
|
||
## 4. 角色矩阵与 auth 现状评估(GOAL 交付 5 + 前置问题 1)
|
||
|
||
### 4.1 auth 现状评估(前置问题 1 的回答)
|
||
|
||
**结论:server/auth 现有粒度足以表达「运维」,零代码扩展,纯配置。**
|
||
|
||
依据(§0.2/§0.3):roles 是自由字符串元组,JMS provider 原样透传平台角色 codes;
|
||
审批角色三层解析(action 级 `APS_APPROVAL_ROLE_POLICIES` → power 级 → 全局 env)是
|
||
既有机制且有黄金测试(test_approval_role_policies.py)。**最小扩展方案**:
|
||
|
||
1. 角色字符串约定 `ops`(运维),由 JMS 平台侧角色 code 直接下发;无 JMS 的现场用
|
||
`JMS_AUTH_DEFAULT_ROLE` 或既有账号体系赋值——**不新造权限模型、不改 auth 任何文件**;
|
||
2. highrisk 动作的发起/批准圈定经 action 级策略配置(部署期 env,示例):
|
||
```json
|
||
APS_APPROVAL_ROLE_POLICIES={"actions":{
|
||
"agent.fallback.execute.highrisk":{"initiate":["ops","admin"],"approve":["ops","admin"]},
|
||
"agent.fallback.ops.config.apply":{"initiate":["ops","admin"],"approve":["admin"]},
|
||
"agent.fallback.policy.update":{"initiate":["admin"],"approve":["admin"]}}}
|
||
```
|
||
(该配置不进代码库,进部署 runbook / fallback.md 文档示例;黄金测试用 monkeypatch
|
||
setenv 同款机制验证,test_approval_role_policies.py:64 先例。)
|
||
3. **可见性门禁**(「非运维物理不可见」)在 fallback 编排层:`propose_reply` 识别到
|
||
S7 运维意图(简报引导 Pi 声明 scenario,或编排器按白名单 roles 预检)时,
|
||
`is_ops_identity()`(roles ∩ 白名单 S7.roles)不满足 → **返回 None(走原话术,
|
||
零副作用零审计,与开关关同形态)**——未授权用户连「存在运维入口」都无从得知。
|
||
注意:这不是提示「权限不足」,而是物理不触发(GOAL「物理不可见/不可用」原文)。
|
||
4. 例外如实登记:`get_identity()` 的 system 放行(harness.py:486)是测试/脚本通道,
|
||
黄金测试里直驱 stage 不受角色闸限制——这与 P1/P2 测试模式一致,不算破窗
|
||
(HTTP 面永远有真实身份,middleware 绑定)。
|
||
|
||
### 4.2 角色 × 场景矩阵(可见性 / 可执行性)
|
||
|
||
图例:✅ 可见可执行(过卡后);👁 只读可见(草稿/诊断产物);⛔ 物理不可见(propose
|
||
返回 None,无入口感知);— 该场景对该角色无定义(按 ⛔ 处理)。
|
||
|
||
| 角色(auth roles) | S1 意图未识别 | S2 执行失败修复 | S3 新格式导入 | **S4 沙盒试排** | S5 ad-hoc 分析 | **S6 集成故障补录** | **S7 运维诊断** | S9 批量处理 | S10 熔断 |
|
||
|---|---|---|---|---|---|---|---|---|---|
|
||
| 普通用户(viewer/无审批角色) | 👁 草稿报告 | 👁 诊断报告 | ⛔(出卡被拒:initiate 角色不符) | ⛔(白名单 S4.roles 不含) | 👁 报告 | ⛔ | ⛔(入口不可见) | ⛔ | —(系统行为,全员可见失败文案) |
|
||
| 计划员(planner,缺省角色) | ✅ P1 草稿 + ✅ P2 计划卡 | ✅ | ✅ | ✅(S4.roles 含 planner;产物仅草稿) | ✅ 只读 | ✅(S6.roles 含 planner;逐笔卡) | ⛔(入口不可见) | ✅ | — |
|
||
| 运维(ops) | 👁 | 👁 | ⛔(S3 属计划员域) | ⛔(S4.roles 不含 ops) | 👁 | 👁 可见对账报告;补录卡发起按 S6.roles 配置(默认不含 ops) | ✅(只读诊断 + 白名单内 P3 配置卡;双人审批) | ⛔ | — |
|
||
| 管理员(admin) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅(唯一可 approve S7 P3 配置变更的角色,按 §4.1 策略示例) | ✅ | — |
|
||
|
||
矩阵落点:**白名单 `scenarios.<S>.roles` 是可见性/发起性的唯一数据源**(文件化、
|
||
改它要过卡);批准性由 `APS_APPROVAL_ROLE_POLICIES` action 级策略表达(env 配置)。
|
||
两张表在 fallback.md P3 章节并排呈现,文档同步铁律适用。
|
||
|
||
### 4.3 触发闸顺序(propose_reply 内,新增第 1.5 关)
|
||
|
||
```
|
||
闸 1:fallback 开关(既有,关 → None)
|
||
闸 1.5(新增):场景-角色预检 —— 编排器先按 query 做轻量场景归类
|
||
(关键词命中只是「归类提示」,不是动作升级——红线 §10 的合规边界:
|
||
归类只决定「给 Pi 的简报里声明哪些场景可用」与「入口可见性」,
|
||
真正的动作仍由计划锁 + 确认卡 + 白名单裁决):
|
||
· 命中运维信号(「日志」「配置」「诊断」「运维」+白名单 S7 enabled)且
|
||
身份 ∉ S7.roles → return None(物理不可见,§4.1.3)
|
||
· 未命中任何 P3 场景信号 → 按 P1/P2 现有路径继续(简报不含 P3 场景段)
|
||
闸 2:运行时可用性 / 闸 3:凭证校验(既有)
|
||
闸 4(新增出卡侧):validate_plan 白名单裁决(§3.3)
|
||
闸 5(stage 既有):_can_initiate_current 角色策略(harness,PermissionError
|
||
→ 编排层捕获归并显式失败文案,不抛穿聊天链路)
|
||
```
|
||
|
||
---
|
||
|
||
## 5. S4 设计:缺算法/策略的沙盒试排(GOAL 交付 2 + 前置问题 2)
|
||
|
||
### 5.1 增量价值边界(前置问题 2 的回答)
|
||
|
||
**结论:S4 = Pi 编排既有沙盒意图 + 沙盒外(run 目录内)临时分析脚本,产物草稿。
|
||
不自建试排引擎、不发明新排产算法。**
|
||
|
||
增量价值在三处(与「flex.simulate_due / scenario.compare 已存在」的重复造轮子风险
|
||
正面对账):
|
||
|
||
1. **意图编排**:现有沙盒意图各自独立、靠用户逐个发起;S4 让 Pi 按用户目标(「这个
|
||
工艺没有现成策略,帮我试几个思路」)**自动组合** simulate_due(交期探测)→
|
||
compare/sensitivity(策略对比)→ 产出横向对比报告。编排逻辑是 LLM 的增量,
|
||
试排本身 100% 复用既有函数。
|
||
2. **参数探索**:临时脚本(run 目录 work/ 内,fs_write 写入、**不被执行**——见 §5.2
|
||
的硬边界)做的是「从快照数据推导候选参数组合」(如按瓶颈工序反推优先级权重),
|
||
推导结果作为沙盒意图的**输入参数**冻结进步骤;计算本身仍在产品沙盒函数内发生。
|
||
3. **草稿叙事**:跨多次沙盒调用的结果汇总成一份带 KPI 对比基线的草稿报告
|
||
(方案 S4 行验证方式「沙盒 KPI 对比基线版本」的落点)。
|
||
|
||
**明确不做**:Pi 生成的试排脚本**不被任何解释器执行**(无 shell_run、无 python 桥——
|
||
围墙内没有代码执行面,P1/P2 皆然,P3 不扩);「临时脚本」的物理形态 = 产物文件
|
||
(work/strategy-notes.md、outbox/draft-report.md),其内容供人审阅、供 Pi 自己
|
||
在围墙内读写迭代,**唯一起算作用的是编排器直调的既有沙盒函数**。这是 S4 风险的
|
||
物理收敛点:LLM 写错脚本的最坏后果 = 草稿报告内容错,世界不动。
|
||
|
||
### 5.2 执行形态
|
||
|
||
```
|
||
propose(plan 模式,简报含 S4 段:可用沙盒意图清单 + 「产物仅为草稿」声明)
|
||
Pi 读 inbox 快照 → 推导候选策略参数 → 产 plan.json:
|
||
steps = [frozen × N],intent ∈ 白名单 S4.intents
|
||
(flex.simulate_due / flex.compare / scenario.compare / scenario.sensitivity),
|
||
params 内联(§0.14③),expected 声明「预期输出类型」而非世界变化
|
||
(沙盒步骤无世界 diff,expected 只允许 {"sandboxOutput": "<说明>"} 形态,
|
||
validate_plan 对 S4 步的 added/removed/modified 字段显式拒绝)
|
||
→ 出卡:action = "agent.fallback.execute"(P2 卡——见 §5.4 的权力取舍),
|
||
summary 逐步行由编排器再生成 + 固定尾行「本计划全部步骤在沙盒内执行,
|
||
不改动任何正式数据」
|
||
execute(execute_plan 新 S4 分支,步骤 3 的 checkpoint 前置之后):
|
||
逐步直调既有沙盒函数(workflow._flex_simulate_due / flex.compare_sort_modes /
|
||
scenario.compare_scenarios 的领域层函数,不经 intent 管线)→
|
||
每步结果写 execution.jsonl + 汇总进 outbox/sandbox-report.md →
|
||
**验证器断言**(fallback_verify 新函数 verify_sandbox_no_main_writes):
|
||
world_fingerprint(执行前) == world_fingerprint(执行后)
|
||
(§0.7:append-only 审计键不影响指纹)且执行期间未产生任何
|
||
category=WORLD_WRITE 的审计事件(扫 cp_before..cp_after 区间 auditEvents)→
|
||
断言失败 → 按执行失败处理:自动回滚 + 显式文案(防御纵深:沙盒函数理论上
|
||
不写主干,断言是把「草稿语义」从约定变成物理判决)
|
||
成功:cp_after 建档 + sandbox-report.md 指纹进审计 rationale
|
||
```
|
||
|
||
### 5.3 为什么不跳过 checkpoint
|
||
|
||
S4 不写主干,理论上不需要回滚锚点;但 ① 断言失败时回滚是兜底语义的一部分(防「沙盒
|
||
函数未来被改出副作用」的漂移);② 前后快照对让「零变化」断言可审计复算(验证报告
|
||
引用两个 pairId,数字只许来自冻结快照——P2 铁律延续)。成本 = 两次快照拷贝,可忽略。
|
||
|
||
### 5.4 S4 出卡权力的取舍
|
||
|
||
S4 全部步骤 power ∈ {P0,P1},理论上可以免卡直通;**但决定仍出 P2 确认卡**,理由:
|
||
① 用户要能看到「Pi 打算试哪些策略组合、用什么参数」再放行(S4 是探索性行为,参数
|
||
组合本身是决策);② 计划指纹 + 漂移比对 + 执行证据链全部复用,免卡要新开一条
|
||
「P1 计划直通」通道,违背改动最小化;③ GOAL S4 行治理策略原文「仅沙盒语义,产物为
|
||
草稿版本」——卡的内容就是草稿声明。代价 = 多一次人工确认(低频探索场景,可接受)。
|
||
|
||
---
|
||
|
||
## 6. S6 设计:集成故障补录与恢复后对账(GOAL 交付 3 + 前置问题 3)
|
||
|
||
### 6.1 阶段划分(四段,每段独立可验)
|
||
|
||
```
|
||
段 A 故障确认(P0 只读):编排器探测 → 状态注入 inbox → Pi 诊断报告
|
||
段 B 待补录清单(P1 草稿):编排器从世界状态推导候选清单 → Pi 补充/整理 → 清单冻结
|
||
段 C 逐笔补录(白名单 + 逐笔确认卡):清单每一项 = 一张独立 P2 卡
|
||
段 D 恢复后对账(P0 读侧对账 + 差异报告 + 证据链冻结)
|
||
```
|
||
|
||
### 6.2 故障检测信号来源(§0.13 结论的落实)
|
||
|
||
不新造告警子系统;三个既有信号源在 propose 时由编排器聚合注入 inbox:
|
||
|
||
1. `mes_connection_status()`(mes.py:28)——connected=False + error code;
|
||
2. `HttpMesClient.readiness(probe=True)`(mes_http.py:234)——connectivity=failed +
|
||
lastError{code,message,statusCode}(轻量 ≤3s,propose 同步预算内可承受;
|
||
probe 失败成本已含在 P1 简报里,不额外阻断);
|
||
3. 世界状态侧投影:`mesLinks`(下发链路)、`workOrders` 进度缺口、
|
||
`wmsPending`/`wmsConsumed`(WMS 事件缓冲)——`export_integration_status()`
|
||
(pi_bridge 新增)把这三者 + ①② 写成 `inbox/integration-status.md` +
|
||
`inbox/pending-sync.json`(结构化),UNTRUSTED_DATA 声明同款包裹。
|
||
|
||
Pi 侧只做一件事:基于 inbox 判断「是否确为断连场景」并产出段 A 诊断报告
|
||
(断连则进入段 B 协议;未断连则如实报告「连接正常,无需补录」——防空转)。
|
||
|
||
### 6.3 段 B:待补录清单产物形态
|
||
|
||
- **候选清单由编排器确定性生成**(不从 Pi 文本解析):扫描 workOrders 中
|
||
`mesLinks` 已下发但进度长时间未回流/状态停滞的条目 + 用户在对话里附的 MES
|
||
导出文件(inbox 客户文件,P2 先例)→ `outbox/backlog-candidates.json`
|
||
(编排器写,sha256 可算);
|
||
- **Pi 的职责收敛为「整理与补充」**:读候选清单 + MES 导出文件,补齐缺失字段
|
||
(外部工单号、实际完工时间、数量),产出 `outbox/backlog.json`(结构化,
|
||
schema 固定:`{"items":[{"woId","externalWoId","progressPct"|"finish","qty"?,"note"?}]}`);
|
||
- 编排器对 backlog.json 做**逐项校验**(woId 存在、externalWoId 与 mesLinks 对得上、
|
||
progressPct ∈ [0,100]、finish 布尔)——任一不过 → 显式失败不出卡(P2 计划校验
|
||
同款哲学:非法产物绝不降级糊弄);
|
||
- 清单同时渲染 `outbox/backlog.md` 供人审(卡片 summary 逐项列明,机器再生成)。
|
||
|
||
### 6.4 段 C:逐笔确认卡链路(「逐笔出卡」的落实)
|
||
|
||
- backlog.json 的**每一项 = 一个单步计划 = 一张独立确认卡**
|
||
(action=`agent.fallback.execute`,scenario=S6,step={frozen, intent="mes.report",
|
||
params={"woId":..., "progressPct"|"finish":...}});
|
||
- `_stage_plan_confirmation` 分流:scenario=S6 → 循环 stage N 张卡(每张独立
|
||
confirmId、独立计划指纹、独立 beforeFingerprint),同一 AgentReply.blocks 依次
|
||
下发(§0.12 前端逐张渲染);N > `maxItemsPerRun`(白名单字段,默认 20)→
|
||
截断并在文案显式声明「本次出卡前 20 笔,剩余 N-20 笔请在批准后重新发起」
|
||
(分批确认,P2-DESIGN §5.2④ 的分段策略延续);
|
||
- 每张卡的执行 = execute_plan 既有路径(checkpoint 对 + 漂移比对 + 逐步执行 +
|
||
diff 验证),步骤执行器复用 `_apply_step` 新增 `mes.report` 分支 →
|
||
`mes.apply_report(store, wo_id, ...)`(§0.10,既有函数一行不改);
|
||
- **逐笔独立成败**:卡与卡之间无事务关联——第 3 笔批准失败/拒绝不影响第 1/2 笔
|
||
已落账结果(补录场景的正确语义:已补的就是事实);每张卡自己的回滚锚点独立;
|
||
- 全部卡处理完后用户在对话里说「补录完了」→ 进入段 D 判定(下一轮 propose)。
|
||
|
||
### 6.5 段 D:恢复后对账(前置问题 3 的回答)
|
||
|
||
**「恢复后」语义:人工触发,不是事件订阅。** 用户明示(「MES 恢复了,对一下账」)
|
||
→ 新一轮 propose → 编排器 probe readiness:**connected/ok 才进入对账协议**;probe 仍
|
||
failed → 如实报告「尚未恢复」并停在段 A 形态(不猜、不轮询、不后台监听——
|
||
无事件系统,不造一个)。
|
||
|
||
**对账流程(全部读侧 P0,零写)**:
|
||
|
||
```
|
||
编排器确定对账域(双来源并集,全部可机读):
|
||
① 世界内 mesLinks(kind=dispatch 的外部工单号集合)
|
||
② 审计链扫描:本断连窗口内 action 属补录集(agent.fallback.execute + mes.report
|
||
步骤)的 WORLD_WRITE/TOOL 事件 → 提取 woId/externalWoId + 补录参数
|
||
对账域内每个 externalWoId:
|
||
orchestrator 直调 fetch_work_order(external_wo_id)(§0.9,只读 P0)
|
||
→ 原始响应原文落 run 目录 inbox/external/<id>.json(UNTRUSTED 标记,
|
||
文件 sha256 记 reconcile-manifest.json)
|
||
比对(fallback_verify 新函数 reconcile_external):
|
||
外部状态/进度/数量 vs 世界侧 workOrders 投影 → 三档结论:
|
||
MATCH(一致)/ DRIFT(外部与本地不一致,列差异字段)/ MISSING(单边存在)
|
||
产物:outbox/reconcile-report.md(机器生成,逐工单一行 + 汇总计数)
|
||
+ reconcile-manifest.json(全部外部响应的 sha256 清单 = **证据链冻结点**)
|
||
审计:write_audit(category="ALGO_RUN", action="agent.fallback.reconcile",
|
||
power="P0", rationale={"domain": n, "match": n, "drift": n, "missing": n,
|
||
"manifestSha256": ..., "externalSnapshotDir": ...}) —— 对账结论与其证据指纹进链
|
||
差异处置:报告里 DRIFT/MISSING 条目只给「建议动作」文本;任何纠正写操作
|
||
→ 回到段 C 逐笔卡(对账本身永远不写,这是它与段 C 的物理分界)
|
||
```
|
||
|
||
**证据链冻结的构成**(三层,逐层可复算):
|
||
① 外部响应原文文件(run 目录,sha256 入 manifest)——世界外证据,回滚抹不掉;
|
||
② manifest sha256 + 各文件 sha256 进审计 rationale(哈希链内);
|
||
③ reconcile-report.md 的数字只许来自 ① 的冻结文件(报告尾固定声明行,
|
||
fallback_verify.build_report 同款铁律)。
|
||
Pi 在对账段的唯一角色:把机读差异「翻译」成处置建议文本——**建议不进任何执行路径**
|
||
(P2-DESIGN §5.1 的信任边界延续)。
|
||
|
||
---
|
||
|
||
## 7. S7 设计:现场运维诊断(GOAL 交付 4)
|
||
|
||
### 7.1 入口判定(「运维人员显式发起」的落实)
|
||
|
||
- 触发 = 对话内显式运维意图(闸 1.5 的场景信号,§4.3)+ 身份 ∈ 白名单 S7.roles
|
||
(默认 ops/admin)——两个条件缺一不可,缺身份 → 物理不可见(§4.1.3);
|
||
- 无独立 UI 入口、无新 HTTP 端点(运维入口 = 同一个聊天框 + 角色门禁;
|
||
「专属入口」由「非运维角色物理不触发」实现,与 GOAL「不新造权限模型」对齐)。
|
||
|
||
### 7.2 只读诊断面(P1 语义,围墙零扩张)
|
||
|
||
`export_ops_diagnostics()`(pi_bridge 新增,编排器在 propose 时调用)把以下写进
|
||
inbox(Pi 仍只有 fs_read 一个读工具——**不登记任何新工具**):
|
||
|
||
| 文件 | 内容 | 脱敏规则 |
|
||
|------|------|---------|
|
||
| `inbox/ops/logs-tail.md` | server 日志目录最近 N 行(`APS_FALLBACK_OPS_LOG_LINES`,默认 300;多文件各取尾部) | 行级脱敏:匹配 `(?i)(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*\S+` 的值替换为 `***REDACTED***`;JMS/KIMI 等 key 形态(长 hex/base64 ≥24 位出现在 key= 后)同款 |
|
||
| `inbox/ops/config-snapshot.md` | features.json 全文 + fallback-highrisk.json 的**裁决结果投影**(非原文,含 path/sha256/error)+ FallbackConfig 公开字段 | 文件内容同样过行级脱敏 |
|
||
| `inbox/ops/health.md` | `/api/readiness` 同源 check_readiness 结果 + 审计链完整性(chain.ok/checked)+ audit_alerts.build_alerts 输出 | 无敏感面 |
|
||
| `inbox/ops/integrations.md` | mes/wms/sap 各 status() + readiness(probe=False)(**S7 默认不主动 probe**,避免诊断动作本身产生外部副作用;probe 显式由用户在对话要求时才做) | — |
|
||
|
||
诊断产物 = P1 草稿报告(诊断结论 + 建议人工操作清单)——方案 S7 行降级策略原文
|
||
「输出诊断报告 + 建议人工操作」的直通形态。
|
||
|
||
### 7.3 改配置:白名单 + 确认卡(P3 双人)
|
||
|
||
S7 白名单 intents 默认两项:
|
||
|
||
- `agent.fallback.policy.update`(P2,§2.4):白名单自身治理;
|
||
- `agent.fallback.ops.config.apply`(**P3**,harness 新登记):受控配置文件变更。
|
||
- params 冻结:`{"file": "features.json", "content": <完整新文档>, "contentSha256": ...,
|
||
"beforeSha256": ...}`——**整文档替换形态**(不做 patch 语法:patch 是注入面,
|
||
整文档可 diff、可 sha256、可人审;文件白名单只允许 `features.json`,
|
||
fallback-highrisk.json 只走 policy.update 专属通道);
|
||
- 出卡前编排器校验:file ∈ 允许集、新文档过 `load_feature_flags` 同款结构校验
|
||
(version/features 段合法——用独立校验函数,不 import feature_flags 的 fail-open
|
||
语义,§0.4);
|
||
- P3 卡 → **双人审批(两个不同用户)→ executionGrant**(§0.1 既有机制)→
|
||
execute_confirmed 新分支:grant 消费 → beforeSha256 漂移比对 → tmp 写 + 回读校验 +
|
||
os.replace 原子替换 + .bak 备份 → WORLD_WRITE 审计(含 grantId 引用、前后 sha256、
|
||
approver 双人列表);
|
||
- 生效语义如实写明:features.json 每次现读(feature_flags 无缓存),**文件替换即生效**;
|
||
但「生效范围 = 可见性开关」——fallback 键本身被关只会让兜底不再触发,不会中断
|
||
在途审批(审批仓独立)。
|
||
|
||
### 7.4 审计回放要求(GOAL「全程审计可回放」)
|
||
|
||
每个 S7 run 的 run 目录追加 `manifest.json`(编排器写):
|
||
```json
|
||
{"runId": "...", "scenario": "S7", "actor": "ops-zhang",
|
||
"whitelistSha256": "...", "artifacts": {"events.jsonl": "sha256:...",
|
||
"calls.jsonl": "sha256:...", "orchestrator.log": "sha256:...",
|
||
"outbox/report.md": "sha256:..."}, "auditSeqRange": [起, 止]}
|
||
```
|
||
回放 = manifest 所列文件逐一验 sha256 + 审计链区间事件链式校验(/api/gov/audit
|
||
既有 chain.ok 机制覆盖区间校验)。「录屏级」的如实替代:**事件流全量落盘 +
|
||
哈希自证**,比录屏更强(可机读、可复算)——文档里如实说明这是事件级回放而非
|
||
屏幕录像。
|
||
|
||
---
|
||
|
||
## 8. 测试矩阵(tests/golden/test_fallback_highrisk.py,全部确定性)
|
||
|
||
风格对齐 test_fallback_execute.py:tmp_path + monkeypatch(APS_FALLBACK_DIR /
|
||
APS_FEATURES_PATH / APS_CHECKPOINT_PATH / **APS_FALLBACK_HIGHRISK_PATH** 指 tmp)+
|
||
FakeStore 挂 `.checkpoints` + fake runner 注入 + bind_identity 绑角色
|
||
(test_approval_role_policies.py:45 `_as` 先例)。清空 LLM env。
|
||
公共辅助:`_write_whitelist(tmp, doc)`、`_stage_plan(store, plan, roles=(...))`、
|
||
`_approve(store, confirm_id, roles=...)`、`_fp(world)`。
|
||
|
||
| # | 用例名 | 前置 | 断言 |
|
||
|---|--------|------|------|
|
||
| H-1 | `test_whitelist_missing_file_denies_all` | 白名单文件不存在 | `load_highrisk_whitelist().ok == False`;S6 计划出卡拒绝(显式「白名单文件缺失」);世界零变更 |
|
||
| H-2 | `test_whitelist_corrupt_json_denies_all` | 文件写非法 JSON | 全拒 + error 显式;S4/S6/S7 计划均拒绝出卡 |
|
||
| H-3 | `test_whitelist_unknown_scenario_key_denies_all` | 文件含 "S99" 键 | **整个文件判损坏全拒**(不是忽略未知键——与 features.json 相反的断言) |
|
||
| H-4 | `test_whitelist_unknown_field_denies_all` | scenario 段含未知字段 | 全拒 + error 带 offending key |
|
||
| H-5 | `test_whitelist_unregistered_intent_denies_all` | intents 含未在 _POWER_MAP 登记的意图 | 全拒 + error 带 intent 名 |
|
||
| H-6 | `test_whitelist_disabled_scenario_refuses_plan` | S6.enabled=false | S6 计划拒绝出卡(「未在白名单启用」);S4 计划不受影响(场景隔离) |
|
||
| H-7 | `test_p3_intent_not_in_whitelist_refused` | 白名单 ok 但 S6.intents 不含 mes.dispatch | 含 mes.dispatch 步的 S6 计划整计划拒绝(P2 T-3 语义的白名单版) |
|
||
| H-8 | `test_p2_scenarios_unaffected_by_whitelist` | 白名单文件缺失 | S3 计划(P2 语义)照常出卡——**白名单不挡 P2 存量路径**(向后兼容核心断言) |
|
||
| H-9 | `test_whitelist_changed_between_stage_and_execute_denied` | 出卡后改白名单文件 | execute_plan denied(「白名单在审批窗口内已变更」);零写入;DENIED 审计 |
|
||
| H-10 | `test_s4_sandbox_plan_executes_with_no_main_writes` | S4 白名单放行;fake runner 产合法 S4 计划(flex.simulate_due frozen 步) | 沙盒函数被调(结果进 execution.jsonl);**前后世界指纹相等**;无 WORLD_WRITE 审计(TOOL/ALGO_RUN 除外);sandbox-report.md 生成;卡片含「不改动任何正式数据」尾行 |
|
||
| H-11 | `test_s4_plan_with_write_intent_refused` | S4 计划混入 import.commit | 出卡拒绝(S4 只许白名单沙盒意图) |
|
||
| H-12 | `test_s4_expected_world_change_fields_refused` | S4 步 expected 含 {"added": 1} | validate_plan 拒绝(沙盒步只许 sandboxOutput 形态) |
|
||
| H-13 | `test_s6_backlog_per_item_cards` | S6 白名单放行;backlog.json 3 项 | 同一 AgentReply.blocks 含 **3 张独立 confirm-card**(独立 confirmId/指纹);逐张批准后逐笔落账 + 每张独立 checkpoint 对;第 2 张拒绝不影响 1/3 |
|
||
| H-14 | `test_s6_backlog_over_max_items_truncated` | maxItemsPerRun=2,清单 3 项 | 只出 2 卡 + 文案显式声明截断 |
|
||
| H-15 | `test_s6_backlog_item_invalid_refused` | backlog 项 woId 不存在 / progressPct=120 | 逐项校验失败 → 不出卡 + 显式原因 |
|
||
| H-16 | `test_s6_reconcile_report_and_evidence_frozen` | 模拟恢复(fake fetch_work_order 注入);对账域 2 项(1 MATCH 1 DRIFT) | reconcile-report.md 结论正确;manifest 含外部响应 sha256;ALGO_RUN 审计 rationale 含 manifestSha256;**世界零变更(对账纯读)** |
|
||
| H-17 | `test_s6_reconcile_blocked_while_still_down` | probe 仍 failed | 不产对账报告,如实回复「尚未恢复」 |
|
||
| H-18 | `test_s7_non_ops_role_invisible` | 白名单 S7.roles=["ops","admin"];bind_identity(roles=("planner",));query 含运维信号 | propose_reply 返回 **None**(走原话术,零 run 目录、零审计——物理不可见的字面断言) |
|
||
| H-19 | `test_s7_ops_readonly_diagnostics` | ops 身份 | inbox/ops/ 四文件生成;**脱敏断言**:埋入 `token=abc123secret` 的日志行在产物中呈 `***REDACTED***`;P1 草稿报告语义 |
|
||
| H-20 | `test_s7_config_apply_p3_dual_approval_flow` | S7 白名单含 ops.config.apply;合法 features.json 新文档 | 卡 power=P3;第一次批准 → PARTIALLY_APPROVED;同人二批 → SOD_DENIED;**不同用户二批** → executionGrant 签发 → 执行原子替换 + .bak 备份 + 回读校验 + 双人审计 |
|
||
| H-21 | `test_policy_update_writes_whitelist_via_card` | policy 计划(diff 声明 addIntents) | 编排器再生成完整文档;P2 卡批准后白名单文件更新;旧文件 .bak 存在;beforeSha256 漂移(出卡后手改文件)→ 执行拒绝;审计含前后 sha256 |
|
||
|
||
另:test_fallback_execute.py 的 T-3 同轮更新(§1 #7);P1/P2 既有 32 例全部回归
|
||
(白名单文件缺失时 P2 路径零影响——H-8 专项守护)。
|
||
|
||
---
|
||
|
||
## 9. 真实冒烟脚本设计(GOAL 交付 7)
|
||
|
||
### 9.1 `poc/pi-fallback/smoke/smoke_s6_reconcile.py`:MES 断连全链路
|
||
|
||
环境:临时 APS_HOME(不污染 server/data)+ features.json 开 fallback +
|
||
**写 fallback-highrisk.json 放行 S6**(冒烟脚本自己造白名单文件——顺带实测
|
||
「文件缺失全拒」的对照组)+ 真实 K2.6 key + 真实 server + 真实 pi。
|
||
|
||
| # | 验收点 | 通过判据 |
|
||
|---|--------|---------|
|
||
| A | 断连注入:server 启动 env `MES_HTTP_BASE_URL=http://127.0.0.1:<未监听端口>`(§0.8 唯一切换点) | `/api/integrations/mes/readiness` connectivity=failed,error code=MES_HTTP_CONNECT_FAILED |
|
||
| B | 造断连前现场:先经既有链路下发 2 张工单到 stub(切回 stub 配置下发后再切断连,或直接种子数据),并造 1 笔「断连期间实际已完工」的待补录事实(用户在对话陈述 + inbox 放 MES 导出 CSV) | 世界内 mesLinks ≥2;导出文件入 inbox |
|
||
| C | 对话「MES 连不上了,今天的报工帮我补一下」 | SSE intent=assistant.reply;inbox/integration-status.md 含 connectivity=failed;Pi 产段 A 诊断 + backlog.json |
|
||
| D | 逐笔卡 | blocks 内确认卡张数 == backlog 项数;逐张批准 → 每笔落账 + 独立 checkpoint 对;summary 逐笔列明且全部由结构化字段再生成(无 Pi 散文) |
|
||
| E | 恢复注入:`reset_http_mes_client()` 切回 stub(冒烟脚本内嵌 shim:重启 server 或环境切换后探活 ok) | readiness connectivity=ok |
|
||
| F | 对话「MES 恢复了,对一下账」 | reconcile-report.md 生成;外部响应原文落盘 + manifest sha256;ALGO_RUN 审计含 manifestSha256;DRIFT/MATCH 结论与脚本构造的事实一致 |
|
||
| G | 对照组(同脚本 flag):删掉白名单文件重跑 C | 兜底显式失败「白名单文件缺失」,零出卡零变更 |
|
||
| H | 收尾:taskkill 后无残留进程;server/data 零污染;git status 干净 | — |
|
||
|
||
### 9.2 `poc/pi-fallback/smoke/smoke_s7_ops.py`:运维只读诊断
|
||
|
||
环境同上 + 白名单放行 S7 + 冒烟身份:JMS_AUTH_DEFAULT_ROLE 或 TestClient 绑身份
|
||
机制给会话 ops 角色(冒烟脚本用 server 的测试登录路径,与 P2 冒烟同手法)。
|
||
|
||
| # | 验收点 | 通过判据 |
|
||
|---|--------|---------|
|
||
| A | ops 身份对话「帮我看下服务日志和配置有没有异常」 | inbox/ops/ 四文件生成;日志尾部含近期行;脱敏生效(预埋 token 行呈 REDACTED) |
|
||
| B | 诊断报告 | P1 草稿语义:报告引用 [callId:] 真实存在;世界零变更(指纹前后相等) |
|
||
| C | 非 ops 对照:planner 身份发同一句 | 走原话术,run 目录零新增(物理不可见冒烟层复证 H-18) |
|
||
| D | (可选,预算允许)config.apply 双人审批链:ops 发起 + 另一 admin 账号二批 | P3 卡双人链走完,features.json 原子替换 + .bak + 审计双人记录 |
|
||
|
||
### 9.3 冒烟分工与确定性边界
|
||
|
||
S6/S7 冒烟归 Agent-N;黄金层(§8)已把白名单各形态、角色越权、逐笔卡、双人链
|
||
全部确定性锁定——冒烟层的增量只是「真实 server + 真实 pi + 真实 MES 适配器在环」
|
||
的接线事实(K 轮 P2 教训:真实子进程才能暴露的缺陷类型——工具事件映射、简报字段
|
||
引导——本轮靠 §0.14 已吸收的 K-1/K-2 修复 + 简报模板 S6/S7 段落显式字段名预防)。
|
||
|
||
---
|
||
|
||
## 10. 爆炸半径分析(rg 实测;标注 LOW/MED/HIGH)
|
||
|
||
### 10.1 fallback_lane.py(P1/P2 自有模块扩展)—— **MED**
|
||
|
||
外部调用方 rg 实测仅两处:workflow.py:583(execute_plan)、workflow.py:2927
|
||
(propose_reply)。模块内改动面:
|
||
- `propose_reply`:闸 1.5 角色预检插入在开关闸之后——开关关路径第一关 return None
|
||
**逐字节不变**(P1 测试守护);未命中 P3 场景信号的路径简报与 P2 相同(P2 T-1..T-17
|
||
守护);
|
||
- `validate_plan`:P2 场景分支条件原样(H-8 守护),P3 场景走新裁决函数——新代码
|
||
路径只在 scenario ∈ {S4,S6,S7} 时触达;
|
||
- `_stage_plan_confirmation`:S6 分流为循环出多卡;单卡路径(scenario != S6)
|
||
逐字节保持(含 §0.14①② 的指纹对齐与 setdefault——新卡路径同样照做);
|
||
- `execute_plan`:S4 沙盒分支插入在 checkpoint 前置之后;P2 双模式路径(FROZEN/
|
||
ASSISTED)不进新分支。
|
||
**降级方案(若评审判 HIGH)**:P3 场景全部冻结为 frozen 直执(§3.4 已如此),
|
||
分支体整体 try/except 归并显式失败 + checkpoint 前置——最坏情况 = 一次显式失败
|
||
且已回滚的确认(P2-DESIGN §9.2 同款兜底语义)。
|
||
|
||
### 10.2 workflow.py execute_confirmed(+2 分支)—— **MED**
|
||
|
||
调用方(P2-DESIGN §9.2 已实测):app.py confirm/confirm-batch、mesh.py、saga.py、
|
||
run_automation_intent——全部「带 confirmId 进来」的通用入口,新分支按 action 精确
|
||
匹配,既有分支逐字节不变。文件写类分支(policy.update / config.apply)全异常归并
|
||
显式失败文案(绝不抛出——confirm 单条调用方无异常隔离,P2 同款纪律);写文件前
|
||
beforeSha256 漂移比对 + tmp+replace + 回读校验,**失败不留下半个文件**。
|
||
降级方案:config.apply 执行失败 = 文件未被替换(tmp 未 promote),显式文案 +
|
||
FAILED 审计——原子写保证无中间态。
|
||
|
||
### 10.3 harness.py(+4 行纯追加)—— **LOW**
|
||
|
||
_POWER_MAP/_POLICY_DESC 各 +2。影响面与 P2 同款(P2-DESIGN §9.1 逐条适用):
|
||
list_policy 多两行(管理台可见,预期内);tool_runtime 登记检查自然生效但
|
||
IntentName 不含新 action,intent 管线永不产出——唯一触达路径是 fallback 编排出卡
|
||
与 execute_confirmed 分支。**额外注意**:新 P3 action 使 stage_confirmation 自动走
|
||
requiredApprovals=2(harness.py:683 既有逻辑按 power 分派)——零代码获得双人语义,
|
||
test_harness_p3.py 的既有双人用例模式直接复用(H-20)。
|
||
|
||
### 10.4 pi_bridge.py(纯追加)—— **LOW**
|
||
|
||
新增两个 export_* 方法 + 简报模板段落;TOOL_REGISTRY 五键不变;handle_fs_read /
|
||
handle_fs_write / ActionMailbox / 凭证签发路径零改动(P1/P2 黄金测试守护)。
|
||
脱敏函数是纯字符串变换,失败归并(单文件脱敏异常 → 该文件跳过 + orchestrator.log
|
||
记一行,不阻断兜底——与 export_snapshot 失败降级同款,fallback_lane.py:791 先例)。
|
||
|
||
### 10.5 fallback_highrisk.py(新建)—— **零存量影响**
|
||
|
||
被 fallback_lane(裁决)与 workflow(policy.update 分支的 validate_whitelist_doc)
|
||
引用;不 import poc;不 import feature_flags(§0.4 语义隔离的代码级落实)。
|
||
|
||
### 10.6 state/ / gateway / 前端 / contracts —— **零改动**
|
||
|
||
checkpoint/审批仓/confirm 通道/ConfirmCard 全部原样复用(§0.1/§0.12)。
|
||
mes.py / mes_http.py / wms_events.py / automation.py / audit_alerts.py /
|
||
feature_flags.py / intent.py / assistant.py:一行不改。
|
||
|
||
**全表无 HIGH 项。** MED 集中点(10.1/10.2)均已给降级方案,且全部被 §8 确定性
|
||
用例直接覆盖成功/失败双路径。
|
||
|
||
---
|
||
|
||
## 11. 明确不做的事(P3 边界外,施工遇到即停)
|
||
|
||
1. **P4 内容**:兜底质量 golden 集四指标评估、20 例注入防护集扩建(本轮注入防线
|
||
沿用 P2 机制 + 白名单 fail-closed,不新造攻击集)、离线降级演练。
|
||
2. **P5 内容**:发布门禁、康尼数据集验收、现场 runbook 正文(§4.1 的策略配置示例
|
||
只进 fallback.md 文档)。
|
||
3. **桌面打包 / sidecar 集成**(sidecar.cjs 一行不动;pi 打包分发是 P5)。
|
||
4. **新 HTTP 端点 / 前端组件 / 独立运维 UI**(§7.1:运维入口 = 聊天框 + 角色门禁;
|
||
ConfirmCard 四 props 之外零改动)。
|
||
5. **集成故障告警子系统 / 事件订阅 / 后台轮询恢复检测**(§0.13/§6.5:恢复是人工
|
||
触发,不造事件系统)。
|
||
6. **Pi 围墙内代码执行面**(§5.1:临时脚本只作产物不执行;无 shell_run、无 python
|
||
桥、无自定义 RPC——aps_invoke 仍只有邮箱一种形态)。
|
||
7. **ASSISTED 模式对 P3 场景开放**(§3.4 取舍);artifactRef 路线对 P3 场景开放
|
||
(§0.14③,P3 全面 params 内联)。
|
||
8. **auth 体系改动**(不新造权限模型:roles 自由字符串 + env 策略既有机制纯配置
|
||
复用,server/auth/ 零改动)。
|
||
9. **features.json 与白名单文件合并 / 共用加载器**(§0.4/§2.2 语义隔离是硬约束)。
|
||
10. **WMS 补录写路径**(S6 本轮只覆盖 MES 报工补录;WMS 侧 wms_events 管线已有
|
||
幂等消费语义,断连补录的场景定义与 MES 不同,留待单独设计轮——文档如实登记)。
|
||
11. **不改 contracts.py / shared/schemas / apps/web / state/ / tool_runtime.py /
|
||
async_jobs.py / gateway/app.py**;不 import poc/ 下任何模块。
|
||
|
||
---
|
||
|
||
## 附:P3 新增环境变量
|
||
|
||
| 变量 | 默认 | 说明 |
|
||
|------|------|------|
|
||
| `APS_FALLBACK_HIGHRISK_PATH` | `path_under_data("fallback-highrisk.json")` | P3 白名单文件路径(fail-closed) |
|
||
| `APS_FALLBACK_OPS_LOG_LINES` | `300` | S7 诊断注入的日志尾部行数 |
|
||
| `APS_FALLBACK_S6_MAX_CARDS` | 白名单 `maxItemsPerRun`(缺省 20) | S6 单轮出卡上限(白名单字段优先,env 为全局封顶) |
|
||
|
||
(角色策略复用既有 `APS_APPROVAL_ROLE_POLICIES` / `APS_APPROVAL_*_ROLES`,不新增。)
|