@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
@@ -1,7 +1,8 @@
1
1
  import { once } from "node:events";
2
2
  import { sessionLogDigest, sessionLogDigestsComparable, validateFileHistoryExport } from "@sema-agent/core";
3
3
  import { StringDecoder } from "node:string_decoder";
4
- import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
4
+ import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory } from "../../session-sync.js";
5
+ import { fileHistoryImportFace } from "../../plugins/landed-attendants.js";
5
6
  import * as facts from "../../capability-facts.js";
6
7
  import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntryError, newStagingToken, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
7
8
  import { isUuidShape } from "../../security.js";
@@ -9,7 +10,7 @@ import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logReque
9
10
  import { StampedSessionArchiveError } from "../../session-error-wire.js";
10
11
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
11
12
  import { storeFaultClassOf } from "../../plugins/store-fault.js";
12
- import { enter, readSessionAgent, readSyncGate, refuse } from "../owner-access.js";
13
+ import { enter, readSessionAgent, readSyncGate, recheckSyncGate, refuse } from "../owner-access.js";
13
14
  import { listFilterOf, ownerKeyOf, sameKey } from "../../owner-faces.js";
14
15
  const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
15
16
  const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
@@ -17,6 +18,7 @@ import { SHA256_HEX_RE } from "../../digest-form.js";
17
18
  import { assertNever } from "../route-ctx.js";
18
19
  import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
19
20
  import { readSessionPinned, readSessionTitle } from "../../session-row.js";
21
+ import { incarnationLogPrefix, SessionOccupantChangedError } from "../../session-incarnation.js";
20
22
  import { defaultTaskRegistry } from "@sema-agent/core";
21
23
  import { SessionActiveRunError, SessionForkInProgressError, SessionWriterPresentError } from "../../execution-placement.js";
22
24
  import { probeSessionChildren, sessionTenantScope } from "../../session-children-retire.js";
@@ -121,6 +123,33 @@ export async function handleSessionSync(req, res, match, ctx) {
121
123
  }
122
124
  return true;
123
125
  };
126
+ const stillAdmitted = async (port) => {
127
+ const admitted = port === "pull" ? gate.face : gate.occupant;
128
+ return admitted === undefined || (await recheckSyncGate(sessionStorage, sessionId, admitted, port));
129
+ };
130
+ const pullAnswer = async (send) => {
131
+ let same;
132
+ try {
133
+ same = await stillAdmitted("pull");
134
+ }
135
+ catch (e) {
136
+ if (sendPullUnreadable(req, res, deps.logger, e))
137
+ return;
138
+ throw e;
139
+ }
140
+ if (!same) {
141
+ sendSessionAbsent("missing");
142
+ return;
143
+ }
144
+ send();
145
+ };
146
+ const pushAnswer = async (send) => {
147
+ if (!(await stillAdmitted("push"))) {
148
+ sendError(res, 409, "conflict", "sync conflict");
149
+ return;
150
+ }
151
+ send();
152
+ };
124
153
  const replaySessionRow = async (row, face) => {
125
154
  if (Object.keys(row).length === 0)
126
155
  return true;
@@ -166,9 +195,28 @@ export async function handleSessionSync(req, res, match, ctx) {
166
195
  }
167
196
  };
168
197
  const canReplayHistory = fileHistoryImportFace(historyStore) !== undefined;
