@sema-agent/server 7.136.0 → 7.137.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 (214) hide show
  1. package/MIGRATION.md +173 -0
  2. package/USAGE.md +84 -16
  3. package/deploy/sema-up/README.md +14 -0
  4. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  5. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  6. package/dist/approval-content-kind.d.ts +6 -0
  7. package/dist/approval-content-kind.js +7 -0
  8. package/dist/approval-gate-currency.d.ts +48 -6
  9. package/dist/approval-gate-currency.js +16 -9
  10. package/dist/approval-hmac.d.ts +13 -1
  11. package/dist/approval-hmac.js +1 -1
  12. package/dist/approval.js +3 -3
  13. package/dist/auth-bridge.js +2 -2
  14. package/dist/boot/execution-env.d.ts +24 -1
  15. package/dist/boot/execution-env.js +69 -6
  16. package/dist/boot/reapers.d.ts +3 -0
  17. package/dist/boot/reapers.js +15 -1
  18. package/dist/boot/resolve-spec.d.ts +4 -0
  19. package/dist/boot/resolve-spec.js +16 -17
  20. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  21. package/dist/boot/stage-04-stores.d.ts +1 -1
  22. package/dist/boot/stage-05-execution-env.d.ts +3 -1
  23. package/dist/boot/stage-05-execution-env.js +11 -2
  24. package/dist/boot/stage-06-runners.d.ts +3 -1
  25. package/dist/boot/stage-06-runners.js +5 -1
  26. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  27. package/dist/boot/stage-07-capability-layer.js +1 -1
  28. package/dist/boot/stage-08-reapers.d.ts +4 -1
  29. package/dist/boot/stage-08-reapers.js +2 -1
  30. package/dist/boot/stage-09-leader.d.ts +4 -1
  31. package/dist/boot/stage-10-http-server.d.ts +4 -1
  32. package/dist/boot/stage-10-http-server.js +3 -2
  33. package/dist/boot/stores.d.ts +1 -1
  34. package/dist/broker/broker.d.ts +89 -0
  35. package/dist/broker/broker.js +287 -0
  36. package/dist/broker/constants.d.ts +39 -0
  37. package/dist/broker/constants.js +15 -0
  38. package/dist/broker/http1.d.ts +111 -0
  39. package/dist/broker/http1.js +256 -0
  40. package/dist/broker/lease-book.d.ts +87 -0
  41. package/dist/broker/lease-book.js +142 -0
  42. package/dist/broker/lease-file.d.ts +60 -0
  43. package/dist/broker/lease-file.js +127 -0
  44. package/dist/broker/main.d.ts +2 -0
  45. package/dist/broker/main.js +20 -0
  46. package/dist/capabilities/composite-members.d.ts +2 -1
  47. package/dist/capabilities/composite-members.js +2 -1
  48. package/dist/capabilities/engine-capabilities.js +1 -0
  49. package/dist/capabilities/scenarios.d.ts +23 -8
  50. package/dist/capabilities/scenarios.js +13 -8
  51. package/dist/capabilities/skills.d.ts +28 -5
  52. package/dist/capabilities/skills.js +8 -0
  53. package/dist/center-credential-renewer.js +5 -1
  54. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  55. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  56. package/dist/checkpoint-identity.d.ts +3 -3
  57. package/dist/config-catalog.js +9 -6
  58. package/dist/config-types.d.ts +9 -0
  59. package/dist/config.d.ts +1 -1
  60. package/dist/config.js +34 -8
  61. package/dist/deployment-governance.d.ts +2 -2
  62. package/dist/deployment-governance.js +7 -9
  63. package/dist/device-ws-protocol.d.ts +1 -0
  64. package/dist/device-ws-protocol.js +2 -1
  65. package/dist/file-history-disclosure.d.ts +4 -2
  66. package/dist/file-history-disclosure.js +2 -0
  67. package/dist/hooks/classic-stations.d.ts +11 -54
  68. package/dist/http/admission.js +10 -7
  69. package/dist/http/approval-sweeps.js +4 -3
  70. package/dist/http/caller-trust.d.ts +2 -1
  71. package/dist/http/caller-trust.js +1 -3
  72. package/dist/http/checkpoint-ctx.d.ts +48 -7
  73. package/dist/http/checkpoint-ctx.js +40 -0
  74. package/dist/http/dispatch.js +15 -10
  75. package/dist/http/durable-run-leg.d.ts +5 -1
  76. package/dist/http/durable-run-leg.js +1 -1
  77. package/dist/http/health-self-report.d.ts +2 -2
  78. package/dist/http/mcp-remote-text.d.ts +5 -0
  79. package/dist/http/mcp-remote-text.js +7 -0
  80. package/dist/http/principal-gate.d.ts +15 -10
  81. package/dist/http/principal-gate.js +48 -9
  82. package/dist/http/resume-legs.d.ts +2 -20
  83. package/dist/http/resume-legs.js +52 -38
  84. package/dist/http/route-ctx.d.ts +8 -2
  85. package/dist/http/routes/approvals-assistant.js +18 -4
  86. package/dist/http/routes/capabilities.js +11 -1
  87. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  88. package/dist/http/routes/mcp-reconnect.js +5 -7
  89. package/dist/http/routes/runs.js +2 -1
  90. package/dist/http/routes/session-sync.js +42 -6
  91. package/dist/http/routes/sessions.js +2 -1
  92. package/dist/http/routes/tasks.js +8 -6
  93. package/dist/http/server.d.ts +4 -0
  94. package/dist/http/stored-body-replay.d.ts +3 -3
  95. package/dist/http/stored-body-replay.js +6 -1
  96. package/dist/http/wire-types.d.ts +52 -27
  97. package/dist/identity-source.d.ts +65 -0
  98. package/dist/identity-source.js +45 -0
  99. package/dist/memory-store-refusal.d.ts +3 -3
  100. package/dist/model-entry-refusal.d.ts +2 -0
  101. package/dist/model-entry-refusal.js +11 -0
  102. package/dist/model-select.d.ts +6 -17
  103. package/dist/model-select.js +0 -3
  104. package/dist/namespaced-rule-name.d.ts +1 -1
  105. package/dist/observability/fail-open.d.ts +13 -1
  106. package/dist/observability/fail-open.js +13 -1
  107. package/dist/observability/metrics.js +1 -1
  108. package/dist/observability/run-terminal.js +1 -0
  109. package/dist/operator-ask.d.ts +2 -2
  110. package/dist/org-delivery-gate.js +1 -1
  111. package/dist/org-memory-admission.d.ts +2 -2
  112. package/dist/org-memory-admission.js +39 -2
  113. package/dist/parked-decide.js +3 -2
  114. package/dist/peer-directory.js +2 -0
  115. package/dist/permission-rule-list.d.ts +2 -2
  116. package/dist/plugins/checkpoint-store-sql.d.ts +19 -9
  117. package/dist/plugins/checkpoint-store-sql.js +6 -6
  118. package/dist/plugins/file-history-store-sql.js +7 -14
  119. package/dist/plugins/file-run-store.js +8 -2
  120. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  121. package/dist/plugins/k8s-exec-protocol.js +116 -28
  122. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  123. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  124. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  125. package/dist/plugins/k8s-pod-reaper.js +122 -0
  126. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  127. package/dist/plugins/local-checkpoint-store.js +3 -3
  128. package/dist/plugins/memory-run-store.js +2 -0
  129. package/dist/plugins/one-time-migrations.d.ts +28 -9
  130. package/dist/plugins/one-time-migrations.js +48 -5
  131. package/dist/plugins/permission-rule-store-file.js +2 -2
  132. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  133. package/dist/plugins/permission-rule-store-sql.js +16 -17
  134. package/dist/plugins/pg-pool.js +4 -0
  135. package/dist/plugins/pg-session-storage.js +2 -2
  136. package/dist/plugins/remote-env-adb.d.ts +3 -0
  137. package/dist/plugins/remote-env-adb.js +1 -0
  138. package/dist/plugins/remote-env-device.d.ts +3 -0
  139. package/dist/plugins/remote-env-device.js +4 -0
  140. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  141. package/dist/plugins/remote-env-e2b.js +1 -0
  142. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  143. package/dist/plugins/remote-env-file-error.js +7 -1
  144. package/dist/plugins/remote-env-host.d.ts +7 -1
  145. package/dist/plugins/remote-env-host.js +7 -1
  146. package/dist/plugins/remote-env-k8s.d.ts +137 -10
  147. package/dist/plugins/remote-env-k8s.js +276 -14
  148. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  149. package/dist/plugins/remote-env-local-docker.js +1 -0
  150. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  151. package/dist/plugins/remote-env-ssh.js +1 -0
  152. package/dist/plugins/run-store-sql.js +10 -4
  153. package/dist/plugins/session-ownership.d.ts +17 -3
  154. package/dist/plugins/session-ownership.js +19 -5
  155. package/dist/plugins/sql-errors.d.ts +23 -2
  156. package/dist/plugins/sql-errors.js +15 -0
  157. package/dist/plugins/store-contracts.d.ts +10 -1
  158. package/dist/plugins/store-fault.d.ts +2 -2
  159. package/dist/plugins/store-fault.js +4 -2
  160. package/dist/plugins/tidb-pool.js +7 -0
  161. package/dist/plugins/tidb-session-store.js +2 -2
  162. package/dist/request-key-closure.js +1 -1
  163. package/dist/request-value-closure.d.ts +31 -2
  164. package/dist/request-value-closure.js +7 -1
  165. package/dist/rule-entry-list.d.ts +3 -2
  166. package/dist/rule-entry-list.js +2 -2
  167. package/dist/rules-consent.js +7 -4
  168. package/dist/run-local.d.ts +2 -2
  169. package/dist/runs.js +2 -1
  170. package/dist/runtime-governance.d.ts +5 -5
  171. package/dist/sandbox-git-lease.d.ts +49 -0
  172. package/dist/sandbox-git-lease.js +60 -0
  173. package/dist/sandbox-git-plane.d.ts +84 -0
  174. package/dist/sandbox-git-plane.js +105 -0
  175. package/dist/security.d.ts +16 -4
  176. package/dist/security.js +8 -3
  177. package/dist/server-secret-env.d.ts +6 -0
  178. package/dist/server-secret-env.js +14 -0
  179. package/dist/session-error-wire.d.ts +14 -6
  180. package/dist/session-error-wire.js +2 -1
  181. package/dist/session-policy-wire.d.ts +1 -1
  182. package/dist/session-policy-wire.js +2 -2
  183. package/dist/session-sync.d.ts +1 -1
  184. package/dist/spec-fields.d.ts +43 -13
  185. package/dist/spec-fields.js +33 -12
  186. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  187. package/dist/stored-request-prompt-profile-m30.js +25 -0
  188. package/dist/task-mcp.d.ts +1 -1
  189. package/dist/task-settings.d.ts +2 -2
  190. package/dist/tool-approval.d.ts +12 -2
  191. package/dist/tool-approval.js +8 -3
  192. package/dist/tool-rule-names.d.ts +5 -0
  193. package/dist/trace/core-keyset-guard.d.ts +4 -4
  194. package/dist/trace/engine-notice-wire.d.ts +1 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/exit-seat.js +2 -2
  197. package/dist/trace/ledger-sink.d.ts +4 -5
  198. package/dist/trace/ledger-sink.js +3 -7
  199. package/dist/trace/project.d.ts +37 -3
  200. package/dist/trace/project.js +20 -6
  201. package/dist/trace/redact.d.ts +18 -6
  202. package/dist/trace/redact.js +118 -53
  203. package/dist/trace/self-minted-wire.d.ts +377 -3
  204. package/dist/trace/self-minted-wire.js +122 -0
  205. package/dist/trace/wire-projection-faces.d.ts +5 -3
  206. package/dist/trace/wire-projection-faces.js +7 -3
  207. package/dist/web-admin-client/apply-effective.js +0 -1
  208. package/dist/web-admin-client/center-request.d.ts +20 -2
  209. package/dist/web-admin-client/center-request.js +40 -5
  210. package/dist/web-admin-client/http-client.js +6 -6
  211. package/dist/web-admin-client/types.d.ts +5 -5
  212. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  213. package/package.json +2 -2
  214. package/wire-changes/server@7.137.0.json +825 -0
package/MIGRATION.md CHANGED
@@ -7,6 +7,179 @@
7
7
  > ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
8
8
  > 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
9
9
 
