@sema-agent/server 7.136.0 → 7.138.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 (327) hide show
  1. package/MIGRATION.md +501 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +149 -34
  5. package/deploy/sema-up/README.md +14 -0
  6. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +21 -14
  9. package/dist/approval-card.js +1 -0
  10. package/dist/approval-content-kind.d.ts +6 -0
  11. package/dist/approval-content-kind.js +7 -0
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/audit.js +1 -0
  18. package/dist/auth-bridge.js +2 -2
  19. package/dist/background-agent-name.d.ts +5 -0
  20. package/dist/background-agent-name.js +5 -0
  21. package/dist/boot/budget-tracing.d.ts +1 -12
  22. package/dist/boot/deployment-mods.d.ts +46 -16
  23. package/dist/boot/deployment-mods.js +82 -6
  24. package/dist/boot/execution-env.d.ts +24 -1
  25. package/dist/boot/execution-env.js +69 -6
  26. package/dist/boot/governance-seams.d.ts +10 -17
  27. package/dist/boot/governance-seams.js +5 -17
  28. package/dist/boot/leader.d.ts +3 -0
  29. package/dist/boot/leader.js +1 -0
  30. package/dist/boot/org-delivery.js +15 -2
  31. package/dist/boot/reapers.d.ts +3 -0
  32. package/dist/boot/reapers.js +15 -1
  33. package/dist/boot/resolve-spec.d.ts +7 -0
  34. package/dist/boot/resolve-spec.js +36 -26
  35. package/dist/boot/retention-lane.d.ts +11 -5
  36. package/dist/boot/retention-lane.js +2 -2
  37. package/dist/boot/runner-deps.d.ts +7 -3
  38. package/dist/boot/runner-deps.js +1 -0
  39. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  40. package/dist/boot/stage-04-stores.d.ts +2 -13
  41. package/dist/boot/stage-05-execution-env.d.ts +6 -13
  42. package/dist/boot/stage-05-execution-env.js +17 -5
  43. package/dist/boot/stage-06-runners.d.ts +6 -13
  44. package/dist/boot/stage-06-runners.js +10 -3
  45. package/dist/boot/stage-07-capability-layer.d.ts +7 -13
  46. package/dist/boot/stage-07-capability-layer.js +3 -3
  47. package/dist/boot/stage-08-reapers.d.ts +7 -13
  48. package/dist/boot/stage-08-reapers.js +2 -1
  49. package/dist/boot/stage-09-leader.d.ts +7 -13
  50. package/dist/boot/stage-09-leader.js +2 -2
  51. package/dist/boot/stage-10-http-server.d.ts +7 -13
  52. package/dist/boot/stage-10-http-server.js +6 -3
  53. package/dist/boot/stores.d.ts +1 -1
  54. package/dist/boot/web-admin.d.ts +5 -2
  55. package/dist/boot/web-admin.js +3 -3
  56. package/dist/brain.js +5 -5
  57. package/dist/broker/broker.d.ts +89 -0
  58. package/dist/broker/broker.js +287 -0
  59. package/dist/broker/constants.d.ts +39 -0
  60. package/dist/broker/constants.js +15 -0
  61. package/dist/broker/http1.d.ts +111 -0
  62. package/dist/broker/http1.js +256 -0
  63. package/dist/broker/lease-book.d.ts +87 -0
  64. package/dist/broker/lease-book.js +142 -0
  65. package/dist/broker/lease-file.d.ts +60 -0
  66. package/dist/broker/lease-file.js +127 -0
  67. package/dist/broker/main.d.ts +2 -0
  68. package/dist/broker/main.js +20 -0
  69. package/dist/budget.d.ts +17 -15
  70. package/dist/budget.js +2 -2
  71. package/dist/capabilities/composite-members.d.ts +2 -1
  72. package/dist/capabilities/composite-members.js +2 -1
  73. package/dist/capabilities/engine-capabilities.js +1 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -11
  75. package/dist/capabilities/mcp-probe.js +44 -5
  76. package/dist/capabilities/scenarios.d.ts +23 -8
  77. package/dist/capabilities/scenarios.js +13 -8
  78. package/dist/capabilities/skills.d.ts +28 -5
  79. package/dist/capabilities/skills.js +8 -0
  80. package/dist/capability-facts.d.ts +6 -0
  81. package/dist/capability-facts.js +5 -0
  82. package/dist/center-credential-renewer.js +5 -1
  83. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  84. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  85. package/dist/checkpoint-identity.d.ts +13 -6
  86. package/dist/config-catalog.js +16 -7
  87. package/dist/config-types.d.ts +24 -7
  88. package/dist/config.d.ts +1 -1
  89. package/dist/config.js +42 -13
  90. package/dist/deployment-governance.d.ts +2 -2
  91. package/dist/deployment-governance.js +7 -9
  92. package/dist/deployment-shape.d.ts +6 -6
  93. package/dist/device-ws-protocol.d.ts +1 -0
  94. package/dist/device-ws-protocol.js +2 -1
  95. package/dist/file-history-disclosure.d.ts +4 -2
  96. package/dist/file-history-disclosure.js +2 -0
  97. package/dist/gate-settlement.d.ts +71 -0
  98. package/dist/gate-settlement.js +40 -0
  99. package/dist/hooks/classic-stations.d.ts +12 -54
  100. package/dist/hooks/classic-stations.js +1 -0
  101. package/dist/hooks/command-spawn.d.ts +2 -2
  102. package/dist/hooks/hook-runner.js +14 -14
  103. package/dist/http/admission.js +14 -9
  104. package/dist/http/approval-sweeps.js +4 -3
  105. package/dist/http/caller-trust.d.ts +2 -1
  106. package/dist/http/caller-trust.js +1 -3
  107. package/dist/http/checkpoint-ctx.d.ts +48 -7
  108. package/dist/http/checkpoint-ctx.js +40 -0
  109. package/dist/http/dispatch.d.ts +4 -0
  110. package/dist/http/dispatch.js +18 -10
  111. package/dist/http/durable-run-leg.d.ts +5 -1
  112. package/dist/http/durable-run-leg.js +5 -3
  113. package/dist/http/health-self-report.d.ts +2 -2
  114. package/dist/http/memory-compliance-failure-reply.js +3 -0
  115. package/dist/http/principal-gate.d.ts +15 -10
  116. package/dist/http/principal-gate.js +48 -9
  117. package/dist/http/resume-legs.d.ts +7 -31
  118. package/dist/http/resume-legs.js +185 -126
  119. package/dist/http/route-ctx.d.ts +38 -53
  120. package/dist/http/route-ctx.js +2 -5
  121. package/dist/http/routes/a2a-serve.js +10 -4
  122. package/dist/http/routes/agents-roster.d.ts +1 -1
  123. package/dist/http/routes/agents-roster.js +5 -1
  124. package/dist/http/routes/approvals-assistant.js +24 -10
  125. package/dist/http/routes/capabilities.js +23 -11
  126. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  127. package/dist/http/routes/mcp-reconnect.js +7 -7
  128. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/rules.js +3 -3
  131. package/dist/http/routes/runs.js +13 -8
  132. package/dist/http/routes/session-sync.js +42 -6
  133. package/dist/http/routes/sessions.js +6 -3
  134. package/dist/http/routes/side-query.js +2 -3
  135. package/dist/http/routes/tasks.js +794 -780
  136. package/dist/http/server.d.ts +35 -25
  137. package/dist/http/stored-body-replay.d.ts +3 -3
  138. package/dist/http/stored-body-replay.js +6 -1
  139. package/dist/http/wire-types.d.ts +66 -33
  140. package/dist/identity-source.d.ts +65 -0
  141. package/dist/identity-source.js +45 -0
  142. package/dist/index.d.ts +2 -0
  143. package/dist/index.js +1 -0
  144. package/dist/leader/wire.d.ts +7 -0
  145. package/dist/leader/wire.js +5 -4
  146. package/dist/mcp-assertion-header.d.ts +51 -40
  147. package/dist/mcp-assertion-header.js +10 -11
  148. package/dist/mcp-authorization-face.d.ts +27 -0
  149. package/dist/mcp-authorization-face.js +15 -0
  150. package/dist/memory-operator-faces.d.ts +13 -4
  151. package/dist/memory-operator-faces.js +1 -17
  152. package/dist/memory-store-refusal.d.ts +3 -3
  153. package/dist/memory-sync-client.js +72 -57
  154. package/dist/memory-sync.d.ts +1 -1
  155. package/dist/model-entry-refusal.d.ts +2 -0
  156. package/dist/model-entry-refusal.js +11 -0
  157. package/dist/model-route-gap.d.ts +6 -22
  158. package/dist/model-route-gap.js +7 -5
  159. package/dist/model-select.d.ts +6 -17
  160. package/dist/model-select.js +0 -3
  161. package/dist/mods-declaration.d.ts +110 -0
  162. package/dist/mods-declaration.js +109 -0
  163. package/dist/namespaced-rule-name.d.ts +1 -1
  164. package/dist/observability/err-text.d.ts +5 -0
  165. package/dist/observability/err-text.js +9 -0
  166. package/dist/observability/fail-open.d.ts +17 -1
  167. package/dist/observability/fail-open.js +17 -1
  168. package/dist/observability/metrics.js +1 -1
  169. package/dist/observability/run-terminal.js +1 -0
  170. package/dist/observability/secret-env-scrub.d.ts +1 -1
  171. package/dist/observability/secret-env-scrub.js +1 -1
  172. package/dist/operator-ask.d.ts +2 -2
  173. package/dist/org-delivery-gate.js +1 -1
  174. package/dist/org-memory-admission.d.ts +2 -2
  175. package/dist/org-memory-admission.js +39 -2
  176. package/dist/parked-decide.d.ts +15 -2
  177. package/dist/parked-decide.js +17 -10
  178. package/dist/peer-directory.js +2 -0
  179. package/dist/peer-seat.d.ts +150 -0
  180. package/dist/peer-seat.js +239 -0
  181. package/dist/permission-rule-list.d.ts +2 -2
  182. package/dist/permission-rule-vocab.d.ts +7 -7
  183. package/dist/permission-rule-vocab.js +3 -3
  184. package/dist/plain-string-record.d.ts +38 -0
  185. package/dist/plain-string-record.js +38 -0
  186. package/dist/plugins/background-agent-store-sql.js +6 -3
  187. package/dist/plugins/caching-session-store.d.ts +5 -0
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
  190. package/dist/plugins/checkpoint-store-sql.js +14 -14
  191. package/dist/plugins/file-history-store-sql.js +10 -16
  192. package/dist/plugins/file-run-store.js +8 -2
  193. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  194. package/dist/plugins/k8s-exec-protocol.js +116 -28
  195. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  196. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  197. package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
  198. package/dist/plugins/k8s-pod-reaper.js +122 -0
  199. package/dist/plugins/local-checkpoint-store.d.ts +4 -17
  200. package/dist/plugins/local-checkpoint-store.js +3 -4
  201. package/dist/plugins/local-session-store.d.ts +66 -27
  202. package/dist/plugins/local-session-store.js +60 -28
  203. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  204. package/dist/plugins/mailbox-store-sql.js +103 -15
  205. package/dist/plugins/memory-run-store.js +2 -0
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +34 -12
  209. package/dist/plugins/one-time-migrations.js +51 -9
  210. package/dist/plugins/permission-rule-store-file.js +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  212. package/dist/plugins/permission-rule-store-sql.js +37 -30
  213. package/dist/plugins/pg-pool.js +4 -0
  214. package/dist/plugins/pg-session-storage.d.ts +10 -2
  215. package/dist/plugins/pg-session-storage.js +20 -7
  216. package/dist/plugins/remote-env-adb.d.ts +6 -1
  217. package/dist/plugins/remote-env-adb.js +2 -1
  218. package/dist/plugins/remote-env-device.d.ts +6 -1
  219. package/dist/plugins/remote-env-device.js +5 -1
  220. package/dist/plugins/remote-env-e2b.d.ts +5 -0
  221. package/dist/plugins/remote-env-e2b.js +2 -1
  222. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  223. package/dist/plugins/remote-env-file-error.js +7 -1
  224. package/dist/plugins/remote-env-host.d.ts +10 -2
  225. package/dist/plugins/remote-env-host.js +8 -2
  226. package/dist/plugins/remote-env-k8s.d.ts +140 -11
  227. package/dist/plugins/remote-env-k8s.js +277 -15
  228. package/dist/plugins/remote-env-local-docker.d.ts +5 -0
  229. package/dist/plugins/remote-env-local-docker.js +2 -1
  230. package/dist/plugins/remote-env-ssh.d.ts +6 -1
  231. package/dist/plugins/remote-env-ssh.js +2 -1
  232. package/dist/plugins/retention-store-sql.d.ts +14 -2
  233. package/dist/plugins/retention-store-sql.js +6 -0
  234. package/dist/plugins/run-store-sql.js +10 -4
  235. package/dist/plugins/session-fork.d.ts +10 -13
  236. package/dist/plugins/session-fork.js +5 -25
  237. package/dist/plugins/session-ownership.d.ts +24 -3
  238. package/dist/plugins/session-ownership.js +24 -5
  239. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  240. package/dist/plugins/session-workspace-cell.js +38 -0
  241. package/dist/plugins/sql-errors.d.ts +23 -2
  242. package/dist/plugins/sql-errors.js +15 -0
  243. package/dist/plugins/store-backend.js +7 -7
  244. package/dist/plugins/store-contracts.d.ts +10 -1
  245. package/dist/plugins/store-fault.d.ts +2 -2
  246. package/dist/plugins/store-fault.js +4 -2
  247. package/dist/plugins/tidb-pool.js +13 -0
  248. package/dist/plugins/tidb-session-store.d.ts +17 -2
  249. package/dist/plugins/tidb-session-store.js +18 -7
  250. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  251. package/dist/plugins/tool-result-store-sql.js +8 -7
  252. package/dist/plugins/worktree-isolation.d.ts +3 -2
  253. package/dist/plugins/worktree-isolation.js +1 -7
  254. package/dist/project-identity.d.ts +1 -14
  255. package/dist/project-identity.js +0 -4
  256. package/dist/request-key-closure.js +1 -1
  257. package/dist/request-value-closure.d.ts +31 -2
  258. package/dist/request-value-closure.js +7 -1
  259. package/dist/rule-entry-list.d.ts +3 -2
  260. package/dist/rule-entry-list.js +2 -2
  261. package/dist/rules-consent.js +12 -7
  262. package/dist/run-local.d.ts +2 -2
  263. package/dist/run-local.js +12 -6
  264. package/dist/runs.d.ts +7 -1
  265. package/dist/runs.js +8 -2
  266. package/dist/runtime-governance.d.ts +5 -5
  267. package/dist/sandbox-git-lease.d.ts +49 -0
  268. package/dist/sandbox-git-lease.js +60 -0
  269. package/dist/sandbox-git-plane.d.ts +84 -0
  270. package/dist/sandbox-git-plane.js +105 -0
  271. package/dist/security.d.ts +16 -4
  272. package/dist/security.js +8 -3
  273. package/dist/server-secret-env.d.ts +12 -3
  274. package/dist/server-secret-env.js +18 -2
  275. package/dist/session-error-wire.d.ts +14 -6
  276. package/dist/session-error-wire.js +2 -1
  277. package/dist/session-incarnation.d.ts +4 -5
  278. package/dist/session-incarnation.js +2 -2
  279. package/dist/session-policy-wire.d.ts +1 -1
  280. package/dist/session-policy-wire.js +2 -2
  281. package/dist/session-sync.d.ts +1 -1
  282. package/dist/session-workspace.d.ts +130 -0
  283. package/dist/session-workspace.js +83 -0
  284. package/dist/spec-fields.d.ts +44 -14
  285. package/dist/spec-fields.js +33 -12
  286. package/dist/standing-answer-grant.d.ts +2 -13
  287. package/dist/standing-answer-grant.js +0 -7
  288. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  289. package/dist/stored-request-prompt-profile-m30.js +25 -0
  290. package/dist/task-a2a.d.ts +44 -14
  291. package/dist/task-a2a.js +46 -20
  292. package/dist/task-mcp.d.ts +34 -21
  293. package/dist/task-mcp.js +46 -22
  294. package/dist/task-settings.d.ts +2 -2
  295. package/dist/tool-approval.d.ts +43 -9
  296. package/dist/tool-approval.js +57 -15
  297. package/dist/tool-rule-names.d.ts +5 -0
  298. package/dist/trace/core-keyset-guard.d.ts +34 -19
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +4 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +5 -2
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/ledger-sink.d.ts +4 -5
  306. package/dist/trace/ledger-sink.js +3 -7
  307. package/dist/trace/project.d.ts +62 -10
  308. package/dist/trace/project.js +49 -9
  309. package/dist/trace/redact.d.ts +18 -6
  310. package/dist/trace/redact.js +154 -60
  311. package/dist/trace/self-minted-wire.d.ts +500 -7
  312. package/dist/trace/self-minted-wire.js +182 -1
  313. package/dist/trace/wire-projection-faces.d.ts +7 -5
  314. package/dist/trace/wire-projection-faces.js +9 -4
  315. package/dist/user-authored-text.d.ts +2 -2
  316. package/dist/user-authored-text.js +16 -1
  317. package/dist/web-admin-client/apply-effective.js +0 -1
  318. package/dist/web-admin-client/center-request.d.ts +23 -4
  319. package/dist/web-admin-client/center-request.js +42 -8
  320. package/dist/web-admin-client/http-client.js +6 -6
  321. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  322. package/dist/web-admin-client/skills-mcp.js +31 -18
  323. package/dist/web-admin-client/types.d.ts +10 -5
  324. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  325. package/package.json +3 -3
  326. package/wire-changes/server@7.137.0.json +825 -0
  327. package/wire-changes/server@7.138.0.json +446 -0
