57 KiB
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. 现状关键事实(设计依据,均已源码核实)
- 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 意图直接继承这套双人+授权语义,一行新机制不造。 - 角色体系是自由字符串 + 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_POLICIESJSON 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。 - 匿名/测试身份:
get_identity()无 HTTP 上下文返回 roles=("system",),_current_identity_has_approval_role对 system 放行(harness.py:486)——黄金测试沿用 test_approval_role_policies.py 的bind_identity(IdentityContext(roles=(...)))模式。 - feature_flags 语义对照(feature_flags.py:8-12):features.json 是 fail-open 「可用性/可见性」开关(损坏回退默认全开、fallback 键例外默认关),显式声明"不是权限 边界"。P3 白名单语义必须相反:fail-closed,任何配置问题 → 全拒,且它是权限边界 的一部分。两者文件分离、加载器分离、文档各写各的语义(§2.1),绝不复用同一加载函数 (避免语义混淆——GOAL 硬约束)。
- fallback 核心符号的外部调用方只有两处(rg 实测):workflow.py:583
(execute_plan)与 workflow.py:2927(propose_reply)。
FALLBACK_EXECUTABLE_INTENTS/validate_plan/check_step_request/plan_fingerprint无模块外调用——P3 对计划锁 的扩展爆炸半径天然收敛(§9)。 - 沙盒意图已有成熟先例: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)。 - world_fingerprint 排除 append-only 键(harness.py:102-118,_VOLATILE_WORLD_KEYS 含 auditEvents/logs/mesSyncJournal)——沙盒执行「零主干写」的验证器断言可以直接用 前后指纹相等(审计自然增长不误报),S4 断言与 S6 漂移检测都建立在这上面。
- 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)。 - MES 外部状态只读回流口已有:
HttpMesClient.fetch_work_order(external_wo_id)(mes_http.py:341,GET 轮询,只读)+ MockMesClient 同接口——S6 恢复后对账的读侧 (P0)不需要新 HTTP 面(§6.4)。 - 补录写入口已有:
mes.apply_report(store, wo_id, progress_pct, finish, actor)(mes.py:1408,P1mes.report,workflow.py:2485-2515 既有批量报工先例——逐笔循环- 逐笔 try/except + 汇总文案)。S6 补录 = 编排既有 mes.report,不造新写通道。
- 计划锁现状(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 放行路径。 - P3 确认卡零前端改动(P2-DESIGN §0.6 结论延续):ConfirmCard 只读
confirmId/title/summary/power 四 props;双人审批的 PARTIALLY_APPROVED /
needsSecondConfirm 流程在 app.py confirm 端点与审批仓内闭环,前端确认卡组件不变。
S6 逐笔多卡 = 多个
confirm-card块在同一 AgentReply.blocks 里依次下发,前端逐张 渲染(既有能力,S6 冒烟需实测确认呈现顺序)。 - automation.py / audit_alerts.py 与 S6 触发无关(精读结论):automation.py 是 Gear/受控自动化升级机制(规则触发业务动作),audit_alerts.py 只聚合审计链完整性告警; 现状没有任何「集成故障告警」子系统——S6 的故障信号来源只能是 ① 用户在对话里 陈述(触发兜底)+ ② 编排器在 propose 时主动探测 mes status/readiness 注入 inbox (§6.2)。不新造告警子系统(GOAL 边界外)。
- 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 意图。 - 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):
{
"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):
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)。最小扩展方案:
- 角色字符串约定
ops(运维),由 JMS 平台侧角色 code 直接下发;无 JMS 的现场用JMS_AUTH_DEFAULT_ROLE或既有账号体系赋值——不新造权限模型、不改 auth 任何文件; - highrisk 动作的发起/批准圈定经 action 级策略配置(部署期 env,示例):
(该配置不进代码库,进部署 runbook / fallback.md 文档示例;黄金测试用 monkeypatch setenv 同款机制验证,test_approval_role_policies.py:64 先例。)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"]}}} - 可见性门禁(「非运维物理不可见」)在 fallback 编排层:
propose_reply识别到 S7 运维意图(简报引导 Pi 声明 scenario,或编排器按白名单 roles 预检)时,is_ops_identity()(roles ∩ 白名单 S7.roles)不满足 → 返回 None(走原话术, 零副作用零审计,与开关关同形态)——未授权用户连「存在运维入口」都无从得知。 注意:这不是提示「权限不足」,而是物理不触发(GOAL「物理不可见/不可用」原文)。 - 例外如实登记:
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 已存在」的重复造轮子风险 正面对账):
- 意图编排:现有沙盒意图各自独立、靠用户逐个发起;S4 让 Pi 按用户目标(「这个 工艺没有现成策略,帮我试几个思路」)自动组合 simulate_due(交期探测)→ compare/sensitivity(策略对比)→ 产出横向对比报告。编排逻辑是 LLM 的增量, 试排本身 100% 复用既有函数。
- 参数探索:临时脚本(run 目录 work/ 内,fs_write 写入、不被执行——见 §5.2 的硬边界)做的是「从快照数据推导候选参数组合」(如按瓶颈工序反推优先级权重), 推导结果作为沙盒意图的输入参数冻结进步骤;计算本身仍在产品沙盒函数内发生。
- 草稿叙事:跨多次沙盒调用的结果汇总成一份带 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:
mes_connection_status()(mes.py:28)——connected=False + error code;HttpMesClient.readiness(probe=True)(mes_http.py:234)——connectivity=failed + lastError{code,message,statusCode}(轻量 ≤3s,propose 同步预算内可承受; probe 失败成本已含在 P1 简报里,不额外阻断);- 世界状态侧投影:
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 |
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 键本身被关只会让兜底不再触发,不会中断 在途审批(审批仓独立)。
- params 冻结:
7.4 审计回放要求(GOAL「全程审计可回放」)
每个 S7 run 的 run 目录追加 manifest.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 边界外,施工遇到即停)
- P4 内容:兜底质量 golden 集四指标评估、20 例注入防护集扩建(本轮注入防线 沿用 P2 机制 + 白名单 fail-closed,不新造攻击集)、离线降级演练。
- P5 内容:发布门禁、康尼数据集验收、现场 runbook 正文(§4.1 的策略配置示例 只进 fallback.md 文档)。
- 桌面打包 / sidecar 集成(sidecar.cjs 一行不动;pi 打包分发是 P5)。
- 新 HTTP 端点 / 前端组件 / 独立运维 UI(§7.1:运维入口 = 聊天框 + 角色门禁; ConfirmCard 四 props 之外零改动)。
- 集成故障告警子系统 / 事件订阅 / 后台轮询恢复检测(§0.13/§6.5:恢复是人工 触发,不造事件系统)。
- Pi 围墙内代码执行面(§5.1:临时脚本只作产物不执行;无 shell_run、无 python 桥、无自定义 RPC——aps_invoke 仍只有邮箱一种形态)。
- ASSISTED 模式对 P3 场景开放(§3.4 取舍);artifactRef 路线对 P3 场景开放 (§0.14③,P3 全面 params 内联)。
- auth 体系改动(不新造权限模型:roles 自由字符串 + env 策略既有机制纯配置 复用,server/auth/ 零改动)。
- features.json 与白名单文件合并 / 共用加载器(§0.4/§2.2 语义隔离是硬约束)。
- WMS 补录写路径(S6 本轮只覆盖 MES 报工补录;WMS 侧 wms_events 管线已有 幂等消费语义,断连补录的场景定义与 MES 不同,留待单独设计轮——文档如实登记)。
- 不改 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,不新增。)