@sema-agent/server 7.151.0 → 7.152.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 (286) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +15 -3
  3. package/README.zh-CN.md +10 -2
  4. package/USAGE.md +93 -64
  5. package/deploy/sema-up/README.md +5 -4
  6. package/deploy/sema-up/cluster-up.sh +6 -6
  7. package/deploy/sema-up/kube-up.sh +5 -5
  8. package/deploy/sema-up/lib/flagshape.sh +36 -8
  9. package/deploy/sema-up/machines.example.yaml +3 -1
  10. package/deploy/sema-up/sema-up.sh +22 -16
  11. package/dist/approval-ask-audit-store.d.ts +1 -1
  12. package/dist/approval-ask-machine.d.ts +4 -10
  13. package/dist/approval-ask-retention.d.ts +1 -1
  14. package/dist/approval-card.d.ts +10 -12
  15. package/dist/approval-gate-currency.d.ts +8 -8
  16. package/dist/approval-gate-currency.js +3 -4
  17. package/dist/approval-hmac.d.ts +17 -1
  18. package/dist/approval-hmac.js +7 -1
  19. package/dist/approval-queue-kind.d.ts +1 -1
  20. package/dist/approval.d.ts +2 -2
  21. package/dist/approval.js +2 -2
  22. package/dist/bind-host.d.ts +1 -3
  23. package/dist/bind-host.js +2 -3
  24. package/dist/boot/coordinators.d.ts +1 -1
  25. package/dist/boot/coordinators.js +9 -10
  26. package/dist/boot/device-lane.js +2 -0
  27. package/dist/boot/execution-env.d.ts +1 -0
  28. package/dist/boot/execution-env.js +31 -5
  29. package/dist/boot/leader.d.ts +31 -30
  30. package/dist/boot/leader.js +38 -28
  31. package/dist/boot/leg-path-adjudication.d.ts +40 -0
  32. package/dist/boot/leg-path-adjudication.js +18 -0
  33. package/dist/boot/parked-revive-gate.js +3 -15
  34. package/dist/boot/reapers.d.ts +1 -1
  35. package/dist/boot/reapers.js +1 -1
  36. package/dist/boot/resolve-spec.js +12 -26
  37. package/dist/boot/runner-deps.d.ts +39 -35
  38. package/dist/boot/runner-deps.js +92 -89
  39. package/dist/boot/shutdown.d.ts +6 -0
  40. package/dist/boot/shutdown.js +10 -4
  41. package/dist/boot/stage-02-web-admin-pull.js +4 -5
  42. package/dist/boot/stage-05-execution-env.js +2 -1
  43. package/dist/boot/stage-06-runners.d.ts +2 -0
  44. package/dist/boot/stage-06-runners.js +10 -4
  45. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  46. package/dist/boot/stage-07-capability-layer.js +1 -1
  47. package/dist/boot/stage-08-reapers.d.ts +1 -0
  48. package/dist/boot/stage-09-leader.d.ts +1 -0
  49. package/dist/boot/stage-09-leader.js +2 -2
  50. package/dist/boot/stage-10-http-server.d.ts +8 -0
  51. package/dist/boot/stage-10-http-server.js +11 -15
  52. package/dist/boot/stores.js +7 -4
  53. package/dist/boot/web-admin.js +2 -7
  54. package/dist/capabilities/approvals-push.d.ts +3 -3
  55. package/dist/capabilities/scenarios.d.ts +28 -13
  56. package/dist/capabilities/scenarios.js +2 -2
  57. package/dist/capability-facts.d.ts +22 -20
  58. package/dist/capability-facts.js +18 -34
  59. package/dist/card-rule-offers.d.ts +32 -0
  60. package/dist/card-rule-offers.js +130 -0
  61. package/dist/config-catalog.js +12 -11
  62. package/dist/config-dir-trust.js +4 -2
  63. package/dist/config-invariants.d.ts +18 -7
  64. package/dist/config-invariants.js +51 -8
  65. package/dist/config-lkg.d.ts +5 -19
  66. package/dist/config-lkg.js +8 -62
  67. package/dist/config-provider.d.ts +1 -1
  68. package/dist/config-root-migration.d.ts +2 -2
  69. package/dist/config-types.d.ts +28 -38
  70. package/dist/config.d.ts +7 -5
  71. package/dist/config.js +55 -16
  72. package/dist/control-socket.d.ts +51 -0
  73. package/dist/control-socket.js +115 -0
  74. package/dist/deployment-governance.d.ts +2 -1
  75. package/dist/device-ws-hub.d.ts +7 -0
  76. package/dist/device-ws-hub.js +7 -1
  77. package/dist/elicitation.js +2 -2
  78. package/dist/entry-problems.d.ts +40 -3
  79. package/dist/entry-problems.js +3 -1
  80. package/dist/execution-env-refusal.d.ts +8 -2
  81. package/dist/execution-env-refusal.js +3 -0
  82. package/dist/fleet/engine-signal.d.ts +12 -29
  83. package/dist/fleet/engine-signal.js +1 -7
  84. package/dist/fleet-client.js +2 -5
  85. package/dist/gate-settlement.d.ts +1 -8
  86. package/dist/gate-settlement.js +0 -4
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +2 -3
  89. package/dist/hitl-delivery.js +0 -2
  90. package/dist/hooks/hook-llm.js +4 -2
  91. package/dist/hooks/hook-runner.d.ts +3 -2
  92. package/dist/hooks/hook-runner.js +16 -1
  93. package/dist/host-decision.d.ts +11 -11
  94. package/dist/http/active-run-conflict.d.ts +35 -2
  95. package/dist/http/active-run-conflict.js +48 -19
  96. package/dist/http/approval-sweeps.d.ts +1 -1
  97. package/dist/http/approval-sweeps.js +5 -2
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +6 -2
  101. package/dist/http/dispatch.js +25 -5
  102. package/dist/http/durable-run-leg.js +0 -1
  103. package/dist/http/health-reader.d.ts +16 -9
  104. package/dist/http/health-reader.js +2 -1
  105. package/dist/http/health-self-report.js +1 -1
  106. package/dist/http/owner-access.d.ts +2 -3
  107. package/dist/http/owner-access.js +3 -4
  108. package/dist/http/principal-gate.d.ts +49 -21
  109. package/dist/http/principal-gate.js +61 -11
  110. package/dist/http/request-channel.d.ts +23 -0
  111. package/dist/http/request-channel.js +9 -0
  112. package/dist/http/request-guards.js +17 -2
  113. package/dist/http/resume-ctx-read.js +20 -3
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +111 -41
  116. package/dist/http/route-ctx.d.ts +73 -7
  117. package/dist/http/route-ctx.js +17 -0
  118. package/dist/http/route-table.d.ts +4 -0
  119. package/dist/http/route-table.js +4 -0
  120. package/dist/http/routes/admin-config-refresh.js +3 -3
  121. package/dist/http/routes/admin-drain.d.ts +3 -0
  122. package/dist/http/routes/admin-drain.js +7 -7
  123. package/dist/http/routes/admin-memory-optout.js +3 -3
  124. package/dist/http/routes/adoption.js +3 -5
  125. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  126. package/dist/http/routes/approvals-assistant.js +95 -48
  127. package/dist/http/routes/capabilities.d.ts +4 -0
  128. package/dist/http/routes/capabilities.js +30 -5
  129. package/dist/http/routes/config-catalog.js +2 -2
  130. package/dist/http/routes/devices.js +1 -1
  131. package/dist/http/routes/diagnostics.d.ts +3 -3
  132. package/dist/http/routes/diagnostics.js +19 -7
  133. package/dist/http/routes/images.js +10 -10
  134. package/dist/http/routes/memory-bundle.js +3 -3
  135. package/dist/http/routes/memory-compliance.js +3 -3
  136. package/dist/http/routes/memory-consolidation.js +2 -2
  137. package/dist/http/routes/memory-origin.js +3 -3
  138. package/dist/http/routes/memory-policy.js +2 -2
  139. package/dist/http/routes/observability.js +3 -3
  140. package/dist/http/routes/retention-ops.js +3 -3
  141. package/dist/http/routes/rules.js +5 -5
  142. package/dist/http/routes/run-permission-mode.d.ts +2 -1
  143. package/dist/http/routes/run-permission-mode.js +2 -2
  144. package/dist/http/routes/runs.d.ts +4 -9
  145. package/dist/http/routes/runs.js +73 -33
  146. package/dist/http/routes/session-sync.d.ts +18 -6
  147. package/dist/http/routes/session-sync.js +38 -36
  148. package/dist/http/routes/sessions.js +20 -9
  149. package/dist/http/routes/tasks.js +28 -37
  150. package/dist/http/routes/workflows.js +1 -1
  151. package/dist/http/server.d.ts +9 -3
  152. package/dist/http/server.js +15 -3
  153. package/dist/http/submit-ctx.d.ts +1 -1
  154. package/dist/http/submit-ctx.js +2 -0
  155. package/dist/human-facing-args.d.ts +33 -0
  156. package/dist/human-facing-args.js +33 -0
  157. package/dist/index.d.ts +1 -1
  158. package/dist/k8s-api-credentials.d.ts +29 -0
  159. package/dist/k8s-api-credentials.js +25 -0
  160. package/dist/lane-home-probe.d.ts +1 -1
  161. package/dist/lane-home-probe.js +2 -1
  162. package/dist/leader/diffup.d.ts +19 -0
  163. package/dist/leader/diffup.js +7 -0
  164. package/dist/leader/leader.d.ts +114 -10
  165. package/dist/leader/leader.js +69 -5
  166. package/dist/leader/repair-wire.js +2 -2
  167. package/dist/leader/run-entry.d.ts +2 -1
  168. package/dist/leader/run-entry.js +1 -1
  169. package/dist/leader/wire.d.ts +42 -132
  170. package/dist/leader/wire.js +81 -67
  171. package/dist/memory-scope.d.ts +20 -9
  172. package/dist/memory-scope.js +15 -3
  173. package/dist/model-entry-headers.d.ts +9 -10
  174. package/dist/model-entry-headers.js +0 -4
  175. package/dist/model-entry-refusal.d.ts +7 -4
  176. package/dist/model-entry-refusal.js +11 -5
  177. package/dist/model-plane-face.d.ts +1 -1
  178. package/dist/model-plane-face.js +3 -1
  179. package/dist/model-route-endpoint.d.ts +13 -7
  180. package/dist/model-route-endpoint.js +14 -11
  181. package/dist/model-select.d.ts +22 -0
  182. package/dist/model-select.js +9 -0
  183. package/dist/model-unreachable-notice.d.ts +11 -0
  184. package/dist/model-unreachable-notice.js +4 -4
  185. package/dist/object-store-buckets.d.ts +640 -6
  186. package/dist/object-store-buckets.js +166 -0
  187. package/dist/observability/fail-open.d.ts +20 -16
  188. package/dist/observability/fail-open.js +20 -16
  189. package/dist/observability/logger.js +3 -1
  190. package/dist/operator-roster.d.ts +68 -0
  191. package/dist/operator-roster.js +32 -0
  192. package/dist/orchestration/workflow-park-decision.d.ts +1 -1
  193. package/dist/outbound-dispatcher.d.ts +71 -8
  194. package/dist/outbound-dispatcher.js +48 -7
  195. package/dist/owner-faces.d.ts +1 -1
  196. package/dist/parked-decide.d.ts +2 -2
  197. package/dist/permission-mode-admission.d.ts +59 -26
  198. package/dist/permission-mode-admission.js +37 -10
  199. package/dist/plugins/approval-ask-store-file.d.ts +4 -0
  200. package/dist/plugins/approval-ask-store-file.js +3 -0
  201. package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
  202. package/dist/plugins/approval-ask-store-memory.js +7 -1
  203. package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
  204. package/dist/plugins/approval-ask-store-sql.js +12 -1
  205. package/dist/plugins/blob-backend.d.ts +83 -7
  206. package/dist/plugins/blob-backend.js +57 -6
  207. package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
  208. package/dist/plugins/checkpoint-index-columns.js +3 -6
  209. package/dist/plugins/checkpoint-store-face.d.ts +119 -61
  210. package/dist/plugins/checkpoint-store-face.js +134 -55
  211. package/dist/plugins/landed-attendants.d.ts +2 -2
  212. package/dist/plugins/local-checkpoint-store.d.ts +13 -3
  213. package/dist/plugins/local-checkpoint-store.js +38 -49
  214. package/dist/plugins/one-time-migrations.d.ts +51 -21
  215. package/dist/plugins/one-time-migrations.js +67 -45
  216. package/dist/plugins/pg-pool.js +11 -9
  217. package/dist/plugins/remote-env-host.d.ts +29 -10
  218. package/dist/plugins/remote-env-host.js +99 -19
  219. package/dist/plugins/remote-env-k8s.d.ts +68 -2
  220. package/dist/plugins/remote-env-k8s.js +107 -24
  221. package/dist/plugins/replica-registration.d.ts +1 -1
  222. package/dist/plugins/replica-registration.js +1 -1
  223. package/dist/plugins/s3-presign.d.ts +23 -0
  224. package/dist/plugins/s3-presign.js +37 -3
  225. package/dist/plugins/scheduler-support.d.ts +2 -2
  226. package/dist/plugins/scheduler-support.js +0 -3
  227. package/dist/plugins/schema-indexes.js +2 -0
  228. package/dist/plugins/send-file-ledger.d.ts +2 -2
  229. package/dist/plugins/send-file-ledger.js +2 -2
  230. package/dist/plugins/send-user-file.d.ts +3 -4
  231. package/dist/plugins/send-user-file.js +56 -52
  232. package/dist/plugins/session-blob-claim-token.d.ts +15 -0
  233. package/dist/plugins/session-blob-claim-token.js +20 -0
  234. package/dist/plugins/session-blob-namespace.d.ts +46 -0
  235. package/dist/plugins/session-blob-namespace.js +116 -0
  236. package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
  237. package/dist/plugins/snapshot-bucket-presence.js +8 -2
  238. package/dist/plugins/sql-json-column.d.ts +1 -1
  239. package/dist/plugins/store-backend.d.ts +44 -2
  240. package/dist/plugins/store-backend.js +75 -53
  241. package/dist/plugins/task-attachment-store.d.ts +2 -1
  242. package/dist/plugins/task-attachment-store.js +5 -1
  243. package/dist/plugins/tidb-pool.js +19 -24
  244. package/dist/plugins/workspace-custody.d.ts +4 -3
  245. package/dist/question.d.ts +2 -1
  246. package/dist/question.js +5 -13
  247. package/dist/request-form.d.ts +6 -1
  248. package/dist/request-form.js +4 -1
  249. package/dist/rule-issue-reading.d.ts +1 -1
  250. package/dist/rules-consent.d.ts +1 -1
  251. package/dist/run-local.js +8 -9
  252. package/dist/runs.d.ts +17 -8
  253. package/dist/runs.js +15 -2
  254. package/dist/runtime-governance.d.ts +2 -2
  255. package/dist/sema-config-dir.d.ts +2 -1
  256. package/dist/session-sync.d.ts +27 -11
  257. package/dist/session-sync.js +15 -7
  258. package/dist/stores-sql/blob-index-sql.d.ts +30 -9
  259. package/dist/stores-sql/blob-index-sql.js +40 -21
  260. package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
  261. package/dist/stores-sql/checkpoint-store-sql.js +70 -146
  262. package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
  263. package/dist/stores-sql/file-history-store-sql.js +100 -28
  264. package/dist/stores-sql/retention-store-sql.js +3 -9
  265. package/dist/task-settings.d.ts +6 -5
  266. package/dist/task-settings.js +3 -3
  267. package/dist/tool-approval.d.ts +342 -518
  268. package/dist/tool-approval.js +498 -1207
  269. package/dist/trace/core-keyset-guard.d.ts +1 -1
  270. package/dist/trace/ledger-events.d.ts +3 -3
  271. package/dist/trace/project.d.ts +2 -3
  272. package/dist/trace/redact.js +3 -2
  273. package/dist/trace/self-minted-wire.d.ts +287 -157
  274. package/dist/trace/self-minted-wire.js +93 -48
  275. package/dist/web-admin-client/apply-effective.js +2 -2
  276. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  277. package/dist/web-admin-client/read-warnings.js +1 -1
  278. package/dist/web-admin-client/types.d.ts +1 -1
  279. package/dist/workspace-space.d.ts +10 -4
  280. package/dist/workspace-space.js +4 -1
  281. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
  282. package/package.json +6 -2
  283. package/scripts/embed-minimal.mjs +3 -3
  284. package/wire-changes/server@7.152.0.json +900 -0
  285. package/dist/config-d-migration.d.ts +0 -37
  286. package/dist/config-d-migration.js +0 -60
package/MIGRATION.md CHANGED
@@ -8,6 +8,272 @@
8
8
  > 本文件只记会影响存量部署行为的变更:某一版没有节,即那一版没有记入这里的升级动作;完整的变更流水账(修复、文字订正、内部重构)是内部记录,不随 npm 包出。
9
9
  > 文中的「契约 §N」「契约附录 X」指服务端 wire 契约的节号(内部记录,不随包),只作查证坐标。
10
10
 