10
+ ## 7.137.0 —— 对 web-admin 中心的请求不再跟随重定向(安全收紧;行为面;反代会回 3xx 的部署升级前把两席写成最终地址) · k8s 车道 `K8S_SANDBOX_ENV` 的值改经每只 pod 自己的 Secret(部署面 BREAKING;沙箱 Role 要 secrets 的 `create`,名单非空而 Role 不许 ⇒ 起服拒;共用沙箱命名空间的两套部署升级前各给一个命名空间) · SQL 后端 `task_run` 表加三列 `identity_source` / `identity_scope` / `identity_principal_inc`(🔴 升级前先 `ALTER TABLE` 加列;持久数据,不能删表重建;嵌入方 `RunRecord` 多必填键 `identity`〔BREAKING,类型面〕) · 请求键 `promptProfile` 退役、换成布尔 `promptLean`(BREAKING,wire;零别名;升级前停驻的任务存体起服原地改写〔一次性迁移 M30,窗 30 天〕,没改写到的续跑 409) · 模型目录条目带 `promptGuidance` 的不服务、fable 系模型 id 不再带 fable 提示段(行为变更) · 带图预检的计量 `images_omitted_total` 与日志行 `images_omitted_no_vision_model` 退役(运维面;行为变更;看板 / 告警改读会话流的 `model.capability_downgraded`) · 引擎 7.56.0 的部署序:先升全部读者、回滚前先决完停驻行(三处:停驻审批行 `mount_site`、技能座 `skill_scopes`、会话规则文法 5) · 规则名里的 `*`:限制侧整名 glob、授予侧一条孤 `*` 不再让整张放行表作废(行为变更;升级前先审放行表) · 规则名外缘的空白被拒(行为变更;人敲的规则编辑文本修剪后再提交) · 嵌入方 hook / mod 的 handler 答案按数据收、非普通记录一律拒(行为变更,fail-closed) · 不是名字的主体在鉴权边界被拒:`400 principal_not_a_name`(行为变更;反代把 UTF-8 用户名原样塞进主体头的部署升级前核) · 计划审阅的决断体可带 `checkpointId`:门换了 ⇒ `409 approval_stale`、不落决断(wire additive;决断方先读能力位 `planReviewCheckpointId` 再带键) · `delegation.ask_unresolvable` 通告的 `detail` 多可选键 `remedies`(wire 加性;宿主按 `kind` 渲染出路,闭集外的 `kind` 忽略) · 引擎 7.56.0 的五个新键本版刻意不投(`requestedBy`、`answeredBy`、`hostFloor`;登记披露,无消费方动作) · 本版其余 wire 面变化的消费方动作逐面表态(多数零动作;设备执行器的修订 3、`attachments.mcpUnavailable`、`resultEntryIds` 各有一句真动作) · 7.136.x 停下、技能请求项带 `mods` 的任务续跑失败(行为变更;新提交早已 400) · 订正 7.136.0 节第 4 条:没有返回任务目标全文的读口(`GET /v1/runs/:id` 不带目标)
11
+
12
+ 1. **对 web-admin 中心的请求不再跟随重定向(安全收紧;行为面)**:此前只有令牌续签不跟重定向,其余对中心的请求(拉配置、principal 视图、skill 正文、prompt 产物、配额租约、
13
+ fleet 公告 / 用量 / 注销、服务租约、执行公钥上报)被传输层静默跟随 —— POST 307 / 308 把请求体带去 `Location`,同 origin 的跟随连 `Authorization` 一起带走。本版起每条都不跟:
14
+ 中心回重定向(任何 3xx,304 除外)⇒ 该条请求被拒,拒句 `web-admin center request: endpoint answered with a redirect (HTTP <n>); it was not followed and no credential was sent anywhere else — …`
15
+ 点名这条请求打的那一席。**只伤一类部署**:`SEMA_WEB_ADMIN_URL`(或 `SEMA_WEB_ADMIN_LIVE_URL`)指的不是中心的最终地址,而是会 301 / 302 / 307 / 308 的反代(`http` → `https` 跳转、主机名规范化、尾斜杠重定向)。
16
+ **升级前做**:二选一 —— 让反代直接回答,或把这两席写成最终地址;用文件形用户会话凭证(`SEMA_WEB_ADMIN_TOKEN_FILE` 里的信封)的部署,还要用新地址重新登录一次,使信封里的 `issuer` 也是最终地址
17
+ (续签遇重定向落终态「被拒」:`center_credential_renew_failed`,`reason: "rejected"`,直到凭证文件变化)。**升级后看**:起服那一行 `config_source_unreachable_using_env`(起服拉配置撞上重定向时拒句落在这一行,多数部署第一眼看到的是它)
18
+ 与 `web_admin_refresh_failed` / `fleet_announce_failed` / `fleet_usage_report_failed`、鉴权桥的 `auth_bridge_jwks_fetch_failed` 这几条 warn 的错误字段里是否出现
19
+ `answered with a redirect`;出现 = 还有一席没写成最终地址(JWKS 那一行点名 `AUTH_BRIDGE_JWKS_URL` 或它派生自的签发方地址)。其余部署零动作。
20
+
21
+ 2. **k8s 车道:`K8S_SANDBOX_ENV` 的值改经每只 pod 自己的 Secret ⇒ 沙箱 Role 要 secrets 的 `create`(部署面 BREAKING;名单非空而 Role 不许 ⇒ 起服拒)**:本版起名单点到的值不再写进 pod spec,
22
+ 只进该 pod 的一只 Secret(pod 的容器 env 带 `secretKeyRef`;Secret 属于 pod,随 pod 由集群回收)。**升级前做**:设了 `K8S_SANDBOX_ENV` 的部署,给服务账号在沙箱命名空间的 Role 加一条
23
+ `- apiGroups: [""] resources: [secrets] verbs: [create]`(随包 `deploy/sema-up/chart/templates/sandbox-rbac.yaml` 与 `deploy/sema-up/stack/sema-stack-cluster.yaml` 已带)。
24
+ 动词恰是服务为这只 Secret 真发的调用:`create`(建每只 pod 的 Secret,之后不再碰);**不要**给 `get`(服务从不读回 Secret,给了就让服务账号读得到沙箱命名空间里的每一只 Secret)
25
+ 与 `delete`(Secret 属于它的 pod,由集群的垃圾回收随 pod 删,不经服务账号)。
26
+ 缺了 ⇒ 起服拒,拒句带 `[sandbox-secret-rbac]`、点名缺的动词;起服问不到(API 连不上 / 服务账号建不了 SelfSubjectAccessReview)⇒ 照起,但第一个转发的 run 连接即拒(`auth_failed`)、它的 pod 被删 —— 值不会回落进 pod spec。
27
+ 没设 `K8S_SANDBOX_ENV`(或点到的键在服务进程环境里都没有值)的部署零动作。建议同时给集群开 Secret 静态加密(值现在在 etcd 的 Secret 里)。
28
+ 成员 git 凭据代理的租约 Secret 另要 `patch`(改写租约用 merge-patch;**不需要** `update`)—— 本版该平面恒关、没有代理会被挂上,起服门也不问它,**现在不用给**;
29
+ 平面打开的那一版再加(那时平面判官问的是 `create` / `patch`,不许 ⇒ 平面熄、读数 `secret_seat_denied`)。
30
+ 另一条行为变更不需要动手:服务会删已结束的沙箱 pod(容器全部结束,或它的 run 已终且过了 `max(6 分钟, WORKSPACE_SNAPSHOT_TIMEOUT_MS + 15 秒)`)—— 事后 `kubectl logs` 看不到了,要留现场在 run 结束前取。
31
+ 这里的 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 是**建这只 pod 的副本**的值:建 pod 时算一次、写进 pod 注解 `sema.io/run-end-hold-ms`,回收器只读这一格、不读自己副本的配置 ——
32
+ 滚动升级或改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时每只 pod 按建它的副本的时限,时限短的副本不会提前删时限长的副本还在存快照的 pod;不带这条注解的 pod(本版之前建的)不按 run 终态删,容器结束后照删。
33
+ 给沙箱命名空间配了限制 pod 注解的准入策略的部署,升级前放行注解键 `sema.io/run-end-hold-ms`(否则建 pod 被拒)。
34
+ **两套部署共用一个沙箱命名空间**(缺省都是 `sandboxes`)时,回收器按身份标签列 pod、标签里没有部署身份 ⇒ 一套部署会删另一套**容器已结束**的 pod:
35
+ 升级前用 `K8S_NAMESPACE` 给每套部署各一个沙箱命名空间(本版没有关掉回收器的旋钮)。
36
+
37
+ 3. **SQL 后端:`task_run` 表加三列 `identity_source` / `identity_scope` / `identity_principal_inc`(🔴 升级前先加列;持久数据,不能删表重建;嵌入方 BREAKING,类型面)**:
38
+ 本服务受理一条 run 时,把这次请求在凭据门上验过的身份来源(`service_token` / `sso` / `direct_door_jwt`)连同登录令牌的 scope 写进 run 行(化身号一列先恒空,
39
+ 契约包给出那一位之后才有值),之后只读 —— 续跑、挂起、终局、重启都不改。本服务不发 `ALTER`:存量库缺这三列 ⇒ 起服列册对账拒启(`config.schema_mismatch`),拒句带可直接粘贴执行的语句。
40
+ **升级前做**(MySQL 协议,三句):
41
+ `ALTER TABLE task_run ADD COLUMN identity_source VARCHAR(32) NULL;`
42
+ `ALTER TABLE task_run ADD COLUMN identity_scope VARCHAR(190) NULL;`
43
+ `ALTER TABLE task_run ADD COLUMN identity_principal_inc VARCHAR(64) NULL;`
44
+ (PostgreSQL,三句)
45
+ `ALTER TABLE task_run ADD COLUMN identity_source VARCHAR(32) COLLATE "C";`
46
+ `ALTER TABLE task_run ADD COLUMN identity_scope VARCHAR(190) COLLATE "C";`
47
+ `ALTER TABLE task_run ADD COLUMN identity_principal_inc VARCHAR(64) COLLATE "C";`
48
+ 存量行三列是 NULL,不回填(没有事实可回填):读法是「这一行没有属主在受理时验过的凭据的记录」,与无鉴权写面(`ALLOW_UNAUTHED_WRITES`)受理的 run、
49
+ 别人替属主按下唤醒 / 决定投递铸的新一跑同读,一律不合格。行上的 `source` 列(系统名,登录令牌直连写 `sso`)只作归因,不被读成认证来源。
50
+ **顺序**:先升库(加列)、再升副本 —— 加完列之后,仍是 7.136.x 的副本**再启动**会把三列看成幽灵列而拒启(只挡重启;已在跑的旧副本建行时带列名清单、不写这三列,
51
+ 它们受理的 run 三列为 NULL,同上读法)。**不要**删表重建 `task_run`:表里是 run 账本(在飞 / 停驻的 run 与提交幂等键)。`DB_BACKEND=local` 与内存后端零动作
52
+ (文件后端的 `run.json` 多三键,升级前写下的文件读成 NULL)。
53
+ **回滚到 7.136.x:只回退二进制不可行**(MySQL 协议与 PG 皆然)—— 加过列的库上,7.136.x 的起服列册对账把多出的三列读成幽灵列、起服拒(拒句点名 `UNKNOWN column(s) identity_source, identity_scope, identity_principal_inc`、不给 ALTER)。
54
+ 回滚 = ① 先停光全部本版副本;② 手工 `ALTER TABLE task_run DROP COLUMN identity_source;`、`ALTER TABLE task_run DROP COLUMN identity_scope;`、`ALTER TABLE task_run DROP COLUMN identity_principal_inc;`(两方言同三句);③ 再起 7.136.x。
55
+ DROP 只丢本版受理的 run 的身份记录:再升回本版、照上面加列之后,这些行读成 NULL(不合格),run 的其余读写不受影响。
56
+ **嵌入方(类型面,BREAKING)**:包根导出的 `RunRecord` 多一个必填键 `identity` —— 三形之一:`{ source: "sso", scope, principalInc }`(`principalInc` 可为 `null`)、
57
+ `{ source: "service_token" 或 "direct_door_jwt", scope: null, principalInc: null }`、`null`。只读它的代码零动作;手写的 `RunRecord` 字面量(测试夹具、自造的 run 读者)tsc 红 —— 补 `identity: null`。
58
+
59
+ 4. **请求键 `promptProfile` 退役,换成布尔 `promptLean`(BREAKING,wire;零别名;引擎 7.56.0)**:新提交三腿(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)上带 `promptProfile` ⇒
60
+ `400 request.body_shape`(`unknownKeys` 点名它)。**调用方改法**:`promptProfile: "classic"` ⇒ `promptLean: false`;`promptProfile: "simple"` ⇒ 不带键(或 `promptLean: true`)。
61
+ `promptLean` 不是布尔 ⇒ `400 request.field_invalid`。
62
+ **停驻中的任务**:7.136.x 及以前停驻、请求体里带 `promptProfile` 的任务,本版**起服时**把存下的请求体原地改写(同一张映射:`"classic"` ⇒ `promptLean: false`,长形照旧;
63
+ `"simple"` / 词表外 ⇒ 删键,缺省形;体里已有布尔 `promptLean` ⇒ 它说了算、只删旧键;续跑时钟不动),起服一行运维日志 `checkpoint_ctx_prompt_profile_rewritten`(扫了几行、改了几行,不带值)。
64
+ 这是**一次性迁移,窗 = 本版发布日起 30 天**,窗内每次起服都扫;改写过的任务此后续跑几次、窗关之后再续跑,形都不变 —— 零动作。
65
+ **升级序(与第 7 条同向,且更严)**:**先停光 7.136.x 副本,再起第一个本版副本**(它起服即改写)。改写过的体 7.136.x 读不懂 —— 它只认 `promptProfile`,读到改写后的体回到缺省形
66
+ (`"classic"` 的长形静默丢),所以本版副本起过服之后,不能再有 7.136.x 副本续跑这些任务;反过来,本版起服之后才由 7.136.x 副本停下的任务存体里仍是旧键。
67
+ **升级前先缩光副本,按安装路径分三种**(滚动更新做不到「先停光 7.136.x、再起第一个本版副本」;下面三段各管一种安装路径,照你的那一种做):
68
+ **Helm 形安装**(`kube-up.sh` 的 `--kube=existing` / `--kube=k3s`;`--kube=machines` 下设了 `STACK_ENGINE=helm`):图表只在钥匙卷是 `ReadWriteOnce` 时给 Deployment 写 `Recreate`;缺省的多副本(`server.replicas: 3`、`ReadWriteMany`)不写更新策略,`helm upgrade` 走 Kubernetes 缺省的滚动更新 —— 第一只本版 pod 起服改写存体时 7.136.x 副本还在跑。所以升级前先 `kubectl -n <命名空间> scale deploy/sema-server --replicas=0`(启用 `serverV4pro` 时同样缩 `deploy/sema-server-v4pro`),等旧 pod 退光,再 `helm upgrade`(重跑 `sema-up` / `kube-up.sh` 在这些安装形上走的就是这条),升完核副本数回到 `server.replicas`。
69
+ **`--kube=machines` 缺省形**(不设 `STACK_ENGINE` = `render`;`cluster-up.sh` 渲染清单后 `kubectl apply`,不是 Helm):清单里 `sema-server` 的副本数按机器数取 2(少于 3 台)或 3(3 台及以上),没有 `strategy` 键 = Kubernetes 缺省的滚动更新,也没有 `serverV4pro`。所以升级前先 `kubectl -n sema scale deploy/sema-server --replicas=0`(第一台机器上写 `k3s kubectl`),等旧 pod 退光,再 `kubectl apply`(重跑 `sema-up` 做的就是这一步;清单里的 `replicas` 把副本数写回 2 或 3),升完核副本数。
70
+ **compose 两档**(`--profile=full-single` / `local-plus`):`server` 服务只有一只容器、没有副本数键 —— 没有第二只旧副本在滚动更新里陪跑,零动作。
71
+ **没改写到的**任务(窗内从没起过本版的部署;违反上面的升级序、在本版副本起服之后由 7.136.x 副本停下的任务)续跑答 `409 checkpoint.unsupported_version`(`retriable: true`,卡仍待决;
72
+ 句子点名 `promptProfile`,不回显值),不按旧键读、也不静默丢键:窗内在最后一只 7.136.x 副本停下之后重启一只本版副本,它起服即改写这些任务(重启用的配置里须 `DURABLE_APPROVAL=true` —— 多租户部署缺省关,要显式写,且多租户还需 `OPERATOR_PRINCIPALS` 非空,否则起服拒(拒句点名出路);起服日志里 `checkpoint_ctx_prompt_profile_rewritten`(M30)与 `checkpoint_ctx_credentials_sealed`(M28)两行都出现、且 M30 那一行是 `"window":"open"`,才说明守卫过了、改写跑过 —— M28 的窗先关,窗关后它那一行 `"window":"closed"` 属正常);
73
+ 窗关之后仍带旧键的任务只能取消后作为新任务重提(`promptLean` 代替 `promptProfile`)。跳过本版、直接升到更晚版本的部署,先在本版上起一次服。
74
+ **库形嵌入方**(包根的 `createHttpServer` + 自建 `StoreBackend`,不经 `@sema-agent/server/main` 这个服务进程入口):本条的起服改写(M30;`checkpoint` 存体里请求凭据的封签 M28 同形同处)只在服务进程的起服第 6 段(`src/boot/stage-06-runners.ts`)里跑 ——
75
+ 包根没有导出它,公开的迁移序列(`createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()`)也不含它(它要等 checkpoint 店建成之后才能跑)。所以库形嵌入方自己的装配不会改写存体:7.136.x 停驻、体里带 `promptProfile` 的任务续跑一律答 `409 checkpoint.unsupported_version`,
76
+ 上面「窗内重启一只本版副本即改写」的出路对它不成立。窗内的出路:用本包的服务进程入口(`import "@sema-agent/server/main"` 或 `node dist/main.js`,指向同一份库 / 数据根)起一次服 —— 那一次起服即改写,但有两个前提并列,缺哪个都不行:① `DURABLE_APPROVAL=true`(多租户部署缺省关,要显式写,且多租户还需 `OPERATOR_PRINCIPALS` 非空,否则这一次起服拒(拒句点名出路:设 `OPERATOR_PRINCIPALS`,或单租户部署去掉 `REQUIRE_PRINCIPAL`);两只迁移只在 checkpoint 店存在时跑,店只在这个开关为真时建,开关为假 ⇒ 这一次起服静默不跑它们;这道守卫早于本版,本版只补了这句说明);② 只对 M28 的窗内(UTC 日历日,含 2026-11-20 当天)有意义:服务进程入口读到的保管目录里要有嵌入方封签用的那对钥匙 —— 做法是 `SEALED_KEY_DIR` 指向嵌入方自己 `ensureCheckpointCtxSealing(sealedKeyDir)` 用的那个目录(服务进程入口缺省读 `<数据根>/sealed-key`;读到的目录里没有可用的钥匙文件 ⇒ 服务进程新建一对钥匙去封剩余的明文行,嵌入方不持有这对钥匙,这些行的续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`,嵌入方要先持有那对钥匙〔保管目录只在起服时读〕才读得开,否则取消后作为新任务重提)。两只迁移的窗不同:M28(封签剩余明文凭据行)的窗到 2026-11-20,M30(改写 `promptProfile`)的窗到 2026-11-24 —— 2026-11-21 至 2026-11-24 之间的那一次起服只改写 `promptProfile`、不再封签明文凭据行(前提 ② 在那几天不相干;仍是明文的行续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"plaintext"`,取消后作为新任务重提),两行日志名都会出现(M28 那一行是 `"window":"closed"`、`rowsScanned` 为 0)。起服后核日志行 `checkpoint_ctx_credentials_sealed`(M28)与 `checkpoint_ctx_prompt_profile_rewritten`(M30)都出现 —— 没出现 = 守卫挡了、迁移没跑;出现了还要看字段,不只看行名:`"window":"open"` 且 `rowsScanned` 是你预期的行数,才说明那一只迁移真扫过(`"window":"closed"` ⇒ 一行没读)。M30 的窗关之后(2026-11-24 之后)与其余部署同,取消后作为新任务重提。
77
+ **回滚到 7.136.x**:被改写过的停驻任务在 7.136.x 上续跑回到缺省形(长形丢,零通告)—— 回滚前先决完或取消停驻的任务(与第 7 条「回滚前先决完停驻行」同一个动作)。
78
+
79
+ 5. **模型目录条目带 `promptGuidance` 的不服务;fable 系模型 id 的 fable 提示段(行为变更;引擎 7.56.0)**:目录条目(中心配置 / `config.d` / 部署 env 条目)写了非空 `promptGuidance` ⇒
80
+ 那一条不服务(与别的坏条目同一个拒因 `config.model_entry_invalid`,`fields: ["promptGuidance"]`,起服 / 刷新时运维日志点名)。**做法**:删掉条目里的 `promptGuidance`;引导句的去处是
81
+ 模型声明的提示特征 `promptTraits` —— 契约包的模型条目还没有这个座,在它到之前本服务不透传。经本服务目录跑 fable 系模型 id 的部署:引擎不再按模型 id 认 fable 系,
82
+ 这些 run 本版起不带 fable 提示段、取模型缺省的短形;要那几段的部署在契约包加座之前留在 7.136.x。
83
+
84
+ 6. **带图预检的计量与日志行退役(运维面;行为变更)**:计量 `images_omitted_total{model}` 与运维日志行 `images_omitted_no_vision_model` 本版起不再出现 —— 带图请求遇不收图的模型,
85
+ 由引擎在真替换图片的那一刻在会话流 / 账本里发 `engine_notice` `model.capability_downgraded`(`detail: { model, capability: "image", status, sessionId, runId }`)。
86
+ **做法**:引用这两者的看板 / 告警改读那条通告(按 `code` 计数);不改的看板会静默少一条线。
87
+
88
+ 7. **引擎 7.56.0 的部署序:先升全部读者,回滚前先决完停驻行(三处)**:
89
+ - **停驻审批行的 `mount_site`**:本版停下的、引擎挂载的工具(每一只内置工具,问答工具在内)的审批行多一个成员与一个要求词;7.136.x 及以前的副本读到它在认领之前拒
90
+ (`checkpoint.unsupported_version`,`version_newer`,行留 pending)。⇒ 多副本滚动升级:先把会续跑的每只副本都升到本版,再放新的停驻;**回滚到 7.136.x 之前**先把本版停下的审批决完或取消。
91
+ - **技能座的 `skill_scopes`**:引擎给「带 hooks 的技能在场时停下的 run」写这个要求词;本服务不编技能 hooks(能力位 `mods.skill_seat` 恒 false、请求面拒 `skills[].mods`),
92
+ 本服务写下的停驻行不会带它 ⇒ 零动作。
93
+ - **会话规则文法 5**:本版的写口给会话规则行盖文法 5 的戳,7.136.x 及以前的副本读不了这种行(那条会话的每一跑开跑即失败,见下面「回滚」);持久规则桶同理(SQL 后端 `permission_rule` 的 `grammar` 列:
94
+ 本版写过的桶旧副本整桶拒)。⇒ 先把读同一只会话规则店 / 规则桶的每只副本都升到本版,再放会话规则与「始终允许」的写;
95
+ **回滚**:本版写下的行旧版读不了,旧版的写口也改不了它(写前读那一行即拒)。实测(同一 local 数据根,本版写过会话规则 —— 连只写一条 `Write` 也盖文法 5 —— 再起 7.136.0):
96
+ 这些会话的**每一跑开跑即 `failed`**、终局码 `corrupt`(一次工具调用都没有),`GET /v1/sessions/:id/policy` 答 `500 corrupt`;没写过会话规则的会话照常。
97
+ 回滚后这些会话的出路只有删会话(或再升回本版)。
98
+ **不要**在库里把戳改小:同一条 `toolDeny` 的 `*` 在文法 4 里什么也不拒(改戳 = 静默放宽)。
99
+
100
+ 8. **规则名里的 `*`:限制侧是整名 glob、授予侧的孤 `*` 不再让整张放行表作废(行为变更;引擎 7.56.0)**:
101
+ - **限制侧**(请求 `settings.permissions.deny` / `ask`、运维 `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` 与中心下发的同名表、`excludeTools`、会话规则 `toolDeny`、技能 `disallowedTools` / 子代 `denyTools`):
102
+ 名字里的 `*` 读成整名 glob,孤 `*` = 每一只工具。7.136.x 上这些写法在受理口一律被拒,所以没有存量部署受伤;**变的是调用方的读法**:此前
103
+ `deny:["*"]` ⇒ 422 `invalid.tool_name`、`excludeTools:["*"]` ⇒ 400 `config.tool_face_control`、技能 `disallowedTools:["*"]` ⇒ 400 `request.field_invalid`、子代 `denyTools:["*"]` ⇒ 400 `config.agent.unknown_tool` 这几条拒不再出现 ——
104
+ 它们现在 200 / 受理,而且**真的**全拒 / 全问(`deny:["*"]` 的 run 里每一只工具都被拒)。拿那几条 4xx 当信号的调用方:改按 200 与实际策略读。
105
+ - **授予侧**(请求 `settings.permissions.allow`、技能 `allowedTools`、运维放行席):一条孤 `*` 什么也不授(照旧被通告,`wildcard_all`),但**同表别的条目照常放行** ——
106
+ 此前整张表读空、一条也不放。**升级前做**:靠授予侧一条 `*` 把整张放行表作废的,把表里不该放行的条目删掉(放宽方向,先审已有的表)。
107
+ **回滚到 7.136.x**:限制侧受理过的 `*` 条目(尤其会话规则行,见第 7 条)旧版读不了或读成什么也不拒 —— 回滚前先清掉本版写下的这类条目。
108
+
109
+ 9. **规则名外缘的空白从 7.137.0 起被拒:卡上人敲的规则编辑文本带前导 / 尾随空白或工具名后空格 ⇒ 裁决之前 `400`(行为变更;引擎 7.56.0;消费者动作 = 去掉空白)**:
110
+ `persistRule: { rule, edited: true }` 的文本,**前导空白**(含制表符)、**尾随空白**、**工具名与 `(` 之间的空格**(`" Bash(adb *)"`、`"Bash(adb *) "`、`"Bash (adb *)"`)不再被收下 ——
111
+ 裁决之前 `400 request.body_shape`,体带 `ruleRejectCode: "invalid.grammar"`,`error` 句点名去掉空白后的写法(`Bash(adb *)`);这只 ask 未结算、卡还在、店里零落地。7.136.x 上**前导空白**与**工具名后空格**的文本被收下并落成一行规则(尾随空白那时就已拒,不是本版新拒)。
112
+ **做法**:界面 / 壳把人敲的文本**修剪后再提交**(引擎的解析器不修剪,修剪是编辑器表面的事);文本**内部**的多余空白不在本条之内(`Bash(adb *)` 仍落 `Bash(adb:*)`)。无存量数据要迁移。
113
+
114
+ 10. **嵌入方:hook / mod 的 handler 答案按数据收,非普通记录的答案一律拒(行为变更,fail-closed;引擎 7.56.0「答案是数据」)**:handler(`PreToolUse` 钩子答案、`prompt.compose` 的 overlay 答案等)返回的值只收原始值或普通记录 / 数组(整只拷贝并冻结);
115
+ **类实例、`Map`、函数、symbol、bigint、环、原型上带契约成员的对象、不可枚举成员的载体**是站点违约 —— 该次判决被拒(`PreToolUse` 上读数 `ran=0 denied=1`),此前原型载体与类实例被照常接受。
116
+ 本服务自己铸的 handler 答案逐点核过:答案铸点(带 `action` / `block` / `additionalContext` / `overlays` 键的对象字面量)共 20 处 —— `settings.hooks` 注册 19 处、部署座品牌行 1 处 —— 全是普通对象字面量:`settings.hooks` 的成员只有闭集词串(`action`)、截断后的文本串与钩子 stdout 经 `JSON.parse` 得到的 `updatedInput`;
117
+ 品牌行的成员是引擎交回的上一手答案原样接上本服务的一条 overlay 常量;部署座的拒绝计量与工具追踪两只观察者、`StopFailure` / `PostCompact` 的翻译恒答 `undefined`。
118
+ 服务部署零动作;只有**自己往引擎里挂 handler 的嵌入方**要核:把答案改成普通对象字面量。
119
+
120
+ 11. **不是名字的主体在鉴权边界被拒:`400 principal_not_a_name`(行为变更;引擎 7.56.0 的主体名判官)**:主体头(去掉首尾空白之后)、SSO 令牌的 sub、直连门 JWT 的 sub
121
+ 带控制、行段分隔或不可见格式字符(引擎 `principalNameOf` 的判据),或 sub 只有空白 ⇒ 本版起 400 带码,7.136.x 收下;空的 / 只有空白的主体头照旧读作「没有主体」。
122
+ **最常见的来源**:反代把 UTF-8 用户名原样塞进主体头 —— 本服务按 latin1 一字节一字符读头,UTF-8 字节里的 0x80–0x9F 读成 C1 控制字符、0xAD 读成软连字符(格式字符),
123
+ 于是「张三」(E4 B8 **89**)、`Martín`(C3 **AD**)都被读成「不是名字」的乱码。
124
+ **怎么核**:升级前在反代上看主体头的字节 —— 有 0x80–0x9F 或 0xAD 的字节 ⇒ 本版起被拒,必须改;其余非 ASCII 字节照收(但同一个人在签名令牌的门上解出的是 UTF-8 原文、
125
+ 在主体头上是 latin1 乱码,两个串,建议一并改);或在 7.136.x 的会话列表 / 审批队列里找属主带乱码的行。升级后看 `400 principal_not_a_name` 的计数。
126
+ **怎么改**:反代把主体头编成 ASCII 或其它不含控制字符的规范形(用户 id、百分号编码等)再发 —— 同一个人在每条门上要是同一个串。
127
+ **存量行**:7.136.x 按乱码串记下属主的会话、停驻审批、规则桶,本版上没有主体够得着(那串不再是谁的名字)。本版**没有通用的迁移口**,如实说清能做到哪一步:
128
+ SQL 后端的多租部署,运维可以试收编口 `POST /v1/adoption`(operator 面)把一位用户的行迁到新名字下 —— `fromPrincipal` 写 7.136.x **真存下的**那一串:
129
+ 用户名的 UTF-8 字节按 latin1 逐字节读出、再去掉首尾空白(边界读头时剥过;0xA0 在 latin1 是 NBSP,串尾的会被剥掉),JSON 里用 `\u00XX` 转义写;
130
+ `toPrincipal` 写改编码之后的新串(收编口不判它是不是一位主体的名字,由运维保证;同一个 `fromPrincipal` 只能收编一次)。**限制**:收编口要求旧串编码成记忆作用域段后
131
+ 不超过 64 个字符(非 ASCII 每个字节约占 6 个字符)⇒ 大约三个汉字以内的名字迁得动,更长的会被拒(`400 request.field_invalid`)。迁不动的、以及文件后端
132
+ (`DB_BACKEND=local`,没有收编口)上的旧行,留在旧串名下:只能回滚到 7.136.x 继续用,或删掉。`MEMORY_ORG_DIRECTORY_JSON` 的主体键同判:有这类键 ⇒ 起服拒
133
+ (拒句按键在 JSON 文本里的出现序点名:`principal key #N (in text order)`),改成新串。
134
+ **回滚到 7.136.x**:同一份乱码串照旧被收下,存量行照旧够得着;本版下以新串写下的行归新串。
135
+
136
+ 12. **计划审阅的决断体可带 `checkpointId`(决断方看到的那道门):门换了 ⇒ `409 approval_stale`、不落决断(wire additive;服务部署零动作,消费方动作见下)**:
137
+ `POST /v1/assistant/tasks/:id/plan_review` 认可选键 `checkpointId`;带了而它不是该会话此刻待决的那道 plan 门 ⇒ `409 approval_stale`(与 `/decide` 同码同句),计划未批、卡仍待决,
138
+ 体带当前门 `currentPending: { checkpointId }`(当前门没有可信身份 / 恰被并发消费时缺席);不带键照旧受理。
139
+ 陈旧判过了之后、认领之前门再换(窄窗)⇒ `409 conflict.not_resumable`(认领输家那一码,决断同样不落;直连门上这一份证明的 nonce 已用掉,重签再决)。
140
+ · **决断方(client-core `decidePlanReview` 等)**:透传人看到的那道门的 `checkpointId` —— 取 park 结果的 `terminal.checkpointId`(`GET /v1/runs/:id` 的 `result`、同步提交体、流的 `done.result`),
141
+ 或运维队列 / 收件行上的 `checkpointId`、`GET /v1/runs/:id/events` 的 `needs_review` 账本行 `data.checkpointId`(同一个值)。值形:非空串、≤ 128 字符,否则 `400 request.field_invalid`。
142
+ **先读能力位再带键**:`GET /v1/capabilities` 的 `planReviewCheckpointId === true` 才带(位缺席 = 老 server,带了那一次决断整个失败)—— 别按版本号嗅探。
143
+ · **壳 / cli**:读 `409 approval_stale` 体的 `currentPending.checkpointId`,重取**那道门的计划**给人看,人认可后带新身份再决 —— 别拿它直接重发(那是在批一份没人看过的计划);
144
+ 体里没有 `currentPending` ⇒ 重拉队列或 run 再决。
145
+ · **直连门签名器**:送这个键就要把它签进 HMAC 信封第 6 位的扩展对象(闭集字典序 `answer` / `checkpointId` / `permissionModeAfter`,例:`["s1","nonce-1",null,"approve",null,{"checkpointId":"cp_…"}]`);
146
+ 不送它的签名器零改动(缺席时字节逐字不变);带了不签 ⇒ `401 approval_mac_invalid`。
147
+ · **跨版本**(读上一条的能力位就不会撞上):7.86.0–7.136.x 的 server 收到这个键答 `400 request.body_shape`(`unknownKeys` 含 `checkpointId`)—— 位缺席时不带键(= 回到没有陈旧保护的旧行为);
148
+ 7.86.0 之前的 server 没有键闭集门,键被**静默丢**(200 照收、陈旧保护不在)—— 对那么老的 server 别指望这一位。
149
+ · **重拉还是同一个身份**:体里没有 `currentPending`、重拉 run 结果拿到的身份与你送的一样仍 409(队列 / 收件行上这一道门不带 `checkpointId`)⇒ 这道门的身份没过服务端的信任边界(坏行),带它永远对不上;
150
+ 人看过**当前**计划之后不带这个键再决(这一次没有陈旧保护,告知运维查那一行)。
151
+ · 连带:`/decide` 收到 `checkpointId` 仍是 400,桶由 `unknownKeys` 换成 `unsupportedKeys`(指路计划审阅那道门);按桶分支的客户端核一下。
152
+
153
+ 13. **`delegation.ask_unresolvable` 通告的 `detail` 多可选键 `remedies`(wire 加性;消费方动作 = 读它、按 `kind` 渲染出路;引擎 7.56.0)**:用户读的 `detail.remedy` / `message` 两句自引擎 7.56.0 起不再教 `bypassPermissions`(模式放行那条出路只在运维放开该模式时才可提);这条出路改作数据:`detail.remedies` = 闭集 `approver` / `permission_mode` 上的非空表,恰在 `detail.remedy` 在场时带 —— `{ kind: "approver" }` 与 `{ kind: "permission_mode", value: "bypassPermissions", disclosedOn: "gate.disposition.released" }`(放行披露在那只调用的 `tool_end.gate.disposition.released`)。本服务只透传:不渲染、不改写、不补句。
154
+ **消费方动作**:渲染这条通告的宿主(client-core / cli / web-admin)读 `detail.remedies`、按 `kind` 渲染出路,闭集外的 `kind` 忽略;`remedies` 缺席 = 无出路可渲染。只读 `remedy` 句的消费者看不到 `permission_mode` 那条出路(此前句子里有,现在只在数据里)。服务部署零动作。
155
+
156
+ 14. **引擎 7.56.0 的五个新键本版刻意不投:`tool_start.requestedBy`、`tool_end.requestedBy`、`tool_end.answeredBy`、两个出口的 `hostFloor`(登记披露;无消费方动作)**:无消费方动作。这五位是引擎新增、本服务的帧投影登记簿表态「不投」的键(扩展自己发起调用的归因三位:本部署挂的每张注册都声明 `uses.calls: []`,零到达;主机底线姿态两位:本服务不声明 `RunnerDeps.hostFloor`,恒不会被铸出)。它们在 7.136.0 的 wire 上从未出现过,所以不是面丢失;翻转条件与理由见 CHANGELOG 7.137.0 节同名条。
157
+
158
+ 15. **本版其余 wire 面变化的消费方动作逐面表态(多数零动作;设备执行器的修订 3、`attachments.mcpUnavailable`、`resultEntryIds` 各有一句真动作)**:CHANGELOG 7.137.0 节里下列 wire 面的变化,逐面写消费方要做什么:
159
+ - **会话同步拉取口**(`GET /v1/sessions/:id/sync/manifest` · `…/entries` · `…/history/blobs/:hash`):库断连(含排在最前的会话门那一读)与部署错配(缺表 / 权限拒)由 `500 internal.error` 改答 `503 state.file_history_unreadable`,
160
+ 体带 `permanence`(连接类 `transient` / 部署错配 `terminal`)。**消费方动作**:按 `permanence` 分支 —— `transient` 稍后重发同一请求;`terminal` 不重试、报运维(运维建表 / 授权之后同一请求可再发);
161
+ 别再拿这三口的 `500` 当这两类故障的信号。文件历史店的故障答(`422 adopt_failed` / `restore_failed` 等)对缺表 / 权限拒同样带 `permanence: "terminal"`,同一读法。
162
+ - **`invalid_session`**:已存日志父链断裂的会话,`POST /v1/tasks` 同步腿由 `200` + failed 体改 `500 invalid_session`;`GET /v1/sessions/:id` / `…/memory-status` 的码由 `internal.error` 改 `invalid_session`。
163
+ **消费方动作**:当确定性失败处理(重试改不了判 —— 对 5xx 自动重试的端对这个码别重试),出路是换新会话或从断点之上的条目分叉。
164
+ - **`inheritEnv` 的三只 `400 request.field_invalid`**:拒体多 `field: "inheritEnv"` 与 `reason`(`unknown_word` · `not_a_string` · `lane_not_host` · `no_authorizer_session`)。**无动作**(加性;严格校验拒体键集的客户端放行这两键)。
165
+ - **`409 resume.constraint_rejected` 体的 `acceptableForm`**:**消费方动作**(批准界面):在场就把那一形呈给批准人、照它再批(读法与审批卡上的 `args` 同一种:已脱敏,超帽 ⇒ 缺席);缺席照旧。
166
+ - **`wiring_manifest.skills`**:**无动作**(加性;对 `wiring_manifest` 键集做严格校验的消费方放行它)。名册列的是这条 run **交给**引擎的技能;引擎对单只技能有大小上限,超过的那一只名册里有、引擎不装(CHANGELOG 同条的已知残余)。
167
+ - **`deviceExecutor.relayUrl`**:**消费方动作**(设备拨号方):在场就拨它、不再自己拼引擎地址 + `wsPath`;缺席照旧拼。
168
+ - **`inherit_env.names_withheld`(会话流通告)**:**无动作**(加性;渲染通告的宿主可按 `code` 显示「这些变量没给工具 shell」—— 只有名字没有值,执行环境说不出时 `state: "unknown"`;闭集外的码照旧忽略)。
169
+ - **设备协议修订 3 与结局码 `name_too_long`**(执行器实现者):能力位 `deviceExecutor.protocolVersion` 与 challenge 帧报 `3`。**动作**:要报「路径里有一段超过文件系统名字上限」的执行器,按随包 `docs/DEVICE-EXECUTOR-PROTOCOL.md` §1.2
170
+ 声明修订 3(取 challenge 的与自己实现的较小者)再发 `name_too_long`(只用于文件指令);仍声明 1 / 2 的执行器不受影响,别发这个词。读文件失败码的调用方:`name_too_long` 与 `not_found` 同读(文件不在)。
171
+ - **`attachments.mcpUnavailable`**(请求键,布尔):**消费方动作**:要关掉引擎的「这一回合有 MCP 服务器挂不上」附件帧的调用方发 `attachments.mcpUnavailable: false`(缺省 = 开);非布尔 ⇒ `400 request.field_invalid`。
172
+ 7.136.x 上这个键被静默丢(压回缺省开),所以别按版本号嗅探 —— 对 7.136.x 发它是无害的无效动作。
173
+ - **`result.resultEntryIds`**(引擎的结果键,随整只结果过境):**消费方动作**(可选):要「这段答复对应会话里哪几条消息」的读它(会话条目 id,会话序;空表 = 没读自任何已提交消息,缺席 = 出处不明,两者不同读);不读的忽略未知键即可。
174
+
175
+ 16. **7.136.x 停下、技能请求项带 `mods` 的任务续跑失败(行为变更;新提交早已 400)**:7.136.x 的受理门容技能项的未知键,引擎 7.55.x 忽略 `skills[].mods`,带它的请求照常成功;本版起新提交带它 ⇒ `400 request.field_invalid`。
176
+ 7.136.x 上停下、存下的请求体里技能项带 `mods` 的任务,本版续跑时这一项原样交给引擎 7.56.0,引擎把它当一张技能扩展注册读、形不对即拒 —— 那一次续跑失败(7.136.x 上它被忽略)。
177
+ **怎么核**:只有手写过 `skills[].mods` 的调用方会有这种停驻任务。**做法**:升级前在 7.136.x 上决完或取消它们;升级后撞上的,取消后去掉 `mods` 重提。
178
+
179
+ 17. **订正 7.136.0 节第 4 条:没有返回任务目标全文的读口(`GET /v1/runs/:id` 不带目标)**:7.136.0 节第 4 条让「把 inbox `objective` 当完整目标用的」消费方「改读 run 详情面」—— 这句不真:
180
+ `GET /v1/runs/:id` 不带任务目标(没有 `objective`,也没有 `objectivePreview`)。本服务**没有返回任务目标全文的读口**:inbox 行的 `objective` 与 `GET /v1/assistant/tasks` 的 `objectivePreview`(写入时已脱敏、约 120 字符)是目标唯一的读数。
181
+ **消费方动作**:要全文展示 / 按全文匹配的,在提交时自己留存目标(提交方本来就持有它);会话历史里那一轮的用户消息是引擎处理过的输入(部署的钩子可能在前面加了文字),不保证与提交的目标逐字相同,别把它当目标原文。
182
+
10
183
  ## 7.136.0 —— 中心登录令牌必须带 `scope`,不带的令牌验证不过(安全;行为面收紧,BREAKING;另含嵌入方 `webAdminJwtVerifier` 类型面) · anthropic 车道两把凭据同设起服拒(`ANTHROPIC_API_KEY` 与 `ANTHROPIC_AUTH_TOKEN`;行为收紧) · 一次性迁移 M5 退役(持久数据面;行为变更;嵌入方 `OneTimeMigrationReport` 去两键〔BREAKING,类型面〕;跳过 7.104.0–7.135.x 直升的库补列与铸令牌要自己做) · `GET /v1/assistant/inbox` 行的 `objective` 改成 run 行的目标预览(wire 值变更) · 审批卡信封多一个键 `approvalCard`(整队一次滚完;回滚到 7.135.x 时待决卡按 fail-safe 出口收场) · k8s 工作区快照桶起服时核存在性:没建桶的部署起服拒(行为收紧;升级前确认桶在) · `WORKSPACE_SNAPSHOT_PREFIX` 成为 `/` 分隔的闭集路径:不合形起服拒、未配快照存储也核(行为收紧;同时放宽到多段) · 子进程的命令行停驻时只以封签落盘(安全;零动作,升级序与回滚要留意:先停光 7.135.x 副本再起第一个 7.136.0 副本)
