@sema-agent/server 7.146.0 → 7.148.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 (850) hide show
  1. package/MIGRATION.md +1423 -24
  2. package/README.md +21 -8
  3. package/README.zh-CN.md +15 -9
  4. package/USAGE.md +537 -216
  5. package/deploy/facts/lanes.json +7 -0
  6. package/deploy/sema-up/README.md +18 -7
  7. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +42 -4
  9. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -4
  10. package/deploy/sema-up/chart/templates/server.yaml +5 -4
  11. package/deploy/sema-up/chart/values.yaml +4 -3
  12. package/deploy/sema-up/cluster-up.sh +75 -176
  13. package/deploy/sema-up/kube-up.sh +29 -49
  14. package/deploy/sema-up/lib/chart-run.sh +23 -0
  15. package/deploy/sema-up/lib/dataroot.sh +3 -3
  16. package/deploy/sema-up/lib/flagshape.sh +4 -0
  17. package/deploy/sema-up/lib/health.sh +62 -13
  18. package/deploy/sema-up/lib/helm-values-cluster.py +18 -0
  19. package/deploy/sema-up/lib/helm-values-kube.py +35 -0
  20. package/deploy/sema-up/lib/lanes.json +2 -5
  21. package/deploy/sema-up/lib/regcred.py +8 -0
  22. package/deploy/sema-up/lib/remote/helm-install.sh +3 -0
  23. package/deploy/sema-up/lib/remote/image-import.sh +6 -0
  24. package/deploy/sema-up/lib/remote/machine-precheck.sh +11 -0
  25. package/deploy/sema-up/lib/remote/region-probe.sh +3 -0
  26. package/deploy/sema-up/lib/remote/stack-ready.sh +26 -0
  27. package/deploy/sema-up/lib/remote/vxlan-guard.sh +8 -0
  28. package/deploy/sema-up/lib/render-stack.py +17 -0
  29. package/deploy/sema-up/lib/shell-text.sh +87 -0
  30. package/deploy/sema-up/lib/usage-flags.txt +10 -0
  31. package/deploy/sema-up/lib/usage-head.txt +1 -0
  32. package/deploy/sema-up/lib/usage-tail.txt +13 -0
  33. package/deploy/sema-up/preflight.sh +10 -8
  34. package/deploy/sema-up/sema-up.sh +26 -48
  35. package/deploy/sema-up/smoke.sh +7 -6
  36. package/deploy/sema-up/stack/compose.dataroot.yaml +3 -0
  37. package/deploy/sema-up/stack/compose.yaml +18 -5
  38. package/deploy/sema-up/stack/sema-stack-cluster.yaml +25 -3
  39. package/dist/adoption/plan.d.ts +24 -0
  40. package/dist/adoption/plan.js +53 -1
  41. package/dist/adoption/runner.d.ts +10 -1
  42. package/dist/adoption/runner.js +36 -16
  43. package/dist/adoption/wire.d.ts +3 -3
  44. package/dist/approval-card.d.ts +32 -34
  45. package/dist/approval-gate-currency.js +3 -2
  46. package/dist/approval-reconciler.d.ts +4 -13
  47. package/dist/approval-reconciler.js +2 -2
  48. package/dist/approval.d.ts +16 -14
  49. package/dist/approval.js +3 -3
  50. package/dist/audit.d.ts +10 -3
  51. package/dist/audit.js +17 -9
  52. package/dist/auth-bridge.d.ts +0 -14
  53. package/dist/auth-bridge.js +0 -7
  54. package/dist/auto-mode-face.d.ts +10 -7
  55. package/dist/auto-mode-face.js +7 -4
  56. package/dist/background-agent-name.d.ts +19 -1
  57. package/dist/background-agent-name.js +12 -2
  58. package/dist/background-session-reap.d.ts +1 -1
  59. package/dist/bake-runner/main.js +9 -6
  60. package/dist/bench/s1/arms.js +6 -5
  61. package/dist/bench/s1/live-deps.js +8 -3
  62. package/dist/bench/s1/run-firm.js +6 -5
  63. package/dist/bind-host.d.ts +25 -0
  64. package/dist/bind-host.js +25 -0
  65. package/dist/boot/adoption.d.ts +3 -0
  66. package/dist/boot/adoption.js +1 -0
  67. package/dist/boot/crash-last.js +2 -1
  68. package/dist/boot/deployment-mods.d.ts +1 -1
  69. package/dist/boot/device-lane.js +5 -5
  70. package/dist/boot/engine-lease.d.ts +12 -11
  71. package/dist/boot/execution-env.d.ts +19 -8
  72. package/dist/boot/execution-env.js +80 -62
  73. package/dist/boot/execution-lane.d.ts +37 -9
  74. package/dist/boot/execution-lane.js +74 -20
  75. package/dist/boot/exit-boundary.d.ts +3 -3
  76. package/dist/boot/exit-boundary.js +6 -5
  77. package/dist/boot/exit-last.js +2 -1
  78. package/dist/boot/fatal-exit.d.ts +3 -5
  79. package/dist/boot/fatal-exit.js +2 -2
  80. package/dist/boot/governance-seams.d.ts +2 -2
  81. package/dist/boot/leader.d.ts +15 -0
  82. package/dist/boot/leader.js +18 -16
  83. package/dist/boot/memory-boundary.d.ts +11 -11
  84. package/dist/boot/memory-boundary.js +3 -3
  85. package/dist/boot/memory-consolidation.d.ts +50 -53
  86. package/dist/boot/memory-consolidation.js +11 -18
  87. package/dist/boot/org-memory.js +4 -3
  88. package/dist/boot/parked-revive-gate.d.ts +1 -1
  89. package/dist/boot/parked-revive-gate.js +3 -3
  90. package/dist/boot/reapers.d.ts +4 -3
  91. package/dist/boot/reapers.js +20 -17
  92. package/dist/boot/resolve-spec.d.ts +5 -3
  93. package/dist/boot/resolve-spec.js +133 -165
  94. package/dist/boot/retention-lane.d.ts +7 -45
  95. package/dist/boot/retention-lane.js +2 -2
  96. package/dist/boot/runner-deps.d.ts +28 -7
  97. package/dist/boot/runner-deps.js +11 -4
  98. package/dist/boot/runtime-caps.d.ts +2 -2
  99. package/dist/boot/runtime-caps.js +16 -2
  100. package/dist/boot/session-faces.d.ts +17 -7
  101. package/dist/boot/session-faces.js +122 -96
  102. package/dist/boot/shutdown.d.ts +5 -4
  103. package/dist/boot/shutdown.js +6 -4
  104. package/dist/boot/stage-01-config.d.ts +1 -1
  105. package/dist/boot/stage-01-config.js +14 -2
  106. package/dist/boot/stage-02-web-admin-pull.d.ts +2 -1
  107. package/dist/boot/stage-02-web-admin-pull.js +15 -8
  108. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  109. package/dist/boot/stage-03-store-backend.js +4 -3
  110. package/dist/boot/stage-04-stores.d.ts +3 -1
  111. package/dist/boot/stage-04-stores.js +2 -2
  112. package/dist/boot/stage-05-execution-env.d.ts +9 -5
  113. package/dist/boot/stage-05-execution-env.js +5 -5
  114. package/dist/boot/stage-06-runners.d.ts +13 -15
  115. package/dist/boot/stage-06-runners.js +24 -18
  116. package/dist/boot/stage-07-capability-layer.d.ts +14 -11
  117. package/dist/boot/stage-07-capability-layer.js +9 -12
  118. package/dist/boot/stage-08-reapers.d.ts +14 -11
  119. package/dist/boot/stage-08-reapers.js +3 -3
  120. package/dist/boot/stage-09-leader.d.ts +16 -18
  121. package/dist/boot/stage-09-leader.js +12 -4
  122. package/dist/boot/stage-10-http-server.d.ts +16 -18
  123. package/dist/boot/stage-10-http-server.js +9 -5
  124. package/dist/boot/store-evidence.d.ts +20 -0
  125. package/dist/boot/store-evidence.js +13 -0
  126. package/dist/boot/stores.d.ts +12 -0
  127. package/dist/boot/stores.js +39 -17
  128. package/dist/boot/task-list-lane.d.ts +4 -4
  129. package/dist/boot/web-admin.d.ts +4 -2
  130. package/dist/boot/web-admin.js +58 -38
  131. package/dist/boot/webfetch-summarize-lane.d.ts +1 -1
  132. package/dist/boot-reclaim.d.ts +3 -1
  133. package/dist/boot-reclaim.js +1 -1
  134. package/dist/brain.d.ts +7 -18
  135. package/dist/brain.js +5 -17
  136. package/dist/budget.js +6 -6
  137. package/dist/capabilities/center-plugins.d.ts +9 -1
  138. package/dist/capabilities/center-plugins.js +37 -11
  139. package/dist/capabilities/center-wiring.js +1 -1
  140. package/dist/capabilities/composite-members.d.ts +3 -2
  141. package/dist/capabilities/composite-members.js +3 -2
  142. package/dist/capabilities/execution-lane.d.ts +7 -7
  143. package/dist/capabilities/execution-lane.js +2 -2
  144. package/dist/capabilities/hands-lane.d.ts +1 -1
  145. package/dist/capabilities/repo-tools.d.ts +2 -0
  146. package/dist/capabilities/repo-tools.js +14 -12
  147. package/dist/capabilities/scenarios.d.ts +38 -33
  148. package/dist/capabilities/scenarios.js +24 -25
  149. package/dist/capabilities/settings-contract.d.ts +29 -0
  150. package/dist/capabilities/settings-contract.js +14 -0
  151. package/dist/capabilities/skills.d.ts +6 -6
  152. package/dist/capabilities/tool-defer.d.ts +1 -1
  153. package/dist/capability-facts.d.ts +69 -4
  154. package/dist/capability-facts.js +77 -5
  155. package/dist/card-void.d.ts +4 -4
  156. package/dist/card-void.js +1 -1
  157. package/dist/center-credential-renewer.d.ts +6 -2
  158. package/dist/center-credential-renewer.js +10 -5
  159. package/dist/config-catalog.d.ts +16 -1
  160. package/dist/config-catalog.js +68 -61
  161. package/dist/config-dir-trust.d.ts +37 -4
  162. package/dist/config-dir-trust.js +88 -44
  163. package/dist/config-invariants.d.ts +58 -4
  164. package/dist/config-invariants.js +104 -23
  165. package/dist/config-lkg.d.ts +24 -2
  166. package/dist/config-lkg.js +22 -9
  167. package/dist/config-provider.d.ts +9 -9
  168. package/dist/config-provider.js +25 -108
  169. package/dist/config-pull-failure.d.ts +50 -0
  170. package/dist/config-pull-failure.js +48 -0
  171. package/dist/config-types.d.ts +215 -202
  172. package/dist/config.d.ts +69 -14
  173. package/dist/config.js +188 -96
  174. package/dist/degenerate-instrument.js +1 -1
  175. package/dist/delegated-session-owner.d.ts +1 -1
  176. package/dist/deleted-session-ids.d.ts +3 -2
  177. package/dist/deploy-facts.js +3 -2
  178. package/dist/deployment-governance.d.ts +3 -3
  179. package/dist/deployment-shape.d.ts +51 -16
  180. package/dist/deployment-shape.js +31 -6
  181. package/dist/egress-redirect.d.ts +25 -7
  182. package/dist/egress-redirect.js +20 -2
  183. package/dist/elicitation.d.ts +6 -5
  184. package/dist/elicitation.js +23 -22
  185. package/dist/engine-identity.d.ts +28 -0
  186. package/dist/engine-identity.js +10 -0
  187. package/dist/entry-problems.d.ts +5 -7
  188. package/dist/entry-problems.js +1 -1
  189. package/dist/env-facts.d.ts +7 -5
  190. package/dist/env-facts.js +12 -7
  191. package/dist/env-witness.d.ts +7 -0
  192. package/dist/env-witness.js +16 -0
  193. package/dist/execution-config-root.d.ts +5 -5
  194. package/dist/execution-config-root.js +5 -5
  195. package/dist/execution-env-purpose.d.ts +14 -0
  196. package/dist/execution-env-purpose.js +29 -0
  197. package/dist/execution-lane-caps.d.ts +53 -103
  198. package/dist/execution-lane-caps.js +51 -83
  199. package/dist/execution-placement.d.ts +65 -9
  200. package/dist/execution-placement.js +58 -0
  201. package/dist/field-conflict.d.ts +7 -11
  202. package/dist/field-conflict.js +3 -3
  203. package/dist/file-history-capture.d.ts +3 -3
  204. package/dist/file-history-disclosure.d.ts +2 -2
  205. package/dist/file-history-disclosure.js +1 -0
  206. package/dist/fleet/drain-entry.d.ts +17 -10
  207. package/dist/fleet/drain-entry.js +1 -1
  208. package/dist/fleet/fleet-bus.d.ts +21 -3
  209. package/dist/fleet/fleet-bus.js +35 -25
  210. package/dist/fleet/fleet-reconciler.js +2 -2
  211. package/dist/fleet/serving-lease.js +6 -7
  212. package/dist/fleet-client.js +3 -3
  213. package/dist/fleet-lease.d.ts +0 -1
  214. package/dist/fleet-lease.js +4 -5
  215. package/dist/history-anchor.d.ts +42 -0
  216. package/dist/history-anchor.js +34 -0
  217. package/dist/hitl-delivery.d.ts +90 -0
  218. package/dist/hitl-delivery.js +98 -0
  219. package/dist/hooks/classic-stations.d.ts +8 -11
  220. package/dist/hooks/classic-stations.js +0 -7
  221. package/dist/hooks/command-spawn.d.ts +1 -4
  222. package/dist/hooks/command-spawn.js +0 -11
  223. package/dist/hooks/hook-llm.d.ts +5 -1
  224. package/dist/hooks/hook-llm.js +24 -15
  225. package/dist/hooks/hook-runner.d.ts +119 -65
  226. package/dist/hooks/hook-runner.js +415 -217
  227. package/dist/host-lsp-manager.d.ts +22 -8
  228. package/dist/host-lsp-manager.js +4 -1
  229. package/dist/http/active-run-conflict.d.ts +2 -43
  230. package/dist/http/active-run-conflict.js +3 -7
  231. package/dist/http/admission.d.ts +4 -18
  232. package/dist/http/admission.js +75 -78
  233. package/dist/http/approval-sweeps.d.ts +5 -8
  234. package/dist/http/approval-sweeps.js +55 -30
  235. package/dist/http/caller-trust.d.ts +8 -10
  236. package/dist/http/caller-trust.js +1 -2
  237. package/dist/http/checkpoint-ctx.d.ts +17 -0
  238. package/dist/http/checkpoint-ctx.js +6 -0
  239. package/dist/http/claim-heartbeat.d.ts +49 -0
  240. package/dist/http/claim-heartbeat.js +46 -0
  241. package/dist/http/claim-latency.d.ts +10 -0
  242. package/dist/http/claim-latency.js +12 -0
  243. package/dist/http/claimed-run.d.ts +4 -3
  244. package/dist/http/dispatch.d.ts +8 -1
  245. package/dist/http/dispatch.js +77 -31
  246. package/dist/http/durable-run-leg.d.ts +3 -3
  247. package/dist/http/durable-run-leg.js +18 -12
  248. package/dist/http/execution-context.d.ts +18 -42
  249. package/dist/http/execution-context.js +11 -5
  250. package/dist/http/health-reader.d.ts +36 -0
  251. package/dist/http/health-reader.js +21 -0
  252. package/dist/http/health-self-report.d.ts +9 -9
  253. package/dist/http/health-self-report.js +4 -4
  254. package/dist/http/idempotency.d.ts +9 -5
  255. package/dist/http/idempotency.js +4 -4
  256. package/dist/http/memory-compliance-failure-reply.d.ts +15 -16
  257. package/dist/http/memory-compliance-failure-reply.js +7 -2
  258. package/dist/http/memory-face-fault-reply.d.ts +67 -0
  259. package/dist/http/memory-face-fault-reply.js +71 -0
  260. package/dist/http/memory-store-refusal-reply.d.ts +3 -3
  261. package/dist/http/memory-store-refusal-reply.js +6 -0
  262. package/dist/http/owner-access.d.ts +391 -56
  263. package/dist/http/owner-access.js +372 -33
  264. package/dist/http/principal-gate.d.ts +26 -94
  265. package/dist/http/principal-gate.js +24 -81
  266. package/dist/http/refusal-body.d.ts +35 -2
  267. package/dist/http/refusal-body.js +34 -0
  268. package/dist/http/request-guards.d.ts +10 -14
  269. package/dist/http/request-guards.js +34 -68
  270. package/dist/http/resume-composition.d.ts +37 -0
  271. package/dist/http/resume-composition.js +18 -0
  272. package/dist/http/resume-ctx-read.d.ts +35 -12
  273. package/dist/http/resume-ctx-read.js +25 -6
  274. package/dist/http/resume-legs.d.ts +24 -24
  275. package/dist/http/resume-legs.js +260 -171
  276. package/dist/http/retired-skill-manifest.d.ts +1 -1
  277. package/dist/http/rewind-files-reply.d.ts +34 -0
  278. package/dist/http/rewind-files-reply.js +36 -0
  279. package/dist/http/route-ctx.d.ts +56 -47
  280. package/dist/http/route-table.js +2 -0
  281. package/dist/http/routes/a2a-serve.d.ts +43 -5
  282. package/dist/http/routes/a2a-serve.js +58 -37
  283. package/dist/http/routes/admin-config-refresh.js +2 -2
  284. package/dist/http/routes/admin-drain.js +2 -2
  285. package/dist/http/routes/admin-memory-optout.js +4 -3
  286. package/dist/http/routes/adoption.d.ts +3 -0
  287. package/dist/http/routes/adoption.js +17 -10
  288. package/dist/http/routes/agents-roster.js +13 -7
  289. package/dist/http/routes/approvals-assistant.js +73 -88
  290. package/dist/http/routes/attachments.js +2 -2
  291. package/dist/http/routes/background.d.ts +3 -3
  292. package/dist/http/routes/background.js +9 -17
  293. package/dist/http/routes/capabilities.js +23 -20
  294. package/dist/http/routes/config-catalog.js +2 -2
  295. package/dist/http/routes/devices.d.ts +3 -3
  296. package/dist/http/routes/devices.js +5 -5
  297. package/dist/http/routes/diagnostics.d.ts +27 -3
  298. package/dist/http/routes/diagnostics.js +27 -21
  299. package/dist/http/routes/engine.js +2 -2
  300. package/dist/http/routes/fleet.js +5 -3
  301. package/dist/http/routes/images.js +10 -6
  302. package/dist/http/routes/leader.d.ts +7 -2
  303. package/dist/http/routes/leader.js +12 -3
  304. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  305. package/dist/http/routes/mcp-reconnect.js +7 -15
  306. package/dist/http/routes/memory-bundle.js +4 -7
  307. package/dist/http/routes/memory-compliance.js +4 -4
  308. package/dist/http/routes/memory-consolidation.js +6 -9
  309. package/dist/http/routes/memory-origin.d.ts +3 -1
  310. package/dist/http/routes/memory-origin.js +7 -8
  311. package/dist/http/routes/memory-policy.js +17 -12
  312. package/dist/http/routes/notify-wake.js +12 -20
  313. package/dist/http/routes/observability.js +3 -3
  314. package/dist/http/routes/principal-inbox.d.ts +1 -1
  315. package/dist/http/routes/principal-inbox.js +2 -2
  316. package/dist/http/routes/projects.d.ts +70 -0
  317. package/dist/http/routes/projects.js +87 -0
  318. package/dist/http/routes/retention-ops.js +6 -9
  319. package/dist/http/routes/rules.js +50 -7
  320. package/dist/http/routes/run-memory-capture.d.ts +2 -2
  321. package/dist/http/routes/run-memory-capture.js +6 -12
  322. package/dist/http/routes/run-permission-mode.d.ts +2 -2
  323. package/dist/http/routes/run-permission-mode.js +10 -17
  324. package/dist/http/routes/runs.js +148 -233
  325. package/dist/http/routes/session-memory-sync.d.ts +2 -2
  326. package/dist/http/routes/session-memory-sync.js +7 -13
  327. package/dist/http/routes/session-sync.d.ts +4 -2
  328. package/dist/http/routes/session-sync.js +48 -30
  329. package/dist/http/routes/sessions-list.d.ts +3 -3
  330. package/dist/http/routes/sessions-list.js +9 -9
  331. package/dist/http/routes/sessions.d.ts +7 -1
  332. package/dist/http/routes/sessions.js +177 -112
  333. package/dist/http/routes/shared-memory.js +6 -6
  334. package/dist/http/routes/side-query.js +2 -2
  335. package/dist/http/routes/tasks.js +144 -122
  336. package/dist/http/routes/trace-usage.d.ts +0 -12
  337. package/dist/http/routes/trace-usage.js +40 -46
  338. package/dist/http/routes/workflows.d.ts +39 -4
  339. package/dist/http/routes/workflows.js +42 -14
  340. package/dist/http/run-concurrency-cap.d.ts +20 -12
  341. package/dist/http/run-concurrency-cap.js +39 -26
  342. package/dist/http/run-read-face.d.ts +3 -5
  343. package/dist/http/run-read-face.js +8 -13
  344. package/dist/http/send.d.ts +12 -3
  345. package/dist/http/send.js +42 -6
  346. package/dist/http/server.d.ts +63 -50
  347. package/dist/http/server.js +52 -16
  348. package/dist/http/sse-log.d.ts +17 -10
  349. package/dist/http/sse-log.js +7 -7
  350. package/dist/http/stored-body-replay.d.ts +17 -3
  351. package/dist/http/stored-body-replay.js +11 -2
  352. package/dist/http/submit-ctx.d.ts +16 -8
  353. package/dist/http/submit-ctx.js +6 -2
  354. package/dist/http/submit-replay.d.ts +19 -6
  355. package/dist/http/submit-replay.js +18 -6
  356. package/dist/http/sync-submit-body.d.ts +3 -4
  357. package/dist/http/sync-submit-body.js +4 -5
  358. package/dist/http/wire-types.d.ts +23 -13
  359. package/dist/identity-source.d.ts +36 -5
  360. package/dist/identity-source.js +27 -0
  361. package/dist/index.d.ts +3 -2
  362. package/dist/index.js +2 -1
  363. package/dist/instruction-source-notice.d.ts +1 -1
  364. package/dist/key-resolver.d.ts +32 -0
  365. package/dist/key-resolver.js +23 -9
  366. package/dist/leader/diffup.js +2 -1
  367. package/dist/leader/endpoint.d.ts +26 -5
  368. package/dist/leader/endpoint.js +154 -14
  369. package/dist/leader/fanout.d.ts +3 -0
  370. package/dist/leader/fanout.js +78 -63
  371. package/dist/leader/leader.d.ts +16 -0
  372. package/dist/leader/leader.js +43 -3
  373. package/dist/leader/merge.d.ts +9 -1
  374. package/dist/leader/merge.js +16 -1
  375. package/dist/leader/repair-wire.js +1 -1
  376. package/dist/leader/wire.d.ts +36 -3
  377. package/dist/leader/wire.js +59 -18
  378. package/dist/lsp-evict.d.ts +6 -4
  379. package/dist/lsp-evict.js +2 -3
  380. package/dist/memory-embedder-reembed.d.ts +38 -0
  381. package/dist/memory-embedder-reembed.js +20 -0
  382. package/dist/memory-incident-sink.d.ts +0 -28
  383. package/dist/memory-incident-sink.js +5 -0
  384. package/dist/memory-operator-faces.d.ts +5 -4
  385. package/dist/memory-operator-faces.js +1 -1
  386. package/dist/memory-scope.d.ts +31 -4
  387. package/dist/memory-scope.js +65 -16
  388. package/dist/memory-store-refusal.d.ts +20 -4
  389. package/dist/memory-store-refusal.js +1 -1
  390. package/dist/memory-sync-client.d.ts +5 -2
  391. package/dist/memory-sync-client.js +19 -14
  392. package/dist/memory-sync.js +1 -1
  393. package/dist/model-compat.d.ts +51 -28
  394. package/dist/model-compat.js +55 -23
  395. package/dist/model-context-window.d.ts +18 -0
  396. package/dist/model-context-window.js +2 -0
  397. package/dist/model-entry-headers.d.ts +46 -4
  398. package/dist/model-entry-headers.js +2 -2
  399. package/dist/model-entry-refusal.d.ts +46 -17
  400. package/dist/model-entry-refusal.js +47 -27
  401. package/dist/model-plane-face.d.ts +19 -11
  402. package/dist/model-plane-face.js +9 -1
  403. package/dist/model-plane-probe.d.ts +2 -0
  404. package/dist/model-plane-probe.js +17 -0
  405. package/dist/model-provider.d.ts +3 -3
  406. package/dist/model-route-endpoint.d.ts +9 -5
  407. package/dist/model-route-endpoint.js +29 -7
  408. package/dist/model-select.d.ts +16 -0
  409. package/dist/model-select.js +4 -0
  410. package/dist/mountinfo.d.ts +37 -0
  411. package/dist/mountinfo.js +47 -0
  412. package/dist/namespaced-rule-name.d.ts +28 -2
  413. package/dist/namespaced-rule-name.js +10 -1
  414. package/dist/net-address.d.ts +1 -1
  415. package/dist/observability/corrupt-read-seat.d.ts +4 -4
  416. package/dist/observability/corrupt-read-seat.js +5 -3
  417. package/dist/observability/fail-open.d.ts +75 -47
  418. package/dist/observability/fail-open.js +77 -47
  419. package/dist/observability/logger.d.ts +22 -1
  420. package/dist/observability/logger.js +89 -12
  421. package/dist/observability/metrics.d.ts +2 -0
  422. package/dist/observability/metrics.js +5 -4
  423. package/dist/observability/otel-exporter.js +2 -1
  424. package/dist/observability/principal-context.d.ts +14 -7
  425. package/dist/observability/principal-context.js +8 -3
  426. package/dist/observability/run-terminal.d.ts +7 -1
  427. package/dist/observability/run-terminal.js +5 -2
  428. package/dist/observability/secret-env-scrub.d.ts +30 -4
  429. package/dist/observability/secret-env-scrub.js +15 -3
  430. package/dist/orchestration/hardened-vm-runner.d.ts +1 -0
  431. package/dist/orchestration/hardened-vm-runner.js +11 -5
  432. package/dist/orchestration/hardened-vm-worker-runner.d.ts +0 -14
  433. package/dist/orchestration/hardened-vm-worker-runner.js +5 -0
  434. package/dist/orchestration/hardened-vm-worker.js +2 -1
  435. package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
  436. package/dist/orchestration/workflow-agent-session-index.js +1 -6
  437. package/dist/orchestration/workflow-completion-inbox.d.ts +29 -9
  438. package/dist/orchestration/workflow-completion-inbox.js +65 -46
  439. package/dist/org-delivery-gate.d.ts +5 -4
  440. package/dist/org-delivery-gate.js +7 -6
  441. package/dist/org-memory-admission.d.ts +5 -2
  442. package/dist/org-memory-admission.js +25 -10
  443. package/dist/outbound-dispatcher.d.ts +1 -1
  444. package/dist/outbound-dispatcher.js +6 -12
  445. package/dist/owner-brands.d.ts +81 -0
  446. package/dist/owner-brands.js +42 -0
  447. package/dist/owner-faces.d.ts +531 -0
  448. package/dist/owner-faces.js +126 -0
  449. package/dist/parked-decide.d.ts +100 -31
  450. package/dist/parked-decide.js +162 -48
  451. package/dist/parked-revive-surface.d.ts +14 -4
  452. package/dist/parked-revive-surface.js +5 -4
  453. package/dist/peer-directory.d.ts +6 -8
  454. package/dist/peer-directory.js +3 -2
  455. package/dist/permission-rule-vocab.d.ts +53 -8
  456. package/dist/permission-rule-vocab.js +30 -2
  457. package/dist/plugins/adoption-log-sql.d.ts +0 -22
  458. package/dist/plugins/adoption-log-sql.js +5 -3
  459. package/dist/plugins/approval-ask-retired-delete-by-task-m50.d.ts +7 -0
  460. package/dist/plugins/approval-ask-retired-delete-by-task-m50.js +17 -0
  461. package/dist/plugins/approval-ask-store-file.d.ts +28 -9
  462. package/dist/plugins/approval-ask-store-file.js +74 -18
  463. package/dist/plugins/approval-ask-store-memory.d.ts +17 -7
  464. package/dist/plugins/approval-ask-store-memory.js +35 -12
  465. package/dist/plugins/approval-ask-store-sql.d.ts +42 -20
  466. package/dist/plugins/approval-ask-store-sql.js +44 -11
  467. package/dist/plugins/approval-exemption-skill-m46.d.ts +20 -0
  468. package/dist/plugins/approval-exemption-skill-m46.js +25 -0
  469. package/dist/plugins/approval-exemption-store.d.ts +6 -1
  470. package/dist/plugins/approval-exemption-store.js +14 -0
  471. package/dist/plugins/approval-nonce-store.d.ts +39 -12
  472. package/dist/plugins/approval-nonce-store.js +72 -23
  473. package/dist/plugins/blob-backend.d.ts +40 -195
  474. package/dist/plugins/blob-backend.js +41 -234
  475. package/dist/plugins/caching-session-store.d.ts +22 -5
  476. package/dist/plugins/caching-session-store.js +43 -4
  477. package/dist/plugins/checkpoint-index-columns.d.ts +33 -6
  478. package/dist/plugins/checkpoint-index-columns.js +39 -3
  479. package/dist/plugins/checkpoint-store-face.d.ts +84 -11
  480. package/dist/plugins/checkpoint-store-face.js +18 -1
  481. package/dist/plugins/device-store-sql.d.ts +2 -2
  482. package/dist/plugins/device-store-sql.js +6 -10
  483. package/dist/plugins/ensure-index.d.ts +13 -1
  484. package/dist/plugins/ensure-index.js +154 -21
  485. package/dist/plugins/ensure-schema-lock-policy.d.ts +0 -15
  486. package/dist/plugins/ensure-schema-lock-policy.js +3 -2
  487. package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
  488. package/dist/plugins/execution-lane-rename-m40.js +10 -0
  489. package/dist/plugins/execution-placement-sql.d.ts +63 -88
  490. package/dist/plugins/execution-placement-sql.js +94 -54
  491. package/dist/plugins/file-run-store.d.ts +89 -35
  492. package/dist/plugins/file-run-store.js +197 -142
  493. package/dist/plugins/host-platform.d.ts +24 -3
  494. package/dist/plugins/host-platform.js +15 -1
  495. package/dist/plugins/image-bake-store-sql.js +2 -0
  496. package/dist/plugins/imported-layer-root-retirement.d.ts +19 -10
  497. package/dist/plugins/imported-layer-root-retirement.js +4 -8
  498. package/dist/plugins/inbox-payload-column.d.ts +14 -0
  499. package/dist/plugins/inbox-payload-column.js +13 -0
  500. package/dist/plugins/k8s-pod-reaper.d.ts +1 -3
  501. package/dist/plugins/k8s-pod-reaper.js +0 -1
  502. package/dist/plugins/leader-run-store-sql.d.ts +22 -0
  503. package/dist/plugins/leader-run-store-sql.js +34 -4
  504. package/dist/plugins/local-checkpoint-store.d.ts +63 -23
  505. package/dist/plugins/local-checkpoint-store.js +204 -81
  506. package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
  507. package/dist/plugins/local-data-dir-modes-m39.js +183 -0
  508. package/dist/plugins/local-docker-capacity.d.ts +93 -0
  509. package/dist/plugins/local-docker-capacity.js +194 -0
  510. package/dist/plugins/local-docker-reaper.d.ts +92 -0
  511. package/dist/plugins/local-docker-reaper.js +102 -0
  512. package/dist/plugins/local-engine-high-water.d.ts +13 -0
  513. package/dist/plugins/local-engine-high-water.js +62 -0
  514. package/dist/plugins/local-session-store.d.ts +442 -143
  515. package/dist/plugins/local-session-store.js +941 -289
  516. package/dist/plugins/local-session-window.d.ts +3 -2
  517. package/dist/plugins/local-session-window.js +3 -2
  518. package/dist/plugins/local-sidecar-quarantine.js +3 -2
  519. package/dist/plugins/local-task-attachment-store.d.ts +20 -1
  520. package/dist/plugins/local-task-attachment-store.js +32 -12
  521. package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
  522. package/dist/plugins/memory-embedder-fingerprint.js +52 -12
  523. package/dist/plugins/memory-embedder.d.ts +1 -1
  524. package/dist/plugins/memory-embedder.js +11 -9
  525. package/dist/plugins/memory-engine-pg.d.ts +7 -5
  526. package/dist/plugins/memory-engine-pg.js +18 -9
  527. package/dist/plugins/memory-engine-sql-apply.d.ts +21 -8
  528. package/dist/plugins/memory-engine-sql-apply.js +7 -9
  529. package/dist/plugins/memory-engine-tidb.d.ts +12 -7
  530. package/dist/plugins/memory-engine-tidb.js +18 -10
  531. package/dist/plugins/memory-optout-grant-store-sql.d.ts +1 -1
  532. package/dist/plugins/memory-optout-grant-store-sql.js +3 -3
  533. package/dist/plugins/memory-run-store.d.ts +39 -27
  534. package/dist/plugins/memory-run-store.js +72 -70
  535. package/dist/plugins/one-time-migration-marker.d.ts +10 -0
  536. package/dist/plugins/one-time-migration-marker.js +7 -0
  537. package/dist/plugins/one-time-migrations.d.ts +325 -74
  538. package/dist/plugins/one-time-migrations.js +705 -230
  539. package/dist/plugins/org-delivery-quota-store.js +2 -1
  540. package/dist/plugins/owner-only-file-mode.d.ts +29 -0
  541. package/dist/plugins/owner-only-file-mode.js +141 -0
  542. package/dist/plugins/permission-rule-store-file.d.ts +11 -11
  543. package/dist/plugins/permission-rule-store-file.js +14 -2
  544. package/dist/plugins/permission-rule-store-sql.d.ts +4 -63
  545. package/dist/plugins/permission-rule-store-sql.js +6 -86
  546. package/dist/plugins/pg-pool.d.ts +3 -32
  547. package/dist/plugins/pg-pool.js +27 -11
  548. package/dist/plugins/release-version.d.ts +20 -0
  549. package/dist/plugins/release-version.js +28 -0
  550. package/dist/plugins/remote-env-adb.d.ts +20 -4
  551. package/dist/plugins/remote-env-adb.js +39 -11
  552. package/dist/plugins/remote-env-e2b.d.ts +19 -28
  553. package/dist/plugins/remote-env-e2b.js +40 -29
  554. package/dist/plugins/remote-env-host.d.ts +11 -10
  555. package/dist/plugins/remote-env-host.js +40 -27
  556. package/dist/plugins/remote-env-k8s.d.ts +45 -47
  557. package/dist/plugins/remote-env-k8s.js +122 -62
  558. package/dist/plugins/remote-env-local-docker.d.ts +152 -15
  559. package/dist/plugins/remote-env-local-docker.js +370 -75
  560. package/dist/plugins/remote-env-ssh.d.ts +8 -2
  561. package/dist/plugins/remote-env-ssh.js +26 -24
  562. package/dist/plugins/remote-scratchpad.js +4 -0
  563. package/dist/plugins/remote-shell.d.ts +59 -30
  564. package/dist/plugins/remote-shell.js +175 -62
  565. package/dist/plugins/retention-declaration.d.ts +37 -0
  566. package/dist/plugins/retention-declaration.js +9 -0
  567. package/dist/plugins/retention-deployment.d.ts +158 -0
  568. package/dist/plugins/retention-deployment.js +119 -0
  569. package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
  570. package/dist/plugins/retention-lane-store-sql.js +103 -1
  571. package/dist/plugins/retention-sql.d.ts +29 -0
  572. package/dist/plugins/retention-sql.js +8 -0
  573. package/dist/plugins/run-preview-m45.d.ts +11 -0
  574. package/dist/plugins/run-preview-m45.js +19 -0
  575. package/dist/plugins/run-store-sql.d.ts +56 -68
  576. package/dist/plugins/run-store-sql.js +127 -116
  577. package/dist/plugins/s3-answer.d.ts +2 -1
  578. package/dist/plugins/sandbox-reaper.d.ts +26 -0
  579. package/dist/plugins/sandbox-reaper.js +2 -0
  580. package/dist/plugins/scenario-prefix-switch-m42.d.ts +54 -0
  581. package/dist/plugins/scenario-prefix-switch-m42.js +55 -0
  582. package/dist/plugins/scheduler-support.d.ts +16 -1
  583. package/dist/plugins/scheduler-support.js +22 -2
  584. package/dist/plugins/schema-consistency.d.ts +42 -15
  585. package/dist/plugins/schema-consistency.js +213 -13
  586. package/dist/plugins/schema-drift-sentence.d.ts +4 -1
  587. package/dist/plugins/schema-drift-sentence.js +4 -4
  588. package/dist/plugins/schema-ensure-legs.d.ts +10 -1
  589. package/dist/plugins/schema-ensure-legs.js +12 -2
  590. package/dist/plugins/schema-indexes.js +3 -0
  591. package/dist/plugins/schema-live-columns.d.ts +14 -5
  592. package/dist/plugins/schema-live-columns.js +10 -10
  593. package/dist/plugins/send-user-file.js +2 -1
  594. package/dist/plugins/session-birth.d.ts +37 -0
  595. package/dist/plugins/session-birth.js +38 -0
  596. package/dist/plugins/session-epoch-anchor.d.ts +40 -12
  597. package/dist/plugins/session-epoch-anchor.js +33 -11
  598. package/dist/plugins/session-ext-table.d.ts +98 -0
  599. package/dist/plugins/session-ext-table.js +134 -0
  600. package/dist/plugins/session-fork.d.ts +16 -2
  601. package/dist/plugins/session-fork.js +3 -0
  602. package/dist/plugins/session-ownership.d.ts +1 -1
  603. package/dist/plugins/session-placement.d.ts +22 -16
  604. package/dist/plugins/session-placement.js +15 -12
  605. package/dist/plugins/session-row-sql.d.ts +20 -7
  606. package/dist/plugins/session-row-sql.js +16 -8
  607. package/dist/plugins/session-run-rollup.d.ts +29 -0
  608. package/dist/plugins/session-run-rollup.js +40 -0
  609. package/dist/plugins/session-store-sql.d.ts +157 -0
  610. package/dist/plugins/session-store-sql.js +398 -0
  611. package/dist/plugins/session-withdraw-sql.d.ts +36 -0
  612. package/dist/plugins/session-withdraw-sql.js +42 -0
  613. package/dist/plugins/session-workspace-cell.d.ts +2 -2
  614. package/dist/plugins/session-workspace-cell.js +2 -2
  615. package/dist/plugins/shared-memory-store-sql.d.ts +10 -69
  616. package/dist/plugins/shared-memory-store-sql.js +15 -14
  617. package/dist/plugins/shell-sentinel-owner-m48.d.ts +20 -0
  618. package/dist/plugins/shell-sentinel-owner-m48.js +3 -0
  619. package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
  620. package/dist/plugins/snapshot-bucket-presence.js +1 -1
  621. package/dist/plugins/sql-driver.d.ts +24 -8
  622. package/dist/plugins/sql-driver.js +43 -9
  623. package/dist/plugins/sql-errors.d.ts +35 -5
  624. package/dist/plugins/sql-errors.js +55 -3
  625. package/dist/plugins/sql-json-column.d.ts +8 -7
  626. package/dist/plugins/sql-json-column.js +10 -6
  627. package/dist/plugins/store-backend.d.ts +114 -18
  628. package/dist/plugins/store-backend.js +340 -87
  629. package/dist/plugins/store-contracts.d.ts +97 -30
  630. package/dist/plugins/store-contracts.js +23 -1
  631. package/dist/plugins/store-fault.d.ts +28 -2
  632. package/dist/plugins/store-fault.js +20 -2
  633. package/dist/plugins/task-attachment-store.d.ts +21 -11
  634. package/dist/plugins/task-attachment-store.js +37 -19
  635. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  636. package/dist/plugins/text-column-capacity.d.ts +21 -0
  637. package/dist/plugins/text-column-capacity.js +5 -0
  638. package/dist/plugins/tidb-pool.d.ts +3 -2
  639. package/dist/plugins/tidb-pool.js +42 -39
  640. package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
  641. package/dist/plugins/web-search.js +5 -3
  642. package/dist/plugins/workflow-run-store-sql.d.ts +6 -1
  643. package/dist/plugins/workflow-run-store-sql.js +11 -5
  644. package/dist/principal-jwt.d.ts +4 -4
  645. package/dist/principal-jwt.js +20 -5
  646. package/dist/principal-shape.d.ts +41 -0
  647. package/dist/principal-shape.js +19 -0
  648. package/dist/question.d.ts +13 -23
  649. package/dist/question.js +21 -39
  650. package/dist/read-face-posture.d.ts +6 -6
  651. package/dist/read-face-posture.js +2 -2
  652. package/dist/request-declaration-refusal.d.ts +35 -12
  653. package/dist/request-declaration-refusal.js +21 -9
  654. package/dist/request-form.d.ts +58 -0
  655. package/dist/request-form.js +72 -0
  656. package/dist/request-image-urls.d.ts +8 -2
  657. package/dist/request-image-urls.js +2 -1
  658. package/dist/request-images.d.ts +20 -0
  659. package/dist/request-images.js +59 -0
  660. package/dist/request-key-closure.d.ts +6 -7
  661. package/dist/request-key-closure.js +15 -2
  662. package/dist/request-value-closure.d.ts +5 -5
  663. package/dist/request-value-closure.js +1 -1
  664. package/dist/resource-suspend.d.ts +7 -16
  665. package/dist/resource-suspend.js +1 -1
  666. package/dist/retired-name-host-notice.d.ts +21 -0
  667. package/dist/retired-name-host-notice.js +12 -0
  668. package/dist/rule-entry-list.d.ts +2 -2
  669. package/dist/rule-entry-list.js +3 -4
  670. package/dist/rule-issue-reading.d.ts +58 -0
  671. package/dist/rule-issue-reading.js +14 -0
  672. package/dist/rules-consent.d.ts +46 -16
  673. package/dist/rules-consent.js +102 -53
  674. package/dist/run-acceptance.d.ts +3 -1
  675. package/dist/run-acceptance.js +3 -0
  676. package/dist/run-local.d.ts +14 -6
  677. package/dist/run-local.js +109 -93
  678. package/dist/run-stale-default.d.ts +7 -0
  679. package/dist/run-stale-default.js +2 -0
  680. package/dist/runs.d.ts +73 -44
  681. package/dist/runs.js +65 -43
  682. package/dist/runtime-caps-resolver.d.ts +36 -14
  683. package/dist/runtime-caps-resolver.js +31 -14
  684. package/dist/runtime-governance.d.ts +2 -2
  685. package/dist/runtime-governance.js +5 -3
  686. package/dist/sealed-key-layout.d.ts +12 -6
  687. package/dist/sealed-key-layout.js +5 -1
  688. package/dist/sealed-key.d.ts +12 -2
  689. package/dist/sealed-key.js +116 -26
  690. package/dist/security.d.ts +133 -161
  691. package/dist/security.js +163 -59
  692. package/dist/sema-config-dir.d.ts +20 -0
  693. package/dist/sema-config-dir.js +27 -2
  694. package/dist/server-secret-env.d.ts +23 -5
  695. package/dist/server-secret-env.js +14 -4
  696. package/dist/service-version.d.ts +2 -1
  697. package/dist/service-version.js +2 -2
  698. package/dist/session-children-retire.d.ts +7 -5
  699. package/dist/session-children-retire.js +7 -2
  700. package/dist/session-deletion.d.ts +88 -0
  701. package/dist/session-deletion.js +8 -0
  702. package/dist/session-erase.d.ts +145 -0
  703. package/dist/session-erase.js +115 -0
  704. package/dist/session-error-wire.d.ts +6 -6
  705. package/dist/session-error-wire.js +1 -1
  706. package/dist/session-incarnation.d.ts +60 -10
  707. package/dist/session-incarnation.js +19 -2
  708. package/dist/session-policy-wire.d.ts +6 -3
  709. package/dist/session-policy-wire.js +11 -1
  710. package/dist/session-resident-background.d.ts +29 -47
  711. package/dist/session-resident-background.js +77 -8
  712. package/dist/session-row.d.ts +3 -2
  713. package/dist/session-rules-seat.d.ts +12 -0
  714. package/dist/session-rules-seat.js +19 -0
  715. package/dist/session-sync-kernel.d.ts +1 -1
  716. package/dist/session-titler.d.ts +7 -15
  717. package/dist/session-titler.js +1 -1
  718. package/dist/session-workspace.d.ts +11 -2
  719. package/dist/session-workspace.js +5 -0
  720. package/dist/sighup-idle.d.ts +1 -1
  721. package/dist/spec-fields.d.ts +57 -3
  722. package/dist/spec-fields.js +72 -51
  723. package/dist/store-posture.d.ts +138 -21
  724. package/dist/store-posture.js +188 -3
  725. package/dist/stored-request-hooks-m20.d.ts +3 -2
  726. package/dist/stored-request-hooks-m20.js +21 -16
  727. package/dist/stores-sql/background-agent-store-sql.d.ts +11 -2
  728. package/dist/stores-sql/background-agent-store-sql.js +73 -20
  729. package/dist/stores-sql/blob-index-sql.d.ts +148 -0
  730. package/dist/stores-sql/blob-index-sql.js +154 -0
  731. package/dist/stores-sql/checkpoint-store-sql.d.ts +34 -29
  732. package/dist/stores-sql/checkpoint-store-sql.js +86 -71
  733. package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
  734. package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
  735. package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
  736. package/dist/stores-sql/mailbox-store-sql.js +31 -32
  737. package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +30 -81
  738. package/dist/{plugins → stores-sql}/pg-session-storage.js +123 -244
  739. package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +197 -223
  740. package/dist/{plugins → stores-sql}/retention-store-sql.js +148 -207
  741. package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
  742. package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
  743. package/dist/stores-sql/session-policy-store-sql.d.ts +33 -11
  744. package/dist/stores-sql/session-policy-store-sql.js +44 -36
  745. package/dist/stores-sql/session-write-hooks.d.ts +65 -0
  746. package/dist/stores-sql/session-write-hooks.js +2 -0
  747. package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +14 -15
  748. package/dist/{plugins → stores-sql}/tidb-session-storage.js +22 -28
  749. package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +67 -138
  750. package/dist/{plugins → stores-sql}/tidb-session-store.js +130 -200
  751. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  752. package/dist/stores-sql/tool-result-store-sql.js +1 -1
  753. package/dist/stores-sql/windowed-session-storage.d.ts +61 -0
  754. package/dist/stores-sql/windowed-session-storage.js +70 -0
  755. package/dist/submission-exit.d.ts +50 -0
  756. package/dist/submission-exit.js +108 -0
  757. package/dist/task-a2a.d.ts +7 -1
  758. package/dist/task-a2a.js +18 -17
  759. package/dist/task-cwd.d.ts +31 -3
  760. package/dist/task-cwd.js +25 -1
  761. package/dist/task-mcp.d.ts +11 -3
  762. package/dist/task-mcp.js +16 -19
  763. package/dist/task-settings.d.ts +23 -14
  764. package/dist/task-settings.js +35 -13
  765. package/dist/terminal.d.ts +25 -43
  766. package/dist/terminal.js +31 -11
  767. package/dist/tool-approval.d.ts +152 -137
  768. package/dist/tool-approval.js +199 -199
  769. package/dist/trace/core-keyset-guard.d.ts +22 -9
  770. package/dist/trace/core-vocab-wire.d.ts +95 -0
  771. package/dist/trace/core-vocab-wire.js +15 -0
  772. package/dist/trace/engine-notice-wire.d.ts +1 -1
  773. package/dist/trace/engine-notice-wire.js +2 -0
  774. package/dist/trace/exit-seat.d.ts +52 -4
  775. package/dist/trace/exit-seat.js +43 -36
  776. package/dist/trace/extension-call-face.d.ts +3 -2
  777. package/dist/trace/extension-call-face.js +3 -1
  778. package/dist/trace/ledger-events.d.ts +4 -7
  779. package/dist/trace/ledger-sink.d.ts +1 -30
  780. package/dist/trace/ledger-sink.js +0 -11
  781. package/dist/trace/model-segment.d.ts +5 -5
  782. package/dist/trace/model-segment.js +5 -5
  783. package/dist/trace/project.d.ts +88 -58
  784. package/dist/trace/project.js +64 -28
  785. package/dist/trace/redact.d.ts +124 -28
  786. package/dist/trace/redact.js +936 -328
  787. package/dist/trace/self-minted-wire.d.ts +750 -29
  788. package/dist/trace/self-minted-wire.js +262 -25
  789. package/dist/trace/sema-provenance.d.ts +45 -41
  790. package/dist/trace/sema-provenance.js +81 -10
  791. package/dist/trace/task-notification-facets.d.ts +70 -2
  792. package/dist/trace/task-notification-facets.js +71 -1
  793. package/dist/trace/wire-projection-faces.d.ts +43 -8
  794. package/dist/trace/wire-projection-faces.js +28 -3
  795. package/dist/unread-request-members.d.ts +66 -0
  796. package/dist/unread-request-members.js +41 -0
  797. package/dist/upstream-body.d.ts +23 -0
  798. package/dist/upstream-body.js +16 -0
  799. package/dist/url-refusal.d.ts +3 -3
  800. package/dist/url-refusal.js +11 -4
  801. package/dist/user-authored-text.d.ts +1 -1
  802. package/dist/user-input-ledger.d.ts +7 -8
  803. package/dist/user-input-ledger.js +1 -1
  804. package/dist/web-admin-client/apply-effective.d.ts +6 -3
  805. package/dist/web-admin-client/apply-effective.js +33 -40
  806. package/dist/web-admin-client/apply-ledger.d.ts +16 -7
  807. package/dist/web-admin-client/apply-ledger.js +5 -2
  808. package/dist/web-admin-client/candidate-gate.d.ts +35 -3
  809. package/dist/web-admin-client/candidate-gate.js +53 -5
  810. package/dist/web-admin-client/center-request.d.ts +6 -3
  811. package/dist/web-admin-client/center-request.js +17 -16
  812. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  813. package/dist/web-admin-client/effective-keys.js +1 -2
  814. package/dist/web-admin-client/entry-metadata.d.ts +8 -0
  815. package/dist/web-admin-client/entry-metadata.js +13 -0
  816. package/dist/web-admin-client/facade.d.ts +1 -1
  817. package/dist/web-admin-client/http-client.d.ts +2 -1
  818. package/dist/web-admin-client/http-client.js +40 -13
  819. package/dist/web-admin-client/mcp-revocation.d.ts +1 -1
  820. package/dist/web-admin-client/mcp-revocation.js +2 -1
  821. package/dist/web-admin-client/read-warnings.d.ts +1 -0
  822. package/dist/web-admin-client/read-warnings.js +1 -0
  823. package/dist/web-admin-client/restart-signal.js +13 -6
  824. package/dist/web-admin-client/skills-mcp.d.ts +22 -8
  825. package/dist/web-admin-client/skills-mcp.js +26 -21
  826. package/dist/web-admin-client/types.d.ts +35 -51
  827. package/dist/workspace-space.d.ts +3 -2
  828. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
  829. package/package.json +16 -12
  830. package/scripts/drain-check-local.mjs +118 -173
  831. package/scripts/drain-check.mysql.sql +20 -16
  832. package/scripts/drain-check.pg.sql +20 -16
  833. package/wire-changes/server@7.147.0.json +813 -0
  834. package/wire-changes/server@7.148.0.json +1517 -0
  835. package/dist/http/memory-control-plane-corrupt-reply.d.ts +0 -25
  836. package/dist/http/memory-control-plane-corrupt-reply.js +0 -19
  837. package/dist/http/memory-layer-reply.d.ts +0 -30
  838. package/dist/http/memory-layer-reply.js +0 -24
  839. package/dist/lsp/e2b-bridge.d.ts +0 -57
  840. package/dist/lsp/e2b-bridge.js +0 -139
  841. package/dist/lsp/e2b-manager.d.ts +0 -93
  842. package/dist/lsp/e2b-manager.js +0 -178
  843. package/dist/lsp/lsp-frames.d.ts +0 -14
  844. package/dist/lsp/lsp-frames.js +0 -25
  845. package/dist/lsp/manager.d.ts +0 -129
  846. package/dist/lsp/manager.js +0 -127
  847. package/dist/lsp/types.d.ts +0 -12
  848. package/dist/lsp/types.js +0 -2
  849. package/dist/lsp/ws-transport.d.ts +0 -40
  850. package/dist/lsp/ws-transport.js +0 -210
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,25 +57,26 @@ 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` 同值)。
60
61
  > 🔴 **显式 `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
62
 
62
63
  > 🔴 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`)。
63
64
 
64
- > 🔴 **`REMOTE_EXEC` 的形是部署声明的有序车道表**(契约 `runtime.execution.lanes` 渲成 env 的形:逗号分隔、首项是缺省车道;单词 = 一条车道的表,读法与以前逐字同)。每段逐字是一个车道词(不 trim、不折大小写),不许空段、不许同一车道出现两次;写着契约退役车道值(如 `remote-docker`)⇒ 拒句给出去向。**本引擎每个进程只装一条车道的执行座**:表里不止一条(`REMOTE_EXEC=k8s,device`)⇒ 起服拒,拒句点名全部声明的车道与出路(一台引擎一条车道:`REMOTE_EXEC=k8s`,其余车道各起一台)—— 不只服务首项而让其余车道无声地什么都不跑。
65
+ > 🔴 **`REMOTE_EXEC` 的形是部署声明的有序车道表**(契约 `runtime.execution.lanes` 渲成 env 的形:逗号分隔、首项是缺省车道;单词 = 一条车道的表,读法与以前逐字同)。每段逐字是一个车道词(不 trim、不折大小写),不许空段(只含空白也算空)、不许同一车道出现两次,`none`(不给工具执行环境)只能独成一表;拒句一次列出全部问题,以契约的车道表规则句收尾;写着契约退役车道值(如 `remote-docker`)⇒ 拒句给出去向。**本引擎每个进程只装一条车道的执行座**:表里不止一条(`REMOTE_EXEC=k8s,device`)⇒ 起服拒,拒句点名全部声明的车道与出路(一台引擎一条车道:`REMOTE_EXEC=k8s`,其余车道各起一台)—— 不只服务首项而让其余车道无声地什么都不跑。
65
66
 
66
- > 🔴 **7.104.0 起多租户姿态不许跑不分租户的执行车道**:`REQUIRE_PRINCIPAL=true` ∧ 最终生效的车道在执行车道能力表上不分租户 ⇒ **拒启**。按表不按词,今天覆盖 `host` / `ssh` / `adb`(所有任务落同一个共享目标:worker 宿主 / 一台 SSH 对端 / 一台 ADB 设备)。判的是合成后的车道(env 的 `REMOTE_EXEC`、`CONFIG_PROVIDER=local` 而 `REMOTE_EXEC` 未设的隐式 host、local 形 `config.d/remote-exec.json` 的覆写,在中心拉取与任何配置应用之前合成)。拒因(逐字,以 host 为例):`REQUIRE_PRINCIPAL=true (multi-tenant posture) with the "host" execution lane runs every tenant's Bash on one shared target — this lane does not separate tenants; set REMOTE_EXEC to e2b / k8s / local-docker / device, or unset REQUIRE_PRINCIPAL for a single-user box`。车道 × 租户形:
67
+ > 🔴 **7.104.0 起多租户姿态不许跑不分租户的执行车道**:`REQUIRE_PRINCIPAL=true` ∧ 最终生效的车道在执行车道能力表上不分租户 ⇒ **拒启**。按表不按词,今天覆盖 `host` / `ssh` / `adb`(所有任务落同一个共享目标:worker 宿主 / 一台 SSH 对端 / 一台 ADB 设备)。判的是合成后的车道(env 的 `REMOTE_EXEC`、local 形 `config.d/remote-exec.json` 的声明或覆写,在中心拉取与任何配置应用之前合成;7.147.0 起两处都没有 ⇒ 拒启)。拒因(逐字,以 host 为例):`REQUIRE_PRINCIPAL=true (multi-tenant posture) with the "host" execution lane runs every tenant's Bash on one shared target — this lane does not separate tenants; set REMOTE_EXEC to e2b / k8s / local-docker / device, or unset REQUIRE_PRINCIPAL for a single-user box`。车道 × 租户形:
67
68
  >
68
69
  > | 车道 | 单用户 | 多租户(`REQUIRE_PRINCIPAL=true`) | `READ_FACE` 缺席时的读面缺省(7.133.0 起车道行声明) |
69
70
  > |---|---|---|---|
70
- > | `host`(显式 / `CONFIG_PROVIDER=local` 隐式 / 文件覆写) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
71
+ > | `host`(env 显式 / 文件声明或覆写) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
71
72
  > | `ssh`(一台常驻对端) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
72
73
  > | `adb`(一台设备) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
73
74
  > | `e2b` / `k8s` / `local-docker`(每任务一只沙箱) | ✅ | ✅ | **`open`**(来源 **`lane`**,单用户 / 多租户同;车道行显式声明)。🔴 **挂载告诫**:把宿主目录(`hostPath` / bind mount)或共享卷挂进沙箱的部署,`open` 面上这些宿主文件对模型零问可读 —— 显式 `READ_FACE=roots`(env 恒赢)或给它们加读拒行(`READ_DENY_PATTERNS`) |
74
75
  > | `device`(每人自己的设备) | ✅ | ✅ | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
75
- > | 未点名(云形进程内 stub,零手带工具、非手带工具照常执行) | ✅(`REQUIRE_EXECUTION_LANE=true` 可改成拒启) | ✅(同左) | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
76
+ > | 未点名(7.147.0 起) | ❌ 拒启(拒句一行给修法;旧拒启旋钮 `REQUIRE_EXECUTION_LANE` 已退役,设了即拒启) | ❌ 拒启 | — |
77
+ > | `none`(声明「不给智能体的工具执行环境」:执行环境工厂与执行环境座都不给,零手带工具、非手带工具照常执行;`GET /v1/capabilities` 报 `none`;7.146.0 及以前「非 local 没写车道」的进程内桩改名而来) | ✅ | ✅ | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
76
78
 
77
- > 🔴 **硬断预告(7.147.0 起)**:每个部署都要显式声明自己最终生效的执行车道 —— env `REMOTE_EXEC`(`CONFIG_PROVIDER=local` 形下 `config.d/remote-exec.json` 也算);没有声明 ⇒ 拒启(`CONFIG_PROVIDER=local` 不再隐含 `host`,非 local 未设也不再静默落进「不挂手」的进程内桩)。「不给智能体的工具提供执行环境」改写成车道词 `none`,取代词表外的状态名 `in-process`(`GET /v1/capabilities` 的 `executionLane.provider`、`GET /v1/sessions/:id/execution-placement` 的 `lane`、409 的 `boundLane`、403 `execution_lane_not_allowed` 的 `extra.lane` 换值);`REQUIRE_EXECUTION_LANE` 退役(设了任何非空值 ⇒ 拒启,拒句指向 `REMOTE_EXEC`),自己拼的清单或拷过随包部署产物的 env 里写着它的,升级前要去掉。库里或数据根里已有记着 `in-process` 的会话 / 审批行的部署(跑过未点名车道的进程内桩的),须停全部旧副本、跑一次性迁移、再起 7.147.0,不可滚动混跑;web-admin 渲染 env 的中心 worker,须先停渲 `REQUIRE_EXECUTION_LANE`,再升到 7.147.0。照旧行为的一行修法:7.146.0 里非 local 且未设车道的 ⇒ 7.147.0 写 `REMOTE_EXEC=none`,`CONFIG_PROVIDER=local` 且未设的 ⇒ 写 `REMOTE_EXEC=host`(7.146.0 及以前不认 `none` 这个词,别提前写)。7.146.0 本身零行为变化。
79
+ > 🔴 **硬断(自 7.147.0 起)**:每个部署都要显式声明自己最终生效的执行车道 —— env `REMOTE_EXEC`(`CONFIG_PROVIDER=local` 形下 `config.d/remote-exec.json` 也算);没有声明 ⇒ 拒启,拒句以 `No execution lane is declared` 起、一行给出照旧行为的修法(`CONFIG_PROVIDER=local` 不再隐含 `host`,非 local 未设也不再静默落进「不挂手」的进程内桩)。「不给智能体的工具提供执行环境」写成车道词 `none`,取代词表外的状态名 `in-process`(wire 五处换值,零别名:`GET /v1/capabilities` 的 `executionLane.provider`、`GET /v1/sessions/:id/execution-placement` 的 `lane`、409 `execution_lane.not_served` 与 409 `execution_lane.tree_bound` 的 `boundLane`、403 `execution_lane_not_allowed` 的 `extra.lane`);`REQUIRE_EXECUTION_LANE` 已退役(设了任何非空值 ⇒ 拒启,拒句指向 `REMOTE_EXEC`),自己拼的清单或拷过随包部署产物的 env 里写着它的,升级前要去掉。库里或数据根里记着 `in-process` 的会话 / 审批行由一次性迁移 M40 在起服时改成 `none`:停全部旧副本、再起 7.147.0,不可滚动混跑;SQL 后端上判活窗(`REAP_RUN_STALE_SEC`)内还有 run 在心跳 ⇒ 起服拒 `config.execution_lane_rename_refused`,拒句给出可再起的时刻;守卫只看心跳,没有在跑的 run 的空闲旧副本它看不见 —— 停全部旧副本是运维的动作(细节与回滚见 MIGRATION 7.147.0 节「执行车道恒点名」一条)。web-admin 渲染 env 的中心 worker,须先停渲 `REQUIRE_EXECUTION_LANE`、同时让每个 worker 的 env 带 `REMOTE_EXEC`,再升到 7.147.0。照旧行为的一行修法:7.146.0 里非 local 且未设车道的 ⇒ 7.147.0 写 `REMOTE_EXEC=none`,`CONFIG_PROVIDER=local` 且未设的 ⇒ 写 `REMOTE_EXEC=host`(7.146.0 及以前不认 `none` 这个词,别提前写)。
78
80
 
79
81
  > **设备 WS 未鉴权握手的 per-remote 帽(`DEVICE_MAX_PREAUTH_PER_REMOTE`,缺省 8)按真实对端计**:缺省按 socket 对端计 —— 引擎在反代 / 托管入口 / 公司 NAT 之后时,全员同一个 socket 对端,共用这 8 个名额。把**自己的**反代写进 `DEVICE_TRUSTED_PROXY_CIDRS`(CIDR,逗号分隔;缺省空 = 不读任何代理头):socket 对端落在名单内才读 `X-Forwarded-For`,取自右向左第一个不在名单内的地址作帽键;名单外的对端一律不采代理头;请求头行数到顶(100 行)/ 头缺席 / 坏形 / 全在名单内 ⇒ 退回 socket 对端 + 一行 warn `device_preauth_forwarded_unusable`。名单每项是 IP 地址或 CIDR(裸地址 = 单台);名单里的来源能改帽键,所以只放自己控制的反代;前缀 0、主机位非零、不是 IP 地址或 CIDR 的项、空项拒启。不读 `Forwarded` / `X-Real-IP`,不支持 PROXY protocol。全局帽 64 不变。这根旋钮的语义行与缺省句也由 `GET /v1/config/catalog` 报出(域 `orchestration`,安全轴)。
80
82
 
@@ -91,8 +93,12 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
91
93
  > 写在 `config.d/mcp.json`,键与中心发布的 `mcp` 域同形,没有另一个工具目录。读之前先判:从 `/` 走到 `config.d`、再到读者会读的每一份文档(符号链接照跟、跟到哪判到哪),
92
94
  > 每一级属主只能是本服务的用户、root 或 `CONFIG_LOCAL_OWNER_UID`(多信的那一个 uid;形错拒启),不得组 / 他人可写(属主可信的粘滞目录如 `/tmp` 除外,但要读的一项缺在这样的目录里也拒),
93
95
  > 文档是常规文件且 ≤ 16 MiB,整条路径链接合计 ≤ 40 跳、不得指向 procfs 一类内核对象。
96
+ > 同一只判官还守另两份「盘上字节决定起什么」的文件:中心配置的盘上 LKG(读回之前;中心慢或断时它就是本服务的配置)与调度共享店(`SCHEDULER_STORE_PATH`,缺省 `<SEMA_CONFIG_DIR>/scheduled_tasks.json`,每次读写之前)——
97
+ > 别人改得动 ⇒ LKG 不读回(`config_lkg_unusable` 一行带同族拒句,env 兜底)、调度店这一次读写整条拒(运维日志 `scheduler_store_untrusted` 带同族拒句);可信属主集同一张(含 `CONFIG_LOCAL_OWNER_UID`)。
94
98
  > 起服不过 ⇒ 拒启(fatal 标题 `[config.dir_untrusted]`;`run-local` 退 2);起服之后才变坏 ⇒ 那一拍 `web_admin_refresh_failed` 带同一句、新字节不读、已应用的配置不动。拒句点名那一个路径、实测值与改法
95
- > (`chmod go-w` / `chown` / `CONFIG_LOCAL_OWNER_UID=<uid>`)。Windows 上属主与模式位不可判:只判结构,另记一次 fail-open 告警。容器形(rootless / userns-remap / ConfigMap / fsGroup)见源码仓文档 DEPLOY-PREREQS 的 7.133.0 节(不随包)。
99
+ > (`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`。
100
+ > 谁建的根:本服务在根缺席时**自己首建**的目录 —— 数据根、`SEMA_CONFIG_DIR` 声明的配置根、本地配置根,与每一级新建的祖先 —— 一律 `0700`(创建时给的模式;umask 只减位不加位,所以不会比 0700 更宽 —— umask 002 的 Debian / Ubuntu 上也不会建出 0775 而在下一次起服被这一判官拒;umask 连属主位也掩掉的极端值,如 0200,会让目录比 0700 更窄,那是 umask 自己的意思);
101
+ > 别的工具(`mkdir -p`、编排器、壳)建出来的 0775 根照拒,改法同上。**存量**的 0775 根(老版本在 umask 002 上建出来的)判官照拒、拒句给 `chmod go-w`,本服务不替人改一个判官刚拒掉的目录的模式。
96
102
 
97
103
  > 🔴 **本地目录顶替 env 模型表要配置根上的一份确认(7.143.0 起)**:`config.d/models.json` 只要有启用条目就管模型面 —— 采纳之后服务表整张换成目录表。设了 `MODEL_ID`(env 模型表已声明)时,这一换只在配置根(放 `config.d/` 的那个目录)上的
98
104
  > `config.env-models.json` 读作 `{"envModels":"replaced"}` 时发生;缺席 / `{"envModels":"kept"}` / 读不懂 ⇒ 这份目录的模型面被拒(码 `config.catalog_replaces_env_models`,`GET /v1/models` 的 `modelPlane.planeRefusalCode` 与 `/health.modelRefusals.plane` 报它),
@@ -148,8 +154,9 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
148
154
  - **凭据什么时候生效**:网关 key `MODEL_API_KEY`(以及 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)在启动时读一次,**换值 = 重启本服务**
149
155
  —— 没有热应用通道(以前的版本同样如此:这几把 key 只来自启动时的环境变量;引擎 7.51.0 起模型路由的构造配置也只读一次)。
150
156
  目录条目**自己的**凭据(`apiKeyEnv` 点名的变量、`sealedApiKey`)跟着中心 / config.d 的模型面热应用走,不用重启:新起的任务与调用
151
- 用新的一代(在跑的任务用它起跑时那一代)。两处例外:降级壳焊住的目标连凭据身份一起按 `degrade-route` 重启片走;**记忆整理席**的模型在启动时定
152
- (重启生效),它的凭据却跟着热应用 —— 同名条目把端点和凭据一起换掉时,重启之前整理请求会带**新**凭据打**旧**端点,这种换法之后请重启本服务。
157
+ 用新的一代(在跑的任务用它起跑时那一代)。启动时焊死模型的两只座是一条规则:降级壳焊住的目标、**记忆整理席**解到的条目,凭据**身份**随焊住的模型一起冻结
158
+ (`apiKeyEnv` 点名的变量名照旧每次按名现读值 —— 同名轮换不用重启;重贴 `sealedApiKey` / 换变量名 = 新身份),同名条目把端点和凭据一起换掉之后、重启之前,
159
+ 这两只座打的仍是焊住的旧端点、带的是旧一代的凭据(绝不把新凭据发往旧端点);身份或端点变了 ⇒ `/health` 报 `restartRequired`(`degrade-route` / `memory-consolidation`)。
153
160
  - **`agents[].model` 写成内联 Model 对象时,这一只模型的请求带哪条凭据**:本服务只验 `id` 非空,引擎对内联对象原样信任、不查目录;每次请求只发**一条**凭据 —— 凭据词头与部署钥互斥,只发赢家。
154
161
  优先序:这次调用的 per-model 钥(本服务按内联对象的 `name` 去目录找同名条目的 `apiKeyEnv` / `sealedApiKey`)> 内联对象自己 `headers` 里的凭据词头 > 部署网关钥(`MODEL_API_KEY`)。三种构造:
155
162
  ① 内联对象声明了凭据词头(如 `x-team-token`)、没有同名目录条目带自己的钥、部署只有网关钥 ⇒ 内联头赢:这只子代的请求带 `x-team-token`、**不带** `authorization`(网关钥不发,所以要求 `authorization` 的上游会拒这条请求);
@@ -166,6 +173,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
166
173
  brain、带着**它自己的** `apiKeyEnv` 钥匙打到网关。不合 settings-schema 形的条目同律(码 `config.model_entry_invalid`,如非 http(s)
167
174
  的 `baseUrl` —— 此前被「退回部署网关」;读腿逐条丢掉的条目同样留名字,点它照样 400);目录里**每条**都不可用 ⇒ 那一代模型面这一组被拒。
168
175
  🔴 **日志与拒因句里的条目名先过名筛**(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 回显不在此列(它回显的是调用方自己点的那个名)。
176
+ 🔴 **进程写进 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)不经本服务的出口(见下文「网关密钥」那一句)。
169
177
  🔴 **被拒条目的名字不许被档位 / 别名铸到别的条目上**:不可用条目恰好叫 `sonnet` / `opus` / `pro` / `best` 这类档位词或 CC 别名、
170
178
  而档位表会铸出这个词时,这份候选的模型面整组被拒(码 `config.model_refused_name_minted`,warn `web_admin_model_plane_refused` 点名
171
179
  「被拒名 → 目标条目」)—— 否则显式点它、或绑它的角色,会静默跑在另一条条目的端点与账号上;出路:改名 / 修好条目,或改绑那条档位绑定。
@@ -173,10 +181,12 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
173
181
  - **会话自动标题与 hooks 的 `prompt` / `agent` 条目走哪条线路,和主请求读同一只判据**:`provider="anthropic"` 的模型只在 anthropic 路由装上(配了 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)时走云 `/v1/messages`;
174
182
  没装(如 `MODEL_PROVIDER=anthropic` 却只配了 `MODEL_GATEWAY_BASEURL` + `MODEL_API_KEY`)时主请求落网关,这两个面的廉价直连调用同样落网关的 openai 兼容线路(`/chat/completions`),
175
183
  端点与凭据照网关的来 —— 此前它们按 `provider` 字面去找一条没装的 anthropic 路由而整条拒发,会话列表没有自动标题。网关端点也解析不到才拒(`config.model_route_endpoint_missing`)。
184
+ - **已知限制:hook 的 LLM 出口不拦明文非回环(会话自动标题与 hooks 的 `prompt` 条目的直连调用)**:这两个面的请求由本服务自己发,发送前过引擎的路由门(凭据 ↔ 端点配对、来源绑定),但那一道门不问「经什么链路发」—— 目录条目的 `baseUrl`(没写时落到所在腿的根:网关腿是 `MODEL_GATEWAY_BASEURL`,anthropic 腿是 `ANTHROPIC_BASE_URL`)写成 `http://<非回环主机>` 且带 key,请求照带着 key 经明文链路发出,**今天不拒**。引擎的明文判官到货前由部署自审:被这两个面选中的目录条目(或它们落到的腿的根)只写 `https://` 或回环地址。判官到货后本条改为拒(届时 MIGRATION 另写升级前做)。
176
185
  - **预览与自动标题只读你自己写的那段文字**:会话列表的 `objectivePreview`、run / leader 行的 `objective_preview`、自动标题取材、收件箱 `objective` 与任务视图 `name` 读同一张闭集 ——
177
186
  引擎注入的 `<system-reminder>` / `<task-notification>` / `<attachment>` 块,以及壳铸进用户轮的**本地命令回显**(`<command-name>` / `<command-message>` / `<command-args>`、`<local-command-stdout>` / `<local-command-stderr>` / `<local-command-caveat>`、
178
187
  `<bash-input>` / `<bash-stdout>` / `<bash-stderr>` / `<bash-exit-code>`、`<user-memory-input>`)都不算你写的:`/compact` 一类本地命令之后的下一条提交,预览与标题取的是随后那句话,不再是回显;只有回显、没有你的话 ⇒ run / leader 行的预览是空串(任务名折成 `(notification turn)`),会话列表的预览回落到更早的一句、都没有则缺席(`null`)。
179
188
  代价:正文里真的写了这些开标签字面的那一段在预览 / 标题里被剥(只是**提到**这些词的原样保留),原文在会话历史读面完整可见。
189
+ 会话列表的预览就是 run 行那一列(最近一条带你的话的 run;SQL 后端与 `DB_BACKEND=local` 同读,`?q=` 搜任一 run 的这一列):零 run 的会话(fork / 导入 / 同步来的)没有预览,靠标题认;run 中途补的话(steer)不顶替那条 run 的目标。
180
190
  - **装了 prompt 屏的部署,预览与标题只取引擎原样交付的目标**(7.145.0+):部署座的 mod 包或请求的 `settings.hooks` 声明了 `prompt.submit` / `classic.UserPromptSubmit` 的腿,建 run 行时不写预览、续跑输入里的目标留空、fleet 行不带名;
181
191
  目标的受理帧原样送达(不带 `rewrittenBy`)⇒ 那一拍补上预览、fleet 行名并起标题;屏改写过 / 丢弃 / 抛错 ⇒ 这些面一直缺席,标题不自动起 —— 屏前文本不落进 run 行、续跑输入,也不送进标题模型。
182
192
  续跑输入里的目标在这类腿上一直留空,所以 wake 新建的 run 行没有预览、续跑腿的 fleet 行不带名(原样交下也一样)。订 `prompt.*` / `classic.*` 通配的只观察型运维 mod 也被算成屏(判据读声明、与引擎的通配语义一致),这类部署上只回结果的腿(`verify` / `cascade`)与 wake / 续跑腿同样缺席,其余腿在受理那一拍补上。
@@ -199,7 +209,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
199
209
  MODEL_FIRST_BYTE_TIMEOUT_MS=8000 # fetch() → 响应头的等待(7.103.0 起;取代退役的 MODEL_CONNECT_TIMEOUT_MS —— 设了旧键即拒启)。
200
210
  # 不设 = core 缺省(本仓不抄那个数,见 core 7.33.x MIGRATION);窗上另加 core 按 body 大小给的上传宽限,
201
211
  # 到点 core 自有一次加宽重试(不吃重试上限;配了备用网关也一样,之后才切备 —— server 不铸 firstByteRetries)、终局句点名本键。自托管慢网关(prefill 在响应头之前)
202
- # 按排队 + prefill 留足;failover 拓扑须显式设(未设时预算短的调用切不到备;见 DEPLOY-PREREQS)
212
+ # 按排队 + prefill 留足;failover 拓扑须显式设(未设时预算短的调用切不到备)
203
213
  MODEL_FIRST_TOKEN_TIMEOUT_MS=30000 # 响应头之后迟迟不吐第一个 delta(网关 hang);reasoning 的首个 thinking 也算首 token
204
214
  # 缺省 600000(7.71.0 起,原 120000):自托管后端长 prefill 下首字合理地要等几分钟,
205
215
  # 120s 会把一次正常的慢启动判成失败去重试。快失败部署显式设回 120000
@@ -297,8 +307,9 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
297
307
  # 不想要这两类触发就显式设回 rate_limit,breaker_open。拼错词 boot 拒启(不静默忽略)。
298
308
  ```
299
309
  - 🔴 **思考档两键自 7.57.0 起是闭集拒启族**(此前拼错**静默丢**):`MODEL_DEFAULT_THINKING` 收
300
- `minimal|low|medium|high|xhigh|max`(llm-core `ThinkingLevel` 六档)加哨兵词 `off`(= 显式「本部署
301
- 不设模型级默认档」,与不设同义);`MODEL_REASONING_EFFORT_LEVELS` 收这六档的**子集**(CSV,声明
310
+ `minimal|low|medium|high|xhigh|max`(llm-core `ThinkingLevel` 六档)加 `off`(7.148.0 起 = 显式「本部署
311
+ 的模型缺省**不思考**」,铸进引擎 `Model.defaultThinking: "off"`:没点档的请求带显式「关」(网关有关思考拼法处写那只关键),缺省开思考的网关(如 Qwen chat template)也就不思考;显式点了档的请求照点的档;7.147.x 及以前它是
312
+ 哨兵词、与不设同义 —— 想要「不设、交给模型自己的缺省」请**不设**这根旋钮);`MODEL_REASONING_EFFORT_LEVELS` 收这六档的**子集**(CSV,声明
302
313
  「本 openai 端点吃得下哪些档」,高档 clamp-down 不 422;`off` 不是档位,写在这里拒启并点破)。
303
314
  未知词 **boot 拒启并点名坏词**——与 `MODEL_DEGRADE_ON` 同律:`hight` 此前静默落成「无默认档」、
304
315
  `med,high` 此前静默窄成只剩 `high` 一档,而这根旋钮的全部用途就是宣告高档可用,静默落空 = 高档
@@ -311,12 +322,22 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
311
322
  只有 `qwen-chat-template`(`chat_template_kwargs.enable_thinking=false`)关得掉,而 `MODEL_EXTRA_BODY`
312
323
  穿不过引擎的 OpenAI 保留键表 —— 不设它,这类部署每一轮都白烧一段思考且读数上看不出来。它与
313
324
  `MODEL_REASONING_EFFORT_LEVELS` 铸进**同一个** `compat` 对象(两根旋钮同时设两键都在)。
314
- - 🔴 **逐模型声明(目录腿):`models.models[].compat`**(settings-schema ≥1.10.0;7.69.0 消费)——
315
- 同一张 `OpenAICompletionsCompat` 五键(`thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
316
- `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`)可以**按模型**写在
317
- `config.d/models.json` 或配置控制面的目录条目上,逐键盖过上面两根 env 缺省(BL-8:没写的键仍继承 env)。
318
- **坏形/坏词/未知键 ⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
325
+ - 🔴 **逐模型声明(目录腿):`models.models[].compat`**(settings-schema ≥1.10.0;7.69.0 消费;7.147.0 起按线路读)——
326
+ 键集由条目的 `api` 定:`openai-completions` 条目是 `OpenAICompletionsCompat` 五键(`thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
327
+ `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`);`anthropic-messages` 条目是 `AnthropicMessagesCompat` 五键
328
+ (`thinkingMode`:`budget` | `adaptive`、`effortLevels`、`supportsTemperature` / `contextManagement` / `interleavedThinking` 三个布尔;7.146.x 及更早这一线路的 `compat` 整只丢)。
329
+ 可以**按模型**写在 `config.d/models.json` 或配置控制面的目录条目上,逐键盖过上面 env 缺省(BL-8:没写的键仍继承 env);
330
+ env 缺省只铸 openai 线路的键,只盖同一线路的条目(anthropic 条目不继承它们)。
331
+ **坏形/坏词/未知键(含写了别的线路的键)⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
319
332
  点名 warn(引擎回落到自己的推断 = 今天的行为);一条写坏的声明**不会**让这只模型或整套目录消失。
333
+ - 🔴 **逐档思考声明(目录腿):`models.models[].thinkingLevelMap`**(settings-schema 20.0.0;7.147.0 消费)—— 这只端点每一档思考发什么值:
334
+ 键是 `off` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`,值是非空串(那一档发给端点的拼写)或 `null`(这一档端点不支持),没写的档引擎照自己的推断。
335
+ 本服务原样交给引擎(不补缺省、不另判),`GET /v1/models` 的行原样带它;openai-completions / responses 条目上引擎据它发请求、答 `supportsThinkingOff` / `supportedEffortLevels`
336
+ (`null` 档不进 `supportedEffortLevels`;`openai-completions` 条目的 `compat.thinkingFormat` 缺席或是 `openai` 时,`off` 写成非空串 ⇒ 关得掉,写 `null` 或不写 ⇒ 最低一席是 `low`;
337
+ 其余 `thinkingFormat` 关不关得掉由引擎按方言自判,不读 `off`)。⚠️ **anthropic-messages 条目上当前引擎(core 7.65.0)整个不读这张表**:原样带、零效果 ——
338
+ `null` 档照旧在选项集里、请求照发,`off: null` 不改「关得掉」;那条线上要「关不掉」写 `compat.thinkingMode: "adaptive"`,要收窄档位写 `compat.effortLevels`。
339
+ 形由契约包判:未知键 / 非对象 / 空串 / 非串非 `null` ⇒ **那一条被丢**(读腿点名);被 `roles` / `atMention.only` / `tierGroups` 引用的条目坏了 ⇒ 契约包把整个 `models` 域读成缺省,本服务据此**整份拒候选**(停在上一份好配置;本地 `config.d` 起服回落 env)。
340
+ env 腿(`AGENT_MODEL_*`)没有这一席。
320
341
  - 反应式降级 brain 包在**最外层**;fallback brain 用自己的凭据(decorator 清掉主模型的 per-call key 防外泄给别的 provider)。**7.106.0+**:
321
342
  降级目标的自有凭据按**启动那一代的身份**取值(env 变量名 / 托管密钥),与目标端点同代 —— 同名变量换值热生效;目录换 key 名字 / 重贴托管密钥 / 换端点
322
343
  要按 `/health.restart.reasons ∋ degrade-route` 重启。**坑**:`MODEL_DEGRADE_TO` 最好别和被限流的是同一网关/账号,否则反应式切过去照样撞同一个 rate_limit。
@@ -463,7 +484,17 @@ MODEL_ID=<your-model-name> MODEL_CHEAP_ID=<your-cheap-model-name>
463
484
  存量中心配置里的 `roles.team` / `roles.synthesize` 由 settings-schema 的一次性迁移搬走,side query 带 `modelRole` 表外词(含 `advisor`)⇒ **400**
464
485
  `request.field_invalid`(不再回落主模型;带了 `model` 也一样拒)。要给委派子代配模型就绑 `subagent`,主任务绑 `default`。
465
486
  - 调用方可在 `objective` 里 `@<模型名>` 选模型,**只认已配置的名单**(`MODEL_ID` / `MODEL_CHEAP_ID`,或目录条目)——注入不了 `baseUrl`/`apiKey`;无 `@` → 默认模型;`@` 点中一条已发布但本部署不可用的目录条目 ⇒ 400 `request.model_unavailable`。
466
- - **`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。
487
+ - **`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。
488
+ - **起服前的模型面读数**(7.148.0 起):引擎起来之前要 `modelPlane` 同一格(这份配置会服务哪张表、目录候选的结局),跑包里的无监听探针 ——
489
+ ```bash
490
+ node <@sema-agent/server 包根>/dist/model-plane-probe.js # 子路径 @sema-agent/server/model-plane-probe;env 与工作目录 = 将要起的那只引擎的
491
+ # stdout 恰一行,例:{"servedTable":"catalog","catalog":"adopted"}
492
+ ```
493
+ 它跑起服的前两段(配置面 + 配置源 / 候选判官 / 模型面装配 + 模型表的跨域不变量)到模型表定形即停:不开店、不监听,与起服后 `GET /v1/models` 读同一只读口,
494
+ 同一份配置下与起服第 2 段末那一格字节同(起服拉取超过 `CONFIG_BOOT_FETCH_BUDGET_MS` 或预算写 0 时,答的是那次拉取落地之前的回落读数,stderr 有 `config_boot_fetch_deferred`,
495
+ 探针不等迟到的那一份);日志全在 stderr。退出码 `0` = 答案在 stdout;非 `0` = 起服会在这两段拒,stderr 是与起服同一条 `fatal` 行。
496
+ 动作与起服前两段相同(配置根上的一次性迁移 —— 其中「目录顶替 env 表」的决定本身就是模型面的输入 —— 、`SEMA_CONFIG_DIR` 不在就建、封签钥匙保管店与提示词店的目录;
497
+ 远端配置源另有中心拉取、LKG 落盘、执行公钥上报、文件形凭证的续签,已发出的续签落盘之后才退),所以在本会起服的地方调;一台引擎已经在服就读它的 `GET /v1/models`。
467
498
  - 不设 `MODEL_CHEAP_ID` → 所有角色 = 主模型(行为不变)。
468
499
  - `MODEL_CHEAP_ID` 写成与 `MODEL_ID` **同一个 id**(7.104.0 起)→ 那就是主模型本身:不铸 cheap 槽,所有角色 = 主模型,起服一行
469
500
  `config_env_entry_cheap_same_as_main`(同时设的其余 `MODEL_CHEAP_*` 键一并不生效)。要给同一只上游 id 第二种形(另一个价 / 思考 /
@@ -513,18 +544,22 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
513
544
  | `MEMORY_EMBEDDER_DIM` | 缺省 | 声明维度(**正整数**,如 `1024`)。必须与模型真实维度一致 |
514
545
  | `MEMORY_EMBEDDER_TIMEOUT_MS` | `30000`(`[1000, 300000]`) | 单次 embed 的活性上限。挂死的端点会把整条记忆写入路径吊住 |
515
546
  | `MEMORY_EMBEDDER_API_KEY` | 缺省 | 可选 Bearer(自托管 TEI/vllm/ollama 通常不需要;公有云端点需要) |
547
+ | `MEMORY_EMBEDDER_REEMBED` | 缺省 | **清列重嵌的确认口令**,值 = 要确认的那个身份 `<model>/<dimensions>`(维度在**最后一个** `/` 之后,模型名本身可带 `/`,如 `BAAI/bge-m3/1024`)。只在下条指纹门判「对不上」时起作用:在场且恰好点名本次配置的 `MODEL`/`DIM` ⇒ 清掉向量列、按这个身份重嵌一次;缺席 ⇒ 对不上就拒启、一行不清。形不对(`true` / `1` 这类开关形一律不是口令)或对不上本次配置 ⇒ 拒启;三键不全时单独在场同上面的半配拒 |
516
548
 
517
549
  ⚠️ **半配 = 拒启**:上表前三键是一个整体,少任何一件都启动报错并**点名缺哪几键**。静默忽略的后果不是「功能缺席」而是「operator 以为向量面开着」——检索照常出结果(词面档),差异只在答案质量里,零日志。同理:只配 `TIMEOUT`/`API_KEY` 而三键不全、或 `MEMORY_ENGINE=off` 却配了 embedder,都是死旋钮,一律拒启。
518
- ⚠️ **维度守卫是拒启/抛错,不是 warn**:向量列写错维度是**静默毒库**——本仓 pg 记忆表的 embedding 列是 `jsonb`(无维度约束),错长度向量既不会让 DB 报错,也不会让检索报错(那些行只是悄悄退回词面档)。所以坏 `DIM`(非正整数)启动即拒;运行期端点返回的向量长度与 `DIM` 不符,embedder **直接抛错**,绝不截断/补零。换 `MODEL`/`DIM` 的**清空由指纹门自动做**(见下条),不必手动清。
550
+ ⚠️ **维度守卫是拒启/抛错,不是 warn**:向量列写错维度是**静默毒库**——本仓 pg 记忆表的 embedding 列是 `jsonb`(无维度约束),错长度向量既不会让 DB 报错,也不会让检索报错(那些行只是悄悄退回词面档)。所以坏 `DIM`(非正整数)启动即拒;运行期端点返回的向量长度与 `DIM` 不符,embedder **直接抛错**,绝不截断/补零。换 `MODEL`/`DIM` 时**起服拒、不自动清**(指纹门,见下条):要清列重嵌须显式给确认口令 `MEMORY_EMBEDDER_REEMBED`。
519
551
  ⚠️ **embedder 出故障的方向**:端点 5xx/超时会让当次记忆**写入**报一条 `io error: …` 冲突(不写坏向量),检索腿则退回词面档继续服务——即「坏了退回 lexical」,不是「坏了写坏数据」。故障有专门信号:metric `memory_embed_failed_total{backend}` + 日志 `memory_embedder_failed`(只看 PatchReport 里的冲突会把供应商故障误读成并发改动)。
520
552
  ⚠️ **只对新写入的条目生效(没有回填腿)**:开启前就存在的记忆条目 embedding 列是空的,只有被再次写入时才会补上向量;这些行在检索里落**词面档**。🔴 **词面档不是「排名靠后」,是有门槛**:与查询**零词交集**的行被直接**丢出结果集**(词面距离在无交集时无定义,检索当场跳过该行),而带向量的行有余弦读数可用、**不会被这道门丢掉**(前提是 embedder 当时也把**查询**嵌出来了;embedder 故障那一刻查询侧拿不到向量,整轮检索退回词面档,于是零交集的行连同它们一起被丢)。所以没有向量的行对**改述 / 同义 / 跨语言**这类查询**检索不到**——那恰恰是向量档买的东西。要立刻全量生效,得自己重写一遍语料。
521
- 🔴 **换 `MODEL`/`DIM` 的行为:下次 boot 自动清空向量列(embedder 指纹门,7.15.0 之后的下一个发布起)**。本服务在 `agent_memory_engine_meta` 单行元表里记住当前 embedder 的**指纹** `{model, dimensions}`(明文,便于排障;**endpoint 不进指纹**——换域名/代理不换语义空间)。每次启动比对一次:
522
- - **相等** ⇒ 零动作、零写;
523
- - **不等 / 元表还没有这一行而库里已有向量 / 那一行读不出来**(手改、回滚残留)⇒ **先把 `agent_memory_engine_entry.embedding` 整列清成 NULL,再写新指纹**(次序不可换:反过来若中途崩溃,新指纹已记而旧向量还在,此后每次 boot 都判「相等」,残留永不复检);
524
- - 启动日志 `memory_embedder_identity_changed` 带**旧值/新值/清了几行/耗时 ms/原因**(`changed` | `unattributed` | `unreadable`),首次记录指纹是 `memory_embedder_identity_armed`;比对腿**自身失败一律拒启**(吞掉 = 门在场却没跑成)。⚠️ 这条行按「identity 变了」发,**不是**按「清了几行」发:当时库里恰好没有向量可清(上一轮已清过 / 缺席窗内被写成 NULL)照样发,`cleared: 0` —— 换 embedder 这件事在启动日志里不该无痕。真正零响亮的只有「相等」那一档。⚠️ **这句承诺以「进程活到把日志发出来」为界**:清列与写元表是两条各自提交的语句,日志在两步都完成之后才发,所以进程恰好在清完之后崩溃(或被杀),下次启动会按当时的真实状态重判(报「首次记录指纹、没有向量可清」,或者元表已写完就直接判「相等」不发声)——**那一次清掉了多少行会从日志里丢失**。数据面不受影响(次序钉保证的就是这个:向量已 NULL、不会残留旧空间),丢的只是审计行;要精确到行数的审计请以 DB 侧的变更记录为准。
525
- - 清空**只丢可再生的向量缓存**(正文/frontmatter 分毫不动);清完**不回填**,行被下次写入时自然按新模型重嵌,期间检索退回词面档。🔴 **代价按上面那条读**:退词面档 = 与查询零词交集的行**检索不到**(不是排到后面),所以清空之后、重嵌之前,那些行对改述/同义/跨语言查询是**不可达**的。⚠️ **误配代价诚实说**:把 `MODEL` 改错一次再改回来,向量**不会自动回来**——只随行被再次写入才重嵌;对写完就不动的记忆库,误配一次 = 向量面实质长期缺席(正文零损失,检索退 lexical,零交集查询够不着)。要立刻恢复只能自己重写一遍语料(或接受词面档)。
553
+ 🔴 **换 `MODEL`/`DIM` 的行为:下次 boot 拒启,向量一行不动;清列重嵌只在运维显式确认后做(embedder 指纹门)**。本服务在 `agent_memory_engine_meta` 单行元表里记住当前 embedder 的**指纹** `{model, dimensions}`(明文,便于排障;**endpoint 不进指纹**——换域名/代理不换语义空间)。每次启动比对一次:
554
+ - **相等** ⇒ 零动作、零写;元表还没有这一行且库里没有向量 ⇒ 只记下指纹(`memory_embedder_identity_armed`);
555
+ - **对不上**,三态:`changed`(记下的与配置的不等)/ `unattributed`(元表还没有这一行而库里已有向量)/ `unreadable`(那一行读不出来:手改、回滚残留)⇒ **缺省拒启**,一行不清、元表不写;拒句点名所处的态、记下的与配置的身份和出路:
556
+ ① `changed` 才有的一条 —— 把 `MEMORY_EMBEDDER_MODEL` / `MEMORY_EMBEDDER_DIM` 改回记下的身份;② **显式确认重嵌** —— 设 `MEMORY_EMBEDDER_REEMBED=<本次配置的 model>/<dim>`(拒句里给出可逐字照抄的那一条),**停机**(`scale 0`)后再起;③ 撤掉 `MEMORY_EMBEDDER_*`,服务以词面档起来,存量向量原样留着(回头配回同一身份可续用,见下面「没配」一条)。
557
+ 为什么缺省是拒启:清列**不可逆**(行只在下次被写时才重嵌),拒启**可逆**;换模型 / 改维度的一次手滑不该在起服时就丢掉全部向量。
558
+ - **确认了**(口令恰好点名本次配置的身份)⇒ **先把 `agent_memory_engine_entry.embedding` 整列清成 NULL,再写新指纹**(次序不可换:反过来若中途崩溃,新指纹已记而旧向量还在,此后每次 boot 都判「相等」,残留永不复检)。口令绑定身份,所以它天然是一次性的:新指纹记下之后下一次起服判「相等」,口令不再有东西可确认(留着无害,可随时撤);之后再换一次模型,留着的旧口令对不上新配置 ⇒ 起服拒,不会替下一次换模型静默清列。
559
+ - 已确认的清列,启动日志 `memory_embedder_identity_changed` 带**旧值/新值/清了几行/耗时 ms/原因**(`changed` | `unattributed` | `unreadable`),`note` 点名确认口令;首次记录指纹是 `memory_embedder_identity_armed`;比对腿**自身失败一律拒启**(吞掉 = 门在场却没跑成)。⚠️ 这条行按「identity 变了」发,**不是**按「清了几行」发:当时库里恰好没有向量可清(上一轮已清过 / 缺席窗内被写成 NULL)照样发,`cleared: 0` —— 换 embedder 这件事在启动日志里不该无痕。真正零响亮的只有「相等」那一档。⚠️ **这句承诺以「进程活到把日志发出来」为界**:清列与写元表是两条各自提交的语句,日志在两步都完成之后才发,所以进程恰好在清完之后崩溃(或被杀),下次启动会按当时的真实状态重判(报「首次记录指纹、没有向量可清」,或者元表已写完就直接判「相等」不发声)——**那一次清掉了多少行会从日志里丢失**。数据面不受影响(次序钉保证的就是这个:向量已 NULL、不会残留旧空间),丢的只是审计行;要精确到行数的审计请以 DB 侧的变更记录为准。
560
+ - 清空(已确认时)**只丢可再生的向量缓存**(正文/frontmatter 分毫不动);清完**不回填**,行被下次写入时自然按新模型重嵌,期间检索退回词面档。🔴 **代价按上面那条读**:退词面档 = 与查询零词交集的行**检索不到**(不是排到后面),所以清空之后、重嵌之前,那些行对改述/同义/跨语言查询是**不可达**的。⚠️ **误配的代价**:把 `MODEL` 改错一次,起服就拒,改回来即照常起 —— 向量一行不丢;只有在**给了确认口令**之后,清掉的向量才**不会自动回来**(只随行被再次写入才重嵌;对写完就不动的记忆库 = 向量面实质长期缺席,正文零损失,检索退 lexical,零交集查询够不着,要立刻恢复只能自己重写一遍语料)。
526
561
  - ⚠️ **换 embedder 请停机换:先 `scale 0`,再起**。指纹门管的是 **boot 面**;**滚动升级窗**里新副本清完列之后,仍在服役的旧副本还在按**旧**模型写向量,滚动结束后表里混着两个空间而元表已是新指纹 ⇒ 此后恒判「相等」,门再也不会复检。这是纪律面的残余,不假装根治(行级 embedder 记账列可结构性根治,超出本件射程)。滚动事故后的补救 = 手动清一次:`UPDATE agent_memory_engine_entry SET embedding = NULL WHERE embedding IS NOT NULL;`(同一条也可用于「误配后立刻重置」)。
527
- 🔴 **同族的第二格:回滚到指纹门之前的版本**(7.14.x / 7.15.x —— 有 embedder、无指纹门)。那些版本照写 `embedding` 列却**根本不认识这张元表**,所以「装本版(元表记 B)→ 回滚到旧版跑一段(往列里写 A 空间向量,元表纹丝不动)→ 再升回本版、配置仍是 B」这条路走完,元表与配置都说 B 而列里躺着 A 空间向量 ⇒ 此后恒判「相等」,门再也不会复检。**跨门回滚与滚动窗要做同一件事**:回滚之前(或重新升级之后)手动跑上面那条清列 SQL,或干脆 `DELETE FROM agent_memory_engine_meta WHERE meta_key = 'embedder_identity';` 让下次启动按「无主向量」保守清一次。
562
+ 🔴 **同族的第二格:回滚到指纹门之前的版本**(7.14.x / 7.15.x —— 有 embedder、无指纹门)。那些版本照写 `embedding` 列却**根本不认识这张元表**,所以「装本版(元表记 B)→ 回滚到旧版跑一段(往列里写 A 空间向量,元表纹丝不动)→ 再升回本版、配置仍是 B」这条路走完,元表与配置都说 B 而列里躺着 A 空间向量 ⇒ 此后恒判「相等」,门再也不会复检。**跨门回滚与滚动窗要做同一件事**:回滚之前(或重新升级之后)手动跑上面那条清列 SQL,或干脆 `DELETE FROM agent_memory_engine_meta WHERE meta_key = 'embedder_identity';` 让下次启动按「无主向量」判(拒启;带上确认口令 `MEMORY_EMBEDDER_REEMBED` 起一次即清一次)。
528
563
  - **没配 `MEMORY_EMBEDDER_*` 时门整个不装**(元表不动,旧指纹保留),启动日志 `memory_embedder_identity_absent` 说明:缺席期间**被改写过**的行,其 embedding 本来就被写成 NULL,所以「配置回归同一 identity 后向量续用」只对缺席期间**未被触碰**的行成立。
529
564
  - 残余:同名模型在不同供应商若其实是不同实现(自托管 finetune 撞名),指纹辨不出——明知异实现时改 `MEMORY_EMBEDDER_MODEL` 名,或手动跑上面那条清列 SQL。`MEMORY_ENGINE_BACKEND=mysql` 无向量面(v1 词面档),没有这张元表也没有这道门。
530
565
  📋 **档位对运维可见**:启动日志 `memory_engine_enabled` 行带 `vectorMode` 字段(取自后端实例的真值,不是配置推断)——`lexical` 在跑就必须看得见;配了 embedder 时同行还带 `embedderModel`/`embedderDim`,换模型这件事在日志里留痕。
@@ -532,7 +567,7 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
532
567
  它的同步身份 = `MEMORY_SYNC_URL` 的规范形(小写协议与主机、无缺省端口、路径去尾斜杠)。中心用 `MEMORY_SYNC_SERVE_IDENTITY` 声明同一个值(能力位 `memorySyncServe.identity` 逐字是它),
533
568
  只服务点名这一身份的轮(不是规范形 ⇒ 400 `request.field_invalid`;别的身份 ⇒ 409 `config.memory_sync_target_mismatch`,零读零写;没声明 ⇒ 501 `feature.memory_sync_serve_disabled`)。
534
569
  中心侧同步口是 `POST /v2/memory/sync/:scope`;游标绑中心的**化身**(每个层分区一只,分区第一次被同步读到时出生、存在中心库):中心被清空重建、或游标是升级前写下的 ⇒
535
- 中心在写之前拒这一轮,本机这一层停驻(同其余停驻,出路 = 删这一层的游标文件)。中心从备份恢复之后,删掉 `agent_memory_engine_sync_partition` 的行再起服(轮换化身,见 DEPLOY-PREREQS)。
570
+ 中心在写之前拒这一轮,本机这一层停驻(同其余停驻,出路 = 删这一层的游标文件)。中心从备份恢复之后,删掉 `agent_memory_engine_sync_partition` 的行再起服(轮换化身)。
536
571
  壳在把会话推上云之前调 `POST /v1/sessions/:id/memory/sync`(体 `{ target }` = 目标引擎的 `memorySyncServe.identity`;能力位 `sessionMemorySync`),拿到引擎的同步报告:
537
572
  这个会话读的每一层一个结局(`synced` / `incomplete` / `refused` / `failed`),或整轮 `refused`(目标不是本机腿到达的那台 / 会话有在飞 run,或读完它的记忆观测之后又受理了 run / 会话记录前进了)。
538
573
 
@@ -552,17 +587,17 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
552
587
  | env | 缺省 | 说明 |
553
588
  |---|---|---|
554
589
  | `MEMORY_ORG_ADMISSION_MODE` | `enforce` | `enforce`=判决即结果;`audit`=**运维诊断位**——准入面判决照算、拒绝降为日志+metric,零行为变化(不整拒、不窄化写面);`/v1/memory/export`、`/v2/memory/sync/:scope` 的 `org:` 属主门在 audit 下**逐字保持收编前的 operator-only**(未验证的目录不得开数据面)。两模式都要求目录 client 在场 |
555
- | `MEMORY_ORG_DIRECTORY_JSON` | 缺省 | 单机形静态授权表 `{"<principal>":{"org:acme":{"write":true}}}`。**启动期整表校验,坏表拒启动**。有 web-admin 时被忽略(center 胜 + warn) |
590
+ | `MEMORY_ORG_DIRECTORY_JSON` | 缺省 | 单机形静态授权表 `{"<principal>":{"org:acme":{"write":true}}}`;scope 键须过引擎的 org scope 键判官(`org:` 加一段名:`[A-Za-z0-9._-]` 直写、其余字符写成大写 `%XX`,段至多 64 个编码字符;可用引擎的 `formatOrgScope` 铸,例 `org:team%40acme`);web-admin 远程腿下发的目录同判 —— 一代目录里有一把键不过,该主体的这一代整代不采用(瞬时拒,可重试;服务日志 `org_directory_generation_refused` 点名坏键与版本)。**启动期整表校验,坏表拒启动**。有 web-admin 时被忽略(center 胜 + warn) |
556
591
  | `MEMORY_ORG_GRANT_TTL_MS` | `60000`(`[1000, 3600000]`) | 授予/负结果的缓存 TTL = 「有界 LKG」:**新任务/新 resume 腿**的准入判决滞后 ≤ 此值;**在跑任务不受吊销影响**(判决点在 prepare 期) |
557
592
  | `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS` | `10000`(`[500, 600000]`) | 目录取不到之后的退避窗(只用于取数失败臂,不用于负结果) |
558
593
  | `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`=本部署未设,引擎自缺省) |
559
594
  | `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`=本部署未设,引擎自缺省) |
560
- | `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 |
595
+ | `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) |
561
596
 
562
597
  ⚠️ **拒启动**:多租户 + 记忆面点亮 + `projects[].defaultScopes` 里有 org 键 + 目录源缺席 ⇒ 启动报错
563
598
  并点名 projectId(该部署的每个此类请求都会在 prepare 期整拒,响亮拒启动比静默全拒服务诚实)。
564
599
  ⚠️ **回滚脚枪**:回滚到「无 web-admin」模板前先清掉残留的 `MEMORY_ORG_DIRECTORY_JSON` —— 否则
565
- 它作为 operator 自证通道**复活旧授权**(详见 `docs/DEPLOY-PREREQS.md`)。
600
+ 它作为 operator 自证通道**复活旧授权**。
566
601
 
567
602
  **可选 — 模型级联 cascade(core 1.45,opt-in)**
568
603
  ```bash
@@ -576,27 +611,68 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
576
611
  - 入口:`import "@sema-agent/server/main"`(exports 正门;此前宿主只能用 node_modules 路径字符串定位
577
612
  `dist/main.js`,那是未承诺的内部路径)。语义承诺三条:**import 即 boot**(副作用模块形,无需调用);
578
613
  **不读 `process.argv`**(宿主可用 argv 传自己的标记,`ps` args 判据不受干扰);`./package.json` 可读。
579
- - **库形嵌入**(包根 `.` 导出:`loadConfig` + `createHttpServer` + 自建 `StoreBackend` / `ServiceDeps`):`loadConfig()` 之后调
614
+ - **库形嵌入**(包根 `.` 导出:`loadConfig` + `createHttpServer` + 自建 `StoreBackend` / `ServiceDeps`):先调一次包根的 `declareServerWithheldEnv()`
615
+ (先于 `loadConfig()`、任何执行环境构造与子进程):本进程的自有凭据变量名(配置目录全部 secret 型名 + 名单型旋钮名)向引擎声明,`inheritEnv` 三词下都不交给宿主子进程,
616
+ 名单型旋钮点到它们拒启。`loadConfig()` 的前提就是这份声明 —— 没声明的进程里它抛,拒句点名这一步(服务的三个进程入口第一步就声明)。`loadConfig()` 之后调
580
617
  `composeExecutionLane(config, ctx)`(HTTP 服务与 `run-local` 都这么做;7.104.0+)。执行车道的四条拒启(`leader-host-lane` /
581
618
  `multi-tenant-shared-lane` / `device-durable-store` / `device-durable-approval`)自 7.104.0 起判在这一步,**不在** `loadConfig()` 里 ——
582
- 只调 `loadConfig()` 的装配对这几形照起。`ctx.configSource === "local"` 时它先读 `config.d/remote-exec.json` 覆写车道(`drivesFromFile`
583
- = 你的装配驱动得了的文件车道),再判;不走文件覆写的装配直接调同一份判据 `assertExecutionLaneInvariants(config)`。
619
+ 只调 `loadConfig()` 的装配对这几形照起。`ctx.configSource === "local"` 时它先读 `config.d/remote-exec.json` 覆写车道,再判。`ctx.entry`
620
+ = 你的装配这只入口:`runs` 是它跑得了的车道闭集(文件或 env 声明了表外的一条 ⇒ 拒),`declares` 可选(文件与 env 都没点名时由入口声明,排在二者之后;文件在场即终结选择 —— 文件点的车道取不到坐标 ⇒ 拒,不由 env 的另一条或入口声明顶上;
621
+ `run-local` 声明 `host`);自建 HTTP 装配递包根导出的 `HTTP_SERVICE_LANE_ENTRY`(全部车道、不替部署声明);不走文件覆写的装配直接调同一份判据 `assertExecutionLaneInvariants(config)`。
584
622
  `ctx` 的型 `ExecutionLaneComposeCtx` 也从包根导出(自己的装配里要写它的签名 / 抽 helper 时直接 import,不必手抄一份形)。
585
623
  读口场景门 `scenarioGate` 是 `createHttpServer` 的必填件(7.131.0+):用包根导出的 `createScenarioGate({ config, scenarios, principalCaps, logger }, "peek")` 铸(与任务提交腿同一个工厂,型 `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources` 一并导出),没递就是 tsc 红。
586
624
  **模型路由端点的拒启同理判在目录应用之后**:`judgeServedModelRoute(config, { envTableIsPlaceholder })`(包根导出,HTTP 服务与 `run-local` 调的就是它)只判将要服务的那张表(目录表或 env 表,句子随视图);
587
625
  只调 `loadConfig()` 的装配不再在读配置那一拍拿到路由拒启 —— 在**自己的**配置源应用之后调它一次,有判决就拒启(`reason` 是给运维的原句)。严格名册布防(env 表是占位)传 `envTableIsPlaceholder: true`,没有名册门的装配传 `false`。
588
626
  - **库形嵌入的 caps `store` 位**(7.106.0 起;7.106.0 合并复审 X9 补一句):`createHttpServer` 的 `storePosture` dep **不传** ⇒ `GET /v1/capabilities` 的
589
- `store` 取诚实下限 `{declared: <config.dbBackend>, backend: "memory", durable: false}`,且**没有 `store.sql`**(即使递了引擎读口 `sqlEngine`、真有 SQL 后端 ——
590
- 7.105.0 上这类嵌入方的顶层 `sql` 是有值的)。要 caps 说真话就把你装出来的后端事实经 `storePosture` 递进来(形 = `src/store-posture.ts` 的 `StorePosture`)。
627
+ `store` 取诚实下限 `{declared: <config.dbBackend>, backend: "memory", durability: "ephemeral"}`(`/health.store.durability` 同值),且**没有 `store.sql`**(即使递了引擎读口 `sqlEngine`、真有 SQL 后端 ——
628
+ 7.105.0 上这类嵌入方的顶层 `sql` 是有值的)。要 caps 说真话就把你装出来的后端事实经 `storePosture` 递进来(形 = `src/store-posture.ts` 的 `StorePosture`;7.148.0 起多两格必填 `durability` 三态与逐根明细 `roots`,自己判不了介质就写 `"unknown"`,不要写 `"durable"`)。
591
629
  - **`PORT=0` = 让 OS 分口,口由引擎自己报**:引擎自己绑内核分给它的口,`listening` 启动日志行(stdout,info 级;`LOG_LEVEL` 高于 info 会滤掉它)的 `port` 与 `/health.port` 报**实际绑定口**
592
630
  (`PORT` 非 0 时两者与配置值相等)。要用 OS 分的口起引擎的宿主:子进程以 `PORT=0` 起,读 stdout 那一行 `listening` 的 `port`,再用 `/health` 的 `pid` / `dataRoot` 验答话的是自己起的那个进程;
593
631
  **不要**先在外面挑一个空闲口、关掉、再交给子进程去绑 —— 关与子进程真正 `listen` 之间有窗,并行负载下别的进程能抢走这个号。`configHash` 仍按**配置值**铸(配置指纹不随 OS 分的口漂)。
594
632
  - `/health` 身份字段承诺:`pid`(= 引擎进程)、`startedAt`(**7.67.0+**,= 引擎进程起点的 epoch ms,
595
- 一次铸)与 `dataRoot`(= 生效数据根,解析恒回退 `~/.ai-agent`,与 DB_BACKEND 无关)**恒在**——宿主用
633
+ 一次铸)与 `dataRoot`(= 生效数据根,解析恒回退 `~/.ai-agent`,与 DB_BACKEND 无关)**恒在**(**7.148.0+**:配了服务凭据的引擎上,
634
+ 这些键只给出示了凭据的读者 —— 宿主验身份时带起引擎用的**同一枚** `Authorization: Bearer <SERVICE_AUTH_TOKEN>` 读;没出示凭据的读者只拿 `status` / `auth` / `operator` / `center` 四键;
635
+ 没配服务凭据的嵌入形照旧匿名全键,见契约 §0.4「读者分档」)——宿主用
596
636
  它们验证「这个端口上的 /health 是不是我起的那个引擎」。`startedAt` 变了 = 这个端口后面换了一条命
597
637
  (另一个宿主重启了共享引擎):壳据此把「引擎换代」当**一等事实**处理,不必等某个带鉴权的请求先撞一次
598
638
  401 才发现(`/health` 无鉴权,心跳恒绿)。⚠️ 只给这一个数:service token 本体与代际计数**不上**这一面。
599
639
  钉:`test/health-identity-contract.test.ts`。
640
+ - **数据根引擎版本高水位 marker(7.147.0+;只 `DB_BACKEND=local`)**:`<数据根>/engine-high-water.json`(`/health.dataRoot` 那只目录的顶层),
641
+ 形 = **恰一键** `{"highWater":"x.y.z"}` —— 打开过这只数据根的最高引擎(本包)版本(引擎判过之后、往根里写第一个字节之前就抬,所以它是「可能写过这只根的最高版本」,不是服务成功的记录)。`highWater` 是纯 `x.y.z`(无前导零、无 `v` 前缀、无预发布 / 构建后缀、
642
+ 每段是安全整数),三段按数比。宿主(壳侧判官)与引擎读**同一只文件、同一读法**,不另记第二份高水位:
643
+ - 文件不在 ⇒ **缺席**:还没有 7.147.0 及以后的引擎打开过这只根 —— 不比;
644
+ - 在而读不出(I/O 错;悬空符号链接 —— 链接在、目标不在)/ 不是 JSON 对象 / 缺 `highWater` / 有别的键 / `highWater` 不是纯 `x.y.z` ⇒ **读不出**:引擎起服拒,**不当缺席**;有别的键 = 更新的引擎写下的形(数据根比本构建新);
645
+ - 读得出 ⇒ 与本端版本比:`highWater` 高于本端 ⇒ 本端引擎起服拒 `config.schema_mismatch`(拒句点名这只文件、高水位与本构建版本;
646
+ 包根导出的 `LocalDataRootSchemaMismatchError.findings` 里一段 `problem: "newer-engine"`,同一段必带 `engineHighWater: { highWater, engine }` —— 读数类型 `LocalDataRootMismatch` 是以 `problem` 为判别位的联合),不必先起一只注定被拒的引擎再解析 stderr。
647
+ - **写者只有引擎**,判与抬在**打开数据根的那一刻**一段做完(数据根锁在手 = 单写者;判拒发生在钥匙对与空账本已落、店尚未开之后;下一版改为判拒先于任何写):先判,判拒 ⇒ 不抬、marker 与已有文件的字节不动(判之前已落盘的东西见下面「残余」);
648
+ 判过 ⇒ 先抬再写 —— 本构建高于记录(或缺席)⇒ tmp + rename 原子替换(属主独占 `0600`,目录项也要证落盘;并发读者只见整份旧值或整份新值),相等不写(只证它在盘上:fsync 文件与目录)。所以店构造里的自修、
649
+ 起服对账、一次性迁移与任何服务写都在抬之后:写到一半进程死了,水位已是本构建,比它旧的引擎重开被拒,本构建重开续跑。对账(严格边档 / 快照消化)或迁移在抬之后被拒 ⇒ 水位留在本构建。
650
+ 本构建版本按**发布线**读:`x.y.z`,可带 `-预发布` / `+构建` 后缀,后缀只剥不比(`7.147.0-rc.1` 按 `7.147.0` 判与抬,marker 恒写纯三段);读不出(例:读不出 `package.json` 时的 `unknown`)⇒ 引擎拒开这只根(marker 不读不写)。
651
+ 宿主只读:不写、不删、不改。写不进(含目录项证不了落盘)⇒ 不以这只根服务(起服拒;缺省推导的 local 在起服时为空的根上按既有降级臂退内存)。
652
+ 残余(如实,**不是「零写」**):判在「打开数据根」这一步做,而这一步之前数据根里已经落下了东西。7.147.0 的真字节读数:`sealed-key/exec-*.json` 两只(模型钥匙一只、停驻行封签的钥匙目录里一只;文件 `0600`;被拒的新构建自铸的新密钥对)、
653
+ `checkpoints/checkpoints.jsonl` 与 `background-agents/agents.jsonl` 两只 0 B 账本、十余只店目录 —— 引擎内核 `FileStorageBackend` 的构造把取锁与它自己的店放在同一步(建子目录、店目录锁、内核账本撕裂尾的收拢),那几笔在判之前。
654
+ 判拒本身不抬 marker、不改任何已有文件的字节;被拒的新构建在盘上留下的就是上面这些新文件与空目录。**旧构建重开这只根**:按 `publicKeyId` 读不到新钥匙文件 ⇒ 当缺席、重铸自己的,不读错。
655
+ 判拒先于任何写,不在本版(下一版改)。
656
+ - SQL 后端不读不写它(滚动升级窗内合法混跑,另有列册对账)。只有**旧的那一端**是 7.147.0 及以后才守得住(更早的引擎不读它)。
657
+ 回滚的唯一支持形 = 恢复升级前的数据根备份(marker 随备份回到旧值);同一台机器要并行跑多个引擎版本 ⇒ 各给一个数据根(`LOCAL_DATA_ROOT`)。不设绕过旋钮。
658
+ - **两条入口同一只判与抬(7.148.0+)**:`run-local` 与 HTTP 服务的 local 臂经同一只打开函数打开数据根 —— 7.147.x 及更早的 `run-local` 不读不写这只 marker,本版起它同样判、同样抬。
659
+ - **跳版拒启(判的第二道,同一段、抬之前)**:数据根里有「会话存档在、会话元数据格缺」的会话(7.148.0 之前的 `run-local` 建的会话就是这一形),本构建已不带把它们收进行的那条一次性迁移,
660
+ 而 ① 打开之前的高水位早于 7.148.0 或缺席(带那条迁移的版本从没打开过这只根),或 ② 带那条迁移的版本打开过、没收完(数据根顶层还留着 `archive-token-adoption.pending`)⇒ 起服拒
661
+ `config.schema_mismatch`,`findings` 里一段 `problem: "skipped-required-migration"`,必带 `skippedMigration: { highWater, requiredSince, sessions }`(`highWater` 为 `null` = 没有 marker)。
662
+ 哪一种由这两格推出,不另带位:`highWater` 为 `null` 或早于 `requiredSince` ⇒ ①;不早于 `requiredSince` ⇒ ②(拒句点名那只标记文件,说「没收完」)。拒在抬之前、marker 不动,所以出路那一版起得来 ——
663
+ 出路 = 先用一个仍带那条迁移、**不低于 `requiredSince` 且不低于 `highWater`** 的版本(服务或 `run-local`)对这只根**完整**起一次,再起本构建(低于 `highWater` 的会被「高水位高于本端」拒在门外)。
664
+ `archive-token-adoption.pending` 是引擎内部的欠账记录,不是契约(宿主不读不写):带那条迁移的版本在抬高水位之前写下它(这只根还欠、存档目录里有存档),
665
+ 收齐并证明之后删掉 —— 第一次起动被打断(进程被杀、迁移写失败、嵌入方不走 `ensureSchema()`、会话目录列不出 ……)它就留着;收完之前更新的版本拒开。
666
+ 标记在而根里已没有「存档在、格缺」的会话(收齐之后、删之前停了)⇒ 无事可收:打开它的版本在抬高水位之前删掉它、照起。
667
+ 带那条迁移的版本不判这一道:它在本次起服里收(没收完的那一次留下标记,见上);起动时会话存档目录读不出 ⇒ 打开即拒,不抬高水位。
668
+ 钉:`test/local-engine-high-water.test.ts` · `test/local-root-one-row-store.test.ts`(跳版拒启臂)· `test/archive-token-adoption-duty.test.ts`(收令牌欠账)。
669
+ - **已知限制:混版回退 —— 7.147.0 起写下的会话行,在 ≤7.146.x 的版本上续跑不保证**:7.147.0 起会话行上本服务记的十二格搬出了引擎的座表(SQL 后端住旁表 `session_ext`、`session_meta` 只剩引擎的列,一次性迁移 M41 停-迁-起;local 数据根另有上面的引擎版本高水位 marker)。
670
+ 把部署回退到 ≤7.146.x、再续跑升级之后写下或改写过的会话,结果**不保证**:可能照常续到终答(一次续跑返回 200),也可能被拒 —— 两种都不在支持范围内,本服务不承诺其中任何一种,也不为它加兼容读法(一条旧版读法就是第二份读法)。
671
+ **出路**:回退前先排空 —— 让待续跑的停驻 run 先收完(完成或取消),再回退;随包的排空检查(`scripts/drain-check-local.mjs`〔`DB_BACKEND=local`〕/ `scripts/drain-check.mysql.sql` / `scripts/drain-check.pg.sql`,只读)是读出停驻行存量的工具;或不跨版回退。回退的唯一支持形是恢复升级前的数据根 / 库备份。
672
+ - **已知限制:`REMOTE_EXEC=host` 车道上请求不带 `cwd` 时,停驻在审批门上的后台子代被批准后续跑的命令落在已删目录**:请求不带 `cwd` 时,host 车道为这条任务铸一个一次性的工作区 `<HOST_WORKSPACE_BASE,缺省 os.tmpdir()>/sema-host-<id>-<rand>`,
673
+ 父 run 结束、销毁执行环境时整个 `rm -rf`。后台子代停驻在审批门上、父 run 先收了场,之后被批准续跑:续跑的命令落在那个已被删的目录上 —— 命令没有运行,引擎的拒句是 `…the task root directory no longer exists (or is no longer a directory): <路径>`,
674
+ 而续跑提醒仍写成命令已执行,两者对不上(以拒句为准,命令没有跑)。
675
+ **出路**:提交时带 `cwd` —— 带了 `cwd` 的请求不走一次性工作区,没有这个删除(`sema` CLI 恒带;SDK 只有 launch 助手默认传 `process.cwd()`,直接调提交接口的要自己带);或等下一版 —— 停驻的 run 持有它的工作区(与 local-docker 车道同形:停驻续跑接着用停驻时那只环境)。本版不改这条行为。
600
676
  - `/health` 活体 store 探测(5.14.0,SQL 后端专属 additive):后台探针(`STORE_PROBE_INTERVAL_MS`,
601
677
  默认 15000;0=关;负/非数启动响亮拒)缓存一份 DB 真往返结果,`storeProbe:{live,ageMs,error?}` 在
602
678
  探针接线时恒在,顶层告警键 `storeLive:false` 只在死时出现。**status 保持 "ok"**(liveness≠readiness
@@ -644,7 +720,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
644
720
  - 模型拒因(**7.103.0+**):`/health.modelRefusals` 与 fleet 心跳 `modelRefusals` 同一只取值口、同一个对象(形 = settings-schema
645
721
  6.2.0 `ModelRefusalsSignal`:`entries` = 目录条目名 → 拒因码,`plane` = 模型面组拒码;值只码)。描述最近一次被判的中心候选目录;
646
722
  没有可报的拒 ⇒ 键缺席。名字是秘密形 / 空串 / `__proto__` 的不可用条目不上这两处(出口过名筛;引擎内部照旧拒它)。消费方读 `version`
647
- 判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。契约段:`docs/ASSISTANT-WIRE-CONTRACT.md`「模型拒因」。
723
+ 判「缺席 = 没有拒」还是「老引擎、不可知」;读值用 settings-schema `readModelRefusals`。
648
724
  钉:`test/model-refusals-s768.test.ts`。
649
725
  - **`SESSION_CACHE_TTL_SEC`(缺省 `300`,单位 s;`0` = 关)—— 会话暖缓存,多副本安全**:每只副本把用过的会话留在内存里,
650
726
  下一轮不必从店重新唤醒整只会话。每次命中先读店里这只会话的当前叶(SQL 后端一条主键 SELECT):与暖份的叶相同 ⇒ 照用;
@@ -653,8 +729,8 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
653
729
  —— SQL 后端一条主键 SELECT;文件后端(`DB_BACKEND=local`)是把这只会话的存档文件再读一遍(只读头,不回放日志)。命中却已陈旧的次数计在 `session_cache_stale_hit_total`(高 = 同一会话在 TTL 内常换副本,加亲和可省掉这些重读)。
654
730
  `0` = 每一轮都从店唤醒,不复用。⚠️ 核的是叶:另一只副本用会话同步**整段替换**(含 `overwrite-dst`)把已有条目的内容改了、
655
731
  末叶却没变的那一形,别的副本上的暖份在 TTL 内仍是旧内容(与改前同;写不会因此失败)。依赖这一形即时可见的部署设 `0`。
656
- - 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 **durable(local)**
657
- (1.292+ 裸 boot 默认 durable;/health 的 `sessionBackend` 报 `durable(local)`)——session 行落盘在
732
+ - 数据驻留提示:`DB_BACKEND=local` 下显式 `SESSION_BACKEND=memory` 会被收编为 local 文件店
733
+ (1.292+ 裸 boot 默认 local;/health 的 `sessionBackend` 报 `local`)——session 行落盘在
658
734
  数据根下,清数据/隐私预期要按「sessions 在 engine-data 里」来做,不要按「只在内存」。
659
735
  - **后台子代的转录住哪、活多久(按车道)**:规则一条 —— 一只后台子代的持久记录在,它的转录就是宿主会话店委派分区里的一只分区会话:活过重启、随记录同亡;
660
736
  进程内短 TTL 店只装没有持久记录指着的会话(普通子任务、前台子代)。`subagent_type: "fork"` 的后台子代不在这条规则里(各车道同):它的转录是从父会话分叉出来、
@@ -678,7 +754,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
678
754
  行与编号高水位一并抹掉(同 anchors/附件/快照的级联)——会话 id 可被重新认领,残留就等于把上一位
679
755
  主人的任务标题与编号进度交给下一个化身。
680
756
  - **会话的行事实只住在数据根的 `session-incarnations.json`**(每只会话一格:属主 / 化身 / 执行放置 / 建档时刻 / fork 谱系);`sessions/*.jsonl` 是匿名的对话内容。
681
- 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`;会话同步导入的第一阶段对 fork / stale 且没带同意的推送也直接答它),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
757
+ 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`;会话同步导入的第一阶段对 fork / stale 且没带同意的推送也直接答它,7.147.0 起干跑 `POST …/sync/plan` 对这两形同样),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
682
758
  `session-incarnations.json` 本构建读不出(更新的引擎写过、格里多出不认得的键;或文件坏了 / 读不了)⇒ 起服拒 `config.schema_mismatch`(与 SQL 后端列册对账同一个码,拒句点名文件与键;
683
759
  出路 = 部署写下它的那一版,或恢复升级前的数据根备份;读不了 ⇒ 修好权限 / 盘再起);起服之后才读不出(跳过了 `ensureSchema()` 的嵌入方;或违规共享的数据根上另一只引擎写了它)⇒ 同码拒,
684
760
  形读不出则那只边档的每一读每一写拒到重启,盘上字节不被盖掉。
@@ -716,15 +792,20 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
716
792
  🔴 **两条取数据根的路是同一条腿、同一只旋钮**:HTTP 服务的 local 臂与 `run-local`(一次性 CLI)共用
717
793
  同一只等待腿 —— **不为前台形留第二套语义**。前台形要「立刻告诉我谁占着」就设 `STORE_LOCK_WAIT_MS=0`,
718
794
  那是文档化的通道,而不是两条路各有一套默认。
795
+ 🔴 **7.148.0 起两条路是同一只打开函数、同一份装配**:`run-local` 不再自己构造引擎的文件后端、不再用引擎的裸会话店 ——
796
+ 它与 HTTP 服务的 local 臂一样:取锁 → 引擎版本高水位(判与抬)→ 本服务的店 → 起服对账 → 一次性迁移 → 才读会话。
797
+ 于是同一个数据根上**只有一只会话行店**:同一只会话在两条入口上是同一位住客(会话元数据边档那一格的化身),
798
+ 一条入口上落下的记忆记录(`memory.capture:"off"` 的采集 opt-out、外部内容带来的污染标记)在另一条入口上照样生效;
799
+ `run-local` 新建的会话当场有会话元数据格(建档时刻真值;HTTP 服务列它时不再补铸另一只住客令牌)。
800
+ 代价(如实):HTTP 服务拒启的数据根(会话元数据读不出、残留迁移快照、引擎高水位高于本构建、跳版)现在也挡住 `run-local`(doctor 形一行 + 退 2);
801
+ 升级后第一次 `run-local` 多跑一遍起服对账与一次性迁移。
719
802
  🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
720
803
  永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`;唯一例外是**确定的拒启表态**,见下一条「跳版升级的旧库」,它不等窗)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
721
- ⚠️ **裸 boot(没有显式 `DB_BACKEND`)那一形的时序也跟着变**:它的锁冲突走的不是拒启而是既有的
722
- 「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`),所以第二只实例此前**当场**降级、
723
- 现在要**等满窗**才降级 —— 方向仍是好的(窗内拿到就用上真数据根,而不是静默丢掉持久面),但它是一次
724
- 真实的启动时长变化。显式 `DB_BACKEND=local` 不走降级臂(拒启),`0` 关掉两形的等待。
804
+ ⚠️ **裸 boot(没有显式 `DB_BACKEND`)撞锁与显式 `DB_BACKEND=local` 同形**:等满窗仍拿不到 ⇒ 拒启(同一句拒句)。
805
+ 「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`)只对**空 / 不存在**的数据根开 —— 有人持锁的根上
806
+ 一定有东西,开不了就拒,不退内存(退了 = 这只根上已有的会话 / run 账本整片看不见)。`0` 关掉等待。
725
807
  🔴 **新引擎的锁等待窗应 ≥ 前任的排空窗 + 15 s 终局 + 一拍**(前任的锁要到它收尾链尾部才还回来;前任最晚何时退出见 `DRAIN_GRACE_MS` 下面的「停机上界(契约句)」):
726
808
  壳 / 桌面按**自己的**排空窗给新引擎进程注入这个值 —— 那是配置渠道的本意;server 不改缺省(缺省 `60000`:改缺省会让所有部署的起服失败延后)。
727
- 三问成文见 `docs/DEPLOY-PREREQS.md`。
728
809
  - **跳版升级的旧库(退役表 `snapshot_manifest` / 退役列 `checkpoint.pending_steer` 还在)起服即拒 `config.schema_mismatch`,而且拒启之前库里一张表都没建**:
729
810
  连上库后**先探这两件、再建表** —— 探针先于第一条 `CREATE`(两个 SQL 方言同一只判据),所以按拒句指路去 7.125.x 起服一次时,库还是你交给本版之前的样子,
730
811
  不会带着本版新建的表与新列去撞 7.125.x 的对账。这是确定的拒启表态:不等 `STORE_LOCK_WAIT_MS` 的窗、当场拒,拒句点名在场的坐标与出路(先在 7.125.x 对这只库起服一次让迁移跑完,再升)。
@@ -760,11 +841,11 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
760
841
  所以等过 `PRINCIPAL_JWT_MAX_TTL_SEC` 加容差再移、再重启);某会话的豁免账坏 ⇒ **那个会话**的每个工具重新问人、
761
842
  它的豁免列表 / 撤销答 500、`remember:"session"` 不生效(`rememberApplied:false`),删掉该会话即恢复;规则两本日志坏 ⇒
762
843
  规则车道整面拒(与此前同,拒绝句自此带码)。不拒启。② **跳过 + 计数**(可用性轴):run 店 / 续跑锚 / 发文件账本 /
763
- ask 账本,计数 `fail_open_total{tag="server.<店>.corrupt-record-skipped"}` + stderr 契约行。逐店后果见
764
- `docs/FAIL-OPEN-CENSUS.md` 第 75、93–98 行。崩在半行的**尾**行不算坏记录(那是非优雅退出的正常落盘形)。
844
+ ask 账本,计数 `fail_open_total{tag="server.<店>.corrupt-record-skipped"}` + stderr 契约行。逐店后果写在随包 `dist/observability/fail-open.js` 里 `FAIL_OPEN_TAGS` 各 tag 的 `note` 中。崩在半行的**尾**行不算坏记录(那是非优雅退出的正常落盘形)。
765
845
 
766
846
  **监听绑址(1.306+)—— 桌面/单机形请注意**
767
- - `BIND_HOST` = 监听地址;显式设置**恒生效**(要在无鉴权下对外暴露,显式写 `BIND_HOST=0.0.0.0` 即可)。
847
+ - `BIND_HOST` = 监听地址;显式设置**恒生效**。**7.148.0+**:单用户部署(`REQUIRE_PRINCIPAL` 未开)上用它把**无鉴权写面**暴露到本机回环以外 ⇒ 服务进程起服拒
848
+ (不变量 `single-user-anonymous-boundary`:网络上任何人都能不带主体提交 run,而那样的 run 读这台部署那个用户的规则桶);要对外暴露写面,配 `SERVICE_AUTH_TOKEN`。多租部署上照旧恒生效。
768
849
  - `HOST` 不再被读取(曾经的兼容别名已撤——zsh 默认把 `HOST` 设成机器名的暗通道风险已消除):
769
850
  绑址唯一口 = `BIND_HOST`,设或不设都与 `HOST` 环境变量的值无关。
770
851
  - **缺省自动收窄**:当写面无鉴权时(`ALLOW_UNAUTHED_WRITES=true` 且**完全没有**
@@ -776,8 +857,9 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
776
857
  —— 没有任何东西把 B 的路径映到 A。要么把服务(或 `run-local`)跑在放着文件的那台机器上,要么走容器车道
777
858
  (`e2b`/`k8s`/`local-docker`),那里的工作区是沙箱自己的,`cwd` 被忽略。
778
859
  - **读面(7.86.0 起)**:`GET /v1/capabilities` 的 `executionLane` 把本部署的车道广告成
779
- `{provider:<执行车道八词>, toolsOnThisHost:boolean}`。7.97.0 起 `REMOTE_EXEC` 未设 ⇒
780
- `provider:"in-process"`、`toolsOnThisHost:false`(本部署没有执行车道 = 无手;改前折报 `"host"`/`true`;7.147.0 起这一形拒启、这个状态名改叫车道词 `none`,见上文硬断预告)。
860
+ `{provider:<执行车道八词>, toolsOnThisHost:boolean}`。没有执行环境的部署(无手)报
861
+ `provider:"none"`、`toolsOnThisHost:false`(`REMOTE_EXEC=none` 显式声明;7.97.0 起到 7.147.0 之前 `REMOTE_EXEC` 未设落进的是同一个无手状态,在这个读面上叫 `in-process`,此前折报 `"host"`/`true`);
862
+ 7.147.0 起未声明车道即拒启,`in-process` 这个状态名随之改名 `none`(见上文车道表之后的「硬断(自 7.147.0 起)」一段)。
781
863
  要本机有手,显式设 `REMOTE_EXEC=host`(sema-up 产物已显式写车道)。
782
864
  壳/客户端判「工具跑不跑在本机」**读这一位**,不要再从自己这一侧的 `REMOTE_EXEC` 猜 —— 连远端引擎时
783
865
  那个 env 说的是**壳自己这台**的事。⚠️ 它是**文件系统同一性**位,**不是**权限位:`cwd` 兑不兑现仍由
@@ -795,24 +877,24 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
795
877
  - `writeFile`(Write / Edit / NotebookEdit 落盘的那一步)= **整文件替换:目标要么整份是旧内容、要么整份是新内容;取消(`aborted`)⇒ 目标未碰**。
796
878
  host 车道 = 引擎的整文件替换(同目录暂存 + fsync + rename);ssh / adb / local-docker / k8s / e2b = 先在目标目录下建一只生来 0700 的私有目录
797
879
  `.<名>.stage-<12 位十六进制>/`,整份传进去,再在目标机上一次 `mv -f` 换上去;device = 设备侧执行器在同一步里暂存再换上。
798
- 传输中途断、取消、写到一半出错 ⇒ 目标逐字是旧内容,私有目录被删(删不掉 ⇒ 错误句点名残留的目录)。修前 host 与五条远端车道是就地截断写:中途中断留下新内容的前半截,原件找不回。
880
+ 传输中途断、取消、写到一半出错 ⇒ 目标逐字是旧内容,私有目录被删(删不掉 ⇒ 错误句点名残留的目录,跟一跳重新暂存的那只也点名;脚本点名的残留目录补清一次,仍在的才计 `server.remote-write.staging-dir-left`)。修前 host 与五条远端车道是就地截断写:中途中断留下新内容的前半截,原件找不回。
799
881
  - **服务进程被 `SIGKILL` 时,暂存目录会留在目标旁(可直接删)**:上一条的清理是服务进程自己做的 —— `kill -9`、内存杀手、容器被强停这类**没有机会跑清理**的退出,正在传输的那一次写留下 `.<名>.stage-<12 位十六进制>/`,里面是在传的新内容(上传途中被杀只有前半截,传完才被杀则是整份);**目标逐字是旧内容**,整文件替换的保证不受影响,多出来的只是这只目录。ssh 车道实测(被强杀的写都留下了);其余四条远端车道走同一段暂存脚本,同形。取消(Ctrl-C 中断那一次写)不留 —— 取消路径上清理那一往返照跑。**本版不自动清**:下一次对同一目标的写不会去扫陈旧的暂存目录,它会一直留着,确认没有另一个进程正在写同一个目标之后 `rm -rf` 即可(名字里的十六进制每次写都不同,在写的那一只和陈旧的那一只光看名字分不出)。host 车道的暂存是引擎的,同形的残留名是 `<目标>.tmp.<进程号>.<十六进制>`,同样可以删。
800
882
  - 暂存自创建起不比目标更可读:远端五车道的暂存只在私有目录里(别的用户进不去;替换 0600 的文件时新内容在传输期间只有写入方与 root 读得到),device 的暂存件以 0600 打开;
801
883
  host 车道的这一保证在引擎原语里:引擎 7.60.0 起目标存在时暂存件以 0600 打开。新建的文件是写入方身份的 umask 形(local-docker = 容器内镜像用户的)。
802
884
  私有目录建出后要读回核实是 0700 且属写入方、且目录上没有 ACL 条目:macOS 的原生 ACL 与 FreeBSD / illumos ZFS 的 NFSv4 ACL 不经模式位 —— 工作区父目录带可继承的允许条目时,新建的私有目录一建出来就带着它,读回 `700` 也挡不住,
803
- 所以读到条目就去掉(macOS `chmod -N`、FreeBSD `setfacl -b`、illumos `chmod A-`)、去完再读;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、继承来的 ACL 条目去不掉或读不出、目标机的系统不在 Linux / macOS / FreeBSD / illumos 之内、目标机没有 `stat`)⇒ 不暂存,退回就地写目标**
885
+ 所以读到条目就去掉(macOS `chmod -N`、FreeBSD `setfacl -b`、illumos `chmod A-`)、去完再读;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、继承来的 ACL 条目去不掉或读不出、目标机的系统不在 Linux / macOS / FreeBSD / illumos 之内、目标机读不出模式串〔`ls` 不可用 / 输出读不懂〕)⇒ 不暂存,退回就地写目标**
804
886
  (两只文件面、五条腿都有;非原子;ssh / local-docker / k8s 写进原 inode、权限位不变,e2b 的 `files.write` 与 adb 的 `adb push` 整份写目标、inode / 权限位由对端定;
805
887
  遥测计 `server.remote-write.stage-seal-fallback`);读 ACL 的臂按**目标机**的 `uname -s` 选(Linux:POSIX ACL 由模式位读回的掩码覆盖,不另读 · Darwin · FreeBSD · SunOS);FreeBSD / illumos 两臂按系统文档写、未上真机,读 ACL 的工具报错 / 输出形不对 / `uname` 退出状态不成都按读不出处理(读 ACL 的工具自己静默吞掉的读取错误看不出);
806
888
  Linux 上经 NFS 挂载、由服务端自己执行的 NFSv4 ACL 在客户端没有可读的条目,不在覆盖内;其它系统(OpenBSD / NetBSD / AIX / Cygwin 一类)的目标机每一次写都走就地写并计数。
807
889
  host 车道的暂存是引擎的,device 车道的暂存是设备侧执行器的 —— 这一道不管它们;adb 的追加 = 7.138.x 的形:读旧 + 拼 + 整份 push(push 没有追加写)。Android 共享存储(`/sdcard` 一类)上 adb 的写按 sdcardfs / MediaProvider FUSE 的权限语义**推断**每一次都走这一形(未上真机,真机读数候 test)—— adb 的工作区放在 `/data/local/tmp` 一类认权限位的目录即照常暂存。
808
890
  - 一个身份:准备目录、上传、提交、清理都以同一个身份做。local-docker 的上传是容器内以镜像的用户经 stdin 收字节(不是守护进程以 root 的 `docker cp`)——
809
891
  写文件与代理的 shell 同一套权限,镜像用户写不了的路径答 `permission_denied` —— 镜像里没预建的工作区目录由容器启动以 root 建出,镜像用户 ≠ root 时要在镜像里预建并归镜像用户。
810
- - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
811
- 目录不收新名字(只读目录里的可写文件)⇒ 直接写目标一次(旧形,同样非原子;远端车道在这一形里写目标那一步因取消中断答 `unknown`「目标可能只写了一半」,不答 `aborted`;取消落在那一步开始之前(读旧也在这之前)照答 `aborted`、目标未碰;那一步不收取消(ssh 的 sftp)⇒ 照样写完、答成功;
812
- host 车道的回落形由引擎就地写,中途取消目前仍答 `aborted` —— 已知缺口,修在引擎)。
892
+ - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内;那里的私有目录同样核不过 ⇒ 与上面同一条退路:清掉原暂存件、直接写目标、计 `stage-seal-fallback`);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
893
+ 目录不收新名字(只读目录里的可写文件)⇒ 直接写目标一次(旧形,同样非原子;远端车道在这一形里分两段读取消:备件阶段〔第一个字节发往目标机之前:adb 在本机落临时件、ssh 开 sftp 通道含懒重连、k8s 建连、追加的读旧 + 拼〕取消 ⇒ `aborted`、目标未碰;传输那一步因取消中断 ⇒ `unknown`「目标可能只写了一半」,不答 `aborted`;那一步不收取消(ssh 的 sftp)⇒ 照样写完、答成功;
894
+ host 车道的回落形由引擎就地写:取消只在第一个字节之前生效(答 `aborted`、目标未碰),已开写的照样写完、答成功或它自己的错)。
813
895
  已有文件的权限位带过去;属主不带(替换后的文件属于写入方)。
814
- - 已知语义变化(与引擎 / 参照实现同形):rename 换的是目录项 ⇒ **硬链接被断开**(另一条链接留旧内容);目录可写时**没有写权限位的文件也会被替换**(权限位保留);
815
- host 车道上名字超过约 230 字节(随服务进程号的位数浮动)的文件写不进(引擎的暂存名超出 255 字节的名字上限 ⇒ 错误码 `unknown`,`ENAMETOOLONG`;远端五车道的暂存名会截短,不受影响)。
896
+ - 已知语义变化(与引擎 / 参照实现同形):rename 换的是目录项 ⇒ **硬链接被断开**(另一条链接留旧内容);目录可写时**没有写权限位的文件也会被替换**(权限位保留)。
897
+ 名字逼近 255 字节上限的文件照常写成:host 车道(引擎)与远端五车道的暂存名都截短到上限之内。
816
898
  - `appendFile`(后台输出镜像等)= **追加**:暂存的只是要加的字节,提交是目标机上一次 `cat >>`(host = 本服务的一次 O_APPEND 写);inode 不换(`tail -f`、硬链接照旧)、已有字节从不重写;
817
899
  取消 / 传输中途断 ⇒ 一个字节不加;提交本身在目标机上失败(如磁盘满)⇒ 可能只加了一部分。提交那一次往返本身断了 ⇒ 错误码 `unknown`,
818
900
  句子写明「提交跑没跑不可知」—— 重试前先读一次目标。
@@ -849,10 +931,10 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
849
931
  # k8s 集群内 pod 可指内网 MinIO 省公网带宽)
850
932
  ```
851
933
  - **词表优先级**:内网端点 `MINIO_ENDPOINT` 优先,`S3_ENDPOINT` 是外接 S3 兼容位(内网/公网同址场景可只给它,同时充当公网面);公网端点 `S3_PUBLIC_ENDPOINT` 优先,缺席回落 `S3_ENDPOINT`。空串一律按未设处理。
852
- - **🔴 云形(`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 默认无帽)。
853
- - **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 的发布顺序补值后再改写执行;这是起服对账拒启句同一生成器的既有性质。
934
+ - **🔴 云形(`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 默认无帽)。
935
+ - **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`)。打印的语句全部生效之前起服一直拒 —— 只执行了第一句(列已在、仍可空)同样拒,并只再给后两步;空表上打印的语句逐字全部可执行。
854
936
  - **会话同步重推(同图)要读 blob**(7.136.0 起):`POST /v1/sessions/:id/sync/import` 的 identical 重推臂,对云端「已握着同一张文件历史图」的目标,也先把信封引用的每一枚 blob 经云端自己的店读一遍、核哈希(不回写),再答成功 —— 对象存储里的对象若在图发布之后丢了,这一腿答 `422 missing_blob`(`PUT` 缺的 blob 再推;图不动),不再答 `200 identical`。代价:无变化会话的频繁重推多付对象存储读流量(每枚引用的对象一次下载,正比于引用的 blob 字节);没有旋钮。
855
- - **文件历史捕获总开关**(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)。
937
+ - **文件历史捕获总开关**(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)。
856
938
  - **🔴 公桶策略必须 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)。
857
939
  - **两条轨**:ttl=0(默认)→ 匿名 GET 公桶下 `uuidv7/<name>` 不可猜 key,链接永久、可回收(删对象);0<ttl≤7 天 → 私桶 + SigV4 限时签名链接(7 天是 SigV4 物理上限,更久用 ttl=0)。
858
940
  - **执行 lane 与租户门**:e2b/k8s 沙箱 lane 任意租户可用(沙箱文件系统=租户边界,沙箱内 `curl -T` 直传、字节不中转、凭据不进沙箱);host/ssh lane 仅单用户部署(`REQUIRE_PRINCIPAL` 未开)时挂载。
@@ -944,15 +1026,28 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
944
1026
  - 中心**空/未发布** → `applyEffective` 回落 env + 内建 teams 并 warn `config_center_unpublished`,**不影响在跑的服务**(接了也安全)。
945
1027
  - **逐条容错**(7.101.0 起):目录里一条条目不合形(如 `contextWindow:"abc"`)⇒ **只丢那一条**(warn `config_entry_dropped`,
946
1028
  点名坐标 `models.<下标>`、闭集原因词 `reasonCode` 与字段诊断;7.123.0 起同一条也在 `GET /v1/models` 的 `droppedEntries` 里自报),其余条目照用;refresh 拍 / 迟到的 boot 拉取**不再因此整拒候选**(此前一条坏条目让整份目录停在
947
- 上一代 —— 上一代若还是 env,env 模型就一直服务)。颗粒更粗的失效(整个域读不懂、拼错键、坏文件)仍整拒候选、保 LKG。
1029
+ 上一代 —— 上一代若还是 env,env 模型就一直服务)。**条目体内带契约不认识的键**(比本服务新的写者写的前瞻键、拼错的键,如 `thinkingLevelMapX`)与形错同律(7.148.0 起,settings-schema 22.0.0 / 23.0.0 / 24.0.0):
1030
+ 对「条目缺席只会收窄可达面」的域(`models` `mcp` `systems` `collab` `hosts` `projects` `a2a`)只丢那一条、词 `entry-unknown-key`,不再剥键照装、也不再整拒候选 —— 条目不装,**删掉那个键或改对拼写即回**。
1031
+ 一张**值名单**(工具名单 `allowTools`、范围名单 `scenarios` / `rosters` / `allowSkills`、`gitRemotes`、模型 `promptGuidance` …)里坏一个元素 ⇒ 持有它的那一条整条丢(词 `entry-schema-invalid`,诊断句点到元素;mcp 服务器 / a2a 对端不挂载,模型进 `droppedEntries`),不逐元素截短 —— 被截短的拒绝名单比作者所写更松(被截短的允许名单则少放了作者要放的),都不是作者写的那一张。
1032
+ **条目缺席会放宽可达面的收紧域不逐条丢**:`scenarios`(条目覆盖内建场景名、收窄工具面;条目丢 ⇒ 内建复活)、`plugins`(条目是来源引用,承运的 hooks 可以是守卫;引用丢 ⇒ 守卫缺席)与 `skills`(条目按名覆盖出厂技能、收窄工具面 / 加拒绝名单;条目丢 ⇒ 出厂技能回位)—— 条目体内的未知键 ⇒ 整份候选拒
1033
+ (`config_candidate_rejected`,`domains` 含该域);坏形条目(`skills` 还有同名两条、`skills: null`、拒绝名单 / 工具名单里的坏元素)⇒ 整域读不进、整包读被拒(成因 `rejected`):整份中心配置落回上一份好配置,首启无 LKG 落回 env(刷新拍保持上一代,运维刷新口 502);
1034
+ `CONFIG_PROVIDER=local` 的 `config.d/scenarios.json` / `plugins.json` / `skills.json` 同(没有 LKG:起服落回 env)。一条坏条目会让这三域的全部覆盖 / 引用随这份配置一起不收 —— 删掉那一条或改对即回。
1035
+ 中心发来的 skills 清单(`/effective` 的 `skills`)与 `config.d/skills.json` 同一只判官:任一条坏 / 缺 `contentHash` / 同名两条 / `null` ⇒ 整份候选不收。
1036
+ 颗粒更粗的失效(整个域读不懂、域顶层拼错键、坏文件)仍整拒候选、保 LKG。
948
1037
  - **没有 `MODEL_ID` 也能起**(7.101.0 起):目录(`config.d/models.json` 或中心)有可用条目时 env 可以不写 `MODEL_ID` —— 缺省模型取
949
1038
  目录缺省。判据在目录应用**之后**:env `MODEL_ID` 或目录缺省任一在场即过;两者皆无 ⇒ 拒启(码 `config.default_model_missing`,指路二选一)。
1039
+ 若是因本地候选被判官整拒(悬空符号链接 / 坏 JSON / 空文件 / 域判形失败 …)才落到 env 表,7.147.0 起拒启句同一行多一段回落原因:被拒的文档名、拒码、原因类词(悬空链接 / 读不出 / 解析错误 / 空文件 …)与文档路径(悬空链接给出链接自己的路径,不带它指向的目标路径;不带配置值,原句只在 `config_candidate_rejected` 行);句首 `MODEL_ID is required when no catalog default resolves [config.default_model_missing]` 不变。
950
1040
  中心拉取超出 `CONFIG_BOOT_FETCH_BUDGET_MS` 又没有 LKG 时,boot 那一刻没有目录 ⇒ 没有 `MODEL_ID` 兜底就拒启(调大预算让 boot 等目录,
951
1041
  或写一个 `MODEL_ID` 兜底)。
952
1042
  - **灰度姿势**(配置控制面 AI 建议):先 `SEMA_WEB_ADMIN_DRY_RUN=true` 起一轮,看日志 `web_admin_dry_run`(中心给的 models/roles/teams + 会否覆盖 default、per-model apiKeyEnv)对得上 env 再去掉该 flag 真正 apply。
953
1043
  - 拉取**只读、只取逻辑配置**(模型名册/角色/团队);密钥/网关仍在本服务 env(中心只发 env-**名** 引用,不发密钥值)。回滚=去掉 `SEMA_WEB_ADMIN_URL` 即纯 env。
954
1044
  - **配置热更新真表(7.38+ 现行)**——refresh 拍(60s 轮询,或 `POST /v1/admin/config/refresh` 手动触发,见 API 表)对各域的生效方式:
955
1045
 
1046
+ **「热」的前提是部署有配置管道**:`CONFIG_PROVIDER=local`(读本地配置根下的 `config.d`)或接了中心(`SEMA_WEB_ADMIN_URL`;不写 `CONFIG_PROVIDER` 时有它即走中心)。
1047
+ 两者都没有(缺省)= 纯 env:HTTP 服务不读 `config.d`、没有刷新拍,下表的「热」对它不成立,配置只来自 env。刷新节拍固定 60 s、没有旋钮;配置没变的那一拍
1048
+ (中心答 304 / 本地树内容没变)不打 `web_admin_refreshed` 行 —— 看不到它不说明刷新坏了,读 `/health` 的 `configTargetVersion` / `configAppliedVersion`。
1049
+ 不想等节拍就调手动刷新口(要运维名册,名单外 403,见 API 表)。
1050
+
956
1051
  | 域 | 生效 | 说明 |
957
1052
  |---|---|---|
958
1053
  | models / roles / default 模型 | **热**(下一 refresh 拍) | 全 boot Runner **原子换代**(swap 失败=候选整拒,活配置零触碰);此前「改 models 需重启」的时代已随 Runner swap 腿落地终结 |
@@ -961,7 +1056,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
961
1056
  | plugins(插件引用) | 重启生效 | 插件只在启动期物化;改了之后 `/health` 报 `restartRequired`,`restart.reasons` 含 `skills`(7.100.0 前这一改动**不报**,要等一次无关的重启) |
962
1057
  | skills / mcp / a2a / scenarios(技能 / MCP / A2A / 场景) | 重启生效 | 只在启动期物化;中心改了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 各含其名。**换账号**(凭证身份变化)同理:这些面仍是启动时那个人的直到重启(热面则在换人那一刻降档、新身份的候选落地时换代,见上文 `SEMA_WEB_ADMIN_TOKEN_FILE` 段) |
963
1058
  | 审批名单(`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`,该理由退役) |
964
- | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次;中心改了 roles / 档位 / 目录条目后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报) |
1059
+ | 记忆整理阀门判决 + 驱动席(`MEMORY_CONSOLIDATION_DRIVER=on`,或未设而出厂缺省按目录派生 —— 7.104.0 起) | 重启生效 | 阀门开关与整理跑用的模型都在启动期定一次,那只模型的凭据身份随它冻结;中心改了 roles / 档位 / 目录条目(含那一条的凭据身份:重贴 `sealedApiKey`、换 `apiKeyEnv` 变量名)后,**判决会翻**(派生开 ↔ 关,含只把主模型挪到整理座那一条上)或开着而座漂了 ⇒ `/health` 报 `restartRequired`,`restart.reasons` 含 `memory-consolidation`(7.100.0 新理由;7.104.0 起派生关的那台同样报;变量值的同名轮换不报、不用重启) |
965
1060
 
966
1061
  生效与否的观测口=`/health` 世代账键(`configTargetVersion`/`configAppliedVersion`+两 ordinal+`configApplyStaleMs`,见 API 表 `/health` 行)——target≠applied 持续=「改了没生效」的机读信号。
967
1062
  - **`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 店 ⇒ 整组拒」只说服务腿)。此前三张名单在这条腿上无声不生效。
@@ -1009,7 +1104,7 @@ QUESTION_TTL_MS=300000
1009
1104
  > —— 壳/SDK 不实现消费就等于用户看不到审批卡。🔴 **7.133.0 起一条腿一个帧族**:协议上场的 worker(`GET /v1/capabilities` 的
1010
1105
  > `streamApproval` 为真)只发 `approval_request` / `approval_complete` / `approval_revoke`,旧族 `tool_approval` / `tool_approval_complete`
1011
1106
  > 不再发;协议不上场的 worker 只发旧族。帧格式、消费端契约、五合取全表在
1012
- > [`docs/ASSISTANT-WIRE-CONTRACT.md` §4a-quint](docs/ASSISTANT-WIRE-CONTRACT.md)(错误码 `feature.approval_ask_disabled` 在附录 A)。
1107
+ > 契约 §4a-quint(内部记录;错误码 `feature.approval_ask_disabled` 在契约附录 A)。
1013
1108
  > 🔴 **7.134.0 起卡只发给声明认得这一族的客户端**:提交体 `settings.approvalFrameFamilies: ("tool_approval" | "approval_request")[]` 声明这条 run 的客户端
1014
1109
  > 渲染得了哪些族;一条 run 只在声明含 `GET /v1/capabilities` 顶层 `approvalFrameFamilies` 那个词时收到活卡。缺席 / 不含 ⇒ 这条 run 不呈卡(不换族、不双发)、
1015
1110
  > 开流不重放挂着的卡,每只审批问(会话里已记住的放行照旧先兑现)当场收场,结局看这台 worker 有没有耐久店(`DURABLE_APPROVAL` 开着):在场 ⇒ 交 `UNATTENDED_APPROVAL_POLICY`:`park`(缺省)⇒ 任务停驻、`/v1/approvals/:sessionId/decide` 作答;`deny` ⇒ 当场拒,拒因句点名缺的族与出路;
@@ -1099,7 +1194,7 @@ UNREACHED_ASK_TTL_MS=3600000
1099
1194
  ⚠️ **这条腿上的窗照样是 `STREAM_ASK_WINDOW_MS`**:此前它被硬编码成 5min、旋钮在这些部署上
1100
1195
  静默无效(慢组场景只能真等满 5 分钟);现在调它就是调这条腿。另:这类部署上**迟到决议**
1101
1196
  (卡挂过窗、人才按 Yes)不能走 `POST /v1/tool-approvals/:id/respond`(没有持久 ask 行 ⇒ 恒 404
1102
- `tool_approval.not_pending`、不带 `cause`),要走 `POST /v1/approvals/:sessionId/decide`
1197
+ `not_found.ask`;7.147.x 及以前 `tool_approval.not_pending`、不带 `cause`),要走 `POST /v1/approvals/:sessionId/decide`
1103
1198
  ——前提是配了 `DURABLE_APPROVAL=true`(local 车道有文件形 checkpoint 店),否则窗到期即 fail-closed 拒。
1104
1199
  ⚠️ **上面这段「没有持久 ask 行 ⇒ 恒 404」自 7.81.0 起不再适用于 `DB_BACKEND=local` 本身**:local 的
1105
1200
  ask 账已是文件形,配齐 park 设施的单机部署上迟到决议走 `respond` 的赎回席是通的;本段现在只说
@@ -1109,7 +1204,7 @@ UNREACHED_ASK_TTL_MS=3600000
1109
1204
  (代价是超限的 ask 走 park,要有人去审批队列捞);库压大 → 调小 `STREAM_APPROVAL_RECONCILE_BATCH`
1110
1205
  (代价是收敛变慢,靠队列轮转保证下轮接着扫)。
1111
1206
  - **停驻行最长活多久(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`(两店同辖)。
1112
- - **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)行,取行序是「卡列有词的先取,其后按到期时刻」:混跑窗里旧副本停驻的问答行(卡列为空)排在带卡词的真到期行之后。
1207
+ - **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`)。
1113
1208
  - **审批账本保留多久(`APPROVAL_ASK_RETENTION_MS`,缺省 `2592000000` = 30 天;local 文件后端与 SQL 店同一条律)**:审批在盘上有三本账 —— 问账(SQL 的 `approval_ask` / `approval_batch`,local 的
1114
1209
  `<数据根>/approval-asks/asks.jsonl`)与 local 审计账(`<数据根>/approval-ask-audit/ledger.jsonl`,`GET /v1/approvals` 的 `crashConverged` 读它)。一问**结了**且它最后一次动静早于
1115
1210
  `now − APPROVAL_ASK_RETENTION_MS`,它在三本账里的全部记录由维护 tick 整问删掉。「结了」= 该问在它那本账里到了终局,**且它挂靠的东西都不再能动**:挂靠 = 发起它的 run、宿主会话、
@@ -1122,8 +1217,11 @@ UNREACHED_ASK_TTL_MS=3600000
1122
1217
  非终态的问行(`STREAM_PENDING` / `PARKING`)永不按龄删 —— 它们归对账器与起服收敛。节律:首拍即清扫一次,之后每小时至多一次;每次先从 run 账、后台子代账与 checkpoint 店列出还能动的东西(任一读不出 ⇒ 这次不删),
1123
1218
  问账一批至多 500 行、一次清扫删到不满一批为止。读数:起服一行 `approval_ask_retention`(`windowMs`;`0` = 关),真删了东西时一行 `approval_ask_retention_swept`
1124
1219
  (`cutoffMs` / `asks` / `batches` / `auditAsks` / `auditRecords`;半途失败时已经删掉的照样打,带 `incomplete: true`)+ 计数器 `approval_ask_retention_rows_deleted_total{ledger=asks|batches|audit}`。
1125
- local 的问账日志上,任何真删了行的调用(留存清扫的两只动词与 `deleteByTask`)的那一拍,同步把整本原子重写成存活行的快照 —— `asks.jsonl` 里同拍不再有被删的问的字节(含 `Write` / `Edit` 这类工具实参的字面);没删东西的调用不落记录、不重写;
1126
- 压实失败不影响调用(行照删,计 `fail_open_total{tag="server.approval-ask-journal.compaction-failed"}`,日志里留着删行记录,下一次写或下一次起服再压实)。
1220
+ local 的问账日志上,任何真删了行的调用(留存清扫的两只动词与删会话时的审批腿)的那一拍,同步把整本原子重写成存活行的快照 —— `asks.jsonl` 里同拍不再有被删的问的字节(含 `Write` / `Edit` 这类工具实参的字面);没删东西的调用不落记录、不重写;
1221
+ 留存清扫的压实失败不影响调用(行照删,计 `fail_open_total{tag="server.approval-ask-journal.compaction-failed"}`,日志里留着删行记录,下一次写或下一次起服再压实);
1222
+ 保留窗只管「没人删」的那部分:删一只会话(`DELETE /v1/sessions/:id` 与 SQL 后端的托管留存清扫,同一只谓词)同拍删掉问账里挂靠它的问 —— 宿主会话或发问那条腿的会话是它
1223
+ (没有会话的 adhoc 腿把自己的 run id 填进会话格,那样的行挂靠的是 run,不算)—— 与它们所在的批;已经没有问的空批不带会话格,由本条按龄收;审计账不随会话删。
1224
+ 删会话时的审批腿不同:被删的问的字节没离开账本就不算删完 —— 压实失败 ⇒ 这次会话删除答 500、会话行留着,重发删除即补压。
1127
1225
  起服时因撕尾 / 坏行隔离出去的原件 `asks.jsonl.corrupt-<ts>` 是取证材料,**没有清扫腿**,里面仍有当时的字节,要清由人来(正常部署恒零)。
1128
1226
  **关法**:`APPROVAL_ASK_RETENTION_MS=0` = 永久保留(要在升级后的第一次清扫之前设 —— 首拍即扫);正值低于 1 天钳到 1 天并在启动告警里点名(刚结束的 run 的客户端还可能对它的卡重试 / 晚答,
1129
1227
  一天吸收这一段);负值 / 非整数 / 非数拒启。run 结束超过窗之后才对它的卡作答 ⇒ 决断口与流内审批口答 404。
@@ -1175,6 +1273,10 @@ UNATTENDED_APPROVAL_POLICY=park
1175
1273
  - 已知边界:「本会话放行」记在本部署的会话豁免店(SQL 后端跨副本共享;`DB_BACKEND=local` 只在这台机器的数据根里)。续跑落到另一台**读不到这只店**的
1176
1274
  实例上(local 车道换机 / 换数据根)⇒ 名单的问再问一次 —— 方向是多问一次,不是静默放行。名单靠别名命中的调用(嵌入方给工具挂了 `Bash` 别名)
1177
1275
  按调用的 wire 名记、按 wire 名读;一张卡上同时有名单的问与别人的问(人写的 ask 规则、强制位、读会话目录外 ……)⇒ 那张卡的「本会话放行」不记。
1276
+ - 已知边界(两只家并存):活卡上对**引擎的问**点的「本会话放行」记在审批席的进程内表(按副本、随进程;能力桶:`Write` 盖 `Edit` / `NotebookEdit`),**不在**上面那只豁免店里 ——
1277
+ `GET /v1/approvals/:sessionId/exemptions` 列不出它、`DELETE …/exemptions/:toolName` 撤不到它(回 `revoked:true` 只说耐久那一行没了)、删会话与留存清理也清不掉它,换副本 / 重启即失。
1278
+ 名单的问点的「本会话放行」与停驻之后 `remember:"session"` 记的放行在豁免店里,列表与撤销都管。读豁免店的两道策略层门(`APPROVAL_REQUIRE` 名单门、`run_workflow` 问句门)经同一只读口:
1279
+ 两秒内读不到(店慢 / 店坏)⇒ 照问;店答的不是布尔真值也不放行。命中时的审计行是 `approval_exempted`(字段 `seat`:`deployment_list` / `workflow_gate`;`run_workflow` 问句此前的事件名 `workflow_gate_exempted` 已退役,按它告警的运维改读这一条)。
1178
1280
 
1179
1281
  **无人值守腿:`default` 模式 + 按类作答的审批席(7.109.0 起)—— 没有旋钮**
1180
1282
 
@@ -1247,7 +1349,7 @@ WRITE_PROTECTED_TABLE_REPLACE='[{"name":".envrc","kind":"basename"}]' # 整表
1247
1349
  - **配置根(`SEMA_CONFIG_DIR`)之内的整棵树都在这张表的射程里**(引擎 7.46.0 起):缺省表里的 `.sema` 行不只守家目录下的 `~/.sema`,也被放在你声明的配置根下 ——
1248
1350
  根之内**任何**路径(不止配置文件)的写(`Write` / `Edit` / `NotebookEdit`)都要过 `write_protection` 一问。所以**配置根不要设成工作目录本身或它的祖先**
1249
1351
  (容器里 `SEMA_CONFIG_DIR=/workspace` 而项目在 `/workspace/proj`、或直接设成 `HOME`):项目里的每一次写都出一张卡,allow 规则清不掉它,`acceptEdits` 不放,
1250
- `dontAsk` 拒,无人值守腿(leader、无终端的 `run-local`)拒(leader 配了耐久审批时停驻等人答),只有 `bypassPermissions` 放行。**起服不报、首条任务也不报** —— 第一次知道是第一次写。出路:把配置根指到项目之外的专用目录
1352
+ `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` 落在配置根之下的部署,第一次知道仍是第一次写。出路:把配置根指到项目之外的专用目录
1251
1353
  (不设即缺省 `~/.sema`);确实要保留祖先形配置根的,`WRITE_PROTECTED_TABLE_REPLACE` 给一张不含 `.sema` 行的表可去掉这一问,代价是 `~/.sema` 下的这一问一并没了。
1252
1354
  - **与 `SENSITIVE_WRITE_PATTERNS` 并列、不合流**:那一根是**模式**(正则/glob)型 DENY 集,这一根是
1253
1355
  字面名表的 `allow → ask` 降级。两套互不覆盖,读面也分开报 —— 一条路径被哪一套拦住,是两个不同的问题
@@ -1286,8 +1388,7 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
1286
1388
  **不变**。⚠️ **但「不铸」在引擎里的读法自 core 7.22.0(内置读 deny 表出厂 OFF 的裁定)起翻了面**:`READ_DENY_BUILTIN_TIERS`
1287
1389
  未设 = **OFF**(内建表一行不生效),此前是「除 shell-history 外四档全开」。⇒ 一台没配过这根 env 的机器
1288
1390
  升级后**读得到**根内的 `.npmrc` / `.ssh` / `.aws` / `.claude/settings.json` 这类名字,且整个递归读族
1289
- 零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`
1290
- (运维面全文见 `docs/DEPLOY-PREREQS.md` 的 core 7.22.0 段)。`open` 与这张表仍是**两个并列机制**:
1391
+ 零卡执行。**要老姿态就显式写** `READ_DENY_BUILTIN_TIERS=credentials,browser,wallet,agent-config`。`open` 与这张表仍是**两个并列机制**:
1291
1392
  `open` 放开的是「工作区外」那道围栏,内建表判的是名字。
1292
1393
  🔴 **7.106.0(core 7.34.0 R-2)再翻一次**:未设 = **`credentials` + 家目录锚定的 `agent-config` 两档在岗**(`browser` / `wallet` /
1293
1394
  `shell-history` 仍是选装;显式 `READ_DENY_BUILTIN_TIERS=none` 仍是空姿态)⇒ 未配置的机器升级后**读不到**根内的 `.npmrc` / `.ssh` / `.aws` 这类名字
@@ -1303,8 +1404,7 @@ READ_DENY_PATTERNS='[{"pattern":"**/secrets/**"},{"pattern":".ssh","caseSensitiv
1303
1404
  `read_path_denied`(带 `target` 与**命中的那条 pattern**),`gate.disposition.deniedBy = "read_boundary"`。
1304
1405
  同批**根内的内容遍历**(`grep -r` / `diff -r` / `rg`)改成**枚举**:树下没有拒表行 ⇒ 零卡执行,
1305
1406
  有 ⇒ 点名那一行拒。⇒ **要让某条路径重新读得到,就把它从这三张表里拿掉**(删掉那条自配模式 / 档位
1306
- 不选 / 用 `READ_DENY_BUILTIN_EXCLUDE` 删掉那一行);三问与运维面全文见
1307
- `docs/DEPLOY-PREREQS.md` 的 `core 7.25.0 提货` 段。
1407
+ 不选 / 用 `READ_DENY_BUILTIN_EXCLUDE` 删掉那一行)。
1308
1408
  · 「这一档到底是谁定的」由 operator 面 `GET /v1/diagnostics/wiring` 的 `readFace` 段答:
1309
1409
  `{face, source, note}`,`source` ∈ `env` / `center` / `lane` / `posture` / `engine-default`:`env`(本机 env 显式)/ `center`(组织下发)/ `lane`(隔离型执行车道的契约行声明,带挂载告诫)/
1310
1410
  `posture`(单机派生)/ `engine-default`(没钉,引擎当家),`note` 是指路句。
@@ -1395,9 +1495,11 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1395
1495
  🔴 **签发方配置不算证据**:`PRINCIPAL_JWT_PUBKEYS` / `_ISS` / `_AUD` 与 `AUTH_BRIDGE_ISSUER` 在不在场都不改变判定
1396
1496
  (7.95.0–7.98.x 曾把它们当证据,并对「配了签发方却没设 `REQUIRE_PRINCIPAL`」的机器拒启;那条拒启随之删除)。
1397
1497
  auth-bridge 那条 `?? webAdmin.baseUrl` 回退、`SEMA_WEB_ADMIN_*`(含 worker 名)同样不读。
1398
- ⚠️ 反面是**全量召回缺口**:忘设 `REQUIRE_PRINCIPAL` 的团队机器按单用户运行、本门放行 —— 多租机器必须自己设它,
1399
- 后果与被排除的候选(`OPERATOR_PRINCIPALS`、`SERVICE_AUTH_TOKENS` 条目数、`DB_BACKEND`、`BIND_HOST`)逐条写在
1400
- `docs/DEPLOY-PREREQS.md`。
1498
+ ⚠️ 反面是**全量召回缺口**:忘设 `REQUIRE_PRINCIPAL` 的团队机器按单用户运行、本门放行 —— 多租机器必须自己设它。
1499
+ 后果:缺席的主体走单用户 `default` 赦免,而那条赦免同时是 `settings.hooks` / `settings.env` / `cwd` / `additionalDirectories` 四条能力授予面的闸 ——
1500
+ 一台多用户机器按单用户执法,这些面对每个调用方打开(四条的谓词各不相同,别照「都一样」读:`settings.hooks` 只看 `REQUIRE_PRINCIPAL`、不看车道;`additionalDirectories` 只在 `REMOTE_EXEC=host` 且单用户时兑现;
1501
+ `settings.env` / `cwd` 另有 device 车道臂)。被排除的候选各自是单机常态,证明不了多租:`OPERATOR_PRINCIPALS` 非空(开了 `IMAGE_BAKES_ENABLED` 的单机被强制填它)、
1502
+ `SERVICE_AUTH_TOKENS` 条目数(一台机器给 cli 与 web-admin 各发一只令牌)、`DB_BACKEND=mysql|pg`(耐久性轴)、`BIND_HOST` 非回环(容器里 `0.0.0.0` 是单机常态)。
1401
1503
  - ⚰️ **提交体 `approverPosture` 7.106.0 退役**(core 7.34.0 的单键权限模式):7.93.0–7.105.0 上它是一句宿主姿态声明、托管形上被忽略并留声;
1402
1504
  core 7.34.0 起按名拒这个键,本仓整键删 —— fresh 提交体仍带它 ⇒ 400 `request.body_shape`(`unknownKeys:["approverPosture"]`);续跑腿重放不过受理门、
1403
1505
  盘上体里的该键零读点(惰性),parked 任务不因它砖死。能力位 `taskApproverPosture` 同批删。
@@ -1428,7 +1530,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1428
1530
  被撤名单,别按「我加回去了」推断。
1429
1531
  当前被撤名单可从 `GET /v1/diagnostics/wiring` 的 `mcpRevocations.revoked` 读(`null`=本进程无
1430
1532
  配置管道);`GET /v1/sessions/:id/mcp` 面板与执行面同一台账(被撤服务器的工具不再展示)。
1431
- 纯 env 配置的 `MCP_SERVERS` 部署无中心撤销语义,行为逐字不变。
1533
+ 没有配置管道的部署(纯 env)没有部署级 MCP 声明 —— 本服务没有声明 MCP 服务器的 env 旋钮 —— 也就没有撤销语义。
1432
1534
 
1433
1535
  **技能从哪来(来源与覆盖序)**
1434
1536
 
@@ -1440,7 +1542,7 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1440
1542
  不会降成每个场景都有的全局技能;第一行不是围栏的文件没有 frontmatter,仍是全局技能。兄弟技能照装。
1441
1543
  2. **配置源的技能**:配了中心 = web-admin 的 skills 域;`CONFIG_PROVIDER=local` = `config.d/skills.json`(`{ "skills": [{ "name", "description", "content", "scenarios"?, "enabled"?, "allowedTools"?, "disallowedTools"? }] }`,
1442
1544
  正文写在 `content` 里;两只工具名单 = 字符串数组,缺席 = 没有,在场 = 整只原样交给引擎:`allowedTools` 是该技能被激活的那一 run 的免问规则来源,`disallowedTools` 是拒规则来源,
1443
- 拒侧引擎读成空的条目 ⇒ 那次激活不发生(`userInvokedSkills` 按名点用的终局 `failed`、码 `config.skill_deny_unreadable`;模型自己调 `Skill` 工具的,那一次调用被拒);中心的技能域同形)。**按名盖在出厂技能上**(同名 ⇒ 配置源这条赢),`enabled: false` 的不装;两种配置源同一条路径(7.128.0 起;此前本地形整域不生效)。
1545
+ 拒侧引擎读成空的条目 ⇒ 那次激活不发生(`userInvokedSkills` 按名点用的终局 `failed`、码 `config.skill_deny_unreadable`,同步腿零工作拒 **409**(7.148.0 起;此前 HTTP 200 + failed 体且占着幂等键 —— 运维改好那一条之后同一请求原样重发即可);模型自己调 `Skill` 工具的,那一次调用被拒);中心的技能域同形)。**按名盖在出厂技能上**(同名 ⇒ 配置源这条赢),`enabled: false` 的不装;两种配置源同一条路径(7.128.0 起;此前本地形整域不生效)。
1444
1546
  起服每装上一条一行 `web_admin_skill`(`overrodeBuiltin` 说盖没盖出厂的);拿不到正文的一条一行 `web_admin_skill_failed`(`reason`:`body_absent` / `read_failed`;**正文为空串视同缺席**:`content` 缺省或写成 `""` 的条目,本地与中心同判 `body_absent`),
1445
1547
  这一条不装、同名出厂技能保留、起服照常。重启生效(与 MCP 同:改了之后 `/health` 的 `restart.reasons` 含 `skills`)。
1446
1548
  3. **插件带的技能**(plugins 域):只补**没被占的名字**,撞名让位(起服行 `center_plugins_applied` 的 `letGo`)。
@@ -1457,7 +1559,8 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1457
1559
  每条 `{ name, description, source }`:`source` 是 `"factory"`(第 1 条,本部署的技能目录)或 `"center"`(第 2、3 条,配置源交来的;盖掉出厂同名的也是 `"center"`),不带正文。
1458
1560
  与这个场景的 run 交给引擎的是**同一只过滤**:卡上列的就是本服务交给这一 run 的部署技能,点名(提交体 `userInvokedSkills`)用它的 `name`;没有技能 ⇒ `skills: []`。
1459
1561
  两种情形卡上有、run 里没有:带 `disableSkills: true` 的那一发(一只都不装);正文超过引擎的加载上限(1 048 576 字符)的技能 —— 引擎不装它、每一 run 报一条配置错,点它的名字答 `400 config.user_invoked_skill_unknown`(缩正文或把材料挪进附件)。
1460
- 内建 `code` 与 `default` 共用一只工厂,技能按 `default` 过滤 —— 只写了 `scenarios: code` 的技能不进内建 `code` 的 run,卡上也不列(要进,标 `default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,按它自己的名字 `code` 过滤。
1562
+ 内建 `code` 与 `default` 共用一只工厂(工具面恒等),技能范围各按自己的名字过滤(7.148.0 起;此前 `code` 借 `default` 的范围,两个方向都违上面「写了 = 只进这些场景」):只写了 `scenarios: code` 的技能进内建 `code` 的 run、卡上也列,不进 `default`;
1563
+ 只写了 `scenarios: default` 的技能进 `default`、不进 `code`(要两个场景都进,写 `scenarios: code, default` 或不标);配置中心声明了同名场景 `code` 盖掉内建时,同样按它自己的名字 `code` 过滤。
1461
1564
  能力位 `scenarioSkills`(恒 `true`)缺席 = 老 server,卡上没有这一键;别从 `400 config.user_invoked_skill_unknown` 的拒句里刮名字(那是引擎的散文,会变)。
1462
1565
 
1463
1566
  **看一条 run 装着哪些技能(7.137.0 起)**:这条 run 的 `wiring_manifest` 帧(流式 SSE、`GET /v1/runs/:id/events`、非流式 `POST /v1/tasks` 200 体的 `wiringManifest`)带本服务自铸段
@@ -1487,18 +1590,19 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1487
1590
  运维 warn `mcp_injection_dropped`(`malformed_entry` / `field: toolAxes`)+ 用户面通告 `mcp.injection_dropped`(续跑家族同投);`POST /v1/capabilities/mcp/probe` 同拒同句,
1488
1591
  判官受理的声明与提交腿同读(只有空条目的工具 = 没声明,受理)。
1489
1592
  - **请求体自带的 `a2aPeers[].toolAxes`**(键 = 对端的远端 skill id)读同一只判官(引擎根导出的 `readToolAxes`,A2A 一面;形同上):读不出 —— 出词表、`egress` 非布尔、
1490
- **未知成员**、某只 skill 的值不是对象、`__proto__` 键、不是纯数据 —— ⇒ 那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑(与这条腿上其余字段坏形同一处置)。
1593
+ **未知成员**、某只 skill 的值不是对象、`__proto__` 键、不是纯数据 —— ⇒ **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)在入口 `400 request.field_invalid`,
1594
+ 拒句点名 `a2aPeers[i].toolAxes` 与 peer 名,整份请求不收、零 run、无 warn(7.136.0 起,拒形与判序见 9.5h;7.135.0 及以前这一形是「那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑」);
1595
+ **续跑重放存下的旧请求体**不拒(拒会砖死升级前停驻的任务):那一条对端整条不挂、点名进运维 warn `a2a_injection_dropped`,任务照跑(与这条腿上其余字段坏形同一处置)。
1491
1596
  此前本服务对 A2A 一面用自己的读法、未知成员静默忽略;引擎的准备门对同一份声明本就整单拒,两层现在是同一只判官、同一个方向。
1492
1597
  续跑重放升级前存下的请求体时与 `mcpServers[].toolAxes` 同一只一次性迁移 M19:窗内判官判不过的那一格按 7.131.x 的 A2A 读法重读一次(未知成员剔、三轴留,产物再过同一只判官),
1493
1598
  对端照挂、一行运维 warn `a2a_tool_axes_read_as_before_upgrade`(`peer` / `migration` / `problem` / `note`);已知轴写坏(出词表、`egress` 非布尔、某只 skill 的值不是对象、
1494
1599
  整份不是对象)的对端 7.131.x 当时就整条丢了,重读照旧整条不挂(`a2a_injection_dropped` 点名);窗关之后回到上面的读法(整条不挂)。
1495
1600
  - **请求体自带的 `mcpServers[].transport.env` / `transport.headers`**(「名 → 串」记录,与 `a2aPeers[].headers` 同一只读法):名叫 `__proto__` 的成员、值不是串的成员、
1496
1601
  整只不是纯数据 ⇒ 新提交 `400 request.field_invalid` 点名第一处(`__proto__` 成员点名成员级路径 `…headers.__proto__`;值不是串 / 整只读不成点名那个键,原因句点名成员)。
1497
- 续跑重放存量请求体(JSON 文本铸出来的记录)时复现 7.131.x 的读法,唯一偏离是引擎拒的 `__proto__` 成员:记录里有值不是串的成员 ⇒ 整键不交(7.131.x 原跑就是这样挂的,不半程换身份);
1498
- 等价只对 JSON 存体成立 —— 直接递 `Map`、类实例、`process.env` 的嵌入方按上面的更严读法(7.131.x 对带自有串属性的类实例照收);
1499
- 只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交。server 照挂,运维 warn `mcp_transport_members_dropped`(`server` / `lost` / `lostTotal?` / `note`)点名没交出去的键 / 成员与整键没交的
1500
- `transport.args` / `transport.principalHeader`。A2A 对端 `headers` 同读:值不是串 ⇒ 对端照旧不挂;只有 `__proto__` 成员坏 ⇒ 续跑照挂、剔掉它,一行 `a2a_peer_headers_members_dropped`
1501
- (`peer` / `lost` / `note`);新提交上两形都整条不挂。
1602
+ 续跑重放存量请求体时,按 7.131.x 的读法会交不出去的成员 —— `__proto__` 名的成员、有值不是串的成员的整键、`transport.args` / `transport.principalHeader` —— 在本部署兑现请求 MCP 时**拒续跑**(名字被部署占住的条目不算:它整条输给部署):
1603
+ `409 resume_blocked_by_policy`(`blockedBy: "request.field_invalid"`,`field` = 第一处成员坐标如 `mcpServers[1].transport.headers`;句子列出全部坐标,只给键名、不回显值),卡 / 任务仍待决、行不动;
1604
+ 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` ——
1605
+ 调用方只看到续跑成功,而 MCP 请求没带它写的鉴权头;这两行 warn 本版退役。出路:续跑请求带 `mcpServers` / `a2aPeers` 的合形整表(下一条「续跑时换表」;判的是盖过这张表之后的体),或取消那只任务、按本版的形重提。新提交上两形都 `400 request.field_invalid`(同一只判)。
1502
1606
  - **续跑时换表(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`;续跑请求体带上同名键(与提交体同形、同一只判官)就以它为准,
1503
1607
  并写回成之后续跑的表 —— 用户移除过一台 MCP 服务器之后,壳送此刻的整张表,续跑就不再重连那一台(停在那一台调用上的任务由引擎判:引擎 7.61.0 起三码各答各形,以 core 为准:那只工具不在了 ⇒ `resume.tool_unavailable`(引擎重开检查点:卡仍 pending、可再决);同名而已不是批准时那一只 ⇒ `resume.tool_contract_mismatch`(同样重开);还是那一只而它的面或声明变了 ⇒ `resume.target_moved`(这次调用不执行、批准用掉,须重新发起),本服务逐字过境);直连门部署的两道回决门拒收这两键(契约 §4e-bis)。
1504
1608
 
@@ -1513,7 +1617,7 @@ MCP_ELICITATION_TTL_MS=300000 # 一张没人填的表单挂多久后释放(
1513
1617
  ```
1514
1618
  - 四个节流钮都是 `numEnvBounded`:**越界启动期响亮拒,不静默夹取**(一个手滑的多小时 TTL 会让表单实际上永不过期)。
1515
1619
  - 总开关关着时四个节流钮解析照跑但无消费者——不设=行为不变。帧格式见
1516
- [`docs/ASSISTANT-WIRE-CONTRACT.md` §4a-quater](docs/ASSISTANT-WIRE-CONTRACT.md)(`elicitation` / `elicitation_complete`)。
1620
+ 契约 §4a-quater(内部记录;`elicitation` / `elicitation_complete`)。
1517
1621
  - **回给 MCP server 的词**(一事一词):没人应答的释放 —— 答题窗到期、run 中止、腿退出、表单没送达 —— 回 `cancel`;本部署按规则当场拒(没有 run 上下文 / url 形 / 节流越界)回 `decline`;
1518
1622
  用户经 respond 答的词原样落地。7.135.x 及以前,答题窗到期与表单没送达回 `decline`。判法与 helper 该怎么读见契约 §4a-quater 的「终局词律」。
1519
1623
  - 两帧同样**随 run 账本回放**(与上面的问题卡同一条规则:`/v1/tasks/stream` 腿有 durable run 行时先落账再直写,`/v1/runs` 腿只有账本),重载后经 `GET /v1/runs/:id/events` 取回表单卡;
@@ -1529,24 +1633,35 @@ A2A_SERVE_ENABLED=true # 总开关,默认 **false**(operat
1529
1633
  A2A_SERVE_URL=https://agent.example.com/v1/a2a # 【开了就必填】卡上公告的对外 JSON-RPC 端点
1530
1634
  A2A_SERVE_NAME=sema-edge # 卡上的 name(默认 "sema-server")
1531
1635
  A2A_SERVE_DESCRIPTION="…" # 可选
1532
- A2A_SERVE_SKILLS='[{"id":"review","name":"Code review","description":"…","scenario":"code-review"}]'
1636
+ A2A_SERVE_SKILLS='{"version":2,"skills":[{"id":"review","name":"Code review","description":"…","scenario":"code-review"},
1637
+ {"id":"health","name":"Health","scenario":"health","audience":["user:alice"]}]}'
1533
1638
  A2A_SERVE_BLOCKING_WAIT_MS=45000 # blocking:true 的服务端等待窗(默认 45000,有界 [0,300000])
1534
1639
  ```
1535
1640
  - **`A2A_SERVE_URL` 开了就必填,且必须是可解析的 http(s) URL** —— 缺席/坏形**启动期拒**。可达地址是
1536
1641
  运维声明的,服务端**不从请求 `Host` 头推导**:那等于让一个匿名的发现请求决定我们公网卡上公告的地址。
1537
1642
  (同族先例:`FLEET_ADVERTISE_ADDRESS`。)🔒 URL **不许带 userinfo**(`https://user:pass@host/…`)——
1538
1643
  它逐字上匿名可拉的卡,等于把凭据发给每个发现请求;带了**拒启**。入站鉴权走**头形**(服务凭据)。
1539
- - **`A2A_SERVE_SKILLS` = JSON 数组,坏形拒启**(非 JSON / 非数组 / 条目缺 `id` 或 `scenario` / `id` 重复
1540
- / 超长 / 超过 64 条)。声明面**没有**「丢掉坏的那条继续跑」的合法语义:那会让你以为公告了一个技能而
1541
- 对端根本看不到。每条 skill 的 `scenario` 是**内部**路由键 —— 它**不上卡**(自动枚举场景名给公网是
1542
- 本设计唯一点名禁止的事),只决定点名这条 skill 的请求跑哪个场景。
1644
+ - **`A2A_SERVE_SKILLS` = 版本化外壳 `{"version":2,"skills":[…]}`,坏形拒启**(非 JSON / 不是外壳 / `version` 不是数字 `2` /
1645
+ 外壳或条目里有别的键 / 条目缺 `id` 或 `scenario` / `id` 重复 / 超长 / 超过 64 条 / `audience` 不合形)。**旧的裸数组形拒启**:
1646
+ 旧值原样放进 `{"version":2,"skills":<旧值>}` 即可(MIGRATION 本版同条)。声明面**没有**「丢掉坏的那条继续跑」的合法语义:
1647
+ 那会让你以为公告(或限制)了一个技能而对端看到的是另一回事。每条 skill 的 `scenario` 是**内部**路由键 —— 它**不上卡**
1648
+ (自动枚举场景名给公网是本设计唯一点名禁止的事),只决定点名这条 skill 的请求跑哪个场景。
1649
+ - **`audience`(可选)= 这条技能发布给谁**(验过的主体名,精确串,1–64 项;没写 = 所有调用方)。不在名单里的调用方在任何卡上都看不见它、
1650
+ 按 id 点名得到与「没有这条技能」逐字节相同的 `-32602`。名单里的人经 `POST /v1/a2a` 的方法 `agent/getAuthenticatedExtendedCard`
1651
+ 拉到自己那份卡(公开卡上恒有一位 `supportsAuthenticatedExtendedCard: true`,与配没配名单无关)。身份只认**验过的人**:BFF 形上游注入的
1652
+ 主体头、直连门的 principal JWT、SSO 凭据;只带 service 凭据(含代部署本人)的请求没有人、只得公开技能;fleet 资格与运维名册不扩大投影(运维按他自己这个人投影)。
1653
+ 🔴 **发布 ≠ 授权**:`audience` 只管「谁看得见、谁能按 id 点名」,不管谁能跑背后的场景 —— 不带 `sema.skillId` 的消息、映射到同一场景的公开技能、
1654
+ 原生提交族(`POST /v1/runs` / `POST /v1/tasks` / `POST /v1/tasks/stream` 体里的 `scenario`)都不经过它,唯一的闸是场景门(中心白名单)。
1655
+ 受限技能**不要**映射到部署缺省场景,也**不要**与公开技能共用场景。名单的强度 = 这台部署的身份边界:没有身份边界的部署(没接任何已验身份来源:没配服务凭据、没装鉴权桥、没开直连门;只设 trace 凭据或只开 `REQUIRE_PRINCIPAL` 不算)上写了 `audience` ⇒ 起服拒(拒句一行给两条出路:接一个已验身份来源 / 删掉 `audience`),故不存在该形;
1656
+ 卡与技能选择只认验过的身份来源(出示服务凭据之后的主体头 / SSO 凭据 / 直连门 JWT),不出示凭据、只自报主体头的调用方读成匿名(只看公开技能);BFF 形上,名单的强度等于「持共享 service 凭据的调用方够不到 worker」
1657
+ (worker 对持凭据者直接可达时谁都能自报主体头);不想依赖网络隔离就用直连门或 SSO 凭据。完整读法见契约附录 C.7。
1543
1658
  - 两条路由的**鉴权位置刻意不同**:`GET /.well-known/agent-card.json` 在服务凭据门**外**(公网匿名可
1544
1659
  发现,零存量披露);`POST /v1/a2a` 在门**内**(与本仓其余 API 同一套凭据)。
1545
1660
  - `message/send` / `tasks/get` 都站在 **durable run store** 上(能力位 `a2aServe` 与这条是同一只判据)—— 缺它时这台 worker 整个**不作为 A2A agent 对外存在**:
1546
1661
  卡与 `POST /v1/a2a` 一起 404(7.116.0 起;此前卡照发、两个方法回 JSON-RPC `-32004`,现在不再出现这个码)。开关打开时 boot 日志有一行 info `a2a_serve_enabled`
1547
1662
  (键:`card` · `rpc` · `advertisedUrl` · `skills` · `blockingWaitMs`);开关打开而没有 run store 时改打一行 warn `a2a_serve_not_served`(`reason` + `detail`,`detail` 点名出路)。
1548
- - 完整 wire 契约(方法表、错误码全表、Task 形与任务态投影、反枚举语义、版本槽)见
1549
- [`docs/ASSISTANT-WIRE-CONTRACT.md` 附录 C](docs/ASSISTANT-WIRE-CONTRACT.md)。
1663
+ - 完整 wire 契约(方法表、错误码全表、Task 形与任务态投影、反枚举语义、版本槽、技能发布与扩展卡)在
1664
+ 契约附录 C(内部记录,不随包)。
1550
1665
 
1551
1666
  **可选 — 父进程存活监视(`SEMA_PARENT_PID`,默认不装配)**
1552
1667
 
@@ -1654,17 +1769,28 @@ ENGINE_HANDOVER_LINGER_MS=1800000 # 可选,默认 30 min,下界 60000(壳经 POS
1654
1769
  两键都缺席 ⇒ 整件不装配,零定时器。
1655
1770
  - **自退判据 = 三合取,且必须连续满**「当前窗」(窗长由车道定,从上到下第一条成立者:交接 = `ENGINE_HANDOVER_LINGER_MS`;父没了 =
1656
1771
  `ENGINE_ORPHAN_LINGER_MS`;还没被碰过 = `max(ENGINE_LINGER_MS, ENGINE_FIRST_ATTACH_MS)`;其余(父还活着、已被碰过)= `ENGINE_LINGER_MS`;孤儿窗见上一节,「首次附着前的宽限」与「交接车道」见下面各自一条。判据一条,窗长按车道取):① 没有附着的 SSE 流;② 没有在飞的 run
1657
- (读的是**阻断集** `http + leader`,与排空等的、SIGHUP 空闲窗同一个读数;`/health` 的 `inflight` 是它再加会话驻留的后台 shell / monitor 的**总数**,见下一条);③ 没有非探针 HTTP 触点(`/health`、`/readyz`、`/metrics*` 不算 ——
1772
+ (读的是**阻断集** `http + leader + backgroundAgents` —— 含活过起它的那条腿、还在跑的后台子代理 —— 与排空等的、SIGHUP 空闲窗同一个读数;`/health` 的 `inflight` 是它再加会话驻留的后台 shell / monitor 的**总数**,见下一条);③ 没有非探针 HTTP 触点(`/health`、`/readyz`、`/metrics*` 不算 ——
1658
1773
  探针不是「有人在用」),**且**最后一条附着流断开也已满窗。任何一条不满足即把计时**清零**。
1659
1774
  (最后那半句不是修辞:一条活得比 15s 拍频还短的流整个生命周期都落在两拍之间,自查腿从没看见过它 ——
1660
1775
  只有在**关闭点**记一笔离场时刻,「已经没人附着满 N 秒」才是精确的。)
1661
- - **`/health.inflight` 的两个事实(7.146.0 起)**:模型在会话里 `run_in_background` 起的 shell / monitor 活过起它的那一条腿 —— 此前 `/health.inflight` 对它们读 0,读 `/health` 判「引擎是否空闲」的判官只好另拼谓词。
1662
- 现在 `/health.inflight` 是**总数**:这台引擎此刻托管着多少活,把仍在跑的、会话驻留的、**没声明 retain** 的后台 shell / monitor 一并数进去;分项在 `/health.inflightBreakdown { http, leader, sessionResident }`,`inflight` 恒等于三项之和。
1663
- **判停读的是另一个和 —— 阻断集 `http + leader`**(还有没有必须等完的活):自退、排空、SIGHUP 空闲窗都读它,驻留 shell 只报、不阻断,所以**判停行为与 7.145.x 逐字同**
1664
- (壳没了且没人附着时引擎照退,驻留进程随受控退被收掉;一只不退出的 `while true` 不会让孤儿引擎永生,也不会让排空等满窗)。
1665
- **谁受影响**:把 `inflight` 当「HTTP 腿数」读的监控面(数值会比 7.145.x 大,原口径 = `inflight − inflightBreakdown.sessionResident` = `http + leader`)。**补偿**:分项键;要不要因为引擎托管着驻留 shell 而推迟换代 / 回收由判官自己定。
1776
+ - **`/health.inflight` 的两个事实(7.146.0 起;后台子代理 7.147.0 起)**:模型在会话里 `run_in_background` 起的 shell / monitor 与(单用户形下)后台子代理活过起它的那一条腿 —— 此前 `/health.inflight` 对它们读 0,读 `/health` 判「引擎是否空闲」的判官只好另拼谓词。
1777
+ 现在 `/health.inflight` 是**总数**:这台引擎此刻托管着多少活,把仍在跑的后台子代理与仍在跑的、会话驻留的、**没声明 retain** 的后台 shell / monitor 一并数进去;
1778
+ 分项在 `/health.inflightBreakdown { http, leader, backgroundAgents, sessionResident }`,`inflight` 恒等于四项之和。
1779
+ **判停读的是另一个和 —— 阻断集 `http + leader + backgroundAgents`**(还有没有必须等完的活):自退、排空、SIGHUP 空闲窗都读它。后台子代理在内 —— 它是一条在干活的模型腿,
1780
+ 壳没了、没人附着时引擎等它干完再退(7.147.0 起;此前随受控退被中断);停在审批门上的子代理不计(耐久停驻,下次起来仍可续)。驻留 shell 只报、不阻断
1781
+ (驻留进程随受控退被收掉;一只不退出的 `while true` 不会让孤儿引擎永生,也不会让排空等满窗)。活过起它的腿的 workflow 不计(任务表分不出它在干活还是在等审批)。
1782
+ **谁受影响**:把 `inflight` 当「HTTP 腿数」读的监控面(数值变大,原口径 = `http + leader`);排空会多等还在跑的后台子代理(上界仍是排空期限);一只挂死的后台子代理撑着孤儿窗 / 租约窗,至多一个停滞窗(下一条)。
1783
+ **补偿**:分项键;`POST /v1/sessions/:id/children/retire` 结清以该会话为根的后台子代;要不要因为引擎托管着驻留 shell 而推迟换代 / 回收由判官自己定。
1784
+ - **后台子代理的停滞收割(`BG_AGENT_STALL_MS`,7.147.0 起)**:读在飞数之前(判停的每一拍、`/health`),引擎先把**最后一次工具活动**距今超过这个窗的会话驻留后台子代理收掉 ——
1785
+ 行落 `killed`、归因 `system`、它的 run 中止,运维日志一行 `background_agent_stall_reaped`。于是一只不再有进展的子代理(挂死的,或收尾时引擎等的那次账本写挂住的)最多撑住自退窗一个停滞窗。
1786
+ 「静默」只按工具活动算:子代的一次工具开始 / 结束是一拍;一轮很长的模型输出、一条很长的工具调用、在审批 / 问答卡上等人,在窗内都只是静默。
1787
+ 缺省 = 下限 = 本部署等人的四个窗取大(`STREAM_ASK_WINDOW_MS` / `UNREACHED_ASK_TTL_MS` / `QUESTION_TTL_MS` / `MCP_ELICITATION_TTL_MS`)+ 引擎单命令上限(`BASH_MAX_TIMEOUT_MS`,缺省 600 s);
1788
+ 单用户形 = 24 h(活审批卡窗)+ 10 min。**只许拉大**:写得比下限小(含 `0`)⇒ 钳到下限并在起服 warn 点名 —— 比本部署自己允许的最长等人还短的窗,会把一只等你回来批的子代当僵尸收掉;
1789
+ 想让僵尸收得更快,调小那几个等人窗(没开审批 / 问答的部署,那几个窗本来就不起作用,调小它们没有别的代价)。
1790
+ **谁受伤**:单步静默超过窗的合法子代会被收 —— MCP 调用(引擎时限缺省约 27.8 h,调用中的进度通知不算活动)、前台嵌套委派(子委派里的工具活动与等人都不给它打拍)、
1791
+ 一轮极长的模型输出;引擎按墙钟量静默,墙钟前跳或整机睡眠超过窗,醒来第一读就会收掉睡前之后再没打拍的子代。补偿 = 把 `BG_AGENT_STALL_MS` 拉大。
1666
1792
  读数只会少数、不会多数:枚举的会话集是本进程提交过的会话(LRU 上界 4096、删会话即清),被挤出的会话、不经提交链的内部车道起的驻留行、删会话级联中途失败的会话不可见。
1667
- 某个会话的驻留行读失败时按 1 只在跑计(读不出 ⇒ 非空闲)并记一行 `session_resident_background_unreadable`(每会话一次),读口本身不抛。
1793
+ 某个会话的任务表读失败时按 1 只在跑的后台子代理计(读不出 ⇒ 非空闲,判停也看得见)并记一行 `session_resident_background_unreadable`(每会话一次),读口本身不抛。
1668
1794
  - **「run 在跑就不退」是结构性保护,没有旋钮可以关掉它** —— 最后一个壳关掉、后台 run 还在烧的场景里,
1669
1795
  引擎必须活到那条 run 落地。
1670
1796
  - **`ENGINE_LINGER_MS` 防的是壳重启窗**:壳崩了/重启的 1-3s 里附着数确实是 0,窗太短就在用户眼皮底下
@@ -1728,7 +1854,7 @@ ENGINE_HANDOVER_LINGER_MS=1800000 # 可选,默认 30 min,下界 60000(壳经 POS
1728
1854
  **布尔旋钮的取值与极性(运维必读)**
1729
1855
 
1730
1856
  布尔 env **收 `true` / `false` / `1` / `0` / `yes` / `no` / `on` / `off`**(大小写不敏感;词表自 7.16.0
1731
- 起放宽)——`LSP_ENABLED=1` 这种人眼读作"开"的写法,现在代码里也
1857
+ 起放宽)——`LEADER_ENABLED=1` 这种人眼读作"开"的写法,现在代码里也
1732
1858
  读作"开",不再是此前"看着开、其实关"的陷阱。词表**外**的值(如 `enabled`、`TRUE1`)⇒ 进程**直接
1733
1859
  拒启**,报错点名该 env 名/收到的原始值/可接受词表(fail-loud,不留静默默认)。旧的「`config_env_
1734
1860
  invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并退役,不再覆盖布尔旋钮——该事件现在
@@ -1757,26 +1883,21 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1757
1883
  | `CONFIG_LKG_DISABLED` | `CONFIG_LKG_ENABLED=false` | 开 | 中心 effective 配置的 LKG 落盘(读写双关) |
1758
1884
  | `HOST_BG_DISABLED` | `HOST_BG_ENABLED=false` | 开 | host lane 后台 shell 能力总闸 |
1759
1885
  | `HOST_EXEC_SPOOL_DISABLED` | `HOST_EXEC_SPOOL_ENABLED=false` | 开 | host lane exec 的 spool 形 stdio(关掉=回退管道旧形) |
1760
- | `LSP_ENABLED=false`(拆分前用来关 host 腿那半) | `LSP_HOST_ENABLED=false` | 开 | 见下 |
1886
+ | `LSP_ENABLED`(任一值) | 无替代名 —— 撤掉它;host 车道的开关是 `LSP_HOST_ENABLED` | — | 沙箱车道的 LSP 桥已删除(e2b / k8s 不挂 LSP),见下 |
1761
1887
 
1762
- 四个旧名都是"负名"(`X_DISABLED`),读的时候要双重否定;新名一律正向 + 缺省显式。
1888
+ 前四个旧名都是"负名"(`X_DISABLED`),读的时候要双重否定;新名一律正向 + 缺省显式。
1763
1889
  🪦 **墓碑语义**:旧名**在场即拒启**——值是 `true` 还是 `false` 都一样(设 `false` 的人同样以为它还生效),
1764
1890
  **正名设没设也一样**(7.104.0 起;此前正名设了时旧名被静默忽略 —— 迁移到一半、两个名字都留着的部署以为旧名还在生效)。
1765
1891
  拒句与其余退役 env 名同一句、同一个码 `[config.env_key_retired]`,点名旧名、正名与换算(`X_DISABLED=true` 即 `X_ENABLED=false`)。
1766
1892
  所以不存在"两个名字同时设"而能起服的状态,也不存在"旧名还在悄悄生效"的状态。墓碑**不设到期**:退役名的拒启是永久的,
1767
1893
  只有某个名字要被赋予新含义时才删它的墓碑(届时 MIGRATION 写明)。
1768
1894
 
1769
- **`LSP_ENABLED` 一分为二**:它原来同时驱动两条腿,而且两腿缺省相反——沙箱腿缺省**关**(要烤好的
1770
- `ai-agent-code-lsp` 模板;⚠️ 该模板**只烤 TS/JS**——Python/Go 烤在 `dev-base`,dart/kotlin/java/swift
1771
- 烤在 `dev-mobile`,四模板物理独立,`E2B_TEMPLATE` 选错则未烤语言的 LSP **静默退化到 grep/read**;
1772
- 对照表见 ARCHITECTURE §7,真源=`src/lsp/e2b-bridge.ts`),host 腿缺省**开**(只要 PATH 上有 language server,没有就优雅退回 grep/read)。
1773
- 拆分后沙箱腿是 `LSP_ENABLED`(opt-in)—— **本版在 e2b / k8s 上不挂任何东西**(见下段「LSP 的车道射程」);host 腿归 `LSP_HOST_ENABLED`(opt-out)。
1774
- 🪦 `LSP_ENABLED=false`(拆分前唯一的 host 腿逃生舱)自 3.0.0 起是墓碑:拒启并指路 `LSP_HOST_ENABLED`。
1775
- `LSP_ENABLED=true` 照常起服,但本版只换来一行 `lsp_not_mounted`(沙箱两车道不挂 LSP,见下段);绑定读在这两条车道实现之日它重新生效。
1776
-
1777
- **LSP 的车道射程(7.127.0 起,引擎 7.50.0)**:LSP 会话只读执行环境「绑定持有」的那只文件(判过的对象就是读到的对象)。host 车道实现了绑定读,LSP 照常(`LSP_HOST_ENABLED`);
1778
- 沙箱两车道(e2b / k8s)的执行环境本版不实现绑定读 —— 挂上就是每个文件同步都被拒 ⇒ **这两条车道不挂 LSP**:开着 `LSP_ENABLED=true` 照常起服,起服打一行
1779
- `lsp_not_mounted`(带原因),模型名册里没有 LSP 工具、`wiring_manifest` 上没有 `lsp` 段,代码导航回到 grep / read。ssh / adb / device / local-docker 本来就不挂。
1895
+ **LSP 只有一根旋钮 `LSP_HOST_ENABLED`(缺省开),只在 host 车道且单主时挂**:本机语言服务器是 worker 上的子进程、在 worker 盘上找根,
1896
+ 所以它只在「worker 盘就是调用方的盘」时挂 —— host 车道、未设 `REQUIRE_PRINCIPAL=true`(多主的 host 部署本来就拒启)。PATH 上有对应的
1897
+ language server 就用,没有就优雅退回 grep / read;`LSP_HOST_ENABLED=false` 关掉。**其余车道没有 LSP**:e2b / k8s / local-docker / ssh / adb /
1898
+ device / none 一律不挂,模型名册里没有 LSP 工具、`wiring_manifest` 上没有 `lsp` 段,代码导航用 grep / read。服务入口与 `run-local` 同一条律。
1899
+ 沙箱车道的 LSP 桥已删除(7.127.0 起它本来就挂不上:引擎只读执行环境「绑定持有」的那只文件,沙箱两车道不实现绑定读);它的开关 `LSP_ENABLED`
1900
+ 是墓碑(上表),设了任一值都拒启,空串按未设。e2b 模板里烤的语言服务器只是工具链内容,见 `e2b-template/README.md`。
1780
1901
 
1781
1902
  **host 腿语言服务器拿到的环境 + 点名放行 `LSP_ENV_ALLOW`(7.101.0 起)**:host 腿的语言服务器是本机子进程,它拿到的环境
1782
1903
  = 服务进程环境**剥掉凭据形的键**(与模型可驱动的 host shell 同一只剥除:配置目录 `secret` 型键按名剥 ∘ 引擎按键名形状剥
@@ -1791,7 +1912,8 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1791
1912
  **「剥了什么」这一行**:起服(host 腿语言服务器管理器构造时)一行 `warn` `secret_env_scrubbed`,`site:"server.lsp.spawn-env"`,
1792
1913
  字段 `keys`(这次剥掉的键名)· `allowed`(当前放行名单)· `hint`(出路句,点名 `LSP_ENV_ALLOW=NAME,NAME`)· `summary`;**值永不进日志**。
1793
1914
  放回的键不算剥除(不在 `keys` 里、不进计数 `secret_env_scrubbed_total`);同一个键每进程只报一次;没有剥除 ⇒ 不发。
1794
- 其余剥除现场(模型可驱动的 host shell `server.remote-env-host.spawn-env`、三条 git 腿、local-docker / adb 两条 CLI 腿、settings hooks 的命令腿)没有部署级放行旋钮,它们的同名行照旧是 `info`、不带 `allowed` / `hint`。
1915
+ 其余剥除现场(三条 git 腿、local-docker / adb 两条 CLI 腿、settings hooks 的命令腿、本服务自己的 git worktree 基环境 `server.remote-env-host.git-base`〔它从不交给引擎〕)没有部署级放行旋钮,它们的同名行照旧是 `info`、不带 `allowed` / `hint`。
1916
+ **模型可驱动的 host shell(`site:"server.remote-env-host.spawn-env"`)不写这一行**:它扣了哪些变量名由引擎说,不再有第二个读者 —— 根腿第一次调 shell 工具时一条通告 `inherit_env.names_withheld`(`detail.names` 全部被扣的名、`detail.ownCredentials` 其中本部署自有凭据那一半,每会话每名字集一次〔引擎的去重记录有界,尽力而为〕;委派子代调 shell 不出这条;**只给名字、从不带值**),运维在服务日志的结构化 `engine_notice` 行里读同一条;计数 `secret_env_scrubbed_total{site="server.remote-env-host.spawn-env"}` 照记(不带名字)。按这条腿的 `secret_env_scrubbed` 行告警 / 过滤的运维脚本改读 `engine_notice` 行(`code` = `inherit_env.names_withheld`)。
1795
1917
  现场按**腿**命名(`server.<模块>.<腿>`),不按继承词:7.131.x 及以前 host shell 那条腿叫 `server.remote-env-host.inherit-scrub`,7.132.0 起改名(计数器 `secret_env_scrubbed_total{site}` 与日志字段 `site` 同改,无别名)。
1796
1918
 
1797
1919
  **每一条宿主子进程腿的 env 都经同一只组合(7.132.0 起射程写全)**:本服务在宿主上起的每一只子进程,env 都由引擎按这条腿的继承词组合,本服务自己的凭据(起服声明的名与值)任何词都不交。
@@ -1806,8 +1928,7 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1806
1928
  (下一段),不是 sema-server 部署方的旋钮(引擎 CHANGELOG 里把它说成部署方出路的那一句,对 sema-server 部署不成立)。7.129.0 起这条腿的三个继承词只在引擎
1807
1929
  一处判(`inheritedHostEnv`),本服务自己的凭据名起服向引擎声明一次、任何词都不交(下面「请求级 `inheritEnv`」段)。被剥变量的
1808
1930
  出路**按用途分**:git 凭据走 git 配置文件 / credential helper(隔离全局配置的两条 git 腿 —— project-memory 读仓、center-plugins 克隆 ——
1809
- 另见 `docs/DEPLOY-PREREQS.md` 7.103.2 那一节的「按腿分」);带口令的出网代理走不带口令的形(`~/.gitconfig` 里只带用户名的 `http.proxy` + credential helper,见
1810
- `docs/DEPLOY-PREREQS.md`「带口令的出网代理」);语言服务器 / bake-runner 走各自的名单 `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`;沙箱车道走
1931
+ 读不到宿主 `~/.gitconfig`,无害设置只能留在 env:凭据条目别写进 `GIT_CONFIG_PARAMETERS`,或改用 `GIT_CONFIG_COUNT` 组;其余四腿可写进宿主 git 配置文件);带口令的出网代理走不带口令的形(`~/.gitconfig` 里只带用户名的 `http.proxy` + credential helper 供口令,或让代理按来源免密);语言服务器 / bake-runner 走各自的名单 `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`;沙箱车道走
1811
1932
  `E2B_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` / `K8S_SANDBOX_ENV`。要不要给 host shell 加部署级名单属于能力面变更(安全轴),不在文档里顺手给。
1812
1933
 
1813
1934
  **请求级 `inheritEnv`(7.106.0 起)—— 调用方声明,不是部署旋钮**:提交体顶层键 `inheritEnv:"all"` 让**这一条请求**的模型驱动 host shell
@@ -1817,7 +1938,7 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1817
1938
  交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配 `inheritEnv:"all"`;7.146.0 起请求 `settings.env` 不收凭据,见 9.5j);这些变量此刻的**值**也交给引擎按值扣(同值换名的副本同样进不了模型 shell),引擎判某个值「不是凭据值」(空白 / 占位词 / 一个字符重复 / 不足 8 字符)⇒
1818
1939
  那一个值不按值扣(名照扣、不拒启,能力位 `inherit_env.own_credential_values_withheld` 答 false),起服与模型目录换代时每个这样的变量出**一条** warn 结构化行 `own_credential_value_not_withheld { name, reason }`
1819
1940
  (`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`
1820
- ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带 `"all"` ⇒ 400。词表外的值(`"ALL"` · `""` · 名单 `["PATH"]` · 布尔 · `null` …)、非 host 车道带 `"all"`、部署不接 authorizer 时带 `"all"` 这三只都是 `400 request.field_invalid`,7.137.0 起体另带 `field: "inheritEnv"` 与 `reason`(`"unknown_word"` = 是字符串但不在词表;`"not_a_string"` = 根本不是字符串;`"lane_not_host"` = `"all"` 落在非 host 车道;`"no_authorizer_session"` = `"all"` 而部署不接 authorizer),壳按这两位认拒因、不解析句子(句子 / 码 / 状态不变;续跑腿同判,折叠 409 随体带同一组)。托管部署的 `422 request.host_posture_refused` 不在这组:它说的是「这台部署的姿态不给」,不是这个键写错,按 `errorCode` 认、不带这两位。部署方
1941
+ ⇒ 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` 认、不带这两位。部署方
1821
1942
  **没有**旋钮能替调用方打开它,也没有旋钮能在单用户部署上关掉它(单用户部署的用户把他自己的环境交给他自己驱动的 shell);起服一行 info
1822
1943
  `task_inherit_env_honored {honored}` 告诉你这台机器认不认。上面「部署级名单不给模型驱动 shell」那条不变 —— 两件事是两条轴(部署名单 vs 调用方 × 单用户射程)。
1823
1944
 
@@ -1827,8 +1948,8 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1827
1948
  `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 需要点名放行,现在不用)。
1828
1949
  **精确名,不是形**:`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2` 这类装饰过的变体照剥。引擎自己的「剥了什么」通告码
1829
1950
  `config.secret_env_scrubbed` 只在引擎**自己**剥的子进程上发;本服务九条宿主腿(host shell / project-memory / center-plugins / leader 三条 git 腿 / 语言服务器 /
1830
- bake-runner / local-docker 与 adb 两条 CLI 腿 / settings hooks 命令腿)都交本服务组合好的环境,所以运维面看的是上面这行
1831
- `secret_env_scrubbed`,不是那只通告码。这只通告码**每进程一次**、引擎里几处剥除共用一闩(工具 shell / 语言服务器 / 7.103.2 起还有 `runSpec` 的
1951
+ bake-runner / local-docker 与 adb 两条 CLI 腿 / settings hooks 命令腿)都交本服务组合好的环境,所以运维面看的不是那只通告码:除 host shell 外的各条腿看上面这行
1952
+ `secret_env_scrubbed`,host shell 腿看 `engine_notice` 行里的 `inherit_env.names_withheld`(上一段)。这只通告码**每进程一次**、引擎里几处剥除共用一闩(工具 shell / 语言服务器 / 7.103.2 起还有 `runSpec` 的
1832
1953
  oracle 门子进程,core 7.33.8):oracle 门若先于工具 shell 剥,通告只进 oracle 门那一处(`RunSpecOptions.onNotice`,没接就进 console),接在执行环境上的
1833
1954
  结构化通告收不到。本服务不调 `runSpec`,不受影响;嵌入引擎、自接 `runSpec` 的宿主给它交同一只 `onNotice`。
1834
1955
  **OTLP 头变量一族(7.103.2 起,core 7.33.7)**:`OTEL_EXPORTER_OTLP_HEADERS` 与按信号分的 `_TRACES_` / `_METRICS_` / `_LOGS_` / `_PROFILES_HEADERS`、
@@ -1897,8 +2018,8 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1897
2018
 
1898
2019
  > **dev-open 部署的主体头是过滤 / 展示用,不是隔离。** 没接任何身份来源(没配 `SERVICE_AUTH_TOKEN(S)`、没装中心鉴权桥、没开直连门 —— 即 `/health.auth.kinds` 为空)、
1899
2020
  > 也没配 `TRACE_TOKEN`、没开 `REQUIRE_PRINCIPAL` 的部署上,`x-agent-principal` 谁都能自报:
1900
- > 会话列表与单会话的读写口(视图 / head / 事件流 / 改名置顶 / 记忆状态与抹除 / 后台任务 / MCP 重连 / fork / delete)都不按它钉人,带谁的头都读得到同一批会话;`/metrics` 族不要凭据(没配 `METRICS_TOKEN` 时)。
1901
- > 它仍决定新会话记在谁名下、记忆按谁的 scope 走;规则口(`…/policy`)与 `…/notify` 用它选落哪一把属主键。要按用户隔离,给部署设上面几者之一(典型:service token + 写主体头的网关)。
2021
+ > 会话列表与单会话的读写口(视图 / head / 事件流 / 改名置顶 / 记忆状态与抹除 / 后台任务 / MCP 重连 / 规则口 `…/policy` / `…/notify` / fork / delete)都不按它钉人,带谁的头都读得到同一批会话;`/metrics` 族不要凭据(没配 `METRICS_TOKEN` 时)。
2022
+ > 它仍决定新会话记在谁名下、记忆按谁的 scope 走(规则口存、`…/notify` 盖的都是会话属主那一把键,不是这个头)。要按用户隔离,给部署设上面几者之一(典型:service token + 写主体头的网关)。
1902
2023
  > **装了中心鉴权桥(`SEMA_WEB_ADMIN_URL` 在位即默认开,或 `AUTH_BRIDGE_ISSUER`)就不是 dev-open**(7.132.0 起;此前这一形被读成 dev-open):带登录令牌的调用方按令牌里的身份钉人,
1903
2024
  > 不带任何凭据的列表 `401 auth.unauthorized`、`/metrics` 族 `401`(要 `METRICS_TOKEN`)。仍成立的边界:没配 service token 的部署上,不带登录令牌的请求自报的主体头仍按身份读
1904
2025
  > (与 `REQUIRE_PRINCIPAL` 无 token 形同读)—— 要让「没令牌就没有身份」成立,配 `SERVICE_AUTH_TOKEN`(全局凭据门:每个请求要么带 service token、要么带登录令牌)。
@@ -1924,7 +2045,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1924
2045
 
1925
2046
  | env 键 | 缺省 | 说明 |
1926
2047
  |---|---|---|
1927
- | `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) |
2048
+ | `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(内部记录) |
1928
2049
 
1929
2050
  > ⚠️ **Bearer 是"每个请求",不只是 POST。** 配了 `SERVICE_AUTH_TOKEN` 后,**`GET /v1/runs/:id`、`GET /v1/runs/:id/events`(SSE)、`/metrics`** 等除 `/health` / `/readyz` 两只探针外的路由都要带 `Authorization: Bearer`——漏带一律 `401`。下面示例为简洁**省略了 Bearer**,真实调用请逐个补上。
1930
2051
  >
@@ -1932,7 +2053,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1932
2053
  > 同一道门上「没带」与「带错」逐字同一句(区分就是探测面):① 全局凭据门 —— 说「带这台部署的 service token 作 Bearer」,并指 sema-up 部署的
1933
2054
  > 令牌在 `~/.sema-up/<project>/.env` 的 `SERVICE_AUTH_TOKEN`;② 运维口(`/metrics*`、`GET /v1/tasks/source-summary`)—— 说要运维凭据
1934
2055
  > (不带主体头的 service token —— 在部署让它代自己说话时:`/health` 的 `auth.serviceTokenActsForDeployment` 为 `true`,7.146.0 起句子带这一条件;带主体头的 service token 请求代那位主体、不是运维凭据,7.134.0 起;指标口另认只读的 `METRICS_TOKEN`,`source-summary` 另认只读的 `TRACE_TOKEN`);③ 过了凭据门、但这一口要调用方身份(属主寻址读写、规则、记忆)—— 说要一个验过的
1935
- > 调用方身份,**不**叫你再去带 service token。逐字句子见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A.1 码表。壳(sema CLI)接 sema-up 引擎的
2056
+ > 调用方身份,**不**叫你再去带 service token。逐字句子就在应答的 `error` 字段里。壳(sema CLI)接 sema-up 引擎的
1936
2057
  > 令牌交法见 `deploy/sema-up/README.md`「壳接入」。
1937
2058
 
1938
2059
  **整只请求体的上限**:8 MiB;**TiDB 部署更矮** —— 一份提交体要整只落进库的一行,上限 = 库的单行上限(TiDB `txn-entry-size-limit`,缺省 6 MiB)− 64 KiB,
@@ -1950,14 +2071,14 @@ PostgreSQL 仍只有 8 MiB。这一窗的 413 句子说是假定(`the database's
1950
2071
  PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_large`(`wall` 点名那堵墙);刚认领的 run 当场撤掉,撤认领的库写自己也失败时一行 `submit_ctx_run_release_failed`(带步名)
1951
2072
  并计 `submit_ctx_run_release_failed_total{step}`,会话到 `REAP_RUN_STALE_SEC` 才放。
1952
2073
 
1953
- **请求体字段(常用;受理**闭集**以 `docs/ASSISTANT-WIRE-CONTRACT.md` §1 为准 —— 本表不再重抄一份):**
2074
+ **请求体字段(常用;受理**闭集**以契约 §1 为准(内部记录)—— 本表不再重抄一份):**
1954
2075
  | 字段 | 必填 | 说明 |
1955
2076
  |---|---|---|
1956
2077
  | `objective` | ✅ | 要做什么(自然语言)。**唯一必填。** |
1957
2078
  | `sessionId` | — | 续聊:带上次返回的 `sessionId`,服务端自动 wake 历史。**只收 canonical uuid**(任意版本,小写;与会话口同一只形判):自选名 / 业务键 / 空串 ⇒ `400 request.id_invalid`,缺省请**省略这个键**(服务端现铸 uuid)。想要好记的名字,自己记 name → uuid 的映射 |
1958
2079
  | `images` | — | 图文输入 `[{data,mimeType}|{url}]`(模型需支持 vision)。**随提交的这条目标一次送达**:续跑 / 投递腿(审批 / 资源停车 / wake / plan_review 续跑、在宿主会话上新起的一跑)重建配置时不再带它(与 `userAttachments` 同律;图在首跑已是会话里的消息) |
1959
2080
  | `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 形走本地文件店。 |
1960
- | `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` |
2081
+ | `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` |
1961
2082
  | `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` |
1962
2083
  | `repo` | — | `code-review`/`scan` 必填,见 §5(7.126.0 起 `council` / `debate` / `lenses` / `rounds` / `team` / `businessContext` 是未知键 ⇒ 400:它们喂的评审团腿与 `discuss` 场景随引擎 7.49.0 删掉团队运行时一并删除) |
1963
2084
  | `systemPrompt` / `appendSystemPrompt` | — | 调用方自带的系统提示 / 追加段(各 ≤16384 字符,超限 400 `request.field_invalid`;`appendSystemPrompt` 必须非空串)—— 任务自带时优先于角色 / 全局缺省(见 §模型角色)。`systemPrompt` 是**角色层**:它整替所选角色的基底(`code-review` 场景把自己的评审员文本放在它前面),宪法、行为段、记忆段由引擎组在它之后;追加段恒排在宪法之后。逐字含引擎提示段的提示(一份已组装的提示)⇒ 400 `prompt.assembled_role_refused`(7.132.0 起) |
@@ -1995,8 +2116,7 @@ PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_la
1995
2116
 
1996
2117
  > ⚠️ **任务成败以 `terminal.kind`/`terminal.code` 为准,勿以 HTTP 状态码判**:同步腿 200=提交受理成功(任务可能
1997
2118
  > `terminal.kind:"failed"`+`code`);`POST /v1/runs` 202=已受理(执行期结局落在终态 run 记录/`done` 帧)。执行期
1998
- > 错误结构上无法用 HTTP 状态码承载——流的响应头在 run 开跑前就发完了(详见
1999
- > `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A)。
2119
+ > 错误结构上无法用 HTTP 状态码承载——流的响应头在 run 开跑前就发完了。
2000
2120
 
2001
2121
  ---
2002
2122
 
@@ -2117,19 +2237,20 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2117
2237
  | 端点 | 用途 |
2118
2238
  |---|---|
2119
2239
  | `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 读得到,前缀永久保留。停驻时宿主行属主与引擎交来的不等 ⇒ 停驻否决(那张待批卡作废、那一只子代以失败收尾) |
2120
- | `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"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2121
- | `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+**) |
2240
+ | `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(决断已逐行)。停驻的**技能卡**(对引擎 `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"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2241
+ | `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 分钟上限对读失败的拍同样算数。`pending` 帧带的行与 `GET /v1/approvals` 同一只投影(含上面的技能卡三位,**7.136.0+**;含 `agent`,新订阅首拍补给的帧与之后推出的帧同值,**7.145.0+**) |
2122
2242
  | `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) |
2123
- | `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 |
2243
+ | `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;本口在店读不出时只对本副本自己铸的活登记回落登记(登记属主认你 ⇒ `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 |
2124
2244
  | `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) |
2125
2245
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**、附件供给、**技能清单**(见下) |
2126
- | `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;`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` |
2246
+ | `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` |
2127
2247
  | `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` 已退役。 |
2128
- | `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 原文。 |
2248
+ | `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 原文。 |
2129
2249
  | `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。 |
2130
2250
  | `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 |
2131
- | `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 **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
2251
+ | `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 **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
2132
2252
  | `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 |
2253
+ | `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`)。不计费;无凭证部署上与提交面同一道门 |
2133
2254
  | `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「探针」 |
2134
2255
  | `GET /metrics` | Prometheus 指标(有 token 时需带) |
2135
2256
 
@@ -2152,9 +2273,15 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2152
2273
  > **operator 鉴权(审批队列与全部运维面,一条读法)**:`OPERATOR_PRINCIPALS=ops:alice,ops:bob`(CSV)= 谁能当 operator —— 列任意 owner 待办 + 决议(批/否)。非名单 principal 列待办只看自己的、且**不能决议**(防「请求方批自己的高危操作」跳过 F4 闸);
2153
2274
  > **名单为空 = 没有人是 operator**(每个调用方只看自己的待批 —— 不带主体的调用方看到的是匿名卡〔不带主体提交的任务停下的那几张,即决断口对它开放的那几张〕,`?owner=` 不生效;7.133.0 之前审批四个读面在名单为空时把每个调用方当 operator,那条读法已删)。
2154
2275
  > **单用户部署当自己的 operator**:`OPERATOR_SINGLE_USER=true` = 「这台部署的每个调用方都是 operator」,对全部运维面生效;只在 `REQUIRE_PRINCIPAL` 未开且 `OPERATOR_PRINCIPALS` 为空时成立(与任一同在 ⇒ 拒启);
2155
- > 没配服务凭据的部署上能连上端口的都是 operator;不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
2156
- > 部署形不是输入:单用户部署上 `GET /v1/diagnostics/wiring` 与 `GET /v1/outcomes` 也只认运维 —— 名单空且没声明 ⇒ 两口 501 `capability.operator_principals_required`,具名名单在单用户部署上同样生效(此前诊断面在名单为空的单用户形上、outcomes 在任何单用户形上都不问名单)。
2157
- > 单用户 + 名单空 + 没声明 ⇒ 起服 warn `operator_undeclared_single_user`(不拒启;句子点名这两口与审批队列,出路 = 一行 `OPERATOR_SINGLE_USER=true`)。
2276
+ > 声明要有一道身份边界:没配服务凭据(`SERVICE_AUTH_TOKEN` / `SERVICE_AUTH_TOKENS`)的部署上能连上端口的都是 operator,所以**声明 ∧ 没配凭据 ∧ 监听地址不只在本机回环**(缺省全接口 / `0.0.0.0` / 局域网地址 / 主机名)⇒ 服务进程起服拒
2277
+ > (不变量 `operator-single-user-boundary`;拒句一行给三条出路:配凭据 / `BIND_HOST=127.0.0.1` 只听本机回环 —— 端口要发布给别的主机的容器改配凭据 / 撤声明换具名 `OPERATOR_PRINCIPALS`);
2278
+ > `ALLOW_UNAUTHED_WRITES=true` 且没配凭据时监听地址本来就收窄到 127.0.0.1,照起。不监听的 `run-local` 不判这一条。
2279
+ > 不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
2280
+ > 部署形不是输入:单用户部署上 `GET /v1/diagnostics/wiring` 与 `GET /v1/outcomes` 也只认运维,具名名单在单用户部署上同样生效。
2281
+ > 🔴 **单用户部署必须声明谁是运维**:`REQUIRE_PRINCIPAL` 未开、`OPERATOR_PRINCIPALS` 为空、`OPERATOR_SINGLE_USER` 不是 `true` ⇒ 装配运维面时拒(服务进程起服拒;直接调 `createHttpServer` 的嵌入方同拒;不变量 `operator-declaration-required`),
2282
+ > stderr 一行拒句点名两条出路 —— `OPERATOR_SINGLE_USER=true`(每个调用方都是运维)或 `OPERATOR_PRINCIPALS=<主体,…>`(只有点名的主体);不止一个人调用的 worker 是多租,设 `REQUIRE_PRINCIPAL=true`。
2283
+ > 没有旋钮、没有宽限、没有缺省;`OPERATOR_SINGLE_USER=false` 与没写同判。`sema-up --users=single` 与 chart(`server.requirePrincipal` 未开)渲 `OPERATOR_SINGLE_USER=true`。
2284
+ > 不挂 HTTP 面的 `run-local` 不判这一条;运维面 501 `capability.operator_principals_required` 从此只出现在「多租 + 空名单」上。
2158
2285
  > ⚠️ **多租户形拒启**:`DURABLE_APPROVAL=true` + `REQUIRE_PRINCIPAL=true` 而 `OPERATOR_PRINCIPALS` 空 ⇒ 进程启动失败并点名修法(空名单下没人能当这台多租 worker 审批队列的 operator)。设名单,或确属单租户则不设 `REQUIRE_PRINCIPAL`。
2159
2286
  > 🔒 **停驻任务续跑上下文里的凭据是密封的(7.134.0 起)**:任务停驻时存下的提交体里,请求自带的凭据(`mcpServers[].transport.headers` / `.env` 的值、`a2aPeers[].headers` 的值、`settings.env` 的值、
2160
2287
  > `settings.webSearch.apiKey`、http hook 条目的静态头 `settings.hooks.<事件>[].hooks[].headers` 的值、command hook 条目的插件选项 `settings.hooks.<事件>[].hooks[].plugin.options` 的值〔7.135.0 起〕、子进程的命令行 —— stdio 传输的 `mcpServers[].transport.command` / `.args` 与 command hook 条目的 `settings.hooks.<事件>[].hooks[].command` / `.args`〔7.136.0 起;`args` 整只数组封成一个值〕)只以封签落盘(对着这一域自己的保管钥匙 `<数据根>/sealed-key/checkpoint-ctx-keys/` 最新那把公钥封、
@@ -2182,7 +2309,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2182
2309
  > 治理拍),锁上会让**每一条**任务在引擎门口被拒,而那把锁本就无物可守(策略已是部署所有、请求放松不了);
2183
2310
  > ②`mcp` + 部署自己配了 MCP 服务器——部署的服务器走同一个字段,锁上同样每条腿必拒。两者都在**启动期**点名拒,
2184
2311
  > 不留给运维在每条任务上猜。
2185
- > 同一条判据也管 A2A:`LOCKED_CONFIG_KEYS` 含 `a2a`,或合规档位禁 `a2a_peers`,而部署自己配了 A2A 对端(中心 `a2a` 域)⇒ 同样在启动期点名拒。
2312
+ > 同一条判据也管 A2A:`LOCKED_CONFIG_KEYS` 含 `a2a`,或合规档位禁 `a2a_peers`,而部署自己配了 A2A 对端(中心 `a2a` 域,或本地 `config.d/a2a.json`,见 9.5i-bis)⇒ 同样在启动期点名拒。
2186
2313
  > - `RETENTION_MAX_AGE_DAYS=<非负数>`:托管留存**视界**(天;`0` 合法=立即可删)。坏值(非数/负数/设了但为空)
2187
2314
  > 一律拒启——删数据的旋钮不许 NaN 流通。**它只是"保留多久"这句声明**;真正按它删数据的是下面两根。
2188
2315
  > - `RETENTION_SWEEP_INTERVAL_SEC=<非负整数>`(默认 `0` = **关**;上限 `2147483` 秒 ≈ 24.8 天):
@@ -2207,7 +2334,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2207
2334
  > - 能力位 `capabilities.retention` = `{mode, maxAgeDays}`(lane 开着)或 `null`(关着)。
2208
2335
  > - **子代转录(placed 分区)的留存天数没有独立旋钮座**(设计判定 2026-08-30):placed 转录的
2209
2336
  > 删除权**独家**归联合 reap(`reapDurableAgents`:行赢删才 release 转录;上面这条 lane 的候选谓词显式排除
2210
- > placed,见 `plugins/retention-store-sql.ts` 第五腿)——生效视界 = `BG_AGENT_RETENTION_MS`(缺省 7 天,
2337
+ > placed,见 `stores-sql/retention-store-sql.ts` 第五腿)——生效视界 = `BG_AGENT_RETENTION_MS`(缺省 7 天,
2211
2338
  > 毫秒形,同时是 agent 行的留存旋钮;分区孤儿腿的 `olderThanMs` 同源)。core 的
2212
2339
  > `SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT`(30 天)是共享词表常量,**本服务未按它跑**;天数形专用
2213
2340
  > 旋钮要等 core 在 reap policy 开出转录独立年龄座后再铸(届时名 `SUBAGENT_TRANSCRIPT_RETENTION_DAYS`)。
@@ -2279,15 +2406,15 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2279
2406
  > - **整理每次写库都问写判官(7.127.0 起,引擎 7.50.0)**:
2280
2407
  > - **自动整理**(任务结束按建议起跑)用这条腿自己的判官 —— 该 run 主体的人的持久路径规则 + 部署的调用方策略,与模型自己的 `Write` 同一套判:
2281
2408
  > 规则点名库目录 ⇒ 在任何占位与模型调用之前拒,运维行 `engine_notice` 一条 `memory.consolidation_refused`(`detail.refusalCode: "memory.consolidation_write_refused"`);
2282
- > 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 读不到任何人的持久规则(只剩部署策略那一层)。
2409
+ > 只点名产物文件 ⇒ 应用时拒、什么都不写,一条 `memory.consolidation_incomplete`(`stop: "refused"`);用户任务不受影响。没有主体的 run 在单用户部署上读本机属主的规则桶(**7.148.0+**,见 §「不带主体的 run 读哪只规则桶」),多租部署上读不到任何人的持久规则(只剩部署策略那一层)。
2283
2410
  > ⚠️ **引擎 7.50.x 上这条腿在缺省部署上每一次都被拒**:只要部署的敏感路径守卫在场(`SENSITIVE_WRITE_PATTERNS` 未设即在场),引擎问写判官时递给部署策略的是一只已中止的信号,守卫判不了库目录的真实落点、按 `target_unresolved` 拒 —— 与规则、权限模式无关(`memory.consolidation_refused`,人读句只带拒因前 200 字)。修复随引擎提货;要整理用手动阀门,不要为此设 `SENSITIVE_WRITE_PATTERNS=off`(见 MIGRATION 7.127.2 第 3 条)。
2284
- > - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,但它的写判官座与自动腿是**同一只**引擎判官构造器(此前由本服务代答「无异议」)。喂给它的部署事实是部署的合成规则店与
2285
- > `~/` 行的 home(`REMOTE_EXEC_HOME_DIR`;未声明时 host / in-process 车道取本机 home、e2b 取 `/home/user`,k8s / ssh 等车道缺席)。**这两样在手动阀门上今天都读不到人的行**:
2286
- > 阀门不带主体(没有来源能回答「这个库属于谁」),引擎读的是匿名地板、不读任何人的持久路径行 —— 所以「`~/` 收紧行无处锚定 ⇒ 拒闭」这条引擎规则在生产上走不到
2287
- > (只有调用方自己带着主体去喂这只构造器时才走得到),与下一句的「三层没喂」是同一件事的两面。
2288
- > **三层没喂,所以手动阀门今天仍不受它们约束**:人的持久路径规则(阀门没有来源能回答「这个库属于谁」)、部署的审批名单 / 自治档 / 敏感路径守卫(`APPROVAL_DENY=Write` 拦不住手动阀门)、
2289
- > 项目根(相对行无处锚定)。这些只约束自动整理腿。每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-layers-unfed`(计数 + 探针行 + 一次性告警)—— 这是一笔显式登记的欠账,
2290
- > 不是常态;读谁的规则、怎么喂部署策略待设计,落地后本句改写。
2411
+ > - **手动阀门**(`POST /v1/admin/memory/consolidation/run`)没有腿,但它的写判官座与自动腿是**同一只**引擎判官构造器,每一次真跑现铸一只(并入正在跑的那一轮的调用方拿同一份收执、用起跑者的判官)。喂给它的部署事实:
2412
+ > 部署的合成规则店;**人** = 发起这一轮的调用方(**7.148.0+**):带主体 ⇒ 那位主体的持久路径规则,不带主体 ⇒ 单用户部署上本机属主的规则桶(多租部署上阀门口本来就要主体);
2413
+ > `~/` 行的 home(`REMOTE_EXEC_HOME_DIR`;未声明时 host / in-process 车道取本机 home、e2b 取 `/home/user`,k8s / ssh 等车道缺席 ⇒ `~/` 收紧行无处锚定 = 拒闭)。
2414
+ > **还没喂的两样**:部署的调用方策略(审批名单 `APPROVAL_DENY` / `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO`、敏感路径守卫 `SENSITIVE_WRITE_PATTERNS`、命令策略 —— 腿上这是一整条治理链,
2415
+ > 整条喂要先有引擎在请求上盖的那只豁免根放过记忆库自己的写,宿主输入没有这一位)与项目根(相对行无处锚定 = 拒闭)——
2416
+ > 部署的写策略因此只约束自动整理腿:`APPROVAL_DENY=Write` 拦不住手动阀门,人的 deny 行拦得住。每次被问记一次 fail-open 计数 `server.memory-consolidation.operator-valve-layers-unfed`
2417
+ > (计数 + 探针行 + 一次性告警)—— 这是一笔显式登记的欠账,等引擎的宿主输入收豁免根、整条治理链喂上之后本句改写。
2291
2418
  > 驱动的每一条通告 —— 非收敛停的 `memory.consolidation_incomplete`、问判官**之前**推不出库家落点时同码拒的 `memory.consolidation_refused`(生产上可达,与判官答什么无关)——
2292
2419
  > 都经同一只 `engine_notice` 转发器进运维行;早问拒仍按路由的码臂答 `409 memory.consolidation_refused`。
2293
2420
  > - **怎么让整理写**:删掉或收窄点名记忆库路径的规则;只想禁止自动整理就设 `MEMORY_AUTO_CONSOLIDATION=off`。
@@ -2313,11 +2440,11 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2313
2440
  > 🔴 因此:**没配 `SERVICE_AUTH_TOKEN` 且未开 `ALLOW_UNAUTHED_WRITES` 的 worker 上这一口恒 503**,
2314
2441
  > 而能力位仍报 true(本族共有缺口,附录 B.1 已如实登记)。启动日志为此打一条
2315
2442
  > `memory_consolidation_run_credential_gated`;状态读那一口不受影响(它不烧模型)。
2316
- > 🔴 另一条启动告警:没有运维名册(`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`)时两口恒 501 `capability.operator_principals_required`(没有运维名册 = 没有任何人是 operator),
2317
- > 日志里是 `memory_consolidation_valve_unreachable`。
2443
+ > 谁是运维:阀门开得起来的只有单用户 + file 记忆后端的部署,而单用户部署起服就得声明运维(`OPERATOR_SINGLE_USER=true` 或具名 `OPERATOR_PRINCIPALS`,
2444
+ > 见本文 `OPERATOR_SINGLE_USER` 段);具名名单外的调用方答 403 `auth.operator_only`。
2318
2445
  > - `GET /v1/admin/memory/consolidation[?scope=]` —— 状态投影(阀门/座位/scope 表/上一轮的账)。
2319
2446
  > - 能力位 `capabilities.memoryConsolidationDriver`(阀门上了场 ∧ 有运维名册:名单非空或单用户声明)。
2320
- > - 逐键语义与停因闭集见 `docs/ASSISTANT-WIRE-CONTRACT.md` §10。
2447
+ > - 逐键语义与停因闭集在契约 §10(内部记录)。
2321
2448
 
2322
2449
  > **⚠️ hook-wired 部署里,parked 后台子代可能赎回不了(常态,不是升级窗口)。**
2323
2450
  > 引擎 5.19.0 起,一个任务的 **PreToolUse screening 面下延管辖它委派出去的子代**,于是 hook-wired 父
@@ -2379,6 +2506,16 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2379
2506
  > 与历史记录轨迹;真正持久化的规则本身在 `permission_rule` 表,不受影响、不需要重建。
2380
2507
  > 探针只在规则车道真被装配时跑(`PERMISSION_RULES_ENABLED=false` 的部署一次都不碰这张表,不受影响)。
2381
2508
  >
2509
+ > **不带主体的 run 读哪只规则桶(7.148.0+)**:规则店按人分桶 —— 带 `x-agent-principal`(或验过的主体)的 run 读那位主体的桶;**不带主体**的 run(裸 HTTP、没设主体的 SDK、本机控制台)
2510
+ > 在**单用户部署**(`REQUIRE_PRINCIPAL` 未开)上读**本机属主的桶**(file 后端 `<数据根>/permission-rules/local-owner.json`;SQL 后端 `permission_rule` 表 `owner_kind='local-owner'` 的行),
2511
+ > 那只桶里的 deny / ask / allow 行对它生效;运维手动整理阀门不带主体的调用方同读这只桶。条件是规则店装配了(`PERMISSION_RULES_ENABLED` 开着且后端有规则店;没装配 = 没有持久规则,谁都读不到)。
2512
+ > **多租部署**(`REQUIRE_PRINCIPAL=true`)上不带主体的调用方不代表任何人,读零条持久规则(那种部署也不受理不带主体的 run)。7.147.x 及以前不带主体的 run 在两种部署上都读零条。
2513
+ > 射程:**cli 用户不受影响** —— cli 恒解析出一位主体(`SEMA_LIVE_PRINCIPAL`,缺省 `default`),TUI 与 `-p` 同主体,卡上「不再询问」落那位主体的桶。
2514
+ > ⚠️ 委派出去的子代不在射程:`Agent` 工具起的子任务跑在另一只不挂规则店的 Runner 上,今天读零条持久规则 —— 本机属主桶(与任何主体的桶)里的 deny 不约束子代;这一形与带主体的 run 相同,本版不变。
2515
+ > 不带主体的问卡不给「不再询问」(回决那侧对无主的问没有可写的桶);本机属主桶本版没有 HTTP 自助口 —— 看 / 删它的行在存储层做(或 `PERMISSION_RULES_ENABLED=false` 整条关掉)。
2516
+ > 身份边界:单用户部署上不带主体的 run 就是那个用户,所以「写面无鉴权(`ALLOW_UNAUTHED_WRITES=true` 且没配服务凭据)∧ 显式 `BIND_HOST` 不只在本机回环」⇒ 服务进程起服拒
2517
+ > (不变量 `single-user-anonymous-boundary`;两条出路:配凭据 / 只听本机回环;见 §「监听绑址」)。
2518
+ >
2382
2519
  > **人的持久规则的读法(7.127.0 起,引擎 7.50.0)**:每个调用读一次规则店;一条持久 deny 行在门上拒它所在那一面的**每个**工具(Read / Grep / Glob / RepoMap /
2383
2520
  > 写工具 / 声明路径目标的调用方工具),`tool_end.gate.disposition.deniedBy = "persisted_rule"`。店的某个分区读不出 ⇒ 该分区上次读到的 deny / ask 行照旧生效、allow 行不在,
2384
2521
  > 射程内的调用真人问(bypass 也问,无审批面即拒),run 一条 `permission.rule_store_unavailable`。
@@ -2519,22 +2656,23 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2519
2656
  | HTTP `429` | 限流 | 看 `Retry-After` 退避 |
2520
2657
  | 任一 4xx / 5xx 的 `errorCode`(7.146.0 起查契约附录 A 的**码族**列) | 目录每个码恰属五族之一:`transient`(原样重发等一等可能成)· `deterministic`(重发改不了判,别自动重发)· `store_judged`(逐次由店判,读体键 `permanence`)· `pending_decision`(卡仍待决,读体键 `retriable`:它说「卡还在」,不说「重发会成功」)· `retention_expired`(留存已被收走,新开) | 「要不要重试」「要不要重读待决列表」「要不要新开」按这一列读,不自己抄码表(契约文档附录 A.1g;MIGRATION 第 17 条)。表外码(core 原码镜像)没有族,照旧看状态与前缀 |
2521
2658
  | HTTP `429` + `errorCode:"limit.rate_exceeded"` + `reason:"run_concurrency_cap"` | 本副本在飞 run 已到并发帽(`RUN_MAX_CONCURRENT` 全局,或 `RUN_MAX_CONCURRENT_PER_PRINCIPAL` 按提交主体;缺省关;新提交的四个口,幂等重放不吃) | 看 `Retry-After`(5 秒)/ 体 `retryAfterSec` 退避后重投;或等在飞的 run 收尾 / 调高帽 |
2522
- | 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.141.0–7.143.0 答的码是 `conflict`) | 这只会话的存档仍带更早版本写下的属主戳(一次性迁移改写不了的,或跳过带迁移的版本升上来的数据根;起服 warn `local_archive_kept_stamped` 点名):不在它上面造新存档、不原地换它的日志(干净追加 / 载荷分叉 / fork / stale 各形的推送同答),什么都没写;读 / 续跑 / 拉取照常,「id 与内容都相同」的重推照常落定 | 按体里的固定句办:删掉这只会话(`DELETE /v1/sessions/:id`)再推。与暂存提交时属主变了的 `errorCode:"conflict"`(出路相反:从第一阶段重来)按码分开 |
2659
+ | HTTP `429` + `errorCode:"limit.rate_exceeded"` + `reason:"sandbox_capacity"`(7.148.0 起) | local-docker 车道容器帽前此刻已有起动在等空位(`DOCKER_MAX_CONTAINERS`;缺省关;同四个口、同位,幂等重放不吃;§9.3k) | 看 `Retry-After`(5 秒)退避后重投;或等在跑的容器收尾 / 调高帽 / 清掉残留容器 |
2660
+ | 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"`(出路相反:从第一阶段重来)按码分开 |
2523
2661
  | HTTP `429` + `errorCode:"limit.cost_quota_exceeded"` | per-principal 累计成本配额越顶(`MAX_PRINCIPAL_COST_USD`;**进场门**,不打断在跑的 run) | 看 `Retry-After` / 体 `retryAfterSec` 退避(体 `windowResetsAt` = 当代窗滚过的时刻);窗滚过或调高上限后放行 |
2524
2662
  | 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 …`),只作展示、别锚文案 |
2525
2663
  | 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` 分支,句子只作展示 |
2526
2664
  | 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` 分支,句子只作展示 |
2527
2665
  | 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` 改那一处(合并 / 拆小条目、缩短文字、修那一条路径);句子只作展示 |
2528
- | 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` 定位到那几个键;句子只作展示、别锚文案 |
2666
+ | 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` 定位到那几个键;句子只作展示、别锚文案 |
2529
2667
  | 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` = 本码) |
2530
- | 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` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2531
- | 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` 后的现名,或删掉条目 |
2668
+ | 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` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2669
+ | 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` 后的现名,或删掉条目 |
2532
2670
  | HTTP `400` + `errorCode:"prompt.assembled_role_refused"`(7.132.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `systemPrompt` / `appendSystemPrompt`(含 `settings.outputStyle`)逐字含引擎提示里的一段锁定文本(宪法、安全段、`# Harness` 这样的标题行)—— 一份已组装的提示。开跑前拒、零调用,句子点名字段与命中段 | 只写角色层:删掉抄进来的引擎段(引擎自己组宪法与行为段;追加段恒排在宪法之后) |
2533
- | 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` 的条数 ⇒ 列表被截 |
2671
+ | 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` 的条数 ⇒ 列表被截 |
2534
2672
  | 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 记录答,不重判、不带;在飞合流回放的是首发那一腿铸的应答 |
2535
2673
  | 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` 后的现名(或删掉条目);超帽的合并成更宽的条目 |
2536
- | 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` |
2537
- | 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 |
2674
+ | 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` |
2675
+ | 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 |
2538
2676
  | HTTP `429` + `errorCode:"usage.window_exhausted"` | 部署级治理窗耗尽(`USAGE_WINDOWS`,token 或 $ 天花板先满者) | 看响应体 `retryAfterSec` 退避;窗滑动/桶到期后放行 |
2539
2677
  | `terminal:{kind:"failed", code:"usage.window_exhausted"}` | 已受理的 run 在 **turn 边界**撞上治理窗且无法 durable 挂起 | 同上退避后重投;配好 checkpoint 基建则改为 `suspended` 等窗自动续跑 |
2540
2678
  | 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`;原样重试不改判 |
@@ -2549,12 +2687,12 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2549
2687
  **机器码**:每个 4xx/5xx 响应体都带一个 `errorCode`(与人类文案 `error` 并列),这是**唯一**该拿来做
2550
2688
  程序分支的字段——**别锚 `error` 文案**。前缀族固定:`auth.` / `request.` / `not_found.` / `conflict.` /
2551
2689
  `limit.` / `usage.`(部署级治理窗)/ `capability.`(本部署没接这个面)/ `feature.`(开关没开)/
2552
- `internal.` / `state.`;未知码按前缀兜底永远安全。完整码表见 `docs/ASSISTANT-WIRE-CONTRACT.md` §附录 A。
2690
+ `internal.` / `state.`;未知码按前缀兜底永远安全。完整码表在契约附录 A(内部记录,不随包;随包 `wire-changes/server@<版本>.json` 逐版记着 wire 键与闭集成员的增删)。
2553
2691
 
2554
2692
  **进程警告面(日志采集方注意)**:引擎(core ≥5.16)在持久 agent 行的 org 判决写回被丢弃等场合走
2555
2693
  Node `process.emitWarning`,警告文案前缀固定 `sema durable-agents:`——采集规则按该前缀匹配,别当噪音过滤掉。
2556
2694
  server 自身的兜底留痕走 stderr 上的一行 JSON 日志(`msg` 逐字 `[sema] fail-open: <tag>`,字段 `{ tag, count }`,逐 tag 一次,不随 `LOG_LEVEL`)+ `fail_open_total` 指标,
2557
- tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配期忠告(core `onError(phase:"config")`)落
2695
+ tag 词表与每条的最坏后果 = 随包 `dist/observability/fail-open.js` 里 `FAIL_OPEN_TAGS` 每项的 `cls` / `note`(键名与类型也在同目录 `.d.ts`)。引擎装配期忠告(core `onError(phase:"config")`)落
2558
2696
  `runner_config_advisory` 日志行,带结构化 `code`(如 `config.toolpolicy.unmatched_names`)与 `classification`
2559
2697
  (core 给宿主定级的判别词;词表外原样透传并标 `classificationKnown:false`),指标 `runner_config_advisory_total`
2560
2698
  按 `code` / `classification` / `level` 打标;其中 `shell-gate-off` 在本 session 显式声明 `permissionMode:
@@ -2576,8 +2714,7 @@ tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配
2576
2714
  **非读**命令(`rm` / `curl` / `git` / `npm`)与工作区内的普通读在 `off` 下仍然零审批。注文案里那句
2577
2715
  「the read boundary is still judged on every command」就是这件事。⇒ **运维侧后果**:一台把 `bypassPermissions`
2578
2716
  当「无人值守直跑」用的部署,现在会在这三类命令上落审批(无活体席 ⇒ durable park,出现在 `GET /v1/approvals`)。
2579
- 要让它们重新不问,得从**读面**下手(`READ_FACE` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`,
2580
- 见 `docs/DEPLOY-PREREQS.md`),`permissionMode` 那根杆子够不着它。
2717
+ 要让它们重新不问,得从**读面**下手(`READ_FACE` / `READ_DENY_BUILTIN_TIERS` / `READ_DENY_BUILTIN_EXCLUDE`),`permissionMode` 那根杆子够不着它。
2581
2718
 
2582
2719
  ### 9.1 limits.approachNotice(6.1.0 起)
2583
2720
 
@@ -2686,11 +2823,11 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2686
2823
 
2687
2824
  - **没配快照存储**(`MINIO_ENDPOINT` / `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` 缺任一)⇒ 工作区每轮为空,`GET /v1/capabilities` 不带 `workspaceSync`;起服一行 `workspace_persist_unavailable`(`missing` 点名缺的键;缺省 `space` 也出,设 `WORKSPACE_PERSIST=off` 即不出)。
2688
2825
  - **没有存储后端**(`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 却一份都不存。
2689
- - **快照桶不存在**(`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」。
2826
+ - **快照桶不存在**(`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 仍是悬空恢复点。
2690
2827
  - **读 `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` 即恢复。
2691
2828
  - **`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 及以前这一形不出任何行。
2692
2829
  - **`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 从空目录起)。与上一条「悬空」分开:那是对象**不在**(没东西可恢复 ⇒ 空目录 + 响亮),这是对象**在**而拿不回来(⇒ 拒)。
2693
- - **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 去改指针。
2830
+ - **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` 减去提交前等待的时长),回读不另开一只时限。
2694
2831
  - **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` 不动,下一轮恢复上一份 —— 取消 = 丢掉这一轮的工作。
2695
2832
  - **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:配额等)不在此列,仍是那一次尝试的失败。
2696
2833
  - **删最老的失败** ⇒ 一行 `workspace_snapshot_prune_failed`,不影响本轮(空间里只是多留几份)。
@@ -2821,7 +2958,7 @@ k8s 与 e2b 两条沙箱车道各有一只**活性界**:一条命令无输出满
2821
2958
  - 两样都没说(引擎自己的 git 探针、文件读写这类)⇒ 车道活性界兜底,它是这条命令唯一的挂死护栏。
2822
2959
 
2823
2960
  界从调用开始算,覆盖命令全程 —— 含 e2b 供应商建命令那一段(命令还没起跑,自然没有输出):建命令卡住按这条命令最早的那一界切(说了超时按超时,没说界按活性界)。
2824
- e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时先由它切:超时或活性界长于 60 s 的命令(含缺省 120 s 的活性界)建命令卡住在约 60 s 被切 —— 缓冲 exec(Bash 走的这一面)报 `timeout`,流式 exec 报 `connect_failed`。
2961
+ e2b SDK 自己的建命令握手另有一界(缺省 60 s),比命令的界早时先由它切:超时或活性界长于 60 s 的命令(含缺省 120 s 的活性界)建命令卡住在约 60 s 被切 —— 缓冲 exec(Bash 走的这一面)与流式 exec 同报 `timeout`(那是供应商的界;建命令的其余错误仍报 `connect_failed`)。
2825
2962
 
2826
2963
  | 旋钮 | 车道 | 缺省 | 说明 |
2827
2964
  |---|---|---|---|
@@ -2920,6 +3057,56 @@ ssh / adb / local-docker 三条车道的流式调用同读这一条律:没有车
2920
3057
  - **运维**:每次播种一行 info `workspace_seeded{sessionId, bundleId, entries}`;删会话时种子随附件一并删。
2921
3058
  - **不删包**:首交成功之后不要 `DELETE /v1/attachments/:id` —— 无持久的车道每个新回合都要再解它。
2922
3059
 
3060
+ ### 9.3k local-docker 车道:容器帽(`DOCKER_MAX_CONTAINERS`;7.148.0 起,缺省关)
3061
+
3062
+ local-docker 车道每条 run、每只委派子代各起一只容器,停驻期(等审批)的容器不销毁;`DOCKER_MEMORY` 只封单只。要给整条车道封顶(家用 / 多租常驻部署的内存预算):
3063
+
3064
+ | 旋钮 | 值 | 含义 |
3065
+ |---|---|---|
3066
+ | `DOCKER_MAX_CONTAINERS` | 非负整数,缺省 `0` = 关 | 本 worker 同时在跑的容器数上限;坏值(负 / 小数 / 非数)拒启 |
3067
+
3068
+ - **按内存定上限**:上限只有这一只旋钮。每只容器的 `--memory` 都是起它那一刻的 `DOCKER_MEMORY` ⇒ `DOCKER_MAX_CONTAINERS = floor(愿给容器的总内存 / DOCKER_MEMORY)`(例:总预算 8 GiB、`DOCKER_MEMORY=2g` ⇒ 4)。
3069
+ 改小 `DOCKER_MEMORY` 重起之后,上一次留下的容器仍按它们起动时的限额占内存、按只计 —— 换算只对按新值起的容器成立,改值前先清掉残留。
3070
+ - **怎么数**:只有一个答者 —— docker daemon 上带本 worker 标签 `sema.local-docker=<worker>` 的容器(**标签恒在,上限随时可开**:本 worker 起的每只容器都带这枚标签,与上限开没开无关,上限关着时起的开启后照计;`docker ps -a` 全状态读,在跑 = running / paused / restarting / removing),
3071
+ 加上本进程 `docker run` 回执丢了而 daemon 仍列着的那几只(不确定 ≠ 没发生;daemon 不再列它 ⇒ 放)。停驻期的、委派子代的容器都在跑,都计。读不出 daemon ⇒ 不算 0,按「证不了有空位」处理。
3072
+ - **射程 = 本 worker**:worker = 本服务数据根(`LOCAL_DATA_ROOT` → `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`)绝对路径的摘要,同一数据根上重起不变 ——
3073
+ 上一次进程崩掉留下的容器照计(不计会让重起之后超额受理);别的数据根是别的 worker,不计。准入的互斥与「回执未定」的记账只在一个进程里:一个数据根只跑一只服务进程 ——
3074
+ 同一数据根上同时跑的两只进程看得见对方**已在跑**的容器(只会少受理),但看不见对方正在起、还没列出的那几只,同时起动时合计可超过上限;多只进程 / 多只 worker 共用一只 daemon 时的整机合计不在本旋钮。
3075
+ 只有 7.148.0 之前起的容器不带标签、不计 —— 升级后第一次开帽前先清掉它们(`docker ps -a --filter name=sema-docker-`)。拒句里给本 worker 的完整过滤式;`docker ps -a --filter label=sema.local-docker` 列出所有 worker 的。
3076
+ - **没空位怎么办**:起容器(首个工具调用、子代、续跑腿)按到达顺序等,别的容器被删即唤醒(外部删除每秒重读看见);每只执行环境一份等待预算(docker 控制操作的界,缺省 60 s,跨尝试共用 —— 花完之后每次只读一次、
3077
+ 有空位就起、没有立拒)。到期 ⇒ 这一次工具调用拿到带 `limit.rate_exceeded` · `sandbox_capacity` 的拒句(在用数 / 帽 / 等了多久 / 出路),run 本身不被终结;运维行 `local_docker_capacity_refused`。
3078
+ - **提交门**:闸里有起动在等空位时,新提交答 `429 limit.rate_exceeded` + `reason:"sandbox_capacity"` + `Retry-After: 5`(上面错误表一行);队列空而车道满时照常受理,在首个工具调用处等。
3079
+ - **读数**:`GET /health` 的 `sandboxCapacity: { lane, limit, inUse, readAt, waiting }`(帽开着才在场;`inUse` 是闸最近一次读 daemon 的数,本口不调 docker)。
3080
+ - **停驻 → 续跑**:续跑腿接着用停驻时那只容器(容器名由 run 的身份确定:`sema-docker-<身份>-<12 位摘要>`;服务重起之后续跑 ⇒ 附着那只在跑的容器),在跑的不另要一格,续跑腿收场时删它。
3081
+ 停驻期间 daemon 重起 / 宿主重启让它停了(`exited`)⇒ 续跑时经准入(开了上限就等空位)再 `docker start` 它、附着(文件在);停在 `paused` / `dead` 一类状态、同名容器不带本 worker 标签、
3082
+ `docker inspect` 读不出 ⇒ 这一次连接拒、什么都不起不删(拒句点名容器)。执行环境只碰它负责的那只容器实例(按容器 id,不按名);删除证实不了的那只永不再附着。
3083
+ 重起之后那只容器已经不在 ⇒ 续跑腿起一只新的空容器(停驻前的文件不在)。
3084
+ - **无主容器回收**:停驻中取消、停驻过期(引擎只在自己结清停驻时销毁停驻环境,本服务的取消与过期在店层结清)与重起之后才结清的停驻,停驻时那只容器没有销毁者 —— 维护 tick 上的沙箱回收腿
3085
+ (与 k8s 车道的 pod 回收共一只座、一个节拍:起服后第一个维护 tick 跑一轮,此后每 5 分钟一轮;不新开计时器、没有旋钮)按它那条 run 的行判:**run 行已终局(completed / failed / blocked)并过了 5 分钟保持窗 ⇒ 删**(引擎在 `done` 帧之后还要用环境收尾:边界捕获 / 后台 shell 清扫 / 销毁,终局行比这些早 —— 窗内不动;钟 = run 行转终态那一拍)
3086
+ (本进程还拿着那只执行环境 ⇒ 经它收场;重起之后没有环境对象 ⇒ 认领这只名下本 worker 的容器,按容器 id 经同一只欠着机删,证实才清),这与一条 run 正常收场时引擎删它的容器是同一件事;
3087
+ **run 行是活的(跑着 / 停驻着,还能被批准或续跑)⇒ 不动**。拿不准恒不删:容器名读不回身份(本版之前起的容器名带随机尾、可读形被截断的身份)、没有 run 行(委派子代的容器:身份是它自己的会话号,不在 run 账本里)、
3088
+ 读 run 账本抛、状态词不在词表里、终局时刻读不出、没有存储后端 ⇒ 都不动(这几类仍按上面「怎么数」里的过滤式手删)。别的 worker 的容器不在列(只列本 worker 标签);列出之后名下被换成不带本 worker 标签的容器 ⇒ 不删。
3089
+ 每轮一行 `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`),
3090
+ 每删一只一行 `local_docker_reaped`,计数 `local_docker_containers_reaped_total{reason="run_ended"}`。**列举失败**(docker 不可达)⇒ 这一轮一只都不删:一行 warn `local_docker_reap_list_failed` + fail-open 计数
3091
+ `server.local-docker-reaper.degraded`(detail `why=list_failed`),下一轮照常再列;**某一只删除没证实**(daemon 出错、容器还在)⇒ 不挡同轮其余:一行 warn `local_docker_reap_unconfirmed` + 同一个计数(`why=delete_unconfirmed`),
3092
+ 这一只仍欠着(永不再附着,下一轮先删它)—— 计数持续非零就核 docker daemon 与 `docker rm -f` 的权限。**运维可见的差别**:被取消 / 过期的 run 的容器在 run 终局 5 分钟后的下一轮(最长再加一个 5 分钟周期)被删,不再留着供事后 `docker exec` 进去看;
3093
+ 删之前要取的文件在取消之前 `docker cp` 出来。
3094
+
3095
+ ### 9.3l 前台命令超时转后台(`AUTO_BACKGROUND_ON_TIMEOUT`;7.148.0 起,缺省开)
3096
+
3097
+ 前台 Bash 命令撞上**它自己的**超时(`timeout` 参数,或引擎缺省值)时,合格的命令转成后台任务继续跑,而不是被杀 —— 长构建 / 迁移这类不幂等的命令不必盲重跑。
3098
+ 资格由引擎判(命令里没有 `git`、首词不是 `sleep`);转了之后模型读到「超时了但仍在跑,已转后台(task_id=…)」,用 `TaskOutput` / `TaskStop` 照常读 / 停,完成时照常收到通知。
3099
+ 同一个开关在引擎里还管第二个触发:有人的输入(steer / 排队的消息)等着本轮边界、而合格命令还在跑 ⇒ 引擎把它转后台,好让输入先送达(命令照跑,结果走后台通知)。引擎刻意只设这一个开关。
3100
+
3101
+ | 旋钮 | 缺省 | 说明 |
3102
+ |---|---|---|
3103
+ | `AUTO_BACKGROUND_ON_TIMEOUT` | `true` | `false` ⇒ 两个触发都关:超时即杀、steer 等前台命令跑完(引擎缺省形)。运维旋钮无条件:不能转后台的车道上照起、不拒启 |
3104
+
3105
+ - **哪些车道生效**:执行环境能把运行中的命令领养成后台的车道 —— host(单用户且 `HOST_BG_ENABLED` 开着)/ k8s / e2b。local-docker / ssh / adb / device / none 没有后台 shell,超时照杀。
3106
+ - **哪些 Runner**:主 Runner、委派子代、hook 代理与 leader 车道的 Runner 同一个值(手带只有一个铸点)。
3107
+ - **读数**:`GET /health` 的 `autoBackgroundOnTimeout`(恒在场):`armed` = 本车道上生效;`off` = 旋钮关了;`unsupported` = 旋钮开着,但本车道不能转后台。
3108
+ - **界**:转后台的命令照常受后台 shell 的并发上限(满了 ⇒ 这一发不转、照杀)与后台超时硬上限(到点被杀);它占着的进程 / 沙箱算力在它结束之前不释放。
3109
+
2923
3110
  ### 9.4 auto 模式(`permissionMode:"auto"`):武装三态、本地 org deny、自查(与 core 的意图武装式合修)
2924
3111
 
2925
3112
  **背景**:此前引擎只在「组织授予」(web-admin 解出的 per-principal `runtimeCaps.autoMode === true`)时武装
@@ -2940,7 +3127,7 @@ armed ⟺ 本次请求 permissionMode 是 "auto" ∧ 本部署装配了分类
2940
3127
  | `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 域) |
2941
3128
  | `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 轴) |
2942
3129
  | `CROSS_SESSION_DIALOG_EXPIRY` | 未设(⇒ 引擎缺省 `5m`) | 同族:CC settings 键名 `dialogExpiry` 的部署层 —— 被 hold 的跨会话消息等待人审多久后按**安全缺省**结算(过期丢弃,**带回执**告知发送方,不静默吞)。词表 `60s`/`5m`/`10m`/`never`(`never` = 不设期限);未设 ⇒ 本仓**不铸键**,由引擎落它自己的缺省(不复制上游缺省值)。词表外拒启。⚠️ 生效前提同上。另注:CC 的同名键还有第二个消费面(转发到远端客户端的审批对话框停靠时长),本旋钮**今天不驱动**那一面 |
2943
- | `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 封。⚠️ 本版只有投递半:读信的助理腿随引擎下一版,在那之前信留在箱里没人读 |
3130
+ | `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` 这类不透明串、带空白 / 大写 / 换行的值**拒启**,拒句是引擎原句,引用值至多 96 字符;此前这类值照收,收件箱等一只永远不会来的读者);未设 / 空串 ⇒ 没有这条车道,一切与上一版逐字节同。额度写死在门里:发件人每小时 60 次尝试(成没成都算)、同一对主体每小时 10 封、一位收件人每小时合计 30 封、收件箱上界 50 封。⚠️ 本服务只有投递半:引擎(7.148.0 带的版本起)有读信的助理腿,但那条腿要以这只 handle 作任务 id 跑,本服务今天没有起它的入口(读法候引擎确认),在那之前信留在箱里没人读 |
2944
3131
 
2945
3132
  **自查读面**(`GET /v1/capabilities.permissionModeAuto`,壳的 `sema doctor permissions` 消费;server 7.106.0 起引擎只剩一种武装极性,
2946
3133
  7.107.0 起成员 `intentArming` 删除,见该条):
@@ -3010,7 +3197,7 @@ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST
3010
3197
  🔴 **这个体是闭集(7.86.0)**:受理集 = `decision` / `editedPlan` / `reason` / `permissionModeAfter` /
3011
3198
  `boundCallId` 五键,集外键(含定谳前的旧拼法 `nextPermissionMode`)**响亮拒** `400 request.body_shape` +
3012
3199
  机读两桶逐字点名,**不再静默丢** —— 与 `/v1/approvals/:id/decide` 同一只机器、同码同串。
3013
- 逐字契约见 `docs/ASSISTANT-WIRE-CONTRACT.md` §4c。
3200
+ 逐字契约见契约 §4c(内部记录)。
3014
3201
 
3015
3202
  ### 9.4c 两层「放行 / 拒绝」别读混:单用户 allow-all 基线与敏感路径守卫集(7.141.0 起成文)
3016
3203
 
@@ -3027,7 +3214,7 @@ plan 起步的任务把计划交给人之后停在 `plan_review` 门上。`POST
3027
3214
  - **只有 user 层的 `/` 形随层变**:`userSettings` 里的 `Read(/secret/**)` 守的是**执行环境的配置根**下的 `secret`(host 车道 = 本服务的 `SEMA_CONFIG_DIR`,缺省 `~/.sema`),
3028
3215
  不再守任务根下的 `secret` —— 与参照实现读 user 层 `/` 的位置相同。别的来源与纯串读在任务根。要守每个项目自己的目录:把规则放进项目层,或写相对形 `secret/**`(任务根下任意深度);固定位置写 `//` 绝对形。
3029
3216
  - **沙箱车道的配置根**:运维把 `SEMA_CONFIG_DIR` 列进 `E2B_SANDBOX_ENV` / `K8S_SANDBOX_ENV` / `DOCKER_SANDBOX_ENV` 转进沙箱 ⇒ 读那个值;没转 ⇒ 执行环境 home 下的 `.sema`
3030
- (e2b = `/home/user/.sema`;k8s / local-docker / ssh / adb / device 要 `REMOTE_EXEC_HOME_DIR` 声明 home)。两样都没有 ⇒ 这样的条目 422,`detail.problems[].reason:"home_unresolvable"`(与 `~/` 缺 home 同一个词);
3217
+ (e2b = `/home/user/.sema`;k8s / local-docker / ssh / adb / device 要 `REMOTE_EXEC_HOME_DIR` 声明 home)。两样都没有 ⇒ 这样的条目 422,`problems[].reason:"home_unresolvable"`(与 `~/` 缺 home 同一个词);
3031
3218
  出路:转 `SEMA_CONFIG_DIR`、声明 `REMOTE_EXEC_HOME_DIR`,或把规则写成 `//` 绝对形。转进去的值不是绝对路径 ⇒ deny / ask 422 `unsupported.path_base`(allow 桶见下面「allow 桶同读」)。
3032
3219
  - **层不改准入**:多租部署上的准入档只按这次请求验过的凭据推(9 节速查 `below_trust_floor` 一行);条目上的层只决定规则读在哪。
3033
3220
  - **回滚 / 混版副本**:本版之前的服务对带层条目是桶门 `bad_entry`(新提交 400,续跑 409),不会静默读丢;端见位才发,所以没升级的服务上它照旧只收到纯串。
@@ -3118,23 +3305,23 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3118
3305
  ### 9.5b `settings.hooks` 的 matcher:提交时判什么、拒什么(7.126.0 起良性设置的结局只取决于配置)
3119
3306
 
3120
3307
  `PreToolUse` / `PostToolUse` / `PostToolUseFailure` 三个工具事件的 `matcher` 按调用派发到的工具的 **wire 名**匹配(不是别名)。**新提交**(`POST /v1/runs` · `/v1/tasks` · `/v1/tasks/stream`;续跑重放不判)
3121
- 对每只 matcher 做判断,下面四种情形都答 **`400 request.field_invalid`**,体带 `field: "settings.hooks"` 与 `detail`,按 `detail.reason` 分形(闭集,穷举读):
3308
+ 对每只 matcher 做判断,下面四种情形都答 **`400 request.field_invalid`**,体根上带 `field` = 那一组 matcher 的坐标(`settings.hooks.<event>[<groupIndex>].matcher`)与 `reason`,按 `reason` 分形(闭集,穷举读;7.148.0 起在体根上,此前是 `field: "settings.hooks"` + 对象形 `detail`):
3122
3309
 
3123
- | `detail.reason` | 触发 | `detail` 其余键 | 怎么改 |
3310
+ | `reason` | 触发 | 体根上其余键 | 怎么改 |
3124
3311
  |---|---|---|---|
3125
- | `dead_alias` | matcher 选中某个工具的别名、却选不中它的正名(钩子永不命中) | `event` · `alias` · `canonical` | 改成正名(`ListPeers` → `ListAgents`) |
3312
+ | `dead_alias` | matcher 选中某个工具的别名、却选不中它的正名(钩子永不命中) | `event` · `groupIndex` · `alias` · `canonical` | 改成正名(`ListPeers` → `ListAgents`) |
3126
3313
  | `over_budget` | 正则形 matcher 的一次求值在 `1 000 ms` 内没有结束(灾难回溯) | `event` · `groupIndex`(该事件下第几组,从 0 起)· `budgetMs` | 改成工具名列表(`A\|B`),或去掉嵌套重复的正则写法(如 `(.*)*`) |
3127
3314
  | `budget_exceeded`(7.132.0 起) | 这次提交里所有已完成的正则形求值的耗时之和超过受理总额度 `5 000 ms`(每只都在单次额度之内,但合起来太慢) | `event` · `groupIndex`(越线的那一组)· `totalMs` | 把慢的 matcher 改成工具名列表(`A\|B`)或简化正则;正则形 matcher 越少越好 |
3128
3315
  | `invalid_regex` | 正则形 matcher 编不过(如 `(`、`[Read`);**写在任一契约事件上都判**(7.132.0 起;此前只判三个工具事件) | `event`(任一契约事件)· `groupIndex` | 修正则,或改成工具名列表(`A\|B`);matcher 没有用处的事件(如 `Stop`)直接去掉这个字段 |
3129
3316
 
3130
- `detail` 恒带坐标 `path`(7.132.0 起;matcher 四形 = `["hooks", <event>, <组下标>, "matcher"]`;串段与 `key` 过拒句回显口径:剥控制与双向字符、截到 64 字 + `…`)。**配置本身读不出**的拒(7.132.0 起同带 `field: "settings.hooks"` + `detail`,句子骨架不变):
3317
+ `field` 是判决所在处的坐标(方括号形,与 `unknownKeys` 同一种写法;串段与 `key` 过拒句回显口径:剥控制与双向字符、截到 64 字 + `…`;7.132.0–7.147.0 是 `detail.path` 段形数组)。**配置本身读不出**的拒(7.132.0 起同带机读位,7.148.0 起在体根上,句子骨架不变):
3131
3318
 
3132
- | `detail.reason` | 触发 | `detail` 其余键 |
3319
+ | `reason` | 触发 | 体根上其余键 |
3133
3320
  |---|---|---|
3134
- | 契约分类器的码(`guard-entry-invalid` 等十一词,连字符拼法) | 分类器整份拒(一块可能藏着守卫钩子)或要剔掉一块(提交面不剔、整份拒) | `path`(那一块)· `key`(那一块上让它坏的键,点得出时) |
3135
- | `unsupported_entry_type` | 本服务不执行的条目类型(`mcp_tool`) | `path`(那一条的 `type`)· `type` |
3136
- | `over_cap` / `too_long` | 服务侧上限:组数 16、一个事件的条目总数 32(`path` 指第一条超出的条目)、命令 / URL 8192 字、header 32 个 / 合计 8192 字、`allowedEnvVars` 32 个 | `path` · `count` · `cap` |
3137
- | `unnamed_refusal` | 分类器说拒却一块都没点名(契约漂移,fail-closed) | `path` |
3321
+ | 契约分类器的码(`guard-entry-invalid` 等十一词,连字符拼法) | 分类器整份拒(一块可能藏着守卫钩子)或要剔掉一块(提交面不剔、整份拒) | `field`(那一块)· `key`(那一块上让它坏的键,点得出时) |
3322
+ | `unsupported_entry_type` | 本服务不执行的条目类型(`mcp_tool`) | `field`(那一条的 `type`)· `type` |
3323
+ | `over_cap` / `too_long` | 服务侧上限:组数 16、一个事件的条目总数 32(`field` 指第一条超出的条目)、命令 / URL 8192 字、header 32 个 / 合计 8192 字、`allowedEnvVars` 32 个 | `field` · `count` · `cap` |
3324
+ | `unnamed_refusal` | 分类器说拒却一块都没点名(契约漂移,fail-closed) | `field` |
3138
3325
 
3139
3326
  - **预算有两层**:① 每只 matcher、每次求值各一份(`1 000 ms`)—— 不共享、不随同一份设置里 matcher 的多少缩水,这一份不读钟;② 一次受理的总额度(`5 000 ms`,7.132.0 起)—— 所有已完成的正则形求值的耗时之和,
3140
3327
  超过它就拒(`budget_exceeded`)。字面形(`A|B`)与非工具事件不进求值、不占总额度。良性 matcher 的求值是微秒到毫秒级,两层都给了数个数量级的余量(一份最大的良性配置 —— 3 个工具事件 × 16 组 —— 总耗时在毫秒级),
@@ -3146,7 +3333,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3146
3333
  **超预算 ⇒ 这一组按「选中」处理**(守卫钩子不被跳过,组里的条目照常跑;`if` 超预算同:条目照跑;这样跑起来的条目只有否决生效 —— 放行与 `updatedInput` 不作数,托管组的条目另记没跑成、`PreToolUse` 折问人)并**响亮登记**:一行 warn `hook_matcher_over_budget`(`event` · `scope` · `expression` · `value` · `budgetMs` · `selected: true`)
3147
3334
  + fleet 流上一帧 `hook_notice` / `hook_non_blocking_failure`,`reason: "matcher_over_budget"`(契约附录 E.2)。每次发生各登记一次,一直在坏就一直说;最坏占用是「(超预算的组数 + 超预算的 `if` 条目数)× `1 000 ms`」每次工具调用 —— 看到这行日志就把那只 matcher(或 `if` 条件)改成工具名列表(`A|B`)或简化正则。
3148
3335
  日志与帧里的 matcher / 条件 / 名字先脱敏、再剥控制与双向字符、截 200 字。
3149
- - 拒句点名是**哪一只** matcher(`settings.hooks.<event>[<groupIndex>].matcher`):`over_budget` 一形带预算、`budget_exceeded` 一形带总额度、都不回显 matcher 原文;`invalid_regex` 一形回显 matcher 原文与编译错误句(各截 200 字符,`detail` 不带这两段)。
3336
+ - 拒句点名是**哪一只** matcher(`settings.hooks.<event>[<groupIndex>].matcher`):`over_budget` 一形带预算、`budget_exceeded` 一形带总额度、都不回显 matcher 原文;`invalid_regex` 一形回显 matcher 原文与编译错误句(各截 200 字符,机读位不带这两段)。
3150
3337
  - **这一判只在 hooks 会被采纳的车道上执行正则**(单用户车道;多租户车道 `settings.hooks` 整只被忽略,只做不执行正则的字面判):多租户部署不会因此多出一个在提交线程上跑调用方正则的面。
3151
3338
  - 不变的:matcher 超 `8 192` 字符不读(照旧受理,运维侧记一笔 `server.hooks.matcher-unjudged`)。
3152
3339
  - **编不过的正则与事件无关**(7.132.0 起):写在 33 个契约事件里的哪一个上都 `400`(上表第四行)—— `StopFailure` 的 `error` 词、`PreCompact` / `PostCompact` 的 `trigger` 词、`SessionStart` 等事件,以及 matcher 被忽略的 `Stop` / `UserPromptSubmit` / `PostToolBatch`。
@@ -3175,29 +3362,45 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3175
3362
  - 被采纳的 `settings.hooks`(单用户车道)在引擎里是**每条车道一张**扩展注册:非托管车道 `settings.hooks`;有托管组(`source` 为 `managedPolicy` / `managedPlugin:*`,见 9.5c-quater)时
3176
3363
  另一张托管车道 `settings.hooks.managed`,排在前面(7.146.0 起;此前恒一张)。`wiring_manifest.mods[]` 里看得到它们;审批卡 `allowRuleShadowed` 的 `hook` 成员、问因项、钩子失败时模型读到的拒句都点名作答 / 失败的那一张。本部署自己另挂两张只观察的注册:恒挂的拒绝计量
3177
3364
  `server.permission-denied-meter`,`TOOL_TRACE=true` 时的 `server.tool-trace`(品牌部署另有一张提示装配站上的 `server.brand-identity`,只答品牌行 overlay,不在工具门上)。观察者的判词不采纳;本服务自铸的注册在工具门(`classic.*` 站)上没有链成员(品牌行那一张在提示装配站 `prompt.compose` 上是链成员,不碰工具门;运维声明的 mod 包可以有,见 9.5c-quater 末条)。
3178
- - 每个事件的语义与此前相同:`Stop` 条目在主任务结束与委派子代结束时都跑;`PermissionDenied` / `SubagentStart` / `SubagentStop` / 两个 model-switch 事件属壳,服务端不跑。
3365
+ - `Stop` 条目只在主任务结束时跑(载荷 `hook_event_name: "Stop"`);委派子代结束不跑任何 `Stop` 条目 —— 子代结束是 `SubagentStop`,与 `PermissionDenied` / `SubagentStart` / 两个 model-switch 事件同属壳,服务端不跑
3366
+ (7.147.x 及以前:`Stop` 条目在委派子代结束时也跑、载荷照写 `Stop`)。
3367
+ - `once` 条目在一次请求里成功一次之后就不再跑:跑完、给出了裁决、而且这次事件的钩子判决不是拒 / 拦,才算用掉(`async` / `asyncRewake` 交出即用掉);
3368
+ 失败、这次事件被拒 / 拦(`PreToolUse` 的退出 2 / `block` / `deny` / `continue:false`;`UserPromptSubmit` / `PreCompact` 拦下;`Stop` 推回 —— 同一次事件里跑过的 `once` 条目都不算)、
3369
+ 判在截断标记上的判词(放行不作数)、没执行的那一次不算,下一次事件照跑;`Post*` 的退出 2 / `block` 是反馈,照常算用掉。成功之前它没被用掉,所以同一请求里并发的两次事件可能各跑一次、各成功一次;
3370
+ 托管相改写输入而整相重判时,同一次事件里它在两遍里各跑一次(只读重判那一遍的结局)。7.147.x 及以前:起跑之前就算用掉,失败或拦下过一次的 `once` 守卫在这次请求里不再跑。
3179
3371
  - **`HOOKS_TIMEOUT_MS`** = 每张车道注册的 `budgetMs`(两张各一份同值;一个事件最坏 2 倍)。引擎对它的定义是 "Each handler's own time" —— 每个 handler 自己的时间(一个事件跑一遍这条车道的钩子条目;
3180
3372
  引擎的钟,handler 等 `next` / `$` 调用的那段暂停),**不是**钩子子进程的墙钟。**未设 = 600000 ms**(= 单条命令超时的上限;引擎的钟在 handler 起跑时就开始走,
3181
3373
  命令的 `timeout` 钟要等 shell 解析和起进程之后,所以 `timeout` 配到 600 s 的命令跑满时,预算会先到);`0` 按字面(零预算)。超预算:`PreToolUse` 上该调用被拒(fail-closed,拒句点名 `extension "settings.hooks"`)、
3182
3374
  `UserPromptSubmit` 上该提交被拒,其余事件跳过这一轮这条车道的钩子。托管车道是外层(先等非托管答完,等的那段引擎停它的表):非托管条目吃光的只是非托管那张的预算,
3183
3375
  其余事件上托管钩子照跑;托管车道自己的预算用尽 ⇒ 撞上的托管条目发 `hook_notice` / `hook_managed_unrun{reason:"budget_exhausted"}`(`detail` 点名引擎预算;每个事件恰一帧,任务取消不记)。
3184
- `Stop` / `SubagentStop` 上每条车道的 `continue:false` 只撤本车道的推回,别的车道与部署座链成员的推回照收(两车道意见相左 ⇒ 这一轮不收场);
3376
+ `Stop` 上每条车道的 `continue:false` 只撤本车道的推回,别的车道与部署座链成员的推回照收(两车道意见相左 ⇒ 这一轮不收场);
3185
3377
  观察站(`PostCompact` / `StopFailure`)上托管车道先跑、非托管后跑。
3186
3378
  子进程自己的墙钟另有两只,照旧:单条命令的 `timeout`(上限 600 s)与每条车道一个事件 120 s 的总墙钟(到点不再起下一条)。
3187
- - **预算到、或任务被取消 ⇒ 这个 handler 里在跑的命令钩子被叫停**(7.133.0 起;此前它要跑到自己的 `timeout` 才停,取消之后还可能接着写文件):进程组先收 SIGTERM,
3188
- 1 秒到点组长还在就补一发组 SIGKILL(组长在宽限内退了就不再发 —— 退出的进程号可能已被回收;代价:组长一退,组里屏蔽 SIGTERM 且不攥管道的后代不再被追杀),
3189
- 进程组长退出才算收走;这个 handler 里还没起的后续条目不再起。结局不变(上一条的 fail-closed 拒 / 跳过),运维日志多一行
3190
- `hook_command_killed`(`event` / `type` / `killed: true` / `signal`:`SIGTERM` = 组长在宽限内就退了,`SIGKILL` = 宽限到点组长还在、升级过)。钩子确实要跑得更久:调大 `HOOKS_TIMEOUT_MS`
3379
+ - **预算到、或任务被取消 ⇒ 这个 handler 里在跑的命令钩子被叫停**(7.133.0 起;此前它要跑到自己的 `timeout` 才停,取消之后还可能接着写文件):进程树先收 SIGTERM,
3380
+ 1 秒到点树里还有进程(组长没退,或组长退了而组里还有成员)就补 SIGKILL,进程组长退出才算收走;树里一个进程都没有就不再发信号(退出的进程号可能已被回收);
3381
+ 这个 handler 里还没起的后续条目不再起。结局不变(上一条的 fail-closed 拒 / 跳过),运维日志多一行
3382
+ `hook_command_killed`(`event` / `type` / `killed: true` / `signal`:`SIGTERM` = 组长在宽限内就退了,`SIGKILL` = 宽限到点树里还有进程、升级过)。钩子确实要跑得更久:调大 `HOOKS_TIMEOUT_MS`
3191
3383
  (与条目自己的 `timeout`);要在被叫停时收尾,在命令里 `trap … TERM` 快速落盘(宽限 1 秒,不可配)。
3384
+ - **命令钩子跑完 / 超时 / 被叫停之后,它的整棵进程树被清**(7.148.0 起;三条结局一个,同步条目与 `async` / `asyncRewake` 条目同一把梯子 —— 叫停路只有同步条目,`async` 条目照旧 fire-and-forget、不随叫停信号收):树 = 钩子进程所在的进程组 ∪ 它往下的后代 ——
3385
+ 脱离了进程组的后代(自立会话,或钩子壳自己起的新会话)只要它的父进程在被收走那一拍还活着,也被收。钩子自己跑完(组长退出、管道关闭)之后,树里还留着后台成员 ⇒ 整组 SIGKILL(没有后台成员的钩子零信号;管道里的输出照旧等齐、不被截)。
3386
+ 超时直接发 SIGKILL(不发 SIGTERM),立即返回(不等孙进程放开管道,结局仍是非阻断故障);1 秒后复核:树里已空 ⇒ info 一行 `hook_command_tree_swept`(`event` / `type: "command"` / `end: "exit"` / `"timeout"` / `"halt"` / `shots: 1` / `clean: true`);
3387
+ 还有进程 ⇒ 补一枪并同一事件 warn(`shots: 2` / `clean: false`)。超时与叫停的升级先后到点也只有一轮 SIGKILL、一行清扫日志。此前超时只发一枪组 SIGKILL 就放手、跑完之后不清留在同组的后台进程。
3388
+ 射程如实:钩子壳在被清扫**之前**就已退出、而后代又在新会话里的形(后代被过继给 init、自成一组),与叫停时 SIGTERM 之后组长退了、屏蔽 SIGTERM 的自立会话后代,没有任何按进程号 / 进程组 / 父链的收法够得着,仍活着,清扫行读的是「可见的树」。
3389
+ 需要长命后台进程的活不属于钩子(含钩子正常退出后留下的后台进程)—— 放到钩子之外起。
3192
3390
 
3193
3391
  ### 9.5c-ter `settings.hooks` 的键:写了就要被读,否则整份新提交 400(7.135.0 起)
3194
3392
 
3195
3393
  - 一只匹配组 / 一个条目上出现 hooks 读者不读的键 ⇒ 新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)`400 request.body_shape`,体里 `unknownKeys` 逐个点名全坐标
3196
- (如 `settings.hooks.PreToolUse.0.hooks.0.onFailure`),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3197
- command 条目上是契约字段加 `plugin` / `args`,其余条目类型上是各自的契约字段(`plugin` / `args` 写在 http / prompt / agent 条目上同样被拒)。
3198
- - 常见撞上的形:参照实现 2.1.296 新增的 `onFailure`(本服务不执行这个键 —— 7.134.x 及更早它被悄悄剥掉,写了 `"block"` 的守卫起不来时照放行)、拼错的 `timout` / `matchr`。
3199
- 出路:删掉那个键,或改成读者读的拼法;拒体里的坐标就是要动的那一处。
3200
- - 停驻任务续跑时不判这一条(存量请求里的这些键照旧被剥掉、其余钩子照采纳)。
3394
+ (方括号形,如 `settings.hooks.Stop[0].hooks[0].onFailure`,与该面拒体 `field` 同一种写法;7.148.0 起,此前是点分数字段),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3395
+ command 条目上是契约字段(含 `args` / `onFailure`)加 `plugin`,http 条目上是契约字段(含 `onFailure`),其余条目类型上是各自的契约字段
3396
+ (`plugin` / `args` 写在 http / prompt / agent 条目上、`onFailure` 写在 prompt / agent 条目上同样被拒)。每一型的契约字段读能力位 `settingsContract.hookEntryFields`。
3397
+ - 常见撞上的形:写错条目类型的键(`plugin` / `args` 写在 http / prompt / agent 上、`onFailure` 写在 prompt / agent 上、`continueOnBlock` 写在 prompt 之外、
3398
+ `rewakeMessage` / `rewakeSummary` 写在 command 之外、`cloud` 写在 command / http 之外)、拼错的 `timout` / `matchr`。
3399
+ 出路:删掉那个键,或改成读者读的拼法;拒体里的坐标就是要动的那一处。(7.135.0–7.146.x 上 command / http 条目的 `onFailure` 也在这里被拒;7.147.0 起它是契约字段、本服务执行它,见 9.5c-quinquies。)
3400
+ - 参照实现的四个键在各自的归属型上受理(7.147.0 起;此前在这里被拒):command 的 `rewakeMessage` / `rewakeSummary` / `cloud`、http 的 `cloud`、prompt 的 `continueOnBlock` ——
3401
+ **本服务如实带、不执行**:唤醒文本不加前缀、没有摘要行、`cloud` 写什么都照跑、`continueOnBlock` 不改 `Stop` 拦下的结局。`rewakeMessage` / `rewakeSummary` 要非空串、
3402
+ `continueOnBlock` 要布尔,值坏 ⇒ 与其它契约字段写坏同一种拒(守卫事件整份拒);`cloud` 任意值。
3403
+ - 停驻任务续跑时不判这一条(存量请求里读者不携带的键照旧被剥掉、其余钩子照采纳)。
3201
3404
 
3202
3405
  ### 9.5c-quater `settings.hooks` 的托管标签:调用方自报,服务端只校闭集、不验身份,失败方向只收紧(7.133.0 起)
3203
3406
 
@@ -3205,20 +3408,33 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3205
3408
  由请求体里的调用方写,服务端只判它在不在闭集里(集外词 / 形错 ⇒ `400 request.field_invalid`,`field: "settings.hooks"`),**不验身份**,也不按调用方的档位剥掉、改写或拒绝托管词。
3206
3409
  本服务没有用户 / 项目 / 本地 / 托管层的设置文件:请求体 `settings` 是启动方(壳 / 嵌入方)端合并之后交来的那一份,所以「托管」今天的唯一来源就是壳 / 嵌入方的自报;
3207
3410
  `settings.hooks` 只在单用户车道被采纳(多租户车道整只忽略),能自报的就是持有这台部署凭据的调用方(壳 / 嵌入方)。不要把它读成「组织策略来源」的证明。
3208
- 标签只改两件事,失败方向**只收紧**:① 位置 —— 非托管相 → 托管插件相 → 托管策略相(终判相),托管条目在非托管改写之后的最终输入上判,托管相自己改写了输入则整相按新输入重判一遍;
3411
+ 标签只改两件事,失败方向**只收紧**(比的是下方 —— 同座非托管车道、运维 handler 与常规门:托管答案不比下方宽;与不带标签的同一条钩子逐员比则不全同,见本节末条):① 位置 —— 非托管相 → 托管插件相 → 托管策略相(终判相),托管条目在非托管改写之后的最终输入上判,托管相自己改写了输入则整相按新输入重判一遍;
3209
3412
  所以托管条目的 `allow` 绑定的是最终要执行的那份输入(一条非托管 `allow` 若排在另一条非托管改写之前,会因输入被改而作废;托管的不会 —— 它是改写之后才判的,效果等同于调用方把同一条钩子不带标签写在数组末尾);
3210
3413
  ② 失败方向 —— 托管条目没跑成、或判词不可信,`PreToolUse` 折 `ask`(非托管相里没跑成的条目照旧只是无意见;判词是闭集外的词则两相都折 `ask`);引擎 handler 预算、事件墙钟、prompt 条数、上下文份额各车道独立计量(每条车道一张注册)。
3211
3414
  **`allow` 不看相**:放行只看「判词是 `allow` ∧ 判词可信 ∧ 它判过的就是最终输入」,出口对象不带车道 —— 同一条钩子、同一个判词、同一份输入,自报 `managedPolicy` 拿不到任何额外放行(任务座之下有部署座链成员的站上同样如此 —— 见本节末条)。
3212
3415
  所以调用方给自己的钩子写 `source: "managedPolicy"` 不是抬权口,而是「请按失效关闭跑这一组、并让它最后判」的开关;不要这层严格,去掉 `source` 即可(缺席 = 非托管)。
3213
3416
  过渡(7.146.0 起,至引擎为钩子提供「请求收场」席位止):同一请求同时带托管与非托管的 Stop 钩组、且两车道意见相左(一边 `continue:false`、一边 `block`)时,`continue:false` 只撤本车道的推回,另一车道的 `block` 作数 —— 这是托管标签在 Stop 上会改变的第三件事。目标形 = 任一 `continue:false` 即请求钩子整体无意见(7.145.0 形,与 CC 一致),席位到货当版恢复并删本句。
3214
- - **任务座之下有部署座链成员的站上,托管条目只许收紧**(7.146.0 起):运维声明的 mod 包(`MODS_ROOT` / `MODS_PACKAGES`)在 `classic.PreToolUse` 上以 `append` 档(排在请求钩子所在的任务座之后)挂了链成员
3215
- (观察者不算;读引擎的准入记录)⇒ 托管车道照旧最后判,但那一站上只许收紧:否决 / 问(含托管条目没跑成折的问)/ 上下文照收;托管的 `allow` ⇒ 这次调用拒(与不带标签的 `allow` 被引擎座信任律拒同一个结局,不落到常规门);
3216
- 托管的 `updatedInput`(带 `ask` 的也算)给不进运维 handler(它只判过改写之前的那一份)⇒ 这次调用拒(失效关闭);运维 handler 要真人批的问,托管作答之后照旧要真人批。
3217
- 否则自报的托管组会等运维 handler 判完原输入、再在它之上放行或改写 —— 改写后的输入运维 handler 从没见过。在装了运维 mod 的部署上,覆盖站上的标签换不来任何额外放行(与不带标签同;与 7.145.x 同)。
3218
- 挂法不随站点变:同座的非托管车道与运维 handler 恒先判、否决恒跑 —— 运维 mod 只管某些工具(带匹配器)时,别的工具的调用上同样如此,那些调用上托管条目也按这一条收紧(准入记录不带匹配器;只多收紧,不跳过否决)。
3219
- 不带托管标签的组照旧:运维 handler 在这一次调用的链上时,它们的非否决答复被引擎的座信任律拒。
3417
+ - **任务座之下有部署座链成员的站上,请求钩子只许收紧**(托管车道 7.146.0 起;7.148.0 起两条车道同一条,折叠不放宽这一面):运维声明的 mod 包(`MODS_ROOT` / `MODS_PACKAGES`)在 `classic.PreToolUse` 上以 `append` 档(排在请求钩子所在的任务座之后)挂了链成员
3418
+ (观察者不算;读引擎的准入记录)⇒ 请求钩子(托管与不带标签的组,不看标签)照旧最后判,但那一站上只许收紧 —— 律是**请求钩子的答案不比下方(运维 handler 与常规门)宽**:否决 / 问(含托管条目没跑成折的问)/ 上下文照收(运维 handler 已判过同一份输入:问要人批,只给上下文之后调用落到常规门、与不挂这条钩子同一个结局);`allow` ⇒ 这次调用拒(不落到常规门);
3419
+ `updatedInput`(带 `ask` 的也算)给不进运维 handler(它只判过改写之前的那一份)⇒ 这次调用拒(失效关闭);运维 handler 要真人批的问,请求钩子作答之后照旧要真人批。
3420
+ 否则请求钩子会等运维 handler 判完原输入、再在它之上放行或改写 —— 改写后的输入运维 handler 从没见过。7.147.x 及以前不带标签的组由引擎的座位信任律拒同一类放行(结局相同);那时它的问与只给上下文两形同样被拒(内层挂、跳过下方没判过的运维 handler),7.148.0 起与带托管标签的同读。折叠只合并判决的读法,放行面不变。托管标签是自报的,只改位置与失败方向,不是这条规则的开关。
3421
+ 卡面:请求钩子在下方的问(运维 handler 或同座另一条车道;不论下方的问带哪个成因词)之上也问 ⇒ 问句先列下方那一句、再列钩子那一句(托管车道 `… (a managed PreToolUse hook also asks: …)`,不带标签的组 `… (a PreToolUse hook also asks: …)`);卡上的注册名与问因项(`askReasons`)由引擎按作答的那一张注册铸(已知限制)。
3422
+ 挂法不随站点变:同座的非托管车道与运维 handler 恒先判、否决恒跑 —— 运维 mod 只管某些工具(带匹配器)时,别的工具的调用上同样如此,那些调用上请求钩子也按这一条收紧(准入记录不带匹配器;只多收紧,不跳过否决)。
3220
3423
  只挂观察者的部署(本服务自铸的拒绝计量 / 工具追踪)、链成员只在任务座之上(`prepend` 档,或同档 `user` —— 同档里部署座在前)的部署不收紧:那里运维 handler 已先判、并在答回来的结果上再判。
3221
3424
 
3425
+ ### 9.5c-quinquies `onFailure`:钩子自己坏了时,放行还是拦下(7.147.0 起)
3426
+
3427
+ - 写在 command / http 条目上,两个词:`"continue"`(缺席同义)= 钩子坏了只报一笔故障、动作照常;`"block"` = 钩子坏了**按退出码 2 算** —— 它守的那个动作被拦下。
3428
+ 拦下的形就是这个事件上 exit 2 的形:`PreToolUse` 工具调用被拒、`UserPromptSubmit` 这次提交被拒、`PreCompact` 这次压缩不做;`PostToolUse` / `PostToolUseFailure` / `PostToolBatch`
3429
+ 上工具已经跑过,拦不了,那句话作为反馈交给模型。拦下的理由是一句 `<事件> hook did not reach a verdict (<成因>); blocking because onFailure is "block"`,成因词与故障帧的 `reason` 相同。
3430
+ - **「坏了」怎么算**(本服务比参照实现宽,失效关闭):起不来、超时、退出码非 0 非 2(http:非 2xx)、输出读不成判词、`PreToolUse` 的 `permissionDecision` 不是 allow / deny / ask 之一;
3431
+ 以及本服务自己的门让它没跑成 —— URL 名单拦下、shell 形写了 powershell、`if` 条件评不出、组的 matcher 编不过、这个事件的墙钟用完(墙钟用完之后同一车道里排在后面、被选中的 `block` 条目同样拦下,不看它排第几)。
3432
+ 不算坏:`once` 已经消耗(这次请求里它成功过一次)、`if` 不匹配、matcher 没选中(本就不该跑)。
3433
+ - **不生效**:`async` / `asyncRewake` 的条目(后台跑,本就不拦);`Stop` 事件(拦停止会让该停的停不下);观察站 `StopFailure` / `PostCompact`(那里没有可拦的动作)。
3434
+ - 运维要看懂一次拦下:故障帧 / 运维日志照旧点名是哪一条、什么成因(`hook_non_blocking_failure` 帧的名字是历史名,`block` 条目的那一次随后就被拦下);改条目、改 URL 名单,或把这一条改回 `continue`。
3435
+ - 停驻任务:升级前存下的请求里写了 `onFailure: "block"` 的守卫,续跑时开始按 block 执行(7.146.x 及更早这个键被读者剥掉);存量里写的是两词之外的值 ⇒ 这条车道采纳请求体 hooks(单用户车道)时**拒续跑**
3436
+ `409 resume_blocked_by_policy`(`blockedBy: "request.field_invalid"`,`field: "settings.hooks"`;卡 / 任务仍待决;7.146.x 及以前是整份 hooks 不采纳照跑)—— 见 MIGRATION。
3437
+
3222
3438
  ### 9.5d MCP 服务器 http 传输的身份头:`principalHeader` 与 `assertionHeader` 二选一(请求体与部署声明同一种读法)
3223
3439
 
3224
3440
  `mcpServers[].transport`(`kind:"http"`)上有两个「用哪个头送身份」的可选键,**只能设一个**:`principalHeader`(runner 把该任务已认证的终端用户 principal 注入这个头)与 `assertionHeader`(托管连接器断言所用的头**名**)。
@@ -3232,7 +3448,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3232
3448
  条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)· `sessionMetaKey`(7.138.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))。**新提交上每个键只有一只判官,判官在受理口,写坏的答案只有一种**(下面第一条:400)。`startupTimeoutMs`(这一只 server 自己的启动界,毫秒整数,范围与引擎 env 旋钮 `MCP_TIMEOUT` 同一段)7.134.0 起同样在受理口判 —— 判官是引擎包根导出的 `mcpStartupTimeoutMs`(启动界的唯一读者):数字串 / 小数 / 越界 / `null` ⇒ `400 request.field_invalid`,原因句是引擎的那一句(7.132.0–7.133.x:受理口不判、请求照常 200,引擎拨号时只拒这一只 server);耐久 resume 重放存量请求不判(原样交引擎,坏值在拨号时只拒这一只 server —— `config.mcp_startup_timeout_invalid`,兄弟照挂)。`sessionMetaKey`(声明了它的服务器在每次 `tools/call` 的 `_meta` 下收这条腿的会话 id)7.138.0 起同样在受理口判 —— 判官是引擎包根导出的 `judgeSessionMetaKey`(键名格式、保留前缀、上界都是它的):坏形 ⇒ `400 request.field_invalid`,原因句是引擎的那一句;耐久 resume 重放存量请求时坏形让这一条 server 整条不进。
3233
3449
  - **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`):键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `mcpServers[2].allowTools (server "crm") is not a usable declaration — must be an array of strings (omit the key to leave the server's tools unrestricted) — got a string of 4 characters. …`:点名 `mcpServers[i].<键路径>`、server 名与该键判官的原因句,**整份请求不收** —— 不挂一半、不丢声明、不默认。原因句不回显声明的值,有几处例外,回显只有一只口径(剥控制与双向字符、截到 64 字、超出加 `…`):server 名(含写坏的 `name` 值,另报送来的原长度)、`contentOrigin` 词表外的值;`toolAxes` / `toolFaces` 的原因句是引擎写的(7.134.0 起 `toolFaces` 的判官也是引擎包根导出的 `readToolFaces`):点名成员与闭词表本身,调用方写的串(工具名这类键、闭词表拒的那个词)一律只给记号与码位数(`[redacted] (N characters)`),整句详情由引擎截到 240 字、本服务再剥控制与双向字符 —— 不回显显示文本、不回显合规的列表;`transport.url` 的值从不回显(带凭据的 url 被拒时,原因句只点名命中的那一段与凭据名表里的那个词)。可选键**缺席**永远合法(真「没声明」),必填的 `name` / `transport` 缺席同样 400;在场的 `null` 不是缺席。与单用户 / 多租户无关。列表本身同一条律(7.132.0 起,先于条目判):`mcpServers` 在场却不是数组、超过 32 条(整份拒,不挂前 32 条)、某一条不是对象 ⇒ `400 request.field_invalid`,拒句点名 `mcpServers` 或 `mcpServers[i]` 与形的种类;条目层或 `transport` 层出现词表外的键 ⇒ `400 request.body_shape`,拒体带 `unknownKeys`(全路径、至多 20 条)。`mcpServers` 缺席或为 `null` = 没声明,照常受理。
3234
3450
  - **`transport.url` 不得带凭据(7.135.0 起)**:http 传输的 `url` 带 userinfo(`https://user:pass@host/mcp`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,按解一次百分号编码读)⇒ 新提交 `400 request.field_invalid`,体另带 `field: "mcpServers[i].transport.url"` 与 `reason`(`url_userinfo` | `url_credential_param`),原因句点名出路:**凭据改写进 `transport.headers`**(如 `"Authorization": "Bearer …"`)。为什么:任务停驻时提交体整份进续跑上下文,`transport.headers` 的值在那里只以封签存在,`url` 照原样存(日志与读面同理)。值从不回显;解析不了的 url 不在这一问里判(拨号时失败)。7.134.x 及以前照收、原样落停驻上下文;带 userinfo 的 url 本就连不上(fetch 对带凭据的 URL 构造即抛),带名表参数的升级后要改。同一条规则在 A2A 对端 / http 钩子 / 搜索后端 / 图片 URL 上的几座见 9.5g。
3235
- - **耐久 resume 重放存量请求**:不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` · `sessionMetaKey` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` · `transport` 的 `args`/`principalHeader` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `env`/`headers`(名 → 串记录)对 JSON 存体按 7.131.x 的读法交:有值不是串的成员 ⇒ 整键不交,只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交;server 照挂,一行运维 warn `mcp_transport_members_dropped` 点名(见 9.5c-bis 同段);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
3451
+ - **耐久 resume 重放存量请求**:写坏的条目整条不进或丢键照挂,不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)—— 唯一的拒是 `transport` 里交不出去的成员(放行变宽的那一形,见本条后半)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` · `sessionMetaKey` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `args` / `principalHeader` 写坏,或 `env` / `headers`(名 → 串记录)里有按 7.131.x 读法交不出去的成员(值不是串的成员 ⇒ 整键,`__proto__` 名的成员)⇒ 本部署兑现请求 MCP 时**拒续跑** `409 resume_blocked_by_policy`(点名成员坐标,见 9.5c-bis 同段;7.146.x 及以前剔掉照挂、一行 `mcp_transport_members_dropped`);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
3236
3452
  - `POST /v1/capabilities/mcp/probe` 问的是新提交那**同一只判官**:上面每一形同码同句(含 `unknownKeys`,与 url 带凭据那一形的 `field` / `reason`),拒之前零拨号;判官受理的申报,探测口拨的就是一条任务提交会交给引擎的那一份(判官的合法归一照收)。它多出的两形:本口的体就是这份列表,`mcpServers` 缺席或为 `null` ⇒ `400 request.body_shape`(缺必需字段);声明了 `transport.assertionHeader` 的条目 ⇒ `400 request.field_invalid`(见 9.5d:本版承载、不发送,新提交照挂,探测不拨一份少了断言头的配置 —— 同一只谓词,后果按门分)。7.131.x 上本口另有一只判法(申报原文与读器产物逐字比),列表不是数组 / 条目不是对象答 `request.body_shape`。
3237
3453
  - **`transport.kind` 的词表可读(7.141.0 起)**:`GET /v1/capabilities` 的 `mcpTransportKinds`(`["stdio","http"]`,书写序,每种部署形都在场、不随部署形变)= 请求面读器与引擎今天能拨的传输种类。客户端在投影 `mcpServers` 条目之前据此判目标传输读不读得了(只拒那一台、其余照投),不必靠整次提交挨 400 去试。**位不是放行单**:位外的词(引擎的 `host`、别家的 `sse` / `sdk` / `websocket`)在新提交与探测口上照旧整次 400;本部署收不收自带的 `mcpServers` 读 `mcpInjection`,「端到端可达」没有任何位承诺。键缺席 = 老 server,读作 `["stdio","http"]`;位里出现不认识的词按「不支持该种类」处理。
3238
3454
  - **`toolFaces` 的判官是引擎的 `readToolFaces`(7.134.0 起;此前本服务自带一只只判 JSON 形的读器)**:它在门口就判声明本身能说的一切 —— 成员与类型、五张闭词表、上界、跨成员律 ——
@@ -3276,11 +3492,12 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3276
3492
  | `settings.webSearch.endpoint` | 同段的 `apiKey`(brave / tavily 发成请求头;searxng 腿不发鉴权头,要凭据的实例在部署面声明 `WEB_SEARCH_ENDPOINT`) |
3277
3493
  | `images[i].url`(`{ url }` 臂;7.136.0 起) | 没有封签的头位 —— 图片字节改走内联 `{ data, mimeType }`(7.136.0 起) |
3278
3494
 
3279
- - **判据**:URL 带 userinfo(`https://user:pass@host/…`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,解一次百分号编码读,整名相等)。
3495
+ - **判据**:URL 带 userinfo(`https://user:pass@host/…`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,解一次百分号编码读;整名在表上,或(7.147.0 起)末尾收在强档词上的复合名 —— `db_password` · `PGPASSWORD` · `AWS_SECRET_ACCESS_KEY` 中,只看末尾,`password_hash` / `tokens` 不中)。
3280
3496
  - **答案**:新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)`400 request.field_invalid`,体另带 `field`(上表的体内全坐标)与 `reason`(`url_userinfo` | `url_credential_param`);句子点名坐标、命中的那一段与表里的词,**不回显 URL 的值**,并指出路。MCP 座另有探测口(`POST /v1/capabilities/mcp/probe`,同码同句同机读位)。
3281
3497
  - **A2A 座**并进 `a2aPeers` 的条目判官表(7.136.0 起,与 `mcpServers` 同一副拒绝骨架,见下):`url` / `cardUrl` 先判是 http(s) 串、再判凭据;`a2aPeers` 超过 32 条(挂载上限)整份 `400 request.field_invalid`(`field:"a2aPeers"` · `reason:"over_cap"` · `count` · `cap`),
3282
3498
  判官要么看过每一条对端的 `url` / `cardUrl`,要么这份体不收。7.134.x 及以前超过 32 条是 200、第 33 条起丢弃(运维 warn 点名条数)。**搜索后端座**:userinfo 形此前已是 400(句子逐字不变,本版起体另带 `field` / `reason`),凭据名参数是本版新拒的。
3283
3499
  - **图片座**(7.136.0 起):`images[i]` 的 `{ url }` 臂过同一只判官;内联臂 `{ data, mimeType }` 不落 URL、不归本座。个数帽(20,`413 request.payload_too_large`)与内联字节帽排在它前面。
3500
+ - **`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` 里带多余键或两臂并带的提交。**补偿**:每张图选一臂、删掉多余键。
3284
3501
  **谁受伤**:用带签名的临时地址传图的调用方 —— `?sig=` · `?signature=` · `?token=` · `?key=` · `#access_token=` 这类名字在表里的参数(Azure SAS 的 `sig`、S3 预签名的 `X-Amz-Signature` / `X-Amz-Credential`、OSS / COS 的 `Signature`、带 `?key=` 的图床),7.135.0 及以前照收、取图时把令牌发给图床,本版起 400。
3285
3502
  **补偿**:图片字节读下来改走内联 `{ data, mimeType }`(内联图有自己的字节帽,不落 URL);或换一个不靠这些参数名鉴权的地址(路径上的签名、公开地址)。
3286
3503
  - **不判**:续跑重放存量请求(拒会把升级前停驻的任务永久卡死;已落盘的字节不因拒而消失 —— 介意的部署把这些任务结掉后轮换那几枚凭据);解析不了的 url(拨号 / 取图期失败);部署面(中心 / `config.d` / 部署 env)声明的地址 —— 那是运维的配置,不进任何请求的停驻上下文。
@@ -3291,7 +3508,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3291
3508
  与 9.5e(`mcpServers`)**同一副拒绝骨架、同一个拒形**。条目的受理键 = `name` · `url` · `cardUrl` · `headers` · `principalHeader` · `allowSkills` · `toolAxes`(与引擎的 A2A 对端声明逐键对应)。新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)上:
3292
3509
  - `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` 是「没声明」,空数组受理。
3293
3510
  - 条目上词表外的键 ⇒ `400 request.body_shape`,体带 `unknownKeys`(全路径形,如 `a2aPeers[0].allowSkill`)。
3294
- - 键在场却判不出形 ⇒ `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`)。
3511
+ - 键在场却判不出形 ⇒ `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 及以前只有句子。
3295
3512
  - 判序:先列表、后条目;条目里先词表外的键、后键值(`name` → `url` → `cardUrl` → `headers` → `principalHeader` → `allowSkills` → `toolAxes`),第一处坏形即答。纯值域、与租户车道无关(多租户部署整只 `a2aPeers` 本会被忽略,告诉调用方「这样写不行」只多不少)。
3296
3513
  - **耐久 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`。
3297
3514
  - 7.135.0 及以前:这些形全部 200 —— 坏形的对端悄悄不挂(只在运维 warn 里点名)、词表外的键悄悄不读(`allowSkills` 拼成 `allowSkill` 的对端照样挂上远端卡声明的全部技能)。升级后这些请求变 400:改对那个键(或删掉它)、把列表改成数组、删掉第 33 条起的条目。
@@ -3326,6 +3543,92 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3326
3543
  仍会被座拒(`credential_unavailable` + 一行 `peer_seat_refused`,`reason: handle_not_held`)的窄形:被取消的父腿里一次工具调用起的会话级子代,在那条腿已放之后才取得持有(取消的孤儿窗);
3327
3544
  一条永不落定的准备腿永不放(条目随之常驻,`peer_registry_holders` 高于基线不回落即此形)。
3328
3545
 
3546
+ ### 9.5i-bis 部署声明的 A2A 对端:`config.d/a2a.json`(7.147.0 起)
3547
+
3548
+ 本地配置源(`CONFIG_PROVIDER=local`、`run-local`)从 `config.d/a2a.json` 读这台部署常驻的出站 A2A 对端,键与中心发布的 `a2a` 域同形(契约包 settings-schema 18.0.0 起 `a2a` 是便携域):两条腿过同一只读口、读成同一个声明,同一只解析器拨号。7.146.x 及以前本地腿不读这份文件,只有中心 `/effective.a2a` 能声明部署级对端。
3549
+
3550
+ ```json
3551
+ {
3552
+ "peers": [
3553
+ {
3554
+ "name": "planner",
3555
+ "url": "https://planner.example.com/a2a",
3556
+ "cardUrl": "https://planner.example.com/.well-known/agent-card.json",
3557
+ "headerRefs": { "Authorization": "PLANNER_A2A_TOKEN" },
3558
+ "allowSkills": ["plan", "review"]
3559
+ }
3560
+ ]
3561
+ }
3562
+ ```
3563
+
3564
+ - `name`(必填):对端的身份,线上工具名 `a2a__<name>__<skill>` 的命名空间段;非空、≤ 128 字符、首尾无空白,不截断、不 trim。两条对端重名 ⇒ 读口把整个 `a2a` 域判坏并点名(与其它域整域读坏同一处置)。
3565
+ - `url`(必填):对端服务地址,卡的 well-known 路径从它解析。带 userinfo(`https://u:p@peer/`)、带 fragment、或 query 值像密钥 ⇒ 这一条不进(读口点名,兄弟照拨);凭据走 `headerRefs`。
3566
+ - `cardUrl`(可缺席):对端卡不在 well-known 路径时写;写了就只用它。
3567
+ - `headerRefs`(可缺席):「头名 → 本服务环境变量名」。文件里只写变量名,值在起服时从本服务的环境读(与 `mcp.json` 的 `headerRefs` 同一种读法)。任一引用的变量缺席 ⇒ 这一条整条不拨、一行 warn `web_admin_a2a_env_missing`,兄弟照拨;记录里任一成员写坏(头名不是合法头名、值不是串)⇒ 这一条整条不进(读口点名),不会只带一半凭据拨号。
3568
+ - `principalHeader`(可缺席):把任务的终端用户主体注入到哪个头名(值由运行器填,模型读不到也改不了)。
3569
+ - `allowSkills`(可缺席):只挂对端卡上这几只技能。缺席与空表同读 = 挂卡上全部技能;不想拨这个对端,写 `enabled: false` 或删掉这一条。
3570
+ - `scenarios`(缺省 `[]` = 所有场景)· `enabled`(缺省 `true`)。
3571
+ - 不收 `toolAxes`:远端技能的风险轴只能由调用方在请求里声明(9.5h),部署面不替调用方担保;写了是词表外的键,读口点名。
3572
+ - **生效**:重启生效(与 `mcp.json` 同一条线)。文件改了 ⇒ `/health` 报 `restartRequired`、`restart.reasons` 含 `a2a`。重启信号只看真会改变拨号的写法:没写文件、`peers` 为空、只剩 `enabled: false` 的对端是同一个状态(这台部署没有部署级对端,不签重启);`cardUrl` / `principalHeader` 写空串、`allowSkills` / `headerRefs` 写空,与不写同读。
3573
+ - **从中心腿迁来**:中心发的 `a2a` 域照旧生效,现在也过同一只读口 —— 上面「这一条不进」的几种形,7.146.x 原样透传给引擎,7.147.0 起在读口点名丢掉;条目没写 `enabled` / `scenarios` 时读口补缺省,对拨号同义。
3574
+ - **已知限制:对端拨号面今天没有主机底线**:启用的对端按 `url` / `cardUrl` 取卡、按卡里写的服务端点发送,**不过任何部署主机底线** —— 本服务今天不给引擎接它(`RunnerDeps.hostFloor` 没有写点)。`url` / `cardUrl` 是运维写的,可指向任意主机(含内网与回环);服务端点是对端的卡写的:**不带头的对端**(这次发送不带任何声明的头:没写 `headerRefs`,也不注入主体头)的服务端点可指向任意主机,遇重定向照跟(引擎设跳数上限,没有主机底线);带头的对端另受引擎的端点绑定 —— 服务端点必须落在声明的 `url` / `cardUrl` 所在的源上,且不跟重定向。取卡发生在对端被用到时:每次任务准备、会话 A2A 面板(`GET /v1/sessions/:id/a2a`)缓存缺失 / 过期(5 秒)时;起服本身不取卡。**多租户部署同样挂**:多租户闸只关请求体自带的 `a2aPeers`(9.5h),部署基线照挂。
3575
+ **补偿**:只写你信任的对端 —— 不带头的对端,它的卡能让引擎去敲它写下的任何地址;起服前自审 `config.d/a2a.json`;多租户 / 公共部署不要接不受信对端。出路 = 部署主机底线接线(尚未到货),到货后本条改写。
3576
+
3577
+ ### 9.5i-ter 单机挂载外置 MCP:`config.d/mcp.json`(`CONFIG_PROVIDER=local`;7.148.0 起成文)
3578
+
3579
+ 部署级的 MCP 服务器只有两条声明通道:本地配置源的 `config.d/mcp.json`(本节),与配置中心发布的 `mcp` 域;**没有 env 通道**(本服务没有声明 MCP 服务器的环境变量;请求体自带的 `mcpServers` 是另一条路,见下面「与请求体 `mcpServers` 的关系」)。两条腿过同一只读口、读成同一个声明、由同一个铸点交给引擎,所以单机部署者在这个文件里写的,与中心 `mcp` 域的条目逐键同义。本节只把此前散在各处(目录信任、`toolAxes`、身份头、凭据件)的读法写在一处,不改任何行为。
3580
+
3581
+ ```json
3582
+ {
3583
+ "servers": [
3584
+ {
3585
+ "name": "crm",
3586
+ "transport": { "kind": "stdio", "command": "crm-mcp", "args": ["--readonly"], "envRefs": { "CRM_TOKEN": "CRM_MCP_TOKEN" } },
3587
+ "allowTools": ["search_accounts"]
3588
+ },
3589
+ {
3590
+ "name": "search",
3591
+ "transport": { "kind": "http", "url": "https://mcp.example.com/mcp", "headerRefs": { "Authorization": "SEARCH_MCP_AUTH" } }
3592
+ }
3593
+ ]
3594
+ }
3595
+ ```
3596
+
3597
+ - **何时读、读哪里**:只在 `CONFIG_PROVIDER=local` 下读(`run-local` 没写词时自己写 `local`);不写词且没配中心 = 纯 env,HTTP 服务不读 `config.d`。配置根只有一条链 `CONFIG_LOCAL_DIR` → `AGENT_DATA_DIR` → `~/.ai-agent`,文件是 `<配置根>/config.d/mcp.json`。
3598
+ 文件**缺席** = 没声明;**存在却为空 / 只含空白、或不是 JSON 对象** ⇒ 这份目录的候选整份被拒(起服落回 env 底、刷新拍保持上一代配置;拒句点名那个文件的路径与出路)—— 写到一半的空文件不会被当成「没声明」。
3599
+ - **属主与模式**:文件与它上面每一级目录过的是上文「本地配置源的目录信任」那一只判官(属主只能是本服务的用户 / root / `CONFIG_LOCAL_OWNER_UID`,不得组 / 他人可写,常规文件 ≤ 16 MiB;判据不在这里复写)。拒 = 起服拒启(`[config.dir_untrusted]`);起服之后才变坏 = 那一拍新字节不读、已应用的配置不动。Windows 上属主与模式位判不了,只判结构并记一次 fail-open 告警(上文同一段)。
3600
+ stdio 的 `command` 以本服务的用户起子进程,所以这份文件的写权限就是「让本服务起命令」的权限:别把配置根放在智能体的工具可写的位置。
3601
+ - **条目形**(`servers[]`,与中心 `mcp` 域同形,键名逐字):
3602
+ `name`(必填)字母或数字开头,其后字母 / 数字 / `.` `_` `-`;它是工具命名空间 `mcp__<名>__<工具>` 的一段,归一后落在同一前缀的两个名字(`my.srv` / `my_srv`)⇒ 任务 `409 config.mcp_server_name_collision`;同名两条 ⇒ 整份 `mcp` 域读坏(候选整拒)。
3603
+ `transport.kind` 只有 `stdio` / `http`(与 `GET /v1/capabilities` 的 `mcpTransportKinds` 同两个词;写 `sse` 一类的词 ⇒ 这一条被丢)。stdio:`command`(必填)· `args`(字符串数组)· `envRefs`。http:`url`(必填,`http://` 或 `https://`)· `headerRefs` · `principalHeader` / `assertionHeader`(二选一,见 9.5d)。
3604
+ 其余键:`allowTools`(缺席或空表 = 这台的工具全挂;写了 = 只挂名单里的原始工具名,名单里一个元素写坏 ⇒ 整条被丢,不截短)· `scenarios`(缺省 `[]` = 所有场景)· `enabled`(缺省 `true`,`false` 不挂)· `elicitation`(缺省 `false`)·
3605
+ `toolAxes`(见上文「MCP 条目的工具风险轴」)· `contentOrigin` · `source` · `toolFaces` · `sessionMetaKey` · `startupTimeoutMs`(与请求体条目的同名键读同一只判官,见 9.5e)· `notes`(运维备注,不进规格)。
3606
+ `rosters` 在单机上不筛条目(本地腿没有 worker 身份,按全局取),写不写都挂;`authForm` / `entryKind` 是连接器的声明,本服务只承载、不读。词表外的键(拼错)⇒ 这一条被丢,不会「剥掉那个键照样挂」。
3607
+ - **逐条容错**:一条写坏 ⇒ 只丢那一条(一行 warn `config_entry_dropped`,点名条目的坐标 `servers.<下标>` 与闭集原因词、字段诊断;日志里认条目靠下标,不是名字),兄弟照挂;**一条都没剩**、或坏在整份(同名两条、域顶层拼错键、坏文件)⇒ 整份候选被拒(`config_candidate_rejected`),规则见上文「逐条容错」。
3608
+ `contentOrigin` 出词表也是整条被丢;`toolAxes` / `toolFaces` / `sessionMetaKey` 写坏则是这一台起服时不挂(兄弟照挂,warn `web_admin_mcp_tool_axes_invalid` / `web_admin_mcp_tool_faces_invalid` / `web_admin_mcp_session_meta_key_invalid` 点名)。停用的条目和起服时被跳过的条目照样占着名字,请求体里的同名条目补不了位。
3609
+ **已知限制:被 `config_entry_dropped` 丢掉的条目不进装配,名字也不再被部署占着** —— 单用户 ∧ `mcp` 未锁 ∧ 合规档位允许的部署上,请求体自带的同名条目补得上位。**补偿**:起服后读一遍 `config_entry_dropped`,把丢掉的那一条改对。
3610
+ - **`envRefs` / `headerRefs` 的边界:文件只存名,值只来自本进程的环境**。
3611
+ `envRefs` = 「子进程里的变量名 → 本服务环境里存着值的变量名」,`headerRefs` = 「HTTP 头名 → 本服务环境里存着值的变量名」。两边的变量名都是 `^[A-Z_][A-Z0-9_]*$` 形(大写字母 / 数字 / 下划线)、且不是 AWS 访问钥 id 形(`AKIA…`);头名是 HTTP token、头名本身不得像凭据。
3612
+ 写成值、或写在错的位置 ⇒ **这一条整条不进**(`config_entry_dropped`,兄弟照挂):`command` / `args` 里的秘密形字面量(`sk-…` · `ghp_…` · `Bearer …` · JWT · 私钥头 …)· `url` 带 userinfo、fragment 或秘密形的路径 / query 值 · 引用表里小写或不是变量名形的项 ·
3613
+ stdio 传输里的明文 `env`、http 传输里的明文 `headers`(这两个键只属于请求体,文件里是词表外的键)。
3614
+ 值的去向:本服务**起服时读一次**自己的进程环境(要换值就改环境并重启),放进对端座的登记表(9.5i);本服务的起服路径不读 `<配置根>/.env` 一类的存值文件 —— 变量必须已经在本进程的环境里(起服的 shell、服务管理器或编排器放进去的)。
3615
+ 引用的变量**未设** ⇒ 这一条整条不挂(一行 warn `web_admin_mcp_env_missing`,点名 `server` / `kind`(`env` 或 `header`)/ 键 / 变量名,不带值),兄弟照挂;缺席只判「未设」,本服务不另判空串。
3616
+ 值只在本进程内存的登记表里:不写回文件、不进交给引擎的规格(规格里只有一只不透明引用)、不进停驻任务存下的提交体(部署声明的件不经提交体)。stdio 子进程只继承 MCP SDK 的少数默认变量(`HOME` · `LOGNAME` · `PATH` · `SHELL` · `TERM` · `USER`)与 `envRefs` 点名的那几只,服务进程环境里其余的变量它拿不到(9.5i「扣留次序」),要给它哪一只就在 `envRefs` 里点名;http 的头值就是那只变量的值,本服务不加 `Bearer ` 之类的前缀。
3617
+ **已知限制:文件腿不拦 `url` 的 query 里按名字像凭据的明文参数**(`?api_key=…` · `?token=…`):文件腿只拦秘密形的值、userinfo 与 fragment,请求体新提交那道「参数名在凭据名表里」的判据不在这条腿上(与 9.5g「不判」一条同向 —— 部署面声明的地址是运维的配置)。**补偿**:凭据一律走 `headerRefs`,起服前自审文件里每一条 `url`。
3618
+ - **单机匿名主体**:单机形(`REQUIRE_PRINCIPAL` 不是 `true`)接受没有主体的调用方 —— 不带 principal 头、也没有验过的 SSO 或直连门身份(那两种凭据给出的身份与头无关,有就有主体)—— 这样的 run 没有主体。对挂载的影响:① 部署声明的服务器不按主体分,挂给每一次 run(`scenarios` 空 = 所有场景),有没有主体都一样;
3619
+ ② http 条目的 `principalHeader` 只在 run 有主体时才发 —— **匿名 run 不发这个头**,对端要把「没有这个头」读成默认拒绝 / 公开,不能读成管理员(stdio 条目没有这个头);
3620
+ ③ 本服务不为匿名主体另设 MCP 写拒。要收窄,在条目上用 `allowTools`(只挂白名单工具)与 `toolAxes`(按工具声明效应轴;没声明的工具引擎按「写」算,审批门与计划模式围栏据此判;单用户的审批基线见 9.4c)。多租户(`REQUIRE_PRINCIPAL=true`)部署同样挂这份文件的声明,不按租户筛。
3621
+ - **与请求体 `mcpServers` 的关系**:两者进同一个字段 —— 部署声明是基线,请求体条目叠在上面。请求体只在单用户(调用方就是这台 worker 的超管)∧ `mcp` 未锁 ∧ 合规档位不禁 `mcp_servers` 时才兑现(`GET /v1/capabilities` 的 `mcpInjection` 是这条合取的值);
3622
+ 多租户 ⇒ 请求体 `mcpServers` 整份忽略(运维 warn `mcp_injection_dropped`),部署声明照挂;锁了 `mcp` ⇒ 请求体带 `mcpServers` 同步 `400 config.locked_key`,锁了 `mcp` 而部署真挂上了 MCP 服务器(至少一台启用且装配通过)⇒ 起服拒(见 `LOCKED_CONFIG_KEYS` 一条;全是停用或装配时被跳过的声明不触发)。
3623
+ 同名时部署的赢:请求体里撞名的条目被丢(`mcp_injection_dropped` 点名,用户面通告 `mcp.injection_dropped`)。`principalHeader` 与 `assertionHeader` 两键同设:请求体新提交 `400 request.body_shape`,文件里同设的那一条被丢,拒句同一句(9.5d)。
3624
+ - **生效**:重启生效 —— 启用的条目新增、改运行期字段都要重启(停用的条目不进重启判据,只改 `notes` / `rosters` 也不算);改了之后 `/health` 报 `restartRequired`、`restart.reasons` 含 `mcp`。下一拍(60 秒,或 `POST /v1/admin/config/refresh`)才读到文件;**只从 enabled 集里拿掉条目**(其余条目逐字不变、相对顺序不变)是例外:即时生效、不签重启,见上文「MCP 撤销腿」。
3625
+ `run-local` 每次都是新进程,没有这一说。
3626
+ - **看挂上了没有**:至少挂上一台时,起服一行 info `web_admin_mcp`(`servers` = 真挂上的名字;一台都没挂上就没有这一行);没挂上的原因在 warn 里(`config_entry_dropped` · `web_admin_mcp_env_missing` · `web_admin_mcp_name_unusable` · 上面三条 `toolAxes` / `toolFaces` / `sessionMetaKey` 的 warn);
3627
+ `GET /v1/capabilities/mcp` 现连、列工具(本部署自己的声明,不必先跑 run),`GET /v1/sessions/:id/mcp` 是会话面板。
3628
+ - **连接器**:`@sema-agent/connectors` 的单机形把连接器写成这个文件里的一条 stdio 条目(`command` 是 `sema-connector`,`args` 带它自己的 `--config-dir <配置根的绝对路径>`,恒不带 `envRefs`:连接器自己的凭据由它按那个目录读,不经本服务的环境)。
3629
+ 默认条目集由连接器包的 `seed` 子命令写入,本服务不自铸、不在起服时调用它;写入后按本节的目录信任与 `envRefs` 边界读。用法:先装好要挂的连接器包,再跑一次 `sema-connector seed --config-dir <配置根的绝对路径>`,按连接器文档给每个连接器 `auth`,然后重启本服务(新增条目重启生效)。未授权时连接器停驻、工具列表为空 —— 呈现与步骤以连接器包的文档为准。
3630
+ 本服务对连接器的拒码(如未授权时的 `connector.auth_required`)零特判,按普通的 MCP 工具错误处理:**按拒句文本读,别按这个码做分支**(该码在本服务事件面上的呈现本节不作承诺)。
3631
+
3329
3632
  ### 9.5j 请求的 `settings.env` 不收凭据(7.146.0 起,新提交 400)
3330
3633
 
3331
3634
  **律**:凭据进引擎只经引擎进程启动时的环境,从不经请求。`settings.env` 来自一份模型读得到的设置文件,而本服务把它交给会话的 shell(单用户 host / device 车道原样叠进模型 shell 的环境 ——
@@ -3349,7 +3652,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3349
3652
  不等于 `MODEL_GATEWAY_BASEURL` 一根:本部署的模型出口至少四条——默认网关(`MODEL_GATEWAY_BASEURL`)、
3350
3653
  备用网关(`MODEL_GATEWAY_FALLBACK_URLS`,failover 拓扑参与)、`provider:"anthropic"` 模型的**独立直连
3351
3654
  路由**(缺省根 `https://api.anthropic.com`,除非显式配 `ANTHROPIC_BASEURL`)、以及 web-admin 可对**单个
3352
- 目录模型**下发的 per-model `baseUrl` 覆盖(见 `docs/ARCHITECTURE.md` §10「Trace/redaction」)。要让内容
3655
+ 目录模型**下发的 per-model `baseUrl` 覆盖。要让内容
3353
3656
  不出机器,靠**读侧敏感路径门**(`READ_DENY_PATTERNS` 等,见 §「执行 lane 与租户门」附近)并核对**上述
3354
3657
  四条路由每一条**都指向自己控制的基础设施——只把 `MODEL_GATEWAY_BASEURL` 指到私有网关、放着
3355
3658
  `provider:"anthropic"` 或某个目录模型的 `baseUrl` 覆盖不管,那部分流量仍走公网原厂,不靠
@@ -3372,7 +3675,13 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3372
3675
  ② 名字的左界止于前一枚已认出的凭据(或可读的结构块)的终点:强锚名紧贴在前一枚凭据的尾巴上(`-----END PRIVATE KEY-----password=…`;PGP 收尾栅栏、PuTTY 的 `Private-MAC:` 行、AWS 键 id 同形,中间无分隔)时**首遍即遮**,再过一遍读数不变 ——所以各出口同读,不看出口过几遍脱敏:流式 `done` 帧、同步体、运维日志行、活流 `text_delta` / `reasoning_delta` 逐块帧与 `text_end` / `reasoning_end`(活流与 `done` 帧同一只脱敏器、同一遍,逐块拼接 = 段末帧,契约 §4a-nov-bis)、`GET /v1/runs/:id` 的 run 行、`GET /v1/runs/:id/events` 回放里的 `text` / `reasoning` / `done` / `tool_end` 行。
3373
3676
  ③ 不带引号的值里面的赋值照问:`api_key=abc,password="my secret value"` 里两个值都遮(此前外层不带引号的值把里面的赋值藏住,里面那一只不认)。
3374
3677
  **升级前已落库的行**:回放读面对 run 行与 `text` / `reasoning` / `done` 行再过一遍脱敏 ⇒ 按本读法遮;`tool_end` 行回放读面不再过(只剥媒体字节)⇒ 升级前写下的 ① ② 两形照旧明文,升级后新写的行照遮。
3375
- **仍不遮的一形**:名字整个落在前一枚已认出凭据的值里 —— 以字母数字收尾的令牌后面紧贴强锚名(`ghp_…password=<值>`,中间无分隔;令牌的形状把名字吞了进去)—— 令牌照遮、`<值>` 明文,各出口同读;中间有分隔(空白 / `,` / `;` / 换行)照遮。名字按整名认:前面与字母数字以 `_` / `-` 连写的(`DB_PASSWORD=` / `foo-api_key=`)是另一个名字、不算锚;只有收尾栅栏、没有起始栅栏的 `…KEY-----password=` 同理(孤立的收尾栅栏不是凭据,不立名界)。要靠脱敏挡住的凭据别以这一形写出(MIGRATION「自由文本脱敏的名字面」)。
3678
+ **名字判定读契约**(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'`)读作插值,不是字面量 —— 要靠脱敏挡住的凭据别以这一形写出。
3679
+
3680
+ **不可见码点的读法**(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 的变体选择符)多读一遍(没有凭据形的文本逐字节不变);折行 / 换行拆开的令牌与「删掉」之外的拆法(夹进一个看得见的字母)不在这张表里。
3681
+
3682
+ **运维日志字段的扫描窗**(7.148.0 起成文):hook 吐出的话、工具输出预览、标题这类有界日志字段对超长文本只扫头一段 —— 窗 = 出门区(`max(2048, 界 × 4)` 个读法单元,出门字节从这里取)+ 余量(脱敏器导出的最长识别跨度 8274 个读法单元:识别表里最长的有界结构 = 定长栅栏的 SSH2 私钥块,由识别表派生,不是出口座自定的数)。起于出门区、总长不超过余量的有界结构整段落在窗内,与整段脱敏逐字同判;不设长度上界的结构(PEM 栅栏行的标签、JSON 串值、URL userinfo、带引号的强锚值 …)在收尾落到窗外时,窗读法从起点遮到窗尾 —— 同记号、零明文,遮蔽范围只到窗尾(越过窗尾的部分本来不出门)。**已知边界**:总长越过余量的有界结构不保证与整段读法同判;体长越过识别表上界(8192)的 SSH2 块整段读法本来不认,日志字段与整段读法同读。窗只是日志字段的读法,wire 面(终局文本 / 账本 / 帧)走整段脱敏,不受影响。
3683
+
3684
+ **错误文本的出口契约**(归一化视图 / 遮蔽记号闭形)写在服务端的助手 wire 契约里(内部记录,不随 npm 包出)。
3376
3685
 
3377
3686
  ### 9.7 bake-runner(构建宿主守护进程)的 env(7.103.0 起成文)
3378
3687
 
@@ -3402,7 +3711,7 @@ docker 推送的鉴权走构建宿主上的 docker 凭据存储(`docker login`),
3402
3711
  🔴 **git 的 env 配置族(`GIT_CONFIG_COUNT` / `GIT_CONFIG_PARAMETERS` / `GIT_CONFIG_KEY_<数字>` / `GIT_CONFIG_VALUE_<数字>`)与代理变量一族(`http(s)_proxy` /
3403
3712
  `all_proxy` / `no_proxy` / `npm_config_*proxy` …,任意大小写)是豁免族 —— 名与值都不读、原样交给子进程,凭据在内**(7.106.0 起;7.103.x–7.105.0 的逐条扣、
3404
3713
  原位占位 `sema.withheld`、代理 userinfo 扣全部退役)。⇒ 凭据别写进这几族:git 凭据走 credential helper 的配置文件、代理按来源免密。`SSH_AUTH_SOCK` 与
3405
- 每一只 `OTEL_*` 自同一版起扣;配方真要它们 ⇒ `BAKE_ENV_ALLOW` 点名放回。全文见 `docs/DEPLOY-PREREQS.md`「宿主子进程拿到的 env 改成参照实现的剥除形」节。
3714
+ 每一只 `OTEL_*` 自同一版起扣;配方真要它们 ⇒ `BAKE_ENV_ALLOW` 点名放回。
3406
3715
 
3407
3716
  **`BAKE_ENV_ALLOW=NAME,NAME`。** 配方真要一枚密钥形 env(例如 build.sh 里自己做 registry 登录要 `REGISTRY_PASSWORD`,或
3408
3717
  `git fetch` 的 askpass 读 `GIT_TOKEN`)⇒ 点名它,runner 把该键从本进程 env **原样**放回给每一只子进程组(部署方显式承担)。
@@ -3494,7 +3803,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3494
3803
  写进 `NO_PROXY`。Bun 下代理值**不拒启**(本仓的 undici 在 Bun 里是垫片、不校验;Bun 自己把无 scheme 的值当 http 用,`ftp:` 到请求时才报错),
3495
3804
  上面那条「代理那一跳失败时怎么读」的前缀也只在 Node 下有;e2b SDK 在 Bun 下走全局 fetch,**认**代理变量。
3496
3805
  - **不走这只 dispatcher 的出网**(本版如实列出,不在本版改):e2b SDK(Node 下自带 dispatcher,只认它自己的 `proxy` 选项,server 不传)、k8s 车道
3497
- (`node:https` + `ws`)、LSP 的 `ws` 传输、ssh 车道、MySQL / PG 驱动(TCP)、`bake-runner`(独立进程)。宿主上起的 host shell 与 git 子进程 env 里
3806
+ (`node:https` + `ws`)、ssh 车道、MySQL / PG 驱动(TCP)、`bake-runner`(独立进程)。宿主上起的 host shell 与 git 子进程 env 里
3498
3807
  照常带着代理变量(不被剥名),由 git / curl 这类工具自己认它们;**MCP stdio 服务器不继承**(MCP SDK 只给子进程 `HOME` / `LOGNAME` / `PATH` /
3499
3808
  `SHELL` / `TERM` / `USER` 加上该服务器声明的 env)—— 要它走代理,在它的 env 声明里写上代理变量。
3500
3809
 
@@ -3568,6 +3877,16 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3568
3877
  这段时间里读到它是常态,过了仍 `absent` 才是镜像腿没出。最新 tag 取本地 tag(发车机上有),巡查机上的 tag 不新就先 `git fetch --tags`。
3569
3878
  - **`scripts/remote-gate.sh ship`** 只把 `git ls-files --cached --others --exclude-standard` 列出的文件打包上 k3s8(已跟踪 + 未跟踪而未被 ignore;
3570
3879
  工作区未提交的改动照带),被 gitignore 的本地探针测试 / scratchpad 读数不再上船。清单生成不出来(不在 git 工作树里 / 空树)⇒ 在碰远端目录之前停。
3880
+ - **送树串行锁**:`remote-gate.sh ship` 的上行是整机串行段 —— 手跑车道、stage 轮、发车链、发包车道都经它送树,按**到达先后**在同一把锁上排队(票号队列,先到先得);
3881
+ 只在 tar 流上传期间持锁,容器里的 `npm ci` 在放锁之后跑,`run` / `fetch` 不排队。排队时 stderr 每分钟一行
3882
+ `REMOTE_GATE_SHIP_WAIT owner=… ahead=<前面几张活票> waited=<秒>s holder=<持锁者>`,看得见谁在饿;等锁超过 `GATE_SHIP_LOCK_WAIT_SEC`(缺省 3600)⇒
3883
+ `REMOTE_GATE_SHIP_LOCK_TIMEOUT`、退出码 3、没送树(响亮红,不放行)。锁目录 `GATE_SHIP_LOCK_DIR`(缺省 `/tmp/sema-ship-lock`,队列在它旁边的 `….queue`);
3884
+ 持锁者或排队者进程没了,下一位读到 pid 不在就清;上行的硬时限(`GATE_SHIP_TIMEOUT_SEC`)在拿到锁之后才起,排队不计入。车不再往自己的命令里抄 `mkdir` 轮询片段。
3885
+ - **容器的临时目录**(7.148.0 起):`remote-gate.sh` 起的每只容器 `TMPDIR=/wtmp`,挂车道目录旁边的宿主盘目录 `<车道目录>.tmp`(模式 1777;ship 重建、
3886
+ 每次 run 起容器前补建、`clean` 一并删),不挂进 `/w`(仓内的临时目录会让「夹具不在仓里」「裸仓」两类钉红)。容器自己的 /tmp 在可写层上:起真服的钉把
3887
+ local 店的数据根铸在 TMPDIR 下,落在可写层 ⇒ 店的持久性判 `ephemeral`、引擎声明会话店持久 ⇒ 起服拒 `config.store_not_durable`。
3888
+ **Linux 开发机同一律**:/tmp 是 tmpfs 时,`npm test` 前把 `TMPDIR` 设到一只磁盘目录(仓外),否则起服类钉被 `config.store_not_durable` 拒;
3889
+ macOS 没有挂载表,判 `unknown`、只 warn,不受影响。
3571
3890
  - **core 提货门**(`node scripts/core-read-table-gate.mjs`)求交的是 **(当前钉, 目标]** 区间里 core 的**每一版**清单:从目标清单沿 `from`
3572
3891
  回溯到钉的那条链,每一环的清单都从目标 tag 的树里读(`git -C ../sema-core show v<目标>:wire-changes/core@<ver>.json`),取并集 —— 跳版提货
3573
3892
  不再只读目标那一份;链断了(某环清单读不出、本地 tag 树缺了那一版)⇒ exit 2 点名。门会先 `git fetch --tags` 那个 core 克隆(不切换 checkout)。
@@ -3614,7 +3933,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3614
3933
  - **按名字找兄弟仓的门:改名 ⇒ 红**:门 1.9(`scripts/settings-schema-duty.mjs`)与测试侧按名字找兄弟仓的门一律经
3615
3934
  `scripts/lib/sibling-repo.mjs` 三态定位 —— 名字缺席而改名映射里的对位名在场(本机 checkout 还叫旧名 `sema-web` / `sema-registry-core`,或门的名单
3616
3935
  还写旧名)⇒ **红**并指路(把目录改名 / 重新 clone 成新名),门 1.9 上持有方与非持有方一样;两个名字都不在 ⇒ 照旧(门 1.9:持有方包仓红、非持有方
3617
- 提示;测试侧:skip,skip 句点名找过的名字与 org 根)。改仓名 / 改包名的同窗动作清单在 `docs/PIPELINE-DISCIPLINE.md`。
3936
+ 提示;测试侧:skip,skip 句点名找过的名字与 org 根)。改仓名 / 改包名的同窗动作清单是内部记录。
3618
3937
  - **门 × 凭据**:起链环境里的凭据(`GITHUB_SEMA_ORG_API_TOKEN` / `NPM_TOKEN` / `GH_TOKEN` / `DEEPSEEK_API_KEY` / 三条 `*_TEST_URL` …)照旧由链启动器
3619
3938
  注入,但**每一道门的子进程只拿到 `scripts/lib/gate-credentials.mjs` 表里它那一行**;表外的凭据形变量(core `scrubSecretEnv` 的判据 + 本链
3620
3939
  DSN 名)一律不交。`release.sh` / `post-publish-gates.sh` 开头封口,每道门先换门;链日志 `scratchpad/release-gates-<v>.log` 每门一行
@@ -3643,7 +3962,7 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3643
3962
  手收:`node scripts/db-lane.mjs <车道> --drop`。起链者传的六条 DSN 只是引擎坐标:链里没有一组读写共享测试库的表,手跑的钉 / 别的车 / 别的仓在共享库上留下的瞬时行进不了门 2,
3644
3963
  链里的破坏性钉也不落在别人用的库上。`db-lane.mjs` 三条腿同一坐标读法(手里那条 DSN;InnoDB 缺席才起夹具),坐标在手的腿建不成 ⇒ 整只车道失败,`--drop` 则三条腿逐只都试、没删掉的逐只点名并 rc 1;暖库的 DSN 只从环境读
3645
3964
  (`TIDB_TEST_URL=… PG_TEST_URL=… npx tsx scripts/db-lane-warm.ts`)。
3646
- - **起链 = `scripts/chain-launch.sh <version>`**(7.105.0 起;取代 sema-internal 的 chain-helper):
3965
+ - **起链 = `scripts/chain-launch.sh <version>`**(7.105.0 起;取代此前的内部 chain-helper):
3647
3966
  ```bash
3648
3967
  nohup bash scripts/chain-launch.sh 7.105.0 > scratchpad/chain-7.105.0.log 2>&1 & disown # 在 node_modules 是真目录的树(主树)里
3649
3968
  ```
@@ -3670,9 +3989,8 @@ web-admin 拉取 / fleet 公告 / 凭证续签、WebSearch、对象存储、OTLP
3670
3989
  (`node_modules/@sema-agent/core/wire-changes/core@<ver>.json` 的 `typeKeys[].added`,区间 = 上一 tag 的 core 钉到本版的钉):TaskEvent 臂与裸型名面上的新键没登记进 `excluded` ⇒ `added`(默认上 wire);
3671
3990
  登记进了 `excluded` ⇒ 新的 `excluded` 桶(这个键从未上过 wire,不是收窄,不计 `breaking`);此前被记成 `narrowed` 的就是这一类。嵌套在已有顶层键之下的新成员不在这一源的射程,由自铸登记表登记。随 release commit 提交;查账门(门 1.6)第三腿 `--check` 核它在仓且与生成输出逐字节同,缺席 / 陈旧 ⇒ 红。
3672
3991
  `wire-changes/server@7.104.0.json` 是回填的第一份。7.136.0 起 `wire-changes/` 随 npm 包出:装包的人在包内 `wire-changes/server@<ver>.json` 就能读到每一版的清单(生成时清单里的 `text` 过发布面内部编号词表,命中的编号换成 `[ref]`;`sources` 里点名的契约文档不随包)。
3673
- - **台账 `refs:` 列建议**:`node scripts/ledger-refs.mjs [--comms-dir <dir>] [--json]` 只读 sema-comms 的 `debts/DEBTS-server.md` /
3674
- `PLANS.md`(server 段)/ `bugs/B-*.md`(文件头 `owner:` 格),按「一方点了另一方的号」双向互链,输出每行的 `refs:` 建议值(号按数值认,
3675
- 零填充与不填充的拼法算同一只号;悬空号 stderr 点名)。不改 sema-comms,落账由主会话做。
3992
+ - **台账 `refs:` 列建议**:`node scripts/ledger-refs.mjs [--comms-dir <dir>] [--json]` 只读内部台账(债务账 / 计划账 server 段 / bug 账文件头的 `owner:` 格),按「一方点了另一方的号」双向互链,输出每行的 `refs:` 建议值(号按数值认,
3993
+ 零填充与不填充的拼法算同一只号;悬空号 stderr 点名)。不改内部台账,落账由主会话做。
3676
3994
  - **tag 跑复用 main 上同一提交的 test 结论**(`.github/workflows/ci.yml`):tag run 的 `test` job 先跑 `node scripts/ci-tag-test-reuse.mjs`,
3677
3995
  按 headSha 读 main 上同一提交那次 push run 的 `test` job —— success ⇒ 跳过全套;在跑 ⇒ 等(上限 45 min,复用步 55 min 时限之内);
3678
3996
  失败 / 查不到 / 读不出 ⇒ 照跑全套。结论一行 `TAG-TEST-REUSE decision=reuse|run why=… mainRun=… waited=…s sha=…` 在该 job 的日志与 step summary 里。
@@ -3755,12 +4073,14 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
3755
4073
  多个副本不共享保管目录时,各副本答出的公钥不同,对着 A 副本的公钥封的密文在 B 副本上是 `stale_ciphertext`(逐模型响亮失败,不退回任何共享 key)
3756
4074
  —— 在负载均衡后面取 caps,取到哪台就是哪台的钥匙。出路两条:
3757
4075
 
3758
- - **共享保管目录**:让每个副本的 `<数据根>/sealed-key/` 是同一份钥匙文件(例如把同一个卷挂到这个路径)。先把钥匙备好(先起一个副本让它生成,
3759
- 或放好钥匙文件)再起其余副本 —— 钥匙创建是先读后写、没有互斥:两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时,各副本可能各自生成一把、只持有自己那一把,直到下次重启。目录要对每个副本**可写**(每次起服
4076
+ - **共享保管目录**:让每个副本的 `<数据根>/sealed-key/` 是同一份钥匙文件(例如把同一个卷挂到这个路径)。空目录上首启的生成只有一个口(7.148.0 起):一把可装载的钥匙都没有时,副本以 `wx`(`O_CREAT | O_EXCL`)独占创建互斥文件 `<保管目录>/.keygen.lock`,拿到的那一个生成并发布那一把,
4077
+ 其余副本等它发布后读到同一把 —— 多个副本同时在空目录上首启也只出一把,不必先起一个再起其余、也不必事后重启(要求共享卷的 `O_EXCL` 独占创建是原子的:本地盘与 NFSv3 及以上按协议满足,别的网络文件系统请向存储方确认);
4078
+ 互斥文件只在目录里没有钥匙时才被问到,发布之后即删。拿着互斥的进程崩在生成中途、目录仍空 ⇒ 之后每次起服等 15 秒,仍没有钥匙就建店失败(处置同本节别处的「建不了店」:声明了 `SEALED_KEY_DIR` 或耐久审批在场 ⇒ 起服拒;否则告警 `sealed_key_store_unavailable` / `checkpoint_ctx_sealing_unavailable`),句子点名互斥文件的路径:没有副本正在起服时删掉它再起。
4079
+ 7.147.x 及更早的版本没有这个互斥:它们的副本在空目录上同时首启、都没读到可用的钥匙文件时,可能各自生成一把、只持有自己那一把,直到下次重启 —— 这样分叉过的目录,重启一次让每个副本读到全部钥匙。目录要对每个副本**可写**(每次起服
3760
4080
  会把它设成 `0700`;只读挂载 ⇒ 建店失败 ⇒ `no_key_store`)。⚠️ 只共享 `sealed-key/` 这一个子目录:`DB_BACKEND=local` 的整个数据根禁止跨容器 /
3761
- 跨主机共享(`docs/DEPLOY-PREREQS.md`)。
4081
+ 跨主机共享。
3762
4082
  照随包 `deploy/sema-up` 图表部署时这一步图表替你做了:`sealedKey.persistence` 块(缺省开)把整个 `sealed-key/` 目录挂成一只 PVC,访问模式缺省单副本 `ReadWriteOnce`、多副本 `ReadWriteMany`
3763
- (要存储类支持 RWX;空卷首装后 `rollout restart` 一次,让并发首启时可能各生成的钥匙互相都持有);显式关掉它(`sealedKey.persistence.enabled: false`)图表会同时给 worker 设 `DURABLE_APPROVAL=false`,并拒绝再配 `server.durableApproval: true`。
4083
+ (要存储类支持 RWX;空卷首装的多副本并发首启也只出一把,7.148.0 起不必重启 —— 7.147.x 及更早版本首装出多把钥匙的卷,`rollout restart` 一次让每个 pod 读到全部钥匙);显式关掉它(`sealedKey.persistence.enabled: false`)图表会同时给 worker 设 `DURABLE_APPROVAL=false`,并拒绝再配 `server.durableApproval: true`。
3764
4084
  - **走中心形**:接 web-admin,执行侧公钥登记在中心(起服时带管理令牌自动上报,否则把公钥打印出来供手工登记),web 面对着登记的公钥封、中心存密文。
3765
4085
  调用方不必逐台去问 caps;但同一个 worker 的多个副本各有各的保管店这件事中心形不改变 —— 要它们都开得了同一份密文,仍要共享保管目录。
3766
4086
 
@@ -3768,7 +4088,7 @@ const ciphertext = Buffer.from(sodium.crypto_box_seal(sodium.from_string(apiKey)
3768
4088
  在 B 副本上续跑答 `409 conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`;单副本也要让这个目录跨重启持久。**7.133.x 与 7.134.0 不能混跑**:先停掉全部 7.133.x 副本,再起第一个 7.134.0 副本
3769
4089
  (一次性迁移 M28 在它起服时把升级前停驻行里的明文封好);混跑时 7.133.x 副本续跑一条已封签的行会把凭据位当空值、任务无凭据照跑、不报错。降级到 7.133.x 之前先把带凭据的停驻行结掉(决定或取消)。
3770
4090
  多副本升级时先起一个 7.134.0 副本、等它起好再起其余副本(耐久审批开着时它起好即打出 `checkpoint_ctx_credentials_sealed` 一行):停驻封签的钥匙在新子目录 `checkpoint-ctx-keys/` 里,
3771
- 升级后第一个起服的副本生成它(`sealed-key/` 早已共享好也一样);同时起多个副本可能各建一把、互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件;各建只发生在两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时)。
4091
+ 升级后第一个起服的副本生成它(`sealed-key/` 早已共享好也一样);7.148.0 起同时起多个副本也只出一把(首把钥匙只有一个创建口,见上);7.134.0 – 7.147.x 同时起多个副本可能各建一把、互相答 `key_not_held`,直到各自重启一次(钥匙只在起服时读目录里的全部文件;各建只发生在两个副本在同一个目录上同时首启、都没读到可用的钥匙文件时)。
3772
4092
 
3773
4093
  ### 9.11 会话规则店:抹除封键与显式恢复(7.106.0 起)
3774
4094
 
@@ -3934,15 +4254,16 @@ docker run -d --name sema-server -p 8090:8090 \
3934
4254
  -e MODEL_GATEWAY_BASEURL=https://<your-gateway>/v1 -e MODEL_ID=<your-model-name> -e MODEL_API_KEY=… \
3935
4255
  -e SERVICE_AUTH_TOKEN=<随机长串> \
3936
4256
  sema-server:<版本>
3937
- # 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「监听绑址」段。
4257
+ # 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「监听绑址」段。
3938
4258
  # 5. 验收 —— 就绪 + 版本
3939
4259
  curl -fsS http://127.0.0.1:8090/readyz # {"ready":true}
3940
- curl -s http://127.0.0.1:8090/health # "version" 应等于你构建的 <版本>
4260
+ curl -s -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>" http://127.0.0.1:8090/health # "version" 应等于你构建的 <版本>
4261
+ # 带第 4 步那枚 SERVICE_AUTH_TOKEN 读:不带凭据的读者只拿 status / auth / operator / center 四键,没有 version
3941
4262
  ```
3942
4263
 
3943
4264
  - **随包 `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 之后没有新版;照本节自建,把你的引用填进去。
3944
4265
  - **数据根**:第 4 步 `DB_BACKEND=local` 是单容器形(数据目录禁跨容器共享)。生产用 `DB_BACKEND=mysql|pg` 与 §0「生产」那组坐标替换它 —— **数据根卷仍要挂**:`<数据根>/sealed-key/` 在 SQL 后端下也不进库,跨重启要持久。不设 `AGENT_DATA_DIR` 时数据根落在容器用户家目录下的 `.ai-agent`,容器一删就没。
3945
- - **升级 = 换一只更高版本的镜像**:升级序、先升库再升副本、排空与回滚前置,按你跨过的每一版读 `MIGRATION.md` 与源码仓文档 DEPLOY-PREREQS 的同版节(不随包);一次性迁移与新表在起服时自跑,不用手跑。
4266
+ - **升级 = 换一只更高版本的镜像**:升级序、先升库再升副本、排空与回滚前置,按你跨过的每一版读 `MIGRATION.md` 的同版节(「升级前做」「回滚」都写在那里);一次性迁移与新表在起服时自跑,不用手跑。
3946
4267
 
3947
4268
  **与曾发布镜像的差异**
3948
4269