11
+ ## 7.152.0 —— 1–33 条见下
12
+
13
+ **升级清单(本节唯一的一张;下面各条只讲为什么,不再各自重述升级步骤)** —— 一次读清本版对一只存量部署要做的全部动作。每版同形:① 手工 DDL(逐表逐句,两方言各一份)② 起服自建与一次性迁移 ③ 停-迁-起 ④ 顺序;后续每版的段头照此写,新增一句 `ALTER`、一条一次性迁移、一条停-迁-起时,先改这里,再写条目。
14
+ 已发各节(7.151.0 及以前)逐字只读,不在原处补汇总 —— 跨过它们的部署读下面「跨过 7.151.0」一块。
15
+
16
+ - **从 7.151.x 升级到本版**:
17
+ - 手工 DDL(SQL 后端 `DB_BACKEND=mysql|pg`;一张表一句,**先升库、再升副本**;旧副本只在它重启时按「库比构建新」拒启,多出的这一列是它的幽灵列)。语句逐字是起服列册对账拒句打印的那一句(由本构建的建表语句生成),存量行不回填(第 2 条「运维队列决断口收 `persistRule`」:停驻行的规则 scope 快照):
18
+ - MySQL 协议:
19
+ `ALTER TABLE checkpoint ADD COLUMN card_scope_root VARCHAR(4096) NULL;`
20
+ - PostgreSQL:
21
+ `ALTER TABLE checkpoint ADD COLUMN card_scope_root VARCHAR(4096) COLLATE "C";`
22
+ 其余各条零 DDL。`DB_BACKEND=local` 零 DDL。
23
+ - 起服自建(运维零手工):新表 `file_history_blob_grant` 与它的两只二级索引 `idx_file_history_blob_grant_granted` / `idx_file_history_blob_grant_blob`(起服 `CREATE TABLE IF NOT EXISTS` 自建;空表起步,存量零回填;第 3 条)。另一张新表 `blob_namespace_claim`(一库一行的认领记号,开店时缺席才铸;第 25 条);配了对象存储的部署起服时在会话快照桶写下 `<SESSION_SNAPSHOT_PREFIX>blob-namespace-claim`(第 25 条)。
24
+ - 一次性迁移:**M54**(SQL 两方言;起服自跑,运维零手工 DDL;第 1 条「停驻问的题干与选项整份上待决行」与第 19 条「运维待批队列行的全部动作事实与续跑执行读同一次整行解码」):删 `checkpoint` 的五只展示拷贝列 `tool_name` / `tool_input` / `risk_descriptor` / `rule_suggestions` / `has_bidi_controls`(待决行的入参与其余动作事实改为读时从整行解码投影,升级前停驻的行不重算、不迁值);读数 `checkpointDisplayColumnsDropped`(本次删掉的列数,0–5);它是停-迁-起,见下一行;`DB_BACKEND=local` 恒 0。第 3 条的新表起服自建、不回填,不占一次性迁移。
25
+ - 到期移除的一次性迁移:M13 / M15 / M16(第 18 条;盘上 LKG 信封 2 / 3 不再读回、本地 `config.d` 起服不再自动迁)—— 从 7.124.0 及以后升级的部署零动作;从 ≤ 7.123.x 直升须先经 7.124.0–7.151.x 任一版起服一次,或升级前备份 `config.d` 后手跑 `npx @sema-agent/settings-schema@25 migrate <config.d 目录>`(本版带的契约包 26.0.0 的 `migrate` 不再改写这几形,第 32 条)。
26
+ - 停-迁-起:新增 **M54**(第 1 条 / 第 19 条;SQL 两方言):起服时五列任一还在 ⇒ 先记起服要求、再一句 `ALTER TABLE checkpoint DROP COLUMN …` 删在的那几列(起服自跑,运维零手工 DDL;读数 `checkpointDisplayColumnsDropped`)—— 删列之后更早版本的副本每一次停驻与每一次队列读都失败,**升级前停光全部旧版本副本、等满判活窗**(判活窗内有不知道 M54 的副本在跑 run ⇒ 拒启 `config.checkpoint_display_columns_drop_refused`,一字节不写)。已有几条(**M40** · **M41** · **M42** · **M51**)的守卫同 7.151.0。
27
+ - 升级前自查(不改库;每条一行,出路在各条里):共用一只会话快照桶的各只库各有自己的 `SESSION_SNAPSHOT_PREFIX`(第 25 条;同前缀的第二只库起服拒启)、按最小权限发对象存储策略的给 `<前缀>blob-namespace-claim` 授 `s3:GetObject` / `s3:PutObject`(第 25 条;没授照起、附件与文件历史不再回收)· `SEND_USER_FILE_URL_TTL` 是十进制整数秒(不带单位)、它与 `SESSION_SNAPSHOT_TTL_SEC` ≤ 604800、开了 leader 与对象存储的 `LEADER_TIMEOUT_MS` ≤ 604200000(第 26 条;超了或非整数起服拒)· 设了 `AUTONOMY=plan` 的部署让已批过计划、此刻停着的 run 续完,或接受它们下一次续跑回到 plan、再批一次计划(第 4 条)· 部署 env 里删掉 `STREAM_APPROVAL_ENABLED`(留着即拒启,第 32 条);审批席开着的部署核 `DURABLE_APPROVAL` 与 store backend,缺一 ⇒ 没有审批卡(第 5 条)· 每一份存着的 `models` 文档里 `headers` 不是对象的条目改成对象或删掉该键、读 fleet 心跳 / `/health` 平铺引擎态键的编排改读 `engineSignal`(第 32 条)· local 后端的数据根没有放在共享文件夹 / 别的用户可写的上级目录里(第 8 条)· `REMOTE_EXEC=k8s` 的部署核集群 API 凭据(显式 `K8S_API_URL` / `K8S_TOKEN` / `K8S_CA_CERT_B64`,或 in-cluster 服务账号)在进程里读得到(第 9 条,缺 ⇒ 拒启)· 用 `sema-up` / `kube-up.sh` / `cluster-up.sh` / 机器清单起**新**部署的脚本与自动化,命令行、`-f` 配置、`machines.yaml` 或环境变量里写下了 `--users` / `USERS` / `users:`(第 12 条;已装部署的 `~/.sema-up/<project>/.env` 里有 `REQUIRE_PRINCIPAL` 一行,重跑零动作)· 开了 `LEADER_ENABLED` 的部署读第 20 条「升级前做」(强制耐久门 / 关 fork 的主体、`AUTONOMY` 档与守卫集、设了审批名单而没接停驻店的部署;零 DDL,滚动升级窗里旧副本照旧形跑 leader 任务) · 开了 `LEADER_ENABLED` 或配了 `type: "agent"` 钩子、且目录给模型配了自己钥匙(`apiKeyEnv` / `sealedApiKey`)的部署:核这些钥匙在服务进程里拿得到(拿不到 ⇒ 这两条车道此后响亮失败,不再落网关钥;第 27 条)。
28
+ - 顺序:① 备份 ② 上面那一句 `ALTER`(SQL 后端,先升库)③ 换版本 —— 带第 1 条(M54)的版本在 SQL 后端上**不可滚动**:停光全部旧版本副本、等满判活窗(`REAP_RUN_STALE_SEC`,缺省 120 s,必须大于 30 s 的心跳周期,否则起服拒),再起本版(先起一只,M54 在它起服时删列;拒句里的「可再起的时刻」= 最新心跳 + 判活窗),再起其余。`DB_BACKEND=local` 无 M54。
29
+ - **回滚**:SQL 后端回到 7.150.x / 7.151.x 的两道拒句与要跑的语句见第 1 条(先补五列、再删要求行;回到 ≤ 7.149.x 只有补列一道;回滚后本版期间停驻的行在旧版队列上缺哪几格见第 19 条);第 2 条多出的那一列的回滚见该条;`DB_BACKEND=local` 照数据根高水位不变量(`engine-high-water.json` 记着打开过这只根的最高版本 ⇒ 更旧的构建拒开这只根;常设不变量,不是本版带来的)。
30
+ - **跨过 7.151.0(从 ≤ 7.150.x 直升本版)另加 7.151.0 节段头升级清单的全部动作**(那一张已含「跨过 7.150.0」一块)。
31
+ - **文字面(第 13–17 条、第 28–29 条)**:对已发各节的勘误、补记、订正与成文,零行为变化、零部署动作 —— 读它们改的是你对已发各节的读法,不是部署动作。带读法后果的两条:第 16 条 ②(多副本上带 `mcpServers` / `a2aPeers` 决 workflow 停驻卡:7.150.x 在不持有那条 run 的副本上答 `200`,7.151.0 起各副本一律答 `400` —— 这是 7.151.0 已发的行为,本条只订正已发节里「照旧」那一句);第 28 条(SQL 后端从 7.143.0 回到 7.142.x:旧副本先被起服列册对账拒启,顺序读 7.143.0 节第 9 条与第 26 条的回滚句,再读第 32 条 ②)。
32
+
33
+ 1. **停驻问的题干与选项整份上待决行:给人看的调用入参只有一份读法 —— 脱敏、按 UTF-8 字节量、一个常量 32768;问 / 计划卡整份、从不折叠,其余卡与无卡超常量折叠;问载荷帽删除;待决行 `input` 两孪生都读时投影,SQL 派生列 `checkpoint.tool_input` 退役(行为变更;wire 零新键、零新码、零新帧型,值与阈值变;一次性迁移 M54 删列 —— 与第 19 条的四列同一条迁移,停-迁-起;部署动作 = 升级前停光旧副本)**:
34
+ 旧形(≤ 7.151.x):三只帽、两种量法 —— 待决行 `input`(`GET /v1/approvals` / `/stream`)不分卡、超 8192 个 UTF-16 单元换成 `{ truncated: true, bytes }`(`bytes` 是单元数);活卡 `args` 超 16384 UTF-8 字节 ⇒ `argsOmitted`;`question` 帧的问超 16384 个单元 ⇒ 无帧、该问按 unavailable 结算(durable 腿上改道停驻)。脱敏后 `{questions}` 超 8192 个单元的停驻问,审批人在待决行上看不到题干与选项。SQL 后端在停驻那一笔把折叠后的入参另落一列 `checkpoint.tool_input`(停驻快照里另有整份)。
35
+ 新形:一只函数、一个常量、一种量法(脱敏后 JSON 的 UTF-8 字节数 ≤ 32768)。问(`approvalCard: "question"`)与计划(`"plan"`)卡的入参整份给出、从不折叠 —— 待决行 `input` 恒是整只 `{ questions }`,`question` 帧恒带整份的问;其余卡与无卡的行超常量折叠:活卡 `argsOmitted`,待决行 `{ truncated: true, bytes }`,`bytes` = 脱敏后 JSON 的 UTF-8 字节数(标记形不变,两键同名同型)。序列化不出 ⇒ 活卡 `argsOmitted`、待决行 `null`、问答 unavailable(同前)。待决行 `input` 在两孪生上都是**读时投影**:入参取停驻快照的 `pendingAction.args`(续跑执行的同一份字节),SQL 不再存第二份 —— 升级前停驻的行起服即按本版读法显示,不重算、不迁值。
36
+ **变了的**:① 有问题席、连着流的腿上超 16384 个字符的问由「不投卡、当场停驻、带外补答」改为「投卡,窗走完没人答 ⇒ 不停驻、投递失败臂」(与小问同律);后台腿上同一只问由「当场停驻」改为「先投卡、等满窗、再停驻」。② 动作卡阈值 8192 个单元(待决行)/ 16384 字节(活卡)→ 32768 字节:相对上一版零收窄(上一版待决行整份的入参最坏 24576 字节)。③ 标记 `bytes` 的单位由单元改为字节:展示这个数的消费方读数变(变对)。④ 待决队列与 `/stream` 对大问行变大;库里一行只存一份入参(停驻快照),问行的行墙与别的行同一堵。⑤ **`checkpoint.tool_input` 列退役,升级 = 一次迁移删列(M54;与第 19 条的四列同一条)**,不能两版滚动(见下)。⑥ 待决动作读不出的 SQL 行(手改 / 坏体)`input` 为 `null`(此前读列),行照列、读不出照旧计数(行形见第 19 条);这样的行续跑也读不出它。
37
+ **谁需要**:带外审批人(web-admin 待决队列、用 `GET /v1/approvals` 带答 decide 的运维;多租缺省形 —— 问题席按姿态关、每一问当场停驻 —— 里待决行是唯一看得到题的面)与冷恢复的壳。**谁受伤**:有问题席的连着的流上发大问的 run(变了的 ①);按「问行带 `truncated` ⇒ 卡呈不出、留待带外 decide」或按标记 `bytes` 当单元数读的消费方;按旧阈值断言的消费方;多副本 SQL 部署(升级要先停光旧副本);直接按列读 `checkpoint.tool_input` 的外部脚本 / 报表(列没了;入参在 `checkpoint` 列的 `pendingAction.args`,那是未脱敏的执行记录)。**出路**:读 `input` 的消费方对问 / 计划卡的行直接读整份(不再回落流上的 `tool_start.args`);标记 `bytes` 按 UTF-8 字节读;不加旋钮。
38
+ **一次性迁移 M54**(SQL 两方言;起服自跑,停-迁-起;一条迁移删 `checkpoint` 的五只展示拷贝列 —— 本条的 `tool_input` 与第 19 条的 `tool_name` / `risk_descriptor` / `rule_suggestions` / `has_bidi_controls`):五列任一在 ⇒ 先往 `serving_requirement` 记 `(M54, in_force)`,再一句 `ALTER TABLE checkpoint DROP COLUMN …` 删在的那几列(按后置条件收敛:并发起服的另一只先删了 = 目标达成;删到一半的库下一次起服只删剩下的);五列都不在 ⇒ 零动作;读数 `checkpointDisplayColumnsDropped`(本次删掉的列数,0–5)。没有搬运:五列的值都是写入时从停驻快照反范式下来的拷贝,删列不丢信息。删列之后更早版本的副本每一次停驻(它的 INSERT 点名这些列)与每一次运维队列读(它的 SELECT 点名这些列)都会失败 ⇒ **不能两版滚动**:起服时判活窗内有不知道 M54 的副本在跑 run ⇒ 拒启 `config.checkpoint_display_columns_drop_refused`(拒因 `older_replica`;一字节不写;拒句点名持有者、给全停之后最早何时再起,句尾接库此刻还差的列与升级清单)。守卫只看心跳:没有在跑 run 的空闲旧副本它看不见。`DB_BACKEND=local` 零动作(LOCAL 孪生从来没有这些列)。
39
+ **升级前做**:停光全部旧版本副本、等满判活窗(`REAP_RUN_STALE_SEC`),再起本版。**回滚**(本版起过服、M54 已删列并记下要求的库):回到 7.150.x / 7.151.x ⇒ 起服先被列册对账拒 `config.schema_mismatch`(缺这五列,拒句逐列给 `ALTER TABLE … ADD COLUMN`,升级清单另点名这只库要求、它没有的能力 M54)→ 照跑补列之后被起服准入拒 `config.serving_requirement_unmet`(点名 M54 与删那一行要求的语句)→ 删行之后旧版本起得来;回到 ≤ 7.149.x(没有起服准入)⇒ 只有列册对账那一道,补列即起得来(要求行留着、无人读);回滚后本版期间停驻的待决行在旧版队列上 `input` / `toolName` / `riskDescriptor` 为 `null`、`ruleOffers` / `hasBidiControls` 缺席(旧版本读那几列,补回的列是空的),决断与续跑读停驻快照、不受影响;回滚后新停驻的行回到旧读法。`DB_BACKEND=local`:本条零改库;回滚照数据根高水位不变量(段头「回滚」一行)。
40
+
41
+ 2. **运维队列决断口 `POST /v1/approvals/:sessionId/decide` 收 `persistRule`;停驻卡的「不再询问」在续跑腿结清点兑,对着卡铸时的那一份记录(行为修复;wire 加性;持久面:`checkpoint` 表加一列 `card_scope_root`,SQL 部署升级前手工 DDL 一句)**:
42
+ 旧形:运维队列行(`GET /v1/approvals` 的 `pending[i]`、`/v1/approvals/stream` 的 `pending` 帧)带引擎铸的 `ruleOffers`,决这一行的 `/decide` 却把 `persistRule` 当未知键 400(`request.body_shape` + `unknownKeys`);能兑停驻卡上候选的只有活卡回决口的停驻臂,它在协调器的赎回句柄里另兑一次,回放时还会再兑一次。同一张停驻卡的重放帧与队列行投的是两份候选(两只读者,丢弃规则不同)。
43
+ 新形:`/decide` 收 `persistRule`,体形、拒句、回执五键(`rulePersisted` / `ruleRefusal` / `persistedRule` / `persistedRules` / `persistedRuleAnchors`)与活卡回决口同一套。从运维队列决卡的客户端:行上 `ruleOffers` 里的单条候选原样抄进 `persistRule.rule`(帧上的原字或队列行上的展示形都认,恰好一只命中才兑);合取批把它在行上数组里的下标放进 `persistRule.batchOfferIndex`。停驻卡的规则只在续跑腿的结清点兑一次(与「本会话放行」同一处、同一读),活卡回决口的停驻臂交同一只请求;**幂等回放不兑规则**:`/decide` 的回放回执不带规则位,活卡回决口的回放(同一把 id 的重试、并发后到者)带了 `persistRule` 答 `rulePersisted:false` + `rule_lane_unavailable` —— 首次有没有落,看首次回执或 `GET /v1/rules`。scope 只取铸卡快照:有问行的卡读问行快照,没有问行的直停驻卡读停驻行上新加的快照列(停驻那一刻由跑这条 run 的副本写下)。队列行投的候选 = 兑付读的那一份记录(有问行的卡投问行上的候选),选择键就是面上数组的下标。直连门(`directDoorActive`)部署:要送 `persistRule`,签名器得把它签进信封扩展位 `persistRule`(投影 `["batch", 下标]` 或 `["text", 规则文本, edited]`);没签进去的请求验签失败;不送它的签名器零改动。能力位 `approvalDecideRuleSlot`(恒真)。
44
+ 看不见的边(如实):没有 caller cwd 的部署(远程沙箱 / 未登记的 host / 多租 host 车道)两种卡都答 `scope-unresolved`;升级前停驻、升级后才决的直停驻卡没有快照,答 `scope-unresolved`(与真不可兑同形);相对目录的合取批(`cd ./x && …`)两条腿都还兑不动。
45
+ 谁需要:从运维队列决卡、要「不再询问」落进 server 规则店(按属主、跨壳跨机生效)的壳与 SDK 调用方。谁受伤:按「队列行候选只读」编码的消费方(不会红,语义变了 —— 本机写档照常能用);要在直连门上送这一键的签名器(加扩展位编码);依赖「活卡回决口回放时再兑一次规则」的客户端(回放不再兑,首次回执如实答 `rule_store_error` 时,在下一张同命令的卡上再勾一次);上游漂移或伪造的候选不再出现在队列行上。
46
+ **升级前做**(SQL 部署,起服列册对账缺列即拒启并打印同一句):MySQL 协议 `ALTER TABLE checkpoint ADD COLUMN card_scope_root VARCHAR(4096) NULL;` · PostgreSQL `ALTER TABLE checkpoint ADD COLUMN card_scope_root VARCHAR(4096) COLLATE "C";`(local 部署零动作)。**回滚**(本版 → 上一版):`/decide` 带这一键重新答 400;已经落下的规则留在规则店里(`DELETE /v1/rules` 可撤);新列对上一版是多余列(起服列册对账拒启并点名,出路是本版,或运维 `ALTER TABLE checkpoint DROP COLUMN card_scope_root`)。
47
+
48
+ 3. **会话同步的文件历史 blob 按落点可见:别的主体 / 别的会话传过的 blob 不再让你的推送过预检、不再能从你的会话读出;订正 7.149.0 节第 34 条「另裁」一句为已收(安全收紧;行为变更;wire 零新键、零新码;持久面:新表 `file_history_blob_grant` 起服自建;嵌入方类型面 BREAKING 一处;部署零动作)**:
49
+ 旧形(≤ 7.150.x):`POST /v1/sessions/:id/sync/import` 第一阶段的 blob 在场预检与提交时的重放取值口读的是全局内容地址索引 `file_history_blob`(只有 `blob_hash` 一列作键)—— 别的主体传过的 blob 对你也算在场(「有没有人存过这份内容」的预言机);不上传、只在信封里写别人那一枚的 hash,提交后经自己会话的 `GET …/sync/history/blobs/:hash` 读到别人传上去的字节。
50
+ 新形:一枚 blob 对一次推送 / 拉取的**落点**(落点属主格 + 会话 id,与导入租约同一格:id 被占着 ⇒ 住客的属主格;空闲 ⇒ 这位调用方自己)可见 ⇔ 这只 id 此刻有住客且它的历史图引用它,或这个落点自己经 `PUT …/sync/blobs/:hash` 上传过它(字节核过地址、写成之后才记下),且字节仍在。预检、重放取值口、拉取口三口读同一只可见性;对不可见的 hash 预检答 `422 missing_blob`、拉取答 `404 not_found.blob`,与「谁都没传过」逐字节同。字节仍全局去重一份,可见性记在新表 `file_history_blob_grant`(一行 = 一个落点上传过一枚 blob);会话删除(含留存清扫、覆盖写擦除)同删这只会话的行,收割腿在 blob 孤儿回收的宽限窗(1 h)之后清掉没导入的上传,还被记着的上传不当孤儿回收。顺带收两件同类:字节索引的刷新改为单调(迟到的旧写不再把行的时刻写回更早);在场读按批(每句 ≤ 500 枚),一次很大的引用集不再撞驱动的参数个数上限。
51
+ **存量**:不回填 —— 已有的图行就是持久来历,升级后各会话对自己图里的 blob 照旧可见(重推 / identical / 覆盖写都不必补 `PUT`)。升级时在飞、还没导入的上传没有记录,下一次第一阶段答 `422 missing_blob`,照契约补 `PUT` 再推即可。升级之前已经被利用、写进了图的跨主体引用与「两个租户恰有同一份文件」不可区分,维持原样(撤掉会毁合法历史);要核查的运维用只读查询列出被 ≥ 2 个不同属主格的会话图引用的 blob(两方言同文):`SELECT v.blob_hash, COUNT(DISTINCT COALESCE(m.owner, '')) AS owner_cells FROM file_history_version v JOIN session_meta m ON m.session_id = v.scope WHERE v.blob_hash IS NOT NULL GROUP BY v.blob_hash HAVING COUNT(DISTINCT COALESCE(m.owner, '')) > 1` —— 公共文件与被利用的引用都在其中,查询不判是哪一种。
52
+ **订正 7.149.0 节第 34 条**(已发节不改字节):该条末句「在它另裁收口之前,多主体部署上互不信任的主体之间,文件历史内容并不互相保密(知道 hash 即可取)」—— 本版已收(按落点可见,上文)。仍未收的边界(另一类,如实):会话 id 被删、被别的主体同 id 重建之后,提交之后的重放 / 擦除与拉取口门读之后的读不绑住客世代 —— 含删后迟到的重放留在空闲 id 上的图:这只 id 空闲时它不算来历,被新住客占用之后读作新住客的图。
53
+ **谁需要**:有身份边界的多主体部署(`REQUIRE_PRINCIPAL` / service 凭据 + 主体 / 鉴权桥)—— 会话同步的文件历史在互不信任的主体之间保密,预检不再是跨租户的存在性预言机;dev-open 形没有边界,不受益也不受伤。
54
+ **谁受伤**:① 靠「别的会话 / 别的主体已经传过」而跳过 `PUT` 的客户端 —— 现在 `422 missing_blob`,要在**本会话 id、同一调用方身份**下补 `PUT`(按契约在第一阶段之前逐枚 `PUT` 引用的客户端不受影响;拉取再推送、对没拉到字节的 hash 跳过 `PUT` 的路径会更常见 422);② `PUT` 与导入用了不同身份(例:用「凭据 + 主体」上传、用纯 service 凭据导入到空闲 id)⇒ 落点不同 ⇒ 422;③ 上传与导入相隔超过 1 h(此前同一枚若已被别处的图引用,隔多久都过预检);④ 滚动升级窗内旧副本仍按全局答、它收的 `PUT` 不记来历 ⇒ 新副本上多一轮重传(全部副本升完即收敛);⑤ 嵌入方:自带文件历史店、只有 blob 交换组而没有按落点来历面(`blobLanding`)的店,本版起 blob 两口答 `501 capability.snapshot_store_required`、能力位 `sessionSyncFileHistory` 为假(fail-closed:不退回全局读)。
55
+ **补偿 / 出路**:`422 missing_blob` 的动作不变 —— 在本会话 id、同一身份下补 `PUT` 再推;安全轴不给放宽旋钮;嵌入方店照 SQL 两孪生加 `blobLanding`(`putAt` / `visibleAt`)。**升级前做**:无(段头升级清单:新表起服自建)。**回滚**(本版 → 7.150.x):新表被旧构建忽略(多出的表不算不符),预检与取值口回到全局读;再升无动作。
56
+
57
+ 4. **续跑口按写据认地板的出口:`AUTONOMY=plan` 的部署上,停驻 run 体顶层的 `default` / `acceptEdits` 只有 `plan_review` 批准写下的那一格续跑原样;提交 / 在飞切档写下的同字词,与 7.152.0 之前写下的停驻行(含已批过计划的),下一次续跑照钳成 `plan`(安全轴,失效关闭;行为收紧;wire 零新键、零新码;续跑上下文多一个服务端键,零 DDL;部署零动作,升级前自查一行)**:
58
+ 旧形(7.151.0):续跑口按词值认出口 —— 体顶层原字是 `default` / `acceptEdits` ⇒ 原样,不论是批准写回的、提交时带的还是在飞切档写下的。
59
+ 新形:写体顶层模式词那一格的每个写者(提交、在飞切档、续跑口的钳、`plan_review` 批准 / 编辑)在同一笔写里记下谁写的、写下的是哪个词;续跑口地板在场时只对「`plan_review` 写下、且仍是它写下的那个词」原样,其余照现地板钳。没有这条记录的行(7.152.0 之前写下的)一律照钳。更早版本的副本把词改成另一个词而不改这条记录 ⇒ 对不上 ⇒ 照钳;⚠️ 已知限制:旧副本把词切走又切回记录上的同一个词(批准写下 `acceptEdits` 之后,在旧副本上 `acceptEdits → default → acceptEdits` 切档),记录仍对得上 ⇒ 原样(与 7.151.0 同读,不比上一版宽)。记录读不出 ⇒ 续跑答既有的坏体答复(500 `corrupt`)—— 不论有没有地板(读口先判形)。无地板时记录不参与判定,其余行为逐字不变。
60
+ 谁需要:设了(或事后改成)`AUTONOMY=plan` 的运营者。**谁受伤**:改严之前以 `default` / `acceptEdits` 提交或切档、此刻停驻的 run(续跑被钳进 plan);**`AUTONOMY=plan` 部署上升级时已经批过计划、又停在别的门上的 run** —— 下一次续跑回到 plan,模型要再交一次计划、人再批一次才出 plan(不丢数据、不放宽);滚动升级窗内在旧副本上批准的计划同此。
61
+ 补偿:不新增旋钮。MIGRATION 7.151.0 节第 13 条的「顶层同字词读不出是谁写的,照原样续跑;要它们也收紧 ⇒ `AUTONOMY=read-only`」本版退役(同字词已照钳)。
62
+ **升级前自查**(一行):设了 `AUTONOMY=plan` 的部署,升级前让已批过计划、此刻停着的 run 续完(决完 / 跑完),或接受它们下一次续跑回到 plan、再批一次计划;滚动升级窗内在旧副本上批准的计划同此。
63
+ **回滚**(本版 → 7.151.0):7.151.0 不读这条记录、照词值读(批过的 `acceptEdits` 原样、钳过的 `plan` 原样),零动作;回滚之后再升回本版,旧副本把词改成别的词的行对不上记录 ⇒ 照钳(切走又切回同一个词的除外,见上)。
64
+
65
+ 5. **🔴 审批只有一个帧族 `approval_request`;旧族 `tool_approval` / `tool_approval_complete` 与总开关 `STREAM_APPROVAL_ENABLED` 退役(BREAKING;行为变更;部署动作:删掉这一键)**:
66
+ 旧形:呈卡协议上场的腿发呈卡族,不上场的腿(无 store backend / 无 park 设施 / `STREAM_APPROVAL_ENABLED=false`)发旧族活卡;没有确认落盘身份的问在旧族腿上照样呈活卡;能力位 `approvalFrameFamilies` 取 `["approval_request"]` / `["tool_approval"]` / `[]`。
67
+ 新形:只有呈卡族。一条腿呈卡 ⇔ 这台 worker 呈卡(审批席 ∧ store backend ∧ park 设施)∧ 没关窗 ∧ 这条 run 声明了 `approval_request`;worker 级三项(审批席 ∧ store backend ∧ park 设施)任一不成立 ⇒ 起服不装审批席:没有卡、没有活登记;受门调用的终局归引擎(开关关而有 park 设施 ⇒ 照常 durable park;开关开而无 store backend / 无 park 设施 ⇒ 引擎的无审批席臂当场拒);`/v1/capabilities` 的 `toolApproval` / `streamApproval` / `approvalsStreamLive` 三位为 `false`,有问行的卡的决断口与按问读口答 `501 feature.approval_ask_disabled`;worker 呈卡而这条腿关窗或没声明 ⇒ 这条腿没有活审批通道,每只问当场交 `UNATTENDED_APPROVAL_POLICY`。每张卡先落一行 ask 账再呈;没有确认落盘身份的问不呈卡、不登记(`arm: no_row_no_presenter`)。能力位 `approvalFrameFamilies` 取 `["approval_request"]` / `[]`。
68
+ 看得见的边(如实):① 审批席开着而没有 park 设施的部署(多租户缺省 `DURABLE_APPROVAL` 关;env-only worker 没有 store backend)此前呈旧族活卡、人能批,本版起不装审批席:没有卡,受门调用由引擎的无审批席臂拒(`UNATTENDED_APPROVAL_POLICY` 在这里不起作用,运维日志不再有 `approver_unavailable`,起服 warn `approval_cards_unavailable`),能力位 `toolApproval` 从 `true` 变 `false`,审批卡的两扇口从 `404` 变 `501` —— 出路 = 在 store backend 上设 `DURABLE_APPROVAL=true`;把 `toolApproval` 读作「装了审批代码」的端改读 `approvalFrameFamilies`(`["approval_request"]` ⇔ 这台 worker 呈卡);② 请求声明里带 `"tool_approval"` ⇒ `400 request.field_invalid`(`bad_entry`);升级前已停驻、存体声明里带它的 run 续跑时整只声明按不合法读 ⇒ 那条腿不呈卡(park 政策下照常停驻,经 `POST /v1/approvals/:sessionId/decide` 可决)—— 出路 = 升级前决掉这些停驻,或续跑后由新提交重来;③ 账本里更早版本写下的旧族帧行回放时原样交出,本版不再写;④ 关过总开关、带 store backend 与 park 设施的部署删掉那一键之后走有账形:没有任何活流的 park 政策问先在行上挂到 `UNREACHED_ASK_TTL_MS` 窗满再停(此前当场停驻)。
69
+ **升级前做**:① 部署 env 里删掉 `STREAM_APPROVAL_ENABLED`(任何值都拒启 —— 本版提货的 settings-schema 26.0.0 带这一行墓碑,见第 32 条;它的「关」已经没有可回退的族);② 审批席开着的部署核 `DURABLE_APPROVAL` 与 store backend(没有 ⇒ 本版起没有审批卡);③ 壳 / SDK 只渲呈卡族、请求声明只写 `approval_request`(或照能力位声明)。**回滚**(本版 → 7.151.x):旧族与总开关回来;本版没有持久面改动。
70
+
71
+ 6. **🔴 有问行的审批卡只有一对同根的口:决 `POST /v1/tasks/:taskId/asks/:askId/decision`、读 `GET /v1/tasks/:taskId/asks/:askId`;活卡应答口 `POST /v1/tool-approvals/:id/respond` 与按问读口 `GET /v1/tool-approvals/:id` 已删;决断口收 `remember:"session"` / `persistRule` / `readRootGrant`;运维队列行带 `askId`(BREAKING;部署零动作,消费方动作)**:
72
+ 旧形(7.151.x 及以前):活卡两扇人决口 —— 应答口按 `approvalId` 收 `allow` / `allow_session` / `deny`(三个 live 手势只有它说得出),决断口按 `(taskId, askId)` 收 `approve` / `deny`(三键答 `400 request.body_shape`);读口 `GET /v1/tool-approvals/:id`,已决态说 `allow` / `deny`;队列行上没有问的身份。
73
+ 新形:一张卡一只身份、同根两扇口;旧两路径答路由表的通用 `404 not_found.route`(决议不落、卡仍待决)。对照(逐行):
74
+ | 旧 | 新 |
75
+ |---|---|
76
+ | `POST /v1/tool-approvals/<approvalId>/respond` | `POST /v1/tasks/<taskId>/asks/<askId>/decision`(`askId` / `taskId` 取呈卡帧上的那两格;`livePending[]` 行上在内嵌帧 `frame.askId` / `frame.taskId`,行顶层没有这两键,`askId` 与行顶层 `approvalId` 同值) |
77
+ | `{"decision":"allow"}` | `{"decision":"approve"}` |
78
+ | `{"decision":"allow_session"}` | `{"decision":"approve","remember":"session"}` |
79
+ | `{"decision":"deny"}` / `note` / `updatedInput` / `persistRule` / `readRootGrant` | 原样 |
80
+ | 已决卡再应答 `200 idempotent:true` | 带同一 `idempotencyKey` ⇒ `200` 回放首决;不带 / 换键 ⇒ `409 conflict.ask_decided` 回显首决 |
81
+ | 别的副本上的属主作答 `404 tool_approval.not_pending` | `200`(决断口写行,不按副本分) |
82
+ | 已撤的卡 `404 tool_approval.not_pending` + `cause` | `410 gone.ask` + `cause` |
83
+ | 店读不出行 `404` | `500 internal.error`(可重试) |
84
+ | 转 park 中的行 `202 parking_in_progress` | `425 parking.ask_retry` + `Retry-After` 头(这一次决议没有被受理,等头里的秒数后重发) |
85
+ | `GET /v1/tool-approvals/<approvalId>` | `GET /v1/tasks/<taskId>/asks/<askId>`(体形不变;已决态 `decision` 说 `approve` / `deny`) |
86
+ | 呈卡协议不上场 `501 feature.tool_approval_disabled` | 两扇口 `501 feature.approval_ask_disabled` |
87
+ 回执:带 `remember` ⇒ `rememberApplied`(落没落);带 `persistRule` 的首决 ⇒ 规则位五键;回放不带两者。队列上有问行的停驻卡带 `askId`,决它的路径 = `/v1/tasks/<taskId>/asks/<askId>/decision`。
88
+ 看不见的边(如实):决定打在不持有那条引擎腿的副本上时,`remember` 答 `rememberApplied:false`(进程内放行只记得在持有它的副本上),`readRootGrant` 交不到(回执的 `consumerState` 已说本副本没有消费者);规则位任一副本都兑得了。同一根上读口与决断口在店读不出行时答得不同:读口 `404 not_found.ask`(看不见),决断口 `500 internal.error`。
89
+ 运维与两个 live 词:决断口的门对运维开放 ⇒ 运维可以在别人的**活卡**上带 `remember: "session"` / `persistRule`(7.151.x 上活卡的这两个词只有应答口说得出、应答口只认卡的属主;运维能带 `remember` 的只有卡停驻之后的 `POST /v1/approvals/:sessionId/decide`);本副本的会话放行按卡的属主落键,常驻规则的记录读卡的属主,部署名单上的工具在会话放行店里记作决的那位主体为 `grantedBy`。 读根授权 `readRootGrant`(能力位 `approvalReadRootGrant` 可用、`decision: "approve"` 且没带 `remember` 时收)同此:此前只有应答口收、只认卡的属主,现在运维也能在别人的活卡上带。
90
+ **升级前做**:壳 / SDK / 转发路由按上表换路径与词(锁步 SDK 同版)。**回滚**(本版 → 7.151.x):两条旧路径回来,三键回到 400,队列行少 `askId`;本版没有持久面改动,已落的规则可 `DELETE /v1/rules` 撤。
91
+
92
+ 7. **排空专用的服务主体:新键 `OPERATOR_DRAIN_PRINCIPALS`(名册的排空档)—— 名单里的验过主体只能调 `POST /v1/admin/drain`,在其余每条路由上整次请求 403 `auth.principal_confined`(安全轴,最小权力;行为加性:只在写了新键的部署上变;wire 加性:一只配置键、一个 403 码;设备 hello 的 `auth_failed` 多一个原因(不加词、不升协议修订号);两条新拒启;零 DDL)**:
93
+ 旧形:能调排空口的只有运维名单 `OPERATOR_PRINCIPALS` 里的主体(或单用户声明 `OPERATOR_SINGLE_USER=true` 下的每个调用方);给一位服务主体排空的权力,就要把它写进运维名单 —— 它随之持有每一只运维面(审批队列、记忆治理、配置刷新、收编……),并且仍是一位普通身份(可以提交任务、建会话)。
94
+ 新形:运维名册分两档,读法只有一只:`OPERATOR_PRINCIPALS` = 全运维档(持每一只运维权力,排空在内;语义不变);`OPERATOR_DRAIN_PRINCIPALS` = 排空档(只持排空一权,不是人)。排空档里的主体:调 `POST /v1/admin/drain` 与运维同一道门(身份四元比对、幂等回执不变);**在其余每条路由上**整次请求 403 `auth.principal_confined` —— 先于门前读路、全局凭据门、写门与就绪 503、路由与 405 / 404,三种身份来源(登记中心登录令牌 / 直连门签名 principal 令牌 / 受信前端的主体头)同判;直连门决定口上**决定证明推出的决定者**在排空档里同答本码(零状态变化);`GET /health` 不拒,这只身份不算读路凭据(配了 service 凭据的部署上只拿匿名档的四键)。排空口的 501 只在两档名单都空且没有单用户声明时答,拒句多一条出路;名册外调用方的 403 `auth.operator_only` 码不变,句子改为点名两档名单。`/health.operator` 仍只答全运维档在不在(只有排空档的部署答 `absent`)。设备车道同判:排空档主体名下登记过的设备在 `GET /v1/device/ws` 的 hello 被拒 `helloReject: auth_failed`(见下「设备车道」)。排空档不算运维声明:单用户部署只写它 ⇒ 照旧按「必须声明谁是运维」拒启。
95
+ **新拒启两条**:名册不良构 ⇒ 拒启(不变量 `operator-roster-well-formed`:同一个名字同在 `OPERATOR_PRINCIPALS` 与 `OPERATOR_DRAIN_PRINCIPALS`,或 `OPERATOR_DRAIN_PRINCIPALS` 列了不是主体名的条目 —— 空白 / 不可见字符 / 保留标识 / 超长;拒句点名 env 与条数、不回显名字;嵌入方装配路由时同判,另拒「名单不是名字列表」);`OPERATOR_SINGLE_USER=true` 与 `OPERATOR_DRAIN_PRINCIPALS` 同写 ⇒ 拒启(不变量 `operator-single-user` 的又一形)。只有主动写新键的部署撞得到。
96
+ 谁需要:中心对受管引擎直调排空(签一枚短时登录令牌给它的服务主体,只为调排空口);多租运维把「排空」交给自动化而不交出其余运维面。**谁受伤**:① 没写新键的部署 —— 零;② 把一个名字从 `OPERATOR_PRINCIPALS` 挪进新键的部署 —— 这个名字失去其余全部运维面与一切普通身份面(本意);它挪档前以人身份建下的行留在库里,经 HTTP 再也够不着;它名下已登记的设备重启后连不上(`auth_failed`,拒的原因进运维日志 `device_hello_owner_confined`:设备 id + 档名,不回显主体名)—— 这是期望;③ 同时写了单用户声明与新键的部署 ⇒ 拒启。
97
+ 补偿:新键缺席 = 今天;想让它回到全运维,把名字挪回 `OPERATOR_PRINCIPALS`;只想让它名下的设备继续连,把名字从排空名册移除或换一个不在排空名册里的主体(重启后设备自己连上,执行器不必重新入籍)。
98
+ **看不见的边(如实)**:① 圈禁是**引擎侧**的事实 —— 一台信同一签发方、却**没把这个名字写进** `OPERATOR_DRAIN_PRINCIPALS` 的引擎(手接的个人引擎、漏渲染的受管引擎、把名字从名单删掉之后)把同一枚令牌读成一位普通身份(可提交任务);「截获者最多只能调排空口」只在声明了它的引擎上成立。缓解:签发方不把这个主体名发给任何真人;按目标引擎签 `aud` 并给引擎配 `AUTH_BRIDGE_AUDIENCE`(配了它的引擎之间截获的令牌只在签给的那一台上有用);撤掉这位服务主体时**先停签令牌、等已签的过期,再从名单删名字**。② 中心若用同一枚令牌读 `/health`,圈禁之后只读到匿名档四键(`status` / `auth` / `operator` / `center`)—— 要 `instanceId` 等全键,改读心跳或带 service 凭据。③ 排空口本身的语义不变:不查服务租约围栏,期限已过也照收(进入即到期 ⇒ 立即走向硬停)。④ 没有身份边界的部署(dev-open)上主体头谁都能自报 —— 自报成排空档的名字只会把自己圈禁,与 `OPERATOR_PRINCIPALS` 今天同形。 ⑤ 设备车道(不再是看不见的边,按现态判):设备凭设备密钥对握手、不经 HTTP,按设备行的**属主**绑定 —— 一个名字被挪进排空档之后,它名下已登记的设备在每一次 hello 被拒:`helloReject: auth_failed`(既有拒因词的又一个原因;拒因闭集与协议修订号不变;位置在验签与吊销之后、单活连判与夺租之前 —— 被拒的 hello 不顶替旧连接、不动租约;没有私钥的人问不出这一条),判的是同一只圈禁判官读设备行属主,名册读现态、不把登记时的档位记在行里。名册只来自 env、起服冻结,所以生效点是**重启之后**:滚动换版的窗里,尚未换版的副本上已连着的设备照连到那一副本换版为止。执行器对 `auth_failed` 指数退避、不删钥匙;名字回到 `OPERATOR_PRINCIPALS` 或被移出排空档的那次重启后设备自己连上。排空档主体登记新设备(签发令牌与注册两口)本就在 HTTP 圈禁射程内,403 `auth.principal_confined`。挪档前注销这些设备仍然建议(吊销是终态,库里不留永远被拒的行),不再是唯一防线;它挪档前建下的会话与停驻行仍由内部续跑腿按普通属主续(无运维位),落在这些设备上的执行因设备不在线被拒(设备车道既有词 `device.offline`)。
99
+ **回滚**(本版 → 7.151.x):旧版不认这只键(未知键不拒、不读)⇒ 名单里的主体回到一位普通身份(不再能排空,也不再被圈禁)。回滚前先把要保留排空权的名字挪回 `OPERATOR_PRINCIPALS`,或停用那位服务主体。
100
+
101
+ 8. **同机附着方改走本机控制 socket 读身份:local 后端的数据根下多一只 `control/engine.sock`,经它到达的 `/health` 读者拿全键;`listening` 启动行多两键 `controlSocket` / `controlSocketUnavailable`(行为加性;wire 加性;零新码;部署零动作,消费方动作)**:
102
+ 本版起,`DB_BACKEND=local`(本进程持数据根锁)的引擎在 TCP 监听之后多开一只本机监听口 `<数据根的物理路径>/control/engine.sock`(unix socket;`control/` 0700、属主 = 引擎的 OS 用户)。
103
+ 配了服务凭据的引擎上,经它到达、没出示凭据的 `/health` 读者拿全键(`pid` / `startedAt` / `instanceId` / `version` / `dataRoot` …;`port` 缺席);TCP 上的读法逐字不变(匿名读者只拿 `status` / `auth` / `operator` / `center`,对端是回环也一样)。
104
+ 这只口**不是凭据**:配了服务凭据的引擎上,经它不带凭据的写、审批、只读三族照旧 `401`(与 TCP 同码)。开不了(Windows / 非 local 后端 / 路径超过平台上限 / `control/` 不私有 / 路径上有别的文件 / 绑失败)不拒启,`listening` 行带 `controlSocketUnavailable` 原因词;开了带 `controlSocket` 绝对路径,两键恰一只。
105
+ **消费方动作**(壳 / 桌面宿主):连之前核 `<数据根>/control` 是目录不是符号链接、属主是你、没有组 / 他人位,`engine.sock` 是 socket、属主是你;经 socket 读 `GET /health`,照样与你记下的锚点比 `pid` + `startedAt` + `instanceId`;带凭据的调用发在同一只 socket 上,不拿 socket 的判决去放行 TCP 上的凭据出示。
106
+ `listening` 行带 `controlSocketUnavailable` 时走原来的 TCP 归属证明(独占 `127.0.0.1` 的 `boundAddress` + `port` + `instanceId`),证不出就不出示凭据。读法全文在 USAGE「同机附着方怎么读身份」。
107
+ **部署侧**:不要代理或转发这只 socket(经它进来的读者按引擎的 OS 用户看待);数据根及其上级目录不可被别的 OS 用户写、不被 ACL 放宽;反代拨引擎精确绑定的那只地址。
108
+ **谁需要**:同机附着的壳与桌面宿主(日志被清之后照样读到身份)、带凭据的同机读者(凭据不发在共享绑址的 TCP 口上)。**谁受伤**:没有;拿不到 socket 的主机照旧走 `listening` 行;按 `listening` 行整行逐字节匹配的脚本多一键。
109
+ **补偿**:缺席响亮(原因词);零旋钮。回滚:回到 7.151.x 即无这只口(数据根下留着的 `control/` 目录无害,可删)。
110
+
111
+ 9. **k8s 车道声明而集群 API 凭据缺 ⇒ 起服拒(一句拒 + 两条出路);起服之后凭据失效 ⇒ run `failed` 带 `env.sandbox_refused`(安全轴 fail-closed;行为变更;wire 零新键、零新码;部署零动作,除非凭据本来就缺)**:
112
+ 旧形:`REMOTE_EXEC=k8s` 而没有控制面凭据 ⇒ 服务照常起,起服日志只有出网自检的一行 `sandbox_egress_policy_gap{kind: unreadable}`,之后每条 Bash 答 `shell_unavailable`、run `completed`;令牌在起服之后吊销 / 过期是同一形(pod 创建被 401 拒)。
113
+ 新形:① 判官 `resolveK8sApiAccess`(显式 `K8S_API_URL` / `K8S_TOKEN` / `K8S_CA_CERT_B64`〔或实验室 `K8S_INSECURE_TLS=true`〕优先、in-cluster 服务账号补缺,逐件判)说缺 ⇒ **起服拒**,标题 `[config.k8s_api_credentials_missing]`,句点名「REMOTE_EXEC=k8s 需要集群 API 凭据」、缺的是地址 / 令牌 / CA 哪一件;② 每条 run 铸环境之前再读一次(判官,加一次 SelfSubjectAccessReview):缺或被集群答 HTTP 401 ⇒ run `failed`、码 `env.sandbox_refused`、模型零调用、零 pod(同步腿 409);只有正面证据才拒 —— 探问没答、403、5xx 都不拦,车道自己的连接照常答(每次计入 fail-open 登记 `server.sandbox-credentials.probe-unanswered`)。空 / 只含空白的一件按缺。
114
+ **谁需要**:`REMOTE_EXEC=k8s` 的部署。**谁受伤**:本来就没有可用凭据、靠「服务起着只是沙箱命令都失败」运转的误配部署 —— 升级后起不来(期望);按 run `completed` + 工具结果 `shell_unavailable` 认「集群不通」的告警,在令牌失效那一形上改读 run 终局码(同步腿 `errorCode` / 终局 `terminal.code` = `env.sandbox_refused`,409)。**补偿 / 出路**:拒句两条 —— ① 给凭据:`K8S_API_URL` + `K8S_TOKEN` + `K8S_CA_CERT_B64`(实验室 `K8S_INSECURE_TLS=true`),或把服务放进挂着服务账号的 pod;② 改车道:`REMOTE_EXEC=none` 或别的车道。不加旋钮。
115
+ **升级前做**:`REMOTE_EXEC=k8s` 的部署确认上面三件(或服务账号)在进程里读得到;起服拒句会逐字点名缺的那一件。**回滚**(本版 → 7.151.x):无数据面改动,回滚即回到「照常起服」的旧形。
116
+
117
+ 10. **host 车道临时工作区的删除改在删前那一拍判「此刻谁的工作目录在里面」,判不出就不删;路径「在不在盘」判不出时不再当成「还不存在」(行为修复;wire 零变化;部署零动作)**:
118
+ 射程:`REMOTE_EXEC=host` 的部署,不带 `cwd` 的任务铸的临时工作区(`<HOST_WORKSPACE_BASE>/sema-host-<id>-<rand>`);以及起服时记忆根 / 配置根 / 存储取证三处路径判、host 车道铸委派子代 / 带 `cwd` 任务的执行环境那一判。
119
+ 旧形:委派子代的工作目录落没落在父的临时根里,只在铸子代环境那一拍判一次;那一拍途经的符号链接悬空或之后才建,子代就被当成调用方自有目录,父收场时删掉根(连同子代写下的文件)。路径上没有搜索权限 / 成环的段被当成「还不存在」。判完到删完之间才进来的执行环境写下的文件随根一并被删。
120
+ 新形:最后一只持有者放手时按文件系统当下的答案再判一次;工作目录在根里、还没离场的执行环境接过删除,最后离场的删;判不出 ⇒ 不删(stderr 一行 `kept <根> instead of removing it`),留给进程退出;删时先在同目录改名成一只隐藏墓名(`.sema-host-…removing-…`)再删,判完之后才进来的执行环境写在原路径上新建的目录里。判不出的段不再当成不存在:起服那三处判改报各自的「查不了」行(`memory_engine_dir_check_unavailable` / `sema_config_root_workspace_check_unavailable` / 取证 `path_unresolvable`),host 车道铸带工作目录的腿时,与同一服务里被持有的临时根按表序比(比到包含它的那一只为止;没有被持有的临时根就不比)的途中判不出 ⇒ 这条腿不铸、run 失败。
121
+ **谁需要**:host 车道上委派子代、子代工作目录经符号链接落进父的临时根的部署。**谁受伤**:临时根可能比旧版晚删(等最后一只在里面的执行环境离场;停驻中被取消 / 过期而没被销毁、或判不出时留到进程退出);调用方自有目录经链接落进别人临时根的执行环境接过那只根的删除,它在根里的残余后台输出镜像从此被拒;按 `ls -a <HOST_WORKSPACE_BASE>` 数目录的脚本会短暂看见隐藏墓名;按「记忆根在数据根之外」那一行做告警的运维,在路径有权限受限 / 成环段时改收到「查不了」那一行;带工作目录的新任务在比的途中撞上判不出的一段(它自己的工作目录,或排在前面的某只被持有的临时根 —— 上级目录被收走搜索权限、或被换成成环链接)时铸不出执行环境、run 失败,直到那一段恢复可读或那只根的持有者收场。
122
+ **升级前做**:无。**回滚**:回到上一版即回到「只在铸时判、判不出当不存在」;无存储形变化。
123
+
124
+ 11. **对象存储:引擎对 S3 / MinIO 做的全部请求有了一张随包机读表 `@sema-agent/server/object-store-buckets`;k8s 工作区快照桶的起服探针改列适配器写入的那只前缀(加性;探针前缀一处订正;wire 零变更;部署零动作)**:
125
+ 表(`OBJECT_STORE_ACTIONS`)一行一个请求:桶旋钮 × key 形 × S3 动作 × 执行方(server 进程 / 只持预签名 URL 的执行沙箱 / 拿链接的用户端)× URL 有效期;`OBJECT_STORE_ACTION_PERMISSIONS` 给每个动作要的 S3 权限,`objectKeyGlob` 把 key 形按部署的前缀值写成策略资源。按桶的最小权限清单就从这张表生成(`OBJECT_STORE_ACTION_PERMISSIONS` × `objectKeyGlob`;部署前置清单「对象存储桶」一节是同一张表的渲染,内部记录)—— 想不再给引擎 MinIO root 钥、改发每引擎服务账号的部署,策略照这张表给;`MINIO_BUCKET`(leader diff-out)的两行在表里,缺省与工作区快照桶同名。
126
+ 起服探桶:`WORKSPACE_SNAPSHOT_PREFIX` 未设时工作区快照桶的探针由列 `blobs/` 改列 `kata/`,设了时由列 `<前缀>` 改列 `<前缀>/` —— 与适配器签 key 同一读。全权凭据下答复不变;按前缀授 `s3:ListBucket` 的凭据下,修前探针 403、读成「证明不了」、走兜底口照常起服,修后与适配器同答。会话快照桶的探针仍列 `SESSION_SNAPSHOT_PREFIX`(缺省 `blobs/`)。
127
+ URL 有效期:`WORKSPACE_SNAPSHOT_TTL_SEC=0` 读作未设 —— k8s 快照的对象 URL 由「签成即刻过期」(那条车道本来存不了、取不回快照)改为缺省 600 s,列举 URL 照旧 600 s;其余配置下每个请求的有效期逐值不变。
128
+ 谁需要:按最小权限发对象存储凭据的多租户 / 中心部署。谁受伤:无(请求的 URL、桶、key、有效期在任何可用配置下逐字节不变)。补偿:不需要。**升级前做**:无。**回滚**:回到上一版即回到旧的探针前缀;表与子路径随之不在。
129
+ **圈得住多少**(如实):按引擎发的策略只隔开 key 以每引擎前缀开头的行(`objectStoreRowEngineSlot(row)` 答那只槽);SendUserFile 三轨与 leader 补丁共 15 行按引擎圈不住 —— 共桶的引擎共用这些对象,要隔开只能各用各的桶。共用会话快照桶的引擎必须各设各的 `SESSION_SNAPSHOT_PREFIX`(第 25 条:同桶同前缀的第二只库起服即拒)。
130
+
131
+ 12. **部署脚本与模板不再替部署选「几个人用」:`sema-up` 的 `--users` 必填(`kube-up.sh` / `cluster-up.sh` 单独跑时是环境变量 `USERS`),缺 ⇒ 拒并给 `--users=single` / `--users=multi` 两条出路;租户词翻译成闭集;机器清单模板不再带活的 `users:` 行(部署面 BREAKING;安全轴收紧;零服务端代码、零 wire;存量部署重跑零动作,起新部署的脚本升级前补一个词)**:
132
+ 旧形(≤ 7.151.x):`sema-up --users` 缺省 `single`,`kube-up.sh` / `cluster-up.sh` 的 `USERS` 缺省 `single`,`machines.example.yaml` 里 `users: single` 是写好的活行,租户词翻译把任何认不出的词(含空串)都当单人 —— 没表过态的部署被写上 `OPERATOR_SINGLE_USER=true`(每个调用方都是运维)与 `REQUIRE_PRINCIPAL=false`。服务端拒句里「不止一个人调用 ⇒ 设 `REQUIRE_PRINCIPAL=true`」那一道人判,对用脚本的人永远不出现(MIGRATION 7.147.0 节第 3 条「谁受伤」③ 的形;随包 helm 图表那一层已在 7.151.0 收):用模板起团队集群又忘设人数的部署,起来之后人人是运维。
133
+ 新形:「几个人用」是部署自己的声明,脚本不替它选(缺省 `multi` 同样不取:那是替单人部署开身份隔离)。取值来源只有:显式旗(`--users` / `-f` 配置里的 `USERS` / `machines.yaml` 写了的 `users:` / 单独跑时的环境变量 `USERS`)· 这台部署上一次落在 `.env` 的事实 `REQUIRE_PRINCIPAL`(`false` ⇒ single、`true` ⇒ multi;不带旗重跑沿用)· 向导里人答的那一句;都没有 ⇒ 非零退出,拒句点名「几个人用是这台部署自己的声明」,下一步两条出路:`--users=single`(只给一个人用:每个调用方都是它的运维,身份隔离关)· `--users=multi`(被不止一个人调用:身份隔离开,运维由具名名单 `OPERATOR_PRINCIPALS` 另给)。判在写 `.env`、起容器、动集群、发起 `ssh` 之前(不写 `.env`、零 `ssh` / `kubectl` / `helm`;`sema-up` 的空状态目录 `~/.sema-up/<project>` 先于这一判建出,里面没有 `.env`);拒句只在 `lib/flagshape.sh` 一处生成,三只脚本同调。向导里这一问没有缺省(`--interactive=json` 的 `users` 问句 `default` 由 `"single"` 变空串;有上一次的事实时 `default` 是事实词),空答即拒并列两词。显式旗与事实不一致仍拒并点名两处(原有规则不变)。`data-center` 档不装 agent,没有声明可言:不问、不拒。租户词 → `REQUIRE_PRINCIPAL` / `OPERATOR_SINGLE_USER` 的两只翻译只认 `single` / `multi`,别的词(含空串)⇒ 非零、零输出。`machines.example.yaml` 的 `users:` 成了注释行(复制模板不等于表了态)。同一个词渲出的 `.env` / values / 清单逐字同上一版。
134
+ 谁需要:用模板 / 脚本起团队集群又忘设人数的部署。谁受伤:靠缺省 `single` 起单人部署的自动化 —— CI 里的 `sema-up -f site.env --yes`、`sema cloud deploy --yes`(壳在没有 `--users` 时透传不带它)、手写的 `kube-up.sh` / `cluster-up.sh` 调用、从旧 `machines.example.yaml` 复制来的清单:升级后**首装**被拒,补一个词即回到原状;已装部署零影响(`.env` 里的事实沿用)。补偿:拒句即出路(两条,各带一句释义);重跑沿用 `.env` 事实;本条升级前自查一行。
135
+ **升级前做**(只对起新部署的脚本):在命令行 / `-f` 配置 / 机器清单 / CI 环境里写下 `--users=single`(或 `USERS=single` / `users: single`)或 `--users=multi`;已装部署不必动 —— 重跑 `sema-up`(不带旗)沿用 `.env` 里的 `REQUIRE_PRINCIPAL`。换一台部署的人数仍走原有规则:先确认身份隔离的影响,再清 `.env` 里那一行。
136
+ **回滚**(本版 → 7.151.x):脚本回到缺省 `single`(旧版照旧静默按单人装);`.env` 键值同形、无数据面改动,回滚后不必做任何迁移。
137
+
138
+ 13. **勘误:`GET /v1/approvals` 的停驻行自 7.143.0 起并不带 `sourceSessionId`;停驻行的出处键随引擎持久归属位的版本补(文字订正;零行为变更;wire 零变化;零 DDL;部署零动作)**:
139
+ — **勘误**:7.143.0 节第 18 条(「审批卡的委派子代出处键 `sourceTaskId` 改名 `sourceSessionId`」)的「此前 / 本版」把 `GET /v1/approvals` 的**停驻行**列进了带这一键的载体 —— 那一半从没成立过:运维队列的停驻行在 7.142.x、7.143.0 到 7.151.0 一直不带它,
140
+ 改名只对活的 `tool_approval` / `approval_request` 帧、卡片 JSON 与 `livePending` 行内嵌的帧成立(`livePending` 行顶层带 `fromSubagent` 与同值的 `originTaskId`;该顶层键本版也改名 `sourceSessionId`,见第 21 条)。已发节字节不动,勘误写在这里。
141
+ — **现状**:同步 / workflow 委派子代的卡停驻之后,`pending[]` 行与 `/v1/approvals/stream` 的 `pending` 帧上没有 `fromSubagent` / `sourceSessionId` / `agentId`(同一张卡的活卡帧有);能读出归属的只有 placed 后台委派子代(行上的 `agent`、`taskId` = 句柄)。
142
+ **这三键是否上行随引擎版本**:引擎只在停驻行上持久「这是委派腿」与发起会话,不持久活卡帧上 `fromSubagent` 的判据与脚本声明的腿 task id,三键各有一形与活卡帧不等,本服务不替引擎造判别位;引擎把归属位与腿 task id 记进检查点之后,三键同拍上行(加性)。
143
+ **谁需要**:按第 18 条文字在停驻行上找 `sourceSessionId` 的消费方(收件箱 / 运维界面)。**谁受伤**:无(行为零变化)。**补偿**:不需要;子代归属读 `agent` / `taskId`,读不出就是读不出(缺席 = 这台 server 没有说,不是「不是子代」)。**升级前做**:无。**回滚**:无关(无存储形变化)。
144
+
145
+ 14. **补记 7.148.0 节漏记的一条行为变化:非流式 `POST /v1/tasks` 跑完之后,这条提示词也落回退锚 —— `GET /v1/sessions/:id` 里那条用户消息行带 `taskId`(= 那次 200 体里的 `taskId`),拿它 `resumeAt` 回得去(文字补记;行为变化本身自 7.148.0 起在;wire 零新键、零新码;部署零动作;已发的 7.148.0 节不改字节,本条即补记)**:
146
+ 漏记的是什么:7.148.0 节没有这一条。读者因此不知道,从 ≤ 7.147.x 升上来的部署上,同一条提示词走哪条腿提交,读面上有没有 `messages[].taskId`、`resumeAt` 回不回得去,已经变了。
147
+ 旧形(≤ 7.147.x):用户消息的回退锚只在流式腿 `POST /v1/tasks/stream`、后台腿 `POST /v1/runs` 与续跑腿写;非流式 `POST /v1/tasks` 从不写 —— 同步提交的 200 体里的 `taskId` 拿去 `resumeAt` 得 `404 resume_at.unknown_event`,会话读面里那条消息行不带 `taskId`。
148
+ 新形(7.148.0 起):同步腿不带 `verify` / `cascade` 的提交,把引擎事件喂给与后台腿、续跑腿同一只锚写入器,键 = 那次提交的 durable run id(= 200 体里的 `taskId`),属主 = 提交的已验主体;200 应答发出之前会等这一笔锚写,但**只等有界的一段**(单行店操作期限 2 秒):期限内落定 ⇒ 客户端拿到 200 时锚已在;写失败 ⇒ 200 照发、这一条没有手柄;超期 ⇒ 200 照发、那次写不取消、仍在飞,迟到落定的锚照样可用。后两形都记登记过的 fail-open 计数 `server.resume-anchor.capture-failed`(指标 `fail_open_total{tag="server.resume-anchor.capture-failed"}`)。升级之后的同步提交才有锚,升级前已跑完的不回填;没有 run 店(没有可当手柄的 run id)或没有锚落点席位的部署没有手柄,照旧无锚。**带 `verify` / `cascade` 的同步提交仍没有锚**:这两形走整包 await 的组合腿,不接锚写入器。
149
+ **谁需要**:用非流式 `POST /v1/tasks` 提交、之后要「回到这条提问」的调用方(一发一收形的 SDK / 脚本)。**谁受伤**:每次同步提交多一次有界的锚店单行写(与后台腿同价;锚店挂住时每次锚写至多让 200 晚一个单行店操作期限)。**补偿**:不加旋钮;读 `fail_open_total{tag="server.resume-anchor.capture-failed"}` 判锚写有没有在失败。
150
+ **升级前做**:无。**回滚**:回到 ≤ 7.147.x ⇒ 同步提交不再落锚(消息行不带 `taskId`、`resumeAt` 得 404);无存储形变化(同一张锚店、后台腿本来就写的同一种行)。
151
+
152
+ 15. **订正 7.149.0 节第 36 条的「client-core 的对位版本」:`@sema-agent/client-core` ≥ 0.111.0(文字订正;零行为变化;wire 零变化;部署零动作;已发的 7.149.0 节不改字节,本条即订正)**:
153
+ 订正对象:第 36 条「升级前做」里写「先确认接入的壳已能按数组形读本位(cli ≥ 1.0.164;client-core 的对位版本)」,client-core 一侧没有给版本号。能力位 `sessionReadRedaction` 由布尔改叶名表(7.148.0 节第 49 条)之后,**client-core 0.111.0 是第一个自带叶名表读口 `sessionReadRedactionOf` 的版本**(它认布尔 `true`、认非空串数组、位缺席 / 空表 / 坏形读作「未确认」);此前的版本(0.98.0 起)包内没有读这一位的读器。经 client-core 读这一位的壳,先升到 ≥ 0.111.0 再升本服务;cli 的下限 1.0.164 不变。
154
+ **谁需要** / **谁受伤** / **出路**:同第 36 条(壳先升、再升本服务)。**升级前做**:确认接入的壳的 client-core ≥ 0.111.0。**回滚**:无存储形变化。
155
+
156
+ 16. **勘误 7.151.0 节四处与实现不符的句子:① 第 12 条「此前 `POST /v1/tasks` 答 `200`」② 第 3 条「带 `mcpServers` / `a2aPeers` 决 ⇒ 照旧 `400`」③ 第 16 条列的「请求带的 `cwd`」座 ④ 第 19 / 22 条「到行上的 `deadline`(建行 + 30 天)……收走」(文字订正;零行为变化;wire 零变化;部署零动作;已发的 7.151.0 节不改字节,本条即订正)**:
157
+ ① **第 12 条(`cascade` × `rewindFilesTo`)的旧形不是 `200`**:上一版同体的 `POST /v1/tasks`,提交体带的锚是这只会话里真有的那条提问 ⇒ 梯级在引擎现铸的会话里找不到那条文件边界,引擎在准备期拒(零工作的拒,`rewind_snapshot.unresolvable`),同步腿答 `422 rewind_snapshot.unresolvable`,run 行仍写一行(落 `failed`);带的锚不是这只会话里任何一条提问的手柄 ⇒ `404 rewind_files_to.unknown_event`(锚的解析在装配里,各提交口同读);两种都不是 `200`。`POST /v1/runs` 带真锚时旧形答 `202`(异步受理,引擎的拒落在 run 自己的终局里)。**本版起同体一律先在受理口答 `400 request.field_conflict`**,含假锚(此前 `404`)与真锚(`POST /v1/tasks` 此前 `422`)。所以第 12 条正文与「谁受伤」里的「此前 `200` / `202`」读作:`POST /v1/tasks` 此前是 `422`(真锚)/ `404`(假锚),`POST /v1/runs` 此前是 `202`(真锚)/ `404`(假锚);本版统一为 `400`。同一块里黑盒判据 K2 的「上一版:`200`」同改。
158
+ ② **第 3 条「带 `mcpServers` / `a2aPeers` 决、记录读作 `running` ⇒ 照旧 `400`」只对一种副本成立**:上一版(7.150.x)这句 `400` 由持有那条 run 的进程里的引擎答(引擎对重述键答「停驻原样保留」);不持有它的副本上,本进程的引擎答「这条 run 不在这里」(`not_active`),决断走续跑车道 —— 答 `200`、起宿主一跑(`taskId` = 宿主新一跑)。本版起答 `400` 的是记录栅栏:记录读作 `running`、仍持有这张卡、请求带重述键 ⇒ **任一副本**都答 `400 decide.parked_restatement_unsupported`,卡留 pending。所以:单副本、以及恰好打在持有副本上的决断,「照旧 `400`」成立(只是不再先探引擎);多副本上打在不持有副本的决断,由 `200` + 宿主一跑**变成** `400`(行为收紧:方向与第 3 条正文的「新形」一致,但不是「照旧」)。出路不变:不带 `mcpServers` / `a2aPeers` 重决。
159
+ ③ **第 16 条列的「单用户 host 部署上请求带的 `cwd`」这一座,带 `..` 的形在 HTTP 上够不到**:请求体 `cwd` 在受理口按「主机绝对路径」形判(非空、绝对、无 `..` 段、无 NUL、有长度上限),带 `..` 段的 `cwd` 两版都在门口被拒 `400 request.field_invalid`;拒句只写「cwd must be a non-empty absolute host path」,没有点名 `..`(同一谓词在 `additionalDirectories` 的拒句里写明「no '..' segments」),读起来像「不是绝对路径」。所以对请求 `cwd` 而言,第 16 条「`..` 由文件系统解」没有可观察的变化;`.` 段不被这道形判拦(只拦 `..`),`.` 的读法变化照旧适用。第 16 条射程里不经这道受理门的两座 —— 起服时的 `HOST_WORKSPACE_BASE`、委派给子代的工作目录 —— 才是 `..` 还够得着的座。「升级前做」里「调用方送的 `cwd` 里有没有 `..` / `.` 段」读作:`HOST_WORKSPACE_BASE` 里有没有 `..` / `.`,调用方送的 `cwd` 里有没有 `.`(带 `..` 的 `cwd` 本来就进不了门)。
160
+ ④ **第 19 / 22 条里「到行上的 `deadline`(建行 + 30 天)……收走」「到期收走」只对行上带 `deadline` 的行成立**:带 `deadline` 的行 —— 7.150.x 上同步子代问停驻出的那类行(引擎在腿没有耐久授权声明时缺省铸 `deadline` = 建行 + 30 天,第 19 条「存量行」说的就是它们)—— 到期由检查点店的过期收割收走,句子成立。被中心强制耐久门的主体的同步子代卡(第 22 条的已知缺陷)是另一形:同步子代不继承父腿的耐久授权声明,强制门替那条腿合成的声明只带作用域、没有 TTL,引擎铸的行 `deadline` 是 `null`(不论 `APPROVAL_TIMEOUT_SEC` 设成多少),没有 `deadline` 可到;这样的行只靠**终局兜底**收走:`terminal_at_ms` = 建行时刻 + `APPROVAL_TERMINAL_BACKSTOP_MS`(缺省 30 天;下限 60 秒,非有限值落缺省),由检查点店的收割拍逐行判(SQL 后端与 `DB_BACKEND=local` 同一条公式)。(带耐久授权声明的根腿与后台子代腿才会带上 `APPROVAL_TIMEOUT_SEC` 配的 TTL ⇒ 行带 `deadline`。)所以 `deadline` 为 `null` 的卡,存活期 = 终局兜底窗(缺省同为 30 天,但这时它是唯一的收走条件,调长 `APPROVAL_TERMINAL_BACKSTOP_MS` 就调长它们),不是「到期」;对它 decide 仍答 `409 conflict.resume_context_unavailable`、行不消(第 22 条)。
161
+ **谁需要**:按第 12 / 3 / 16 / 19 / 22 条做升级判断与黑盒验收的人(红臂读数以本条为准)。**谁受伤**:无 —— 订正的是四句与实现不符的文字,零行为变化。**补偿 / 出路**:无需动作;② 里多副本上带重述键决 workflow 停驻卡的调用方,在 7.151.0 起收到的 `400` 即「不带 `mcpServers` / `a2aPeers` 重决」的指路。**升级前做**:无。**回滚**:无存储形变化。
162
+
163
+ 17. **随包文书补全三处(成文;零行为变化;wire 零变化;部署零动作)—— 各句落在 USAGE 对应节**:
164
+ ① **file 记忆控制面账本的形只在开档判一次,运行中被外部写坏本服务不检测**(USAGE「记忆店根搬了家 ⇒ 拒启」条后):起服装配与 `run-local` 装配各在构造 file 记忆后端之前经引擎的只读口问一次账本的形;读不了的形 ⇒ 一行 warn `memory_ledger_malformed`、不拒启;之后不复查、没有定时探测。出路:修好或还原控制面账本后重启(重启即再开档)。
165
+ ② **同一把 `Idempotency-Key` 在首发还在跑时重投,回执有两形(按落在哪个进程)**(USAGE 第 1 节「同一把 `Idempotency-Key` 重投」块):落在首发所在的同一进程 ⇒ 等首发落定、拿同一份结局 + `replay: true`(新合流的上界 = 在飞表条目的 15 分钟;已在等的请求没有第二只超时);落在另一副本、服务重启之后、或条目已过 15 分钟 ⇒ 读行,行上还没有结果 ⇒ 立刻 `200` 的轮询回执 `{ taskId, sessionId, status, replay: true }`。
166
+ ③ **硬杀(`SIGKILL` / 内存杀手)之后,`REMOTE_EXEC=host` 车道上本服务起的前台命令进程组不被回收**(USAGE 已知限制条):退出边界只在 `exit` 事件会触发的退出上收;被信号默认终止的进程没有机会跑;进程重起后也不接管前代的组。出路与读法见 USAGE 该条。
167
+ 这三条是此前随包文书里没有的成文,**不是行为变化**:表里的行为 7.151.0 及更早的版本就是这样。**谁需要**:排障的运维(记忆面 500 / 读到旧数据时判账本形是开档时就不对还是之后才坏;幂等重投短超时的调用方;被 OOM / 硬杀之后机器上留着孤儿命令的 host 部署)。**谁受伤**:无。**升级前做**:无。**回滚**:无。
168
+
169
+ 18. **一次性迁移 M13 / M15 / M16 到期移除:盘上 LKG 信封 formatVersion 2 与 3 不再读回(`loadLkg` 答 `unknown formatVersion 2` / `unknown formatVersion 3`);本地 `config.d` 起服不再被迁移改写(迁移前写法的文档由读腿 / 车道合成响亮拒、一个字节不动);持久数据面;行为变更;运维日志 `config_lkg_migrated` 少一个 `receipts` 键、`config.d` 迁移的三行日志没了;wire 零新键、零新码;嵌入方包根导出无变化;部署零动作,除非从 ≤ 7.123.x 直升本版**:
170
+ 三条一次性迁移的窗(M13:7.123.0 引入,发布日 2026-10-10 + 30 天,2026-11-09 关;M15 / M16:7.124.0 引入,发布日 2026-10-11 + 30 天,2026-11-10 关)都已关,本版删了它们的码。
171
+ M13(信封 2 → 3):`loadLkg` 读回 2 时的内存迁移(`migrateV2Lkg`:旧载体键 `models.droppedIdentities` 搬成 `models.droppedCatalogEntries`、去掉 etag)与它的两只帮手。M15(信封 3 → 4,载荷换代):读回时过契约包一次性迁移 `migrateEffectiveSnapshot` 的那一段(`migrateLkgPayload`)与读回收据,以及对契约包 `one-time-migrations` 子路径的 import。M16(本地 `config.d`):起服时把读腿会读的那几份文档交给契约的目录迁移改写的那一步 —— 接线文件 `config-d-migration.ts` 整只删、HTTP 服务第 2 段与 `run-local` 的两处调用没了。
172
+ 旧形:7.123.x 及以前写下的 LKG(信封 `formatVersion` 2 或 3)读回时在内存里迁成当期形、启动日志 `config_lkg_migrated`(`fromFormat: 2` / `3`,M15 带 `receipts`)、按当期信封写回原位;迁不动的载荷(收据 `refused`)不读回。本地配置源的起服在第一次读 `config.d` 之前改写迁移前写法的文档(`models.atModelAllowlist` / `models.default` → `atMention` / `roles.default`;退役域 `runtime.json` 搬进 `governance.json` / `limits.json` 后删;`remote-exec.json` 的 `apiUrl` 剥掉 `#`;同设两种钥匙的模型条目落成单钥),日志 `config_dir_migrated` / `config_dir_migration_refused` / `config_dir_migration_failed`(这三行里 config.d 迁移的那一份)。
173
+ 新形:LKG 的 2 / 3 旧文件**不读回** —— 起服一行 `config_lkg_unusable`(拒因 `unknown formatVersion 2` / `unknown formatVersion 3`),env 兜底;中心可达照拉(没有 etag 可做条件请求,整拉,读腿铸出完整的被丢条目载体,新 LKG 按当期版本覆盖),断着则 env 兜底,下一份干净候选重写;不采纳载荷(版本判在读完文件之后、载荷判形之前)、不迁、不改写文件;没有新的拒。`config_lkg_migrated` 还在(M22 的 4 → 5 一种),不再带 `receipts`。**`config.d` 起服不再被改写**,迁移前写法的文档一个字节不动:`runtime.json` 带事实 ⇒ 读腿对退役域答 `RetiredDomainError`(整份读不出);`models.atModelAllowlist` / `models.default` ⇒ models 域降级、rosters 整份读不出;`remote-exec.json` 的 `apiUrl` 带 `#`(SS-M7)⇒ 车道合成读它时拒启(`run-local` 退 2);同设两种钥匙的模型条目(SS-M5)⇒ 读腿能抢救就逐条丢弃那一条,没有幸存条目时 models 域降级、整份候选被拒回落 env;拒句 / 告警止于今天的位 / 规则本身(本版提货的契约包 26.0.0 删了这几步,不再点名迁移 id,第 32 条 ⑥)—— 出路 = 把事实手写到今天的位,或升级前用上一个契约 major 手跑 `npx @sema-agent/settings-schema@25 migrate <config.d 目录>`(整域判得过才写、原子写;26.0.0 的 `migrate` 不再改写这几形)。`config_dir_migrated` / `config_dir_migration_failed` 两个事件名还在(配置根上的 SS-M11 迁移用,行里带顶层 `migration`)。
174
+ 直升下限(逐条):M13 —— 从 ≤ 7.122.x(起始版 7.123.0 之前)直升本版的部署,**须先经过含 M13 的版本**(7.123.0–7.151.x 任一版)起服一次;M15 —— 从 ≤ 7.123.x(起始版 7.124.0 之前)直升,**须先经过含 M15 的版本**(7.124.0–7.151.x 任一版)起服一次;M16 —— 从 ≤ 7.123.x(起始版 7.124.0 之前)直升,**须先经过含 M16 的版本**(7.124.0–7.151.x 任一版)起服一次让那几版把 `config.d` 改成新形,或升级前备份 `config.d` 后手跑上面的命令行。不先经的话:两条 LKG 只丢这份缓存(不丢数据),`config.d` 一条在起服时被读腿 / 车道合成拒、字节不动。从 7.124.0 及以后升级的部署零动作。
175
+ 谁需要:无。**谁受伤**:从 ≤ 7.123.x 直升的部署 —— 起服时中心不可达的多一行 `config_lkg_unusable`(这一次从 env 起);**本地配置源(`CONFIG_PROVIDER=local` / `run-local`)而 `config.d` 还是迁移前写法的部署起服被拒或降级**;按 `config_lkg_migrated` 的 `fromFormat: 2` / `3` 或 `receipts` 键、按 `config_dir_migrated`(config.d 那一份)/ `config_dir_migration_refused` / `config_dir_migration_failed`(config.d 那一份)做告警的运维。
176
+ 补偿:先经 7.124.0–7.151.x 任一版起服;或升级前备份 `config.d` 后手跑上面那一条 `@25` 的契约命令行;或删掉旧 LKG 文件(缓存、不是数据);中心可达时下一份干净候选写出当期信封。回滚:无新增影响(本版写盘的信封版本与 7.151.x 相同,只是不再读 2 / 3;本版不改写 `config.d`,回滚后的旧版照旧在自己的起服里迁)。
177
+
178
+ 19. **运维待批队列行的全部动作事实与续跑执行读同一次整行解码(两后端同一只投影);`checkpoint` 的四只展示拷贝列 `tool_name` / `risk_descriptor` / `rule_suggestions` / `has_bidi_controls` 退役,并进 M54(行为变更;wire 零新键、零新码、零新帧型;一次性迁移同第 1 条那一条,零额外运维动作)**:
179
+ 旧形(≤ 7.151.x):SQL 后端的待决队列(`GET /v1/approvals` / `/stream`)读同一张卡用两只 parser、两份存储 —— 工具名 / 绑定两键 / 卡 / 风险描述 / 规则候选 / bidi 位读写入时反范式下来的列,入参与停驻行事实读库引擎内提取的 `pendingAction` 子树,`checkpointId` 与记下的登记行读成员;续跑执行读的是整只文档的一次 `JSON.parse`。带外改过一格列,或 MariaDB 文本承载上原文带重复顶层键(引擎内提取取先到者、`JSON.parse` 取后到者)⇒ 队列显示的不是将执行的那一份。`rowVerdict` 在 SQL 上可缺席。
180
+ 新形:**队列行 = 行址 + 动作事实**。行址(会话 / scope / 门型 / 建行时刻 / 截止 / 所属 run 的认领)取决断口找行的那一处(SQL = 列,local = 引擎摘要);动作事实(工具名、调用 id、绑定两键、入参、卡、风险描述、规则候选、bidi 位、停驻行事实、`checkpointId`、记下的登记行、技能卡位、`rowVerdict`)只出自续跑执行读的那一次整行解码,两后端同一只投影;解不出 ⇒ 动作事实 `null` / 缺席、`rowVerdict` 非 `ok`。SQL 主读只出行址与库内对整只文档算的摘要,动作事实按 (文档字节, 引擎构建) 记忆、未命中按令牌批读整行。两次读之间(主读与整行批读之间)行已离开待决 ⇒ **这一拍不列**;两次读之间行的字节变了 ⇒ 这一拍就带批读那一版(= 此刻续跑会执行的那一版)的全部动作事实 ⇒ `rowVerdict` 两后端**恒在**。
181
+ **变了的**:① 本服务写口写下的行每一键的值不变。② 带外改过列或文档的行:队列显示的就是将执行的那一份。③ SQL 上读不成检查点 / 引擎摘要读不动的行与 local 逐键同形:只剩行址与 `rowVerdict`(非 `ok`),`toolName` / `toolCallId` / `input` / `riskDescriptor` 为 `null`,`boundCallId` / `boundInputHash` / `approvalCard` / `ruleOffers` / `hasBidiControls` / 停驻行事实 / 技能卡位 / `checkpointId` 缺席(此前带列与成员上的那几格);收件箱 / 调度视图上这种行的 `checkpointId` 同样缺席。④ 两次读之间离开待决的行本拍不列(此前列出、不带 `rowVerdict`);字节变了的行本拍带批读那一版(此前不带 `rowVerdict`)。⑤ **读放大(如实)**:SQL 副本多持一块投影记忆,上界 **64 MiB**(按投影 JSON 的 UTF-8 字节累计,另 8192 条,先到者挤;常量,不是旋钮;单只放不进上界的投影不记)—— 待决集的投影总量超出时被挤出的行每拍按未命中整行重读(答案不变,只多读库);起服后第一拍把全部待决行整行读一遍(此前也是);命中时主读每行只出行址与 64 字节摘要(此前每拍每条连接把每行的子树文本整份再拉一遍)。⑥ 计数口径:规则候选的脱敏计数与描述读不成 / 候选丢弃的计数在 SQL 上「每份文档字节每进程一次」,local 仍每次列表一次。⑦ 文档门上的风险描述在场却读不成对象 ⇒ `riskDescriptor` 为 `null`、计 `server.approvals.risk-descriptor-cell-unreadable`(两后端同一座);文档里的规则候选读不成列表 ⇒ 键缺席、计 `server.rule-offers.offer-dropped`;`server.approvals.rule-suggestions-cell-unreadable` 随列退役(按 tag 告警的运维把它从规则里删掉)。⑧ 起服列探针不再查 `has_bidi_controls`。
182
+ **谁需要**:带外审批人与按 `GET /v1/approvals` 带答 decide 的运维(看到的就是将执行的那一份);MariaDB 部署与手修过停驻行的运维。**谁受伤**:SQL 副本多持 ≤ 64 MiB 记忆;SQL 上读不动的行在队列上少了列来的工具名等格;回滚要补五列(第 1 条),回滚后本版期间停驻的行在旧版队列上工具名 / 入参 / 描述为 `null`、候选 / bidi 位缺席;按列读这四列的外部脚本 / 报表失去列(值都在 `checkpoint` 列的文档里:工具名与入参在 `pendingAction`,描述在 `gate.riskDescriptor`,候选在 `pendingAction.ruleOffers`,bidi 位在 `pendingAction.hasBidiControls` —— 那是未脱敏的执行记录);两次读之间离开待决的行本拍不列。**出路**:不加旋钮 —— 记忆挤出只换代价不换答案;读不动的行带 `rowVerdict` 说明为什么,修或删那一行;删四列与删 `tool_input` 同一次停光(第 1 条的升级前做与回滚照读)。
183
+
184
+ 20. **leader 车道的执行器与其余车道同读部署:按主体的运行期准入、部署治理折叠(`AUTONOMY` / `commandPolicy` / `MANUAL_MODE_SHELL_GATE` / 守卫集)落在 leader 的 worker 首跑与各修复腿上;每一条腿按它的耐久事实取审批基线与停驻设施,部署审批名单管到修复腿与不带停驻设施的 worker(安全轴收紧;行为变更;静态车道的停驻与资源分片与 7.151.x 相同;wire 零新键、零新码;受理拒句改一句;部署零动作,开了 leader 的部署见「升级前做」)**:
185
+ 旧形(7.151.x 及以前,`LEADER_ENABLED=true`):leader 的执行器不问按主体的运行期准入 —— 被中心关了 fork、开了强制耐久门的主体,在 worker 腿上「不限」;worker 首跑与修复腿不过部署治理折叠(停驻后的续跑腿才过);模块门修复与单代理修复环不带提交者身份、不带审批基线(三张审批名单管不到这两条修复腿);worker 只在「有停驻店且有任一审批名单」时带停驻设施,不带停驻设施的 worker 也不带任何审批策略(没接停驻店的部署上三张名单管不到 worker);worker 首写续跑上下文失败被吞、停驻设施照挂,停出来的卡决不了。MIGRATION 7.151.0 节第 22 条句尾「leader 车道(`POST /v1/leader`)的执行器本版不在此列」说的就是这一形。
186
+ 新形:leader 的每一只执行器与其余车道展开同一层部署键,每一条腿出生时盖提交者身份、过部署治理折叠(与 HTTP 提交 / 续跑腿同一只),并按这条腿的耐久事实盖审批基线与停驻设施:**耐久 ⇔ 出生点递了停驻设施 ∧ 续跑上下文真落进了停驻店**。工厂车道的 worker 首跑:部署接了停驻店即递(名单只决定哪些工具问,不决定能不能停);静态车道(无 MinIO)的 worker:照 7.151.x 的条件递(停驻店 ∧ 任一审批名单在场),停驻设施、资源分片停驻与审批问停驻逐格与 7.151.x 相同;集成修复 / 模块门修复 / 单代理修复环(续接是本 run 内存里的那一轮)恒不耐久。耐久的腿带停驻设施与 durable 审批基线;不耐久的腿带名单门基线、零停驻设施 —— 三张名单照判,无人值守层答不了的问当场拒;续跑上下文写失败的 worker 取不耐久这一形。相对 7.151.x 的净变化:三条修复腿与不带停驻设施的 worker 开始受审批名单管;工厂车道 × 停驻店 × 名单全空的部署上 worker 也带停驻设施;续跑上下文写失败的 worker 不再停出决不了的卡。被强制耐久门的主体:耐久的 worker 第一次写即停驻,leader 任务以 `suspended` 收场,卡在 `/v1/approvals`(工厂车道的卡决得了、决完的续跑不回集成;静态车道的卡见下面已知缺陷);不耐久的腿上不停驻(引擎的无设施读法,见本条末句)。`AUTONOMY=plan` 的受理拒句改句(出路只指 `AUTONOMY=ask`,并写明代价)。**已知缺陷(7.151.x 同形,本版不改)**:静态 e2b 车道 × 停驻店 × 设了审批名单时,worker 的审批问停驻成卡,卡决完的续跑在一只新执行环境上恢复、拿不到 worker 的工作树;引擎提供「宿主声明本腿审批停驻不可续」的声明位后收(同一车道的资源分片停驻不受影响)。要审批停驻可续 ⇒ 配齐 MinIO 三件套走工厂车道。
187
+ **谁需要** / **谁受伤** / **补偿**:同 CHANGELOG 本节同名一条(零新旋钮)。**升级前做**:开了 `LEADER_ENABLED` 的部署盘点 ① 中心上开了强制耐久门 / 关了 fork 的主体 —— 他们的 leader 任务此后在耐久的 worker 上(工厂车道 × 停驻店;静态车道 × 停驻店 × 设了审批名单)第一次写就停驻收场,在不耐久的 worker 上普通写由无人值守层按类放行、答不了的问拒;要多 worker 编排改走 `POST /v1/runs`,或在中心给这个主体放开;② `AUTONOMY` 设成 `read-only` 或 `ask`、`MANUAL_MODE_SHELL_GATE=always` 的部署 —— leader 任务此后在 `read-only` 下产不出改动、在后两者下每条 shell 要人(耐久的 worker 停驻,不耐久的腿拒);③ 有停驻店而三张审批名单全空的部署(工厂车道)—— worker 模型的 AskUserQuestion 此后停驻成卡;③′ 设了审批名单的部署 —— 名单此后也管三条修复腿,没接停驻店时也管 worker(`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中的调用没人可问 ⇒ 拒);④ 设了守卫集或 `commandPolicy` 的部署 —— 它们此后也管 leader 首跑。强制耐久门的读法归引擎:耐久的 worker 腿上每一问直接停驻;不耐久的腿(没递停驻设施的 worker、续跑上下文没落的 worker、各修复腿)不带停驻设施,按引擎的无设施读法 —— 一条 `config.durable_gate_unavailable` 通告,无人值守层按类答掉的普通写是策略结算的放行(满足强制门),答不了的问拒。**回滚**:回到 7.151.x ⇒ 上面「旧形」。
188
+
189
+ 21. **审批列表行顶层的委派子代出处键 `originTaskId` 改名 `sourceSessionId`(wire BREAKING;零别名;值不变;部署零动作)**:
190
+ 旧形(7.151.x 及以前):`GET /v1/approvals` 的 `livePending[]` 行、`/v1/approvals/stream` 的 `live_pending` 帧、`GET /v1/tool-approvals/:id`(7.152.0 起已删)的 `pending` 体,对委派子代发起的卡,顶层带 `fromSubagent: true` 与 `originTaskId`(键名说 task,值是子代的会话 id);同一行内嵌的呈卡帧的卡里,同一个值叫 `sourceSessionId`。
191
+ 新形:顶层键叫 `sourceSessionId`,值不变,与内嵌帧的卡上的同名键同名同值;`originTaskId` 不再出现(无别名)。`fromSubagent` 判别位不变,新键在场 ⇔ `fromSubagent` 在场;宿主自身发起的卡两键皆缺席。
192
+ 本条取代此前「保留不改」的两句:MIGRATION 7.143.0 节第 18 条的「`fromSubagent` 判别位与 `originTaskId` 不变」与本节第 13 条里「`livePending` 行顶层带 `fromSubagent` 与同值的 `originTaskId`」—— 它们描述 7.151.x 及更早的形(已发节字节不动,取代写在这里)。
193
+ 看得见的边(如实):读 `originTaskId` 的消费方升级后读到缺席 —— 若把它的在场当作「这是委派子代的卡」,会把子代卡当成宿主自身的卡;判子代读 `fromSubagent`(不变),关联子代行读 `sourceSessionId`。撤卡帧的 `taskId` 是出处 run 的 id,是另一个东西,不改。
194
+ **谁需要**:按子代会话 id 把待决卡关联到子代行的壳与 SDK(同一个值现在只有一个名字)。**谁受伤**:读 `livePending[].originTaskId`(或 `live_pending` 帧、`pending` 体顶层同名键)的壳 / SDK / 控制台。**补偿**:改读 `sourceSessionId`,值不变。
195
+ **升级前做**:无(部署零动作;消费方的读点改名随 SDK 与各壳的版本)。**回滚**(本版 → 7.151.x):旧键回来;本版没有持久面改动。
196
+
197
+ 22. **会话头探针 `GET /v1/sessions/:id/head` 带占用两键,ETag 改为 200 体字节的摘要(行为加性;wire 加性;零新码;零 DDL;部署零动作)**:
198
+ 体加 `activeTaskId: string | null`(这只会话单活认领此刻的持有者;认领已放 ⇒ `null`)与 `activeTaskStatus?`(`running` / `suspended` / `needs_review`),与 409 `conflict.session_active_run` 体同一只投影(契约 §29);`DB_BACKEND=memory` 上两键都不在(那种部署不记认领)。
199
+ ETag 不再是 `"<leafId>"`,而是 200 体字节的 sha256 base64url:升级后每只会话的第一次探针拿 200,之后照常 304。删除口与 run 外回退口的 409 体多 `activeTaskStatus`;提交面 409 体在「认领于冲突与观察之间放掉」那一形上 `activeTaskId` 是 `null`(此前空串)。探针每次读 run 店(认领;有持有者时再读那条 run 行),读不吞错:接了持久 run 店的部署上 run 店读不出 ⇒ 探针答 `500 internal.error`(7.151.x 探针只读会话店,碰不到 run 店的故障)。
200
+ **谁需要**:重启后的壳 / 桌面与别的设备作过决断之后的端(答「谁占着这只会话」)。**谁受伤**:从 `leafId` 自己拼 `If-None-Match` 的探针(不再命中 304,内容照旧对);判 `activeTaskId === ""` 的消费方(改判 `null`)。**升级前做**:无。**出路**:回传收到的 ETag。**回滚**:无持久面;回到 7.151.x 探针体少两键、ETag 回到叶 id。
201
+
202
+ 23. **流式提交的提示词回退锚改在提示提交那一刻落下;「没有锚的提问」那句 404 只陈述事实(行为修复;wire 零新键、零新码;一句 404 文案改短;零 DDL;部署零动作)**:
203
+ 旧形(7.151.x 及以前):`POST /v1/tasks/stream` 的提示词回退锚只在终帧之前与断连收尾时写;进程被强杀(SIGKILL / 崩溃 / OOM)⇒ 锚缺,重启后按这条 run 的 `taskId` 发 `resumeAt` / `rewindFilesTo` / run 外口 `to` ⇒ 404,`error` 的括号里点名「带附件的提问在附件帧被中断或帧数不符时不落锚」。
204
+ 新形:四条腿同一只写法、同一个时机 —— 第一条用户提交到手即落锚(第一次模型输出关窗时需要校正再落一次);那句 404 为 `… — no rewind anchor is recorded under that handle: it names a prompt of this session that recorded none; …`,不点名成因。
205
+ **存量**:旧版本下被强杀的流式 run 没有锚,本版不补(读到新句);升级之后的流式 run 不再因强杀缺锚。
206
+ **谁需要** / **谁受伤** / **补偿**:同 CHANGELOG 本节同名一条(零新旋钮)。**升级前做**:按这句 404 `error` 文案里 `attachment` 字样分支的脚本改读 `errorCode`。**回滚**(本版 → 7.151.x):无数据面改动;回滚后流式腿回到「终帧之前 / 断连收尾写锚」,本版写下的锚照常可读。
207
+
208
+ 24. **生效场景读面 `GET /v1/capabilities/scenario` 与场景门的答形 `{ scenario, source }`(wire 加性;行为修复:三张按场景答的读面读场景头;嵌入方型面破坏;零新码;零 DDL;部署零动作)**:
209
+ 新读面答「这次请求会生效哪只场景、它从哪一层来」:200 体 `{ scenario, source }`,`source` ∈ `request` / `ruling` / `deployment`(闭集,穷举读);可选 `?scenario=<名>` 与场景头 `x-agent-scenario` 都是点名(与提交腿同一只读法)。拒与同一请求形的提交拒体逐字同(400 `scenario_unknown` / `scenario_not_allowed` / `scenario_conflict`,503 `state.scenario_assignment_unknown` / `state.scenario_assignment_unavailable`)。能力位 `effectiveScenario`(恒真;键缺席 = 老 server,没有这张面)。
210
+ 带场景头读 `GET /v1/capabilities/mcp` / `GET /v1/sessions/:id/mcp` / `GET /v1/sessions/:id/a2a`:答的是头里那只场景;状态面上头与 `?scenario=` 不同 ⇒ 400 `scenario_conflict`。
211
+ 包根 `ScenarioGate` 的答从串改为 `ScenarioChoice`(`{ scenario, source }`;包根另导出 `ScenarioChoice` / `ScenarioSource`)。
212
+ **谁需要**:不点名场景、要按名取场景卡的交互客户端。**谁受伤**:手写 `ScenarioGate` 的嵌入方(编译红);带场景头读那三张面、依赖「头被忽略」的读者。**升级前做**:无。**出路**:手写门返回 `{ scenario, source }`(或改用包根 `createScenarioGate`,它铸的门就是生产那一只);要指派链的答就不带场景头读。**回滚**:无持久面;回到 7.151.x 没有这张面与这一位,三张读面不读场景头。
213
+
214
+ 25. **🔴 一只会话快照桶 × 一个 `SESSION_SNAPSHOT_PREFIX` 只属一只库:起服开店时证明,别的库认领过 ⇒ 拒启;证不出 ⇒ 照起、不删(数据丢失修复;部署面:共桶同前缀的第二只库起不来;缺认领物件权限的部署照起、回收停到权限补齐;起服自建一张表;wire 零变化)**:
215
+ 旧形(≤ 7.151.x):文件历史与附件的对象键是内容地址,删哪一枚只按本库的引用判 —— 两台各有自己库的引擎共用一只会话快照桶且前缀相同(含都不设)时,一台的附件孤儿清扫把另一台在引的上传当孤儿删掉,同一份内容两边都传过时一台放手就带走另一台的字节;两台照常起服,丢字节在读回时才露面。
216
+ 新形:每只库一只认领记号(表 `blob_namespace_claim`,一库一行,起服自建、缺席才铸);桶里 `<前缀>blob-namespace-claim` 写着命名空间属于哪只库 —— 本库 ⇒ 开店;没有 ⇒ 写下本库的(`If-None-Match: *`,等一秒读回);别的库 ⇒ 拒启;读写不动(连不上 / 403 / 5xx)⇒ 照常起服(一次尝试、不重试;兜底口 `server.session-blob-namespace.claim-unproven` 计一次、一次性 warn)。会话 blob 三域的每一次删除先重读认领物件、写着本库才删 —— 证不出起服之后每一次删除都拒,存储答复后的第一次删除当场认领再删。文件历史与退役纪元两只按索引行找字节的清扫在动任何一行之前先读认领(每拍一次):证不出 ⇒ 这一拍一行不删、一字节不删,认领恢复后的清扫从同一批行重来。这一步每拍都做、哪怕没有东西要删:对象存储短暂不可用期间,没有东西要删的清扫也会在这一步拒并记失败告警(此前这样的清扫不碰对象存储、照常成功);认领物件缺席时(例如运维删掉它之后)这一步会写下本库的记号(create-only,写后等一秒读回),不只是读。**已知限制**:同一次清扫里认领读两次(动任何一行之前一次、删字节之前一次),第一次过、第二次失败(对象存储瞬时错误)⇒ 那一批索引行已删、字节留在桶里,之后的清扫找不回(文件历史与退役纪元的字节只经索引行可达);认领一直读不出时整次清扫在动任何一行之前就拒、不留这个后果,窗只剩两次读之间的瞬时失败 —— 窄,不是零。`SESSION_SNAPSHOT_PREFIX` 以 `attachments/` 或 `file-history/` 结尾 ⇒ 装载拒。
217
+ **升级前做**:核每只会话快照桶被哪几只库用着 —— 两只以上的库共用一只桶且 `SESSION_SNAPSHOT_PREFIX` 相同(含都不设)⇒ 除了留在原前缀的那一只,其余每只库:① 设自己的前缀(段名不要与 `file-history/` / `attachments/` / `blobs/` / `sendfile/` 同名);② 要保住它已引用的字节,先把旧前缀下的 `file-history/` 与 `attachments/` 拷到新前缀(`mc cp --recursive <alias>/<桶>/<旧前缀>attachments/ <alias>/<桶>/<新前缀>attachments/`,file-history 同);③ 再起。只有一只库用这只桶的部署零动作(首次起服写下认领物件)。按最小权限发对象存储策略的部署给 `<前缀>blob-namespace-claim` 加 `s3:GetObject` / `s3:PutObject` —— 没加的部署升级后**照起**,但附件与文件历史从此不再被回收(桶只涨不减),直到策略补齐(可见于 `server.session-blob-namespace.claim-unproven` 的计数与一次性 warn、清扫的拒删行;补齐之后的第一次删除即认领,不必重启)。
218
+ **出路**(拒句点名):认领物件点名的那只库已经不用这只桶(它已退役,或部署换用了另一只库)⇒ 删掉 `<桶>/<前缀>blob-namespace-claim` 再起;否则给这台引擎设自己的前缀。**回滚**:回到 7.151.x 零动作(旧版不读认领物件与新表,两者留着无害)。
219
+
220
+ 26. **预签名 URL 的有效期上限只有一处:604800 s(SigV4 的 7 天),签名座对每一只签名行超了就拒;读它的旋钮装载时超了即拒(行为收紧;部署面:超限配置起服拒;wire 零变化)**:
221
+ 旧形(≤ 7.151.x):`SEND_USER_FILE_URL_TTL` > 604800 照装、每次签发抛;`SESSION_SNAPSHOT_TTL_SEC` > 604800 签出存储拒的 URL;`LEADER_TIMEOUT_MS` > 604200000(开了 leader + 对象存储)签出的补丁链接 worker 跑完才失败。
222
+ 新形:三者与 `WORKSPACE_SNAPSHOT_TTL_SEC`(此前超了按 3600 截)配置装载即拒(拒句点名旋钮与上限 / 最大值);签名座对任何一行超上限的请求拒签、不截。
223
+ `SEND_USER_FILE_URL_TTL` 不是十进制整数的写法(`1h`、`3600.5`、`-5`;`1e-999` 一类也读作 0):旧形读作 0 = 永久公链(想要会过期的链接、写了单位的部署悄悄发出永久公链);新形装载拒,拒句点名旋钮与两种合法形(0 或不设 = 永久公链;1–604800 的十进制整数秒 = 会过期的私有链接);`1e3` / `0x10` / `03600` / `+60` 这类旧形照数读的写法同拒。
224
+ `SESSION_SNAPSHOT_TTL_SEC` 与 `WORKSPACE_SNAPSHOT_TTL_SEC` 同读这一条写法判:旧形 `3600.5` / `1e3` / `0x10` / `03600` / `+60` 照 `Number()` 的读数装下(小数原样签进 `X-Amz-Expires`),`SESSION_SNAPSHOT_TTL_SEC` 的 `1h` / `-5` 告警后走缺省、`WORKSPACE_SNAPSHOT_TTL_SEC` 的 `-5` 由签名座读作缺省;新形这些写法都装载拒,拒句点名旋钮与合法形。存量写成这些形的部署升级时拒启;出路 = 写成十进制整数秒。`0` 的读法两根各自照旧(`SESSION_SNAPSHOT_TTL_SEC=0` 告警后走缺省,`WORKSPACE_SNAPSHOT_TTL_SEC=0` 读作缺省)。`SESSION_SNAPSHOT_TTL_SEC` 写成纯空白(只有空格):此前起服一行告警、走缺省,现在读作未设、没有这行告警、走缺省(值的读数不变,只少一行告警)。
225
+ **升级前做**:核这四根旋钮 —— `SEND_USER_FILE_URL_TTL` 是十进制整数秒 ≤ 604800(或 0 = 永久链;不带单位、小数点、指数)· `SESSION_SNAPSHOT_TTL_SEC` 是十进制整数秒 ≤ 604800 · `WORKSPACE_SNAPSHOT_TTL_SEC` 是十进制整数秒 ≤ 604800 · 开了 leader 且配了 `MINIO_*` 的部署 `LEADER_TIMEOUT_MS` ≤ 604200000。都在范围内的部署零动作。**回滚**:零动作。
226
+
227
+ 27. **leader 车道(`POST /v1/leader`)与 agent 钩子的模型请求用目录条目自己的钥匙(`apiKeyEnv` / `sealedApiKey`),不再用部署网关钥;声明了钥匙却拿不到 ⇒ 响亮失败(行为修复;安全轴收紧;wire 零新键、零新码;零 DDL;部署零动作,自查见「升级前做」)**:
228
+ 旧形(7.151.x 及以前):leader 的执行器(planner 路由、两条车道的 worker、集成修复,以及跑在 worker 执行器上的模块门修复与单代理修复环)与 `type: "agent"` 钩子的执行器发出的模型请求都只带部署网关钥(`MODEL_API_KEY`)—— 目录给模型配的 `apiKeyEnv` / `sealedApiKey` 在这两条车道上不生效:模型走网关的根 ⇒ 网关钥照发、账记在部署网关上;模型在别家的根上 ⇒ 引擎的配对裁决器拒(`route.credential_mismatch`),leader 的腿起不来;声明的变量缺席 / 托管密钥解不开 ⇒ 照样拿网关钥发出去。
229
+ 新形:这两条车道与任务面同读 —— 打一只配了自己钥匙的模型时带它自己的钥匙;没配的模型照旧网关钥(逐字同旧形);声明了却拿不到 ⇒ 那次调用整体失败、拒句点名变量或托管成因、一个请求都不发(leader 的 worker 腿 failed;集成修复轮没跑完 ⇒ 合并保留原失败,拒句带成因;planner 路由不发请求、收成单路,另记 `plan_route_run_incomplete` 一行点名成因;模块门修复腿失败后照既有读法回到判官,另记 `module_repair_incomplete` 一行点名成因;agent 钩子那一条 fail,非阻断记账)。钥与执行器的目录同代:跑在一只执行器上的每条腿带那只执行器出生时那一代的钥匙(模块门修复 / 单代理修复环沿用 worker 的);中心把模型连端点带钥匙一起换掉之后,之后出生的执行器用新的一代;新钥匙不发往旧端点。
230
+ **谁需要** / **谁受伤** / **补偿**:同 CHANGELOG 本节同名一条(零新旋钮)。**升级前做**:开了 `LEADER_ENABLED` 或配了 `type: "agent"` 钩子、且目录给模型配了自己钥匙的部署 —— ① 核那些钥匙在服务进程里拿得到(`apiKeyEnv` 点名的变量有值、`sealedApiKey` 解得开);② 知会账务:这两条车道的用量此后记到各模型自己的账户;③ 不想让某只模型在这两条车道上按自己的账号认证 ⇒ 删掉条目的 `apiKeyEnv` / `sealedApiKey`(任务面同受影响 —— 一只模型只有一种凭据读法)。**回滚**(本版 → 7.151.x):无持久面改动;回滚后这两条车道回到只带网关钥。
231
+
232
+ 28. **勘误 7.143.0 节第 32 条 ②(回滚句):它写的是 `DB_BACKEND=local` 的一形;SQL 后端(`DB_BACKEND=mysql|pg`)回到 7.142.x,旧副本先在起服时被列册对账拒启,删掉本版多出的两列之后才轮到续跑那一步;`422 parked_resume.startup_failed` 的回体键与 `rollback` 的取值域在此成文(文字订正;零行为变更;wire 零变化;部署零动作;已发的 7.143.0 节不改字节,本条即订正)**:
233
+ — **勘误**:7.143.0 节第 32 条 ② 写「回滚:本版停驻的后台子代的卡记着行 id(要求词 `row_task_id`),上一版读不懂这个词,续跑拒 `version_newer`」。这句讲的是回滚之后**续跑那一步**的结局,而且只对 `DB_BACKEND=local` 成立 —— 那一形的旧副本起得来,才轮得到续跑。SQL 后端要先过起服这一关,而该节没有把它接到第 32 条上(删列语句分在第 9 条与第 26 条各自的回滚句里)。
234
+ — **SQL 后端回滚到 7.142.x,两步**:
235
+ ① **起服先被拒**。7.143.0 起这只库在已有的表里多了两列:`task_attachment.seed_session_id`(带唯一索引 `uq_att_seed`,第 9 条)与 `approval_ask.decided_via`(第 26 条)。7.142.x 的起服列册对账(`assertSchemaMatchesCode`,建表之后、服务之前)把库里有、而它的建表语句没有声明的列读作多出来的列,**拒启**,码 `config.schema_mismatch`。拒句以 `config.schema_mismatch: the PostgreSQL database does not match this build's schema — refusing to start.` 开头(MySQL 协议写 `the MySQL-protocol database`),随后逐表一段 `table task_attachment: UNKNOWN column(s) seed_session_id — the database is NEWER than this build (a rolling window where an old replica reached an upgraded database).`(`approval_ask` / `decided_via` 同形),每段下一行 `No ALTER is suggested for this direction: deploy the build that owns those columns (upgrade the database FIRST, then the replicas).`,末尾是闭集三条出路。旧版给的出路是「部署持有这些列的构建」,不给 `ALTER`。
236
+ ② **删列之后才轮到第 32 条 ②**。回到 7.142.x 的路径是第 9 条与第 26 条回滚句写的那一条:先停光本版的全部副本,再删索引 `uq_att_seed` 与 `seed_session_id`、`decided_via` 两列(语句逐字在那两条里),再起 7.142.x;起得来之后,本版停驻的后台子代的卡才按第 32 条 ② 的读法被续跑拒。
237
+ — **`422 parked_resume.startup_failed` 的体**(后台停驻子代的赎回腿;铸点 `src/parked-decide.ts`,7.142.x、7.143.0 与本版同形):回到 7.142.x 之后对这类卡 approve,引擎在认领之前拒(`version_newer`),这条腿把它交成 `422 parked_resume.startup_failed`,不是同步续跑腿上那只 `409 checkpoint.unsupported_version`。体的键:`error`(引擎回执的原句;回执没给句子时是 `the parked resume failed during startup`)· `errorCode`(恒为本码)· `taskId`(后台子代的句柄,`a` 打头的 id)· `rollback`(恒在,取值见下)· `cause`(引擎回执带了闭集成因词 `environment` · `session_gone` · `transcript_corrupt` · `tool_unavailable` · `configuration` · `decision_rejected` 才在,没带或集外词一律缺席)· `blockedBy` / `field` / `reason`(各自在引擎回执 `detail` 的 `code` / `field` / `reason` 是非空串时才在,缺席不造键;这一形上的 `reason` 读作 `version_newer`)。体上没有 `detail`、`retriable` 键。`blockedBy` / `field` / `reason` 自 7.134.0 起在;7.134.0 之前这条腿的体除 `taskId` / `rollback` 之外的判别位只有 `cause`,7.132.0 节第 1 / 2 条「体只带 `cause`」说的就是那个范围。
238
+ — **`rollback` 的取值域**(四个词,引擎回滚口对**这一次 decide 自己的那一次补滚**的处置,原样上 wire;同一个键、同一张词表也在 `409 decide.revive_rejected` · `500 decide.revive_failed` · `409 decide.agent_definition_unavailable` 三只拒体上):
239
+ · `rolled_back` —— 这次补滚把认领放掉了:行是 `parked`、认领已清;读回确认了卡仍 pending(同一张卡可再决)。放掉认领之后读回那一步店读写抛了的形也答这个词 —— 那时卡此刻是否仍 pending 没有再确认;
240
+ · `lost` —— 这次补滚没有以「卡仍 pending」或「行结清为 `failed`」收场,两种形:补滚时行上已不是这一次 decide 的认领(这次补滚没写任何东西;起跑即败时引擎在驱动里先自己回滚了认领是本腿 422 上的常态:行已回 `parked`、卡仍 pending、可再决;也可能引擎已消费它、或别的写者拿走了它);或这次补滚放掉了认领、读回却发现卡已不再 pending,行又没能结清成 `failed`。本服务分不出是哪一种 —— 读 `GET /v1/approvals` 与那一行的状态看这张卡的去向。回到 7.142.x 后的这一形通常读 `lost` 而行仍 pending;
241
+ · `failed` —— 卡已不再 pending(被作废或已被消费),这次补滚把行结清为终局 `failed`(子代 `failed`,什么都没复活);
242
+ · `retry` —— 补滚读写店失败,没有确认写成任何东西:认领可能还在行上(陈旧认领清扫会放掉它),也可能已被引擎在驱动里结清;读待决列表看那张卡的去向。
243
+ **谁需要**:做滚动回滚的运维(SQL 后端部署)。**谁受伤**:① 按第 32 条 ② 的字面以为「回滚之后旧版起得来、只是卡续不了」的运维 —— SQL 后端的旧版在删掉那两列之前起不来;② 按 `409 checkpoint.unsupported_version` 找这一拒的脚本 —— 后台停驻子代的卡在 7.142.x 上答 `422 parked_resume.startup_failed`(同步续跑腿仍是 `409`)。**补偿 / 出路**:本条;删列的语句逐字在第 9 条与第 26 条的回滚句里。**升级前做**:无。**回滚**:无关(无存储形变化)。
244
+
245
+ 29. **勘误 7.141.0 节第 3 条末句:「`/wake` 的会话 park(取消后仍留着)」不适用于 wake 在入口屏 / 预检上被取消、腿以 `checkpoint.resume_aborted` 抛出收场的那一形 —— 7.141.0 至 7.147.0 该形亲手 expire 了那张会话 park,7.148.0 起不再(文字订正;零行为变更;wire 零变化;部署零动作;已发的 7.141.0 节不改字节,本条即订正)**:
246
+ — **勘误**:7.141.0 节第 3 条末句「`/wake` 的会话 park(取消后仍留着)与非取消的重开形里引擎原句原样」(CHANGELOG 7.141.0 节同条同说)只对 wake 的**结果臂**成立:腿交回了结果、之后才被取消 —— wake 的合成行不持有那张 park,结果臂上的取消结算对合成行提前返回,park 留在店里,终局 `message` 是引擎原句。**不成立**的是另一形:wake 在入口屏 / 预检上被取消,腿以 `checkpoint.resume_aborted` 抛出收场(`src/http/resume-legs.ts` 的取消臂)。7.141.0 至 7.147.0 在这一臂上对 wake 同样调取消结算(`settleCardForCancel`):wake 续的是会话的 `task_done` park,它是一张 pending 的卡 ⇒ 被本服务 expire,run 行与账本终局行的 `message` 是该条那句「Cancelled — the approved action never started, and the cancel settled this approval: it cannot be resumed (start a new run to continue)」(码仍是 `cancelled`;句里的「批过的动作」「this approval」对 wake 并不存在 —— wake 的 park 没有待批动作),再 wake 答 `404 not_found.parked_checkpoint`。7.148.0 起这一臂对 wake 的合成行不再 expire(取消结算对合成行恒答 `false`):park 留 pending,终局用引擎原句,再 wake 续它 —— 7.148.0 节第 7 条把这一变化写成了「旧形 / 新形」,本条把两条接上:第 3 条末句说的 park 去向,对这一形在 7.141.0 至 7.147.0 是相反的。7.141.0 之前这一臂是否同形,本条不判。
247
+ **谁需要**:按第 3 条末句判断「取消一次 wake 之后这条会话还能再 wake」的调用方。**谁受伤**:7.141.0 至 7.147.0 上取消了入口屏 / 预检阶段的 wake、再 wake 读到 `404` 的调用方(文字与行为不符)。**补偿 / 出路**:新提交一条任务(新 run 与 park 并存),或等审批窗到期收割(同 7.148.0 节第 7 条);7.148.0 起无需动作。**升级前做**:无。**回滚**:无关(无存储形变化;回到 7.141.0 至 7.147.0 即回到 expire 的形)。
248
+
249
+ 30. **没有把 web-admin 当配置源的进程上,缺省模型检查的拒启句(`MODEL_ID is required when no catalog default resolves [config.default_model_missing] …`)句尾只说 `MODEL_ID` 与 `config.d/models.json` 两条出路;web-admin 是配置源时整句逐字不变;句首锚两形都不动(行为变更;拒启句文字;wire 零变化;部署零动作)**:
250
+ 旧形(≤ 7.151.x):这句拒启句对谁都在句尾说「…enable at least one model this host can serve in the catalog (config.d/models.json, or the web-admin) — either one is enough (a web-admin that answers after the boot budget counts as absent at boot: raise CONFIG_BOOT_FETCH_BUDGET_MS to wait for it).」—— 包括没有 web-admin 当配置源的进程(本机 `sema` 一次性入口、`CONFIG_PROVIDER=local`、纯 env),后半句对它们不是出路。
251
+ 新形:句尾按「web-admin 是不是这个进程的配置源」分两形。是 ⇒ 整句逐字不变(上面那一句,含 web-admin 与 `CONFIG_BOOT_FETCH_BUDGET_MS`);不是 ⇒ 句尾只剩「…in the catalog (config.d/models.json) — either one is enough.」,不出现 web-admin 与 `CONFIG_BOOT_FETCH_BUDGET_MS`。句首 `MODEL_ID is required when no catalog default resolves [config.default_model_missing] — this model table has no default model` 到「. Set MODEL_ID=」之前的整段两形逐字同(被拒候选的「回落原因」段、「every enabled catalog entry is unavailable …」段也两形同);目录腿(候选自己没有缺省)的句子不变。7.147.0 节第 27 条写的「没有被拒候选时整句逐字不变」读作:web-admin 是配置源的进程上逐字不变(7.147.0 节原句不改字节,本条即订正读法;契约里的同一句已按这里改)。
252
+ 同一条律多盖两句:路由端点判官的 env 腿两句拒因(`… resolve to no endpoint [config.model_route_endpoint_missing] …` 与 `… ask for the anthropic lane …`)的第三条出路原也对谁都写「(3) leave MODEL_ID unset and let the default entry of a catalog (config.d/models.json, or the web-admin) serve」。现在同一件事实(`configSourceKind(config) === "remote"`)、同一个腿类型(`ModelRefusalLeg`)分两形:web-admin 是配置源 ⇒ 整句逐字不变;不是 ⇒ 括号只剩 `(config.d/models.json)`,不再提 web-admin。事实由调用点递进来,判官函数自己不读 env、不读配置;句子里「`CONFIG_REQUIRE_ROSTER=true` on a remote registry」那一句不动(名册布防是中心接入的事实,与目录从哪来是两件事);目录腿的句子本来不提 web-admin。**嵌入方(破坏性,编译红)**:包根导出的 `judgeServedModelRoute(config, opts)` 的 `opts` 多一个必填键 `webAdminIsConfigSource`(没有配置源的自建装配传 `false`;有中心且 `CONFIG_PROVIDER` 缺席或为 `remote` 的装配传 `configSourceKind(config) === "remote"` 的值);判官在源码里的抛出形 `assertServedModelRoute` 对应的是没有目录要应用的直接装配,它不跑配置源,恒按「不是」。
253
+ 判据只有一个事实 —— web-admin 是不是这个进程的配置源(配置源判官 `configSourceKind` 答 `remote`):不写 `CONFIG_PROVIDER` 且配了中心 ⇒ 是;`CONFIG_PROVIDER=remote` ⇒ 是;`CONFIG_PROVIDER=local`(含 `sema` / `run-local` 一次性入口,它只装本地配置源)⇒ 否,哪怕配了中心(中心照样管 fleet 面,模型目录只读本地 `config.d`);纯 env ⇒ 否。判官函数自己不读 env、不读配置,这一格由调用点递进来(少递编译红)。
254
+ **已知限制(如实)**:纯 env 进程(不写 `CONFIG_PROVIDER`、没配中心)没有配置源,读不到 `config.d`;它拿到的短句里第二条出路 `config.d/models.json` 要先设 `CONFIG_PROVIDER=local` 才成立,本条没有改这一点(旧形里这一半同样在句子里)。
255
+ **谁需要**:本机个人形(`sema` 一次性入口、`CONFIG_PROVIDER=local`)起引擎没配模型的人 —— 句尾不再指向与他们无关的 web-admin 与启动预算旋钮。**谁受伤**:按整句逐字匹配这句拒启句的脚本(没有 web-admin 配置源的进程上句子变短;按句首 `MODEL_ID is required when no catalog default resolves [config.default_model_missing]` 匹配的不受影响)。**补偿 / 出路**:按码 `config.default_model_missing` 或句首匹配(壳与运维脚本本来认的就是句首)。**升级前做**:无。**回滚**:回到 7.151.x ⇒ 句子回到对谁都说 web-admin 的那一句。
256
+
257
+ 31. **续跑家族的请求在飞时被取消,200 体多 `bindingEnforced: true` 与这次取消的终局句 `errorMessage`(wire 加性;零新码;零 DDL;部署零动作)**:
258
+ 旧形(7.151.x 及以前):`POST /v1/sessions/:id/wake`、`POST /v1/approvals/:sessionId/decide`、`POST /v1/assistant/tasks/:id/resume`、`/plan_review` 的请求在受理之前被取消(`POST /v1/runs/:id/cancel`)⇒ 200 `{ taskId?, sessionId, status: "failed", errorCode: "cancelled" }`,没有句子,也没有 `bindingEnforced`。
259
+ 新形:同一只体多两键 —— `bindingEnforced: true`(与这几扇门的受理体 / 结果体同一位)与 `errorMessage`(这次取消的终局句,与同一 run 的 `GET /v1/runs/:id` 的 `error` / `result.terminal.message` 逐字同;没有 run 行的那一形同样带,`taskId` 缺席)。`retriable` 照旧不在取消体上。体的码与句读这条 run 行上落下的那一份终局:取消落下之后、本腿驱行终局之前行已被别的写者先收成终局(收割 / 判死 `run.stalled`;终局写先到者得)的那一形,体上是行上那一份的码与句(此前恒答 `cancelled`,而 `GET /v1/runs/:id` 答先到的那一份)。
260
+ **如实**:sdk 的契约(wake 200 schema)把取消形写成带 `bindingEnforced: true` 并列为必填,而本服务的取消体自这一位上 wire(5.4.0)至 7.151.x 从没带过它 —— 严格按那份 schema 校验的客户端此前会拒收这只真响应,本版起不再。
261
+ **谁需要** / **谁受伤** / **补偿**:同 CHANGELOG 本节同名一条(零新旋钮)。**升级前做**:无;按键集逐字断言取消体的脚本补两键。**回滚**(本版 → 7.151.x):无持久面;回滚后取消体回到少这两键的形。
262
+
263
+ 32. **🔴 提货 `@sema-agent/settings-schema` 26.0.0(引擎 7.66.1 不变):fleet 心跳与 `/health` 的 8.0 平铺引擎态方言停发;`settings.hooks.PermissionDenied` 在服务端跑;目录条目头袋不是对象的条目被丢、唯一 / 被引用时候选整份拒;`STREAM_APPROVAL_ENABLED` 留着即拒启(wire BREAKING;行为变更;零 DDL;部署动作:删掉退役键、头袋改形)**:
264
+ 旧形(≤ 7.151.x,settings-schema 25.1.0):① 心跳体顶层带 `ready` / `draining`(恒在)、`restart` / `generation` / `leaseState` / `leaseUntil`(在场律同 `engineSignal` 的同名键);`/health` 排空中带 `draining: true` 与 `drainingSince`。② `settings.hooks.PermissionDenied` 契约判给壳,服务端只记一行 debug、从不跑。③ `models[].headers` 是解析透明键:数组 / 串等非对象头袋照进读腿,由本仓判那一条目不可用(`config.model_entry_invalid` 点名 `headers`),其余模型照用。④ 本版其余提交已不读 `STREAM_APPROVAL_ENABLED`,留着它悄悄不起作用。⑤ 本机调度店里仍按会话链键存的 durable 记录(核心 5.5.0 之前写下)每次读被契约重铸到 durable 键(读时改写、下一个写者落盘)。⑥ 存量旧形(`models.atModelAllowlist` / `models.default`、带事实的 `runtime.json`、URL 带 `#`、同设两种钥匙的模型条目)的拒句与告警句尾带一段一次性迁移指路(迁移 id + `npx @sema-agent/settings-schema migrate <config.d directory>`),被拒文档(墓碑)另带迁移 id。
265
+ 新形:① **读引擎态的消费方只读 `engineSignal`**(契约 `EngineSignal`;`readEngineSignal(body)`)—— 心跳体顶层零引擎态键;`/health` 不带 `draining` / `drainingSince`(排空起点 = `engineSignal.draining.since`);`/health` 顶层的 `ready` / `reasons` / `restart` / `restartRequired` / 成员三键 / 世代账是本服务自己的诊断键,照发。② 引擎在门上拒一次调用(策略拒、问被裁成拒、权限模式拒、合规拒、钩子筛查失败的失效关闭)⇒ 跑一次 `PermissionDenied` 条目(观察者,matcher 对 `tool_name`,答 `retry` 无处兑现、记 `hook_result_unsupported`);fresh 提交里这个事件上选中工具别名却选不中正名的 matcher ⇒ `400 request.field_invalid`(与另三个工具事件同句)。③ **`models[].headers` 不是纯 JSON 对象的条目被丢**:有未被引用的兄弟 ⇒ 只丢它(逐条丢弃行 `config_entry_dropped` 点名 `models.<下标>` 与 `headers`,候选照收);它是唯一一条、或被 `roles` / `atMention` / `tierGroups` 引用 ⇒ `models` 整域退缺省 ⇒ **候选整份拒**(`config_candidate_rejected`,`errors[].error` = `models.<下标>.headers: headers must be a plain JSON object`;`GET /v1/models` 的 `candidateRejected.domains` 含 `models`;在服务的留在上一份整套落地的配置,起服时回落链同前)。**运维怎么看见**:上面两行运维日志 + `GET /v1/models.candidateRejected`;拒句点名的是下标与键,不点名条目名(契约这一形的告警不带身份)—— 照下标找那一条。④ env 里留着 `STREAM_APPROVAL_ENABLED`(任何值)⇒ 拒启,拒句带 `config.env_key_retired`、点名这一键、不回显值;**出路 = 删掉它**(没有替代键)。⑤ 那种 durable 记录逐字读出、留在会话键上、无告警:别的会话按 durable 键列不出、取消不了它(出路见「升级前做」⑤)。⑥ **存量旧形的拒句与告警止于今天的位 / 规则本身**(契约到期删码):不再点名迁移 id、不再给 `migrate` 命令行,被拒文档(墓碑)不再带迁移 id —— 逐条丢弃行 `config_entry_dropped`、整域行 `config_domain_invalid`、拒行 `config_candidate_rejected` 的 `errors[].error`、`config.d/remote-exec.json` 的拒启句同读;句子仍点名键与今天的位、不回显值。本版带的契约包的 `migrate` 也不再改写这几形(第 18 条)。
266
+ **升级前做**:① 删掉 `STREAM_APPROVAL_ENABLED`;② 对每一份存着的 `models` 文档(本机 `config.d/models.json`;中心的草稿 / 已发快照 / 回滚史)跑契约包 26.0.0 CHANGELOG「三问 · 补偿」下那一条自查命令(零输出 = 与你无关),把列出的条目的 `headers` 改成对象、或删掉该键;③ 读心跳顶层或 `/health.draining` / `drainingSince` 的编排 / 脚本改读 `engineSignal`;④ 不想在服务端跑的 `PermissionDenied` 条目从请求的 `settings.hooks` 里拿掉,写了别名 matcher 的改写正名;⑤ 用本机调度店的部署对店文件跑契约包 26.0.0 CHANGELOG「到期删码 · 补偿」下那一条自查命令,把列出的 durable 记录的 `scope` 改成它打印的 durable 键(此后读口不再替你重铸);⑥ 存量里还有旧形(见 ⑥)而想自动改写的,升级前用上一个契约 major 跑一次 `npx @sema-agent/settings-schema@25 migrate <config.d 目录>`(先备份),否则按拒句把事实手写到今天的位。**回滚**(本版 → 7.151.x):平铺键回来、`PermissionDenied` 回到不跑、头袋回到本仓判条目;零持久面改动。
267
+
268
+ 33. **`server.model_unreachable` 通告的建议句按失败类分:直连拒连、直连解析不了这两形换了句,超时 / 无路由 / 走了代理的形逐字不变(行为变更;`detail.remedy` 的值与正文末句;wire 键与键形不变;零新码;部署零动作)**:
269
+ 旧形(≤ 7.151.x):对所有建连失败,正文末句都是 `Check the VPN / proxy; if the proxy changed since the server started, restart it (proxy settings are read once at startup).`,`detail.remedy` 都是 `check VPN / proxy; restart if the proxy changed since boot`。
270
+ 新形:建议句取自失败类上的一张闭表,正文末句与 `detail.remedy` 是同一行的两句。这一跳走了代理(`detail.proxyInUse` 在场)⇒ 通用句(即旧形那一句,逐字);否则按 `detail.code` 的类 ——
271
+ · `ECONNREFUSED`(拒连)⇒ 正文末句 `Check that a service is listening at that address and that the model gateway address is configured correctly.`,`remedy` = `check that a service is listening at the model endpoint address; check the model gateway address configuration`(不提 VPN);
272
+ · `ENOTFOUND` / `EAI_AGAIN`(解析不了)⇒ 正文末句 `Check that the model gateway address is written correctly in the configuration and that DNS (and the VPN, if one is in use) can resolve it.`,`remedy` = `check the model gateway address configuration; check DNS / VPN`;
273
+ · `UND_ERR_CONNECT_TIMEOUT` / `EHOSTUNREACH` / `ENETUNREACH`(建连超时 / 无路由 / 网络不可达)⇒ 通用句,逐字不变。
274
+ 通告码、`detail` 其余键(`code` / `targetHost` / `proxyInUse?` / `sessionId` / `taskId?`)、「每腿每目标一次」的去重都不变;建议句(正文末句与 `remedy`)里没有任何配置值、代理地址、凭据(模型端点的 `host:port` 仍只在正文前半句与 `detail.targetHost`,与 7.151.x 同)。
275
+ **谁需要**:在本机起模型服务、或把模型网关地址写错了的用户 —— 没用 VPN、没走代理的人不再被指去查 VPN。**谁受伤**:按整句匹配 `detail.remedy`(或正文末句)的消费方 —— 直连拒连、直连解析不了这两形上句子变了;超时 / 无路由 / 走代理的形逐字不变。**补偿 / 出路**:按通告码 `server.model_unreachable` 与 `detail.code` / `detail.proxyInUse` 判,不匹配建议句(引擎码册里本码的 `message` 本就只作 fallback 展示)。**升级前做**:无。**回滚**:无关(无存储形变化;回到 7.151.x 即回到对所有失败类说通用句的形)。
276
+
11
277
  ## 7.151.0 —— 1–22 条见下
12
278
 
13
279
  **升级清单(本节唯一的一张;下面各条只讲为什么,不再各自重述升级步骤)** —— 一次读清本版对一只存量部署要做的全部动作。每版同形:① 手工 DDL(逐表逐句,两方言各一份)② 起服自建与一次性迁移 ③ 停-迁-起 ④ 顺序;后续每版的段头照此写,新增一句 `ALTER`、一条一次性迁移、一条停-迁-起时,先改这里,再写条目。
package/README.md CHANGED
@@ -158,8 +158,8 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
158
158
  up to the point where the model table is settled, never listens, and prints that same `modelPlane` JSON as one stdout line (byte-identical to the started engine's
159
159
  reading at the end of stage 2; when the boot pull overruns `CONFIG_BOOT_FETCH_BUDGET_MS` it is the fallback served before that pull lands); where the start-up would
160
160
  refuse in those stages it exits non-zero with the start-up's own `fatal` line.
161
- The start-up's own route judge runs **after** the catalog is applied, not in `loadConfig()`: a hand-built assembly calls `judgeServedModelRoute(config, { envTableIsPlaceholder })`
162
- (package root) once after its own config-source apply, like `composeExecutionLane`.
161
+ The start-up's own route judge runs **after** the catalog is applied, not in `loadConfig()`: a hand-built assembly calls `judgeServedModelRoute(config, { envTableIsPlaceholder, webAdminIsConfigSource })`
162
+ (package root) once after its own config-source apply, like `composeExecutionLane` (`webAdminIsConfigSource`: whether a web-admin is that assembly's config source — `false` for an assembly with none; it only decides whether the env-table refusal sentence names the web-admin as a way to get a default catalog).
163
163
  A runnable reference of this whole chain ships in the package: `node scripts/embed-minimal.mjs` (from the package directory) assembles the nine seats `createHttpServer` requires
164
164
  (package-root factories, the engine's `Runner`, and a one-line `resolveSpec` of its own), serves on the host lane from a throwaway data root on a kernel-assigned loopback port,
165
165
  and prints `EMBED-MINIMAL ok port=<n>`; `--omit <seat>` leaves one seat out and says whether `createHttpServer` refused it at construction (`refused`, exit 2) or took it (`accepted`, exit 3).
@@ -211,7 +211,7 @@ The server is configured entirely through environment variables. The most import
211
211
  | Variable | Default | What it does |
212
212
  |----------|---------|--------------|
213
213
  | `PORT` | `8090` | HTTP listen port. `0` = let the OS pick one: the `listening` startup line (stdout, one JSON line at info level; a startup protocol line, not filtered by `LOG_LEVEL`) and `/health.port` report the port actually bound; the same line's `boundAddress` is the address the kernel actually bound |
214
- | `BIND_HOST` (alias `HOST`) | see note | Listen address. An explicit `BIND_HOST` **always wins**. Default: `127.0.0.1` when the write face is unauthenticated (`ALLOW_UNAUTHED_WRITES=true` **and** no service token configured), otherwise all interfaces — deployments with a token are unaffected. Since 3.15.0 the `HOST` alias is **not** fully equivalent: shells commonly set `HOST` to the machine name without the operator knowing, so when the narrowing condition above holds it wins over an inherited `HOST` (logged as `bind_host_from_HOST_env_overridden`). To expose the write face, configure a service token — on a single-user deployment (`REQUIRE_PRINCIPAL` off) an explicit non-loopback `BIND_HOST` with an unauthenticated write face refuses to boot (invariant `single-user-anonymous-boundary`); on a multi-tenant deployment an explicit `BIND_HOST` still exposes it. |
214
+ | `BIND_HOST` | see note | Listen address — the only source (`HOST` is not read: shells commonly set it to the machine name without the operator knowing). An explicit `BIND_HOST` **always wins**. Default: `127.0.0.1` when the write face is unauthenticated (`ALLOW_UNAUTHED_WRITES=true` **and** no service token configured), otherwise all interfaces — deployments with a token are unaffected. To expose the write face, configure a service token — on a single-user deployment (`REQUIRE_PRINCIPAL` off) an explicit non-loopback `BIND_HOST` with an unauthenticated write face refuses to boot (invariant `single-user-anonymous-boundary`); on a multi-tenant deployment an explicit `BIND_HOST` still exposes it. |
215
215
  | `MODEL_GATEWAY_BASEURL` | **no default** | OpenAI-compatible gateway base URL (without `/chat/completions`). **No factory default**: every route that rides the openai-compatible lane must resolve to an explicit endpoint — the model's own catalog `baseUrl`, or this knob — else the server refuses to boot, naming the models and printing the explicit local spelling (`MODEL_GATEWAY_BASEURL=http://127.0.0.1:8000/v1`). An Anthropic-only deployment may leave it unset. Setting `MODEL_GATEWAY_FALLBACK_URLS` without it is refused too |
216
216
  | `MODEL_ID` | **required unless a catalog supplies the default** | Default model id — **no factory default since 3.0.0**. Since 7.101.0 the check runs **after** the catalog is applied: `MODEL_ID` **or** a usable catalog default (`config.d/models.json` / the web-admin) is enough; with neither the server refuses to boot (`config.default_model_missing`, naming both ways out). A catalog that is adopted replaces the env model table, so `MODEL_ID` is then not served: the boot is not refused (even with no gateway, when the catalog entries carry their own `baseUrl`) and logs one `config_env_set_but_ineffective` line naming the catalog default that serves (exactly once, at the moment the catalog is first adopted — at boot, or later when it arrives after the boot fetch budget / on a refresh). The old baked-in default was an internal-only model name, so every external deployment failed later and further from the cause: a gateway `400` plus a cascade of title-hook warnings |
217
217
  | `MODEL_API_KEY` | — | Gateway API key (optional). Pairs only with `MODEL_GATEWAY_BASEURL`: with no gateway it is not loaded and sent nowhere |
@@ -265,6 +265,17 @@ are read from this process's own environment, never from the file. The file is e
265
265
  trusted directory (owner and mode are checked before every read; on Windows only the directory structure can be checked). Where the file lives, the entry keys, the credential boundary,
266
266
  the posture of an anonymous (no-principal) caller and how it relates to a request body's `mcpServers` are in `USAGE.md` §9.5i-ter.
267
267
 
268
+ Object storage (MinIO / S3): the server never creates buckets and uses no S3 SDK — every request is a SigV4 presigned URL signed with the
269
+ engine's one key (`MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY`). What it does to which bucket is one machine-readable table, importable without
270
+ the server's dependency graph: `import { OBJECT_STORE_ACTIONS, OBJECT_STORE_ACTION_PERMISSIONS, objectKeyGlob } from "@sema-agent/server/object-store-buckets"`
271
+ — one row per request (bucket knob, key form, S3 action, who sends it: the server, the execution sandbox holding only the presigned URL, or the
272
+ end user's client; URL lifetime). A tool that issues least-privilege object-store credentials per engine can generate the policy from it; the
273
+ same table rendered as the per-bucket permission list is the deployment prerequisites list's 「对象存储桶」 section (internal record) — generate it from `OBJECT_STORE_ACTION_PERMISSIONS` × `objectKeyGlob`.
274
+ A per-engine policy only separates the rows whose key starts with a per-engine prefix (`objectStoreRowEngineSlot(row)` names that slot; 圈不住 = it returns nothing):
275
+ the SendUserFile tracks (`sendfile/…`, the public bucket's root) and the leader's patches (`leader-diff/…`) are the same objects for every engine on the bucket, so engines that
276
+ share a bucket share those rows whatever their policy says — and engines that share the session snapshot bucket must each set their own `SESSION_SNAPSHOT_PREFIX` (a second
277
+ database on the same bucket × prefix is refused at boot). Every signed URL lives at most `SIGV4_PRESIGN_MAX_SEC` (604800 s).
278
+
268
279
  ## Security & privacy note
269
280
 
270
281
  > 🔴 **Trace redaction ≠ egress control** (anchor: 脱敏 ≠ 出站控制). Trace redaction (the `«redacted»` markers you see in the
@@ -287,6 +298,7 @@ One row per endpoint family (not exhaustive):
287
298
  | `GET /health` · `GET /readyz` · `GET /metrics` | Liveness, readiness (`/readyz`: 200 / 503 for status-code-only probes such as a k8s `readinessProbe`) + Prometheus metrics (`/metrics/summary`, `/metrics/plan-cache`) |
288
299
  | `GET /v1/capabilities` | Deployment capability discovery — what this deployment can actually do, so clients never probe 501s |
289
300
  | `GET /v1/capabilities/mcp` · `POST /v1/capabilities/mcp/probe` | Per-server MCP status with **no run required**: dial each declared server, list its tools, close it again — the first for this deployment's own declarations, the second for a caller's `.mcp.json`. Rows are the engine's own wiring-manifest rows, so a one-shot command and an interactive session read the same verdict. Rate-capped per caller and cached briefly (it really dials) |
301
+ | `GET /v1/capabilities/scenario` | Which scenario this request takes effect under and where it came from — `{ scenario, source }` (`request` / `ruling` / `deployment`), the same answer a submission with the same request shape gets; optional `?scenario=` (or the `x-agent-scenario` header) names one, refusals are the submission's own (capability bit `effectiveScenario`) |
290
302
  | `GET /v1/models` | Model catalog (names only; no gateway URLs or keys) |
291
303
  | `POST /v1/tasks` · `/v1/tasks/stream` | Synchronous task execution; SSE variant streams typed `TaskEvent`s token by token |
292
304
  | `POST /v1/runs` · `GET /v1/runs/:id` | Asynchronous runs: immediate `202`, background execution, poll for status/result |
package/README.zh-CN.md CHANGED
@@ -126,7 +126,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
126
126
  (`{ servedTable: "env" | "catalog", catalog: "absent" | "adopted" | "refused", refusalCode?, gap? }`)—— 问 server,不猜:判官的输入是服务进程的事实。
127
127
  引擎起来之前要同一个答案:跑包里的 `dist/model-plane-probe.js`(子路径 `@sema-agent/server/model-plane-probe`;env / 工作目录 = 将要起的那只引擎的),
128
128
  它跑起服的前两段到模型表定形即停、不监听,stdout 一行就是同一格 JSON(与起服第 2 段末的读数字节同;起服拉取超窗时是拉取落地之前的回落读数);起服会在这两段拒时退非 0,拒句与起服同一行。
129
- 起服自己的路由判官在**目录应用之后**判,不在 `loadConfig()` 里:自建装配在自己的配置源应用之后调一次包根的 `judgeServedModelRoute(config, { envTableIsPlaceholder })`(与 `composeExecutionLane` 同形)。
129
+ 起服自己的路由判官在**目录应用之后**判,不在 `loadConfig()` 里:自建装配在自己的配置源应用之后调一次包根的 `judgeServedModelRoute(config, { envTableIsPlaceholder, webAdminIsConfigSource })`(与 `composeExecutionLane` 同形;`webAdminIsConfigSource` = web-admin 是不是这个装配的配置源,没有配置源的装配传 `false`,只决定 env 表拒因句是否把 web-admin 说成让目录缺省来服务的出路)。
130
130
  - **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
131
131
  `sema-up`(部署引导脚本)。
132
132
  - `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
@@ -165,7 +165,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
165
165
  | 变量 | 默认 | 说明 |
166
166
  |------|------|------|
167
167
  | `PORT` | `8090` | HTTP 监听端口。`0` = 让 OS 分一个:`listening` 启动行(stdout 一行 JSON,info 级;启动协议行,不随 `LOG_LEVEL` 滤掉)与 `/health.port` 报实际绑到的口;同一行的 `boundAddress` 是内核实际绑上的地址 |
168
- | `BIND_HOST`(别名 `HOST`) | 见说明 | 监听地址。显式 `BIND_HOST` **恒生效**。缺省:写面无鉴权时(`ALLOW_UNAUTHED_WRITES=true` **且**未配任何 service token)= `127.0.0.1`,否则全接口——配了 token 的部署不受影响。自 3.15.0 起 `HOST` 别名**不再完全等效**:shell 常把 `HOST` 设成机器名而 operator 并不知情,于是上述收窄条件成立时,收窄压过继承来的 `HOST`(告警事件 `bind_host_from_HOST_env_overridden`)。要暴露写面,配一个 service token —— 单用户部署(`REQUIRE_PRINCIPAL` 未开)上写面无鉴权时显式非回环 `BIND_HOST` 起服拒(不变量 `single-user-anonymous-boundary`);多租部署上显式 `BIND_HOST` 照旧暴露。 |
168
+ | `BIND_HOST` | 见说明 | 监听地址 —— 唯一来源(不读 `HOST`:shell 常把 `HOST` 设成机器名而 operator 并不知情)。显式 `BIND_HOST` **恒生效**。缺省:写面无鉴权时(`ALLOW_UNAUTHED_WRITES=true` **且**未配任何 service token)= `127.0.0.1`,否则全接口——配了 token 的部署不受影响。要暴露写面,配一个 service token —— 单用户部署(`REQUIRE_PRINCIPAL` 未开)上写面无鉴权时显式非回环 `BIND_HOST` 起服拒(不变量 `single-user-anonymous-boundary`);多租部署上显式 `BIND_HOST` 照旧暴露。 |
169
169
  | `MODEL_GATEWAY_BASEURL` | **无缺省** | OpenAI 兼容网关地址(不带 `/chat/completions`)。**无出厂缺省**:每一条落 openai 兼容腿的路由都必须解析到显式端点 —— 目录条目自己的 `baseUrl`,或本键 —— 否则拒启,拒因点名模型并给出本机写法(`MODEL_GATEWAY_BASEURL=http://127.0.0.1:8000/v1`)。anthropic 单路由部署可不设。只设 `MODEL_GATEWAY_FALLBACK_URLS` 不设本键同拒 |
170
170
  | `MODEL_ID` | **必填(目录给出缺省时可省)** | 缺省模型 id ——**3.0.0 起无出厂缺省**。7.101.0 起判据挪到目录应用**之后**:`MODEL_ID` **或**目录里一条可用的缺省(`config.d/models.json` / 配置控制面)任一在场即可;两者皆无 = 拒启(码 `config.default_model_missing`,指路二选一)。目录一旦被采纳就整张换掉 env 表,`MODEL_ID` 此时不服务:起服不拒(目录条目自带 `baseUrl` 时没有网关也能起),并说一行 `config_env_set_but_ineffective` 点名在服务的目录缺省(目录第一次被采纳的那一拍恰好说一次 —— 启动时,或目录晚到 / 刷新时才第一次采纳)。旧的烤死缺省是内网模型名,外部部署必炸且炸在离根因最远处:网关 `400` + 标题 hook 连环告警 |
171
171
  | `MODEL_API_KEY` | — | 网关 key(可选)。只与 `MODEL_GATEWAY_BASEURL` 配对:没设网关时不装载、不发往任何路由 |
@@ -210,6 +210,13 @@ Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`
210
210
  (`envRefs` / `headerRefs`),值从本进程自己的环境里读、从不来自文件。这份文件是可执行配置,所以只从受信目录读(每次读之前判属主与模式;Windows 上只判得了目录结构)。
211
211
  文件放哪、条目有哪些键、凭据的边界、匿名(不带 principal)调用方的姿态、以及它与请求体 `mcpServers` 的关系,见 `USAGE.md` §9.5i-ter。
212
212
 
213
+ 对象存储(MinIO / S3):本服务不建桶、不用 S3 SDK —— 每个请求都是用引擎的那一对钥(`MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY`)签的 SigV4 预签名 URL。
214
+ 它对哪只桶做什么是一张机读表,不带本服务的依赖图即可 import:`import { OBJECT_STORE_ACTIONS, OBJECT_STORE_ACTION_PERMISSIONS, objectKeyGlob } from "@sema-agent/server/object-store-buckets"`
215
+ —— 一个请求一行(桶旋钮、key 形、S3 动作、谁发:server 进程 / 只持预签名 URL 的执行沙箱 / 拿链接的用户端、URL 有效期)。按引擎发最小权限对象存储凭据的工具据它生成策略;
216
+ 同一张表按桶渲染成的权限清单是部署前置清单「对象存储桶」一节(内部记录),由 `OBJECT_STORE_ACTION_PERMISSIONS` × `objectKeyGlob` 生成。
217
+ 按引擎的策略只圈得住 key 以每引擎前缀开头的行(`objectStoreRowEngineSlot(row)` 给出那只槽;没有 = 圈不住):SendUserFile 三轨(`sendfile/…`、公桶根)与 leader 补丁(`leader-diff/…`)对共桶的每台引擎是同一批对象,
218
+ 共桶的引擎不论策略怎么写都共用这些行;共用会话快照桶的引擎必须各设各的 `SESSION_SNAPSHOT_PREFIX`(同桶同前缀的第二只库起服即拒)。每只签名 URL 至多活 `SIGV4_PRESIGN_MAX_SEC`(604800 s)。
219
+
213
220
  ## 安全与隐私提示
214
221
 
215
222
  > 🔴 **trace 脱敏 ≠ 出站控制。** trace 脱敏(你在账本 / SSE / turns / 审批预览里看到的 `«redacted»`
@@ -230,6 +237,7 @@ Web search(部署 env `WEB_SEARCH_*` 七键、per-request `settings.webSearch`
230
237
  | `GET /health` · `GET /readyz` · `GET /metrics` | 存活探针 + 就绪探针(`/readyz`:200 / 503,给只认状态码的探针,如 k8s `readinessProbe`)+ Prometheus 指标(`/metrics/summary`、`/metrics/plan-cache`) |
231
238
  | `GET /v1/capabilities` | 部署能力发现 —— 本部署真正能做什么,客户端免 501 探测 |
232
239
  | `GET /v1/capabilities/mcp` · `POST /v1/capabilities/mcp/probe` | **无需先跑一条 run** 的逐台 MCP 状态:现连、列工具、关掉 —— 前者答本部署自己申报的服务器,后者答调用方自带的 `.mcp.json`。行就是引擎装配名册里的那一行,所以一次性命令与交互会话读到同一个判定。它会真拨号,所以按调用方限速 + 短窗复用 |
240
+ | `GET /v1/capabilities/scenario` | 这次请求会生效哪只场景、它从哪一层来 —— `{ scenario, source }`(`request` / `ruling` / `deployment`),与同一请求形的提交拿到的是同一只答;可选 `?scenario=`(或场景头 `x-agent-scenario`)点名,拒与提交腿同码同句(能力位 `effectiveScenario`) |
233
241
  | `GET /v1/models` | 模型目录(仅名字,不含网关 URL/key) |
234
242
  | `POST /v1/tasks` · `/v1/tasks/stream` | 同步任务执行;SSE 变体逐 token 流式输出类型化 `TaskEvent` |
235
243
  | `POST /v1/runs` · `GET /v1/runs/:id` | 异步 run:立即 `202`,后台续跑,轮询状态/结果 |