11
184
 
12
185
  1. **中心登录令牌必须带 `scope` —— 不带的令牌自本版起验证不过(安全;行为面收紧,BREAKING;另含嵌入方类型面)**:鉴权桥(`SEMA_WEB_ADMIN_URL` 在位默认开,或 `AUTH_BRIDGE_ISSUER`)
package/USAGE.md CHANGED
@@ -419,10 +419,10 @@ MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
419
419
 
420
420
  **可选 — 开发者模式 building blocks(core 1.43,默认中立)**
421
421
  ```bash
422
- # 给指定角色挂「去品牌的通用编码提示词」CODE_AGENT_PROMPT(工程纪律:理解后改/最小复杂度/危险操作谨慎/验证后报告)。
423
- MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「任务没自带 systemPrompt」时跑编码提示
422
+ # 给指定角色按词点名引擎目录的「编码角色基底」(去品牌的通用编码提示词:理解后改/最小复杂度/危险操作谨慎/验证后报告)。
423
+ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「任务自己没说角色基底」时跑编码基底
424
424
  ```
425
- - 经 `RoleSpec.systemPrompt` 挂在**角色**上,非开发角色保持中立、全局默认 `DEFAULT_SYSTEM_PROMPT` 不变;任务自带 `systemPrompt`(或客户端注入)时仍优先。
425
+ - 经 `RoleSpec.promptRole: "code"` 挂在**角色**上(7.137.0 起 / 引擎 7.56.0;此前是把同一段目录文本粘进 `RoleSpec.systemPrompt`),非开发角色保持中立。角色基底只有一条来源链:任务 → 请求的模型角色的预设 → 引擎委派子代的人设,**第一个说出基底的来源整个说了算** —— 任务自带 `systemPrompt`(或客户端注入)、或场景自己写了角色词(cowork 场景写 `cowork`)时任务优先;这些 run 的宿主角色是 `code`(按 `when.host.role` 条件的 overlay 自此对它们生效),基底字节与此前相同。
426
426
  - **验证门**(core 1.44,opt-in):请求体带 `verify:true`(可选 `verifyRounds`,夹到 [1,5]、默认 2)→ 任务跑完后由**独立只读 verifier**(`verifier` 角色,默认=主模型)证据强制地"试图 break 它",FAIL 则把 findings 注回同 session 续跑修复→重验,循环到 PASS 或轮数上限。结果带 `verification:{verdict,rounds,findings,evidence}`(`verdict` 看质量,`result`/`status` 仍是实现的)。**仅 `/v1/tasks`(同步)与 `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多轮非单流,请求 verify 会 400)。verifier 工具默认 = 实现任务工具滤掉 `effect:"write"`(只读边界)。`/metrics` 加 `verifications_total{verdict}`。⚠️ 同步 `/v1/tasks` 形**没有事件通道**(过程帧无处回放,只回终局 JSON)——带 `verify`/`cascade` 的 200 体自 7.23.0 起附可选 `notice` 键指路 `POST /v1/runs`(要过程可见性用异步形)。
427
427
  - **记忆(文件记忆引擎,2026-07-08 起唯一记忆面)**:core 注入式文件引擎——任务开始时 materialize 记忆目录(`MEMORY_ENGINE_DIR`,默认 `~/.ai-agent`),模型用**普通文件技能**读写记忆(CC `# Memory` 指令 + 派生索引;无 remember/recall 工具),任务边界 harvest 门(secret/cap 扫描)提交。单用户默认开,`MEMORY_ENGINE=off` 显式关;多租户恒关(文件基座无租户隔离,fail-closed)。旧 SQL 记忆面(`MEMORY_BACKEND`/`EMBEDDING_*`/`MEMORY_READ_LIMIT`/去重/向量检索、`GET/DELETE /v1/memory` 与 session memory 写 verb)已退役,数据不迁移——升级后对库跑一次 `scripts/drop-memory-tables.sql`。`body.memoryWrite:false` 仍是每请求只读开关(harvest 不提交)。
