@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -27,6 +27,7 @@ import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
27
27
  import { refusalBody } from "./http/refusal-body.js";
28
28
  import { withoutSettlementEvidence } from "./gate-settlement.js";
29
29
  import { extensionRequesterOf } from "./trace/extension-call-face.js";
30
+ import { boundedRotation } from "./bounded-rotation.js";
30
31
  const defaultLogger = createLogger();
31
32
  const MAX_APPROVAL_ARGS_BYTES = 16384;
32
33
  const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
@@ -69,7 +70,7 @@ function durableRuleMaterial(material) {
69
70
  }
70
71
  export const NOT_PENDING_UNKNOWN_CAUSE = "unknown_or_other_replica";
71
72
  export const TOOL_APPROVAL_READ_STATES = ["pending", "decided", "parked", "void"];
72
- function decidedRowFacts(row) {
73
+ export function decidedRowFacts(row) {
73
74
  if (row.state !== "DECIDED" || (row.decision !== "approve" && row.decision !== "deny") || row.provisional || row.decidedAtMs === null)
74
75
  return undefined;
75
76
  return { decision: row.decision === "approve" ? "allow" : "deny", decidedAtMs: row.decidedAtMs, ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}) };
@@ -85,6 +86,22 @@ function buildAskRowReadProjection(row, nowMs) {
85
86
  };
86
87
  }
87
88
  export const ASK_DECISION_CONSUMER_ABSENT = "absent";
89
+ function standingParkedDecision(gate, row, idempotencyKey) {
90
+ if (gate?.kind !== "decided")
91
+ return undefined;
92
+ const decision = gate.decision === "allow" ? "approve" : "deny";
93
+ if (row.decision === decision && row.decidedAtMs !== null && row.decidedAtMs >= gate.decidedAtMs) {
94
+ return {
95
+ kind: "standing",
96
+ decision,
97
+ decidedAtMs: row.decidedAtMs,
98
+ ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}),
99
+ ...(row.decisionActor !== null ? { actor: row.decisionActor } : {}),
100
+ byThisRequest: idempotencyKey !== undefined && row.idempotencyKey === idempotencyKey,
101
+ };
102
+ }
103
+ return { kind: "standing", decision, decidedAtMs: gate.decidedAtMs, ...(gate.decisionNote !== undefined ? { decisionNote: gate.decisionNote } : {}), byThisRequest: false };
104
+ }
88
105
  const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
89
106
  const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
90
107
  export const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
@@ -378,6 +395,18 @@ function parseToolApprovalFields(body, retired) {
378
395
  }
379
396
  return { ok: false, error: 'decision must be one of "allow" | "allow_session" | "deny"' };
380
397
  }
398
+ export function askDecisionRecordOf(write) {
399
+ return {
400
+ decision: write.decision,
401
+ ...(write.actor !== undefined ? { decisionActor: write.actor } : {}),
402
+ decisionNote: write.note ?? null,
403
+ idempotencyKey: write.idempotencyKey ?? null,
404
+ ...liveHostSource(write.decider),
405
+ };
406
+ }
407
+ function liveDecideInputOf(write) {
408
+ return { ...askDecisionRecordOf(write), ...(write.decision === "approve" && write.updatedInput !== undefined ? { updatedInput: write.updatedInput } : {}) };
409
+ }
381
410
  export function buildUnattendedPolicyRefusal(reason) {
382
411
  return { allow: false, settledBy: POLICY_REFUSAL_DECISION.settledBy, reason };
383
412
  }
