@sema-agent/server 7.121.0 → 7.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/MIGRATION.md +175 -0
  2. package/README.md +6 -1
  3. package/README.zh-CN.md +6 -1
  4. package/USAGE.md +57 -18
  5. package/dist/approval-reconciler.d.ts +8 -0
  6. package/dist/approval-reconciler.js +72 -52
  7. package/dist/audit.d.ts +1 -1
  8. package/dist/audit.js +52 -22
  9. package/dist/auto-mode-face.d.ts +16 -5
  10. package/dist/auto-mode-face.js +7 -4
  11. package/dist/bench/s1/live-deps.js +1 -0
  12. package/dist/boot/config-center.js +3 -0
  13. package/dist/boot/leader.js +1 -0
  14. package/dist/boot/reapers.d.ts +3 -0
  15. package/dist/boot/reapers.js +2 -1
  16. package/dist/boot/resolve-spec.js +1 -4
  17. package/dist/boot/shutdown.js +6 -1
  18. package/dist/boot/stage-08-reapers.js +2 -2
  19. package/dist/capabilities/memory-notice.d.ts +1 -1
  20. package/dist/config-catalog.js +4 -4
  21. package/dist/config-center/apply-effective.d.ts +1 -1
  22. package/dist/config-center/apply-effective.js +5 -3
  23. package/dist/config-center/center-request.d.ts +6 -9
  24. package/dist/config-center/center-request.js +11 -27
  25. package/dist/config-center/http-client.js +3 -3
  26. package/dist/config-center/read-warnings.d.ts +56 -35
  27. package/dist/config-center/read-warnings.js +67 -19
  28. package/dist/config-center/types.d.ts +7 -8
  29. package/dist/config-lkg.d.ts +12 -9
  30. package/dist/config-lkg.js +38 -39
  31. package/dist/config-provider.d.ts +3 -2
  32. package/dist/config-provider.js +2 -2
  33. package/dist/config-types.d.ts +20 -9
  34. package/dist/config-types.js +1 -1
  35. package/dist/config.d.ts +10 -10
  36. package/dist/config.js +92 -127
  37. package/dist/fleet/drain-entry.d.ts +28 -3
  38. package/dist/fleet/drain-entry.js +3 -0
  39. package/dist/fleet/engine-signal.d.ts +54 -23
  40. package/dist/fleet/engine-signal.js +29 -5
  41. package/dist/fleet/serving-lease.js +13 -15
  42. package/dist/fleet-client.d.ts +8 -6
  43. package/dist/fleet-client.js +4 -3
  44. package/dist/governance-overrides.d.ts +7 -2
  45. package/dist/http/admission.js +7 -5
  46. package/dist/http/approval-sweeps.js +2 -2
  47. package/dist/http/checkpoint-ctx.d.ts +60 -11
  48. package/dist/http/checkpoint-ctx.js +23 -9
  49. package/dist/http/dispatch.js +22 -19
  50. package/dist/http/health-self-report.d.ts +3 -0
  51. package/dist/http/resume-legs.js +8 -8
  52. package/dist/http/route-ctx.d.ts +19 -3
  53. package/dist/http/route-table.d.ts +3 -0
  54. package/dist/http/route-table.js +4 -0
  55. package/dist/http/routes/admin-drain.d.ts +5 -2
  56. package/dist/http/routes/admin-drain.js +14 -2
  57. package/dist/http/routes/approvals-assistant.d.ts +1 -0
  58. package/dist/http/routes/approvals-assistant.js +6 -6
  59. package/dist/http/routes/capabilities.js +7 -5
  60. package/dist/http/routes/fleet.d.ts +1 -0
  61. package/dist/http/routes/fleet.js +1 -1
  62. package/dist/http/routes/images.d.ts +1 -0
  63. package/dist/http/routes/images.js +1 -1
  64. package/dist/http/routes/leader.js +6 -0
  65. package/dist/http/routes/runs.d.ts +2 -0
  66. package/dist/http/routes/runs.js +5 -4
  67. package/dist/http/routes/sessions.d.ts +1 -0
  68. package/dist/http/routes/sessions.js +2 -5
  69. package/dist/http/routes/trace-usage.d.ts +1 -0
  70. package/dist/http/routes/trace-usage.js +1 -1
  71. package/dist/http/routes/workflows.d.ts +1 -0
  72. package/dist/http/routes/workflows.js +1 -1
  73. package/dist/http/send.d.ts +13 -0
  74. package/dist/http/send.js +9 -0
  75. package/dist/http/server.d.ts +3 -0
  76. package/dist/http/stored-body-replay.d.ts +28 -0
  77. package/dist/http/stored-body-replay.js +22 -0
  78. package/dist/index.d.ts +2 -0
  79. package/dist/index.js +1 -0
  80. package/dist/leader/leader.d.ts +23 -1
  81. package/dist/leader/leader.js +6 -1
  82. package/dist/leader/repair-wire.d.ts +1 -1
  83. package/dist/leader/repair-wire.js +2 -2
  84. package/dist/leader/run-entry.d.ts +27 -0
  85. package/dist/leader/run-entry.js +29 -0
  86. package/dist/leader/wire.d.ts +10 -0
  87. package/dist/leader/wire.js +15 -10
  88. package/dist/model-entry-refusal.d.ts +18 -9
  89. package/dist/model-entry-refusal.js +19 -14
  90. package/dist/model-route-endpoint.d.ts +21 -1
  91. package/dist/model-route-endpoint.js +9 -3
  92. package/dist/model-route-gap.d.ts +103 -0
  93. package/dist/model-route-gap.js +80 -0
  94. package/dist/model-route-url.d.ts +13 -20
  95. package/dist/model-route-url.js +28 -24
  96. package/dist/namespaced-rule-name.d.ts +16 -30
  97. package/dist/namespaced-rule-name.js +2 -20
  98. package/dist/observability/fail-open.d.ts +23 -10
  99. package/dist/observability/fail-open.js +8 -8
  100. package/dist/plugins/approval-ask-store-file.d.ts +1 -0
  101. package/dist/plugins/approval-ask-store-file.js +3 -0
  102. package/dist/plugins/approval-ask-store-memory.d.ts +1 -0
  103. package/dist/plugins/approval-ask-store-memory.js +6 -0
  104. package/dist/plugins/approval-ask-store-sql.d.ts +12 -0
  105. package/dist/plugins/approval-ask-store-sql.js +4 -0
  106. package/dist/plugins/one-time-migrations.js +40 -0
  107. package/dist/plugins/park-commit-watch.d.ts +19 -5
  108. package/dist/plugins/park-commit-watch.js +24 -17
  109. package/dist/plugins/session-policy-store-sql.js +1 -2
  110. package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
  111. package/dist/router/route-orchestration.d.ts +4 -2
  112. package/dist/router/route-orchestration.js +2 -1
  113. package/dist/rule-entry-list.d.ts +3 -13
  114. package/dist/rule-entry-list.js +1 -12
  115. package/dist/run-local.js +4 -2
  116. package/dist/runtime-governance.d.ts +67 -32
  117. package/dist/runtime-governance.js +28 -9
  118. package/dist/session-policy-wire.d.ts +47 -49
  119. package/dist/session-policy-wire.js +41 -36
  120. package/dist/session-sync.d.ts +5 -1
  121. package/dist/session-sync.js +2 -1
  122. package/dist/sighup-idle.d.ts +7 -1
  123. package/dist/sighup-idle.js +4 -4
  124. package/dist/spec-fields.d.ts +4 -4
  125. package/dist/spec-fields.js +1 -4
  126. package/dist/task-settings.d.ts +36 -14
  127. package/dist/task-settings.js +20 -23
  128. package/dist/tool-approval.d.ts +6 -0
  129. package/dist/tool-approval.js +13 -1
  130. package/dist/trace/redact.d.ts +3 -3
  131. package/dist/unattended-permission-mode.d.ts +17 -13
  132. package/dist/unattended-permission-mode.js +3 -1
  133. package/dist/url-shape.d.ts +31 -0
  134. package/dist/url-shape.js +41 -0
  135. package/package.json +3 -3
  136. package/scripts/drain-check-local.mjs +1 -1