169
- const replayHistory = async (envelope, allowReplace) => {
170
- const r = await replayFileHistory(historyStore, sessionId, envelope, blobGetterOf(historyStore), { allowReplace });
171
- return r.ok ? undefined : r;
198
+ const landAttendants = async (attendants, landed, plan) => {
199
+ if (plan.wipe && !(await attendants.wipe(sessionId, landed)).landed)
200
+ return { kind: "gone", leg: "wipe" };
201
+ if (plan.fileHistory !== null && canReplayHistory) {
202
+ const h = await attendants.replayHistory(sessionId, landed, plan.fileHistory, blobGetterOf(historyStore), { allowReplace: plan.allowReplace });
203
+ if (!h.landed)
204
+ return { kind: "gone", leg: "file_history" };
205
+ if (!h.value.ok)
206
+ return { kind: "history_failed", failure: h.value };
207
+ }
208
+ if (plan.policy.length > 0 && !(await attendants.replayPolicy(sessionId, landed, plan.policy, plan.operator)).landed)
209
+ return { kind: "gone", leg: "policy" };
210
+ if (plan.anchors.length > 0 && !(await attendants.putAnchors(sessionId, landed, plan.anchors, plan.owner)).landed)
211
+ return { kind: "gone", leg: "anchors" };
212
+ if (!(await replaySessionRow(plan.row, landed)))
213
+ return { kind: "gone", leg: "row" };
214
+ return { kind: "landed" };
215
+ };
216
+ const sendLandedOccupantGone = (arm, leg, landed) => {
217
+ deps.logger?.warn?.("session_sync_landed_occupant_gone", { sessionId, arm, leg, landed: incarnationLogPrefix(landed.incarnation) });
218
+ deps.metrics?.inc("session_sync_landed_occupant_gone_total", { arm, leg }, 1);
219
+ sendError(res, 409, "conflict", "sync conflict");
172
220
  };
173
221
  if (match.decl.id === "session-sync-manifest") {
174
222
  if (!pullGateOk())
@@ -186,7 +234,7 @@ export async function handleSessionSync(req, res, match, ctx) {
186
234
  sendSessionAbsent("missing");
187
235
  return;
188
236
  }
189
- sendJson(res, 200, { manifest: out.manifest });
237
+ await pullAnswer(() => sendJson(res, 200, { manifest: out.manifest }));
190
238
  return;
191
239
  }
192
240
  if (match.decl.id === "session-sync-entries") {
@@ -202,9 +250,11 @@ export async function handleSessionSync(req, res, match, ctx) {
202
250
  }
203
251
  afterSeq = n;
204
252
  }
253
+ const recheck = async () => { if (!(await stillAdmitted("pull")))
254
+ throw new SessionOccupantChangedError(sessionId); };
205
255
  let it;
206
256
  try {
207
- it = await sessionStorage.exportEntriesStream(sessionId, afterSeq === undefined ? undefined : { afterSeq });
257
+ it = await sessionStorage.exportEntriesStream(sessionId, { ...(afterSeq === undefined ? {} : { afterSeq }), recheck });
208
258
  }
209
259
  catch (e) {
210
260
  if (sendPullUnreadable(req, res, deps.logger, e))
@@ -215,6 +265,19 @@ export async function handleSessionSync(req, res, match, ctx) {
215
265
  sendSessionAbsent("missing");
216
266
  return;
217
267
  }
268
+ let openSame;
269
+ try {
270
+ openSame = await stillAdmitted("pull");
271
+ }
272
+ catch (e) {
273
+ if (sendPullUnreadable(req, res, deps.logger, e))
274
+ return;
275
+ throw e;
276
+ }
277
+ if (!openSame) {
278
+ sendSessionAbsent("missing");
279
+ return;
280
+ }
218
281
  res.writeHead(200, { "content-type": "application/x-ndjson", "transfer-encoding": "chunked" });
219
282
  const write = async (line) => { if (!res.write(line))
220
283
  await once(res, "drain"); };
@@ -229,7 +292,10 @@ export async function handleSessionSync(req, res, match, ctx) {
229
292
  res.end();
230
293
  }
231
294
  catch (e) {
232
- deps.logger?.error?.("sync entries stream failed", { sessionId, count, error: msg(e) });
295
+ if (e instanceof SessionOccupantChangedError)
296
+ deps.logger?.info?.("session_sync_pull_occupant_changed", { sessionId, count });
297
+ else
298
+ deps.logger?.error?.("sync entries stream failed", { sessionId, count, error: msg(e) });
233
299
  res.end();
234
300
  }
235
301
  return;
@@ -264,7 +330,7 @@ export async function handleSessionSync(req, res, match, ctx) {
264
330
  }
265
331
  const referenced = envelope !== null && envelope.tracked.some((t) => t.versions.some((v) => v.blobHash === hash));
266
332
  if (!referenced) {
267
- sendError(res, 404, "not_found.blob", "blob not found");
333
+ await pullAnswer(() => sendError(res, 404, "not_found.blob", "blob not found"));
268
334
  return;
269
335
  }
270
336
  let bytes;
@@ -276,12 +342,15 @@ export async function handleSessionSync(req, res, match, ctx) {
276
342
  return;
277
343
  throw e;
278
344
  }
279
- if (!bytes) {
280
- sendError(res, 404, "not_found.blob", "blob not found");
345
+ const found = bytes;
346
+ if (!found) {
347
+ await pullAnswer(() => sendError(res, 404, "not_found.blob", "blob not found"));
281
348
  return;
282
349
  }
283
- res.writeHead(200, { "content-type": "application/octet-stream" });
284
- res.end(Buffer.from(bytes));
350
+ await pullAnswer(() => {
351
+ res.writeHead(200, { "content-type": "application/octet-stream" });
352
+ res.end(Buffer.from(found));
353
+ });
285
354
  return;
286
355
  }
287
356
  if (match.decl.id === "session-sync-blob-put") {
@@ -386,10 +455,14 @@ export async function handleSessionSync(req, res, match, ctx) {
386
455
  const entryIds = body.entryIds;
387
456
  const policy = policyRead.records;
388
457
  const anchors = anchorsRead.anchors;
389
- const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
458
+ const dstIds = gate.occupant === undefined ? null : ((await sessionStorage.listEntryIds?.(sessionId)) ?? null);
390
459
  const relA = classifySyncRelationshipByIds(entryIds, dstIds);
391
460
  const refusalA = await firstPhaseRefusal(sessionStorage, sessionId, relA.relation, resolution === "overwrite-dst");
392
461
  if (refusalA !== undefined) {
462
+ if (!(await stillAdmitted("push"))) {
463
+ sendError(res, 409, "conflict", "sync conflict");
464
+ return;
465
+ }
393
466
  if (refusalA.kind === "stamped") {
394
467
  if (sendSessionError(res, refusalA.error, "import"))
395
468
  return;
@@ -405,7 +478,7 @@ export async function handleSessionSync(req, res, match, ctx) {
405
478
  if (v.blobHash !== null)
406
479
  distinct.add(v.blobHash);
407
480
  if (await firstMissingBlob(facts.sessionSyncFileHistory(deps), [...distinct]) !== undefined) {
408
- sendMissingBlob(res);
481
+ await pushAnswer(() => sendMissingBlob(res));
409
482
  return;
410
483
  }
411
484
  }
@@ -432,10 +505,10 @@ export async function handleSessionSync(req, res, match, ctx) {
432
505
  case "mirror":
433
506
  break;
434
507
  case "writer":
435
- sendSessionWriter(res);
508
+ await pushAnswer(() => sendSessionWriter(res));
436
509
  return;
437
510
  case "forking":
438
- sendError(res, 409, "conflict.fork_in_progress", new SessionForkInProgressError(sessionId).message);
511
+ await pushAnswer(() => sendError(res, 409, "conflict.fork_in_progress", new SessionForkInProgressError(sessionId).message));
439
512
  return;
440
513
  default: {
441
514
  const unknownRole = placed.cell;
@@ -443,12 +516,13 @@ export async function handleSessionSync(req, res, match, ctx) {
443
516
  }
444
517
  }
445
518
  }
446
- const activeNow = await activityOf(gateFace);
519
+ const activeNow = gate.occupant === undefined ? undefined : await activityOf(gateFace);
447
520
  if (activeNow !== undefined) {
448
- sendActivity(activeNow, "open");
521
+ await pushAnswer(() => sendActivity(activeNow, "open"));
449
522
  return;
450
523
  }
451
524
  if (relA.relation === "identical" && !digestSaysDiffer) {
525
+ const attendantsA = backend.landedAttendants();
452
526
  const seat = sessionStorage.executionPlacement;
453
527
  if (seat !== undefined) {
454
528
  if (gateFace === undefined) {
@@ -474,35 +548,38 @@ export async function handleSessionSync(req, res, match, ctx) {
474
548
  }
475
549
  }
476
550
  }
477
- if (fileHistory !== null && canReplayHistory) {
478
- const failed = await replayHistory(fileHistory, resolution === "overwrite-dst");
479
- if (failed !== undefined) {
480
- sendFileHistoryReplayFailure(res, failed);
551
+ if (gateFace === undefined) {
552
+ sendError(res, 409, "conflict", "sync conflict");
553
+ return;
554
+ }
555
+ let landedA;
556
+ try {
557
+ landedA = await landAttendants(attendantsA, gateFace, { wipe: false, fileHistory, allowReplace: resolution === "overwrite-dst", policy, operator: operatorOk, anchors, owner: importingPrincipal, row });
558
+ }
559
+ catch (e) {
560
+ if (sendSessionPolicyError(res, e))
481
561
  return;
482
- }
562
+ throw e;
483
563
  }
484
- if (deps.sessionPolicyStore) {
485
- try {
486
- await replayPolicyRecords(deps.sessionPolicyStore, sessionId, policy, operatorOk);
487
- }
488
- catch (e) {
489
- if (sendSessionPolicyError(res, e))
490
- return;
491
- throw e;
564
+ switch (landedA.kind) {
565
+ case "landed":
566
+ break;
567
+ case "gone":
568
+ sendLandedOccupantGone("identical", landedA.leg, gateFace);
569
+ return;
570
+ case "history_failed":
571
+ sendFileHistoryReplayFailure(res, landedA.failure);
572
+ return;
573
+ default: {
574
+ const unhandled = landedA;
575
+ throw new Error(`session sync: unknown landing answer ${JSON.stringify(unhandled)}`);
492
576
  }
493
577
  }
494
- if (deps.resumeAnchorStore)
495
- for (const a of anchors)
496
- await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId, importingPrincipal);
497
- if (!(await replaySessionRow(row, gateFace))) {
498
- sendError(res, 409, "conflict", "sync conflict");
499
- return;
500
- }
501
- sendJson(res, 200, {
578
+ await pushAnswer(() => sendJson(res, 200, {
502
579
  relation: "identical",
503
580
  basis: payloadVerified ? "entry-ids+digest" : "entry-ids",
504
581
  payloadVerified,
505
- });
582
+ }));
506
583
  return;
507
584
  }
508
585
  const leaseKey = importLeaseKey(gate.occupant !== undefined ? gate.occupant.owner : importingPrincipal, sessionId);
@@ -514,8 +591,12 @@ export async function handleSessionSync(req, res, match, ctx) {
514
591
  const heldLease = importLeases.get(leaseKey);
515
592
  if (heldLease) {
516
593
  const retryAfterSec = staleMs > 0 ? Math.max(1, Math.ceil((staleMs - (Date.now() - heldLease.at)) / 1000)) : undefined;
517
- sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
518
- + (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
594
+ await pushAnswer(() => sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
595
+ + (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined));
596
+ return;
597
+ }
598
+ if (!(await stillAdmitted("push"))) {
599
+ sendError(res, 409, "conflict", "sync conflict");
519
600
  return;
520
601
  }
521
602
  const token = newStagingToken();
@@ -524,7 +605,7 @@ export async function handleSessionSync(req, res, match, ctx) {
524
605
  importLeases.set(leaseKey, { at: Date.now(), stagingId, sessionId });
525
606
  pendingStagings.set(stagingId, {
526
607
  handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
527
- fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOwner: owner, leaseKey,
608
+ fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOccupant: gateFace, leaseKey,
528
609
  });
529
610
  sendJson(res, 200, { stagingId, relation: relA.relation });
530
611
  return;
@@ -610,19 +691,22 @@ export async function handleSessionSync(req, res, match, ctx) {
610
691
  return;
611
692
  throw e;
612
693
  }
613
- let mayPushAtCommit;
694
+ let gateAtCommit;
614
695
  let activeAtCommit;
696
+ let activeStillAdmitted = true;
615
697
  try {
616
- const gateAtCommit = await judgeSessionGate();
617
- mayPushAtCommit = gateAtCommit.mayPush;
618
- activeAtCommit = mayPushAtCommit ? await activityOf(gateAtCommit.face) : undefined;
698
+ gateAtCommit = await judgeSessionGate();
699
+ const atCommit = gateAtCommit.occupant;
700
+ activeAtCommit = gateAtCommit.mayPush && atCommit !== undefined ? await activityOf(gateAtCommit.face) : undefined;
701
+ if (activeAtCommit !== undefined && atCommit !== undefined)
702
+ activeStillAdmitted = await recheckSyncGate(sessionStorage, sessionId, atCommit, "push");
619
703
  }
620
704
  catch (e) {
621
705
  await pending.handle.abort().catch(() => undefined);
622
706
  cleanupStaging(stagingId);
623
707
  throw e;
624
708
  }
625
- if (!mayPushAtCommit) {
709
+ if (!gateAtCommit.mayPush) {
626
710
  await pending.handle.abort().catch(() => undefined);
627
711
  cleanupStaging(stagingId);
628
712
  sendSessionAbsent("not_visible");
@@ -631,37 +715,43 @@ export async function handleSessionSync(req, res, match, ctx) {
631
715
  if (activeAtCommit !== undefined) {
632
716
  await pending.handle.abort().catch(() => undefined);
633
717
  cleanupStaging(stagingId);
718
+ if (!activeStillAdmitted) {
719
+ sendError(res, 409, "conflict", "sync conflict");
720
+ return;
721
+ }
634
722
  sendActivity(activeAtCommit, "streamed");
635
723
  return;
636
724
  }
637
725
  try {
638
- const committed = await pending.handle.commit(pending.owner, { expectedOwner: pending.expectedOwner, ...(pending.resolution ? { resolution: pending.resolution } : {}) });
639
- if (pending.resolution === "overwrite-dst" && (committed.relation === "fork" || committed.relation === "stale")) {
640
- await wipeSessionAttendants({ fileHistoryStore: historyStore, sessionPolicyStore: deps.sessionPolicyStore, resumeAnchorStore: deps.resumeAnchorStore }, sessionId);
641
- }
642
- if (pending.fileHistory !== null && canReplayHistory) {
643
- const failed = await replayHistory(pending.fileHistory, pending.resolution === "overwrite-dst");
644
- if (failed !== undefined) {
645
- sendFileHistoryReplayFailure(res, failed);
646
- cleanupStaging(stagingId);
647
- return;
648
- }
649
- }
650
- const policyStore = deps.sessionPolicyStore;
651
- if (policyStore)
652
- await replayPolicyRecords(policyStore, sessionId, pending.policy, pending.operatorOk);
653
- const anchorStore = deps.resumeAnchorStore;
654
- if (anchorStore)
655
- for (const a of pending.anchors)
656
- await anchorStore.put(sessionId, a.eventId, a.entryId, pending.owner);
657
- const rowLanded = await replaySessionRow(pending.row, committed.face);
726
+ const attendantsB = backend.landedAttendants();
727
+ const committed = await pending.handle.commit(pending.owner, { expectedOccupant: pending.expectedOccupant, ...(pending.resolution ? { resolution: pending.resolution } : {}) });
658
728
  sessionStorage.evict?.(sessionId);
729
+ const landedB = await landAttendants(attendantsB, committed.face, {
730
+ wipe: pending.resolution === "overwrite-dst" && (committed.relation === "fork" || committed.relation === "stale"),
731
+ fileHistory: pending.fileHistory,
732
+ allowReplace: pending.resolution === "overwrite-dst",
733
+ policy: pending.policy,
734
+ operator: pending.operatorOk,
735
+ anchors: pending.anchors,
736
+ owner: pending.owner,
737
+ row: pending.row,
738
+ });
659
739
  cleanupStaging(stagingId);
660
- if (!rowLanded) {
661
- sendError(res, 409, "conflict", "sync conflict");
662
- return;
740
+ switch (landedB.kind) {
741
+ case "landed":
742
+ sendJson(res, 200, { relation: committed.relation });
743
+ return;
744
+ case "gone":
745
+ sendLandedOccupantGone("commit", landedB.leg, committed.face);
746
+ return;
747
+ case "history_failed":
748
+ sendFileHistoryReplayFailure(res, landedB.failure);
749
+ return;
750
+ default: {
751
+ const unhandled = landedB;
752
+ throw new Error(`session sync: unknown landing answer ${JSON.stringify(unhandled)}`);
753
+ }
663
754
  }
664
- sendJson(res, 200, { relation: committed.relation });
665
755
  }
666
756
  catch (e) {
667
757
  await pending.handle.abort().catch(() => undefined);
@@ -700,9 +790,17 @@ export async function handleSessionSync(req, res, match, ctx) {
700
790
  sendError(res, 400, "request.field_invalid", "entryIds must be an array of strings");
701
791
  return;
702
792
  }
703
- const dst = await sessionStorage.exportEntries(sessionId);
793
+ const dst = gate.occupant === undefined ? null : await sessionStorage.exportEntries(sessionId);
704
794
  const relation = classifySyncRelationshipByIds(body.entryIds, dst ? dst.map((e) => e.id) : null);
795
+ if (gate.occupant === undefined) {
796
+ sendJson(res, 200, relation);
797
+ return;
798
+ }
705
799
  const refusal = await firstPhaseRefusal(sessionStorage, sessionId, relation.relation, false);
800
+ if (!(await stillAdmitted("push"))) {
801
+ sendError(res, 409, "conflict", "sync conflict");
802
+ return;
803
+ }
706
804
  if (refusal?.kind === "stamped") {
707
805
  if (sendSessionError(res, refusal.error, "import"))
708
806
  return;
@@ -791,9 +889,6 @@ function sendSessionSyncUnavailable(res, why) {
791
889
  case "session_import_staging_absent":
792
890
  sendError(res, 501, "capability.session_store_required", "staged session import requires a durable session store");
793
891
  return;
794
- case "file_history_store_absent":
795
- sendError(res, 501, "capability.snapshot_store_required", "session sync is not available on this deployment: no file-history store is wired, so there is no file history to export or import");
796
- return;
797
892
  case "policy_purge_absent":
798
893
  sendError(res, 501, "capability.policy_purge_required", "session sync is not available on this deployment: the session-policy store has no deleteBySession purge verb, so an overwrite-dst import could not wipe the destination's stale policy rows. Every in-tree policy store carries it since core 1.423 — upgrade the policy store");
799
894
  return;
@@ -1,8 +1,9 @@
1
1
  import { isUuidV7 } from "../../security.js";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { sendUnauthorized } from "../principal-gate.js";
4
- import { routeListFilter } from "../owner-access.js";
4
+ import { listOwnerOf, listVisibleSessions, routeListFilter } from "../owner-access.js";
5
5
  import { filterFingerprint } from "../cursor-fingerprint.js";
6
+ import { SESSION_KINDS, isSessionKind } from "../../session-kind.js";
6
7
  import { sessionTempoOfRunStatus } from "../../plugins/store-contracts.js";
7
8
  import { assertNever } from "../route-ctx.js";
8
9
  import { PRINCIPAL_MAX_BYTES } from "../../principal-shape.js";
@@ -21,8 +22,7 @@ export async function handleSessionsList(req, res, match, ctx) {
21
22
  sendError(res, 501, "capability.session_store_required", "session list requires a durable session store (DB_BACKEND=mysql|pg|local)");
22
23
  return;
23
24
  }
24
- const lister = listing.lister;
25
- await handleSessionList(res, lister, new URLSearchParams(match.search), gateOwner, includeUnowned);
25
+ await handleSessionList(res, listing.lister, new URLSearchParams(match.search), { fleetWide, gateOwner, includeUnowned });
26
26
  return;
27
27
  }
28
28
  case "sendfile-links": {
@@ -109,18 +109,24 @@ function decodeSessionCursor(s, expectF) {
109
109
  return undefined;
110
110
  }
111
111
  }
112
- async function handleSessionList(res, lister, query, forceOwner, includeUnowned = false) {
113
- const owner = forceOwner != null ? forceOwner : (query.get("owner") ?? undefined);
112
+ async function handleSessionList(res, lister, query, filter) {
113
+ const kindRaw = query.get("sessionKind");
114
+ if (kindRaw !== null && !isSessionKind(kindRaw)) {
115
+ sendError(res, 400, "request.query_invalid", `sessionKind must be one of ${SESSION_KINDS.map((w) => JSON.stringify(w)).join(" | ")} when present (absent = the default view: top-level sessions only)`);
116
+ return;
117
+ }
118
+ const sessionKind = kindRaw ?? undefined;
119
+ const owner = listOwnerOf(filter, query.get("owner") ?? undefined);
114
120
  const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 50) || 50));
115
121
  const qRaw = (query.get("q") ?? "").trim();
116
122
  const q = qRaw ? qRaw.slice(0, 256) : undefined;
117
- const f = filterFingerprint([owner, includeUnowned, q]);
123
+ const f = filterFingerprint(sessionKind === undefined ? [owner, filter.includeUnowned, q] : [owner, filter.includeUnowned, q, sessionKind]);
118
124
  const cursor = decodeSessionCursor(query.get("cursor"), f);
119
125
  if (cursor === "mismatch") {
120
126
  sendError(res, 400, "request.cursor_filter_mismatch", "cursor was issued under a different filter (owner/q) — restart from the first page without a cursor");
121
127
  return;
122
128
  }
123
- const rows = await lister({ ...(owner !== undefined ? { owner } : {}), ...(includeUnowned ? { includeUnowned: true } : {}), ...(cursor ? { cursor } : {}), limit: limit + 1, ...(q ? { q } : {}) });
129
+ const rows = await listVisibleSessions(lister, filter, query.get("owner") ?? undefined, { ...(cursor ? { cursor } : {}), limit: limit + 1, ...(q ? { q } : {}), ...(sessionKind !== undefined ? { sessionKind } : {}) });
124
130
  const hasMore = rows.length > limit;
125
131
  const page = hasMore ? rows.slice(0, limit) : rows;
126
132
  const last = page[page.length - 1];
@@ -15,7 +15,7 @@ import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeade
15
15
  import { gatedPerson, gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
16
16
  import { personOf } from "../../owner-brands.js";
17
17
  import { leafOfAdmittedHead, sessionFaceOfHead } from "../../session-incarnation.js";
18
- import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse, sessionOwnerUnchanged } from "../owner-access.js";
18
+ import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse, sessionOccupantUnchanged } from "../owner-access.js";
19
19
  import { assertNever } from "../route-ctx.js";
20
20
  import * as facts from "../../capability-facts.js";
21
21
  import { rosterAgentId } from "./agents-roster.js";
@@ -462,7 +462,7 @@ export async function handleSessions(req, res, match, ctx) {
462
462
  }
463
463
  ownerCheckPending = true;
464
464
  ownerCheckStartedAt = Date.now();
465
- void sessionOwnerUnchanged(evEntry, evFact.sessionStorage, evSession, owner).then((same) => {
465
+ void sessionOccupantUnchanged(evEntry, evFact.sessionStorage, evSession, evAdmitted.row).then((same) => {
466
466
  ownerCheckPending = false;
467
467
  if (!closed && !same)
468
468
  onEnd();
@@ -592,7 +592,7 @@ export async function handleSessions(req, res, match, ctx) {
592
592
  sendJson(res, 200, {
593
593
  sessionId,
594
594
  autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
595
- ...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked().map((p) => ({ scope: p.scope, wayOut: p.wayOut })) } } : {}),
595
+ ...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked() } } : {}),
596
596
  ...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
597
597
  ...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
598
598
  ...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
@@ -905,7 +905,7 @@ export async function handleSessions(req, res, match, ctx) {
905
905
  sendError(res, 501, "capability.session_store_required", "session policy requires a session-store backend");
906
906
  return;
907
907
  }
908
- const store = policyFact.policyStore;
908
+ const store = policyFact.backend.sessionPolicy();
909
909
  const policyEntry = enter(req, res, deps, match.decl.id);
910
910
  if (policyEntry === undefined)
911
911
  return;
@@ -947,8 +947,12 @@ export async function handleSessions(req, res, match, ctx) {
947
947
  }
948
948
  const rules = read.rules;
949
949
  try {
950
- const stored = await store.putRules(sessionId, target, rules, { ...(typeof body.expectedRev === "number" ? { expectedRev: body.expectedRev } : {}), operator });
951
- sendJson(res, 200, { rules: stored });
950
+ const stored = await policyFact.backend.landedAttendants().putPolicy(sessionId, sessionFaceOfHead(sessionId, policyAdmitted.row), target, rules, { ...(typeof body.expectedRev === "number" ? { expectedRev: body.expectedRev } : {}), operator });
951
+ if (!stored.landed) {
952
+ refuse(policyEntry, res, "missing");
953
+ return;
954
+ }
955
+ sendJson(res, 200, { rules: stored.value });
952
956
  }
953
957
  catch (e) {
954
958
  if (sendSessionPolicyError(res, e))