@@ -562,8 +591,8 @@ export class ToolApprovalCoordinator {
562
591
  admitMaxPerOwner;
563
592
  admitByTask = new Map();
564
593
  admitByOwner = new Map();
565
- suspendedPollOffset = 0;
566
- adoptedPollOffset = 0;
594
+ suspendedPollCursor = { offset: 0 };
595
+ adoptedPollCursor = { offset: 0 };
567
596
  editedDecisionsInFlight = new Map();
568
597
  storeErrorWarned = false;
569
598
  storeErrorTally = 0;
@@ -2460,21 +2489,50 @@ export class ToolApprovalCoordinator {
2460
2489
  const openGenerationNow = this.lookupParkRedemption(row.askId) !== undefined ? await this.openGateGeneration(row) : undefined;
2461
2490
  const prior = this.lookupParkRedemption(row.askId);
2462
2491
  if (prior?.redeemed !== undefined && receiptAnswersNow(prior.redeemed.gateGeneration, openGenerationNow))
2463
- return this.replayRedeemReceipt(id, prior, row, prior.redeemed, parsed);
2492
+ return this.replayRedeemReceipt(id, prior, row, prior.redeemed, parsed, row);
2464
2493
  if (prior?.inflight !== undefined) {
2465
2494
  const shared = await prior.inflight;
2466
2495
  if (shared.status < 200 || shared.status >= 300)
2467
2496
  return shared;
2468
2497
  if (!receiptAnswersNow(shared.gateGeneration, openGenerationNow))
2469
2498
  return this.redeemParkedAsk(id, row, parsed, httpReq, principal);
2470
- return this.replayRedeemReceipt(id, prior, row, { decision: shared.decision, body: shared.body, gateGeneration: shared.gateGeneration, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
2499
+ return this.replayRedeemReceipt(id, prior, row, { decision: shared.decision, body: shared.body, gateGeneration: shared.gateGeneration, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed, shared.recorded ?? { decisionNote: null });
2471
2500
  }
2501
+ const store = this.askStore;
2502
+ if (store === undefined)
2503
+ return this.notPending();
2504
+ const request = {
2505
+ write: { decision: parsed.value === "deny" ? "deny" : "approve", decider: parsed.decider, ...(parsed.note !== undefined ? { note: parsed.note } : {}) },
2506
+ ...(parsed.value === "allow_session" ? { remember: "session" } : {}),
2507
+ ...(parsed.persistRule !== undefined ? { persistRule: parsed.persistRule } : {}),
2508
+ ...(parsed.updatedInput !== undefined && parsed.value !== "deny" ? { updatedInput: parsed.updatedInput } : {}),
2509
+ ...(principal !== undefined ? { principal } : {}),
2510
+ operator: false,
2511
+ ...(httpReq !== undefined ? { httpReq } : {}),
2512
+ };
2513
+ const out = await this.runParkedFlight(redeem, store, row, row.gateToken, request);
2514
+ if (out.status < 200 || out.status >= 300)
2515
+ return { status: out.status, body: out.body };
2516
+ const rule = out.rule;
2517
+ return {
2518
+ status: out.status,
2519
+ body: {
2520
+ ...out.body,
2521
+ approvalId: id,
2522
+ askId: row.askId,
2523
+ accepted: "parked_redeemed",
2524
+ ...(parsed.value === "allow_session" ? { rememberApplied: out.rememberApplied === true } : {}),
2525
+ ...noteRecordedReceipt(parsed.note, noteLandedOnRow(parsed.note, out.recorded?.decisionNote ?? null)),
2526
+ ...(rule?.body ?? {}),
2527
+ },
2528
+ };
2529
+ }
2530
+ async runParkedFlight(redeem, store, row, gateToken, request) {
2531
+ const decision = request.write.decision;
2472
2532
  const admission = this.parkRedemptionAdmission(row.owner);
2473
2533
  if (admission !== undefined)
2474
- return admission;
2534
+ return { ...admission, body: admission.body, decision };
2475
2535
  const memo = this.openParkRedemption(row.askId, row.owner);
2476
- const decision = parsed.value === "deny" ? "deny" : "approve";
2477
- const gateToken = row.gateToken;
2478
2536
  const flight = (async () => {
2479
2537
  let res;
2480
2538
  try {
@@ -2484,12 +2542,13 @@ export class ToolApprovalCoordinator {
2484
2542
  gateBoundCallId: row.gateBoundCallId,
2485
2543
  gateBoundInputHash: row.gateBoundInputHash,
2486
2544
  decision,
2487
- ...(parsed.note !== undefined ? { note: parsed.note } : {}),
2488
- decider: parsed.decider,
2489
- ...(parsed.updatedInput !== undefined && parsed.value !== "deny" ? { updatedInput: parsed.updatedInput } : {}),
2490
- ...(parsed.value === "allow_session" ? { remember: "session" } : {}),
2491
- ...(principal !== undefined ? { principal } : {}),
2492
- ...(httpReq !== undefined ? { httpReq } : {}),
2545
+ ...(request.write.note !== undefined ? { note: request.write.note } : {}),
2546
+ decider: request.write.decider,
2547
+ ...(request.updatedInput !== undefined && decision === "approve" ? { updatedInput: request.updatedInput } : {}),
2548
+ ...(request.remember !== undefined ? { remember: request.remember } : {}),
2549
+ ...(request.principal !== undefined ? { principal: request.principal } : {}),
2550
+ operator: request.operator,
2551
+ ...(request.httpReq !== undefined ? { httpReq: request.httpReq } : {}),
2493
2552
  });
2494
2553
  }
2495
2554
  catch (err) {
@@ -2498,8 +2557,9 @@ export class ToolApprovalCoordinator {
2498
2557
  }
2499
2558
  if (res.status < 200 || res.status >= 300)
2500
2559
  return { ...res, decision };
2501
- const rule = await this.persistRuleFromRow(row, parsed);
2502
- return { ...res, decision, ...(rule !== undefined ? { rule } : {}) };
2560
+ const recorded = await this.recordParkedOutcome(store, row, request.write);
2561
+ const rule = await this.persistRuleFromRow(row, request.persistRule, request.updatedInput !== undefined);
2562
+ return { ...res, decision, ...(recorded !== undefined ? { recorded } : {}), ...(rule !== undefined ? { rule } : {}) };
2503
2563
  })();
2504
2564
  memo.inflight = flight;
2505
2565
  let out;
@@ -2512,25 +2572,57 @@ export class ToolApprovalCoordinator {
2512
2572
  if (out.status < 200 || out.status >= 300) {
2513
2573
  if (this.parkRedemptions.get(row.askId) === memo)
2514
2574
  this.parkRedemptions.delete(row.askId);
2515
- return { status: out.status, body: out.body };
2575
+ return out;
2516
2576
  }
2517
- const rule = out.rule;
2518
2577
  if (this.retainParkRedemption(row.askId, memo))
2519
- memo.redeemed = { decision, body: out.body, gateGeneration: out.gateGeneration, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
2520
- return {
2521
- status: out.status,
2522
- body: {
2523
- ...out.body,
2524
- approvalId: id,
2525
- askId: row.askId,
2526
- accepted: "parked_redeemed",
2527
- ...(parsed.value === "allow_session" ? { rememberApplied: out.rememberApplied === true } : {}),
2528
- ...(rule?.body ?? {}),
2529
- },
2530
- };
2578
+ memo.redeemed = { decision, body: out.body, gateGeneration: out.gateGeneration, ...(out.rule?.terminal === true ? { rule: out.rule.body } : {}) };
2579
+ return out;
2531
2580
  }
2532
- async persistRuleFromRow(row, parsed) {
2533
- const persistRule = parsed.persistRule;
2581
+ async recordParkedOutcome(store, row, write) {
2582
+ try {
2583
+ const out = await this.writeAskDecision(store, row.askId, { kind: "parked" }, row.owner, write);
2584
+ if (out.ok)
2585
+ return out.row;
2586
+ recordFailOpen("server.approval.parked-decision-not-recorded", `askId=${row.askId} reason=${out.reason}`);
2587
+ return undefined;
2588
+ }
2589
+ catch (err) {
2590
+ this.noteStoreError(err, "recordParkedDecision");
2591
+ recordFailOpen("server.approval.parked-decision-not-recorded", `askId=${row.askId} reason=store_error`);
2592
+ return undefined;
2593
+ }
2594
+ }
2595
+ async decideParkedAsk(store, row, request, idempotencyKey) {
2596
+ for (;;) {
2597
+ const gate = await this.readParkedGate(row);
2598
+ const current = await this.askRowFor(store, row.askId, "getAsk(parked-decision)");
2599
+ if (current === null || current.state !== "PARKED" || !askRowCarriesOwner(current, row.owner))
2600
+ return { kind: "gone" };
2601
+ const inflight = this.lookupParkRedemption(row.askId)?.inflight;
2602
+ if (inflight !== undefined) {
2603
+ await inflight;
2604
+ continue;
2605
+ }
2606
+ const standing = standingParkedDecision(gate, current, idempotencyKey);
2607
+ if (standing !== undefined)
2608
+ return standing;
2609
+ if (request.write.decision === "approve" && request.policyMayNotApprove === true)
2610
+ return { kind: "policy_refused" };
2611
+ const redeem = this.parkedRedeem?.();
2612
+ if (redeem === undefined || row.gateToken === null) {
2613
+ if (row.gateToken === null)
2614
+ defaultLogger.error("tool approval parked row carries no gate token — nothing to redeem", { askId: row.askId });
2615
+ return { kind: "gate_only" };
2616
+ }
2617
+ const out = await this.runParkedFlight(redeem, store, current, row.gateToken, request);
2618
+ if (out.status === 200)
2619
+ return { kind: "accepted", body: out.body, ...(out.recorded !== undefined ? { recorded: out.recorded } : {}) };
2620
+ if ((await this.readParkedGate(row))?.kind === "decided")
2621
+ continue;
2622
+ return { kind: "refused", status: out.status, body: out.body };
2623
+ }
2624
+ }
2625
+ async persistRuleFromRow(row, persistRule, inputWasEdited) {
2534
2626
  if (persistRule === undefined)
2535
2627
  return undefined;
2536
2628
  const flag = (rulePersisted, ruleRefusal, echo) => ({
@@ -2550,7 +2642,7 @@ export class ToolApprovalCoordinator {
2550
2642
  const command = row.ruleCommand;
2551
2643
  if (command === null)
2552
2644
  return flag(false, "rule_material_absent");
2553
- if (parsed.updatedInput !== undefined)
2645
+ if (inputWasEdited)
2554
2646
  return flag(false, "rule_input_edited");
2555
2647
  const offers = card.ruleOffers ?? [];
2556
2648
  const scope = row.ruleScopeRoot !== null ? { kind: "project", root: row.ruleScopeRoot } : undefined;
@@ -2607,10 +2699,10 @@ export class ToolApprovalCoordinator {
2607
2699
  return { ...flag(false, "rule_store_error"), terminal: false };
2608
2700
  }
2609
2701
  }
2610
- async replayRedeemReceipt(id, memo, row, receipt, parsed) {
2702
+ async replayRedeemReceipt(id, memo, row, receipt, parsed, recordedNote) {
2611
2703
  let rule = receipt.decision === "approve" ? receipt.rule : this.lateRuleRefusal(parsed);
2612
2704
  if (receipt.decision === "approve" && rule === undefined && parsed.persistRule !== undefined) {
2613
- const flight = memo.ruleInflight ?? this.persistRuleFromRow(row, parsed);
2705
+ const flight = memo.ruleInflight ?? this.persistRuleFromRow(row, parsed.persistRule, parsed.updatedInput !== undefined);
2614
2706
  const isOwner = memo.ruleInflight === undefined;
2615
2707
  if (isOwner)
2616
2708
  memo.ruleInflight = flight;
@@ -2636,6 +2728,7 @@ export class ToolApprovalCoordinator {
2636
2728
  accepted: "parked_redeemed",
2637
2729
  idempotent: true,
2638
2730
  decision: receipt.decision === "approve" ? "allow" : "deny",
2731
+ ...noteRecordedReceipt(parsed.note, noteLandedOnRow(parsed.note, recordedNote.decisionNote)),
2639
2732
  ...(rule ?? {}),
2640
2733
  },
2641
2734
  };
@@ -2719,19 +2812,36 @@ export class ToolApprovalCoordinator {
2719
2812
  return withFlag(false, "rule_store_error");
2720
2813
  }
2721
2814
  }
2815
+ writeAskDecision(store, askId, form, owner, write) {
2816
+ switch (form.kind) {
2817
+ case "live":
2818
+ return this.withStoreDeadline(store.decideAsk(askId, form.batchId, owner, liveDecideInputOf(write)), "decideAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
2819
+ if (!late.ok)
2820
+ return;
2821
+ if (decidedRowFacts(late.row) === undefined) {
2822
+ defaultLogger.error("ask decision landed after its deadline on a row that is not an authoritative human decision — not settled", { askId, state: late.row.state, provisional: late.row.provisional });
2823
+ return;
2824
+ }
2825
+ this.notifyExternalDecision(askId, outcomeOfDecidedRow(late.row));
2826
+ });
2827
+ case "parked":
2828
+ return this.withStoreDeadline(store.recordParkedDecision(askId, owner, askDecisionRecordOf(write)), "recordParkedDecision", DURABLE_CALL_TIMEOUT_MS);
2829
+ default: {
2830
+ const unhandled = form;
2831
+ throw new Error(`writeAskDecision: unhandled form ${JSON.stringify(unhandled)}`);
2832
+ }
2833
+ }
2834
+ }
2722
2835
  async respondWithCas(askStore, askId, batchId, owner, entry, parsed, ruleEligibility) {
2723
2836
  let winnerRow;
2724
2837
  let ambiguousConfirmedRow;
2725
2838
  let noteLanded = false;
2726
2839
  try {
2727
- const decided = await this.withStoreDeadline(askStore.decideAsk(askId, batchId, owner, {
2840
+ const decided = await this.writeAskDecision(askStore, askId, { kind: "live", batchId }, owner, {
2728
2841
  decision: parsed.value === "deny" ? "deny" : "approve",
2729
- ...(parsed.note !== undefined ? { decisionNote: parsed.note } : {}),
2730
- ...(parsed.value !== "deny" && parsed.updatedInput !== undefined ? { updatedInput: parsed.updatedInput } : {}),
2731
- ...liveHostSource(parsed.decider),
2732
- }), "decideAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
2733
- if (late.ok)
2734
- this.notifyExternalDecision(askId, outcomeOfDecidedRow(late.row));
2842
+ decider: parsed.decider,
2843
+ ...(parsed.note !== undefined ? { note: parsed.note } : {}),
2844
+ ...(parsed.updatedInput !== undefined ? { updatedInput: parsed.updatedInput } : {}),
2735
2845
  });
2736
2846
  if (decided.ok) {
2737
2847
  winnerRow = decided.row;
@@ -3046,28 +3156,11 @@ export class ToolApprovalCoordinator {
3046
3156
  continue;
3047
3157
  seen.add(entry.askId);
3048
3158
  }
3049
- const waitingStart = waiting.length > 0 ? this.suspendedPollOffset % waiting.length : 0;
3050
- const adoptedStart = adopted.length > 0 ? this.adoptedPollOffset % adopted.length : 0;
3051
- const ordered = [
3052
- ...[...waiting.slice(waitingStart), ...waiting.slice(0, waitingStart)].map((askId) => ({ askId, tier: "waiting" })),
3053
- ...[...adopted.slice(adoptedStart), ...adopted.slice(0, adoptedStart)].map((askId) => ({ askId, tier: "adopted" })),
3054
- ];
3055
- const deadlineMs = Date.now() + SUSPENDED_POLL_BUDGET_MS;
3056
3159
  let settled = 0;
3057
- let polled = 0;
3058
- let polledWaiting = 0;
3059
- let polledAdopted = 0;
3060
- let truncated = false;
3061
- for (const { askId, tier } of ordered) {
3062
- if (polled >= SUSPENDED_POLL_MAX_PER_TICK || Date.now() >= deadlineMs) {
3063
- truncated = true;
3064
- break;
3065
- }
3066
- polled++;
3067
- if (tier === "waiting")
3068
- this.suspendedPollOffset = waitingStart + ++polledWaiting;
3069
- else
3070
- this.adoptedPollOffset = adoptedStart + ++polledAdopted;
3160
+ const { visited: polled, truncated } = await boundedRotation([
3161
+ { items: waiting, cursor: this.suspendedPollCursor },
3162
+ { items: adopted, cursor: this.adoptedPollCursor },
3163
+ ], { maxPerTick: SUSPENDED_POLL_MAX_PER_TICK, budgetMs: SUSPENDED_POLL_BUDGET_MS }, async (askId) => {
3071
3164
  let row;
3072
3165
  const readSeq = this.nextRowReadSeq();
3073
3166
  try {
@@ -3075,24 +3168,24 @@ export class ToolApprovalCoordinator {
3075
3168
  }
3076
3169
  catch (err) {
3077
3170
  this.noteStoreError(err, "getAsk(poll-suspended)");
3078
- continue;
3171
+ return;
3079
3172
  }
3080
3173
  if (row === null) {
3081
3174
  this.forgetAdoptedAsk(askId);
3082
- continue;
3175
+ return;
3083
3176
  }
3084
3177
  if (row.state === "STREAM_PENDING") {
3085
3178
  this.followRowOwner(askId, row.owner, readSeq);
3086
- continue;
3179
+ return;
3087
3180
  }
3088
3181
  if (this.editedDecisionInFlight(askId))
3089
- continue;
3182
+ return;
3090
3183
  const endingCause = askRowEndingCause(row);
3091
3184
  if (endingCause !== undefined) {
3092
3185
  const before = this.waitingRegistrationCount(askId);
3093
3186
  this.settleVoidedSiblings([askId], endingCause);
3094
3187
  settled += before;
3095
- continue;
3188
+ return;
3096
3189
  }
3097
3190
  const outcome = replayTerminalAskRow(row);
3098
3191
  if (outcome === "unavailable") {
@@ -3100,11 +3193,11 @@ export class ToolApprovalCoordinator {
3100
3193
  const parkCause = askRowParkTrigger(row) ?? (Date.now() >= row.expiresAtMs ? APPROVAL_ENDING_CAUSES.TTL_EXPIRED : APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
3101
3194
  this.settleSameAskIdParkRoute(askId, parkCause);
3102
3195
  settled += before;
3103
- continue;
3196
+ return;
3104
3197
  }
3105
3198
  const delivered = this.notifyExternalDecision(askId, outcome).settled;
3106
3199
  settled += delivered;
3107
- }
3200
+ });
3108
3201
  const backlog = Math.max(0, waiting.length + adopted.length - polled);
3109
3202
  if (truncated) {
3110
3203
  defaultLogger.warn("approval_suspended_poll_truncated", {
@@ -3113,8 +3206,8 @@ export class ToolApprovalCoordinator {
3113
3206
  backlog,
3114
3207
  maxPerTick: SUSPENDED_POLL_MAX_PER_TICK,
3115
3208
  budgetMs: SUSPENDED_POLL_BUDGET_MS,
3116
- resumeFromOffset: this.suspendedPollOffset,
3117
- resumeAdoptedFromOffset: this.adoptedPollOffset,
3209
+ resumeFromOffset: this.suspendedPollCursor.offset,
3210
+ resumeAdoptedFromOffset: this.adoptedPollCursor.offset,
3118
3211
  });
3119
3212
  }
3120
3213
  return { polled, settled, backlog, truncated };