@sema-agent/server 7.148.0 → 7.150.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 (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -18,20 +18,43 @@ export interface OrgDeliveryQuotaPolicy {
18
18
  /** 一个收件人在一个窗里能收的封数上界(全部发件人合计)。 */
19
19
  readonly perRecipient: number;
20
20
  }
21
+ /** 一次记账的判决:放行,或拒 + 到店钟当前窗结束的整秒数(头注「拒的判决带重试指引」)。 */
22
+ export type OrgDeliveryQuotaVerdict = {
23
+ readonly granted: true;
24
+ } | {
25
+ readonly granted: false;
26
+ readonly retryAfterSec: number;
27
+ };
28
+ /**
29
+ * 店读不出自己的状态(File 腿:快照坏了)⇒ **拒用**的那一只错。fail-closed:每一次记账都抛它,绝不当成「放行」。
30
+ * 两句话各有读者,所以分开存:`message` = 运维句(点名文件与坏处、出路 —— 只进运维日志);{@link ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE} = 上线的
31
+ * 固定句(不点名宿主路径、不带坏处原文 —— 调用方是组织里的任何成员,不是运维)。HTTP 兜底(`http/server.ts`)按本类映射成带因的 500
32
+ * (码字面量只写在那一处发送点,wire 码目录的扫描面看得见它)。
33
+ */
34
+ export declare const ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE = "the cross-principal delivery quota state on this server cannot be read, so every delivery is refused until an operator repairs it \u2014 nothing was delivered, and retrying will not change the answer";
35
+ export declare class OrgDeliveryQuotaUnreadableError extends Error {
36
+ constructor(operatorSentence: string);
37
+ }
21
38
  export interface OrgDeliveryQuotaStore {
22
39
  /**
23
- * 原子地把这位发件人的一次投递**尝试**算进店钟当前窗:未满 ⇒ +1、答 `true`;已满 ⇒ 什么都不记、答 `false`。
40
+ * 原子地把这位发件人的一次投递**尝试**算进店钟当前窗:未满 ⇒ +1、答放行;已满 ⇒ 什么都不记、答拒(带重试指引)。
24
41
  * 店读不出 / 写不进 ⇒ **抛**(调用方按失败处置,绝不当成「放行」)。
25
42
  */
26
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
43
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
27
44
  /**
28
- * 原子地把一封 (fromScope → toScope) 的信算进**店钟当前窗**的两只计数器:两界都未满 ⇒ 两只各 +1、答 `true`;
29
- * 任一界已满 ⇒ 什么都不记、答 `false`。店读不出 / 写不进 ⇒ **抛**。
45
+ * 原子地把一封 (fromScope → toScope) 的信算进**店钟当前窗**的两只计数器:两界都未满 ⇒ 两只各 +1、答放行;
46
+ * 任一界已满 ⇒ 什么都不记、答拒(带重试指引)。店读不出 / 写不进 ⇒ **抛**。
30
47
  */
31
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
48
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
32
49
  }
33
50
  /** 窗起点(一处算法,三条腿共用)。 */
34
51
  export declare function quotaWindowStart(nowMs: number, windowMs: number): number;
52
+ /**
53
+ * 拒的判决的退避秒数(三条腿共用的**唯一**算式):店钟 `nowMs` 到当前窗结束,向上取整。≥ 1 是桶公式的推论、不另设下限:`nowMs` 严格早于窗末
54
+ * (`floor(now / w)` 之下 `(桶 + 1) × w − now ∈ (0, w]`),正数向上取整至少是 1 —— 窗末前最后 1 ms 也答 1,不叫客户端「立刻重试」。
55
+ * 桶与退避秒数的公式不在这里写第二份:都读 `observability/cost-quota.ts` 的 `alignedWindow` / `retryAfterSecUntil`(成本配额、速率限制器同锚)。
56
+ */
57
+ export declare function quotaRetryAfterSec(nowMs: number, windowMs: number): number;
35
58
  /** 店里记着的那个窗(`heldStart`)的计数此刻还算不算数 —— 头注那条律的**唯一**写法:此刻的窗,或此刻的下一个窗。 */
36
59
  export declare function heldWindowCounts(heldStart: number, at: number, windowMs: number): boolean;
37
60
  /** 一位发件人的当前窗(`windowMs` = 开窗那一次的窗宽:窗何时结束是条目自己的事实,File 腿据它丢已过窗的条目)。 */
@@ -58,8 +81,8 @@ export declare class MemoryOrgDeliveryQuotaStore implements OrgDeliveryQuotaStor
58
81
  protected state: QuotaWindows;
59
82
  /** @param nowMs 店钟(单进程腿:店 = 这个进程,缺省本进程墙钟)。 */
60
83
  constructor(nowMs?: () => number);
61
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
62
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
84
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
85
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
63
86
  /**
64
87
  * 落新的整份状态,只留窗还没结束的条目(按条目自己的窗宽判;已结束的发件人 / 收件人整个丢掉 —— 有界:量 = 本窗活跃的主体数)。
65
88
  * 丢掉的条目按头注那条律本来就不再算数(过去的窗),所以剪枝不改任何判决;剪枝写在**这里**而不是 File 腿里,Memory 与 File
@@ -89,16 +112,16 @@ export declare class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaSto
89
112
  nowMs?: () => number;
90
113
  warn?: (event: string, fields: Record<string, unknown>) => void;
91
114
  });
92
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
93
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
115
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
116
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
94
117
  protected persist(next: QuotaWindows): void;
95
118
  }
96
119
  export declare class SqlOrgDeliveryQuotaStore implements OrgDeliveryQuotaStore {
97
120
  private readonly db;
98
121
  constructor(db: SqlDriver);
99
122
  private q;
100
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
101
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
123
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
124
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
102
125
  /**
103
126
  * 一个记账事务:库钟先读(池上一条值语句,不在事务里留普通 SELECT),判过 ⇒ 提交,判拒 ⇒ 回滚(零残留)。
104
127
  * 🔴 **引擎判了死锁 ⇒ 整事务重来**(`retryOnDeadlock`:全仓唯一的重来写法、唯一的上界与判据):被挑中的一方已整事务回滚、锁全放,
@@ -4,13 +4,25 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { atomicWriteFile, ensureDir } from "@sema-agent/core";
5
5
  import { mysqlDriver, pgDriver, sqlDbNowMs } from "./sql-driver.js";
6
6
  import { retryOnDeadlock } from "./sql-errors.js";
7
- import { alignedWindow } from "../observability/cost-quota.js";
7
+ import { alignedWindow, retryAfterSecUntil } from "../observability/cost-quota.js";
8
8
  export const ORG_DELIVERY_SENDER_QUOTA_TABLE = "org_delivery_sender_quota";
9
9
  export const ORG_DELIVERY_RECIPIENT_QUOTA_TABLE = "org_delivery_recipient_quota";
10
10
  export const ORG_DELIVERY_PAIR_QUOTA_TABLE = "org_delivery_pair_quota";
11
+ export const ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE = "the cross-principal delivery quota state on this server cannot be read, so every delivery is refused until an operator repairs it — nothing was delivered, and retrying will not change the answer";
12
+ export class OrgDeliveryQuotaUnreadableError extends Error {
13
+ constructor(operatorSentence) {
14
+ super(operatorSentence);
15
+ this.name = "OrgDeliveryQuotaUnreadableError";
16
+ }
17
+ }
11
18
  export function quotaWindowStart(nowMs, windowMs) {
12
19
  return alignedWindow(nowMs, windowMs).bucket * windowMs;
13
20
  }
21
+ const GRANTED = Object.freeze({ granted: true });
22
+ export function quotaRetryAfterSec(nowMs, windowMs) {
23
+ return retryAfterSecUntil(alignedWindow(nowMs, windowMs));
24
+ }
25
+ const refused = (nowMs, windowMs) => ({ granted: false, retryAfterSec: quotaRetryAfterSec(nowMs, windowMs) });
14
26
  export function heldWindowCounts(heldStart, at, windowMs) {
15
27
  return heldStart === at || heldStart === at + windowMs;
16
28
  }
@@ -21,25 +33,27 @@ export class MemoryOrgDeliveryQuotaStore {
21
33
  this.nowMs = nowMs;
22
34
  }
23
35
  async admitAttempt(fromScope, policy) {
24
- const at = quotaWindowStart(this.nowMs(), policy.windowMs);
36
+ const now = this.nowMs();
37
+ const at = quotaWindowStart(now, policy.windowMs);
25
38
  const held = this.state.senders.get(fromScope);
26
39
  const current = held !== undefined && heldWindowCounts(held.windowStart, at, policy.windowMs) ? held : { windowStart: at, windowMs: policy.windowMs, attempts: 0 };
27
40
  if (current.attempts >= policy.attemptsPerSender)
28
- return false;
41
+ return refused(now, policy.windowMs);
29
42
  const senders = new Map(this.state.senders).set(fromScope, { ...current, attempts: current.attempts + 1 });
30
43
  this.commit({ senders, recipients: this.state.recipients });
31
- return true;
44
+ return GRANTED;
32
45
  }
33
46
  async take(fromScope, toScope, policy) {
34
- const at = quotaWindowStart(this.nowMs(), policy.windowMs);
47
+ const now = this.nowMs();
48
+ const at = quotaWindowStart(now, policy.windowMs);
35
49
  const held = this.state.recipients.get(toScope);
36
50
  const current = held !== undefined && heldWindowCounts(held.windowStart, at, policy.windowMs) ? held : { windowStart: at, windowMs: policy.windowMs, total: 0, pairs: new Map() };
37
51
  const pair = current.pairs.get(fromScope) ?? 0;
38
52
  if (current.total >= policy.perRecipient || pair >= policy.perPair)
39
- return false;
53
+ return refused(now, policy.windowMs);
40
54
  const next = { ...current, total: current.total + 1, pairs: new Map(current.pairs).set(fromScope, pair + 1) };
41
55
  this.commit({ senders: this.state.senders, recipients: new Map(this.state.recipients).set(toScope, next) });
42
- return true;
56
+ return GRANTED;
43
57
  }
44
58
  commit(next) {
45
59
  const now = this.nowMs();
@@ -97,7 +111,7 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
97
111
  parsed = { ok: false, problem: err instanceof Error ? err.message : String(err) };
98
112
  }
99
113
  if (!parsed.ok) {
100
- this.unusable = `the cross-principal delivery quota file ${this.path} cannot be read (${parsed.problem}) — every delivery is refused until an operator moves it away (moving it resets the current window's counts)`;
114
+ this.unusable = `the cross-principal delivery quota file ${this.path} cannot be read (${parsed.problem}) — every delivery is refused until an operator repairs or moves it away and restarts this server — the file is read once at startup (moving it away resets the current window's counts)`;
101
115
  (opts?.warn ?? createLogger().warn)("org_delivery_quota_corrupt_read", { path: this.path, problem: parsed.problem });
102
116
  return;
103
117
  }
@@ -105,12 +119,12 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
105
119
  }
106
120
  async admitAttempt(fromScope, policy) {
107
121
  if (this.unusable !== undefined)
108
- throw new Error(this.unusable);
122
+ throw new OrgDeliveryQuotaUnreadableError(this.unusable);
109
123
  return super.admitAttempt(fromScope, policy);
110
124
  }
111
125
  async take(fromScope, toScope, policy) {
112
126
  if (this.unusable !== undefined)
113
- throw new Error(this.unusable);
127
+ throw new OrgDeliveryQuotaUnreadableError(this.unusable);
114
128
  return super.take(fromScope, toScope, policy);
115
129
  }
116
130
  persist(next) {
@@ -141,16 +155,17 @@ export class SqlOrgDeliveryQuotaStore {
141
155
  return retryOnDeadlock(this.db.dialect, () => this.inTxOnce(table, policy, body));
142
156
  }
143
157
  async inTxOnce(table, policy, body) {
144
- const at = quotaWindowStart(await sqlDbNowMs(this.db, table), policy.windowMs);
158
+ const now = await sqlDbNowMs(this.db, table);
159
+ const at = quotaWindowStart(now, policy.windowMs);
145
160
  const conn = await this.db.connect();
146
161
  try {
147
162
  await conn.begin();
148
- const granted = await body(conn, at);
149
- if (granted)
163
+ const ok = await body(conn, at);
164
+ if (ok)
150
165
  await conn.commit();
151
166
  else
152
167
  await conn.rollback();
153
- return granted;
168
+ return ok ? GRANTED : refused(now, policy.windowMs);
154
169
  }
155
170
  catch (err) {
156
171
  await conn.rollback().catch((rollbackErr) => {
@@ -121,7 +121,7 @@ export declare function assertPermissionRuleApprovalSchema(query: (sql: string)
121
121
  *
122
122
  * 判据同款:发一条恒空的 `WHERE 1=0` 读;**只有可证的缺列**才给「缺列」的拒因(其余错误原样 rethrow ——
123
123
  * 超时/连接重置/权限/表不在都会让这条读抛,把它们诊断成「缺列」是一条比缺陷更贵的诊断)。出路是**闭集三条**
124
- * (与整库对账同一份,{@link describeProbedTableDrift}),没有销毁性的一条。
124
+ * (与整库对账同一份,{@link probedMissingColumnRefusal}),没有销毁性的一条。
125
125
  *
126
126
  * 文案照实说本表的分量:票是分钟级 TTL 的治理瞬态(过期即死),持久规则在 `permission_rule` 里,一行都不动。
127
127
  */
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import { applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, parseRuleText, readRowBehavior, screenRuleSyncState, PERMISSION_RULE_WRITER, RULE_ADD_ORIGINS, RULE_APPROVAL_KINDS, RULE_APPROVAL_RECORD_SCHEMA, RULE_APPROVAL_STATES, RULE_BEHAVIORS, RULE_SYNC_DROP_CODES, EDITED_RULE_BREADTH_WARNING_CODES, PERSISTED_RULE_MATCHES, EMPTY_DURABLE_RULE_PARTITION, PERMISSION_RULES_LATER_GRAMMAR_CODE, SESSION_RULES_GRAMMAR, readRuleGrammarStamp, readRuleOwner, principalNameOf, readRuleOffer2, } from "@sema-agent/core";
4
4
  import { dialectProtocolJsonEncoder, sqlDbNowMs, SQL_DB_NOW_MS } from "./sql-driver.js";
5
5
  import { isMissingColumnError } from "./sql-errors.js";
6
- import { describeProbedTableDrift } from "./schema-drift-sentence.js";
6
+ import { probedMissingColumnRefusal } from "./schema-drift-sentence.js";
7
7
  import { canonicalJsonStringify } from "@sema-agent/core";
8
8
  import { ensureIndexes } from "./ensure-index.js";
9
9
  import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, StoreCorruptError } from "./sql-json-column.js";
