@sema-agent/server 7.103.2 → 7.105.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 (204) hide show
  1. package/MIGRATION.md +378 -2
  2. package/README.md +6 -1
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +320 -16
  5. package/deploy/sema-up/README.md +57 -7
  6. package/deploy/sema-up/cluster-up.sh +18 -4
  7. package/deploy/sema-up/kube-up.sh +16 -0
  8. package/deploy/sema-up/lib/dataroot.sh +142 -0
  9. package/deploy/sema-up/lib/flagshape.sh +69 -0
  10. package/deploy/sema-up/lib/lanes.py +14 -4
  11. package/deploy/sema-up/lib/shell-access.sh +41 -0
  12. package/deploy/sema-up/preflight.sh +14 -1
  13. package/deploy/sema-up/sema-up.sh +134 -37
  14. package/deploy/sema-up/smoke.sh +5 -0
  15. package/dist/auto-mode-face.js +3 -4
  16. package/dist/boot/budget-tracing.js +1 -1
  17. package/dist/boot/config-center.d.ts +6 -2
  18. package/dist/boot/config-center.js +5 -59
  19. package/dist/boot/execution-env.d.ts +11 -0
  20. package/dist/boot/execution-env.js +14 -0
  21. package/dist/boot/execution-lane.d.ts +25 -0
  22. package/dist/boot/execution-lane.js +95 -0
  23. package/dist/boot/memory-consolidation.d.ts +185 -9
  24. package/dist/boot/memory-consolidation.js +192 -32
  25. package/dist/boot/resolve-spec.d.ts +2 -0
  26. package/dist/boot/resolve-spec.js +28 -24
  27. package/dist/boot/runner-deps.d.ts +6 -2
  28. package/dist/boot/runner-deps.js +6 -1
  29. package/dist/boot/session-faces.d.ts +5 -0
  30. package/dist/boot/session-faces.js +18 -2
  31. package/dist/boot/session-incarnation-seat.d.ts +41 -0
  32. package/dist/boot/session-incarnation-seat.js +69 -0
  33. package/dist/boot/stage-01-config.js +2 -2
  34. package/dist/boot/stage-02-registry-pull.js +3 -1
  35. package/dist/boot/stage-04-stores.d.ts +2 -0
  36. package/dist/boot/stage-04-stores.js +3 -1
  37. package/dist/boot/stage-05-execution-env.d.ts +4 -0
  38. package/dist/boot/stage-05-execution-env.js +2 -2
  39. package/dist/boot/stage-06-runners.d.ts +4 -1
  40. package/dist/boot/stage-06-runners.js +7 -14
  41. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  42. package/dist/boot/stage-08-reapers.d.ts +4 -1
  43. package/dist/boot/stage-09-leader.d.ts +6 -1
  44. package/dist/boot/stage-09-leader.js +2 -1
  45. package/dist/boot/stage-10-http-server.d.ts +6 -1
  46. package/dist/boot/stage-10-http-server.js +4 -1
  47. package/dist/brain.d.ts +1 -1
  48. package/dist/config-catalog.d.ts +16 -1
  49. package/dist/config-catalog.js +9 -9
  50. package/dist/config-center/restart-signal.d.ts +4 -4
  51. package/dist/config-invariants.d.ts +7 -1
  52. package/dist/config-invariants.js +37 -6
  53. package/dist/config-provider.d.ts +42 -10
  54. package/dist/config-provider.js +18 -16
  55. package/dist/config-types.d.ts +37 -16
  56. package/dist/config-types.js +1 -0
  57. package/dist/config.d.ts +28 -30
  58. package/dist/config.js +95 -77
  59. package/dist/env-facts.d.ts +13 -3
  60. package/dist/env-facts.js +15 -3
  61. package/dist/execution-lane-caps.d.ts +15 -0
  62. package/dist/execution-lane-caps.js +8 -0
  63. package/dist/file-history-capture.d.ts +42 -35
  64. package/dist/file-history-capture.js +15 -10
  65. package/dist/file-history-disclosure.d.ts +7 -31
  66. package/dist/file-history-disclosure.js +12 -0
  67. package/dist/fleet/fleet-bus.d.ts +5 -1
  68. package/dist/http/admission.d.ts +12 -5
  69. package/dist/http/admission.js +5 -1
  70. package/dist/http/dispatch.d.ts +18 -0
  71. package/dist/http/dispatch.js +19 -6
  72. package/dist/http/principal-gate.d.ts +22 -0
  73. package/dist/http/principal-gate.js +20 -0
  74. package/dist/http/resume-legs.d.ts +1 -1
  75. package/dist/http/resume-legs.js +9 -5
  76. package/dist/http/route-table.d.ts +15 -4
  77. package/dist/http/route-table.js +6 -5
  78. package/dist/http/routes/agents-roster.d.ts +7 -0
  79. package/dist/http/routes/agents-roster.js +6 -2
  80. package/dist/http/routes/approvals-assistant.d.ts +48 -19
  81. package/dist/http/routes/approvals-assistant.js +117 -50
  82. package/dist/http/routes/capabilities.js +3 -2
  83. package/dist/http/routes/config-catalog.js +1 -0
  84. package/dist/http/routes/fleet.d.ts +3 -2
  85. package/dist/http/routes/images.js +0 -1
  86. package/dist/http/routes/memory-consolidation.d.ts +3 -2
  87. package/dist/http/routes/memory-policy.js +3 -3
  88. package/dist/http/routes/rules.js +2 -2
  89. package/dist/http/routes/runs.js +29 -10
  90. package/dist/http/routes/session-sync.js +14 -15
  91. package/dist/http/routes/sessions-list.js +3 -2
  92. package/dist/http/routes/sessions.js +49 -25
  93. package/dist/http/routes/shared-memory.js +2 -2
  94. package/dist/http/routes/side-query.js +8 -1
  95. package/dist/http/routes/tasks.js +22 -19
  96. package/dist/http/routes/trace-usage.d.ts +2 -0
  97. package/dist/http/routes/trace-usage.js +8 -13
  98. package/dist/http/send.d.ts +12 -0
  99. package/dist/http/send.js +12 -1
  100. package/dist/http/server.d.ts +30 -8
  101. package/dist/http/server.js +11 -5
  102. package/dist/http/sse-lifecycle.d.ts +1 -1
  103. package/dist/http/sse-log.d.ts +12 -9
  104. package/dist/http/sse-log.js +1 -8
  105. package/dist/http/sync-submit-body.d.ts +43 -0
  106. package/dist/http/sync-submit-body.js +4 -0
  107. package/dist/http/wire-types.d.ts +23 -5
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +1 -0
  110. package/dist/leader/merge.d.ts +8 -3
  111. package/dist/memory-incident-sink.js +2 -1
  112. package/dist/model-select.d.ts +9 -0
  113. package/dist/model-select.js +5 -1
  114. package/dist/observability/corrupt-read-seat.d.ts +151 -16
  115. package/dist/observability/corrupt-read-seat.js +158 -15
  116. package/dist/observability/fail-open.d.ts +17 -1
  117. package/dist/observability/fail-open.js +17 -1
  118. package/dist/observability/metrics.js +1 -1
  119. package/dist/observability/run-terminal.d.ts +60 -10
  120. package/dist/observability/run-terminal.js +119 -9
  121. package/dist/parked-decide.d.ts +8 -3
  122. package/dist/parked-decide.js +56 -20
  123. package/dist/parked-revive-surface.d.ts +14 -7
  124. package/dist/parked-revive-surface.js +5 -1
  125. package/dist/plugins/approval-ask-store-file.d.ts +4 -1
  126. package/dist/plugins/approval-ask-store-file.js +9 -12
  127. package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
  128. package/dist/plugins/approval-ask-store-sql.js +4 -1
  129. package/dist/plugins/approval-exemption-store.d.ts +21 -1
  130. package/dist/plugins/approval-exemption-store.js +36 -7
  131. package/dist/plugins/approval-nonce-store.d.ts +16 -2
  132. package/dist/plugins/approval-nonce-store.js +17 -7
  133. package/dist/plugins/caching-session-store.d.ts +5 -0
  134. package/dist/plugins/caching-session-store.js +3 -0
  135. package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
  136. package/dist/plugins/file-resume-anchor-store.js +19 -5
  137. package/dist/plugins/file-run-store.d.ts +21 -8
  138. package/dist/plugins/file-run-store.js +60 -38
  139. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  140. package/dist/plugins/image-bake-store-sql.js +0 -5
  141. package/dist/plugins/local-session-store.d.ts +22 -0
  142. package/dist/plugins/local-session-store.js +74 -11
  143. package/dist/plugins/memory-run-store.d.ts +7 -5
  144. package/dist/plugins/memory-run-store.js +11 -4
  145. package/dist/plugins/one-time-migrations.d.ts +38 -1
  146. package/dist/plugins/one-time-migrations.js +143 -15
  147. package/dist/plugins/permission-rule-store-file.d.ts +9 -2
  148. package/dist/plugins/permission-rule-store-file.js +37 -51
  149. package/dist/plugins/pg-session-storage.d.ts +3 -0
  150. package/dist/plugins/pg-session-storage.js +19 -10
  151. package/dist/plugins/retention-store-sql.js +1 -0
  152. package/dist/plugins/run-store-sql.d.ts +15 -6
  153. package/dist/plugins/run-store-sql.js +15 -13
  154. package/dist/plugins/schema-indexes.js +1 -0
  155. package/dist/plugins/send-file-ledger.d.ts +5 -1
  156. package/dist/plugins/send-file-ledger.js +11 -6
  157. package/dist/plugins/session-event-payload.d.ts +14 -4
  158. package/dist/plugins/session-event-payload.js +5 -2
  159. package/dist/plugins/session-policy-store-sql.js +18 -5
  160. package/dist/plugins/store-backend.d.ts +13 -7
  161. package/dist/plugins/store-backend.js +10 -6
  162. package/dist/plugins/store-contracts.d.ts +7 -2
  163. package/dist/plugins/tidb-pool.js +12 -5
  164. package/dist/plugins/tidb-session-store.d.ts +6 -1
  165. package/dist/plugins/tidb-session-store.js +17 -10
  166. package/dist/project-identity.d.ts +4 -6
  167. package/dist/read-face-posture.d.ts +21 -0
  168. package/dist/read-face-posture.js +9 -0
  169. package/dist/request-key-closure.js +1 -1
  170. package/dist/request-value-closure.d.ts +72 -3
  171. package/dist/request-value-closure.js +12 -4
  172. package/dist/roster-bound-http-status.d.ts +6 -0
  173. package/dist/roster-bound-http-status.js +6 -0
  174. package/dist/rules-consent.js +0 -11
  175. package/dist/run-local.js +22 -27
  176. package/dist/runs.d.ts +22 -2
  177. package/dist/runs.js +29 -7
  178. package/dist/sealed-key.d.ts +31 -4
  179. package/dist/sealed-key.js +9 -0
  180. package/dist/security.d.ts +5 -1
  181. package/dist/session-children-retire.d.ts +92 -0
  182. package/dist/session-children-retire.js +85 -0
  183. package/dist/session-error-wire.d.ts +58 -0
  184. package/dist/session-error-wire.js +38 -0
  185. package/dist/session-incarnation.d.ts +61 -0
  186. package/dist/session-incarnation.js +43 -0
  187. package/dist/session-policy-wire.d.ts +98 -12
  188. package/dist/session-policy-wire.js +71 -8
  189. package/dist/session-records-skipped-notice.d.ts +20 -0
  190. package/dist/session-records-skipped-notice.js +27 -0
  191. package/dist/tool-approval.d.ts +65 -0
  192. package/dist/tool-approval.js +36 -9
  193. package/dist/trace/core-keyset-guard.d.ts +8 -7
  194. package/dist/trace/engine-notice-wire.d.ts +3 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/project.d.ts +11 -6
  197. package/dist/trace/project.js +12 -3
  198. package/dist/trace/redact.d.ts +7 -0
  199. package/dist/trace/redact.js +153 -61
  200. package/dist/trace/wire-projection-faces.d.ts +10 -3
  201. package/dist/trace/wire-projection-faces.js +8 -2
  202. package/package.json +2 -2
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
package/MIGRATION.md CHANGED
@@ -7,6 +7,382 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.105.0(车QQ 部分)—— 门前三族只读路要凭据(S-672)· 会话规则写口拒退役 / 无前缀工具名(S-800)· `ServiceDeps.runnerFor` 必填(S-175)
11
+
12
+ ### wire:HTTP / 错误码
13
+
14
+ - **🔴 行为面:门前三族只读路在配了 service 凭据的部署上要凭据(S-672)。** `GET /v1/tasks`(及 `…/:id/{turns,stream,artifacts,tool-results/:ref}`、
15
+ `/v1/tasks/source-summary`)、`GET /v1/usage/{summary,series,breakdown}`、`GET /v1/workflows[/:id[/journal|/stream]]`、`GET /v1/fleet/stream`:部署配了
16
+ `SERVICE_AUTH_TOKEN` / `SERVICE_AUTH_TOKENS` 时,请求必须带 service token、只读 `TRACE_TOKEN` 或验过的 principal(registry 登录令牌 / 直连门签名 principal 令牌)
17
+ 之一,否则 **401 `auth.unauthorized`**(与全局凭据门同一句)。只带调用方自写的 `x-agent-principal` 头不算。**迁移**:这几条读口带上与提交任务同一把
18
+ service token(或登录令牌);没配 service 凭据的部署不受影响。`/.well-known/agent-card.json` 不变(匿名)。契约 §0.56。
19
+ - **会话规则写口拒「写进去就跑不了」的工具名(S-800)。** `PUT /v1/sessions/:id/policy` 与 `POST /v1/sessions/:id/sync/import` 的 `policy` 面:`toolAllow` /
20
+ `toolDeny` 里有引擎退役名(`Task` / `KillShell` / `MultiEdit` / `bash` / `Recall` …)或含 `__` 却无协议前缀的名字 ⇒ **400 `rules.legacy_tool_name`**
21
+ (新码,体带 `field` / `name`)。7.104.x 及以前照收、之后这个会话每一跑拒启。**迁移**:换成现役名(句子里带指引)/ 给协议工具名加 `mcp__` 等前缀;
22
+ 对未知前缀族严格的消费方收下 `rules.` 族(400,改请求再写,原样重试不改判)。部署自己挂了与退役名同名工具的,写口仍拒 —— 走部署级工具策略。
23
+
24
+ ### 类型面与嵌入方
25
+
26
+ - **🔴 `ServiceDeps.runnerFor` 必填(S-175)。** 修前可选,缺席时每只任务静默跑在 `deps.runner`(最宽的那只)上;现在缺席 = 编译错,绕过类型的 deps 在
27
+ `createHttpServer` 入口抛(报文点名 `runnerFor`)。**迁移**:单 Runner 的嵌入方显式传 `runnerFor: () => runner`(一句表态,不是回落);按场景分有手 / 无手两只 Runner 的嵌入方传自己的选路函数(本仓自己的装配在 `boot/stage-10-http-server.ts`,该工厂不在包根导出面)。
28
+
29
+ ## 7.105.0(车QP 部分)—— 续读不再答 416 `limit.retention_evicted`(S-211 ①)· `TOOL_RESULT_TTL_SEC ≤ 0` 拒启(S-211 ⑤)· 记号表加索引(S-546 A9)
30
+
31
+ - **续读(`Last-Event-ID` / `?from=`)不再答 416 `limit.retention_evicted`**(`GET /v1/runs/:id/events`、`GET /v1/tasks/:id/stream`、
32
+ `GET /v1/images/bakes/:id/events`)。这个码从来判不出一次真淘汰:账本没有局部头部裁剪,唯一的删除是整棵会话树连删 —— run 行
33
+ 随之不在,开流前就是 **404 `not_found.run`**(不变)。唯一走得到那一臂的是「写失败耗号」留下的头部缺行,现在那一形照常 200、
34
+ 投 `seq > from` 的每一行。**消费端动作**:为 416 写的重同步分支可以删(它本来就走不到);按错误码闭集分派的客户端,
35
+ `limit.*` 族今天只剩 429。
36
+ - **`TOOL_RESULT_TTL_SEC` 设 `0` 或负数 ⇒ 拒启**(此前照收,效果是每拍收光全部卸载的工具结果;config center 发布同一个值一直整批拒)。
37
+ 没有「关」:想留久一点就设大。
38
+ - **`session_policy_rev_mark` 多一条索引 `idx_session_policy_rev_mark_session`(`session_id`)**:新建库随建表到货;存量库由启动期的
39
+ 幂等加索引补一次(MySQL 协议在线 `ADD INDEX`、PG `CREATE INDEX CONCURRENTLY`),零运维动作。托管留存的整树清扫从此把被删会话的
40
+ 记号行一并删掉。
41
+
42
+ ## 7.104.0 —— 最重三件:起服拒启收紧(多租户姿态 ∧ 不分租户的执行车道 / `CONFIG_PROVIDER` 闭集 / 退役负名与正名同设)· 同步提交口的 prepare 期拒在零工作时改答错误体(`corrupt_session` 500 / 名册超界三码 503 / `rewind.store_unconfigured` 409;做过工作的多级 run 末级被拒仍 200 + failed 体)· SQL `session_meta.incarnation` 新列 + 一次性迁移 M5(先升库再升副本)
43
+
44
+ > 本版迁移面按面分五小节:wire(HTTP / SSE / 错误码 / 帧)· env 与启动 · 存储与一次性迁移 · 部署:sema-up · 类型面与嵌入方。一项变更跨面时正文落它的主面,别处一句「见 …」指过去。
45
+
46
+ ### wire:HTTP / SSE / 错误码 / 帧
47
+
48
+ #### 同步提交腿 `POST /v1/tasks` 的拒因分诊(统一规则;S-271 / S-724)
49
+
50
+ - **prepare 期拒只在零工作时答错误体(行为面)**:`corrupt_session`(500)/ `rewind.store_unconfigured`(409)/ `rewind.capture_disabled`(422)/ 名册超界三码(503)与其余 prepare 期拒同一条分诊 —— 零工作(整次提交 turns = 0 ∧ tokens = 0)⇒ 错误体;
51
+ 已做过工作的多级 run(`verify` / `cascade`)末级才被拒 ⇒ 200 + failed 体、照常计费与幂等缓存。相对 7.103.2:这几码此前**没有**状态档映射、
52
+ 恒 200 + failed 体,所以调用方看到的变化只有「零工作那一形从 200 变成对应 4xx / 5xx」。7.103.2 已有状态档的码在「多级 run 末级才被拒」这一形上的变化
53
+ 见 S-271 一组第二条。
54
+
55
+ #### 会话存档读不出:500 `corrupt_session`;部分记录被跳过:`engine_notice` `session.records_skipped`(S-612)
56
+
57
+ 内部构造面一条见「类型面与嵌入方」小节。
58
+
59
+ - **🔴 行为面:同步口 200 → 500。** 一只会话的存档**整份读不出**(文件后端:会话日志没有会话头;SQL 后端:一行 `session_event.payload` 读不出一条条目),
60
+ 对它提交一轮在 7.103 上可能答 **200 + failed 体**(owner 已在本进程登记过的会话;SQL 后端一律如此),7.104.0 起答 **HTTP 500**
61
+ `{ "errorCode": "corrupt_session", "error": "<原句>" }`,一个 token 都不烧、不走计费收尾。服务重启后第一次碰坏档的那一形(7.103 答 500 `internal.error`)
62
+ 现在同样是 500 `corrupt_session`;直接读会话的口(头探针 / 会话视图 / 导出 / 分叉 / 2c 同步清单等,清单见契约 §17.2)同答。`errorCode` 字段位置不变。
63
+ **迁移**:按「同步口恒 200」写分支的调用方改为按 `errorCode` 判;这个码是**确定性**失败 —— 对 GET 5xx 自动重试的客户端(sdk 默认策略)请按码跳过重试。
64
+ - **SQL 后端**:坏行的失败句子只留坐标(`session storage: session_event.payload for session <id> row <n> is not a readable session entry
65
+ (malformed|not-json) — …`),**不再**带坏体内容的前 64 个字符(那一段经 run 结果与 500 体上 wire,而截断会让脱敏器认不出凭据);
66
+ 状态码从 200 变 500(上一条)。按旧句子全文匹配的调用方改为按 `errorCode` 判。
67
+ - **会话视图三口**(`GET /v1/sessions/:id` · `…/memory-status` · `…/memory/erase`)在读存档之前先判归属:非属主对一只坏档拿到的应答与对完好档
68
+ 逐字相同(7.103 上是一只 500)。属主照旧。
69
+ - **新 `engine_notice` 码 `session.records_skipped`**(只在文件后端):会话照常唤醒、这一轮照跑,但历史里有 N 条记录读不出被跳过 ⇒ 同一轮里一帧
70
+ `{ dropped, entriesKept, sessionId }`。对未知码严格的消费方要收下这只码(附录 D.3 白名单 26 码);没升级的壳按未知 `engine_notice` 码处置。
71
+ 非流式同步口 `POST /v1/tasks` 不收通告帧(所有通告码同一姿态),那一腿上只有运维日志 `session_repo_corrupt_read`。
72
+ - **老壳所见**:没消费 cli L-568 的壳遇到形 A,屏上是 500 的 `error` 原句加错误码。
73
+
74
+ #### core 名册超界三码在同步提交腿上改答 503(S-271)
75
+
76
+ - **🔴 同步提交腿三码改 503(wire 值形变更)**:`POST /v1/tasks` 上 `config.tool_name_too_long` / `config.tool_contract_too_long` /
77
+ `config.tool_roster_bound`(一只工具的名 / 契约 / `modelGate` 等身份成员超过名册的界,引擎在 prepare 期拒、零计费)由 **200 带 failed 体**
78
+ (`terminal:{kind:"failed", code, message}`)改为 **503 错误体** `{error, errorCode}`(`errorCode` 字节不变,`error` 是引擎点名哪只工具、多长、
79
+ 界多少的那句;不带 `retryAfterSec` / `retry-after`)。超界的只能是部署声明的工具 ⇒ 调用方改请求绕不开,只有部署方改声明才行。
80
+ 异步腿 / 活流腿照旧 2xx + 终态 run 行同码。**只在整次提交零 turn、零 token 时答 503**:`verify` / `cascade` 的前一轮已经跑过、
81
+ 末轮才被拒 ⇒ 照旧 200 + failed 体(计费、同一 Idempotency-Key 重发走重放)。
82
+ **迁移**:同步腿按 200 体的 `terminal.code` 读这三个码的客户端改读 503 错误体的 `errorCode`(200 体那一形仍会在上一句的多轮情形出现,
83
+ 两形都要认);通用重试臂遇到 503 会白重试(每次同样被拒、同样零计费),要省这几次就按码停。部署方 / 嵌入方:看到这三码就缩短对应的工具声明(界:名 128、契约 id / 修订号 64、`modelGate` 64)。
84
+ - **同步腿整张 4xx / 5xx 分诊只认零工作的拒(行为面,边缘形)**:`resume_at.*` / 治理码 / `rewind.*` / `config.*` 等 prepare 期拒码,若出现在
85
+ `verify` / `cascade` 的**末轮**而前几轮已经跑过 turn,本版起答 200 + failed 体(计费、可重放)。其中 7.103.2 已有状态档的码(`resume_at.*`、治理码、
86
+ `rewind.child_scope_unsupported`、`config.memory_capture_unsupported` 等)在这一形上此前答 4xx / 5xx(跳过计费、不进幂等缓存),它们零工作的拒(常态)不变;
87
+ 本版新映射的码(`rewind.store_unconfigured` / `rewind.capture_disabled` / 名册超界三码)此前恒 200 + failed 体,见本小节统一规则。**迁移**:多轮提交的客户端对这些码两形都要认(错误体 `errorCode` / 200 体 `terminal.code`)。
88
+
89
+ #### 请求词 `fileHistory` + `wiring_manifest.fileHistory` + rewind 两码进错误码目录(S-724)
90
+
91
+ `FILE_HISTORY_CAPTURE=false` 只关捕获一条见「env 与启动」小节。
92
+
93
+ - **能力词 `fileHistoryCapture` 换词(wire 面)**:闭集 `"off"` | `"on-optional"`。`"on-optional"` = 捕获中,且提交体带 `fileHistory: "disabled"` 可按 run 关掉;
94
+ `"on-always"` 本版起不再发出(请求词接线后结构上不可达)。**迁移**:消费端把 `"on-optional"` 渲成可用的开关(关 = 每个提交带 `fileHistory:"disabled"`);
95
+ `"on-always"` 分支留给 7.97.0–7.103.x 的 worker。`"off"` 不再意味着「没有 rewind」—— rewind 入口看 `rewindFiles`。
96
+ - **同步提交腿两码改 4xx(行为面)**:`POST /v1/tasks` 上 `rewind.store_unconfigured` 由 200 带 failed 体改为 **409** 错误体(`errorCode` 字节不变;只在整次提交零工作时 —— 已做过工作的多级 run 末级才被拒仍 200 + failed 体,见本小节统一规则);
97
+ 新码 `rewind.capture_disabled` 的 422 映射只对直接构造 TaskSpec 的嵌入方可达(见下一条)。异步腿 / 活流腿照旧 2xx + 终态 run 行同码。
98
+ **迁移**:同步腿按 200 体读 `rewind.store_unconfigured` 的客户端改读错误体;可达面只剩没有存储后端的部署。
99
+ - **`fileHistory:"disabled"` 与还原键同体 ⇒ 400 `request.field_conflict`(受理时)**:`restoreFiles` / `rewindFilesTo` / 退役 `rewindFiles` 任一与关断词同发即拒,
100
+ 会话与文件都不动(引擎自己的拒 `rewind.capture_disabled` 落在按 `resumeAt` 分叉会话之后且不回滚,所以本服务在副作用之前拒)。**迁移**:要还原就别带关断词。
101
+ - **additive**:请求体键 `fileHistory: "disabled"`(唯一词;坏值 400 `request.field_invalid`,fresh / resume 两腿同判);`wiring_manifest` 帧(租户与
102
+ operator 同投)多一段 `fileHistory: "on" | "disabled"`(缺席 = 老引擎,不是「在捕获」)。2c sync 面「没有历史店」的 501 句子改为
103
+ `session sync is not available on this deployment: no file-history store is wired, …`(按句子全文匹配的客户端注意)。
104
+
105
+ #### 同步提交撞 park 的 200 体改为「结果本身 + 回执三键」;park 中的 run 行带 `result`(S-173,additive)
106
+
107
+ 持久数据面与混版窗 / 回滚的修复语句见「存储与一次性迁移」小节,内部面见「类型面与嵌入方」小节。
108
+
109
+ - **`POST /v1/tasks`(非流式)撞 park 的 200 体(wire,additive)**:修前 = `{ taskId, sessionId, status }` 三键;现在 = 封口脱敏后的引擎结果
110
+ (`terminal: { kind: "paused", gate, checkpointId? }`、`toolCallId?`、`result`、`stats` …,与同一次 park 在 `/v1/tasks/stream` 的
111
+ `done.result` 上逐字同形)**再叠**原三键 —— 三键的值与修前逐字相同(`status` = `GET /v1/runs/:id` 行上的词)。恢复凭据从不在体上。
112
+ **谁受伤**:对这张 200 体做**闭集键校验**的消费方(多出来的键会让它红)。**迁移**:放开未知键;判「停在门上」可改读
113
+ `terminal.kind === "paused"`(与流腿同一读法),旧的 `status` 读法照旧成立。终局体不变(不带 `status`,自 7.64.0 起即如此)。
114
+ - **`GET /v1/runs/:id` 对 park 中的 run 带 `result`(wire,additive)**:修前 park 行没有 `result`;现在是停下来那一份结果(凭据摘掉、脱过敏)。
115
+ 行被认领续跑即清空(`running` 行仍无 `result`),终局时整块替换,审批过期收割后清空(行形与修前同)。**随之可见的两处值**:`needs_review`
116
+ park 的信封 `errorCode` = `review.pending`(core 对 review park 的规范码,此前缺席);配了 infra 定价的部署 park 行上也出现 `supervisorCost`。
117
+ **谁受伤**:把「`result` 在场」读成「run 已结束」、或把「`errorCode` 在场」读成「失败」的轮询方 —— 两者都应先读 `status`。
118
+ **迁移**:按 `status` 判停没停(`suspended` / `needs_review` = 停在门上),再读 `result`。
119
+ - **同一列的其它读者(行为面,如实)**:会话列表 `lastTaskRunId` 在 park 中在场(此前只在终局在场);`GET /v1/tasks` 列表行的
120
+ `turns` / `tokens` / `costMicroUsd` 在 park 中带停下来那一刻的读数;`/v1/usage/*` 把 park 行按那一刻的读数计入(下界,终局时整块替换)。
121
+
122
+ #### 删会话后旧的审批卡消失(S-790 / S-506)
123
+
124
+ 会话化身的新列 + 一次性回填、记忆 opt-out 载体见「存储与一次性迁移」小节,嵌入方读口见「类型面与嵌入方」小节。
125
+
126
+ - **删会话带走以它为根的后台子代(行为面)**:`DELETE /v1/sessions/:id` 先把这只会话派出的后台子代结束 —— 停在审批上的作废(卡从待决审批列表
127
+ 消失),本副本上在跑的停掉 —— 做完才删会话记录。**迁移**:依赖「删了会话之后还能对它的旧审批卡做决定」的调用方,那张卡自本版起不在了;
128
+ 决定一张 7.104.0 之前删会话留下的旧卡答 `409 decide.not_parked`。有本副本结束不了的子代(在别的副本上:耐久行的写者不是本副本)⇒ 删除开始之前就拒,答 **409
129
+ `conflict.session_children_alive`**(体带 `remaining` = 那些子代的 id 列表,与 `GET /v1/agents/roster` 的 `handle` 同一个串),什么都没动,
130
+ 等它们收尾后重试即可;本副本停得掉却没在时限内停净 ⇒ `500 internal.error`,会话记录保留,重试收敛;有子代的会话删除会慢一些(每轮重新枚举,至多 6 轮)。两处如实登记(修复验证轮 opus 腿,S-886 / S-887):500 之后**立刻**重试,停止仍在落盘的子代会被判成够不着 ⇒ 那一次答 409(句子说「删之前就拒」,但上一次已删了账本;再晚一拍重试即收敛);曾开耐久审批、留下 parked 子代、之后关闭耐久审批(或切到多租户形)的会话,删除会一直 409(本副本没有 checkpoint 店,作废不了那张卡;7.103.2 上这一形答 200)—— 出路候 S-887。
131
+ - **parked 决定的复核(行为面)**:决定腿在 claim 之前 / 之后各读一次根会话上是哪一位住客,不在或换了人(包括同一个人删了重建)⇒ 回滚、
132
+ `409 decide.not_parked`;行上没有根会话锚 ⇒ `409 decide.row_unrevivable`。零新码、零新体键。
133
+
134
+ #### 回决门键闭集的 `unsupported` 桶换成员与文案(S-355)+ 续跑折叠体 `blockedBy` 恒在(S-607)
135
+
136
+ - **`/decide` 的 400 `request.body_shape` 换桶(S-355,诊断面)**:`permissionModeAfter`(plan_review 的字段)与 `permissionMode`(提交体的键)由
137
+ `unknownKeys` 移到 `unsupportedKeys`;判决不变(都拒、同码同串)。两道回决门(`/decide`、`plan_review`)的 `unsupported` 文案由一句通用标签改为
138
+ **按去处分句**:`unsupported (a body field of <那条门的路径> — …): k`、`unsupported (a path segment of <带这一段的 URL> — …): k`、
139
+ `unsupported (a submit-body key of POST /v1/tasks — …): k`。键名 `unknownKeys` / `unsupportedKeys`、状态码、`Accepted:` 全表不变。
140
+ **迁移**:按 `unknownKeys` 精确匹配这两个键的调用方 / 测试改读 `unsupportedKeys`;按文案全文匹配的改按两个机读桶判。
141
+ 内部面:`decideBodyKeyIssue` / `planReviewBodyKeyIssue` 删,统一为 `decisionBodyKeyIssue(door, body)`(`src/http/routes/approvals-assistant.ts`)。
142
+ - **续跑折叠体 `409 resume_blocked_by_policy` 的 `blockedBy` 恒在(S-607,additive)**:此前只在原拒绝带子码时在场;现在值 = 同一只错误**直接**出去时
143
+ 的 `errorCode`(带码 ⇒ 原码,与此前逐字相同;不带码 ⇒ 按状态的粗码 `request.rejected` / `not_found.resource` / `request.unprocessable` /
144
+ `capability.unavailable` 等)。**迁移**:把「`blockedBy` 缺席」读成「这次拒绝没带子码」的消费方,在本版起的 server 上会读到粗码(判型族按
145
+ `blockedBy ?? errorCode` 取码的,粗码不命中任何专属族,判型不变)。
146
+
147
+ #### 耐久 ask 回决口收 `settledBy`(S-843)
148
+
149
+ - **耐久 ask 回决口收 `settledBy`(S-843,additive)**:`POST /v1/tasks/:taskId/asks/:askId/decision` 的体可带 `settledBy`(只配 `decision:"deny"`;
150
+ 直连门上 `400 settled_by_not_in_proof`,与 `/decide` 同 —— live `/respond` 口没有这一臂;超过 16 字符 / 空串 ⇒ 400),其余规则与拒句与
151
+ `/decide`、`/respond` 相同。缺席 = 今形。
152
+ `400 request.body_shape` 的拒因句末尾多了 `/ settledBy`(按句子全文匹配的客户端注意)。
153
+ - **`settledBy` 超过 16 字符 / 空串 ⇒ 三条回决口都 400、同一句**(车QK F7,行为面):`settledBy must be 1-16 characters — …`。live
154
+ `/respond` 上此前这一形在提交里才被店拒、落进「店抖动不挡回决」臂:答 200、ask 行没决、那个串照样交给了引擎(引擎按词表外 fail-closed 拒);
155
+ 现在裁决之前 400(码 = 这条口体拒的一贯码 `request.body_shape`),卡还在。`/decide` 上此前走到引擎、由引擎按词表拒;现在 400
156
+ `request.field_invalid`。引擎的三个词(`human` / `timeout` / `policy`)都在 16 字符以内,合法请求不受影响。
157
+
158
+ #### `401 auth.unauthorized` 的 message 换成出路句(S-850)
159
+
160
+ - **`401 auth.unauthorized` 的 `error` 换句(wire 文案,码不变)**:此前恒为 `unauthorized` 一个词,现在按这道门收什么给三句出路句之一(凭据门 /
161
+ 运维口 / 要调用方身份,逐字见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A.1)。按 `error == "unauthorized"` 字面判的调用方改按 `errorCode`
162
+ (`auth.unauthorized`)判 —— 状态码与码都没变。
163
+
164
+ #### `GET /v1/capabilities` 多一段 `sealedKey`(模型钥匙 sealed 形的公钥发现面;additive,零迁移)
165
+
166
+ 自建 `ServiceDeps` 的嵌入方一条见「类型面与嵌入方」小节。
167
+
168
+ - **新键,不改旧键**:`sealedKey: { alg, publicKeyId, publicKey }` = 本进程模型钥匙保管店里最新那把 X25519 公钥,用来把模型 key 封成
169
+ `config.d/models.json` 里的 `sealedApiKey` 密文(全文 USAGE §9.10)。只投公钥三键。保管店没建成(起服时保管目录不可用)⇒ 键**缺席**(不是 `null`)——
170
+ 这是能力面里唯一一个「本部署没有这件设施就整个缺席」的键;按「未知键忽略」读 caps 的调用方零改动。
171
+ - **多副本部署要知道**:保管店每台宿主一份(`<数据根>/sealed-key/`)。副本不共享这个目录时,各副本 caps 答出的公钥不同,对着 A 副本公钥封的密文在
172
+ B 副本上是 `stale_ciphertext`(逐模型响亮失败)。要在多副本上用本地 sealed 形:共享这个子目录(先备好钥匙再起副本,目录对每个副本可写)。
173
+ - 私钥、私钥文件路径、持有的旧钥匙 id 都不上 wire;caps 面仍在服务凭据门之后。
174
+
175
+ ### env 与启动
176
+
177
+ #### 多租户姿态 ∧ 不分租户的执行车道拒启 + 执行车道合成步(S-775)
178
+
179
+ - **🔴 `REQUIRE_PRINCIPAL=true` ∧ 最终生效的执行车道不分租户 ⇒ 拒启**(新交叉不变量 `multi-tenant-shared-lane`)。**按能力表不按词**:判据读
180
+ 执行车道能力表的「分不分租户」一位,**今天覆盖 host / ssh / adb**(worker 宿主 / 一台 SSH 对端 / 一台 ADB 设备,所有任务共享同一个目标);
181
+ e2b / k8s / local-docker / device / 未点名车道不受影响。拒启即通知:拒因点名车道与两条出路(`set REMOTE_EXEC to e2b / k8s / local-docker / device,
182
+ or unset REQUIRE_PRINCIPAL for a single-user box`)。host 组合经 deploy / DGX 实查「无」;ssh / adb 组合发车帖问。隐式 host(`CONFIG_PROVIDER=local`
183
+ 而 `REMOTE_EXEC` 未设)与 `config.d/remote-exec.json` 把车道换成 `host` 同样拒;文件把隐式 host 换成 `local-docker` 照起。**迁移**:多租户机器换隔离
184
+ 车道;真是单用户机器就去掉 `REQUIRE_PRINCIPAL`。仓内验收夹具 `acceptance/s609-plan-review` 已改 `REMOTE_EXEC=local-docker`。
185
+ - **执行车道合成步**:env 链 + local 形 `config.d/remote-exec.json` 覆写在中心拉取与任何配置应用**之前**合成,读车道的交叉不变量全部在那之后判。
186
+ **行为变化**:① `LEADER_ENABLED=true` + 文件覆写成 `host` 现在拒启(此前按 env 车道放行);② env 的 `REMOTE_EXEC=device` 被文件换成 host / local-docker
187
+ 时不再按 device 前提误拒;③ `run-local` 的车道拒启改为一行 + 退 2(此前是抛出)。`run-local` 恒为 local 形,照读该文件(与此前相同)。
188
+ - **嵌入方(自己调包根 `loadConfig()` 装配的)**:`leader-host-lane` / `device-durable-store` / `device-durable-approval` 三条 7.103.2 由
189
+ `loadConfig()` 拒的形,本版起 `loadConfig()` 照返回 —— 判据在合成步里。包根新导出 `composeExecutionLane(config, ctx)` 与它的同步判据半场
190
+ `assertExecutionLaneInvariants(config)`(同一只,本仓两个入口调的就是它):在 `loadConfig()` 之后调(车QK F4)。
191
+
192
+ #### `CONFIG_PROVIDER` 闭集拒启(S-841)
193
+
194
+ 类型面一条见「类型面与嵌入方」小节。
195
+
196
+ - **🔴 认不出的 `CONFIG_PROVIDER` ⇒ 拒启(S-841,行为面)**:合法值只有 `local` / `remote` / 不设(空串 = 不设);**逐字比较**,大小写与首尾空白都不宽容。
197
+ 此前认不出的词照起,而且一个进程里三处读法不一:配置源当它是 `local`(配了中心也一样)、隐式 host 执行车道与起服落点披露当它「不是 local」、
198
+ local 配置源的根落回进程 cwd 而车道文件覆写读本地根。拒因句即通知(点名旋钮、列合法词)。**迁移**:改成 `local` / `remote` 之一或删掉。
199
+
200
+ #### 旧负名与正名同设 ⇒ 拒启(墓碑一张表)(S-588)
201
+
202
+ - **🔴 D 族旧负名在场即拒启,不论正名在不在(S-588,行为面)**:下面四个旧名自 server 3.0.0 起就是墓碑,但此前只在**正名没设**时才拒;
203
+ 正名设了而旧名还留着,旧名被静默忽略、照常起服。本版起两名同设同样拒启,拒句与其余退役名同一句、同一个码 `[config.env_key_retired]`。
204
+ **迁移**:删掉旧名;若只设了旧名,按下表换成正名(极性相反,别把旧值照抄过去):
205
+
206
+ | 🪦 删掉 | 等价写法(旧名 `=true` ⇒) | 正名缺省 |
207
+ |---|---|---|
208
+ | `PROJECT_MEMORY_DISABLED` | `PROJECT_MEMORY_ENABLED=false` | 开 |
209
+ | `CONFIG_LKG_DISABLED` | `CONFIG_LKG_ENABLED=false` | 开 |
210
+ | `HOST_BG_DISABLED` | `HOST_BG_ENABLED=false` | 开 |
211
+ | `HOST_EXEC_SPOOL_DISABLED` | `HOST_EXEC_SPOOL_ENABLED=false` | 开 |
212
+
213
+ (旧名 `=false` ⇒ 正名 `=true`,也就是缺省,直接删掉即可。)
214
+ - **墓碑拒句前缀换形**:码后面那段写着「5.0.0 起拒启」的括号换成
215
+ `— refused while present, whether or not the replacement is also set:`(整句:`retired env name(s) set [config.env_key_retired] — refused while present, …`)。
216
+ 按 `retired env name` 或拒启码匹配的不受影响;按旧括号全文匹配的消费方改匹配码。
217
+ - **墓碑不设到期**:退役 env 名的拒启是永久的;某个名字要被赋予新含义时才删它的墓碑,届时本文件写明。
218
+
219
+ #### 记忆整理阀门出厂缺省改派生 + `MODEL_CHEAP_ID` 与 `MODEL_ID` 同 id 不再铸 cheap 槽
220
+
221
+ 类型面(`ServiceConfig.memoryConsolidationDriver` / `ServiceSeamDeps` 的 `memoryConsolidationValve`)见「类型面与嵌入方」小节。
222
+
223
+ - **记忆整理阀门 `MEMORY_CONSOLIDATION_DRIVER` 未设 ≠ `off`(行为面)**:出厂缺省 = 开,但只在整理座落在主模型**之外的目录条目**上时生效(`MODEL_CHEAP_ID`
224
+ 在场,或目录给 consolidate / summarize / flash 档位绑了别的条目;按条目身份判,同 id 异网关的两条算两只);否则关 + 起服 warn
225
+ `memory_consolidation_default_off_no_cheap_model`。**谁受伤**:配了便宜模型、记忆引擎接线(单用户 + file 记忆后端)、没表态阀门的部署 —— 升级后开始
226
+ **自动整库整理**(`MEMORY_AUTO_CONSOLIDATION` 未设 = 引擎缺省开),记忆内容外流到便宜模型、按便宜模型计费;`MODEL_CHEAP_ID` 写成与 `MODEL_ID` 同一个 id
227
+ **不算**(那就是主模型,见下条)。**迁移**:不想要就显式 `MEMORY_AUTO_CONSOLIDATION=off`(只关自动跑)或 `MEMORY_CONSOLIDATION_DRIVER=off`(整条关)。显式 `on` 而座落主模型 ⇒
228
+ 照开 + 起服 warn `memory_consolidation_on_main_model`。`MEMORY_CONSOLIDATION_SCOPES` 配了而阀门未设:便宜模型在场 ⇒ 派生开照起,不在场 ⇒ 照拒
229
+ (判定点在起服装配期,不再在读配置那一拍;`run-local` 调同一只判官,同判同句,一行 + 退 2;7.103.2 的 run-local 对这一形是 loadConfig 抛错)。wire 读数随之翻:`memory-status.autoConsolidationArmed` 真、operator 帧 `wiring_manifest` 多
230
+ `autoConsolidation` 一段(`configFingerprint` 漂一次)、配置目录该行 `effectiveValue` 答真生效值 `on` / `off`(此前未设答 `off`:7.103.2 该行是 `booleanEnum` + `staticDefault:"off"`)。
231
+ - **`MODEL_CHEAP_ID` 与 `MODEL_ID` 同 id ⇒ 不再铸 cheap 槽(行为面)**:env 的 cheap 槽是「主网关换个 id」,同 id 就是主模型本身。此前照铸一只
232
+ `MODEL_CHEAP_*` 形的第二条条目,并把 `<id>` 这个目录名改指它(`default` 仍是主条目):`subagent` / `summarize` 两个 role 和请求里点
233
+ `model: "<id>"` 的调用拿到的是 cheap 形(思考 / 窗 / maxTokens / 视觉 / 价)。现在不铸:两个 role 留在主模型,`<id>` 就是主条目,起服一行
234
+ `config_env_entry_cheap_same_as_main`(同时设的其余 `MODEL_CHEAP_*` 键一并不生效,句子里写明)。**谁受伤**:靠同 id 加
235
+ `MODEL_CHEAP_*` 给同一只模型造第二种形的部署。**迁移**:在 `config.d/models.json` 写两条不同名的条目(同一个上游 id,各自的形),把 roles
236
+ 指过去;不需要第二种形就去掉 `MODEL_CHEAP_ID`。
237
+
238
+ #### scratchpad 宣告不归 `SANDBOX_ENV_FACTS` 管(S-842)
239
+
240
+ - **`SANDBOX_ENV_FACTS=false` 不再关会话 scratchpad(S-842,行为面)**:本键此后只管沙箱事实(profile / capabilities / pkgSource / egress / resume)。
241
+ 会话 scratchpad 的宣告是写面的宣告(core 据它开根围栏的洞,并用同一次裁决决定提示词教不教),与本键无关 ⇒ 这类部署的提示词**开始**出现
242
+ `# Scratchpad Directory` 一节;远程车道(e2b / k8s / local-docker / ssh)的 `/tmp/scratchpad/<sid>` 此后在本键关着时也可 Write(与本键开着同形)。
243
+ 缺省部署:提示词重新出现这一节(此前被 core 跳过),每轮那一句 `scratchpad advertised writable directory skipped` 消失。**迁移**:无动作;
244
+ 依赖「`SANDBOX_ENV_FACTS=false` ⇒ 提示词零环境行」的部署注意多出这一节。
245
+ ⚠️ 数据根很深(`<LOCAL_DATA_ROOT>/scratchpad/<段>` 超过 160 字符,根约 107 字符以上)的部署:会话 scratchpad **不再铸出**(不建、不豁免、不宣告),
246
+ 每个任务一行 warn `scratchpad_not_minted`。此前它照建、照豁免,提示词里只有一条被截断的假路径。**迁移**:缩短 `LOCAL_DATA_ROOT`。
247
+
248
+ #### `FILE_HISTORY_CAPTURE=false` 只关捕获(S-724)
249
+
250
+ 请求词 `fileHistory`、能力词 `fileHistoryCapture`、rewind 两码见「wire」小节。
251
+
252
+ - **🔴 `FILE_HISTORY_CAPTURE=false` 只关捕获(行为面)**:7.97.0–7.103.x 上它把文件历史店**摘掉**(那时引擎只有这一种写法),连已存历史也读不了;
253
+ 本版起店照接,引擎拿到部署捕获位(`RunnerDeps.fileHistoryCapture: "off"`)—— 新编辑照旧**零捕获**,而已存历史**重新可以**被
254
+ `resumeAt`+`restoreFiles` / `rewindFilesTo` 还原、被 2c session-sync 导出 / 导入;能力位 `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync`
255
+ 由 `false` 翻 `true`(能力词 `fileHistoryCapture` 仍报 `"off"`)。**谁受伤**:把这根旋钮当成「整个文件历史功能关掉」来用的部署。
256
+ **迁移**:要让已存历史也不可用,逐会话 `DELETE /v1/sessions/:id`,或在 SQL 后端开留存清扫(`RETENTION_MODE=enforce` + `RETENTION_MAX_AGE_DAYS` +
257
+ `RETENTION_SWEEP_INTERVAL_SEC`,按会话年龄删);**没有**一键让整库历史不可用、也没有单独关掉 session-sync 的旋钮。
258
+ 没有存储后端的部署(env-only worker)不受影响(本来就没有历史店,旋钮在那里无事可关)。
259
+
260
+ ### 存储与一次性迁移
261
+
262
+ #### 一次性迁移的窗登记表(S-590)
263
+
264
+ - **一次性迁移的窗(S-590)**:`src/plugins/one-time-migrations.ts` 的每条迁移在「引入它的版本发布日 + 30 天」之后的第一个 minor 删码,逐条日期只在该文件的
265
+ `ONE_TIME_MIGRATIONS` 登记表(随包在 `dist/plugins/one-time-migrations.d.ts`;起动日志行 `one_time_migrations_applied` 新增的 `removableAfter` 读数同源)。
266
+ **跨版跳升**(从 7.73.x 及更早,或从 7.93.4 及更早)要经过一只还带着对应迁移的版本起一次。自建 `StoreBackend` 的嵌入方:`runOneTimeMigrations()`
267
+ 在**最后一条**删码时从接口上删(tsc 红即通知)。
268
+
269
+ #### 会话化身:`session_meta.incarnation` 新列 + 一次性回填(S-790 / S-506)
270
+
271
+ 删会话带走子代、parked 决定的复核见「wire」小节。
272
+
273
+ - **新列(持久数据面)**:`session_meta.incarnation`(MySQL 协议 `VARCHAR(64) NOT NULL DEFAULT ''`;PG 同名同宽 `COLLATE "C"`)—— 同一个会话 id 上
274
+ **这一位住客**的不透明令牌。新库由中央 DDL 建;存量库由起服的一次性迁移 **M5** 加列并给每一行铸一只互不相同的令牌(窗与删法见
275
+ `src/plugins/one-time-migrations.ts` 的 `ONE_TIME_MIGRATIONS` 登记表;起动日志行 `one_time_migrations_applied` 多两键
276
+ `sessionIncarnationColumn` / `sessionIncarnationsBackfilled`)。**先升库再升副本**这条既有顺序不变:旧副本连上已加列的库会被 S-547 列册探针
277
+ 判「库比代码新」拒启。回填之后读到空令牌的行 ⇒ 读口响亮拒(`session.incarnation_missing`,parked 决定因此拒、记忆座退回按 id 绑定并 warn),
278
+ **不**当作通配 —— 手工改库的运维给那一行填一只随机令牌即可(`session.incarnation_missing` 是内部错误码,日志可见;wire 上是通用 500,不是新码)。local 文件车道:令牌落在数据根的 `session-incarnations.json`(与
279
+ `session-titles.json` 同一族边档),存量会话第一次被读时补铸并落盘,不在起动期扫。**该文件读不出**(坏 JSON / 形不对 / 权限;缺文件不算,那是「还没有」)⇒ 严格拒:
280
+ 建会话 / fork / 导入在任何副作用之前拒,读化身的面同样拒(parked 决定 `500 internal.error`,记忆座退回按 id 绑定并 warn);文件原样留在原处,
281
+ 拒因不带原文;这一拒记在进程里,修完要重启才重读。**出路** = 修好该文件,或把它移开,然后重启。**移开的代价**:所有旧会话的化身在下一次被读时
282
+ 重铸 —— 每只旧会话都成了一位新住客,上一位住客声明的记忆采集 opt-out 从此不再绑它们(按部署缺省采集);要保住 opt-out 就修文件,别移开。
283
+ 令牌不上 wire、不进模型上下文,日志只打前 8 位。
284
+ - **滚动升级窗(SQL 两车道)**:第一台 7.104.0 副本起动跑完 M5 之后,**此前已经在跑**的 ≤ 7.103.x 旧副本不会重新对账列集(S-547 探针只在起动期跑),
285
+ 它们新建的会话行落列缺省 `incarnation = ''`。窗内现象:这些会话上的 parked 决定答 `500 internal.error`(读口响亮拒 `session.incarnation_missing`,
286
+ claim 之前就拒,什么都不动);7.104.0 副本对每只这样的会话打一行 `session_incarnation_unavailable reason=session.incarnation_missing`(按会话去重);
287
+ 记忆座对它们退回按 id 绑定并 warn `memory_session_incarnation_unknown`。**出路**:全部副本升到 7.104.0(旧副本全部退出)之后,重启其中任意一台 ——
288
+ M5 每次起动都跑、幂等(只回填 `incarnation = ''` 的行,已有令牌的行一个字节不动),窗内写下的空行就此补上。
289
+ - **记忆 opt-out 不再跨住客继承(local 文件载体)**:会话声明过记忆采集 opt-out、被删、同一个 id 重建之后,新住客按部署缺省采集。
290
+ ⚠️ SQL 部署的 opt-out 载体(`session_capture_optout`)今天不存令牌 ⇒ 那一形的记录仍按 id 绑(新住客仍被扣 opt-out,保守方向),另车处理。
291
+
292
+ #### park 中的 run 行带 `result`:持久数据面与混版窗 / 回滚(S-173)
293
+
294
+ wire 面(同步提交撞 park 的 200 体、`GET /v1/runs/:id` 行形、同一列的其它读者)见「wire」小节。
295
+
296
+ - **持久数据面**:不加列、不改列 —— 用的是既有 `task_run.result`(JSON / JSONB)。7.104.0 之前 park 的存量行 `result` 仍为空(不回填),
297
+ 读法见契约 §4.0b 同名行。
298
+ - **🔴 混版窗 / 回滚(持久数据面,如实)**:7.104.0 起「除三个带结果的写口外,每一次状态迁移都把 `result` 写空」(认领续跑 + 三条收割);
299
+ **≤7.103.x 的副本不知道这条规则**。滚动升级窗内(或回滚到 7.103.x 之后)一台旧副本认领新版 park 行 ⇒ 行是「`running` + park 结果」
300
+ (它失联后本版的 `reapStale` 收割时会清掉;它正常跑完 ⇒ 终局整块替换);旧副本执行审批过期收割 ⇒ 行成「`failed` + park 结果」,本版不再碰终局行,
301
+ **会一直留着**(`GET /v1/runs/:id` 在这种行上 `status:"failed"`,`result.terminal.kind:"paused"`,`needs_review` 那一形信封 `errorCode` 投成
302
+ `review.pending`)。**补偿**:升级全部完成后(回滚则在回滚后)跑一次下面的修复语句,只清「非 park 行上的 paused 结果」(两方言在车道库实测:
303
+ failed / running 两行清掉,suspended / needs_review / completed 三行不动):
304
+ TiDB / MySQL:`UPDATE task_run SET result = NULL WHERE status NOT IN ('suspended','needs_review') AND JSON_UNQUOTE(JSON_EXTRACT(result, '$.terminal.kind')) = 'paused';`
305
+ PostgreSQL:`UPDATE task_run SET result = NULL WHERE status NOT IN ('suspended','needs_review') AND result -> 'terminal' ->> 'kind' = 'paused';`
306
+ 回滚到 7.103.x 前若想连 park 行的结果一起撤(旧版读面不会用到它),再加 `UPDATE task_run SET result = NULL WHERE status IN ('suspended','needs_review');`。
307
+
308
+ #### local 车道 JSONL 店的坏记录:安全轴三店拒用、其余跳过留痕(S-582)
309
+
310
+ 内部构造面见「类型面与嵌入方」小节。
311
+
312
+ - **🔴 行为面(只影响 `DB_BACKEND=local`)**:八本 JSONL 账重放时**中间**一条记录读不出(一行 `42` / 缺本店必填键的对象 / 半截 JSON)不再静默跳过。
313
+ ① **直连门 nonce 账**(`<数据根>/approval-nonces/consumed.jsonl`)坏一行 ⇒ 该机器上直连门的 plan_review 全部 `500 internal.error`(此前照受理,
314
+ 那只 nonce 可被重放一次);`/decide` 不受影响;不拒启。② **豁免账**(`<数据根>/approval-exemptions/<会话>.jsonl`)坏一行 ⇒ 那个会话的工具照问人、
315
+ 豁免列表 / 撤销答 500、`remember:"session"` 回执 `rememberApplied:false`;其余会话照常。③ **规则两本日志**方向不变(此前已整面拒),拒绝错误自此带码。
316
+ ④ run 店 / 续跑锚 / 发文件账本 / ask 账本照旧跳过,多一行 warn 与 F 计数;run 店的单记录文件(run.json / claim)重放只要跳过了任何一行
317
+ ⇒ 整份读成没有记录(此前尾部的 `42` / 半截 JSON 会退回前一条)。撕尾(崩在半行)不算。
318
+ **迁移**:无需动作。看到 `approval_nonce_corrupt_read` / `approval_exemption_corrupt_read` 时按拒绝句处置那份文件(nonce 账:等过
319
+ `PRINCIPAL_JWT_MAX_TTL_SEC` 加容差再移开、重启 —— 移得太早,其中每条消费在其证明到期前可被重放)。
320
+ - **日志面**:新 warn 事件 `approval_nonce_corrupt_read` / `approval_exemption_corrupt_read` / `permission_rule_approvals_corrupt_read` /
321
+ `permission_rule_tickets_corrupt_read` / `approval_ask_journal_corrupt_read` / `run_store_corrupt_read` / `resume_anchor_corrupt_read` /
322
+ `send_file_ledger_corrupt_read`(字段 `kind` / `path` / `reason` + `line` / `form` 或 `kept` / `dropped` / `total` + `note`)。规则两本日志的坏行**不再**
323
+ 走 `permission_rule_store_disclosure`(那个事件只剩 core 规则文件的披露)—— 按事件名配告警的运维改配新名。新指标标签 `fail_open_total{tag=…}`:
324
+ `server.run-store.corrupt-record-skipped` / `server.resume-anchor.corrupt-record-skipped` / `server.send-file-ledger.corrupt-record-skipped`。
325
+
326
+ ### 部署:sema-up
327
+
328
+ #### sema-up 数据根按项目分、一条根只归一个项目(S-851)
329
+
330
+ - **sema-up 自动数据根按项目分 + 项目标记(部署面)**:多盘机不给 `--data-root` 时,新装项目的数据根是 `<大盘>/sema-data/<project>`(此前是
331
+ `<大盘>/sema-data`,同机第二个项目会自动落进同一份目录);任何来源的数据根首次使用时落一只 `.sema-up-project` 标记,之后装 / 重跑(含
332
+ `--dry-run`)核对 —— 标记是别的项目、目录非空而认不出主人、或这条根嵌在另一个数据根里 ⇒ **非零退出、目录零改动**,拒句点名两边并给出路
333
+ (换 `--project` / 显式 `--data-root`)。**已装的项目照 `.env` 首装值不动**:存量根(没有标记)只要 `.env` 登记的正是它,重跑照起并补写标记。
334
+ **谁要动手**:同机两个项目此前都没给 `--data-root` 的部署 —— 它们可能早已共用 `<大盘>/sema-data`;先重跑的那个认领它,后重跑的那个被拒,按拒句里的
335
+ `docker volume inspect … --format '{{.Name}} {{index .Options "device"}}'` 核对、按 `deploy/sema-up/README.md`「数据根归属」拆开(拆要用
336
+ `--teardown-volumes`:只 `--teardown` 留下的 bind 卷对象仍指着旧目录)。**shell / `-f` 导出了 `DATA_ROOT_DIR` 的部署**:它是 compose 真正插值的那一个,
337
+ 与 `.env` 首装值或 `--data-root` 不一致 ⇒ 拒(此前 compose 静默挂导出值);两处只留一处。
338
+ - **`--project` 只收 compose 的项目名形(部署面)**:它现在也是自动数据根的一段路径 ⇒ 小写字母 / 数字 / `-` / `_`、首字符字母或数字,别的写法
339
+ (`..`、带 `/`、大写、空串)在建任何目录之前拒。docker compose v2 对 `-p` 本来就是这条规则(2.40.3 实测拒 `Alpha`);只有用独立版
340
+ docker-compose v1(它把名字转成小写)起过大写名的存量部署会被拒 —— 改用小写名就是另一个工作目录,旧的 `~/.sema-up/<旧名>/.env` 要搬过去。
341
+
342
+ #### sema-up 的取值旗过形判(布尔 / 端口 / 枚举闭集)
343
+
344
+ - **sema-up 取值旗过形判(部署面)**:`--registry` 只收 `true` / `false`(此前 `yes` / `1` / `on` 被静默当成**关**,且原样落进 `.env`);端口旗
345
+ (`--port` / `--pg-port` / `--minio-port` / `--minio-console-port` / `--git-http-port` / `--git-ssh-port`,以及 `-f` / 环境的 `REGISTRY_PORT`)只收
346
+ 1–65535 的十进制整数;`--profile` / `--users` / `--region` / `--git` / `--kube` / `--sandbox` 只收列出的词;命令行上写了 `--旗=` 却没给值同拒;
347
+ 交互答案(TTY / `--interactive=json`)必须是那道题的选项之一。不合 ⇒ 在建任何目录、写任何文件之前非零退出,拒句点名旗、收到的值与合法形。
348
+ `kube-up.sh` / `cluster-up.sh` 单独跑时的同名环境变量(含 `DRY_RUN`、`STACK_ENGINE`)与 `preflight.sh` / `smoke.sh` 的旗同一规则。
349
+ **谁要动手**:脚本或 `-f` 配置里把布尔旗写成 `yes` / `1` / `on` 的自动化 —— 改成 `true` / `false`;这些值此前一律被当成**关**,要保持今天的行为
350
+ 就写 `false`(写 `true` 会真的打开配置中心)。单独跑 kube-up 写 `DRY_RUN=1` 的改 `DRY_RUN=true`(此前 `1` 被当成真装)。`-f` 配置里写了
351
+ `DRY_RUN` / `TEARDOWN` / `TEARDOWN_VOLUMES` / `SKIP_PREFLIGHT`(它们会盖过同名的无值旗)的,同样只收 `true` / `false`;`MODE_INTERACTIVE` 只收
352
+ `tty` / `json` / `none`。
353
+
354
+ ### 类型面与嵌入方
355
+
356
+ #### 类型面(硬 breaking)
357
+
358
+ - **类型面(硬 breaking,tsc 红即通知)**:`ServiceConfig.memoryConsolidationDriver.enabled: boolean` 删除 → `valve?: "on" | "off"`(表态原词;缺席 = 未设,
359
+ 开不开在 boot 期判定 —— 读 `ServiceConfig` 判「阀门开没开」的调用方改读启动日志 `memory_consolidation_driver_armed` / 能力位
360
+ `memoryConsolidationDriver` / 配置目录该行);配置目录 `MEMORY_CONSOLIDATION_DRIVER` 行 `staticDefault:"off"` 撤、改 `derivedDefaultNote`。
361
+ 公开导出的 `ServiceSeamDeps`(经 `ServiceDeps`)新增**必填**位 `memoryConsolidationValve: { enabled: boolean; source: "explicit" | "derived" }`
362
+ = 本进程阀门的生效判决,只喂配置目录该行的 `effectiveValue`。自建 `ServiceDeps` 的嵌入方填自己进程的真实判决;没有接记忆整理的填
363
+ `{ enabled: false, source: "explicit" }`。(记忆整理阀门;正文见「env 与启动」小节)
364
+ - **类型面(硬 breaking,tsc 红即通知)**:`ServiceConfig.configProvider` `string` → `"local" | "remote"`;`createConfigProvider` 的入参
365
+ `provider` 同收、`localDir` **必填**(不再回落 `process.cwd()` —— 传你的本地配置根;只造中心源的调用方随便传一个,它不被读)。(S-841;正文见「env 与启动」小节)
366
+
367
+ #### 包导出面:自建会话店 / 自建后端 / 自建 `ServiceDeps`
368
+
369
+ - **嵌入方**:公共导出的 `OwnerAwareSessionStore` 多一只可选读口 `sessionFaceOf`(属主 + 化身一次读)—— 自建会话店要让 parked 决定可用必须实现它
370
+ (缺席的部署决定整条 500,不静默跳过复核);`StoreBackend.runOneTimeMigrations()` 的报告型多两键(`sessionIncarnationColumn` /
371
+ `sessionIncarnationsBackfilled`,自建后端 tsc 即通知)。其余改名(`ParkedDecideDeps.sessionOwnerOf` → `sessionFaceOf` 等)都在内部装配面,不在包导出面。(S-790 / S-506;正文见「wire」小节与「存储与一次性迁移」小节)
372
+ - **自建 `ServiceDeps` 的嵌入方**:新增可选位 `sealedKeyPublic`(只含 `latest` 的公钥面);不传 ⇒ 你的 caps 体里没有这一段,其余不变。(`sealedKey`;正文见「wire」小节)
373
+
374
+ #### 内部构造面(对包消费方零影响)
375
+
376
+ - 内部面(非公开导出):三家 run 店的 `setSuspended` / `setNeedsReview` 收第二个必填参数 `result`(结果或 `null`),`result` 列经同一只入口
377
+ `sealRunRowResult`(封口 + 脱敏)落列。契约全文见 `docs/ASSISTANT-WIRE-CONTRACT.md` §5.0c。(S-173;正文见「wire」小节与「存储与一次性迁移」小节)
378
+ - **内部构造面(对包消费方零影响)**:`observability/corrupt-read-seat.ts` 的 `createCorruptReadSeat` 多一个可选第 4 位 `leg`;`plugins/session-event-payload.ts`
379
+ 的 `decodeSessionEventPayload` 对坏体抛 `SessionError("corrupt_session")`(此前是 `StoreCorruptError`)。都不在包的导出面上。(S-612;正文见「wire」小节)
380
+ - **内部构造面(对包消费方零影响)**:`createFilePermissionRuleStores` 的 opts 多一个 `warn`(两份日志的腐读座;`onError` 只接 core 规则文件);
381
+ `FileRuleApprovalRecordStore` / `FileRuleImportTicketStore` 构造参数里原 `onError` 位改为 `warn: Logger["warn"]`;`FileApprovalNonceStore` /
382
+ `FileApprovalExemptionStore` / `FileApprovalAskStore` / `FileResumeAnchorStore` / `FileSendFileLedger` 多一个可选 `{ warn }`、`FileRunStore` 的 opts 多
383
+ `warn`(均可缺席,缺席 ⇒ `console.warn`)。`observability/corrupt-read-seat.ts` 的 `createSkippedRecordSink` 删除(分级收进重放属主)。这些构造器都不在
384
+ 包的导出面上(`package.json` `exports` 只开 `.` / `./main` / `./device-protocol`),`StoreBackend` 交出的实例型不变 —— 列在这里给仓内 fork 与自建装配口。(S-582;正文见「存储与一次性迁移」小节)
385
+
10
386
  ## 7.103.2 —— core 7.33.8 提货:`GIT_CONFIG_PARAMETERS` 里的嵌套命令凭据助手不论第几条都整只扣;OTLP 头变量一族不进宿主子进程(S-845)