package/README.md CHANGED
@@ -199,6 +199,7 @@ The server is configured entirely through environment variables. The most import
199
199
  | `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 |
200
200
  | `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) |
201
201
  | `HOOKS_TIMEOUT_MS` | unset (600000) | Time budget of each handler of the request `settings.hooks` registration (one event's run of the request's hook entries). Unset = 600000 ms — this is the engine's `budgetMs` for the registration (each handler's own time, one event's run of the request's hook entries); when the budget is spent, or the task is cancelled, the command hooks still running in that handler are stopped (SIGTERM, a short grace, then SIGKILL) — they no longer run on to their own `timeout`; the engine's default for an extension (10 s) is not used. `0` is honored as written (a zero budget). The server's own two observer registrations (deny meter, tool trace) do not read it. A non-integer, negative, or above-`setTimeout`-ceiling (2147483647) value **refuses to start** |
202
+ | `MODS_ROOT` / `MODS_PACKAGES` | unset | **Deployment mod packages** loaded into the deployment seat. `MODS_PACKAGES` = a JSON array of `{ "name", "dir", "tier", "options"? }`: `name` is the package's own name (its `.claude-plugin/plugin.json` `name`, spelled exactly); `dir` an absolute path whose real path must sit strictly under the real path of `MODS_ROOT` (an absolute path; compared by path components — a symlink out of the root, a look-alike sibling such as `/mods-evil`, a `..` traversal, or the root itself all refuse to start); `tier` is required (`prepend` / `user` / `append` — the host declares it, a package never does); `options` is handed to the package's `register` as is. The engine's package reader is the only reader of the package files; this server never reads them. Every declared package is loaded once at boot and the whole list goes through the engine's admission: any failure (bad declaration, outside the root, the reader refuses the package, a name mismatch, the admission refuses) **refuses to start** with one single-line sentence naming the slot or the package — the server's own sentence never echoes declared values (paths, `options`); the reader's / admission's own words are passed through (credential shapes redacted, control characters stripped) and may name the package directory. Loaded ⇒ one `mods_package_loaded` log line per package (carrying the engine reader's `digest` — a digest of the bytes of the three package files it read, which tells you which bytes were loaded and is not a code-integrity credential: code the module loads on its own is not in it), `/health.mods.deploymentPackages` and the capability `modsDeploymentPackages: true`; unset (or `[]`) ⇒ both faces stay exactly as without these knobs. A package is **trusted deployment code** — it runs in this process with access to every tenant and the process credentials, the admission is not a sandbox — so install it only from a read-only artifact tree you control. Changing the declaration or a package needs a restart (no automatic restart signal). The config catalog shows `MODS_PACKAGES` as present / length only. `options` is not a credential channel: this server does not withhold package credentials — `MODS_PACKAGES` itself and any variable a package reads are inherited by the model's child processes on the host lane under `inheritEnv: "all"` (only this server's declared own credentials are withheld), so deliver package credentials through a channel the host-lane model shell cannot read |
202
203
  | `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 |
203
204
  | `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none; must be a scenario this deployment has (boot refuses otherwise) |
204
205
  | `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
package/README.zh-CN.md CHANGED
@@ -169,6 +169,7 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
169
169
  | `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 腿照旧生效并说明 |
170
170
  | `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` 照常生效,且无论写在哪一行都优先于普通匹配行) |
171
171
  | `HOOKS_TIMEOUT_MS` | 未设(600000) | 请求 `settings.hooks` 那张扩展注册**每个 handler** 的时间预算(一个事件跑一遍本请求的钩子条目)。未设 = 600000 ms —— 这是引擎对这张注册的 `budgetMs`(每个 handler 自己的时间:一个事件跑一遍本请求的钩子条目);预算到、或任务被取消,这个 handler 里还在跑的命令钩子会被叫停(SIGTERM → 短宽限 → SIGKILL),不再跑到自己的 `timeout`;不用引擎给扩展的缺省(10 s)。`0` **按字面生效**(零预算)。本服务自己的两张观察注册(拒绝计量 / 工具追踪)不读它。非整数 / 负数 / 超 `setTimeout` 上限(2147483647)⇒ **启动即拒** |
172
+ | `MODS_ROOT` / `MODS_PACKAGES` | 未设 | **部署座装载的 mod 包**。`MODS_PACKAGES` = JSON 数组,每项 `{ "name", "dir", "tier", "options"? }`:`name` = 包自己的名(`.claude-plugin/plugin.json` 的 `name`,逐字同);`dir` 绝对路径,真实路径须严格落在 `MODS_ROOT`(绝对路径)的真实路径之下(按路径分量判 —— 符号链接出根、`/mods-evil` 这类前缀相似的兄弟目录、`..` 遍历、根本身都拒启);`tier` 必填(`prepend` / `user` / `append`,宿主声明,包不自报);`options` 原样交包的 `register`。包里的文件只有引擎的读包者一只读者,本服务不读。每只声明的包起服装一次、整列交引擎准入:任一失败(声明坏、越根、读包者拒包、名不同、准入拒)⇒ **拒启**,一行拒句点名哪一席或哪只包 —— 本服务自己写的句子不回显声明的值(路径、`options`);读包者 / 准入交回的话原样透传(凭据形换记号、剥控制字符),其中可能点名包目录。装上了 ⇒ 每包一行 `mods_package_loaded` 日志(带引擎读包者交回的 `digest` —— 装载件三件字节摘要,认得出装的是哪几份字节,非完整性凭据:模块自己再加载的代码不在其内)、`/health.mods.deploymentPackages` 与能力位 `modsDeploymentPackages: true`;未设(或 `[]`)⇒ 两面与没有这两根旋钮时逐字节同。包是**受信的部署代码** —— 在本进程里跑、可触全部租户与进程凭据,准入不是沙箱 —— 只从你控制的只读物料树装。改声明或换包要重启(没有自动重启信号)。配置目录对 `MODS_PACKAGES` 只回在场位与长度。`options` 不是凭据通道:本服务不替包扣凭据 —— `MODS_PACKAGES` 本身与包自己读的任何变量,在宿主车道 `inheritEnv: "all"` 下都会被模型的子进程继承(只有本服务声明的自有凭据被扣),包要的凭据请经宿主车道模型 shell 读不到的渠道交付 |
172
173
  | `CONFIG_PROVIDER` | 未设 | 配置来源:`local`(单机文件 `config.d/`)/ `remote`(registry 控制面)。闭集、逐字比较(7.104.0 起):其余任何值(大小写不同、带首尾空白)拒启;空串 = 未设 |
173
174
  | `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景;必须是本部署在册的场景名,否则拒启 |
174
175
  | `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
package/USAGE.md CHANGED
@@ -130,6 +130,12 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
130
130
  目录条目**自己的**凭据(`apiKeyEnv` 点名的变量、`sealedApiKey`)跟着中心 / config.d 的模型面热应用走,不用重启:新起的任务与调用
131
131
  用新的一代(在跑的任务用它起跑时那一代)。两处例外:降级壳焊住的目标连凭据身份一起按 `degrade-route` 重启片走;**记忆整理席**的模型在启动时定
132
132
  (重启生效),它的凭据却跟着热应用 —— 同名条目把端点和凭据一起换掉时,重启之前整理请求会带**新**凭据打**旧**端点,这种换法之后请重启本服务。
133
+ - **`agents[].model` 写成内联 Model 对象时,这一只模型的请求带哪条凭据**:本服务只验 `id` 非空,引擎对内联对象原样信任、不查目录;每次请求只发**一条**凭据 —— 凭据词头与部署钥互斥,只发赢家。
134
+ 优先序:这次调用的 per-model 钥(本服务按内联对象的 `name` 去目录找同名条目的 `apiKeyEnv` / `sealedApiKey`)> 内联对象自己 `headers` 里的凭据词头 > 部署网关钥(`MODEL_API_KEY`)。三种构造:
135
+ ① 内联对象声明了凭据词头(如 `x-team-token`)、没有同名目录条目带自己的钥、部署只有网关钥 ⇒ 内联头赢:这只子代的请求带 `x-team-token`、**不带** `authorization`(网关钥不发,所以要求 `authorization` 的上游会拒这条请求);
136
+ ② 同名目录条目带 `apiKeyEnv`(或 `sealedApiKey`)⇒ 条目的钥赢:请求带 `authorization`,内联对象上的 `x-team-token` **不发**;
137
+ ③ 内联对象没有凭据词头、也没有同名条目的钥 ⇒ 部署网关钥(`authorization`)—— 且只在内联对象的 `baseUrl` 与网关根配对时;配不上的由引擎的配对律响亮拒(`route.credential_mismatch`),不静默发。
138
+ 哪些头名算「凭据词头」归引擎的词表(读口 `hasAuthCarrier`:`x-team-token` · `helicone-auth` 算,`x-team-id` · `x-request-id` 不算)。
133
139
  - **模型端点回重定向 ⇒ 不跟、该次调用响亮失败**(引擎 7.51.0 起的模型请求;hook 直连腿 —— 会话标题器等 —— 与记忆 embedder 同一条):请求只去被判过的那个根,
134
140
  句子 `endpoint answered with a redirect (HTTP <n>); it was not followed and no credential was sent anywhere else — …`。出路:把最终端点写进 base URL
135
141
  (`http` 根被重定向到 `https`、网关搬家、尾斜杠重定向都是这一形)。
@@ -147,6 +153,10 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
147
153
  - **会话自动标题与 hooks 的 `prompt` / `agent` 条目走哪条线路,和主请求读同一只判据**:`provider="anthropic"` 的模型只在 anthropic 路由装上(配了 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)时走云 `/v1/messages`;
148
154
  没装(如 `MODEL_PROVIDER=anthropic` 却只配了 `MODEL_GATEWAY_BASEURL` + `MODEL_API_KEY`)时主请求落网关,这两个面的廉价直连调用同样落网关的 openai 兼容线路(`/chat/completions`),
149
155
  端点与凭据照网关的来 —— 此前它们按 `provider` 字面去找一条没装的 anthropic 路由而整条拒发,会话列表没有自动标题。网关端点也解析不到才拒(`config.model_route_endpoint_missing`)。