@@ -234,15 +234,13 @@ export async function assertPermissionRuleApprovalSchema(query, dialect) {
234
234
  catch (err) {
235
235
  if (!isMissingColumnError(err, dialect))
236
236
  throw err;
237
- throw new Error(`${PERMISSION_RULE_APPROVAL_TABLE} is missing required columns — refusing to start. This build needs the ` +
237
+ throw await probedMissingColumnRefusal(query, dialect, [PERMISSION_RULE_APPROVAL_TABLE], `${PERMISSION_RULE_APPROVAL_TABLE} is missing required columns — refusing to start. This build needs the ` +
238
238
  `#340 card-edit columns (command / edited_json) and the design/375 record-form columns ` +
239
239
  `(version / offers_json / selected_offer): the approval record now stores its OPTION structure and the ` +
240
240
  `chosen offer, and the store refuses to read rows whose record-form version it cannot vouch for. ` +
241
241
  `This table holds pending/settled approval RECORDS (the audit trail of who said yes to which ` +
242
242
  `rule), not the rules themselves — persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched; ` +
243
- `an in-place ALTER keeps the record trail.\n` +
244
- `Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
245
- (await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_APPROVAL_TABLE])));
243
+ `an in-place ALTER keeps the record trail.`, err);
246
244
  }
247
245
  }
248
246
  export async function assertPermissionRuleTicketSchema(query, dialect) {
@@ -252,14 +250,12 @@ export async function assertPermissionRuleTicketSchema(query, dialect) {
252
250
  catch (err) {
253
251
  if (!isMissingColumnError(err, dialect))
254
252
  throw err;
255
- throw new Error(`${PERMISSION_RULE_TICKET_TABLE} is missing required columns (redeemed_outcome / claim_id / purpose / settled_at_ms) — refusing to start. This build needs ` +
253
+ throw await probedMissingColumnRefusal(query, dialect, [PERMISSION_RULE_TICKET_TABLE], `${PERMISSION_RULE_TICKET_TABLE} is missing required columns (redeemed_outcome / claim_id / purpose / settled_at_ms) — refusing to start. This build needs ` +
256
254
  `the #265 A7 three-state redemption columns (MINTED → CLAIMED → SETTLED, the claim generation that fences a superseded ` +
257
255
  `claimant, and the lane binding that keeps cc-import and local-import tickets from being interchangeable): without them a ` +
258
256
  `local-import ticket can be claimed but never settled, so a redemption that already wrote rules to the store answers its ` +
259
257
  `owner with a retry-later it can never satisfy. This table holds minute-TTL single-use import ` +
260
- `tickets (a governance transient that dies on expiry); persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched.\n` +
261
- `Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
262
- (await describeProbedTableDrift(query, dialect, [PERMISSION_RULE_TICKET_TABLE])));
258
+ `tickets (a governance transient that dies on expiry); persisted rules live in ${PERMISSION_RULE_TABLE} and are untouched.`, err);
263
259
  }
264
260
  }
265
261
  export const PERMISSION_RULE_INDEXES = [
@@ -51,7 +51,7 @@ export declare function createPgPool(opts: string | pg.PoolConfig, onIdleClientE
51
51
  export declare const ENSURE_SCHEMA_LOCK_KEY = 4906;
52
52
  /**
53
53
  * {@link ENSURE_SCHEMA_LOCK_KEY} 的**非阻塞**获取(轮询 + 抖动退避)—— MySQL 孪生
54
- * `tidb-pool.ts acquireEnsureSchemaLock` 的逐条同形(循环共用 `ensure-schema-lock-policy.ts`)。轮到拿到为止;坏连接连续抛上抛
54
+ * `tidb-pool.ts acquireEnsureSchemaLock` 的逐条同形(两只都是 `ensure-schema-lock-policy.ts` 取锁器的薄壳:语句 / 读法 / 循环共用)。轮到拿到为止;坏连接连续抛上抛
55
55
  * (每条 DDL 本来就各自幂等),而不是把启动挂死。
56
56
  *
57
57
  * 🔴 **为什么不能再用阻塞的 `pg_advisory_lock()`**([ref] 复审 H1,对 7.82.1 的回归):本仓自 [ref] 起在
@@ -1,7 +1,7 @@
1
- import { createLogger, writeLogText } from "../observability/logger.js";
1
+ import { createLogger } from "../observability/logger.js";
2
2
  import pg from "pg";
3
3
  import { recordFailOpen } from "../observability/fail-open.js";
4
- import { pollUntilLocked } from "./ensure-schema-lock-policy.js";
4
+ import { acquireAdvisoryLock, releaseAdvisoryLock } from "./ensure-schema-lock-policy.js";
5
5
  import { RUN_IDENTITY_COLUMNS } from "../identity-source.js";
6
6
  import { ensureSchema as ensureSessionSchema } from "../stores-sql/pg-session-storage.js";
7
7
  import { sessionExtDdl } from "./session-ext-table.js";
@@ -26,6 +26,9 @@ import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
26
26
  import { INBOX_PAYLOAD_COLUMN } from "./inbox-payload-column.js";
27
27
  import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
28
28
  import { PRINCIPAL_MAX_BYTES } from "../principal-shape.js";
29
+ import { SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
30
+ import { replicaRegistrationDdl } from "./replica-registration.js";
31
+ import { servingRequirementDdl } from "./serving-requirement.js";
29
32
  export const PG_SCHEMA_STATEMENTS = [
30
33
  `CREATE TABLE IF NOT EXISTS task_run (
31
34
  task_id VARCHAR(64) COLLATE "C" NOT NULL,
@@ -36,6 +39,8 @@ export const PG_SCHEMA_STATEMENTS = [
36
39
  error TEXT COLLATE "C",
37
40
  error_code VARCHAR(64) COLLATE "C",
38
41
  instance_id VARCHAR(64) COLLATE "C",
42
+ -- leg_id: the HOLDING LEG minted at every claim (semantics in the MySQL twin, tidb-pool.ts task_run).
43
+ leg_id VARCHAR(36) COLLATE "C",
39
44
  job_id VARCHAR(64) COLLATE "C",
40
45
  source VARCHAR(64) COLLATE "C",
41
46
  -- identity_*: the run's identity tuple (semantics in the MySQL twin, tidb-pool.ts task_run; column definitions' single owner =
@@ -417,6 +422,9 @@ export const PG_SCHEMA_STATEMENTS = [
417
422
  updated_at_ms BIGINT NOT NULL,
418
423
  PRIMARY KEY (store_id, path)
419
424
  )`,
425
+ migrationMarkerDdl("pg"),
426
+ replicaRegistrationDdl("pg"),
427
+ servingRequirementDdl("pg"),
420
428
  ];
421
429
  export function pgPoolOptions(pgCfg, dbQueryTimeoutMs) {
422
430
  return {
@@ -475,19 +483,12 @@ export function createPgPool(opts, onIdleClientError) {
475
483
  return pool;
476
484
  }
477
485
  export const ENSURE_SCHEMA_LOCK_KEY = 4906;
486
+ const ENSURE_SCHEMA_LOCK_ID = { dialect: "pg", keys: [ENSURE_SCHEMA_LOCK_KEY] };
478
487
  export async function acquirePgEnsureSchemaLock(client, tag) {
479
- await pollUntilLocked(async () => {
480
- const res = await client.query("SELECT pg_try_advisory_lock($1) AS got", [ENSURE_SCHEMA_LOCK_KEY]);
481
- return res.rows[0]?.got === true;
482
- }, { tag, label: `pg advisory lock ${ENSURE_SCHEMA_LOCK_KEY}` });
488
+ await acquireAdvisoryLock(client, ENSURE_SCHEMA_LOCK_ID, tag);
483
489
  }
484
490
  export async function releasePgEnsureSchemaLock(client, tag) {
485
- try {
486
- await client.query("SELECT pg_advisory_unlock($1)", [ENSURE_SCHEMA_LOCK_KEY]);
487
- }
488
- catch (err) {
489
- writeLogText("stderr", `[${tag}] pg_advisory_unlock(${ENSURE_SCHEMA_LOCK_KEY}) failed (lock auto-frees on connection close):`, err);
490
- }
491
+ await releaseAdvisoryLock(client, ENSURE_SCHEMA_LOCK_ID, tag);
491
492
  }
492
493
  export async function ensurePgSchema(pool) {
493
494
  const client = await pool.connect();
@@ -497,6 +498,7 @@ export async function ensurePgSchema(pool) {
497
498
  locked = true;
498
499
  for (const stmt of PG_SCHEMA_STATEMENTS)
499
500
  await client.query(stmt);
501
+ await client.query(markIfAbsentSql("pg"), [SKILL_EXEMPTION_CUTOFF_ID, Date.now()]);
500
502
  await ensureSessionSchema(client);
501
503
  await client.query(sessionExtDdl("pg"));
502
504
  await ensureToolResultSchema(client);
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { anchorUnder } from "./exec-path-law.js";
2
3
  import { FileError, } from "@sema-agent/core";
3
4
  import { runSymlinkChain, shellQuote, kindFromMode } from "./remote-shell.js";
4
5
  import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
@@ -60,11 +61,12 @@ export function createPosixShellFs(deps) {
60
61
  const names = r.value.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
61
62
  const out = [];
62
63
  for (const name of names) {
63
- const info = await fs.statFileInfo(path.posix.join(abs, name), abortSignal);
64
+ const entry = anchorUnder(path.posix, abs, name);
65
+ const info = await fs.statFileInfo(entry, abortSignal);
64
66
  if (info.ok)
65
67
  out.push(info.value);
66
68
  else
67
- out.push({ name, path: path.posix.join(abs, name), kind: "file", size: 0, mtimeMs: 0 });
69
+ out.push({ name, path: entry, kind: "file", size: 0, mtimeMs: 0 });
68
70
  }
69
71
  return ok(out);
70
72
  },
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, joinedPath } from "./exec-path-law.js";
2
3
  import os from "node:os";
3
4
  import fs from "node:fs/promises";
4
5
  import { spawn } from "node:child_process";
@@ -65,7 +66,7 @@ export class RemoteAdbExecutionEnv {
65
66
  this.injectedConnectOnce = deps?.connectOnce;
66
67
  }
67
68
  resolve(p) {
68
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
69
+ return anchoredPath(path.posix, this.cwd, p);
69
70
  }
70
71
  workspaceHandle() {
71
72
  return { sandboxId: this.cfg.serial, provider: PROVIDER, mountPath: this.cwd };
@@ -328,7 +329,7 @@ export class RemoteAdbExecutionEnv {
328
329
  return ok(this.resolve(p));
329
330
  }
330
331
  async joinPath(parts) {
331
- return ok(path.posix.join(...parts));
332
+ return ok(joinedPath(path.posix, parts));
332
333
  }
333
334
  async fsGate(p) {
334
335
  if (this.destroyed)
@@ -331,14 +331,15 @@ export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): E
331
331
  /**
332
332
  * §4.3.3 的 cwd 优先序 —— 与 host 腿的 `effectiveHostWorkspace` **同源**:
333
333
  * ① 本 session 显式注册的 cwd(部署形表行 `callerCwd` 闸下由 resolve-spec 写);
334
- * ② `ctx.parentCwd`(委派子代:自己没注册过 cwd,继承父的工作目录 —— 少了它子代会丢工作目录);
334
+ * ② `ctx.parentCwd`(交活方的工作目录:委派子代继承父的;组合腿 —— 梯子的级、验证器腿,跑在引擎现铸的会话里 —— 由组合入口交提交登记的 cwd;
335
+ * 少了它这些腿会丢工作目录);
335
336
  * ③ 设备入籍时登记并落库的 `workspaceRoot`(缺 cwd 时的解析基准,有明确 wire 契约,不是「executor
336
337
  * 默认根」这种空话)。
337
338
  *
338
339
  * 🔴 **这一只解析源同时守它的后置条件:结果必是 POSIX 绝对路径,否则响亮抛 typed 拒(`.code` = `config.device_cwd_invalid`)。** 协议文档对 executor 的承诺是
339
340
  * 「exec / execStream 的指令帧恒带绝对 `cwd`」—— 不带或带相对的,都会把「用哪个目录」这件事留给 executor
340
341
  * 自己猜(同一个事实两种读法)。判据是入籍口那**同一只**谓词 `isDeviceAbsolutePath`(`device-store.ts`):入籍口用它拒相对的
341
- * `workspaceRoot`;这里的判留作存量行守卫(入籍口收紧之前落库的相对根)并守另两个来源 —— 委派链的父 cwd 来自引擎,不经入籍口。
342
+ * `workspaceRoot`;这里的判留作存量行守卫(入籍口收紧之前落库的相对根)并守另两个来源 —— 第 ② 臂的交活方工作目录不经入籍口。
342
343
  */
343
344
  export declare function effectiveDeviceCwd(sessionCwd: string | undefined, parentCwd: string | undefined, row: Pick<DeviceRow, "workspaceRoot">): string;
344
345
  export {};
@@ -1,4 +1,5 @@
1
1
  import { posix } from "node:path";
2
+ import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
2
3
  import { ExecutionError, FileError, RemoteExecutionError, } from "@sema-agent/core";
3
4
  import { capabilitiesFor } from "../execution-lane-caps.js";
4
5
  import { DEVICE_ERROR_CODES, isDeviceAbsolutePath, } from "../device-store.js";
@@ -174,7 +175,7 @@ export class RemoteDeviceExecutionEnv {
174
175
  "performing a NON-atomic write here is exactly the silent downgrade guarded writes exist to prevent (#157). Refusing; the per-task factory mints a correctly-shaped env on the next task.", absPath);
175
176
  }
176
177
  resolve(p) {
177
- return isDeviceAbsolutePath(p) ? posix.normalize(p) : posix.normalize(posix.join(this.cwd, p));
178
+ return anchoredPath(DEVICE_PATH_FAMILY, this.cwd, p);
178
179
  }
179
180
  instructionCwd(requested) {
180
181
  return requested === undefined ? this.cwd : this.resolve(requested);
@@ -183,7 +184,7 @@ export class RemoteDeviceExecutionEnv {
183
184
  return ok(this.resolve(p));
184
185
  }
185
186
  async joinPath(parts) {
186
- return ok(posix.join(...parts));
187
+ return ok(joinedPath(DEVICE_PATH_FAMILY, parts));
187
188
  }
188
189
  workspaceHandle() {
189
190
  return {
@@ -557,7 +558,7 @@ export class RemoteDeviceExecutionEnv {
557
558
  return this.wrongOutcome("stat", o.kind, abs);
558
559
  if (!o.info)
559
560
  return { ok: false, error: new FileError("unknown", "device returned a stat outcome without info — protocol violation", abs) };
560
- return ok(toFileInfo(o.info));
561
+ return ok(toFileInfo(o.info, abs));
561
562
  });
562
563
  }
563
564
  async doReadLink(p, abortSignal) {
@@ -642,7 +643,7 @@ export class RemoteDeviceExecutionEnv {
642
643
  return this.wrongOutcome("stat", o.kind, abs);
643
644
  if (!o.entries)
644
645
  return { ok: false, error: new FileError("unknown", "device returned a stat outcome without entries — protocol violation", abs) };
645
- return ok(o.entries.map(toFileInfo));
646
+ return ok(o.entries.map((w) => toFileInfo(w, anchorUnder(DEVICE_PATH_FAMILY, abs, posix.basename(w.path)))));
646
647
  });
647
648
  }
648
649
  async writeFile(p, content, abortSignal) {
@@ -738,8 +739,9 @@ function mergedEnv(taskEnv, commandEnv) {
738
739
  function markTruncated(text, truncated) {
739
740
  return truncated === true ? `${text}\n[output truncated by the device executor]` : text;
740
741
  }
741
- function toFileInfo(w) {
742
- return { name: posix.basename(w.path), path: w.path, kind: w.kind === "dir" ? "directory" : w.kind === "symlink" ? "symlink" : "file", size: w.size, mtimeMs: w.mtimeMs };
742
+ const DEVICE_PATH_FAMILY = { sep: posix.sep, isAbsolute: isDeviceAbsolutePath };
743
+ function toFileInfo(w, at) {
744
+ return { name: posix.basename(at), path: at, kind: w.kind === "dir" ? "directory" : w.kind === "symlink" ? "symlink" : "file", size: w.size, mtimeMs: w.mtimeMs };
743
745
  }
744
746
  function toWireExpectation(e) {
745
747
  return {
@@ -45,7 +45,7 @@ export interface RemoteContainerEnvConfig extends RemoteEnvObservers, LaneHomeCo
45
45
  template?: string;
46
46
  /** Sandbox lifetime in ms (E2B billing window). Default {@link DEFAULT_TIMEOUT_MS}. */
47
47
  timeoutMs?: number;
48
- /** Workspace root inside the VM; remote paths normalize against it. Default {@link DEFAULT_MOUNT_PATH}. */
48
+ /** Workspace root inside the VM; a relative path is anchored at it (`exec-path-law.ts`: anchored, never folded). Default {@link DEFAULT_MOUNT_PATH}. */
49
49
  mountPath?: string;
50
50
  /** Outbound network for the sandbox (clay: service needs external-network capability). Default `true`. */
51
51
  allowInternetAccess?: boolean;
@@ -244,13 +244,7 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
244
244
  private execInternal;
245
245
  private execAs;
246
246
  execStream(command: string, options?: ExecStreamOptions): AsyncIterable<OutputChunk>;
247
- readonly backgroundCapabilities: {
248
- readonly supported: true;
249
- readonly maxConcurrent: number;
250
- readonly defaultBgTimeoutSec: number;
251
- readonly maxBgTimeoutSec: number;
252
- readonly supportsDetach: true;
253
- };
247
+ readonly backgroundCapabilities: BackgroundShellCapability["backgroundCapabilities"];
254
248
  private _bgManager?;
255
249
  private get bgManager();
256
250
  private makeBgDriver;
@@ -371,6 +365,8 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
371
365
  private ensureConnected;
372
366
  private fsReady;
373
367
  private tempResult;
368
+ /** 提供方的条目 → `FileInfo`。`path` 取本环境的拼法(`at`:被问的那只路径 / 列目录时锚在它之下的条目),不取提供方回报的那一份
369
+ * —— 提供方可能已把 `..` 折掉,而执行缝路径律(`exec-path-law.ts`)要求条目名在交来的路径之下、能原样再交回来。 */
374
370
  private toFileInfo;
375
371
  private fileError;
376
372
  /** A thrown provider error → this exec's error. The two cuts (aborted, the provider's own command deadline) are a command that RAN: they carry what it had
@@ -1,7 +1,9 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
2
3
  import { Sandbox, CommandExitError, FileType } from "e2b";
3
4
  import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
4
5
  import { capabilitiesFor } from "../execution-lane-caps.js";
6
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
5
7
  import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, e2bFloorCommand, engineStart, } from "./sandbox-read-floor.js";
6
8
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
7
9
  import { fileErrorFromExec } from "./remote-env-file-error.js";
@@ -55,6 +57,14 @@ export class RemoteContainerExecutionEnv {
55
57
  this.sandboxApi = deps.sandboxApi ?? Sandbox;
56
58
  this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
57
59
  this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
60
+ const backgroundShellOn = DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds({ remoteExec: { provider: PROVIDER } });
61
+ this.backgroundCapabilities = {
62
+ supported: backgroundShellOn,
63
+ maxConcurrent: E2B_BG_MAX_CONCURRENT,
64
+ defaultBgTimeoutSec: E2B_BG_DEFAULT_TIMEOUT_SEC,
65
+ maxBgTimeoutSec: E2B_BG_MAX_TIMEOUT_SEC,
66
+ supportsDetach: backgroundShellOn,
67
+ };
58
68
  }
59
69
  capabilities;
60
70
  floor;
@@ -764,13 +774,7 @@ export class RemoteContainerExecutionEnv {
764
774
  options?.signal?.removeEventListener("abort", onAbort);
765
775
  }
766
776
  }
767
- backgroundCapabilities = {
768
- supported: true,
769
- maxConcurrent: E2B_BG_MAX_CONCURRENT,
770
- defaultBgTimeoutSec: E2B_BG_DEFAULT_TIMEOUT_SEC,
771
- maxBgTimeoutSec: E2B_BG_MAX_TIMEOUT_SEC,
772
- supportsDetach: true,
773
- };
777
+ backgroundCapabilities;
774
778
  _bgManager;
775
779
  get bgManager() {
776
780
  return (this._bgManager ??= new BackgroundShellManager(this.makeBgDriver(), this.backgroundCapabilities));
@@ -859,7 +863,7 @@ export class RemoteContainerExecutionEnv {
859
863
  return ok(this.resolve(p));
860
864
  }
861
865
  async joinPath(parts) {
862
- return ok(path.posix.join(...parts));
866
+ return ok(joinedPath(path.posix, parts));
863
867
  }
864
868
  async readTextFile(p, abortSignal) {
865
869
  const sbx = await this.fsReady(p);
@@ -931,8 +935,9 @@ export class RemoteContainerExecutionEnv {
931
935
  if (!sbx.ok)
932
936
  return sbx;
933
937
  try {
934
- const info = await this.rpc("files.getInfo", (signal) => sbx.value.files.getInfo(this.resolve(p), { signal }), abortSignal);
935
- return ok(this.toFileInfo(info));
938
+ const abs = this.resolve(p);
939
+ const info = await this.rpc("files.getInfo", (signal) => sbx.value.files.getInfo(abs, { signal }), abortSignal);
940
+ return ok(this.toFileInfo(info, abs));
936
941
  }
937
942
  catch (e) {
938
943
  return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
@@ -943,8 +948,9 @@ export class RemoteContainerExecutionEnv {
943
948
  if (!sbx.ok)
944
949
  return sbx;
945
950
  try {
946
- const entries = await this.rpc("files.list", (signal) => sbx.value.files.list(this.resolve(p), { signal }), abortSignal);
947
- return ok(entries.map((e) => this.toFileInfo(e)));
951
+ const abs = this.resolve(p);
952
+ const entries = await this.rpc("files.list", (signal) => sbx.value.files.list(abs, { signal }), abortSignal);
953
+ return ok(entries.map((e) => this.toFileInfo(e, anchorUnder(path.posix, abs, e.name))));
948
954
  }
949
955
  catch (e) {
950
956
  return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
@@ -1041,7 +1047,7 @@ export class RemoteContainerExecutionEnv {
1041
1047
  return this.tempResult(r, "mktemp");
1042
1048
  }
1043
1049
  resolve(p) {
1044
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
1050
+ return anchoredPath(path.posix, this.cwd, p);
1045
1051
  }
1046
1052
  async rpc(label, op, userSignal, timeoutMs = this.cfg.rpcTimeoutMs) {
1047
1053
  const deadline = new AbortController();
@@ -1160,11 +1166,11 @@ export class RemoteContainerExecutionEnv {
1160
1166
  return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `${op} failed`) };
1161
1167
  return ok(r.value.stdout.trim());
1162
1168
  }
1163
- toFileInfo(e) {
1169
+ toFileInfo(e, at) {
1164
1170
  const kind = e.symlinkTarget ? "symlink" : e.type === FileType.DIR ? "directory" : "file";
1165
1171
  return {
1166
1172
  name: e.name,
1167
- path: e.path,
1173
+ path: at,
1168
1174
  kind,
1169
1175
  size: e.size ?? 0,
1170
1176
  mtimeMs: e.modifiedTime ? e.modifiedTime.getTime() : 0,