428
428
  **`MEMORY_PERSISTENCE_CAPABLE`(三态,7.14.0 起)**=**部署自述**「本机能不能把用户的『记住 X』落到持久处」,与请求键 `memoryWrite` 两轴正交(后者收紧本次运行,前者是 operator 声明;任一取否即只读方向)。缺省不设=引擎按 roster 自行推断;`true`=本部署有推断看不见的持久通道(自定义 writer / 记忆 MCP / 远程执行车道与记忆根共享挂载);`false`=强制只读披露并关掉文件工具往记忆根的写通道。不认得的词拒启。
@@ -704,6 +704,9 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
704
704
  引擎把**本会话跟踪集**收敛到该消息时刻的边界;`acceptPartialRestore: true` 容忍部分恢复
705
705
  (缺省=响亮失败带逐文件账本)。旧键 `rewindFiles` 的 capture 义=容忍 no-op(首触跟踪恒开),
706
706
  restore 义(与 resumeAt 同发)=typed 拒并指路 `restoreFiles`(迁移显式化是 core 裁定)。
707
+ - rewind 与设备车道(`REMOTE_EXEC=device`):带 `resumeAt` 的那一发是**根提交**(不是续跑一族),`deviceId` 照收、按根提交的规矩判 —— 已绑:缺席 = 沿用 / 同 = 幂等 /
708
+ 异 = 403 `device.identity_mismatch`(rewind 不换设备,换设备走显式换绑);未绑:携 = 首绑、缺席 = 409 `device.not_bound`;非 device 车道携它 = 400 `request.precondition_unmet`。
709
+ `resumeAt` 自己的拒(值形 400、`resume_at.*`)先答,设备准入链后答。
707
710
  - **按 run 关掉捕获**(7.104.0):提交体带 `fileHistory: "disabled"`(唯一词;坏值 400
708
711
  `request.field_invalid`)⇒ 这条 run 的编辑照常落盘,但不做首次触碰备份、不铸回合边界,委派出去的子代继承;