11
387
 
12
388
  - 宿主子进程(host shell / 三条 git 腿 / 语言服务器 / bake-runner)自本版起:`GIT_CONFIG_PARAMETERS` 里要往里读一层命令的内联凭据助手(`!sh -c '…口令…'`)不论第几条
@@ -147,7 +523,7 @@
147
523
  - **类型面**:`ServiceConfig.modelRefusals`(新必填席;手铸 `ServiceConfig` 的消费方 tsc 即红 —— env 形写 `{}`)与 `ServiceConfig.lspEnvAllow`(S-699 新必填席 `readonly string[]`,同样 tsc 即红 —— 缺省形写 `[]`);`SealedKeyPoisonReason` 删 `unsupported_alg`(形错的 sealed 密文现在让整条不可用,到不了毒丸臂);`FoldedReadWarning` 带 `scope` 判别位、`FetchEffectiveResult` 新键 `droppedEntries`。
148
524
  - **记忆抹除面的 409 `memory.layer_unwritable` 撤销(S-697,随 core 7.31.0 #1076 提货;wire 码删除 + 行为面)**:`POST /v1/memory/erase`、`POST /v1/memory/origin/entries/:entryId/clear`、`POST /v1/sessions/:id/memory/erase` 不再在引擎之前做盘上可写预检,这个码不再出现(7.100.0 那条「按 409 体 `chmod u+w <dir>` 后重试」的指路随之撤)—— core 7.31.0 的挂载**一个模式位都不改**(只读层是会话策略:往会话不写的层里写在写点被拒,绕过工具写进去的字节永不被采纳)。7.100.0 上被 409 盖住的三种答复回到 core 自己的读数(非法选择子 400 `config.memory_erasure_request`、清标空理由 422 `memory.origin_clear_invalid`、已提交抹除同 `requestId` 重放的 200 收执),锁住的是别的层时抹除可写层条目照常 200。**升级(一次性)**:存量已锁目录在**最后一个 ≤7.30.x 会话结束后**(= 7.100.0 及更早的 server 副本全部停掉之后)执行 `chmod -R u+w <memoryDir>`(file 记忆引擎的 `<数据根>/memory`)—— 混跑期旧引擎每次只读挂载都会再锁一次,7.31 不替你解。**谁受伤**:没做这一步、又去删一条住在锁住目录里的条目的部署 ⇒ `500`(日志带 core 的同一条 `chmod -R u+w` 补救句),journal 留盘、下次起服失败,照补救句做完即自愈(`docs/DEPLOY-PREREQS.md` 同名段)。**滚动窗内这一形会反复出现**:同一数据根上还有 7.100.0 及更早的副本在跑时,它每次只读挂载一层都会把那一层再锁一次 —— 窗内做过的 `chmod` 会被撤回,7.101.0 副本在那一层上删条目照样 `500` + journal 卡盘。7.101.0 起服时对记忆挂载面探一次:有锁住的层 ⇒ 一条 warn(`engine_notice`,码 `memory.layer_locked_legacy`,`detail.layers` = 层名、`detail.remedy` = 同一句 `chmod -R u+w '<memoryDir>'`),**照常起服**(不拒启:窗内每次 boot 都可能刚被旧副本锁过);看到这一行就先别在那几层上抹除 / 清标,等旧副本全停后执行那一句。按码分支 `memory.layer_unwritable` 的消费方(sdk 错误码目录)删掉那一臂。
149
525
  - **settings 层里残留的 `crossSessionInbound: "accept"` ⇒ 跨会话消息 hold(core 7.31.0 #1078,行为面)—— 本服务不受影响(只 env 层接线)**:本服务三层来源里只有 env 管理层 `CROSS_SESSION_INBOUND` 接线(user / repo 两层不读),而 env 写 `accept` 自 7.100.0 起就拒启 ⇒ 下面这一形只波及**自持 settings 层的嵌入方**。7.30.x 把这个退役词读成 `next-turn` 并每腿通告一次(7.100.0 本节那句「由引擎自己读成 `next-turn`」随之过期);7.31.0 起它与任何未知词同路 —— 该层进 `invalidLayers`、fail-closed 落 `hold`(每条跨会话消息以 `invalid-setting` 因由扣住,`peer.inbound_disposition` 披露 `held`)。env `CROSS_SESSION_INBOUND=accept` 自 7.100.0 起就拒启,不受影响;`@sema-agent/settings-schema` 6.0.0 的文件层本就拒这个词,所以只有绕过 schema 写进去的 settings 文件会撞上。**迁移**(仅嵌入方):把每一层的 `accept` 改写成 `next-turn`。
150
- - **子进程剥名收窄(core 7.31.0 #1081,运行面;7.100.0 本节「子进程按名剥凭据形变量」一条的修正)**:十个预算名(`MAX_NEW_TOKENS` `MAX_COMPLETION_TOKENS` `AIDER_MAP_TOKENS` `AIDER_MAX_CHAT_HISTORY_TOKENS` `MAX_BATCH_PREFILL_TOKENS` `MAX_WAITING_TOKENS` `MAX_TOP_N_TOKENS` `RATE_LIMIT_TOKENS` `PROMPT_TOKENS` `CONTEXT_TOKENS`)与两个开关(`DB_FOREIGN_KEYS` `JSON_SORT_KEYS`)按**精确名**豁免,重新到得了工具子 shell、git 子进程与本地语言服务器;装饰过的变体(`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2`)仍剥。为这五名改过名或用 `inheritEnv:[names]` 点名放行的部署可以撤回那一手(留着也无害)。
526
+ - **子进程剥名收窄(core 7.31.0 #1081,运行面;7.100.0 本节「子进程按名剥凭据形变量」一条的修正)**:十个预算名(`MAX_NEW_TOKENS` `MAX_COMPLETION_TOKENS` `AIDER_MAP_TOKENS` `AIDER_MAX_CHAT_HISTORY_TOKENS` `MAX_BATCH_PREFILL_TOKENS` `MAX_WAITING_TOKENS` `MAX_TOP_N_TOKENS` `RATE_LIMIT_TOKENS` `PROMPT_TOKENS` `CONTEXT_TOKENS`)与两个开关(`DB_FOREIGN_KEYS` `JSON_SORT_KEYS`)按**精确名**豁免,重新到得了工具子 shell、git 子进程与本地语言服务器;装饰过的变体(`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2`)仍剥。为这五名改过名的部署可以撤回那一手(留着也无害);`inheritEnv:[names]` 是嵌入方构造执行环境时的选项,不是部署级放回旋钮(S-852,7.104.0 起文档写明)。
151
527
  - **SQL 后端:`session_meta` 新列 `instruction_source`(S-690,存储面)**:存量 MySQL 协议 / PostgreSQL 库升级后**拒启**(`config.schema_mismatch`,拒因里逐字给出 `ALTER TABLE session_meta ADD COLUMN instruction_source VARCHAR(64) NULL;`,PG 为 `… VARCHAR(64) COLLATE "C";`),照它加列或删库重建;滚动序恒为「**先升库、再升副本**」(加列之后,旧副本重启会把它看成幽灵列而拒启)。`DB_BACKEND=local` 与内存形零动作。见上方「SQL 存储面 BREAKING」表。
152
528
  - **沙箱名单形错拒启 + 空串值照注入(S-713,行为面)**:`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 写了空段、尾逗号(`A,`)、带 `-` / `.` 的键名(`MY-TOKEN`)或点名名单型旋钮(自己 / 另一只沙箱名单 / `LSP_ENV_ALLOW`)⇒ **拒启**(拒句点名键与第几段,不回显原值;修前静默吞或照转发)。点到的键值为空串(compose `X=${X:-}` 在宿主没设时的常态)⇒ **照注入空串**(修前不注入):local-docker 上 `-e X=` 盖掉镜像里的同名缺省值,e2b 上盖掉 `SANDBOX_PKG_SOURCE` 推导出的同名包源变量。**迁移**:按拒句修名单;不想盖缺省值就把那个名从名单里删掉。
153
529
  - **`WEB_SEARCH_ENDPOINT` 带 userinfo ⇒ 拒启(S-716,行为面)**:`WEB_SEARCH_PROVIDER` 点名了后端、`WEB_SEARCH_ENDPOINT` 形如 `https://user:pw@host/…`(或只带 `user@`)的部署升级后拒启(`config.web_search_invalid`);每请求 `settings.webSearch.endpoint` 带这一形从 202 变 `400 request.field_invalid`(修前这一形本来就用不了:fetch 每次都抛,错误文本还带出口令)。**迁移**:brave / tavily 的凭据走 `WEB_SEARCH_API_KEY`(或 `settings.webSearch.apiKey`);带 basic-auth 的 SearXNG 在前面放一层注入鉴权的反代。
@@ -165,7 +541,7 @@
165
541
  - **记忆抹除面:三个既有端点新答 409 `memory.layer_unwritable`(S-693,行为面)**:`POST /v1/memory/erase`、`POST /v1/memory/origin/entries/:entryId/clear`、`POST /v1/sessions/:id/memory/erase`。锁住且有条目的记忆层在场时(默认路径 core 7.30.1 每次项目会话都会锁 `memory/local`),operator / 属主抹除与清标先答 409 `memory.layer_unwritable`(7.99.0:项目层条目 200、`local` 条目 500 + journal 卡盘、下次起服 fatal);同一门序让这种层在场时的非法选择子(400 `config.memory_erasure_request`)、清标空理由(422 `memory.origin_clear_invalid`)与已提交抹除同 `requestId` 重放的 200 收执也先答 409。挂载面里有指向树外目录的软链 ⇒ 未分类 500(判不了,日志点名软链)。**迁移**:按 409 体点名的目录 `chmod u+w <dir>` 后**用同一个 `requestId`** 重试(得到原答复);软链形把软链移出记忆层。core 7.31.0(#1076)提货版撤销本预检与这个码。
166
542
  - **审批名单热应用(S-668 波 2,行为面)**:`governance.approvalRequire` 从「改了要重启」变热,`/health.restart.reasons` 与 `GET /v1/config/catalog` 的 `restartSlices` 不再有 `runtime-gates`(同版波 0:`models-tiers` 出、`memory-consolidation` 进 ⇒ 闭集 7 词)。三处行为变化:① 中心下发的名单形错(非数组 / `null` / 非字符串项)或拼法匹配不到 ⇒ 审批组**整组拒**:活配置零变化、候选不成为 LKG、每拍重判直到修好(改前只 warn、候选照样成为 LKG);没有 checkpoint 店的部署收到非空名单同样整组拒(改前重启后拒启 = 崩溃环);② 中心撤掉这一键 ⇒ 立刻回 env 底 `APPROVAL_REQUIRE`(改前保留到下次重启);③ 开了 leader 端点、有 checkpoint 店、只配 `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 而 require 为空的部署,leader worker **开始执行**这两张表(改前一张都不执行;收紧向)。**迁移**:按 `sema_registry_approval_require_invalid` 日志修名单;依赖「leader 不执行 deny / never-auto」的部署核对名单。
167
543
  - **回滚到 7.99.0(local 车道 + `UNATTENDED_APPROVAL_POLICY=deny`)要先收敛 / 压实 ask 账本(NP-9,持久数据面)**:7.100.0 起部署政策拒当场落 ask 行,File 形账本(`<数据根>/approval-asks/asks.jsonl`)把它记成新动词 `claimTerminalRefuse`。账本里只要还留着这种记录,7.99.0 **启动即拒启**并点名(「verb outside this build's closed set (a DOWNGRADE …)」)—— 不会静默把政策拒读成 VOID,但也起不来。**回滚步骤**:①停止接新任务,等所有 run 收敛(`GET /v1/approvals` 的 `pending` / `livePending` 都空,没有停驻或在飞的审批);②再二选一:让 7.100.0 继续跑到账本到阈压实(压实后账本只剩行快照,7.99.0 读得动,政策结算原样保留),或停机把 `asks.jsonl` 移开(已收敛的部署上只丢已决历史 —— 与 `docs/DEPLOY-PREREQS.md`「要清就停机删文件」同一动作);③再启 7.99.0。SQL 车道(`mysql` / `pg`)不受影响:政策结算是行上的列,7.99.0 原样读得动。
168
- - **子进程按名剥凭据形变量(core 7.30.1 #1072,运行面)**:工具子 shell、git 子进程与本地语言服务器收不到名字以 `KEYS` / `TOKENS` / `PASSWORDS` 结尾的宿主变量;计数豁免只看紧挨结尾词的那一个词 ⇒ `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 这五个 7.30.0 不剥的非密钥名也被剥(core 收紧过头,归 core 修)。**迁移**:host shell 用 `inheritEnv:[names]` 点名放行,或给变量改个不以这三形结尾的名字;规则全文见 `docs/DEPLOY-PREREQS.md`。
544
+ - **子进程按名剥凭据形变量(core 7.30.1 #1072,运行面)**:工具子 shell、git 子进程与本地语言服务器收不到名字以 `KEYS` / `TOKENS` / `PASSWORDS` 结尾的宿主变量;计数豁免只看紧挨结尾词的那一个词 ⇒ `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 这五个 7.30.0 不剥的非密钥名也被剥(core 收紧过头,归 core 修)。**迁移**:给变量改个不以这三形结尾的名字(模型可驱动的 host shell 没有部署级放回旋钮 —— `inheritEnv:[names]` 只是嵌入方构造执行环境时的选项,7.104.0 起文档写明;语言服务器 7.101.0 起有 `LSP_ENV_ALLOW`);规则全文见 `docs/DEPLOY-PREREQS.md`。
169
545
 
170
546
  ## 7.99.0 —— 三条 BREAKING(行为面 / 类型面 / 运行时下限)
171
547
 
package/README.md CHANGED
@@ -123,6 +123,11 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
123
123
  - **Distribution coordinates**: npm = [`@sema-agent/server`](https://www.npmjs.com/package/@sema-agent/server)
124
124
  (public on npmjs) · images = `ghcr.io/sema-agent/sema-server` + `docker.io/claybobby/sema-server`
125
125
  (both public; `:latest` rolling, `:<sha>` pinned).
126
+ - **Library embedding** (package root, e.g. `createHttpServer` + your own `StoreBackend`): after
127
+ `loadConfig()`, call `composeExecutionLane(config, ctx)` — the HTTP service and `run-local` both do. Since
128
+ 7.104.0 the execution-lane start refusals (`LEADER_ENABLED` on the host lane, a multi-tenant posture on a
129
+ shared lane, the device lane without its durable stores) are judged there, not in `loadConfig()`;
130
+ `assertExecutionLaneInvariants(config)` is the same judgment without the `config.d/remote-exec.json` override.
126
131
  - **Bundled binaries**: the package ships two `bin` entries — `run-local` (single-machine local
127
132
  runner) and `sema-up` (deployment bootstrap script).
128
133
  - `run-local` boots the same engine in-process, runs one task from the CLI objective, prints the
@@ -175,7 +180,7 @@ The server is configured entirely through environment variables. The most import
175
180
  | `REMOTE_EXEC` | unset | Sandbox execution lane: `host` / `local-docker` / `e2b` / `k8s` / `ssh` / `adb`; unset = in-process stub (with `CONFIG_PROVIDER=local` the default becomes `host`). Naming a lane without its required env (e.g. `e2b` without `E2B_API_KEY`) or an unrecognized value **refuses to start** — no silent downgrade to the host/in-process lane (fail-closed). Under `CONFIG_PROVIDER=local` a `config.d/remote-exec.json` **wins over** this env (deliberate: the file is the single-machine source of truth). Since 7.57.0 that preemption is **loud** — `remote_exec_lane_preempted_by_file` names the preempted lane, the effective one, and whether it is an **isolation downgrade** (an isolated lane replaced by a non-isolated one); a file naming a not-yet-wired isolated lane (`e2b`/`k8s`/`ssh`/`adb`) leaves the env lane in force and says so |
176
181
  | `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | unset | **SSH lane host-key verification** (`REMOTE_EXEC=ssh` only). `SSH_HOST_FINGERPRINT` = the host key's SHA256 base64 fingerprint (`SHA256:` prefix and `=` padding both optional); `SSH_KNOWN_HOSTS` = a path to a `known_hosts` file, matched literally on `host` / `[host]:port`. **Either one present ⇒ strict verification, a mismatch REFUSES the connection** (the error carries both fingerprints plus an `ssh-keyscan` line); both set ⇒ both must pass. **Both unset ⇒ a loud per-connection warning (`ssh_host_key_unverified`, man-in-the-middle risk) and the connection proceeds** — the deliberate posture for the batch-deploy lane, registered as a P-DEBT fail-open (`docs/FAIL-OPEN-CENSUS.md`). A malformed fingerprint or an unreadable `known_hosts` path **refuses to start**. Not supported in `known_hosts`, on purpose: hashed (`\|1\|…`) lines, wildcard patterns, `@cert-authority` — all skipped, so a host whose only entries are those is REFUSED (`@revoked` is honored and wins over any plain line, whatever the order) |
177
182
  | `HOOKS_TIMEOUT_MS` | unset (engine default 600000) | Per-invocation time bound for every hook seat (core `Hooks.timeoutMs`). Unset = the engine's own default (this server does not copy upstream defaults). `0` is honored as written (every seat expires immediately). A non-integer, negative, or above-`setTimeout`-ceiling (2147483647) value **refuses to start** |
178
- | `CONFIG_PROVIDER` | unset | Config source: `local` (file-backed `config.d/`, single machine) / `remote` (registry control plane) |
183
+ | `CONFIG_PROVIDER` | unset | Config source: `local` (file-backed `config.d/`, single machine) / `remote` (registry control plane). Closed set, matched exactly (7.104.0+): any other value (wrong case, surrounding spaces) refuses to boot; empty = unset |
179
184
  | `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none |
180
185
  | `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
181
186
  | `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
package/README.zh-CN.md CHANGED
@@ -108,6 +108,10 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
108
108
  - **分发坐标**:npm = [`@sema-agent/server`](https://www.npmjs.com/package/@sema-agent/server)
109
109
  (npmjs 公开)· 镜像 = `ghcr.io/sema-agent/sema-server` + `docker.io/claybobby/sema-server`
110
110
  (均 public,`:latest` 滚动 / `:<sha>` 钉版)。
111
+ - **作为库嵌入**(包根导出,如 `createHttpServer` + 自建 `StoreBackend`):`loadConfig()` 之后调
112
+ `composeExecutionLane(config, ctx)` —— HTTP 服务与 `run-local` 都这么做。7.104.0 起执行车道的几条拒启(host 车道上开
113
+ `LEADER_ENABLED`、多租户姿态落在共享车道、device 车道缺耐久店)判在这一步,不在 `loadConfig()` 里;
114
+ `assertExecutionLaneInvariants(config)` 是同一份判据,不做 `config.d/remote-exec.json` 覆写。
111
115
  - **随包二进制**:包内带两个 `bin` —— `run-local`(单机本地 runner)与
112
116
  `sema-up`(部署引导脚本)。
113
117
  - `run-local` 在进程内启同一套引擎,把命令行给的目标跑完一次、打印结果、退出——没有 HTTP 服务,
@@ -152,7 +156,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
152
156
  | `REMOTE_EXEC` | 未设 | 沙箱执行通道:`host` / `local-docker` / `e2b` / `k8s` / `ssh` / `adb`;未设 = 进程内 stub(`CONFIG_PROVIDER=local` 时缺省转 `host`)。点名了通道但必需 env 不全(如 `e2b` 缺 `E2B_API_KEY`)或值不在闭集内 ⇒ **启动即拒**——不再静默降级到 host/进程内通道(fail-closed)。`CONFIG_PROVIDER=local` 下 `config.d/remote-exec.json` **压过**本 env(刻意:单机形的真源是那份文件);7.57.0 起这次抢占**响亮**——`remote_exec_lane_preempted_by_file` 点名被抢占的 lane、最终生效的 lane,以及它是不是一次**隔离降级**(隔离 lane 被换成不隔离的);文件点了尚未接线的隔离 lane(`e2b`/`k8s`/`ssh`/`adb`)时 env 腿照旧生效并说明 |
153
157
  | `SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS` | 未设 | **SSH 通道主机密钥校验**(仅 `REMOTE_EXEC=ssh`)。前者 = 主机公钥的 SHA256 base64 指纹(`SHA256:` 前缀与 `=` 填充都可选);后者 = `known_hosts` 文件路径,按 `host` / `[host]:port` **逐字**匹配行。**任一在场 ⇒ 严格校验,不符即拒连**(拒因带两半指纹 + `ssh-keyscan` 取指纹指路一行);两只同时在场 ⇒ 合取(都要过)。**两只全缺席 ⇒ 每连接一条响亮 warn(`ssh_host_key_unverified`,中间人风险)后照常连** —— 批量部署通道的既定姿态,已登记为 P-DEBT fail-open(`docs/FAIL-OPEN-CENSUS.md`)。指纹拼错 / `known_hosts` 路径读不到 ⇒ **启动即拒**。`known_hosts` 里**刻意不支持**:hashed(`\|1\|…`)行、通配符 pattern、`@cert-authority` —— 一律跳过,于是只有这些条目的主机会被**拒连**(`@revoked` 照常生效,且无论写在哪一行都优先于普通匹配行) |
154
158
  | `HOOKS_TIMEOUT_MS` | 未设(引擎缺省 600000) | 每一只 hook 席位**单次调用**的时间上限(core `Hooks.timeoutMs`)。未设 = 用引擎自己的缺省(本服务不复制上游默认值)。`0` **按字面生效**(每个席位立即到期)。非整数 / 负数 / 超 `setTimeout` 上限(2147483647)⇒ **启动即拒** |
155
- | `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面) |
159
+ | `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面)。闭集、逐字比较(7.104.0 起):其余任何值(大小写不同、带首尾空白)拒启;空串 = 未设 |
156
160
  | `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景 |
157
161
  | `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
158
162
  | `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |