@sema-agent/server 7.135.0 → 7.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/MIGRATION.md +237 -2
  2. package/USAGE.md +146 -41
  3. package/deploy/sema-up/README.md +15 -1
  4. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  5. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  6. package/deploy/sema-up/chart/values.yaml +1 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +60 -4
  9. package/dist/approval-card.js +18 -1
  10. package/dist/approval-content-kind.d.ts +7 -0
  11. package/dist/approval-content-kind.js +8 -1
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/auth-bridge.d.ts +34 -5
  18. package/dist/auth-bridge.js +22 -5
  19. package/dist/boot/budget-tracing.d.ts +1 -2
  20. package/dist/boot/budget-tracing.js +2 -2
  21. package/dist/boot/execution-env.d.ts +24 -1
  22. package/dist/boot/execution-env.js +69 -6
  23. package/dist/boot/org-memory.js +1 -1
  24. package/dist/boot/reapers.d.ts +3 -0
  25. package/dist/boot/reapers.js +15 -1
  26. package/dist/boot/resolve-spec.d.ts +4 -0
  27. package/dist/boot/resolve-spec.js +24 -22
  28. package/dist/boot/runner-deps.js +4 -2
  29. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  30. package/dist/boot/stage-04-stores.d.ts +2 -2
  31. package/dist/boot/stage-05-execution-env.d.ts +4 -2
  32. package/dist/boot/stage-05-execution-env.js +11 -2
  33. package/dist/boot/stage-06-runners.d.ts +4 -2
  34. package/dist/boot/stage-06-runners.js +5 -1
  35. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  36. package/dist/boot/stage-07-capability-layer.js +1 -1
  37. package/dist/boot/stage-08-reapers.d.ts +5 -2
  38. package/dist/boot/stage-08-reapers.js +2 -1
  39. package/dist/boot/stage-09-leader.d.ts +5 -2
  40. package/dist/boot/stage-10-http-server.d.ts +5 -2
  41. package/dist/boot/stage-10-http-server.js +3 -2
  42. package/dist/boot/stores.d.ts +1 -1
  43. package/dist/boot/stores.js +2 -3
  44. package/dist/boot/web-admin.js +10 -17
  45. package/dist/brain.js +12 -3
  46. package/dist/broker/broker.d.ts +89 -0
  47. package/dist/broker/broker.js +287 -0
  48. package/dist/broker/constants.d.ts +39 -0
  49. package/dist/broker/constants.js +15 -0
  50. package/dist/broker/http1.d.ts +111 -0
  51. package/dist/broker/http1.js +256 -0
  52. package/dist/broker/lease-book.d.ts +87 -0
  53. package/dist/broker/lease-book.js +142 -0
  54. package/dist/broker/lease-file.d.ts +60 -0
  55. package/dist/broker/lease-file.js +127 -0
  56. package/dist/broker/main.d.ts +2 -0
  57. package/dist/broker/main.js +20 -0
  58. package/dist/budget.d.ts +3 -3
  59. package/dist/budget.js +4 -0
  60. package/dist/capabilities/composite-members.d.ts +2 -1
  61. package/dist/capabilities/composite-members.js +2 -1
  62. package/dist/capabilities/engine-capabilities.js +1 -0
  63. package/dist/capabilities/mcp-probe.d.ts +5 -4
  64. package/dist/capabilities/scenarios.d.ts +23 -8
  65. package/dist/capabilities/scenarios.js +13 -8
  66. package/dist/capabilities/skills.d.ts +28 -5
  67. package/dist/capabilities/skills.js +8 -0
  68. package/dist/capability-facts.d.ts +3 -7
  69. package/dist/capability-facts.js +3 -5
  70. package/dist/center-credential-renewer.js +5 -1
  71. package/dist/center-credential.d.ts +18 -4
  72. package/dist/center-credential.js +18 -3
  73. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  74. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  75. package/dist/checkpoint-identity.d.ts +3 -3
  76. package/dist/config-catalog.js +21 -16
  77. package/dist/config-d-migration.js +1 -1
  78. package/dist/config-invariants.d.ts +1 -1
  79. package/dist/config-invariants.js +8 -0
  80. package/dist/config-lkg.d.ts +0 -38
  81. package/dist/config-lkg.js +1 -83
  82. package/dist/config-types.d.ts +21 -4
  83. package/dist/config.d.ts +1 -1
  84. package/dist/config.js +60 -16
  85. package/dist/deployment-governance.d.ts +2 -2
  86. package/dist/deployment-governance.js +7 -9
  87. package/dist/deployment-shape.d.ts +1 -1
  88. package/dist/device-ws-protocol.d.ts +1 -0
  89. package/dist/device-ws-protocol.js +2 -1
  90. package/dist/elicitation.d.ts +8 -7
  91. package/dist/elicitation.js +9 -8
  92. package/dist/file-history-disclosure.d.ts +8 -4
  93. package/dist/file-history-disclosure.js +24 -6
  94. package/dist/fleet-client.d.ts +32 -1
  95. package/dist/fleet-client.js +24 -4
  96. package/dist/fleet-lease.d.ts +7 -3
  97. package/dist/fleet-lease.js +31 -23
  98. package/dist/hooks/classic-stations.d.ts +11 -54
  99. package/dist/hooks/hook-llm.js +14 -8
  100. package/dist/hooks/hook-runner.d.ts +4 -4
  101. package/dist/hooks/hook-runner.js +2 -2
  102. package/dist/http/admission.js +10 -7
  103. package/dist/http/approval-sweeps.js +4 -3
  104. package/dist/http/caller-trust.d.ts +2 -1
  105. package/dist/http/caller-trust.js +1 -3
  106. package/dist/http/checkpoint-ctx.d.ts +76 -16
  107. package/dist/http/checkpoint-ctx.js +45 -1
  108. package/dist/http/dispatch.js +24 -19
  109. package/dist/http/durable-run-leg.d.ts +5 -1
  110. package/dist/http/durable-run-leg.js +1 -1
  111. package/dist/http/health-self-report.d.ts +2 -2
  112. package/dist/http/mcp-remote-text.d.ts +5 -0
  113. package/dist/http/mcp-remote-text.js +7 -0
  114. package/dist/http/principal-gate.d.ts +15 -10
  115. package/dist/http/principal-gate.js +48 -9
  116. package/dist/http/resume-ctx-read.d.ts +2 -16
  117. package/dist/http/resume-ctx-read.js +2 -3
  118. package/dist/http/resume-legs.d.ts +2 -20
  119. package/dist/http/resume-legs.js +52 -38
  120. package/dist/http/route-ctx.d.ts +8 -2
  121. package/dist/http/routes/approvals-assistant.d.ts +0 -2
  122. package/dist/http/routes/approvals-assistant.js +65 -16
  123. package/dist/http/routes/capabilities.js +11 -1
  124. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  125. package/dist/http/routes/mcp-reconnect.js +5 -7
  126. package/dist/http/routes/runs.js +4 -3
  127. package/dist/http/routes/session-sync.js +70 -12
  128. package/dist/http/routes/sessions.js +20 -15
  129. package/dist/http/routes/tasks.js +8 -6
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/stored-body-replay.d.ts +3 -3
  132. package/dist/http/stored-body-replay.js +6 -1
  133. package/dist/http/submit-ctx.js +1 -1
  134. package/dist/http/wire-types.d.ts +57 -30
  135. package/dist/identity-source.d.ts +65 -0
  136. package/dist/identity-source.js +45 -0
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.js +1 -0
  139. package/dist/memory-store-refusal.d.ts +3 -3
  140. package/dist/model-entry-refusal.d.ts +2 -0
  141. package/dist/model-entry-refusal.js +11 -0
  142. package/dist/model-route-gap.d.ts +49 -4
  143. package/dist/model-route-gap.js +34 -1
  144. package/dist/model-select.d.ts +6 -17
  145. package/dist/model-select.js +0 -3
  146. package/dist/namespaced-rule-name.d.ts +1 -1
  147. package/dist/object-store-buckets.d.ts +13 -0
  148. package/dist/object-store-buckets.js +3 -0
  149. package/dist/observability/fail-open.d.ts +19 -7
  150. package/dist/observability/fail-open.js +19 -7
  151. package/dist/observability/metrics.js +2 -2
  152. package/dist/observability/principal-context.d.ts +4 -1
  153. package/dist/observability/principal-context.js +6 -1
  154. package/dist/observability/run-terminal.js +1 -0
  155. package/dist/operator-ask.d.ts +2 -2
  156. package/dist/org-delivery-gate.js +1 -1
  157. package/dist/org-memory-admission.d.ts +2 -2
  158. package/dist/org-memory-admission.js +39 -2
  159. package/dist/parked-decide.d.ts +4 -3
  160. package/dist/parked-decide.js +29 -9
  161. package/dist/peer-directory.d.ts +2 -1
  162. package/dist/peer-directory.js +7 -5
  163. package/dist/permission-rule-list.d.ts +2 -2
  164. package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
  165. package/dist/plugins/approval-ask-store-sql.js +3 -0
  166. package/dist/plugins/blob-backend.js +2 -1
  167. package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
  168. package/dist/plugins/checkpoint-index-columns.js +28 -0
  169. package/dist/plugins/checkpoint-store-sql.d.ts +46 -12
  170. package/dist/plugins/checkpoint-store-sql.js +64 -35
  171. package/dist/plugins/file-history-store-sql.d.ts +6 -0
  172. package/dist/plugins/file-history-store-sql.js +37 -37
  173. package/dist/plugins/file-run-store.js +8 -2
  174. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  175. package/dist/plugins/k8s-exec-protocol.js +116 -28
  176. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  177. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  178. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  179. package/dist/plugins/k8s-pod-reaper.js +122 -0
  180. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  181. package/dist/plugins/local-checkpoint-store.js +7 -6
  182. package/dist/plugins/memory-run-store.js +2 -0
  183. package/dist/plugins/one-time-migrations.d.ts +49 -37
  184. package/dist/plugins/one-time-migrations.js +87 -128
  185. package/dist/plugins/permission-rule-store-file.js +2 -2
  186. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  187. package/dist/plugins/permission-rule-store-sql.js +16 -17
  188. package/dist/plugins/pg-pool.js +4 -0
  189. package/dist/plugins/pg-session-storage.js +2 -2
  190. package/dist/plugins/remote-env-adb.d.ts +3 -0
  191. package/dist/plugins/remote-env-adb.js +1 -0
  192. package/dist/plugins/remote-env-device.d.ts +3 -0
  193. package/dist/plugins/remote-env-device.js +4 -0
  194. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  195. package/dist/plugins/remote-env-e2b.js +1 -0
  196. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  197. package/dist/plugins/remote-env-file-error.js +7 -1
  198. package/dist/plugins/remote-env-host.d.ts +7 -1
  199. package/dist/plugins/remote-env-host.js +7 -1
  200. package/dist/plugins/remote-env-k8s.d.ts +154 -14
  201. package/dist/plugins/remote-env-k8s.js +300 -21
  202. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  203. package/dist/plugins/remote-env-local-docker.js +1 -0
  204. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  205. package/dist/plugins/remote-env-ssh.js +1 -0
  206. package/dist/plugins/run-store-sql.js +10 -4
  207. package/dist/plugins/s3-answer.d.ts +15 -0
  208. package/dist/plugins/s3-answer.js +5 -0
  209. package/dist/plugins/session-ownership.d.ts +17 -3
  210. package/dist/plugins/session-ownership.js +19 -5
  211. package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
  212. package/dist/plugins/snapshot-bucket-presence.js +35 -7
  213. package/dist/plugins/sql-errors.d.ts +60 -0
  214. package/dist/plugins/sql-errors.js +64 -1
  215. package/dist/plugins/store-contracts.d.ts +10 -1
  216. package/dist/plugins/store-fault.d.ts +4 -0
  217. package/dist/plugins/store-fault.js +10 -0
  218. package/dist/plugins/tidb-pool.js +8 -1
  219. package/dist/plugins/tidb-session-store.js +2 -2
  220. package/dist/plugins/web-search.d.ts +1 -1
  221. package/dist/plugins/web-search.js +2 -2
  222. package/dist/request-declaration-refusal.d.ts +94 -0
  223. package/dist/request-declaration-refusal.js +78 -0
  224. package/dist/request-image-urls.d.ts +6 -0
  225. package/dist/request-image-urls.js +27 -0
  226. package/dist/request-key-closure.d.ts +5 -0
  227. package/dist/request-key-closure.js +5 -2
  228. package/dist/request-value-closure.d.ts +31 -2
  229. package/dist/request-value-closure.js +7 -1
  230. package/dist/rule-entry-list.d.ts +3 -2
  231. package/dist/rule-entry-list.js +2 -2
  232. package/dist/rules-consent.js +7 -4
  233. package/dist/run-local.d.ts +2 -2
  234. package/dist/runs.js +2 -1
  235. package/dist/runtime-caps-resolver.js +1 -1
  236. package/dist/runtime-governance.d.ts +5 -5
  237. package/dist/sandbox-git-lease.d.ts +49 -0
  238. package/dist/sandbox-git-lease.js +60 -0
  239. package/dist/sandbox-git-plane.d.ts +84 -0
  240. package/dist/sandbox-git-plane.js +105 -0
  241. package/dist/sandbox-pkg-source.d.ts +28 -1
  242. package/dist/sandbox-pkg-source.js +1 -2
  243. package/dist/sealed-key.d.ts +19 -0
  244. package/dist/sealed-key.js +47 -4
  245. package/dist/security.d.ts +16 -4
  246. package/dist/security.js +8 -3
  247. package/dist/server-secret-env.d.ts +6 -0
  248. package/dist/server-secret-env.js +14 -0
  249. package/dist/session-error-wire.d.ts +14 -6
  250. package/dist/session-error-wire.js +2 -1
  251. package/dist/session-incarnation.d.ts +16 -5
  252. package/dist/session-incarnation.js +7 -4
  253. package/dist/session-policy-wire.d.ts +1 -1
  254. package/dist/session-policy-wire.js +2 -2
  255. package/dist/session-row.d.ts +3 -17
  256. package/dist/session-row.js +4 -0
  257. package/dist/session-sync.d.ts +15 -3
  258. package/dist/session-sync.js +5 -2
  259. package/dist/spec-fields.d.ts +43 -13
  260. package/dist/spec-fields.js +33 -12
  261. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  262. package/dist/stored-request-prompt-profile-m30.js +25 -0
  263. package/dist/task-a2a.d.ts +52 -81
  264. package/dist/task-a2a.js +219 -89
  265. package/dist/task-mcp.d.ts +7 -15
  266. package/dist/task-mcp.js +16 -51
  267. package/dist/task-settings.d.ts +2 -2
  268. package/dist/tool-approval.d.ts +35 -12
  269. package/dist/tool-approval.js +59 -39
  270. package/dist/tool-rule-names.d.ts +5 -0
  271. package/dist/trace/core-keyset-guard.d.ts +5 -5
  272. package/dist/trace/engine-notice-wire.d.ts +1 -1
  273. package/dist/trace/engine-notice-wire.js +2 -0
  274. package/dist/trace/exit-seat.js +37 -13
  275. package/dist/trace/ledger-sink.d.ts +4 -5
  276. package/dist/trace/ledger-sink.js +3 -7
  277. package/dist/trace/project.d.ts +37 -3
  278. package/dist/trace/project.js +20 -6
  279. package/dist/trace/redact.d.ts +40 -12
  280. package/dist/trace/redact.js +332 -128
  281. package/dist/trace/self-minted-wire.d.ts +641 -0
  282. package/dist/trace/self-minted-wire.js +201 -0
  283. package/dist/trace/wire-projection-faces.d.ts +5 -3
  284. package/dist/trace/wire-projection-faces.js +7 -3
  285. package/dist/url-credential-refusal.d.ts +52 -0
  286. package/dist/url-credential-refusal.js +14 -0
  287. package/dist/url-refusal.d.ts +13 -4
  288. package/dist/url-refusal.js +8 -2
  289. package/dist/web-admin-client/apply-effective.js +0 -1
  290. package/dist/web-admin-client/center-request.d.ts +20 -2
  291. package/dist/web-admin-client/center-request.js +40 -5
  292. package/dist/web-admin-client/http-client.js +6 -6
  293. package/dist/web-admin-client/types.d.ts +5 -5
  294. package/dist/workspace-space.d.ts +27 -0
  295. package/dist/workspace-space.js +26 -0
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  297. package/package.json +4 -3
  298. package/scripts/drain-check-local.mjs +2 -1
  299. package/scripts/drain-check.mysql.sql +3 -1
  300. package/scripts/drain-check.pg.sql +3 -1
  301. package/skills/schedule.md +1 -1
  302. package/wire-changes/server@7.104.0.json +134 -0
  303. package/wire-changes/server@7.105.0.json +53 -0
  304. package/wire-changes/server@7.106.0.json +475 -0
  305. package/wire-changes/server@7.107.0.json +64 -0
  306. package/wire-changes/server@7.108.0.json +153 -0
  307. package/wire-changes/server@7.109.0.json +46 -0
  308. package/wire-changes/server@7.110.0.json +90 -0
  309. package/wire-changes/server@7.111.0.json +86 -0
  310. package/wire-changes/server@7.112.0.json +60 -0
  311. package/wire-changes/server@7.113.0.json +26 -0
  312. package/wire-changes/server@7.114.0.json +26 -0
  313. package/wire-changes/server@7.115.0.json +97 -0
  314. package/wire-changes/server@7.116.0.json +218 -0
  315. package/wire-changes/server@7.116.1.json +26 -0
  316. package/wire-changes/server@7.117.0.json +65 -0
  317. package/wire-changes/server@7.118.0.json +129 -0
  318. package/wire-changes/server@7.119.0.json +65 -0
  319. package/wire-changes/server@7.120.0.json +89 -0
  320. package/wire-changes/server@7.121.0.json +219 -0
  321. package/wire-changes/server@7.122.0.json +54 -0
  322. package/wire-changes/server@7.123.0.json +26 -0
  323. package/wire-changes/server@7.124.0.json +336 -0
  324. package/wire-changes/server@7.124.1.json +26 -0
  325. package/wire-changes/server@7.125.0.json +111 -0
  326. package/wire-changes/server@7.126.0.json +285 -0
  327. package/wire-changes/server@7.127.0.json +77 -0
  328. package/wire-changes/server@7.127.1.json +26 -0
  329. package/wire-changes/server@7.127.2.json +53 -0
  330. package/wire-changes/server@7.128.0.json +26 -0
  331. package/wire-changes/server@7.129.0.json +106 -0
  332. package/wire-changes/server@7.129.1.json +26 -0
  333. package/wire-changes/server@7.130.0.json +26 -0
  334. package/wire-changes/server@7.131.0.json +118 -0
  335. package/wire-changes/server@7.132.0.json +226 -0
  336. package/wire-changes/server@7.133.0.json +1155 -0
  337. package/wire-changes/server@7.134.0.json +107 -0
  338. package/wire-changes/server@7.135.0.json +174 -0
  339. package/wire-changes/server@7.136.0.json +448 -0
  340. package/wire-changes/server@7.137.0.json +825 -0
  341. package/dist/url-credential.d.ts +0 -52
  342. package/dist/url-credential.js +0 -49
@@ -90,7 +90,7 @@ export const FAIL_OPEN_TAGS = {
90
90
  },
91
91
  "server.elicitation.open-frame-undelivered": {
92
92
  cls: "F",
93
- note: "MCP elicitation 的 open 帧投递失败 ⇒ 结算 decline。方向本身是 fail-closed(问不到人就是拒),缺的只是留痕。",
93
+ note: "MCP elicitation 的 open 帧投递失败 ⇒ 按没人应答放掉(cancel)。方向本身是 fail-closed(问不到人就是没授予),缺的只是留痕。",
94
94
  },
95
95
  "server.question.complete-breadcrumb-dropped": {
96
96
  cls: "F",
@@ -170,11 +170,11 @@ export const FAIL_OPEN_TAGS = {
170
170
  },
171
171
  "server.approvals.park-row-blob-unreadable": {
172
172
  cls: "F",
173
- note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowFactsCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)/ `mandate`(S-721)/ `origin`(S-755)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。 (`member=gate` 那第二座随 core 7.55.0 #1468 P1 阶段二退役:队列行的卡与到期分法改读 `approval_card` / `gate_kind` 两列 —— 写口与 M29 回填同一只派生落下的格 —— 本读面不再读 blob 的 `gate` 子树。)",
173
+ note: "S-700(`plugins/checkpoint-store-sql.ts` 的 `readParkRowActionCell`):运维待批队列(`GET /v1/approvals` / `/stream`)的某一行,`checkpoint` 列里寄存行的 **`pendingAction` 子树**(NP-3 / 7.100.0 起引擎内只取这一棵、整只快照不出库;只为 park 行事实 `requiresRealApproval` / `ruleOffersAbsence` / `mandated`(S-663 二段,core #1079)/ `mandate`(S-721)/ `origin`(S-755)而读 —— 事实位的真源是 `approval-card.ts` 的 `ParkRowFacts`)**在场却不是一只对象**(JSON `null` 字面 / 串 / 数 —— 手改 / 迁移脚本改过)。子树**缺席**(SQL NULL:成员不在,或整只文档不是对象 —— 后者 `get()` 照旧拒;MariaDB 文本承载上的非 JSON 字节同样答 SQL NULL)⇒ 事实缺席、不计数。放行的最坏后果 = **这一行**的 park 行事实(`ParkRowFacts` 各位)缺席(展示/分诊材料;core 定性 display metadata,resume 的带子读 gate 自己的位),队列其余部分照常 —— 而这一行本来就 resume 不了(`get()` 对同一格拒)。不放行的代价是整只 `rows.map` 抛出 ⇒ 一行坏 blob 打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远空),与同一 `rows.map` 里 `rule_suggestions` / `risk_descriptor` 两格同族同判。故 F 类;tag 与那两格**分开计**(哪一格在坏要读得出来)。计数非零 = 这一行的 decide 同样读不动(`get()` 对同一格拒),去按 session 查那一行。 (`member=gate` 那第二座随 core 7.55.0 #1468 P1 阶段二退役:队列行的卡与到期分法改读 `approval_card` / `gate_kind` 两列 —— 写口与 M29 回填同一只派生落下的格 —— 本读面不再读 blob 的 `gate` 子树。)S-2076 起同 tag 另有两座同判据的臂(`SqlCheckpointStore.readParkedSkillCardFacts`:对行上真带技能卡三位之一的行,整只快照读回的体坏、或体是对象但 core 的摘要读不动它 ⇒ 只丢**这一行**的三位〔`boundGrantDigest` / `boundGrants` / `classifierNoRuling`〕,队列照常;`not-text` 同样原样抛)—— 计数非零的处置同上:去按 session 查那一行。",
174
174
  },
175
175
  "server.approvals.durable-row-checkpoint-id-missing": {
176
176
  cls: "F",
177
- note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。",
177
+ note: "7.133.0(`checkpoint-identity.ts` 的 `durableRowIdentity`;两只店的 `listPending` + `/v1/assistant/inbox` handler):三处 durable 读面(运维队列行 `GET /v1/approvals` 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的行)读到一行停驻门**没有可用的 `checkpointId`** ⇒ 这一行**照列、`checkpointId` 键缺席**(消费方按「这一行没有可 join 的身份」处理)。detail = `face=<approvals-queue|assistant-inbox> why=<absent|untrusted>`:`absent` = 行上没有这一格(core 5.42.0 之前停驻的老行 —— core 明写不回填);`untrusted` = 在场却没过信任边界(非串 / 空串 / 超长 / 含 resume token —— 数据污染;没过边界的值不上 wire)。放行(列、键缺席)的最坏后果 = 消费方对这一行做不了跨面 join(三面对账少一行)—— 行本身、决断口(`/v1/approvals/:sessionId/decide`,按会话)、到期收割都照常。不放行有两种形:整只拒的代价是一行老 / 坏行打掉整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧);把这一行藏起来的代价更糟 —— 人看不到要批的东西(审批面上的 fail-open,方向反了)。故 F 类;必须留痕(两种成因从此可分)。计数非零 = 有老 / 坏行在飞(列着、没身份);`/stream` 每 3s 一拍重读,同一行每拍计一次(量值 = 行 × 拍数,在场才是信号)。 7.137.0 起第四面:park 账本行(`GET /v1/runs/:id/events` 的 `suspended` / `needs_review`;唯一构造器 `trace/project.ts` 的 `parkEventData`,写时判一次,detail `face=run-events-park`)—— 暂停因由上的门身份没过信任边界 ⇒ 行照写、`checkpointId` 键缺席(决断方回显不了这道门,退回不带键的决断或从 run 结果取原值;行本身、门、待批调用身份照常)。",
178
178
  },
179
179
  "server.approvals.queue-row-gate-kind-missing": {
180
180
  cls: "F",
@@ -334,7 +334,7 @@ export const FAIL_OPEN_TAGS = {
334
334
  "server.run-terminal.output-truncated": { cls: "F", note: "S-107:持久 error 脱后 UTF-8 字节 > 65,000(TiDB TEXT 65,535 字节)⇒ 脱后切并带标记(切在占位里不是泄漏);计数=多少条终态文案被字节封顶截断" },
335
335
  "server.runs.abort-cause-missing": { cls: "F", note: "S-947 候裁 ⑤(主会话裁:默认值保留,miss 臂必须响):续跑腿 `http/resume-legs.ts` 的 `checkpoint.resume_aborted` 臂 —— core 只在本腿递进去的信号**已中止**时铸该码,而那只信号唯一的写者是取消动词(快路 / 耐久旗轮询,句柄登记 `cancel_verb`),所以**只有 core 契约外的形**(桩 / 非 core Runner 直接抛该码,本腿句柄从未中止)才到得了「句柄上读不到成因」这一臂。取消终局照铸,成因取本腿唯一的中止词 `cancel_verb`(取消终局恒带成因由铸点签名保证,不让它缺);detail `task=<taskId>`;计数 = 多少次取消成因是默认值而不是句柄登记的。生产上恒零是证据,不归零 = 有一条不走 core 契约的 Runner 在跑续跑腿。" },
336
336
  "server.runs.terminal-ledger-write-failed": { cls: "F", note: "S-107(全量门 silent-degradation 棘轮抓出的五处静默 `catch {}`):run 终局链里**账本腿**(flush 缓冲 text/reasoning、append `failed` 行)与终态翻转(setTerminal)已不共命运——账本 INSERT 抛不再让 run 卡 running 到 reaper;但那条抛掉的账本行之前是**静默**吞的。detail `arm=` 四臂(flush-clean-cancel / append-clean-cancel / flush-catch / append-thrown-cancel / append-failed);S-947 起续跑腿(`http/resume-legs.ts`)两座同 tag:resume-append-resume-aborted / resume-append-thrown-cancel(取消抛出两臂的 `failed` 行,修前那两臂根本不落账本终局行):缺的是 run_events 尾巴(缓冲文本或 failed 事件),run 行本身仍是 durable truth,读面/reaper 容忍缺失,故 F 类;计数=多少次终局账本写失败。" },
337
- "server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;计数=多少次整段吃尾。" },
337
+ "server.redact.ppk-eat-to-end": { cls: "F", note: "S-107 第三十二轮重扫:PuTTY .ppk 容器扫描器(redact.ts redactPpkContainers)在「无合格终止 / 收口后同容器范围再见私钥字段 / 收口后到下一版本头之间夹非空白」三形下从头**吃到输入末尾**(fail-closed 取证丢弃)——终态 errorMessage 座上=整条错误文案变成 «redacted:private-key»,与 redactions_applied_total{private-key} 的诚实替换同形不可分,故要有自己的计数/探针;影子扫不计;出口座有界行的 head 读法(只扫一段头)上「窗内无合格终止」是终止可能在窗外的未决 —— 照样遮到窗尾,但不是兜底,不计;计数=多少次整段吃尾。" },
338
338
  "server.approval-card.closed-word-out-of-set": {
339
339
  cls: "F",
340
340
  note: "S-249(`approval-card.ts` 的边界窄读族;今天四个成员:`readRuleOffersAbsence`、S-934 ② 起的 `readAskClassPair`(core 7.37.0 #1214 的类对 `askClass` / `classes`,活卡帧 / 呈卡帧的卡 / 运维队列行同一只,判法 = core 屏 `screenAskClassMembers`(S-955 ⑥ / core 7.38.0 #1249,本仓零自写对判),两键同在同缺,半对 / 集外 / 首员不符 / 重复员 = 屏拒 = 读不出;detail 前缀 `askClass=` / `classes=`),与 S-721 起的 `readClosedWordSeat` —— 一座一只 core 成员筛,座是参数:`mandate`(core 7.32.0 #1092,活卡帧 / 活挂起行 / 运维队列行同一只,闭集筛 = core `isPersistedRuleMandate`)与 S-755 起的 `origin`(运维队列行读 park 行孪生座,闭集筛 = core `isAskOrigin`,与 inbox 行 core 投影处同一只筛)、S-1170 起的 `classifierNoRuling`(core 7.43.0 #1353 的成因词,活卡帧读 `AskRequest.classifierNoRuling`,闭集筛 = core `isAutoModeFallbackCause`)与 `differs`(core 7.44.0 #1374,载体是 `runs.ts` 的 409 `steering.duplicate_input_id` 体读的引擎拒错 `detail.differs`,闭集筛 = core `STEER_REPLAY_FACETS` 的非空子集;集外成员 / 空 / 非数组 ⇒ 不铸 `detail`,码仍是契约);detail 前缀 `ruleOffersAbsence=` / `mandate=` / `origin=` / `classifierNoRuling=` / `differs=` 判别是哪一座;第四个成员是 `approval-content-kind.ts` 的 `readApprovalCard` —— 卡词 `approvalCard` 自己的闭集窄读,活 ask / LOCAL 店停驻门 / SQL 店读出的门子树三个载体共用一只,闭集 = core `TOOL_APPROVAL_CARDS`,detail 前缀 `approvalCard=`):活卡帧 / `card_json` / (S-700 起)运维队列行(`parkRowFacts` 读寄存行上的 park 孪生座;队列是热读面 ⇒ 同一行每读一次计一次)的一个**闭集词**座上,键在场而词不在本仓认得的那张表里(世代差,或第三方 producer)⇒ 该座**按缺席**处置。放行的最坏后果=卡上少一行「为什么这次没有规则候选」的解释(壳退回不显示该行),它**不改变任何裁决**——门在引擎里早已判完,本座是展示/观测面;反方向(把一个消费端词表外的值原样上卡)更坏:下游按闭集写的分支会落进 default 臂或渲染出生词。本臂存在的全部理由是**判别**:修前「上游发了个生词」与「上游根本没发这个键」在遥测上同形,而前者意味着本仓的词表该跟着 core 加词了(型面另有两向编译围栏,但那只在**重新编译**时说话,跑在线上的那一版不会自己红)。detail 带座名与那个词(经 redactSecrets 截 40);数 / 布尔等原始值原样,对象 / 数组 / 函数 / null 只报形名(`<object>` / `<array>` / `<function>` / `<null>`,S-700 codex r1:格式化从不调用值自己的转换方法 —— 持久层里一格 `{\"toString\":null}` 曾让诊断本身抛出、打掉整只队列)。",
@@ -351,9 +351,9 @@ export const FAIL_OPEN_TAGS = {
351
351
  cls: "F",
352
352
  note: "S-287(`plugins/ensure-index.ts` 的 PG 臂):这口 PG **协议**面不认 `CREATE INDEX CONCURRENTLY`。今天唯一的真消费者是**仓内离线镜像的手搓 `PgQueryFn` 假件**(`pg-mem` 不是本仓依赖,`test/memory-embedder-fingerprint.test.ts` 头注已为此记过一次档,这里不重复那个错)。判据是**能力面**不是名字,且**只认阳性信号**:SQLSTATE ∈ {`0A000` feature_not_supported、`42601` syntax_error},或错误**完全不带 `code`**(= 不是驱动也不是服务端给的错误)⇒ 判「不认」,回退成普通 `CREATE INDEX IF NOT EXISTS`;**带任何其它 `code` 的一律原样抛** —— SQLSTATE 的 `55P03`/`57014`/`23505`/`42501`/`25001` 与 libuv 的 `ECONNRESET`/`EPIPE`/`ETIMEDOUT` 都在这一侧(⚠️ 不要按「5 位大写」的形状去分这两族:`EPIPE` 恰好也是 5 位大写)。放行的最坏后果 = 这一次建索引持表级 `SHARE` 锁阻塞该表的写(纯**性能/可用性**面,索引本身照样建成、语义零差别),而能走到这条臂的面**按定义**不是生产 PG。计数非零 = 有一口本以为是真 PG 的部署其实不认 CONCURRENTLY,运维要去看它到底是什么。detail = `<表>.<索引名>`。"
353
353
  },
354
- "server.assistant-inbox.objective-ctx-corrupt": {
354
+ "server.assistant-inbox.objective-preview-unreadable": {
355
355
  cls: "F",
356
- note: "S-609(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):某一行的续跑上下文(checkpoint_ctx)读出来是**坏体**(店的拒绝契约 `StoreCorruptError`;local = 档不是 JSON / 信封不合形,SQL = 列是合法 JSON 但不是一只对象)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡不显示它属于哪个任务(纯展示位;这一格不参与任何决裁、续跑或门判 —— 续跑腿与 plan_review 门对同一行各自走读口、坏体一律响亮 500)。不放行的代价是整只 `summaries.map` 抛出 ⇒ 一行坏体打掉整个用户的 inbox。故 F 类;必须留痕,因为修前那句 `.catch(() => null)` 让「坏体」与「本来就没有」同形无痕(detail = 读口转述的 `where`,人拿它去修那一行)。缺席(行不在 / 店抖动)**不**记本 tag:那是读口的刻意保守,不是失败开放。",
356
+ note: "S-1874 / S-2137(`routes/approvals-assistant.ts` 的 `GET /v1/assistant/inbox`):每行的 `objective` = 这道门所属 run 的目标预览 —— 按 run id 直读那一行的 `objectivePreview` 列(写入时已脱敏 + 截断;`GET /v1/assistant/tasks` 读同一列)。门 → run 是按键直读:门所在会话的认领持有者 / 最近一条腿,委派子代的门读派它的那条 run(停驻子代行上的 `parentTaskId`,行经判别器 `findParkedAgentForCheckpoint` 取)。某一行这几次直读失败(店抖动 / 那一行 run 的 `result` 格读不出 —— SQL `getRun` 对坏格刻意抛)⇒ **这一行**的 `objective` 落 `null`,列表其余照常。放行的最坏后果 = inbox 上这一张卡这一次不显示它属于哪个任务(纯展示位;不参与任何决裁、续跑或门判)。不放行的代价是一行 run 读不出就打掉整个用户的待批列表(人看不到要批的卡)。故 F 类;必须留痕,否则「这一次没读成」与「这道门本来就没有 run」同形。detail = 那一行的会话 id 与错误句。前身(7.97.0–7.135.x):同一格读停驻上下文、坏体落 null 的那一只 tag(objective-ctx-corrupt),随 S-1874 退役(inbox 不再读续跑上下文)。",
357
357
  },
358
358
  "server.run-store.corrupt-record-skipped": {
359
359
  cls: "F",
@@ -433,7 +433,7 @@ export const FAIL_OPEN_TAGS = {
433
433
  },
434
434
  "server.snapshot-bucket.presence-unproven": {
435
435
  cls: "F",
436
- note: "S-1195(7.119.0;`plugins/snapshot-bucket-presence.ts` 的 `ensureSnapshotBucket`,boot 期核一次快照桶 `SESSION_SNAPSHOT_BUCKET`;detail = `bucket=… endpoint=<buildDisplaySafeUrl 的显示形> <HTTP 状态 | request failed: 错类>`):presigned 列举对 `MINIO_ENDPOINT` **连不上 / 403 / 5xx / 没有 NoSuchBucket 的 404**,或探针到窗尾一次都没答过(detail 写 `probe still pending when the window ended`)⇒ **证明不了桶缺**,不拒启、照常起服(只有 404 + `NoSuchBucket` 才是「缺」,那一形窗内重试、到窗拒启,不走本 tag)。三问:①错误落到哪 —— 本 tag 的计数 / 探针行 / 记录器的一次性 warn,detail 带端点显示形与状态或错类;②谁受害 —— 桶其实不存在、或凭据不能列举的部署:之后每一次快照 offload 写与 SendUserFile 私有 presigned 请求会答 404 / 403,孤儿 sweep 每分钟一条失败,运维读到的是一串失败请求而不是「桶没建」(与 7.118.x 的既有形状同形,不是新损失);③缺席倒向哪边 —— 倒向**放行起服**:没有证据既不拒启、也不伪造「桶在场」或「桶缺」,不是门 / 权限 / 凭证。最坏后果 = 缺桶的部署晚一步才暴露;不放行的代价是对象存储一次抖动 / 一次 403 就让整只服务起不来。计数非零 = 这台部署启动时没能确认快照桶,按 detail 的端点与状态去核对网络 / 凭据 / 桶。",
436
+ note: "S-1195(7.119.0;`plugins/snapshot-bucket-presence.ts` 的 `ensureSnapshotBucket`,boot 期核一次快照桶 `SESSION_SNAPSHOT_BUCKET`;S-2021〔7.136.0〕起同一只探针、同一条规则、同一个 tag 也核 k8s 车道的工作区快照桶 `WORKSPACE_SNAPSHOT_BUCKET`——detail 里的 `bucket=` 就是被核的那只桶名;detail = `bucket=… endpoint=<buildDisplaySafeUrl 的显示形> <HTTP 状态 | request failed: 错类>`):presigned 列举对 `MINIO_ENDPOINT` **连不上 / 403 / 5xx / 没有 NoSuchBucket 的 404**,或探针到窗尾一次都没答过(detail 写 `probe still pending when the window ended`)⇒ **证明不了桶缺**,不拒启、照常起服(只有 404 + `NoSuchBucket` 才是「缺」,那一形窗内重试、到窗拒启,不走本 tag)。三问:①错误落到哪 —— 本 tag 的计数 / 探针行 / 记录器的一次性 warn,detail 带端点显示形与状态或错类;②谁受害 —— 桶其实不存在、或凭据不能列举的部署:之后每一次快照 offload 写与 SendUserFile 私有 presigned 请求会答 404 / 403,孤儿 sweep 每分钟一条失败,运维读到的是一串失败请求而不是「桶没建」(与 7.118.x 的既有形状同形,不是新损失);工作区快照桶则是每条 run 读 `latest` 得 404/403——404 + NoSuchBucket 在运行期由 `workspace_snapshot_bucket_missing` 响亮拒绝,403/5xx 走既有的非 404 读失败臂(同样响亮);③缺席倒向哪边 —— 倒向**放行起服**:没有证据既不拒启、也不伪造「桶在场」或「桶缺」,不是门 / 权限 / 凭证。最坏后果 = 缺桶的部署晚一步才暴露;不放行的代价是对象存储一次抖动 / 一次 403 就让整只服务起不来。计数非零 = 这台部署启动时没能确认快照桶,按 detail 的端点与状态去核对网络 / 凭据 / 桶。",
437
437
  },
438
438
  "server.fleet.serving-lease-unconfirmed": {
439
439
  cls: "R",
@@ -459,6 +459,18 @@ export const FAIL_OPEN_TAGS = {
459
459
  cls: "F",
460
460
  note: "k8s 车道工作区空间结束时对**快照对象**的回收(`plugins/remote-env-k8s.ts` `createSpaceReclaimer` 的对象部分;与上一条同一只回收座、两条腿:会话删除协调器与托管留存 sweep):按空间的 `<前缀>/<化身>/space/` 分区(快照 + `latest`)和该会话停驻车道里名字带被结束住客归属记号的 `<前缀>/<会话 id>/*.tar.gz` 分别 LIST、逐个预签 DELETE;LIST 失败、DELETE 失败(HTTP 非 2xx / 非 404、网络)LIST 超过 1000 个对象被截断,或回收座一次调用的绝对截止(= 快照时限;卷回收也在内)到点〔切断在途请求、停掉余下的;这次调用里还没轮到的空间零请求、各记一次;没删的归生命周期规则〕,各记一次(detail `lane=space|park stage=list|delete|budget`;会话删除口把回收座脱离应答路径,座若抛了——契约禁止——记 `stage=seat_threw`)⇒ 这些对象留在桶里,会话照删(不挡删除)。放行的最坏后果 = 一份已无人能读的工作区快照留在桶里(会话已删,经 API 不可达;内容是用户工作区,所以不是「只占盘」—— 留到桶的生命周期规则把它过期掉为止);不碰任何别的空间、不是门不是凭据;反方向(清不掉对象就拒删会话)会让一个对象存储抖动挡住用户的删除权。故 F 类,但必须留痕:静默吞掉之后「这个会话删了、它的工作区内容还在不在桶里」在遥测里没有任何痕迹。同拍一行 `workspace_space_reclaim_failed`(`space` 前缀 / `sessionId` / `lane` / `stage` / `count` / `error`;一次调用里同类〔分区 × 阶段〕的失败合成一行,`count` = 撞上它的空间数,其余键点名调用里排第一的那个,计数仍逐空间;回收座同一时刻最多 4 个空间在收,sweep 与会话删除共用这一只门)。计数非零 = 有对象没回收掉,运维按行里的空间前缀 / 会话 id 去桶里手清,并核对象存储的可达性与 DELETE 权限。回收与同一空间的尚在途的 run 末提交不是互斥的:会话删除时没有活 run(409 挡),但上一条 run 的提交若恰在删除之后才落对象,这批对象不在本次回收射程内(同样归桶的生命周期规则)。停驻分区按会话 id 分区,所以只删名字带被结束住客归属记号的对象(不读任何钟)—— 会话 id 删后被别人重新登记,新住客停驻出的快照带它自己的记号、不碰;不带记号的旧形与 `none` 留给桶的生命周期规则。",
461
461
  },
462
+ "server.sandbox-secret.seat-unverified": {
463
+ cls: "F",
464
+ note: "k8s 车道沙箱秘密座的起服核(`boot/execution-env.ts` `ensureSandboxSecretSeat`,第 5 段 await;只在 `K8S_SANDBOX_ENV` 点到了本进程环境里有的键时问):对沙箱命名空间 secrets 的 env 座那一行动词(create —— 适配器为 env Secret 真发的调用)各发一次 SelfSubjectAccessReview,**问不到**(请求失败 / 审查本身被拒 / 答复没有布尔 `status.allowed`)而没有一个动词答「不许」⇒ 照常起服(答了「不许」⇒ 拒启,不走本 tag)。放行的最坏后果 = RBAC 真缺时第一个要建 Secret 的 run 在连接时被拒(`auth_failed`)、它的 pod 被删 —— 名单值仍然只经该 pod 自己的 Secret,不回落成 pod spec 里的明文;不是门不是凭据的放宽,只是把「缺 RBAC」从起服挪到了第一次连接。detail = `namespace=<ns>` + 每个没问到的动词与原因(不带值)。同拍一行 `sandbox_secret_seat_unverified`。计数非零 = 这台部署起服时没能确认 Secret 动词,按 detail 核 API 可达性与该服务账号能不能建 SelfSubjectAccessReview。",
465
+ },
466
+ "server.sandbox-pod-reaper.list-failed": {
467
+ cls: "F",
468
+ note: "k8s 车道沙箱 Pod 回收器(`plugins/k8s-pod-reaper.ts` `reapOnce`,维护 tick 每副本每 5 分钟一轮)按身份标签列沙箱命名空间的 Pod **失败**(API 不可达 / 403 / 非 2xx)⇒ 这一轮一只都不删、不抛,下一轮一个周期后照常再列。最坏后果 = 已结束的沙箱 Pod 与已终 run 的 Pod(连同它们以 `ownerReferences` 挂着的 Secret —— 部署名单值 / 成员凭据租约)在 etcd 里多留至少一个周期:是**静态数据**多留,不是凭据被用 —— 成员凭据的使用界是代理的租约(到点 destroy,不随回收器);run 收尾的正常删除路不经本腿。不是门 / 凭据的放宽 ⇒ F。计数逐轮一次 + 探针行(`namespace=` + 原因)+ 同拍一行 warn `sandbox_pod_reap_list_failed`;修前只一行 info 轮次行(`listed:false`),不计数、不响。计数持续非零 = 回收器连续列不到 Pod,按原因核 API 可达性与 Role 的 pods `list`。",
469
+ },
470
+ "server.sandbox-pod.delete-failed": {
471
+ cls: "F",
472
+ note: "k8s 车道**删一只沙箱 pod 却没删成**,两个座一只上报口(`plugins/k8s-pod-delete-failure.ts` `reportPodDeleteFailed`;detail = `namespace=<ns> pod=<名> why=<原因词> cause=<http_<状态>|request_failed>`):①连接清理座(`plugins/remote-env-k8s.ts` `discardRefusedPod`,`why` = `env_secret_unsealed|lease_secret_unsealed|broker_image_pull_failed`):env Secret / 租约 Secret 建不成、或代理镜像拉不下 ⇒ 连接被拒、该 pod 要删,而这一删本身失败(请求抛 / API 回 403、5xx 等非 2xx 非 404)⇒ 拒答不变(仍是原来那一条),pod 与挂在它名下的 Secret(部署名单值 / 租约占位)留在命名空间里,由回收器兜(`plugins/k8s-pod-reaper.ts`:带 run 标签的按 run 终态删,不带的等容器全部结束后删);②回收器座(`plugins/k8s-pod-reaper.ts` `reapOnce` 的删一步,`why` = `reaper_containers_ended|reaper_run_ended_past_hold`):兜底本身删失败 ⇒ 这一只留到下一轮(一个周期后)再判再删,一只失败不挡同轮其余。最坏后果 = **静态数据**多留至少一个回收周期(与 `server.sandbox-pod-reaper.list-failed` 同一件事的另一半):没有值进 pod spec、没有连接被放过、成员凭据的使用界是代理的租约 ⇒ 不是门 / 凭据的放宽 ⇒ F。计数逐次 + 探针行 + 同拍一行 error `sandbox_pod_delete_failed`(pod / 命名空间 / 原因词 / 原因码,不带值、不带 API 文本;运维行座缺席或只有 warn 的座上行不发、计数照记);修前连接清理三处 `.catch(() => {})` 吞,零日志零计数,回收器的删失败只落一行 warn(不计数)。计数持续非零 = 删 pod 的 Role 动词(pods `delete`)或 API 可达性有问题,按 `cause` 核;`why` 以 `reaper_` 起头的计数持续非零 = 兜底本身删不动,残留会随轮次累积。",
473
+ },
462
474
  "server.sandbox-egress.policy-gap": {
463
475
  cls: "P-DEBT",
464
476
  note: "k8s 车道沙箱出网的起服自检(`plugins/remote-env-k8s.ts` `checkSandboxEgressPolicy` 读节点表与沙箱 ns 的 NetworkPolicy,判法 `sandbox-egress-policy.ts` `sandboxEgressGaps`;起服一次、不挡起服)读出一个缺口 —— 某节点的 InternalIP / ExternalIP 落在一条放行 ipBlock 内(`kind=node_address_open`)、没有策略以 Egress 选中沙箱 pod(`kind=policy_absent`)、pod 保留 IPv6 而策略层担保不了 v6(`kind=ipv6_enabled`)、节点表或策略表读不到(`kind=unreadable what=nodes|networkpolicies`)⇒ 车道**照常起**,每个缺口一行 `sandbox_egress_policy_gap`(带后果句与去哪改)。归 P 类:出网策略是保护面,缺口意味着沙箱里的代码可能够到节点上的服务;归 DEBT 的理由:修法都在集群配置里(节点地址点名、kubelet 放行 v6 sysctl、CNI 的 v6 执行、RBAC),本服务改不了,而拒起车道会让缺省单节点装法(恒有 `ipv6_enabled`)升级即停摆沙箱执行 —— 车道自己的隔离(VM 边界、零 SA token)仍在。放行的最坏后果 = 读出缺口的那一面照旧可达,直到运维按行里的后果句改配置。计数非零 = 这台部署的沙箱出网面有读得出的缺口。读不出的面(宿主 DNAT 发布端口、「pod 所在节点恒放行」的实现差异、CNI 根本不执行策略)不在本 tag 的射程里。",
@@ -262,7 +262,6 @@ export function createMetrics() {
262
262
  m.counter("resume_anchor_capture_failed", "E18 resume-at per-turn anchor captures that failed (best-effort, run unaffected)");
263
263
  m.counter("brain_retry_total", "Brain-layer status events (S4), by phase (rate_limited/retrying/reconnecting/circuit_open/waiting_first_token/recovered/gave_up)");
264
264
  m.gauge("store_backend_degraded", "1 when the auto DB probe failed at boot and this replica degraded to in-memory (S5)");
265
- m.counter("images_omitted_total", "Tasks whose images will be degraded to text placeholders (no-vision model), by model (S6)");
266
265
  m.counter("runs_reaped_total", "Run rows flipped by the background reapers (S7), by kind (stale/suspended/expired_checkpoint)");
267
266
  m.counter("checkpoints_reaped_total", "Durable checkpoints expired by the backstop reaper (S7)");
268
267
  m.counter("user_input_converged_total", "User-input acceptances (S-928 ledger) converged to crashed by the maintenance tick (the boot pass reports its count on the user_input_ledger_crash_converged warn line instead): steers accepted whose writer process ended before the run settled");
@@ -291,7 +290,7 @@ export function createMetrics() {
291
290
  m.counter("permission_rule_events_total", buildPermissionRuleEventsHelp());
292
291
  m.counter("compaction_events_total", "Compaction lifecycle events by outcome (started/completed/failed/skipped) and trigger");
293
292
  m.counter("hook_llm_calls_total", "prompt/agent hook entries that completed a model call (hooks 3b), by type");
294
- m.counter("auth_bridge_verify_total", "Registry SSO JWT verifications (auth-bridge), by outcome");
293
+ m.counter("auth_bridge_verify_total", "Registry SSO JWT verifications (auth-bridge), by outcome: 'ok', or the refusal word from one closed set (settings-schema api/auth-bridge AUTH_BRIDGE_ERROR_CODES + api/scopes TOKEN_SCOPE_REFUSALS); a refusal sets no identity");
295
294
  m.counter("fleet_rows_reconciled_total", "Fleet rows the #261 liveness-reconcile leg acted on, by `disposition`: 'retired' = durable was terminal, the leg projected the settled pair (terminal task frame → task_remove); 'retired_deferred' = a row that had already left declaratively (removeReason:\"orphaned\") got its settled pair BACKFILLED once durable turned terminal; 'orphaned' / 'orphaned_child' = declarative exit, NO terminal was minted (the reaper owns the death ruling); 'workflow_retired' = same projection for a workflow row. `trigger` rides ONLY the retired_deferred series (S-450): 'event' = the backfill fired on the same tick a terminal write landed through the run-store write口 (single replica), 'cycle' = it fired on the 60s reconcile cycle (cross-replica writes, and terminals flipped by the BATCH reapers, which never pass through the write口). A retired_deferred series that is 100% trigger=cycle means the event leg is not wired or this deployment\'s terminals all come from those two families");
296
295
  m.counter("fleet_lease_issued_total", "Fleet trace-lease tokens issued");
297
296
  m.counter("fleet_lease_rejected_total", "Fleet trace-lease requests rejected");
@@ -303,6 +302,7 @@ export function createMetrics() {
303
302
  m.counter("remote_env_exec_idle_timeout_total", "Remote-env exec idle timeouts, by provider");
304
303
  m.counter("remote_env_file_timeout_total", "Remote-env file-op timeouts, by provider");
305
304
  m.counter("remote_env_rpc_timeout_total", "Remote-env RPC timeouts, by provider");
305
+ m.counter("sandbox_pods_reaped_total", "Sandbox pods deleted by the k8s lane's pod reaper, by reason (containers_ended: every container finished; run_ended_past_hold: the pod's run ended longer ago than the hold)");
306
306
  m.counter("task_outcomes_total", "Task outcome-ledger rows recorded, by status and oracle green flag");
307
307
  m.counter("workflow_runs_total", "Self-orchestration workflow runs, by status");
308
308
  m.histogram("auth_bridge_verify_seconds", "Registry SSO JWT verify latency (hot path sub-ms; cold path includes a JWKS fetch)", [0.001, 0.005, 0.02, 0.1, 0.5, 2, 10]);
@@ -7,7 +7,10 @@
7
7
  */
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
9
9
  export declare const principalContext: AsyncLocalStorage<string>;
10
- /** Run `fn` with `principal` as the ambient context (no-op wrapper when principal is undefined). */
10
+ /** 这次执行的记账身份钉表;不在任何 {@link withPrincipal} 作用域里 ⇒ `undefined`(记账按此刻身份)。 */
11
+ export declare function currentExecutionPins(): Map<object, string> | undefined;
12
+ /** Run `fn` with `principal` as the ambient context (principal-wise a no-op wrapper when principal is undefined — the outer
13
+ * principal, if any, stays). [ref]: also opens the execution's accounting-pin scope when none is open yet (nested calls share it). */
11
14
  export declare function withPrincipal<T>(principal: string | undefined, fn: () => T): T;
12
15
  /** The principal attributed to the current async execution, if any. */
13
16
  export declare function currentPrincipal(): string | undefined;
@@ -1,7 +1,12 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  export const principalContext = new AsyncLocalStorage();
3
+ const executionPins = new AsyncLocalStorage();
4
+ export function currentExecutionPins() {
5
+ return executionPins.getStore();
6
+ }
3
7
  export function withPrincipal(principal, fn) {
4
- return principal ? principalContext.run(principal, fn) : fn();
8
+ const run = () => (principal ? principalContext.run(principal, fn) : fn());
9
+ return executionPins.getStore() !== undefined ? run() : executionPins.run(new Map(), run);
5
10
  }
6
11
  export function currentPrincipal() {
7
12
  return principalContext.getStore();
@@ -153,6 +153,7 @@ const RESULT_KEY_TREATMENT = {
153
153
  retryAfterMs: "verbatim",
154
154
  degraded: "verbatim",
155
155
  structuredOutput: "verbatim",
156
+ resultEntryIds: "verbatim",
156
157
  rewindNotes: "redacted",
157
158
  haltedOnUserRejection: "verbatim",
158
159
  haltedByUser: "verbatim",
@@ -82,8 +82,8 @@ export declare function requestAlwaysHumanAsk(rule: string, message: string): Op
82
82
  * 只加一位、从不放宽)。同步叶保持同步返回;叶抛照抛(fail-closed 的属主仍是叶)。
83
83
  *
84
84
  * **只收叶**:治理层(`runtime-governance.ts` `applyRuntimeGovernance`)对每一片叶各盖一次,再交 core `combinePolicies` 折**一次**
85
- * (折叠是最外层,其输出原样交回 —— core 把落选 ask 的类(含本位带出的 `operator_forced`)写进 `foldedAskClasses` 并担保那只对象)。
86
- * 把一只折叠交给本函数 = 在折叠的输出上展开 —— 那正是 [ref] 修掉的病(7.39.0 起按名拒,响亮、fail-closed,不是静默)。
85
+ * (折叠是最外层,其输出原样交回 —— core 把落选 ask 的类(含本位带出的 `operator_forced`)记进它铸的认证记录 `attestation.foldedClasses`)。
86
+ * 把一只折叠交给本函数 = 在折叠的输出上再盖一次出身(只改幸存者自己的类)—— [ref] 收成「逐叶盖、折一次」,治理层只有一个折叠点。
87
87
  */
88
88
  export declare function createDeploymentAuthoredPolicy(leaf: ToolPolicy): ToolPolicy;
89
89
  //# sourceMappingURL=operator-ask.d.ts.map
@@ -16,7 +16,7 @@ async function shareAnOrg(directory, a, b) {
16
16
  return false;
17
17
  return Object.keys(la.scopes).some((org) => Object.hasOwn(lb.scopes, org));
18
18
  }
19
- const principalShaped = (s) => s !== "" && principalShapeProblem(s) === undefined;
19
+ const principalShaped = (s) => principalShapeProblem(s) === undefined;
20
20
  const fitsInboxKey = (recipient) => mailboxKeyProblem("scope", recipient) === undefined;
21
21
  export function createOrgDeliveryGate(deps) {
22
22
  const nowMs = deps.nowMs ?? Date.now;
@@ -19,8 +19,8 @@ import type { MemoryScopeAdmission } from "@sema-agent/core";
19
19
  /**
20
20
  * 单机腿:装载 `MEMORY_ORG_DIRECTORY_JSON`(`Record<principal, Record<orgScope, {write?}>>`)。启动期调用,
21
21
  * **fail-loud**——任何非法直接 throw 且消息点名坏在哪个键(响亮拒是正确方向:一张半坏的授权表比拒绝
22
- * 启动更危险)。principal 键复用 {@link assertPrincipalShape}(拒保留哨兵/超长租户名,与所有其它
23
- * principal 入口同一道闸);scope 键必须是 `org:` 前缀、值必须是仅含可选 `write: boolean` 的 plain object。
22
+ * 启动更危险)。principal 键复用 {@link assertPrincipalShape}(拒不是名字的串 / 保留哨兵 / 超长租户名,与所有其它
23
+ * principal 入口同一道闸;拒句按键在 JSON 文本里的出现序点名,不回显不是名字的键 —— 对象的枚举序会把整数形的键排到最前,不能拿它编号);scope 键必须是 `org:` 前缀、值必须是仅含可选 `write: boolean` 的 plain object。
24
24
  */
25
25
  export declare function parseOrgDirectoryStatic(json: string): Map<string, Record<string, {
26
26
  write?: boolean;
@@ -2,6 +2,37 @@ import { PrincipalOrgMemoryWire } from "@sema-agent/settings-schema";
2
2
  import { assertPrincipalShape } from "./security.js";
3
3
  const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
4
4
  const ORG_SCOPE_RE = /^org:\S+$/;
5
+ function topLevelKeysInTextOrder(json) {
6
+ const keys = [];
7
+ let depth = 0;
8
+ let expectKey = false;
9
+ for (let i = 0; i < json.length; i++) {
10
+ const c = json[i];
11
+ if (c === '"') {
12
+ let j = i + 1;
13
+ while (json[j] !== '"')
14
+ j += json[j] === "\\" ? 2 : 1;
15
+ if (depth === 1 && expectKey) {
16
+ const key = JSON.parse(json.slice(i, j + 1));
17
+ if (typeof key !== "string")
18
+ throw new Error("MEMORY_ORG_DIRECTORY_JSON: a top-level key did not decode as a string (scanner out of step with the JSON text)");
19
+ keys.push(key);
20
+ expectKey = false;
21
+ }
22
+ i = j;
23
+ }
24
+ else if (c === "{" || c === "[") {
25
+ depth++;
26
+ if (depth === 1)
27
+ expectKey = c === "{";
28
+ }
29
+ else if (c === "}" || c === "]")
30
+ depth--;
31
+ else if (c === "," && depth === 1)
32
+ expectKey = true;
33
+ }
34
+ return keys;
35
+ }
5
36
  export function parseOrgDirectoryStatic(json) {
6
37
  let parsed;
7
38
  try {
@@ -14,8 +45,14 @@ export function parseOrgDirectoryStatic(json) {
14
45
  throw new Error("MEMORY_ORG_DIRECTORY_JSON must be a JSON object: Record<principal, Record<orgScope, {write?: boolean}>>");
15
46
  }
16
47
  const table = new Map();
17
- for (const [principal, scopesRaw] of Object.entries(parsed)) {
18
- assertPrincipalShape(principal);
48
+ for (const [n, principal] of topLevelKeysInTextOrder(json).entries()) {
49
+ try {
50
+ assertPrincipalShape(principal);
51
+ }
52
+ catch (e) {
53
+ throw new Error(`MEMORY_ORG_DIRECTORY_JSON principal key #${n + 1} (in text order): ${e instanceof Error ? e.message : String(e)}`, { cause: e });
54
+ }
55
+ const scopesRaw = parsed[principal];
19
56
  if (!isPlainObject(scopesRaw)) {
20
57
  throw new Error(`MEMORY_ORG_DIRECTORY_JSON[${JSON.stringify(principal)}] must be an object of org scopes`);
21
58
  }
@@ -142,9 +142,10 @@ export declare function findParkedAgentForCheckpoint(agentStore: BackgroundAgent
142
142
  }): Promise<ParkedAgentMatch | undefined>;
143
143
  export interface ParkedDecideDeps {
144
144
  agentStore: BackgroundAgentStore;
145
- checkpointStore: Pick<CheckpointStore, "get">;
146
- /** 与 reaper/scenarios 同一个 registry(claim/rollback 的 CAS 与 in-memory handle 收敛在同处)。 */
147
- registry: Pick<TaskRegistry, "claimParkedAgent" | "rollbackParkedClaim">;
145
+ /** `expire`:[ref] deny 作废复活不了的卡(按卡自己的 scope CAS);`get`:引擎的 claim / 对账读卡。 */
146
+ checkpointStore: Pick<CheckpointStore, "get" | "expire">;
147
+ /** 与 reaper/scenarios 同一个 registry(claim/rollback 的 CAS 与 in-memory handle 收敛在同处;[ref] 作废之后的对账也是它)。 */
148
+ registry: Pick<TaskRegistry, "claimParkedAgent" | "rollbackParkedClaim" | "reconcileParkedAgents">;
148
149
  /** [ref](core 7.32.0):按**行**解析赎回面 —— 驱动哪一只 Agent 工具(生产装配,与 spawn 同源:subRunner / bg opts /
149
150
  * checkpointStore 同实例,[ref] 设计稿 §4),以及与它同出一张列表的继承座 `mountsCallerTool`(一层行才有;属主与全部判据
150
151
  * 在 `parked-revive-surface.ts`)。修前是 boot 期的一只裸 Agent 工具、从不交座 ⇒ 7.32.0 起停在继承工具上的 allow 恒 422。 */
@@ -4,7 +4,7 @@ import { routeWorkflowPark } from "./orchestration/workflow-agent-session-index.
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
6
6
  import { backgroundScopesForCheckpointScope, decodeCheckpointScope, ownerOfCheckpointScope } from "./security.js";
7
- import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
7
+ import { ASK_USER_QUESTION_TOOL_NAME, isEngineQuestionRow } from "./approval-content-kind.js";
8
8
  import { sessionFaceOfHead } from "./session-incarnation.js";
9
9
  import { refusalBody, resumeRefusalFacts } from "./http/refusal-body.js";
10
10
  export async function findParkOrigin(sources, cp) {
@@ -120,12 +120,13 @@ export function buildParkedResumeOutcome(req) {
120
120
  const pa = req.pendingAction;
121
121
  const persistedCallId = pa?.kind === "tool_approval" ? (pa.toolCallId ?? "") : "";
122
122
  const persistedHash = pa?.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
123
+ const engineQuestion = isEngineQuestionRow(req.pendingAction);
123
124
  return {
124
125
  gate: "policy_ask",
125
126
  boundCallId: req.binding?.boundCallId ?? persistedCallId,
126
127
  boundInputHash: req.binding?.boundInputHash ?? persistedHash,
127
128
  decision: req.decision === "approve" ? "allow" : "deny",
128
- ...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
129
+ ...(req.decision === "approve" && !engineQuestion && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
129
130
  ...(req.binding?.feedback !== undefined ? { feedback: req.binding.feedback } : {}),
130
131
  ...(req.answer !== undefined ? { answer: req.answer } : {}),
131
132
  ...(req.reason !== undefined ? { reason: req.reason } : {}),
@@ -231,6 +232,28 @@ function sessionGone(taskId) {
231
232
  }),
232
233
  };
233
234
  }
235
+ function revivabilityOf(row, taskId) {
236
+ if (row.name === undefined) {
237
+ return { kind: "unrevivable", approveRefusal: { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "parked row carries no agent name — the revive path requires a named agent, so this approval cannot be approved; deny it to close the card (the agent then ends as failed)", facts: { taskId } }) } };
238
+ }
239
+ if (row.rootSessionId === undefined) {
240
+ return { kind: "unrevivable", approveRefusal: { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to, so it cannot be approved; deny it to close the card (the agent then ends as failed)", facts: { taskId } }) } };
241
+ }
242
+ return { kind: "revivable", root: row.rootSessionId };
243
+ }
244
+ async function voidUnrevivableCard(deps, req, match) {
245
+ const voided = await deps.checkpointStore.expire(req.token, req.scope);
246
+ if (!voided) {
247
+ return { status: 409, body: refusalBody({ errorCode: "decide.gate_moved", message: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", facts: { taskId: match.handle } }) };
248
+ }
249
+ try {
250
+ await deps.registry.reconcileParkedAgents({ agentStore: deps.agentStore, checkpointStore: deps.checkpointStore }, match.row.scope, Date.now());
251
+ }
252
+ catch (e) {
253
+ quiet(deps.warn, "parked_unrevivable_reconcile_failed", { taskId: match.handle, error: e instanceof Error ? e.message : String(e) });
254
+ }
255
+ return { status: 200, body: { taskId: match.handle, status: "failed", decision: "deny" } };
256
+ }
234
257
  export async function decideParkedAgent(deps, req, match) {
235
258
  const row = match.row;
236
259
  const rememberRoot = req.grantRemember !== undefined ? grantRootOf(row) : undefined;
@@ -241,13 +264,10 @@ export async function decideParkedAgent(deps, req, match) {
241
264
  if (pendingTool === ASK_USER_QUESTION_TOOL_NAME && req.decision === "approve" && req.answer === undefined) {
242
265
  return { status: 400, body: refusalBody({ errorCode: "decide.parked_answer_required", message: "pending action is AskUserQuestion — approve requires body.answer (the operator's answers[]); deny needs none", facts: { taskId: match.handle } }) };
243
266
  }
244
- if (row.name === undefined) {
245
- return { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "parked row carries no agent name — the revive path requires a named agent (row damaged?)", facts: { taskId: match.handle } }) };
246
- }
247
- const root = row.rootSessionId;
248
- if (root === undefined) {
249
- return { status: 409, body: refusalBody({ errorCode: "decide.row_unrevivable", message: "this agent's row carries no root session anchor — the revive path cannot tell which session this approval belongs to (row damaged?)", facts: { taskId: match.handle } }) };
250
- }
267
+ const revivability = revivabilityOf(row, match.handle);
268
+ if (revivability.kind === "unrevivable")
269
+ return req.decision === "deny" ? voidUnrevivableCard(deps, req, match) : revivability.approveRefusal;
270
+ const root = revivability.root;
251
271
  const faceBefore = sessionFaceOfHead(root, await deps.headOf(root));
252
272
  if (faceBefore === undefined)
253
273
  return sessionGone(match.handle);
@@ -50,7 +50,8 @@
50
50
  * · `sessionId` = 会话行 id;过不了 core `isPeerSessionId` 的行、以及**箱句柄不是本 scope 独占**的行
51
51
  * (非小写规范形 / 与别的会话折到同一只箱)**不入视图并计数披露**(不静默,见下方单射门);
52
52
  * · `scope` = 查询参数原样(视图永不跨 scope —— 这就是设计稿 §4-3 那堵墙的全部实现);
53
- * · `name` = `title`,空 ⇒ `session-<id 前 8>`,`nameSource: "auto"`;
53
+ * · `name` = `title`,「空」= 经 core 的 `normalizePeerName` 为空(NFKC → 去控制 / 格式码点 → trim;纯空白、纯零宽 / 方向控制符都算空)⇒ `session-<id 前 8>`,
54
+ * `nameSource: "auto"`;墓碑行的 `label` 同一条判据;
54
55
  * · `startedAt`/`updatedAt` = 会话行的 `firstActivityAt`/`lastActivityAt`;
55
56
  * · `liveness` = **会话行在 ⇒ `"live"`**;`"deleted"` 来自 mailbox 墓碑(= 删除级联**在飞**那段窗口;
56
57
  * 墓碑随箱亡,级联跑完之后那个 id 就是「不存在」—— 理由全文在 `plugins/mailbox-recipient-lifecycle.ts`),
@@ -1,9 +1,9 @@
1
- import { PEER_SESSION_RECORD_SCHEMA_VERSION, isPeerSessionId, peerSessionBoxHandle, SESSION_BOX_PREFIX, } from "@sema-agent/core";
1
+ import { PEER_SESSION_RECORD_SCHEMA_VERSION, isPeerSessionId, normalizePeerName, peerSessionBoxHandle, SESSION_BOX_PREFIX, } from "@sema-agent/core";
2
2
  import { sessionTempoOfRunStatus } from "./plugins/store-contracts.js";
3
3
  import { legacyUnownedAmnestyOpen } from "./security.js";
4
4
  export const PEER_DIRECTORY_CAP = 200;
5
- function autoName(sessionId) {
6
- return `session-${sessionId.slice(0, 8)}`;
5
+ function displayNameOf(candidate, sessionId) {
6
+ return candidate != null && normalizePeerName(candidate) !== "" ? candidate : `session-${sessionId.slice(0, 8)}`;
7
7
  }
8
8
  function sessionIdOfBoxHandle(handle) {
9
9
  return handle.slice(SESSION_BOX_PREFIX.length);
@@ -80,11 +80,12 @@ export function createPeerDirectory(ctx) {
80
80
  schemaVersion: PEER_SESSION_RECORD_SCHEMA_VERSION,
81
81
  sessionId: r.sessionId,
82
82
  scope,
83
- name: r.title !== null && r.title.trim() !== "" ? r.title : autoName(r.sessionId),
83
+ name: displayNameOf(r.title, r.sessionId),
84
84
  nameSource: "auto",
85
85
  startedAt: Number.isFinite(started) ? started : 0,
86
86
  updatedAt: Number.isFinite(updated) ? updated : 0,
87
87
  liveness: "live",
88
+ where: "hosted",
88
89
  ...(tempo !== undefined ? { tempo } : {}),
89
90
  inboundPosture: posture,
90
91
  });
@@ -120,11 +121,12 @@ export function createPeerDirectory(ctx) {
120
121
  schemaVersion: PEER_SESSION_RECORD_SCHEMA_VERSION,
121
122
  sessionId,
122
123
  scope,
123
- name: t.label !== undefined && t.label.trim() !== "" ? t.label : autoName(sessionId),
124
+ name: displayNameOf(t.label, sessionId),
124
125
  nameSource: "auto",
125
126
  startedAt: t.retiredAt,
126
127
  updatedAt: t.retiredAt,
127
128
  liveness: "deleted",
129
+ where: "hosted",
128
130
  diedAt: t.retiredAt,
129
131
  inboundPosture: posture,
130
132
  });
@@ -40,7 +40,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
40
40
  * `namespacedCoveringEntries/Hit`。同族分家,差的只是解析器。
41
41
  *
42
42
  * ⇒ **规则表的 policy 面现在同源**:两种态度都由 `createPermissionRulePolicy` 的 covering 车道解,底下是
43
- * core 的**同一只** `namespacedRuleNameCovers`(协议无关:`mcp__`/`a2a__` 同解;peer 段精确比;工具段 `*`/glob 覆盖)。
43
+ * core 的**同一只** `ruleNameCovers`(协议无关:`mcp__`/`a2a__` 同解;peer 段精确比;工具段 `*`/glob 覆盖)。
44
44
  * 本仓**不自写第二套 glob** —— 那正是当年分家的成因。
45
45
  *
46
46
  * ⚠️ 「同源」的**射程**说准了免得下一个人当成全仓结论(codex 交叉轮 finding-1 追出来的边):
@@ -51,7 +51,7 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
51
51
  * 所以各表收 `mcp__figma__*`(等价、且被推荐)、拒 `mcp__figma`。
52
52
  * · **同源的射程已扩到 operator 名单腿**([ref] 孪生修,[ref]):`createDurableAskPolicy`(`src/approval.ts`)的
53
53
  * deny/neverAuto/require 三集合过去是逐字 `Set.has`,自 core 5.60.1 谓词根导出后改成「精确 ∪ covering」,用的是
54
- * **同一只** `namespacedRuleNameCovers`。⇒ env `APPROVAL_*` 腿 / 中心下发 `runtime.approvalRequire` 腿 / 规则表三家
54
+ * **同一只** `ruleNameCovers`。⇒ env `APPROVAL_*` 腿 / 中心下发 `runtime.approvalRequire` 腿 / 规则表三家
55
55
  * 现在对 covering 拼法的解读逐字一致,`mcp__figma__*` 在哪条腿上都是真判。
56
56
  * · **两段形为什么仍不放开**(理由已换,别再引「approval.ts 还不认」):它一名两义——「整台服务器」与「三段名写漏一段」
57
57
  * 在 boot 期不可分辨,所以各条腿一律响亮拒 + 指引给无歧义的 `mcp__figma__*`。放宽是各腿同时变宽的 behavior-facing
@@ -428,6 +428,14 @@ export interface BatchRow {
428
428
  export type AskCallCoordinate = Pick<AskRow, "taskId" | "legKey" | "toolCallId" | "sourceTaskId">;
429
429
  /** 「这一行在这次调用上」的唯一判据(四列逐字相等):InMemory twin 的筛子与 SQL twin 的出口对齐读同一只。 */
430
430
  export declare function askRowOnCall(row: AskRow, call: AskCallCoordinate): boolean;
431
+ /**
432
+ * 🔴 [ref] —— **一只问的属主判据**(全仓唯一;两扇决议口 —— live 应答口 `POST /v1/tool-approvals/:id/respond`、耐久决断口
433
+ * `POST /v1/tasks/:taskId/asks/:askId/decision` —— 与按问读口都调它):调用方是不是这只问的属主。`owner` 只有一条来源律:有行的问读行上
434
+ * **当下的** `owner` 列(铸行写定,只有 `/v1/adoption` 改写它;进程内任何快照 —— 包括登记在铸卡那一刻记下的那一格 —— 都改不动);
435
+ * 没有行的问(问账没上场 / 旧族腿)只有登记记下的那一位可读。`null` = auth-off 单用户行,恒过。
436
+ * 门判过的那位属主再交给决议 CAS 的作用域({@link askRowCarriesOwner},[ref])。耐久决断口的 operator 越域臂不在这里 —— 那是那扇口自己的授权门。
437
+ */
438
+ export declare function askOwnerAdmits(owner: string | null, principal: string | undefined): boolean;
431
439
  /**
432
440
  * 🔴 [ref] —— **决议作用域的属主维**:这一行此刻带着的属主,是不是这次决议的门判过的那一位(逐字相等;`null` 只等于 `null`)。
433
441
  *
@@ -89,6 +89,9 @@ export function projectAskTerminalClaimTruth(row) {
89
89
  export function askRowOnCall(row, call) {
90
90
  return row.taskId === call.taskId && row.legKey === call.legKey && row.toolCallId === call.toolCallId && row.sourceTaskId === call.sourceTaskId;
91
91
  }
92
+ export function askOwnerAdmits(owner, principal) {
93
+ return owner === null || owner === principal;
94
+ }
92
95
  export function askRowCarriesOwner(row, owner) {
93
96
  return row !== null && row.owner === owner;
94
97
  }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
3
+ import { isNoSuchBucketAnswer } from "./s3-answer.js";
3
4
  import { connectJudged, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
4
5
  import { createLogger } from "../observability/logger.js";
5
6
  const BLOB_GC_GRACE_MS = 3_600_000;
@@ -328,7 +329,7 @@ export class MinioBlobBackend {
328
329
  if (res.ok)
329
330
  return { state: "present" };
330
331
  const body = await res.text().catch(() => "");
331
- if (res.status === 404 && /<Code>NoSuchBucket<\/Code>/.test(body))
332
+ if (isNoSuchBucketAnswer(res.status, body))
332
333
  return { state: "missing" };
333
334
  return { state: "unknown", detail: `HTTP ${res.status}` };
334
335
  }
@@ -10,12 +10,19 @@
10
10
  * 不在这里的:CAS 面推动的状态(`status` / `rev` / `reopen_reason` / 执行记录)、部署铸的值(`terminal_at_ms` 兜底)、部署截长脱敏的展示投影(`tool_input` /
11
11
  * `rule_suggestions`)—— 它们不是行的不变事实,core 也不答。
12
12
  *
13
+ * ## 写之前:一只判官 = {@link judgeCheckpointIndexCells}
14
+ * 一份文档能不能落进这些列,在写之前由**同一只**判官答 —— 写口 `put` 与 M29 的存量回填都问它,界不抄第二份。三个答案:`cells`(放得下,列格在手);`unreadable`(门或待决动作
15
+ * 不是对象 —— core 的派生按这两棵读,读不了会抛裸 TypeError,或对非对象照写出垃圾列格;引擎的 `judgeAsStored` 在 put 之前拦着,所以这样的文档只来自嵌入方 / 手改数据);
16
+ * `card_too_long`(卡词的 UTF-8 字节超出方言的列容量 {@link APPROVAL_CARD_COLUMN_MAX_BYTES} —— 严格 sql_mode 下库抛 `Data too long`,非严格下静默截成半只卡词,两形都不许到库)。
17
+ * 写口把后两个答案翻成类型化拒 {@link CheckpointIndexRefusedError}(零写);M29 把它们记成收据(计数 + 点名,不挡起服)。
18
+ *
13
19
  * ## 读:到期 facet 是由 core 两张表**生成**的一条 SQL 表达式
14
20
  * facet = 部署收割器对一行的 deadline 的读法(`A` = 审批门,deadline 是 SLA 拒;`B` = 无人值守 TTL,到期作废;`-` = 非门停驻)。core 的判官是
15
21
  * `facetOf(gate)`:门上的卡是 `CARD_FACET_OVERRIDES` 的键 ⇒ 那个键的 facet,否则门型在 `PAUSE_REGISTRY` 的 `facet` 列。列上存的是卡词本身
16
22
  * (`approval_card`,不是 facet —— 存一份 facet 会在 core 改表那天陈旧),谓词由那两张表机械生成({@link CHECKPOINT_FACET_CASES} → {@link CHECKPOINT_FACET_SQL}),
17
23
  * 逐行与 `facetOf` 同答(`test/checkpoint-index-columns.test.ts` B 节、db 组同名文件)。本仓不写第二份判据:没有手写门型表、没有手写卡词。
18
24
  * 卡那一侧不带「是不是审批门」的合取:列上的卡只在审批门上非 NULL(同一只派生),与 core 对生成谓词的成文形逐字相同。未登记门型 / NULL 门型 ⇒ 表达式 NULL(无 ELSE)。
25
+ * 拒扫腿的选行序见 {@link CHECKPOINT_SWEEP_ORDER_SQL}:卡列有词的行先取 —— 混跑窗里旧副本写的行卡列为空,它们的 facet 要等下一次起服的 M29 才确证。
19
26
  *
20
27
  * ## 谓词什么时候开
21
28
  * core 的迁移条款:存量行的卡列由同一只函数回填,并且只在部署自己记下「回填跑完了」之后才开生成谓词。本仓的记录 = 起服序列:M29 跑在一次性迁移段
@@ -54,9 +61,45 @@ export declare function buildCheckpointIndexCells(cp: Checkpoint): CheckpointInd
54
61
  * `approval_card` 列放得下的卡词**字节数**(UTF-8 字节,不是字符数),按方言各一格(键表对 {@link SqlDialect} 闭合:加一种方言 ⇒ 编译红)。
55
62
  * MySQL 协议(TiDB / MySQL)的列型是 `TEXT`,上限 65,535 字节 —— 严格模式下超长写入抛 `Data too long`,非严格模式静默截成半只卡词;PG 的 `TEXT` 没有这个上限。
56
63
  * 列型的属主是中央 DDL(`tidb-pool.ts` / `pg-pool.ts` 的 `approval_card`),本表只是那一列的容量事实(`test/checkpoint-index-columns.test.ts` 把两处同读,换列型 ⇒ 红)。
57
- * 引擎照存集外词、长度不由本仓定,但引擎铸得出的卡词是闭集短词 —— 超出容量的只会是手改 / 外来写者写下的存量行;读它的是 M29 回填(超出 ⇒ 计数 + 点名跳过,不挡起服)。
64
+ * 引擎照存集外词、长度不由本仓定,但引擎铸得出的卡词是闭集短词 —— 超出容量的只会是手改 / 外来写者写下的文档;读这张表的只有 {@link judgeCheckpointIndexCells}
65
+ * (写口超出 ⇒ 类型化拒,M29 超出 ⇒ 计数 + 点名跳过、不挡起服)。
58
66
  */
59
67
  export declare const APPROVAL_CARD_COLUMN_MAX_BYTES: Readonly<Record<SqlDialect, number>>;
68
+ /**
69
+ * 一份文档对索引列的判决(闭集:加一种答案 ⇒ 读它的 `switch` 编译红)。
70
+ * · `cells` —— 放得下,列格在手;
71
+ * · `unreadable` —— `member` 指的那一棵(`gate` / `pendingAction`)不是对象:core 的派生读不了它;
72
+ * · `card_too_long` —— 门上的卡词按 UTF-8 字节超出方言的列容量(`bytes` > `maxBytes`)。
73
+ */
74
+ export type CheckpointIndexJudgement = {
75
+ readonly kind: "cells";
76
+ readonly cells: CheckpointIndexCells;
77
+ } | {
78
+ readonly kind: "unreadable";
79
+ readonly member: "gate" | "pendingAction";
80
+ } | {
81
+ readonly kind: "card_too_long";
82
+ readonly bytes: number;
83
+ readonly maxBytes: number;
84
+ };
85
+ /** 不是 `cells` 的两个答案 —— 写口把它们带进 {@link CheckpointIndexRefusedError}。 */
86
+ export type CheckpointIndexRefusal = Exclude<CheckpointIndexJudgement, {
87
+ readonly kind: "cells";
88
+ }>;
89
+ /**
90
+ * **写之前**的唯一判官(写口 `put` 与 M29 回填共读;理由见头注「写之前」)。先看门与待决动作是不是对象 —— `Checkpoint` 的类型说它们必填,但嵌入方 /
91
+ * 手改数据不受类型约束,而 core 的 `checkpointIndexFacts` 对缺门 / 缺待决动作抛裸 TypeError、对非对象的门写出 `gate_kind` 为 undefined 的列格 ——
92
+ * 再派生,最后按方言的列容量数卡词的 UTF-8 **字节**(不是字符数)。纯函数:不读钟、不写任何东西。
93
+ */
94
+ export declare function judgeCheckpointIndexCells(cp: Checkpoint, dialect: SqlDialect): CheckpointIndexJudgement;
95
+ /**
96
+ * 写口 `put` 对一份放不进索引列的文档的拒(类型化,零写;`refusal` 是机读位,与 M29 收据读的是同一个判决)。这是店错误,不是 wire 码 —— 调用方(引擎的停驻写、嵌入方)
97
+ * 怎么收场由它们决定;句子不带卡词本身(卡词是外来数据,只报字节数)。
98
+ */
99
+ export declare class CheckpointIndexRefusedError extends Error {
100
+ readonly refusal: CheckpointIndexRefusal;
101
+ constructor(sessionId: unknown, refusal: CheckpointIndexRefusal);
102
+ }
60
103
  /** facet 的词(core 登记表 `facet` 列的值域)。 */
61
104
  type Facet = (typeof PAUSE_REGISTRY)[GateKind]["facet"];
62
105
  /** 生成谓词的一条 WHEN:某一列的值落在这几个词里 ⇒ 这个 facet。 */
@@ -77,5 +120,11 @@ export declare const CHECKPOINT_FACET_CASES: readonly FacetCase[];
77
120
  * 列名不带表限定:两条读腿都只查 `checkpoint` 一张表。拒扫腿读 `= 'A'`,收割的作废分支读 `COALESCE(…, '') <> 'A'`(NULL 门型的行走作废,与修前同向)。
78
121
  */
79
122
  export declare const CHECKPOINT_FACET_SQL: Readonly<Record<SqlDialect, string>>;
123
+ /**
124
+ * SLA 拒扫腿(`listExpiredApprovalGates`)的选行序:**卡列有词的行先取**,其后 deadline、token。卡列为 NULL 的审批行有两种来历 —— 引擎没给卡的门(照常是审批 SLA),或混跑窗里
125
+ * 不认这一列的旧副本写的行(可能是引擎要拒收 `sla_timeout` 的问答卡,facet 要等下一次起服的 M29 回填才确证);列上没有任何东西能把两者分开,所以不在谓词里猜,而在序里:
126
+ * 有卡词的行 facet 已确证,先占窗;卡列为空的行排在它们之后,各自仍按 deadline 升序。LIMIT 仍是调用方的界,不加旋钮。两方言同一句(`CASE` 在 MySQL 协议与 PG 上同形)。
127
+ */
128
+ export declare const CHECKPOINT_SWEEP_ORDER_SQL = "CASE WHEN approval_card IS NULL THEN 1 ELSE 0 END ASC, deadline ASC, token ASC";
80
129
  export {};
81
130
  //# sourceMappingURL=checkpoint-index-columns.d.ts.map
@@ -36,6 +36,33 @@ export const APPROVAL_CARD_COLUMN_MAX_BYTES = {
36
36
  tidb: 65_535,
37
37
  pg: Number.POSITIVE_INFINITY,
38
38
  };
39
+ export function judgeCheckpointIndexCells(cp, dialect) {
40
+ const gate = cp.gate;
41
+ if (typeof gate !== "object" || gate === null)
42
+ return { kind: "unreadable", member: "gate" };
43
+ const pendingAction = cp.pendingAction;
44
+ if (typeof pendingAction !== "object" || pendingAction === null)
45
+ return { kind: "unreadable", member: "pendingAction" };
46
+ const cells = buildCheckpointIndexCells(cp);
47
+ if (cells.approval_card !== null) {
48
+ const bytes = Buffer.byteLength(cells.approval_card, "utf8");
49
+ const maxBytes = APPROVAL_CARD_COLUMN_MAX_BYTES[dialect];
50
+ if (bytes > maxBytes)
51
+ return { kind: "card_too_long", bytes, maxBytes };
52
+ }
53
+ return { kind: "cells", cells };
54
+ }
55
+ export class CheckpointIndexRefusedError extends Error {
56
+ refusal;
57
+ constructor(sessionId, refusal) {
58
+ const where = `the checkpoint row for session ${JSON.stringify(sessionId) ?? "(none)"} was not written`;
59
+ super(refusal.kind === "unreadable"
60
+ ? `${where}: its document carries no \`${refusal.member}\` object — the index columns are derived from the gate and the pending action, so a document without one cannot be parked`
61
+ : `${where}: the approval card word is ${refusal.bytes} UTF-8 bytes and the approval_card column holds at most ${refusal.maxBytes} on this database`);
62
+ this.name = "CheckpointIndexRefusedError";
63
+ this.refusal = refusal;
64
+ }
65
+ }
39
66
  function groupByFacet(pairs, column) {
40
67
  const groups = new Map();
41
68
  for (const [word, facet] of pairs) {
@@ -59,4 +86,5 @@ export const CHECKPOINT_FACET_SQL = Object.freeze({
59
86
  tidb: renderFacetCase((column) => `CAST(${column} AS BINARY)`),
60
87
  pg: renderFacetCase((column) => column),
61
88
  });
89
+ export const CHECKPOINT_SWEEP_ORDER_SQL = "CASE WHEN approval_card IS NULL THEN 1 ELSE 0 END ASC, deadline ASC, token ASC";
62
90
  //# sourceMappingURL=checkpoint-index-columns.js.map