@timurproko/a1 0.2.7-dev.1249 → 0.2.7-dev.1252

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 (340) hide show
  1. package/README.md +11 -36
  2. package/bin/cli.js +2 -17
  3. package/bin/ui.js +7 -48
  4. package/dist/app/session-shell/exit-notice.d.ts +2 -2
  5. package/dist/app/session-shell/exit-notice.js +10 -10
  6. package/dist/app/session-shell/prompt-submission.d.ts +2 -4
  7. package/dist/app/session-shell/prompt-submission.js +3 -23
  8. package/dist/app/session-shell/response-copy-transport.js +1 -2
  9. package/dist/app/session-shell/session-lifecycle.d.ts +1 -2
  10. package/dist/app/session-shell/session-lifecycle.js +2 -4
  11. package/dist/app/session-shell/session-shell-root.d.ts +0 -31
  12. package/dist/app/session-shell/session-shell-root.js +3 -4
  13. package/dist/app/session-shell/session-shell.js +6 -47
  14. package/dist/app/session-shell/session-viewport-controller.js +3 -6
  15. package/dist/app/session-shell/shell-host.js +2 -18
  16. package/dist/app/session-shell/transcript-document.d.ts +1 -2
  17. package/dist/app/session-shell/transcript-document.js +3 -28
  18. package/dist/app/session-shell/workflow-output.js +1 -1
  19. package/dist/app/session-shell/workflow-router.d.ts +1 -3
  20. package/dist/app/session-shell/workflow-router.js +1 -4
  21. package/dist/cli/dispatch.d.ts +1 -11
  22. package/dist/cli/dispatch.js +5 -92
  23. package/dist/cli/session-context.js +5 -7
  24. package/dist/cli/uninstall.d.ts +1 -5
  25. package/dist/cli/uninstall.js +6 -35
  26. package/dist/cli/version-stats.js +4 -7
  27. package/dist/composition/owned-settings.d.ts +0 -1
  28. package/dist/composition/owned-settings.js +0 -1
  29. package/dist/composition/owned-ui.d.ts +2 -24
  30. package/dist/composition/owned-ui.js +11 -87
  31. package/dist/composition/release-update-observation.d.ts +4 -4
  32. package/dist/composition/release-update-observation.js +17 -47
  33. package/dist/composition/session-backend.d.ts +0 -1
  34. package/dist/composition/session-runtime-registry.d.ts +3 -3
  35. package/dist/composition/settings-route-host.d.ts +0 -7
  36. package/dist/composition/settings-route-host.js +4 -10
  37. package/dist/composition/startup-route.d.ts +0 -1
  38. package/dist/composition/startup-route.js +0 -1
  39. package/dist/contracts/owned-ui/index.d.ts +1 -1
  40. package/dist/contracts/owned-ui/model.d.ts +0 -3
  41. package/dist/contracts/owned-ui/session-workflows.d.ts +1 -4
  42. package/dist/features/launch/development-launch.js +0 -1
  43. package/dist/features/launch/index.d.ts +1 -1
  44. package/dist/features/launch/intent.d.ts +1 -3
  45. package/dist/features/launch/intent.js +2 -4
  46. package/dist/features/launch/session-context.d.ts +2 -2
  47. package/dist/features/owned-ui/reference-screen-app.d.ts +0 -2
  48. package/dist/features/owned-ui/reference-screen-app.js +2 -9
  49. package/dist/features/owned-ui/settings-app.d.ts +0 -6
  50. package/dist/features/owned-ui/settings-app.js +5 -36
  51. package/dist/foundation/launch-context/index.d.ts +0 -12
  52. package/dist/foundation/launch-context/index.js +0 -26
  53. package/dist/foundation/lifecycle/claim-gate.d.ts +8 -0
  54. package/dist/foundation/lifecycle/claim-gate.js +37 -0
  55. package/dist/foundation/lifecycle/index.d.ts +5 -9
  56. package/dist/foundation/lifecycle/index.js +3 -4
  57. package/dist/foundation/lifecycle/model.d.ts +2 -59
  58. package/dist/foundation/lifecycle/model.js +0 -35
  59. package/dist/foundation/lifecycle/paths.d.ts +0 -24
  60. package/dist/foundation/lifecycle/paths.js +0 -40
  61. package/dist/foundation/lifecycle/process-liveness.d.ts +8 -0
  62. package/dist/foundation/lifecycle/process-liveness.js +18 -0
  63. package/dist/foundation/lifecycle/session-claim-lock.d.ts +1 -1
  64. package/dist/foundation/lifecycle/session-claim-lock.js +2 -2
  65. package/dist/foundation/lifecycle/session-repository-context.d.ts +6 -6
  66. package/dist/foundation/lifecycle/session-repository-context.js +2 -3
  67. package/dist/foundation/release/automatic-update.d.ts +1 -3
  68. package/dist/foundation/release/automatic-update.js +9 -30
  69. package/dist/foundation/release/bootstrap.d.ts +2 -25
  70. package/dist/foundation/release/bootstrap.js +33 -344
  71. package/dist/foundation/release/cohort-state.d.ts +21 -46
  72. package/dist/foundation/release/cohort-state.js +30 -62
  73. package/dist/foundation/release/dependency-certification.d.ts +1 -1
  74. package/dist/foundation/release/index.d.ts +10 -17
  75. package/dist/foundation/release/index.js +7 -11
  76. package/dist/foundation/release/latest-release.d.ts +1 -2
  77. package/dist/foundation/release/latest-release.js +1 -2
  78. package/dist/foundation/release/process-liveness.d.ts +2 -0
  79. package/dist/foundation/release/process-liveness.js +12 -0
  80. package/dist/foundation/release/release-gc.d.ts +12 -9
  81. package/dist/foundation/release/release-gc.js +34 -98
  82. package/dist/foundation/release/release-store.d.ts +2 -2
  83. package/dist/foundation/release/release-store.js +2 -2
  84. package/dist/foundation/release/restart-certification.js +1 -1
  85. package/dist/foundation/release/update-activation.d.ts +2 -9
  86. package/dist/foundation/release/update-activation.js +28 -43
  87. package/dist/foundation/release/update-execution-lease.js +1 -1
  88. package/dist/foundation/release/update-launch.d.ts +0 -2
  89. package/dist/foundation/release/update-launch.js +0 -7
  90. package/dist/foundation/release/update-recovery.js +1 -1
  91. package/dist/foundation/release/update-transaction.d.ts +1 -1
  92. package/dist/foundation/release/update-transaction.js +5 -2
  93. package/dist/foundation/release/update.d.ts +2 -10
  94. package/dist/foundation/release/update.js +23 -200
  95. package/dist/foundation/session-writer-lock/index.d.ts +14 -6
  96. package/dist/foundation/session-writer-lock/index.js +48 -103
  97. package/dist/foundation/startup/startup-runtime.d.ts +1 -1
  98. package/dist/foundation/terminal-cleanup/exit-notice.d.ts +6 -0
  99. package/dist/foundation/terminal-cleanup/exit-notice.js +36 -0
  100. package/dist/foundation/terminal-cleanup/index.d.ts +2 -1
  101. package/dist/foundation/terminal-cleanup/index.js +2 -1
  102. package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +0 -5
  103. package/dist/foundation/terminal-cleanup/terminal-reset.js +0 -5
  104. package/dist/integrations/pi/components/index.d.ts +1 -1
  105. package/dist/integrations/pi/components/index.js +1 -1
  106. package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -4
  107. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  108. package/dist/integrations/pi/components/shell-footer-status.js +2 -2
  109. package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
  110. package/dist/integrations/pi/components/shell-presenters-info.d.ts +2 -5
  111. package/dist/integrations/pi/components/shell-presenters-info.js +4 -18
  112. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +1 -1
  113. package/dist/integrations/pi/components/shell-presenters-transcript.js +1 -1
  114. package/dist/integrations/pi/components/shell-shared-facade.d.ts +0 -2
  115. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +2 -5
  116. package/dist/integrations/pi/components/submitted-prompt-adapter.js +2 -16
  117. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +1 -2
  118. package/dist/integrations/pi/components/upstream/components/session-footer.js +6 -8
  119. package/dist/integrations/pi/engine/adapter.d.ts +0 -1
  120. package/dist/integrations/pi/engine/adapter.js +1 -16
  121. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -1
  122. package/dist/integrations/pi/engine/diagnostics-state.js +0 -14
  123. package/dist/integrations/pi/engine/prompt-suggestions.d.ts +0 -1
  124. package/dist/integrations/pi/engine/prompt-suggestions.js +0 -40
  125. package/dist/integrations/pi/engine/session-runtime.js +0 -2
  126. package/dist/integrations/pi/engine/workflow-runner.js +1 -1
  127. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -2
  128. package/dist/integrations/pi/tui-runtime/adapter.js +2 -2
  129. package/dist/product-identity.d.ts +4 -6
  130. package/dist/product-identity.js +6 -11
  131. package/dist/product-identity.json +1 -40
  132. package/dist/runtime-payload-inventory.json +0 -3
  133. package/dist/ui/components/index.d.ts +2 -2
  134. package/dist/ui/components/index.js +1 -1
  135. package/dist/ui/components/submitted-prompt.d.ts +3 -13
  136. package/dist/ui/components/submitted-prompt.js +8 -41
  137. package/dist/ui/components/transcript-viewport.d.ts +1 -3
  138. package/dist/ui/components/transcript-viewport.js +1 -4
  139. package/dist/ui/settings/declarations.d.ts +3 -97
  140. package/dist/ui/settings/declarations.js +3 -78
  141. package/dist/ui/settings/migrations.js +18 -2
  142. package/docs/architecture/boundaries.md +22 -39
  143. package/docs/architecture/code-documentation.md +2 -2
  144. package/docs/architecture/internal-naming.md +9 -17
  145. package/docs/architecture/project-structure.md +9 -14
  146. package/docs/architecture/resource-and-data-policy.md +16 -31
  147. package/docs/architecture/toolchain.md +14 -19
  148. package/docs/architecture/ui-reference-provenance.md +2 -2
  149. package/docs/ci-release-runbook.md +5 -5
  150. package/docs/features/launch-profiles.md +11 -17
  151. package/docs/features/prompt-history.md +10 -11
  152. package/docs/local-worktree-cleanup.md +28 -1
  153. package/docs/manual-owned-ui-checkpoint.md +0 -1
  154. package/docs/validation.md +5 -11
  155. package/docs/worktree-link-recovery.md +6 -6
  156. package/package.json +7 -19
  157. package/bin/guardian.js +0 -42
  158. package/bin/supervisor.js +0 -7
  159. package/bin/tab-launch-context.d.ts +0 -27
  160. package/bin/tab-launch-context.js +0 -94
  161. package/dist/app/session-shell/resident-tab-coordinator.d.ts +0 -38
  162. package/dist/app/session-shell/resident-tab-coordinator.js +0 -77
  163. package/dist/cli/tabs-request.d.ts +0 -7
  164. package/dist/cli/tabs-request.js +0 -1
  165. package/dist/cli/tabs.d.ts +0 -14
  166. package/dist/cli/tabs.js +0 -151
  167. package/dist/composition/resident-tab-bridge.d.ts +0 -41
  168. package/dist/composition/resident-tab-bridge.js +0 -209
  169. package/dist/composition/utility-tab.d.ts +0 -28
  170. package/dist/composition/utility-tab.js +0 -244
  171. package/dist/features/owned-ui/plus-menu-dialog.d.ts +0 -28
  172. package/dist/features/owned-ui/plus-menu-dialog.js +0 -89
  173. package/dist/features/resident-tab/auto-name.d.ts +0 -11
  174. package/dist/features/resident-tab/auto-name.js +0 -44
  175. package/dist/features/resident-tab/index.d.ts +0 -5
  176. package/dist/features/resident-tab/index.js +0 -7
  177. package/dist/features/resident-tab/shortcut-bindings-file.d.ts +0 -8
  178. package/dist/features/resident-tab/shortcut-bindings-file.js +0 -40
  179. package/dist/features/resident-tab/shortcut-declarations.d.ts +0 -13
  180. package/dist/features/resident-tab/shortcut-declarations.js +0 -26
  181. package/dist/features/resident-tab/shortcut-projection.d.ts +0 -29
  182. package/dist/features/resident-tab/shortcut-projection.js +0 -64
  183. package/dist/features/resident-tab/status-deriver.d.ts +0 -10
  184. package/dist/features/resident-tab/status-deriver.js +0 -12
  185. package/dist/foundation/launch-guardian/exit-watcher.d.ts +0 -14
  186. package/dist/foundation/launch-guardian/exit-watcher.js +0 -103
  187. package/dist/foundation/launch-guardian/index.d.ts +0 -2
  188. package/dist/foundation/launch-guardian/index.js +0 -1
  189. package/dist/foundation/launch-guardian/main.d.ts +0 -39
  190. package/dist/foundation/launch-guardian/main.js +0 -232
  191. package/dist/foundation/lifecycle/commands.d.ts +0 -40
  192. package/dist/foundation/lifecycle/commands.js +0 -1
  193. package/dist/foundation/lifecycle/launch-instance.d.ts +0 -46
  194. package/dist/foundation/lifecycle/launch-instance.js +0 -134
  195. package/dist/foundation/lifecycle/native-claim-gate.d.ts +0 -5
  196. package/dist/foundation/lifecycle/native-claim-gate.js +0 -41
  197. package/dist/foundation/lifecycle/supervisor-startup.d.ts +0 -30
  198. package/dist/foundation/lifecycle/supervisor-startup.js +0 -106
  199. package/dist/foundation/process-containment/addon-containment.d.ts +0 -20
  200. package/dist/foundation/process-containment/addon-containment.js +0 -139
  201. package/dist/foundation/process-containment/addon-process-inspector.d.ts +0 -11
  202. package/dist/foundation/process-containment/addon-process-inspector.js +0 -36
  203. package/dist/foundation/process-containment/addon.d.ts +0 -47
  204. package/dist/foundation/process-containment/addon.js +0 -57
  205. package/dist/foundation/process-containment/artifact.d.ts +0 -1
  206. package/dist/foundation/process-containment/artifact.js +0 -35
  207. package/dist/foundation/process-containment/contracts.d.ts +0 -33
  208. package/dist/foundation/process-containment/contracts.js +0 -35
  209. package/dist/foundation/process-containment/index.d.ts +0 -9
  210. package/dist/foundation/process-containment/index.js +0 -7
  211. package/dist/foundation/process-containment/linux-process-inspector.d.ts +0 -7
  212. package/dist/foundation/process-containment/linux-process-inspector.js +0 -27
  213. package/dist/foundation/process-containment/native-process-inspector.d.ts +0 -4
  214. package/dist/foundation/process-containment/native-process-inspector.js +0 -21
  215. package/dist/foundation/protocol/client.d.ts +0 -18
  216. package/dist/foundation/protocol/client.js +0 -130
  217. package/dist/foundation/protocol/index.d.ts +0 -3
  218. package/dist/foundation/protocol/index.js +0 -2
  219. package/dist/foundation/protocol/messages.d.ts +0 -90
  220. package/dist/foundation/protocol/messages.js +0 -100
  221. package/dist/foundation/release/cohort-selection.d.ts +0 -33
  222. package/dist/foundation/release/cohort-selection.js +0 -67
  223. package/dist/foundation/release/endpoint-lock.d.ts +0 -2
  224. package/dist/foundation/release/endpoint-lock.js +0 -37
  225. package/dist/foundation/release/endpoint-recovery.d.ts +0 -10
  226. package/dist/foundation/release/endpoint-recovery.js +0 -45
  227. package/dist/foundation/release/endpoints.d.ts +0 -32
  228. package/dist/foundation/release/endpoints.js +0 -152
  229. package/dist/foundation/release/prepared-update-activation.d.ts +0 -3
  230. package/dist/foundation/release/prepared-update-activation.js +0 -27
  231. package/dist/foundation/release/process-cleanup.d.ts +0 -14
  232. package/dist/foundation/release/process-cleanup.js +0 -76
  233. package/dist/foundation/release/supervisor-identity.d.ts +0 -15
  234. package/dist/foundation/release/supervisor-identity.js +0 -63
  235. package/dist/foundation/resident-tabs/boot-identity.d.ts +0 -6
  236. package/dist/foundation/resident-tabs/boot-identity.js +0 -16
  237. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +0 -20
  238. package/dist/foundation/resident-tabs/bounded-host-command.js +0 -62
  239. package/dist/foundation/resident-tabs/bridge-client.d.ts +0 -73
  240. package/dist/foundation/resident-tabs/bridge-client.js +0 -327
  241. package/dist/foundation/resident-tabs/continuity.d.ts +0 -12
  242. package/dist/foundation/resident-tabs/continuity.js +0 -63
  243. package/dist/foundation/resident-tabs/doctor.d.ts +0 -28
  244. package/dist/foundation/resident-tabs/doctor.js +0 -166
  245. package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +0 -27
  246. package/dist/foundation/resident-tabs/draft-checkpoint.js +0 -145
  247. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +0 -36
  248. package/dist/foundation/resident-tabs/ensure-terminal-host.js +0 -151
  249. package/dist/foundation/resident-tabs/fault-injection.d.ts +0 -8
  250. package/dist/foundation/resident-tabs/fault-injection.js +0 -26
  251. package/dist/foundation/resident-tabs/fixture-control.d.ts +0 -7
  252. package/dist/foundation/resident-tabs/fixture-control.js +0 -35
  253. package/dist/foundation/resident-tabs/host-entry.d.ts +0 -1
  254. package/dist/foundation/resident-tabs/host-entry.js +0 -17
  255. package/dist/foundation/resident-tabs/index.d.ts +0 -15
  256. package/dist/foundation/resident-tabs/index.js +0 -10
  257. package/dist/foundation/resident-tabs/maintenance.d.ts +0 -39
  258. package/dist/foundation/resident-tabs/maintenance.js +0 -75
  259. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +0 -34
  260. package/dist/foundation/resident-tabs/progress-observation-schema.js +0 -1
  261. package/dist/foundation/resident-tabs/progress-observation.d.ts +0 -4
  262. package/dist/foundation/resident-tabs/progress-observation.js +0 -150
  263. package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -63
  264. package/dist/foundation/resident-tabs/prompt-journal.js +0 -213
  265. package/dist/foundation/resident-tabs/protocol.d.ts +0 -12
  266. package/dist/foundation/resident-tabs/protocol.js +0 -195
  267. package/dist/foundation/resident-tabs/recovery.d.ts +0 -26
  268. package/dist/foundation/resident-tabs/recovery.js +0 -205
  269. package/dist/foundation/resident-tabs/release-holds.d.ts +0 -28
  270. package/dist/foundation/resident-tabs/release-holds.js +0 -78
  271. package/dist/foundation/resident-tabs/routes.d.ts +0 -5
  272. package/dist/foundation/resident-tabs/routes.js +0 -5
  273. package/dist/foundation/resident-tabs/routing.d.ts +0 -11
  274. package/dist/foundation/resident-tabs/routing.js +0 -10
  275. package/dist/foundation/resident-tabs/runtime-control.d.ts +0 -38
  276. package/dist/foundation/resident-tabs/runtime-control.js +0 -74
  277. package/dist/foundation/resident-tabs/session-sync.d.ts +0 -14
  278. package/dist/foundation/resident-tabs/session-sync.js +0 -37
  279. package/dist/foundation/resident-tabs/settings.d.ts +0 -16
  280. package/dist/foundation/resident-tabs/settings.js +0 -60
  281. package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +0 -14
  282. package/dist/foundation/resident-tabs/terminal-host-artifact.js +0 -78
  283. package/dist/foundation/resident-tabs/zip-archive.d.ts +0 -9
  284. package/dist/foundation/resident-tabs/zip-archive.js +0 -56
  285. package/dist/foundation/storage/control-store.d.ts +0 -20
  286. package/dist/foundation/storage/control-store.js +0 -331
  287. package/dist/foundation/storage/index.d.ts +0 -1
  288. package/dist/foundation/storage/index.js +0 -1
  289. package/dist/foundation/supervision/index.d.ts +0 -3
  290. package/dist/foundation/supervision/index.js +0 -3
  291. package/dist/foundation/supervision/main.d.ts +0 -1
  292. package/dist/foundation/supervision/main.js +0 -86
  293. package/dist/foundation/supervision/paths.d.ts +0 -1
  294. package/dist/foundation/supervision/paths.js +0 -1
  295. package/dist/foundation/supervision/server.d.ts +0 -46
  296. package/dist/foundation/supervision/server.js +0 -611
  297. package/dist/integrations/pi/engine/offline-settings-port.d.ts +0 -23
  298. package/dist/integrations/pi/engine/offline-settings-port.js +0 -54
  299. package/dist/native/darwin-arm64/manifest.json +0 -23
  300. package/dist/native/darwin-arm64/process-guardian.node +0 -0
  301. package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10500
  302. package/dist/native/darwin-arm64/terminal-host.manifest.json +0 -46
  303. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  304. package/dist/native/darwin-x64/manifest.json +0 -23
  305. package/dist/native/darwin-x64/process-guardian.node +0 -0
  306. package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10737
  307. package/dist/native/darwin-x64/terminal-host.manifest.json +0 -46
  308. package/dist/native/darwin-x64/terminal-host.node +0 -0
  309. package/dist/native/linux-x64/manifest.json +0 -23
  310. package/dist/native/linux-x64/process-guardian.node +0 -0
  311. package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -11226
  312. package/dist/native/linux-x64/terminal-host.manifest.json +0 -46
  313. package/dist/native/linux-x64/terminal-host.node +0 -0
  314. package/dist/native/win32-x64/manifest.json +0 -23
  315. package/dist/native/win32-x64/process-guardian.node +0 -0
  316. package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +0 -10757
  317. package/dist/native/win32-x64/terminal-host.manifest.json +0 -46
  318. package/dist/native/win32-x64/terminal-host.node +0 -0
  319. package/docs/architecture/evidence/resident-tabs/linux-x64.md +0 -37
  320. package/docs/architecture/evidence/resident-tabs/macos-arm64.md +0 -36
  321. package/docs/architecture/evidence/resident-tabs/macos-x64.md +0 -35
  322. package/docs/architecture/evidence/resident-tabs/windows-x64.md +0 -36
  323. package/docs/architecture/evidence/terminal-host-spike/manual-one-pane-terminal-host.md +0 -70
  324. package/docs/architecture/evidence/terminal-host-spike/manual-two-by-two-basic-terminal-host.md +0 -32
  325. package/docs/architecture/evidence/terminal-host-spike/milestone-baseline.json +0 -115
  326. package/docs/architecture/evidence/terminal-host-spike/owned-ui-foundation-prerequisite.json +0 -48
  327. package/docs/architecture/evidence/terminal-host-spike/terminal-host-provenance.json +0 -125
  328. package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-1.json +0 -56
  329. package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-3.json +0 -174
  330. package/docs/architecture/evidence/terminal-host-spike/terminal-spike-acceptance-record.json +0 -18
  331. package/docs/architecture/native-fixture-isolation.md +0 -23
  332. package/docs/architecture/process-guardian-provenance.md +0 -36
  333. package/docs/architecture/resident-tabs-requirements.md +0 -18
  334. package/docs/architecture/resident-tabs.md +0 -668
  335. package/docs/architecture/terminal-host-proof-gate.md +0 -34
  336. package/docs/architecture/terminal-host-provenance.md +0 -81
  337. package/docs/architecture/terminal-host-spike-evidence.md +0 -39
  338. package/docs/launch-recovery.md +0 -23
  339. package/docs/manual-launch-instance-acceptance.md +0 -43
  340. package/docs/manual-resident-tabs-acceptance.md +0 -51