709
712
  `wiring_manifest.fileHistory` 报 `"disabled"`。同一提交再带还原键(`restoreFiles` / `rewindFilesTo` / `rewindFiles`)⇒ 受理时
@@ -739,6 +742,8 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
739
742
  # 凭证两键名 SEMA_WEB_ADMIN_TOKEN / SEMA_WEB_ADMIN_TOKEN_FILE 与「二选一」判官的属主 = @sema-agent/settings-schema
740
743
  # `WEB_ADMIN_ENV` / `judgeWebAdminCredentialDeclaration`(6.1.0 起;管理面 / 壳 / 本服务 import 同一处)
741
744
  SEMA_WEB_ADMIN_URL=http://<web-admin-host>:3100 # 启动拉 GET /api/config/effective(Bearer+ETag),覆盖 env 兜底
745
+ # 必须是中心的最终地址:中心回重定向(301/302/303/307/308)⇒ 该条请求被拒、不跟(请求体与 Authorization 不发往 Location),
746
+ # 拒句 `web-admin center request: endpoint answered with a redirect (HTTP <n>); …` 点名这一席;LIVE_URL 同。反代请直接回答。
742
747
  SEMA_WEB_ADMIN_TOKEN=<SERVICE_PULL_TOKEN 的值> # 取自配置控制面主机 .env;只读拉取令牌
743
748
  # SEMA_WEB_ADMIN_TOKEN_FILE=/path/to/token # 或:凭证文件(与上一行二选一,都设=拒启)。每条中心请求都重读 ⇒ 轮换 / 换账号改写文件即可,
744
749
  # 中心请求面不重启;文件不可读 / 为空 = error 级 center_credential_unreadable,不当作无凭证。
@@ -798,7 +803,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
798
803
  # (不公告就听不到能力位),缺一 ⇒ 起服拒启 config.fleet_member_unfenced —— 成员不许悄悄脱离围栏。
799
804
  # 续租打 POST /api/fleet/live/serving-lease(契约 FLEET_PATHS.servingLease;活性基址见 SEMA_WEB_ADMIN_LIVE_URL)。
800
805
  # 心跳答复体读不出 / 不是 JSON 对象 = 没有能力读数(位与租约钟原样,warn fleet_announce_reply_unreadable)。
801
- # FLEET_SCOPE=acme # 7.121.0:本引擎所在的**租户 scope**(配置渲染器为多租引擎写 = 它的 per-worker 令牌绑的 scope)。缺席 =
806
+ # FLEET_SCOPE=acme # 7.121.0:本引擎所在的**租户 scope**(配置渲染器对每台受管引擎写,缺省 scope 也写 = 它的 per-worker 令牌绑的 scope)。缺席 =
802
807
  # 缺省 scope global;在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启
803
808
  # config.fleet_scope_invalid)。与 FLEET_GENERATION 不联动。只进排空口的身份比对:请求的 scope 与它比,
804
809
  # 请求不带 scope 读成 global。续租体与心跳不带它(scope 由中心凭证绑定)。
