@sema-agent/server 7.147.0 → 7.149.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 (790) hide show
  1. package/MIGRATION.md +1468 -24
  2. package/README.md +35 -7
  3. package/README.zh-CN.md +18 -8
  4. package/USAGE.md +444 -193
  5. package/deploy/sema-up/README.md +16 -5
  6. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  7. package/deploy/sema-up/chart/templates/_helpers.tpl +20 -4
  8. package/deploy/sema-up/chart/values.yaml +3 -2
  9. package/deploy/sema-up/cluster-up.sh +74 -176
  10. package/deploy/sema-up/kube-up.sh +29 -49
  11. package/deploy/sema-up/lib/chart-run.sh +23 -0
  12. package/deploy/sema-up/lib/dataroot.sh +3 -3
  13. package/deploy/sema-up/lib/health.sh +62 -13
  14. package/deploy/sema-up/lib/helm-values-cluster.py +18 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +35 -0
  16. package/deploy/sema-up/lib/regcred.py +8 -0
  17. package/deploy/sema-up/lib/remote/helm-install.sh +3 -0
  18. package/deploy/sema-up/lib/remote/image-import.sh +6 -0
  19. package/deploy/sema-up/lib/remote/machine-precheck.sh +11 -0
  20. package/deploy/sema-up/lib/remote/region-probe.sh +3 -0
  21. package/deploy/sema-up/lib/remote/stack-ready.sh +26 -0
  22. package/deploy/sema-up/lib/remote/vxlan-guard.sh +8 -0
  23. package/deploy/sema-up/lib/render-stack.py +17 -0
  24. package/deploy/sema-up/lib/shell-text.sh +87 -0
  25. package/deploy/sema-up/lib/usage-flags.txt +10 -0
  26. package/deploy/sema-up/lib/usage-head.txt +1 -0
  27. package/deploy/sema-up/lib/usage-tail.txt +13 -0
  28. package/deploy/sema-up/preflight.sh +10 -8
  29. package/deploy/sema-up/sema-up.sh +14 -39
  30. package/deploy/sema-up/smoke.sh +7 -6
  31. package/deploy/sema-up/stack/compose.dataroot.yaml +3 -0
  32. package/deploy/sema-up/stack/compose.yaml +13 -1
  33. package/deploy/sema-up/stack/sema-stack-cluster.yaml +21 -0
  34. package/dist/adoption/plan.d.ts +24 -0
  35. package/dist/adoption/plan.js +53 -1
  36. package/dist/adoption/runner.d.ts +10 -1
  37. package/dist/adoption/runner.js +36 -16
  38. package/dist/approval-card.d.ts +36 -38
  39. package/dist/approval-gate-currency.js +3 -2
  40. package/dist/approval-leg-origin.d.ts +33 -0
  41. package/dist/approval-leg-origin.js +4 -0
  42. package/dist/approval-reconciler.d.ts +4 -13
  43. package/dist/approval-reconciler.js +2 -2
  44. package/dist/approval.d.ts +13 -11
  45. package/dist/approval.js +3 -3
  46. package/dist/audit.d.ts +10 -3
  47. package/dist/audit.js +15 -7
  48. package/dist/auth-bridge.d.ts +9 -15
  49. package/dist/auth-bridge.js +4 -11
  50. package/dist/auth-keys.d.ts +10 -1
  51. package/dist/auth-keys.js +8 -0
  52. package/dist/auto-mode-face.d.ts +10 -7
  53. package/dist/auto-mode-face.js +7 -4
  54. package/dist/background-agent-name.d.ts +19 -1
  55. package/dist/background-agent-name.js +12 -2
  56. package/dist/background-session-reap.d.ts +1 -1
  57. package/dist/bake-runner/main.js +9 -6
  58. package/dist/bench/s1/arms.js +6 -5
  59. package/dist/bench/s1/live-deps.js +5 -2
  60. package/dist/bench/s1/run-firm.js +6 -5
  61. package/dist/bind-host.d.ts +25 -0
  62. package/dist/bind-host.js +25 -0
  63. package/dist/boot/adoption.d.ts +3 -0
  64. package/dist/boot/adoption.js +1 -0
  65. package/dist/boot/deployment-mods.d.ts +1 -1
  66. package/dist/boot/execution-env.d.ts +17 -7
  67. package/dist/boot/execution-env.js +47 -42
  68. package/dist/boot/execution-lane.d.ts +6 -4
  69. package/dist/boot/execution-lane.js +3 -1
  70. package/dist/boot/exit-boundary.d.ts +2 -2
  71. package/dist/boot/exit-boundary.js +6 -5
  72. package/dist/boot/fatal-exit.d.ts +3 -5
  73. package/dist/boot/fatal-exit.js +2 -2
  74. package/dist/boot/leader.d.ts +15 -0
  75. package/dist/boot/leader.js +17 -15
  76. package/dist/boot/memory-consolidation.d.ts +37 -29
  77. package/dist/boot/memory-consolidation.js +11 -5
  78. package/dist/boot/org-delivery.d.ts +3 -2
  79. package/dist/boot/org-memory.js +1 -1
  80. package/dist/boot/parked-revive-gate.js +2 -2
  81. package/dist/boot/reapers.d.ts +4 -3
  82. package/dist/boot/reapers.js +19 -16
  83. package/dist/boot/resolve-spec.d.ts +5 -3
  84. package/dist/boot/resolve-spec.js +60 -70
  85. package/dist/boot/retention-lane.d.ts +4 -4
  86. package/dist/boot/retention-lane.js +2 -2
  87. package/dist/boot/runner-deps.d.ts +28 -7
  88. package/dist/boot/runner-deps.js +14 -5
  89. package/dist/boot/runtime-caps.d.ts +2 -2
  90. package/dist/boot/runtime-caps.js +16 -2
  91. package/dist/boot/session-faces.d.ts +16 -7
  92. package/dist/boot/session-faces.js +114 -94
  93. package/dist/boot/shutdown.d.ts +5 -4
  94. package/dist/boot/shutdown.js +6 -4
  95. package/dist/boot/stage-01-config.d.ts +1 -0
  96. package/dist/boot/stage-01-config.js +13 -4
  97. package/dist/boot/stage-02-web-admin-pull.d.ts +2 -0
  98. package/dist/boot/stage-02-web-admin-pull.js +16 -3
  99. package/dist/boot/stage-03-store-backend.d.ts +4 -0
  100. package/dist/boot/stage-03-store-backend.js +4 -3
  101. package/dist/boot/stage-04-stores.d.ts +3 -0
  102. package/dist/boot/stage-04-stores.js +2 -2
  103. package/dist/boot/stage-05-execution-env.d.ts +8 -3
  104. package/dist/boot/stage-05-execution-env.js +2 -2
  105. package/dist/boot/stage-06-runners.d.ts +12 -13
  106. package/dist/boot/stage-06-runners.js +23 -15
  107. package/dist/boot/stage-07-capability-layer.d.ts +13 -9
  108. package/dist/boot/stage-07-capability-layer.js +5 -3
  109. package/dist/boot/stage-08-reapers.d.ts +13 -9
  110. package/dist/boot/stage-08-reapers.js +3 -3
  111. package/dist/boot/stage-09-leader.d.ts +14 -16
  112. package/dist/boot/stage-09-leader.js +12 -4
  113. package/dist/boot/stage-10-http-server.d.ts +14 -16
  114. package/dist/boot/stage-10-http-server.js +7 -4
  115. package/dist/boot/store-evidence.d.ts +20 -0
  116. package/dist/boot/store-evidence.js +13 -0
  117. package/dist/boot/stores.d.ts +6 -0
  118. package/dist/boot/stores.js +41 -27
  119. package/dist/boot/task-list-lane.d.ts +4 -4
  120. package/dist/boot/web-admin.d.ts +4 -2
  121. package/dist/boot/web-admin.js +55 -35
  122. package/dist/boot/webfetch-summarize-lane.d.ts +1 -1
  123. package/dist/boot-reclaim.d.ts +3 -1
  124. package/dist/boot-reclaim.js +1 -1
  125. package/dist/brain.d.ts +7 -18
  126. package/dist/brain.js +5 -17
  127. package/dist/budget.js +6 -6
  128. package/dist/capabilities/center-plugins.d.ts +7 -1
  129. package/dist/capabilities/center-plugins.js +32 -10
  130. package/dist/capabilities/center-wiring.js +1 -1
  131. package/dist/capabilities/composite-members.d.ts +2 -2
  132. package/dist/capabilities/composite-members.js +2 -2
  133. package/dist/capabilities/repo-tools.d.ts +2 -0
  134. package/dist/capabilities/repo-tools.js +14 -12
  135. package/dist/capabilities/scenarios.d.ts +6 -15
  136. package/dist/capabilities/scenarios.js +11 -16
  137. package/dist/capabilities/skills.d.ts +1 -1
  138. package/dist/capabilities/tool-defer.d.ts +1 -1
  139. package/dist/capability-facts.d.ts +39 -11
  140. package/dist/capability-facts.js +35 -7
  141. package/dist/card-settle-stored-scope.d.ts +56 -0
  142. package/dist/card-settle-stored-scope.js +27 -0
  143. package/dist/card-void.d.ts +8 -13
  144. package/dist/card-void.js +2 -7
  145. package/dist/center-credential-renewer.d.ts +6 -2
  146. package/dist/center-credential-renewer.js +7 -3
  147. package/dist/config-catalog.d.ts +28 -1
  148. package/dist/config-catalog.js +95 -85
  149. package/dist/config-dir-trust.d.ts +37 -4
  150. package/dist/config-dir-trust.js +88 -44
  151. package/dist/config-invariants.d.ts +43 -3
  152. package/dist/config-invariants.js +69 -5
  153. package/dist/config-lkg.d.ts +23 -1
  154. package/dist/config-lkg.js +17 -3
  155. package/dist/config-provider.d.ts +9 -9
  156. package/dist/config-provider.js +25 -110
  157. package/dist/config-pull-failure.d.ts +50 -0
  158. package/dist/config-pull-failure.js +48 -0
  159. package/dist/config-types.d.ts +62 -21
  160. package/dist/config.d.ts +14 -21
  161. package/dist/config.js +141 -64
  162. package/dist/context-window-notice.d.ts +31 -0
  163. package/dist/context-window-notice.js +28 -0
  164. package/dist/delegated-session-owner.d.ts +1 -1
  165. package/dist/deleted-session-ids.d.ts +3 -2
  166. package/dist/deployment-governance.d.ts +3 -3
  167. package/dist/deployment-shape.d.ts +46 -11
  168. package/dist/deployment-shape.js +8 -0
  169. package/dist/egress-redirect.d.ts +25 -7
  170. package/dist/egress-redirect.js +20 -2
  171. package/dist/elicitation.d.ts +6 -5
  172. package/dist/elicitation.js +23 -22
  173. package/dist/engine-identity.d.ts +28 -0
  174. package/dist/engine-identity.js +10 -0
  175. package/dist/entry-problems.d.ts +5 -7
  176. package/dist/entry-problems.js +1 -1
  177. package/dist/env-facts.js +2 -1
  178. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  179. package/dist/env-name-allowlist-knobs.js +5 -0
  180. package/dist/env-witness.d.ts +7 -0
  181. package/dist/env-witness.js +16 -0
  182. package/dist/execution-lane-caps.d.ts +25 -23
  183. package/dist/execution-lane-caps.js +18 -26
  184. package/dist/execution-placement.d.ts +58 -18
  185. package/dist/execution-placement.js +41 -2
  186. package/dist/field-conflict.d.ts +7 -11
  187. package/dist/field-conflict.js +3 -3
  188. package/dist/file-history-capture.d.ts +3 -3
  189. package/dist/file-history-disclosure.js +1 -0
  190. package/dist/fleet/fleet-bus.d.ts +21 -3
  191. package/dist/fleet/fleet-bus.js +35 -25
  192. package/dist/fleet/fleet-reconciler.js +2 -2
  193. package/dist/fleet/serving-lease.js +3 -3
  194. package/dist/fleet-client.js +3 -3
  195. package/dist/fleet-lease.d.ts +0 -1
  196. package/dist/fleet-lease.js +4 -5
  197. package/dist/history-anchor.d.ts +23 -3
  198. package/dist/history-anchor.js +24 -4
  199. package/dist/hitl-delivery.d.ts +90 -0
  200. package/dist/hitl-delivery.js +98 -0
  201. package/dist/hooks/classic-stations.d.ts +8 -11
  202. package/dist/hooks/classic-stations.js +0 -7
  203. package/dist/hooks/command-spawn.d.ts +7 -2
  204. package/dist/hooks/command-spawn.js +13 -6
  205. package/dist/hooks/hook-llm.d.ts +5 -1
  206. package/dist/hooks/hook-llm.js +24 -15
  207. package/dist/hooks/hook-runner.d.ts +72 -57
  208. package/dist/hooks/hook-runner.js +271 -193
  209. package/dist/host-lsp-manager.d.ts +22 -8
  210. package/dist/host-lsp-manager.js +4 -1
  211. package/dist/http/active-run-conflict.d.ts +2 -43
  212. package/dist/http/active-run-conflict.js +3 -7
  213. package/dist/http/admission.d.ts +4 -18
  214. package/dist/http/admission.js +76 -79
  215. package/dist/http/approval-sweeps.d.ts +3 -3
  216. package/dist/http/approval-sweeps.js +56 -31
  217. package/dist/http/caller-trust.d.ts +8 -10
  218. package/dist/http/caller-trust.js +1 -2
  219. package/dist/http/claim-heartbeat.d.ts +49 -0
  220. package/dist/http/claim-heartbeat.js +46 -0
  221. package/dist/http/claimed-run.d.ts +16 -8
  222. package/dist/http/claimed-run.js +8 -0
  223. package/dist/http/dispatch.d.ts +8 -1
  224. package/dist/http/dispatch.js +78 -30
  225. package/dist/http/durable-run-leg.d.ts +11 -4
  226. package/dist/http/durable-run-leg.js +45 -21
  227. package/dist/http/execution-context.d.ts +122 -47
  228. package/dist/http/execution-context.js +135 -43
  229. package/dist/http/health-reader.d.ts +36 -0
  230. package/dist/http/health-reader.js +21 -0
  231. package/dist/http/health-self-report.d.ts +9 -9
  232. package/dist/http/health-self-report.js +4 -4
  233. package/dist/http/idempotency.d.ts +9 -5
  234. package/dist/http/idempotency.js +4 -4
  235. package/dist/http/memory-compliance-failure-reply.d.ts +15 -16
  236. package/dist/http/memory-compliance-failure-reply.js +4 -2
  237. package/dist/http/memory-face-fault-reply.d.ts +67 -0
  238. package/dist/http/memory-face-fault-reply.js +71 -0
  239. package/dist/http/memory-store-refusal-reply.d.ts +1 -1
  240. package/dist/http/memory-store-refusal-reply.js +3 -0
  241. package/dist/http/owner-access.d.ts +392 -67
  242. package/dist/http/owner-access.js +370 -35
  243. package/dist/http/principal-gate.d.ts +39 -89
  244. package/dist/http/principal-gate.js +26 -68
  245. package/dist/http/refusal-body.d.ts +35 -2
  246. package/dist/http/refusal-body.js +34 -0
  247. package/dist/http/request-guards.d.ts +9 -13
  248. package/dist/http/request-guards.js +34 -68
  249. package/dist/http/resume-composition.d.ts +37 -0
  250. package/dist/http/resume-composition.js +18 -0
  251. package/dist/http/resume-ctx-read.d.ts +46 -12
  252. package/dist/http/resume-ctx-read.js +31 -6
  253. package/dist/http/resume-legs.d.ts +54 -19
  254. package/dist/http/resume-legs.js +408 -196
  255. package/dist/http/retired-skill-manifest.d.ts +1 -1
  256. package/dist/http/route-ctx.d.ts +31 -32
  257. package/dist/http/routes/a2a-serve.d.ts +43 -5
  258. package/dist/http/routes/a2a-serve.js +60 -37
  259. package/dist/http/routes/admin-config-refresh.js +2 -2
  260. package/dist/http/routes/admin-drain.js +2 -2
  261. package/dist/http/routes/admin-memory-optout.js +4 -3
  262. package/dist/http/routes/adoption.d.ts +3 -0
  263. package/dist/http/routes/adoption.js +17 -10
  264. package/dist/http/routes/agents-roster.js +13 -7
  265. package/dist/http/routes/approvals-assistant.js +73 -67
  266. package/dist/http/routes/attachments.js +2 -2
  267. package/dist/http/routes/background.d.ts +3 -3
  268. package/dist/http/routes/background.js +9 -17
  269. package/dist/http/routes/capabilities.js +16 -19
  270. package/dist/http/routes/config-catalog.js +2 -2
  271. package/dist/http/routes/devices.d.ts +3 -3
  272. package/dist/http/routes/devices.js +4 -4
  273. package/dist/http/routes/diagnostics.d.ts +29 -3
  274. package/dist/http/routes/diagnostics.js +49 -18
  275. package/dist/http/routes/engine.js +2 -2
  276. package/dist/http/routes/fleet.js +5 -3
  277. package/dist/http/routes/images.js +10 -6
  278. package/dist/http/routes/leader.d.ts +7 -2
  279. package/dist/http/routes/leader.js +12 -3
  280. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  281. package/dist/http/routes/mcp-reconnect.js +7 -15
  282. package/dist/http/routes/memory-bundle.js +4 -7
  283. package/dist/http/routes/memory-compliance.js +4 -4
  284. package/dist/http/routes/memory-consolidation.js +6 -9
  285. package/dist/http/routes/memory-origin.d.ts +2 -0
  286. package/dist/http/routes/memory-origin.js +7 -8
  287. package/dist/http/routes/memory-policy.js +17 -12
  288. package/dist/http/routes/notify-wake.js +10 -19
  289. package/dist/http/routes/observability.js +3 -3
  290. package/dist/http/routes/principal-inbox.d.ts +4 -2
  291. package/dist/http/routes/principal-inbox.js +5 -4
  292. package/dist/http/routes/projects.d.ts +1 -1
  293. package/dist/http/routes/projects.js +3 -2
  294. package/dist/http/routes/retention-ops.js +6 -9
  295. package/dist/http/routes/rules.js +20 -5
  296. package/dist/http/routes/run-memory-capture.d.ts +2 -2
  297. package/dist/http/routes/run-memory-capture.js +6 -12
  298. package/dist/http/routes/run-permission-mode.d.ts +2 -2
  299. package/dist/http/routes/run-permission-mode.js +10 -17
  300. package/dist/http/routes/runs.js +155 -237
  301. package/dist/http/routes/session-memory-sync.d.ts +2 -2
  302. package/dist/http/routes/session-memory-sync.js +7 -13
  303. package/dist/http/routes/session-sync.d.ts +28 -7
  304. package/dist/http/routes/session-sync.js +63 -50
  305. package/dist/http/routes/sessions-list.d.ts +3 -3
  306. package/dist/http/routes/sessions-list.js +9 -9
  307. package/dist/http/routes/sessions.js +87 -124
  308. package/dist/http/routes/shared-memory.js +6 -6
  309. package/dist/http/routes/side-query.js +2 -2
  310. package/dist/http/routes/tasks.js +237 -187
  311. package/dist/http/routes/trace-usage.d.ts +0 -12
  312. package/dist/http/routes/trace-usage.js +40 -46
  313. package/dist/http/routes/workflows.d.ts +39 -4
  314. package/dist/http/routes/workflows.js +42 -14
  315. package/dist/http/run-concurrency-cap.d.ts +20 -12
  316. package/dist/http/run-concurrency-cap.js +39 -26
  317. package/dist/http/run-read-face.d.ts +3 -5
  318. package/dist/http/run-read-face.js +8 -13
  319. package/dist/http/send.d.ts +12 -3
  320. package/dist/http/send.js +42 -6
  321. package/dist/http/server.d.ts +43 -45
  322. package/dist/http/server.js +61 -11
  323. package/dist/http/sse-log.d.ts +17 -10
  324. package/dist/http/sse-log.js +7 -7
  325. package/dist/http/stored-body-replay.d.ts +12 -0
  326. package/dist/http/stored-body-replay.js +2 -2
  327. package/dist/http/submit-ctx.d.ts +17 -8
  328. package/dist/http/submit-ctx.js +8 -5
  329. package/dist/http/submit-replay.d.ts +19 -6
  330. package/dist/http/submit-replay.js +18 -6
  331. package/dist/http/sync-submit-body.d.ts +3 -4
  332. package/dist/http/sync-submit-body.js +4 -5
  333. package/dist/http/wire-types.d.ts +4 -3
  334. package/dist/identity-source.d.ts +36 -5
  335. package/dist/identity-source.js +27 -0
  336. package/dist/index.d.ts +4 -1
  337. package/dist/index.js +2 -0
  338. package/dist/key-resolver.d.ts +32 -0
  339. package/dist/key-resolver.js +23 -9
  340. package/dist/leader/diffup.js +2 -1
  341. package/dist/leader/endpoint.d.ts +26 -5
  342. package/dist/leader/endpoint.js +154 -14
  343. package/dist/leader/fanout.d.ts +3 -0
  344. package/dist/leader/fanout.js +78 -63
  345. package/dist/leader/leader.d.ts +16 -0
  346. package/dist/leader/leader.js +43 -3
  347. package/dist/leader/merge.d.ts +9 -1
  348. package/dist/leader/merge.js +16 -1
  349. package/dist/leader/repair-wire.js +1 -1
  350. package/dist/leader/wire.d.ts +36 -3
  351. package/dist/leader/wire.js +59 -18
  352. package/dist/leg-facts.d.ts +7 -2
  353. package/dist/leg-facts.js +1 -0
  354. package/dist/local-store-declaration.d.ts +54 -0
  355. package/dist/local-store-declaration.js +59 -0
  356. package/dist/lsp-evict.d.ts +6 -4
  357. package/dist/lsp-evict.js +2 -3
  358. package/dist/mcp-assertion-header.d.ts +1 -1
  359. package/dist/mcp-assertion-header.js +1 -1
  360. package/dist/memory-operator-faces.d.ts +5 -4
  361. package/dist/memory-operator-faces.js +1 -1
  362. package/dist/memory-project-layer.js +1 -1
  363. package/dist/memory-scope.d.ts +33 -6
  364. package/dist/memory-scope.js +68 -19
  365. package/dist/memory-store-refusal.d.ts +5 -2
  366. package/dist/memory-sync-client.d.ts +3 -2
  367. package/dist/memory-sync-client.js +8 -5
  368. package/dist/model-compat.d.ts +17 -8
  369. package/dist/model-compat.js +6 -2
  370. package/dist/model-context-window.d.ts +57 -0
  371. package/dist/model-context-window.js +21 -0
  372. package/dist/model-entry-headers.d.ts +46 -4
  373. package/dist/model-entry-headers.js +2 -2
  374. package/dist/model-entry-refusal.d.ts +40 -20
  375. package/dist/model-entry-refusal.js +57 -30
  376. package/dist/model-plane-face.d.ts +19 -11
  377. package/dist/model-plane-face.js +9 -1
  378. package/dist/model-plane-probe.d.ts +2 -0
  379. package/dist/model-plane-probe.js +17 -0
  380. package/dist/model-provider.d.ts +3 -3
  381. package/dist/model-reach-scope.d.ts +2 -1
  382. package/dist/model-route-endpoint.d.ts +9 -5
  383. package/dist/model-route-endpoint.js +29 -7
  384. package/dist/model-select.d.ts +16 -0
  385. package/dist/model-select.js +4 -0
  386. package/dist/mountinfo.d.ts +37 -0
  387. package/dist/mountinfo.js +47 -0
  388. package/dist/namespaced-rule-name.d.ts +23 -14
  389. package/dist/namespaced-rule-name.js +8 -11
  390. package/dist/net-address.d.ts +1 -1
  391. package/dist/observability/corrupt-read-seat.d.ts +4 -4
  392. package/dist/observability/corrupt-read-seat.js +5 -3
  393. package/dist/observability/fail-open.d.ts +72 -40
  394. package/dist/observability/fail-open.js +74 -40
  395. package/dist/observability/logger.d.ts +22 -1
  396. package/dist/observability/logger.js +89 -12
  397. package/dist/observability/metrics.js +4 -5
  398. package/dist/observability/otel-exporter.js +2 -1
  399. package/dist/observability/principal-context.d.ts +14 -7
  400. package/dist/observability/principal-context.js +8 -3
  401. package/dist/observability/run-terminal.d.ts +7 -1
  402. package/dist/observability/run-terminal.js +6 -2
  403. package/dist/observability/secret-env-scrub.d.ts +1 -1
  404. package/dist/observability/secret-env-scrub.js +1 -1
  405. package/dist/orchestration/hardened-vm-runner.d.ts +1 -0
  406. package/dist/orchestration/hardened-vm-runner.js +11 -5
  407. package/dist/orchestration/hardened-vm-worker-runner.d.ts +0 -14
  408. package/dist/orchestration/hardened-vm-worker-runner.js +5 -0
  409. package/dist/orchestration/hardened-vm-worker.js +2 -1
  410. package/dist/orchestration/workflow-completion-inbox.d.ts +3 -3
  411. package/dist/orchestration/workflow-completion-inbox.js +3 -2
  412. package/dist/org-delivery-gate.d.ts +36 -10
  413. package/dist/org-delivery-gate.js +12 -8
  414. package/dist/outbound-dispatcher.d.ts +1 -1
  415. package/dist/outbound-dispatcher.js +6 -12
  416. package/dist/owner-brands.d.ts +81 -0
  417. package/dist/owner-brands.js +42 -0
  418. package/dist/owner-faces.d.ts +532 -0
  419. package/dist/owner-faces.js +126 -0
  420. package/dist/park-row-commit.d.ts +67 -0
  421. package/dist/park-row-commit.js +76 -0
  422. package/dist/parked-decide.d.ts +76 -12
  423. package/dist/parked-decide.js +152 -11
  424. package/dist/parked-revive-surface.d.ts +14 -4
  425. package/dist/parked-revive-surface.js +5 -4
  426. package/dist/peer-directory.d.ts +7 -9
  427. package/dist/peer-directory.js +3 -2
  428. package/dist/permission-rule-vocab.d.ts +53 -8
  429. package/dist/permission-rule-vocab.js +30 -2
  430. package/dist/plugins/adoption-log-sql.d.ts +3 -25
  431. package/dist/plugins/adoption-log-sql.js +23 -21
  432. package/dist/plugins/approval-ask-retired-delete-by-task-m50.d.ts +7 -0
  433. package/dist/plugins/approval-ask-retired-delete-by-task-m50.js +17 -0
  434. package/dist/plugins/approval-ask-store-file.d.ts +18 -7
  435. package/dist/plugins/approval-ask-store-file.js +37 -14
  436. package/dist/plugins/approval-ask-store-memory.d.ts +12 -4
  437. package/dist/plugins/approval-ask-store-memory.js +24 -10
  438. package/dist/plugins/approval-ask-store-sql.d.ts +40 -21
  439. package/dist/plugins/approval-ask-store-sql.js +44 -15
  440. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  441. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  442. package/dist/plugins/approval-exemption-skill-m46.d.ts +14 -0
  443. package/dist/plugins/approval-exemption-skill-m46.js +21 -0
  444. package/dist/plugins/approval-exemption-store.d.ts +32 -2
  445. package/dist/plugins/approval-exemption-store.js +42 -19
  446. package/dist/plugins/approval-nonce-store.d.ts +39 -12
  447. package/dist/plugins/approval-nonce-store.js +72 -23
  448. package/dist/plugins/blob-backend.d.ts +2 -0
  449. package/dist/plugins/blob-backend.js +9 -6
  450. package/dist/plugins/caching-session-store.d.ts +13 -1
  451. package/dist/plugins/caching-session-store.js +43 -4
  452. package/dist/plugins/checkpoint-index-columns.d.ts +32 -5
  453. package/dist/plugins/checkpoint-index-columns.js +36 -1
  454. package/dist/plugins/checkpoint-store-face.d.ts +83 -10
  455. package/dist/plugins/checkpoint-store-face.js +18 -1
  456. package/dist/plugins/device-store-sql.js +1 -0
  457. package/dist/plugins/ensure-index.d.ts +13 -1
  458. package/dist/plugins/ensure-index.js +154 -21
  459. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -18
  460. package/dist/plugins/ensure-schema-lock-policy.js +76 -9
  461. package/dist/plugins/file-run-store.d.ts +85 -32
  462. package/dist/plugins/file-run-store.js +177 -94
  463. package/dist/plugins/host-platform.d.ts +24 -3
  464. package/dist/plugins/host-platform.js +15 -1
  465. package/dist/plugins/image-bake-store-sql.js +2 -0
  466. package/dist/plugins/imported-layer-root-retirement.d.ts +2 -2
  467. package/dist/plugins/k8s-pod-reaper.d.ts +1 -3
  468. package/dist/plugins/k8s-pod-reaper.js +0 -1
  469. package/dist/plugins/leader-run-store-sql.d.ts +22 -0
  470. package/dist/plugins/leader-run-store-sql.js +34 -4
  471. package/dist/plugins/local-checkpoint-store.d.ts +62 -23
  472. package/dist/plugins/local-checkpoint-store.js +197 -77
  473. package/dist/plugins/local-data-dir-modes-m39.js +2 -1
  474. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  475. package/dist/plugins/local-data-root-admission.js +100 -0
  476. package/dist/plugins/local-docker-capacity.d.ts +93 -0
  477. package/dist/plugins/local-docker-capacity.js +194 -0
  478. package/dist/plugins/local-docker-reaper.d.ts +92 -0
  479. package/dist/plugins/local-docker-reaper.js +102 -0
  480. package/dist/plugins/local-engine-high-water.d.ts +29 -6
  481. package/dist/plugins/local-engine-high-water.js +27 -19
  482. package/dist/plugins/local-session-store.d.ts +458 -140
  483. package/dist/plugins/local-session-store.js +906 -292
  484. package/dist/plugins/local-session-window.d.ts +3 -2
  485. package/dist/plugins/local-session-window.js +3 -2
  486. package/dist/plugins/local-sidecar-quarantine.js +3 -2
  487. package/dist/plugins/local-task-attachment-store.d.ts +18 -1
  488. package/dist/plugins/local-task-attachment-store.js +22 -1
  489. package/dist/plugins/memory-embedder.js +9 -7
  490. package/dist/plugins/memory-engine-pg.d.ts +3 -2
  491. package/dist/plugins/memory-engine-pg.js +14 -5
  492. package/dist/plugins/memory-engine-sql-apply.d.ts +8 -4
  493. package/dist/plugins/memory-engine-sql-apply.js +6 -8
  494. package/dist/plugins/memory-engine-tidb.d.ts +4 -2
  495. package/dist/plugins/memory-engine-tidb.js +11 -2
  496. package/dist/plugins/memory-optout-grant-store-sql.d.ts +1 -1
  497. package/dist/plugins/memory-optout-grant-store-sql.js +3 -3
  498. package/dist/plugins/memory-run-store.d.ts +53 -36
  499. package/dist/plugins/memory-run-store.js +116 -88
  500. package/dist/plugins/one-time-migration-marker.d.ts +26 -0
  501. package/dist/plugins/one-time-migration-marker.js +13 -0
  502. package/dist/plugins/one-time-migrations.d.ts +214 -47
  503. package/dist/plugins/one-time-migrations.js +481 -135
  504. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  505. package/dist/plugins/org-delivery-quota-store.js +31 -15
  506. package/dist/plugins/permission-rule-store-file.d.ts +3 -3
  507. package/dist/plugins/permission-rule-store-sql.d.ts +5 -64
  508. package/dist/plugins/permission-rule-store-sql.js +11 -95
  509. package/dist/plugins/pg-pool.d.ts +2 -32
  510. package/dist/plugins/pg-pool.js +24 -17
  511. package/dist/plugins/remote-env-device.d.ts +3 -2
  512. package/dist/plugins/remote-env-e2b.d.ts +8 -32
  513. package/dist/plugins/remote-env-e2b.js +44 -32
  514. package/dist/plugins/remote-env-host.d.ts +51 -17
  515. package/dist/plugins/remote-env-host.js +111 -42
  516. package/dist/plugins/remote-env-k8s.d.ts +40 -45
  517. package/dist/plugins/remote-env-k8s.js +111 -60
  518. package/dist/plugins/remote-env-local-docker.d.ts +152 -15
  519. package/dist/plugins/remote-env-local-docker.js +352 -72
  520. package/dist/plugins/remote-scratchpad.js +4 -0
  521. package/dist/plugins/retention-deployment.d.ts +49 -17
  522. package/dist/plugins/retention-deployment.js +39 -8
  523. package/dist/plugins/run-preview-m45.d.ts +11 -0
  524. package/dist/plugins/run-preview-m45.js +19 -0
  525. package/dist/plugins/run-store-sql.d.ts +82 -75
  526. package/dist/plugins/run-store-sql.js +164 -125
  527. package/dist/plugins/s3-answer.d.ts +2 -1
  528. package/dist/plugins/sandbox-reaper.d.ts +26 -0
  529. package/dist/plugins/sandbox-reaper.js +2 -0
  530. package/dist/plugins/scenario-prefix-switch-m42.d.ts +56 -0
  531. package/dist/plugins/scenario-prefix-switch-m42.js +56 -0
  532. package/dist/plugins/scheduler-support.d.ts +16 -1
  533. package/dist/plugins/scheduler-support.js +22 -2
  534. package/dist/plugins/schema-consistency.d.ts +131 -14
  535. package/dist/plugins/schema-consistency.js +388 -18
  536. package/dist/plugins/schema-drift-sentence.d.ts +10 -6
  537. package/dist/plugins/schema-drift-sentence.js +15 -7
  538. package/dist/plugins/schema-ensure-legs.d.ts +9 -0
  539. package/dist/plugins/schema-ensure-legs.js +1 -1
  540. package/dist/plugins/schema-indexes.js +3 -0
  541. package/dist/plugins/schema-live-columns.d.ts +14 -5
  542. package/dist/plugins/schema-live-columns.js +10 -10
  543. package/dist/plugins/send-user-file.js +2 -1
  544. package/dist/plugins/session-birth.d.ts +37 -0
  545. package/dist/plugins/session-birth.js +38 -0
  546. package/dist/plugins/session-epoch-anchor.d.ts +40 -12
  547. package/dist/plugins/session-epoch-anchor.js +33 -11
  548. package/dist/plugins/session-ext-table.d.ts +27 -3
  549. package/dist/plugins/session-ext-table.js +19 -6
  550. package/dist/plugins/session-fork.d.ts +15 -1
  551. package/dist/plugins/session-fork.js +3 -0
  552. package/dist/plugins/session-ownership.d.ts +1 -1
  553. package/dist/plugins/session-placement.d.ts +27 -20
  554. package/dist/plugins/session-placement.js +15 -12
  555. package/dist/plugins/session-row-sql.d.ts +12 -0
  556. package/dist/plugins/session-row-sql.js +3 -3
  557. package/dist/plugins/session-run-rollup.d.ts +29 -0
  558. package/dist/plugins/session-run-rollup.js +40 -0
  559. package/dist/plugins/session-store-sql.d.ts +38 -12
  560. package/dist/plugins/session-store-sql.js +130 -30
  561. package/dist/plugins/session-withdraw-sql.d.ts +36 -0
  562. package/dist/plugins/session-withdraw-sql.js +42 -0
  563. package/dist/plugins/shared-memory-store-sql.d.ts +0 -69
  564. package/dist/plugins/shared-memory-store-sql.js +2 -1
  565. package/dist/plugins/shell-sentinel-owner-m48.d.ts +42 -0
  566. package/dist/plugins/shell-sentinel-owner-m48.js +9 -0
  567. package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
  568. package/dist/plugins/sql-driver.d.ts +1 -1
  569. package/dist/plugins/sql-driver.js +2 -1
  570. package/dist/plugins/sql-errors.d.ts +32 -5
  571. package/dist/plugins/sql-errors.js +55 -7
  572. package/dist/plugins/sql-json-column.d.ts +8 -7
  573. package/dist/plugins/sql-json-column.js +10 -6
  574. package/dist/plugins/stop-then-start-sentence.d.ts +16 -0
  575. package/dist/plugins/stop-then-start-sentence.js +7 -0
  576. package/dist/plugins/store-backend.d.ts +99 -16
  577. package/dist/plugins/store-backend.js +230 -65
  578. package/dist/plugins/store-contracts.d.ts +123 -33
  579. package/dist/plugins/store-contracts.js +38 -1
  580. package/dist/plugins/store-fault.d.ts +28 -2
  581. package/dist/plugins/store-fault.js +20 -2
  582. package/dist/plugins/task-attachment-store.d.ts +15 -3
  583. package/dist/plugins/task-attachment-store.js +22 -9
  584. package/dist/plugins/tidb-pool.d.ts +11 -6
  585. package/dist/plugins/tidb-pool.js +39 -18
  586. package/dist/plugins/web-search.js +5 -3
  587. package/dist/plugins/workflow-run-store-sql.d.ts +6 -1
  588. package/dist/plugins/workflow-run-store-sql.js +11 -5
  589. package/dist/plugins/workspace-custody.d.ts +44 -0
  590. package/dist/plugins/workspace-custody.js +47 -0
  591. package/dist/principal-jwt.d.ts +4 -4
  592. package/dist/principal-jwt.js +20 -5
  593. package/dist/principal-shape.d.ts +41 -0
  594. package/dist/principal-shape.js +19 -0
  595. package/dist/project-identity.d.ts +8 -7
  596. package/dist/project-identity.js +40 -17
  597. package/dist/project-layer-key.d.ts +13 -2
  598. package/dist/project-layer-key.js +9 -1
  599. package/dist/question.d.ts +13 -23
  600. package/dist/question.js +21 -39
  601. package/dist/read-deny-floor-notice.d.ts +36 -0
  602. package/dist/read-deny-floor-notice.js +40 -0
  603. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  604. package/dist/request-declaration-disposition.js +39 -0
  605. package/dist/request-declaration-refusal.d.ts +35 -12
  606. package/dist/request-declaration-refusal.js +21 -9
  607. package/dist/request-form.d.ts +58 -0
  608. package/dist/request-form.js +72 -0
  609. package/dist/request-image-urls.d.ts +8 -2
  610. package/dist/request-image-urls.js +2 -1
  611. package/dist/request-images.d.ts +20 -0
  612. package/dist/request-images.js +59 -0
  613. package/dist/request-key-closure.d.ts +6 -7
  614. package/dist/request-key-closure.js +15 -2
  615. package/dist/resource-suspend.d.ts +7 -16
  616. package/dist/resource-suspend.js +1 -1
  617. package/dist/retired-name-host-notice.d.ts +21 -0
  618. package/dist/retired-name-host-notice.js +12 -0
  619. package/dist/rule-entry-list.d.ts +2 -2
  620. package/dist/rule-entry-list.js +3 -4
  621. package/dist/rule-issue-reading.d.ts +58 -0
  622. package/dist/rule-issue-reading.js +14 -0
  623. package/dist/rules-consent.d.ts +5 -3
  624. package/dist/rules-consent.js +1 -0
  625. package/dist/run-acceptance.d.ts +3 -1
  626. package/dist/run-acceptance.js +3 -0
  627. package/dist/run-cancel-card.d.ts +28 -0
  628. package/dist/run-cancel-card.js +19 -0
  629. package/dist/run-cancel-context.d.ts +10 -0
  630. package/dist/run-cancel-context.js +1 -0
  631. package/dist/run-local.d.ts +14 -6
  632. package/dist/run-local.js +117 -90
  633. package/dist/run-stale-default.d.ts +7 -0
  634. package/dist/run-stale-default.js +2 -0
  635. package/dist/runs.d.ts +56 -21
  636. package/dist/runs.js +73 -43
  637. package/dist/runtime-caps-resolver.d.ts +21 -6
  638. package/dist/runtime-caps-resolver.js +17 -4
  639. package/dist/runtime-governance.d.ts +2 -2
  640. package/dist/runtime-governance.js +5 -3
  641. package/dist/sealed-key-layout.d.ts +12 -5
  642. package/dist/sealed-key-layout.js +5 -0
  643. package/dist/sealed-key.d.ts +12 -2
  644. package/dist/sealed-key.js +115 -27
  645. package/dist/security.d.ts +128 -158
  646. package/dist/security.js +163 -59
  647. package/dist/sema-config-dir.d.ts +20 -0
  648. package/dist/sema-config-dir.js +25 -0
  649. package/dist/server-secret-env.d.ts +55 -25
  650. package/dist/server-secret-env.js +75 -22
  651. package/dist/session-agent.d.ts +3 -2
  652. package/dist/session-children-retire.d.ts +13 -8
  653. package/dist/session-children-retire.js +9 -7
  654. package/dist/session-deletion.d.ts +88 -0
  655. package/dist/session-deletion.js +8 -0
  656. package/dist/session-erase.d.ts +145 -0
  657. package/dist/session-erase.js +115 -0
  658. package/dist/session-error-wire.d.ts +5 -5
  659. package/dist/session-error-wire.js +1 -1
  660. package/dist/session-incarnation.d.ts +52 -5
  661. package/dist/session-incarnation.js +19 -2
  662. package/dist/session-policy-wire.d.ts +6 -3
  663. package/dist/session-policy-wire.js +11 -1
  664. package/dist/session-resident-background.d.ts +0 -71
  665. package/dist/session-resident-background.js +4 -3
  666. package/dist/session-row.d.ts +2 -1
  667. package/dist/session-rules-seat.d.ts +12 -0
  668. package/dist/session-rules-seat.js +19 -0
  669. package/dist/session-titler.d.ts +7 -15
  670. package/dist/session-titler.js +1 -1
  671. package/dist/session-workspace.d.ts +3 -3
  672. package/dist/spec-fields.d.ts +57 -3
  673. package/dist/spec-fields.js +72 -51
  674. package/dist/store-admission.d.ts +33 -0
  675. package/dist/store-admission.js +49 -0
  676. package/dist/store-posture.d.ts +141 -20
  677. package/dist/store-posture.js +205 -3
  678. package/dist/stored-request-hooks-m20.js +2 -2
  679. package/dist/stores-sql/background-agent-store-sql.d.ts +11 -2
  680. package/dist/stores-sql/background-agent-store-sql.js +73 -20
  681. package/dist/stores-sql/checkpoint-store-sql.d.ts +34 -29
  682. package/dist/stores-sql/checkpoint-store-sql.js +78 -49
  683. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  684. package/dist/stores-sql/file-history-store-sql.js +3 -5
  685. package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
  686. package/dist/stores-sql/mailbox-store-sql.js +31 -32
  687. package/dist/stores-sql/pg-session-storage.d.ts +7 -5
  688. package/dist/stores-sql/pg-session-storage.js +54 -46
  689. package/dist/stores-sql/retention-store-sql.d.ts +87 -19
  690. package/dist/stores-sql/retention-store-sql.js +92 -27
  691. package/dist/stores-sql/session-policy-store-sql.d.ts +32 -10
  692. package/dist/stores-sql/session-policy-store-sql.js +44 -36
  693. package/dist/stores-sql/session-write-hooks.d.ts +12 -6
  694. package/dist/stores-sql/tidb-session-storage.d.ts +4 -4
  695. package/dist/stores-sql/tidb-session-storage.js +13 -8
  696. package/dist/stores-sql/tidb-session-store.d.ts +36 -17
  697. package/dist/stores-sql/tidb-session-store.js +69 -39
  698. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  699. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  700. package/dist/stores-sql/windowed-session-storage.d.ts +5 -0
  701. package/dist/stores-sql/windowed-session-storage.js +9 -0
  702. package/dist/submission-exit.d.ts +50 -0
  703. package/dist/submission-exit.js +108 -0
  704. package/dist/task-a2a.d.ts +3 -8
  705. package/dist/task-a2a.js +28 -30
  706. package/dist/task-cwd.d.ts +43 -7
  707. package/dist/task-cwd.js +11 -0
  708. package/dist/task-mcp.d.ts +3 -3
  709. package/dist/task-mcp.js +4 -4
  710. package/dist/task-settings.d.ts +14 -10
  711. package/dist/task-settings.js +19 -7
  712. package/dist/terminal.d.ts +27 -43
  713. package/dist/terminal.js +49 -14
  714. package/dist/tool-approval.d.ts +173 -163
  715. package/dist/tool-approval.js +247 -232
  716. package/dist/trace/core-keyset-guard.d.ts +13 -6
  717. package/dist/trace/core-vocab-wire.d.ts +95 -0
  718. package/dist/trace/core-vocab-wire.js +15 -0
  719. package/dist/trace/engine-notice-wire.d.ts +24 -21
  720. package/dist/trace/engine-notice-wire.js +7 -3
  721. package/dist/trace/exit-seat.d.ts +30 -4
  722. package/dist/trace/exit-seat.js +40 -36
  723. package/dist/trace/extension-call-face.d.ts +3 -2
  724. package/dist/trace/extension-call-face.js +3 -1
  725. package/dist/trace/ledger-events.d.ts +4 -7
  726. package/dist/trace/ledger-sink.d.ts +4 -31
  727. package/dist/trace/ledger-sink.js +0 -11
  728. package/dist/trace/model-segment.d.ts +5 -5
  729. package/dist/trace/model-segment.js +5 -5
  730. package/dist/trace/project.d.ts +59 -44
  731. package/dist/trace/project.js +60 -26
  732. package/dist/trace/redact.d.ts +135 -27
  733. package/dist/trace/redact.js +884 -260
  734. package/dist/trace/self-minted-wire.d.ts +567 -20
  735. package/dist/trace/self-minted-wire.js +205 -24
  736. package/dist/trace/wire-projection-faces.d.ts +5 -6
  737. package/dist/trace/wire-projection-faces.js +2 -2
  738. package/dist/upstream-body.d.ts +23 -0
  739. package/dist/upstream-body.js +16 -0
  740. package/dist/url-refusal.d.ts +3 -3
  741. package/dist/url-refusal.js +11 -4
  742. package/dist/user-authored-text.d.ts +1 -1
  743. package/dist/user-input-ledger.d.ts +7 -8
  744. package/dist/user-input-ledger.js +1 -1
  745. package/dist/web-admin-client/apply-effective.d.ts +6 -3
  746. package/dist/web-admin-client/apply-effective.js +33 -23
  747. package/dist/web-admin-client/apply-ledger.d.ts +16 -7
  748. package/dist/web-admin-client/apply-ledger.js +5 -2
  749. package/dist/web-admin-client/center-request.d.ts +2 -2
  750. package/dist/web-admin-client/center-request.js +15 -15
  751. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  752. package/dist/web-admin-client/effective-keys.js +1 -1
  753. package/dist/web-admin-client/entry-metadata.d.ts +8 -0
  754. package/dist/web-admin-client/entry-metadata.js +13 -0
  755. package/dist/web-admin-client/http-client.d.ts +1 -1
  756. package/dist/web-admin-client/http-client.js +23 -4
  757. package/dist/web-admin-client/mcp-revocation.d.ts +1 -1
  758. package/dist/web-admin-client/mcp-revocation.js +2 -1
  759. package/dist/web-admin-client/read-warnings.d.ts +1 -0
  760. package/dist/web-admin-client/read-warnings.js +1 -0
  761. package/dist/web-admin-client/restart-signal.js +8 -4
  762. package/dist/web-admin-client/skills-mcp.d.ts +2 -2
  763. package/dist/web-admin-client/types.d.ts +1 -2
  764. package/dist/workspace-space.d.ts +3 -2
  765. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
  766. package/docs/error-text-lexicon.json +317 -0
  767. package/package.json +8 -3
  768. package/scripts/drain-check-local.mjs +118 -147
  769. package/scripts/drain-check.mysql.sql +22 -2
  770. package/scripts/drain-check.pg.sql +22 -2
  771. package/scripts/embed-minimal.mjs +183 -0
  772. package/wire-changes/server@7.148.0.json +1517 -0
  773. package/wire-changes/server@7.149.0.json +372 -0
  774. package/dist/http/memory-control-plane-corrupt-reply.d.ts +0 -25
  775. package/dist/http/memory-control-plane-corrupt-reply.js +0 -19
  776. package/dist/http/memory-layer-reply.d.ts +0 -30
  777. package/dist/http/memory-layer-reply.js +0 -24
  778. package/dist/lsp/e2b-bridge.d.ts +0 -57
  779. package/dist/lsp/e2b-bridge.js +0 -139
  780. package/dist/lsp/e2b-manager.d.ts +0 -93
  781. package/dist/lsp/e2b-manager.js +0 -178
  782. package/dist/lsp/lsp-frames.d.ts +0 -14
  783. package/dist/lsp/lsp-frames.js +0 -25
  784. package/dist/lsp/manager.d.ts +0 -129
  785. package/dist/lsp/manager.js +0 -127
  786. package/dist/lsp/types.d.ts +0 -12
  787. package/dist/lsp/types.js +0 -2
  788. package/dist/lsp/ws-transport.d.ts +0 -40
  789. package/dist/lsp/ws-transport.js +0 -210
  790. package/dist/plain-string-record.js +0 -70
package/USAGE.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # 调用方手册(OA / 智能体 / 你自己)
2
2
 
3
3
  > 一句话心智模型:**你只对一个地址发普通 HTTP 请求——LB `http://<host>:8090`;请求体里只写"要做什么"。**
4
- > 工具面与安全策略由服务端 `resolveSpec` 注入,请求体**替换不了**它们(`tools` 不是受理键,传了 400;按请求只能**减**:`allowTools`(7.126.0 起,只留列出的)/ `excludeTools` / `settings.permissions` 的 deny · ask;7.117.0 起 `settings.permissions.allow` 整份交给引擎的 allow 层:表里的调用免问(普通问与没人表态两类,内置工具的写门 / shell 普通问也清),deny / ask 规则与运维的强制类照赢;能力位 `settings.permissions.allowLayer`;7.133.0 起多租部署上按调用方的信任档受理 —— 档只由验过的凭据推,低于 `global` 的调用方带非空 `allow`(或按名点用带 `allowedTools` 的自带技能)⇒ 400,能力位 `settings.permissions.callerTrust`,见下方排障表)。
4
+ > 工具面与安全策略由服务端 `resolveSpec` 注入,请求体**替换不了**它们(`tools` 不是受理键,传了 400;按请求只能**减**:`allowTools`(7.126.0 起,只留列出的)/ `excludeTools` / `settings.permissions` 的 deny · ask;7.117.0 起 `settings.permissions.allow` 整份交给引擎的 allow 层:表里的调用免问(普通问与没人表态两类,内置工具的写门 / shell 普通问也清),deny / ask 规则与运维的强制类照赢;能力位 `settings.permissions.allowLayer`;7.133.0 起多租部署上按调用方的信任档受理 —— 档只由验过的凭据推,低于 `global` 的调用方带非空 `allow`(或按名点用带 `allowedTools` 的自带技能)⇒ 400,能力位 `settings.permissions.callerTrust`,见下方排障表;按名点用**部署 / 中心**的技能不过这道档 —— 那两只清单是运维授权,与 `APPROVAL_GRANT` 同级,点名只是选用)。
5
5
  > `model` 与 `systemPrompt` / `appendSystemPrompt` 是受理键,但 `model` 只能点服务端已配置目录里的名字 / 档位词 / 模型 id(未知 ⇒ 400)。凭据永远不进模型 / 请求体。
6
6
  > 你不"调度 worker",你只发任务;LB + TiDB 自己协调。每个字段都来自当前真实代码。
7
+ > 文中的「契约 §N」「契约附录 X」指服务端 wire 契约的节号(内部记录,不随包),只作查证坐标。
7
8
 
8
9
  ---
9
10
 
@@ -39,8 +40,8 @@ docker compose up --build
39
40
  > + TiDB 悲观事务模式(设不上就拒启),PG 腿 `READ COMMITTED`(同样是设了再验,不问服务器缺省 ——
40
41
  > 把 PG 钉成 RR 或让部署把默认配成 RR/SERIALIZABLE,都会让每条 `FOR UPDATE` 变成 `40001`)。本仓所有
41
42
  > `SELECT … FOR UPDATE` 的单赢家判据都以此为前提。运维读面:
42
- > `GET /v1/diagnostics/wiring` 的 `sqlEngine` 段与 `GET /v1/capabilities` 的 `store.sql` 位(7.106.0 起;此前顶层 `sql`)。判据与升级句见
43
- > `docs/DEPLOY-PREREQS.md`(源码仓内文档,不随 npm 包;下文同名指路同)「事务读语义的结构保证」。
43
+ > `GET /v1/diagnostics/wiring` 的 `sqlEngine` 段与 `GET /v1/capabilities` 的 `store.sql` 位(7.106.0 起;此前顶层 `sql`)。无需运维动作(设不上才拒启):钉的只是本仓自己的连接,不碰库的全局设置(全局配成 READ COMMITTED 的 MySQL 协议库同样被这条会话级覆盖,有意 ——
44
+ > RC 会把「一个事务一个快照」悄悄降成「每条语句各自取快照」)。
44
45
  ```bash
45
46
  IMAGE_REGISTRY=<registry-namespace> \
46
47
  DB_BACKEND=mysql MYSQL_HOST=… MYSQL_USER=… MYSQL_PASSWORD=… MYSQL_DATABASE=sema_server \
@@ -56,7 +57,9 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
56
57
  > 要内存 去掉坐标或 `DB_BACKEND=memory`。本仓两份 compose
57
58
  > 显式带 `DB_BACKEND=mysql`。同版:`DB_BACKEND=mysql|pg` 显式而店打不开,拒句点名 `DB_BACKEND=<引擎>`(修前只有驱动的一句 `connect ECONNREFUSED …`)。
58
59
  > 🔴 **7.119.0 起 `SESSION_BACKEND=auto` 配了坐标而库连不上同样拒启,不再有「auto 内存起服」这条出路**:`auto` = 「配了该引擎的坐标就是那只库(boot 期窗内重试、到窗拒启),没配就是内存」,
59
- > 由配置决定、不由起库成败决定;内存部署写 `DB_BACKEND=memory` 或 `SESSION_BACKEND=memory`。`GET /health` 的 `sessionBackend` 如实说装好的后端(`durable(pg)` / `memory`)。持久化姿态读面:caps `store`(附录 B)。
60
+ > 由配置决定、不由起库成败决定;内存部署写 `DB_BACKEND=memory` 或 `SESSION_BACKEND=memory`。`GET /health` 的 `sessionBackend` 如实说装好的后端种类(`pg` / `mysql` / `local` / `memory`)。持久化姿态读面:caps `store`(附录 B;`store.durability` 三态 = 店活不活得过容器 / Pod 替换,`/health.store.durability` 同值)。
61
+ > 它报的是**参与判定的根里最弱的那只**:数据根(会话店 + 检查点店)、封签钥匙目录(缺省 `<数据根>/sealed-key`)、file 记忆根(只在 file 记忆引擎真接了线时参与;`MEMORY_ENGINE_DIR`,缺省 `AGENT_DATA_DIR`,再缺省 `~/.ai-agent` —— **跟 HOME 走,不跟 `LOCAL_DATA_ROOT`**)。
62
+ > 拒启(`config.store_not_durable`)看数据根,与**独立的**封签钥匙目录(只在写了 `SEALED_KEY_DIR`、停驻审批耐久时:它易失 ⇒ 停驻行活过替换而封它凭据的钥匙不活,拒;出路 = 挂卷 / `DURABLE_APPROVAL=false`);缺省的钥匙目录与 file 记忆根只进读数与起服 warn:接了 file 记忆引擎时**只给数据根挂卷不够** —— HOME 在 emptyDir / 容器可写层上时起得来,但 `/health.store.durability` 报 `ephemeral`(记忆随替换而去);要它报 `durable`,参与判定的根都落到卷上。逐根明细:`GET /v1/diagnostics/wiring` 的 `storeDurability.roots`。
60
63
  > 🔴 **显式 `DB_BACKEND=mysql|pg` 而该引擎的坐标一个都没配 ⇒ 配置期拒启,与 `SESSION_BACKEND` 怎么写无关**(未设 / `auto` / `mysql` / `memory` 同一句拒句,`REQUIRE_PRINCIPAL=true` 亦然):写下 `DB_BACKEND=pg` 就是要这只持久引擎,没有坐标它开不了店。修前 `SESSION_BACKEND=auto`(「坐标不在就不探测」)下这一形静默跑内存、`/health` 照样 200、姿态位照报 `declared: "pg"`。拒句点名缺的坐标键名(`PG_HOST` / `PG_USER` / `PG_DATABASE`;`MYSQL_*` 同形),不回显任何值;出路:补齐坐标;或要无 SQL 的部署写 `DB_BACKEND=local`(单用户文件店)/ `DB_BACKEND=memory`。坐标齐、`DB_BACKEND=local|memory`、不写 `DB_BACKEND` 的形零变化。同族:`MEMORY_ENGINE_BACKEND=pg|mysql` 要绑的池没开而 `DB_BACKEND` 已点名该引擎时,拒句改点名真正开店的条件(持久会话后端 + 必需坐标键),不再说「要 `DB_BACKEND=…`」。
61
64
 
62
65
  > 🔴 7.96.0 起根 `docker-compose.yml` 的 `REMOTE_EXEC`(执行车道)是**必填**:不设 ⇒ `docker compose config` 当场失败(此前静默起成一个没有手的 worker)。单人自用 `REMOTE_EXEC=host`(手落在 worker 容器里);多人 / 生产选隔离车道 `e2b`(配 `E2B_API_KEY`)或 `k8s`(`K8S_SANDBOX_IMAGE`、`K8S_NAMESPACE`〔没有内置缺省〕都是必需件,未设、空串或只含空白一律按缺 ⇒ 起服拒;`K8S_API_URL` 与 `K8S_TOKEN` 也要设;根 compose 透传前两名〔空串透传 = 按缺,要填〕,后两名要在 compose override 里加进服务的 `environment`,或改走随包 chart `deploy/sema-up`)。
@@ -92,8 +95,12 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
92
95
  > 写在 `config.d/mcp.json`,键与中心发布的 `mcp` 域同形,没有另一个工具目录。读之前先判:从 `/` 走到 `config.d`、再到读者会读的每一份文档(符号链接照跟、跟到哪判到哪),
93
96
  > 每一级属主只能是本服务的用户、root 或 `CONFIG_LOCAL_OWNER_UID`(多信的那一个 uid;形错拒启),不得组 / 他人可写(属主可信的粘滞目录如 `/tmp` 除外,但要读的一项缺在这样的目录里也拒),
94
97
  > 文档是常规文件且 ≤ 16 MiB,整条路径链接合计 ≤ 40 跳、不得指向 procfs 一类内核对象。
98
+ > 同一只判官还守另两份「盘上字节决定起什么」的文件:中心配置的盘上 LKG(读回之前;中心慢或断时它就是本服务的配置)与调度共享店(`SCHEDULER_STORE_PATH`,缺省 `<SEMA_CONFIG_DIR>/scheduled_tasks.json`,每次读写之前)——
99
+ > 别人改得动 ⇒ LKG 不读回(`config_lkg_unusable` 一行带同族拒句,env 兜底)、调度店这一次读写整条拒(运维日志 `scheduler_store_untrusted` 带同族拒句);可信属主集同一张(含 `CONFIG_LOCAL_OWNER_UID`)。
95
100
  > 起服不过 ⇒ 拒启(fatal 标题 `[config.dir_untrusted]`;`run-local` 退 2);起服之后才变坏 ⇒ 那一拍 `web_admin_refresh_failed` 带同一句、新字节不读、已应用的配置不动。拒句点名那一个路径、实测值与改法
96
- > (`chmod go-w` / `chown` / `CONFIG_LOCAL_OWNER_UID=<uid>`)。Windows 上属主与模式位不可判:只判结构,另记一次 fail-open 告警。容器形(rootless / userns-remap / ConfigMap / fsGroup)见源码仓文档 DEPLOY-PREREQS 的 7.133.0 节(不随包)。
101
+ > (`chmod go-w` / `chown` / `CONFIG_LOCAL_OWNER_UID=<uid>`)。Windows 上属主与模式位不可判:只判结构,另记一次 fail-open 告警。容器形(按机制推得,尚未逐形在真机容器上实测):rootless Docker / Podman 里宿主用户映射为容器 root,零动作;`--userns-remap` 下宿主文件呈现为未映射 uid(常见 65534),设 `CONFIG_LOCAL_OWNER_UID=65534`,或把配置烤进镜像 / 在 initContainer 里改属主;k8s ConfigMap 卷认(`defaultMode` 别给组 / 他人写位);挂了 `fsGroup` 的 emptyDir 目录组可写 ⇒ 拒,改用 ConfigMap 卷或在 initContainer 里 `chmod -R go-w`。
102
+ > 谁建的根:本服务在根缺席时**自己首建**的目录 —— 数据根、`SEMA_CONFIG_DIR` 声明的配置根、本地配置根,与每一级新建的祖先 —— 一律 `0700`(创建时给的模式;umask 只减位不加位,所以不会比 0700 更宽 —— umask 002 的 Debian / Ubuntu 上也不会建出 0775 而在下一次起服被这一判官拒;umask 连属主位也掩掉的极端值,如 0200,会让目录比 0700 更窄,那是 umask 自己的意思);
103
+ > 别的工具(`mkdir -p`、编排器、壳)建出来的 0775 根照拒,改法同上。**存量**的 0775 根(老版本在 umask 002 上建出来的)判官照拒、拒句给 `chmod go-w`,本服务不替人改一个判官刚拒掉的目录的模式。
97
104
 
98
105
  > 🔴 **本地目录顶替 env 模型表要配置根上的一份确认(7.143.0 起)**:`config.d/models.json` 只要有启用条目就管模型面 —— 采纳之后服务表整张换成目录表。设了 `MODEL_ID`(env 模型表已声明)时,这一换只在配置根(放 `config.d/` 的那个目录)上的
99
106
  > `config.env-models.json` 读作 `{"envModels":"replaced"}` 时发生;缺席 / `{"envModels":"kept"}` / 读不懂 ⇒ 这份目录的模型面被拒(码 `config.catalog_replaces_env_models`,`GET /v1/models` 的 `modelPlane.planeRefusalCode` 与 `/health.modelRefusals.plane` 报它),
@@ -149,8 +156,9 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
149
156
  - **凭据什么时候生效**:网关 key `MODEL_API_KEY`(以及 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)在启动时读一次,**换值 = 重启本服务**
150
157
  —— 没有热应用通道(以前的版本同样如此:这几把 key 只来自启动时的环境变量;引擎 7.51.0 起模型路由的构造配置也只读一次)。
151
158
  目录条目**自己的**凭据(`apiKeyEnv` 点名的变量、`sealedApiKey`)跟着中心 / config.d 的模型面热应用走,不用重启:新起的任务与调用
152
- 用新的一代(在跑的任务用它起跑时那一代)。两处例外:降级壳焊住的目标连凭据身份一起按 `degrade-route` 重启片走;**记忆整理席**的模型在启动时定
153
- (重启生效),它的凭据却跟着热应用 —— 同名条目把端点和凭据一起换掉时,重启之前整理请求会带**新**凭据打**旧**端点,这种换法之后请重启本服务。
159
+ 用新的一代(在跑的任务用它起跑时那一代)。启动时焊死模型的两只座是一条规则:降级壳焊住的目标、**记忆整理席**解到的条目,凭据**身份**随焊住的模型一起冻结
160
+ (`apiKeyEnv` 点名的变量名照旧每次按名现读值 —— 同名轮换不用重启;重贴 `sealedApiKey` / 换变量名 = 新身份),同名条目把端点和凭据一起换掉之后、重启之前,
161
+ 这两只座打的仍是焊住的旧端点、带的是旧一代的凭据(绝不把新凭据发往旧端点);身份或端点变了 ⇒ `/health` 报 `restartRequired`(`degrade-route` / `memory-consolidation`)。
154
162
  - **`agents[].model` 写成内联 Model 对象时,这一只模型的请求带哪条凭据**:本服务只验 `id` 非空,引擎对内联对象原样信任、不查目录;每次请求只发**一条**凭据 —— 凭据词头与部署钥互斥,只发赢家。
155
163
  优先序:这次调用的 per-model 钥(本服务按内联对象的 `name` 去目录找同名条目的 `apiKeyEnv` / `sealedApiKey`)> 内联对象自己 `headers` 里的凭据词头 > 部署网关钥(`MODEL_API_KEY`)。三种构造:
156
164
  ① 内联对象声明了凭据词头(如 `x-team-token`)、没有同名目录条目带自己的钥、部署只有网关钥 ⇒ 内联头赢:这只子代的请求带 `x-team-token`、**不带** `authorization`(网关钥不发,所以要求 `authorization` 的上游会拒这条请求);
@@ -167,6 +175,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
167
175
  brain、带着**它自己的** `apiKeyEnv` 钥匙打到网关。不合 settings-schema 形的条目同律(码 `config.model_entry_invalid`,如非 http(s)
168
176
  的 `baseUrl` —— 此前被「退回部署网关」;读腿逐条丢掉的条目同样留名字,点它照样 400);目录里**每条**都不可用 ⇒ 那一代模型面这一组被拒。
169
177
  🔴 **日志与拒因句里的条目名先过名筛**(7.115.0 起):被拒条目的名字写进启动 / 刷新日志(`web_admin_model_no_brain` / `_entry_invalid` 的 `model`、`web_admin_models` 行的 `unavailable`、缺省 / 档位 / 角色绑定三条 warn 里指向被拒条目的名字)与「缺省模型缺席」「被拒名被增广铸出」两句拒因之前,先问 settings-schema 的 `isReportableRefusalName`(`[A-Za-z0-9._:/@+-]` 闭形、无密钥形、无带 userinfo 的 URL 形)。**不可披露的名只写码**:warn 不带 `model`、改带 `modelNameWithheld: true`(`unavailable` 只列可披露的名并带 `unavailableNamesWithheld` 个数);拒因句里该位写 `(name withheld)`。引擎自己的判决不受影响(显式点它照样拿拒因码);**要在日志里认出是哪一条**,把目录条目名改成名筛的闭形字符集(或按 `code` / `provider` / `fields` 在目录里找)。显式选中不可用条目的 400 回显不在此列(它回显的是调用方自己点的那个名)。
178
+ 🔴 **进程写进 stdout / stderr 的每一行都过同一只秘密形筛**(7.148.0 起):结构化日志行、诊断散文行、fail-open 契约行与致命行出门前都过本服务的脱敏器(与 wire 上的脱敏同一张形表)——名筛放行的凭据形名(`npm_…` 这类过得了闭形字符集的)、上游回显、错误消息里的令牌,在日志里读作 `«redacted:<类>»` 记号,类词(`key` / `gh-token` / `slack-token` / `aws-key`)留着族,同行其余字段照旧;行仍是一条 JSON;地板上真遮掉的凭据计进 `redactions_applied_total`。筛自己抛 ⇒ 结构化行换成固定的 `log_line_withheld`(`reason: "secret_screen_failed"`)、散文行换成 `«redacted:oversized»`,不回落原文(计数 `fail_open_total{tag="server.run-terminal.redact-threw"}`)。不过筛的只有两只非日志输出:`run-local` 的任务结果(stdout)与它的 y/N 问句。引擎(依赖包)在没有通告席的收值口自己打的裸文本、以及 stdio 型 MCP 服务器子进程的 stderr(引擎的传输缺省继承本进程 stderr)不经本服务的出口(见下文「网关密钥」那一句)。
170
179
  🔴 **被拒条目的名字不许被档位 / 别名铸到别的条目上**:不可用条目恰好叫 `sonnet` / `opus` / `pro` / `best` 这类档位词或 CC 别名、
171
180
  而档位表会铸出这个词时,这份候选的模型面整组被拒(码 `config.model_refused_name_minted`,warn `web_admin_model_plane_refused` 点名
172
181
  「被拒名 → 目标条目」)—— 否则显式点它、或绑它的角色,会静默跑在另一条条目的端点与账号上;出路:改名 / 修好条目,或改绑那条档位绑定。
@@ -179,6 +188,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
179
188
  引擎注入的 `<system-reminder>` / `<task-notification>` / `<attachment>` 块,以及壳铸进用户轮的**本地命令回显**(`<command-name>` / `<command-message>` / `<command-args>`、`<local-command-stdout>` / `<local-command-stderr>` / `<local-command-caveat>`、
180
189
  `<bash-input>` / `<bash-stdout>` / `<bash-stderr>` / `<bash-exit-code>`、`<user-memory-input>`)都不算你写的:`/compact` 一类本地命令之后的下一条提交,预览与标题取的是随后那句话,不再是回显;只有回显、没有你的话 ⇒ run / leader 行的预览是空串(任务名折成 `(notification turn)`),会话列表的预览回落到更早的一句、都没有则缺席(`null`)。
181
190
  代价:正文里真的写了这些开标签字面的那一段在预览 / 标题里被剥(只是**提到**这些词的原样保留),原文在会话历史读面完整可见。
191
+ 会话列表的预览就是 run 行那一列(最近一条带你的话的 run;SQL 后端与 `DB_BACKEND=local` 同读,`?q=` 搜任一 run 的这一列):零 run 的会话(fork / 导入 / 同步来的)没有预览,靠标题认;run 中途补的话(steer)不顶替那条 run 的目标。
182
192
  - **装了 prompt 屏的部署,预览与标题只取引擎原样交付的目标**(7.145.0+):部署座的 mod 包或请求的 `settings.hooks` 声明了 `prompt.submit` / `classic.UserPromptSubmit` 的腿,建 run 行时不写预览、续跑输入里的目标留空、fleet 行不带名;
183
193
  目标的受理帧原样送达(不带 `rewrittenBy`)⇒ 那一拍补上预览、fleet 行名并起标题;屏改写过 / 丢弃 / 抛错 ⇒ 这些面一直缺席,标题不自动起 —— 屏前文本不落进 run 行、续跑输入,也不送进标题模型。
184
194
  续跑输入里的目标在这类腿上一直留空,所以 wake 新建的 run 行没有预览、续跑腿的 fleet 行不带名(原样交下也一样)。订 `prompt.*` / `classic.*` 通配的只观察型运维 mod 也被算成屏(判据读声明、与引擎的通配语义一致),这类部署上只回结果的腿(`verify` / `cascade`)与 wake / 续跑腿同样缺席,其余腿在受理那一拍补上。
@@ -201,7 +211,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
201
211
  MODEL_FIRST_BYTE_TIMEOUT_MS=8000 # fetch() → 响应头的等待(7.103.0 起;取代退役的 MODEL_CONNECT_TIMEOUT_MS —— 设了旧键即拒启)。
202
212
  # 不设 = core 缺省(本仓不抄那个数,见 core 7.33.x MIGRATION);窗上另加 core 按 body 大小给的上传宽限,
203
213
  # 到点 core 自有一次加宽重试(不吃重试上限;配了备用网关也一样,之后才切备 —— server 不铸 firstByteRetries)、终局句点名本键。自托管慢网关(prefill 在响应头之前)
204
- # 按排队 + prefill 留足;failover 拓扑须显式设(未设时预算短的调用切不到备;见 DEPLOY-PREREQS)
214
+ # 按排队 + prefill 留足;failover 拓扑须显式设(未设时预算短的调用切不到备)
205
215
  MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # 响应头之后迟迟不吐第一个 delta(网关 hang);reasoning 的首个 thinking 也算首 token
206
216
  # 缺省 600000(7.71.0 起,原 120000):自托管后端长 prefill 下首字合理地要等几分钟,
207
217
  # 120s 会把一次正常的慢启动判成失败去重试。快失败部署显式设回 120000
@@ -299,8 +309,9 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
299
309
  # 不想要这两类触发就显式设回 rate_limit,breaker_open。拼错词 boot 拒启(不静默忽略)。
300
310
  ```
301
311
  - 🔴 **思考档两键自 7.57.0 起是闭集拒启族**(此前拼错**静默丢**):`MODEL_DEFAULT_THINKING` 收
302
- `minimal|low|medium|high|xhigh|max`(llm-core `ThinkingLevel` 六档)加哨兵词 `off`(= 显式「本部署
303
- 不设模型级默认档」,与不设同义);`MODEL_REASONING_EFFORT_LEVELS` 收这六档的**子集**(CSV,声明
312
+ `minimal|low|medium|high|xhigh|max`(llm-core `ThinkingLevel` 六档)加 `off`(7.148.0 起 = 显式「本部署
313
+ 的模型缺省**不思考**」,铸进引擎 `Model.defaultThinking: "off"`:没点档的请求带显式「关」(网关有关思考拼法处写那只关键),缺省开思考的网关(如 Qwen chat template)也就不思考;显式点了档的请求照点的档;7.147.x 及以前它是
314
+ 哨兵词、与不设同义 —— 想要「不设、交给模型自己的缺省」请**不设**这根旋钮);`MODEL_REASONING_EFFORT_LEVELS` 收这六档的**子集**(CSV,声明
304
315
  「本 openai 端点吃得下哪些档」,高档 clamp-down 不 422;`off` 不是档位,写在这里拒启并点破)。
305
316
  未知词 **boot 拒启并点名坏词**——与 `MODEL_DEGRADE_ON` 同律:`hight` 此前静默落成「无默认档」、
306
317
  `med,high` 此前静默窄成只剩 `high` 一档,而这根旋钮的全部用途就是宣告高档可用,静默落空 = 高档
@@ -336,7 +347,7 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
336
347
  - **成本天花板要有价才咬得住**:`MAX_TASK_COST_USD` / `MAX_PRINCIPAL_COST_USD` 量的是 `costUsd`,未定价的模型算不出成本 ⇒ 这两道闸**永不触发**。目录里一只带价模型都没有却配了天花板时,boot 会打一条 `cost_ceiling_without_pricing` 告警(不拒启);token 轴的 `MAX_TASK_TOKENS` 不受影响。
337
348
  - 成本计量**自动开**:从 config 的 `model.cost`(per-1M 绝对 USD)注入 `pricing`,core 算出权威的整数 `costMicroUsd`(避免浮点累计误差)。`/metrics` 新增:`model_cost_micro_usd_total{model}`(覆盖所有 brain 调用=主任务+异步+委派子任务的总花费)、`brain_first_token_ms`(网关 hang 早警)、`brain_call_latency_ms`、`tool_calls_total{name,ok}`、`budget_exceeded_total{code}`、`cost_quota_rejected_total`、`degraded_total{reason}`(1.40 降级)。
338
349
  - **近预算降级 vs 硬闸**:降级(`MODEL_DEGRADE_TO`,到 `atCostFraction` 切便宜模型)是**撑长**预算、任务仍完成(出口质量下降、发 `task.degraded` 事件可告警);硬闸(`maxCostUsd` 全额)仍在,切了便宜模型还超全额 → `limits.max_cost_exceeded` 停。
339
- - **`METRICS_TOKEN`**(可选,只读):设了它,`GET /metrics`+`/metrics/summary` 接受**它或** `SERVICE_AUTH_TOKEN`。都没配时 `/metrics` 族只在 dev-open 部署上免凭据(见 §1 dev-open 那一段:
350
+ - **`METRICS_TOKEN`**(可选,只读):设了它,`GET /metrics`+`/metrics/summary` 接受**它或** `SERVICE_AUTH_TOKEN`。都没配时 `/metrics` 族只在 dev-open 部署上、对不带主体头的请求免凭据(见 §1 dev-open 那一段:
340
351
  装了中心鉴权桥、配了 `TRACE_TOKEN` 或开了 `REQUIRE_PRINCIPAL` 的部署上匿名 `401` —— 7.132.0 起,此前前两形照开;`/metrics/plan-cache` 按主体分键,匿名可读 = 名册外泄)。**全 fleet 设同一个值** → 管理端(sema-web,规划名 sema-admin)用**一个** token 拉所有 worker 的指标,**无需持有各 worker 的全权 `SERVICE_AUTH_TOKEN`**(不破坏 secret 边界)。即使外泄也只暴露指标(只读)。
341
352
  - **`GET /metrics/summary`**(token-gated,同 `/metrics`):`/metrics` 的**精炼 JSON**——`{model, runsActive, tasks{status}, tokensTotal, costUsd, costUsdByModel, taskDurationAvgSec, brainFirstTokenAvgMs, brainCallAvgMs, cacheHitRateAvg, rateLimited, costQuotaRejected, budgetExceeded, limitCeilingAfterAnswer, degraded, cascade, verifications, toolErrors, httpErrorsByClass, httpErrorsByRoute}`(**7.126.0 起没有 `councilRuns`** —— 它数的 code-review 多镜头评审团随引擎 7.49.0 删掉团队运行时一并删除,Prometheus 的 `council_runs_total` / `council_tokens` 同删;按 v1 形解析这张表的看板升级前先把这一键改成可缺)(`httpErrorsByClass` / `httpErrorsByRoute` 自 **7.87.0**,additive:窗内 HTTP **失败**请求按状态码类聚合(键只有 `4xx` / `5xx`,2xx/3xx 不进;⚠️ `4xx` 含本仓给「客户端提前断连」铸的 `499`,SSE 长连上是常态)+ 失败最多的前 10 条 route 标签(`routeLabel` 低基数形,id 折成 `:id`,未命中路由表的落 `other`)—— 这两格是**不装 Prometheus 的部署**看见一场 404 风暴的唯一地方;两键**恒在场**,零失败时是 `{}`,不用缺席表示零)(`limitCeilingAfterAnswer` 自 7.71.0,按 `origin` 分桶的「答后撞顶」计数;与 `budgetExceeded` **总体不同** —— 前者每条引擎 run 都过,后者只由 HTTP 提交腿递增 —— 不要无条件相加)。给**轻量 fleet 看板**用(管理端 sema-web 的 fleet 页按 worker 拉、渲染卡片、按轮询算速率;**不用 Prometheus/Grafana**)。counter 是累计值、histogram 报均值。
342
353
  - 预算闸是 core 强制的:`maxCostUsd` pre-call 估算(没花钱就拒)+ 流中途取消 + turn 边界(`limits.max_cost_exceeded`);`maxTokens` 超 → `limits.max_tokens_exceeded`(core 5.8.0 起 `budget.*` 前缀退役)。**review 网关**建议设 `MAX_TASK_COST_USD` 防单任务烧掉共享云 key。
@@ -475,7 +486,17 @@ MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
475
486
  存量中心配置里的 `roles.team` / `roles.synthesize` 由 settings-schema 的一次性迁移搬走,side query 带 `modelRole` 表外词(含 `advisor`)⇒ **400**
476
487
  `request.field_invalid`(不再回落主模型;带了 `model` 也一样拒)。要给委派子代配模型就绑 `subagent`,主任务绑 `default`。
477
488
  - 调用方可在 `objective` 里 `@<模型名>` 选模型,**只认已配置的名单**(`MODEL_ID` / `MODEL_CHEAP_ID`,或目录条目)——注入不了 `baseUrl`/`apiKey`;无 `@` → 默认模型;`@` 点中一条已发布但本部署不可用的目录条目 ⇒ 400 `request.model_unavailable`。
478
- - **`GET /v1/models`**(带 Bearer)→ `{models:[{name,id,provider,reasoning,vision,…}],default,defaultModel,droppedEntries}`,只含名字/能力、**不含密钥** —— 消费方(如 OA)拿它做 `@` 自动补全下拉的数据源;`reasoning` / `vision` 两列是**三态**:声明了给布尔,**未声明 = 键缺席**(7.131.0 起;env 部署不设 `MODEL_REASONING` / `MODEL_VISION` 就是未声明,没有 `true` 缺省 —— 需要思考的模型显式写 `MODEL_REASONING=true`,纯文本模型写 `MODEL_VISION=false`;缺席时引擎对图收、对思考档不发),契约 §A.1d;`droppedEntries`(7.123.0 起恒在场,零条 = `[]`)是本代目录里被读腿逐条丢掉的条目(下标 + 闭集原因词 + 可披露身份),契约 §A.1d。 7.126.0 起另有**可选**顶层键 `candidateRejected: { domains, at, source }`:最近一次配置候选被候选判官整拒时在场(`domains` = 被判官整拒点名的配置域名 —— 判形失败的域、本机 `config.d` 里读不出的文档所在的域、带事实的退役域,`source` = `local` 本机 `config.d` / `remote` 中心,`at` = 判下它的时刻)—— 在场就说明这份目录是回落的那一份(整份候选没收,候选里的模型一个都不在),真因域在 `domains` 里;后来的候选被判 clean,或配置源回到已应用的那一代(`config.d` 原样改回 / 中心退回那一版,日志 `config_source_back_to_applied`)即缺席;dry-run 姿势(web-admin 腿 compare-only)恒缺席(拒判只走日志)。**缺席不等于配置一定干净**(也可能是更早的 server)。只含域名 / 时刻 / 源形态,拒因句与文件路径只在引擎日志 `config_candidate_rejected` 里,契约 §A.1d。 7.133.0 起另有**恒在场**的顶层键 `modelPlane: { servedTable, catalog, refusalCode?, gap? }` —— 壳问 server,不猜:`servedTable`(`"env"` | `"catalog"`)= 此刻服务哪张模型表;`catalog`(`"absent"` | `"adopted"` | `"refused"`)= 最近一份目录候选在两道判官手里的结局(`refused` = 候选判官整拒,或模型面组拒;`adopted` 说的是「没有被判官整拒」,不是「最新候选已落地」,后者问 `/health` 的世代账;没有 LKG 时服务的是 env 表,有 LKG 时目录表仍在服,谁在服归 `servedTable`;逐条被拒的条目不算整拒);`refusalCode` / `gap` 是起服路由判官(`judgeServedModelRoute`)对当前 config 的判决,通过 ⇒ 两键缺席(进程起来了,正常缺席;自建装配 / 不拒启的入口可能在场),不带拒因句。只在起服后可答,没有起服前的预判口;新键追加在应答最末。契约 §A.1d。 7.143.0 起 `modelPlane` 段末多一个条件键 `planeRefusalCode` —— 最新到达的那份目录候选被模型面组拒时在场、值 = 那一拒的码(`catalog:"refused"` 而它缺席 ⇔ 最新到达的候选被候选判官整拒,看 `candidateRejected`);`catalog` 的 `refused` 读同一份事实:更新的候选到了、没走到模型面那一判(被整拒,或过了判官却在换装时被拒)时不再挂着旧候选的拒。契约 §A.1d。 7.135.0 起另有**恒在场**的顶层键 `aliases: [{ name, atMentionable? }]` —— `model` 被认、却不是 `models` 行名的每一个词:`default` 别名(服务表有缺省模型才在)、按部署档位绑定铸出的档位词 / CC 别名 / `best`、以及被认的上游 id 写法;成员资格由受理口的判官逐词定(服务端不写词表)。判受理一条:`model` 的值是某一行或某一别名的 `name`(大小写敏感),且那一项的 `atMentionable` 不是 `false` ⇒ 过模型门;其余 ⇒ 400(不要再拿行的 `id` 推)。`atMentionable` 与行同一只判定、同一条在场律(`atMention` 不是 `all` 才出键)。零条 = `[]`,缺席 = 更早的 server(档位词 / `default` 判「说不清」)。契约 §A.1d。
489
+ - **`GET /v1/models`**(带 Bearer)→ `{models:[{name,id,provider,reasoning,vision,…}],default,defaultModel,droppedEntries}`,只含名字/能力、**不含密钥** —— 消费方(如 OA)拿它做 `@` 自动补全下拉的数据源;`reasoning` / `vision` 两列是**三态**:声明了给布尔,**未声明 = 键缺席**(7.131.0 起;env 部署不设 `MODEL_REASONING` / `MODEL_VISION` 就是未声明,没有 `true` 缺省 —— 需要思考的模型显式写 `MODEL_REASONING=true`,纯文本模型写 `MODEL_VISION=false`;缺席时引擎对图收、对思考档不发),契约 §A.1d;`droppedEntries`(7.123.0 起恒在场,零条 = `[]`)是本代目录里被读腿逐条丢掉的条目(下标 + 闭集原因词 + 可披露身份),契约 §A.1d。 7.126.0 起另有**可选**顶层键 `candidateRejected: { domains, at, source }`:最近一次配置候选被候选判官整拒时在场(`domains` = 被判官整拒点名的配置域名 —— 判形失败的域、本机 `config.d` 里读不出的文档所在的域、带事实的退役域,`source` = `local` 本机 `config.d` / `remote` 中心,`at` = 判下它的时刻)—— 在场就说明这份目录是回落的那一份(整份候选没收,候选里的模型一个都不在),真因域在 `domains` 里;后来的候选被判 clean,或配置源回到已应用的那一代(`config.d` 原样改回 / 中心退回那一版,日志 `config_source_back_to_applied`)即缺席;dry-run 姿势(web-admin 腿 compare-only)恒缺席(拒判只走日志)。**缺席不等于配置一定干净**(也可能是更早的 server)。只含域名 / 时刻 / 源形态,拒因句与文件路径只在引擎日志 `config_candidate_rejected` 里,契约 §A.1d。 7.133.0 起另有**恒在场**的顶层键 `modelPlane: { servedTable, catalog, refusalCode?, gap? }` —— 壳问 server,不猜:`servedTable`(`"env"` | `"catalog"`)= 此刻服务哪张模型表;`catalog`(`"absent"` | `"adopted"` | `"refused"`)= 最近一份目录候选在两道判官手里的结局(`refused` = 候选判官整拒,或模型面组拒;`adopted` 说的是「没有被判官整拒」,不是「最新候选已落地」,后者问 `/health` 的世代账;没有 LKG 时服务的是 env 表,有 LKG 时目录表仍在服,谁在服归 `servedTable`;逐条被拒的条目不算整拒);`refusalCode` / `gap` 是起服路由判官(`judgeServedModelRoute`)对当前 config 的判决,通过 ⇒ 两键缺席(进程起来了,正常缺席;自建装配 / 不拒启的入口可能在场),不带拒因句。新键追加在应答最末。 7.149.0 起行上另有**可选** `contextWindowSource`(`"declared"` | `"assumed"`,与同一行 `contextWindow` 同进退):`assumed` = 这只模型的窗没人声明(目录条目没写 `contextWindow`,部署也没设 `MODEL_CONTEXT_WINDOW`;cheap 槽另看 `MODEL_CHEAP_CONTEXT_WINDOW`),本服务按假定值(131072)算压缩触发点 —— 真实窗口更小的模型会先撞上限,更大的模型压缩会提前;起服 / 目录落地时这样的模型各一行 warn `model_context_window_assumed`(点名模型、假定值、去哪声明;每个模型每进程一次)。要消掉它就声明真实窗口,契约 §A.1d。引擎起来之前要同一格:见下面「起服前的模型面读数」。契约 §A.1d。 7.143.0 起 `modelPlane` 段末多一个条件键 `planeRefusalCode` —— 最新到达的那份目录候选被模型面组拒时在场、值 = 那一拒的码(`catalog:"refused"` 而它缺席 ⇔ 最新到达的候选被候选判官整拒,看 `candidateRejected`);`catalog` 的 `refused` 读同一份事实:更新的候选到了、没走到模型面那一判(被整拒,或过了判官却在换装时被拒)时不再挂着旧候选的拒。契约 §A.1d。 7.135.0 起另有**恒在场**的顶层键 `aliases: [{ name, atMentionable? }]` —— `model` 被认、却不是 `models` 行名的每一个词:`default` 别名(服务表有缺省模型才在)、按部署档位绑定铸出的档位词 / CC 别名 / `best`、以及被认的上游 id 写法;成员资格由受理口的判官逐词定(服务端不写词表)。判受理一条:`model` 的值是某一行或某一别名的 `name`(大小写敏感),且那一项的 `atMentionable` 不是 `false` ⇒ 过模型门;其余 ⇒ 400(不要再拿行的 `id` 推)。`atMentionable` 与行同一只判定、同一条在场律(`atMention` 不是 `all` 才出键)。零条 = `[]`,缺席 = 更早的 server(档位词 / `default` 判「说不清」)。契约 §A.1d。
490
+ - **起服前的模型面读数**(7.148.0 起):引擎起来之前要 `modelPlane` 同一格(这份配置会服务哪张表、目录候选的结局),跑包里的无监听探针 ——
491
+ ```bash
492
+ node <@sema-agent/server 包根>/dist/model-plane-probe.js # 子路径 @sema-agent/server/model-plane-probe;env 与工作目录 = 将要起的那只引擎的
493
+ # stdout 恰一行,例:{"servedTable":"catalog","catalog":"adopted"}
494
+ ```
495
+ 它跑起服的前两段(配置面 + 配置源 / 候选判官 / 模型面装配 + 模型表的跨域不变量)到模型表定形即停:不开店、不监听,与起服后 `GET /v1/models` 读同一只读口,
496
+ 同一份配置下与起服第 2 段末那一格字节同(起服拉取超过 `CONFIG_BOOT_FETCH_BUDGET_MS` 或预算写 0 时,答的是那次拉取落地之前的回落读数,stderr 有 `config_boot_fetch_deferred`,
497
+ 探针不等迟到的那一份);日志全在 stderr。退出码 `0` = 答案在 stdout;非 `0` = 起服会在这两段拒,stderr 是与起服同一条 `fatal` 行。
498
+ 动作与起服前两段相同(配置根上的一次性迁移 —— 其中「目录顶替 env 表」的决定本身就是模型面的输入 —— 、`SEMA_CONFIG_DIR` 不在就建、封签钥匙保管店与提示词店的目录;
499
+ 远端配置源另有中心拉取、LKG 落盘、执行公钥上报、文件形凭证的续签,已发出的续签落盘之后才退),所以在本会起服的地方调;一台引擎已经在服就读它的 `GET /v1/models`。
479
500
  - 不设 `MODEL_CHEAP_ID` → 所有角色 = 主模型(行为不变)。
480
501
  - `MODEL_CHEAP_ID` 写成与 `MODEL_ID` **同一个 id**(7.104.0 起)→ 那就是主模型本身:不铸 cheap 槽,所有角色 = 主模型,起服一行
481
502
  `config_env_entry_cheap_same_as_main`(同时设的其余 `MODEL_CHEAP_*` 键一并不生效)。要给同一只上游 id 第二种形(另一个价 / 思考 /
@@ -548,7 +569,7 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
548
569
  它的同步身份 = `MEMORY_SYNC_URL` 的规范形(小写协议与主机、无缺省端口、路径去尾斜杠)。中心用 `MEMORY_SYNC_SERVE_IDENTITY` 声明同一个值(能力位 `memorySyncServe.identity` 逐字是它),
549
570
  只服务点名这一身份的轮(不是规范形 ⇒ 400 `request.field_invalid`;别的身份 ⇒ 409 `config.memory_sync_target_mismatch`,零读零写;没声明 ⇒ 501 `feature.memory_sync_serve_disabled`)。
550
571
  中心侧同步口是 `POST /v2/memory/sync/:scope`;游标绑中心的**化身**(每个层分区一只,分区第一次被同步读到时出生、存在中心库):中心被清空重建、或游标是升级前写下的 ⇒
551
- 中心在写之前拒这一轮,本机这一层停驻(同其余停驻,出路 = 删这一层的游标文件)。中心从备份恢复之后,删掉 `agent_memory_engine_sync_partition` 的行再起服(轮换化身,见 DEPLOY-PREREQS)。
572
+ 中心在写之前拒这一轮,本机这一层停驻(同其余停驻,出路 = 删这一层的游标文件)。中心从备份恢复之后,删掉 `agent_memory_engine_sync_partition` 的行再起服(轮换化身)。
552
573
  壳在把会话推上云之前调 `POST /v1/sessions/:id/memory/sync`(体 `{ target }` = 目标引擎的 `memorySyncServe.identity`;能力位 `sessionMemorySync`),拿到引擎的同步报告:
553
574
  这个会话读的每一层一个结局(`synced` / `incomplete` / `refused` / `failed`),或整轮 `refused`(目标不是本机腿到达的那台 / 会话有在飞 run,或读完它的记忆观测之后又受理了 run / 会话记录前进了)。
554
575
 
@@ -573,12 +594,12 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
573
594
  | `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS` | `10000`(`[500, 600000]`) | 目录取不到之后的退避窗(只用于取数失败臂,不用于负结果) |
574
595
  | `MEMORY_DELEGATION_EVIDENCE` | 缺省(=引擎自缺省 `static-face`) | **委派臂的证据标准**(core 5.45.0 起)。`static-face`=今日行为:委派的 attestation 缺失/未知**且**静态工具面够得着外部内容 ⇒ 标记本会话记忆为已污染(可能性即暴露)。`attested-only`=**只**豁免那一条静态面标记,且**真的豁免了一次**才响亮通告(`engine_notice` 的 `memory.delegation_static_mark_waived`,每个 prepared leg **至多**一次);送达 `external` attestation 照标、非委派的污染类工具照标、委派工具自身被分类为污染的照标。⚠️ 通告**不是 leg 计数器**:不含委派的 leg、污染面没挂载的 leg、送达了 attestation 的委派都不发 —— 看不到通告的常态含义是「没有可豁免的事」,不是「配置坏了」。坏值**启动期拒**。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此放松部署)。⚠️ **已接受的代价**:`attested-only` 下**后台**子代的真实外部接触不标记本会话(其内容经 TaskOutput / 任务通知注入 / AgentTranscript 摘要回流,三条都不带 attestation);异常收尾的前台子代同理。已标记的会话永不回滚清洗。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.delegationEvidence`(`null`=本部署未设,引擎自缺省) |
575
596
  | `MEMORY_PROVENANCE` | 缺省(=引擎自缺省 `carry`) | **记忆 provenance 总开关**(core 5.46.0 起)。一个会话被判为**已暴露**之后,它的记忆写怎么处置。`carry`=引擎缺省:**普通**记忆写照常提交,但带上引擎铸的 `origin` 标记(随条目走后端/同步/导出包);指令形文件(feedback / pinned / triggers / applies-when)仍被扣下隔离;派生索引的会话散文照回滚;内容扫描门原样跑(**标记不是豁免**)。`off`=此前的行为:不铸 origin 标记,已暴露会话的 harvest **一条都不收**(整体隔离候人审)。⚠️ 已提交的标记在编辑时照样带下去 —— `off` 停的是**铸**,从不抹掉已记的事实。与 `MEMORY_DELEGATION_EVIDENCE` **正交**(四种组合全合法):那一键决定**什么时候**判会话为已暴露,本键决定判了之后**对写做什么**。坏值**启动期拒**(`OFF`/`false`/`none` 这类「看起来像关掉」的写法一律不折成缺省 —— 静默回默认 = 一台自认为「暴露会话零收录」的部署其实在照常收录)。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此改姿态);**不冻进 checkpoint**(resume 腿跟当前部署配置走)。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.provenance`(`null`=本部署未设,引擎自缺省) |
576
- | `MEMORY_CAPTURE_POLICY` | 缺省(=引擎自缺省 `open`) | **记忆采集 opt-out 的部署姿态**(core 7.0.0 起)。任务可带 `memory.capture:"off"` 声明「本会话不进长期记忆」(一次性,resume 恒不采集,无反向拼写),本键决定怎么裁那条声明:`open`=按面值生效,**只有**该 principal 的显式 `false` verdict 拒(403 `memory.capture_optout_denied`),resolver 故障**放行**+披露〔授权表读故障 `memory_optout_grant_resolve_failed`、中心拉取故障 `runtime_caps_resolve_failed` 各一行 warn;center-only 源上中心健康却省略 `allowMemoryOptOut` 同样放行,但**零运维行**(有本地授权表时 verdict 由它显式答,不缺席);授权表故障那一行之后回落到中心那一层的结果〕(隐私轴 fail-safe:错拒=记了用户明说不要记的会话,不可逆);`governed`=verdict **必答**(混合车队:部分用户强制留存),故障/缺席**拒跑**(fail-closed);`capture-required`=全体声明拒(一行配置)。verdict 源**两条**(析取):①SQL 店后端上的授权表 `memory_optout_grant`(三层折叠:per-principal 行 → 部署缺省行 → 代码缺省 allow;管理面 `GET|PUT /v1/admin/memory-optout`(PUT=部署缺省行)· `PUT|DELETE …/:principal`,operator-only,能力位 `memoryOptOutGrant`,零缓存写后即生效);②非 dry-run 的 web-admin 在该 principal 的 entitlement caps 里发 `allowMemoryOptOut`。🔴 **`governed` 而两源皆无 ⇒ 启动期拒启**(无源 = 每个声明都被拒且没人知道为什么;接 `DB_BACKEND=mysql|pg`、接非 dry-run center,或改姿态)。⚠️ **center-only 源的语义**(只配了 center、没有 SQL 授权表):`allowMemoryOptOut` 在 entitlement schema 里是**可选键** —— center 不发它 = 该 principal 的 verdict **缺席** = `governed` 下每次 opt-out 恒拒(403 `memory.capture_optout_denied`),而 worker **照常起动**;这一形部署级证不死(per-principal 授权只有运行期才知道),所以 server 两处响亮:启动期一行 `memory_capture_governed_center_only_source`、运行期首次真缺席一行 `memory_capture_governed_verdict_absent`(只在 `governed` 挂;每进程每成因一次,成因两员:无主体的匿名 / 系统 run `anonymous_lane`、具名主体而 center 没发该键 `center_verdict_absent`;纯披露不改判)。要确定性 verdict 就接 SQL 授权表(它三层折叠后恒答显式 boolean)。坏值**启动期拒**(`Governed`/`required`/`off` 一律不折成缺省:两种姿态的故障极性**相反**)。**部署席 only**;**不冻进 checkpoint**。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。记录载体(core 7.0.2 起):SQL 后端(`DB_BACKEND=mysql\|pg`)上记录落 `session_capture_optout` 表(跨副本存活,随中央 schema 自建零旋钮)——**远端车道(e2b/k8s)的 `capture:"off"` 声明自此真持久**;`DB_BACKEND=local` 自 7.71.0 起在记忆引擎接线时供 core 的控制面文件三腿(钉在本部署记忆引擎那只控制面目录上,按记忆平面取用)⇒ 单机默认部署(`CONFIG_PROVIDER=local`、`REMOTE_EXEC` 未设)上声明与中途翻转 verb 从 409 转为真受理(此前该 409 在这一形上是结构性的:core 判「远端」看的是执行环境的接口形,host 车道同被判 remote);记忆面暗的部署本席位缺席而 core 也不走采集腿。今天仍答 409 `config.memory_capture_unsupported` 的只剩把记忆引擎接了却没给任何店的自建装配形。用户面四口:提交键 `memoryCapture:"off"`(声明形)· `POST /v1/runs/:id/memory/capture-optout`(live 中途翻转,owner 门)· `POST /v1/sessions/:id/memory/erase`(所有者自助抹除,select 硬限本会话)· `GET /v1/sessions/:id/memory-status`(状态读面,owner 门,五可选键诚实缺席)。契约全文 `docs/ASSISTANT-WIRE-CONTRACT.md` §12 |
597
+ | `MEMORY_CAPTURE_POLICY` | 缺省(=引擎自缺省 `open`) | **记忆采集 opt-out 的部署姿态**(core 7.0.0 起)。任务可带 `memory.capture:"off"` 声明「本会话不进长期记忆」(一次性,resume 恒不采集,无反向拼写),本键决定怎么裁那条声明:`open`=按面值生效,**只有**该 principal 的显式 `false` verdict 拒(403 `memory.capture_optout_denied`),resolver 故障**放行**+披露〔授权表读故障 `memory_optout_grant_resolve_failed`、中心拉取故障 `runtime_caps_resolve_failed` 各一行 warn;center-only 源上中心健康却省略 `allowMemoryOptOut` 同样放行,但**零运维行**(有本地授权表时 verdict 由它显式答,不缺席);授权表故障那一行之后回落到中心那一层的结果〕(隐私轴 fail-safe:错拒=记了用户明说不要记的会话,不可逆);`governed`=verdict **必答**(混合车队:部分用户强制留存),故障/缺席**拒跑**(fail-closed);`capture-required`=全体声明拒(一行配置)。verdict 源**两条**(析取):①SQL 店后端上的授权表 `memory_optout_grant`(三层折叠:per-principal 行 → 部署缺省行 → 代码缺省 allow;管理面 `GET|PUT /v1/admin/memory-optout`(PUT=部署缺省行)· `PUT|DELETE …/:principal`,operator-only,能力位 `memoryOptOutGrant`,零缓存写后即生效);②非 dry-run 的 web-admin 在该 principal 的 entitlement caps 里发 `allowMemoryOptOut`。🔴 **`governed` 而两源皆无 ⇒ 启动期拒启**(无源 = 每个声明都被拒且没人知道为什么;接 `DB_BACKEND=mysql|pg`、接非 dry-run center,或改姿态)。⚠️ **center-only 源的语义**(只配了 center、没有 SQL 授权表):`allowMemoryOptOut` 在 entitlement schema 里是**可选键** —— center 不发它 = 该 principal 的 verdict **缺席** = `governed` 下每次 opt-out 恒拒(403 `memory.capture_optout_denied`),而 worker **照常起动**;这一形部署级证不死(per-principal 授权只有运行期才知道),所以 server 两处响亮:启动期一行 `memory_capture_governed_center_only_source`、运行期首次真缺席一行 `memory_capture_governed_verdict_absent`(只在 `governed` 挂;每进程每成因一次,成因两员:无主体的匿名 / 系统 run `anonymous_lane`、具名主体而 center 没发该键 `center_verdict_absent`;纯披露不改判)。要确定性 verdict 就接 SQL 授权表(它三层折叠后恒答显式 boolean)。坏值**启动期拒**(`Governed`/`required`/`off` 一律不折成缺省:两种姿态的故障极性**相反**)。**部署席 only**;**不冻进 checkpoint**。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。记录载体(core 7.0.2 起):SQL 后端(`DB_BACKEND=mysql\|pg`)上记录落 `session_capture_optout` 表(跨副本存活,随中央 schema 自建零旋钮)——**远端车道(e2b/k8s)的 `capture:"off"` 声明自此真持久**;`DB_BACKEND=local` 自 7.71.0 起在记忆引擎接线时供 core 的控制面文件三腿(钉在本部署记忆引擎那只控制面目录上,按记忆平面取用)⇒ 单机默认部署(`CONFIG_PROVIDER=local`、`REMOTE_EXEC` 未设)上声明与中途翻转 verb 从 409 转为真受理(此前该 409 在这一形上是结构性的:core 判「远端」看的是执行环境的接口形,host 车道同被判 remote);记忆面暗的部署本席位缺席而 core 也不走采集腿。今天仍答 409 `config.memory_capture_unsupported` 的只剩把记忆引擎接了却没给任何店的自建装配形。用户面四口:提交键 `memoryCapture:"off"`(声明形)· `POST /v1/runs/:id/memory/capture-optout`(live 中途翻转,owner 门)· `POST /v1/sessions/:id/memory/erase`(所有者自助抹除,select 硬限本会话)· `GET /v1/sessions/:id/memory-status`(状态读面,owner 门,五可选键诚实缺席)。契约全文是内部记录(§12) |
577
598
 
578
599
  ⚠️ **拒启动**:多租户 + 记忆面点亮 + `projects[].defaultScopes` 里有 org 键 + 目录源缺席 ⇒ 启动报错
579
600
  并点名 projectId(该部署的每个此类请求都会在 prepare 期整拒,响亮拒启动比静默全拒服务诚实)。
580
601
  ⚠️ **回滚脚枪**:回滚到「无 web-admin」模板前先清掉残留的 `MEMORY_ORG_DIRECTORY_JSON` —— 否则
581
- 它作为 operator 自证通道**复活旧授权**(详见 `docs/DEPLOY-PREREQS.md`)。
602
+ 它作为 operator 自证通道**复活旧授权**。
582
603
 
583
604
  **可选 — 模型级联 cascade(core 1.45,opt-in)**
584
605
  ```bash
@@ -586,30 +607,40 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
586
607
  MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheapest→strongest;不设=cascade 不可用
587
608
  ```
588
609
  - 结果带 `cascadeOutcome:"passed"|"exhausted"` + `escalated`/`finalRung`/`attempts[]`。`/metrics` 加 `cascade_total{outcome}`。
610
+ - 梯子的每一级在引擎为它现铸的会话里冷跑,不在提交的会话上取会话;提交的会话只是梯子的宿主(停驻卡记在它名下)。与 `requireExistingSession:true` 同体照跑(7.148.0 起;更早的版本这一形每一级都被拒):会话不在 ⇒ 受理即 404 `resume.session_not_found`(不登记这只 id);受理之后、执行之前被删 ⇒ 执行起点 404 同码,一级都没起(带 run 店的部署 —— 三种出厂后端都带;嵌入装配不带 run 店时执行起点不判)。
589
611
  - **每档用自己的上游 key**:从配置控制面模型的 `apiKeyEnv`(env 变量**名**,非密钥值)或 `sealedApiKey`(密文,见 §9.10)解析——同一网关下不同模型/账号各用各的 key,两者都没配的模型回落到网关 key(`MODEL_API_KEY`;sealed 开不了的模型**不**回落,见 §9.10)。`baseUrl` 仍由 brain 层统一持有(非目标)。启动日志 `perModelKeys=N` 报有几个模型带了自己的 key。
590
612
 
591
613
  **嵌入形契约(1.309+;桌面宿主把引擎作为依赖内嵌启动的正门)**
592
614
  - 入口:`import "@sema-agent/server/main"`(exports 正门;此前宿主只能用 node_modules 路径字符串定位
593
615
  `dist/main.js`,那是未承诺的内部路径)。语义承诺三条:**import 即 boot**(副作用模块形,无需调用);
594
616
  **不读 `process.argv`**(宿主可用 argv 传自己的标记,`ps` args 判据不受干扰);`./package.json` 可读。
595
- - **库形嵌入**(包根 `.` 导出:`loadConfig` + `createHttpServer` + 自建 `StoreBackend` / `ServiceDeps`):`loadConfig()` 之后调
617
+ - **库形嵌入**(包根 `.` 导出:`loadConfig` + `createHttpServer` + 自建 `StoreBackend` / `ServiceDeps`):先调一次包根的 `declareServerWithheldEnv()`
618
+ (先于 `loadConfig()`、任何执行环境构造与子进程):本进程的自有凭据变量名(配置目录全部 secret 型名 + 名单型旋钮名)向引擎声明,`inheritEnv` 三词下都不交给宿主子进程,
619
+ 名单型旋钮点到它们拒启。`loadConfig()` 的前提就是这份声明 —— 没声明的进程里它抛,拒句点名这一步(服务的三个进程入口第一步就声明)。`loadConfig()` 之后调
596
620
  `composeExecutionLane(config, ctx)`(HTTP 服务与 `run-local` 都这么做;7.104.0+)。执行车道的四条拒启(`leader-host-lane` /
597
621
  `multi-tenant-shared-lane` / `device-durable-store` / `device-durable-approval`)自 7.104.0 起判在这一步,**不在** `loadConfig()` 里 ——
598
622
  只调 `loadConfig()` 的装配对这几形照起。`ctx.configSource === "local"` 时它先读 `config.d/remote-exec.json` 覆写车道,再判。`ctx.entry`
599
623
  = 你的装配这只入口:`runs` 是它跑得了的车道闭集(文件或 env 声明了表外的一条 ⇒ 拒),`declares` 可选(文件与 env 都没点名时由入口声明,排在二者之后;文件在场即终结选择 —— 文件点的车道取不到坐标 ⇒ 拒,不由 env 的另一条或入口声明顶上;
600
624
  `run-local` 声明 `host`);自建 HTTP 装配递包根导出的 `HTTP_SERVICE_LANE_ENTRY`(全部车道、不替部署声明);不走文件覆写的装配直接调同一份判据 `assertExecutionLaneInvariants(config)`。
601
625
  `ctx` 的型 `ExecutionLaneComposeCtx` 也从包根导出(自己的装配里要写它的签名 / 抽 helper 时直接 import,不必手抄一份形)。
602
- 读口场景门 `scenarioGate` 是 `createHttpServer` 的必填件(7.131.0+):用包根导出的 `createScenarioGate({ config, scenarios, principalCaps, logger }, "peek")` 铸(与任务提交腿同一个工厂,型 `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources` 一并导出),没递就是 tsc 红。
626
+ 读口场景门 `scenarioGate` 是 `createHttpServer` 的必填件(7.131.0+):用包根导出的 `createScenarioGate({ config, scenarios, principalCaps, logger }, "peek")` 铸(与任务提交腿同一个工厂,型 `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources` 一并导出),没递就是 tsc 红;绕过类型(JS 嵌入)没递或形不对,`createHttpServer` 构造即拒并点名这一席。
603
627
  **模型路由端点的拒启同理判在目录应用之后**:`judgeServedModelRoute(config, { envTableIsPlaceholder })`(包根导出,HTTP 服务与 `run-local` 调的就是它)只判将要服务的那张表(目录表或 env 表,句子随视图);
604
628
  只调 `loadConfig()` 的装配不再在读配置那一拍拿到路由拒启 —— 在**自己的**配置源应用之后调它一次,有判决就拒启(`reason` 是给运维的原句)。严格名册布防(env 表是占位)传 `envTableIsPlaceholder: true`,没有名册门的装配传 `false`。
629
+ 随包的可运行参照:`node scripts/embed-minimal.mjs`(在包目录下跑)按上面这条链装一台最小服务 —— 包根工厂、引擎的 `Runner` 与自写的一行 `resolveSpec` 凑齐 `createHttpServer` 的九个必填席,
630
+ host 车道、一次性数据根、内核分口的回环口,起到监听就打一行 `EMBED-MINIMAL ok port=<n>`;`--omit <席名>` 少给一席,说清 `createHttpServer` 是构造即拒(`refused`,退 2)还是照收(`accepted`,退 3)。
631
+ 构造期判且拒句点名席名的是 `runnerFor` / `authorize` / `deploymentMods` / `peerDesk` / `scenarioGate` 五席(省掉 `config` 抛一句不点名的 `TypeError`);`runner` / `sideQuery` / `resolveSpec` 只有型是提示,绕过类型的嵌入省掉它们构造期照收。
605
632
  - **库形嵌入的 caps `store` 位**(7.106.0 起;7.106.0 合并复审 X9 补一句):`createHttpServer` 的 `storePosture` dep **不传** ⇒ `GET /v1/capabilities` 的
606
- `store` 取诚实下限 `{declared: <config.dbBackend>, backend: "memory", durable: false}`,且**没有 `store.sql`**(即使递了引擎读口 `sqlEngine`、真有 SQL 后端 ——
607
- 7.105.0 上这类嵌入方的顶层 `sql` 是有值的)。要 caps 说真话就把你装出来的后端事实经 `storePosture` 递进来(形 = `src/store-posture.ts` 的 `StorePosture`)。
633
+ `store` 取诚实下限 `{declared: <config.dbBackend>, backend: "memory", durability: "ephemeral"}`(`/health.store.durability` 同值),且**没有 `store.sql`**(即使递了引擎读口 `sqlEngine`、真有 SQL 后端 ——
634
+ 7.105.0 上这类嵌入方的顶层 `sql` 是有值的)。要 caps 说真话就把你装出来的后端事实经 `storePosture` 递进来(形 = `src/store-posture.ts` 的 `StorePosture`;7.148.0 起多两格必填 `durability` 三态与逐根明细 `roots`,自己判不了介质就写 `"unknown"`,不要写 `"durable"`)。
635
+ - **库形嵌入的 SQL 后端不跑列册对账**:嵌入方的公开序列(`createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()`)不跑列册对账(`assertSchemaMatchesCode` / `projectPendingShape`),
636
+ 一次性迁移的守卫尾因此保留;本服务的起服序才跑对账(排在一次性迁移段之前)。
608
637
  - **`PORT=0` = 让 OS 分口,口由引擎自己报**:引擎自己绑内核分给它的口,`listening` 启动日志行(stdout,info 级;`LOG_LEVEL` 高于 info 会滤掉它)的 `port` 与 `/health.port` 报**实际绑定口**
609
638
  (`PORT` 非 0 时两者与配置值相等)。要用 OS 分的口起引擎的宿主:子进程以 `PORT=0` 起,读 stdout 那一行 `listening` 的 `port`,再用 `/health` 的 `pid` / `dataRoot` 验答话的是自己起的那个进程;
610
639
  **不要**先在外面挑一个空闲口、关掉、再交给子进程去绑 —— 关与子进程真正 `listen` 之间有窗,并行负载下别的进程能抢走这个号。`configHash` 仍按**配置值**铸(配置指纹不随 OS 分的口漂)。
611
640
  - `/health` 身份字段承诺:`pid`(= 引擎进程)、`startedAt`(**7.67.0+**,= 引擎进程起点的 epoch ms,
612
- 一次铸)与 `dataRoot`(= 生效数据根,解析恒回退 `~/.ai-agent`,与 DB_BACKEND 无关)**恒在**——宿主用
641
+ 一次铸)与 `dataRoot`(= 生效数据根,解析恒回退 `~/.ai-agent`,与 DB_BACKEND 无关)**恒在**(**7.148.0+**:配了服务凭据的引擎上,
642
+ 这些键只给出示了凭据的读者 —— 宿主验身份时带起引擎用的**同一枚** `Authorization: Bearer <SERVICE_AUTH_TOKEN>` 读;没出示凭据的读者只拿 `status` / `auth` / `operator` / `center` 四键;
643
+ 没配服务凭据的嵌入形照旧匿名全键,见契约 §0.4「读者分档」)——宿主用
613
644
  它们验证「这个端口上的 /health 是不是我起的那个引擎」。`startedAt` 变了 = 这个端口后面换了一条命
614
645
  (另一个宿主重启了共享引擎):壳据此把「引擎换代」当**一等事实**处理,不必等某个带鉴权的请求先撞一次
615
646
  401 才发现(`/health` 无鉴权,心跳恒绿)。⚠️ 只给这一个数:service token 本体与代际计数**不上**这一面。
@@ -621,15 +652,41 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
621
652
  - 在而读不出(I/O 错;悬空符号链接 —— 链接在、目标不在)/ 不是 JSON 对象 / 缺 `highWater` / 有别的键 / `highWater` 不是纯 `x.y.z` ⇒ **读不出**:引擎起服拒,**不当缺席**;有别的键 = 更新的引擎写下的形(数据根比本构建新);
622
653
  - 读得出 ⇒ 与本端版本比:`highWater` 高于本端 ⇒ 本端引擎起服拒 `config.schema_mismatch`(拒句点名这只文件、高水位与本构建版本;
623
654
  包根导出的 `LocalDataRootSchemaMismatchError.findings` 里一段 `problem: "newer-engine"`,同一段必带 `engineHighWater: { highWater, engine }` —— 读数类型 `LocalDataRootMismatch` 是以 `problem` 为判别位的联合),不必先起一只注定被拒的引擎再解析 stderr。
624
- - **写者只有引擎**,判与抬在**打开数据根的那一刻**一段做完(数据根锁在手 = 单写者;先于引擎这一侧的任何一只店被建起 —— 店在构造里就会写):先判,判拒 ⇒ 本仓零字节写、不抬;
625
- 判过 ⇒ 先抬再写 —— 本构建高于记录(或缺席)⇒ tmp + rename 原子替换(属主独占 `0600`,目录项也要证落盘;并发读者只见整份旧值或整份新值),相等不写(只证它在盘上:fsync 文件与目录)。所以店构造里的自修、
626
- 起服对账、一次性迁移与任何服务写都在抬之后:写到一半进程死了,水位已是本构建,比它旧的引擎重开被拒,本构建重开续跑。对账(严格边档 / 快照消化)或迁移在抬之后被拒 ⇒ 水位留在本构建。
655
+ - **写者只有引擎**。**判**在数据根**准入**里(7.149.0 起):只读,在引擎进程对这只根的**第一笔写之前**(HTTP 服务起服第 1 段、`run-local` 同位置;
656
+ 嵌入方的公开序列 `createStoreBackend()` 在打开时当场取)—— 判拒 ⇒ 数据根**一个字节都不动**:不抬 marker,不建保管店钥匙、店目录、空账本,不取锁(拒句末行就说这一件)。
657
+ **抬**在打开数据根的那一刻(数据根锁在手 = 单写者):锁内先核准入判过的每一只输入都没动(物理数据根是同一只;marker、会话元数据边档的字节,存档目录的会话 id,收令牌欠账标记在不在,都与准入那一次观察相同)
658
+ —— 动了 = 准入与取锁之间另一只引擎碰过这只根(不写 marker 的更早版本也算)⇒ 拒,拒句点名动的是哪一只、出路是重起再判
659
+ (这一只拒发生时本进程已落下准入之后的正常写,拒句不声称数据根没动;它是准入唯一的例外,因为引擎内核把取锁与它自己的建店放在同一步);
660
+ 核过 ⇒ 先抬再写 —— 本构建高于记录(或缺席)⇒ tmp + rename 原子替换(属主独占 `0600`,目录项也要证落盘;并发读者只见整份旧值或整份新值),相等不写(只证它在盘上:fsync 文件与目录)。所以店构造里的自修、
661
+ 起服对账、一次性迁移与任何服务写都在抬之后:写到一半进程死了,水位已是本构建,比它旧的引擎重开被拒,本构建重开续跑。抬之后的拒(快照消化、一次性迁移的前提)⇒ 水位留在本构建。
627
662
  本构建版本按**发布线**读:`x.y.z`,可带 `-预发布` / `+构建` 后缀,后缀只剥不比(`7.147.0-rc.1` 按 `7.147.0` 判与抬,marker 恒写纯三段);读不出(例:读不出 `package.json` 时的 `unknown`)⇒ 引擎拒开这只根(marker 不读不写)。
628
663
  宿主只读:不写、不删、不改。写不进(含目录项证不了落盘)⇒ 不以这只根服务(起服拒;缺省推导的 local 在起服时为空的根上按既有降级臂退内存)。
629
- 残余(如实):引擎内核 `FileStorageBackend` 的构造把取锁与它自己的店放在同一步(建子目录、店目录锁、内核账本撕裂尾的收拢)—— 那几笔在判之前。
664
+ **拒启 = 零写(7.149.0 起)**:7.148.x 及更早判在「打开数据根」这一步,判之前已落下新文件 —— `sealed-key/exec-*.json` 两只(模型钥匙一只、停驻行封签的钥匙目录里一只;
665
+ 被拒的构建自铸的新密钥对)、`checkpoints/checkpoints.jsonl` 与 `background-agents/agents.jsonl` 两只 0 B 账本、十余只店目录、`prompt-epoch/` 店目录,配置根与数据根合一时还有
666
+ `config.migration.SS-M11.json`;严格边档读不出那一形连 marker 首写与本服务各店目录都在拒之前。用 7.148.x 或更早的版本被拒过的根,多出来的就是这些新文件与空目录
667
+ (**旧构建重开这只根**:按 `publicKeyId` 读不到新钥匙文件 ⇒ 当缺席、重铸自己的,不读错);7.149.0 起这些都在准入之后,被拒的起服一个都不落。
630
668
  - SQL 后端不读不写它(滚动升级窗内合法混跑,另有列册对账)。只有**旧的那一端**是 7.147.0 及以后才守得住(更早的引擎不读它)。
631
669
  回滚的唯一支持形 = 恢复升级前的数据根备份(marker 随备份回到旧值);同一台机器要并行跑多个引擎版本 ⇒ 各给一个数据根(`LOCAL_DATA_ROOT`)。不设绕过旋钮。
632
- 钉:`test/local-engine-high-water.test.ts`。
670
+ - **两条入口同一只判与抬(7.148.0+)**:`run-local` 与 HTTP 服务的 local 臂经同一只打开函数打开数据根 —— 7.147.x 及更早的 `run-local` 不读不写这只 marker,7.148.0 起它同样判、同样抬;
671
+ 7.149.0 起两条入口都在各自第一笔写之前取同一只准入。
672
+ - **跳版拒启(准入的一格,抬之前)**:数据根里有「会话存档在、会话元数据格缺」的会话(7.148.0 之前的 `run-local` 建的会话就是这一形),本构建已不带把它们收进行的那条一次性迁移,
673
+ 而 ① 打开之前的高水位早于 7.148.0 或缺席(带那条迁移的版本从没打开过这只根),或 ② 带那条迁移的版本打开过、没收完(数据根顶层还留着 `archive-token-adoption.pending`)⇒ 起服拒
674
+ `config.schema_mismatch`,`findings` 里一段 `problem: "skipped-required-migration"`,必带 `skippedMigration: { highWater, requiredSince, sessions }`(`highWater` 为 `null` = 没有 marker)。
675
+ 哪一种由这两格推出,不另带位:`highWater` 为 `null` 或早于 `requiredSince` ⇒ ①;不早于 `requiredSince` ⇒ ②(拒句点名那只标记文件,说「没收完」)。拒在准入里、数据根零写、marker 不动,所以出路那一版起得来 ——
676
+ 出路 = 先用一个仍带那条迁移、**不低于 `requiredSince` 且不低于 `highWater`** 的版本(服务或 `run-local`)对这只根**完整**起一次,再起本构建(低于 `highWater` 的会被「高水位高于本端」拒在门外)。
677
+ `archive-token-adoption.pending` 是引擎内部的欠账记录,不是契约(宿主不读不写):带那条迁移的版本在抬高水位之前写下它(这只根还欠、存档目录里有存档),
678
+ 收齐并证明之后删掉 —— 第一次起动被打断(进程被杀、迁移写失败、嵌入方不走 `ensureSchema()`、会话目录列不出 ……)它就留着;收完之前更新的版本拒开。
679
+ 标记在而根里已没有「存档在、格缺」的会话(收齐之后、删之前停了)⇒ 无事可收:打开它的版本在锁内、抬高水位之前删掉它、照起。
680
+ 带那条迁移的版本不判这一道:它在本次起服里收(没收完的那一次留下标记,见上);起动时会话存档目录读不出 ⇒ 打开即拒,不抬高水位。
681
+ 钉:`test/local-engine-high-water.test.ts` · `test/local-root-one-row-store.test.ts`(跳版拒启臂)· `test/archive-token-adoption-duty.test.ts`(收令牌欠账)。
682
+ - **已知限制:混版回退 —— 7.147.0 起写下的会话行,在 ≤7.146.x 的版本上续跑不保证**:7.147.0 起会话行上本服务记的十二格搬出了引擎的座表(SQL 后端住旁表 `session_ext`、`session_meta` 只剩引擎的列,一次性迁移 M41 停-迁-起;local 数据根另有上面的引擎版本高水位 marker)。
683
+ 把部署回退到 ≤7.146.x、再续跑升级之后写下或改写过的会话,结果**不保证**:可能照常续到终答(一次续跑返回 200),也可能被拒 —— 两种都不在支持范围内,本服务不承诺其中任何一种,也不为它加兼容读法(一条旧版读法就是第二份读法)。
684
+ **出路**:回退前先排空 —— 让待续跑的停驻 run 先收完(完成或取消),再回退;随包的排空检查(`scripts/drain-check-local.mjs`〔`DB_BACKEND=local`〕/ `scripts/drain-check.mysql.sql` / `scripts/drain-check.pg.sql`,只读)是读出停驻行存量的工具;或不跨版回退。回退的唯一支持形是恢复升级前的数据根 / 库备份。
685
+ - **已知限制:`REMOTE_EXEC=host` 车道上请求不带 `cwd` 时,进程重起之后才被批准的停驻后台子代,续跑的命令落在已删目录**:请求不带 `cwd` 时,host 车道为这条任务铸一个一次性的工作区 `<HOST_WORKSPACE_BASE,缺省 os.tmpdir()>/sema-host-<id>-<rand>`,
686
+ 由落在里面的执行环境(父 run 与按父的工作目录落根的委派子代)共同持有,最后一只放手的删它 —— 同一进程里父 run 先收了场、停驻在审批门上的后台子代之后被批准,续跑接着在那只目录里跑,子代最后一条腿收场时目录才删。
687
+ **进程退出时**(正常退 / 未捕获异常 / 有处理器的信号)退出兜底删掉本进程铸的全部临时目录,含仍被停驻子代持有的;重起之后再批准,续跑的命令落在已删目录上 —— 命令没有运行,引擎的拒句是 `…the task root directory no longer exists (or is no longer a directory): <路径>`,
688
+ 而续跑提醒仍写成命令已执行(以拒句为准)。停驻中被取消 / 过期的后台子代持有的临时目录活到进程退出、由退出兜底删。
689
+ **出路**:提交时带 `cwd` —— 带了 `cwd` 的请求不走一次性工作区,没有这个删除(`sema` CLI 恒带;SDK 只有 launch 助手默认传 `process.cwd()`,直接调提交接口的要自己带)。
633
690
  - `/health` 活体 store 探测(5.14.0,SQL 后端专属 additive):后台探针(`STORE_PROBE_INTERVAL_MS`,
634
691
  默认 15000;0=关;负/非数启动响亮拒)缓存一份 DB 真往返结果,`storeProbe:{live,ageMs,error?}` 在
635
692
  探针接线时恒在,顶层告警键 `storeLive:false` 只在死时出现。**status 保持 "ok"**(liveness≠readiness
@@ -677,7 +734,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
677
734
  - 模型拒因(**7.103.0+**):`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 同一只取值口、同一个对象(形 = settings-schema
678
735
  6.2.0 `ModelRefusalsSignal`:`entries` = 目录条目名 → 拒因码,`plane` = 模型面组拒码;值只码)。描述最近一次被判的中心候选目录;
679
736
  没有可报的拒 ⇒ 键缺席。名字是秘密形 / 空串 / `__proto__` 的不可用条目不上这两处(出口过名筛;引擎内部照旧拒它)。消费方读 `version`
680
- 判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。契约段:`docs/ASSISTANT-WIRE-CONTRACT.md`「模型拒因」。
737
+ 判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。
681
738
  钉:`test/model-refusals-s768.test.ts`。
682
739
  - **`SESSION_CACHE_TTL_SEC`(缺省 `300`,单位 s;`0` = 关)—— 会话暖缓存,多副本安全**:每只副本把用过的会话留在内存里,
683
740
  下一轮不必从店重新唤醒整只会话。每次命中先读店里这只会话的当前叶(SQL 后端一条主键 SELECT):与暖份的叶相同 ⇒ 照用;
@@ -686,8 +743,8 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
686
743
  —— SQL 后端一条主键 SELECT;文件后端(`DB_BACKEND=local`)是把这只会话的存档文件再读一遍(只读头,不回放日志)。命中却已陈旧的次数计在 `session_cache_stale_hit_total`(高 = 同一会话在 TTL 内常换副本,加亲和可省掉这些重读)。
687
744
  `0` = 每一轮都从店唤醒,不复用。⚠️ 核的是叶:另一只副本用会话同步**整段替换**(含 `overwrite-dst`)把已有条目的内容改了、
688
745
  末叶却没变的那一形,别的副本上的暖份在 TTL 内仍是旧内容(与改前同;写不会因此失败)。依赖这一形即时可见的部署设 `0`。
689
- - 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
690
- (1.292+ 裸 boot 默认 durable;/health 的 `sessionBackend` 报 `durable(local)`)——session 行落盘在
746
+ - 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 local 文件店
747
+ (1.292+ 裸 boot 默认 local;/health 的 `sessionBackend` 报 `local`)——session 行落盘在
691
748
  数据根下,清数据/隐私预期要按「sessions 在 engine-data 里」来做,不要按「只在内存」。
692
749
  - **后台子代的转录住哪、活多久(按车道)**:规则一条 —— 一只后台子代的持久记录在,它的转录就是宿主会话店委派分区里的一只分区会话:活过重启、随记录同亡;
693
750
  进程内短 TTL 店只装没有持久记录指着的会话(普通子任务、前台子代)。`subagent_type: "fork"` 的后台子代不在这条规则里(各车道同):它的转录是从父会话分叉出来、
@@ -713,7 +770,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
713
770
  - **会话的行事实只住在数据根的 `session-incarnations.json`**(每只会话一格:属主 / 化身 / 执行放置 / 建档时刻 / fork 谱系);`sessions/*.jsonl` 是匿名的对话内容。
714
771
  备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`;会话同步导入的第一阶段对 fork / stale 且没带同意的推送也直接答它,7.147.0 起干跑 `POST …/sync/plan` 对这两形同样),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
715
772
  `session-incarnations.json` 本构建读不出(更新的引擎写过、格里多出不认得的键;或文件坏了 / 读不了)⇒ 起服拒 `config.schema_mismatch`(与 SQL 后端列册对账同一个码,拒句点名文件与键;
716
- 出路 = 部署写下它的那一版,或恢复升级前的数据根备份;读不了 ⇒ 修好权限 / 盘再起);起服之后才读不出(跳过了 `ensureSchema()` 的嵌入方;或违规共享的数据根上另一只引擎写了它)⇒ 同码拒,
773
+ 7.149.0 起这一判在数据根准入里,先于任何写 —— 拒启零写;出路 = 部署写下它的那一版,或恢复升级前的数据根备份;读不了 ⇒ 修好权限 / 盘再起);起服之后才读不出(违规共享的数据根上另一只引擎写了它)⇒ 同码拒,
717
774
  形读不出则那只边档的每一读每一写拒到重启,盘上字节不被盖掉。
718
775
  - 多实例边界:同一数据根同时只允许一个实例(BootLock 独占,第二个进程拒启;死主自愈含 SIGKILL)。
719
776
  core 5.54.0 起拒启错误带**可分派 code**(`FileStoreLockError`,message 点名占用方 pid),运维按码
@@ -749,6 +806,13 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
749
806
  🔴 **两条取数据根的路是同一条腿、同一只旋钮**:HTTP 服务的 local 臂与 `run-local`(一次性 CLI)共用
750
807
  同一只等待腿 —— **不为前台形留第二套语义**。前台形要「立刻告诉我谁占着」就设 `STORE_LOCK_WAIT_MS=0`,
751
808
  那是文档化的通道,而不是两条路各有一套默认。
809
+ 🔴 **7.148.0 起两条路是同一只打开函数、同一份装配**:`run-local` 不再自己构造引擎的文件后端、不再用引擎的裸会话店 ——
810
+ 它与 HTTP 服务的 local 臂一样:数据根准入(只读,第一笔写之前)→ 取锁 → 引擎版本高水位(核与抬)→ 本服务的店 → 起服对账 → 一次性迁移 → 才读会话。
811
+ 于是同一个数据根上**只有一只会话行店**:同一只会话在两条入口上是同一位住客(会话元数据边档那一格的化身),
812
+ 一条入口上落下的记忆记录(`memory.capture:"off"` 的采集 opt-out、外部内容带来的污染标记)在另一条入口上照样生效;
813
+ `run-local` 新建的会话当场有会话元数据格(建档时刻真值;HTTP 服务列它时不再补铸另一只住客令牌)。
814
+ 代价(如实):HTTP 服务拒启的数据根(会话元数据读不出、残留迁移快照、引擎高水位高于本构建、跳版)现在也挡住 `run-local`(doctor 形一行 + 退 2);
815
+ 升级后第一次 `run-local` 多跑一遍起服对账与一次性迁移。
752
816
  🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
753
817
  永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`;唯一例外是**确定的拒启表态**,见下一条「跳版升级的旧库」,它不等窗)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
754
818
  ⚠️ **裸 boot(没有显式 `DB_BACKEND`)撞锁与显式 `DB_BACKEND=local` 同形**:等满窗仍拿不到 ⇒ 拒启(同一句拒句)。
@@ -756,7 +820,6 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
756
820
  一定有东西,开不了就拒,不退内存(退了 = 这只根上已有的会话 / run 账本整片看不见)。`0` 关掉等待。
757
821
  🔴 **新引擎的锁等待窗应 ≥ 前任的排空窗 + 15 s 终局 + 一拍**(前任的锁要到它收尾链尾部才还回来;前任最晚何时退出见 `DRAIN_GRACE_MS` 下面的「停机上界(契约句)」):
758
822
  壳 / 桌面按**自己的**排空窗给新引擎进程注入这个值 —— 那是配置渠道的本意;server 不改缺省(缺省 `60000`:改缺省会让所有部署的起服失败延后)。
759
- 三问成文见 `docs/DEPLOY-PREREQS.md`。
760
823
  - **跳版升级的旧库(退役表 `snapshot_manifest` / 退役列 `checkpoint.pending_steer` 还在)起服即拒 `config.schema_mismatch`,而且拒启之前库里一张表都没建**:
761
824
  连上库后**先探这两件、再建表** —— 探针先于第一条 `CREATE`(两个 SQL 方言同一只判据),所以按拒句指路去 7.125.x 起服一次时,库还是你交给本版之前的样子,
762
825
  不会带着本版新建的表与新列去撞 7.125.x 的对账。这是确定的拒启表态:不等 `STORE_LOCK_WAIT_MS` 的窗、当场拒,拒句点名在场的坐标与出路(先在 7.125.x 对这只库起服一次让迁移跑完,再升)。
@@ -783,6 +846,14 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
783
846
  `run.json` / `events.jsonl` / claim 文件与每会话块账本 `run-blobs/<会话>.jsonl` 的读取。目录里本来就没有的东西(`.DS_Store` 一类杂物、
784
847
  尚未落盘的目录)照旧跳过 —— 拒的是「读不出来」,不是「没有」。
785
848
  ⚠️ 射程**不含**「文件读得出、里面有一条坏记录」:那一形在两档下都是跳过 + 披露 + 计数(下一条)。
849
+ - **`LOCAL_STORE_DURABILITY=ephemeral`(闭集只有这一个词,缺席 = 店型的设计声明;7.149.0 起)—— 运维声明 local 店易失**:
850
+ local 店对引擎的声明(会话店顶层、检查点店「两半」)只有一只读者:写了这一词 ⇒ 两半都声明 `process-local`;没写而店落在**调用方自己的盘**上
851
+ (`REMOTE_EXEC=host` ∧ 没开 `REQUIRE_PRINCIPAL`:壳自起的本机引擎、开发机)且数据根介质判 `ephemeral` ⇒ 同样 `process-local`;其余 ⇒ 设计声明 `durable`(活过进程重启)。
852
+ 起服布线审计拒 `config.store_not_durable` 的条件是「声明 `durable` ∧ 会话店的介质(数据根)判 `ephemeral`」(容器可写层 overlay / tmpfs / k8s emptyDir)—— 声明是 `process-local` 就不拒。
853
+ **后果**:店里的会话、停驻审批与记忆不随容器 / Pod 替换保留(进程重启照旧活过);引擎静态装配读 `session.store = process_local`(接了检查点店〔`DURABLE_APPROVAL`〕时 `parkLane.checkpointDurability` 同读);起服一行 `wiring_static_warning`
854
+ 写明依据、介质判定与挂卷出路。`/health.store.durability` 与 `GET /v1/capabilities` 的 `store.durability` 照旧按介质如实报 —— 挂了卷还写这一词,读面报 `durable`、声明照降。
855
+ 别的任何值(`true` / `durable` / 大小写不同 …)拒启:声明只有「我就是易失」这一句。SQL / memory 后端不读它。`REMOTE_EXEC=device` 的部署要 SQL 后端(既有不变量),与本键无关。
856
+ 🔴 **部署者自己设**:壳 / 编排器 / 安装器不替用户写这只键(替用户写 = 替他放弃这份承诺);壳自起的本机引擎不需要它。
786
857
  - **local 车道 JSONL 店的坏记录(7.104.0 起)**:`DB_BACKEND=local` 的八本 JSONL 账(run 店四面、续跑锚、
787
858
  发文件账本、流内审批 ask 账本、直连门 nonce 账、「本会话不再询问」豁免账、规则审批记录 / 导入票两本)重放时,
788
859
  **中间**一条记录读不出(一行 `42`、缺本店必填键的对象、半截 JSON)不再静默:每条打一行 warn(事件名
@@ -792,11 +863,11 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
792
863
  所以等过 `PRINCIPAL_JWT_MAX_TTL_SEC` 加容差再移、再重启);某会话的豁免账坏 ⇒ **那个会话**的每个工具重新问人、
793
864
  它的豁免列表 / 撤销答 500、`remember:"session"` 不生效(`rememberApplied:false`),删掉该会话即恢复;规则两本日志坏 ⇒
794
865
  规则车道整面拒(与此前同,拒绝句自此带码)。不拒启。② **跳过 + 计数**(可用性轴):run 店 / 续跑锚 / 发文件账本 /
795
- ask 账本,计数 `fail_open_total{tag="server.<店>.corrupt-record-skipped"}` + stderr 契约行。逐店后果见
796
- `docs/FAIL-OPEN-CENSUS.md` 第 75、93–98 行。崩在半行的**尾**行不算坏记录(那是非优雅退出的正常落盘形)。
866
+ ask 账本,计数 `fail_open_total{tag="server.<店>.corrupt-record-skipped"}` + stderr 契约行。逐店后果写在随包 `dist/observability/fail-open.js` 里 `FAIL_OPEN_TAGS` 各 tag 的 `note` 中。崩在半行的**尾**行不算坏记录(那是非优雅退出的正常落盘形)。
797
867
 
798
868
  **监听绑址(1.306+)—— 桌面/单机形请注意**
799
- - `BIND_HOST` = 监听地址;显式设置**恒生效**(要在无鉴权下对外暴露,显式写 `BIND_HOST=0.0.0.0` 即可)。
869
+ - `BIND_HOST` = 监听地址;显式设置**恒生效**。**7.148.0+**:单用户部署(`REQUIRE_PRINCIPAL` 未开)上用它把**无鉴权写面**暴露到本机回环以外 ⇒ 服务进程起服拒
870
+ (不变量 `single-user-anonymous-boundary`:网络上任何人都能不带主体提交 run,而那样的 run 读这台部署那个用户的规则桶);要对外暴露写面,配 `SERVICE_AUTH_TOKEN`。多租部署上照旧恒生效。
800
871
  - `HOST` 不再被读取(曾经的兼容别名已撤——zsh 默认把 `HOST` 设成机器名的暗通道风险已消除):
801
872
  绑址唯一口 = `BIND_HOST`,设或不设都与 `HOST` 环境变量的值无关。
802
873
  - **缺省自动收窄**:当写面无鉴权时(`ALLOW_UNAUTHED_WRITES=true` 且**完全没有**
@@ -842,10 +913,10 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
842
913
  写文件与代理的 shell 同一套权限,镜像用户写不了的路径答 `permission_denied` —— 镜像里没预建的工作区目录由容器启动以 root 建出,镜像用户 ≠ root 时要在镜像里预建并归镜像用户。
843
914
  - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内;那里的私有目录同样核不过 ⇒ 与上面同一条退路:清掉原暂存件、直接写目标、计 `stage-seal-fallback`);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
844
915
  目录不收新名字(只读目录里的可写文件)⇒ 直接写目标一次(旧形,同样非原子;远端车道在这一形里分两段读取消:备件阶段〔第一个字节发往目标机之前:adb 在本机落临时件、ssh 开 sftp 通道含懒重连、k8s 建连、追加的读旧 + 拼〕取消 ⇒ `aborted`、目标未碰;传输那一步因取消中断 ⇒ `unknown`「目标可能只写了一半」,不答 `aborted`;那一步不收取消(ssh 的 sftp)⇒ 照样写完、答成功;
845
- host 车道的回落形由引擎就地写,中途取消目前仍答 `aborted` —— 已知缺口,修在引擎)。
916
+ host 车道的回落形由引擎就地写:取消只在第一个字节之前生效(答 `aborted`、目标未碰),已开写的照样写完、答成功或它自己的错)。
846
917
  已有文件的权限位带过去;属主不带(替换后的文件属于写入方)。
847
- - 已知语义变化(与引擎 / 参照实现同形):rename 换的是目录项 ⇒ **硬链接被断开**(另一条链接留旧内容);目录可写时**没有写权限位的文件也会被替换**(权限位保留);
848
- host 车道上名字超过约 230 字节(随服务进程号的位数浮动)的文件写不进(引擎的暂存名超出 255 字节的名字上限 ⇒ 错误码 `unknown`,`ENAMETOOLONG`;远端五车道的暂存名会截短,不受影响)。
918
+ - 已知语义变化(与引擎 / 参照实现同形):rename 换的是目录项 ⇒ **硬链接被断开**(另一条链接留旧内容);目录可写时**没有写权限位的文件也会被替换**(权限位保留)。
919
+ 名字逼近 255 字节上限的文件照常写成:host 车道(引擎)与远端五车道的暂存名都截短到上限之内。
849
920
  - `appendFile`(后台输出镜像等)= **追加**:暂存的只是要加的字节,提交是目标机上一次 `cat >>`(host = 本服务的一次 O_APPEND 写);inode 不换(`tail -f`、硬链接照旧)、已有字节从不重写;
850
921
  取消 / 传输中途断 ⇒ 一个字节不加;提交本身在目标机上失败(如磁盘满)⇒ 可能只加了一部分。提交那一次往返本身断了 ⇒ 错误码 `unknown`,
851
922
  句子写明「提交跑没跑不可知」—— 重试前先读一次目标。
@@ -882,10 +953,10 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
882
953
  # k8s 集群内 pod 可指内网 MinIO 省公网带宽)
883
954
  ```
884
955
  - **词表优先级**:内网端点 `MINIO_ENDPOINT` 优先,`S3_ENDPOINT` 是外接 S3 兼容位(内网/公网同址场景可只给它,同时充当公网面);公网端点 `S3_PUBLIC_ENDPOINT` 优先,缺席回落 `S3_ENDPOINT`。空串一律按未设处理。
885
- - **🔴 云形(`DB_BACKEND=mysql|pg`)快照 blob 强制对象存储(1.295+,clay 拍)**:缺 MinIO 三键 ⇒ **boot 拒启**——单行 SQL blob 写会撞 TiDB `txn-entry-size-limit`(默认 6MiB,真库实测矮墙)/ mysql 协议 `max_allowed_packet`,大字节归对象存储(与 D-1 附件面同裁定)。单机/测试台显式逃生:`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true`(SQL 店此时带 per-blob 帽,MySQL 协议(InnoDB 与 TiDB)默认 = **写入那条数据库连接**报的两堵已知墙取最小:单行上限减 1 KiB(blob 行自己的余量:哈希键 + 长度 + 时间戳;TiDB 才有)与包墙 `max_allowed_packet` 扣 1 KiB 语句余量后折半(⌊(包墙 − 1 KiB) ÷ 2⌋:文本协议把 blob 写成十六进制,语句至少是它的 2 倍);任一读数读不出时按该库族文档缺省并留一行运维日志 `fail_open`(tag `server.sql.packet-cap-unread` / `server.sql.tidb-row-cap-unread`),超限 PUT 413 `blob_too_large_for_sql`,句子按帽的出处分三形(运维旋钮 / 库报的墙 / 读不出而按文档缺省假定的墙),机读位 `wallSource` 同词 `operator` / `reported` / `assumed`。包墙是每条连接自己的会话值(7.133.0 起按连接判):DBA 调大 `max_allowed_packet` 之后,池里的旧连接在它第一次按包墙拒 blob 时自换(运维日志一行 `sql_connection_packet_wall_stale`,那一笔在新连接上重判),不必重启副本;`GET /v1/diagnostics/wiring` 的 `sqlEngine.maxRowBytes` / `sqlEngine.maxPacketBytes` 是最近一次初始化的那条连接的读数(见部署前置清单 DEPLOY-PREREQS(仓内)「数据库」条);帽可用 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 按部署真实限值覆写,两方言生效;pg 默认无帽)。
886
- - **per-file rewind 历史的边界上限**(与 workspace 浏览面同一个整树快照纪元退役批留下的保留条款):`FILE_HISTORY_RETENTION_KEEP` —— 每个 scope(会话)存活的**最新** boundary 数,**每次 boundary 提交后自剪**(最旧者先亡,刚提交者恒存活;失引用的版本行级联删,blob 字节走异步 grace 窗 GC;v1 基线永存)。未设=core 缺省 **100**(由 core 的 `resolveFileHistoryRetention` 兑现,server 不复制那个数);`unbounded` 是**唯一**的「不剪」拼法(整店自剪关闭,GC 归部署自排 `reap`);`0`/负数/小数/非数 ⇒ **boot 拒启**(码 `config.retention_policy_invalid`,与 core 内置后端同码同判——**省略这个键不等于不剪**,它选的是缺省 100)。三条车道(TiDB / PG / local 文件店)同旋钮同判。⚠️ 升级即生效:存量超 100 boundary 的 scope 在下一次提交时被剪到 100;要保全量先写 `unbounded`。被剪 entry 的键**已花掉**(再次 snapshot 同 entry 典型拒,不复铸)。排序=DB 分配的 per-scope 发布序号 `file_history_boundary.publish_seq`(不是墙钟):**自 7.50.x 有存量 `file_history_boundary` 的部署升级本版会在 boot 期被 `assertFileHistorySchema` 拒启,拒因打印这张表缺的列的 `ALTER TABLE … ADD COLUMN …`,由部署者自己执行**(列集对账与拒启句只打印语句、永不改库;per-file rewind 历史照留,会话与其他持久面不动)。⚠️ 打印的语句取自建表语句原文:`publish_seq` 是 `NOT NULL` 且无缺省的列,在已有行的 PostgreSQL 表上那句原文会被引擎拒(列含空值)—— 这种存量表要部署者自己按 per-scope 的发布顺序补值后再改写执行;这是起服对账拒启句同一生成器的既有性质。
956
+ - **🔴 云形(`DB_BACKEND=mysql|pg`)快照 blob 强制对象存储(1.295+,clay 拍)**:缺 MinIO 三键 ⇒ **boot 拒启**——单行 SQL blob 写会撞 TiDB `txn-entry-size-limit`(默认 6MiB,真库实测矮墙)/ mysql 协议 `max_allowed_packet`,大字节归对象存储(与 D-1 附件面同裁定)。单机/测试台显式逃生:`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true`(SQL 店此时带 per-blob 帽,MySQL 协议(InnoDB 与 TiDB)默认 = **写入那条数据库连接**报的两堵已知墙取最小:单行上限减 1 KiB(blob 行自己的余量:哈希键 + 长度 + 时间戳;TiDB 才有)与包墙 `max_allowed_packet` 扣 1 KiB 语句余量后折半(⌊(包墙 − 1 KiB) ÷ 2⌋:文本协议把 blob 写成十六进制,语句至少是它的 2 倍);任一读数读不出时按该库族文档缺省并留一行运维日志 `fail_open`(tag `server.sql.packet-cap-unread` / `server.sql.tidb-row-cap-unread`),超限 PUT 413 `blob_too_large_for_sql`,句子按帽的出处分三形(运维旋钮 / 库报的墙 / 读不出而按文档缺省假定的墙),机读位 `wallSource` 同词 `operator` / `reported` / `assumed`。包墙是每条连接自己的会话值(7.133.0 起按连接判):DBA 调大 `max_allowed_packet` 之后,池里的旧连接在它第一次按包墙拒 blob 时自换(运维日志一行 `sql_connection_packet_wall_stale`,那一笔在新连接上重判),不必重启副本;`GET /v1/diagnostics/wiring` 的 `sqlEngine.maxRowBytes` / `sqlEngine.maxPacketBytes` 是最近一次初始化的那条连接的读数;帽可用 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 按部署真实限值覆写,两方言生效;pg 默认无帽)。
957
+ - **per-file rewind 历史的边界上限**(与 workspace 浏览面同一个整树快照纪元退役批留下的保留条款):`FILE_HISTORY_RETENTION_KEEP` —— 每个 scope(会话)存活的**最新** boundary 数,**每次 boundary 提交后自剪**(最旧者先亡,刚提交者恒存活;失引用的版本行级联删,blob 字节走异步 grace 窗 GC;v1 基线永存)。未设=core 缺省 **100**(由 core 的 `resolveFileHistoryRetention` 兑现,server 不复制那个数);`unbounded` 是**唯一**的「不剪」拼法(整店自剪关闭,GC 归部署自排 `reap`);`0`/负数/小数/非数 ⇒ **boot 拒启**(码 `config.retention_policy_invalid`,与 core 内置后端同码同判——**省略这个键不等于不剪**,它选的是缺省 100)。三条车道(TiDB / PG / local 文件店)同旋钮同判。⚠️ 升级即生效:存量超 100 boundary 的 scope 在下一次提交时被剪到 100;要保全量先写 `unbounded`。被剪 entry 的键**已花掉**(再次 snapshot 同 entry 典型拒,不复铸)。排序=DB 分配的 per-scope 发布序号 `file_history_boundary.publish_seq`(不是墙钟):**自 7.50.x 有存量 `file_history_boundary` 的部署升级本版会在 boot 期被 `assertFileHistorySchema` 拒启,拒因打印这张表缺的列的 `ALTER TABLE … ADD COLUMN …`,由部署者自己执行**(列集对账与拒启句只打印语句、永不改库;per-file rewind 历史照留,会话与其他持久面不动)。⚠️ `publish_seq` 是 `NOT NULL` 且无缺省的列(要值列:已有行的值只有部署者知道),拒启句对它打印**三步配方**而不是一句 ADD COLUMN:先 `ADD COLUMN publish_seq BIGINT;`(可空加列),再一行 `-- ` 注释(回填 `UPDATE file_history_boundary SET publish_seq = … WHERE publish_seq IS NULL;`,值由部署者按 per-scope 的发布顺序补;注释行不会被误执行),最后收紧(PostgreSQL `ALTER COLUMN … SET NOT NULL`;MySQL 协议先开 `STRICT_ALL_TABLES` 再 `MODIFY COLUMN`)。打印的语句全部生效之前起服一直拒 —— 只执行了第一句(列已在、仍可空)同样拒,并只再给后两步;空表上打印的语句逐字全部可执行。
887
958
  - **会话同步重推(同图)要读 blob**(7.136.0 起):`POST /v1/sessions/:id/sync/import` 的 identical 重推臂,对云端「已握着同一张文件历史图」的目标,也先把信封引用的每一枚 blob 经云端自己的店读一遍、核哈希(不回写),再答成功 —— 对象存储里的对象若在图发布之后丢了,这一腿答 `422 missing_blob`(`PUT` 缺的 blob 再推;图不动),不再答 `200 identical`。代价:无变化会话的频繁重推多付对象存储读流量(每枚引用的对象一次下载,正比于引用的 blob 字节);没有旋钮。
888
- - **文件历史捕获总开关**(7.97.0 立,7.104.0 改语义):`FILE_HISTORY_CAPTURE`(缺省 `true`;布尔词表 true/false/1/0/yes/no/on/off,未知词 ⇒ boot 拒启;TiDB / PG / local 三条车道同义)。`false` ⇒ **只关捕获**:历史店照接,引擎拿到部署捕获位 `RunnerDeps.fileHistoryCapture: "off"`,改动文件的首次快照**一个都不捕获**、不铸回合边界(`wiring_manifest.fileHistory` 报 `"disabled"`);**已存历史照常可用** —— `resumeAt`+`restoreFiles` / `rewindFilesTo` 照常还原、2c session-sync 照常导出 / 导入。`GET /v1/capabilities` 的 `fileHistoryCapture` 报 `"off"`,而 `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync` 仍为 `true`(缺省部署报 `"on-optional"` = 捕获中且请求可按 run 关掉,见上一段)。🔴 **7.97.0–7.103.x 的旧语义**:`false` = 不把店交给引擎,连已存历史也读不了(还原走 `rewind.store_unconfigured`、sync 501)—— 把本旋钮当成「整个文件历史功能关掉」来用的部署,升级后已存历史**重新可以被还原 / 同步**。要让已存历史也不可用,今天的出路只有**逐会话** `DELETE /v1/sessions/:id`(擦除面不受本旋钮管)与 SQL 后端的留存清扫(`RETENTION_MODE=enforce` + `RETENTION_MAX_AGE_DAYS` + `RETENTION_SWEEP_INTERVAL_SEC`,按会话年龄删,local 车道没有留存车道);**没有**「整库历史一键不可用」的旋钮。⚠️ **关掉 ≠ 删掉**:翻旋钮不批量删除存量历史;删会话照删已捕获的历史(三条车道同读:local 车道自 7.146.0 / 引擎 7.64.0 起也走整会话清理动词 —— 边界、版本记录、v1 基线与只被这只会话引用的 blob 一起删;此前 local 走 `reap`,v1 基线留存)。关着时 `FILE_HISTORY_RETENTION_KEEP` 没有效果(不再有边界提交,自剪不会触发),值仍照常校验。没有存储后端的部署(env-only worker)本来就没有历史店,旋钮在那里无事可关、不下发捕获位(引擎对「off 但没店」拒 prepare)。
959
+ - **文件历史捕获总开关**(7.97.0 立,7.104.0 改语义):`FILE_HISTORY_CAPTURE`(缺省 `true`;布尔词表 true/false/1/0/yes/no/on/off,未知词 ⇒ boot 拒启;TiDB / PG / local 三条车道同义)。`false` ⇒ **只关捕获**:历史店照接,引擎拿到部署捕获位 `RunnerDeps.fileHistoryCapture: "off"`,改动文件的首次快照**一个都不捕获**、不铸回合边界(`wiring_manifest.fileHistory` 报 `"disabled"`);**已存历史照常可用** —— `resumeAt`+`restoreFiles` / `rewindFilesTo` 照常还原、2c session-sync 照常导出 / 导入。`GET /v1/capabilities` 的 `fileHistoryCapture` 报 `"off"`,而 `restoreFiles` / `rewindFilesTo` / `sessionSync` 仍为 `true`(缺省部署报 `"on-optional"` = 捕获中且请求可按 run 关掉,见上一段)。🔴 **7.97.0–7.103.x 的旧语义**:`false` = 不把店交给引擎,连已存历史也读不了(还原走 `rewind.store_unconfigured`、sync 501)—— 把本旋钮当成「整个文件历史功能关掉」来用的部署,升级后已存历史**重新可以被还原 / 同步**。要让已存历史也不可用,今天的出路只有**逐会话** `DELETE /v1/sessions/:id`(擦除面不受本旋钮管)与 SQL 后端的留存清扫(`RETENTION_MODE=enforce` + `RETENTION_MAX_AGE_DAYS` + `RETENTION_SWEEP_INTERVAL_SEC`,按会话年龄删,local 车道没有留存车道);**没有**「整库历史一键不可用」的旋钮。⚠️ **关掉 ≠ 删掉**:翻旋钮不批量删除存量历史;删会话照删已捕获的历史(三条车道同读:local 车道自 7.146.0 / 引擎 7.64.0 起也走整会话清理动词 —— 边界、版本记录、v1 基线与只被这只会话引用的 blob 一起删;此前 local 走 `reap`,v1 基线留存)。关着时 `FILE_HISTORY_RETENTION_KEEP` 没有效果(不再有边界提交,自剪不会触发),值仍照常校验。没有存储后端的部署(env-only worker)本来就没有历史店,旋钮在那里无事可关、不下发捕获位(引擎对「off 但没店」拒 prepare)。
889
960
  - **🔴 公桶策略必须 GetObject-only**:`mc anonymous set download` 会**连带打开 ListBucket**——匿名 `GET /<bucket>/?list-type=2` 能枚举全部不可猜 key,能力链接设计即告失效。正确姿势=`mc anonymous set-json`,policy 只含 `Action:["s3:GetObject"]` on `arn:aws:s3:::<bucket>/*`(验证:对象 GET 200、桶 LIST 403)。
890
961
  - **两条轨**:ttl=0(默认)→ 匿名 GET 公桶下 `uuidv7/<name>` 不可猜 key,链接永久、可回收(删对象);0<ttl≤7 天 → 私桶 + SigV4 限时签名链接(7 天是 SigV4 物理上限,更久用 ttl=0)。
891
962
  - **执行 lane 与租户门**:e2b/k8s 沙箱 lane 任意租户可用(沙箱文件系统=租户边界,沙箱内 `curl -T` 直传、字节不中转、凭据不进沙箱);host/ssh lane 仅单用户部署(`REQUIRE_PRINCIPAL` 未开)时挂载。
@@ -977,7 +1048,14 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
977
1048
  - 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
978
1049
  - **逐条容错**(7.101.0 起):目录里一条条目不合形(如 `contextWindow:"abc"`)⇒ **只丢那一条**(warn `config_entry_dropped`,
979
1050
  点名坐标 `models.<下标>`、闭集原因词 `reasonCode` 与字段诊断;7.123.0 起同一条也在 `GET /v1/models` 的 `droppedEntries` 里自报),其余条目照用;refresh 拍 / 迟到的 boot 拉取**不再因此整拒候选**(此前一条坏条目让整份目录停在
980
- 上一代 —— 上一代若还是 env,env 模型就一直服务)。颗粒更粗的失效(整个域读不懂、拼错键、坏文件)仍整拒候选、保 LKG。
1051
+ 上一代 —— 上一代若还是 env,env 模型就一直服务)。**条目体内带契约不认识的键**(比本服务新的写者写的前瞻键、拼错的键,如 `thinkingLevelMapX`)与形错同律(7.148.0 起,settings-schema 22.0.0 / 23.0.0 / 24.0.0):
1052
+ 对「条目缺席只会收窄可达面」的域(`models` `mcp` `systems` `collab` `hosts` `projects` `a2a`)只丢那一条、词 `entry-unknown-key`,不再剥键照装、也不再整拒候选 —— 条目不装,**删掉那个键或改对拼写即回**。
1053
+ 一张**值名单**(工具名单 `allowTools`、范围名单 `scenarios` / `rosters` / `allowSkills`、`gitRemotes`、模型 `promptGuidance` …)里坏一个元素 ⇒ 持有它的那一条整条丢(词 `entry-schema-invalid`,诊断句点到元素;mcp 服务器 / a2a 对端不挂载,模型进 `droppedEntries`),不逐元素截短 —— 被截短的拒绝名单比作者所写更松(被截短的允许名单则少放了作者要放的),都不是作者写的那一张。
1054
+ **条目缺席会放宽可达面的收紧域不逐条丢**:`scenarios`(条目覆盖内建场景名、收窄工具面;条目丢 ⇒ 内建复活)、`plugins`(条目是来源引用,承运的 hooks 可以是守卫;引用丢 ⇒ 守卫缺席)与 `skills`(条目按名覆盖出厂技能、收窄工具面 / 加拒绝名单;条目丢 ⇒ 出厂技能回位)—— 条目体内的未知键 ⇒ 整份候选拒
1055
+ (`config_candidate_rejected`,`domains` 含该域);坏形条目(`skills` 还有同名两条、`skills: null`、拒绝名单 / 工具名单里的坏元素)⇒ 整域读不进、整包读被拒(成因 `rejected`):整份中心配置落回上一份好配置,首启无 LKG 落回 env(刷新拍保持上一代,运维刷新口 502);
1056
+ `CONFIG_PROVIDER=local` 的 `config.d/scenarios.json` / `plugins.json` / `skills.json` 同(没有 LKG:起服落回 env)。一条坏条目会让这三域的全部覆盖 / 引用随这份配置一起不收 —— 删掉那一条或改对即回。
1057
+ 中心发来的 skills 清单(`/effective` 的 `skills`)与 `config.d/skills.json` 同一只判官:任一条坏 / 缺 `contentHash` / 同名两条 / `null` ⇒ 整份候选不收。
1058
+ 颗粒更粗的失效(整个域读不懂、域顶层拼错键、坏文件)仍整拒候选、保 LKG。
981
1059
  - **没有 `MODEL_ID` 也能起**(7.101.0 起):目录(`config.d/models.json` 或中心)有可用条目时 env 可以不写 `MODEL_ID` —— 缺省模型取
982
1060
  目录缺省。判据在目录应用**之后**:env `MODEL_ID` 或目录缺省任一在场即过;两者皆无 ⇒ 拒启(码 `config.default_model_missing`,指路二选一)。
983
1061
  若是因本地候选被判官整拒(悬空符号链接 / 坏 JSON / 空文件 / 域判形失败 …)才落到 env 表,7.147.0 起拒启句同一行多一段回落原因:被拒的文档名、拒码、原因类词(悬空链接 / 读不出 / 解析错误 / 空文件 …)与文档路径(悬空链接给出链接自己的路径,不带它指向的目标路径;不带配置值,原句只在 `config_candidate_rejected` 行);句首 `MODEL_ID is required when no catalog default resolves [config.default_model_missing]` 不变。
@@ -987,6 +1065,11 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
987
1065
  - 拉取**只读、只取逻辑配置**(模型名册/角色/团队);密钥/网关仍在本服务 env(中心只发 env-**名** 引用,不发密钥值)。回滚=去掉 `SEMA_WEB_ADMIN_URL` 即纯 env。
988
1066
  - **配置热更新真表(7.38+ 现行)**——refresh 拍(60s 轮询,或 `POST /v1/admin/config/refresh` 手动触发,见 API 表)对各域的生效方式:
989
1067
 
1068
+ **「热」的前提是部署有配置管道**:`CONFIG_PROVIDER=local`(读本地配置根下的 `config.d`)或接了中心(`SEMA_WEB_ADMIN_URL`;不写 `CONFIG_PROVIDER` 时有它即走中心)。
1069
+ 两者都没有(缺省)= 纯 env:HTTP 服务不读 `config.d`、没有刷新拍,下表的「热」对它不成立,配置只来自 env。刷新节拍固定 60 s、没有旋钮;配置没变的那一拍
1070
+ (中心答 304 / 本地树内容没变)不打 `web_admin_refreshed` 行 —— 看不到它不说明刷新坏了,读 `/health` 的 `configTargetVersion` / `configAppliedVersion`。
1071
+ 不想等节拍就调手动刷新口(要运维名册,名单外 403,见 API 表)。
1072
+
990
1073
  | 域 | 生效 | 说明 |
991
1074
  |---|---|---|
992
1075
  | models / roles / default 模型 | **热**(下一 refresh 拍) | 全 boot Runner **原子换代**(swap 失败=候选整拒,活配置零触碰);此前「改 models 需重启」的时代已随 Runner swap 腿落地终结 |
@@ -995,7 +1078,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
995
1078
  | plugins(插件引用) | 重启生效 | 插件只在启动期物化;改了之后 `/health` 报 `restartRequired`,`restart.reasons` 含 `skills`(7.100.0 前这一改动**不报**,要等一次无关的重启) |
996
1079
  | skills / mcp / a2a / scenarios(技能 / MCP / A2A / 场景) | 重启生效 | 只在启动期物化;中心改了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 各含其名。**换账号**(凭证身份变化)同理:这些面仍是启动时那个人的直到重启(热面则在换人那一刻降档、新身份的候选落地时换代,见上文 `SEMA_WEB_ADMIN_TOKEN_FILE` 段) |
997
1080
  | 审批名单(`governance.approvalRequire`) | **热**(下一 refresh 拍) | 7.100.0 起:下一个任务调名单里的工具即停车等审批;撤键回 env 底(`APPROVAL_REQUIRE`)。**没有 checkpoint 店**的部署(`DURABLE_APPROVAL` 关 / 后端无 checkpoint 面)发非空名单 ⇒ 审批组**整组拒**(旧名单继续服务、`configAppliedVersion` 停旧代、拒因见日志 `web_admin_approval_require_invalid` 与诊断端点 `configApply.groups.approvalGate`)—— 与启动期「名单已配却无 durable 门 ⇒ 拒启」同一条判据。此前 `/health` 报 `restartRequired`(`runtime-gates`,该理由退役) |
998
- | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
1081
+ | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次,那只模型的凭据身份随它冻结;中心改了 roles / 档位 / 目录条目(含那一条的凭据身份:重贴 `sealedApiKey`、换 `apiKeyEnv` 变量名)后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报;变量值的同名轮换不报、不用重启) |
999
1082
 
1000
1083
  生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
1001
1084
  - **`run-local`(一次性本机 CLI)上三张审批名单同样执法**(7.101.0 起,TTY 问 / 无 TTY 拒):`APPROVAL_DENY` 当场拒;`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` 命中 ⇒ stdin 是 TTY 就 `y/N` 问人,否则当场拒并在 stderr 说出路(7.116.0 起按**这一问的类**说,不再对每一问打同一句全量旋钮清单:没人表态的服务器工具调用 ⇒ 指 `APPROVAL_GRANT`;名单产的问 ⇒ 点名三张名单键;部署治理盖了出身的问 ⇒ 点治理旋钮;引擎自己的问 ⇒ 不点旋钮、只说去终端答;引擎没有说明这一问的类 ⇒ 说明这一点、只给终端出路);`APPROVAL_AUTO_BUDGET` 照常生效(never-auto 不吃预算);TTY 问句带上这次调用的参数,人答 N 而停下的 run 退 1(停因 `haltedOnUserRejection`)。**无 TTY 的拒记在部署政策名下**(7.110.0 起;此前记成人拒):该次调用 `tool_end.gate.settlement` = `{kind:"policy_refused", who:{party:"policy"}}`(7.113.0 起;7.110.x–7.112.x 为 `party:"none"`),模型读到的是引擎的策略句(「部署政策拒了、此处没有交互审批,别重试,先做不需要审批的部分」)加一句拒因,而不是「用户不想继续,停下来等他」—— 不停批,模型接着做别的部分;那只要人批的动作没执行 ⇒ run 照样退 1,非 `--json` 形 stderr 停因行是 `task incomplete (approval unanswered): …`。参数展示不出而不问就拒的那一形(TTY 在场也一样)同此。这条腿没有 checkpoint 店也**不**拒启 —— 它有一只当场作答的席,名单的 ask 不需要 park(上表「无 checkpoint 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
@@ -1043,7 +1126,7 @@ QUESTION_TTL_MS=300000
1043
1126
  > —— 壳/SDK 不实现消费就等于用户看不到审批卡。🔴 **7.133.0 起一条腿一个帧族**:协议上场的 worker(`GET /v1/capabilities` 的
1044
1127
  > `streamApproval` 为真)只发 `approval_request` / `approval_complete` / `approval_revoke`,旧族 `tool_approval` / `tool_approval_complete`
1045
1128
  > 不再发;协议不上场的 worker 只发旧族。帧格式、消费端契约、五合取全表在
1046
- > [`docs/ASSISTANT-WIRE-CONTRACT.md` §4a-quint](docs/ASSISTANT-WIRE-CONTRACT.md)(错误码 `feature.approval_ask_disabled` 在附录 A)。
1129
+ > 契约 §4a-quint(内部记录;错误码 `feature.approval_ask_disabled` 在契约附录 A)。
1047
1130
  > 🔴 **7.134.0 起卡只发给声明认得这一族的客户端**:提交体 `settings.approvalFrameFamilies: ("tool_approval" | "approval_request")[]` 声明这条 run 的客户端
1048
1131
  > 渲染得了哪些族;一条 run 只在声明含 `GET /v1/capabilities` 顶层 `approvalFrameFamilies` 那个词时收到活卡。缺席 / 不含 ⇒ 这条 run 不呈卡(不换族、不双发)、
1049
1132
  > 开流不重放挂着的卡,每只审批问(会话里已记住的放行照旧先兑现)当场收场,结局看这台 worker 有没有耐久店(`DURABLE_APPROVAL` 开着):在场 ⇒ 交 `UNATTENDED_APPROVAL_POLICY`:`park`(缺省)⇒ 任务停驻、`/v1/approvals/:sessionId/decide` 作答;`deny` ⇒ 当场拒,拒因句点名缺的族与出路;
@@ -1133,7 +1216,7 @@ UNREACHED_ASK_TTL_MS=3600000
1133
1216
  ⚠️ **这条腿上的窗照样是 `STREAM_ASK_WINDOW_MS`**:此前它被硬编码成 5min、旋钮在这些部署上
1134
1217
  静默无效(慢组场景只能真等满 5 分钟);现在调它就是调这条腿。另:这类部署上**迟到决议**
1135
1218
  (卡挂过窗、人才按 Yes)不能走 `POST /v1/tool-approvals/:id/respond`(没有持久 ask 行 ⇒ 恒 404
1136
- `tool_approval.not_pending`、不带 `cause`),要走 `POST /v1/approvals/:sessionId/decide`
1219
+ `not_found.ask`;7.147.x 及以前 `tool_approval.not_pending`、不带 `cause`),要走 `POST /v1/approvals/:sessionId/decide`
1137
1220
  ——前提是配了 `DURABLE_APPROVAL=true`(local 车道有文件形 checkpoint 店),否则窗到期即 fail-closed 拒。
1138
1221
  ⚠️ **上面这段「没有持久 ask 行 ⇒ 恒 404」自 7.81.0 起不再适用于 `DB_BACKEND=local` 本身**:local 的
1139
1222
  ask 账已是文件形,配齐 park 设施的单机部署上迟到决议走 `respond` 的赎回席是通的;本段现在只说
@@ -1143,7 +1226,7 @@ UNREACHED_ASK_TTL_MS=3600000
1143
1226
  (代价是超限的 ask 走 park,要有人去审批队列捞);库压大 → 调小 `STREAM_APPROVAL_RECONCILE_BATCH`
1144
1227
  (代价是收敛变慢,靠队列轮转保证下轮接着扫)。
1145
1228
  - **停驻行最长活多久(local 文件后端与 SQL 店同一公式)**:一张 pending 的 checkpoint 行在 `max(createdAt + APPROVAL_TERMINAL_BACKSTOP_MS, deadline + 1h)` 到点后由后台 reaper(每 `REAP_INTERVAL_SEC`,缺省 60 s 一拍)expire;`deadline` 只在配了审批 SLA / TTL 时有,没配就是 `createdAt + 后备上限`。旋钮缺省 30 天、下钳 60 s、非有限值落缺省。到点的行先 expire,挂起的 run 行随后由 reaper 翻成 `failed`(`errorCode: approval.expired`)。所以「老停驻行在 local 后端最长活多久」= 后备上限(缺省 30 天)+ 最多一拍;要缩短就调 `APPROVAL_TERMINAL_BACKSTOP_MS`(两店同辖)。
1146
- - **SQL 店写停驻行的预判与 SLA 拒扫的取行序(7.136.0 起)**:`put` 先看这份检查点放不放得进索引列 —— 没有 `gate` / `pendingAction` 对象,或门上卡词的 UTF-8 **字节**数超过 `approval_card` 列的容量(MySQL 协议 `TEXT` 65,535 字节;PG 无上限),抛 `CheckpointIndexRefusedError`(`err.refusal.kind` 为 `unreadable` / `card_too_long`),一句 SQL 都不发 —— 严格与非严格 sql_mode 的库都一样,不会落成半只卡词;引擎自己铸的检查点不会触发。SLA 拒扫每拍至多取 `LIMIT`(缺省 100)行,取行序是「卡列有词的先取,其后按到期时刻」:混跑窗里旧副本停驻的问答行(卡列为空)排在带卡词的真到期行之后。
1229
+ - **SQL 店写停驻行的预判与 SLA 拒扫的取行序(7.136.0 起)**:`put` 先看这份检查点放不放得进索引列 —— 没有 `gate` / `pendingAction` 对象,或门上卡词的 UTF-8 **字节**数超过 `approval_card` 列的容量(MySQL 协议 `TEXT` 65,535 字节;PG 无上限),抛 `CheckpointIndexRefusedError`(`err.refusal.kind` 为 `unreadable` / `card_too_long`),一句 SQL 都不发 —— 严格与非严格 sql_mode 的库都一样,不会落成半只卡词;引擎自己铸的检查点不会触发。SLA 拒扫每拍至多取一页(100 行),取行序是到期时刻(同刻按行键);拒扫按页**轮转**走全部到期审批 —— 每拍从上一拍取到的最后一行之后接着取,一页取不满就回到队首(local 文件后端同律)。所以一页每拍都被拒的行(引擎认领前拒、停驻子代、本版不重放的存量体、混跑窗里旧副本停驻的问答行……)挡不住排在后面的到期审批:到期审批数为 N 时一圈走 ⌊N/100⌋ + 1 拍(最后一拍取到的不满一页),每一张每圈被试一次;一行的存体读法抛出只让这一行这一拍不续(运维日志 `deny_sweep_row_failed`)。
1147
1230
  - **审批账本保留多久(`APPROVAL_ASK_RETENTION_MS`,缺省 `2592000000` = 30 天;local 文件后端与 SQL 店同一条律)**:审批在盘上有三本账 —— 问账(SQL 的 `approval_ask` / `approval_batch`,local 的
1148
1231
  `<数据根>/approval-asks/asks.jsonl`)与 local 审计账(`<数据根>/approval-ask-audit/ledger.jsonl`,`GET /v1/approvals` 的 `crashConverged` 读它)。一问**结了**且它最后一次动静早于
1149
1232
  `now − APPROVAL_ASK_RETENTION_MS`,它在三本账里的全部记录由维护 tick 整问删掉。「结了」= 该问在它那本账里到了终局,**且它挂靠的东西都不再能动**:挂靠 = 发起它的 run、宿主会话、
@@ -1156,8 +1239,11 @@ UNREACHED_ASK_TTL_MS=3600000
1156
1239
  非终态的问行(`STREAM_PENDING` / `PARKING`)永不按龄删 —— 它们归对账器与起服收敛。节律:首拍即清扫一次,之后每小时至多一次;每次先从 run 账、后台子代账与 checkpoint 店列出还能动的东西(任一读不出 ⇒ 这次不删),
1157
1240
  问账一批至多 500 行、一次清扫删到不满一批为止。读数:起服一行 `approval_ask_retention`(`windowMs`;`0` = 关),真删了东西时一行 `approval_ask_retention_swept`
1158
1241
  (`cutoffMs` / `asks` / `batches` / `auditAsks` / `auditRecords`;半途失败时已经删掉的照样打,带 `incomplete: true`)+ 计数器 `approval_ask_retention_rows_deleted_total{ledger=asks|batches|audit}`。
1159
- local 的问账日志上,任何真删了行的调用(留存清扫的两只动词与 `deleteByTask`)的那一拍,同步把整本原子重写成存活行的快照 —— `asks.jsonl` 里同拍不再有被删的问的字节(含 `Write` / `Edit` 这类工具实参的字面);没删东西的调用不落记录、不重写;
1160
- 压实失败不影响调用(行照删,计 `fail_open_total{tag="server.approval-ask-journal.compaction-failed"}`,日志里留着删行记录,下一次写或下一次起服再压实)。
1242
+ local 的问账日志上,任何真删了行的调用(留存清扫的两只动词与删会话时的审批腿)的那一拍,同步把整本原子重写成存活行的快照 —— `asks.jsonl` 里同拍不再有被删的问的字节(含 `Write` / `Edit` 这类工具实参的字面);没删东西的调用不落记录、不重写;
1243
+ 留存清扫的压实失败不影响调用(行照删,计 `fail_open_total{tag="server.approval-ask-journal.compaction-failed"}`,日志里留着删行记录,下一次写或下一次起服再压实);
1244
+ 保留窗只管「没人删」的那部分:删一只会话(`DELETE /v1/sessions/:id` 与 SQL 后端的托管留存清扫,同一只谓词)同拍删掉问账里挂靠它的问 —— 宿主会话或发问那条腿的会话是它
1245
+ (没有会话的 adhoc 腿把自己的 run id 填进会话格,那样的行挂靠的是 run,不算)—— 与它们所在的批;已经没有问的空批不带会话格,由本条按龄收;审计账不随会话删。
1246
+ 删会话时的审批腿不同:被删的问的字节没离开账本就不算删完 —— 压实失败 ⇒ 这次会话删除答 500、会话行留着,重发删除即补压。
1161
1247
  起服时因撕尾 / 坏行隔离出去的原件 `asks.jsonl.corrupt-<ts>` 是取证材料,**没有清扫腿**,里面仍有当时的字节,要清由人来(正常部署恒零)。
1162
1248
  **关法**:`APPROVAL_ASK_RETENTION_MS=0` = 永久保留(要在升级后的第一次清扫之前设 —— 首拍即扫);正值低于 1 天钳到 1 天并在启动告警里点名(刚结束的 run 的客户端还可能对它的卡重试 / 晚答,
1163
1249
  一天吸收这一段);负值 / 非整数 / 非数拒启。run 结束超过窗之后才对它的卡作答 ⇒ 决断口与流内审批口答 404。
@@ -1209,6 +1295,12 @@ UNATTENDED_APPROVAL_POLICY=park
1209
1295
  - 已知边界:「本会话放行」记在本部署的会话豁免店(SQL 后端跨副本共享;`DB_BACKEND=local` 只在这台机器的数据根里)。续跑落到另一台**读不到这只店**的
1210
1296
  实例上(local 车道换机 / 换数据根)⇒ 名单的问再问一次 —— 方向是多问一次,不是静默放行。名单靠别名命中的调用(嵌入方给工具挂了 `Bash` 别名)
1211
1297
  按调用的 wire 名记、按 wire 名读;一张卡上同时有名单的问与别人的问(人写的 ask 规则、强制位、读会话目录外 ……)⇒ 那张卡的「本会话放行」不记。
1298
+ - 已知边界(两只家并存):活卡上对**引擎的问**点的「本会话放行」记在审批席的进程内表(按副本、随进程;能力桶:`Write` 盖 `Edit` / `NotebookEdit`),**不在**上面那只豁免店里 ——
1299
+ `GET /v1/approvals/:sessionId/exemptions` 列不出它、`DELETE …/exemptions/:toolName` 撤不到它(回 `revoked:true` 只说耐久那一行没了)、删会话与留存清理也清不掉它的表项,换副本 / 重启即失。
1300
+ 它**绑会话这一位住客**(7.149.0 起):键含这条腿的会话此刻的住客令牌,所以会话删了(`DELETE /v1/sessions/:id`、留存清理、别的副本)再用同一个 id 重新登记之后,
1301
+ 上一位住客点的放行读不中,新一位住客的第一次调用照样出卡;读不出住客令牌的腿(会话店不带执行席 —— 引擎缺省的内存会话店;SQL 与 local 后端恒带)点「本会话放行」只放行这一次(回执 `rememberApplied:false`,日志一行 `session_grant_refused_occupant_unreadable`)。
1302
+ 名单的问点的「本会话放行」与停驻之后 `remember:"session"` 记的放行在豁免店里,列表与撤销都管。读豁免店的两道策略层门(`APPROVAL_REQUIRE` 名单门、`run_workflow` 问句门)经同一只读口:
1303
+ 两秒内读不到(店慢 / 店坏)⇒ 照问;店答的不是布尔真值也不放行。命中时的审计行是 `approval_exempted`(字段 `seat`:`deployment_list` / `workflow_gate`;`run_workflow` 问句此前的事件名 `workflow_gate_exempted` 已退役,按它告警的运维改读这一条)。
1212
1304
 
1213
1305
  **无人值守腿:`default` 模式 + 按类作答的审批席(7.109.0 起)—— 没有旋钮**
1214
1306
 
@@ -1281,7 +1373,7 @@ WRITE_PROTECTED_TABLE_REPLACE='[{"name":".envrc","kind":"basename"}]' # 整表
1281
1373
  - **配置根(`SEMA_CONFIG_DIR`)之内的整棵树都在这张表的射程里**(引擎 7.46.0 起):缺省表里的 `.sema` 行不只守家目录下的 `~/.sema`,也被放在你声明的配置根下 ——
1282
1374
  根之内**任何**路径(不止配置文件)的写(`Write` / `Edit` / `NotebookEdit`)都要过 `write_protection` 一问。所以**配置根不要设成工作目录本身或它的祖先**
1283
1375
  (容器里 `SEMA_CONFIG_DIR=/workspace` 而项目在 `/workspace/proj`、或直接设成 `HOME`):项目里的每一次写都出一张卡,allow 规则清不掉它,`acceptEdits` 不放,
1284
- `dontAsk` 拒,无人值守腿(leader、无终端的 `run-local`)拒(leader 配了耐久审批时停驻等人答),只有 `bypassPermissions` 放行。**起服不报、首条任务也不报** —— 第一次知道是第一次写。出路:把配置根指到项目之外的专用目录
1376
+ `dontAsk` 拒,无人值守腿(leader、无终端的 `run-local`)拒(leader 配了耐久审批时停驻等人答),只有 `bypassPermissions` 放行。**起服 warn 一行**(事件 `sema_config_root_encloses_workspace`,字段 `configRoot` / `workspace` / `workspaceFrom`:配置根就是工作区或它的祖先时,两条路径与「工作区」读自哪里都在行里;不拒启、不改任何行为;起服一次,首条任务之后不再多报;这一行只说位置关系、不读写保护表 —— 已用 `WRITE_PROTECTED_TABLE_REPLACE` 去掉 `.sema` 行的部署同样会收到,按事件名过滤即可)。「工作区」在起服时只有一条读链:`run-local` 的 `--workspace` ?? `REMOTE_EXEC=host` 的 `HOST_WORKSPACE_BASE` ?? 进程工作目录(手不在本机盘上的车道不判);请求体里按任务给的 `cwd` 起服时不可知,这一行看不见它 —— 项目只靠请求 `cwd` 落在配置根之下的部署,第一次知道仍是第一次写。出路:把配置根指到项目之外的专用目录
1285
1377
  (不设即缺省 `~/.sema`);确实要保留祖先形配置根的,`WRITE_PROTECTED_TABLE_REPLACE` 给一张不含 `.sema` 行的表可去掉这一问,代价是 `~/.sema` 下的这一问一并没了。
1286
1378
  - **与 `SENSITIVE_WRITE_PATTERNS` 并列、不合流**:那一根是**模式**(正则/glob)型 DENY 集,这一根是
1287
1379
  字面名表的 `allow → ask` 降级。两套互不覆盖,读面也分开报 —— 一条路径被哪一套拦住,是两个不同的问题
@@ -1320,8 +1412,7 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
1320
1412
  **不变**。⚠️ **但「不铸」在引擎里的读法自 core 7.22.0(内置读 deny 表出厂 OFF 的裁定)起翻了面**:`READ_DENY_BUILTIN_TIERS`
1321
1413
  未设 = **OFF**(内建表一行不生效),此前是「除 shell-history 外四档全开」。⇒ 一台没配过这根 env 的机器
1322
1414
  升级后**读得到**根内的 `.npmrc` / `.ssh` / `.aws` / `.claude/settings.json` 这类名字,且整个递归读族
1323
- 零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`
1324
- (运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。`open` 与这张表仍是**两个并列机制**:
1415
+ 零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`。`open` 与这张表仍是**两个并列机制**:
1325
1416
  `open` 放开的是「工作区外」那道围栏,内建表判的是名字。
1326
1417
  🔴 **7.106.0(core 7.34.0 R-2)再翻一次**:未设 = **`credentials` + 家目录锚定的 `agent-config` 两档在岗**(`browser` / `wallet` /
1327
1418
  `shell-history` 仍是选装;显式 `READ_DENY_BUILTIN_TIERS=none` 仍是空姿态)⇒ 未配置的机器升级后**读不到**根内的 `.npmrc` / `.ssh` / `.aws` 这类名字
@@ -1337,8 +1428,7 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
1337
1428
  `read_path_denied`(带 `target` 与**命中的那条 pattern**),`gate.disposition.deniedBy = "read_boundary"`。
1338
1429
  同批**根内的内容遍历**(`grep -r` / `diff -r` / `rg`)改成**枚举**:树下没有拒表行 ⇒ 零卡执行,
1339
1430
  有 ⇒ 点名那一行拒。⇒ **要让某条路径重新读得到,就把它从这三张表里拿掉**(删掉那条自配模式 / 档位
1340
- 不选 / 用 `READ_DENY_BUILTIN_EXCLUDE` 删掉那一行);三问与运维面全文见
1341
- `docs/DEPLOY-PREREQS.md` 的 `core 7.25.0 提货` 段。
1431
+ 不选 / 用 `READ_DENY_BUILTIN_EXCLUDE` 删掉那一行)。
1342
1432
  · 「这一档到底是谁定的」由 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace` 段答:
1343
1433
  `{face, source, note}`,`source` ∈ `env` / `center` / `lane` / `posture` / `engine-default`:`env`(本机 env 显式)/ `center`(组织下发)/ `lane`(隔离型执行车道的契约行声明,带挂载告诫)/
1344
1434
  `posture`(单机派生)/ `engine-default`(没钉,引擎当家),`note` 是指路句。
@@ -1429,9 +1519,11 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1429
1519
  🔴 **签发方配置不算证据**:`PRINCIPAL_JWT_PUBKEYS` / `_ISS` / `_AUD` 与 `AUTH_BRIDGE_ISSUER` 在不在场都不改变判定
1430
1520
  (7.95.0–7.98.x 曾把它们当证据,并对「配了签发方却没设 `REQUIRE_PRINCIPAL`」的机器拒启;那条拒启随之删除)。
1431
1521
  auth-bridge 那条 `?? webAdmin.baseUrl` 回退、`SEMA_WEB_ADMIN_*`(含 worker 名)同样不读。
1432
- ⚠️ 反面是**全量召回缺口**:忘设 `REQUIRE_PRINCIPAL` 的团队机器按单用户运行、本门放行 —— 多租机器必须自己设它,
1433
- 后果与被排除的候选(`OPERATOR_PRINCIPALS`、`SERVICE_AUTH_TOKENS` 条目数、`DB_BACKEND`、`BIND_HOST`)逐条写在
1434
- `docs/DEPLOY-PREREQS.md`。
1522
+ ⚠️ 反面是**全量召回缺口**:忘设 `REQUIRE_PRINCIPAL` 的团队机器按单用户运行、本门放行 —— 多租机器必须自己设它。
1523
+ 后果:缺席的主体走单用户 `default` 赦免,而那条赦免同时是 `settings.hooks` / `settings.env` / `cwd` / `additionalDirectories` 四条能力授予面的闸 ——
1524
+ 一台多用户机器按单用户执法,这些面对每个调用方打开(四条的谓词各不相同,别照「都一样」读:`settings.hooks` 只看 `REQUIRE_PRINCIPAL`、不看车道;`additionalDirectories` 只在 `REMOTE_EXEC=host` 且单用户时兑现;
1525
+ `settings.env` / `cwd` 另有 device 车道臂)。被排除的候选各自是单机常态,证明不了多租:`OPERATOR_PRINCIPALS` 非空(开了 `IMAGE_BAKES_ENABLED` 的单机被强制填它)、
1526
+ `SERVICE_AUTH_TOKENS` 条目数(一台机器给 cli 与 web-admin 各发一只令牌)、`DB_BACKEND=mysql|pg`(耐久性轴)、`BIND_HOST` 非回环(容器里 `0.0.0.0` 是单机常态)。
1435
1527
  - ⚰️ **提交体 `approverPosture` 7.106.0 退役**(core 7.34.0 的单键权限模式):7.93.0–7.105.0 上它是一句宿主姿态声明、托管形上被忽略并留声;
1436
1528
  core 7.34.0 起按名拒这个键,本仓整键删 —— fresh 提交体仍带它 ⇒ 400 `request.body_shape`(`unknownKeys:["approverPosture"]`);续跑腿重放不过受理门、
1437
1529
  盘上体里的该键零读点(惰性),parked 任务不因它砖死。能力位 `taskApproverPosture` 同批删。
@@ -1462,7 +1554,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1462
1554
  被撤名单,别按「我加回去了」推断。
1463
1555
  当前被撤名单可从 `GET /v1/diagnostics/wiring` 的 `mcpRevocations.revoked` 读(`null`=本进程无
1464
1556
  配置管道);`GET /v1/sessions/:id/mcp` 面板与执行面同一台账(被撤服务器的工具不再展示)。
1465
- 纯 env 配置的 `MCP_SERVERS` 部署无中心撤销语义,行为逐字不变。
1557
+ 没有配置管道的部署(纯 env)没有部署级 MCP 声明 —— 本服务没有声明 MCP 服务器的 env 旋钮 —— 也就没有撤销语义。
1466
1558
 
1467
1559
  **技能从哪来(来源与覆盖序)**
1468
1560
 
@@ -1474,7 +1566,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1474
1566
  不会降成每个场景都有的全局技能;第一行不是围栏的文件没有 frontmatter,仍是全局技能。兄弟技能照装。
1475
1567
  2. **配置源的技能**:配了中心 = web-admin 的 skills 域;`CONFIG_PROVIDER=local` = `config.d/skills.json`(`{ "skills": [{ "name", "description", "content", "scenarios"?, "enabled"?, "allowedTools"?, "disallowedTools"? }] }`,
1476
1568
  正文写在 `content` 里;两只工具名单 = 字符串数组,缺席 = 没有,在场 = 整只原样交给引擎:`allowedTools` 是该技能被激活的那一 run 的免问规则来源,`disallowedTools` 是拒规则来源,
1477
- 拒侧引擎读成空的条目 ⇒ 那次激活不发生(`userInvokedSkills` 按名点用的终局 `failed`、码 `config.skill_deny_unreadable`;模型自己调 `Skill` 工具的,那一次调用被拒);中心的技能域同形)。**按名盖在出厂技能上**(同名 ⇒ 配置源这条赢),`enabled: false` 的不装;两种配置源同一条路径(7.128.0 起;此前本地形整域不生效)。
1569
+ 拒侧引擎读成空的条目 ⇒ 那次激活不发生(`userInvokedSkills` 按名点用的终局 `failed`、码 `config.skill_deny_unreadable`,同步腿零工作拒 **409**(7.148.0 起;此前 HTTP 200 + failed 体且占着幂等键 —— 运维改好那一条之后同一请求原样重发即可);模型自己调 `Skill` 工具的,那一次调用被拒);中心的技能域同形)。**按名盖在出厂技能上**(同名 ⇒ 配置源这条赢),`enabled: false` 的不装;两种配置源同一条路径(7.128.0 起;此前本地形整域不生效)。
1478
1570
  起服每装上一条一行 `web_admin_skill`(`overrodeBuiltin` 说盖没盖出厂的);拿不到正文的一条一行 `web_admin_skill_failed`(`reason`:`body_absent` / `read_failed`;**正文为空串视同缺席**:`content` 缺省或写成 `""` 的条目,本地与中心同判 `body_absent`),
1479
1571
  这一条不装、同名出厂技能保留、起服照常。重启生效(与 MCP 同:改了之后 `/health` 的 `restart.reasons` 含 `skills`)。
1480
1572
  3. **插件带的技能**(plugins 域):只补**没被占的名字**,撞名让位(起服行 `center_plugins_applied` 的 `letGo`)。
@@ -1491,7 +1583,8 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1491
1583
  每条 `{ name, description, source }`:`source` 是 `"factory"`(第 1 条,本部署的技能目录)或 `"center"`(第 2、3 条,配置源交来的;盖掉出厂同名的也是 `"center"`),不带正文。
1492
1584
  与这个场景的 run 交给引擎的是**同一只过滤**:卡上列的就是本服务交给这一 run 的部署技能,点名(提交体 `userInvokedSkills`)用它的 `name`;没有技能 ⇒ `skills: []`。
1493
1585
  两种情形卡上有、run 里没有:带 `disableSkills: true` 的那一发(一只都不装);正文超过引擎的加载上限(1 048 576 字符)的技能 —— 引擎不装它、每一 run 报一条配置错,点它的名字答 `400 config.user_invoked_skill_unknown`(缩正文或把材料挪进附件)。
1494
- 内建 `code` 与 `default` 共用一只工厂,技能按 `default` 过滤 —— 只写了 `scenarios: code` 的技能不进内建 `code` 的 run,卡上也不列(要进,标 `default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,按它自己的名字 `code` 过滤。
1586
+ 内建 `code` 与 `default` 共用一只工厂(工具面恒等),技能范围各按自己的名字过滤(7.148.0 起;此前 `code` 借 `default` 的范围,两个方向都违上面「写了 = 只进这些场景」):只写了 `scenarios: code` 的技能进内建 `code` 的 run、卡上也列,不进 `default`;
1587
+ 只写了 `scenarios: default` 的技能进 `default`、不进 `code`(要两个场景都进,写 `scenarios: code, default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,同样按它自己的名字 `code` 过滤。
1495
1588
  能力位 `scenarioSkills`(恒 `true`)缺席 = 老 server,卡上没有这一键;别从 `400 config.user_invoked_skill_unknown` 的拒句里刮名字(那是引擎的散文,会变)。
1496
1589
 
1497
1590
  **看一条 run 装着哪些技能(7.137.0 起)**:这条 run 的 `wiring_manifest` 帧(流式 SSE、`GET /v1/runs/:id/events`、非流式 `POST /v1/tasks` 200 体的 `wiringManifest`)带本服务自铸段
@@ -1521,16 +1614,18 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1521
1614
  运维 warn `mcp_injection_dropped`(`malformed_entry` / `field: toolAxes`)+ 用户面通告 `mcp.injection_dropped`(续跑家族同投);`POST /v1/capabilities/mcp/probe` 同拒同句,
1522
1615
  判官受理的声明与提交腿同读(只有空条目的工具 = 没声明,受理)。
1523
1616
  - **请求体自带的 `a2aPeers[].toolAxes`**(键 = 对端的远端 skill id)读同一只判官(引擎根导出的 `readToolAxes`,A2A 一面;形同上):读不出 —— 出词表、`egress` 非布尔、
1524
- **未知成员**、某只 skill 的值不是对象、`__proto__` 键、不是纯数据 —— ⇒ 那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑(与这条腿上其余字段坏形同一处置)。
1617
+ **未知成员**、某只 skill 的值不是对象、`__proto__` 键、不是纯数据 —— ⇒ **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)在入口 `400 request.field_invalid`,
1618
+ 拒句点名 `a2aPeers[i].toolAxes` 与 peer 名,整份请求不收、零 run、无 warn(7.136.0 起,拒形与判序见 9.5h;7.135.0 及以前这一形是「那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑」);
1619
+ **续跑重放存下的旧请求体**不拒(拒会砖死升级前停驻的任务):那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑(与这条腿上其余字段坏形同一处置)。
1525
1620
  此前本服务对 A2A 一面用自己的读法、未知成员静默忽略;引擎的准备门对同一份声明本就整单拒,两层现在是同一只判官、同一个方向。
1526
1621
  续跑重放升级前存下的请求体时与 `mcpServers[].toolAxes` 同一只一次性迁移 M19:窗内判官判不过的那一格按 7.131.x 的 A2A 读法重读一次(未知成员剔、三轴留,产物再过同一只判官),
1527
1622
  对端照挂、一行运维 warn `a2a_tool_axes_read_as_before_upgrade`(`peer` / `migration` / `problem` / `note`);已知轴写坏(出词表、`egress` 非布尔、某只 skill 的值不是对象、
1528
1623
  整份不是对象)的对端 7.131.x 当时就整条丢了,重读照旧整条不挂(`a2a_injection_dropped` 点名);窗关之后回到上面的读法(整条不挂)。
1529
- - **请求体自带的 `mcpServers[].transport.env` / `transport.headers`**(「名 → 串」记录,与 `a2aPeers[].headers` 同一只读法):名叫 `__proto__` 的成员、值不是串的成员、
1530
- 整只不是纯数据 ⇒ 新提交 `400 request.field_invalid` 点名第一处(`__proto__` 成员点名成员级路径 `…headers.__proto__`;值不是串 / 整只读不成点名那个键,原因句点名成员)。
1531
- 续跑重放存量请求体时,按 7.131.x 的读法会交不出去的成员 —— `__proto__` 名的成员、有值不是串的成员的整键、`transport.args` / `transport.principalHeader` —— 在本部署兑现请求 MCP 时**拒续跑**(名字被部署占住的条目不算:它整条输给部署):
1624
+ - **请求体自带的 `mcpServers[].transport.env` / `transport.headers`**(「名 → 串」记录,与 `a2aPeers[].headers` 同一只读法、同一张保留成员名表):名叫保留名(`__proto__` / `constructor` / `prototype`,大小写敏感)的成员、值不是串的成员、
1625
+ 整只不是纯数据 ⇒ 新提交 `400 request.field_invalid` 点名第一处(保留名成员点名成员级路径如 `…headers.constructor`;值不是串 / 整只读不成点名那个键,原因句点名成员)。
1626
+ 续跑重放存量请求体时,按 7.131.x 的读法会交不出去的成员 —— 保留名的成员、有值不是串的成员的整键、`transport.args` / `transport.principalHeader` —— 在本部署兑现请求 MCP 时**拒续跑**(名字被部署占住的条目不算:它整条输给部署):
1532
1627
  `409 resume_blocked_by_policy`(`blockedBy: "request.field_invalid"`,`field` = 第一处成员坐标如 `mcpServers[1].transport.headers`;句子列出全部坐标,只给键名、不回显值),卡 / 任务仍待决、行不动;
1533
- A2A 对端 `headers` 只在 `__proto__` 名的成员上同拒(`a2aPeers[i].headers.__proto__`,本部署兑现请求 A2A 时);`headers` 里有值不是串的成员 ⇒ 那一条对端整条不挂(`a2a_injection_dropped` 点名)、任务照跑 —— 整条不进不在拒的射程里(MCP 一面同:整条不进的条目照旧 `mcp_injection_dropped`)。7.146.x 及以前续跑照挂、剔掉那几处,一行运维 warn `mcp_transport_members_dropped` / `a2a_peer_headers_members_dropped` ——
1628
+ A2A 对端 `headers` 在保留名的成员上同拒(`a2aPeers[i].headers.<保留名>`,本部署兑现请求 A2A 时);`headers` 里有值不是串的成员 ⇒ 那一条对端整条不挂(`a2a_injection_dropped` 点名)、任务照跑 —— 整条不进不在拒的射程里(MCP 一面同:整条不进的条目照旧 `mcp_injection_dropped`)。7.146.x 及以前续跑照挂、剔掉那几处,一行运维 warn `mcp_transport_members_dropped` / `a2a_peer_headers_members_dropped` ——
1534
1629
  调用方只看到续跑成功,而 MCP 请求没带它写的鉴权头;这两行 warn 本版退役。出路:续跑请求带 `mcpServers` / `a2aPeers` 的合形整表(下一条「续跑时换表」;判的是盖过这张表之后的体),或取消那只任务、按本版的形重提。新提交上两形都 `400 request.field_invalid`(同一只判)。
1535
1630
  - **续跑时换表(7.143.0 起)**:停着的任务续跑(`POST /v1/approvals/:sessionId/decide` · `POST /v1/assistant/tasks/:id/plan_review` · `POST /v1/assistant/tasks/:id/resume` · `POST /v1/sessions/:id/wake`,各续各的门:工具审批卡 / `plan_review` 门 / 资源限停驻 / `task_done` 停驻)默认重放提交时存下的 `mcpServers` / `a2aPeers`;续跑请求体带上同名键(与提交体同形、同一只判官)就以它为准,
1536
1631
  并写回成之后续跑的表 —— 用户移除过一台 MCP 服务器之后,壳送此刻的整张表,续跑就不再重连那一台(停在那一台调用上的任务由引擎判:引擎 7.61.0 起三码各答各形,以 core 为准:那只工具不在了 ⇒ `resume.tool_unavailable`(引擎重开检查点:卡仍 pending、可再决);同名而已不是批准时那一只 ⇒ `resume.tool_contract_mismatch`(同样重开);还是那一只而它的面或声明变了 ⇒ `resume.target_moved`(这次调用不执行、批准用掉,须重新发起),本服务逐字过境);直连门部署的两道回决门拒收这两键(契约 §4e-bis)。
@@ -1546,7 +1641,7 @@ MCP_ELICITATION_TTL_MS=300000 # 一张没人填的表单挂多久后释放(
1546
1641
  ```
1547
1642
  - 四个节流钮都是 `numEnvBounded`:**越界启动期响亮拒,不静默夹取**(一个手滑的多小时 TTL 会让表单实际上永不过期)。
1548
1643
  - 总开关关着时四个节流钮解析照跑但无消费者——不设=行为不变。帧格式见
1549
- [`docs/ASSISTANT-WIRE-CONTRACT.md` §4a-quater](docs/ASSISTANT-WIRE-CONTRACT.md)(`elicitation` / `elicitation_complete`)。
1644
+ 契约 §4a-quater(内部记录;`elicitation` / `elicitation_complete`)。
1550
1645
  - **回给 MCP server 的词**(一事一词):没人应答的释放 —— 答题窗到期、run 中止、腿退出、表单没送达 —— 回 `cancel`;本部署按规则当场拒(没有 run 上下文 / url 形 / 节流越界)回 `decline`;
1551
1646
  用户经 respond 答的词原样落地。7.135.x 及以前,答题窗到期与表单没送达回 `decline`。判法与 helper 该怎么读见契约 §4a-quater 的「终局词律」。
1552
1647
  - 两帧同样**随 run 账本回放**(与上面的问题卡同一条规则:`/v1/tasks/stream` 腿有 durable run 行时先落账再直写,`/v1/runs` 腿只有账本),重载后经 `GET /v1/runs/:id/events` 取回表单卡;
@@ -1562,24 +1657,35 @@ A2A_SERVE_ENABLED=true # 总开关,默认 **false**(operat
1562
1657
  A2A_SERVE_URL=https://agent.example.com/v1/a2a # 【开了就必填】卡上公告的对外 JSON-RPC 端点
1563
1658
  A2A_SERVE_NAME=sema-edge # 卡上的 name(默认 "sema-server")
1564
1659
  A2A_SERVE_DESCRIPTION="…" # 可选
1565
- A2A_SERVE_SKILLS='[{"id":"review","name":"Code review","description":"…","scenario":"code-review"}]'
1660
+ A2A_SERVE_SKILLS='{"version":2,"skills":[{"id":"review","name":"Code review","description":"…","scenario":"code-review"},
1661
+ {"id":"health","name":"Health","scenario":"health","audience":["user:alice"]}]}'
1566
1662
  A2A_SERVE_BLOCKING_WAIT_MS=45000 # blocking:true 的服务端等待窗(默认 45000,有界 [0,300000])
1567
1663
  ```
1568
1664
  - **`A2A_SERVE_URL` 开了就必填,且必须是可解析的 http(s) URL** —— 缺席/坏形**启动期拒**。可达地址是
1569
1665
  运维声明的,服务端**不从请求 `Host` 头推导**:那等于让一个匿名的发现请求决定我们公网卡上公告的地址。
1570
1666
  (同族先例:`FLEET_ADVERTISE_ADDRESS`。)🔒 URL **不许带 userinfo**(`https://user:pass@host/…`)——
1571
1667
  它逐字上匿名可拉的卡,等于把凭据发给每个发现请求;带了**拒启**。入站鉴权走**头形**(服务凭据)。
1572
- - **`A2A_SERVE_SKILLS` = JSON 数组,坏形拒启**(非 JSON / 非数组 / 条目缺 `id` 或 `scenario` / `id` 重复
1573
- / 超长 / 超过 64 条)。声明面**没有**「丢掉坏的那条继续跑」的合法语义:那会让你以为公告了一个技能而
1574
- 对端根本看不到。每条 skill 的 `scenario` 是**内部**路由键 —— 它**不上卡**(自动枚举场景名给公网是
1575
- 本设计唯一点名禁止的事),只决定点名这条 skill 的请求跑哪个场景。
1668
+ - **`A2A_SERVE_SKILLS` = 版本化外壳 `{"version":2,"skills":[…]}`,坏形拒启**(非 JSON / 不是外壳 / `version` 不是数字 `2` /
1669
+ 外壳或条目里有别的键 / 条目缺 `id` 或 `scenario` / `id` 重复 / 超长 / 超过 64 条 / `audience` 不合形)。**旧的裸数组形拒启**:
1670
+ 旧值原样放进 `{"version":2,"skills":<旧值>}` 即可(MIGRATION 本版同条)。声明面**没有**「丢掉坏的那条继续跑」的合法语义:
1671
+ 那会让你以为公告(或限制)了一个技能而对端看到的是另一回事。每条 skill 的 `scenario` 是**内部**路由键 —— 它**不上卡**
1672
+ (自动枚举场景名给公网是本设计唯一点名禁止的事),只决定点名这条 skill 的请求跑哪个场景。
1673
+ - **`audience`(可选)= 这条技能发布给谁**(验过的主体名,精确串,1–64 项;没写 = 所有调用方)。不在名单里的调用方在任何卡上都看不见它、
1674
+ 按 id 点名得到与「没有这条技能」逐字节相同的 `-32602`。名单里的人经 `POST /v1/a2a` 的方法 `agent/getAuthenticatedExtendedCard`
1675
+ 拉到自己那份卡(公开卡上恒有一位 `supportsAuthenticatedExtendedCard: true`,与配没配名单无关)。身份只认**验过的人**:BFF 形上游注入的
1676
+ 主体头、直连门的 principal JWT、SSO 凭据;只带 service 凭据(含代部署本人)的请求没有人、只得公开技能;fleet 资格与运维名册不扩大投影(运维按他自己这个人投影)。
1677
+ 🔴 **发布 ≠ 授权**:`audience` 只管「谁看得见、谁能按 id 点名」,不管谁能跑背后的场景 —— 不带 `sema.skillId` 的消息、映射到同一场景的公开技能、
1678
+ 原生提交族(`POST /v1/runs` / `POST /v1/tasks` / `POST /v1/tasks/stream` 体里的 `scenario`)都不经过它,唯一的闸是场景门(中心白名单)。
1679
+ 受限技能**不要**映射到部署缺省场景,也**不要**与公开技能共用场景。名单的强度 = 这台部署的已验身份来源:没接任何已验身份来源的部署(没配服务凭据、没装鉴权桥、没开直连门;只设 trace 凭据或只开 `REQUIRE_PRINCIPAL` 不算)上写了 `audience` ⇒ 起服拒(拒句一行给两条出路:接一个已验身份来源 / 删掉 `audience`),故不存在该形;
1680
+ 卡与技能选择只认验过的身份来源(出示服务凭据之后的主体头 / SSO 凭据 / 直连门 JWT),不出示凭据、只自报主体头的调用方读成匿名(只看公开技能);BFF 形上,名单的强度等于「持共享 service 凭据的调用方够不到 worker」
1681
+ (worker 对持凭据者直接可达时谁都能自报主体头);不想依赖网络隔离就用直连门或 SSO 凭据。完整读法见契约附录 C.7。
1576
1682
  - 两条路由的**鉴权位置刻意不同**:`GET /.well-known/agent-card.json` 在服务凭据门**外**(公网匿名可
1577
1683
  发现,零存量披露);`POST /v1/a2a` 在门**内**(与本仓其余 API 同一套凭据)。
1578
1684
  - `message/send` / `tasks/get` 都站在 **durable run store** 上(能力位 `a2aServe` 与这条是同一只判据)—— 缺它时这台 worker 整个**不作为 A2A agent 对外存在**:
1579
1685
  卡与 `POST /v1/a2a` 一起 404(7.116.0 起;此前卡照发、两个方法回 JSON-RPC `-32004`,现在不再出现这个码)。开关打开时 boot 日志有一行 info `a2a_serve_enabled`
1580
1686
  (键:`card` · `rpc` · `advertisedUrl` · `skills` · `blockingWaitMs`);开关打开而没有 run store 时改打一行 warn `a2a_serve_not_served`(`reason` + `detail`,`detail` 点名出路)。
1581
- - 完整 wire 契约(方法表、错误码全表、Task 形与任务态投影、反枚举语义、版本槽)见
1582
- [`docs/ASSISTANT-WIRE-CONTRACT.md` 附录 C](docs/ASSISTANT-WIRE-CONTRACT.md)。
1687
+ - 完整 wire 契约(方法表、错误码全表、Task 形与任务态投影、反枚举语义、版本槽、技能发布与扩展卡)在
1688
+ 契约附录 C(内部记录,不随包)。
1583
1689
 
1584
1690
  **可选 — 父进程存活监视(`SEMA_PARENT_PID`,默认不装配)**
1585
1691
 
@@ -1772,7 +1878,7 @@ ENGINE_HANDOVER_LINGER_MS=1800000 # 可选,默认 30 min,下界 60000(壳经 POS
1772
1878
  **布尔旋钮的取值与极性(运维必读)**
1773
1879
 
1774
1880
  布尔 env **收 `true` / `false` / `1` / `0` / `yes` / `no` / `on` / `off`**(大小写不敏感;词表自 7.16.0
1775
- 起放宽)——`LSP_ENABLED=1` 这种人眼读作"开"的写法,现在代码里也
1881
+ 起放宽)——`LEADER_ENABLED=1` 这种人眼读作"开"的写法,现在代码里也
1776
1882
  读作"开",不再是此前"看着开、其实关"的陷阱。词表**外**的值(如 `enabled`、`TRUE1`)⇒ 进程**直接
1777
1883
  拒启**,报错点名该 env 名/收到的原始值/可接受词表(fail-loud,不留静默默认)。旧的「`config_env_
1778
1884
  invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并退役,不再覆盖布尔旋钮——该事件现在
@@ -1801,26 +1907,21 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1801
1907
  | `CONFIG_LKG_DISABLED` | `CONFIG_LKG_ENABLED=false` | 开 | 中心 effective 配置的 LKG 落盘(读写双关) |
1802
1908
  | `HOST_BG_DISABLED` | `HOST_BG_ENABLED=false` | 开 | host lane 后台 shell 能力总闸 |
1803
1909
  | `HOST_EXEC_SPOOL_DISABLED` | `HOST_EXEC_SPOOL_ENABLED=false` | 开 | host lane exec 的 spool 形 stdio(关掉=回退管道旧形) |
1804
- | `LSP_ENABLED=false`(拆分前用来关 host 腿那半) | `LSP_HOST_ENABLED=false` | 开 | 见下 |
1910
+ | `LSP_ENABLED`(任一值) | 无替代名 —— 撤掉它;host 车道的开关是 `LSP_HOST_ENABLED` | — | 沙箱车道的 LSP 桥已删除(e2b / k8s 不挂 LSP),见下 |
1805
1911
 
1806
- 四个旧名都是"负名"(`X_DISABLED`),读的时候要双重否定;新名一律正向 + 缺省显式。
1912
+ 前四个旧名都是"负名"(`X_DISABLED`),读的时候要双重否定;新名一律正向 + 缺省显式。
1807
1913
  🪦 **墓碑语义**:旧名**在场即拒启**——值是 `true` 还是 `false` 都一样(设 `false` 的人同样以为它还生效),
1808
1914
  **正名设没设也一样**(7.104.0 起;此前正名设了时旧名被静默忽略 —— 迁移到一半、两个名字都留着的部署以为旧名还在生效)。
1809
1915
  拒句与其余退役 env 名同一句、同一个码 `[config.env_key_retired]`,点名旧名、正名与换算(`X_DISABLED=true` 即 `X_ENABLED=false`)。
1810
1916
  所以不存在"两个名字同时设"而能起服的状态,也不存在"旧名还在悄悄生效"的状态。墓碑**不设到期**:退役名的拒启是永久的,
1811
1917
  只有某个名字要被赋予新含义时才删它的墓碑(届时 MIGRATION 写明)。
1812
1918
 
1813
- **`LSP_ENABLED` 一分为二**:它原来同时驱动两条腿,而且两腿缺省相反——沙箱腿缺省**关**(要烤好的
1814
- `ai-agent-code-lsp` 模板;⚠️ 该模板**只烤 TS/JS**——Python/Go 烤在 `dev-base`,dart/kotlin/java/swift
1815
- 烤在 `dev-mobile`,四模板物理独立,`E2B_TEMPLATE` 选错则未烤语言的 LSP **静默退化到 grep/read**;
1816
- 对照表见 ARCHITECTURE §7,真源=`src/lsp/e2b-bridge.ts`),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
1817
- 拆分后沙箱腿是 `LSP_ENABLED`(opt-in)—— **本版在 e2b / k8s 上不挂任何东西**(见下段「LSP 的车道射程」);host 腿归 `LSP_HOST_ENABLED`(opt-out)。
1818
- 🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
1819
- `LSP_ENABLED=true` 照常起服,但本版只换来一行 `lsp_not_mounted`(沙箱两车道不挂 LSP,见下段);绑定读在这两条车道实现之日它重新生效。
1820
-
1821
- **LSP 的车道射程(7.127.0 起,引擎 7.50.0)**:LSP 会话只读执行环境「绑定持有」的那只文件(判过的对象就是读到的对象)。host 车道实现了绑定读,LSP 照常(`LSP_HOST_ENABLED`);
1822
- 沙箱两车道(e2b / k8s)的执行环境本版不实现绑定读 —— 挂上就是每个文件同步都被拒 ⇒ **这两条车道不挂 LSP**:开着 `LSP_ENABLED=true` 照常起服,起服打一行
1823
- `lsp_not_mounted`(带原因),模型名册里没有 LSP 工具、`wiring_manifest` 上没有 `lsp` 段,代码导航回到 grep / read。ssh / adb / device / local-docker 本来就不挂。
1919
+ **LSP 只有一根旋钮 `LSP_HOST_ENABLED`(缺省开),只在 host 车道且单主时挂**:本机语言服务器是 worker 上的子进程、在 worker 盘上找根,
1920
+ 所以它只在「worker 盘就是调用方的盘」时挂 —— host 车道、未设 `REQUIRE_PRINCIPAL=true`(多主的 host 部署本来就拒启)。PATH 上有对应的
1921
+ language server 就用,没有就优雅退回 grep / read;`LSP_HOST_ENABLED=false` 关掉。**其余车道没有 LSP**:e2b / k8s / local-docker / ssh / adb /
1922
+ device / none 一律不挂,模型名册里没有 LSP 工具、`wiring_manifest` 上没有 `lsp` 段,代码导航用 grep / read。服务入口与 `run-local` 同一条律。
1923
+ 沙箱车道的 LSP 桥已删除(7.127.0 起它本来就挂不上:引擎只读执行环境「绑定持有」的那只文件,沙箱两车道不实现绑定读);它的开关 `LSP_ENABLED`
1924
+ 是墓碑(上表),设了任一值都拒启,空串按未设。e2b 模板里烤的语言服务器只是工具链内容(模板的月检只验它们在不在,不验有没有谁在驱动它们)。
1824
1925
 
1825
1926
  **host 腿语言服务器拿到的环境 + 点名放行 `LSP_ENV_ALLOW`(7.101.0 起)**:host 腿的语言服务器是本机子进程,它拿到的环境
1826
1927
  = 服务进程环境**剥掉凭据形的键**(与模型可驱动的 host shell 同一只剥除:配置目录 `secret` 型键按名剥 ∘ 引擎按键名形状剥
@@ -1851,17 +1952,17 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1851
1952
  (下一段),不是 sema-server 部署方的旋钮(引擎 CHANGELOG 里把它说成部署方出路的那一句,对 sema-server 部署不成立)。7.129.0 起这条腿的三个继承词只在引擎
1852
1953
  一处判(`inheritedHostEnv`),本服务自己的凭据名起服向引擎声明一次、任何词都不交(下面「请求级 `inheritEnv`」段)。被剥变量的
1853
1954
  出路**按用途分**:git 凭据走 git 配置文件 / credential helper(隔离全局配置的两条 git 腿 —— project-memory 读仓、center-plugins 克隆 ——
1854
- 另见 `docs/DEPLOY-PREREQS.md` 7.103.2 那一节的「按腿分」);带口令的出网代理走不带口令的形(`~/.gitconfig` 里只带用户名的 `http.proxy` + credential helper,见
1855
- `docs/DEPLOY-PREREQS.md`「带口令的出网代理」);语言服务器 / bake-runner 走各自的名单 `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`;沙箱车道走
1955
+ 读不到宿主 `~/.gitconfig`,无害设置只能留在 env:凭据条目别写进 `GIT_CONFIG_PARAMETERS`,或改用 `GIT_CONFIG_COUNT` 组;其余四腿可写进宿主 git 配置文件);带口令的出网代理走不带口令的形(`~/.gitconfig` 里只带用户名的 `http.proxy` + credential helper 供口令,或让代理按来源免密);语言服务器 / bake-runner 走各自的名单 `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`;沙箱车道走
1856
1956
  `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`。要不要给 host shell 加部署级名单属于能力面变更(安全轴),不在文档里顺手给。
1857
1957
 
1858
1958
  **请求级 `inheritEnv`(7.106.0 起)—— 调用方声明,不是部署旋钮**:提交体顶层键 `inheritEnv:"all"` 让**这一条请求**的模型驱动 host shell
1859
1959
  继承整份用户环境(CC 交互形,用户自己的凭据形变量在内)—— 🔴 **但不含本服务自己的凭据**(7.129.0 起,引擎 7.51.1):配置目录全部 `type:"secret"` 名
1860
1960
  (`SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` …)与名单型旋钮名起服已向引擎声明为本进程自有凭据,生效配置里每条模型条目的 `apiKeyEnv` 也在名单里
1861
1961
  (中心热应用新增 / 改名条目时在模型目录换代前重声明,名单只随落地的世代增长 —— 改名后旧名照扣到重启,没落地的候选不留痕;条目的 `apiKeyEnv` 是引擎扣不住的名,或它点到的名正被某只名单型旋钮交给沙箱 / 语言服务器(见下文沙箱名单段)⇒ 该候选整拒),`"all"` = 整份环境**减**它们,任何继承词都不把它们
1862
- 交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配 `inheritEnv:"all"`;7.146.0 起请求 `settings.env` 不收凭据,见 9.5j);这些变量此刻的**值**也交给引擎按值扣(同值换名的副本同样进不了模型 shell),引擎判某个值「不是凭据值」(空白 / 占位词 / 一个字符重复 / 不足 8 字符)⇒
1863
- 那一个值不按值扣(名照扣、不拒启,能力位 `inherit_env.own_credential_values_withheld` 答 false),起服与模型目录换代时每个这样的变量出**一条** warn 结构化行 `own_credential_value_not_withheld { name, reason }`
1864
- (`reason` 是引擎的拒句原文,从不回显值;起服第一步 logger 还没建时,这一行当拍以同一行形写 stderr,不缓冲;logger 接上之后——模型目录换代并进的条目的值被拒——这一行受 `LOG_LEVEL` 约束,`error` 级读不到,机读事实读上面那个能力位)—— 按事件名告警,给该变量一个真值即可。值是**网关密钥**(`MODEL_API_KEY` / `ANTHROPIC_API_KEY`,起服交给模型适配器的那一个)时,进程输出里在这一行之外还有**引擎自己**的一行裸文本(`a credential the engine was handed is …`):引擎在适配器构造口收下密钥的那一刻对同一个值说一次,这个收值口没有通告席,落引擎自己的 `console.warn`(引擎已知限制;本服务不是它的写者,也不改写它);给密钥一个真值两行一起消失。`"scrub"` / 缺席 = 上面的缺省剥除。🔴 **`"all"` 只在单用户部署兑现**:`REQUIRE_PRINCIPAL=true`
1962
+ 交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配 `inheritEnv:"all"`;7.146.0 起请求 `settings.env` 不收凭据,见 9.5j);这些变量此刻装着的**每一枚凭据**也交给引擎按值扣 —— 整值,列表形另加每一枚:`SERVICE_AUTH_TOKENS` 表里的每一枚令牌、`APPROVAL_HMAC_KEYS` 里的每一把钥(7.149.0 起;此前只交整张表的原文,表里一枚令牌换个变量名放着照进模型 shell)——
1963
+ 同值换名的副本同样进不了模型 shell(整串比:`Bearer <令牌>`、令牌多一个字符 / 少一个字符、只是系统名的值都不是副本,照交),引擎判某一枚「不是凭据值」(空白 / 占位词 / 一个字符重复 / 不足 8 字符)⇒
1964
+ 那一枚不按值扣(同一变量的其余几枚照扣,名照扣、不拒启,能力位 `inherit_env.own_credential_values_withheld` 答 false),起服与模型目录换代时每个这样的变量出**一条** warn 结构化行 `own_credential_value_not_withheld { name, reason }`
1965
+ (`reason` 是引擎的拒句原文,从不回显值;起服第一步 logger 还没建时,这一行当拍以同一行形写 stderr,不缓冲;logger 接上之后——模型目录换代并进的条目的值被拒——这一行受 `LOG_LEVEL` 约束,`error` 级读不到,机读事实读上面那个能力位)—— 按事件名告警,给该变量一个真值即可。值是**网关密钥**(`MODEL_API_KEY` / `ANTHROPIC_API_KEY`,起服交给模型适配器的那一个)时也只有这一行:引擎在适配器构造口收下同一个值,声明口拒过的值它不再说第二遍(此前这里写的「另有引擎自己的一行裸文本」已不成立);给密钥一个真值这一行消失。`"scrub"` / 缺席 = 上面的缺省剥除。🔴 **`"all"` 只在单用户部署兑现**:`REQUIRE_PRINCIPAL=true`
1865
1966
  ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带 `"all"` ⇒ 400。词表外的值(`"ALL"` · `""` · 名单 `["PATH"]` · 布尔 · `null` …)、非 host 车道带 `"all"` 这两类都是 `400 request.field_invalid`,7.137.0 起体另带 `field: "inheritEnv"` 与 `reason`(`"unknown_word"` = 是字符串但不在词表;`"not_a_string"` = 根本不是字符串;`"lane_not_host"` = `"all"` 落在非 host 车道;7.147.0 起不再有第四词 `"no_authorizer_session"` —— 一次提交的根会话恒由 authorizer 交出),壳按这两位认拒因、不解析句子(句子 / 码 / 状态不变;续跑腿同判,折叠 409 随体带同一组)。托管部署的 `422 request.host_posture_refused` 不在这组:它说的是「这台部署的姿态不给」,不是这个键写错,按 `errorCode` 认、不带这两位。部署方
1866
1967
  **没有**旋钮能替调用方打开它,也没有旋钮能在单用户部署上关掉它(单用户部署的用户把他自己的环境交给他自己驱动的 shell);起服一行 info
1867
1968
  `task_inherit_env_honored {honored}` 告诉你这台机器认不认。上面「部署级名单不给模型驱动 shell」那条不变 —— 两件事是两条轴(部署名单 vs 调用方 × 单用户射程)。
@@ -1880,14 +1981,14 @@ oracle 门子进程,core 7.33.8):oracle 门若先于工具 shell 剥,通告只
1880
1981
  旧拼法 `_SPAN_` / `_METRIC_HEADERS` 按名剥(值盲);`OTEL_EXPORTER_OTLP_HEADERS` 此前已由本服务的配置目录按名剥,另外六名此前原样交出。语言服务器 /
1881
1982
  bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1882
1983
 
1883
- **「点名放行」只有一种意思**:本服务里凡是「一张名单点到哪些环境变量键」的地方(这里的 `LSP_ENV_ALLOW`、沙箱车道的
1984
+ **「点名放行」只有一种意思**:本服务里凡是「一张名单点到哪些环境变量键」的地方(这里的 `LSP_ENV_ALLOW`、settings hook 环境的 `HOOK_ENV_ALLOW`(9.5c)、沙箱车道的
1884
1985
  `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`(见下表),与**嵌入方**构造 host shell 时的 `inheritEnv:[names]` —— 后者不是部署旋钮,见上一段)用的是同一只谓词 ——
1885
- 点到的键从服务进程环境原样交出去(空串值照交),密钥形也照交;源里没有的名什么也不产生。四只名单型旋钮(`LSP_ENV_ALLOW` /
1886
- `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`)另走同一只名单解析:形错拒启点名第几段、不回显原值,点名任一只名单型旋钮或本服务自有凭据名拒启,
1986
+ 点到的键从服务进程环境原样交出去(空串值照交),密钥形也照交;源里没有的名什么也不产生。五只名单型旋钮(`LSP_ENV_ALLOW` / `HOOK_ENV_ALLOW` /
1987
+ `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`)另走同一只名单解析:形错拒启点名第几段、不回显原值,点名任一只名单型旋钮或本服务自有凭据(名是,或值是本服务的某一枚凭据 —— 另一个名下的副本,7.149.0 起)拒启,
1887
1988
  本进程环境里没有的名按序号一行 `config_env_entry_rejected`,旋钮原文按凭据类对待(配置目录只回在场位、不进任何子进程)。
1888
1989
  几处只在**没点到的键**上不同:语言服务器在缺省剥除的结果之上放回点名键(没点到的照剥,非密钥键照常在);host shell 的
1889
1990
  `inheritEnv:[names]` 与三只沙箱名单是 hermetic 的(没点到的一律不交,与引擎 `NodeExecutionEnv` 同名选项同义;7.129.0 起 host shell 的名单由引擎读 ——
1890
- 点到本服务自有凭据名 ⇒ 执行环境构造时即拒,拒句带名)。
1991
+ 点到本服务自有凭据名 ⇒ 执行环境构造时即拒,拒句带名;点到的变量值是本服务某一枚凭据的副本 ⇒ 引擎扣下、不交)。
1891
1992
  两处都不向对方看齐:host shell 改成「剥后放回」等于在模型可驱动的 shell 上放宽,语言服务器改成 hermetic 会让它当场找不到自己的工具链。
1892
1993
 
1893
1994
  **沙箱带外 env 名单(`E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`,前两只 7.101.0 起、`K8S_SANDBOX_ENV` 7.126.0 起,同上一段的解析与谓词)**:沙箱里的工具要用本服务
@@ -1901,12 +2002,12 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1901
2002
 
1902
2003
  三只旋钮只在**自己的车道被选中时**才求值(别的车道上它们是无关配置,不校验、不告警)。未设 / 空串 = 不注入。**形错拒启**:空段
1903
2004
  (`A,,B`、尾逗号)、非变量名形(`1BAD`、带空格、`NAME=value` 整段贴进来)、点名名单型旋钮(`E2B_SANDBOX_ENV=E2B_SANDBOX_ENV`,
1904
- 或 `LSP_ENV_ALLOW` / 另一只沙箱名单)⇒ 起服失败,拒句点名键与第几段,**不回显原值**。**点名本服务自有凭据名** —— 配置目录里的 secret 型变量(如
1905
- `SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` / `E2B_API_KEY` / `K8S_TOKEN` / `GIT_API_TOKEN`)—— 同样起服失败:名单会把这枚凭据原样装进模型读得到的沙箱;
1906
- 与 `LSP_ENV_ALLOW` 同一条律、同一只判官(引擎拒白名单的那一只,按平台比名),拒句同一个开头 `this deployment's own credential — `,点名键与第几段(`entry #k of n`,k 从 1 起,就是你写的名单里的第 k 项;一句话里只有这一套序号),不回显名也不回显值。
1907
- 沙箱要凭据 ⇒ 给它铸一把自己的(名与值都不同于本服务的任何凭据),放进服务进程环境再点那个名;判的是名字,把同一个值换个名字再点,起服认不出、值照样进沙箱 ——
1908
- 所以「值也不同」是出路的一半。**中心 / config.d 模型条目后来点到同一个名**:条目的 `apiKeyEnv` 在模型目录换代时才并进本服务自有凭据名,那一刻对本表三只与
1909
- `LSP_ENV_ALLOW` 此刻交出去的名用同一只判官重判 —— 新并进的名正被某只名单交出去 ⇒ 那份候选整拒、上一代照常服务,拒句点名旋钮、名与条目;出路二选一:给沙箱另铸一把并改名单
2005
+ 或 `LSP_ENV_ALLOW` / 另一只沙箱名单)⇒ 起服失败,拒句点名键与第几段,**不回显原值**。**点名本服务自有凭据** —— 配置目录里的 secret 型变量(如
2006
+ `SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` / `E2B_API_KEY` / `K8S_TOKEN` / `GIT_API_TOKEN`),**或值等于本服务某一枚凭据的另一个变量**
2007
+ (换了名的副本,列表形凭据逐枚比,7.149.0 起)—— 同样起服失败:名单会把这枚凭据原样装进模型读得到的沙箱;
2008
+ 与 `LSP_ENV_ALLOW` 同一条律、同一只判官(引擎此刻的自有凭据声明:名按平台比,值整串比),拒句同一个开头 `this deployment's own credential — `(副本那一形其后多半句 `a copy under another name …`),点名键与第几段(`entry #k of n`,k 从 1 起,就是你写的名单里的第 k 项;一句话里只有这一套序号),不回显名也不回显值。
2009
+ 沙箱要凭据 ⇒ 给它铸一把自己的(名与值都不同于本服务的任何凭据),放进服务进程环境再点那个名。**中心 / config.d 模型条目后来点到同一枚凭据**:条目的 `apiKeyEnv` 在模型目录换代时才并进本服务自有凭据(名与值),那一刻对本表三只与
2010
+ `LSP_ENV_ALLOW` 此刻交出去的变量用同一只判官重判 —— 新并进的名正被某只名单交出去,或新并进的值正是某只名单交出去的变量的值 ⇒ 那份候选整拒、上一代照常服务,拒句点名旋钮、名与条目;出路二选一:给沙箱另铸一把并改名单
1910
2011
  (名单起服读,改了要重启),或在中心把条目的 `apiKeyEnv` 改指别的变量(下一拍自愈)。形对但服务进程环境里**没有**这个名 ⇒ 不注入,
1911
2012
  起服一行 `config_env_entry_rejected` 只报第几段。点到的键值为空串 ⇒ 照样注入一个空串(7.101.0 前跳过,见 CHANGELOG)。
1912
2013
  `REQUIRE_PRINCIPAL=true` 下照常注入(与 `LSP_ENV_ALLOW` 不同:沙箱 env 是部署方给隔离沙箱显式配的);上面两类拒启不随姿态变。
@@ -1940,10 +2041,13 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1940
2041
  | `Authorization: Bearer <SERVICE_AUTH_TOKEN>` | 设了 `SERVICE_AUTH_TOKEN` 时,除 `/health` / `/readyz` 外的所有请求 | **谁有权调本服务**(OA 后端持有,服务到服务) |
1941
2042
  | `x-agent-principal: user:42` | 设了 `REQUIRE_PRINCIPAL=true` 时 | **代表哪个终端用户**(决定 session 归属 + 记忆隔离;**绝不从 body 取**)。7.134.0 起也决定 fleet 面看得见什么:带了它的 service 凭据请求只看得到、动得到这位用户的(舰队流 / 列表 / 名册 / trace / 用量 / 删除 / fork / 同步);全舰队视图不带它(直连门部署上身份是签名的 principal 令牌,不是这个头) |
1942
2043
 
1943
- > **dev-open 部署的主体头是过滤 / 展示用,不是隔离。** 没接任何身份来源(没配 `SERVICE_AUTH_TOKEN(S)`、没装中心鉴权桥、没开直连门 —— 即 `/health.auth.kinds` 为空)、
1944
- > 也没配 `TRACE_TOKEN`、没开 `REQUIRE_PRINCIPAL` 的部署上,`x-agent-principal` 谁都能自报:
1945
- > 会话列表与单会话的读写口(视图 / head / 事件流 / 改名置顶 / 记忆状态与抹除 / 后台任务 / MCP 重连 / fork / delete)都不按它钉人,带谁的头都读得到同一批会话;`/metrics` 族不要凭据(没配 `METRICS_TOKEN` 时)。
1946
- > 它仍决定新会话记在谁名下、记忆按谁的 scope 走;规则口(`…/policy`)与 `…/notify` 用它选落哪一把属主键。要按用户隔离,给部署设上面几者之一(典型:service token + 写主体头的网关)。
2044
+ > **dev-open 部署的主体头是自报的,不是隔离。** 没接任何身份来源(没配 `SERVICE_AUTH_TOKEN(S)`、没装中心鉴权桥、没开直连门 —— 即 `/health.auth.kinds` 为空)、
2045
+ > 也没配 `TRACE_TOKEN`、没开 `REQUIRE_PRINCIPAL` 的部署上,`x-agent-principal` 谁都能自报。它说的是「这次请求为谁」,每一口同读(7.149.0 起;与提交续用别人会话被拒同一读法):
2046
+ > 带了头的请求只看得到、动得到这位主体的会话与 run(会话列表 / 单会话读写口 / 规则口 `…/policy` / `…/notify` / fork / run 轮询 —— 别人的与不存在的同答 404;删除别人的会话答 `200 {"deleted":false}`,与删不存在的同),
2047
+ > `/metrics` 族与 `GET /v1/tasks/source-summary` 对它答 `401`(配了 `METRICS_TOKEN` 时出示它照放);
2048
+ > **不带头**(也不带这台部署验不过的 Bearer —— 带了它的请求没有可信身份,不代部署本人)的请求代部署本人:列表与单会话读写口看得到、动得到全部会话,run 轮询看得到全部 run,
2049
+ > `/metrics` 族不要凭据(没配 `METRICS_TOKEN` 时);run 的驱动口(取消 / steer …)与提交续用别人的会话照旧按属主判,对它同样不开。它也决定新会话记在谁名下、记忆按谁的 scope 走。
2050
+ > 头谁都能自报 ⇒ 这给的是一致的视图,不是隔离;要按用户隔离,给部署设上面几者之一(典型:service token + 写主体头的网关)。≤7.148.x:带不带头都看得到全部会话(只有提交续用按头判)。
1947
2051
  > **装了中心鉴权桥(`SEMA_WEB_ADMIN_URL` 在位即默认开,或 `AUTH_BRIDGE_ISSUER`)就不是 dev-open**(7.132.0 起;此前这一形被读成 dev-open):带登录令牌的调用方按令牌里的身份钉人,
1948
2052
  > 不带任何凭据的列表 `401 auth.unauthorized`、`/metrics` 族 `401`(要 `METRICS_TOKEN`)。仍成立的边界:没配 service token 的部署上,不带登录令牌的请求自报的主体头仍按身份读
1949
2053
  > (与 `REQUIRE_PRINCIPAL` 无 token 形同读)—— 要让「没令牌就没有身份」成立,配 `SERVICE_AUTH_TOKEN`(全局凭据门:每个请求要么带 service token、要么带登录令牌)。
@@ -1969,7 +2073,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1969
2073
 
1970
2074
  | env 键 | 缺省 | 说明 |
1971
2075
  |---|---|---|
1972
- | `PRINCIPAL_HEADER` | `x-agent-principal` | 上面那个身份头的**名字**。🔴 **缺省值是跨仓 wire 常量,不是给部署方自定义的**:这个名字**不在任何 wire 面上广播**(`/v1/capabilities`、`/health` 都没有它),所以没有下游能在运行期问 server「你叫它什么」——SDK 有一个 `principalHeader` 逃生舱但 cli/client-core 都没接线,浏览器端 BFF 是逐字硬编码,`mcp-oa` 侧车自己读同名 env(各读各的)。本旋钮只服务于「入口网关已经把身份写进别的头名」这一种特殊部署(让 server 迁就既有网关),不是给部署方起新名字用的。**改名即断下游**,但后果分四形(逐条对过代码):`REQUIRE_PRINCIPAL=true` 下提交腿(`POST /v1/tasks|/v1/runs`)是 `401` + **粗码 `auth.unauthorized`**(authorizer 抛的无 code HttpError 走状态码兜底映射),属主寻址的读/动词面是 `401 auth.principal_required`(路由级细码);两者的文案都逐字回显**你配的头名**,那是现场唯一能指认改名的线索。`REQUIRE_PRINCIPAL` 未开时更坏:**新会话照常成功**、每一条被当成**匿名**(session 归属 / 记忆隔离 / 规则车道 / operator 判定静默走无身份分支,无任何错误码),而**接续一条已属主的会话**会 `401`(`session is principal-owned; missing principal header …`)。⚠️ **SSO 与 direct-door 两条身份来源不经过这个头**(`verifiedPrincipal`),改名对它们无影响 —— 混合形部署因此会出现「一半调用方有身份、一半静默变匿名」的混着长。改了后果自负,且必须同批把每一家下游改到同名。取值须是**单个**合法 header 名(带空格/逗号 ⇒ 启动期拒启:它会撕裂 CORS `allow-headers`)。成文契约见 [`docs/ASSISTANT-WIRE-CONTRACT.md` §0.5](docs/ASSISTANT-WIRE-CONTRACT.md) |
2076
+ | `PRINCIPAL_HEADER` | `x-agent-principal` | 上面那个身份头的**名字**。🔴 **缺省值是跨仓 wire 常量,不是给部署方自定义的**:这个名字**不在任何 wire 面上广播**(`/v1/capabilities`、`/health` 都没有它),所以没有下游能在运行期问 server「你叫它什么」——SDK 有一个 `principalHeader` 逃生舱但 cli/client-core 都没接线,浏览器端 BFF 是逐字硬编码,`mcp-oa` 侧车自己读同名 env(各读各的)。本旋钮只服务于「入口网关已经把身份写进别的头名」这一种特殊部署(让 server 迁就既有网关),不是给部署方起新名字用的。**改名即断下游**,但后果分四形(逐条对过代码):`REQUIRE_PRINCIPAL=true` 下提交腿(`POST /v1/tasks|/v1/runs`)是 `401` + **粗码 `auth.unauthorized`**(authorizer 抛的无 code HttpError 走状态码兜底映射),属主寻址的读/动词面是 `401 auth.principal_required`(路由级细码);两者的文案都逐字回显**你配的头名**,那是现场唯一能指认改名的线索。`REQUIRE_PRINCIPAL` 未开时更坏:**新会话照常成功**、每一条被当成**匿名**(session 归属 / 记忆隔离 / 规则车道 / operator 判定静默走无身份分支,无任何错误码),而**接续一条已属主的会话**会 `401`(`session is principal-owned; missing principal header …`)。⚠️ **SSO 与 direct-door 两条身份来源不经过这个头**(`verifiedPrincipal`),改名对它们无影响 —— 混合形部署因此会出现「一半调用方有身份、一半静默变匿名」的混着长。改了后果自负,且必须同批把每一家下游改到同名。取值须是**单个**合法 header 名(带空格/逗号 ⇒ 启动期拒启:它会撕裂 CORS `allow-headers`)。成文契约在契约 §0.5(内部记录) |
1973
2077
 
1974
2078
  > ⚠️ **Bearer 是"每个请求",不只是 POST。** 配了 `SERVICE_AUTH_TOKEN` 后,**`GET /v1/runs/:id`、`GET /v1/runs/:id/events`(SSE)、`/metrics`** 等除 `/health` / `/readyz` 两只探针外的路由都要带 `Authorization: Bearer`——漏带一律 `401`。下面示例为简洁**省略了 Bearer**,真实调用请逐个补上。
1975
2079
  >
@@ -1977,7 +2081,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1977
2081
  > 同一道门上「没带」与「带错」逐字同一句(区分就是探测面):① 全局凭据门 —— 说「带这台部署的 service token 作 Bearer」,并指 sema-up 部署的
1978
2082
  > 令牌在 `~/.sema-up/<project>/.env` 的 `SERVICE_AUTH_TOKEN`;② 运维口(`/metrics*`、`GET /v1/tasks/source-summary`)—— 说要运维凭据
1979
2083
  > (不带主体头的 service token —— 在部署让它代自己说话时:`/health` 的 `auth.serviceTokenActsForDeployment` 为 `true`,7.146.0 起句子带这一条件;带主体头的 service token 请求代那位主体、不是运维凭据,7.134.0 起;指标口另认只读的 `METRICS_TOKEN`,`source-summary` 另认只读的 `TRACE_TOKEN`);③ 过了凭据门、但这一口要调用方身份(属主寻址读写、规则、记忆)—— 说要一个验过的
1980
- > 调用方身份,**不**叫你再去带 service token。逐字句子见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A.1 码表。壳(sema CLI)接 sema-up 引擎的
2084
+ > 调用方身份,**不**叫你再去带 service token。逐字句子就在应答的 `error` 字段里。壳(sema CLI)接 sema-up 引擎的
1981
2085
  > 令牌交法见 `deploy/sema-up/README.md`「壳接入」。
1982
2086
 
1983
2087
  **整只请求体的上限**:8 MiB;**TiDB 部署更矮** —— 一份提交体要整只落进库的一行,上限 = 库的单行上限(TiDB `txn-entry-size-limit`,缺省 6 MiB)− 64 KiB,
@@ -1995,14 +2099,14 @@ PostgreSQL 仍只有 8 MiB。这一窗的 413 句子说是假定(`the database's
1995
2099
  PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_large`(`wall` 点名那堵墙);刚认领的 run 当场撤掉,撤认领的库写自己也失败时一行 `submit_ctx_run_release_failed`(带步名)
1996
2100
  并计 `submit_ctx_run_release_failed_total{step}`,会话到 `REAP_RUN_STALE_SEC` 才放。
1997
2101
 
1998
- **请求体字段(常用;受理**闭集**以 `docs/ASSISTANT-WIRE-CONTRACT.md` §1 为准 —— 本表不再重抄一份):**
2102
+ **请求体字段(常用;受理**闭集**以契约 §1 为准(内部记录)—— 本表不再重抄一份):**
1999
2103
  | 字段 | 必填 | 说明 |
2000
2104
  |---|---|---|
2001
2105
  | `objective` | ✅ | 要做什么(自然语言)。**唯一必填。** |
2002
2106
  | `sessionId` | — | 续聊:带上次返回的 `sessionId`,服务端自动 wake 历史。**只收 canonical uuid**(任意版本,小写;与会话口同一只形判):自选名 / 业务键 / 空串 ⇒ `400 request.id_invalid`,缺省请**省略这个键**(服务端现铸 uuid)。想要好记的名字,自己记 name → uuid 的映射 |
2003
2107
  | `images` | — | 图文输入 `[{data,mimeType}|{url}]`(模型需支持 vision)。**随提交的这条目标一次送达**:续跑 / 投递腿(审批 / 资源停车 / wake / plan_review 续跑、在宿主会话上新起的一跑)重建配置时不再带它(与 `userAttachments` 同律;图在首跑已是会话里的消息) |
2004
2108
  | `attachmentIds` | — | D-1 通用文件上传(1.289+):先 `POST /v1/attachments?name=…`(raw body,content-type=mime)拿句柄,提交时引用 ≤16 个;文件物化到执行环境工作目录 `attachments/` 下(沙箱车道在恢复点放:沙箱铸出或恢复之后、交给任务之前),objective 尾部自动追加文件清单(内容不进会话流)。一只上传一经绑定就属于那个会话,交给别的会话 ⇒ `409 attachment.bound_elsewhere`(7.142.0 起;每个会话各传一次)。mime 为 `application/vnd.sema.project-bundle` 的是**项目包**(工作区的种子,只进沙箱车道,见 §9.3j)。单文件缺省 ≤32 MiB(`ATTACHMENT_MAX_BYTES`);可配 mime 白名单(`ATTACHMENT_MIME_ALLOWLIST` CSV,缺省不限);上传后未引用的按 `ATTACHMENT_UNBOUND_TTL_MS`(缺省 24h)回收。**云形态(tidb/pg)字节本体存对象存储——MinIO 必配**(`MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY`,与快照 lane 同一组变量),未配则附件面 501;local 形走本地文件店。 |
2005
- | `userAttachments` | — | 请求层附件(≤20 条),原样交给引擎、随**这条目标**送到模型:`{ path, lines? }` 读这次 run 的工作区 / `{ name, text, startLine? }` 宿主给的文本 / `{ name, image: { data, mimeType } }` 宿主给的图片(base64)。形判属主是引擎:坏形、带行号超 Read 上限(100 000 字符)的 text、过不了准入的图 ⇒ `400 config.user_attachment_invalid`(`error` 以字段路径起);`path` 只在有手场景(这次 run 有工作区)受理 —— 无手场景(`scan` / `code-review`、无手叠层、`provider: "in-process"` 的部署)上整条 `path` 条目同码 400,改发 text / image 条目;能力位 `supplies` 是席位能承载的供给,不按场景裁 —— **这个场景在本部署上受理哪些**读 `GET /v1/capabilities/scenarios/<name>` 的 `attachmentSupplies`(7.133.0 起;席位 `supplies` 的子集,不含 `path` 的场景发 path 条目必同码 400,发之前读它,不必撞 400);读不到的文件不拒,它的消息里说明没附上,结局见流上目标那一帧 `human_input.attachments`。只随目标走:steer / interrupt 带它 400,续跑不重发。带附件的提交在第一次回复出来之前被切断(halt / interrupt)时,这次 run 不落回退锚 —— 拿它的 taskId 去 `resumeAt` ⇒ `404 resume_at.unknown_event`,请显式给 resumeAt。与 `attachmentIds` 分工:那条把文件放进工作区让模型按需去读(内容不进上下文),这条把内容直接送进上下文。能力位 `userAttachments` |
2109
+ | `userAttachments` | — | 请求层附件(≤20 条),原样交给引擎、随**这条目标**送到模型:`{ path, lines? }` 读这次 run 的工作区 / `{ name, text, startLine? }` 宿主给的文本 / `{ name, image: { data, mimeType } }` 宿主给的图片(base64)。形判属主是引擎:坏形、带行号超 Read 上限(100 000 字符)的 text、过不了准入的图 ⇒ `400 config.user_attachment_invalid`(`error` 以字段路径起);`path` 只在有手场景(这次 run 有工作区)受理 —— 无手场景(`scan` / `code-review`、无手叠层、`provider: "in-process"` 的部署)上整条 `path` 条目同码 400,改发 text / image 条目;能力位 `supplies` 是席位能承载的供给,不按场景裁 —— **这个场景在本部署上受理哪些**读 `GET /v1/capabilities/scenarios/<name>` 的 `attachmentSupplies`(7.133.0 起;席位 `supplies` 的子集,不含 `path` 的场景发 path 条目必同码 400,发之前读它,不必撞 400);读不到的文件不拒,它的消息里说明没附上,结局见流上目标那一帧 `human_input.attachments`。只随目标走:steer 带它 400(interrupt 不收任何字段,带它也 400),续跑不重发。带附件的提交在第一次回复出来之前被切断(halt / interrupt)时,这次 run 不落回退锚 —— 拿它的 taskId 去 `resumeAt` ⇒ `404 resume_at.unknown_event`,请显式给 resumeAt。与 `attachmentIds` 分工:那条把文件放进工作区让模型按需去读(内容不进上下文),这条把内容直接送进上下文。能力位 `userAttachments` |
2006
2110
  | `scenario` | — | 省略时取本部署的 `DEFAULT_SCENARIO`(工厂缺省场景名 = **`code`**——CC 编码 persona 蒸馏版,default 全量工具面,终验旋钮独立;`default` 场景**已非出厂缺省**,要用它须显式传 `scenario:"default"`,或部署方显式设 `DEFAULT_SCENARIO=default`)。可选内建名:`default`(通用/助理场景,域中性 persona,CC agent loop 全量工具面)/ **`cowork`**(办公向场景,**7.133.0+**:`default` 的工具面去掉编码类工具 —— 不带 `Bash` / `Edit` / `Write` / `NotebookEdit` / `RepoMap` / `LSP` / `EnterWorktree` / `ExitWorktree` / `Monitor`,留 `Read` / `Glob` / `Grep` 文件读、浏览(WebFetch / WebSearch)与连接器挂载;role 层是 core 的 Cowork 角色基底,调用方的 `systemPrompt` 照样整替它;仍挂手,所以附件 `path` 在有执行环境的部署上照常受理;你自己请求体里的 `excludeTools` 与它取并,只会多卸)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力写死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计)。**缺省与指派都必须是本部署在册的场景名,悬空不回落 `default`**:`DEFAULT_SCENARIO` 不在册 ⇒ **起服拒**(拒句点名那个名字与在册词表);中心按主体指派的场景名本部署没有 ⇒ 该主体的 run 答 **503 `state.scenario_assignment_unknown`**(体 `detail: { scenario, source: "ruling" }`,不带 `Retry-After`;修法 = 中心改指派,或把那个场景加回本部署,然后原样重试);请求体显式给的未知名仍是 400 `scenario_unknown` |
2007
2111
  | `repo` | — | `code-review`/`scan` 必填,见 §5(7.126.0 起 `council` / `debate` / `lenses` / `rounds` / `team` / `businessContext` 是未知键 ⇒ 400:它们喂的评审团腿与 `discuss` 场景随引擎 7.49.0 删掉团队运行时一并删除) |
2008
2112
  | `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色)。`systemPrompt` 是**角色层**:它整替所选角色的基底(`code-review` 场景把自己的评审员文本放在它前面),宪法、行为段、记忆段由引擎组在它之后;追加段恒排在宪法之后。逐字含引擎提示段的提示(一份已组装的提示)⇒ 400 `prompt.assembled_role_refused`(7.132.0 起) |
@@ -2040,8 +2144,7 @@ PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_la
2040
2144
 
2041
2145
  > ⚠️ **任务成败以 `terminal.kind`/`terminal.code` 为准,勿以 HTTP 状态码判**:同步腿 200=提交受理成功(任务可能
2042
2146
  > `terminal.kind:"failed"`+`code`);`POST /v1/runs` 202=已受理(执行期结局落在终态 run 记录/`done` 帧)。执行期
2043
- > 错误结构上无法用 HTTP 状态码承载——流的响应头在 run 开跑前就发完了(详见
2044
- > `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A)。
2147
+ > 错误结构上无法用 HTTP 状态码承载——流的响应头在 run 开跑前就发完了。
2045
2148
 
2046
2149
  ---
2047
2150
 
@@ -2162,18 +2265,18 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2162
2265
  | 端点 | 用途 |
2163
2266
  |---|---|
2164
2267
  | `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(`DB_BACKEND=local` 到 7.140.x 也是)/ 会话店不存元组(`DB_BACKEND=memory`;嵌入方自建、不声明委派分区的店),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是引擎判的委派树属主(**7.144.0+**:没人派生的 run 读自己那一行的属主、按化身绑一次,原样带给每一层子代;三只持久会话店〔MySQL 协议 / PostgreSQL / local〕建行那一笔原样记引擎交来的属主,`null` = 根匿名;停驻挂点只比对宿主行属主与引擎交来的属主,不认领、不推断。**7.133.0–7.143.x** 是本服务在建行那一笔按根 / 父推断:根会话的行在 ⇒ 它的属主,根行已不在 ⇒ 它记录的父会话的,都不在 ⇒ 哨兵 `_orphan:<id>`;7.132.x 及更早的 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 后端两店每次起服由一次性迁移 M23 在它的窗内回填升级前写下的无主 placed 行(按 7.133.0 起的读法:根的属主,根行不在取记录的父的,都不在写 `_orphan:<id>`;起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0),local 后端没有回填臂(**7.145.0+** local 后端建子代行先记属主、再建转录存档,进程崩在两笔之间留下的是一只没有转录的属主格 —— 不是子代行,不再新生无主子代行;更早版本崩在两笔之间留下的无主子代行照旧无主、不回填,出路是取消 / 删掉那只后台子代)。属主 `_orphan:<id>` 由 7.133.0–7.143.x 的建行推断与 M23 回填写下(7.144.0 起本服务不再推哨兵,建行原样记引擎交来的属主 —— 已有的哨兵属主随引擎的树属主原样带到它下面新建的子代行上):任何主体都不匹配,只有 fleet-wide / operator 读得到,前缀永久保留。停驻时宿主行属主与引擎交来的不等 ⇒ 停驻否决(那张待批卡作废、那一只子代以失败收尾) |
2165
- | `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+**)。一张停驻行的存储体读不出摘要(手改成 `{}`、整段缺 `state` 等)只坏它自己那一行(**7.145.0+**,MySQL 协议 / PostgreSQL 后端):收件箱与运维队列照列它、带引擎判官的 `rowVerdict`(只缺 `pendingAction` 的行判官答 `ok`,引擎侧已知缺口),任务视图照列那条 run,三个面都不再整张答 500 / 503;别的卡的决断照常,这张卡自己的决断答 409 带码拒,`error` 以行上 `rowVerdict.sentence` 开头(**7.146.0+**;此前存储体 `{}` 的卡被指向 plan_review 的口、存储体读不成检查点的卡只有店的诊断);`DB_BACKEND=local` 上列表面本版仍整张 503(决断已逐行)。停驻的**技能卡**(对引擎 `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=` 跨看也是原文)。停驻行带 `agent`(**7.145.0+**):行的会话在委派树里的位置 `{ id?, parentSessionId?, rootSessionId? }`,与 `GET /v1/sessions/<sessionId>` 体顶层的 `agent` 同一只读口、逐值同答 —— 后台委派子代停驻的行(`taskId` 为 `null`、`sessionId` 是子代自己的会话)凭它连回发起委派的会话(`rootSessionId` / `parentSessionId` = 本端会话 ⇒ 本端的卡);缺席 = 这台 server 没有说(宿主会话自己的停驻、同步委派的子代、`subagent_type: "fork"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2166
- | `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+**;含 `agent`,新订阅首拍补给的帧与之后推出的帧同值,**7.145.0+**) |
2268
+ | `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`;稍后重发,这一答不表示队列为空);本服务自己的程序错(代码缺陷 / 读腿漂移)⇒ `500` + `errorCode:"internal.error"`(**7.148.0+**,此前同落 503;重发不会好,看运维行 `request_error`)。`GET /v1/assistant/inbox` / `GET /v1/assistant/tasks` 读同一个队列,两种读失败同码同状态(**7.135.0+**)。一张停驻行的存储体读不出摘要(手改成 `{}`、整段缺 `state` 等)只坏它自己那一行(**7.145.0+**,MySQL 协议 / PostgreSQL 后端):收件箱与运维队列照列它、带引擎判官的 `rowVerdict`(只缺 `pendingAction` 的行判官答 `ok`,引擎侧已知缺口),任务视图照列那条 run,三个面都不再整张答 500 / 503;别的卡的决断照常,这张卡自己的决断答 409 带码拒,`error` 以行上 `rowVerdict.sentence` 开头(**7.146.0+**;此前存储体 `{}` 的卡被指向 plan_review 的口、存储体读不成检查点的卡只有店的诊断);`DB_BACKEND=local` 上列表面本版仍整张 503(决断已逐行)。开了工具审批时响应另带 `livePending[]`(本副本上的活卡,决议走 `POST /v1/tool-approvals/<approvalId>/respond`):**7.149.0+** 它与下面的按问读口同一只读法 —— 列出的每一张,读口对同一调用方都答 `pending`(反过来不然:具名主体的列表不含无主的卡,运维列表含读口不放行的卡),店读不出某张卡的行 ⇒ 这张不列(读口同时答 404 `not_found.ask`;`livePending` 为空不等于没有待批,可能是此刻读不出),行已被别处收走 ⇒ 不列,属主取行上当下的那一位(此前按本副本登记列,店抖动期照列而读口 404);店恢复后重列即回,每次列表对每个有行的不同 ask 多一次按 id 的点读。停驻的**技能卡**(对引擎 `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=` 跨看也是原文)。停驻行带 `agent`(**7.145.0+**):行的会话在委派树里的位置 `{ id?, parentSessionId?, rootSessionId? }`,与 `GET /v1/sessions/<sessionId>` 体顶层的 `agent` 同一只读口、逐值同答 —— 后台委派子代停驻的行(`taskId` 为 `null`、`sessionId` 是子代自己的会话)凭它连回发起委派的会话(`rootSessionId` / `parentSessionId` = 本端会话 ⇒ 本端的卡);缺席 = 这台 server 没有说(宿主会话自己的停驻、同步委派的子代、`subagent_type: "fork"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2269
+ | `GET /v1/approvals/stream` | 同一队列的 SSE 推送(范围同上):首拍快照 → `pending` / `resolved` 增量(开了工具审批时另有 `live_pending` / `live_resolved`),空闲时心跳,15 分钟上限(`STREAM_MAX_DURATION` 帧后重连)。**心跳只表示流活着,不表示这一拍读过队列**:队列读不出时每次读失败发一帧 `error`(`approvals.read_unavailable`,流留着、下一拍再读,恢复后同一条流继续);某一行的存储体读不出来、重试不会好时发 `approvals.read_corrupt` 并收流(运维处置那一行后再重连);本服务自己的程序错发 `internal.error` 并收流(**7.148.0+**,此前同落 `approvals.read_unavailable`、流不收)。15 分钟上限对读失败的拍同样算数。`live_pending` / `live_resolved` 与 `GET /v1/approvals` 的 `livePending[]` 同一只读函数(**7.149.0+** 同按问读口读行:读不出行 ⇒ 这一拍离场、店恢复后再入场一帧,推流每拍对每个有行的不同 ask 多一次点读;问账读不出而队列读得出的拍才发这一对帧)。`pending` 帧带的行与 `GET /v1/approvals` 同一只投影(含上面的技能卡三位,**7.136.0+**;含 `agent`,新订阅首拍补给的帧与之后推出的帧同值,**7.145.0+**) |
2167
2270
  | `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,卡处理不掉)。有根会话锚的停驻子代卡上,`decide` 与删根会话 / 卡作废并发:写口放行之后卡已被作废 ⇒ `422 parked_resume.startup_failed`(卡已作废、什么都没复活;与 `409 decide.gate_moved` 并列为并发终局码,调用方须同时处理 409 / 422) |
2168
- | `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 |
2271
+ | `GET /v1/tool-approvals/<approvalId>` | 按问读口(**7.133.0+**;与 `POST …/respond` 同门同身份,不是你的 ask 与查无同形 404 —— **7.148.0+** 两口与耐久决断口同码 `not_found.ask`、不带状态位,`tool_approval.not_pending` 只对属主出;**7.136.0+** 两扇决议口 —— `POST …/respond` 与耐久决断口 `POST /v1/tasks/<taskId>/asks/<askId>/decision` —— 与本口同一只属主判据:有行的问读行上当下的属主,收编之后新属主三口都认、前属主三口都 404;此前本副本上的活卡读的是铸卡那一刻记下的属主;**7.149.0+** 本口在店读不出时看不见这只问,答 404 `not_found.ask`(与查无同体;活卡与起服接管的登记同答 —— 7.136.0–7.148.x 对本副本自己铸的活卡回落登记,登记属主认你 ⇒ `pending`),店恢复后重读即回):一只审批问此刻在哪一态,`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 |
2169
2272
  | `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) |
2170
2273
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**、附件供给、**技能清单**(见下) |
2171
- | `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;**7.147.0+** 部署接了中心而中心答不出这位主体的裁定(不可达 / 超时 / 答复读不出 / 指派两键形不对)⇒ 点名与不点名同答 `503 state.scenario_assignment_unavailable`(体无 `detail`、带 `permanence: "transient"`),与提交任务时同一句,不再按「无治理」读部署缺省场景;`GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板同读这位主体 run 时的场景:a2a 面板悬空时同样整面 503,而 mcp 面板另有「这个会话上一次真挂上了什么」那一半(`lastLegMcp`),悬空时答 `200`、`lastLegMcp` 照答、`servers` 缺席、拒因在 `scenarioRefusal: { errorCode, error, detail }`(与状态面整面 503 的体是同一份成员,`detail` 是门拒的机读事实 `{ scenario, source }`;`servers` 缺席 = 场景这一半没答,`servers: []` = 该场景没配 MCP));`?scenario=<名>`(**7.130.0+**)= 点名场景,可见集与同场景任务 run 时是同一只解析、过同一只场景门 —— 不在册 `400 scenario_unknown`(附在册词表;空串 `?scenario=` 同此,与提交 `scenario: ""` 同答)、这位 principal 的场景白名单集外 `400 scenario_not_allowed`(附 allowlist),两者的体与提交任务时的拒逐字同形;重复给出参数 `400 request.field_invalid`(提交腿没有这一形)。场景门接缝 `scenarioGate` 是 `createHttpServer` 的必填件:自建 `ServiceDeps` 的嵌入方用包根导出的 `createScenarioGate` 铸它(`"peek"` 腿给读面),没递就是 tsc 红。老 server 忽略这个参数、回缺省场景那一份,按 `GET /v1/capabilities` 的 `version` 判。`REQUIRE_PRINCIPAL` 下无身份 `401`(先于场景门)。每一行(两条口同形)在 §G.7 的行形之外多两个可选键(**7.133.0+**):`toolNames`(这台会挂上的工具名,引擎命名空间形 `mcp__<server>__<tool>`,读数序)+ `toolNamesTruncated: true`(名单超过 200 只时只截表,`toolCount` 仍是真数;读数用 `toolCount`,别用 `toolNames.length`);该台读数里没有名单 ⇒ 两键缺席(不编 `[]`),没连上的台是在场的 `[]`;老 server 没有这两键,按缺席读。中心声明里有两台归一后挂在同一个工具命名空间前缀下(`my.srv` / `my_srv` 同挂 `mcp__my_srv__`)⇒ **7.143.0+** `409 config.mcp_server_name_collision` + 引擎原句(点名两个原拼写;此前无码 `500`),与同一份声明集在 `POST /v1/tasks` 上的答复同码、同状态、同句 —— 出路 = 改配置里的名字。自带 `.mcp.json` 的探测口是 `POST /v1/capabilities/mcp/probe` |
2274
+ | `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;**7.147.0+** 部署接了中心而中心答不出这位主体的裁定(不可达 / 超时 / 答复读不出 / 指派两键形不对)⇒ 点名与不点名同答 `503 state.scenario_assignment_unavailable`(体无 `detail`、带 `permanence: "transient"`),与提交任务时同一句,不再按「无治理」读部署缺省场景;`GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板同读这位主体 run 时的场景:a2a 面板悬空时同样整面 503,而 mcp 面板另有「这个会话上一次真挂上了什么」那一半(`lastLegMcp`),悬空时答 `200`、`lastLegMcp` 照答、`servers` 缺席、拒因在 `scenarioRefusal: { errorCode, error, scenario, source }`(与状态面整面 503 的体是同一份成员,`scenario` · `source` 是门拒的机读事实、与 `errorCode` / `error` 同层 —— 7.148.0 起,此前嵌在 `detail` 里;`servers` 缺席 = 场景这一半没答,`servers: []` = 该场景没配 MCP));`?scenario=<名>`(**7.130.0+**)= 点名场景,可见集与同场景任务 run 时是同一只解析、过同一只场景门 —— 不在册 `400 scenario_unknown`(附在册词表;空串 `?scenario=` 同此,与提交 `scenario: ""` 同答)、这位 principal 的场景白名单集外 `400 scenario_not_allowed`(附 allowlist),两者的体与提交任务时的拒逐字同形;重复给出参数 `400 request.field_invalid`(提交腿没有这一形)。场景门接缝 `scenarioGate` 是 `createHttpServer` 的必填件:自建 `ServiceDeps` 的嵌入方用包根导出的 `createScenarioGate` 铸它(`"peek"` 腿给读面),没递就是 tsc 红。老 server 忽略这个参数、回缺省场景那一份,按 `GET /v1/capabilities` 的 `version` 判。`REQUIRE_PRINCIPAL` 下无身份 `401`(先于场景门)。每一行(两条口同形)在 §G.7 的行形之外多两个可选键(**7.133.0+**):`toolNames`(这台会挂上的工具名,引擎命名空间形 `mcp__<server>__<tool>`,读数序)+ `toolNamesTruncated: true`(名单超过 200 只时只截表,`toolCount` 仍是真数;读数用 `toolCount`,别用 `toolNames.length`);该台读数里没有名单 ⇒ 两键缺席(不编 `[]`),没连上的台是在场的 `[]`;老 server 没有这两键,按缺席读。中心声明里有两台归一后挂在同一个工具命名空间前缀下(`my.srv` / `my_srv` 同挂 `mcp__my_srv__`)⇒ **7.143.0+** `409 config.mcp_server_name_collision` + 引擎原句(点名两个原拼写;此前无码 `500`),与同一份声明集在 `POST /v1/tasks` 上的答复同码、同状态、同句 —— 出路 = 改配置里的名字。自带 `.mcp.json` 的探测口是 `POST /v1/capabilities/mcp/probe` |
2172
2275
  | `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,或单用户部署声明了 `OPERATOR_SINGLE_USER=true`;两条出路都没有〔名单为空且没声明〕⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_WEB_ADMIN_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;`deadlineAt` 离本引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS`(缺省 24 h,一台实例活着而不服务的最长时长;7.122.0 起)⇒ 同码 400(什么都不变;拒句点名字段与上界的值;只在这一发会进入排空时判 —— 已在排空或硬停已在进行时任何重发都回 `already_draining`),已过的期限照收(进入即到期);嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_WEB_ADMIN_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
2173
- | `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`:没有运维名册〔`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`〕⇒ 501 `capability.operator_principals_required`,有名册而调用方不是运维 ⇒ 403)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,managedNow,effectiveNow}}`(🔴 7.106.0 起 `center.domainExists` 删除:座位在加载期对契约 `DOMAIN_SCHEMAS` 核过域 + 路径,核不过即拒启 ⇒ 有 `center` 块 ⇔ 中心可写;readFace 四键 / `PERMISSIONS_DISABLE_AUTO_MODE` / `MODEL_DEGRADE_ON` / `_REACTIVE` 七行的座位随之删除);顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 501 `feature.config_catalog_disabled`(路径恒挂载)+ 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
2276
+ | `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`:没有运维名册〔`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`〕⇒ 501 `capability.operator_principals_required`,有名册而调用方不是运维 ⇒ 403)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,managedNow,effectiveNow}}`(🔴 7.106.0 起 `center.domainExists` 删除:座位在加载期对契约 `DOMAIN_SCHEMAS` 核过域 + 路径,核不过即拒启 ⇒ 有 `center` 块 ⇔ 中心可写;readFace 四键 / `PERMISSIONS_DISABLE_AUTO_MODE` / `MODEL_DEGRADE_ON` / `_REACTIVE` 七行的座位随之删除);顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 501 `feature.config_catalog_disabled`(路径恒挂载)+ 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(未设 ⇒ `null`;7.147.x 及以前 `off` 也回 `null`,**7.148.0 起** `off` 是真档、回 `"off"`)而不是 env 原文。 |
2174
2277
  | `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:没有运维名册〔`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`〕⇒ 501;名单非空而调用方不在名单上 ⇒ 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。候选被判官整拒(坏域 / 坏 prompts / 带事实的退役域 —— 7.126.0 起 / 本机 `config.d` 里读不出的文档,门控域在内)是这一拍的结论,不是失败:照样 200,`targetVersion ≠ appliedVersion`,拒因在 `config_candidate_rejected` 日志行;拍真的失败(配置源不可达 / 读不了 / 应用抛错)⇒ `502 internal.config_refresh_failed`。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
2175
2278
  | `POST /v1/adoption/<adoptionId>/sessions` `{"sessions":[{"clientSessionId":"…","entries":[…]}]}` | **收编批量导入**(**7.146.0+**;能力位 `adoptionImport`,按调用方):收编的**源主体本人**把本机会话导进那条收编的目的主体名下(operator 不能代,请求体不能点名目的地);导进来的是只读镜像,在云端接着跑先 fork。`adoptionId` 从 `GET /v1/capabilities` 的 `adoptionImport.adoptionId` 读(只对源主体本人在场,另带两只帽 `maxSessions` / `maxSessionBytes`)。云端会话 id 由 `(adoptionId, clientSessionId)` 确定性派生 ⇒ 重投安全。200 + 逐元素结局 `imported` / `already_imported` / `rejected{code: too_large \| invalid_envelope \| duplicate_client_session_id}`;信封恰好两键,多一个键(如 `cwd`)⇒ 这个元素 `invalid_envelope`。整请求拒:401 / 501 `capability.adoption_store_required` / 404 `not_found.adoption` / 403 `auth.adoption_source_only` / 409 `adoption.not_adopted` / 400(体形、元素数超帽)。帽 = `ADOPTION_IMPORT_MAX_SESSIONS`(缺省 100)/ `ADOPTION_IMPORT_MAX_SESSION_BYTES`(缺省 6 MiB)。契约 §26 |
2176
- | `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
2279
+ | `GET /health` | 健康(无需鉴权,恒 200;**7.148.0+** 配了服务凭据的部署上,没出示凭据的读者只拿 `status` / `auth` / `operator` / `center` 四键,出示了凭据与没配服务凭据的部署照旧全键 —— 下面各键都按「全键读者」说)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本;**7.148.0+** 在应用那一拍收口时才推进,与该拍的 `restart` 判定同一段写出 —— 读到它到某一代时,同一次读数里的 `restart` / `restartRequired` 已是那一拍的判定)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
2177
2280
  | `GET /v1/sessions/:id/background` · `POST /v1/sessions/:id/background/stop` `{"includeRetained":true\|false}` | **会话后台任务的读口与停止口**(**7.102.0+**;能力位 `background.listFace` / `background.exitFaces`,老 server 404)。读口列**本会话**、**本副本**上的后台任务行(`{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained?, sessionId? }] }`,`retained:true` = 声明了保留的活服务,正常退出不会停它;`sessionId` = 后台 agent 行的子代会话 id,绑定之后才在);停止口「软 → 等一次 `HOST_BG_KILL_GRACE_MS` → 硬」收掉本会话的后台 shell(SIGTERM 处理器先有一次宽限跑完,之后组 SIGKILL,屏蔽 SIGTERM 的循环也活不过去;有后台行时应答慢一次宽限)并逐字返回受据 `{ receipts: [{ id, outcome }] }`:`killed` / `already_gone` / `retained_skipped`(`includeRetained:false` 留下的活服务)/ `no_pgid`(远端 / 沙箱车道没有本机进程组可发,已派普通 kill,「可能还在跑」;host 车道的 `already_gone` 只由观察得出 —— 引擎已收割组长或内核答组已空,7.103.0 起;darwin 上梯子那一枪先落地、引擎还没收割的那一拍如实答 `no_pgid`,判「收掉没有」读进程)。`includeRetained` 必填:`true` = 连活服务一起停(壳的「Exit and stop tasks」),`false` = 活服务照留(`sema kill`)。鉴权 = 会话属主或显式 operator(未知与他人同 404);停止口在无凭据写门内(没配 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` ⇒ 503 `auth.service_token_required`)。`DELETE /v1/sessions/:id` 走同一只收敛(`includeRetained:false`);run 取消(Esc)**不**收后台 shell。契约 §16 |
2178
2281
  | `POST /v1/sessions/:id/rewind-files` `{"to":"<提问的 taskId>","dryRun":true\|false}` | **run 外只恢复代码**(**7.147.0+**;能力位 `sessionRewindFiles` / `sessionRewindFilesPreview`,老 server 404):不跑模型、不分叉对话,把这只会话被代理改过的文件收敛到那条提问开始时的样子(`to` 与提交体 `rewindFilesTo` 同一个手柄,会话视图消息行的 `messages[].taskId`)。会话属主或运维可调;只在 `DB_BACKEND=local` 且工作区活过一次执行的部署上开(mysql / pg 多副本共用会话店、`local-docker` / `e2b` / k8s / 多租 host ⇒ 能力位假、口 501);同会话有 run 在跑或停在审批上 ⇒ `409 conflict.session_active_run`(带 `activeTaskId`,等它结束或取消再发);引擎进程内仍有在途工作 ⇒ `409 rewind.session_busy`;那条提问没有可用的文件边界 ⇒ `422 rewind_snapshot.unresolvable`;未知手柄 ⇒ `404 rewind_files_to.unknown_event`;host 车道上 server 重启之后、这只会话下一条带 `cwd` 的提交之前,或登记的目录已被移走 ⇒ `409 conflict.session_workspace_unknown`(本进程不知道它的工作目录,先发一条带 `cwd` 的提交再回退);只读镜像会话 ⇒ `409 conflict.session_mirror`。取执行环境时不物化会话附件(预览与真回退都不改写 `attachments/`)。`dryRun:true` ⇒ `{ dryRun:true, preview }`:`preview: null` = 这台给不出统计(`sessionRewindFilesPreview` 为假,今天 SQL 后端如此),**不是**「什么都不会变」;`dryRun` 缺席 / `false` ⇒ `{ dryRun:false, restore }` 台账,`outcome` 是 `complete` 或 `partial`(`partial` 带每一个被拒 / 失败的文件,原样再发会继续收敛)。没有 `acceptPartialRestore`(带了 ⇒ 400 `request.body_shape`)。不计费;无凭证部署上与提交面同一道门 |
2179
2282
  | `GET /readyz` | **就绪探针**(**7.102.0+**,无需鉴权;7.121.0 起是唯一的探针路径)。就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`,`reasons` ⊆ {`draining`, `model_roster_pending`}(按优先序)。与 `/health` 的 `ready` / `readyReason` / `reasons` 同一只判;库连不上**不算**未就绪(`storeLive` 只在 `/health` 披露)。给只认状态码的**摘流**探针用(k8s `readinessProbe.httpGet`、LB 健康检查、镜像 `HEALTHCHECK`);存活探针用 TCP 探端口,不打 HTTP,见 §0「探针」 |
@@ -2198,7 +2301,10 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2198
2301
  > **operator 鉴权(审批队列与全部运维面,一条读法)**:`OPERATOR_PRINCIPALS=ops:alice,ops:bob`(CSV)= 谁能当 operator —— 列任意 owner 待办 + 决议(批/否)。非名单 principal 列待办只看自己的、且**不能决议**(防「请求方批自己的高危操作」跳过 F4 闸);
2199
2302
  > **名单为空 = 没有人是 operator**(每个调用方只看自己的待批 —— 不带主体的调用方看到的是匿名卡〔不带主体提交的任务停下的那几张,即决断口对它开放的那几张〕,`?owner=` 不生效;7.133.0 之前审批四个读面在名单为空时把每个调用方当 operator,那条读法已删)。
2200
2303
  > **单用户部署当自己的 operator**:`OPERATOR_SINGLE_USER=true` = 「这台部署的每个调用方都是 operator」,对全部运维面生效;只在 `REQUIRE_PRINCIPAL` 未开且 `OPERATOR_PRINCIPALS` 为空时成立(与任一同在 ⇒ 拒启);
2201
- > 没配服务凭据的部署上能连上端口的都是 operator;不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
2304
+ > 声明要有一道身份边界:没配服务凭据(`SERVICE_AUTH_TOKEN` / `SERVICE_AUTH_TOKENS`)的部署上能连上端口的都是 operator,所以**声明 ∧ 没配凭据 ∧ 监听地址不只在本机回环**(缺省全接口 / `0.0.0.0` / 局域网地址 / 主机名)⇒ 服务进程起服拒
2305
+ > (不变量 `operator-single-user-boundary`;拒句一行给三条出路:配凭据 / `BIND_HOST=127.0.0.1` 只听本机回环 —— 端口要发布给别的主机的容器改配凭据 / 撤声明换具名 `OPERATOR_PRINCIPALS`);
2306
+ > `ALLOW_UNAUTHED_WRITES=true` 且没配凭据时监听地址本来就收窄到 127.0.0.1,照起。不监听的 `run-local` 不判这一条。
2307
+ > 不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
2202
2308
  > 部署形不是输入:单用户部署上 `GET /v1/diagnostics/wiring` 与 `GET /v1/outcomes` 也只认运维,具名名单在单用户部署上同样生效。
2203
2309
  > 🔴 **单用户部署必须声明谁是运维**:`REQUIRE_PRINCIPAL` 未开、`OPERATOR_PRINCIPALS` 为空、`OPERATOR_SINGLE_USER` 不是 `true` ⇒ 装配运维面时拒(服务进程起服拒;直接调 `createHttpServer` 的嵌入方同拒;不变量 `operator-declaration-required`),
2204
2310
  > stderr 一行拒句点名两条出路 —— `OPERATOR_SINGLE_USER=true`(每个调用方都是运维)或 `OPERATOR_PRINCIPALS=<主体,…>`(只有点名的主体);不止一个人调用的 worker 是多租,设 `REQUIRE_PRINCIPAL=true`。
@@ -2328,15 +2434,15 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2328
2434
  > - **整理每次写库都问写判官(7.127.0 起,引擎 7.50.0)**:
2329
2435
  > - **自动整理**(任务结束按建议起跑)用这条腿自己的判官 —— 该 run 主体的人的持久路径规则 + 部署的调用方策略,与模型自己的 `Write` 同一套判:
2330
2436
  > 规则点名库目录 ⇒ 在任何占位与模型调用之前拒,运维行 `engine_notice` 一条 `memory.consolidation_refused`(`detail.refusalCode: "memory.consolidation_write_refused"`);
2331
- > 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 读不到任何人的持久规则(只剩部署策略那一层)。
2437
+ > 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 在单用户部署上读本机属主的规则桶(**7.148.0+**,见 §「不带主体的 run 读哪只规则桶」),多租部署上读不到任何人的持久规则(只剩部署策略那一层)。
2332
2438
  > ⚠️ **引擎 7.50.x 上这条腿在缺省部署上每一次都被拒**:只要部署的敏感路径守卫在场(`SENSITIVE_WRITE_PATTERNS` 未设即在场),引擎问写判官时递给部署策略的是一只已中止的信号,守卫判不了库目录的真实落点、按 `target_unresolved` 拒 —— 与规则、权限模式无关(`memory.consolidation_refused`,人读句只带拒因前 200 字)。修复随引擎提货;要整理用手动阀门,不要为此设 `SENSITIVE_WRITE_PATTERNS=off`(见 MIGRATION 7.127.2 第 3 条)。
2333
- > - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,但它的写判官座与自动腿是**同一只**引擎判官构造器(此前由本服务代答「无异议」)。喂给它的部署事实是部署的合成规则店与
2334
- > `~/` 行的 home(`REMOTE_EXEC_HOME_DIR`;未声明时 host / in-process 车道取本机 home、e2b 取 `/home/user`,k8s / ssh 等车道缺席)。**这两样在手动阀门上今天都读不到人的行**:
2335
- > 阀门不带主体(没有来源能回答「这个库属于谁」),引擎读的是匿名地板、不读任何人的持久路径行 —— 所以「`~/` 收紧行无处锚定 ⇒ 拒闭」这条引擎规则在生产上走不到
2336
- > (只有调用方自己带着主体去喂这只构造器时才走得到),与下一句的「三层没喂」是同一件事的两面。
2337
- > **三层没喂,所以手动阀门今天仍不受它们约束**:人的持久路径规则(阀门没有来源能回答「这个库属于谁」)、部署的审批名单 / 自治档 / 敏感路径守卫(`APPROVAL_DENY=Write` 拦不住手动阀门)、
2338
- > 项目根(相对行无处锚定)。这些只约束自动整理腿。每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-layers-unfed`(计数 + 探针行 + 一次性告警)—— 这是一笔显式登记的欠账,
2339
- > 不是常态;读谁的规则、怎么喂部署策略待设计,落地后本句改写。
2439
+ > - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,但它的写判官座与自动腿是**同一只**引擎判官构造器,每一次真跑现铸一只(并入正在跑的那一轮的调用方拿同一份收执、用起跑者的判官)。喂给它的部署事实:
2440
+ > 部署的合成规则店;**人** = 发起这一轮的调用方(**7.148.0+**):带主体 ⇒ 那位主体的持久路径规则,不带主体 ⇒ 单用户部署上本机属主的规则桶(多租部署上阀门口本来就要主体);
2441
+ > `~/` 行的 home(`REMOTE_EXEC_HOME_DIR`;未声明时 host / in-process 车道取本机 home、e2b 取 `/home/user`,k8s / ssh 等车道缺席 ⇒ `~/` 收紧行无处锚定 = 拒闭)。
2442
+ > **还没喂的两样**:部署的调用方策略(审批名单 `APPROVAL_DENY` / `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO`、敏感路径守卫 `SENSITIVE_WRITE_PATTERNS`、命令策略 —— 腿上这是一整条治理链,
2443
+ > 整条喂要先有引擎在请求上盖的那只豁免根放过记忆库自己的写,宿主输入没有这一位)与项目根(相对行无处锚定 = 拒闭)——
2444
+ > 部署的写策略因此只约束自动整理腿:`APPROVAL_DENY=Write` 拦不住手动阀门,人的 deny 行拦得住。每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-layers-unfed`
2445
+ > (计数 + 探针行 + 一次性告警)—— 这是一笔显式登记的欠账,等引擎的宿主输入收豁免根、整条治理链喂上之后本句改写。
2340
2446
  > 驱动的每一条通告 —— 非收敛停的 `memory.consolidation_incomplete`、问判官**之前**推不出库家落点时同码拒的 `memory.consolidation_refused`(生产上可达,与判官答什么无关)——
2341
2447
  > 都经同一只 `engine_notice` 转发器进运维行;早问拒仍按路由的码臂答 `409 memory.consolidation_refused`。
2342
2448
  > - **怎么让整理写**:删掉或收窄点名记忆库路径的规则;只想禁止自动整理就设 `MEMORY_AUTO_CONSOLIDATION=off`。
@@ -2366,7 +2472,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2366
2472
  > 见本文 `OPERATOR_SINGLE_USER` 段);具名名单外的调用方答 403 `auth.operator_only`。
2367
2473
  > - `GET /v1/admin/memory/consolidation[?scope=]` —— 状态投影(阀门/座位/scope 表/上一轮的账)。
2368
2474
  > - 能力位 `capabilities.memoryConsolidationDriver`(阀门上了场 ∧ 有运维名册:名单非空或单用户声明)。
2369
- > - 逐键语义与停因闭集见 `docs/ASSISTANT-WIRE-CONTRACT.md` §10。
2475
+ > - 逐键语义与停因闭集在契约 §10(内部记录)。
2370
2476
 
2371
2477
  > **⚠️ hook-wired 部署里,parked 后台子代可能赎回不了(常态,不是升级窗口)。**
2372
2478
  > 引擎 5.19.0 起,一个任务的 **PreToolUse screening 面下延管辖它委派出去的子代**,于是 hook-wired 父
@@ -2428,6 +2534,16 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2428
2534
  > 与历史记录轨迹;真正持久化的规则本身在 `permission_rule` 表,不受影响、不需要重建。
2429
2535
  > 探针只在规则车道真被装配时跑(`PERMISSION_RULES_ENABLED=false` 的部署一次都不碰这张表,不受影响)。
2430
2536
  >
2537
+ > **不带主体的 run 读哪只规则桶(7.148.0+)**:规则店按人分桶 —— 带 `x-agent-principal`(或验过的主体)的 run 读那位主体的桶;**不带主体**的 run(裸 HTTP、没设主体的 SDK、本机控制台)
2538
+ > 在**单用户部署**(`REQUIRE_PRINCIPAL` 未开)上读**本机属主的桶**(file 后端 `<数据根>/permission-rules/local-owner.json`;SQL 后端 `permission_rule` 表 `owner_kind='local-owner'` 的行),
2539
+ > 那只桶里的 deny / ask / allow 行对它生效;运维手动整理阀门不带主体的调用方同读这只桶。条件是规则店装配了(`PERMISSION_RULES_ENABLED` 开着且后端有规则店;没装配 = 没有持久规则,谁都读不到)。
2540
+ > **多租部署**(`REQUIRE_PRINCIPAL=true`)上不带主体的调用方不代表任何人,读零条持久规则(那种部署也不受理不带主体的 run)。7.147.x 及以前不带主体的 run 在两种部署上都读零条。
2541
+ > 射程:**cli 用户不受影响** —— cli 恒解析出一位主体(`SEMA_LIVE_PRINCIPAL`,缺省 `default`),TUI 与 `-p` 同主体,卡上「不再询问」落那位主体的桶。
2542
+ > ⚠️ 委派出去的子代不在射程:`Agent` 工具起的子任务跑在另一只不挂规则店的 Runner 上,今天读零条持久规则 —— 本机属主桶(与任何主体的桶)里的 deny 不约束子代;这一形与带主体的 run 相同,本版不变。
2543
+ > 不带主体的问卡不给「不再询问」(回决那侧对无主的问没有可写的桶);本机属主桶本版没有 HTTP 自助口 —— 看 / 删它的行在存储层做(或 `PERMISSION_RULES_ENABLED=false` 整条关掉)。
2544
+ > 身份边界:单用户部署上不带主体的 run 就是那个用户,所以「写面无鉴权(`ALLOW_UNAUTHED_WRITES=true` 且没配服务凭据)∧ 显式 `BIND_HOST` 不只在本机回环」⇒ 服务进程起服拒
2545
+ > (不变量 `single-user-anonymous-boundary`;两条出路:配凭据 / 只听本机回环;见 §「监听绑址」)。
2546
+ >
2431
2547
  > **人的持久规则的读法(7.127.0 起,引擎 7.50.0)**:每个调用读一次规则店;一条持久 deny 行在门上拒它所在那一面的**每个**工具(Read / Grep / Glob / RepoMap /
2432
2548
  > 写工具 / 声明路径目标的调用方工具),`tool_end.gate.disposition.deniedBy = "persisted_rule"`。店的某个分区读不出 ⇒ 该分区上次读到的 deny / ask 行照旧生效、allow 行不在,
2433
2549
  > 射程内的调用真人问(bypass 也问,无审批面即拒),run 一条 `permission.rule_store_unavailable`。
@@ -2568,22 +2684,23 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2568
2684
  | HTTP `429` | 限流 | 看 `Retry-After` 退避 |
2569
2685
  | 任一 4xx / 5xx 的 `errorCode`(7.146.0 起查契约附录 A 的**码族**列) | 目录每个码恰属五族之一:`transient`(原样重发等一等可能成)· `deterministic`(重发改不了判,别自动重发)· `store_judged`(逐次由店判,读体键 `permanence`)· `pending_decision`(卡仍待决,读体键 `retriable`:它说「卡还在」,不说「重发会成功」)· `retention_expired`(留存已被收走,新开) | 「要不要重试」「要不要重读待决列表」「要不要新开」按这一列读,不自己抄码表(契约文档附录 A.1g;MIGRATION 第 17 条)。表外码(core 原码镜像)没有族,照旧看状态与前缀 |
2570
2686
  | HTTP `429` + `errorCode:"limit.rate_exceeded"` + `reason:"run_concurrency_cap"` | 本副本在飞 run 已到并发帽(`RUN_MAX_CONCURRENT` 全局,或 `RUN_MAX_CONCURRENT_PER_PRINCIPAL` 按提交主体;缺省关;新提交的四个口,幂等重放不吃) | 看 `Retry-After`(5 秒)/ 体 `retryAfterSec` 退避后重投;或等在飞的 run 收尾 / 调高帽 |
2687
+ | HTTP `429` + `errorCode:"limit.rate_exceeded"` + `reason:"sandbox_capacity"`(7.148.0 起) | local-docker 车道容器帽前此刻已有起动在等空位(`DOCKER_MAX_CONTAINERS`;缺省关;同四个口、同位,幂等重放不吃;§9.3k) | 看 `Retry-After`(5 秒)退避后重投;或等在跑的容器收尾 / 调高帽 / 清掉残留容器 |
2571
2688
  | HTTP `409` + `errorCode:"conflict.session_archive_stamped"`(7.144.0 起;`DB_BACKEND=local`;`POST /v1/sessions/:id/fork` 与会话同步导入的提交 `POST …/sync/import/:stagingId/entries`;7.145.0 起第一阶段 `POST …/sync/import` 对按 id 分类出 fork / stale 且没带同意的推送也直接答它,此前先答通用 `conflict`;7.147.0 起干跑 `POST …/sync/plan` 对这两形同样答它〔此刻读,提交里持锁的那一问仍是权威〕,此前答 `200` + `relation`;7.141.0–7.143.0 答的码是 `conflict`) | 这只会话的存档仍带更早版本写下的属主戳(一次性迁移改写不了的,或跳过带迁移的版本升上来的数据根;起服 warn `local_archive_kept_stamped` 点名):不在它上面造新存档、不原地换它的日志(干净追加 / 载荷分叉 / fork / stale 各形的推送同答),什么都没写;读 / 续跑 / 拉取照常,「id 与内容都相同」的重推照常落定 | 按体里的固定句办:删掉这只会话(`DELETE /v1/sessions/:id`)再推。与暂存提交时属主变了的 `errorCode:"conflict"`(出路相反:从第一阶段重来)按码分开 |
2572
2689
  | HTTP `429` + `errorCode:"limit.cost_quota_exceeded"` | per-principal 累计成本配额越顶(`MAX_PRINCIPAL_COST_USD`;**进场门**,不打断在跑的 run) | 看 `Retry-After` / 体 `retryAfterSec` 退避(体 `windowResetsAt` = 当代窗滚过的时刻);窗滚过或调高上限后放行 |
2573
2690
  | HTTP `400` + `errorCode:"request.id_invalid"` | 调用方给的 id 形不对。**会话 id 的形门只有一只** = 任意版本 canonical uuid(壳预设的 v4 也过门):会话口 `:id`(fork / 抹除 / 会话策略 / 2c 同步)与提交口(`POST /v1/tasks` / `/v1/tasks/stream` / `/v1/runs` 体里的 `sessionId`,A2A 的 `contextId` 同)同判,提交口的句子在句首之后点名「改用 uuid、自己记映射」;`GET /v1/sendfile-links?before=` 游标与 `body.taskId` 幂等键要求 uuidv7(时间序 / 幂等键契约) | 按 `errorCode` 分支;句子各自点名要求的形(`must be a canonical uuid` / `must be a uuidv7 …`),只作展示、别锚文案 |
2574
2691
  | HTTP `400` + `errorCode:"request.invalid_json"` + `reason:"not_json"` \| `"not_object"`(`reason` 7.133.0 起) | 请求体不是合法 JSON(`not_json`;所有读体的口同一个体 —— 含 `notify` / `wake` / 权限规则这几口,它们 7.132.x 及以前答 `request.body_shape`,其余多数口答 `request.rejected`),或合法 JSON 却不是这一口要的对象(`not_object`,`POST /v1/runs/:id/compact`)。读体超 8 MiB 是 `413 request.payload_too_large`(`field:"body"` / `reason:"too_large"` / `cap`,没有 `count`),不是本码 | 修请求体;按 `errorCode` + `reason` 分支,句子只作展示 |
2575
2692
  | 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` 分支,句子只作展示 |
2576
2693
  | 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` 改那一处(合并 / 拆小条目、缩短文字、修那一条路径);句子只作展示 |
2577
- | 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` 定位到那几个键;句子只作展示、别锚文案 |
2694
+ | HTTP `400` + `errorCode:"request.field_conflict"`(7.133.0 起带机读位,7.148.0 起在体根上;`requireExistingSession` 缺 `sessionId` 一处是 422) | 请求里的字段互斥或互为前置(`verify` × `cascade`、`capabilitiesNeeded` 没有 `sandboxImageProfile`、`disableSkills` 同体带技能、决定口的 `remember` / `answer` / `editedPlan` 错门 …)。体根上带 `reason` + `fields`(7.133.0–7.147.0 是 `detail: { kind, fields }`):`reason` 是闭集词(`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`(折叠后的字符数与帽) | 按 `reason` 分支(例:`cascade_verify` ⇒ 去掉 `verify` / `cascade` 重提),按 `fields` 定位到那几个键;句子只作展示、别锚文案 |
2578
2695
  | HTTP `400` + `errorCode:"request.env_credential_refused"`(7.146.0 起) | 请求 `settings.env` 里有凭据:名字是本引擎的凭据变量(配置目录 `secret` 行 ∪ 模型条目 `apiKeyEnv`)或值是秘密形。体带 `field: "settings.env"` 与 `entries: [{ key, match }]`,值不回显。把这些条目挪进引擎进程的环境后重发(见 9.5j);停驻任务窗关后的续跑答 409 `resume_blocked_by_policy`(`blockedBy` = 本码) |
2579
- | 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` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2580
- | 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` 后的现名,或删掉条目 |
2696
+ | 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"]`;**没有后继**的 `TeamCreate` 7.148.0 起照收 —— 引擎保留这一行并在会话流说一句 `config.legacy_tool_name`)。放行侧 `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 上标题器仍会为这次提交调一次模型)。这次提交7.148.0 起这次提交铸下的会话在应答之前撤回(列表里没有它、按 id 读 404;已成功绑定附件 / 已有环境见证等保留条件命中者照旧留,见契约 §20) | 改成句子里 `write` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2697
+ | HTTP `400` + `errorCode:"config.agent.unknown_tool"`(7.131.0 起) | 请求 `agents[i].denyTools`(限制侧)里有读成空的条目:近似名(`BASH`)、有后继的退役名(`bash`;没有后继的 `TeamCreate` 7.148.0 起照收、引擎通告 `config.legacy_tool_name`)、任何工具名都不可能有的拼法;`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` 后的现名,或删掉条目 |
2581
2698
  | HTTP `400` + `errorCode:"prompt.assembled_role_refused"`(7.132.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `systemPrompt` / `appendSystemPrompt`(含 `settings.outputStyle`)逐字含引擎提示里的一段锁定文本(宪法、安全段、`# Harness` 这样的标题行)—— 一份已组装的提示。开跑前拒、零调用,句子点名字段与命中段 | 只写角色层:删掉抄进来的引擎段(引擎自己组宪法与行为段;追加段恒排在宪法之后) |
2582
- | 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` 的条数 ⇒ 列表被截 |
2699
+ | HTTP `400`(`request.field_invalid` · `config.agent.unknown_tool` · `config.tool_face_control`)· HTTP `422`(设置规则桶)· HTTP `409` `resume_blocked_by_policy` 的拒体根上另带 `field` + `problems` + `total`(7.133.0 起;7.148.0 起在体根上,此前 `problems` / `total` 嵌在 `detail` 里;句子与码不变) | 工具名清单与设置规则里**读成空**的条目被拒时,句子仍只点名先撞上的那一张清单(前 5 条 + `; and K more`);机读位把这一遍判下来的**全部**问题一次列全:`problems` 每条 `{ code, field, entry, reason }`(`code` = 这一条所在清单被拒时的码,`field` = 清单的体内坐标,`entry` = 条目的脱敏原值,`reason` = 原因词),`total` = 问题总数(`problems` 至多 1000 条),`field` = 第一条问题的坐标(句子点名的那张清单),体的 `errorCode` = 第一条问题的 `code`;只有一条问题就是长度 1 的列表,不是另一种形。超帽的清单整只拒、不逐条读,不在列表里(第一处命中就是它时体只带撞帽的 `field` / `reason` / `count` / `cap`)。同步提交腿上引擎开跑前拒这两张工具名清单(`config.agent.unknown_tool` / `config.tool_face_control`:有后继的退役名、文法读不出的放行条目)的 400 体同形:句子是引擎的,只点名它先撞上的一条,`problems` 是受理口对放行侧选择器清单的读数(只列与体的码同码的那一族清单,所以第一条问题的 `code` 恒等于 `errorCode`) | 按 `problems` 逐条提示 / 改;不要解析句子;`total` 大于 `problems` 的条数 ⇒ 列表被截 |
2583
2700
  | 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 记录答,不重判、不带;在飞合流回放的是首发那一腿铸的应答 |
2584
2701
  | HTTP `422`(`request settings cannot be applied as written: …`)/ HTTP `400` + `errorCode:"request.field_invalid"`(7.131.0 起的读成空) | 请求体 `settings.permissions.deny` / `ask` 里的孪生与退役名 ⇒ 422、句子点名带内容的现名(`BASH(*)` ⇒ 写 `Bash(*)`);同两桶里参数不属于该工具的规则(`WebFetch(foo:…)`、`WebSearch(domain:…)`)⇒ 422 `unsupported.param_unknown`,句子列出该工具能写的参数(7.135.0 起;此前受理、永不命中);`settings.permissions.allow` 与技能 `allowedTools` 里的**近似名**(`BASH`)⇒ 400(退役名 · 内容形 · 文法读不出(带空白 / 括号不配 / 规则内容文法不成立,如 `Bash(echo $HOME)`)· 对端段通配照 7.130.0 受理,引擎通告)—— 全部读**同一张名册**(引擎目录 ∪ 本服务自铸的工具名)。运维 `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO` / `APPROVAL_GRANT` 里的孪生 **拒启**,中心下发的 `governance.approvalRequire` 里的孪生整组被拒、保前值。续跑:停驻在升级前的任务,超 1000 条的清单(任一方向)与限制方向的存量孪生 `409 resume_blocked_by_policy`,放行方向的存量近似名照旧续跑 + 一行 warn `stored_allow_list_inert_resumed` | 改成句子里 `write` 后的现名(或删掉条目);超帽的合并成更宽的条目 |
2585
- | HTTP `400` + `errorCode:"request.field_invalid"` + `detail.problems[].reason:"below_trust_floor"`(7.133.0 起) | 多租部署(`REQUIRE_PRINCIPAL=true`)上调用方的信任档低于 `settings.permissions.allow`(或 `userInvokedSkills` 点用的自带技能的 `allowedTools`,`field: skills[i].allowedTools`)要的 `global`:档只由这次请求验过的凭据推 —— 部署的 service 凭据、或中心登录令牌在活动 scope 里的角色 ≥ `editor`(settings-schema 的治理写角色)⇒ `global`;角色更低 / 令牌不带角色 / 直连门 principal 令牌 / 没有验过的凭据 ⇒ `project`(`detail.callerTrust`)。`deny` / `ask` 与空 `allow` 照收;单用户部署任何调用方都是 `global` | 去掉 `allow`、开跑时答卡;请运维把规则放进 `APPROVAL_GRANT`;或请 scope 管理员把成员角色抬到 `editor` 及以上。提交前可读 `GET /v1/capabilities` 的 `settings.permissions.callerTrust` |
2586
- | HTTP `422`(`request settings cannot be applied as written: …`)+ `detail.problems[].reason:"home_unresolvable"`,句子说 `user-settings path rule rooted at the configuration directory`(7.141.0 起) | 请求 `settings.permissions.deny` / `ask` 里一条 `layer:"userSettings"` 的 `/` 形路径规则,而这条沙箱车道既没把 `SEMA_CONFIG_DIR` 转进沙箱、也没有可知的 home(k8s / local-docker / ssh / adb / device 且未声明 `REMOTE_EXEC_HOME_DIR`)—— 配置根读不出,不猜目录(续跑同体 409 同句) | 往沙箱 env 名单里加 `SEMA_CONFIG_DIR`,或声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形 / 放进项目层(读在任务根);见 9.4d |
2702
+ | HTTP `400` + `errorCode:"request.field_invalid"` + `problems[].reason:"below_trust_floor"`(7.133.0 起;7.148.0 起在体根上) | 多租部署(`REQUIRE_PRINCIPAL=true`)上调用方的信任档低于 `settings.permissions.allow`(或 `userInvokedSkills` 点用的自带技能的 `allowedTools`,`field: skills[i].allowedTools`)要的 `global`:档只由这次请求验过的凭据推 —— 部署的 service 凭据、或中心登录令牌在活动 scope 里的角色 ≥ `editor`(settings-schema 的治理写角色)⇒ `global`;角色更低 / 令牌不带角色 / 直连门 principal 令牌 / 没有验过的凭据 ⇒ `project`(体根上的 `callerTrust`)。`deny` / `ask` 与空 `allow` 照收;单用户部署任何调用方都是 `global` | 去掉 `allow`、开跑时答卡;请运维把规则放进 `APPROVAL_GRANT`;或请 scope 管理员把成员角色抬到 `editor` 及以上。提交前可读 `GET /v1/capabilities` 的 `settings.permissions.callerTrust` |
2703
+ | HTTP `422`(`request settings cannot be applied as written: …`)+ `problems[].reason:"home_unresolvable"`,句子说 `user-settings path rule rooted at the configuration directory`(7.141.0 起) | 请求 `settings.permissions.deny` / `ask` 里一条 `layer:"userSettings"` 的 `/` 形路径规则,而这条沙箱车道既没把 `SEMA_CONFIG_DIR` 转进沙箱、也没有可知的 home(k8s / local-docker / ssh / adb / device 且未声明 `REMOTE_EXEC_HOME_DIR`)—— 配置根读不出,不猜目录(续跑同体 409 同句) | 往沙箱 env 名单里加 `SEMA_CONFIG_DIR`,或声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形 / 放进项目层(读在任务根);见 9.4d |
2587
2704
  | HTTP `429` + `errorCode:"usage.window_exhausted"` | 部署级治理窗耗尽(`USAGE_WINDOWS`,token 或 $ 天花板先满者) | 看响应体 `retryAfterSec` 退避;窗滑动/桶到期后放行 |
2588
2705
  | `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
2589
2706
  | HTTP `409` + `errorCode:"env.home_unconfirmed"`(同步 `POST /v1/tasks`;流式 / 后台腿是 `terminal:{kind:"failed", code:"env.home_unconfirmed"}`;7.144.0 起) | 执行环境的家目录主张证不了:部署给环境的家目录(`REMOTE_EXEC_HOME_DIR`,没设时 host 车道 = 服务进程的家目录)与环境里命令看到的 `HOME`(含会话 `settings.env` 里写的 `HOME`)对不上 / 读不出 / 不是绝对路径 ⇒ 环境在开跑前就不铸:一条命令没跑、模型一次没调、一个 token 没烧,不管模型要跑的命令带不带路径都是这一句(句子点名两个家目录与改法)。今天只有 host 车道(含 `run-local`)这样答;远端五车道的环境也在开跑前打开、拒在那一拍判出,但 run 照样开跑:不带路径的命令拿到同一句(工具结果里);读边界要先读环境才判得了的命令先被读边界拦下、句子不点名家目录(路径不在读拒表上时;全 shell 面要人批:`~/` 下的递归读五条车道都是,`~/` 下的普通读在 ssh / adb,工作区内相对路径的普通读在 adb;`AUTONOMY=read-only` 上 adb 的工作区相对路径读被拒);ssh / adb 上会话绑了附件 ⇒ 开跑前 `failed`、终局不带码(句子以 `attachment materialization failed` 起头);从快照恢复的续跑腿(e2b;配了快照存储的 k8s)⇒ `resume.env_failed` | 把 `REMOTE_EXEC_HOME_DIR` 设成环境里 `echo $HOME` 的结果(或删掉它 —— host / e2b 删掉后主张回落车道的出厂答案,仍要与 `HOME` 一致),或让环境的 `HOME` 等于声明,或去掉会话 `settings.env` 里的 `HOME`;原样重试不改判 |
@@ -2598,12 +2715,14 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2598
2715
  **机器码**:每个 4xx/5xx 响应体都带一个 `errorCode`(与人类文案 `error` 并列),这是**唯一**该拿来做
2599
2716
  程序分支的字段——**别锚 `error` 文案**。前缀族固定:`auth.` / `request.` / `not_found.` / `conflict.` /
2600
2717
  `limit.` / `usage.`(部署级治理窗)/ `capability.`(本部署没接这个面)/ `feature.`(开关没开)/
2601
- `internal.` / `state.`;未知码按前缀兜底永远安全。完整码表见 `docs/ASSISTANT-WIRE-CONTRACT.md` §附录 A。
2718
+ `internal.` / `state.`;未知码按前缀兜底永远安全。完整码表在契约附录 A(内部记录,不随包;随包 `wire-changes/server@<版本>.json` 逐版记着 wire 键与闭集成员的增删)。
2719
+ `error` 文案里本服务自己写的部分有用词约束 —— 内部词闭集与内部编号形、按 `<状态码>:<errorCode>` 逐族的放行与理由 —— 随包在 `docs/error-text-lexicon.json`,
2720
+ 本服务自己的文案检查读的就是这一份;要对拒句做用词验收,按它判(射程、匹配式与放行怎么读写在文件的 `description` 里:句中插进来的值,如调用方送来的名字,不在约束里)。
2602
2721
 
2603
2722
  **进程警告面(日志采集方注意)**:引擎(core ≥5.16)在持久 agent 行的 org 判决写回被丢弃等场合走
2604
2723
  Node `process.emitWarning`,警告文案前缀固定 `sema durable-agents:`——采集规则按该前缀匹配,别当噪音过滤掉。
2605
2724
  server 自身的兜底留痕走 stderr 上的一行 JSON 日志(`msg` 逐字 `[sema] fail-open: <tag>`,字段 `{ tag, count }`,逐 tag 一次,不随 `LOG_LEVEL`)+ `fail_open_total` 指标,
2606
- tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配期忠告(core `onError(phase:"config")`)落
2725
+ tag 词表与每条的最坏后果 = 随包 `dist/observability/fail-open.js` 里 `FAIL_OPEN_TAGS` 每项的 `cls` / `note`(键名与类型也在同目录 `.d.ts`)。引擎装配期忠告(core `onError(phase:"config")`)落
2607
2726
  `runner_config_advisory` 日志行,带结构化 `code`(如 `config.toolpolicy.unmatched_names`)与 `classification`
2608
2727
  (core 给宿主定级的判别词;词表外原样透传并标 `classificationKnown:false`),指标 `runner_config_advisory_total`
2609
2728
  按 `code` / `classification` / `level` 打标;其中 `shell-gate-off` 在本 session 显式声明 `permissionMode:
@@ -2625,8 +2744,7 @@ tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配
2625
2744
  **非读**命令(`rm` / `curl` / `git` / `npm`)与工作区内的普通读在 `off` 下仍然零审批。注文案里那句
2626
2745
  「the read boundary is still judged on every command」就是这件事。⇒ **运维侧后果**:一台把 `bypassPermissions`
2627
2746
  当「无人值守直跑」用的部署,现在会在这三类命令上落审批(无活体席 ⇒ durable park,出现在 `GET /v1/approvals`)。
2628
- 要让它们重新不问,得从**读面**下手(`READ_FACE` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`,
2629
- 见 `docs/DEPLOY-PREREQS.md`),`permissionMode` 那根杆子够不着它。
2747
+ 要让它们重新不问,得从**读面**下手(`READ_FACE` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`),`permissionMode` 那根杆子够不着它。
2630
2748
 
2631
2749
  ### 9.1 limits.approachNotice(6.1.0 起)
2632
2750
 
@@ -2735,11 +2853,11 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2735
2853
 
2736
2854
  - **没配快照存储**(`MINIO_ENDPOINT` / `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` 缺任一)⇒ 工作区每轮为空,`GET /v1/capabilities` 不带 `workspaceSync`;起服一行 `workspace_persist_unavailable`(`missing` 点名缺的键;缺省 `space` 也出,设 `WORKSPACE_PERSIST=off` 即不出)。
2737
2855
  - **没有存储后端**(`DB_BACKEND=memory`;配了 SQL 的 `DB_BACKEND` 却显式 `SESSION_BACKEND=memory` 时库不开,同一形)× `space`(含缺省,且配了快照存储)⇒ **拒启**,拒句一行点名 `WORKSPACE_PERSIST=space`、缺的存储后端、会话店种类(与 `/health.sessionBackend` 同一读法)与可用的后端。出路:`DB_BACKEND=local`,或 SQL(`DB_BACKEND=mysql|pg`,`SESSION_BACKEND` 不设 `memory`),或设 `WORKSPACE_PERSIST=off`。`SESSION_BACKEND=memory` 配 `DB_BACKEND=local` 不在此列:被收编成 local 店,照起。拒启而不是降级:否则能力位报 `space`、每条 run 却一份都不存。
2738
- - **快照桶不存在**(`WORKSPACE_SNAPSHOT_BUCKET` 指的桶没建):**起服拒启**(k8s 车道配了快照存储时,server 在打开任何店之前核这只桶;拒句点名桶名、`MINIO_ENDPOINT` 的显示安全形与出路 —— 建桶,或把 `WORKSPACE_SNAPSHOT_BUCKET` 指向已有的桶;建桶晚于 server 起是编排里的常态,所以缺桶在 `STORE_LOCK_WAIT_MS` 窗内重试、到窗才拒);连不上 / 403 / 5xx 这些证明不了桶缺的答复不拒启,走 fail-open 兜底口 `server.snapshot-bucket.presence-unproven`。起服之后桶才没的(被删),或起服核证明不了的部署:读 `latest` 得 `404 NoSuchBucket` ⇒ 这条 run **连沙箱失败**(`connect_failed`,句子带 `snapshot_bucket_missing`、桶名与出路),一只 pod 都不建,另有一行 `workspace_snapshot_bucket_missing`(`space` / `bucket` / `code` / `effect`),每只执行环境至多一次;**不再**读成「空间第一条 run」而给一个空目录、每轮只留下一条不点名桶的存失败。无 `NoSuchBucket` 的 404(桶在、键不在;代理的 404)仍是「空间第一条 run」。
2856
+ - **快照桶不存在**(`WORKSPACE_SNAPSHOT_BUCKET` 指的桶没建):**起服拒启**(k8s 车道配了快照存储时,server 在打开任何店之前核这只桶;拒句点名桶名、`MINIO_ENDPOINT` 的显示安全形与出路 —— 建桶,或把 `WORKSPACE_SNAPSHOT_BUCKET` 指向已有的桶;建桶晚于 server 起是编排里的常态,所以缺桶在 `STORE_LOCK_WAIT_MS` 窗内重试、到窗才拒);连不上 / 403 / 5xx 这些证明不了桶缺的答复不拒启,走 fail-open 兜底口 `server.snapshot-bucket.presence-unproven`。起服之后桶才没的(被删),或起服核证明不了的部署:读 `latest` 得 `404 NoSuchBucket` ⇒ 这条 run **连沙箱失败**(`connect_failed`,句子带 `snapshot_bucket_missing`、桶名与出路),一只 pod 都不建,另有一行 `workspace_snapshot_bucket_missing`(`space` / `bucket` / `code` / `effect`),每只执行环境至多一次;**不再**读成「空间第一条 run」而给一个空目录、每轮只留下一条不点名桶的存失败。无 `NoSuchBucket` 的 404(桶在、键不在;代理的 404)仍是「空间第一条 run」。**恢复归档那一发**(停驻续跑;或 `latest` 读之后、取归档之前桶没了)得 `404 NoSuchBucket` 同一读法:pod 把存储的答复原样交回、由 worker 用同一只判据读 ⇒ 同拒(句子带 `snapshot_bucket_missing`、桶名与出路)、同一行(停驻车道带 `sessionId`,不带 `space`)、刚建的 pod 释放,这只执行环境此后每次连接答同一拒;此前读成悬空恢复点(`workspace_latest_dangling`),续跑在空目录上照跑。无 `NoSuchBucket` 的 404 仍是悬空恢复点。
2739
2857
  - **读 `latest` 失败**(非 404 的 HTTP 错、网络错)、**读或弃提交围栏那一行失败**或**读会话住客失败**(会话店报错、答出的不是住客令牌,或没在 `rpcTimeoutMs`〔30 秒,与读围栏行、读 `latest` 同一只墙〕内答 —— 挂住的会话店不再让 run 的起步永挂)⇒ 这条 run 连沙箱失败(`connect_failed`,工具调用报沙箱不可用;停驻 run 续跑时的恢复同样失败),**不会**悄悄给一个空目录、也不猜「没有提交在飞」—— 「以为恢复了其实没有」比失败更坏。读住客失败时一只 pod 都不建,另有一行 `workspace_space_unreadable_occupant`(`sessionId` / `code`:会话店错误的码,不带正文;读超时的码是 `incarnation_read_timeout`),每条 run 至多一次。修好存储 / 数据库后重跑,或设 `WORKSPACE_PERSIST=off` 即恢复。
2740
2858
  - **`latest` 指向的快照对象不在了**(被删 / 被只盖 `.tar.gz` 的生命周期规则清掉)⇒ 沙箱**照常起**:一行 `workspace_latest_dangling`(`lane: "space"` / `space` / `key` / `deleted`),删掉悬空的 `latest`,这条 run 从空目录开始,它的 run 末快照成为新的 `latest`(数据已被运维规则清走:响亮、可继续)。指针删不掉 ⇒ 同一行 `deleted: false` + `error`,下一条 run 再说一次。**打包过的停驻快照对象不在了**(`kata-…` 形,同样被清走)⇒ 同一条规则、同一行名:续跑**照常起**,一行 `workspace_latest_dangling`(`lane: "park"` / `key` / `sessionId`),这条腿从空目录开始(停驻车道没有指针可删;空间开着时它的 run 末快照成为新的 `latest`)。7.142.x 及以前这一形不出任何行。
2741
2859
  - **`latest` 指向的快照在、却拿不回来**(桶答了却不是 200 / 解压失败 / 超过快照时限 / 传输中断)⇒ 这条 run **仍然连沙箱失败**(`connect_failed`)、**不**退到空目录 —— 用户的改动还在桶里,静默空目录 = 让人以为丢了。**一条 run 只恢复一次**(同一只执行环境上并发的连沙箱 —— 显式 `connect()` 与首个工具调用的懒连接 —— 共用同一次建连:一只 pod、一次恢复、一个终局,被拒也是同一个拒;恢复还没完成时才到的工具调用和 `reconnect` 也等这一次建连 —— pod 先于它的工作区存在,不在半恢复的 `/workspace` 上先跑):瞬时类(`timeout` 超过快照时限 / `transport` 传输中断:执行通道断了,或 pod 取包的 GET 没拿到完整应答 —— 连不上、被重置、传到一半被截)在恢复这一步里换一只新 pod 再试一次(约 1 秒后),再试成了 ⇒ 照常起、不出行;`store_busy`(桶答 `408` / `429` / `500` / `502` / `503` / `504` —— 「现在不行」,同一请求再发可能换答案)同属瞬时类;非瞬时类(`download` 桶答了、答的是其余非 200 —— 如 `401` / `403` / `501`;`untar` 包完整下来了却解不开)不重试。每次没成的尝试,新起的 pod 当场删掉(残缺目录既不给工具用,也不会在 run 末被快照回去覆盖 `latest`)。恢复终局是拒 ⇒ **恰一行** `workspace_restore_failed`(`space` / `snapshotId` / `class` = `store_busy` \| `download` \| `untar` \| `timeout` \| `transport` / `attempts` = 这次恢复的尝试次数(`1` 或 `2`)/ `boundMs` = 每次尝试的时限 / `error`,预签 URL 已脱敏),这条 run 之后的每次沙箱操作都答**同一句**拒(不再建 pod、不再读 `latest`、不再出行);建 pod 本身失败不是恢复的结局(不出这行,下一次操作重来)。拒句写明类别与尝试次数,点名两条出路:**调大 `WORKSPACE_SNAPSHOT_TIMEOUT_MS`**(大工作区撞上界),或 **`WORKSPACE_PERSIST=off` 跑一轮**(空目录起,不删任何快照;改回 `space` 后下一条 run 照常恢复那一份)。没有「删指针」的接口:丢弃这份快照 = 丢弃用户数据,要做就由运维在对象存储侧有意识地删 `<前缀>/<化身>/space/latest`(下一条 run 从空目录起)。与上一条「悬空」分开:那是对象**不在**(没东西可恢复 ⇒ 空目录 + 响亮),这是对象**在**而拿不回来(⇒ 拒)。
2742
- - **run 末提交围栏失守、上传失败或 `latest` 写失败** ⇒ 一行 `workspace_snapshot_failed`(`space` / `stage` = `fence` \| `upload` \| `pointer` / `code` / `error`,预签 URL 已脱敏),这条 run 在 `GET /v1/fleet/stream` 上的终态 task 帧带 `workspaceSnapshot: "failed"`;pod 照删;`latest` 不动 ⇒ 下一条 run 恢复的是**上一份**。`latest` 只指向真写出的对象:run 末保存走到上传那一步时沙箱 pod 已不在(它在提交围栏认领期间消失)也按上传失败处理(`stage: upload`、`code: connect_failed`),不铸一个没有对象的 snapshotId 去改指针。
2860
+ - **run 末提交围栏失守、上传失败或 `latest` 写失败** ⇒ 一行 `workspace_snapshot_failed`(`space` / `stage` = `fence` \| `upload` \| `pointer` / `code` / `error`,预签 URL 已脱敏),这条 run 在 `GET /v1/fleet/stream` 上的终态 task 帧带 `workspaceSnapshot: "failed"`;pod 照删;`latest` 不动 ⇒ 下一条 run 恢复的是**上一份**。`latest` 只指向真写出的对象:run 末保存走到上传那一步时沙箱 pod 已不在(它在提交围栏认领期间消失)也按上传失败处理(`stage: upload`、`code: connect_failed`),不铸一个没有对象的 snapshotId 去改指针。**上传成没成只看 worker 对那只对象的回读**(预签 `HEAD`,2xx = 在;两条车道同一判据,停驻挂起也只在回读之后才把 snapshotId 交给引擎):pod 里 curl 报的退出码与 HTTP 状态只用来解释失败 —— curl 拿到 3xx(不跟随)退出 0 而对象不在 ⇒ 上传失败(`error` 里点名回读结果与 pod 报的状态);对象已经落下、curl 才丢了应答(非零退出)⇒ 算存成。`latest` 在回读之后才动。**`latest` 写(以及悬空恢复点删 `latest`)的应答没回到 worker**(请求抛出:连接重置、时限到点、被拒的重定向)⇒ 回读一次指针定结局:指着这一份(删:已不在)⇒ 算成;否则 / 回读失败 ⇒ 失败(`stage: pointer`)。回读只在那一写的期限之内作数(run 末那一写的期限 = 提交围栏的期限;过了期限才答 / 来不及回读 ⇒ 失败,`error` 写「deadline passed」,不删旧份)。存储答了非 2xx ⇒ 照旧直接算失败。上传与对象回读共用这次保存的数据界(`WORKSPACE_SNAPSHOT_TIMEOUT_MS` 减去提交前等待的时长),回读不另开一只时限。
2743
2861
  - **run 末快照保存前一律先停 pod 里的进程并确认**(与那一刻有没有命令在飞无关)。命令「结束」(自己退出、被取消、超时)不等于它起的进程结束(断开 exec 套接字不会让 pod 里的进程停),在飞登记里已经没有命令也不能证明 pod 静了,所以保存前都先杀掉 pod 里除 init 与容器保活进程外的全部进程并确认;没连过沙箱的 run 没有可存的工作区,不走这一步。**run 结束时还有命令在跑**(取消 / 超时结束的 run 的命令没来得及停)⇒ 杀之前**先等**它们结束:最长一只 `rpcTimeoutMs` 墙(缺省 30 秒;等待算在这次提交的快照时限里,上传拿到的是剩下的);等不到的还要断开仍开着的 exec 套接字(那条命令的调用方读到 `aborted`),**照样保存**,并出一行 `workspace_snapshot_commands_cut`(`space` / `inFlight` / `waitedMs` / `killed`)。**没能确认进程停下**(杀进程那一步失败,或几次复查后仍有活进程;有没有命令在飞、等到与否都一样)⇒ **不保存**:一行 `workspace_snapshot_failed`(`stage: upload`、`code: command_in_flight`),`latest` 不动 —— 可能还在变的工作区不当 `latest`。**确认不了的四种形**(工作区不存,下一轮恢复的是上一份 —— 本轮的工作在下一轮看不到;7.131.x 在「run 结束时没有命令在飞」时照存):镜像里经 `sudo` / setuid 程序起的、以别的 uid 跑的进程(`kill -9` 没有权限,例如 `sudo -u postgres` 起的库进程)· 不可中断睡眠(D 态)的进程 · `podSpecPatch` 打开了 `shareProcessNamespace`(同 pod 别的容器的进程也在 `/proc` 里)· 镜像里没有 `tr`、而容器保活进程不是 1 号进程(pod 起动先跑包源钩子的那一形:认不出保活进程、把它杀掉,容器随之结束)。**出路**:k8s 车道镜像以一个非 root 用户跑、不经 sudo / setuid 起常驻进程,带 `tr`,不开 `shareProcessNamespace`;按 `workspace_snapshot_failed{stage: upload, code: command_in_flight}` 告警;不需要跨轮保留的部署设 `WORKSPACE_PERSIST=off`。杀进程脚本只在「该杀的都不活了」时退出 0(僵尸进程算死:容器保活进程不收尸)。从 destroy 开始起这只执行环境不再收新命令(工具调用报沙箱已销毁)。`command_in_flight` 的拒只留给停驻挂起(run 还活着,撕裂的归档才是真风险)。此前 run 结束时只要还有命令在飞,快照就整份被拒(`workspace_snapshot_failed`,`stage: upload`),`latest` 不动,下一轮恢复上一份 —— 取消 = 丢掉这一轮的工作。
2744
2862
  - **pod 被集群在准入时拒**(pod 建出来后、跑起来之前就到了 `Failed`:如 `K8S_SANDBOX_IPV6=disabled` 而 kubelet 没放开那两只 sysctl 时的 `SysctlForbidden`、节点亲和不符、节点放不下)⇒ 这只执行环境**记住这次拒**直到销毁:这条 run 的每次操作答同一句 `connect_failed`(沙箱不可用;句子点名 pod 的阶段与 kubelet 的原因),**不再每次操作重建 pod**(此前一条 run 里可建出几十只 `Failed` pod),与「恢复被拒」同一只记住的槽、同一个写口;一行 `sandbox_pod_refused`(`pod` / `phase` / `reason` / `detail`:kubelet 的原话,截断 / `sessionId`),每只执行环境至多一次。重跑这条 run 是一只新的执行环境,重新问集群。API 拒建 pod(HTTP 非 2xx:配额等)不在此列,仍是那一次尝试的失败。
2745
2863
  - **删最老的失败** ⇒ 一行 `workspace_snapshot_prune_failed`,不影响本轮(空间里只是多留几份)。
@@ -2870,7 +2988,7 @@ k8s 与 e2b 两条沙箱车道各有一只**活性界**:一条命令无输出满
2870
2988
  - 两样都没说(引擎自己的 git 探针、文件读写这类)⇒ 车道活性界兜底,它是这条命令唯一的挂死护栏。
2871
2989
 
2872
2990
  界从调用开始算,覆盖命令全程 —— 含 e2b 供应商建命令那一段(命令还没起跑,自然没有输出):建命令卡住按这条命令最早的那一界切(说了超时按超时,没说界按活性界)。
2873
- e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时先由它切:超时或活性界长于 60 s 的命令(含缺省 120 s 的活性界)建命令卡住在约 60 s 被切 —— 缓冲 exec(Bash 走的这一面)报 `timeout`,流式 exec 报 `connect_failed`。
2991
+ e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时先由它切:超时或活性界长于 60 s 的命令(含缺省 120 s 的活性界)建命令卡住在约 60 s 被切 —— 缓冲 exec(Bash 走的这一面)与流式 exec 同报 `timeout`(那是供应商的界;建命令的其余错误仍报 `connect_failed`)。
2874
2992
 
2875
2993
  | 旋钮 | 车道 | 缺省 | 说明 |
2876
2994
  |---|---|---|---|
@@ -2969,6 +3087,56 @@ ssh / adb / local-docker 三条车道的流式调用同读这一条律:没有车
2969
3087
  - **运维**:每次播种一行 info `workspace_seeded{sessionId, bundleId, entries}`;删会话时种子随附件一并删。
2970
3088
  - **不删包**:首交成功之后不要 `DELETE /v1/attachments/:id` —— 无持久的车道每个新回合都要再解它。
2971
3089
 
3090
+ ### 9.3k local-docker 车道:容器帽(`DOCKER_MAX_CONTAINERS`;7.148.0 起,缺省关)
3091
+
3092
+ local-docker 车道每条 run、每只委派子代各起一只容器,停驻期(等审批)的容器不销毁;`DOCKER_MEMORY` 只封单只。要给整条车道封顶(家用 / 多租常驻部署的内存预算):
3093
+
3094
+ | 旋钮 | 值 | 含义 |
3095
+ |---|---|---|
3096
+ | `DOCKER_MAX_CONTAINERS` | 非负整数,缺省 `0` = 关 | 本 worker 同时在跑的容器数上限;坏值(负 / 小数 / 非数)拒启 |
3097
+
3098
+ - **按内存定上限**:上限只有这一只旋钮。每只容器的 `--memory` 都是起它那一刻的 `DOCKER_MEMORY` ⇒ `DOCKER_MAX_CONTAINERS = floor(愿给容器的总内存 / DOCKER_MEMORY)`(例:总预算 8 GiB、`DOCKER_MEMORY=2g` ⇒ 4)。
3099
+ 改小 `DOCKER_MEMORY` 重起之后,上一次留下的容器仍按它们起动时的限额占内存、按只计 —— 换算只对按新值起的容器成立,改值前先清掉残留。
3100
+ - **怎么数**:只有一个答者 —— docker daemon 上带本 worker 标签 `sema.local-docker=<worker>` 的容器(**标签恒在,上限随时可开**:本 worker 起的每只容器都带这枚标签,与上限开没开无关,上限关着时起的开启后照计;`docker ps -a` 全状态读,在跑 = running / paused / restarting / removing),
3101
+ 加上本进程 `docker run` 回执丢了而 daemon 仍列着的那几只(不确定 ≠ 没发生;daemon 不再列它 ⇒ 放)。停驻期的、委派子代的容器都在跑,都计。读不出 daemon ⇒ 不算 0,按「证不了有空位」处理。
3102
+ - **射程 = 本 worker**:worker = 本服务数据根(`LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`)绝对路径的摘要,同一数据根上重起不变 ——
3103
+ 上一次进程崩掉留下的容器照计(不计会让重起之后超额受理);别的数据根是别的 worker,不计。准入的互斥与「回执未定」的记账只在一个进程里:一个数据根只跑一只服务进程 ——
3104
+ 同一数据根上同时跑的两只进程看得见对方**已在跑**的容器(只会少受理),但看不见对方正在起、还没列出的那几只,同时起动时合计可超过上限;多只进程 / 多只 worker 共用一只 daemon 时的整机合计不在本旋钮。
3105
+ 只有 7.148.0 之前起的容器不带标签、不计 —— 升级后第一次开帽前先清掉它们(`docker ps -a --filter name=sema-docker-`)。拒句里给本 worker 的完整过滤式;`docker ps -a --filter label=sema.local-docker` 列出所有 worker 的。
3106
+ - **没空位怎么办**:起容器(首个工具调用、子代、续跑腿)按到达顺序等,别的容器被删即唤醒(外部删除每秒重读看见);每只执行环境一份等待预算(docker 控制操作的界,缺省 60 s,跨尝试共用 —— 花完之后每次只读一次、
3107
+ 有空位就起、没有立拒)。到期 ⇒ 这一次工具调用拿到带 `limit.rate_exceeded` · `sandbox_capacity` 的拒句(在用数 / 帽 / 等了多久 / 出路),run 本身不被终结;运维行 `local_docker_capacity_refused`。
3108
+ - **提交门**:闸里有起动在等空位时,新提交答 `429 limit.rate_exceeded` + `reason:"sandbox_capacity"` + `Retry-After: 5`(上面错误表一行);队列空而车道满时照常受理,在首个工具调用处等。
3109
+ - **读数**:`GET /health` 的 `sandboxCapacity: { lane, limit, inUse, readAt, waiting }`(帽开着才在场;`inUse` 是闸最近一次读 daemon 的数,本口不调 docker)。
3110
+ - **停驻 → 续跑**:续跑腿接着用停驻时那只容器(容器名由 run 的身份确定:`sema-docker-<身份>-<12 位摘要>`;服务重起之后续跑 ⇒ 附着那只在跑的容器),在跑的不另要一格,续跑腿收场时删它。
3111
+ 停驻期间 daemon 重起 / 宿主重启让它停了(`exited`)⇒ 续跑时经准入(开了上限就等空位)再 `docker start` 它、附着(文件在);停在 `paused` / `dead` 一类状态、同名容器不带本 worker 标签、
3112
+ `docker inspect` 读不出 ⇒ 这一次连接拒、什么都不起不删(拒句点名容器)。执行环境只碰它负责的那只容器实例(按容器 id,不按名);删除证实不了的那只永不再附着。
3113
+ 重起之后那只容器已经不在 ⇒ 续跑腿起一只新的空容器(停驻前的文件不在)。
3114
+ - **无主容器回收**:停驻中取消、停驻过期(引擎只在自己结清停驻时销毁停驻环境,本服务的取消与过期在店层结清)与重起之后才结清的停驻,停驻时那只容器没有销毁者 —— 维护 tick 上的沙箱回收腿
3115
+ (与 k8s 车道的 pod 回收共一只座、一个节拍:起服后第一个维护 tick 跑一轮,此后每 5 分钟一轮;不新开计时器、没有旋钮)按它那条 run 的行判:**run 行已终局(completed / failed / blocked)并过了 5 分钟保持窗 ⇒ 删**(引擎在 `done` 帧之后还要用环境收尾:边界捕获 / 后台 shell 清扫 / 销毁,终局行比这些早 —— 窗内不动;钟 = run 行转终态那一拍)
3116
+ (本进程还拿着那只执行环境 ⇒ 经它收场;重起之后没有环境对象 ⇒ 认领这只名下本 worker 的容器,按容器 id 经同一只欠着机删,证实才清),这与一条 run 正常收场时引擎删它的容器是同一件事;
3117
+ **run 行是活的(跑着 / 停驻着,还能被批准或续跑)⇒ 不动**。拿不准恒不删:容器名读不回身份(本版之前起的容器名带随机尾、可读形被截断的身份)、没有 run 行(委派子代的容器:身份是它自己的会话号,不在 run 账本里)、
3118
+ 读 run 账本抛、状态词不在词表里、终局时刻读不出、没有存储后端 ⇒ 都不动(这几类仍按上面「怎么数」里的过滤式手删)。别的 worker 的容器不在列(只列本 worker 标签);列出之后名下被换成不带本 worker 标签的容器 ⇒ 不删。
3119
+ 每轮一行 `local_docker_reap_round`(`worker` / `listed` / `scanned` / 按原因分的 `deleted` 与 `skipped`〔`run_live` · `within_run_end_hold` · `run_end_unreadable` · `run_unread` · `run_read_failed` · `run_status_unknown` · `run_store_absent` · `identity_unreadable` · `nothing_to_remove`〕/ `unconfirmed`),
3120
+ 每删一只一行 `local_docker_reaped`,计数 `local_docker_containers_reaped_total{reason="run_ended"}`。**列举失败**(docker 不可达)⇒ 这一轮一只都不删:一行 warn `local_docker_reap_list_failed` + fail-open 计数
3121
+ `server.local-docker-reaper.degraded`(detail `why=list_failed`),下一轮照常再列;**某一只删除没证实**(daemon 出错、容器还在)⇒ 不挡同轮其余:一行 warn `local_docker_reap_unconfirmed` + 同一个计数(`why=delete_unconfirmed`),
3122
+ 这一只仍欠着(永不再附着,下一轮先删它)—— 计数持续非零就核 docker daemon 与 `docker rm -f` 的权限。**运维可见的差别**:被取消 / 过期的 run 的容器在 run 终局 5 分钟后的下一轮(最长再加一个 5 分钟周期)被删,不再留着供事后 `docker exec` 进去看;
3123
+ 删之前要取的文件在取消之前 `docker cp` 出来。
3124
+
3125
+ ### 9.3l 前台命令超时转后台(`AUTO_BACKGROUND_ON_TIMEOUT`;7.148.0 起,缺省开)
3126
+
3127
+ 前台 Bash 命令撞上**它自己的**超时(`timeout` 参数,或引擎缺省值)时,合格的命令转成后台任务继续跑,而不是被杀 —— 长构建 / 迁移这类不幂等的命令不必盲重跑。
3128
+ 资格由引擎判(命令里没有 `git`、首词不是 `sleep`);转了之后模型读到「超时了但仍在跑,已转后台(task_id=…)」,用 `TaskOutput` / `TaskStop` 照常读 / 停,完成时照常收到通知。
3129
+ 同一个开关在引擎里还管第二个触发:有人的输入(steer / 排队的消息)等着本轮边界、而合格命令还在跑 ⇒ 引擎把它转后台,好让输入先送达(命令照跑,结果走后台通知)。引擎刻意只设这一个开关。
3130
+
3131
+ | 旋钮 | 缺省 | 说明 |
3132
+ |---|---|---|
3133
+ | `AUTO_BACKGROUND_ON_TIMEOUT` | `true` | `false` ⇒ 两个触发都关:超时即杀、steer 等前台命令跑完(引擎缺省形)。运维旋钮无条件:不能转后台的车道上照起、不拒启 |
3134
+
3135
+ - **哪些车道生效**:执行环境能把运行中的命令领养成后台的车道 —— host(单用户且 `HOST_BG_ENABLED` 开着)/ k8s / e2b。local-docker / ssh / adb / device / none 没有后台 shell,超时照杀。
3136
+ - **哪些 Runner**:主 Runner、委派子代、hook 代理与 leader 车道的 Runner 同一个值(手带只有一个铸点)。
3137
+ - **读数**:`GET /health` 的 `autoBackgroundOnTimeout`(恒在场):`armed` = 本车道上生效;`off` = 旋钮关了;`unsupported` = 旋钮开着,但本车道不能转后台。
3138
+ - **界**:转后台的命令照常受后台 shell 的并发上限(满了 ⇒ 这一发不转、照杀)与后台超时硬上限(到点被杀);它占着的进程 / 沙箱算力在它结束之前不释放。
3139
+
2972
3140
  ### 9.4 auto 模式(`permissionMode:"auto"`):武装三态、本地 org deny、自查(与 core 的意图武装式合修)
2973
3141
 
2974
3142
  **背景**:此前引擎只在「组织授予」(web-admin 解出的 per-principal `runtimeCaps.autoMode === true`)时武装
@@ -2989,7 +3157,7 @@ armed ⟺ 本次请求 permissionMode 是 "auto" ∧ 本部署装配了分类
2989
3157
  | `AUTO_MODE_CLASSIFIER_TIMEOUT_MS` | 未设(⇒ core 缺省) | 7.102.0 起:auto 分类器**一级梯子**的判决上限(整数 ms)→ core `RunnerDeps.autoMode.timeoutMs`。一级到点没裁 ⇒ 走下一级(`AUTO_MODE_CLASSIFIER_FALLBACK` 决定有没有下一级),走完仍没裁 ⇒ 这次门控调用**整条拒**、不出卡;最坏等待 ≈ 级数 × 本值 + 一次装配。**自托管慢网关**(排队 + prefill 以分钟计)在缺省上限内答不出 ⇒ auto 下工具一律被拒 —— 按后端实测留足(如 `180000`)。未设 / 空串 ⇒ 本仓**不铸键**,由引擎落它自己的缺省(不复制上游数字);合法域 `[1000, 2147483647]` 内的十进制整数,`0`(本座没有「关」)/ 亚秒 / 小数 / `1e5` 这类拼法 / 越 Node 定时器上界一律**拒启**。旋钮无条件(与 auto 是否武装无关);不影响 `permissionModeAuto` 读面。目录行见 `GET /v1/config/catalog`(approval 域) |
2990
3158
  | `CROSS_SESSION_INBOUND` | 未设 | 跨终端会话设计线:CC settings 键名 `crossSessionInbound` 的**部署/组织层**(引擎层形的 `managed` 层,CC `policySettings` 对位)—— 本部署对**入站跨会话消息**的治理四态(词表取自引擎闭集,core 7.30.0 起):`wake` 投递且**宿主**可在收件箱增长时起一个 turn / `next-turn` 在下一个 turn 边界投递 / `hold` 停在收件会话的待审队列(模型看不见、不能据此行动)/ `refuse` 本部署整体退出这条车道。⚠️ 本服务宿主**不起 turn**,所以在本服务上 `wake` 按引擎契约的降级臂读作 `next-turn`。🔴 **旧词 `accept` 自 7.100.0(core 7.30.0 提货)起拒启** —— 它的原义就是 `next-turn`,改成那个词即可(零别名,拒启即通告)。🔴 **未设 ≠ `next-turn`**:未设 = 这一层不表态,由其余层与引擎的 mode-parity 判定决定。词表外的值**拒启**。⚠️ **生效前提**:引擎的 peer 目录席(`RunnerDeps.peerDirectory`)在场 —— 7.91.0 起已接线(会话枚举面 ∧ mailbox 店 ∧ 收件人生命周期面三合取),缺任一合取项的部署上本旋钮仍「就位待命」;目录行见 `GET /v1/config/catalog`(approval 域,security 轴) |
2991
3159
  | `CROSS_SESSION_DIALOG_EXPIRY` | 未设(⇒ 引擎缺省 `5m`) | 同族:CC settings 键名 `dialogExpiry` 的部署层 —— 被 hold 的跨会话消息等待人审多久后按**安全缺省**结算(过期丢弃,**带回执**告知发送方,不静默吞)。词表 `60s`/`5m`/`10m`/`never`(`never` = 不设期限);未设 ⇒ 本仓**不铸键**,由引擎落它自己的缺省(不复制上游缺省值)。词表外拒启。⚠️ 生效前提同上。另注:CC 的同名键还有第二个消费面(转发到远端客户端的审批对话框停靠时长),本旋钮**今天不驱动**那一面 |
2992
- | `PRINCIPAL_INBOX_ASSISTANT` | 未设 | 7.133.0 起:本部署 **principal 收件箱**的消费方声明 —— 独占读它的那只助理的 agent handle(→ 引擎 `RunnerDeps.principalInboxConsumer: { assistant }`)。**它就是跨主体投递的开关**:设了 ⇒ 挂上组织投递门与受理口 `POST /v1/principal-inbox/messages`(同一组织里给另一个用户投一封信,契约 §22;能力位 `principalInboxDelivery`),同时**要求**耐久 mailbox(`DB_BACKEND=mysql\|pg\|local`)、组织成员目录(中心,或 `MEMORY_ORG_DIRECTORY_JSON`)与配额店(SQL 后端起服时建三张配额表),缺一**拒启**并点名缺哪件;空白 handle 拒启;未设 / 空串 ⇒ 没有这条车道,一切与上一版逐字节同。额度写死在门里:发件人每小时 60 次尝试(成没成都算)、同一对主体每小时 10 封、一位收件人每小时合计 30 封、收件箱上界 50 封。⚠️ 本版只有投递半:读信的助理腿随引擎下一版,在那之前信留在箱里没人读 |
3160
+ | `PRINCIPAL_INBOX_ASSISTANT` | 未设 | 7.133.0 起:本部署 **principal 收件箱**的消费方声明 —— 独占读它的那只助理的 agent handle(→ 引擎 `RunnerDeps.principalInboxConsumer: { assistant }`)。**它就是跨主体投递的开关**:设了 ⇒ 挂上组织投递门与受理口 `POST /v1/principal-inbox/messages`(同一组织里给另一个用户投一封信,契约 §22;能力位 `principalInboxDelivery`),同时**要求**耐久 mailbox(`DB_BACKEND=mysql\|pg\|local`)、组织成员目录(中心,或 `MEMORY_ORG_DIRECTORY_JSON`)与配额店(SQL 后端起服时建三张配额表),缺一**拒启**并点名缺哪件;值必须是 agent handle 形(`a` 后跟 6–32 位小写十六进制,任务登记表铸的那一形;7.148.0 起由引擎判 —— `asst-1` 这类不透明串、带空白 / 大写 / 换行的值**拒启**,拒句头说清读法(关 = 未设或空串;空白串不是「空」)并带回被拒的原值(JSON 引号,至多 96 字符),后接引擎原句(引用值同样至多 96 字符);此前这类值照收,收件箱等一只永远不会来的读者);未设 / 空串 ⇒ 没有这条车道,一切与上一版逐字节同。额度写死在门里:发件人每个挂钟整点桶 60 次尝试(成没成都算)、同一对主体每个挂钟整点桶 10 封、一位收件人每个挂钟整点桶合计 30 封、收件箱上界 50 封;「桶」是对齐店钟整点的固定窗(窗宽一小时),不是滑动的「过去 60 分钟」(桶边界两侧各满额,跨边界的一小时里最多两倍)。额度拒的 429 带 `retryAfterSec`(到当前桶结束的整秒数)与同值 `Retry-After` 头,按它重投;本服务的配额状态文件(`DB_BACKEND=local`)读不出时一切投递都拒,答带因的 `500 internal.org_delivery_quota_unreadable`(运维修好或移走坏文件并**重启本服务**即恢复 —— 店只在起服时读这份文件,服务进程运行中把文件移走不会让它恢复;移走 = 当前桶计数清零)。⚠️ 本服务只有投递半:引擎(7.148.0 带的版本起)有读信的助理腿,但那条腿要以这只 handle 作任务 id 跑,本服务今天没有起它的入口(读法候引擎确认),在那之前信留在箱里没人读 |
2993
3161
 
2994
3162
  **自查读面**(`GET /v1/capabilities.permissionModeAuto`,壳的 `sema doctor permissions` 消费;server 7.106.0 起引擎只剩一种武装极性,
2995
3163
  7.107.0 起成员 `intentArming` 删除,见该条):
@@ -3059,7 +3227,7 @@ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST
3059
3227
  🔴 **这个体是闭集(7.86.0)**:受理集 = `decision` / `editedPlan` / `reason` / `permissionModeAfter` /
3060
3228
  `boundCallId` 五键,集外键(含定谳前的旧拼法 `nextPermissionMode`)**响亮拒** `400 request.body_shape` +
3061
3229
  机读两桶逐字点名,**不再静默丢** —— 与 `/v1/approvals/:id/decide` 同一只机器、同码同串。
3062
- 逐字契约见 `docs/ASSISTANT-WIRE-CONTRACT.md` §4c。
3230
+ 逐字契约见契约 §4c(内部记录)。
3063
3231
 
3064
3232
  ### 9.4c 两层「放行 / 拒绝」别读混:单用户 allow-all 基线与敏感路径守卫集(7.141.0 起成文)
3065
3233
 
@@ -3076,7 +3244,7 @@ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST
3076
3244
  - **只有 user 层的 `/` 形随层变**:`userSettings` 里的 `Read(/secret/**)` 守的是**执行环境的配置根**下的 `secret`(host 车道 = 本服务的 `SEMA_CONFIG_DIR`,缺省 `~/.sema`),
3077
3245
  不再守任务根下的 `secret` —— 与参照实现读 user 层 `/` 的位置相同。别的来源与纯串读在任务根。要守每个项目自己的目录:把规则放进项目层,或写相对形 `secret/**`(任务根下任意深度);固定位置写 `//` 绝对形。
3078
3246
  - **沙箱车道的配置根**:运维把 `SEMA_CONFIG_DIR` 列进 `E2B_SANDBOX_ENV` / `K8S_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 转进沙箱 ⇒ 读那个值;没转 ⇒ 执行环境 home 下的 `.sema`
3079
- (e2b = `/home/user/.sema`;k8s / local-docker / ssh / adb / device 要 `REMOTE_EXEC_HOME_DIR` 声明 home)。两样都没有 ⇒ 这样的条目 422,`detail.problems[].reason:"home_unresolvable"`(与 `~/` 缺 home 同一个词);
3247
+ (e2b = `/home/user/.sema`;k8s / local-docker / ssh / adb / device 要 `REMOTE_EXEC_HOME_DIR` 声明 home)。两样都没有 ⇒ 这样的条目 422,`problems[].reason:"home_unresolvable"`(与 `~/` 缺 home 同一个词);
3080
3248
  出路:转 `SEMA_CONFIG_DIR`、声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形。转进去的值不是绝对路径 ⇒ deny / ask 422 `unsupported.path_base`(allow 桶见下面「allow 桶同读」)。
3081
3249
  - **层不改准入**:多租部署上的准入档只按这次请求验过的凭据推(9 节速查 `below_trust_floor` 一行);条目上的层只决定规则读在哪。
3082
3250
  - **回滚 / 混版副本**:本版之前的服务对带层条目是桶门 `bad_entry`(新提交 400,续跑 409),不会静默读丢;端见位才发,所以没升级的服务上它照旧只收到纯串。
@@ -3167,23 +3335,23 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3167
3335
  ### 9.5b `settings.hooks` 的 matcher:提交时判什么、拒什么(7.126.0 起良性设置的结局只取决于配置)
3168
3336
 
3169
3337
  `PreToolUse` / `PostToolUse` / `PostToolUseFailure` 三个工具事件的 `matcher` 按调用派发到的工具的 **wire 名**匹配(不是别名)。**新提交**(`POST /v1/runs` · `/v1/tasks` · `/v1/tasks/stream`;续跑重放不判)
3170
- 对每只 matcher 做判断,下面四种情形都答 **`400 request.field_invalid`**,体带 `field: "settings.hooks"` 与 `detail`,按 `detail.reason` 分形(闭集,穷举读):
3338
+ 对每只 matcher 做判断,下面四种情形都答 **`400 request.field_invalid`**,体根上带 `field` = 那一组 matcher 的坐标(`settings.hooks.<event>[<groupIndex>].matcher`)与 `reason`,按 `reason` 分形(闭集,穷举读;7.148.0 起在体根上,此前是 `field: "settings.hooks"` + 对象形 `detail`):
3171
3339
 
3172
- | `detail.reason` | 触发 | `detail` 其余键 | 怎么改 |
3340
+ | `reason` | 触发 | 体根上其余键 | 怎么改 |
3173
3341
  |---|---|---|---|
3174
- | `dead_alias` | matcher 选中某个工具的别名、却选不中它的正名(钩子永不命中) | `event` · `alias` · `canonical` | 改成正名(`ListPeers` → `ListAgents`) |
3342
+ | `dead_alias` | matcher 选中某个工具的别名、却选不中它的正名(钩子永不命中) | `event` · `groupIndex` · `alias` · `canonical` | 改成正名(`ListPeers` → `ListAgents`) |
3175
3343
  | `over_budget` | 正则形 matcher 的一次求值在 `1 000 ms` 内没有结束(灾难回溯) | `event` · `groupIndex`(该事件下第几组,从 0 起)· `budgetMs` | 改成工具名列表(`A\|B`),或去掉嵌套重复的正则写法(如 `(.*)*`) |
3176
3344
  | `budget_exceeded`(7.132.0 起) | 这次提交里所有已完成的正则形求值的耗时之和超过受理总额度 `5 000 ms`(每只都在单次额度之内,但合起来太慢) | `event` · `groupIndex`(越线的那一组)· `totalMs` | 把慢的 matcher 改成工具名列表(`A\|B`)或简化正则;正则形 matcher 越少越好 |
3177
3345
  | `invalid_regex` | 正则形 matcher 编不过(如 `(`、`[Read`);**写在任一契约事件上都判**(7.132.0 起;此前只判三个工具事件) | `event`(任一契约事件)· `groupIndex` | 修正则,或改成工具名列表(`A\|B`);matcher 没有用处的事件(如 `Stop`)直接去掉这个字段 |
3178
3346
 
3179
- `detail` 恒带坐标 `path`(7.132.0 起;matcher 四形 = `["hooks", <event>, <组下标>, "matcher"]`;串段与 `key` 过拒句回显口径:剥控制与双向字符、截到 64 字 + `…`)。**配置本身读不出**的拒(7.132.0 起同带 `field: "settings.hooks"` + `detail`,句子骨架不变):
3347
+ `field` 是判决所在处的坐标(方括号形,与 `unknownKeys` 同一种写法;串段与 `key` 过拒句回显口径:剥控制与双向字符、截到 64 字 + `…`;7.132.0–7.147.0 是 `detail.path` 段形数组)。**配置本身读不出**的拒(7.132.0 起同带机读位,7.148.0 起在体根上,句子骨架不变):
3180
3348
 
3181
- | `detail.reason` | 触发 | `detail` 其余键 |
3349
+ | `reason` | 触发 | 体根上其余键 |
3182
3350
  |---|---|---|
3183
- | 契约分类器的码(`guard-entry-invalid` 等十一词,连字符拼法) | 分类器整份拒(一块可能藏着守卫钩子)或要剔掉一块(提交面不剔、整份拒) | `path`(那一块)· `key`(那一块上让它坏的键,点得出时) |
3184
- | `unsupported_entry_type` | 本服务不执行的条目类型(`mcp_tool`) | `path`(那一条的 `type`)· `type` |
3185
- | `over_cap` / `too_long` | 服务侧上限:组数 16、一个事件的条目总数 32(`path` 指第一条超出的条目)、命令 / URL 8192 字、header 32 个 / 合计 8192 字、`allowedEnvVars` 32 个 | `path` · `count` · `cap` |
3186
- | `unnamed_refusal` | 分类器说拒却一块都没点名(契约漂移,fail-closed) | `path` |
3351
+ | 契约分类器的码(`guard-entry-invalid` 等十一词,连字符拼法) | 分类器整份拒(一块可能藏着守卫钩子)或要剔掉一块(提交面不剔、整份拒) | `field`(那一块)· `key`(那一块上让它坏的键,点得出时) |
3352
+ | `unsupported_entry_type` | 本服务不执行的条目类型(`mcp_tool`) | `field`(那一条的 `type`)· `type` |
3353
+ | `over_cap` / `too_long` | 服务侧上限:组数 16、一个事件的条目总数 32(`field` 指第一条超出的条目)、命令 / URL 8192 字、header 32 个 / 合计 8192 字、`allowedEnvVars` 32 个 | `field` · `count` · `cap` |
3354
+ | `unnamed_refusal` | 分类器说拒却一块都没点名(契约漂移,fail-closed) | `field` |
3187
3355
 
3188
3356
  - **预算有两层**:① 每只 matcher、每次求值各一份(`1 000 ms`)—— 不共享、不随同一份设置里 matcher 的多少缩水,这一份不读钟;② 一次受理的总额度(`5 000 ms`,7.132.0 起)—— 所有已完成的正则形求值的耗时之和,
3189
3357
  超过它就拒(`budget_exceeded`)。字面形(`A|B`)与非工具事件不进求值、不占总额度。良性 matcher 的求值是微秒到毫秒级,两层都给了数个数量级的余量(一份最大的良性配置 —— 3 个工具事件 × 16 组 —— 总耗时在毫秒级),
@@ -3195,7 +3363,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3195
3363
  **超预算 ⇒ 这一组按「选中」处理**(守卫钩子不被跳过,组里的条目照常跑;`if` 超预算同:条目照跑;这样跑起来的条目只有否决生效 —— 放行与 `updatedInput` 不作数,托管组的条目另记没跑成、`PreToolUse` 折问人)并**响亮登记**:一行 warn `hook_matcher_over_budget`(`event` · `scope` · `expression` · `value` · `budgetMs` · `selected: true`)
3196
3364
  + fleet 流上一帧 `hook_notice` / `hook_non_blocking_failure`,`reason: "matcher_over_budget"`(契约附录 E.2)。每次发生各登记一次,一直在坏就一直说;最坏占用是「(超预算的组数 + 超预算的 `if` 条目数)× `1 000 ms`」每次工具调用 —— 看到这行日志就把那只 matcher(或 `if` 条件)改成工具名列表(`A|B`)或简化正则。
3197
3365
  日志与帧里的 matcher / 条件 / 名字先脱敏、再剥控制与双向字符、截 200 字。
3198
- - 拒句点名是**哪一只** matcher(`settings.hooks.<event>[<groupIndex>].matcher`):`over_budget` 一形带预算、`budget_exceeded` 一形带总额度、都不回显 matcher 原文;`invalid_regex` 一形回显 matcher 原文与编译错误句(各截 200 字符,`detail` 不带这两段)。
3366
+ - 拒句点名是**哪一只** matcher(`settings.hooks.<event>[<groupIndex>].matcher`):`over_budget` 一形带预算、`budget_exceeded` 一形带总额度、都不回显 matcher 原文;`invalid_regex` 一形回显 matcher 原文与编译错误句(各截 200 字符,机读位不带这两段)。
3199
3367
  - **这一判只在 hooks 会被采纳的车道上执行正则**(单用户车道;多租户车道 `settings.hooks` 整只被忽略,只做不执行正则的字面判):多租户部署不会因此多出一个在提交线程上跑调用方正则的面。
3200
3368
  - 不变的:matcher 超 `8 192` 字符不读(照旧受理,运维侧记一笔 `server.hooks.matcher-unjudged`)。
3201
3369
  - **编不过的正则与事件无关**(7.132.0 起):写在 33 个契约事件里的哪一个上都 `400`(上表第四行)—— `StopFailure` 的 `error` 词、`PreCompact` / `PostCompact` 的 `trigger` 词、`SessionStart` 等事件,以及 matcher 被忽略的 `Stop` / `UserPromptSubmit` / `PostToolBatch`。
@@ -3207,11 +3375,15 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3207
3375
 
3208
3376
  `http` 条目 header **值**里的 `$NAME` / `${NAME}` 按条目的 `allowedEnvVars` 展开,读法是契约包 settings-schema 的 `interpolateHookEnvVars`(壳与本服务同调,同一份钩子定义到端点的字节相同):
3209
3377
  - 名字只认大写 `[A-Z_][A-Z0-9_]*`;小写名(`$my_token`)是普通文字,原样发出。
3210
- - 列在 `allowedEnvVars` 里 ⇒ 展开成钩子环境里的值(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`;服务进程的密钥不在其中);钩子环境里没有这个名字 ⇒ 空串。
3211
- - 名单外的引用 ⇒ 空串;凭据名表里的名字(契约包 `HOOK_ENV_CREDENTIAL_NAMES`:`CLAUDE_CODE_OAUTH_TOKEN` 一类 27 个名字 × 裸名与 `INPUT_` 前缀;**不含** `ANTHROPIC_API_KEY` / `MODEL_API_KEY` 这类模型凭据名)一律空串,点进名单也一样。本服务自己的模型凭据不靠这张表护:它们从不进钩子环境(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`,服务进程的密钥名在扣留名单里),所以 `${ANTHROPIC_API_KEY}` 在本服务上展开成空串是因为钩子环境里没有它,不是因为它在凭据名表里;7.146.0 起调用方在 `settings.env` 里给这类名字(或任何秘密形的值)新提交即 `400 request.env_credential_refused`(9.5j)。
3378
+ - 列在 `allowedEnvVars` 里 ⇒ 展开成钩子环境里的值;钩子环境里没有这个名字 ⇒ 空串(不进置空留痕,与参照实现同)。**钩子环境**只有一份(命令钩子的子进程环境读同一份):
3379
+ 十二名基础变量(`PATH` / `HOME` / `SHELL` / `USER` / `LOGNAME` / `TERM` / `TZ` / `LANG` / `LC_ALL` / `LC_CTYPE` / `LC_MESSAGES` / `TMPDIR`)∪ 部署方 `HOOK_ENV_ALLOW` 点名放回的服务进程变量,之上叠同请求 `settings.env` 与钩子自己的变量(后者赢)。
3380
+ 服务进程的其余变量(含本服务的密钥)不在其中;请求层(条目 `allowedEnvVars`、下面的 `settings.httpHookAllowedEnvVars`)只收窄能插哪些名,**授予不了**任何服务进程变量。
3381
+ - 名单外的引用 ⇒ 空串;凭据名表里的名字(契约包 `HOOK_ENV_CREDENTIAL_NAMES`:`CLAUDE_CODE_OAUTH_TOKEN` 一类 27 个名字 × 裸名与 `INPUT_` 前缀;**不含** `ANTHROPIC_API_KEY` / `MODEL_API_KEY` 这类模型凭据名)一律空串,点进名单也一样。本服务自己的模型凭据不靠这张表护:它们从不进钩子环境(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`,服务进程的密钥名在扣留名单里),所以 `${ANTHROPIC_API_KEY}` 在本服务上展开成空串是因为钩子环境里没有它,不是因为它在凭据名表里(`HOOK_ENV_ALLOW` 点名它 ⇒ 拒启);7.146.0 起调用方在 `settings.env` 里给这类名字(或任何秘密形的值)新提交即 `400 request.env_credential_refused`(9.5j)。
3212
3382
  - 每个 header 值剥 CR / LF / NUL;header 名不展开。
3213
3383
  - 置空(名单外 / 凭据名)时日志一行 `hook_http_header_env_blanked`(名字与原因,不带值)。
3214
- 例:`"headers": { "Authorization": "Bearer ${HOOK_TOKEN}" }`、`"allowedEnvVars": ["HOOK_TOKEN"]`,并在请求 `settings.env` 里给 `HOOK_TOKEN`(7.146.0 起值是秘密形的 token 不能经 `settings.env` 给,今天没有替代通道 —— 9.5j 已知限制)。7.125.x 及更早的读法不同(升级见 MIGRATION)。
3384
+ 例:`"headers": { "Authorization": "Bearer ${HOOK_TOKEN}" }`、`"allowedEnvVars": ["HOOK_TOKEN"]`,`HOOK_TOKEN` 放在引擎进程的环境里,部署方写 `HOOK_ENV_ALLOW=HOOK_TOKEN`(值从不经请求体;壳自起的本机引擎由壳按钩子条目的 `allowedEnvVars` 写这只旋钮)。不是凭据的值也可以经同请求 `settings.env` 给。7.125.x 及更早的读法不同(升级见 MIGRATION)。
3385
+ **`HOOK_ENV_ALLOW`(部署旋钮,名单型;缺省空 = 不放回)**:`HOOK_ENV_ALLOW=NAME,NAME` 点到、且服务进程环境里在的变量原样放回钩子环境(同上面的「点名放行」一只谓词与一只解析:形错 / 点名名单型旋钮 / 点名本服务自有凭据名 ⇒ 拒启点名第几段、不回显值;环境里没有的名按序号一行 `config_env_entry_rejected`;点名值是本服务某一枚凭据的另一个变量(换了名的副本;列表形凭据逐枚比 —— `SERVICE_AUTH_TOKENS` 每一枚令牌、`APPROVAL_HMAC_KEYS` 每一把钥)同样拒启,组合这一刻引擎照样扣下这样的值)。
3386
+ 点了名就是部署方显式承担:那几只变量对本引擎跑的**每一只** settings hook 可读(命令钩子的环境与 http 钩子的 header),http 钩子发往哪里由 `settings.allowedHttpHookUrls` 管。起服读一次,改名单要重起。`REQUIRE_PRINCIPAL=true` 下忽略(请求的 settings hooks 在该姿态不跑)并起服 warn 一次 `config_env_set_but_ineffective`。
3215
3387
  **两张名单 `settings.allowedHttpHookUrls` / `settings.httpHookAllowedEnvVars`(7.142.0 起;CC settings 同名键)**:读法与判决都在契约包 settings-schema,本服务只在 http 条目**拨出之前**问一次(能力位 `taskSettings.httpHookAllowlists`,单用户车道为真):
3216
3388
  - `allowedHttpHookUrls`(URL 模式的串数组,例 `["https://hooks.example.com/*"]`)在场 ⇒ 条目 `url` 必须命中其中一条才发出;不命中 / 名单是 `[]` / 名单不是串数组 ⇒ **一个字节都不发**。非托管组:这次 hook 算非阻断故障(fleet 流 `hook_notice` `url_not_allowed`,运维 warn `hook_http_withheld`);
3217
3389
  托管组(`source: managedPolicy` / `managedPlugin:*`):失效关闭,`PreToolUse` 在没有否决时改问人。句子点名 `settings.hooks.<事件>` 与规则(`url` / `empty` / `malformed`)。出路:把地址的模式加进名单,或改 hook 的 `url`。
@@ -3224,25 +3396,36 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3224
3396
  - 被采纳的 `settings.hooks`(单用户车道)在引擎里是**每条车道一张**扩展注册:非托管车道 `settings.hooks`;有托管组(`source` 为 `managedPolicy` / `managedPlugin:*`,见 9.5c-quater)时
3225
3397
  另一张托管车道 `settings.hooks.managed`,排在前面(7.146.0 起;此前恒一张)。`wiring_manifest.mods[]` 里看得到它们;审批卡 `allowRuleShadowed` 的 `hook` 成员、问因项、钩子失败时模型读到的拒句都点名作答 / 失败的那一张。本部署自己另挂两张只观察的注册:恒挂的拒绝计量
3226
3398
  `server.permission-denied-meter`,`TOOL_TRACE=true` 时的 `server.tool-trace`(品牌部署另有一张提示装配站上的 `server.brand-identity`,只答品牌行 overlay,不在工具门上)。观察者的判词不采纳;本服务自铸的注册在工具门(`classic.*` 站)上没有链成员(品牌行那一张在提示装配站 `prompt.compose` 上是链成员,不碰工具门;运维声明的 mod 包可以有,见 9.5c-quater 末条)。
3227
- - 每个事件的语义与此前相同:`Stop` 条目在主任务结束与委派子代结束时都跑;`PermissionDenied` / `SubagentStart` / `SubagentStop` / 两个 model-switch 事件属壳,服务端不跑。
3399
+ - `Stop` 条目只在主任务结束时跑(载荷 `hook_event_name: "Stop"`);委派子代结束不跑任何 `Stop` 条目 —— 子代结束是 `SubagentStop`,与 `PermissionDenied` / `SubagentStart` / 两个 model-switch 事件同属壳,服务端不跑
3400
+ (7.147.x 及以前:`Stop` 条目在委派子代结束时也跑、载荷照写 `Stop`)。
3401
+ - `once` 条目在一次请求里成功一次之后就不再跑:跑完、给出了裁决、而且这次事件的钩子判决不是拒 / 拦,才算用掉(`async` / `asyncRewake` 交出即用掉);
3402
+ 失败、这次事件被拒 / 拦(`PreToolUse` 的退出 2 / `block` / `deny` / `continue:false`;`UserPromptSubmit` / `PreCompact` 拦下;`Stop` 推回 —— 同一次事件里跑过的 `once` 条目都不算)、
3403
+ 判在截断标记上的判词(放行不作数)、没执行的那一次不算,下一次事件照跑;`Post*` 的退出 2 / `block` 是反馈,照常算用掉。成功之前它没被用掉,所以同一请求里并发的两次事件可能各跑一次、各成功一次;
3404
+ 托管相改写输入而整相重判时,同一次事件里它在两遍里各跑一次(只读重判那一遍的结局)。7.147.x 及以前:起跑之前就算用掉,失败或拦下过一次的 `once` 守卫在这次请求里不再跑。
3228
3405
  - **`HOOKS_TIMEOUT_MS`** = 每张车道注册的 `budgetMs`(两张各一份同值;一个事件最坏 2 倍)。引擎对它的定义是 "Each handler's own time" —— 每个 handler 自己的时间(一个事件跑一遍这条车道的钩子条目;
3229
3406
  引擎的钟,handler 等 `next` / `$` 调用的那段暂停),**不是**钩子子进程的墙钟。**未设 = 600000 ms**(= 单条命令超时的上限;引擎的钟在 handler 起跑时就开始走,
3230
3407
  命令的 `timeout` 钟要等 shell 解析和起进程之后,所以 `timeout` 配到 600 s 的命令跑满时,预算会先到);`0` 按字面(零预算)。超预算:`PreToolUse` 上该调用被拒(fail-closed,拒句点名 `extension "settings.hooks"`)、
3231
3408
  `UserPromptSubmit` 上该提交被拒,其余事件跳过这一轮这条车道的钩子。托管车道是外层(先等非托管答完,等的那段引擎停它的表):非托管条目吃光的只是非托管那张的预算,
3232
3409
  其余事件上托管钩子照跑;托管车道自己的预算用尽 ⇒ 撞上的托管条目发 `hook_notice` / `hook_managed_unrun{reason:"budget_exhausted"}`(`detail` 点名引擎预算;每个事件恰一帧,任务取消不记)。
3233
- `Stop` / `SubagentStop` 上每条车道的 `continue:false` 只撤本车道的推回,别的车道与部署座链成员的推回照收(两车道意见相左 ⇒ 这一轮不收场);
3410
+ `Stop` 上每条车道的 `continue:false` 只撤本车道的推回,别的车道与部署座链成员的推回照收(两车道意见相左 ⇒ 这一轮不收场);
3234
3411
  观察站(`PostCompact` / `StopFailure`)上托管车道先跑、非托管后跑。
3235
3412
  子进程自己的墙钟另有两只,照旧:单条命令的 `timeout`(上限 600 s)与每条车道一个事件 120 s 的总墙钟(到点不再起下一条)。
3236
- - **预算到、或任务被取消 ⇒ 这个 handler 里在跑的命令钩子被叫停**(7.133.0 起;此前它要跑到自己的 `timeout` 才停,取消之后还可能接着写文件):进程组先收 SIGTERM,
3237
- 1 秒到点组长还在就补一发组 SIGKILL(组长在宽限内退了就不再发 —— 退出的进程号可能已被回收;代价:组长一退,组里屏蔽 SIGTERM 且不攥管道的后代不再被追杀),
3238
- 进程组长退出才算收走;这个 handler 里还没起的后续条目不再起。结局不变(上一条的 fail-closed 拒 / 跳过),运维日志多一行
3239
- `hook_command_killed`(`event` / `type` / `killed: true` / `signal`:`SIGTERM` = 组长在宽限内就退了,`SIGKILL` = 宽限到点组长还在、升级过)。钩子确实要跑得更久:调大 `HOOKS_TIMEOUT_MS`
3413
+ - **预算到、或任务被取消 ⇒ 这个 handler 里在跑的命令钩子被叫停**(7.133.0 起;此前它要跑到自己的 `timeout` 才停,取消之后还可能接着写文件):进程树先收 SIGTERM,
3414
+ 1 秒到点树里还有进程(组长没退,或组长退了而组里还有成员)就补 SIGKILL,进程组长退出才算收走;树里一个进程都没有就不再发信号(退出的进程号可能已被回收);
3415
+ 这个 handler 里还没起的后续条目不再起。结局不变(上一条的 fail-closed 拒 / 跳过),运维日志多一行
3416
+ `hook_command_killed`(`event` / `type` / `killed: true` / `signal`:`SIGTERM` = 组长在宽限内就退了,`SIGKILL` = 宽限到点树里还有进程、升级过)。钩子确实要跑得更久:调大 `HOOKS_TIMEOUT_MS`
3240
3417
  (与条目自己的 `timeout`);要在被叫停时收尾,在命令里 `trap … TERM` 快速落盘(宽限 1 秒,不可配)。
3418
+ - **命令钩子跑完 / 超时 / 被叫停之后,它的整棵进程树被清**(7.148.0 起;三条结局一个,同步条目与 `async` / `asyncRewake` 条目同一把梯子 —— 叫停路只有同步条目,`async` 条目照旧 fire-and-forget、不随叫停信号收):树 = 钩子进程所在的进程组 ∪ 它往下的后代 ——
3419
+ 脱离了进程组的后代(自立会话,或钩子壳自己起的新会话)只要它的父进程在被收走那一拍还活着,也被收。钩子自己跑完(组长退出、管道关闭)之后,树里还留着后台成员 ⇒ 整组 SIGKILL(没有后台成员的钩子零信号;管道里的输出照旧等齐、不被截)。
3420
+ 超时直接发 SIGKILL(不发 SIGTERM),立即返回(不等孙进程放开管道,结局仍是非阻断故障);1 秒后复核:树里已空 ⇒ info 一行 `hook_command_tree_swept`(`event` / `type: "command"` / `end: "exit"` / `"timeout"` / `"halt"` / `shots: 1` / `clean: true`);
3421
+ 还有进程 ⇒ 补一枪并同一事件 warn(`shots: 2` / `clean: false`)。超时与叫停的升级先后到点也只有一轮 SIGKILL、一行清扫日志。此前超时只发一枪组 SIGKILL 就放手、跑完之后不清留在同组的后台进程。
3422
+ 射程如实:钩子壳在被清扫**之前**就已退出、而后代又在新会话里的形(后代被过继给 init、自成一组),与叫停时 SIGTERM 之后组长退了、屏蔽 SIGTERM 的自立会话后代,没有任何按进程号 / 进程组 / 父链的收法够得着,仍活着,清扫行读的是「可见的树」。
3423
+ 需要长命后台进程的活不属于钩子(含钩子正常退出后留下的后台进程)—— 放到钩子之外起。
3241
3424
 
3242
3425
  ### 9.5c-ter `settings.hooks` 的键:写了就要被读,否则整份新提交 400(7.135.0 起)
3243
3426
 
3244
3427
  - 一只匹配组 / 一个条目上出现 hooks 读者不读的键 ⇒ 新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)`400 request.body_shape`,体里 `unknownKeys` 逐个点名全坐标
3245
- (如 `settings.hooks.Stop.0.hooks.0.onFailure`),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3428
+ (方括号形,如 `settings.hooks.Stop[0].hooks[0].onFailure`,与该面拒体 `field` 同一种写法;7.148.0 起,此前是点分数字段),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3246
3429
  command 条目上是契约字段(含 `args` / `onFailure`)加 `plugin`,http 条目上是契约字段(含 `onFailure`),其余条目类型上是各自的契约字段
3247
3430
  (`plugin` / `args` 写在 http / prompt / agent 条目上、`onFailure` 写在 prompt / agent 条目上同样被拒)。每一型的契约字段读能力位 `settingsContract.hookEntryFields`。
3248
3431
  - 常见撞上的形:写错条目类型的键(`plugin` / `args` 写在 http / prompt / agent 上、`onFailure` 写在 prompt / agent 上、`continueOnBlock` 写在 prompt 之外、
@@ -3265,16 +3448,12 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3265
3448
  **`allow` 不看相**:放行只看「判词是 `allow` ∧ 判词可信 ∧ 它判过的就是最终输入」,出口对象不带车道 —— 同一条钩子、同一个判词、同一份输入,自报 `managedPolicy` 拿不到任何额外放行(任务座之下有部署座链成员的站上同样如此 —— 见本节末条)。
3266
3449
  所以调用方给自己的钩子写 `source: "managedPolicy"` 不是抬权口,而是「请按失效关闭跑这一组、并让它最后判」的开关;不要这层严格,去掉 `source` 即可(缺席 = 非托管)。
3267
3450
  过渡(7.146.0 起,至引擎为钩子提供「请求收场」席位止):同一请求同时带托管与非托管的 Stop 钩组、且两车道意见相左(一边 `continue:false`、一边 `block`)时,`continue:false` 只撤本车道的推回,另一车道的 `block` 作数 —— 这是托管标签在 Stop 上会改变的第三件事。目标形 = 任一 `continue:false` 即请求钩子整体无意见(7.145.0 形,与 CC 一致),席位到货当版恢复并删本句。
3268
- - **任务座之下有部署座链成员的站上,托管条目只许收紧**(7.146.0 起):运维声明的 mod 包(`MODS_ROOT` / `MODS_PACKAGES`)在 `classic.PreToolUse` 上以 `append` 档(排在请求钩子所在的任务座之后)挂了链成员
3269
- (观察者不算;读引擎的准入记录)⇒ 托管车道照旧最后判,但那一站上只许收紧 —— 律是**托管答案不比下方(运维 handler 与常规门)宽**:否决 / 问(含托管条目没跑成折的问)/ 上下文照收;托管的 `allow` ⇒ 这次调用拒(与不带标签的 `allow` 被引擎座信任律拒同一个结局,不落到常规门);
3270
- 托管的 `updatedInput`(带 `ask` 的也算)给不进运维 handler(它只判过改写之前的那一份)⇒ 这次调用拒(失效关闭);运维 handler 要真人批的问,托管作答之后照旧要真人批。
3271
- 否则自报的托管组会等运维 handler 判完原输入、再在它之上放行或改写 —— 改写后的输入运维 handler 从没见过。
3272
- 运维 handler 在这一次调用的链上时,与不带标签的同一条钩子逐员比:放行 / 改写 / 否决同一个结局(与 7.145.x 同);问与只给上下文两员与不带标签不同 —— 不带标签的这两种答复内层挂、跳过下方没判过的运维 handler,被引擎座信任律拒(7.145.x 上带标签的同形同样被拒);
3273
- 带标签的在运维 handler 判过之后作数:问照问(要人批),只给上下文照收、调用落到常规门(与不挂这条钩子同一个结局)。两员都不越过运维 handler 与常规门,所以覆盖站上的标签换不来越过下方的放行。
3274
- 运维 mod 带匹配器而这一次调用不中时(见下一句),带标签的放行 / 改写照样拒、不带标签的作数(链上没有运维 handler);问与只给上下文两形下两者同结局 —— 只多收紧。
3275
- 卡面:托管条目在下方的问(运维 handler 或同座非托管车道;不论下方的问带哪个成因词)之上也问 ⇒ 问句先列下方那一句、再列托管那一句(`… (a managed PreToolUse hook also asks: …)`);卡上的注册名与问因项(`askReasons`)由引擎按作答的那一张注册铸,托管车道作答时只点名 `settings.hooks.managed`(已知限制)。
3276
- 挂法不随站点变:同座的非托管车道与运维 handler 恒先判、否决恒跑 —— 运维 mod 只管某些工具(带匹配器)时,别的工具的调用上同样如此,那些调用上托管条目也按这一条收紧(准入记录不带匹配器;只多收紧,不跳过否决)。
3277
- 不带托管标签的组照旧:运维 handler 在这一次调用的链上时,它们的非否决答复被引擎的座信任律拒。
3451
+ - **任务座之下有部署座链成员的站上,请求钩子只许收紧**(托管车道 7.146.0 起;7.148.0 起两条车道同一条,折叠不放宽这一面):运维声明的 mod 包(`MODS_ROOT` / `MODS_PACKAGES`)在 `classic.PreToolUse` 上以 `append` 档(排在请求钩子所在的任务座之后)挂了链成员
3452
+ (观察者不算;读引擎的准入记录)⇒ 请求钩子(托管与不带标签的组,不看标签)照旧最后判,但那一站上只许收紧 —— 律是**请求钩子的答案不比下方(运维 handler 与常规门)宽**:否决 / 问(含托管条目没跑成折的问)/ 上下文照收(运维 handler 已判过同一份输入:问要人批,只给上下文之后调用落到常规门、与不挂这条钩子同一个结局);`allow` ⇒ 这次调用拒(不落到常规门);
3453
+ `updatedInput`(带 `ask` 的也算)给不进运维 handler(它只判过改写之前的那一份)⇒ 这次调用拒(失效关闭);运维 handler 要真人批的问,请求钩子作答之后照旧要真人批。
3454
+ 否则请求钩子会等运维 handler 判完原输入、再在它之上放行或改写 —— 改写后的输入运维 handler 从没见过。7.147.x 及以前不带标签的组由引擎的座位信任律拒同一类放行(结局相同);那时它的问与只给上下文两形同样被拒(内层挂、跳过下方没判过的运维 handler),7.148.0 起与带托管标签的同读。折叠只合并判决的读法,放行面不变。托管标签是自报的,只改位置与失败方向,不是这条规则的开关。
3455
+ 卡面:请求钩子在下方的问(运维 handler 或同座另一条车道;不论下方的问带哪个成因词)之上也问 ⇒ 问句先列下方那一句、再列钩子那一句(托管车道 `… (a managed PreToolUse hook also asks: …)`,不带标签的组 `… (a PreToolUse hook also asks: …)`);卡上的注册名与问因项(`askReasons`)由引擎按作答的那一张注册铸(已知限制)。
3456
+ 挂法不随站点变:同座的非托管车道与运维 handler 恒先判、否决恒跑 —— 运维 mod 只管某些工具(带匹配器)时,别的工具的调用上同样如此,那些调用上请求钩子也按这一条收紧(准入记录不带匹配器;只多收紧,不跳过否决)。
3278
3457
  只挂观察者的部署(本服务自铸的拒绝计量 / 工具追踪)、链成员只在任务座之上(`prepend` 档,或同档 `user` —— 同档里部署座在前)的部署不收紧:那里运维 handler 已先判、并在答回来的结果上再判。
3279
3458
 
3280
3459
  ### 9.5c-quinquies `onFailure`:钩子自己坏了时,放行还是拦下(7.147.0 起)
@@ -3284,7 +3463,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3284
3463
  上工具已经跑过,拦不了,那句话作为反馈交给模型。拦下的理由是一句 `<事件> hook did not reach a verdict (<成因>); blocking because onFailure is "block"`,成因词与故障帧的 `reason` 相同。
3285
3464
  - **「坏了」怎么算**(本服务比参照实现宽,失效关闭):起不来、超时、退出码非 0 非 2(http:非 2xx)、输出读不成判词、`PreToolUse` 的 `permissionDecision` 不是 allow / deny / ask 之一;
3286
3465
  以及本服务自己的门让它没跑成 —— URL 名单拦下、shell 形写了 powershell、`if` 条件评不出、组的 matcher 编不过、这个事件的墙钟用完(墙钟用完之后同一车道里排在后面、被选中的 `block` 条目同样拦下,不看它排第几)。
3287
- 不算坏:`once` 已经跑过一次、`if` 不匹配、matcher 没选中(本就不该跑)。
3466
+ 不算坏:`once` 已经消耗(这次请求里它成功过一次)、`if` 不匹配、matcher 没选中(本就不该跑)。
3288
3467
  - **不生效**:`async` / `asyncRewake` 的条目(后台跑,本就不拦);`Stop` 事件(拦停止会让该停的停不下);观察站 `StopFailure` / `PostCompact`(那里没有可拦的动作)。
3289
3468
  - 运维要看懂一次拦下:故障帧 / 运维日志照旧点名是哪一条、什么成因(`hook_non_blocking_failure` 帧的名字是历史名,`block` 条目的那一次随后就被拦下);改条目、改 URL 名单,或把这一条改回 `continue`。
3290
3469
  - 停驻任务:升级前存下的请求里写了 `onFailure: "block"` 的守卫,续跑时开始按 block 执行(7.146.x 及更早这个键被读者剥掉);存量里写的是两词之外的值 ⇒ 这条车道采纳请求体 hooks(单用户车道)时**拒续跑**
@@ -3352,6 +3531,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3352
3531
  - **A2A 座**并进 `a2aPeers` 的条目判官表(7.136.0 起,与 `mcpServers` 同一副拒绝骨架,见下):`url` / `cardUrl` 先判是 http(s) 串、再判凭据;`a2aPeers` 超过 32 条(挂载上限)整份 `400 request.field_invalid`(`field:"a2aPeers"` · `reason:"over_cap"` · `count` · `cap`),
3353
3532
  判官要么看过每一条对端的 `url` / `cardUrl`,要么这份体不收。7.134.x 及以前超过 32 条是 200、第 33 条起丢弃(运维 warn 点名条数)。**搜索后端座**:userinfo 形此前已是 400(句子逐字不变,本版起体另带 `field` / `reason`),凭据名参数是本版新拒的。
3354
3533
  - **图片座**(7.136.0 起):`images[i]` 的 `{ url }` 臂过同一只判官;内联臂 `{ data, mimeType }` 不落 URL、不归本座。个数帽(20,`413 request.payload_too_large`)与内联字节帽排在它前面。
3534
+ - **`images` 的条目座**(7.148.0 起,与 `mcpServers` · `a2aPeers` 同一副拒绝骨架):新提交上 `images` 不是数组 / 某一条不是对象 / 一条的键值不是串 ⇒ `400 request.field_invalid`(体带 `field`:`images` · `images[i]` · `images[i].<键>`,条目级另带 `index`);一条图片是两臂之一(`url` 键在场 = URL 臂 `{ url }`,否则内联字节臂 `{ data, mimeType }`),本臂以外的键(含另一臂的键)⇒ `400 request.body_shape` + `unknownKeys`。字节的准入(类型 / 签名 / 5 MiB 帽)与 URL 的 https / 取图判仍是引擎的 `config.image_invalid`;个数帽与内联字节帽的 `413` 在形判之前;续跑重放存量体不判。`images:[null]` 此前是 `500`。**谁受伤**:`images` 里带多余键或两臂并带的提交。**补偿**:每张图选一臂、删掉多余键。
3355
3535
  **谁受伤**:用带签名的临时地址传图的调用方 —— `?sig=` · `?signature=` · `?token=` · `?key=` · `#access_token=` 这类名字在表里的参数(Azure SAS 的 `sig`、S3 预签名的 `X-Amz-Signature` / `X-Amz-Credential`、OSS / COS 的 `Signature`、带 `?key=` 的图床),7.135.0 及以前照收、取图时把令牌发给图床,本版起 400。
3356
3536
  **补偿**:图片字节读下来改走内联 `{ data, mimeType }`(内联图有自己的字节帽,不落 URL);或换一个不靠这些参数名鉴权的地址(路径上的签名、公开地址)。
3357
3537
  - **不判**:续跑重放存量请求(拒会把升级前停驻的任务永久卡死;已落盘的字节不因拒而消失 —— 介意的部署把这些任务结掉后轮换那几枚凭据);解析不了的 url(拨号 / 取图期失败);部署面(中心 / `config.d` / 部署 env)声明的地址 —— 那是运维的配置,不进任何请求的停驻上下文。
@@ -3362,7 +3542,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3362
3542
  与 9.5e(`mcpServers`)**同一副拒绝骨架、同一个拒形**。条目的受理键 = `name` · `url` · `cardUrl` · `headers` · `principalHeader` · `allowSkills` · `toolAxes`(与引擎的 A2A 对端声明逐键对应)。新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)上:
3363
3543
  - `a2aPeers` 在场却不是数组 ⇒ `400 request.field_invalid`(`a2aPeers is a string of 4 characters, not an array of A2A peer declarations. …`);超过 32 条 ⇒ `400 request.field_invalid` + `field:"a2aPeers"` · `reason:"over_cap"` · `count` · `cap`;某一条不是对象 ⇒ `400 request.field_invalid`,点名 `a2aPeers[i]`。缺席 / `null` 是「没声明」,空数组受理。
3364
3544
  - 条目上词表外的键 ⇒ `400 request.body_shape`,体带 `unknownKeys`(全路径形,如 `a2aPeers[0].allowSkill`)。
3365
- - 键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `a2aPeers[1].url (peer "billing") is not a usable declaration — must be a string starting with "http://" or "https://" (never file://, never a bare host) — got a string of 15 characters. …`:点名 `a2aPeers[i].<键路径>`、peer 名与该键判官的原因句,**整份请求不收**,原因句不回显值。判官就是读器用的那一只:`name`(非空串、线长界、无首尾空白)· `url`(必填,http(s) 串)· `cardUrl`(可缺席,在场必须是 http(s) 串)· `headers`(「名 → 串」的纯数据记录,无 `__proto__` 名成员)· `principalHeader`(串)· `allowSkills`(串数组)· `toolAxes`(引擎的判官,A2A 一面,请求侧不收 `boundaryFlow`)。`url` / `cardUrl` 带凭据另见 9.5g(体另带 `field` / `reason`)。
3545
+ - 键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `a2aPeers[1].url (peer "billing") is not a usable declaration — must be a string starting with "http://" or "https://" (never file://, never a bare host) — got a string of 15 characters. …`:点名 `a2aPeers[i].<键路径>`、peer 名与该键判官的原因句,**整份请求不收**,原因句不回显值。判官就是读器用的那一只:`name`(非空串、线长界、无首尾空白)· `url`(必填,http(s) 串)· `cardUrl`(可缺席,在场必须是 http(s) 串)· `headers`(「名 → 串」的纯数据记录,无 `__proto__` 名成员)· `principalHeader`(串)· `allowSkills`(串数组)· `toolAxes`(引擎的判官,A2A 一面,请求侧不收 `boundaryFlow`)。`url` / `cardUrl` 带凭据另见 9.5g(体另带 `field` / `reason`)。 机读位(7.148.0 起):这一族 `request.field_invalid` 拒的体都带 `field`(体内坐标,与句子点名的同一个),条目级另带 `index`(`a2aPeers` 里的下标);`mcpServers` 与 `images` 同形。7.147.x 及以前只有句子。
3366
3546
  - 判序:先列表、后条目;条目里先词表外的键、后键值(`name` → `url` → `cardUrl` → `headers` → `principalHeader` → `allowSkills` → `toolAxes`),第一处坏形即答。纯值域、与租户车道无关(多租户部署整只 `a2aPeers` 本会被忽略,告诉调用方「这样写不行」只多不少)。
3367
3547
  - **耐久 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`。
3368
3548
  - 7.135.0 及以前:这些形全部 200 —— 坏形的对端悄悄不挂(只在运维 warn 里点名)、词表外的键悄悄不读(`allowSkills` 拼成 `allowSkill` 的对端照样挂上远端卡声明的全部技能)。升级后这些请求变 400:改对那个键(或删掉它)、把列表改成数组、删掉第 33 条起的条目。
@@ -3428,6 +3608,61 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3428
3608
  - **已知限制:对端拨号面今天没有主机底线**:启用的对端按 `url` / `cardUrl` 取卡、按卡里写的服务端点发送,**不过任何部署主机底线** —— 本服务今天不给引擎接它(`RunnerDeps.hostFloor` 没有写点)。`url` / `cardUrl` 是运维写的,可指向任意主机(含内网与回环);服务端点是对端的卡写的:**不带头的对端**(这次发送不带任何声明的头:没写 `headerRefs`,也不注入主体头)的服务端点可指向任意主机,遇重定向照跟(引擎设跳数上限,没有主机底线);带头的对端另受引擎的端点绑定 —— 服务端点必须落在声明的 `url` / `cardUrl` 所在的源上,且不跟重定向。取卡发生在对端被用到时:每次任务准备、会话 A2A 面板(`GET /v1/sessions/:id/a2a`)缓存缺失 / 过期(5 秒)时;起服本身不取卡。**多租户部署同样挂**:多租户闸只关请求体自带的 `a2aPeers`(9.5h),部署基线照挂。
3429
3609
  **补偿**:只写你信任的对端 —— 不带头的对端,它的卡能让引擎去敲它写下的任何地址;起服前自审 `config.d/a2a.json`;多租户 / 公共部署不要接不受信对端。出路 = 部署主机底线接线(尚未到货),到货后本条改写。
3430
3610
 
3611
+ ### 9.5i-ter 单机挂载外置 MCP:`config.d/mcp.json`(`CONFIG_PROVIDER=local`;7.148.0 起成文)
3612
+
3613
+ 部署级的 MCP 服务器只有两条声明通道:本地配置源的 `config.d/mcp.json`(本节),与配置中心发布的 `mcp` 域;**没有 env 通道**(本服务没有声明 MCP 服务器的环境变量;请求体自带的 `mcpServers` 是另一条路,见下面「与请求体 `mcpServers` 的关系」)。两条腿过同一只读口、读成同一个声明、由同一个铸点交给引擎,所以单机部署者在这个文件里写的,与中心 `mcp` 域的条目逐键同义。本节只把此前散在各处(目录信任、`toolAxes`、身份头、凭据件)的读法写在一处,不改任何行为。
3614
+
3615
+ ```json
3616
+ {
3617
+ "servers": [
3618
+ {
3619
+ "name": "crm",
3620
+ "transport": { "kind": "stdio", "command": "crm-mcp", "args": ["--readonly"], "envRefs": { "CRM_TOKEN": "CRM_MCP_TOKEN" } },
3621
+ "allowTools": ["search_accounts"]
3622
+ },
3623
+ {
3624
+ "name": "search",
3625
+ "transport": { "kind": "http", "url": "https://mcp.example.com/mcp", "headerRefs": { "Authorization": "SEARCH_MCP_AUTH" } }
3626
+ }
3627
+ ]
3628
+ }
3629
+ ```
3630
+
3631
+ - **何时读、读哪里**:只在 `CONFIG_PROVIDER=local` 下读(`run-local` 没写词时自己写 `local`);不写词且没配中心 = 纯 env,HTTP 服务不读 `config.d`。配置根只有一条链 `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,文件是 `<配置根>/config.d/mcp.json`。
3632
+ 文件**缺席** = 没声明;**存在却为空 / 只含空白、或不是 JSON 对象** ⇒ 这份目录的候选整份被拒(起服落回 env 底、刷新拍保持上一代配置;拒句点名那个文件的路径与出路)—— 写到一半的空文件不会被当成「没声明」。
3633
+ - **属主与模式**:文件与它上面每一级目录过的是上文「本地配置源的目录信任」那一只判官(属主只能是本服务的用户 / root / `CONFIG_LOCAL_OWNER_UID`,不得组 / 他人可写,常规文件 ≤ 16 MiB;判据不在这里复写)。拒 = 起服拒启(`[config.dir_untrusted]`);起服之后才变坏 = 那一拍新字节不读、已应用的配置不动。Windows 上属主与模式位判不了,只判结构并记一次 fail-open 告警(上文同一段)。
3634
+ stdio 的 `command` 以本服务的用户起子进程,所以这份文件的写权限就是「让本服务起命令」的权限:别把配置根放在智能体的工具可写的位置。
3635
+ - **条目形**(`servers[]`,与中心 `mcp` 域同形,键名逐字):
3636
+ `name`(必填)字母或数字开头,其后字母 / 数字 / `.` `_` `-`;它是工具命名空间 `mcp__<名>__<工具>` 的一段,归一后落在同一前缀的两个名字(`my.srv` / `my_srv`)⇒ 任务 `409 config.mcp_server_name_collision`;同名两条 ⇒ 整份 `mcp` 域读坏(候选整拒)。
3637
+ `transport.kind` 只有 `stdio` / `http`(与 `GET /v1/capabilities` 的 `mcpTransportKinds` 同两个词;写 `sse` 一类的词 ⇒ 这一条被丢)。stdio:`command`(必填)· `args`(字符串数组)· `envRefs`。http:`url`(必填,`http://` 或 `https://`)· `headerRefs` · `principalHeader` / `assertionHeader`(二选一,见 9.5d)。
3638
+ 其余键:`allowTools`(缺席或空表 = 这台的工具全挂;写了 = 只挂名单里的原始工具名,名单里一个元素写坏 ⇒ 整条被丢,不截短)· `scenarios`(缺省 `[]` = 所有场景)· `enabled`(缺省 `true`,`false` 不挂)· `elicitation`(缺省 `false`)·
3639
+ `toolAxes`(见上文「MCP 条目的工具风险轴」)· `contentOrigin` · `source` · `toolFaces` · `sessionMetaKey` · `startupTimeoutMs`(与请求体条目的同名键读同一只判官,见 9.5e)· `notes`(运维备注,不进规格)。
3640
+ `rosters` 在单机上不筛条目(本地腿没有 worker 身份,按全局取),写不写都挂;`authForm` / `entryKind` 是连接器的声明,本服务只承载、不读。词表外的键(拼错)⇒ 这一条被丢,不会「剥掉那个键照样挂」。
3641
+ - **逐条容错**:一条写坏 ⇒ 只丢那一条(一行 warn `config_entry_dropped`,点名条目的坐标 `servers.<下标>` 与闭集原因词、字段诊断;日志里认条目靠下标,不是名字),兄弟照挂;**一条都没剩**、或坏在整份(同名两条、域顶层拼错键、坏文件)⇒ 整份候选被拒(`config_candidate_rejected`),规则见上文「逐条容错」。
3642
+ `contentOrigin` 出词表也是整条被丢;`toolAxes` / `toolFaces` / `sessionMetaKey` 写坏则是这一台起服时不挂(兄弟照挂,warn `web_admin_mcp_tool_axes_invalid` / `web_admin_mcp_tool_faces_invalid` / `web_admin_mcp_session_meta_key_invalid` 点名)。停用的条目和起服时被跳过的条目照样占着名字,请求体里的同名条目补不了位。
3643
+ **已知限制:被 `config_entry_dropped` 丢掉的条目不进装配,名字也不再被部署占着** —— 单用户 ∧ `mcp` 未锁 ∧ 合规档位允许的部署上,请求体自带的同名条目补得上位。**补偿**:起服后读一遍 `config_entry_dropped`,把丢掉的那一条改对。
3644
+ - **`envRefs` / `headerRefs` 的边界:文件只存名,值只来自本进程的环境**。
3645
+ `envRefs` = 「子进程里的变量名 → 本服务环境里存着值的变量名」,`headerRefs` = 「HTTP 头名 → 本服务环境里存着值的变量名」。两边的变量名都是 `^[A-Z_][A-Z0-9_]*$` 形(大写字母 / 数字 / 下划线)、且不是 AWS 访问钥 id 形(`AKIA…`);头名是 HTTP token、头名本身不得像凭据。
3646
+ 写成值、或写在错的位置 ⇒ **这一条整条不进**(`config_entry_dropped`,兄弟照挂):`command` / `args` 里的秘密形字面量(`sk-…` · `ghp_…` · `Bearer …` · JWT · 私钥头 …)· `url` 带 userinfo、fragment 或秘密形的路径 / query 值 · 引用表里小写或不是变量名形的项 ·
3647
+ stdio 传输里的明文 `env`、http 传输里的明文 `headers`(这两个键只属于请求体,文件里是词表外的键)。
3648
+ 值的去向:本服务**起服时读一次**自己的进程环境(要换值就改环境并重启),放进对端座的登记表(9.5i);本服务的起服路径不读 `<配置根>/.env` 一类的存值文件 —— 变量必须已经在本进程的环境里(起服的 shell、服务管理器或编排器放进去的)。
3649
+ 引用的变量**未设** ⇒ 这一条整条不挂(一行 warn `web_admin_mcp_env_missing`,点名 `server` / `kind`(`env` 或 `header`)/ 键 / 变量名,不带值),兄弟照挂;缺席只判「未设」,本服务不另判空串。
3650
+ 值只在本进程内存的登记表里:不写回文件、不进交给引擎的规格(规格里只有一只不透明引用)、不进停驻任务存下的提交体(部署声明的件不经提交体)。stdio 子进程只继承 MCP SDK 的少数默认变量(`HOME` · `LOGNAME` · `PATH` · `SHELL` · `TERM` · `USER`)与 `envRefs` 点名的那几只,服务进程环境里其余的变量它拿不到(9.5i「扣留次序」),要给它哪一只就在 `envRefs` 里点名;http 的头值就是那只变量的值,本服务不加 `Bearer ` 之类的前缀。
3651
+ **已知限制:文件腿不拦 `url` 的 query 里按名字像凭据的明文参数**(`?api_key=…` · `?token=…`):文件腿只拦秘密形的值、userinfo 与 fragment,请求体新提交那道「参数名在凭据名表里」的判据不在这条腿上(与 9.5g「不判」一条同向 —— 部署面声明的地址是运维的配置)。**补偿**:凭据一律走 `headerRefs`,起服前自审文件里每一条 `url`。
3652
+ - **单机匿名主体**:单机形(`REQUIRE_PRINCIPAL` 不是 `true`)接受没有主体的调用方 —— 不带 principal 头、也没有验过的 SSO 或直连门身份(那两种凭据给出的身份与头无关,有就有主体)—— 这样的 run 没有主体。对挂载的影响:① 部署声明的服务器不按主体分,挂给每一次 run(`scenarios` 空 = 所有场景),有没有主体都一样;
3653
+ ② http 条目的 `principalHeader` 只在 run 有主体时才发 —— **匿名 run 不发这个头**,对端要把「没有这个头」读成默认拒绝 / 公开,不能读成管理员(stdio 条目没有这个头);
3654
+ ③ 本服务不为匿名主体另设 MCP 写拒。要收窄,在条目上用 `allowTools`(只挂白名单工具)与 `toolAxes`(按工具声明效应轴;没声明的工具引擎按「写」算,审批门与计划模式围栏据此判;单用户的审批基线见 9.4c)。多租户(`REQUIRE_PRINCIPAL=true`)部署同样挂这份文件的声明,不按租户筛。
3655
+ - **与请求体 `mcpServers` 的关系**:两者进同一个字段 —— 部署声明是基线,请求体条目叠在上面。请求体只在单用户(调用方就是这台 worker 的超管)∧ `mcp` 未锁 ∧ 合规档位不禁 `mcp_servers` 时才兑现(`GET /v1/capabilities` 的 `mcpInjection` 是这条合取的值);
3656
+ 多租户 ⇒ 请求体 `mcpServers` 整份忽略(运维 warn `mcp_injection_dropped`),部署声明照挂;锁了 `mcp` ⇒ 请求体带 `mcpServers` 同步 `400 config.locked_key`,锁了 `mcp` 而部署真挂上了 MCP 服务器(至少一台启用且装配通过)⇒ 起服拒(见 `LOCKED_CONFIG_KEYS` 一条;全是停用或装配时被跳过的声明不触发)。
3657
+ 同名时部署的赢:请求体里撞名的条目被丢(`mcp_injection_dropped` 点名,用户面通告 `mcp.injection_dropped`)。`principalHeader` 与 `assertionHeader` 两键同设:请求体新提交 `400 request.body_shape`,文件里同设的那一条被丢,拒句同一句(9.5d)。
3658
+ - **生效**:重启生效 —— 启用的条目新增、改运行期字段都要重启(停用的条目不进重启判据,只改 `notes` / `rosters` 也不算);改了之后 `/health` 报 `restartRequired`、`restart.reasons` 含 `mcp`。下一拍(60 秒,或 `POST /v1/admin/config/refresh`)才读到文件;**只从 enabled 集里拿掉条目**(其余条目逐字不变、相对顺序不变)是例外:即时生效、不签重启,见上文「MCP 撤销腿」。
3659
+ `run-local` 每次都是新进程,没有这一说。
3660
+ - **看挂上了没有**:至少挂上一台时,起服一行 info `web_admin_mcp`(`servers` = 真挂上的名字;一台都没挂上就没有这一行);没挂上的原因在 warn 里(`config_entry_dropped` · `web_admin_mcp_env_missing` · `web_admin_mcp_name_unusable` · 上面三条 `toolAxes` / `toolFaces` / `sessionMetaKey` 的 warn);
3661
+ `GET /v1/capabilities/mcp` 现连、列工具(本部署自己的声明,不必先跑 run),`GET /v1/sessions/:id/mcp` 是会话面板。
3662
+ - **连接器**:`@sema-agent/connectors` 的单机形把连接器写成这个文件里的一条 stdio 条目(`command` 是 `sema-connector`,`args` 带它自己的 `--config-dir <配置根的绝对路径>`,恒不带 `envRefs`:连接器自己的凭据由它按那个目录读,不经本服务的环境)。
3663
+ 默认条目集由连接器包的 `seed` 子命令写入,本服务不自铸、不在起服时调用它;写入后按本节的目录信任与 `envRefs` 边界读。用法:先装好要挂的连接器包,再跑一次 `sema-connector seed --config-dir <配置根的绝对路径>`,按连接器文档给每个连接器 `auth`,然后重启本服务(新增条目重启生效)。未授权时连接器停驻、工具列表为空 —— 呈现与步骤以连接器包的文档为准。
3664
+ 本服务对连接器的拒码(如未授权时的 `connector.auth_required`)零特判,按普通的 MCP 工具错误处理:**按拒句文本读,别按这个码做分支**(该码在本服务事件面上的呈现本节不作承诺)。
3665
+
3431
3666
  ### 9.5j 请求的 `settings.env` 不收凭据(7.146.0 起,新提交 400)
3432
3667
 
3433
3668
  **律**:凭据进引擎只经引擎进程启动时的环境,从不经请求。`settings.env` 来自一份模型读得到的设置文件,而本服务把它交给会话的 shell(单用户 host / device 车道原样叠进模型 shell 的环境 ——
@@ -3440,7 +3675,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3440
3675
  - **出路**:把凭据挪进引擎进程的环境 —— 壳起的本机引擎由壳的凭据文件带入(还把凭据写进 `settings.json` 的老壳先升级),服务形部署放进进程环境 / 编排器的 secret 注入。
3441
3676
  要让模型 shell 持一枚令牌(`GH_TOKEN` 一类):放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配请求级 `inheritEnv:"all"`(单用户 host 车道)。
3442
3677
  值形扫描是粗判:一串不是凭据、却被判成秘密形的值同样要挪出 `settings.env`(改名不救值形)。
3443
- - **已知限制**:http 钩子 header 的 `$NAME` 插值只读钩子环境(十二名基础变量 + `settings.env`,9.5c)—— 秘密形的 webhook token 今天在本服务的钩子上**没有替代通道**;不像秘密的 token 照旧经 `settings.env` 给。
3678
+ - **钩子要用的 token**:http 钩子 header 的 `$NAME` 插值只读钩子环境(9.5c)。秘密形的 webhook token 放进引擎进程的环境,部署方在 `HOOK_ENV_ALLOW` 点名、条目 `allowedEnvVars` 列它(9.5c);拒句里的「挪进引擎进程环境」对钩子就是指这一步。不像秘密的值照旧可以经 `settings.env` 给。
3444
3679
  - **续跑**:升级前受理、停驻的任务,存体里带这类条目时,一次性迁移 M38 的窗(本版发布日 + 30 天;窗口见登记表)内续跑把它们从 shell / hooks 的环境里剥掉、任务照续
3445
3680
  (运维 warn `settings_env_credentials_dropped`:`migration` / `sessionId` / `dropped: { name, value_shape }` —— 只计数,不带键名与值);窗关后续跑口答 409 `resume_blocked_by_policy`(`blockedBy: "request.env_credential_refused"`,同一组机读位),出路是取消那条 run 后挪走凭据重提。
3446
3681
 
@@ -3451,7 +3686,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3451
3686
  不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
3452
3687
  备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
3453
3688
  路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及 web-admin 可对**单个
3454
- 目录模型**下发的 per-model `baseUrl` 覆盖(见 `docs/ARCHITECTURE.md` §10「Trace/redaction」)。要让内容
3689
+ 目录模型**下发的 per-model `baseUrl` 覆盖。要让内容
3455
3690
  不出机器,靠**读侧敏感路径门**(`READ_DENY_PATTERNS` 等,见 §「执行 lane 与租户门」附近)并核对**上述
3456
3691
  四条路由每一条**都指向自己控制的基础设施——只把 `MODEL_GATEWAY_BASEURL` 指到私有网关、放着
3457
3692
  `provider:"anthropic"` 或某个目录模型的 `baseUrl` 覆盖不管,那部分流量仍走公网原厂,不靠
@@ -3476,7 +3711,11 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3476
3711
  **升级前已落库的行**:回放读面对 run 行与 `text` / `reasoning` / `done` 行再过一遍脱敏 ⇒ 按本读法遮;`tool_end` 行回放读面不再过(只剥媒体字节)⇒ 升级前写下的 ① ② 两形照旧明文,升级后新写的行照遮。
3477
3712
  **名字判定读契约**(7.147.0 起):「是不是凭据名」整段交契约包的名字判官 —— 整名在凭据名表上,或末段 / 尾串收在强档词上:以 `password` / `passwd` / `pwd` / `secret` / `apikey` 收尾(整段或与前缀粘连)的复合名(`DB_PASSWORD=` / `PGPASSWORD=` / `dbPassword=` / `--db-password=` / compose 的 `POSTGRES_PASSWORD: <值>`)与三条尾串(`SECRET_KEY` / `AWS_SECRET_ACCESS_KEY` 读作 `secret`;`x-api-key` / `OPENAI_API_KEY` 读作 `api_key`;`github_access_token` 读作 `access_token`)都是凭据名,锚强度按它读作的那个表词;只看末尾(`password_hash=` / `secretName:` / `pwd_dir=` / `api_key_count=` / `token_count=` 不是)。名字长度不封顶。四个读者同读:自由文本(属性路径只把最后一段交判官:`spring.datasource.password=`)、JSON 键与 URL 参数名(整段交判官;URL 参数名按收方的解析读:`+` 是空格、百分号解一次 —— `?db.password=` / `?x+password=` 同遮)、诊断面。受理口的 URL 凭据判官读同一只判官,两边对同一个名字同答。所以此前「名字整个落在前一枚已认出凭据的值里」那一形(`ghp_…password=<值>`,令牌的形状把名字吞了进去)也遮:名字是整段,收在强档词上。代价:以强档词收尾、值不是凭据的名字(`show_password=1` / `is_secret=true`)的值也换成记号;单行出门的运维日志里,行尾凭据值之后被剥掉换行而拼上来的下一行首词随值一起遮。**仍不遮的一形**:引号里以 `$` 起头的值(`DB_PASSWORD='$ecret12'`)读作插值,不是字面量 —— 要靠脱敏挡住的凭据别以这一形写出。
3478
3713
 
3479
- **错误文本的出口契约**(归一化视图 / 遮蔽记号闭形)写在源码仓内的助手 wire 契约 `ASSISTANT-WIRE-CONTRACT.md` 里(不随 npm 包出)。
3714
+ **不可见码点的读法**(7.148.0 起):自由文本脱敏把「读者看不见的码点」按两种读法各读一遍、取并集 —— 留着当分隔,或删掉当不存在(夹在凭据中段的这类码点把一枚凭据拆成两半、各半都过短,删掉才拼得回)。删除集是一张表:core 的格式字符(`\p{Cf}`)、契约包的剥除集(C0 / DEL / 双向控制符),以及按 Unicode 属性并上的 `Default_Ignorable_Code_Point`(变体选择符 U+FE00–FE0F / U+E0100–E01EF、蒙古文自由变体选择符 U+180B–180D、谚文填充符 U+3164 / U+FFA0、高棉固有元音 U+17B4 / U+17B5 …)∪ 一般类别 `Mn`(非间距组合符:U+0301 / U+034F 一族)∪ C1 控制符(U+0080–U+009F)。转义写法(`\ufe0f` / `\u0301` / `\u{e0100}`)同一条律。已知边界:带这类码点的文本(组合符写法的越南语 / 泰语 / 阿拉伯语 / 日语浊点、emoji 的变体选择符)多读一遍(没有凭据形的文本逐字节不变);折行 / 换行拆开的令牌与「删掉」之外的拆法(夹进一个看得见的字母)不在这张表里。
3715
+
3716
+ **运维日志字段的扫描窗**(7.148.0 起成文):hook 吐出的话、工具输出预览、标题这类有界日志字段对超长文本只扫头一段 —— 窗 = 出门区(`max(2048, 界 × 4)` 个读法单元,出门字节从这里取)+ 余量(脱敏器导出的最长识别跨度 8274 个读法单元:识别表里最长的有界结构 = 定长栅栏的 SSH2 私钥块,由识别表派生,不是出口座自定的数)。起于出门区、总长不超过余量的有界结构整段落在窗内,与整段脱敏逐字同判;不设长度上界的结构(PEM 栅栏行的标签、JSON 串值、URL userinfo、带引号的强锚值 …)在收尾落到窗外时,窗读法从起点遮到窗尾 —— 同记号、零明文,遮蔽范围只到窗尾(越过窗尾的部分本来不出门)。**已知边界**:总长越过余量的有界结构不保证与整段读法同判;体长越过识别表上界(8192)的 SSH2 块整段读法本来不认,日志字段与整段读法同读。窗只是日志字段的读法,wire 面(终局文本 / 账本 / 帧)走整段脱敏,不受影响。
3717
+
3718
+ **错误文本的出口契约**(归一化视图 / 遮蔽记号闭形)写在服务端的助手 wire 契约里(内部记录,不随 npm 包出)。
3480
3719
 
3481
3720
  ### 9.7 bake-runner(构建宿主守护进程)的 env(7.103.0 起成文)
3482
3721
 
@@ -3487,7 +3726,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3487
3726
  | env | 缺省 | 说明 |
3488
3727
  |---|---|---|
3489
3728
  | `IMAGE_API_BASE` | 必填 | image-api 根(尾斜杠去掉) |
3490
- | `BAKE_RUNNER_TOKEN` | 必填 | claim / heartbeat 的运维主体 bearer。不进日志,**7.103.0 起也不进任何子进程** |
3729
+ | `BAKE_RUNNER_TOKEN` | 必填 | claim / heartbeat 的运维主体 bearer。不进日志;7.103.0 起缺省不进子进程(形状剥),但它不在 runner 的自有凭据声明里 —— 别在 `BAKE_ENV_ALLOW` 里点它,也别把同一个值换个名放进 runner 的环境(那两形照交给配方脚本;已知缺口) |
3491
3730
  | `BAKE_RUNNER_ID` | `bake-runner-<HOSTNAME>` | 认领时报给 image-api 的 runner 名 |
3492
3731
  | `RECIPE_REF` | `HEAD` | 配方 checkout 的 ref(`HEAD` = 用现成 checkout,不 fetch) |
3493
3732
  | `RECIPE_DIR` | `/opt/recipes` | 配方仓 checkout 根(build.sh 的 cwd) |
@@ -3506,12 +3745,12 @@ docker 推送的鉴权走构建宿主上的 docker 凭据存储(`docker login`),
3506
3745
  🔴 **git 的 env 配置族(`GIT_CONFIG_COUNT` / `GIT_CONFIG_PARAMETERS` / `GIT_CONFIG_KEY_<数字>` / `GIT_CONFIG_VALUE_<数字>`)与代理变量一族(`http(s)_proxy` /
3507
3746
  `all_proxy` / `no_proxy` / `npm_config_*proxy` …,任意大小写)是豁免族 —— 名与值都不读、原样交给子进程,凭据在内**(7.106.0 起;7.103.x–7.105.0 的逐条扣、
3508
3747
  原位占位 `sema.withheld`、代理 userinfo 扣全部退役)。⇒ 凭据别写进这几族:git 凭据走 credential helper 的配置文件、代理按来源免密。`SSH_AUTH_SOCK` 与
3509
- 每一只 `OTEL_*` 自同一版起扣;配方真要它们 ⇒ `BAKE_ENV_ALLOW` 点名放回。全文见 `docs/DEPLOY-PREREQS.md`「宿主子进程拿到的 env 改成参照实现的剥除形」节。
3748
+ 每一只 `OTEL_*` 自同一版起扣;配方真要它们 ⇒ `BAKE_ENV_ALLOW` 点名放回。
3510
3749
 
3511
3750
  **`BAKE_ENV_ALLOW=NAME,NAME`。** 配方真要一枚密钥形 env(例如 build.sh 里自己做 registry 登录要 `REGISTRY_PASSWORD`,或
3512
3751
  `git fetch` 的 askpass 读 `GIT_TOKEN`)⇒ 点名它,runner 把该键从本进程 env **原样**放回给每一只子进程组(部署方显式承担)。
3513
3752
  与服务侧 `LSP_ENV_ALLOW` 同一只解析器、同一只放行谓词:
3514
- - 形错(空段 / 尾逗号 / 非变量名形,如 `$NPM_TOKEN`、`a b`、`NAME=value` 整段贴入 / 点名任一只名单型旋钮 / 点名本服务自有凭据名,7.132.0 起)⇒ runner **拒启**,
3753
+ - 形错(空段 / 尾逗号 / 非变量名形,如 `$NPM_TOKEN`、`a b`、`NAME=value` 整段贴入 / 点名任一只名单型旋钮 / 点名本服务自有凭据名,7.132.0 起;点名值是本服务某一枚凭据的另一个变量,7.149.0 起)⇒ runner **拒启**,
3515
3754
  `bake_runner_fatal` 那一行点名旋钮与第几段(`entry #k of n`,一句话只有这一套序号),**不回显值**(自有凭据那一形与服务侧同一句:说明这是本部署自己的凭据、给出路,并说明引擎拒它进任何名单);
3516
3755
  - 本进程环境里没有的名 ⇒ 起服一行 `config_env_entry_rejected`(只报序号)、不放行;
3517
3756
  - 旋钮原文本身按凭据类对待,不进子进程;
@@ -3598,7 +3837,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3598
3837
  写进 `NO_PROXY`。Bun 下代理值**不拒启**(本仓的 undici 在 Bun 里是垫片、不校验;Bun 自己把无 scheme 的值当 http 用,`ftp:` 到请求时才报错),
3599
3838
  上面那条「代理那一跳失败时怎么读」的前缀也只在 Node 下有;e2b SDK 在 Bun 下走全局 fetch,**认**代理变量。
3600
3839
  - **不走这只 dispatcher 的出网**(本版如实列出,不在本版改):e2b SDK(Node 下自带 dispatcher,只认它自己的 `proxy` 选项,server 不传)、k8s 车道
3601
- (`node:https` + `ws`)、LSP 的 `ws` 传输、ssh 车道、MySQL / PG 驱动(TCP)、`bake-runner`(独立进程)。宿主上起的 host shell 与 git 子进程 env 里
3840
+ (`node:https` + `ws`)、ssh 车道、MySQL / PG 驱动(TCP)、`bake-runner`(独立进程)。宿主上起的 host shell 与 git 子进程 env 里
3602
3841
  照常带着代理变量(不被剥名),由 git / curl 这类工具自己认它们;**MCP stdio 服务器不继承**(MCP SDK 只给子进程 `HOME` / `LOGNAME` / `PATH` /
3603
3842
  `SHELL` / `TERM` / `USER` 加上该服务器声明的 env)—— 要它走代理,在它的 env 声明里写上代理变量。
3604
3843
 
@@ -3672,6 +3911,16 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3672
3911
  这段时间里读到它是常态,过了仍 `absent` 才是镜像腿没出。最新 tag 取本地 tag(发车机上有),巡查机上的 tag 不新就先 `git fetch --tags`。
3673
3912
  - **`scripts/remote-gate.sh ship`** 只把 `git ls-files --cached --others --exclude-standard` 列出的文件打包上 k3s8(已跟踪 + 未跟踪而未被 ignore;
3674
3913
  工作区未提交的改动照带),被 gitignore 的本地探针测试 / scratchpad 读数不再上船。清单生成不出来(不在 git 工作树里 / 空树)⇒ 在碰远端目录之前停。
3914
+ - **送树串行锁**:`remote-gate.sh ship` 的上行是整机串行段 —— 手跑车道、stage 轮、发车链、发包车道都经它送树,按**到达先后**在同一把锁上排队(票号队列,先到先得);
3915
+ 只在 tar 流上传期间持锁,容器里的 `npm ci` 在放锁之后跑,`run` / `fetch` 不排队。排队时 stderr 每分钟一行
3916
+ `REMOTE_GATE_SHIP_WAIT owner=… ahead=<前面几张活票> waited=<秒>s holder=<持锁者>`,看得见谁在饿;等锁超过 `GATE_SHIP_LOCK_WAIT_SEC`(缺省 3600)⇒
3917
+ `REMOTE_GATE_SHIP_LOCK_TIMEOUT`、退出码 3、没送树(响亮红,不放行)。锁目录 `GATE_SHIP_LOCK_DIR`(缺省 `/tmp/sema-ship-lock`,队列在它旁边的 `….queue`);
3918
+ 持锁者或排队者进程没了,下一位读到 pid 不在就清;上行的硬时限(`GATE_SHIP_TIMEOUT_SEC`)在拿到锁之后才起,排队不计入。车不再往自己的命令里抄 `mkdir` 轮询片段。
3919
+ - **容器的临时目录**(7.148.0 起):`remote-gate.sh` 起的每只容器 `TMPDIR=/wtmp`,挂车道目录旁边的宿主盘目录 `<车道目录>.tmp`(模式 1777;ship 重建、
3920
+ 每次 run 起容器前补建、`clean` 一并删),不挂进 `/w`(仓内的临时目录会让「夹具不在仓里」「裸仓」两类钉红)。容器自己的 /tmp 在可写层上:起真服的钉把
3921
+ local 店的数据根铸在 TMPDIR 下,落在可写层 ⇒ 店的持久性判 `ephemeral`;测试基座部署的车道是 `none`(店在调用方看不见的 worker 盘上 ⇒ 设计声明 `durable`)⇒ 起服拒 `config.store_not_durable`(`REMOTE_EXEC=host` 的格两半声明降为 `process-local`、不拒、warn 一行)。
3922
+ **Linux 开发机同一律**:/tmp 是 tmpfs 时,`npm test` 前把 `TMPDIR` 设到一只磁盘目录(仓外),否则起服类钉被 `config.store_not_durable` 拒;
3923
+ macOS 没有挂载表,判 `unknown`、只 warn,不受影响。
3675
3924
  - **core 提货门**(`node scripts/core-read-table-gate.mjs`)求交的是 **(当前钉, 目标]** 区间里 core 的**每一版**清单:从目标清单沿 `from`
3676
3925
  回溯到钉的那条链,每一环的清单都从目标 tag 的树里读(`git -C ../sema-core show v<目标>:wire-changes/core@<ver>.json`),取并集 —— 跳版提货
3677
3926
  不再只读目标那一份;链断了(某环清单读不出、本地 tag 树缺了那一版)⇒ exit 2 点名。门会先 `git fetch --tags` 那个 core 克隆(不切换 checkout)。
@@ -3718,7 +3967,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3718
3967
  - **按名字找兄弟仓的门:改名 ⇒ 红**:门 1.9(`scripts/settings-schema-duty.mjs`)与测试侧按名字找兄弟仓的门一律经
3719
3968
  `scripts/lib/sibling-repo.mjs` 三态定位 —— 名字缺席而改名映射里的对位名在场(本机 checkout 还叫旧名 `sema-web` / `sema-registry-core`,或门的名单
3720
3969
  还写旧名)⇒ **红**并指路(把目录改名 / 重新 clone 成新名),门 1.9 上持有方与非持有方一样;两个名字都不在 ⇒ 照旧(门 1.9:持有方包仓红、非持有方
3721
- 提示;测试侧:skip,skip 句点名找过的名字与 org 根)。改仓名 / 改包名的同窗动作清单在 `docs/PIPELINE-DISCIPLINE.md`。
3970
+ 提示;测试侧:skip,skip 句点名找过的名字与 org 根)。改仓名 / 改包名的同窗动作清单是内部记录。
3722
3971
  - **门 × 凭据**:起链环境里的凭据(`GITHUB_SEMA_ORG_API_TOKEN` / `NPM_TOKEN` / `GH_TOKEN` / `DEEPSEEK_API_KEY` / 三条 `*_TEST_URL` …)照旧由链启动器
3723
3972
  注入,但**每一道门的子进程只拿到 `scripts/lib/gate-credentials.mjs` 表里它那一行**;表外的凭据形变量(core `scrubSecretEnv` 的判据 + 本链
3724
3973
  DSN 名)一律不交。`release.sh` / `post-publish-gates.sh` 开头封口,每道门先换门;链日志 `scratchpad/release-gates-<v>.log` 每门一行
@@ -3747,7 +3996,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3747
3996
  手收:`node scripts/db-lane.mjs <车道> --drop`。起链者传的六条 DSN 只是引擎坐标:链里没有一组读写共享测试库的表,手跑的钉 / 别的车 / 别的仓在共享库上留下的瞬时行进不了门 2,
3748
3997
  链里的破坏性钉也不落在别人用的库上。`db-lane.mjs` 三条腿同一坐标读法(手里那条 DSN;InnoDB 缺席才起夹具),坐标在手的腿建不成 ⇒ 整只车道失败,`--drop` 则三条腿逐只都试、没删掉的逐只点名并 rc 1;暖库的 DSN 只从环境读
3749
3998
  (`TIDB_TEST_URL=… PG_TEST_URL=… npx tsx scripts/db-lane-warm.ts`)。
3750
- - **起链 = `scripts/chain-launch.sh <version>`**(7.105.0 起;取代 sema-internal 的 chain-helper):
3999
+ - **起链 = `scripts/chain-launch.sh <version>`**(7.105.0 起;取代此前的内部 chain-helper):
3751
4000
  ```bash
3752
4001
  nohup bash scripts/chain-launch.sh 7.105.0 > scratchpad/chain-7.105.0.log 2>&1 & disown # 在 node_modules 是真目录的树(主树)里
3753
4002
  ```
@@ -3774,9 +4023,8 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3774
4023
  (`node_modules/@sema-agent/core/wire-changes/core@<ver>.json` 的 `typeKeys[].added`,区间 = 上一 tag 的 core 钉到本版的钉):TaskEvent 臂与裸型名面上的新键没登记进 `excluded` ⇒ `added`(默认上 wire);
3775
4024
  登记进了 `excluded` ⇒ 新的 `excluded` 桶(这个键从未上过 wire,不是收窄,不计 `breaking`);此前被记成 `narrowed` 的就是这一类。嵌套在已有顶层键之下的新成员不在这一源的射程,由自铸登记表登记。随 release commit 提交;查账门(门 1.6)第三腿 `--check` 核它在仓且与生成输出逐字节同,缺席 / 陈旧 ⇒ 红。
3776
4025
  `wire-changes/server@7.104.0.json` 是回填的第一份。7.136.0 起 `wire-changes/` 随 npm 包出:装包的人在包内 `wire-changes/server@<ver>.json` 就能读到每一版的清单(生成时清单里的 `text` 过发布面内部编号词表,命中的编号换成 `[ref]`;`sources` 里点名的契约文档不随包)。
3777
- - **台账 `refs:` 列建议**:`node scripts/ledger-refs.mjs [--comms-dir <dir>] [--json]` 只读 sema-comms 的 `debts/DEBTS-server.md` /
3778
- `PLANS.md`(server 段)/ `bugs/B-*.md`(文件头 `owner:` 格),按「一方点了另一方的号」双向互链,输出每行的 `refs:` 建议值(号按数值认,
3779
- 零填充与不填充的拼法算同一只号;悬空号 stderr 点名)。不改 sema-comms,落账由主会话做。
4026
+ - **台账 `refs:` 列建议**:`node scripts/ledger-refs.mjs [--comms-dir <dir>] [--json]` 只读内部台账(债务账 / 计划账 server 段 / bug 账文件头的 `owner:` 格),按「一方点了另一方的号」双向互链,输出每行的 `refs:` 建议值(号按数值认,
4027
+ 零填充与不填充的拼法算同一只号;悬空号 stderr 点名)。不改内部台账,落账由主会话做。
3780
4028
  - **tag 跑复用 main 上同一提交的 test 结论**(`.github/workflows/ci.yml`):tag run 的 `test` job 先跑 `node scripts/ci-tag-test-reuse.mjs`,
3781
4029
  按 headSha 读 main 上同一提交那次 push run 的 `test` job —— success ⇒ 跳过全套;在跑 ⇒ 等(上限 45 min,复用步 55 min 时限之内);
3782
4030
  失败 / 查不到 / 读不出 ⇒ 照跑全套。结论一行 `TAG-TEST-REUSE decision=reuse|run why=… mainRun=… waited=…s sha=…` 在该 job 的日志与 step summary 里。
@@ -3859,12 +4107,14 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
3859
4107
  多个副本不共享保管目录时,各副本答出的公钥不同,对着 A 副本的公钥封的密文在 B 副本上是 `stale_ciphertext`(逐模型响亮失败,不退回任何共享 key)
3860
4108
  —— 在负载均衡后面取 caps,取到哪台就是哪台的钥匙。出路两条:
3861
4109
 
3862
- - **共享保管目录**:让每个副本的 `<数据根>/sealed-key/` 是同一份钥匙文件(例如把同一个卷挂到这个路径)。先把钥匙备好(先起一个副本让它生成,
3863
- 或放好钥匙文件)再起其余副本 —— 钥匙创建是先读后写、没有互斥:两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时,各副本可能各自生成一把、只持有自己那一把,直到下次重启。目录要对每个副本**可写**(每次起服
4110
+ - **共享保管目录**:让每个副本的 `<数据根>/sealed-key/` 是同一份钥匙文件(例如把同一个卷挂到这个路径)。空目录上首启的生成只有一个口(7.148.0 起):一把可装载的钥匙都没有时,副本以 `wx`(`O_CREAT | O_EXCL`)独占创建互斥文件 `<保管目录>/.keygen.lock`,拿到的那一个生成并发布那一把,
4111
+ 其余副本等它发布后读到同一把 —— 多个副本同时在空目录上首启也只出一把,不必先起一个再起其余、也不必事后重启(要求共享卷的 `O_EXCL` 独占创建是原子的:本地盘与 NFSv3 及以上按协议满足,别的网络文件系统请向存储方确认);
4112
+ 互斥文件只在目录里没有钥匙时才被问到,发布之后即删。拿着互斥的进程崩在生成中途、目录仍空 ⇒ 之后每次起服等 15 秒,仍没有钥匙就建店失败(处置同本节别处的「建不了店」:声明了 `SEALED_KEY_DIR` 或耐久审批在场 ⇒ 起服拒;否则告警 `sealed_key_store_unavailable` / `checkpoint_ctx_sealing_unavailable`),句子点名互斥文件的路径:没有副本正在起服时删掉它再起。
4113
+ 7.147.x 及更早的版本没有这个互斥:它们的副本在空目录上同时首启、都没读到可用的钥匙文件时,可能各自生成一把、只持有自己那一把,直到下次重启 —— 这样分叉过的目录,重启一次让每个副本读到全部钥匙。目录要对每个副本**可写**(每次起服
3864
4114
  会把它设成 `0700`;只读挂载 ⇒ 建店失败 ⇒ `no_key_store`)。⚠️ 只共享 `sealed-key/` 这一个子目录:`DB_BACKEND=local` 的整个数据根禁止跨容器 /
3865
- 跨主机共享(`docs/DEPLOY-PREREQS.md`)。
4115
+ 跨主机共享。
3866
4116
  照随包 `deploy/sema-up` 图表部署时这一步图表替你做了:`sealedKey.persistence` 块(缺省开)把整个 `sealed-key/` 目录挂成一只 PVC,访问模式缺省单副本 `ReadWriteOnce`、多副本 `ReadWriteMany`
3867
- (要存储类支持 RWX;空卷首装后 `rollout restart` 一次,让并发首启时可能各生成的钥匙互相都持有);显式关掉它(`sealedKey.persistence.enabled: false`)图表会同时给 worker 设 `DURABLE_APPROVAL=false`,并拒绝再配 `server.durableApproval: true`。
4117
+ (要存储类支持 RWX;空卷首装的多副本并发首启也只出一把,7.148.0 起不必重启 —— 7.147.x 及更早版本首装出多把钥匙的卷,`rollout restart` 一次让每个 pod 读到全部钥匙);显式关掉它(`sealedKey.persistence.enabled: false`)图表会同时给 worker 设 `DURABLE_APPROVAL=false`,并拒绝再配 `server.durableApproval: true`。
3868
4118
  - **走中心形**:接 web-admin,执行侧公钥登记在中心(起服时带管理令牌自动上报,否则把公钥打印出来供手工登记),web 面对着登记的公钥封、中心存密文。
3869
4119
  调用方不必逐台去问 caps;但同一个 worker 的多个副本各有各的保管店这件事中心形不改变 —— 要它们都开得了同一份密文,仍要共享保管目录。
3870
4120
 
@@ -3872,7 +4122,7 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
3872
4122
  在 B 副本上续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`;单副本也要让这个目录跨重启持久。**7.133.x 与 7.134.0 不能混跑**:先停掉全部 7.133.x 副本,再起第一个 7.134.0 副本
3873
4123
  (一次性迁移 M28 在它起服时把升级前停驻行里的明文封好);混跑时 7.133.x 副本续跑一条已封签的行会把凭据位当空值、任务无凭据照跑、不报错。降级到 7.133.x 之前先把带凭据的停驻行结掉(决定或取消)。
3874
4124
  多副本升级时先起一个 7.134.0 副本、等它起好再起其余副本(耐久审批开着时它起好即打出 `checkpoint_ctx_credentials_sealed` 一行):停驻封签的钥匙在新子目录 `checkpoint-ctx-keys/` 里,
3875
- 升级后第一个起服的副本生成它(`sealed-key/` 早已共享好也一样);同时起多个副本可能各建一把、互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件;各建只发生在两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时)。
4125
+ 升级后第一个起服的副本生成它(`sealed-key/` 早已共享好也一样);7.148.0 起同时起多个副本也只出一把(首把钥匙只有一个创建口,见上);7.134.0 – 7.147.x 同时起多个副本可能各建一把、互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件;各建只发生在两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时)。
3876
4126
 
3877
4127
  ### 9.11 会话规则店:抹除封键与显式恢复(7.106.0 起)
3878
4128
 
@@ -4038,15 +4288,16 @@ docker run -d --name sema-server -p 8090:8090 \
4038
4288
  -e MODEL_GATEWAY_BASEURL=https://<your-gateway>/v1 -e MODEL_ID=<your-model-name> -e MODEL_API_KEY=… \
4039
4289
  -e SERVICE_AUTH_TOKEN=<随机长串> \
4040
4290
  sema-server:<版本>
4041
- # SERVICE_AUTH_TOKEN 不是起服必需,但没有它(且没开 ALLOW_UNAUTHED_WRITES)提交任务一律 503 auth.service_token_required —— 第 5 步只验就绪,交任务前先配它。另一形是显式 ALLOW_UNAUTHED_WRITES=1:那时没有 service 令牌的 worker 只绑 127.0.0.1(自收窄),容器里还要 BIND_HOST=0.0.0.0 才能经 -p 访问,并把第 4 步的映射改成 `-p 127.0.0.1:8090:8090`(否则无鉴权的提交口暴露给整个局域网)—— 两形的取舍见 §0「监听绑址」段。
4291
+ # SERVICE_AUTH_TOKEN 不是起服必需,但没有它(且没开 ALLOW_UNAUTHED_WRITES)提交任务一律 503 auth.service_token_required —— 第 5 步只验就绪,交任务前先配它。另一形是显式 ALLOW_UNAUTHED_WRITES=1:那时没有 service 令牌的 worker 只绑 127.0.0.1(自收窄),容器外经 -p 访问不到;7.148.0 起单用户部署(没设 REQUIRE_PRINCIPAL)上再写 BIND_HOST=0.0.0.0 把无鉴权写面暴露出去 ⇒ 起服拒(不变量 single-user-anonymous-boundary;进程看不见宿主把端口只发布到回环)—— 容器形一律配 SERVICE_AUTH_TOKEN。两形的取舍见 §0「监听绑址」段。
4042
4292
  # 5. 验收 —— 就绪 + 版本
4043
4293
  curl -fsS http://127.0.0.1:8090/readyz # {"ready":true}
4044
- curl -s http://127.0.0.1:8090/health # "version" 应等于你构建的 <版本>
4294
+ curl -s -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>" http://127.0.0.1:8090/health # "version" 应等于你构建的 <版本>
4295
+ # 带第 4 步那枚 SERVICE_AUTH_TOKEN 读:不带凭据的读者只拿 status / auth / operator / center 四键,没有 version
4045
4296
  ```
4046
4297
 
4047
4298
  - **随包 `deploy/sema-up`**:图表 `server.image` 必填,填你自建的引用;节点本地导入的(第 3 步 k3s)`--set server.image=sema-server:<版本> --set server.pullPolicy=Never`,推了 registry 的填完整引用并保持缺省 `IfNotPresent`。图表不覆写镜像的启动命令,两条路径的镜像都按它自己的 `CMD` 起。`sema-up` 脚本自己的入口:`full-cluster --kube=machines`(`-f machines.yaml`,≥ 3 台)在环境里设 `SEMA_SERVER_IMAGE_TAR=<docker save 出的 tar 或 .tar.gz>`,脚本逐台灌入并 `k3s ctr images import`,部署的镜像名是固定的 `docker.io/library/sema-server:local`、拉取策略 `Never` —— **所以 tar 里的镜像必须带 `sema-server:local` 这个标签**:`docker tag sema-server:<版本> sema-server:local && docker save sema-server:local -o sema-server.tar`(只存 `sema-server:<版本>` 的 tar 导入成功,但部署找不到 `:local`);或设 `server_image:` / `SEMA_SERVER_IMAGE=<你的 registry 引用>`,二选一;含 server 的 compose 档(`full-single` / `local-plus`)与 `full-cluster --kube=existing|k3s` 要的是 `SEMA_SERVER_IMAGE`。⚠️ 缺镜像时 `sema-up` 打印的提示与 `machines.example.yaml` 的注释仍写着 ghcr 引用 —— 那是曾发布的镜像,7.128.0 之后没有新版;照本节自建,把你的引用填进去。
4048
4299
  - **数据根**:第 4 步 `DB_BACKEND=local` 是单容器形(数据目录禁跨容器共享)。生产用 `DB_BACKEND=mysql|pg` 与 §0「生产」那组坐标替换它 —— **数据根卷仍要挂**:`<数据根>/sealed-key/` 在 SQL 后端下也不进库,跨重启要持久。不设 `AGENT_DATA_DIR` 时数据根落在容器用户家目录下的 `.ai-agent`,容器一删就没。
4049
- - **升级 = 换一只更高版本的镜像**:升级序、先升库再升副本、排空与回滚前置,按你跨过的每一版读 `MIGRATION.md` 与源码仓文档 DEPLOY-PREREQS 的同版节(不随包);一次性迁移与新表在起服时自跑,不用手跑。
4300
+ - **升级 = 换一只更高版本的镜像**:升级序、先升库再升副本、排空与回滚前置,按你跨过的每一版读 `MIGRATION.md` 的同版节(「升级前做」「回滚」都写在那里);一次性迁移与新表在起服时自跑,不用手跑。
4050
4301
 
4051
4302
  **与曾发布镜像的差异**
4052
4303