package/MIGRATION.md CHANGED
@@ -7,6 +7,158 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.123.0 —— `GET /v1/models` 自报被读腿丢掉的目录条目(additive)· LKG 信封 formatVersion 2 → 3(升级零动作,回滚有条件)· settings-schema 10.1.1(存量配置里夹私钥 PEM 头的条目升级后被丢) · core 7.48.0(会话规则写口拒空串项 / `mcp__<server>__` 拒码变 / `/` 开头的读拒行根锚定 —— 回滚有条件 / shell 读法收窄与放宽) · `permissionModeAuto.reason` 新词 `operator_floor`
11
+
12
+ **升级前必读**:`SEMA_CONFIG_DIR` 设成工作目录本身或它的祖先(容器里 `SEMA_CONFIG_DIR=/workspace` 而项目在 `/workspace/proj`、或直接设成 `HOME`)的部署,项目里的写都要过 `write_protection` 一问、allow 规则放不掉 —— 把配置根指到项目之外的目录(第 4 条);依赖「`AUTONOMY=plan` 下人批了一次写就写成」的壳核一遍第 5 条的射程(写面根围栏之内);把订阅流的 `error` 终帧整类当「run 已动过」的消费端核第 6 条。往会话规则里写空串 / 纯空白项、或按 `request.field_invalid` 处理 `mcp__<server>__` 的调用方核第 7、8 条;设了 `CONFIG_LKG_PATH` 或运维读拒行以 `/` 开头的部署核第 9 条 —— **回滚到 7.122.0 及以前前先清空停泊任务**;无人值守腿(leader、无终端的 `run-local`)上跑带流喂 shell 的脚本核第 10 条。
13
+
14
+ 1. **`GET /v1/models` 顶层新键 `droppedEntries`**(additive,恒在场;零条 = `[]`,键缺席 = 更早的 server):升级零动作。把未知顶层键当错误的严格解析器要放行该键。
15
+ 2. **LKG 信封 `formatVersion` 2 → 3**(被丢目录条目的记录随 effective 一起落盘,墓碑与 `droppedEntries` 读同一份)。**升级**:启动时读回 7.122.0 及以前写的文件(2)一次性迁移,运维日志 `config_lkg_migrated` 一行;迁移来的文件 **ETag 不复用** ⇒ 中心可达后第一拍是整拉(不是 304),记录随即补全;中心断着的那段时间 `droppedEntries` 只列旧文件记下的那几行。**回滚到 7.122.0 及以前**:那些进程读不懂 3,把这份 LKG 当不可用(运维日志 `config_lkg_unusable` 一行)—— 中心可达则照常拉取、断着则 env 兜底,下一份干净候选按旧格式重写;回滚前确认中心可达,或删掉 LKG 文件让下一次拉取重写。迁移臂在本版发布 30 天后的版本删除。
16
+ 3. **`@sema-agent/settings-schema` 9.0.0 → 10.1.1**:写入校验在串中**任意位置**认私钥 PEM 头(此前只认串首),读侧同一套 schema ⇒ **存量**配置里自由文本位(目录条目的 `extraBody` / `compat` / `headers` / `baseUrl`、mcp 的 `url` / `command` / `args` 等)夹着私钥 PEM 头的:可降级域(`models` / `mcp` / `plugins` …)那一条在读腿被逐条丢掉(目录条目出现在 `GET /v1/models.droppedEntries`,原因 `entry-schema-invalid`;其余域的被丢条目只进 `config_entry_dropped` 日志行);gate 域(`workers` / `entitlement` …)整域读拒 —— 读拒发生在拉取段(读候选即拒),与「源不可达」走同一臂、同一事件名(与 7.122.0 及以前任何 gate 域坏形同一行):**起服**时运维行 `config_source_unreachable_using_env { source:"remote", err:<schema 诊断,带字段路径> }`,随后按 LKG → env 兜底(盘上有干净 LKG 就从它起服,运维行 `config_loaded_lkg`,`/v1/models` 与 `droppedEntries` 是 LKG 那一代;没有才走 env,此时 `droppedEntries:[]`);**运行中刷新**时运维行 `sema_registry_refresh_failed`,ETag 不前进、正在服务的那一代保持不变,下一拍重拉同一份候选。两行都不点名出错的域,`err` 里的字段路径是唯一线索。升级前把私钥移出配置串(引用 env 名或密封托管)并轮换那把密钥。
17
+ 4. **`SEMA_CONFIG_DIR` 不要设成工作目录本身或它的祖先**(引擎 7.46.0 起一直如此,7.120.0 节第 3 条的另一面;本版零代码改动,补记一条此前没写的事实):引擎内建写保护表里的 `.sema` 行不只守家目录下的 `~/.sema`,
18
+ 也被放在声明的配置根下 —— 根**之内任何路径**(不止配置文件)的写都要过 `write_protection` 一问。所以配置根是项目的祖先时(容器里 `SEMA_CONFIG_DIR=/workspace` 而项目在 `/workspace/proj`、直接设成 `HOME`、或设成项目目录本身),
19
+ 项目里的每一次写(`Write` / `Edit` / `NotebookEdit`)都出这一张卡:allow 规则清不掉它(请求 `settings.permissions.allow`、`APPROVAL_GRANT`、持久规则同),`acceptEdits` 不放,`dontAsk` 拒;无人值守腿(leader、无终端的 `run-local`)的作答集只有 `ordinary` 类、这一类不在其中 ⇒ 拒(leader 配了耐久审批的部署上改为停驻,等人经 `/decide` 答);
20
+ 只有 `bypassPermissions` 放行(托管部署上请求这一档另受 `TASK_WRITE_FACE_ESCALATION` 约束)。**起服不报、首条任务也不报**:本服务不拿配置根与工作目录比,引擎只验它是已存在的绝对目录 —— 第一次知道是第一次写。
21
+ **出路**:把配置根指到项目之外的专用目录(不设即缺省 `~/.sema`;`/workspace/.cfg` 一类不含项目的目录也行)。LKG(`config-lkg-*.json`)、skill 缓存(`skill-cache/`)、scheduler 共享店(`scheduled_tasks.json`)的缺省位置随配置根走,
22
+ 改根不会替你搬旧文件 —— 要保留 scheduler 共享店就手动把 `scheduled_tasks.json` 搬过去。确实要保留祖先形配置根的:`WRITE_PROTECTED_TABLE_REPLACE` 给一张不含 `.sema` 行的表可去掉这一问,代价是 `~/.sema` 下的这一问一并没了。
23
+ 5. **勘误(7.122.0 节第 7 条;已发节不改字节):`AUTONOMY=plan` 下「一次写在有人可答时出一张卡(人批了就写)」的射程是写面根围栏之内,不是任意路径**。批准只清模式门(plan 门那一问);
24
+ 引擎文件工具(`Write` / `Edit` / `NotebookEdit`)自己的根围栏不受批准影响,围栏 = 任务 `cwd` + 引擎宣告的可写目录(记忆目录、会话 scratchpad)+ 请求的 `additionalDirectories`。
25
+ 围栏外的路径,卡批了也不写:工具答错误,`tool_end.errorCode` = `path_not_in_root`,拒句 `… resolves outside the allowed root(s) (<根清单>); access denied.` 点名这些根。
26
+ 把 7.122.0 那句读成「批了就写,不论路径」的壳 —— 例如计划文件的路径在工作目录与上述目录之外的(默认配置根 `~/.sema` 之下就是一例)—— 要么让路径落进围栏(会话 scratchpad 或请求的 `additionalDirectories`),要么别指望那次写成功。
27
+ 写面声明为 open 的任务不受这道围栏约束(`bypassPermissions` 派生的开放写面;`GET /v1/capabilities` 的 `taskWriteFaceOpen` 披露本部署认不认)。本版零行为变化,这一条只改文字。
28
+ 6. **补记(7.122.0 节第 6 条):订阅流的排空终帧 `error{ errorCode:"draining" }` 是连接面的信号,不是 run 状态信号**(零行为变化,只补文字)。排空进入那一拍,服务端对**每一条**打开着的只读订阅流发这一帧
29
+ (`/v1/approvals/stream`、`/v1/fleet/stream`、`/v1/sessions/:id/events`、`/v1/runs/:id/events`、`/v1/tasks/:id/stream`、`/v1/runs/:id/subagents/:sub/stream`、`/v1/workflows/:id/stream`、`/v1/images/bakes/:id/events`),
30
+ 与流所观察的 run / 审批此刻在什么状态无关 —— 在跑的、停在一张审批卡上的、已终局的收到同一帧,帧里只有 `type` / `errorCode` / `message`,不带任何 run 或审批的状态。消费端:收到它 ⇒ 重连到替换实例;
31
+ **不要**据此判「run 已续跑 / 已动过 / 卡已决」,不要因此收掉在等的审批卡、也不要把重连前持住的停驻标当已决丢弃;按 `errorCode` 分支,别把 `error` 帧型整类当作 run 状态的信号。
32
+ 要知道 run 或审批此刻的状态,重连之后读 `GET /v1/runs/:id` / `GET /v1/approvals`。
33
+ 7. **会话规则写口对「不是一列非空字符串」的桶答 400**(`PUT /v1/sessions/:id/policy`、`POST /v1/sessions/:id/sync/import` 的 `policy` 面;引擎 7.48.0 的规则表判官):
34
+ 空串 / 纯空白项(`toolDeny: [""]`、`commandDeny: [" "]`、`toolAllow: ["Read", ""]`、`allowDirs: ["/work", "\t"]`)此前存得进(一条什么都不匹配的规则),现在
35
+ `400 request.field_invalid`,店里零字节;句子是引擎的,点名桶与值类型(`session rules <桶> must be an array of strings, got … — a rule list is written as a list of names (["rm"], not "rm")`)。
36
+ 修前就是 400 的裸字符串 / `null` / 混入非字符串三形码不变、句子换成同一句(按码匹配的不受影响)。**存量**:旧版写下的空串项行照常读出、照常执行;
37
+ 非 operator 想删掉空串 **deny** 项会被收紧门判成放宽(403 `loosen_forbidden`)—— 由运营方(operator)`PUT` 一份干净的规则体,或删会话。
38
+ 2c 推送遇到云端那一把键上是这种存量行 ⇒ 照常写:operator 推送照写(干净规则替换存量行,清它的另一条路);非 operator 推送若去掉了空串 **deny** 项 ⇒ 403 `loosen_forbidden`
39
+ (与 7.122.0 同答;装上引擎 7.48.0 而不改本服务时这里是 500)。推送体自己的记录里带空串项 ⇒ Phase A 400(先在源端清掉那一项再推)。
40
+ 8. **工具段为空的协议名(`mcp__<server>__` / `a2a__<peer>__`)由引擎判官判**(判词 `unmintable_segment`,与对端段铸不出的名字同一判):
41
+ 会话规则两写口的拒码 **`400 request.field_invalid` → `400 rules.legacy_tool_name`**(体带 `field` / `name` / `verdict: "unmintable_segment"` / `guidance`);
42
+ 请求 `settings.permissions.allow` 与技能 `allowedTools` 里它**由拒(422 / 400)变收**,引擎在开跑时读成 `tool_name` 并通告(`permission.allowed_tool_not_granted`);
43
+ 技能 `disallowedTools` 里它仍 400 `request.field_invalid`(句子换成引擎条目读法的那一句);运维 `APPROVAL_*` 四表、中心下发名表、请求 deny / ask 桶照拒,指引换成引擎的句子。
44
+ **存量**:7.115.x 及以前写进会话规则的这种名字,本版起每一跑在准备阶段拒(`config.legacy_tool_name`,一个 turn 不跑;同步提交腿 200 + failed 体)——
45
+ 7.122.0 及以前那条规则什么都不守、run 照跑。属主自己删得掉(删一条非现行 deny 不算放宽)。**出路**:写 `mcp__<server>`(整台服务器)或 `mcp__<server>__<tool>`(一只工具)。
46
+ 9. **`/` 开头的读拒行根锚定**(引擎 7.48.0):只守那一个绝对位置,不再按尾段在任意深度匹配;路径经符号链接的目录时,引擎在每次准备时把这一行沿链放置到字面头真正通向的地方
47
+ (链接目标同样被拒)。`CONFIG_LKG_PATH` 派生的读拒行收成一条 = 覆写的绝对路径(此前两条:写下的路径 + 本服务解出的落点);用户仓里同尾的路径读得到、Grep 也找得到;
48
+ `permission.read_deny_unapplied` 的 `detail.rows` 里部署 LKG 路径由两行变一行。运维 `READ_DENY_PATTERNS` 里 `/` 开头的行同此 —— **要任意深度就去掉前导 `/`**。
49
+ **回滚到 7.122.0 及以前**:本版(引擎 7.48.0)写出的检查点带 `/` 开头的读拒行时,7.47.x 引擎的工作者恢复它即拒(`checkpoint.invalid_outcome`,原因 `read_face_damaged`),
50
+ 检查点停泊不动 —— 回滚前先在本版上续跑完、或确认没有这类停泊任务(部署设了 `CONFIG_LKG_PATH`、或运维读拒行以 `/` 开头,就有这类行)。
51
+ 10. **shell 读法:收窄五条、放宽四条**(引擎 7.48.0 披露为可否决;本服务零改码,三种放行形 —— 无 allow 的 `default`、请求 `settings.permissions.allow: ["Bash"]`、
52
+ `bypassPermissions` —— 原样过境)。**多问**:`P=rm; $P -rf <根>` 这类经变量拼出的危险删除在 allow 与 bypass 下也是一张 `dangerous_delete` 卡;
53
+ **流喂 / 进程替换的 shell 一律 `dangerous_delete`,无害文本也是**(`ls | xargs -I{} sh -c 'echo {}'`、`bash <(printf 'echo hi')`,三种放行形各一张卡)。
54
+ 无人值守腿(leader 各条腿、无终端的 `run-local`)的作答集只有 `ordinary` ⇒ 这些卡没人答 ⇒ 拒(leader 配了耐久审批则停驻等 `/decide`)—— 跑在这些腿上的脚本里用
55
+ `xargs … sh -c` / `bash <(…)` 驱动 shell 循环的,先改成不经流喂 shell 的写法。**少问**(只在请求 allow `Bash` 与 `bypassPermissions` 下;`default` 照问):
56
+ 未加引号的多词值(`X='a b'; cat $X`)、名字取多个值后经 `su -c "$X"`、嵌套循环、带通配符的未引号值不再各出一张卡。
57
+
58
+ 11. **`GET /v1/capabilities` 的 `permissionModeAuto.reason` 闭集加一词 `operator_floor`**(additive;改正 7.122.0 节第 7 条「能力面答 `reason:"mode_not_auto"`」那半句,已发节不改字节)。
59
+ `AUTONOMY=plan` 的部署上,意图(查询串 `?permissionMode=`,缺席按 auto)被入口地板改成了别的词 ⇒ `reason:"operator_floor"`(此前 `mode_not_auto`):意图缺席、`auto`,以及任何比 `plan` 松的词
60
+ (`default` / `acceptEdits` / `bypassPermissions` / `dontAsk`)都是这一词;意图本身是 `plan` ⇒ 仍是 `mode_not_auto`。没有地板的部署读数不变。`armed` 不变。
61
+ 消费端:按 `reason` 指路的壳(`sema doctor permissions` 一类)给 `operator_floor` 一条「运维的 `AUTONOMY=plan` 让每个任务从 plan 起步,改请求的词到不了 auto;找运维换档」的出路;
62
+ 对 `reason` 写穷尽 switch 的类型面加这一臂,未知词照旧按「未武装」兜底。
63
+
64
+ ## 7.122.0 —— 配置型 URL 拒句零回显(两种模型路由形改拒启)· 排空口的期限有上界(`FLEET_LEASE_OUTAGE_MAX_MS`)· 服务租约能力位撤掉再宣告不重置失联钟 · 改路停驻的审批在停驻当拍收口(迟到作答的 202 写真)· 排空不被只读订阅流挂住 · plan 模式一只判官(运维 `AUTONOMY=plan` = 提交入口的模式词地板)
65
+
66
+ **升级前必读**:按拒句文字匹配配置拒启的脚本改按拒启码匹配(第 1 条);模型路由三键里没有 `ftp://` 一类非 http(s) 根(被消费的根修前已拒启,不被消费的那一根升级后也拒启)、没有以空 `#` 结尾的根(第 2 条,升级后拒启);
67
+ 调 `POST /v1/admin/drain` 的编排脚本 / 执行器发的 `deadlineAt` 不超过「此刻 + `FLEET_LEASE_OUTAGE_MAX_MS`」(缺省 24 h)(第 3 条);
68
+ 设了 `AUTONOMY=plan` 的部署核一遍它要的是「plan 模式」还是「整条 run 只读」,升级窗内有停驻的 run 时先读第 7 条;开了 leader 端点的这类部署上 `POST /v1/leader` 答 `422 request.autonomy_lane_refused`(第 7 条);
69
+ 请求 `settings.permissions.deny` / `.ask` 里没有 `Monitor(<命令>)`(改写成 `Bash(<命令>)`;停驻中带这一形的任务取消后按改正的规则重提)(第 8 条);
70
+ `Bash(...)` 命令规则现在也判 `Monitor`,allow 也清它的普通问 —— 核一遍自己的 allow 规则(第 9 条);
71
+ 跑在无人值守腿(leader、无终端的 `run-local`)上的脚本里没有带命令的 `sudo -s|-i`(升级后那是一张没人答的 `probe` 卡 ⇒ 拒,第 10 条)。
72
+
73
+ 1. **配置型 URL 的拒句只点名规则,不再回显值里解析出的片段**:中心基址三席、`FLEET_ADVERTISE_ADDRESS`、请求期中心基址检查、中心凭证续签的 issuer、
74
+ 模型路由三键(`MODEL_GATEWAY_BASEURL` / `MODEL_GATEWAY_FALLBACK_URLS` 逐项 / `ANTHROPIC_BASE_URL`)、`MEMORY_EMBEDDER_ENDPOINT`、`A2A_SERVE_URL` 同一只判官、同一种拒句:
75
+ `<键> <规则词>; got "<值的样子>"` —— 规则词不含 scheme,合格的 http(s) 形只展示到 origin(其后一律 `/…`),其余整只 `«redacted:url»`。
76
+ 旧拒句文字(`scheme must be http or https (got "…")`、`must be http(s) (got`、`is not a parseable absolute URL`、`must be a parseable http(s) URL`、
77
+ `must not carry credentials in the URL`,以及 userinfo / fragment 规则词的旧括号句 `(credentials never ride in a base URL: …)` / `(request paths are appended to this base)`)不再出现;
78
+ 带码的四席拒启码不变(`config.fleet_advertise_address_invalid` / `config.model_route_url_invalid` / `config.gateway_url_userinfo` / `config.model_route_url_fragment`)—— 按码匹配
79
+ (一处角形换了码:模型路由根是层级式非 http(s) **又**带 userinfo 或 fragment 时,见第 2 条);
80
+ 中心基址三席、`MEMORY_EMBEDDER_ENDPOINT`、`A2A_SERVE_URL` 的拒启句修前修后都不带机读码 —— 这五席按规则词前缀匹配
81
+ (`is not a parseable http(s) URL` / `must not carry userinfo` / `must not carry a query` / `must not carry a fragment`),不匹配括号内的解释句。拒句里不再有路径:要认是哪一条 URL,看自己的配置。
82
+ 2. **模型路由三键的两种形**:层级式非 http(s)(`ftp://…` 一类)⇒ 声明期拒启、`config.model_route_url_invalid`(被消费的根修前已由引擎的路由构造器拒启 —— 引擎的句、无本仓码、整串回显;本部署不消费的那一根修前照常起服,升级后同样拒启);
83
+ 这一形**同时**带 userinfo 或 fragment(`ftp://u:p@gw/v1`、`ftp://gw/v1#x`)⇒ 同样先判形、答 `config.model_route_url_invalid` —— 修前分别答 `config.gateway_url_userinfo` /
84
+ `config.model_route_url_fragment`,按这两个码认这类根的告警改认 `config.model_route_url_invalid`;
85
+ 根以一个空的 `#` 结尾(`https://gw/v1#`)⇒ `config.model_route_url_fragment`(修前放行)—— 删掉那个 `#`。query 照收。
86
+ 3. **排空口 `deadlineAt` 离引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS` ⇒ `400 request.field_invalid`**,什么都不变(没进排空、在飞不动);拒句点名 `deadlineAt` 与上界的值。
87
+ 修前任何期限都收(`此刻 + 365 天` ⇒ 200,实例不就绪直到在飞归零或第二发停止信号)。上界与服务租约的失联容忍是同一席(一台实例活着而不服务的最长时长);
88
+ 非 fleet 成员同样生效(没设就是缺省 24 h)。要发更远的期限 ⇒ 调大这台部署的 `FLEET_LEASE_OUTAGE_MAX_MS`(它同时放宽租约失联容忍)。期限按引擎的挂钟量:
89
+ 调用方与引擎时钟有偏差时,不要贴着上界发。已过的期限照旧收(进入即到期)。只在这一发会进入排空时判:已在排空(或硬停已在进行)时的重发(含换了期限的)照回 `already_draining`。
90
+ 4. **服务租约能力位撤掉再宣告,不再重置失联钟**(只影响 fleet 成员 ∧ 中心宣告过 `servingLease` 的部署;零动作):钟只由授予与本进程第一次见到能力位写。
91
+ 中心降级到不宣告能力位超过 `FLEET_LEASE_OUTAGE_MAX_MS` 再升回来时,一直没被确认的成员在位再出现后即拒新工作(`503 state.lease_expired`),中心授予即恢复 ——
92
+ 修前位再出现那一拍重新起钟。
93
+ 5. **改路停驻的审批在停驻当拍收口;迟到作答的 `202` 写真**(零动作;按 202 处置的壳核一遍):排空进入或卡窗到期把活卡改路耐久停驻之后,审批行在
94
+ 引擎把停车行写进店的那一拍就收口(修前要等对账周期扫,缺省 60 s 一拍;local 车道实测排空窗内 / 窗到期后整窗收不了)。于是对旧 approvalId 的
95
+ `POST /v1/tool-approvals/:id/respond` 修后通常直接 `200 accepted:"parked_redeemed"`。仍可能出现的 `202 reason:"parking_in_progress"`(停驻与写店之间那一段)
96
+ **不是结算 —— 作答没有生效**:体多两键 `message`(`parking in progress — not applied; retry after retryAfterMs or answer through decidePath`)与
97
+ `decidePath`(耐久回决口 `/v1/approvals/<sessionId>/decide` 的路径串)。壳按 `retryAfterMs` 重试一次,或改走 `decidePath`;把 202 当已结算的壳改掉这一处。
98
+ `decidePath` 只在停车行所在会话读得准时在场:委派子代腿与无会话的临时腿上它缺席,`message` 改为 `… or answer through the parent session's durable decide`。
99
+ 6. **排空不再被只读订阅流挂住**(零动作;读 `/health.inflight` 的监控核一遍):`/health.inflight`(排空等的同一只读数)只数在飞的工作 —— run 腿、计费提交、
100
+ HITL 应答与普通请求;只读订阅流(`/v1/approvals/stream`、`/v1/fleet/stream`、`/v1/sessions/:id/events`、`/v1/runs/:id/events`、`/v1/tasks/:id/stream`、
101
+ `/v1/runs/:id/subagents/:sub/stream`、`/v1/workflows/:id/stream`、`/v1/images/bakes/:id/events`)不再计入,读数变小。修前壳附着(常开审批订阅流)时 SIGTERM
102
+ 排空跑满整窗、再等强断才退出;修后订阅流在排空进入那一拍收到一帧 `event: error` + `{type:"error", errorCode:"draining", message}`(与流帽那一帧同一套重连协议)
103
+ 后被服务端关闭,排空只等真在飞的工作。消费端按「服务端收流 ⇒ 重连」处置(到替换实例)。排空期间新开的订阅照常服务,硬停起点同样收这一帧。
104
+ SIGHUP 空闲窗照旧把只挂着订阅流的壳当「有人在用」。
105
+ 7. **plan 模式一只判官**:用户选的 plan(顶层 `permissionMode:"plan"` / `settings.permissions.defaultMode:"plan"`)、运维 `AUTONOMY=plan`、模型自己调 `EnterPlanMode`
106
+ 都只落引擎的 `permissionMode:"plan"`。工具表与 `default` 逐名相同(委派工具 `Agent`、`Write` / `Edit` 都在),写与非只读调用在调用时由引擎的 plan 门问(有人可答)/
107
+ 拒(无人可答);委派子代以 plan 为天花板继承同一道门。带 `agents` 的 plan 任务不再以 `config.agents.no_delegation_tool` 整轮失败。
108
+ · **依赖「plan 下工具表里没有 `Agent` / `Write` / `Edit`」的消费方**:改按调用结果判(plan 门的问 / 拒),不按工具表判。
109
+ · **run 的模式词只有一个源 —— 存储的提交体顶层 `permissionMode`**:写者是每一个出生点(新的持久化写)与 `plan_review` 写回,治理层不再按段重折模式词。
110
+ · **`AUTONOMY=plan` = 提交入口的模式词地板**:不再是只读手。每个新提交的任务以 `plan` 起步(请求不带模式词 ⇒ `plan`;带任何词 ⇒ 钳成 `plan`),
111
+ 一次写在有人可答时出一张卡(人批了就写);批准(或 `edit`)一份计划之后,整条 run 按批准时选的档(`permissionModeAfter`,缺席 = `default`)跑完,
112
+ 此后每一次挂起→续跑都不回 plan;`permissionModeAfter` 对这类任务照收。热改 `AUTONOMY` 只影响之后提交的任务,在飞的 run 保持它的入口词。
113
+ 地板盖在每一个 spec 出生点:run-local 盖在它自建的 spec 上,leader run 盖在它出生时的入口体上。**跑 leader 的这类部署:`POST /v1/leader` 答
114
+ `422 request.autonomy_lane_refused`**,一条 run 都不建(上一版:受理,worker 首跑不经治理、写照落,只有 `/decide` 续跑按段折只读手)——
115
+ plan 靠人审通道(`plan_review`)出口,leader 的 worker 会话没有那条通道。出路:要 leader 照跑 ⇒ `AUTONOMY=read-only`(续跑只读手)或 `ask`;
116
+ 要计划审阅 ⇒ 改走 `POST /v1/runs`(计划经 `POST /v1/assistant/tasks/:id/plan_review` 批准)。能力位 `leader` 在这类部署上答 `false`(值变键不变),提交方按位预判;按码分支。
117
+ `ROUTER_ENABLED` 上非隔离车道按可写任务升级到监督姿态。
118
+ 请求 `dontAsk` 的无人调用方在这类部署上同样被钳成 `plan`(引擎的模式秩里 `dontAsk` 高于 `plan`):写与交计划撞 plan 门的卡(无人可答 ⇒ 拒;配耐久审批 ⇒ 停驻等人),
119
+ 批准计划时 `permissionModeAfter` 只有 `default` / `acceptEdits` 两档、回不到 `dontAsk`(上一版:只读手 + `dontAsk`,问一律由模式拒、不停驻)。
120
+ 能力面:`GET /v1/capabilities` 的 `permissionModeAuto.armed` 在这类部署上答 `false`(`reason:"mode_not_auto"`,在这里提交 auto 会被钳成 plan),`taskWriteFaceOpen` 照旧 `false`,
121
+ `leader` 由 `true` 变 `false`(车道挂着但不受理;既有 leader run 的 `GET` 照常)—— 三位读同一只地板钳。
122
+ 托管部署(`REQUIRE_PRINCIPAL=true`,未放开 `TASK_WRITE_FACE_ESCALATION`)上 `AUTONOMY=plan` × `bypassPermissions` 的请求由 `422` 变为受理并在 plan 里起步。
123
+ 要「整条 run 一个字节都不写」⇒ 改设 `AUTONOMY=read-only`。
124
+ · **升级窗内停驻着的 run × `AUTONOMY=plan`**:上一版写下的续跑上下文体里是请求原词,运维的 plan 那时由服务在每一次续跑重新施加(只读手)。
125
+ 本版不按原词重放它们:续跑(`/decide`、`plan_review`、`resume`、`wake`、终态 steer)⇒ `409 checkpoint.unsupported_version`,行字节不动、卡仍待决
126
+ (审批卡停驻的行在 `wake` / `resume` 上先被既有的人审门拒 —— `409 wake.gate_pending` / `gate_not_resumable` —— 本码在 `/decide` 与 `plan_review` 上读到),
127
+ SLA 到期拒扫跳过这类行。出路:升级前把它们决掉;或取消后作为新任务重提;或升级窗内把部署设成 `AUTONOMY=read-only`(按段的只读围栏,
128
+ 与上一版的结局相同,不触发这条拒),它们收尾之后再改回 `plan`。没设 `AUTONOMY=plan` 的部署不受影响;体词已是 `plan` 的 `plan_review` 审阅行照常批准,批准后按批准的档续跑。
129
+ 随包的排空检查(`scripts/drain-check-local.mjs` 与两份 `drain-check.*.sql`)本版**不数**这一类行(判据下一版补);升级前自查:`GET /v1/approvals` 列出的待决行,
130
+ 逐条看它所在任务提交时的模式词(顶层 `permissionMode` / `settings.permissions.defaultMode`;无词也算)—— 不是 `plan` 的那些,升级后在 `AUTONOMY=plan` 部署上续跑会被拒。
131
+ · **`AUTONOMY=read-only`**:仍是只读手 + 写面 `roots`,委派工具不再被剥 —— 子代同样只读(引擎把只读手夹进子代),带 `agents` 的任务照常起跑。
132
+ · **`plan_review` 的 `edit`**:按人改过的计划开干,与 `approve` 同:续跑解到 `permissionModeAfter`(缺席 = `default`),`edit` 现在也收这一位(修前 `400`)——
133
+ 修前 `edit` 之后仍是只读手;要「改了计划但继续只读研究」请用 `reject` + `reason`。
134
+ · **`plan_review` 的两句 `400 request.field_conflict` 改了文字**(码不变,按码匹配):错门句 `permissionModeAfter is only valid with a decision that carries the plan out ('approve' or 'edit'; got 'reject')`;
135
+ 存储模式词不是 plan 的任务送它 ⇒ `permissionModeAfter does not apply to this task: it was not started in plan mode (its stored permission mode is not "plan"), so approving the plan returns it to the mode it was in before planning`。
136
+ 8. **`Monitor(<命令>)` 不是规则拼法:请求 `settings.permissions.deny` / `.ask` 里写它 ⇒ 提交 `422`**(引擎 7.47.0 的规则编译器拒它)。修前 `Monitor(touch:*)` /
137
+ `Monitor(git push:*)` / `Monitor(timeout:5)` / `Monitor(command:touch*)` 照收、编译成一条什么都匹配不上的规则(以为拦住了 `Monitor` 的命令,其实没有);
138
+ `Monitor(touch k1)` 一类已是 422,句子换了。现在句子点名条目、引擎的码(`invalid.command_rule` / `invalid.primary_field`)与引擎的句(命令拼法给出该写的
139
+ `Bash(<命令>)`)。**出路**:改写成 `Bash(<命令>)` —— 它同时判 `Bash` 与 `Monitor`(第 9 条);`Monitor(description:…)` 与裸 `Monitor` 照收。
140
+ 停驻中的任务、请求体带这一形的:升级后续跑重解析同一份请求体,答 `409 resume_blocked_by_policy`(`blockedBy: "request.unprocessable"`)。出路 = 取消它、按改正的
141
+ `Bash(<命令>)` 重提。在 7.121.0 上把它决完也能走,但那一形在 7.121.0 上什么都不匹配 —— 决完 = 这条任务在没有那条 deny / ask 的情况下跑完
142
+ (`bypassPermissions` 下被它点名的命令照跑、不问),只在接受这一点时才选。
143
+ 同一只「编不成规则」的指引(请求 deny / ask 的 422 句、`APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 的拒启句、中心下发 `governance.approvalRequire`
144
+ 的 warn detail)不再缀「Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use mcp__<server>__* …」那一句,
145
+ 只带引擎的码与句 —— 按那句匹配的脚本改按码匹配。其余受理口不变:请求 allow 桶照收(引擎开跑时通告读成空)、`APPROVAL_GRANT` 拒启、
146
+ `POST /v1/rules` 400 `rule_rejected`、CC 导入不成候选。
147
+ 9. **`Bash(...)` 命令规则判每一只跑 shell 命令的工具,`Monitor` 在内**(引擎行为,零动作;核一遍自己的规则):deny / ask —— `deny Bash(touch:*)` 下 `Monitor`
148
+ 跑 `touch k1` 在每一种模式都被拒(修前 `default` 一张卡、`bypassPermissions` 下照跑),`ask Bash(git push:*)` 下是一张 `bypassPermissions` 不放的卡;
149
+ **allow(放宽)** —— 一条 `Bash(...)` allow 现在也清 `Monitor` 的普通问,`dontAsk` 下也是(`Bash(tail:*)` 允许 `Monitor` 跑 `tail -f app.log` 不再出卡)。
150
+ 来源不分:请求的三只桶、`APPROVAL_GRANT`、持久规则行(卡上「总是允许」、`POST /v1/rules`、两条导入车道)、技能授予。裸 `Bash` 规则仍只管名叫 `Bash`
151
+ 的工具 —— 不要 shell 就 `Bash` 与 `Monitor` 两个名字都拒。
152
+ 子代 `agents[].denyTools` / `allowTools` 是**工具名表**,不是规则文法(引擎的子代定义:`Tool(content)` 把整只 `Tool` 对该子代藏起):同一串 `Bash(touch:*)` 写在请求 deny / ask / allow 里按命令判 `Bash` 与 `Monitor`,写在子代 `denyTools` 里是对该子代藏起整只 `Bash`、够不到 `Monitor`(`Monitor` 等 hands 之外的引擎工具不受这两张表管;要按命令拦而保留工具,写调用方权限策略的 deny 规则)。
153
+ 10. **经 `sudo -s|-i` / `su` / `runuser` 的删除与读(引擎行为;无人值守腿上的脚本要核一遍)**:`sudo -s rm -rf '$HOME'`、`su root -c 'rm -rf ~'`、`runuser -u root -- rm -rf ~` 一类删受保护位置的命令,
154
+ 在 `bypassPermissions` 与 `Bash` allow 下也停在一张要人的 `dangerous_delete` 卡上(修前:`'$HOME'` 形是不点名家目录的 `unverifiable_delete`,`su` / `runuser` 形
155
+ allow 下一张 `probe` 卡、`bypassPermissions` 零卡照跑);每个带命令的 `sudo -s|-i` 都按 `bash -c` 读 —— 无害的 `sudo -s ls` 在 `default` 与 `Bash` allow 下
156
+ 多一张卡(对照 `sudo ls` 照放)。**无人值守腿上这张卡没人答 ⇒ 拒**:leader 的各条腿与无终端的 `run-local` 按类作答的答集只有 `ordinary`(`probe` 不在其中),
157
+ 所以 `Bash` allow 下的 `sudo -s ls` 被拒(修前这张卡不存在,放行);leader 配了耐久审批的部署上改为停驻、等人经 `/decide` 答;`run-local` 有终端时问人。
158
+ 要免问地以他人身份跑命令:字面写出目标。括号类里带引号 / 反斜杠的字符(`["a"]*`)现在按「任一字符」判,读拒与删除守卫会多拦几个 bash 本不碰的名字。
159
+ `tool_end.gate.disposition.released.reason` 多一个词 `write_protection_unresolved`(线名为 `Bash` / `Monitor`、声明写路径目标的调用方工具在 `bypassPermissions`
160
+ 下的释放;引擎自带的两只工具铸不出它)—— 按词分派的消费方认这个词。
161
+
10
162
  ## 7.121.0 —— 编排器片 2(引擎侧):`/readyz` 唯一探针 · 排空口契约体 · 服务租约客户端 · fleet 成员必须能被围栏
11
163
 
12
164
  **升级前核查(一句话)**:探针改指 `/readyz`(第 1 条);调 `POST /v1/admin/drain {reason}` 的改发契约体,多租引擎的排空请求带它的 `scope`(第 2 条);读 `/health.drainReason` / `modelPlaneDeferred` 的删读点(第 2 / 5 条,两键都已删);
@@ -1344,6 +1496,29 @@ wire 面(同步提交撞 park 的 200 体、`GET /v1/runs/:id` 行形、同一
1344
1496
  另外六名新出现在 `exact-name` 桶。
1345
1497
  - 零 wire 键、零 DDL。
1346
1498
 
1499
+ ## 7.103.1 —— 补记:ssh / k8s / adb 三条远端车道的工作目录进不去时,命令不再在远端起始目录里照跑(改为不执行 + 一句诊断)
1500
+
1501
+ > 这一变化随 7.103.1 发出,当时本文件没有写。7.103.1 的另一块(git 配置组的判定改归引擎)见 7.103.0 的 core 7.33.1 提货一节里标 `⤷ 7.103.1` 的条目。
1502
+
1503
+ 1. **远端车道(`REMOTE_EXEC=ssh` / `k8s` / `adb`)的命令前面拼着「先进工作目录」;进不去时,命令现在一段也不跑**。
1504
+ · **修前(≤7.103.0)**:交给远端 shell 的是 `cd '<目录>' && export K='v'; <命令>` —— `&&` 只绑住第一只 `export`,目录不存在 / 进不去 / 不是目录时,`;` 之后的命令**照样在远端 shell 的起始目录里跑**
1505
+ (ssh 为目标账户的登录目录),只少了第一只 `export`;引擎的 `Bash` 工具带 `env`(`PWD`)调用,所以模型命令走的就是这一形。没有 `env` 的调用方形是 `cd … && <命令>`,只短路命令的第一段:`a; b`、`a || b`、多行命令的后半同样照跑。
1506
+ 结果是命令在**错的目录**里「成功」了(退出码是命令自己的,通常 0),没有任何一行告诉你工作目录没进去。
1507
+ · **现在(7.103.1 起)**:`cd '<目录>' || { echo '<守卫行>' >&2; exit 125; }; …` —— 进不去 ⇒ 命令**不执行**。缓冲执行面(模型的 `Bash`、带工作目录的 git 探针等)把它报成执行层的 `spawn_error`(与 host 车道的 spawn 失败同码同句族),
1508
+ 句子 `<ssh|k8s|adb> spawn failed: sema: working directory unavailable: <目录> — the command did not run (<远端 cd 自己的报错>)`。引擎的 `Bash` 工具据此判目录是不是真没了:
1509
+ 模型自己 `cd` 进去又删掉的目录 ⇒ 工作目录复位到任务根,答「… The working directory has been reset to <根>; re-run your command (cd first if you need a different directory).」,模型重发即可;
1510
+ 任务根本身没了 ⇒ 答「… no longer exist … this task cannot recover its working tree」,无从复位。目录在但**进不去**(权限)时引擎不判死目录、不复位,模型只看到 `command did not run (spawn_error)` 那一句,
1511
+ 本轮后续前台 `Bash` 都是它 —— 修目录权限(与 host 车道同形)。
1512
+ · **流式执行与后台任务不走 `spawn_error`**:cd 失败时交出退出码 125 + stderr 一行 `sema: working directory unavailable [<nonce>]: <目录> (cd failed; command not run)`(命令自己退 125 的不会被误认:缓冲面在 cd 成功之后、命令之前会往 stderr 写一行
1513
+ `sema: command started [<同一 nonce>]`,服务在交出之前剥掉它,没见到这一行才算「命令没跑」;文件面探针只用绝对路径、不进工作目录,工作目录进不去不影响它们答真话)。
1514
+ · **谁会看到**:远端工作目录被删 / 挂载没起 / 权限不对的部署 —— 以前这些命令在别的目录里「跑成功」,现在是显式失败。按退出码判成败、或把命令输出当数据的脚本要知道这一点。
1515
+ · **部署前提**:命令串用了 `{ …; }` 组命令,**远端 shell 须兼容 POSIX sh**。ssh 车道走目标账户的**登录 shell**(`-c`):登录 shell 是 csh / tcsh(fish 看版本)的账户整条语法错、命令一条也不跑
1516
+ (修前无 `env` 的 `cd 'X' && cmd` 在 csh 里能跑)—— 给 agent 用的账户把登录 shell 设成 `/bin/sh` / bash / zsh / dash。k8s 走容器里的 `sh -c`,adb 走设备的 `sh`。
1517
+ · **出路**:在远端建好并能进工作目录 —— `K8S_MOUNT_PATH`(缺省 `/workspace`)在容器里存在;`ADB_MOUNT_PATH`(缺省 `/data/local/tmp`)在设备上存在且可进;`SSH_MOUNT_PATH` 在目标机上存在且可进
1518
+ (不设 ⇒ 调用方没给工作目录的那几次不进目录、没有守卫)。
1519
+ · **不在本条范围**:`local-docker` 与 `e2b` 两条车道不经这一组装,它们的死工作目录报法本节未核。
1520
+ · 零 wire 键、零 DDL、零新旋钮。
1521
+
1347
1522
  ## 7.103.0(车OY 部分)—— server 进程出网认 `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`(S-776)
1348
1523
 
1349
1524
  - **出网跟着代理变量走**:server 进程(HTTP 服务与 `run-local`)的出网 —— 引擎 → 网关、中心拉取 / 公告 / 凭证续签、WebSearch、对象存储、OTLP … ——
package/README.md CHANGED
@@ -131,6 +131,11 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
131
131
  Build-time tools (e.g. a catalog editor) read two allowlists from the package root instead of hand-copying them: `RETIRED_ENVS` (the retired-env
132
132
  tombstones) and, since 7.110.0, `CATALOG_ENTRY_HEADER_LANES` / `CATALOG_ENTRY_HEADER_NAMES` (+ type `CatalogEntryHeaderName`) — the closed allowlist of
133
133
  model-catalog entry `headers` and the API line that reads each name. Both are frozen, the very objects the server judges with.
134
+ A shell or package that must know **before the engine starts** whether the model route would stop it imports `judgeModelRouteGap(env)`
135
+ (pure; takes an env snapshot) from the package root: `{ gap: "endpoint_missing" | "lane_credential_missing" | "none" }`, plus `code` and the
136
+ refusal sentence `reason` when there is a gap. It is the very function `loadConfig()` judges the model route with — not a copy of it —
137
+ so its reading and the start-up refusal are the same by construction. `none` says the route gap is absent, not that the engine will start
138
+ (other invariants refuse with their own codes).
134
139
  - **Bundled binaries**: the package ships two `bin` entries — `run-local` (single-machine local
135
140
  runner) and `sema-up` (deployment bootstrap script).
136
141
  - `run-local` boots the same engine in-process, runs one task from the CLI objective, prints the
@@ -227,7 +232,7 @@ and the tool-level error forms) is documented in `USAGE.md` §9.5.
227
232
  ## Security & privacy note
228
233
 
229
234
  > 🔴 **Trace redaction ≠ egress control** (anchor: 脱敏 ≠ 出站控制). Trace redaction (the `«redacted»` markers you see in the
230
- > ledger / SSE / turns / approval previews) only rewrites the **persistence and display** surfaces —
235
+ > ledger / SSE / turns / approval previews) only rewrites the **read / display faces** (ledger, SSE, turns, approval previews, `GET /v1/sessions/:id`); it does **not** touch the engine's session archive (the audit source of truth — stored as written, redacted only on the HTTP read face, and replayed to the model route as written on every later turn) nor the session-sync faces `/v1/sessions/:id/sync/*` (the durable log goes in and out verbatim) — those two are **original text** —
231
236
  > **the request body carrying a tool result to the model's actual route is the original, unredacted
232
237
  > text.** That route is not just `MODEL_GATEWAY_BASEURL`: it can also be the fallback gateway list
233
238
  > (`MODEL_GATEWAY_FALLBACK_URLS`), an independent Anthropic route for `provider:"anthropic"` models
package/README.zh-CN.md CHANGED
@@ -114,6 +114,9 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
114
114
  `assertExecutionLaneInvariants(config)` 是同一份判据,不做 `config.d/remote-exec.json` 覆写。
115
115
  构建期工具(如目录编辑页)从包根取两张表、不手抄:`RETIRED_ENVS`(退役 env 墓碑)与 7.110.0 起的 `CATALOG_ENTRY_HEADER_LANES` /
116
116
  `CATALOG_ENTRY_HEADER_NAMES`(+ 型 `CatalogEntryHeaderName`)—— 模型目录条目 `headers` 的闭集白名单及读每个名字的线路。两者都冻结,就是服务端自己判据用的那一只。
117
+ 壳 / 包要在**引擎起来之前**知道模型路由会不会让它起不来,从包根调 `judgeModelRouteGap(env)`(纯函数,吃一份 env 快照):
118
+ `{ gap: "endpoint_missing" | "lane_credential_missing" | "none" }`,有缺口时另带 `code` 与拒启句 `reason`。它就是 `loadConfig()` 判模型路由用的那一只函数
119
+ (不是它的副本),所以读数与起服拒启同源。`none` 只说路由缺口不在,不担保起得来(别的不变量各自带码拒启)。
117
120
  - **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
118
121
  `sema-up`(部署引导脚本)。
119
122
  - `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
@@ -193,7 +196,9 @@ Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`
193
196
  ## 安全与隐私提示
194
197
 
195
198
  > 🔴 **trace 脱敏 ≠ 出站控制。** trace 脱敏(你在账本 / SSE / turns / 审批预览里看到的 `«redacted»`
196
- > 标记)只作用于**持久化与展示面**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
199
+ > 标记)只作用于**读面 / 展示面**(账本 / SSE / turns / 审批预览 / `GET /v1/sessions/:id`);**不碰**引擎会话存档
200
+ > (审计真源:store 里怎么写就怎么回、HTTP 读面才脱敏,之后每一次续轮按原文回送模型路由)与 session-sync 七口
201
+ > `/v1/sessions/:id/sync/*`(逐字 durable log 出入)——这两面是**原形**——**发往「该 run 实际选中的模型路由」的请求体是工具结果原文**。
197
202
  > 这条路由不止 `MODEL_GATEWAY_BASEURL` 一根:还可能是备用网关列表(`MODEL_GATEWAY_FALLBACK_URLS`)、
198
203
  > `provider:"anthropic"` 模型的独立直连路由(缺省根 `https://api.anthropic.com`,除非显式配
199
204
  > `ANTHROPIC_BASEURL`)、或配置中心可对单个目录模型下发的 per-model `baseUrl` 覆盖(见
package/USAGE.md CHANGED
@@ -369,7 +369,7 @@ OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer xxx # 逗号分隔的 k=v
369
369
  MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
370
370
  ```
371
371
  - 调用方可在 `objective` 里 `@<模型名>` 选模型,**只认已配置的名单**(`MODEL_ID` / `MODEL_CHEAP_ID`,或目录条目)——注入不了 `baseUrl`/`apiKey`;无 `@` → 默认模型;`@` 点中一条已发布但本部署不可用的目录条目 ⇒ 400 `request.model_unavailable`(S-704)。
372
- - **`GET /v1/models`**(带 Bearer)→ `{models:[{name,id,provider,reasoning,vision}],default}`,只含名字/能力、**不含密钥** —— 消费方(如 OA)拿它做 `@` 自动补全下拉的数据源。
372
+ - **`GET /v1/models`**(带 Bearer)→ `{models:[{name,id,provider,reasoning,vision,…}],default,defaultModel,droppedEntries}`,只含名字/能力、**不含密钥** —— 消费方(如 OA)拿它做 `@` 自动补全下拉的数据源;`droppedEntries`(7.123.0 起恒在场,零条 = `[]`)是本代目录里被读腿逐条丢掉的条目(下标 + 闭集原因词 + 可披露身份),契约 §A.1d。
373
373
  - 不设 `MODEL_CHEAP_ID` → 所有角色 = 主模型(行为不变)。
374
374
  - `MODEL_CHEAP_ID` 写成与 `MODEL_ID` **同一个 id**(7.104.0 起)→ 那就是主模型本身:不铸 cheap 槽,所有角色 = 主模型,起服一行
375
375
  `config_env_entry_cheap_same_as_main`(同时设的其余 `MODEL_CHEAP_*` 键一并不生效)。要给同一只上游 id 第二种形(另一个价 / 思考 /
@@ -488,6 +488,13 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
488
488
  **7.106.0+**(S-810):候选**部分应用**(某组被整组拒、其余组已提交)的拍也照同一律算这一信号 —— 修前那一拍不签;被拒的是审批组 / 模型面组时
489
489
  (新进程 boot 重判可能拒启)信号扣下、只出 warn `restart_signal_withheld_boot_may_refuse`(点名组与差异)。
490
490
  钉:`test/health-readyz.test.ts` / `test/health-readiness-single-source.test.ts`。
491
+ - **引擎信号 `engineSignal`(7.123.0+,`/health` 与 fleet 心跳都带,恒在场)**:引擎态整只,型 = settings-schema `EngineSignal`
492
+ (`@sema-agent/settings-schema/fleet`;字段以契约包为准)。不带 `instanceId`(身份只在体顶层)。与扁平键**同一次读数**:
493
+ `engineSignal.draining`(排空事实 `{actionId, since, deadlineAt}`)在场 ⇔ 扁平 `draining: true`,`drainingSince` = `engineSignal.draining.since`;
494
+ `ready` / `restart` / 成员三键 / `/health` 世代账两版本与嵌套里的同名键同值。缺席 = 更早的引擎。⚠️ `/health` 无鉴权而排空中
495
+ `engineSignal.draining.actionId` 是本次排空的动作号:引擎自起的排空是引擎的闭集词,经 `POST /v1/admin/drain` 进的是调用方自取的幂等键原样
496
+ —— 用不透明 id(推荐 `manual:<uuid>`),别放秘密或人名。扁平布尔对 `draining` / `drainingSince`
497
+ 在 settings-schema 12.0.0 删、本引擎 7.124.0+ 删发 —— 读它们的编排脚本 / 壳改读 `engineSignal.draining`。
491
498
  - **配置接力位 `configHandoff`(7.121.0+,只在 `/health`,不上心跳)**:`{ state: "missing", version, since }`,**在场 ⇔ 部署声明了耐久接力(`CONFIG_LKG_DURABLE=true`
492
499
  ∧ LKG 启用)∧ 应用着的那一代没在下一次起服将用的凭证身份的 LKG 桶里确认耐久落盘** —— 中心断着时重启按**此刻的凭证身份**选 LKG 桶,那只桶里没有这一代就回到
493
500
  别的配置。三种形:LKG 发布失败(保留候选、启动期技能债)、部分应用(某组被整拒、其余组已热应用,LKG 停在上一个完整世代)、凭据换了人(凭证文件换成别的身份,
@@ -676,6 +683,8 @@ SEMA_REGISTRY_DRY_RUN=true # 安全灰度:只 LOG 中心配
676
683
  # publishNotReadyAddresses,Ingress 的活性规则指它)。缺席 = SEMA_REGISTRY_URL。用量、配额租约、
677
684
  # 拉配置始终打 SEMA_REGISTRY_URL。中心基址三席(SEMA_REGISTRY_URL / _LIVE_URL / _WEB_URL)同一条判据:
678
685
  # 可解析的 http(s) URL,不带凭据 / query / fragment(基址后面要拼路径),坏形拒启(没接中心也拒)。
686
+ # 拒句只点名规则,不回显 scheme:不是合格 http(s) 形的值(`host:8080`、`<令牌>:<口令>@host`、`ftp://…`)
687
+ # 整只成 `«redacted:url»`;合格 http(s) 形只到 `scheme://host[:port]`,后面的凭据 / 路径 / query / fragment 一律 `/…`。
679
688
  # SEMA_REGISTRY_WEB_URL=https://<中心 web 面> # 7.121.0:这个中心 **web 面**的固定基址,引擎原样转述到 `/health` 的 `center.webBaseUrl`
680
689
  # (壳按它拼会话深链,路径怎么拼归壳)。缺席即缺席,引擎不拼不猜。形判同上(三席一条判据);
681
690
  # 只在接了中心(SEMA_REGISTRY_URL)时挂。
@@ -698,7 +707,8 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
698
707
  # 符号 / 空白拒启);缺席 = 非 fleet 成员(单机形:不续服务租约、不受围栏)。回声到心跳与 /health 的
699
708
  # generation(连同 leaseState / leaseUntil);排空口按它比 expectedGeneration。
700
709
  # 服务租约只在中心于心跳答复里宣告 capabilities.servingLease 之后生效;没宣告 ⇒ leaseState none,
701
- # 零续租零 503。🔴 设了它就必须配齐中心三件(URL + 凭证 + SEMA_REGISTRY_WORKER)与 FLEET_ADVERTISE_ADDRESS
710
+ # 零续租零 503;位撤掉再宣告不重置 T_max 的钟(最后一次授予的到期 / 本进程第一次见到位的时刻照算)。
711
+ # 🔴 设了它就必须配齐中心三件(URL + 凭证 + SEMA_REGISTRY_WORKER)与 FLEET_ADVERTISE_ADDRESS
702
712
  # (不公告就听不到能力位),缺一 ⇒ 起服拒启 config.fleet_member_unfenced —— 成员不许悄悄脱离围栏。
703
713
  # 续租打 POST /api/fleet/live/serving-lease(契约 FLEET_PATHS.servingLease;活性基址见 SEMA_REGISTRY_LIVE_URL)。
704
714
  # 心跳答复体读不出 / 不是 JSON 对象 = 没有能力读数(位与租约钟原样,warn fleet_announce_reply_unreadable)。
@@ -706,9 +716,12 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
706
716
  # 缺省 scope global;在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启
707
717
  # config.fleet_scope_invalid)。与 FLEET_GENERATION 不联动。只进排空口的身份比对:请求的 scope 与它比,
708
718
  # 请求不带 scope 读成 global。续租体与心跳不带它(scope 由中心凭证绑定)。
709
- # FLEET_LEASE_OUTAGE_MAX_MS=86400000 # 7.121.0:服务租约**中心不可达的容忍上限**(缺省 24 h;< 60000 / 0 / 非整数拒启)。租约过期(中心
710
- # 不可达 / 答 unknown)后照常服务,过期满这么久 ⇒ 新工作 503 state.lease_expired(Retry-After 60;
711
- # 取消 / HITL 应答 / 续跑 / 读面照放行,在飞不动,/readyz 不翻);中心恢复授予即恢复。
719
+ # FLEET_LEASE_OUTAGE_MAX_MS=86400000 # 7.121.0:一台实例**活着而不服务的最长时长**(缺省 24 h;< 60000 / 0 / 非整数拒启)。两处读它:
720
+ # ① 服务租约中心不可达的容忍上限 —— 租约过期(中心不可达 / 答 unknown)后照常服务,过期满这么久 ⇒
721
+ # 新工作 503 state.lease_expired(Retry-After 60;取消 / HITL 应答 / 续跑 / 读面照放行,在飞不动,
722
+ # /readyz 不翻);中心恢复授予即恢复。从没被授予过的,从本进程第一次见到能力位起算。
723
+ # ② 7.122.0 起排空口 POST /v1/admin/drain 的期限上界:deadlineAt 离此刻超过它 ⇒ 400
724
+ # request.field_invalid(非 fleet 成员同样生效 —— 这一席无条件解析)。
712
725
  # 中心以正面证据拒(被取代 / 已退役)不受它管:当拍排空、到期自停,数据根记一只
713
726
  # fleet-serving-lease-denied.json,同 worker 同代际重启后仍记住、第一条心跳就是排空态(换代由中心做,
714
727
  # 不要手删)。那只文件写不进(数据根只读 / 盘满)⇒ 本进程照拒照停,但 error 行
@@ -717,7 +730,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
717
730
  ```
718
731
  - 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
719
732
  - **逐条容错**(S-704 ②,7.101.0 起):目录里一条条目不合形(如 `contextWindow:"abc"`)⇒ **只丢那一条**(warn `config_entry_dropped`,
720
- 点名坐标 `models.<下标>` 与字段诊断),其余条目照用;refresh 拍 / 迟到的 boot 拉取**不再因此整拒候选**(此前一条坏条目让整份目录停在
733
+ 点名坐标 `models.<下标>`、闭集原因词 `reasonCode` 与字段诊断;7.123.0 起同一条也在 `GET /v1/models` 的 `droppedEntries` 里自报),其余条目照用;refresh 拍 / 迟到的 boot 拉取**不再因此整拒候选**(此前一条坏条目让整份目录停在
721
734
  上一代 —— 上一代若还是 env,env 模型就一直服务)。颗粒更粗的失效(整个域读不懂、拼错键、坏文件)仍整拒候选、保 LKG。
722
735
  - **没有 `MODEL_ID` 也能起**(S-705,7.101.0 起):目录(`config.d/models.json` 或中心)有可用条目时 env 可以不写 `MODEL_ID` —— 缺省模型取
723
736
  目录缺省。判据在目录应用**之后**:env `MODEL_ID` 或目录缺省任一在场即过;两者皆无 ⇒ 拒启(码 `config.default_model_missing`,指路二选一)。
@@ -945,8 +958,9 @@ APPROVAL_GRANT='mcp__figma__*,a2a__planner__*,Bash(git:*)' # 逗号分隔的 a
945
958
  leader 没有请求体 —— 运维为自己接的服务器、自家信得过的命令,在这里写一次。
946
959
  - **值形(与引擎的文法相同)**:工具名、`mcp__<server>__*` 或两段形 `mcp__<server>`、`a2a__<peer>__*`、`Bash(<命令>)` / `Bash(<前缀>:*)` 命令规则、
947
960
  `Read(//<dir>/**)` 读目录。`auto` 模式下交出任意代码的条目(整只 shell、解释器 / 启动器前缀)交分类器判(引擎的剔除表)。
961
+ 命令规则 `Bash(<命令>)` 按命令判每一只跑 shell 命令的工具(`Bash` 与 `Monitor`;7.122.0 起);`Monitor(<命令>)` 不是规则拼法(引擎读成空 ⇒ 启动拒,见下)。
948
962
  坏值 ⇒ **启动拒**(句子点名那一条、引擎给的原因词、怎么写):引擎读成空的条目(`*`、`mcp__*`、写路径形 `Edit(src/**)`、域名形、带空白的名字;
949
- 7.118.0 起条目名也过引擎的判官 —— 退役名 / 无前缀短名 / 协议名服务器段铸不出读成 `tool_name`,句子带现名)、工具段为空的协议名(`mcp__srv__`)。
963
+ 7.118.0 起条目名也过引擎的判官 —— 退役名 / 无前缀短名 / 协议名服务器段铸不出、工具段为空(`mcp__srv__`,7.123.0 起由引擎判;此前本服务另判)读成 `tool_name`,句子带现名或改法)。
950
964
  内容形里带逗号的规则写不进(这张表的分隔符就是逗号)。
951
965
  - **永远清不掉的**:任何一层的 deny(`APPROVAL_DENY`、请求 / 会话规则的 deny、命令策略拒档、敏感写路径)、ask 规则(`APPROVAL_REQUIRE`、请求的 ask)、
952
966
  `APPROVAL_NEVER_AUTO`、`MANUAL_MODE_SHELL_GATE=always`、写保护路径、治理、PreToolUse hook 的问、plan 模式。命中而清不掉时卡上带
@@ -985,6 +999,11 @@ WRITE_PROTECTED_TABLE_REPLACE='[{"name":".envrc","kind":"basename"}]' # 整表
985
999
  - 租户面 `GET /v1/capabilities` → `writeProtection: {armed, rows, replaced}`(只给计数,不给行名);
986
1000
  - operator 面 `GET /v1/diagnostics/wiring` → `writeProtection: {rows[], source, droppedDefaultRows?}`
987
1001
  (`source`:`default` / `extra` / `replace` / `off`;逐行内容只在这一面)。
1002
+ - **配置根(`SEMA_CONFIG_DIR`)之内的整棵树都在这张表的射程里**(引擎 7.46.0 起):缺省表里的 `.sema` 行不只守家目录下的 `~/.sema`,也被放在你声明的配置根下 ——
1003
+ 根之内**任何**路径(不止配置文件)的写(`Write` / `Edit` / `NotebookEdit`)都要过 `write_protection` 一问。所以**配置根不要设成工作目录本身或它的祖先**
1004
+ (容器里 `SEMA_CONFIG_DIR=/workspace` 而项目在 `/workspace/proj`、或直接设成 `HOME`):项目里的每一次写都出一张卡,allow 规则清不掉它,`acceptEdits` 不放,
1005
+ `dontAsk` 拒,无人值守腿(leader、无终端的 `run-local`)拒(leader 配了耐久审批时停驻等人答),只有 `bypassPermissions` 放行。**起服不报、首条任务也不报** —— 第一次知道是第一次写。出路:把配置根指到项目之外的专用目录
1006
+ (不设即缺省 `~/.sema`);确实要保留祖先形配置根的,`WRITE_PROTECTED_TABLE_REPLACE` 给一张不含 `.sema` 行的表可去掉这一问,代价是 `~/.sema` 下的这一问一并没了。
988
1007
  - **与 `SENSITIVE_WRITE_PATTERNS` 并列、不合流**:那一根是**模式**(正则/glob)型 DENY 集,这一根是
989
1008
  字面名表的 `allow → ask` 降级。两套互不覆盖,读面也分开报 —— 一条路径被哪一套拦住,是两个不同的问题
990
1009
  (解法一个是改模式、一个是改名表)。
@@ -1070,7 +1089,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1070
1089
  - **它管什么**:`permissionMode:"bypassPermissions"` 派生 `TaskSpec.writeFace:"open"` ——
1071
1090
  也就是说**一个调用方**可以掀掉写围栏(任务根之外也能写)。而引擎的解析序在普通部署上是
1072
1091
  **任务席赢部署席**,引擎里唯一压得住它的两形(org 治理 / 只读 verifier 挂载)在本仓一个结构上不可达、
1073
- 一个要靠 `AUTONOMY=read-only|plan`(那是**整条 run 禁写**,不是等比例旋钮)。⇒ 天花板由 **server 在
1092
+ 一个要靠 `AUTONOMY=read-only`(那是**整条 run 禁写**,不是等比例旋钮)。⇒ 天花板由 **server 在
1074
1093
  受理请求时**执行:不给的时候 **422 响亮拒**(`errorCode: request.host_posture_refused`),而**不是**
1075
1094
  被静默钳回 roots(静默钳是本仓明令要修掉的失败形:调用方明说「别拦我」,却撞上一道它掀不掉
1076
1095
  也看不见的墙)。
@@ -1105,10 +1124,13 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1105
1124
  上层折成 `409 resume_blocked_by_policy`(`blockedBy: request.host_posture_refused`)。
1106
1125
  运维的解法是 `TASK_WRITE_FACE_ESCALATION=allow`。
1107
1126
  - **能力面披露位**:`GET /v1/capabilities` 的 `taskWriteFaceOpen`(`false` ⇒ 发那个键会拿 422;或 —— 在
1108
- `AUTONOMY=read-only|plan` 的部署上(7.96.0 起)—— 受理但写面被钳回 roots)——壳不必靠踩一次才学会。
1109
- - **`AUTONOMY=read-only|plan` 一行未动**。⚠️ 两者叠加时**本门先说话**:托管形 + `AUTONOMY=read-only`
1127
+ `AUTONOMY=read-only` 的部署上(7.96.0 起)受理但写面被钳回 roots,在 `AUTONOMY=plan` 的部署上(7.122.0 起)受理但模式词
1128
+ 在提交入口被钳成 `plan`)——壳不必靠踩一次才学会。
1129
+ - **`AUTONOMY=read-only` 一行未动**。⚠️ 两者叠加时**本门先说话**:托管形 + `AUTONOMY=read-only`
1110
1130
  + `bypassPermissions` 的请求现在是 422,而 7.94 及以前它会跑起来(写被 autonomy 禁掉)。
1111
1131
  要保留旧结局就 `TASK_WRITE_FACE_ESCALATION=allow`(写面仍被 autonomy 压回 roots)。
1132
+ `AUTONOMY=plan`(7.122.0 起)反过来:它是提交入口的模式词地板,先于本门 —— 托管形 + `AUTONOMY=plan` + `bypassPermissions` 的请求
1133
+ 受理、在 plan 里起步(这一跑的模式词是 `plan`,写面不开,本门无话可说)。
1112
1134
 
1113
1135
  - **MCP 撤销腿(7.39 起)**:MCP 服务器 mount 仍是 restart-to-apply(新增/改址要
1114
1136
  restart 才生效),但**从 enabled 集删除一个服务器即时生效**——refresh 拍收货后,被删服务器的
@@ -1577,7 +1599,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1577
1599
  | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
1578
1600
  | `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** service-token/单用户形;带 principal 的调用方锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
1579
1601
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**(见下) |
1580
- | `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 ⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_REGISTRY_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_REGISTRY_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
1602
+ | `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 ⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_REGISTRY_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;`deadlineAt` 离本引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS`(缺省 24 h,一台实例活着而不服务的最长时长;7.122.0 起)⇒ 同码 400(什么都不变;拒句点名字段与上界的值;只在这一发会进入排空时判 —— 已在排空或硬停已在进行时任何重发都回 `already_draining`),已过的期限照收(进入即到期);嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_REGISTRY_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
1581
1603
  | `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`,空名单=谁都不是)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,managedNow,effectiveNow}}`(🔴 7.106.0 起 `center.domainExists` 删除:座位在加载期对契约 `DOMAIN_SCHEMAS` 核过域 + 路径,核不过即拒启 ⇒ 有 `center` 块 ⇔ 中心可写;readFace 四键 / `PERMISSIONS_DISABLE_AUTO_MODE` / `MODEL_DEGRADE_ON` / `_REACTIVE` 七行的座位随之删除,S-866 ⑤a);顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 路径 404 + 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
1582
1604
  | `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:空 `OPERATOR_PRINCIPALS` ⇒ 恒 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
1583
1605
  | `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
@@ -2043,11 +2065,15 @@ GET /v1/capabilities?permissionMode=auto
2043
2065
  → permissionModeAuto: { accepted:true, classifierSeat:true, entitlementSource:false, armed:true, model:"default" }
2044
2066
  GET /v1/capabilities?permissionMode=default
2045
2067
  → permissionModeAuto: { …, armed:false, reason:"mode_not_auto" }
2068
+ GET /v1/capabilities?permissionMode=auto (AUTONOMY=plan 的部署)
2069
+ → permissionModeAuto: { …, armed:false, reason:"operator_floor" }
2046
2070
  ```
2047
2071
 
2048
2072
  - `armed`:按上式此刻会不会武装(`?permissionMode=` 折进本次意图;查询串缺席 = 按 auto 意图答「若请求会不会武装」;
2049
2073
  core 闭集之外 400 `request.field_invalid`)。
2050
- - `reason`(未武装时,闭集**六词**,判序即武装式合取序):`mode_not_auto`(意图不是 auto)/ `deployment_incapable`
2074
+ - `reason`(未武装时,闭集**七词**,判序即武装式合取序):`mode_not_auto`(意图不是 auto,且原样到得了引擎 —— 改请求)/ `operator_floor`
2075
+ (7.123.0 起:本部署 `AUTONOMY=plan` 的入口地板把意图改成了别的词、不是 auto —— 意图缺席、`auto`、任何比 `plan` 松的词都是这一词;
2076
+ 出路是运维换档,改请求的词到不了 auto;7.122.0 在这里答 `mode_not_auto`)/ `deployment_incapable`
2051
2077
  (装配面没挂分类器席位——生产装配恒挂,桩与一次性 CLI 不挂)/ `org_denied`(`caps.autoMode === false`:center 显式 deny、
2052
2078
  center 硬失败 fail-closed 拒项)/ `local_denied`(本地旋钮 `PERMISSIONS_DISABLE_AUTO_MODE=true`,改本机 env)/ `settings_denied`
2053
2079
  (settings 层 `permissions.disableAutoMode:"disable"`——本面不产,归 run 级面)/ `resolver_fault`(per-principal resolver 抛错,
@@ -2067,11 +2093,24 @@ GET /v1/capabilities?permissionMode=default
2067
2093
 
2068
2094
  ### 9.4b plan 模式(`permissionMode:"plan"`):批准一份计划时,顺带说「解到哪一档」(7.86.0 起)
2069
2095
 
2070
- 只读起步的任务把计划交给人之后停在 `plan_review` 门上。`POST /v1/assistant/tasks/:id/plan_review` 的
2071
- `decision:"approve"` 可携 **`permissionModeAfter`**(闭集 `"default" | "acceptEdits"`,**缺席 ⇒ `"default"`**):
2072
- `acceptEdits` = 继续跑时对自己工作目录内的写不再逐次征询,`default` = 有手但每次写照常征询;`edit` / `reject`
2073
- 的语义是重做计划,只读**原样保留**,在它们上送这一位是 `400`(词表外的值、以及本来就不是只读起步的任务送它,
2074
- 同样是 `400`)。解除会写回这条会话的续跑输入 —— 此后每一次挂起→续跑都沿用解除后的档,不回落只读。
2096
+ **plan 只有一个判官 —— 引擎的 plan 模式(7.122.0 起)**:请求顶层 `permissionMode:"plan"`、`settings.permissions.defaultMode:"plan"`、
2097
+ 运维 `AUTONOMY=plan` 与模型自己调 `EnterPlanMode`,落到的都是同一个模式词。提交之后,一条 run 的模式词只有一个源 —— 存储的提交体顶层
2098
+ `permissionMode`;写它的只有提交入口与下面的 `plan_review` 写回。运维 `AUTONOMY=plan` 是**提交入口的模式词地板**:受理时盖进提交体
2099
+ (请求不带模式词 ⇒ `plan`;带任何词 ⇒ 钳成 `plan`;bundle 的 `defaultMode` 不动,顶层赢),之后不再按段续跑重钳;热改 `AUTONOMY` 只影响之后提交的任务,
2100
+ 在飞的 run 保持它的入口词。工具表与 `default` 同面(写手、委派工具 `Agent` 都在),
2101
+ 写与非只读调用在**调用时**由引擎的 plan 门问(有人可答)/ 拒(无人可答)—— 问是一张审批卡,人批了就写,
2102
+ **射程是写面根围栏之内**(任务 `cwd`、引擎宣告的可写目录〔记忆目录、会话 scratchpad〕、请求的 `additionalDirectories`):批准只清模式门,引擎文件工具自己的根围栏不受批准影响,
2103
+ 围栏外的路径批了也不写(`tool_end.errorCode` = `path_not_in_root`,拒句点名 roots;写面声明为 open 的任务除外);委派的子代以 plan 为天花板继承同一道门。所以请求体带
2104
+ `agents`(自定义子代定义)的任务进 plan 照常起跑。此前本服务把用户选的 plan 另翻成「只读手 + 剥委派工具」,带 `agents` 的任务因此整轮被引擎拒
2105
+ (`config.agents.no_delegation_tool`);只读手现在只是 `AUTONOMY=read-only` 一个词的事(子代同受只读,委派照挂)。
2106
+
2107
+ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST /v1/assistant/tasks/:id/plan_review` 里把计划**带出 plan** 的决定
2108
+ —— `decision:"approve"`(按原计划开干)与 `decision:"edit"`(按人改过的计划开干;7.122.0 起)—— 可携 **`permissionModeAfter`**
2109
+ (闭集 `"default" | "acceptEdits"`,**缺席 ⇒ `"default"`**):`acceptEdits` = 继续跑时对自己工作目录内的写不再逐次征询,`default` = 有手但每次写照常征询。
2110
+ `reject` 的语义是重做计划,plan **原样保留**,在它上面送这一位是 `400`(判据与引擎同句:只有 `reject` 不出 plan)。词表外的值、以及存储模式词不是 plan
2111
+ 的任务(模型自己调 `EnterPlanMode` 那一形 —— 批准把它送回进 plan 之前的档)送它,同样是 `400`;`AUTONOMY=plan` 下提交的任务存储词就是 plan,照收。
2112
+ 解除会写回这条会话的续跑输入 —— 此后每一次挂起→续跑都沿用解除后的档,不回落 plan(`AUTONOMY=plan` 也不把它拉回:地板只在提交入口盖一次)。
2113
+ 7.122.0 之前 `edit` 之后仍是只读手(模型被告知开干却没有写手),`edit` 上送这一位是 `400`。
2075
2114
  🔴 **这个体是闭集(7.86.0,S-438)**:受理集 = `decision` / `editedPlan` / `reason` / `permissionModeAfter` /
2076
2115
  `boundCallId` 五键,集外键(含定谳前的旧拼法 `nextPermissionMode`)**响亮拒** `400 request.body_shape` +
2077
2116
  机读两桶逐字点名,**不再静默丢** —— 与 `/v1/approvals/:id/decide` 同一只机器、同码同串。
@@ -2157,7 +2196,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2157
2196
  ### 9.6 trace 脱敏的射程:与出站请求体无关
2158
2197
 
2159
2198
  🔴 **脱敏 ≠ 出站控制**:trace 脱敏(账本 / SSE / turns / 审批预览里的 `«redacted»` 标记)只作用于
2160
- **持久化与展示面**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
2199
+ **读面 / 展示面**(账本 / SSE / turns / 审批预览 / `GET /v1/sessions/:id`);**不碰**引擎会话存档(审计真源:store 里怎么写就怎么回、HTTP 读面才脱敏,之后每一次续轮按原文回送模型路由)与 session-sync 七口 `/v1/sessions/:id/sync/*`(逐字 durable log 出入)——这两面是**原形**——**工具结果发往「该 run 实际选中的模型路由」的请求体是原文**。⚠️ 「实际选中的路由」
2161
2200
  不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
2162
2201
  备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
2163
2202
  路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及配置中心可对**单个
@@ -7,6 +7,7 @@ import type { Logger } from "./observability/logger.js";
7
7
  import type { Metrics } from "./observability/metrics.js";
8
8
  import { type DenyReason, type VoidReason } from "./approval-deny-reasons.js";
9
9
  import { type ApprovalRevokeFrame } from "./approval-card.js";
10
+ import type { ParkCommitWatch } from "./plugins/park-commit-watch.js";
10
11
  /** 判据表 v2 的一次判定结果(纯数据;执行器按 kind 分派到店面的一次 CAS)。 */
11
12
  export type ReconcileAction = {
12
13
  kind: "bind";
@@ -230,6 +231,13 @@ export interface ApprovalReconcilerDeps {
230
231
  */
231
232
  unattendedPolicy: UnattendedApprovalPolicy;
232
233
  emitRevoke?: ApprovalRevokeEmitter | undefined;
234
+ /**
235
+ * S-1280:park 提交的唯一观察点(`plugins/park-commit-watch.ts`;在场 ⇔ 本部署有 checkpoint 店)。在场 ⇒ 收敛器订阅它:一只审批停车行
236
+ * 提交的那一拍,对**这次调用**的 `PARKING` 行即时跑判据 1(与周期扫同一只执行体),`put` 等它收完才交还 core。缺席 ⇒ 只有周期扫(修前形)。
237
+ */
238
+ parkCommits?: Pick<ParkCommitWatch, "onParkCommitted" | "track"> | undefined;
239
+ /** {@link PARK_COMMIT_BIND_BUDGET_MS} 的注入口 —— 不接 env(不是部署旋钮,是这一拍拖住 core 停驻的上界),可注入只为让判据观测得到。 */
240
+ parkCommitBindBudgetMs?: number | undefined;
233
241
  }
234
242
  /** {@link createApprovalReconciler} 返回的活对象。 */
235
243
  export interface ApprovalReconciler {