@@ -1321,6 +1326,11 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1321
1326
  内建 `code` 与 `default` 共用一只工厂,技能按 `default` 过滤 —— 只写了 `scenarios: code` 的技能不进内建 `code` 的 run,卡上也不列(要进,标 `default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,按它自己的名字 `code` 过滤。
1322
1327
  能力位 `scenarioSkills`(恒 `true`)缺席 = 老 server,卡上没有这一键;别从 `400 config.user_invoked_skill_unknown` 的拒句里刮名字(那是引擎的散文,会变)。
1323
1328
 
1329
+ **看一条 run 装着哪些技能(7.137.0 起)**:这条 run 的 `wiring_manifest` 帧(流式 SSE、`GET /v1/runs/:id/events`、非流式 `POST /v1/tasks` 200 体的 `wiringManifest`)带本服务自铸段
1330
+ `skills` = 上面 1–4 叠出来、交给引擎的那一组(同一组、同一个序),每条 `{ name, description, source }`:`source` 除了 `"factory"` / `"center"` 再多一个 `"request"`(第 4 条,请求体自带的;
1331
+ 与部署同名的那条被丢,不在册)。`disableSkills: true` 或一只都没有 ⇒ `skills: []`;委派子代的帧不带这一段;缺席 = 老 server 或装配不是本服务出口产的,别读成 `[]`。
1332
+ 续跑腿报的是续跑那一次装配的名册。`description` 是展示文本(请求自带的那几条是调用方写的,上 wire 前过脱敏、截长)。部署技能正文超过引擎加载上限的那一只,这一段仍会列出(引擎不装它,见上一段)。
1333
+
1324
1334
  **MCP 条目的工具风险轴(`toolAxes`,7.128.0 起)**
1325
1335
 
1326
1336
  `config.d/mcp.json` 的 `servers[]`(或配置中心下发的同形条目)可带 `toolAxes`,为这台 server 的**每只工具**声明风险轴;声明逐字上引擎的 `McpServerSpec.toolAxes`,喂审批门与计划模式围栏:
@@ -1615,7 +1625,7 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1615
1625
  (`SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` …)与名单型旋钮名起服已向引擎声明为本进程自有凭据,生效配置里每条模型条目的 `apiKeyEnv` 也在名单里
1616
1626
  (中心热应用新增 / 改名条目时在模型目录换代前重声明,名单只随落地的世代增长 —— 改名后旧名照扣到重启,没落地的候选不留痕;条目的 `apiKeyEnv` 是引擎扣不住的名 ⇒ 该候选整拒),`"all"` = 整份环境**减**它们,任何继承词都不把它们
1617
1627
  交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 另放一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名,或经 `settings.env` 显式授予);`"scrub"` / 缺席 = 上面的缺省剥除。🔴 **`"all"` 只在单用户部署兑现**:`REQUIRE_PRINCIPAL=true`
1618
- ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带键 ⇒ 400。部署方
1628
+ ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带 `"all"` ⇒ 400。词表外的值(`"ALL"` · `""` · 名单 `["PATH"]` · 布尔 · `null` …)、非 host 车道带 `"all"`、部署不接 authorizer 时带 `"all"` 这三只都是 `400 request.field_invalid`,7.137.0 起体另带 `field: "inheritEnv"` 与 `reason`(`"unknown_word"` = 是字符串但不在词表;`"not_a_string"` = 根本不是字符串;`"lane_not_host"` = `"all"` 落在非 host 车道;`"no_authorizer_session"` = `"all"` 而部署不接 authorizer),壳按这两位认拒因、不解析句子(句子 / 码 / 状态不变;续跑腿同判,折叠 409 随体带同一组)。托管部署的 `422 request.host_posture_refused` 不在这组:它说的是「这台部署的姿态不给」,不是这个键写错,按 `errorCode` 认、不带这两位。部署方
1619
1629
  **没有**旋钮能替调用方打开它,也没有旋钮能在单用户部署上关掉它(单用户部署的用户把他自己的环境交给他自己驱动的 shell);起服一行 info
1620
1630
  `task_inherit_env_honored {honored}` 告诉你这台机器认不认。上面「部署级名单不给模型驱动 shell」那条不变 —— 两件事是两条轴(部署名单 vs 调用方 × 单用户射程)。
1621
1631
 
@@ -1650,7 +1660,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1650
1660
  |---|---|---|---|
1651
1661
  | `E2B_SANDBOX_ENV` | `REMOTE_EXEC=e2b` | 逗号分隔的**裸变量名**,如 `E2B_SANDBOX_ENV=GIT_TOKEN,NPM_TOKEN` | 点到的键从服务进程环境**原样**注入沙箱(e2b 沙箱级 env;与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢) |
1652
1662
  | `DOCKER_SANDBOX_ENV` | `REMOTE_EXEC=local-docker` | 同上 | 点到的键以 `docker exec -e K=V` 注入每条命令(与单条命令自带的 env 同名时,命令自带的赢) |
1653
- | `K8S_SANDBOX_ENV` | `REMOTE_EXEC=k8s` | 同上 | 点到的键从服务进程环境**原样**写进沙箱 pod 的容器 env(与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢;详见 §9.3c) |
1663
+ | `K8S_SANDBOX_ENV` | `REMOTE_EXEC=k8s` | 同上 | 点到的键的值从服务进程环境**原样**写进该沙箱 pod 自己的 Secret,pod 的容器 env 只带 `secretKeyRef` 引用(pod spec 里没有值;与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢;要 Role 的 secrets `create`,详见 §9.3c) |
1654
1664
 
1655
1665
  三只旋钮只在**自己的车道被选中时**才求值(别的车道上它们是无关配置,不校验、不告警)。未设 / 空串 = 不注入。**形错拒启**:空段
1656
1666
  (`A,,B`、尾逗号)、非变量名形(`1BAD`、带空格、`NAME=value` 整段贴进来)、点名名单型旋钮(`E2B_SANDBOX_ENV=E2B_SANDBOX_ENV`,
@@ -2278,6 +2288,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2278
2288
  | `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
2279
2289
  | HTTP `501`(`/v1/runs`) | 内存模式不支持异步 | 配持久存储(`DB_BACKEND=mysql` / `pg` + 坐标;7.106.0 起给了坐标必须点名 `DB_BACKEND`) |
2280
2290
  | HTTP `500` + `errorCode:"corrupt_session"`(7.104.0 起;流式口在应答头之后则是 `done` 帧的 `result.terminal.code`,后台 run 是 run 行的 `errorCode`) | 这只会话的**存档读不出**、唤不醒(文件后端:会话日志没有会话头;SQL 后端:一行事件读不出)。这一轮没跑,一个 token 没烧。直接读这只会话的口(头探针 / 事件订阅 / 会话视图 / 改名 / 导出 / 分叉 / 2c 同步)同答(契约 §17;文件后端上事件订阅与改名 7.136.0 起同答,此前一个 200 起流却零帧、一个 404) | **别重试**(确定性失败)。**文件后端**:运维看 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数、stderr 契约行 `[sema] fail-open: server.session-repo.corrupt-record-skipped` 与随带的一条 `fail_open` warn(后两者每进程每 tag 只一次,warn 带首条明细);逐条明细(文件路径、会话 id、行号、成因)每命中一行进探针文件 —— 起服前设 `SEMA_FAILOPEN_PROBE=<文件路径>`(会话头那一行本身读不出时,它就是其中一条);修或移开那份存档。**SQL 后端**不经这只座:坏的那一行由错误句子点名(`session_event.payload` + 会话 id + 序号 `seq`)。要继续工作就换一个新会话 |
2291
+ | HTTP `500` + `errorCode:"invalid_session"`(7.137.0 起;流式口在应答头之后则是 `done` 帧的 `result.terminal.code`,后台 run 是 run 行的 `errorCode`) | 这只会话**存着的日志**读得出、结构走不通:某条目的父不在日志里(悬空)、条目是自己的祖先(自指 / 成环)、叶子不在日志里。这一轮没跑、一个 token 没烧(同步提交答 500、不计费)。读整条分支的口(会话视图 `GET /v1/sessions/:id` / `…/memory-status`)同答;事件订阅与头探针只读会话头的叶子,对这一形照旧 200 / 起流(契约 §17.7)。体的 `error` 是引擎原句(点名条目 id,不含盘上路径与会话 id),没有 `reason` 机读位。会话同步**导入**时调用方交来的日志没过校验是另一件事:`422 invalid_entries` | **别重试**(确定性失败)。换一个新会话;或从断点之上的条目分叉;或由运维修好 / 移走那份日志。同一 `Idempotency-Key` 重发会重新受理(这类零工作的拒不占键) |
2281
2292
  | 流里一帧 `engine_notice` `code:"session.records_skipped"`,`detail:{ dropped, entriesKept, sessionId }`(7.104.0 起,只在文件后端) | 这只会话的存档打开时有 `dropped` 条记录读不出、被跳过了;这一轮**照跑**,但模型读到的历史只剩 `entriesKept` 条。存档没修好之前,每次真正打开这只会话都会再说一次(会话缓存命中的轮次不打开、不说;`SESSION_CACHE_TTL_SEC=0` 时每一轮都说) | 告诉用户「这段对话的一部分历史丢了」;运维按 `fail_open_total{tag="server.session-repo.corrupt-record-skipped"}` 的计数与探针文件(`SEMA_FAILOPEN_PROBE=<文件路径>`,每命中一行)里的逐条明细(文件路径、会话 id、行号、成因)查那份文件。非流式 `POST /v1/tasks` 收不到通告帧(只有运维面的计数与探针) |
2282
2293
  | 流里一帧 `engine_notice` `code:"route.finish_reason_unknown"`,`detail:{ finishReason, sessionId, runId }`(7.126.0 起) | 提供方用一个引擎读不懂的结束原因结束了这一轮:回答按模型留下的样子保留,但可能被提供方截短了 | 告诉用户「这一段回答可能不完整」;同一个词同一 run 只说一次。运维按 `finishReason` 查是哪家网关 / 模型发的 |
2283
2294
  | 流里一帧 `engine_notice` `code:"route.stream_end_ignored"`,`detail:{ statement, word, frames, sessionId, runId }`(7.126.0 起) | 提供方已经结束了这一轮,它的流却又发来了东西(又一个结束 / 一个错误 / 内容);第一个结束算数,之后的没进这一轮 | 一般无需处置;频繁出现 ⇒ 运维查那条网关的流式实现(`statement` / `word` 说的是迟到的第一帧) |
@@ -2442,19 +2453,30 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2442
2453
  ### 9.3c k8s 车道:点名 env 转发(`K8S_SANDBOX_ENV`)
2443
2454
 
2444
2455
  沙箱 pod 里的工具要用服务进程环境里的凭据(典型:团队形态下 `git clone` / `git push` 私有仓)时,把**变量名**写进 `K8S_SANDBOX_ENV`,值留在服务进程环境里。
2445
- 它和 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 是**同一个法**(解析、拒启、告警、目录行、起服行 `sandboxEnvKeys`、多租户姿态全部见上文「沙箱带外 env 名单」一节),这里只写 k8s 车道自己的三件事:
2456
+ 它和 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 是**同一个法**(解析、拒启、告警、目录行、起服行 `sandboxEnvKeys`、多租户姿态全部见上文「沙箱带外 env 名单」一节),这里只写 k8s 车道自己的四件事:
2446
2457
 
2447
2458
  名单里点名 `SEMA_PKG_SOURCE`(或任何包源变量)= 把服务进程自己的那份值原样下发,显式胜过 `SANDBOX_PKG_SOURCE` 推导出的那份 —— pod 的启动命令据此走包源 hook;这是「显式名单胜派生」在 k8s 车道上的自然结果,不是第二条通道。
2448
2459
 
2449
- - **值去哪**:只进沙箱 pod 的容器 env(pod spec 的 `containers[0].env[]`,pod 里每个进程、每条 exec 命令都看得到)。它与 `SANDBOX_PKG_SOURCE` 推导出的包源变量在装配处合并一次,**同名时名单赢**(部署方的刻意覆盖);
2450
- 适配器不把它写进 pod spec 的其它位置、不拼进执行命令串、不写进 `/workspace`,因而不随跨轮保留 / 挂起的快照进对象存储;不进事件日志(起服行只报键名)。
2460
+ - **值去哪**(7.137.0 起):只进**该 pod 自己的一只 Secret**(名 `sbx-env-<随机>`,键 `env.<变量名>`,`immutable`,`ownerReferences` 指向这只 pod —— pod 删了,
2461
+ 集群的垃圾回收把 Secret 一起删掉);pod spec 里的容器 env 只带 `valueFrom.secretKeyRef` 引用,**不带值**。容器起来之后 pod 里每个进程、每条 exec 命令照样看得到这些变量。
2462
+ 次序是先建 pod、再用 pod 的 `uid` 建 Secret(容器在 Secret 到位前不启动);Secret 建不成 ⇒ 这只 pod 被删、这次连接拒(Role 不许 = `auth_failed`,其余 = `connect_failed`,拒句不带值),
2463
+ **不回落**成把值写进 pod spec。它与 `SANDBOX_PKG_SOURCE` 推导出的包源变量在装配处分座一次,**同名时名单赢**(派生的那条不出现;部署方的刻意覆盖);
2464
+ 适配器不把值拼进执行命令串、不写进 `/workspace`,因而不随跨轮保留 / 挂起的快照进对象存储;不进事件日志(起服行只报键名)。
2451
2465
  pod 里的进程读得到这些值 —— 那正是转发的目的;进程自己若把值写进 `/workspace`,它就会随快照走。
2466
+ - **RBAC**:沙箱命名空间里服务账号要有 `secrets` 的 `create`(Role 规则 `- apiGroups: [""] resources: [secrets] verbs: [create]`;随包 `deploy/sema-up` 的 Role 已带)。
2467
+ 它恰是服务为 env Secret 真发的调用;不要给 `get`(服务从不读回,给了就读得到命名空间里每一只 Secret)与 `delete`(Secret 随属主 pod 由集群回收)。租约 Secret 的 `patch` 只在 §9.3h 的平面打开时要。
2468
+ 名单非空时起服各问一次 SelfSubjectAccessReview:答了「不许」⇒ **拒启**,拒句带 `[sandbox-secret-rbac]`、点名缺的动词与这条规则;问不到(API 连不上 / 审查本身被拒)⇒ 照常起服,
2469
+ 一行 `sandbox_secret_seat_unverified` + fail-open 计数 `server.sandbox-secret.seat-unverified` —— 那时若 Role 真缺,第一个要转发的 run 在连接时被拒、它的 pod 被删。
2470
+ - **放弃的 pod 删不掉(运维面,7.137.0 起)**:上面这类拒绝(env Secret 建不成 · 成员凭据代理的租约 Secret 建不成 · 代理镜像拉不下)要删那只刚建的 pod;这一删自己失败(请求抛 / API 回 403 或 5xx,比如 Role 缺 pods `delete`)时,
2471
+ 连接的拒答不变,服务多记一行 error `sandbox_pod_delete_failed`(`pod` / `namespace` / `why` / `cause`,不带值、不带 API 文本)+ fail-open 计数 `server.sandbox-pod.delete-failed`;pod 与它名下的 Secret 留给回收器(§9.3g)。
2472
+ 计数持续非零就按 `cause` 核 Role 的 pods `delete` 与 API 可达性。
2452
2473
  - **没设 = 零转发**:`K8S_SANDBOX_ENV` 未设 / 空串时,pod 与未引入本旋钮时逐字同形(env 只有包源那一组)。只在 `REMOTE_EXEC=k8s` 时求值。
2453
2474
  - **凭据的推荐用法**:`K8S_SANDBOX_ENV=GIT_TOKEN`,镜像里的 git 配成用环境变量取凭据(例如 `GIT_ASKPASS` 指向一个读 `$GIT_TOKEN` 的小脚本,或 credential helper),
2454
2475
  令牌就不进提示词、命令串和事件日志。
2455
2476
 
2456
- **风险模型(与 e2b / docker 同一档:运维信任形)**:值以明文写在 pod spec 里 —— 在该 namespace 有 `get pods` 权限的人、记录请求体的集群 API 审计日志、节点上能检视容器的人都读得到。
2457
- 所以给的是**最小权限**令牌(能只读就只读、限单仓、可吊销),并按 namespace 收紧读 pod 的 RBAC。本旋钮只是把值放进 pod;「凭证只留在适配器侧、永不进 pod」是另一种形,不是本键的语义。
2477
+ **风险模型(与 e2b / docker 同一档:运维信任形)**:值不再出现在 pod spec 里(`get pods` 读不到),但它在该 pod 的 Secret 里 —— 在该 namespace 有 `get secrets` 权限的人、
2478
+ 节点上能检视容器的人、以及集群 etcd(Secret 是否静态加密取决于集群配置,服务读不到)都读得到;记录请求体的 API 审计日志按其审计策略可能记下建 Secret 的请求体。
2479
+ 所以给的仍是**最小权限**令牌(能只读就只读、限单仓、可吊销),并按 namespace 收紧读 secrets 的 RBAC、给集群开 Secret 静态加密。本旋钮只是把值放进 pod;「凭证只留在适配器侧、永不进 pod」是另一种形,不是本键的语义。
2458
2480
 
2459
2481
  ### 9.3d k8s 车道:按工作区空间的构建缓存卷(7.126.0 起,缺省关)
2460
2482
 
@@ -2559,6 +2581,50 @@ e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时
2559
2581
  ssh / adb / local-docker 三条车道的流式调用同读这一条律:没有车道活性界,只受自己说的静默界 `readTimeoutMs` 约束,到点的切断句与 host 同一句
2560
2582
  (`no output for <N> s — cut off by the command's own idle bound (readTimeoutMs)`);`readTimeoutMs` 为 `0` / 负数 / 非数不算说了界。
2561
2583
 
2584
+ ### 9.3g k8s 车道:收不掉的沙箱 Pod 由服务删(reaper;7.137.0 起)
2585
+
2586
+ `activeDeadlineSeconds` 到点只让 pod 的容器终止(pod 进 `Failed`),**不删 pod 对象**;run 收尾时服务自己删 pod,而服务进程崩溃、或收尾那一删失败时,pod 对象(连同它的 Secret,§9.3c)就一直留着。
2587
+ 本版起每个副本在起服后的第一个维护 tick 跑一轮回收、此后每 5 分钟一轮,只删两种 pod(按身份标签 `app=sema-sandbox,managed-by=sema-server` 列本沙箱命名空间):
2588
+
2589
+ - **容器已全部结束**(`status.phase` 是 `Succeeded` / `Failed`)—— 删它不掐断任何东西;
2590
+ - **它的 run 已终**:顶层 run 的 pod 带标签 `sema.io/run=<run id>`;run 行在服务的 run 账本里已是终态(completed / failed / blocked),且自 run 行**转终态的那一刻**起超过
2591
+ `max(6 分钟, 工作区快照时限 + 15 秒)` —— 快照时限是**建这只 pod 的副本**的 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`(缺省 300 000 ⇒ 6 分钟;抬到 600 000 ⇒ 615 秒),因为 run 收尾在写完终态之后还可能在 pod 上存快照,至多这么久。
2592
+ 这个上界在建 pod 时算一次、写进 pod 的注解 `sema.io/run-end-hold-ms`(毫秒),回收器只读 pod 自带的这一格、不读自己副本的配置:滚动升级或改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时,每只 pod 按建它的副本的时限,
2593
+ 时限短的副本不会提前删时限长的副本还在存快照的 pod。带 run 标签的 pod 上注解缺席、不是十进制整数或不在 [360 000, 3 615 000] 里(不是本服务按配置写的数)⇒ 不按这一条删(跳过原因 `hold_unreadable`),容器结束后按上一条删;本版之前建的 pod 既没有 run 标签、也没有这个注解 —— 它们落在下面「不带 run 标签」一类(跳过原因 `no_run_label`),只在容器结束后删。
2594
+ 持久化开 / 关的部署同一条;没有单独的旋钮。
2595
+
2596
+ 还在跑的 run(以及停驻待决的 run)的 pod **从不**被它删,哪怕 pod 排队很久才起来;查不到 run 行、读不了 run 账本的 pod 只按第一条删。
2597
+ **不带 run 标签的 pod**(委派子代 / 后台代理 / 服务自己起的 run)**只在它的容器全部结束之后才删** —— 会话级后台代理刻意活过父 run 的那一轮,「会话里已没有在跑的 run」不等于「这只 pod 没人用」。
2598
+ 每轮一行 `sandbox_pod_reap_round`(`listed` / `scanned` / 按原因分的 `deleted` 与 `skipped` / `gone` / `deleteFailed`),每删一只一行 `sandbox_pod_reaped`(`pod` / `reason` / `run`),计数 `sandbox_pods_reaped_total{reason}`。
2599
+ **列举失败**(API 不可达 / 403)⇒ 这一轮一只都不删:一行 warn `sandbox_pod_reap_list_failed`(带原因)+ fail-open 计数 `server.sandbox-pod-reaper.list-failed`,下一轮照常再列 —— 计数持续非零就核 API 可达性与 Role 的 pods `list`。
2600
+ **某一只删失败**(Role 缺 pods `delete` / API 抖)⇒ 不挡同轮其余:一行 error `sandbox_pod_delete_failed`(`pod` / `namespace` / `why` = `reaper_containers_ended` · `reaper_run_ended_past_hold` / `cause` = `http_<状态>` · `request_failed`,不带值、不带 API 文本)+ fail-open 计数 `server.sandbox-pod.delete-failed`(与连接清理删失败是同一个计数,按 `why` 分座),这一只下一轮再判再删 —— `why` 以 `reaper_` 起头的计数持续非零就核 Role 的 pods `delete`:兜底本身删不动,残留会随轮次累积。
2601
+ 没有存储后端(查不了 run 行)的部署上本回收照跑:「容器已全部结束」那一条照删,带 run 标签的 pod 跳过(`run_store_absent`)。
2602
+ 用的是 Role 里既有的 pods `list` / `delete`。**运维可见的差别**:已结束的沙箱 pod 不再留着供事后 `kubectl logs` / `kubectl describe` 看;run 自己的日志面(事件流、账本)不受影响。没有旋钮。
2603
+ **两套部署共用一个沙箱命名空间**(缺省都是 `sandboxes`):回收器按身份标签 `app=sema-sandbox,managed-by=sema-server` 列 pod,标签里没有部署身份 ⇒ 一套部署会删另一套**容器已结束**的 pod
2604
+ (只有调用方把同一个 run id 交给两套部署时才会碰到对方的活 pod)。用 `K8S_NAMESPACE` 给每套部署各一个沙箱命名空间。
2605
+
2606
+ ### 9.3h k8s 车道:成员 git 凭据代理(7.137.0 起落组件;本版平面恒关)
2607
+
2608
+ 成员的 git 凭据只进**本人 run 的 pod**,而且不进 agent 容器:它进同一只 pod 里的第二只容器 —— 凭据代理(`containers[1]`)。agent 容器里的 git 经
2609
+ `url."http://127.0.0.1:47311/o/<i>/".insteadOf = "<origin>/"`(非秘密 env `GIT_CONFIG_COUNT` / `GIT_CONFIG_KEY_<i>` / `GIT_CONFIG_VALUE_<i>`)把对 `<origin>` 的 clone / fetch / push 交给代理;
2610
+ 代理按条目把请求转给 origin,只在条目的**租约**有效时加 `Authorization`。
2611
+
2612
+ - **代理只做 git smart-HTTP**:`/o/<i>/<owner>/<repo>/info/refs`(GET,`service=git-upload-pack|git-receive-pack`)与两个 pack 端点(POST);别的路径、带 `%` 的路径、别的方法 / 查询 / framing 一律 400,
2613
+ 上游收不到。重定向从不跟随;上游 401 改写成 git 自己的报错(`fatal: remote error: <句子>`),句子由服务写在条目上。只对 https 的 origin 附凭据;http 的 origin 照常路由(公开仓可用),永不带凭据。
2614
+ 上游每一等都有界(常量,无旋钮):连接(TCP + TLS 握手)10 秒;应答头自请求**发完**起 30 秒(大 push 上传中不受它限)。超时 ⇒ 502,正文带 `upstream_timeout` 与一句原因,上游连接被断。
2615
+ - **租约**:服务把租约写进该 pod 自己的一只 Secret(`sema.io/sandbox-lease`,只一个键 `broker.json`,属于这只 pod,随 pod 由集群回收);每个可用条目一份租约(`leaseId` + `ttlMs`,至多 5 分钟)。
2616
+ 代理按**自己的单调钟**从「第一次见到这个 `leaseId`」起算,到点对该条目所有已认证连接立即掐断(不做优雅关)、此后不再附认证;续用只有一种 —— 服务写新租约(新 `leaseId`)。
2617
+ **硬界是 pod 被删**(run 收尾删,或 §9.3g 的回收);「最后一份租约什么时候被投影进 pod」取决于 k8s,不是服务的承诺;上游已经收到的字节不在任何界内。
2618
+ - **pod 形**:只在**平面开着且该 run 的身份来源是 `sso`** 时挂代理;否则 pod 与不挂时逐字节同。代理容器的 `securityContext` 由适配器逐键写死(uid 0、只读根、丢全部能力、禁提权、`RuntimeDefault` seccomp),
2619
+ 租约卷只读、0400、只挂代理容器;`shareProcessNamespace:false`;连接要等代理容器就绪(exec 探针)才算成。部署的 pod 补丁在代理在场时只许 `volumes` / `volumeMounts` / `containerSecurityContext` / `labels`
2620
+ (容器级只落 agent 容器),**`podSecurityContext` 整只不许** —— 用了它的部署,平面关(`pod_patch_reaches_broker`)而不拒启;把 pod 级加固挪进 `containerSecurityContext`。
2621
+ - **平面开没开,看一处**:`GET /v1/capabilities` 的 `credentialPlane.sandboxGit`(`on` + 逐 origin `delivers`,或 `off` + 第一个不成立的前提词,九词闭集与判序见契约附录 B)。
2622
+ 起服时若是 k8s 多租户部署,日志一行 `credential_plane_sandbox_git`(`state` / `reason` / `why`)。
2623
+ - **旋钮**:`K8S_SANDBOX_BROKER_IMAGE`(代理容器的镜像;建议填本引擎自身镜像 —— 代理是本包里固定路径的程序;不设 ⇒ `broker_image_unset`)。Role 在 §9.3c 的 `create` 之外还要 `patch`(改写租约;租约 Secret 只用这两个;本版平面恒关,不用先给)。
2624
+ - ⚠️ **7.137.0 平面恒 `off`**:两处读口还没接线 —— 取值口的 worker 凭据、有效配置的 `sandboxGit` 块;读数停在 `no_caller_credential`(或更前的词),没有 pod 会挂上代理。
2625
+ run 行的身份来源不是接线点:读的是受理时记在 run 行上的那一份身份。本节写的是组件的行为与边界;
2626
+ 打开平面的那一版会在本节补上取值与续期。
2627
+
2562
2628
  ### 9.4 auto 模式(`permissionMode:"auto"`):武装三态、本地 org deny、自查(与 core 的意图武装式合修)
2563
2629
 
2564
2630
  **背景**:此前引擎只在「组织授予」(web-admin 解出的 per-principal `runtimeCaps.autoMode === true`)时武装
@@ -3339,14 +3405,16 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
3339
3405
  **协议命名空间的覆盖形(7.135.0 起有能力位)。** 条目写协议命名空间 —— 整对端 `mcp__srv` / `a2a__peer`、工具段通配 `mcp__srv__*`、工具段 glob `mcp__srv__get_*` —— 写口收(`200`),引擎把它们读成**一组工具**:
3340
3406
  `toolDeny` 里拒这一组,`toolAllow` 里只放这一组。要不要放开对这几形的扣下,读 `GET /v1/capabilities` 的顶层布尔 `sessionRulesNamespaceForms`(不按版本号猜):`true` = 引擎协议表上每个命名空间的这三形写口真收、
3341
3407
  引擎真认成一组;键缺席(≤7.134.x)或 `false` ⇒ 当不放宽。它与 `sessionPolicy`(这台有没有会话规则写面)正交 —— 没有会话规则店的部署上它也是 `true`,所以条件是两位同真。位只说命名空间覆盖形:
3342
- `*`、对端段的部分通配(`mcp__git*`)、尾分隔(`mcp__srv__`)照旧 `400 rules.inert_entry`(下),`mcp__*` 只在 `toolDeny` 收。
3408
+ `toolAllow` 里的 `*`、对端段的部分通配(`mcp__git*`)、尾分隔(`mcp__srv__`)照旧 `400 rules.inert_entry`(下),`mcp__*` 只在 `toolDeny` 收。
3409
+ **`toolDeny` 里带 `*` 的工具名是整名 glob(7.137.0 起,引擎会话规则文法 5)**:`*` 拒每一只工具,`mcp__git*` 拒名字以 `mcp__git` 起头的每一只工具(`mcp__github__…`、`mcp__gitlab__…`),
3410
+ `Bas*` 拒 `Bash`;写口收(`200`),行盖文法 5 的戳。`toolAllow` 不变:`*` 只在协议名的工具段里读(`mcp__srv__get_*`),别处的 `*` 不指任何范围、照旧拒。尾分隔 `mcp__srv__` 两只桶都照旧拒。
3343
3411
 
3344
3412
  **两只拒码**(判官是引擎的那一只,两条写口与店里的拒同码、同一份拒体;同步导入的写口在句首加记录坐标 `policy: [i].rules: `;店里一个字节都不落):
3345
3413
 
3346
3414
  - `400 rules.invalid_shape` —— 规则体形不对:`rules` 缺席或不是对象、有一个不是桶的键(含退役的命令桶,**不会被静默忽略**)、一只桶不是一列字符串、`allowDirs` 里有空目录。
3347
3415
  体顶层 `field`(哪个键;整体不是对象时是空串)与 `got`(那个值的类型,不回显值),`error` 是引擎的句子。
3348
- - `400 rules.inert_entry` —— 一条条目读成「什么都不是」,写进去什么都不拒 / 不许:退役的工具名、带 `__` 却没有协议前缀的名、协议名里铸不出的段(含对端段里的部分通配 `mcp__git*`)、`*`、
3349
- `toolAllow` 里的整协议名 `mcp__*`(7.135.0 起 `toolDeny` 里的 `mcp__*` / `mcp__*__*` 是一条规则:拒每一台 MCP 服务器的每一只工具)、工具没有的规则形
3416
+ - `400 rules.inert_entry` —— 一条条目读成「什么都不是」,写进去什么都不拒 / 不许:退役的工具名、带 `__` 却没有协议前缀的名、协议名里铸不出的段(尾分隔 `mcp__srv__`;`toolAllow` 里对端段的部分通配 `mcp__git*`)、
3417
+ `toolAllow` 里的 `*` 与整协议名 `mcp__*`(7.135.0 起 `toolDeny` 里的 `mcp__*` / `mcp__*__*` 是一条规则:拒每一台 MCP 服务器的每一只工具;7.137.0 起 `toolDeny` 里任何带 `*` 的名都是整名 glob,不在本码)、工具没有的规则形
3350
3418
  (`Monitor(touch:*)`)、文法不成的写法(空串、`Bash(`)。体顶层 `field` / `entry` / `reason`,外加 `verdict` / `currentName` / `guidance`(有就带)。按 `reason` 改写那一条再写。
3351
3419
 
3352
3420
  一条什么都不拒的 deny 已经在库里时,会话属主自己删得掉(删它不算放宽)。
@@ -3356,8 +3424,8 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
3356
3424
  下一次工具调用起生效;收不回 operator 写下的那一部分(两边都写过的条目也算):`403 loosen_forbidden`,句子 `a non-operator session-rule write may not loosen what an operator wrote; …`
3357
3425
  (7.134.x 及以前的句子是 `… may only tighten …`,按句子匹配的脚本改读码)。operator 写一次就把整行变成 operator 的。`operatorRules` 由引擎铸,客户端改规则时**不要**回传它。
3358
3426
 
3359
- **行自带文法戳。** 7.129.0 起每一行会话规则自带规则文法戳 `grammar`(写口判官盖引擎当前的文法:7.129.0–7.131.x 写下的行是 `1`,7.132.0–7.134.x 写下的行是 `2`,7.135.0 起写下的行是 `4`;
3360
- 早先写下的行保留自己的戳、引擎按它的戳读;店整只存、读回原样交出;7.134.x 及更早的引擎读不了戳 3 / 4 的行 —— 整行拒、那条会话每一次调用都拒,升级序见 MIGRATION 7.135.0)—— `GET` / `PUT` 回包的 `rules` 与同步导出的
3427
+ **行自带文法戳。** 7.129.0 起每一行会话规则自带规则文法戳 `grammar`(写口判官盖引擎当前的文法:7.129.0–7.131.x 写下的行是 `1`,7.132.0–7.134.x 写下的行是 `2`,7.135.0–7.136.x 写下的行是 `4`,7.137.0 起写下的行是 `5`;
3428
+ 早先写下的行保留自己的戳、引擎按它的戳读;店整只存、读回原样交出;7.134.x 及更早的引擎读不了戳 3 / 4 的行、7.136.x 及更早的读不了戳 5 的行 —— 整行拒、那条会话每一次调用都拒,升级序见 MIGRATION 7.135.0 / 7.137.0)—— `GET` / `PUT` 回包的 `rules` 与同步导出的
3361
3429
  记录里多这一个键。客户端改规则时只写三只桶,不要把读回的 `rev` / `gen` / `grammar` / `operatorRules` 一起回传:带着 `rev` 却没有 `grammar` 的值引擎按存量行的文法(0)读,括号形 allow 条目答
3362
3430
  `400 rules.inert_entry`(`reason: "earlier_grammar"`;出路在句子里);`grammar` 坏或比引擎新 ⇒ `400 rules.invalid_shape`(`field: "grammar"`)。
3363
3431
 
@@ -170,6 +170,20 @@ server 的 `<数据根>/sealed-key/` 存着停驻任务续跑上下文里请求
170
170
  - PVC 的 `accessModes` 建好后不可改:副本数跨过 1 的边界时 `helm upgrade` 会撞「spec is immutable」;卷里是私钥,换卷前先结掉停驻行或备份钥匙文件。
171
171
  PVC 带 `helm.sh/resource-policy: keep`:`helm uninstall` 与把 `enabled` 改成 `false` 的 upgrade 都不删它(确认不要了再 `kubectl -n <ns> delete pvc sema-server-sealed-key`)。
172
172
 
173
+ ## 升级时的副本更新(7.137.0:停驻任务的请求体改写)
174
+
175
+ 7.137.0 起服时把 7.136.x 及以前停驻的任务存下的请求体原地改写(请求键 `promptProfile` 换成布尔 `promptLean`;窗内每次起服都扫,细节见随包 `MIGRATION.md` 7.137.0 节第 4 条)。
176
+ 改写过的体 7.136.x 读不懂,所以升级序是**先停光 7.136.x 副本,再起第一个 7.137.0 副本** —— 滚动更新做不到这一点。
177
+
178
+ **升级前先缩光副本,按安装路径分三种**(滚动更新做不到「先停光 7.136.x、再起第一个本版副本」;下面三段各管一种安装路径,照你的那一种做):
179
+
180
+ - **Helm 形安装**(`kube-up.sh` 的 `--kube=existing` / `--kube=k3s`;`--kube=machines` 下设了 `STACK_ENGINE=helm`):图表只在钥匙卷是 `ReadWriteOnce` 时给 Deployment 写 `Recreate`;缺省的多副本(`server.replicas: 3`、`ReadWriteMany`)不写更新策略,`helm upgrade` 走 Kubernetes 缺省的滚动更新 —— 第一只本版 pod 起服改写存体时 7.136.x 副本还在跑。所以升级前先 `kubectl -n <命名空间> scale deploy/sema-server --replicas=0`(启用 `serverV4pro` 时同样缩 `deploy/sema-server-v4pro`),等旧 pod 退光,再 `helm upgrade`(重跑 `sema-up` / `kube-up.sh` 在这些安装形上走的就是这条),升完核副本数回到 `server.replicas`。
181
+ - **`--kube=machines` 缺省形**(不设 `STACK_ENGINE` = `render`;`cluster-up.sh` 渲染清单后 `kubectl apply`,不是 Helm):清单里 `sema-server` 的副本数按机器数取 2(少于 3 台)或 3(3 台及以上),没有 `strategy` 键 = Kubernetes 缺省的滚动更新,也没有 `serverV4pro`。所以升级前先 `kubectl -n sema scale deploy/sema-server --replicas=0`(第一台机器上写 `k3s kubectl`),等旧 pod 退光,再 `kubectl apply`(重跑 `sema-up` 做的就是这一步;清单里的 `replicas` 把副本数写回 2 或 3),升完核副本数。
182
+ - **compose 两档**(`--profile=full-single` / `local-plus`):`server` 服务只有一只容器、没有副本数键 —— 没有第二只旧副本在滚动更新里陪跑,零动作。
183
+
184
+ 已经滚动升过的:等最后一只 7.136.x 副本退出之后,`kubectl -n <命名空间> rollout restart deploy/sema-server`(启用 `serverV4pro` 时再加 `deploy/sema-server-v4pro`)重起一轮本版副本 —— 起服即改写窗内没改写到的任务(重启用的配置里须 `DURABLE_APPROVAL=true` —— 多租户部署缺省关,要显式写,且多租户还需 `OPERATOR_PRINCIPALS` 非空,否则起服拒(拒句点名出路);起服日志里 `checkpoint_ctx_prompt_profile_rewritten`(M30)与 `checkpoint_ctx_credentials_sealed`(M28)两行都出现、且 M30 那一行是 `"window":"open"`,才说明守卫过了、改写跑过 —— M28 的窗先关,窗关后它那一行 `"window":"closed"` 属正常)。
185
+ Helm 形里,未启用 `serverV4pro`、钥匙卷是 `ReadWriteOnce` 的形(`server.replicas=1`、或随包 `values-full-single.yaml` 这类显式盖 `ReadWriteOnce` 的形)图表对 `deploy/sema-server` 本来用 `Recreate`,旧 pod 先停,零动作;启用 `serverV4pro` 时两只 Deployment 各自 `Recreate`、互不等待,仍按上面 Helm 形一条先把两只都缩到 0。
186
+
173
187
  ## 凭据纪律
174
188
 
175
189
  全部凭据生成一次、落 `~/.sema-up/<project>/.env`(0600),重跑**绝不轮换**;
@@ -1,4 +1,4 @@
1
- # ── 沙箱 lane RBAC(k8s+kata;pod 五动词 + 缓存卷四动词 + 出网自检的两只只读:本 ns 的 NetworkPolicy、集群的节点表)────────────────────────────────
1
+ # ── 沙箱 lane RBAC(k8s+kata;pod 五动词 + 缓存卷四动词 + 沙箱秘密座 create + 出网自检的两只只读:本 ns 的 NetworkPolicy、集群的节点表)────────────────────────────────
2
2
  apiVersion: v1
3
3
  kind: ServiceAccount
4
4
  metadata:
@@ -33,6 +33,12 @@ rules:
33
33
  - apiGroups: [""]
34
34
  resources: [persistentvolumeclaims]
35
35
  verbs: [get, create, delete, deletecollection]
36
+ # 沙箱秘密座:K8S_SANDBOX_ENV 点名的值只进每只 pod 自己的 Secret(pod spec 只带 secretKeyRef;Secret 属于 pod,随 pod 由集群回收)
37
+ # 动词 = 适配器为 env Secret 真发的调用:create;不读回(无 get)、不删(随属主 pod 由集群 GC)。名单非空而缺它 ⇒ server 起服拒(起服 SelfSubjectAccessReview 问的正是它)
38
+ # 成员 git 凭据平面(本版恒关)打开时,租约 Secret 还要 patch —— 那时再加
39
+ - apiGroups: [""]
40
+ resources: [secrets]
41
+ verbs: [create]
36
42
  # 起服的出网自检读本 ns 的 NetworkPolicy(只读)
37
43
  - apiGroups: [networking.k8s.io]
38
44
  resources: [networkpolicies]
@@ -92,7 +92,7 @@ kind: Service
92
92
  metadata: { name: sema-minio, namespace: sema }
93
93
  spec: { selector: { app: sema-minio }, ports: [{ name: s3, port: 9000 }, { name: console, port: 9001 }] }
94
94
  ---
95
- # ── 沙箱 lane RBAC(k8s+kata;测试机同款五动词 + 缓存卷 + 出网自检两只只读)────────────────────────────────
95
+ # ── 沙箱 lane RBAC(k8s+kata;pod 五动词 + 缓存卷四动词 + 沙箱秘密座 create + 出网自检的两只只读:本 ns 的 NetworkPolicy、集群的节点表)────────────────────────────────
96
96
  apiVersion: v1
97
97
  kind: ServiceAccount
98
98
  metadata: { name: sema-runner, namespace: sema-sandbox }
@@ -122,6 +122,12 @@ rules:
122
122
  - apiGroups: [""]
123
123
  resources: [persistentvolumeclaims]
124
124
  verbs: [get, create, delete, deletecollection]
125
+ # 沙箱秘密座:K8S_SANDBOX_ENV 点名的值只进每只 pod 自己的 Secret(pod spec 只带 secretKeyRef;Secret 属于 pod,随 pod 由集群回收)
126
+ # 动词 = 适配器为 env Secret 真发的调用:create;不读回(无 get)、不删(随属主 pod 由集群 GC)。名单非空而缺它 ⇒ server 起服拒(起服 SelfSubjectAccessReview 问的正是它)
127
+ # 成员 git 凭据平面(本版恒关)打开时,租约 Secret 还要 patch —— 那时再加
128
+ - apiGroups: [""]
129
+ resources: [secrets]
130
+ verbs: [create]
125
131
  # 起服的出网自检读本 ns 的 NetworkPolicy(只读)
126
132
  - apiGroups: [networking.k8s.io]
127
133
  resources: [networkpolicies]
@@ -19,6 +19,12 @@
19
19
  * 不是卡的渲染读法,不在本模块的规则里。
20
20
  */
21
21
  export { ASK_USER_QUESTION_TOOL_NAME } from "@sema-agent/core";
22
+ /**
23
+ * core 7.56.0 [ref]:停驻行是不是**引擎自己的问题工具**的(content-ask)—— 读**行的记录**:引擎铸的挂载点 `pendingAction.engineMounted: true` 与保留名同在。
24
+ * 调用方自己挂的同名工具不带挂载点(引擎只给引擎挂载的工具铸这一位),本版之前停驻的行也没有 ⇒ 都不是。读者:两只决定铸点(顶层 `/decide` 与停驻子代赎回腿)——
25
+ * 引擎问题工具的决定**不转** `updatedInput`(解析器忽略它,而认领前的编辑复判会在问题专用的 resume-edit 策略下把它拒成 `resume.constraint_rejected`)。
26
+ */
27
+ export declare function isEngineQuestionRow(pendingAction: unknown): boolean;
22
28
  import { PAUSE_REGISTRY, type GateKind, type ToolApprovalCard } from "@sema-agent/core";
23
29
  /**
24
30
  * 一格卡词的**闭集窄读**(三个载体共用这一只:活 ask、LOCAL 店的停驻门、SQL 店读出的门子树)。成员 ⇒ 那个词;缺席 ⇒ `undefined`(不计);
@@ -1,4 +1,11 @@
1
1
  export { ASK_USER_QUESTION_TOOL_NAME } from "@sema-agent/core";
2
+ import { ASK_USER_QUESTION_TOOL_NAME as QUESTION_TOOL_NAME } from "@sema-agent/core";
3
+ export function isEngineQuestionRow(pendingAction) {
4
+ if (pendingAction === null || typeof pendingAction !== "object")
5
+ return false;
6
+ const pa = pendingAction;
7
+ return pa.kind === "tool_approval" && pa.engineMounted === true && pa.toolName === QUESTION_TOOL_NAME;
8
+ }
2
9
  import { PAUSE_REGISTRY, TOOL_APPROVAL_CARDS, facetOf, isGateKind, parkedApprovalCardOf } from "@sema-agent/core";
3
10
  import { recordFailOpen } from "./observability/fail-open.js";
4
11
  import { redactSecrets } from "./trace/redact.js";