156
+ - **预览与自动标题只读你自己写的那段文字**:会话列表的 `objectivePreview`、run / leader 行的 `objective_preview`、自动标题取材、收件箱 `objective` 与任务视图 `name` 读同一张闭集 ——
157
+ 引擎注入的 `<system-reminder>` / `<task-notification>` / `<attachment>` 块,以及壳铸进用户轮的**本地命令回显**(`<command-name>` / `<command-message>` / `<command-args>`、`<local-command-stdout>` / `<local-command-stderr>` / `<local-command-caveat>`、
158
+ `<bash-input>` / `<bash-stdout>` / `<bash-stderr>` / `<bash-exit-code>`、`<user-memory-input>`)都不算你写的:`/compact` 一类本地命令之后的下一条提交,预览与标题取的是随后那句话,不再是回显;只有回显、没有你的话 ⇒ run / leader 行的预览是空串(任务名折成 `(notification turn)`),会话列表的预览回落到更早的一句、都没有则缺席(`null`)。
159
+ 代价:正文里真的写了这些开标签字面的那一段在预览 / 标题里被剥(只是**提到**这些词的原样保留),原文在会话历史读面完整可见。
150
160
  - **缺省推断**:`MODEL_PROVIDER` **未设**、但显式配了 Anthropic 协议 base URL(`ANTHROPIC_BASE_URL`)**和**对应凭证(`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)时,缺省自动判 `anthropic` 并打一行启动 warn(`model_provider_inferred`)——纯净机直连 Anthropic 兼容上游不再需要显式第七个键。显式 `MODEL_PROVIDER` 恒赢;只给 base URL 或只给凭证不推断;两者皆无 = `gateway`,与以前逐字节一致。
151
161
  - 启动日志的 `brain` 字段会回显当前组合(`failover` / `anthropicRoute` / `anthropicCacheBreakpoints`)。
152
162
  - **在服务进程之外读同一套 env 路由**(子路径 `@sema-agent/server/model-route-env`;纯数据 + 纯函数,import 无副作用,值依赖只有 `@sema-agent/core` 与 `@sema-agent/settings-schema`):
@@ -171,7 +181,13 @@ MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # 响应头之后迟迟不吐第一个 delt
171
181
  MODEL_IDLE_TIMEOUT_MS=20000 # (1.40.1)出过 token 后中途卡死:每个 delta 重置,静默超时→中止(补 first-token 只管首字);缺省 300000
172
182
  # ④ 断路器:主网关连败 N 次即"开路"→ 快速失败,让 failover 立刻切备(不再逐个等超时)
173
183
  MODEL_CIRCUIT_BREAKER=true MODEL_CB_FAILURE_THRESHOLD=5 MODEL_CB_COOLDOWN_MS=30000
174
- # ⑤ 每次调用的重试上限(0–20)。**不设 = 用引擎默认**(server 不再写死它)
184
+ # ⑤ 每次调用的重试上限(0–20)。显式设了**恒赢**:原样传给引擎,并遮蔽引擎自己的 `SEMA_MAX_RETRIES` 环境变量。
185
+ # **不设**时按拓扑分两态(启动日志 `brain` 字段里的 `gatewayRetryCap` 回显是哪一态):
186
+ # · 外层有快切层 —— 配了备用网关(`MODEL_GATEWAY_FALLBACK_URLS`)、开了断路器(`MODEL_CIRCUIT_BREAKER`;单路网关上断路器自己不生效,这一条照算)、
187
+ # 或开了反应式降级(`MODEL_DEGRADE_REACTIVE`,须配着降级目标 `MODEL_DEGRADE_TO`;只设前者不装配降级、不算快切层)⇒ openai 兼容腿 server **传 2**(`fast-fail:2`):走共享长阶梯的瞬时失败(网络 / 5xx / 429)按引擎默认的 10 次递增退避要耗上百秒才把错误交给外层,切备 / 断路器 / 降级全看不见错误(确定性的拒连另有短阶梯,默认 2 次,不在此列);
188
+ # 这一态下 `SEMA_MAX_RETRIES` 被遮蔽(server 传了值),要别的次数就显式设 GATEWAY_MAX_RETRIES;
189
+ # · 没有快切层(单路网关,限流 provider 的形)⇒ 不传(`engine-default`),引擎默认当家:读 `SEMA_MAX_RETRIES`(上限 15),没设就是 10。
190
+ # 云 Anthropic 腿(ANTHROPIC_MAX_RETRIES)没有这条收窄:不设就是引擎 / SDK 默认;降级兜底腿也只吃显式的 GATEWAY_MAX_RETRIES(它是最外层,按引擎默认耐心重试)。
175
191
  GATEWAY_MAX_RETRIES=10 # openai 兼容腿(第三方限流 provider 走的就是这条)
176
192
  ANTHROPIC_MAX_RETRIES=10 # 云 Anthropic 腿
177
193
  # ⑥ 慢后端上开 auto 模式(permissionMode:"auto"):分类器那一级也在等同一个排队 + prefill 的后端,到点没裁 ⇒ 这次工具调用被拒。
@@ -180,7 +196,7 @@ AUTO_MODE_CLASSIFIER_TIMEOUT_MS=180000
180
196
  ```
181
197
  - **全部默认关**(超时=0、断路器=false)→ 不设就和以前**逐字节一致**。
182
198
  - 断路器**只在配了 `MODEL_GATEWAY_FALLBACK_URLS`(≥2 路)时才有意义**——它的价值是"开路即快速失败 → failover 立刻切备";单网关下它是 no-op(启动日志 `circuitBreakerNoop` 会提示)。只 `network/server/rate_limit` 计入连败,`auth`/`invalid_request` 不计(坏 key 熔断整网关无意义)。备用网关(最后一路)不套断路器。
183
- - **重试上限**(2026-07-31):两个键**不设就不传给引擎** —— 引擎默认当家。以前这里是 server 侧硬编码 `2`(openai 腿甚至零配置出口),而**显式传参压过引擎默认**,于是引擎抬默认对 server 部署毫无效果;第三方限流 provider 下"重试两次就放弃"正是由此而来。现在:不设=继承引擎默认(抬默认那天自动跟上),设了=按设的走。钉在 `test/brain.test.ts` 的「网关腿重试次数」两条上。
199
+ - **重试上限**(2026-07-31 起不再硬编码):`ANTHROPIC_MAX_RETRIES` **不设就不传给引擎**(引擎 / SDK 默认当家);`GATEWAY_MAX_RETRIES` 不设时按拓扑分两态(上面 ⑤ 段:外层有快切层 ⇒ 网关各路 server 传 2 并遮蔽 `SEMA_MAX_RETRIES`,没有快切层 ⇒ 不传、引擎默认当家),降级兜底腿只吃显式值。以前这里是 server 侧硬编码 `2`(openai 腿甚至零配置出口),而**显式传参压过引擎默认**,于是引擎抬默认对 server 部署毫无效果;第三方限流 provider 下"重试两次就放弃"正是由此而来。现在:单路网关不设=继承引擎默认(抬默认那天自动跟上),设了=按设的走。钉在 `test/brain.test.ts` 的「网关腿重试次数」两条上。
184
200
  - 断路器状态:配了 `SESSION_BACKEND=mysql` 时自动用**跨副本共享态**(SQL `breaker_state` 表,写穿+刷新最终一致),否则进程内 Map。启动日志 `breakerState` 字段回显 `shared(mysql)`/`in-process`/`off`。
185
201
  断路器键 = `<provider>:<模型>@<路由身份摘要>`(摘要 24 hex;整只键 ≤ 190 字节,`<provider>:<模型>` 过长时截成前缀 + `~` + 摘要;键里不带 URL —— 它进共享表 `breaker_state`、追踪事件与开路错误文本 `circuit open for <键>`;7.132.x 及以前的表 `circuit_breaker` 由起服迁移删掉)。摘要对应哪条路由读启动日志 `brain.circuitBreakerRoutes`(每条带断路器的路由一行:展示串 + 摘要)。
186
202
 
@@ -419,10 +435,10 @@ MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
419
435
 
420
436
  **可选 — 开发者模式 building blocks(core 1.43,默认中立)**
421
437
  ```bash
422
- # 给指定角色挂「去品牌的通用编码提示词」CODE_AGENT_PROMPT(工程纪律:理解后改/最小复杂度/危险操作谨慎/验证后报告)。
423
- MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「任务没自带 systemPrompt」时跑编码提示
438
+ # 给指定角色按词点名引擎目录的「编码角色基底」(去品牌的通用编码提示词:理解后改/最小复杂度/危险操作谨慎/验证后报告)。
439
+ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「任务自己没说角色基底」时跑编码基底
424
440
  ```
425
- - 经 `RoleSpec.systemPrompt` 挂在**角色**上,非开发角色保持中立、全局默认 `DEFAULT_SYSTEM_PROMPT` 不变;任务自带 `systemPrompt`(或客户端注入)时仍优先。
441
+ - 经 `RoleSpec.promptRole: "code"` 挂在**角色**上(7.137.0 起 / 引擎 7.56.0;此前是把同一段目录文本粘进 `RoleSpec.systemPrompt`),非开发角色保持中立。角色基底只有一条来源链:任务 → 请求的模型角色的预设 → 引擎委派子代的人设,**第一个说出基底的来源整个说了算** —— 任务自带 `systemPrompt`(或客户端注入)、或场景自己写了角色词(cowork 场景写 `cowork`)时任务优先;这些 run 的宿主角色是 `code`(按 `when.host.role` 条件的 overlay 自此对它们生效),基底字节与此前相同。
426
442
  - **验证门**(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
443
  - **记忆(文件记忆引擎,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
444
  **`MEMORY_PERSISTENCE_CAPABLE`(三态,7.14.0 起)**=**部署自述**「本机能不能把用户的『记住 X』落到持久处」,与请求键 `memoryWrite` 两轴正交(后者收紧本次运行,前者是 operator 声明;任一取否即只读方向)。缺省不设=引擎按 roster 自行推断;`true`=本部署有推断看不见的持久通道(自定义 writer / 记忆 MCP / 远程执行车道与记忆根共享挂载);`false`=强制只读披露并关掉文件工具往记忆根的写通道。不认得的词拒启。
@@ -704,6 +720,9 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
704
720
  引擎把**本会话跟踪集**收敛到该消息时刻的边界;`acceptPartialRestore: true` 容忍部分恢复
705
721
  (缺省=响亮失败带逐文件账本)。旧键 `rewindFiles` 的 capture 义=容忍 no-op(首触跟踪恒开),
706
722
  restore 义(与 resumeAt 同发)=typed 拒并指路 `restoreFiles`(迁移显式化是 core 裁定)。
723
+ - rewind 与设备车道(`REMOTE_EXEC=device`):带 `resumeAt` 的那一发是**根提交**(不是续跑一族),`deviceId` 照收、按根提交的规矩判 —— 已绑:缺席 = 沿用 / 同 = 幂等 /
724
+ 异 = 403 `device.identity_mismatch`(rewind 不换设备,换设备走显式换绑);未绑:携 = 首绑、缺席 = 409 `device.not_bound`;非 device 车道携它 = 400 `request.precondition_unmet`。
725
+ `resumeAt` 自己的拒(值形 400、`resume_at.*`)先答,设备准入链后答。
707
726
  - **按 run 关掉捕获**(7.104.0):提交体带 `fileHistory: "disabled"`(唯一词;坏值 400
708
727
  `request.field_invalid`)⇒ 这条 run 的编辑照常落盘,但不做首次触碰备份、不铸回合边界,委派出去的子代继承;
709
728
  `wiring_manifest.fileHistory` 报 `"disabled"`。同一提交再带还原键(`restoreFiles` / `rewindFilesTo` / `rewindFiles`)⇒ 受理时
@@ -739,6 +758,8 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
739
758
  # 凭证两键名 SEMA_WEB_ADMIN_TOKEN / SEMA_WEB_ADMIN_TOKEN_FILE 与「二选一」判官的属主 = @sema-agent/settings-schema
740
759
  # `WEB_ADMIN_ENV` / `judgeWebAdminCredentialDeclaration`(6.1.0 起;管理面 / 壳 / 本服务 import 同一处)
741
760
  SEMA_WEB_ADMIN_URL=http://<web-admin-host>:3100 # 启动拉 GET /api/config/effective(Bearer+ETag),覆盖 env 兜底
761
+ # 必须是中心的最终地址:中心回重定向(301/302/303/307/308)⇒ 该条请求被拒、不跟(请求体与 Authorization 不发往 Location),
762
+ # 拒句 `web-admin center request: endpoint answered with a redirect (HTTP <n>); …` 点名这一席;LIVE_URL 同。反代请直接回答。
742
763
  SEMA_WEB_ADMIN_TOKEN=<SERVICE_PULL_TOKEN 的值> # 取自配置控制面主机 .env;只读拉取令牌
743
764
  # SEMA_WEB_ADMIN_TOKEN_FILE=/path/to/token # 或:凭证文件(与上一行二选一,都设=拒启)。每条中心请求都重读 ⇒ 轮换 / 换账号改写文件即可,
744
765
  # 中心请求面不重启;文件不可读 / 为空 = error 级 center_credential_unreadable,不当作无凭证。
@@ -798,7 +819,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
798
819
  # (不公告就听不到能力位),缺一 ⇒ 起服拒启 config.fleet_member_unfenced —— 成员不许悄悄脱离围栏。
799
820
  # 续租打 POST /api/fleet/live/serving-lease(契约 FLEET_PATHS.servingLease;活性基址见 SEMA_WEB_ADMIN_LIVE_URL)。
800
821
  # 心跳答复体读不出 / 不是 JSON 对象 = 没有能力读数(位与租约钟原样,warn fleet_announce_reply_unreadable)。
801
- # FLEET_SCOPE=acme # 7.121.0:本引擎所在的**租户 scope**(配置渲染器为多租引擎写 = 它的 per-worker 令牌绑的 scope)。缺席 =
822
+ # FLEET_SCOPE=acme # 7.121.0:本引擎所在的**租户 scope**(配置渲染器对每台受管引擎写,缺省 scope 也写 = 它的 per-worker 令牌绑的 scope)。缺席 =
802
823
  # 缺省 scope global;在场必须是 scope id 形(小写字母 / 数字 / 内部连字符,1–64;空串拒启
803
824
  # config.fleet_scope_invalid)。与 FLEET_GENERATION 不联动。只进排空口的身份比对:请求的 scope 与它比,
804
825
  # 请求不带 scope 读成 global。续租体与心跳不带它(scope 由中心凭证绑定)。
@@ -1321,6 +1342,11 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1321
1342
  内建 `code` 与 `default` 共用一只工厂,技能按 `default` 过滤 —— 只写了 `scenarios: code` 的技能不进内建 `code` 的 run,卡上也不列(要进,标 `default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,按它自己的名字 `code` 过滤。
1322
1343
  能力位 `scenarioSkills`(恒 `true`)缺席 = 老 server,卡上没有这一键;别从 `400 config.user_invoked_skill_unknown` 的拒句里刮名字(那是引擎的散文,会变)。
1323
1344
 
1345
+ **看一条 run 装着哪些技能(7.137.0 起)**:这条 run 的 `wiring_manifest` 帧(流式 SSE、`GET /v1/runs/:id/events`、非流式 `POST /v1/tasks` 200 体的 `wiringManifest`)带本服务自铸段
1346
+ `skills` = 上面 1–4 叠出来、交给引擎的那一组(同一组、同一个序),每条 `{ name, description, source }`:`source` 除了 `"factory"` / `"center"` 再多一个 `"request"`(第 4 条,请求体自带的;
1347
+ 与部署同名的那条被丢,不在册)。`disableSkills: true` 或一只都没有 ⇒ `skills: []`;委派子代的帧不带这一段;缺席 = 老 server 或装配不是本服务出口产的,别读成 `[]`。
1348
+ 续跑腿报的是续跑那一次装配的名册。`description` 是展示文本(请求自带的那几条是调用方写的,上 wire 前过脱敏、截长)。部署技能正文超过引擎加载上限的那一只,这一段仍会列出(引擎不装它,见上一段)。
1349
+
1324
1350
  **MCP 条目的工具风险轴(`toolAxes`,7.128.0 起)**
1325
1351
 
1326
1352
  `config.d/mcp.json` 的 `servers[]`(或配置中心下发的同形条目)可带 `toolAxes`,为这台 server 的**每只工具**声明风险轴;声明逐字上引擎的 `McpServerSpec.toolAxes`,喂审批门与计划模式围栏:
@@ -1615,7 +1641,7 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1615
1641
  (`SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` …)与名单型旋钮名起服已向引擎声明为本进程自有凭据,生效配置里每条模型条目的 `apiKeyEnv` 也在名单里
1616
1642
  (中心热应用新增 / 改名条目时在模型目录换代前重声明,名单只随落地的世代增长 —— 改名后旧名照扣到重启,没落地的候选不留痕;条目的 `apiKeyEnv` 是引擎扣不住的名 ⇒ 该候选整拒),`"all"` = 整份环境**减**它们,任何继承词都不把它们
1617
1643
  交给模型 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。部署方
1644
+ ⇒ 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
1645
  **没有**旋钮能替调用方打开它,也没有旋钮能在单用户部署上关掉它(单用户部署的用户把他自己的环境交给他自己驱动的 shell);起服一行 info
1620
1646
  `task_inherit_env_honored {honored}` 告诉你这台机器认不认。上面「部署级名单不给模型驱动 shell」那条不变 —— 两件事是两条轴(部署名单 vs 调用方 × 单用户射程)。
1621
1647
 
@@ -1650,7 +1676,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1650
1676
  |---|---|---|---|
1651
1677
  | `E2B_SANDBOX_ENV` | `REMOTE_EXEC=e2b` | 逗号分隔的**裸变量名**,如 `E2B_SANDBOX_ENV=GIT_TOKEN,NPM_TOKEN` | 点到的键从服务进程环境**原样**注入沙箱(e2b 沙箱级 env;与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢) |
1652
1678
  | `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) |
1679
+ | `K8S_SANDBOX_ENV` | `REMOTE_EXEC=k8s` | 同上 | 点到的键的值从服务进程环境**原样**写进该沙箱 pod 自己的 Secret,pod 的容器 env 只带 `secretKeyRef` 引用(pod spec 里没有值;与 `SANDBOX_PKG_SOURCE` 推导出的包源变量同名时名单赢;要 Role 的 secrets `create`,详见 §9.3c) |
1654
1680
 
1655
1681
  三只旋钮只在**自己的车道被选中时**才求值(别的车道上它们是无关配置,不校验、不告警)。未设 / 空串 = 不注入。**形错拒启**:空段
1656
1682
  (`A,,B`、尾逗号)、非变量名形(`1BAD`、带空格、`NAME=value` 整段贴进来)、点名名单型旋钮(`E2B_SANDBOX_ENV=E2B_SANDBOX_ENV`,
@@ -1876,7 +1902,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1876
1902
  | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server / 会话店不存元组(local 后端),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是铸行那一刻它所在委派树的根会话的属主(**7.133.0+**,写下那一行时按当时读到的写入;此前 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(存量无主会话互认臂 —— 单用户形下 `default` 主对无主的普通会话放行,fork / 删除 / `/sync/*`(同步族自 7.134.0 起)同走这一只 —— 对这些 placed 行同样不放行,审批豁免、notify 同样不放行;fork / 删除 / `/sync/*` 的 fleet 调用方与此处同一个判据:出示 service 凭据、不带主体头,带主体头的 service 凭据请求是那位主体,7.134.0 起);SQL 后端每次起服回填这些行(起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0,或等它们下一次停驻)。推不出属主的子代写成属主 `_orphan:<id>`,只有 fleet-wide / operator 读得到:SQL 后端的子代行根会话已删时按它记录的父会话,父也删了才是 `_orphan:`;停驻时拷进宿主店的那一份(local 后端)只按根判,根会话已删 ⇒ `_orphan:`;子代被派出 / 批准之后、再次停驻读根之前根会话被删并由别人以同一 id 重新登记 ⇒ 其下的子代再次停驻被否决(读过根之后才换的,写进去的仍是读根那一刻的属主) |
1877
1903
  | `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的。队列里某一行的存储体读不出来、重试不会好时,答 `500` + `errorCode:"approvals.read_corrupt"`(**7.134.0+**;与订阅流同码、同一只判官;运维处置那一行后再读,别原样重试);此刻读不出队列(连接 / 超时 / 店抖动)⇒ `503` + `errorCode:"approvals.read_unavailable"`(**7.135.0+**,此前 500 `internal.error`;稍后重发,这一答不表示队列为空)。`GET /v1/assistant/inbox` / `GET /v1/assistant/tasks` 读同一个队列,两种读失败同码同状态(**7.135.0+**)。停驻的**技能卡**(对引擎 `Skill` 工具的调用)的行带 `boundGrantDigest` / `boundGrants`(批准它还授予的东西:技能名 + 两只工具清单;清单只在哈希等于摘要时才有,是部署文本,渲染要按上下文转义),分类器没给判词的卡带 `classifierNoRuling`(`error` / `timeout` / `parse_error`)—— **7.136.0+**,与 `GET /v1/assistant/inbox` 行逐字同值(此前只有收件箱行与活卡帧带);普通卡三位都缺席。`GET /v1/assistant/inbox` 行的 `objective` 是门所属 run 的**目标预览**(写入时已脱敏、截到约 120 字符,与 `GET /v1/assistant/tasks` 读同一列;委派子代的门读派它的那条 run;没有 run 账本 ⇒ `null`;**7.136.0+**,此前原样回显存着的任务目标,运维 `?owner=` 跨看也是原文) |
1878
1904
  | `GET /v1/approvals/stream` | 同一队列的 SSE 推送(范围同上):首拍快照 → `pending` / `resolved` 增量(开了工具审批时另有 `live_pending` / `live_resolved`),空闲时心跳,15 分钟上限(`STREAM_MAX_DURATION` 帧后重连)。**心跳只表示流活着,不表示这一拍读过队列**:队列读不出时每次读失败发一帧 `error`(`approvals.read_unavailable`,流留着、下一拍再读,恢复后同一条流继续);某一行的存储体读不出来、重试不会好时发 `approvals.read_corrupt` 并收流(运维处置那一行后再重连)。15 分钟上限对读失败的拍同样算数。`pending` 帧带的行与 `GET /v1/approvals` 同一只投影(含上面的技能卡三位,**7.136.0+**) |
1879
- | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役。停驻的后台子代**复活不了**(行上没有 agent 名 —— 今天未命名的后台子代就是这一形 —— 或没有根会话锚)⇒ `deny` 作废这张卡:`200 {taskId, status:"failed", decision:"deny"}`,卡离开待决队列、子代终局 `failed`;`approve` 答 `409 decide.row_unrevivable`、拒句指路 deny(**7.136.0+**;此前两个决定都 409,卡处理不掉) |
1905
+ | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役。停驻的后台子代**复活不了**(行上没有根会话锚;未起名的后台子代不在此列 —— 引擎 7.57.0 起每只子代都有名字,升级前的无名行读作派生名,照常复活)⇒ `deny` 作废这张卡:`200 {taskId, status:"failed", decision:"deny"}`,卡离开待决队列、子代终局 `failed`;`approve` 答 `409 decide.row_unrevivable`、拒句指路 deny(**7.136.0+**;此前两个决定都 409,卡处理不掉) |
1880
1906
  | `GET /v1/tool-approvals/<approvalId>` | 按问读口(**7.133.0+**;与 `POST …/respond` 同门同身份,不是你的 ask 与查无同形 404;**7.136.0+** 两扇决议口 —— `POST …/respond` 与耐久决断口 `POST /v1/tasks/<taskId>/asks/<askId>/decision` —— 与本口同一只属主判据:有行的问读行上当下的属主,收编之后新属主三口都认、前属主三口都 404;本口在店读不出时只对本副本自己铸的活登记回落登记(登记属主认你 ⇒ `pending`),起服接管的登记不回落、答 404 —— 与同一刻的应答口同答,此前本副本上的活卡读的是铸卡那一刻记下的属主):一只审批问此刻在哪一态,`state` ∈ `pending` / `decided` / `parked` / `void`(闭集)。停驻卡被答过 ⇒ `decided`、停驻 run 被取消 ⇒ `void`(**7.134.0+**;7.133.x 恒答 `parked`);停驻行的门已结算却读不出可用的决议(含坏行:状态词 / 种类 / 当事方不在闭集、结算记录过不了引擎的不变量筛、缺必填成员、时刻 / 方向读不出)⇒ 404 `tool_approval.not_pending`,运维日志一行 error(`defect` 首条缺陷句 · `defectCount`)。停驻行的 `decided` 可回到 `parked`:续跑腿在被批准的动作开跑前失败时,引擎重开同一张门 —— 读口答 `parked` + `decidePath` 并另带 `reopened`(**7.135.0+**):`reason` 是引擎的重开因由词(`env_failed` 按原决议经 `decidePath` 再交一次 —— 本副本还留着首次受理回执时(缺省 1 小时内)`…/respond` 回放那张回执、不驱续跑;`tool_unavailable` / `constraint_rejected` 重新决;`config_refused` 改好配置再交或重新决),`decision` / `decidedAtMs` / `decisionNote?` 是重开之前的那次决议(读不出 ⇒ 只带 `reason`);再决之后回到 `decided`。续跑被取消 ⇒ 重开的门被收掉,读作 `void`。全文见契约 §4a-bis-read |
1881
1907
  | `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** fleet 调用方:不带主体头的 service 凭据 / 只读 `TRACE_TOKEN`(只在没配 service 凭据的部署上够得着这一口)/ dev-open;带主体头的调用方 —— 含带 service 凭据的 —— 锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
1882
1908
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**、附件供给、**技能清单**(见下) |
@@ -1921,7 +1947,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1921
1947
  >
1922
1948
  > **部署治理三声明(7.21.0 起)**——三根都是**配置期**旋钮,「谁能改它们」≡「谁能改
1923
1949
  > 部署配置」,不引入新鉴权面;三根**都不设 = 现行为逐字不变**,坏值一律**拒启**(安全轴上的旋钮不静默失效)。
1924
- > - `COMPLIANCE_PROFILE=hipaa|zdr`(+ 可选 `COMPLIANCE_ADDITIONAL_DENIES=mcp_servers,workflows,web_fetch,org_memory_mount`,
1950
+ > - `COMPLIANCE_PROFILE=hipaa|zdr`(+ 可选 `COMPLIANCE_ADDITIONAL_DENIES=mcp_servers,workflows,web_fetch,org_memory_mount,a2a_peers`,
1925
1951
  > **只能在档位内建 floor 上加禁,不能减**):合规档位否决。档位在场时被禁能力不挂载,任务**显式请求**被禁能力
1926
1952
  > ⇒ 响亮拒(prepare 期终态 `config.compliance_denied`)。hipaa 内建禁 `web_fetch`+`org_memory_mount`,zdr 内建禁
1927
1953
  > `org_memory_mount`。⚠️ 档位是**部署级**(对本部署所有 principal 同一份)——per-principal 档位需要中心侧下发面,
@@ -1931,8 +1957,8 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1931
1957
  > ②禁 `web_fetch`(**`hipaa` 的内建 floor 就含它**)而本部署是**单用户**形——单用户花名册恒挂 WebFetch
1932
1958
  > (多租户形才会把它摘掉)。也就是说 **`COMPLIANCE_PROFILE=hipaa` 目前只能跑在 `REQUIRE_PRINCIPAL=true`
1933
1959
  > 的部署上**;拒启文案会逐条说明改法。
1934
- > - `LOCKED_CONFIG_KEYS=mcp,compliancePosture,retentionPolicy`(闭集:`mcp`/`toolPolicy`/`compliancePosture`/`retentionPolicy`):
1935
- > 管理员锁定层。锁了 `mcp` ⇒ 任务自带 `mcpServers` 被**同步 400 整拒**(`errorCode:"config.locked_key"`,不静默丢)。
1960
+ > - `LOCKED_CONFIG_KEYS=mcp,compliancePosture,retentionPolicy`(闭集 = 引擎的锁注册表:`mcp`/`a2a`/`toolPolicy`/`compliancePosture`/`retentionPolicy`;`a2a` 自引擎 7.59.0 起在册):
1961
+ > 管理员锁定层。锁了 `mcp` ⇒ 任务自带 `mcpServers` 被**同步 400 整拒**(`errorCode:"config.locked_key"`,不静默丢)。锁了 `a2a` ⇒ 任务自带的 `a2aPeers`(键在场且不是 `null`,空数组也算)同样被**同步 400 整拒**(`errorCode:"config.locked_key"`,与 `mcpServers` 同码同状态);`GET /v1/capabilities` 的 `a2aInjection` 为 false。续跑重放存体带 `a2aPeers` 的停驻任务在这样的部署上同拒,折成 `409 resume_blocked_by_policy`(`blockedBy:"config.locked_key"`)。
1936
1962
  > ⚠️ **本服务拒绝两把装不上的锁**:①`toolPolicy`——本服务每条腿都自铸有效工具策略(部署审批基线 + tighten-only
1937
1963
  > 治理拍),锁上会让**每一条**任务在引擎门口被拒,而那把锁本就无物可守(策略已是部署所有、请求放松不了);
1938
1964
  > ②`mcp` + 部署自己配了 MCP 服务器——部署的服务器走同一个字段,锁上同样每条腿必拒。两者都在**启动期**点名拒,
@@ -2267,8 +2293,8 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2267
2293
  | HTTP `400` + `errorCode:"request.field_invalid"`(`PATCH /v1/sessions/:id` / 2c 导入的 `title`) | 标题不合形:1–120 个 Unicode 字符、**首尾不得有空白**(服务端不代为 trim,先于 C0 判)、不得含 C0 控制字符、**至少有一个可见字符**(只由零宽 / 方向控制符等不可见码点构成的串被拒,句子 `title must contain at least one visible character`;夹在可见字符之间的照收);`title: null` 清回自动标题;空串是 `request.body_shape` | 客户端送出前自行 trim、拒空显示的标题;按 `errorCode` 分支,句子只作展示 |
2268
2294
  | HTTP `400` + `errorCode:"request.field_invalid"`(撞 413 的帽见 `request.payload_too_large`)+ `reason:"over_cap"` / `"too_long"` / `"bad_entry"`(7.132.0 起:工具名清单、子代定义、两张宿主路径清单;7.133.0 起提交体上其余的帽同样带:`jobId` · `systemPrompt` · `appendSystemPrompt` · `settings.outputStyle` · `settings.env` · `clientContext.*` · `sandboxImageProfile` · `outputSchema` · `resumeAt` · 顶层技能 `content` / `baseDir`;子代定义里技能自己的 `content` / `baseDir` 同样带,坐标 `agents[i].skills[j].content`,句子仍是形句) | 撞了帽:`field` = 撞帽那一处的体内坐标(`skills[1].allowedTools` · `agents[0].denyTools[3]` · `agents` · `additionalDirectories` · `systemPrompt` · `settings.env.<变量名>` …),`count` = 送来的条数(`over_cap`)或字符数(`too_long`),`cap` = 帽;`bad_entry` = 清单第 `index` 条坏了(`field` = 那张清单)。7.132.0 起覆盖的面句子头一句恒为 `<field> has <count> entries\|characters — at most <cap> are accepted`;7.133.0 起补上的面句子不变、只加这一组键。同一个字段的形错(不是串、空串)只有句子 | 按 `field` 改那一处(合并 / 拆小条目、缩短文字、修那一条路径);句子只作展示 |
2269
2295
  | HTTP `400` + `errorCode:"request.field_conflict"`(7.133.0 起带 `detail`;`requireExistingSession` 缺 `sessionId` 一处是 422) | 请求里的字段互斥或互为前置(`verify` × `cascade`、`capabilitiesNeeded` 没有 `sandboxImageProfile`、`disableSkills` 同体带技能、决定口的 `remember` / `answer` / `editedPlan` 错门 …)。体带 `detail: { kind, fields }`:`kind` 是闭集词(`cascade_verify` · `image_profile` · `resume_anchor` · `system_prompt_fold` · `file_history_restore` · `skills_disabled` · `existing_session` · `scratchpad_root` · `ask_answer` · `policy_person_ask` · `plan_edit` · `plan_mode_after` · `bound_call_door` · `remember_decision`,全表见契约附录 A),`fields` = 冲突涉及的请求体坐标(句子主语在前;互斥的类只点请求里真带了的键,「X 要求 Y」的类 Y 是缺席的前置、也在里面);`system_prompt_fold` 另带 `count` / `cap`(折叠后的字符数与帽) | 按 `detail.kind` 分支(例:`cascade_verify` ⇒ 去掉 `verify` / `cascade` 重提),按 `detail.fields` 定位到那几个键;句子只作展示、别锚文案 |
2270
- | HTTP `400` + `errorCode:"config.tool_face_control"`(7.126.0 起;流式 / 后台腿的引擎侧拒是 `failed` 终局带同码,受理口拒三条腿都是提交时 400) | 请求的 `allowTools` / `excludeTools` 里有一个**读成空**的条目:任何工具名都不可能有的拼法(空白、括号不配、空名)、`allowTools` 写了 `mcp__*`(引擎拒)、`excludeTools` 写了 `*`,或(7.131.0 起,受理口拒)**近似名**(大小写孪生 `BASH`;放行侧 `allowTools` 与限制侧 `excludeTools` 都拒 —— 想放的什么也没放 / 想禁的照用)与**限制侧的退役名**(`excludeTools: ["Task"]`)。放行侧 `allowTools` 的**内容形**(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告(运维行 `runner_config_advisory`);**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。**真未知名**(拼写合法、这台部署没有工具叫它,懒挂载的合法形)7.129.0 起照跑:条目原样留在表里,运维行告知一次。选择器只取工具名那一段判名字(限制侧 `Bash(echo $HOME)` 不拒),`allowTools:["*"]` 与限制侧 `mcp__*` / `a2a__*`(整个协议)合法。清单超 1000 条 ⇒ 整只 `400 request.field_invalid`(`count` / `cap`),帽内拒句只点名前 5 条 + `; and K more`。拒时:主模型零调用、标题器零调用(7.127.0 起;7.126.x 上标题器仍会为这次提交调一次模型)。这次提交留下的会话无标题、零消息 | 改成句子里 `write` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2271
- | HTTP `400` + `errorCode:"config.agent.unknown_tool"`(7.131.0 起) | 请求 `agents[i].denyTools`(限制侧)里有读成空的条目:近似名(`BASH`)、退役名(`bash`)、任何工具名都不可能有的拼法、`*`;`agents[i].allowTools`(放行侧)里的**近似名**。开跑前拒、主模型零调用、一个 token 没烧,句子点名条目、原因词与现名。限制侧 `Tool(content)` 的内容不读(读成整只工具)、限制侧整个协议的拒 `mcp__*` / `a2a__*`、真未知名(`NotMounted`,照跑、告运维一次)不拒;放行侧的内容形(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告,选择器清单里**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒;文法读不出的放行条目授不出去 = 收窄而不是放行:规则表(`permissions.allow` / 技能 `allowedTools`)上受理口不拒、引擎开跑时告运维;选择器清单(根 / 子代 `allowTools`)上受理口不拒、引擎开跑前自己拒(子代 ⇒ `config.agent.unknown_tool`、根 ⇒ `config.tool_face_control`,同步腿都是 `400`〔7.132.0 起;此前子代这一形答 HTTP 200 + failed 终局体〕;异步 / 活流腿 HTTP 2xx、同码落在终态 run 行) | 改成句子里 `write` 后的现名,或删掉条目 |
2296
+ | HTTP `400` + `errorCode:"config.tool_face_control"`(7.126.0 起;流式 / 后台腿的引擎侧拒是 `failed` 终局带同码,受理口拒三条腿都是提交时 400) | 请求的 `allowTools` / `excludeTools` 里有一个**读成空**的条目:任何工具名都不可能有的拼法(空白、括号不配、空名)、`allowTools` 写了 `mcp__*`(引擎拒)、`excludeTools` 够着了这一腿依赖的协议工具(`*` / 前缀 glob / 直接点名都算,原因词 `protocol_denied`;`excludeTools` 写 `*` 本身不拒 —— 7.137.0 起限制侧名内的 `*` 是整名 glob,卸每一只工具),或(7.131.0 起,受理口拒)**近似名**(大小写孪生 `BASH`;放行侧 `allowTools` 与限制侧 `excludeTools` 都拒 —— 想放的什么也没放 / 想禁的照用)与**限制侧的退役名**(`excludeTools: ["Task"]`)。放行侧 `allowTools` 的**内容形**(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告(运维行 `runner_config_advisory`);**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。**真未知名**(拼写合法、这台部署没有工具叫它,懒挂载的合法形)7.129.0 起照跑:条目原样留在表里,运维行告知一次。选择器只取工具名那一段判名字(限制侧 `Bash(echo $HOME)` 不拒),`allowTools:["*"]` 与限制侧 `mcp__*` / `a2a__*`(整个协议)合法。清单超 1000 条 ⇒ 整只 `400 request.field_invalid`(`count` / `cap`),帽内拒句只点名前 5 条 + `; and K more`。拒时:主模型零调用、标题器零调用(7.127.0 起;7.126.x 上标题器仍会为这次提交调一次模型)。这次提交留下的会话无标题、零消息 | 改成句子里 `write` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2297
+ | HTTP `400` + `errorCode:"config.agent.unknown_tool"`(7.131.0 起) | 请求 `agents[i].denyTools`(限制侧)里有读成空的条目:近似名(`BASH`)、退役名(`bash`)、任何工具名都不可能有的拼法;`agents[i].allowTools`(放行侧)里的**近似名**。开跑前拒、主模型零调用、一个 token 没烧,句子点名条目、原因词与现名。限制侧 `Tool(content)` 的内容不读(读成整只工具)、名内的 `*` 是整名 glob(7.137.0 起,孤 `*` = 每一只工具)、限制侧整个协议的拒 `mcp__*` / `a2a__*`、真未知名(`NotMounted`,照跑、告运维一次)不拒;放行侧的内容形(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告,选择器清单里**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒;文法读不出的放行条目授不出去 = 收窄而不是放行:规则表(`permissions.allow` / 技能 `allowedTools`)上受理口不拒、引擎开跑时告运维;选择器清单(根 / 子代 `allowTools`)上受理口不拒、引擎开跑前自己拒(子代 ⇒ `config.agent.unknown_tool`、根 ⇒ `config.tool_face_control`,同步腿都是 `400`〔7.132.0 起;此前子代这一形答 HTTP 200 + failed 终局体〕;异步 / 活流腿 HTTP 2xx、同码落在终态 run 行) | 改成句子里 `write` 后的现名,或删掉条目 |
2272
2298
  | HTTP `400` + `errorCode:"prompt.assembled_role_refused"`(7.132.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `systemPrompt` / `appendSystemPrompt`(含 `settings.outputStyle`)逐字含引擎提示里的一段锁定文本(宪法、安全段、`# Harness` 这样的标题行)—— 一份已组装的提示。开跑前拒、零调用,句子点名字段与命中段 | 只写角色层:删掉抄进来的引擎段(引擎自己组宪法与行为段;追加段恒排在宪法之后) |
2273
2299
  | HTTP `400`(`request.field_invalid` · `config.agent.unknown_tool` · `config.tool_face_control`)· HTTP `422`(设置规则桶)· HTTP `409` `resume_blocked_by_policy` 的拒体另带 `field` + `detail`(7.133.0 起;additive,句子与码不变) | 工具名清单与设置规则里**读成空**的条目被拒时,句子仍只点名先撞上的那一张清单(前 5 条 + `; and K more`);机读位把这一遍判下来的**全部**问题一次列全:`detail.problems` 每条 `{ code, field, entry, reason }`(`code` = 这一条所在清单被拒时的码,`field` = 清单的体内坐标,`entry` = 条目的脱敏原值,`reason` = 原因词),`detail.total` = 问题总数(`problems` 至多 1000 条),顶层 `field` = 第一条问题的坐标(句子点名的那张清单),体的 `errorCode` = 第一条问题的 `code`;只有一条问题就是长度 1 的列表,不是另一种形。超帽的清单整只拒、不逐条读,不在列表里(第一处命中就是它时体只带撞帽的 `field` / `reason` / `count` / `cap`)。同步提交腿上引擎开跑前拒这两张工具名清单(`config.agent.unknown_tool` / `config.tool_face_control`:有后继的退役名、文法读不出的放行条目)的 400 体同形:句子是引擎的,只点名它先撞上的一条,`detail.problems` 是受理口对放行侧选择器清单的读数(只列与体的码同码的那一族清单,所以第一条问题的 `code` 恒等于 `errorCode`) | 按 `detail.problems` 逐条提示 / 改;不要解析句子;`total` 大于 `problems` 的条数 ⇒ 列表被截 |
2274
2300
  | HTTP `200` / `202` 应答里的 `warnings`(7.133.0 起;同步腿 200 体 · 流式腿 `meta` 帧 · 异步腿 202 回执;**有话说才在场**) | 请求的放行侧选择器清单(根 `allowTools`、子代定义的 `allowTools`)里有什么也不放的条目,而受理口没拒(放行侧只拒近似名,其余交引擎):`reason` 是引擎条目读法的原因词(`tool_name` · `grammar` · …)或 `not_in_roster`(读得出、却不在名册 —— 引擎目录 ∪ 本服务自铸工具 ∪ 本请求挂的调用方工具(主名 · 别名 · 子代工具池);协议命名空间名 `mcp__*` / `a2a__*` 懒挂载、`*` 是「不设名单」,都不判)。每条 `{ code, field, entry, reason }`,形同上一行。这不是拒:请求照常受理、照跑,引擎对这些条目的既有处置不变 | 把 `warnings` 当提示给用户看(「这几条什么也没放」);同一 `Idempotency-Key` 的重放从 run 记录答,不重判、不带;在飞合流回放的是首发那一腿铸的应答 |
@@ -2278,6 +2304,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2278
2304
  | `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
2279
2305
  | HTTP `501`(`/v1/runs`) | 内存模式不支持异步 | 配持久存储(`DB_BACKEND=mysql` / `pg` + 坐标;7.106.0 起给了坐标必须点名 `DB_BACKEND`) |
2280
2306
  | 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`)。要继续工作就换一个新会话 |
2307
+ | 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
2308
  | 流里一帧 `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
2309
  | 流里一帧 `engine_notice` `code:"route.finish_reason_unknown"`,`detail:{ finishReason, sessionId, runId }`(7.126.0 起) | 提供方用一个引擎读不懂的结束原因结束了这一轮:回答按模型留下的样子保留,但可能被提供方截短了 | 告诉用户「这一段回答可能不完整」;同一个词同一 run 只说一次。运维按 `finishReason` 查是哪家网关 / 模型发的 |
2283
2310
  | 流里一帧 `engine_notice` `code:"route.stream_end_ignored"`,`detail:{ statement, word, frames, sessionId, runId }`(7.126.0 起) | 提供方已经结束了这一轮,它的流却又发来了东西(又一个结束 / 一个错误 / 内容);第一个结束算数,之后的没进这一轮 | 一般无需处置;频繁出现 ⇒ 运维查那条网关的流式实现(`statement` / `word` 说的是迟到的第一帧) |
@@ -2442,19 +2469,30 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2442
2469
  ### 9.3c k8s 车道:点名 env 转发(`K8S_SANDBOX_ENV`)
2443
2470
 
2444
2471
  沙箱 pod 里的工具要用服务进程环境里的凭据(典型:团队形态下 `git clone` / `git push` 私有仓)时,把**变量名**写进 `K8S_SANDBOX_ENV`,值留在服务进程环境里。
2445
- 它和 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 是**同一个法**(解析、拒启、告警、目录行、起服行 `sandboxEnvKeys`、多租户姿态全部见上文「沙箱带外 env 名单」一节),这里只写 k8s 车道自己的三件事:
2472
+ 它和 `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 是**同一个法**(解析、拒启、告警、目录行、起服行 `sandboxEnvKeys`、多租户姿态全部见上文「沙箱带外 env 名单」一节),这里只写 k8s 车道自己的四件事:
2446
2473
 
2447
2474
  名单里点名 `SEMA_PKG_SOURCE`(或任何包源变量)= 把服务进程自己的那份值原样下发,显式胜过 `SANDBOX_PKG_SOURCE` 推导出的那份 —— pod 的启动命令据此走包源 hook;这是「显式名单胜派生」在 k8s 车道上的自然结果,不是第二条通道。
2448
2475
 
2449
- - **值去哪**:只进沙箱 pod 的容器 env(pod spec 的 `containers[0].env[]`,pod 里每个进程、每条 exec 命令都看得到)。它与 `SANDBOX_PKG_SOURCE` 推导出的包源变量在装配处合并一次,**同名时名单赢**(部署方的刻意覆盖);
2450
- 适配器不把它写进 pod spec 的其它位置、不拼进执行命令串、不写进 `/workspace`,因而不随跨轮保留 / 挂起的快照进对象存储;不进事件日志(起服行只报键名)。
2476
+ - **值去哪**(7.137.0 起):只进**该 pod 自己的一只 Secret**(名 `sbx-env-<随机>`,键 `env.<变量名>`,`immutable`,`ownerReferences` 指向这只 pod —— pod 删了,
2477
+ 集群的垃圾回收把 Secret 一起删掉);pod spec 里的容器 env 只带 `valueFrom.secretKeyRef` 引用,**不带值**。容器起来之后 pod 里每个进程、每条 exec 命令照样看得到这些变量。
2478
+ 次序是先建 pod、再用 pod 的 `uid` 建 Secret(容器在 Secret 到位前不启动);Secret 建不成 ⇒ 这只 pod 被删、这次连接拒(Role 不许 = `auth_failed`,其余 = `connect_failed`,拒句不带值),
2479
+ **不回落**成把值写进 pod spec。它与 `SANDBOX_PKG_SOURCE` 推导出的包源变量在装配处分座一次,**同名时名单赢**(派生的那条不出现;部署方的刻意覆盖);
2480
+ 适配器不把值拼进执行命令串、不写进 `/workspace`,因而不随跨轮保留 / 挂起的快照进对象存储;不进事件日志(起服行只报键名)。
2451
2481
  pod 里的进程读得到这些值 —— 那正是转发的目的;进程自己若把值写进 `/workspace`,它就会随快照走。
2482
+ - **RBAC**:沙箱命名空间里服务账号要有 `secrets` 的 `create`(Role 规则 `- apiGroups: [""] resources: [secrets] verbs: [create]`;随包 `deploy/sema-up` 的 Role 已带)。
2483
+ 它恰是服务为 env Secret 真发的调用;不要给 `get`(服务从不读回,给了就读得到命名空间里每一只 Secret)与 `delete`(Secret 随属主 pod 由集群回收)。租约 Secret 的 `patch` 只在 §9.3h 的平面打开时要。
2484
+ 名单非空时起服各问一次 SelfSubjectAccessReview:答了「不许」⇒ **拒启**,拒句带 `[sandbox-secret-rbac]`、点名缺的动词与这条规则;问不到(API 连不上 / 审查本身被拒)⇒ 照常起服,
2485
+ 一行 `sandbox_secret_seat_unverified` + fail-open 计数 `server.sandbox-secret.seat-unverified` —— 那时若 Role 真缺,第一个要转发的 run 在连接时被拒、它的 pod 被删。
2486
+ - **放弃的 pod 删不掉(运维面,7.137.0 起)**:上面这类拒绝(env Secret 建不成 · 成员凭据代理的租约 Secret 建不成 · 代理镜像拉不下)要删那只刚建的 pod;这一删自己失败(请求抛 / API 回 403 或 5xx,比如 Role 缺 pods `delete`)时,
2487
+ 连接的拒答不变,服务多记一行 error `sandbox_pod_delete_failed`(`pod` / `namespace` / `why` / `cause`,不带值、不带 API 文本)+ fail-open 计数 `server.sandbox-pod.delete-failed`;pod 与它名下的 Secret 留给回收器(§9.3g)。
2488
+ 计数持续非零就按 `cause` 核 Role 的 pods `delete` 与 API 可达性。
2452
2489
  - **没设 = 零转发**:`K8S_SANDBOX_ENV` 未设 / 空串时,pod 与未引入本旋钮时逐字同形(env 只有包源那一组)。只在 `REMOTE_EXEC=k8s` 时求值。
2453
2490
  - **凭据的推荐用法**:`K8S_SANDBOX_ENV=GIT_TOKEN`,镜像里的 git 配成用环境变量取凭据(例如 `GIT_ASKPASS` 指向一个读 `$GIT_TOKEN` 的小脚本,或 credential helper),
2454
2491
  令牌就不进提示词、命令串和事件日志。
2455
2492
 
2456
- **风险模型(与 e2b / docker 同一档:运维信任形)**:值以明文写在 pod spec 里 —— 在该 namespace 有 `get pods` 权限的人、记录请求体的集群 API 审计日志、节点上能检视容器的人都读得到。
2457
- 所以给的是**最小权限**令牌(能只读就只读、限单仓、可吊销),并按 namespace 收紧读 pod 的 RBAC。本旋钮只是把值放进 pod;「凭证只留在适配器侧、永不进 pod」是另一种形,不是本键的语义。
2493
+ **风险模型(与 e2b / docker 同一档:运维信任形)**:值不再出现在 pod spec 里(`get pods` 读不到),但它在该 pod 的 Secret 里 —— 在该 namespace 有 `get secrets` 权限的人、
2494
+ 节点上能检视容器的人、以及集群 etcd(Secret 是否静态加密取决于集群配置,服务读不到)都读得到;记录请求体的 API 审计日志按其审计策略可能记下建 Secret 的请求体。
2495
+ 所以给的仍是**最小权限**令牌(能只读就只读、限单仓、可吊销),并按 namespace 收紧读 secrets 的 RBAC、给集群开 Secret 静态加密。本旋钮只是把值放进 pod;「凭证只留在适配器侧、永不进 pod」是另一种形,不是本键的语义。
2458
2496
 
2459
2497
  ### 9.3d k8s 车道:按工作区空间的构建缓存卷(7.126.0 起,缺省关)
2460
2498
 
@@ -2559,6 +2597,50 @@ e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时
2559
2597
  ssh / adb / local-docker 三条车道的流式调用同读这一条律:没有车道活性界,只受自己说的静默界 `readTimeoutMs` 约束,到点的切断句与 host 同一句
2560
2598
  (`no output for <N> s — cut off by the command's own idle bound (readTimeoutMs)`);`readTimeoutMs` 为 `0` / 负数 / 非数不算说了界。
2561
2599
 
2600
+ ### 9.3g k8s 车道:收不掉的沙箱 Pod 由服务删(reaper;7.137.0 起)
2601
+
2602
+ `activeDeadlineSeconds` 到点只让 pod 的容器终止(pod 进 `Failed`),**不删 pod 对象**;run 收尾时服务自己删 pod,而服务进程崩溃、或收尾那一删失败时,pod 对象(连同它的 Secret,§9.3c)就一直留着。
2603
+ 本版起每个副本在起服后的第一个维护 tick 跑一轮回收、此后每 5 分钟一轮,只删两种 pod(按身份标签 `app=sema-sandbox,managed-by=sema-server` 列本沙箱命名空间):
2604
+
2605
+ - **容器已全部结束**(`status.phase` 是 `Succeeded` / `Failed`)—— 删它不掐断任何东西;
2606
+ - **它的 run 已终**:顶层 run 的 pod 带标签 `sema.io/run=<run id>`;run 行在服务的 run 账本里已是终态(completed / failed / blocked),且自 run 行**转终态的那一刻**起超过
2607
+ `max(6 分钟, 工作区快照时限 + 15 秒)` —— 快照时限是**建这只 pod 的副本**的 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`(缺省 300 000 ⇒ 6 分钟;抬到 600 000 ⇒ 615 秒),因为 run 收尾在写完终态之后还可能在 pod 上存快照,至多这么久。
2608
+ 这个上界在建 pod 时算一次、写进 pod 的注解 `sema.io/run-end-hold-ms`(毫秒),回收器只读 pod 自带的这一格、不读自己副本的配置:滚动升级或改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时,每只 pod 按建它的副本的时限,
2609
+ 时限短的副本不会提前删时限长的副本还在存快照的 pod。带 run 标签的 pod 上注解缺席、不是十进制整数或不在 [360 000, 3 615 000] 里(不是本服务按配置写的数)⇒ 不按这一条删(跳过原因 `hold_unreadable`),容器结束后按上一条删;本版之前建的 pod 既没有 run 标签、也没有这个注解 —— 它们落在下面「不带 run 标签」一类(跳过原因 `no_run_label`),只在容器结束后删。
2610
+ 持久化开 / 关的部署同一条;没有单独的旋钮。
2611
+
2612
+ 还在跑的 run(以及停驻待决的 run)的 pod **从不**被它删,哪怕 pod 排队很久才起来;查不到 run 行、读不了 run 账本的 pod 只按第一条删。
2613
+ **不带 run 标签的 pod**(委派子代 / 后台代理 / 服务自己起的 run)**只在它的容器全部结束之后才删** —— 会话级后台代理刻意活过父 run 的那一轮,「会话里已没有在跑的 run」不等于「这只 pod 没人用」。
2614
+ 每轮一行 `sandbox_pod_reap_round`(`listed` / `scanned` / 按原因分的 `deleted` 与 `skipped` / `gone` / `deleteFailed`),每删一只一行 `sandbox_pod_reaped`(`pod` / `reason` / `run`),计数 `sandbox_pods_reaped_total{reason}`。
2615
+ **列举失败**(API 不可达 / 403)⇒ 这一轮一只都不删:一行 warn `sandbox_pod_reap_list_failed`(带原因)+ fail-open 计数 `server.sandbox-pod-reaper.list-failed`,下一轮照常再列 —— 计数持续非零就核 API 可达性与 Role 的 pods `list`。
2616
+ **某一只删失败**(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`:兜底本身删不动,残留会随轮次累积。
2617
+ 没有存储后端(查不了 run 行)的部署上本回收照跑:「容器已全部结束」那一条照删,带 run 标签的 pod 跳过(`run_store_absent`)。
2618
+ 用的是 Role 里既有的 pods `list` / `delete`。**运维可见的差别**:已结束的沙箱 pod 不再留着供事后 `kubectl logs` / `kubectl describe` 看;run 自己的日志面(事件流、账本)不受影响。没有旋钮。
2619
+ **两套部署共用一个沙箱命名空间**(缺省都是 `sandboxes`):回收器按身份标签 `app=sema-sandbox,managed-by=sema-server` 列 pod,标签里没有部署身份 ⇒ 一套部署会删另一套**容器已结束**的 pod
2620
+ (只有调用方把同一个 run id 交给两套部署时才会碰到对方的活 pod)。用 `K8S_NAMESPACE` 给每套部署各一个沙箱命名空间。
2621
+
2622
+ ### 9.3h k8s 车道:成员 git 凭据代理(7.137.0 起落组件;本版平面恒关)
2623
+
2624
+ 成员的 git 凭据只进**本人 run 的 pod**,而且不进 agent 容器:它进同一只 pod 里的第二只容器 —— 凭据代理(`containers[1]`)。agent 容器里的 git 经
2625
+ `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 交给代理;
2626
+ 代理按条目把请求转给 origin,只在条目的**租约**有效时加 `Authorization`。
2627
+
2628
+ - **代理只做 git smart-HTTP**:`/o/<i>/<owner>/<repo>/info/refs`(GET,`service=git-upload-pack|git-receive-pack`)与两个 pack 端点(POST);别的路径、带 `%` 的路径、别的方法 / 查询 / framing 一律 400,
2629
+ 上游收不到。重定向从不跟随;上游 401 改写成 git 自己的报错(`fatal: remote error: <句子>`),句子由服务写在条目上。只对 https 的 origin 附凭据;http 的 origin 照常路由(公开仓可用),永不带凭据。
2630
+ 上游每一等都有界(常量,无旋钮):连接(TCP + TLS 握手)10 秒;应答头自请求**发完**起 30 秒(大 push 上传中不受它限)。超时 ⇒ 502,正文带 `upstream_timeout` 与一句原因,上游连接被断。
2631
+ - **租约**:服务把租约写进该 pod 自己的一只 Secret(`sema.io/sandbox-lease`,只一个键 `broker.json`,属于这只 pod,随 pod 由集群回收);每个可用条目一份租约(`leaseId` + `ttlMs`,至多 5 分钟)。
2632
+ 代理按**自己的单调钟**从「第一次见到这个 `leaseId`」起算,到点对该条目所有已认证连接立即掐断(不做优雅关)、此后不再附认证;续用只有一种 —— 服务写新租约(新 `leaseId`)。
2633
+ **硬界是 pod 被删**(run 收尾删,或 §9.3g 的回收);「最后一份租约什么时候被投影进 pod」取决于 k8s,不是服务的承诺;上游已经收到的字节不在任何界内。
2634
+ - **pod 形**:只在**平面开着且该 run 的身份来源是 `sso`** 时挂代理;否则 pod 与不挂时逐字节同。代理容器的 `securityContext` 由适配器逐键写死(uid 0、只读根、丢全部能力、禁提权、`RuntimeDefault` seccomp),
2635
+ 租约卷只读、0400、只挂代理容器;`shareProcessNamespace:false`;连接要等代理容器就绪(exec 探针)才算成。部署的 pod 补丁在代理在场时只许 `volumes` / `volumeMounts` / `containerSecurityContext` / `labels`
2636
+ (容器级只落 agent 容器),**`podSecurityContext` 整只不许** —— 用了它的部署,平面关(`pod_patch_reaches_broker`)而不拒启;把 pod 级加固挪进 `containerSecurityContext`。
2637
+ - **平面开没开,看一处**:`GET /v1/capabilities` 的 `credentialPlane.sandboxGit`(`on` + 逐 origin `delivers`,或 `off` + 第一个不成立的前提词,九词闭集与判序见契约附录 B)。
2638
+ 起服时若是 k8s 多租户部署,日志一行 `credential_plane_sandbox_git`(`state` / `reason` / `why`)。
2639
+ - **旋钮**:`K8S_SANDBOX_BROKER_IMAGE`(代理容器的镜像;建议填本引擎自身镜像 —— 代理是本包里固定路径的程序;不设 ⇒ `broker_image_unset`)。Role 在 §9.3c 的 `create` 之外还要 `patch`(改写租约;租约 Secret 只用这两个;本版平面恒关,不用先给)。
2640
+ - ⚠️ **7.137.0 平面恒 `off`**:两处读口还没接线 —— 取值口的 worker 凭据、有效配置的 `sandboxGit` 块;读数停在 `no_caller_credential`(或更前的词),没有 pod 会挂上代理。
2641
+ run 行的身份来源不是接线点:读的是受理时记在 run 行上的那一份身份。本节写的是组件的行为与边界;
2642
+ 打开平面的那一版会在本节补上取值与续期。
2643
+
2562
2644
  ### 9.4 auto 模式(`permissionMode:"auto"`):武装三态、本地 org deny、自查(与 core 的意图武装式合修)
2563
2645
 
2564
2646
  **背景**:此前引擎只在「组织授予」(web-admin 解出的 per-principal `runtimeCaps.autoMode === true`)时武装
@@ -2783,7 +2865,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2783
2865
  - 被采纳的 `settings.hooks`(单用户车道)在引擎里是**一张**扩展注册,名字 `settings.hooks`(`wiring_manifest.mods[]` 里看得到它;
2784
2866
  审批卡 `allowRuleShadowed` 的 `hook` 成员、钩子失败时模型读到的拒句都点名它)。本部署自己另挂两张只观察的注册:恒挂的拒绝计量
2785
2867
  `server.permission-denied-meter`,`TOOL_TRACE=true` 时的 `server.tool-trace`(品牌部署另有一张提示装配站上的 `server.brand-identity`,只答品牌行 overlay,不在工具门上)。观察者的判词不采纳,链里唯一的答者是请求的钩子。
2786
- - 每个事件的语义与此前相同:`Stop` 条目在主任务结束与委派子代结束时都跑;`PermissionDenied` / `SubagentStop` / 两个 model-switch 事件属壳,服务端不跑。
2868
+ - 每个事件的语义与此前相同:`Stop` 条目在主任务结束与委派子代结束时都跑;`PermissionDenied` / `SubagentStart` / `SubagentStop` / 两个 model-switch 事件属壳,服务端不跑。
2787
2869
  - **`HOOKS_TIMEOUT_MS`** = 这张注册的 `budgetMs`。引擎对它的定义是 "Each handler's own time" —— 每个 handler 自己的时间(一个事件跑一遍本请求的钩子条目;
2788
2870
  引擎的钟,handler 等 `next` / `$` 调用的那段暂停),**不是**钩子子进程的墙钟。**未设 = 600000 ms**(= 单条命令超时的上限;引擎的钟在 handler 起跑时就开始走,
2789
2871
  命令的 `timeout` 钟要等 shell 解析和起进程之后,所以 `timeout` 配到 600 s 的命令跑满时,预算会先到);`0` 按字面(零预算)。超预算:`PreToolUse` 上该调用被拒(fail-closed,拒句点名 `extension "settings.hooks"`)、
@@ -2807,16 +2889,16 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2807
2889
 
2808
2890
  `mcpServers[].transport`(`kind:"http"`)上有两个「用哪个头送身份」的可选键,**只能设一个**:`principalHeader`(runner 把该任务已认证的终端用户 principal 注入这个头)与 `assertionHeader`(托管连接器断言所用的头**名**)。
2809
2891
  - 请求体(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)与部署声明(中心下发 / 本地 `config.d/mcp.json`)判据相同:两键同设 ⇒ 请求体新提交腿 `400 request.body_shape`(拒句点名条目与两键,与契约包解析期的拒句逐字相同);`assertionHeader` 不是字符串 ⇒ `400 request.field_invalid`;部署声明里同设的那一条不进。耐久 resume 腿重放存量请求体时不拒、也不摘 server:server 照挂,`principalHeader` 胜、`assertionHeader` 不生效,一行 warn `mcp_assertion_header_ignored`(`server` / `reason` / `note`)。
2810
- - `assertionHeader` 的**值**是头名,本服务不铸、也不校验具体词(只判是字符串且与 `principalHeader` 互斥);头名由引擎(上游)定为 **`connector-assertion`**(无 `x-` 前缀;引擎发送座落地后本服务只认这一个值),条目填别的名 ⇒ 连接器侧 401。
2811
- - 本版只承载、不发送断言:声明了 `assertionHeader` 的 server 照常挂载,但不带断言头;每台这样的 server 一行 info `mcp_assertion_header_not_sent`(`server` / `note`)。探测口 `POST /v1/capabilities/mcp/probe` 对声明了该键的条目整条拒 `400 request.field_invalid`(探测不拨一份少了断言头的配置)。
2812
- **发不发有一位可读**:`GET /v1/capabilities` 的 `mcpAssertionHeaderSent`(布尔,今天恒 `false`)—— 它与剥键、那行 info、探测口的那一拒读同一只开关,**互斥**:位 `false` ⇒ 三者都在(别指望断言头会发出去);位 `true` ⇒ 三者都不再出现、声明的头名随规格交给引擎。按位分支,不要匹配 info 文字;键缺席 = 老 server,按「不发送」读。
2892
+ - `assertionHeader` 的**值**是头名,本服务不铸、也不校验具体词(只判是字符串且与 `principalHeader` 互斥);引擎的断言头是固定的名 **`connector-assertion`**(无 `x-` 前缀),值由部署的对端座对 `assertion` 种类的引用作答 —— 本部署没有断言来源,座对这一种恒拒(9.5i),所以这里写的头名**不发送**(下一条)。
2893
+ - 本版只承载、不发送断言(对端座没有断言来源):声明了 `assertionHeader` 的 server 照常挂载,但不带断言头;每台这样的 server 一行 info `mcp_assertion_header_not_sent`(`server` / `note`)。探测口 `POST /v1/capabilities/mcp/probe` 对声明了该键的条目整条拒 `400 request.field_invalid`(探测不拨一份少了断言头的配置)。
2894
+ **发不发有一位可读**:`GET /v1/capabilities` 的 `mcpAssertionHeaderSent`(布尔,今天恒 `false`)—— 它与剥键、那行 info、探测口的那一拒读同一只开关,**互斥**:位 `false` ⇒ 三者都在(别指望断言头会发出去);位 `true`(座有了断言来源之后)⇒ 三者都不再出现、声明的头名随规格交给引擎。按位分支,不要匹配 info 文字;键缺席 = 老 server,按「不发送」读。
2813
2895
 
2814
2896
  ### 9.5e 请求体 `mcpServers` 的列表与条目键:写坏,整份新提交 400(7.131.0 起;列表级与词表外的键 7.132.0 起;`startupTimeoutMs` 与 `toolFaces` 的引擎判官 7.134.0 起;`transport.url` 带凭据 7.135.0 起)
2815
2897
 
2816
- 条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))。**新提交上每个键只有一只判官,判官在受理口,写坏的答案只有一种**(下面第一条:400)。`startupTimeoutMs`(这一只 server 自己的启动界,毫秒整数,范围与引擎 env 旋钮 `MCP_TIMEOUT` 同一段)7.134.0 起同样在受理口判 —— 判官是引擎包根导出的 `mcpStartupTimeoutMs`(启动界的唯一读者):数字串 / 小数 / 越界 / `null` ⇒ `400 request.field_invalid`,原因句是引擎的那一句(7.132.0–7.133.x:受理口不判、请求照常 200,引擎拨号时只拒这一只 server);耐久 resume 重放存量请求不判(原样交引擎,坏值在拨号时只拒这一只 server —— `config.mcp_startup_timeout_invalid`,兄弟照挂)。
2898
+ 条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)· `sessionMetaKey`(7.138.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))。**新提交上每个键只有一只判官,判官在受理口,写坏的答案只有一种**(下面第一条:400)。`startupTimeoutMs`(这一只 server 自己的启动界,毫秒整数,范围与引擎 env 旋钮 `MCP_TIMEOUT` 同一段)7.134.0 起同样在受理口判 —— 判官是引擎包根导出的 `mcpStartupTimeoutMs`(启动界的唯一读者):数字串 / 小数 / 越界 / `null` ⇒ `400 request.field_invalid`,原因句是引擎的那一句(7.132.0–7.133.x:受理口不判、请求照常 200,引擎拨号时只拒这一只 server);耐久 resume 重放存量请求不判(原样交引擎,坏值在拨号时只拒这一只 server —— `config.mcp_startup_timeout_invalid`,兄弟照挂)。`sessionMetaKey`(声明了它的服务器在每次 `tools/call` 的 `_meta` 下收这条腿的会话 id)7.138.0 起同样在受理口判 —— 判官是引擎包根导出的 `judgeSessionMetaKey`(键名格式、保留前缀、上界都是它的):坏形 ⇒ `400 request.field_invalid`,原因句是引擎的那一句;耐久 resume 重放存量请求时坏形让这一条 server 整条不进。
2817
2899
  - **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`):键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `mcpServers[2].allowTools (server "crm") is not a usable declaration — must be an array of strings (omit the key to leave the server's tools unrestricted) — got a string of 4 characters. …`:点名 `mcpServers[i].<键路径>`、server 名与该键判官的原因句,**整份请求不收** —— 不挂一半、不丢声明、不默认。原因句不回显声明的值,有几处例外,回显只有一只口径(剥控制与双向字符、截到 64 字、超出加 `…`):server 名(含写坏的 `name` 值,另报送来的原长度)、`contentOrigin` 词表外的值;`toolAxes` / `toolFaces` 的原因句是引擎写的(7.134.0 起 `toolFaces` 的判官也是引擎包根导出的 `readToolFaces`):点名工具名(引擎截 80 字)与成员,`toolFaces` 还会点名闭词表拒的那个词,整句详情由引擎截到 240 字、本服务再剥控制与双向字符 —— 不回显显示文本、不回显合规的列表;`transport.url` 的值从不回显(带凭据的 url 被拒时,原因句只点名命中的那一段与凭据名表里的那个词)。可选键**缺席**永远合法(真「没声明」),必填的 `name` / `transport` 缺席同样 400;在场的 `null` 不是缺席。与单用户 / 多租户无关。列表本身同一条律(7.132.0 起,先于条目判):`mcpServers` 在场却不是数组、超过 32 条(整份拒,不挂前 32 条)、某一条不是对象 ⇒ `400 request.field_invalid`,拒句点名 `mcpServers` 或 `mcpServers[i]` 与形的种类;条目层或 `transport` 层出现词表外的键 ⇒ `400 request.body_shape`,拒体带 `unknownKeys`(全路径、至多 20 条)。`mcpServers` 缺席或为 `null` = 没声明,照常受理。
2818
2900
  - **`transport.url` 不得带凭据(7.135.0 起)**:http 传输的 `url` 带 userinfo(`https://user:pass@host/mcp`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,按解一次百分号编码读)⇒ 新提交 `400 request.field_invalid`,体另带 `field: "mcpServers[i].transport.url"` 与 `reason`(`url_userinfo` | `url_credential_param`),原因句点名出路:**凭据改写进 `transport.headers`**(如 `"Authorization": "Bearer …"`)。为什么:任务停驻时提交体整份进续跑上下文,`transport.headers` 的值在那里只以封签存在,`url` 照原样存(日志与读面同理)。值从不回显;解析不了的 url 不在这一问里判(拨号时失败)。7.134.x 及以前照收、原样落停驻上下文;带 userinfo 的 url 本就连不上(fetch 对带凭据的 URL 构造即抛),带名表参数的升级后要改。同一条规则在 A2A 对端 / http 钩子 / 搜索后端 / 图片 URL 上的几座见 9.5g。
2819
- - **耐久 resume 重放存量请求**:不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` · `transport` 的 `args`/`principalHeader` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `env`/`headers`(名 → 串记录)对 JSON 存体按 7.131.x 的读法交:有值不是串的成员 ⇒ 整键不交,只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交;server 照挂,一行运维 warn `mcp_transport_members_dropped` 点名(见 9.5c-bis 同段);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
2901
+ - **耐久 resume 重放存量请求**:不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` · `sessionMetaKey` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` · `transport` 的 `args`/`principalHeader` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `env`/`headers`(名 → 串记录)对 JSON 存体按 7.131.x 的读法交:有值不是串的成员 ⇒ 整键不交,只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交;server 照挂,一行运维 warn `mcp_transport_members_dropped` 点名(见 9.5c-bis 同段);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
2820
2902
  - `POST /v1/capabilities/mcp/probe` 问的是新提交那**同一只判官**:上面每一形同码同句(含 `unknownKeys`,与 url 带凭据那一形的 `field` / `reason`),拒之前零拨号;判官受理的申报,探测口拨的就是一条任务提交会交给引擎的那一份(判官的合法归一照收)。它多出的两形:本口的体就是这份列表,`mcpServers` 缺席或为 `null` ⇒ `400 request.body_shape`(缺必需字段);声明了 `transport.assertionHeader` 的条目 ⇒ `400 request.field_invalid`(见 9.5d:本版承载、不发送,新提交照挂,探测不拨一份少了断言头的配置 —— 同一只谓词,后果按门分)。7.131.x 上本口另有一只判法(申报原文与读器产物逐字比),列表不是数组 / 条目不是对象答 `request.body_shape`。
2821
2903
  - **`toolFaces` 的判官是引擎的 `readToolFaces`(7.134.0 起;此前本服务自带一只只判 JSON 形的读器)**:它在门口就判声明本身能说的一切 —— 成员与类型、五张闭词表、上界、跨成员律 ——
2822
2904
  所以下面几形 7.133.x 受理、7.134.0 起 400(重放腿整条 server 不进、中心 / `config.d` 腿跳过该 server 并点名 warn):空工具名;保留审批卡(`renderHints.approvalCard: "question"` / `"plan"`);
@@ -2878,7 +2960,33 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
2878
2960
  - 判序:先列表、后条目;条目里先词表外的键、后键值(`name` → `url` → `cardUrl` → `headers` → `principalHeader` → `allowSkills` → `toolAxes`),第一处坏形即答。纯值域、与租户车道无关(多租户部署整只 `a2aPeers` 本会被忽略,告诉调用方「这样写不行」只多不少)。
2879
2961
  - **耐久 resume 重放存量请求**不拒(拒会把升级前 park 的任务永久卡死):写坏的条目不挂、运维 warn `a2a_injection_dropped` 点名 —— `dropped` 的每个条目是 `{ peer, reason, field }`:`reason` 是上面判官对那个键的原因句(与受理口 400 拒句里的同一句,不回显值;只有一臂例外:一次性迁移的重读入参克隆不了 —— 访问器成员 / BigInt / Proxy / 环 / 非纯数据 —— 时带前缀 `declaration not cloneable (<because>) — the one-time migration re-reads the stored declaration as plain JSON data and this one is not; `,**其后**才是判官的那一句),`field` 是 `a2aPeers[i].<键路径>`(与 MCP 面 `mcp_injection_dropped` 的条目同键名,名字键 `server` ↔ `peer` 除外);整只不是数组 ⇒ 整只忽略 + `a2a_injection_ignored`。
2880
2962
  - 7.135.0 及以前:这些形全部 200 —— 坏形的对端悄悄不挂(只在运维 warn 里点名)、词表外的键悄悄不读(`allowSkills` 拼成 `allowSkill` 的对端照样挂上远端卡声明的全部技能)。升级后这些请求变 400:改对那个键(或删掉它)、把列表改成数组、删掉第 33 条起的条目。
2881
- - A2A 没有运维锁、没有合规能力位:引擎的锁键与合规能力两个闭集里都没有 A2A 的词,所以 `LOCKED_CONFIG_KEYS` 写不进 `a2a`、合规档位也禁不了 A2A(7.135.0 及以前的代码里有一条永远不触发的「锁了就整拒」分支,本版删掉)。`GET /v1/capabilities` 的 `a2aInjection` 只回答「单用户」这一问。
2963
+ - A2A 的运维锁与合规位(引擎 7.59.0 起在册):`LOCKED_CONFIG_KEYS` 可写 `a2a`、合规档位可禁 `a2a_peers`(`COMPLIANCE_ADDITIONAL_DENIES` 的闭集多这一词)。请求自带的 `a2aPeers` 与 `mcpServers` 同一条合取才兑现:单用户 ∧ `a2a` 未锁 ∧ 合规档位不禁 `a2a_peers`,`GET /v1/capabilities` 的 `a2aInjection` 为这条合取的值。否决的后果按种类分:锁了 `a2a` 而请求仍带 `a2aPeers` ⇒ 同步 `400 config.locked_key`(与 `mcpServers` 的锁同形;续跑重放腿折成 `409 resume_blocked_by_policy`);没锁 `a2a` 而多租户或合规档位禁了 `a2a_peers` ⇒ 忽略(请求照常 200,运维 warn `a2a_injection_ignored`);锁先判,与是否多租户 / 合规禁项无关。条目与列表的形判(本节上面各条)与这道闸无关,照拒。
2964
+
2965
+ ### 9.5i MCP / A2A 的凭据件只经部署的对端座作答(7.138.0 起)
2966
+
2967
+ 对端的「件」—— http MCP 请求的凭据头(`mcpServers[].transport.headers`、名册的 `headerRefs`)、stdio MCP 子进程的环境变量(`transport.env`、名册的 `envRefs`)、A2A 发送的头(`a2aPeers[].headers`、名册的 `headerRefs`)——
2968
+ 本版起**不再写进交给引擎的声明**:本服务的对端座在起子进程 / 发请求的那一刻按原字节作答,声明里只剩一只不透明引用。**请求体的形不变**(仍收 `headers` / `env`,不收引用位;`credentials` 是词表外的键 ⇒ `400 request.body_shape`),
2969
+ 名册声明的写法不变(`envRefs` / `headerRefs` 仍在起服时从本进程环境读一次,缺任一变量整条跳过;改变量要重启)。契约 §0.10 成文了同一件事的 wire 面。
2970
+
2971
+ **谁需要读这一节**:用 `.mcp.json` 带凭据头 / 子进程环境的单用户壳,带 `a2aPeers[].headers` 的调用方,名册里用 `envRefs` / `headerRefs` 的部署,自建 `createHttpServer` 的嵌入方(MIGRATION 7.138.0 节第 24 条)。
2972
+
2973
+ - **件的形由引擎在作答处判,比此前严**。下列形读器拒 —— 那一台 `credential_unavailable`(提交照收、兄弟照挂、运行期才见):stdio 件名不是标识符形(含 `.` / `-`,如 `MY-VAR`)· 任一件值含 CR / LF / NUL(多行 PEM、Secret 文件的尾换行;http 头值首尾的换行此前被发送时的头规范化吞掉,现被拒)·
2974
+ http / A2A 头名不是 HTTP token · 两个头名只差大小写 · MCP http 的件头名是传输自己写的头(`content-type` · `accept` · `mcp-session-id` · `mcp-protocol-version` · `last-event-id`;A2A 没有这张表)·
2975
+ stdio 的秘密形件(变量名被引擎认作凭据名)的值恰等于一只每个子进程都会继承的变量(`PATH` / `HOME` 一类)的值。
2976
+ - **在哪看见**:MCP 的连接期 `wiring_manifest.mcp[]` 行(`status:"failed"`、`errorCode:"credential_unavailable"`、`delivered:"no"`)与调用期 `tool_end.errorCode`;`GET /v1/sessions/:id/mcp` 面板的那一台 `failed`。A2A 这一侧只有文本:会话 A2A 面板 `peers[].cardFetched:false` + `error`,run 内是工具结果的错误文本(没有机读码)。
2977
+ 运维日志:本服务的座自己拒时(引用不在表里 / 绑定不符 / 引用种类座不答)一行 warn `peer_seat_refused { protocol, peer, reason }`(`reason` ∈ `handle_not_held` · `binding_mismatch` · `kind_env` · `kind_vault` · `kind_assertion` · `kind_unknown`),不带件值、不带引用 id;因件的形被引擎读器拒的那一台没有这一行,看上面的码 / 文本。
2978
+ - **扣留次序**:引擎把座答里认作凭据的 http 头值与 stdio 秘密形件值记为自己持有的凭据值 —— 服务进程环境里同值的变量不再交给模型可驱动的 host shell(Bash)子进程(MCP stdio 子进程只继承 MCP SDK 的少数默认变量〔`HOME` · `LOGNAME` · `PATH` · `SHELL` · `TERM` · `USER`〕与座给的件,拿不到服务进程环境的其余变量)。座的答案先过读器、放行了才记:
2979
+ 被读器拒的形(件值含 CR / LF / NUL、头名不是 HTTP token、两个头名只差大小写、变量名不是标识符形)的值**不进扣留表**,那一台失败而环境里同值的变量照旧交给 host shell 子进程(上一发布版先记后判);MCP 传输自己写的头名在读器之后的协议层才拒,值此时已在表里。名册 `headerRefs` 指向的变量值带尾换行时尤其要去掉那个换行。
2980
+ - **`/metrics` 两只读数**:`peer_registry_entries`(登记表现有条目数)· `peer_registry_holders`(各条目的宿主租约数之和 —— 条目的持有者只有宿主租约;一只租约持有三个条目记三次)。没有过期、没有上限;在飞腿全落定后两只回到名册基线(名册里带件条目的条数)。基线之上持续不回落 = 有作用域没放。
2981
+ - **`principalHeader` 的名**:MCP 条目 `transport.principalHeader` 取传输自己写的头名(上面五个,任何大小写)或引擎的断言头 `connector-assertion`,A2A 条目 `principalHeader` 取 `connector-assertion` ⇒ **新提交 `400 request.field_invalid`**
2982
+ (点名 `mcpServers[i].transport.principalHeader` / `a2aPeers[i].principalHeader`,不回显写的串)。耐久 resume 重放存量体不 4xx:MCP 那一台由引擎在拨号时拒,A2A 那一条对端整条不进(warn `a2a_injection_dropped`)。
2983
+ - **断言头不发送**:引擎的断言头是固定的名 `connector-assertion`、值由对端座作答;本部署没有断言来源,座对这一种恒拒(9.5d)。这说的是 `assertionHeader` 声明本身不产生断言头:调用方自己写在 `headers` 里、名字恰为 `connector-assertion` 的头仍按普通件处理(引擎只在它作 `principalHeader` 时保留这个名)。
2984
+ - **探测口与面板**:`POST /v1/capabilities/mcp/probe` 带 `headers` / `env` 的申报与任务提交走同一条路(件在这次走查的租约下登记,拨号落地即放;同样的件形判;缓存键含件);`GET /v1/capabilities/mcp` 与两张会话面板拨名册声明,递同一只座。
2985
+ - **补偿**:多行值改 base64 或改传文件路径,Secret 文件去掉尾换行;变量名改标识符形;件头名改非协议名;`principalHeader` 换成非协议、非引擎自有的名(例 `x-agent-principal`);秘密形变量别设成与 `PATH` / `HOME` 之类同值。
2986
+
2987
+ **已知缺口(本版披露不修):会话级子代在父腿作用域结束之后首拨 http / stdio、或再发 http 会被座拒。** 引擎持有引用的计数那一半本版不在,座只认宿主租约:一个会话级(后台)子代继承父的、请求体自带的 http / stdio MCP 声明,在父腿结束之后再问 ⇒ 该次发送 / 那一台的挂载 `credential_unavailable`
2988
+ (响亮拒:一行不带值的 `peer_seat_refused`,`reason: handle_not_held`;http 在首次连接与每次发送时问座,stdio 在起子进程时问座 —— 父腿作用域结束之后才首拨的 stdio 子代同样被拒);父腿很快收尾时,子代连自己的挂载都会被座拒。同一腿内(含 cascade 各级、同步腿)、名册声明的服务器(名册件由进程寿命的名册租约持有)与父腿作用域之内已起的 stdio 子进程不受影响;A2A 没有这个形(委派子代不继承父的 A2A 对端)。
2989
+ 补偿:请求体带件 http / stdio MCP 的任务别依赖「父腿先结束、子代后调用」,让父腿等子代收尾,或把这类服务器改成名册声明;缺口在引擎持有那一半到货后翻绿。
2882
2990
 
2883
2991
  ### 9.6 trace 脱敏的射程:与出站请求体无关
2884
2992
 
@@ -3107,7 +3215,9 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3107
3215
  (安装树 `node_modules/@sema-agent/core` 不再接受)。命中的处置写进**随版认领表** `docs/core-consumption/ACK-core@<目标>.json`(维护者文件,不随包):
3108
3216
  门按 `section` + `key` 整串全等逐键放行被人核过的命中,条目的 `where` 须把这一命中现在的读点列全,读数行 `acked=` 如实带出放行条数;
3109
3217
  表里有而读数里没有的条目、读点变了的条目按过期红(`stale=`),表自报的版本或区间下界与本次不符、形坏都是 exit 2;
3110
- 没有通配、没有全局放行旗标,放行面只此一张表(细则在门脚本头注)。
3218
+ 没有通配、没有全局放行旗标,放行面只此一张表(细则在门脚本头注)。一个发布版本的区间里提了不止一次货时,每次提货各带一份认领表、**首尾相接**:目标那一份的 `from` 指向前一份的版本,
3219
+ 最前一份的 `from` 是区间下界;门沿 `from` 从目标那一份逐份往下读,命中按它来自的 core 版本归到那一段的那一份认领,中间缺一份 ⇒ exit 2 点名缺的文件与那一段(缺段 = 这一段的命中谁也没核过);
3220
+ 不合并成一份、也不手改 `from`(每一份都是当时人核过的陈述)。
3111
3221
  - **core RC 探针**(维护者裁定,2026-09-28):core 出 RC tarball 时直讯本仓(`npm pack` 产物路径 + shasum + `wire-changes/core@<ver>.json` 的 commit),
3112
3222
  当拍跑:
3113
3223
  ```bash
@@ -3182,13 +3292,16 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3182
3292
  十一键老形、手跑的 `chain=-` 行都不是本轮的 —— 没等到本轮的行 ⇒ 退出码 1 并说没出现,不转述别轮的读数。手跑 `release.sh` / `post-publish-gates.sh` 不给标识 = `chain=-`。之后转述门 3.5 的 `PUBLISH` 行;release.sh 红而 tag 已在 ⇒ 立即收尾并指路 `publish-local.sh` + `post-publish-gates.sh`;
3183
3293
  绿 ⇒ 等 `GATES456`(窗 = `GATE4_WINDOW_SEC` + 20 min)后转述 `GATE2` / `PUBLISH` / 判决行。退出码见脚本头注(0 = 全绿)。
3184
3294
  - **wire 变更机器清单**(7.105.0 起):`node scripts/wire-changes-server.mjs <version>` 生成 `wire-changes/server@<version>.json`
3185
- (形照 core 的 `wire-changes/core@<ver>.json`:`added` / `removed` / `changed` / `narrowed` + `closedSetMembers` + `breaking`)。源 = 契约附录
3295
+ (形照 core 的 `wire-changes/core@<ver>.json`:`added` / `removed` / `changed` / `narrowed` + `closedSetMembers` + `breaking`;另有 `registered` ——
3296
+ 早于本版就在 wire 上、本版只是补登记的成员 —— 与 7.138.0 起的 `excluded` 桶〔见下〕)。源 = 契约附录
3186
3297
  A.1 ∪ A.1b 错误码(属性 = 状态码)、B.1 能力位 / B.2 `taskSettings` 子键(属性 = 谓词类)、D.3 `engine_notice` 白名单(属性 = `detail`)、
3187
3298
  `src/trace/wire-projection-faces.ts` 的 `WIRE_PROJECTION_FACES`;对比上一 tag 与本版(tag 在读 tag,不在读工作树)。源读不懂 ⇒ exit 2
3188
3299
  (不产出「零变更」清单)。面表里带 `keys` 声明的面(7.133.0 起:`prompt_assembled` 清单行的十二层,账本行 / turns 块 / trace 帧同一形)
3189
3300
  做**键级**比较,与面的增删用同一张表、同一只比较器:删一个键或改了键的值形 / 容器 / 嵌套面 / 由必选变可选 ⇒ `removed` / `narrowed`(`breaking`),
3190
3301
  新增键 ⇒ `added`,由可选变必选 ⇒ `changed`;该面此前没有键声明(首次登记)时既有的键进 `registered`,不当本版新上 wire。其余帧面的键集仍由 `coreType`
3191
- 在安装态 core 里展开,生成器看不见它们的键级增删(core 升版带来的键变化由提货批的读面 × core 清单求交门与各面的 `_Guard*` 编译钉盯)。随 release commit 提交;查账门(门 1.6)第三腿 `--check` 核它在仓且与生成输出逐字节同,缺席 / 陈旧 ⇒ 红。
3302
+ 在安装态 core 里展开,生成器看不见它们的键级删改(由提货批的读面 × core 清单求交门与各面的 `_Guard*` 编译钉盯);**core 新长出来的顶层键**(7.138.0 起)改读 core 的随包清单
3303
+ (`node_modules/@sema-agent/core/wire-changes/core@<ver>.json` 的 `typeKeys[].added`,区间 = 上一 tag 的 core 钉到本版的钉):TaskEvent 臂与裸型名面上的新键没登记进 `excluded` ⇒ `added`(默认上 wire);
3304
+ 登记进了 `excluded` ⇒ 新的 `excluded` 桶(这个键从未上过 wire,不是收窄,不计 `breaking`);此前被记成 `narrowed` 的就是这一类。嵌套在已有顶层键之下的新成员不在这一源的射程,由自铸登记表登记。随 release commit 提交;查账门(门 1.6)第三腿 `--check` 核它在仓且与生成输出逐字节同,缺席 / 陈旧 ⇒ 红。
3192
3305
  `wire-changes/server@7.104.0.json` 是回填的第一份。7.136.0 起 `wire-changes/` 随 npm 包出:装包的人在包内 `wire-changes/server@<ver>.json` 就能读到每一版的清单(生成时清单里的 `text` 过发布面内部编号词表,命中的编号换成 `[ref]`;`sources` 里点名的契约文档不随包)。
3193
3306
  - **台账 `refs:` 列建议**:`node scripts/ledger-refs.mjs [--comms-dir <dir>] [--json]` 只读 sema-comms 的 `debts/DEBTS-server.md` /
3194
3307
  `PLANS.md`(server 段)/ `bugs/B-*.md`(文件头 `owner:` 格),按「一方点了另一方的号」双向互链,输出每行的 `refs:` 建议值(号按数值认,
@@ -3339,14 +3452,16 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
3339
3452
  **协议命名空间的覆盖形(7.135.0 起有能力位)。** 条目写协议命名空间 —— 整对端 `mcp__srv` / `a2a__peer`、工具段通配 `mcp__srv__*`、工具段 glob `mcp__srv__get_*` —— 写口收(`200`),引擎把它们读成**一组工具**:
3340
3453
  `toolDeny` 里拒这一组,`toolAllow` 里只放这一组。要不要放开对这几形的扣下,读 `GET /v1/capabilities` 的顶层布尔 `sessionRulesNamespaceForms`(不按版本号猜):`true` = 引擎协议表上每个命名空间的这三形写口真收、
3341
3454
  引擎真认成一组;键缺席(≤7.134.x)或 `false` ⇒ 当不放宽。它与 `sessionPolicy`(这台有没有会话规则写面)正交 —— 没有会话规则店的部署上它也是 `true`,所以条件是两位同真。位只说命名空间覆盖形:
3342
- `*`、对端段的部分通配(`mcp__git*`)、尾分隔(`mcp__srv__`)照旧 `400 rules.inert_entry`(下),`mcp__*` 只在 `toolDeny` 收。
3455
+ `toolAllow` 里的 `*`、对端段的部分通配(`mcp__git*`)、尾分隔(`mcp__srv__`)照旧 `400 rules.inert_entry`(下),`mcp__*` 只在 `toolDeny` 收。
3456
+ **`toolDeny` 里带 `*` 的工具名是整名 glob(7.137.0 起,引擎会话规则文法 5)**:`*` 拒每一只工具,`mcp__git*` 拒名字以 `mcp__git` 起头的每一只工具(`mcp__github__…`、`mcp__gitlab__…`),
3457
+ `Bas*` 拒 `Bash`;写口收(`200`),行盖文法 5 的戳。`toolAllow` 不变:`*` 只在协议名的工具段里读(`mcp__srv__get_*`),别处的 `*` 不指任何范围、照旧拒。尾分隔 `mcp__srv__` 两只桶都照旧拒。
3343
3458
 
3344
3459
  **两只拒码**(判官是引擎的那一只,两条写口与店里的拒同码、同一份拒体;同步导入的写口在句首加记录坐标 `policy: [i].rules: `;店里一个字节都不落):
3345
3460
 
3346
3461
  - `400 rules.invalid_shape` —— 规则体形不对:`rules` 缺席或不是对象、有一个不是桶的键(含退役的命令桶,**不会被静默忽略**)、一只桶不是一列字符串、`allowDirs` 里有空目录。
3347
3462
  体顶层 `field`(哪个键;整体不是对象时是空串)与 `got`(那个值的类型,不回显值),`error` 是引擎的句子。
3348
- - `400 rules.inert_entry` —— 一条条目读成「什么都不是」,写进去什么都不拒 / 不许:退役的工具名、带 `__` 却没有协议前缀的名、协议名里铸不出的段(含对端段里的部分通配 `mcp__git*`)、`*`、
3349
- `toolAllow` 里的整协议名 `mcp__*`(7.135.0 起 `toolDeny` 里的 `mcp__*` / `mcp__*__*` 是一条规则:拒每一台 MCP 服务器的每一只工具)、工具没有的规则形
3463
+ - `400 rules.inert_entry` —— 一条条目读成「什么都不是」,写进去什么都不拒 / 不许:退役的工具名、带 `__` 却没有协议前缀的名、协议名里铸不出的段(尾分隔 `mcp__srv__`;`toolAllow` 里对端段的部分通配 `mcp__git*`)、
3464
+ `toolAllow` 里的 `*` 与整协议名 `mcp__*`(7.135.0 起 `toolDeny` 里的 `mcp__*` / `mcp__*__*` 是一条规则:拒每一台 MCP 服务器的每一只工具;7.137.0 起 `toolDeny` 里任何带 `*` 的名都是整名 glob,不在本码)、工具没有的规则形
3350
3465
  (`Monitor(touch:*)`)、文法不成的写法(空串、`Bash(`)。体顶层 `field` / `entry` / `reason`,外加 `verdict` / `currentName` / `guidance`(有就带)。按 `reason` 改写那一条再写。
3351
3466
 
3352
3467
  一条什么都不拒的 deny 已经在库里时,会话属主自己删得掉(删它不算放宽)。
@@ -3356,8 +3471,8 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
3356
3471
  下一次工具调用起生效;收不回 operator 写下的那一部分(两边都写过的条目也算):`403 loosen_forbidden`,句子 `a non-operator session-rule write may not loosen what an operator wrote; …`
3357
3472
  (7.134.x 及以前的句子是 `… may only tighten …`,按句子匹配的脚本改读码)。operator 写一次就把整行变成 operator 的。`operatorRules` 由引擎铸,客户端改规则时**不要**回传它。
3358
3473
 
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` 与同步导出的
3474
+ **行自带文法戳。** 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.x 写下的行是 `5`,钉引擎 7.57.0 起写下的行是 `6`(裸工具名成为整工具规则);
3475
+ 早先写下的行保留自己的戳、引擎按它的戳读;店整只存、读回原样交出;7.134.x 及更早的引擎读不了戳 3 / 4 的行、7.136.x 及更早的读不了戳 5 的行、7.137.x 及更早的读不了戳 6 的行 —— 整行拒、那条会话每一次调用都拒,升级序见 MIGRATION(先升读者、再升写者))—— `GET` / `PUT` 回包的 `rules` 与同步导出的
3361
3476
  记录里多这一个键。客户端改规则时只写三只桶,不要把读回的 `rev` / `gen` / `grammar` / `operatorRules` 一起回传:带着 `rev` 却没有 `grammar` 的值引擎按存量行的文法(0)读,括号形 allow 条目答
3362
3477
  `400 rules.inert_entry`(`reason: "earlier_grammar"`;出路在句子里);`grammar` 坏或比引擎新 ⇒ `400 rules.invalid_shape`(`field: "grammar"`)。
3363
3478