@@ -1,46 +0,0 @@
1
- import { rename, rm, writeFile } from "node:fs/promises";
2
- import { type SupervisorSnapshot } from "../lifecycle/index.js";
3
- import { type MaterializedRelease } from "../release/index.js";
4
- import { ControlStore } from "../storage/index.js";
5
- import { type ProductPaths } from "./paths.js";
6
- interface EndpointMetadataCommitOperations {
7
- readonly platform?: NodeJS.Platform;
8
- readonly write?: typeof writeFile;
9
- readonly replace?: typeof rename;
10
- readonly remove?: typeof rm;
11
- readonly wait?: (delayMs: number) => Promise<void>;
12
- }
13
- /** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
14
- export declare function commitEndpointMetadata(path: string, source: string, operations?: EndpointMetadataCommitOperations): Promise<void>;
15
- /** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
16
- export declare class SupervisorServer {
17
- #private;
18
- readonly id: `${string}-${string}-${string}-${string}-${string}`;
19
- readonly bootNonce: string;
20
- readonly pidStartIdentity: string;
21
- readonly startedAt: string;
22
- readonly paths: ProductPaths;
23
- readonly store: ControlStore;
24
- readonly release: MaterializedRelease;
25
- readonly terminateProcess: (code: number) => void;
26
- readonly reconciliationDeadlineMs: number;
27
- readonly shutdownDeadlineMs: number;
28
- readonly readActiveReleaseId: (() => Promise<string | null>) | null;
29
- readonly supersededPollMs: number;
30
- constructor(store: ControlStore, paths: ProductPaths | undefined, release: MaterializedRelease, bootNonce?: string, terminateProcess?: (code: number) => void, reconciliationDeadlineMs?: number, shutdownDeadlineMs?: number,
31
- /**
32
- * Which release new sessions start on. A cohort that is no longer that release keeps
33
- * serving the instances it already has, takes no new ones, and exits when its last one
34
- * leaves. Absent, this supervisor never considers itself superseded.
35
- */
36
- readActiveReleaseId?: (() => Promise<string | null>) | null, supersededPollMs?: number);
37
- snapshot(): SupervisorSnapshot;
38
- listen(): Promise<void>;
39
- /** Whether new sessions start on some other release. */
40
- get superseded(): boolean;
41
- /** Leaves, taking this cohort's own endpoint artifacts with it. */
42
- retire(): Promise<void>;
43
- close(stopAgents?: boolean): Promise<void>;
44
- closeForReleaseReplacement(stopAgents: boolean): Promise<void>;
45
- }
46
- export {};
@@ -1,611 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
- import { createConnection, createServer } from "node:net";
4
- import { platform } from "node:os";
5
- import { dirname } from "node:path";
6
- import { assertLaunchInstanceOutcome, assertNativeProcessIdentity, assertProcessContainmentIdentity, } from "../lifecycle/index.js";
7
- import { encodeFrame, isCommandMessage, isControlHello, LineFrameDecoder, localControlHello, MAX_CONTROL_FRAME_BYTES, negotiateControlFeatures } from "../protocol/index.js";
8
- import { listRecordedEndpoints, readEndpointMetadata, supervisorProcessState, currentSupervisorStartIdentity, withEndpointLock, processIsAlive } from "../release/index.js";
9
- import { ControlStore } from "../storage/index.js";
10
- import { resolveProductPaths } from "./paths.js";
11
- import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
12
- const WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS = [5, 10, 20, 40, 80, 160];
13
- /** Atomically replaces endpoint metadata while tolerating bounded Windows reader sharing. */
14
- export async function commitEndpointMetadata(path, source, operations = {}) {
15
- const temporary = `${path}.${process.pid}.tmp`;
16
- const write = operations.write ?? writeFile;
17
- const replace = operations.replace ?? rename;
18
- const remove = operations.remove ?? rm;
19
- const wait = operations.wait ?? (delayMs => new Promise(resolvePromise => setTimeout(resolvePromise, delayMs)));
20
- let committed = false;
21
- try {
22
- await write(temporary, source, { mode: 0o600 });
23
- for (let attempt = 0;; attempt += 1) {
24
- try {
25
- await replace(temporary, path);
26
- committed = true;
27
- return;
28
- }
29
- catch (error) {
30
- const delayMs = WINDOWS_METADATA_REPLACE_RETRY_DELAYS_MS[attempt];
31
- if ((operations.platform ?? platform()) !== "win32" || delayMs === undefined || !isWindowsSharingViolation(error))
32
- throw error;
33
- await wait(delayMs);
34
- }
35
- }
36
- }
37
- finally {
38
- if (!committed)
39
- await remove(temporary, { force: true }).catch(() => undefined);
40
- }
41
- }
42
- /** Owns one release-cohort endpoint and the authenticated launch instances registered through it. */
43
- export class SupervisorServer {
44
- id = randomUUID();
45
- bootNonce;
46
- pidStartIdentity = currentSupervisorStartIdentity();
47
- startedAt = new Date().toISOString();
48
- paths;
49
- #server = null;
50
- #ownsEndpoint = false;
51
- #instances = new Map();
52
- #instanceOwners = new Map();
53
- #clientIds = new Map();
54
- #clientInstances = new Map();
55
- #uncertainInstances = new Set();
56
- #reconciliations = new Map();
57
- #revision = 0;
58
- #clients = new Set();
59
- #results = new Map();
60
- #metadataWrites = Promise.resolve();
61
- #closing = false;
62
- #superseded = false;
63
- #supersededPoll = null;
64
- store;
65
- release;
66
- terminateProcess;
67
- reconciliationDeadlineMs;
68
- shutdownDeadlineMs;
69
- readActiveReleaseId;
70
- supersededPollMs;
71
- constructor(store, paths = resolveProductPaths(), release, bootNonce = randomUUID(), terminateProcess = code => process.exit(code), reconciliationDeadlineMs = 3_000, shutdownDeadlineMs = 1_500,
72
- /**
73
- * Which release new sessions start on. A cohort that is no longer that release keeps
74
- * serving the instances it already has, takes no new ones, and exits when its last one
75
- * leaves. Absent, this supervisor never considers itself superseded.
76
- */
77
- readActiveReleaseId = null, supersededPollMs = 1_000) {
78
- this.store = store;
79
- this.release = release;
80
- this.terminateProcess = terminateProcess;
81
- this.reconciliationDeadlineMs = reconciliationDeadlineMs;
82
- this.shutdownDeadlineMs = shutdownDeadlineMs;
83
- this.readActiveReleaseId = readActiveReleaseId;
84
- this.supersededPollMs = supersededPollMs;
85
- this.bootNonce = bootNonce;
86
- this.paths = paths;
87
- for (const instance of store.loadActiveLaunchInstances(bootNonce))
88
- this.#instances.set(instance.id, instance);
89
- }
90
- snapshot() {
91
- return {
92
- revision: this.#revision,
93
- activeInstances: [...this.#instances.values()].flatMap(instance => {
94
- if (instance.state !== "requested" && instance.state !== "active" && instance.state !== "stopping")
95
- return [];
96
- return [{ id: instance.id, profileId: instance.profileId, state: instance.state }];
97
- }),
98
- };
99
- }
100
- async listen() {
101
- await withEndpointLock(this.paths.endpointMetadataPath, async () => {
102
- if (this.#server)
103
- throw new Error("supervisor is already listening");
104
- await mkdir(this.paths.runtimeDir, { recursive: true, mode: 0o700 });
105
- await mkdir(this.paths.endpointsDir, { recursive: true, mode: 0o700 });
106
- const recorded = await readEndpointMetadata(this.paths.endpointMetadataPath);
107
- if (recorded && await supervisorProcessState(recorded) !== "gone") {
108
- throw Object.assign(new Error("another supervisor still owns this endpoint"), { code: "EADDRINUSE" });
109
- }
110
- if (platform() !== "win32") {
111
- if (this.paths.endpointDirectory)
112
- await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
113
- const existing = await lstat(this.paths.endpoint).catch(error => {
114
- if (error.code === "ENOENT")
115
- return null;
116
- throw error;
117
- });
118
- if (existing) {
119
- if (await endpointIsLive(this.paths.endpoint)) {
120
- throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
121
- }
122
- await rm(this.paths.endpoint, { force: true });
123
- }
124
- }
125
- const server = createServer(socket => this.#attach(socket));
126
- this.#server = server;
127
- await new Promise((resolve, reject) => {
128
- server.once("error", reject);
129
- server.listen(this.paths.endpoint, () => {
130
- this.#ownsEndpoint = true;
131
- server.off("error", reject);
132
- resolve();
133
- });
134
- });
135
- // Concurrency: only the bound winner may reconcile shared control data. Other cohort boots
136
- // remain protected even when temporarily unresponsive; their instances never become ours.
137
- await withEndpointLock(this.paths.databasePath, async () => {
138
- // Concurrency: all cohorts sharing SQLite serialize boot reconciliation plus publication, so a new
139
- // cohort cannot insert instances between another cohort's inventory and stale-row update.
140
- const protectedBoots = [];
141
- for (const other of await listRecordedEndpoints(this.paths)) {
142
- if (other.paths.endpointMetadataPath !== this.paths.endpointMetadataPath
143
- && await supervisorProcessState(other.metadata) !== "gone")
144
- protectedBoots.push(other.metadata.bootNonce);
145
- }
146
- this.store.reconcilePriorBootLaunchInstances(this.bootNonce, new Date().toISOString(), protectedBoots);
147
- await this.#writeEndpointMetadata();
148
- });
149
- if (this.readActiveReleaseId) {
150
- const poll = setInterval(() => void this.#refreshCohortRole(), this.supersededPollMs);
151
- poll.unref();
152
- this.#supersededPoll = poll;
153
- }
154
- });
155
- }
156
- // Invariant: superseding a cohort never interrupts its held instances; it retires only when empty.
157
- async #refreshCohortRole() {
158
- if (this.#closing || !this.readActiveReleaseId)
159
- return;
160
- const active = await this.readActiveReleaseId().catch(() => null);
161
- if (active === null)
162
- return;
163
- this.#superseded = active !== this.release.releaseId;
164
- if (this.#superseded && this.#instances.size === 0)
165
- await this.retire();
166
- }
167
- /** Whether new sessions start on some other release. */
168
- get superseded() {
169
- return this.#superseded;
170
- }
171
- /** Leaves, taking this cohort's own endpoint artifacts with it. */
172
- async retire() {
173
- if (this.#closing)
174
- return;
175
- await this.close(false);
176
- }
177
- async close(stopAgents = false) {
178
- if (this.#closing)
179
- return;
180
- if (stopAgents && this.#ownsEndpoint && !await this.#drainInstances("update", this.shutdownDeadlineMs)) {
181
- throw new Error("active launch instances did not release ownership within the shutdown deadline");
182
- }
183
- this.#closing = true;
184
- if (this.#supersededPoll)
185
- clearInterval(this.#supersededPoll);
186
- this.#supersededPoll = null;
187
- for (const client of this.#clients)
188
- client.destroy();
189
- // Concurrency: disconnect reconciliation can still commit a terminal outcome after its
190
- // bounded process wait. Settle it before retiring the endpoint and closing shared storage.
191
- await Promise.allSettled(this.#reconciliations.values());
192
- await withEndpointLock(this.paths.endpointMetadataPath, async () => {
193
- try {
194
- await this.#metadataWrites.catch(() => undefined);
195
- if (this.#ownsEndpoint) {
196
- // Concurrency: remove only our publication, before releasing the listening endpoint.
197
- // A contender that failed to bind must never erase the successful owner's discovery file.
198
- const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
199
- if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
200
- await rm(this.paths.endpointMetadataPath, { force: true });
201
- }
202
- // Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
203
- // let a replacement bind before the old server finishes releasing that path.
204
- }
205
- }
206
- finally {
207
- // Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
208
- if (this.#server)
209
- await new Promise(resolve => this.#server?.close(() => resolve()));
210
- this.#server = null;
211
- this.#ownsEndpoint = false;
212
- this.store.close();
213
- }
214
- });
215
- }
216
- async closeForReleaseReplacement(stopAgents) {
217
- await this.close(stopAgents);
218
- this.terminateProcess(0);
219
- }
220
- #attach(socket) {
221
- socket.setNoDelay(true);
222
- const decoder = new LineFrameDecoder();
223
- let welcomed = false;
224
- socket.on("data", chunk => {
225
- try {
226
- for (const value of decoder.push(chunk)) {
227
- if (!welcomed) {
228
- if (isMessageType(value, "identity-probe")) {
229
- this.#send(socket, { type: "identity", supervisorId: this.id, bootNonce: this.bootNonce, pidStartIdentity: this.pidStartIdentity, releaseId: this.release.releaseId });
230
- continue;
231
- }
232
- if (isMessageType(value, "release-idle-ownership")) {
233
- const request = value;
234
- const identityMatches = request.bootNonce === this.bootNonce && typeof request.candidateReleaseId === "string";
235
- const released = identityMatches && this.#instances.size === 0;
236
- this.#send(socket, {
237
- type: "release-ownership-result",
238
- released,
239
- reason: released ? "idle cohort released ownership"
240
- : identityMatches ? "launch instances are still active" : "boot nonce or candidate release mismatch",
241
- liveInstanceIds: this.#liveInstanceIds(),
242
- });
243
- if (released)
244
- setTimeout(() => void this.closeForReleaseReplacement(false), 25);
245
- continue;
246
- }
247
- if (isMessageType(value, "release-update-ownership")) {
248
- const request = value;
249
- void this.#handleUpdateRelease(socket, request);
250
- continue;
251
- }
252
- if (!isControlHello(value)) {
253
- this.#send(socket, { type: "protocol-error", code: "invalid-control-handshake", message: `the first message must be a valid ${PRODUCT_TEXT.displayName} control feature handshake`, diagnostics: { received: value } });
254
- socket.destroy();
255
- return;
256
- }
257
- const serverHello = localControlHello();
258
- const negotiation = negotiateControlFeatures(value, serverHello);
259
- if (!negotiation.ok) {
260
- this.#send(socket, { type: "protocol-error", code: "incompatible-control-features", message: negotiation.diagnostic, diagnostics: negotiation });
261
- socket.destroy();
262
- return;
263
- }
264
- welcomed = true;
265
- this.#clients.add(socket);
266
- this.#clientIds.set(socket, value.clientId);
267
- this.#clientInstances.set(socket, new Set());
268
- this.#send(socket, { type: "server-hello", ...serverHello, releaseId: this.release.releaseId, supervisorId: this.id, bootNonce: this.bootNonce, pidStartIdentity: this.pidStartIdentity, negotiatedFeatures: negotiation.negotiatedFeatures, snapshot: this.snapshot() });
269
- }
270
- else if (isCommandMessage(value)) {
271
- void this.#execute(value.command, socket);
272
- }
273
- }
274
- }
275
- catch (error) {
276
- this.#send(socket, { type: "protocol-error", code: "framing-error", message: error instanceof Error ? error.message : String(error) });
277
- socket.destroy();
278
- }
279
- });
280
- socket.on("close", () => this.#onSocketClosed(socket));
281
- socket.on("error", () => this.#onSocketClosed(socket));
282
- }
283
- async #execute(command, socket) {
284
- const clientId = this.#clientIds.get(socket);
285
- if (!clientId) {
286
- this.#send(socket, { type: "protocol-error", code: "unauthenticated-command", message: "launch-instance commands require an authenticated client" });
287
- return;
288
- }
289
- const resultKey = `${clientId}:${command.requestId}`;
290
- const recorded = this.#results.get(resultKey);
291
- if (recorded) {
292
- this.#send(socket, { type: "command-result", result: recorded });
293
- return;
294
- }
295
- let result;
296
- try {
297
- if (command.type === "create-launch-instance")
298
- await this.#createLaunchInstance(command, socket, clientId);
299
- if (command.type === "activate-launch-instance")
300
- this.#activateLaunchInstance(command, socket, clientId);
301
- if (command.type === "begin-launch-instance-stop")
302
- this.#beginLaunchInstanceStop(command, socket, clientId);
303
- if (command.type === "complete-launch-instance")
304
- this.#completeLaunchInstance(command, socket, clientId);
305
- if (command.type === "reconcile-launch-instance") {
306
- this.#ownedInstance(command.instanceId, socket, clientId);
307
- await this.#reconcileLaunchInstance(command.instanceId);
308
- }
309
- if (command.type === "resynchronize")
310
- this.#send(socket, { type: "snapshot", snapshot: this.snapshot() });
311
- await this.#metadataWrites;
312
- result = { requestId: command.requestId, ok: true, revision: this.#revision };
313
- }
314
- catch (error) {
315
- const message = error instanceof Error ? error.message : String(error);
316
- const explicitCode = error instanceof Error && "code" in error ? error.code : undefined;
317
- const code = explicitCode === "release-superseded"
318
- ? "release-superseded"
319
- : /not found/i.test(message) ? "not-found"
320
- : /owner|ownership|authenticated/i.test(message) ? "ownership-error" : "driver-error";
321
- result = { requestId: command.requestId, ok: false, revision: this.#revision, error: { code, message } };
322
- }
323
- this.#results.set(resultKey, result);
324
- this.#send(socket, { type: "command-result", result });
325
- }
326
- async #createLaunchInstance(command, socket, clientId) {
327
- assertNativeProcessIdentity(command.guardianIdentity);
328
- const existing = this.store.loadLaunchInstance(command.instanceId);
329
- if (!existing && this.readActiveReleaseId) {
330
- const active = await this.readActiveReleaseId().catch(() => null);
331
- if (active !== null)
332
- this.#superseded = active !== this.release.releaseId;
333
- }
334
- if (this.#superseded && !existing) {
335
- throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer active for new sessions`)), {
336
- code: "release-superseded",
337
- });
338
- }
339
- if (existing) {
340
- if (existing.ownerClientId !== clientId
341
- || existing.profileId !== command.profileId
342
- || existing.shutdownPolicy !== command.shutdownPolicy
343
- || !sameProcessIdentity(existing.guardianIdentity, command.guardianIdentity)) {
344
- throw new Error("launch instance already exists with different authenticated ownership");
345
- }
346
- if (existing.state === "completed" || existing.state === "interrupted")
347
- throw new Error("launch instance already has a terminal outcome");
348
- this.#instances.set(existing.id, existing);
349
- this.#bindOwner(existing.id, socket);
350
- return;
351
- }
352
- const instance = {
353
- id: command.instanceId,
354
- ownerClientId: clientId,
355
- profileId: command.profileId,
356
- state: "requested",
357
- shutdownPolicy: command.shutdownPolicy,
358
- guardianIdentity: command.guardianIdentity,
359
- rootIdentity: null,
360
- containmentIdentity: null,
361
- createdAt: new Date().toISOString(),
362
- activatedAt: null,
363
- stoppingAt: null,
364
- completedAt: null,
365
- outcome: null,
366
- };
367
- this.store.createLaunchInstance(instance);
368
- this.#instances.set(instance.id, instance);
369
- this.#bindOwner(instance.id, socket);
370
- this.#mutated();
371
- }
372
- #activateLaunchInstance(command, socket, clientId) {
373
- assertNativeProcessIdentity(command.rootIdentity);
374
- assertProcessContainmentIdentity(command.containmentIdentity);
375
- const instance = this.#ownedInstance(command.instanceId, socket, clientId);
376
- if (instance.state === "active"
377
- && sameProcessIdentity(instance.rootIdentity, command.rootIdentity)
378
- && sameContainmentIdentity(instance.containmentIdentity, command.containmentIdentity))
379
- return;
380
- if (instance.state !== "requested")
381
- throw new Error("launch instance is not requested");
382
- const activatedAt = new Date().toISOString();
383
- if (!this.store.activateLaunchInstance(instance.id, clientId, command.rootIdentity, command.containmentIdentity, activatedAt)) {
384
- throw new Error("launch instance activation ownership mismatch");
385
- }
386
- this.#instances.set(instance.id, { ...instance, state: "active", rootIdentity: command.rootIdentity, containmentIdentity: command.containmentIdentity, activatedAt });
387
- this.#mutated();
388
- }
389
- #beginLaunchInstanceStop(command, socket, clientId) {
390
- const instance = this.#ownedInstance(command.instanceId, socket, clientId);
391
- if (instance.state === "stopping")
392
- return;
393
- if (instance.state !== "active")
394
- throw new Error("active launch instance not found");
395
- const stoppingAt = new Date().toISOString();
396
- if (!this.store.beginLaunchInstanceStop(instance.id, clientId, stoppingAt))
397
- throw new Error("launch instance stop ownership mismatch");
398
- this.#instances.set(instance.id, { ...instance, state: "stopping", stoppingAt });
399
- this.#mutated();
400
- }
401
- #completeLaunchInstance(command, socket, clientId) {
402
- assertLaunchInstanceOutcome(command.outcome);
403
- const stored = this.store.loadLaunchInstance(command.instanceId);
404
- if (!stored)
405
- throw new Error("launch instance not found");
406
- if (stored.ownerClientId !== clientId || this.#instanceOwners.get(stored.id) !== socket)
407
- throw new Error("launch instance ownership mismatch");
408
- if (stored.state === "completed" || stored.state === "interrupted") {
409
- if (stored.state === command.terminalState && JSON.stringify(stored.outcome) === JSON.stringify(command.outcome))
410
- return;
411
- throw new Error("launch instance already has a different terminal outcome");
412
- }
413
- const completedAt = new Date().toISOString();
414
- if (!this.store.completeLaunchInstance(stored.id, clientId, command.terminalState, command.outcome, completedAt)) {
415
- throw new Error("launch instance completion ownership mismatch");
416
- }
417
- this.#instances.delete(stored.id);
418
- this.#uncertainInstances.delete(stored.id);
419
- this.#unbindOwner(stored.id, socket);
420
- this.#mutated();
421
- // Invariant: the last session on a superseded cohort has left; there is nothing here to keep.
422
- if (this.#superseded && this.#instances.size === 0)
423
- void this.retire();
424
- }
425
- #ownedInstance(instanceId, socket, clientId) {
426
- const instance = this.#instances.get(instanceId) ?? this.store.loadLaunchInstance(instanceId);
427
- if (!instance || instance.state === "completed" || instance.state === "interrupted")
428
- throw new Error("active launch instance not found");
429
- if (instance.ownerClientId !== clientId || this.#instanceOwners.get(instanceId) !== socket)
430
- throw new Error("launch instance ownership mismatch");
431
- return instance;
432
- }
433
- #bindOwner(instanceId, socket) {
434
- const current = this.#instanceOwners.get(instanceId);
435
- if (current && current !== socket)
436
- throw new Error("launch instance already belongs to another authenticated connection");
437
- this.#instanceOwners.set(instanceId, socket);
438
- this.#clientInstances.get(socket)?.add(instanceId);
439
- }
440
- #unbindOwner(instanceId, socket) {
441
- this.#instanceOwners.delete(instanceId);
442
- this.#clientInstances.get(socket)?.delete(instanceId);
443
- }
444
- #onSocketClosed(socket) {
445
- if (!this.#clients.delete(socket) && !this.#clientIds.has(socket))
446
- return;
447
- const instanceIds = [...(this.#clientInstances.get(socket) ?? [])];
448
- this.#clientInstances.delete(socket);
449
- this.#clientIds.delete(socket);
450
- for (const instanceId of instanceIds) {
451
- if (this.#instanceOwners.get(instanceId) === socket)
452
- this.#instanceOwners.delete(instanceId);
453
- if (!this.#closing)
454
- void this.#reconcileLaunchInstance(instanceId);
455
- }
456
- }
457
- #reconcileLaunchInstance(instanceId) {
458
- const existing = this.#reconciliations.get(instanceId);
459
- if (existing)
460
- return existing;
461
- if (this.#closing)
462
- return Promise.resolve();
463
- const reconciliation = this.#performReconciliation(instanceId).finally(() => this.#reconciliations.delete(instanceId));
464
- this.#reconciliations.set(instanceId, reconciliation);
465
- return reconciliation;
466
- }
467
- async #performReconciliation(instanceId) {
468
- const instance = this.#instances.get(instanceId);
469
- if (!instance)
470
- return;
471
- const deadline = Date.now() + this.reconciliationDeadlineMs;
472
- while (instance.rootIdentity && processIsAlive(instance.rootIdentity.pid) && Date.now() < deadline) {
473
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
474
- }
475
- if (instance.state === "requested" || (instance.rootIdentity && !processIsAlive(instance.rootIdentity.pid))) {
476
- const outcome = { kind: "interrupted", reason: "owner-disconnect", message: "launch instance owner disconnected before reporting a terminal outcome" };
477
- if (this.store.completeLaunchInstance(instance.id, instance.ownerClientId, "interrupted", outcome, new Date().toISOString())) {
478
- this.#instances.delete(instance.id);
479
- this.#uncertainInstances.delete(instance.id);
480
- this.#mutated();
481
- }
482
- return;
483
- }
484
- this.#uncertainInstances.add(instance.id);
485
- await this.#writeEndpointMetadata();
486
- }
487
- async #handleUpdateRelease(socket, request) {
488
- const identityMatches = request.bootNonce === this.bootNonce && typeof request.targetVersion === "string";
489
- if (!identityMatches) {
490
- this.#send(socket, {
491
- type: "release-update-result",
492
- accepted: false,
493
- reason: "boot nonce or target version mismatch",
494
- liveInstanceIds: this.#liveInstanceIds(),
495
- });
496
- return;
497
- }
498
- const released = await this.#drainInstances("update", this.shutdownDeadlineMs);
499
- this.#send(socket, {
500
- type: "release-update-result",
501
- accepted: released,
502
- reason: released
503
- ? `verified ${PRODUCT_TEXT.displayName} owner drained all launch instances for immediate update shutdown`
504
- : "one or more launch instances did not release verified ownership within the update deadline",
505
- liveInstanceIds: this.#liveInstanceIds(),
506
- });
507
- if (released)
508
- setTimeout(() => void this.closeForReleaseReplacement(false), 25);
509
- }
510
- async #drainInstances(reason, timeoutMs) {
511
- if (this.#instances.size === 0)
512
- return true;
513
- this.#requestStopAll(reason);
514
- const deadline = Date.now() + timeoutMs;
515
- while (Date.now() < deadline) {
516
- if (this.#instances.size === 0) {
517
- // Concurrency: allow the terminal command result to flush before closing owner sockets.
518
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
519
- return this.#instances.size === 0;
520
- }
521
- await new Promise(resolvePromise => setTimeout(resolvePromise, 25));
522
- }
523
- return this.#instances.size === 0;
524
- }
525
- #requestStopAll(reason) {
526
- for (const [instanceId, socket] of this.#instanceOwners) {
527
- this.#send(socket, { type: "stop-launch-instance", requestId: randomUUID(), instanceId, reason });
528
- }
529
- }
530
- #mutated() {
531
- this.#revision += 1;
532
- void this.#writeEndpointMetadata();
533
- }
534
- #liveInstanceIds() {
535
- return [...this.#instances.keys()];
536
- }
537
- #writeEndpointMetadata() {
538
- if (this.#closing)
539
- return Promise.resolve();
540
- const liveInstanceIds = this.#liveInstanceIds();
541
- const metadata = {
542
- schema: PRODUCT_IDENTITY.protocol.supervisorSchema,
543
- ...localControlHello(this.release.releaseId),
544
- supervisorId: this.id,
545
- pid: process.pid,
546
- pidStartIdentity: this.pidStartIdentity,
547
- nativeStartIdentity: this.pidStartIdentity,
548
- bootNonce: this.bootNonce,
549
- endpoint: this.paths.endpoint,
550
- databasePath: this.paths.databasePath,
551
- startedAt: this.startedAt,
552
- releaseId: this.release.releaseId,
553
- releaseRoot: this.release.releaseRoot,
554
- contentDigest: this.release.contentDigest,
555
- ownership: {
556
- state: this.#uncertainInstances.size > 0 ? "blocked" : liveInstanceIds.length > 0 ? "busy" : "idle",
557
- liveInstanceIds,
558
- nonResumableInstanceIds: liveInstanceIds,
559
- uncertainInstanceIds: [...this.#uncertainInstances],
560
- },
561
- };
562
- const commit = () => commitEndpointMetadata(this.paths.endpointMetadataPath, JSON.stringify(metadata, null, 2));
563
- // Platform: one exhausted filesystem replacement must fail its command, but it must not
564
- // poison every later ownership revision after the sharing condition has cleared.
565
- this.#metadataWrites = this.#metadataWrites.then(commit, commit);
566
- return this.#metadataWrites;
567
- }
568
- #send(socket, message) {
569
- if (socket.destroyed)
570
- return;
571
- const frame = encodeFrame(message);
572
- const bytes = Buffer.byteLength(frame, "utf8");
573
- if (bytes > MAX_CONTROL_FRAME_BYTES || socket.writableLength + bytes > MAX_CONTROL_FRAME_BYTES * 2) {
574
- socket.destroy(new Error("slow client exceeded the bounded control/output queue"));
575
- return;
576
- }
577
- socket.write(frame);
578
- }
579
- }
580
- function sameProcessIdentity(left, right) {
581
- return left !== null && left.pid === right.pid && left.startIdentity === right.startIdentity;
582
- }
583
- function sameContainmentIdentity(left, right) {
584
- return left !== null && left.provider === right.provider && left.token === right.token;
585
- }
586
- function isMessageType(value, type) {
587
- return typeof value === "object" && value !== null && "type" in value && value.type === type;
588
- }
589
- function isWindowsSharingViolation(error) {
590
- return error instanceof Error && "code" in error
591
- && (error.code === "EPERM" || error.code === "EACCES" || error.code === "EBUSY");
592
- }
593
- async function ensureManagedEndpointDirectory(path) {
594
- await mkdir(path, { recursive: true, mode: 0o700 });
595
- const metadata = await lstat(path);
596
- if (!metadata.isDirectory() || metadata.isSymbolicLink())
597
- throw new Error(`managed endpoint path is not a directory: ${path}`);
598
- const currentUser = process.getuid?.();
599
- if (currentUser !== undefined && metadata.uid !== currentUser)
600
- throw new Error(`managed endpoint directory has another owner: ${path}`);
601
- await chmod(path, 0o700);
602
- }
603
- async function endpointIsLive(endpoint) {
604
- return await new Promise(resolve => {
605
- const socket = createConnection(endpoint);
606
- const done = (live) => { socket.destroy(); resolve(live); };
607
- socket.once("connect", () => done(true));
608
- socket.once("error", () => done(false));
609
- setTimeout(() => done(false), 250).unref();
610
- });
611
- }