chainlesschain 0.166.85 → 0.166.87

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 (201) hide show
  1. package/README.md +8 -0
  2. package/package.json +3 -2
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-Cgo7OFpi.js → AIOps-DdQjhXg9.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-lUMnE2Ln.js → ActionButton-CuwA3hPA.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-CsvKzTot.js → Analytics-DTVXJSAC.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-C2z1h1Pv.js → AppLayout-j06JBfEQ.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-C5Q0ZhNb.js → Artifacts-BFAK8LEW.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-DXnfPKsI.js → Audit-BiGxRpOf.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-DjbGcLm3.js → BackgroundAgents-B6uHaPkZ.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-9bVrKvXE.js → Backup-BNAesB8q.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-bx5ri7BD.js → BaseInput-BOuetfCd.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-CCFmeUMY.js → Chat-cVzo3m_D.js} +4 -4
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-DZ2Ys-CI.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-ei7MQXe9.js → Checkbox-BaCTS-H8.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-CXgBJ9iu.js → Codegen-CBE8NiYh.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-DMkMsizS.js → Col-C9wLiUTN.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-CvpB_UFR.js → Community-jdClBSIp.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-gay5hbFX.js → Compact-BIjauIlM.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-dG8JQyRL.js → Compliance-CZ-AoaNj.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-BA3xfaTW.js → Cowork-BbTKtDS4.js} +4 -4
  23. package/src/assets/web-panel/assets/{Cron-D1_HsxyY.js → Cron-ImKdtwNi.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-DZqoqoy5.js → Crosschain-B54GSHf5.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-Cwnr8hCO.js → DID-BQt5WUzs.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-CxevypiI.js → Dashboard--gNOffif.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-Bs7-yWgr.js → Dropdown-342A5YJw.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-CcEs2Puo.js → EmailListRenderer-CtIZ3ZHd.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-C9ipBzAQ.js → EvolutionSettings-CyZPoTio.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Dt6--mGn.js → FamilyGuardDashboard-B1MCkFEu.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-CD0JN5FF.js → Federation-DZWoERUy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-POypz3Xv.js → FormItemContext-Onmb0mp3.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9f-0F5.js → GenericCardRenderer-u1qhNNc7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-BbTh5Atv.js → Git-BEowR1C5.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-C71dgoir.js → Governance-BiYd3AAg.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DwSHxbJB.js → Inference-CsC4Vjh5.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-Cx1oQYQZ.js → KnowledgeGraph-BXA7ikPM.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BAJWe00o.js → Logs-C9WoplQm.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-CD8a7lHT.js → MarkdownRenderer-DecEsXsy.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-fwuY7P7Y.js → Marketplace-CCoJBfRL.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-Di4693uc.js → McpTools-CzaOWBoA.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-Cz3L-GUB.js → Memory-BPwh7u1-.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-BuXKIkSb.js → MobileBridge-Bk47v6z2.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-D-CY--Sb.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-LnACMFd0.js → Mtc-ExVyAGlW.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-BZy7yE0t.js → MtcAudit-Rrw10csE.js} +5 -5
  47. package/src/assets/web-panel/assets/{Multisig-BFG5iVrE.js → Multisig-8TxxwxZZ.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DgXi04vJ.js → NLProgramming-CNckjtnY.js} +1 -1
  49. package/src/assets/web-panel/assets/Notes-C4j6cZTu.js +7 -0
  50. package/src/assets/web-panel/assets/{NotificationSettings-AixrsHw5.js → NotificationSettings-CchU8rWd.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-ZJBoyJ16.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-UNCBC3ux.js → Organization-F0Je7EdZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{Overflow-Drh8oAFT.js → Overflow-B2k-JA3u.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-bc2q0xEB.js → P2P-yui9Xxe6.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-BGwKxcDM.js → PdhVaultBrowser-CegM7hT6.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-DOnUxUzu.js → Permissions-CSFL0uvh.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B3qPZkrr.js → PersonalDataHub-BrMcd-vr.js} +6 -6
  58. package/src/assets/web-panel/assets/{Pipeline-B6hfa4-i.js → Pipeline-fzrliaVP.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bun3c35F.js → Privacy-BrkUjJ_g.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CCObdzk9.js → ProjectInit-CF_QCsW9.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-C6lKrSXY.js → ProjectSettings-Duwrd8aC.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DYcta7MZ.js → Projects-B0dLVXzU.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-BtykxKCe.js → Providers-Db8iS1xQ.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-D9CDyroY.js → QrScannerModal-CHbNzEQs.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-CJauCxXx.js → QuickAsk-CIESMfeR.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BbiYZofR.js → Recommend-BTMbCMtR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-BymCrgFW.js → RemoteSession-DavyK6J-.js} +3 -3
  68. package/src/assets/web-panel/assets/{Reputation-BWrGpMpF.js → Reputation-BFKtMelA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-9pOYIhiW.js → Row-BI9AZUvw.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-D3bQJ4qB.js → RssFeed-1nwXt8NY.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-BpKOOFE0.js → Search-Ck8PDYbk.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-DC6C0NSz.js → Security-BgDqpCbq.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-Lkf1046J.js → Services-DF8anXm4.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-eCQ_SsDb.js → Skeleton-CoHyY4ZX.js} +1 -1
  75. package/src/assets/web-panel/assets/Skills-dOaD4Ztl.js +1 -0
  76. package/src/assets/web-panel/assets/{Sla-BQbKy8XJ.js → Sla-XuOawvQY.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-DHsRx-XA.js → SpeechSettings-DFJ7nAiu.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-CC281X_J.js → SyncSettings-B2xYj-Pi.js} +2 -2
  79. package/src/assets/web-panel/assets/Tasks-D_SzNTLi.js +1 -0
  80. package/src/assets/web-panel/assets/{Templates-Cr2vC0tg.js → Templates-BXFmKh7W.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-DeYO5pIG.js → Tenant-CRg2AXbd.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-DBNgbfPr.js → Terminal-DQgxnuep.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-MoxgaF3x.js → TimelineRenderer-BrPQhQVc.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-CT2QfAN7.js → Tokens-RByy4USy.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DPUnomxs.js → Trigger-pE3y8u73.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-DnufuaTx.js → Trust-DhID9C33.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CJ6iOdPs.js → UkeySign-CPxEPNBH.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-xBWkH8bL.js → VideoEditing-ZD8DUxeQ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-WYV-4hMR.js → Wallet-Br9JREEd.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DJ5Mhd9L.js → WebAuthn-Cn-YacJg.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-B__UdOjN.js → WorkflowEditor-BTu0oqMp.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CLlmBVhp.js → chat-CEjws_QF.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-B8zoPQYm.js → colors-Bv2RNebW.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CvJ-dUsH.js → compact-item-gSc3EofP.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-LAtMm8SC.js → createContext-kyFDaqWC.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Dcc8pILJ.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CLYvg42H.js → hasIn-BuMWcjhx.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cv7vwX3z.js → index--nrQmk8X.js} +1 -1
  99. package/src/assets/web-panel/assets/index-05NB95_V.js +1 -0
  100. package/src/assets/web-panel/assets/{index-BUVrYDNV.js → index-42PTBO7a.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B6zlMzZz.js → index-BCS5j4SQ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-oX216HRH.js → index-BSSIJ-rv.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B02qgCao.js → index-BUOCcXou.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_RPWP_5.js → index-Bg5jis0q.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DX99e8Xy.js → index-C-LPpwe9.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DDm9m4vC.js → index-C56Drzol.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DHq2Of6z.js → index-C7Jo20JB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C_dq-6_3.js → index-C9U9SjcT.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D0Z13xcZ.js → index-CF3TjDNz.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B1hHp9_M.js → index-CMS3Mk93.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfOOACfd.js → index-CNt2X-aK.js} +1 -1
  112. package/src/assets/web-panel/assets/index-Cb6q8wlI.js +1 -0
  113. package/src/assets/web-panel/assets/{index-DYW4Z3y-.js → index-Cj4RA7oA.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C2KFnUsv.js → index-CkfvP2dN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BkcQJvsz.js → index-Cm8K8Evn.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CVDcRQuX.js → index-CvSuM_CB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BCzyJ1xS.js → index-Czo_hw97.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cpda0SAz.js → index-D3m3EXbL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-iJ6s24bA.js → index-DR561Sqc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Di9vKira.js → index-DRa4R7vv.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-09YzRb6Z.js → index-DYKSuD02.js} +3 -3
  122. package/src/assets/web-panel/assets/{index-C5v7cEtm.js → index-DYcPWVN9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BGrQ2uEl.js → index-DbVUA5l3.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-4rr5jFOL.js → index-DbkyTMj-.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-GR3omApz.js → index-Dej4AGOz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-f1ptYBMr.js → index-EpxWlmip.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3uD5H7u.js → index-GpvWtvw3.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C6PETyel.js → index-NQ0c4hJ1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CnLXLaEF.js → index-OjZUHz5z.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DpeV-6y4.js → index-U-ytt84A.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DNQUdPam.js → index-_0bfDTDu.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BM2HqHdT.js → index-jmMAqBAm.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Bu7-I2L4.js → index-nLemnBZ2.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CUcE6rIa.js → index-q65c1kp8.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DX0aHEHL.js → index-sJ1F5Txa.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CxJ4NPVq.js → index-xfS_WO9b.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-QD-koeaw.js → initDefaultProps-B-wqiSu7.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CTItLW4-.js → motion-BK4_B4MD.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-CGsdTOdN.js → move-BU2TgDDO.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-C1kW_CNU.js → mtc-parser-DA3E4_EX.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B4PilaYy.js → omit-CaHz1e_q.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DBGUE12e.js → pickAttrs-NXZ19amf.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DZxCLjuo.js → placementArrow-BCXI9AHO.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-CZjQiocN.js → responsiveObserve-DdlZ9tLk.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-Btz5hh2_.js → slide-DCZm_Vc4.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-CfuxuLIt.js → statusUtils-CuEULKQr.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DdO0Obo_.js → styleChecker-Bo_UXG8x.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-CxftJ-Zs.js → useFlexGapSupport-B9PaTcFH.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-pVJChh6K.js → useFs-DwSTGdVR.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-wv7Q0uNE.js → usePersonalDataHub-FswSxnDC.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DVOVfAqC.js → vnode-EUztvYTj.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-B5tebZuT.js → zoom-D41MAgV-.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +1 -1
  155. package/src/commands/eval.js +118 -18
  156. package/src/commands/memory.js +34 -15
  157. package/src/commands/sandbox.js +9 -0
  158. package/src/data/changelog.json +26 -0
  159. package/src/lib/causal-observability.js +6 -45
  160. package/src/lib/context-memory-kernel/durable-memory-port.js +54 -12
  161. package/src/lib/context-memory-kernel/index.js +1 -0
  162. package/src/lib/context-memory-kernel/memory-query-index.js +278 -0
  163. package/src/lib/context-memory-kernel/memory-service.js +60 -18
  164. package/src/lib/context-memory-kernel/runtime.js +5 -3
  165. package/src/lib/context-memory-kernel/segmented-memory-port.js +955 -0
  166. package/src/lib/eval/outcomes.js +20 -10
  167. package/src/lib/eval/runner.js +49 -5
  168. package/src/lib/eval/tasks.js +8 -3
  169. package/src/lib/eval/verify01-contracts.js +349 -0
  170. package/src/lib/eval/verify01-execution.js +605 -0
  171. package/src/lib/execution-support.js +58 -0
  172. package/src/lib/llm-pricing.js +98 -11
  173. package/src/lib/model-catalog-review.js +116 -0
  174. package/src/lib/model-context-catalog.js +23 -1
  175. package/src/lib/openai-responses.js +39 -0
  176. package/src/lib/packer/pack-update-applier.js +13 -8
  177. package/src/lib/permission-authority.js +146 -10
  178. package/src/lib/process-execution-broker/index.js +8 -0
  179. package/src/lib/process-execution-broker/linux-subreaper-process.js +24 -2
  180. package/src/lib/process-execution-broker/process-ownership-journal.js +168 -7
  181. package/src/lib/process-execution-broker/process-ownership-quarantine.js +56 -1
  182. package/src/lib/process-execution-broker/process-recovery-cgroup.js +272 -0
  183. package/src/lib/sandbox-egress-authority-monitor.js +62 -5
  184. package/src/lib/scoped-permission-store.js +104 -1
  185. package/src/lib/settings-authority-domain.cjs +37 -2
  186. package/src/lib/settings-loader.cjs +67 -34
  187. package/src/lib/settings-permission-authority.cjs +373 -0
  188. package/src/runtime/agent-core.js +88 -407
  189. package/src/runtime/headless-runner.js +8 -3
  190. package/src/runtime/headless-stream.js +10 -3
  191. package/src/runtime/permission-authority-host.js +151 -0
  192. package/src/runtime/provider-stream-state.js +379 -0
  193. package/src/assets/web-panel/assets/ChatBubbleRenderer-ClqsIfTT.js +0 -1
  194. package/src/assets/web-panel/assets/MobileProjects-Bk-lK4Um.js +0 -1
  195. package/src/assets/web-panel/assets/Notes-BAj0Kgg4.js +0 -7
  196. package/src/assets/web-panel/assets/OrderTableRenderer-Dwqk3FlG.js +0 -1
  197. package/src/assets/web-panel/assets/Skills-BvDSQ3-d.js +0 -1
  198. package/src/assets/web-panel/assets/Tasks-CHHSWHom.js +0 -1
  199. package/src/assets/web-panel/assets/devWarning-DgZ4fdGI.js +0 -1
  200. package/src/assets/web-panel/assets/index-jLEdASOV.js +0 -1
  201. package/src/assets/web-panel/assets/index-jesGo4rl.js +0 -1
@@ -53,6 +53,7 @@ import {
53
53
  getProcessOwnershipStatus,
54
54
  observeProcessOwnership,
55
55
  prepareProcessOwnership,
56
+ recoverProcessOwnership,
56
57
  } from "./process-ownership-quarantine.js";
57
58
  import {
58
59
  MACOS_MCP_LAUNCHER_INPUTS,
@@ -4525,6 +4526,10 @@ class ProcessExecutionBroker extends EventEmitter {
4525
4526
  return getProcessOwnershipStatus();
4526
4527
  }
4527
4528
 
4529
+ recoverProcessOwnership(executionId, options) {
4530
+ return recoverProcessOwnership(executionId, options);
4531
+ }
4532
+
4528
4533
  listWorkspaceTransactions(options = {}) {
4529
4534
  return this._workspaceTransactionManager(
4530
4535
  options.stateDir,
@@ -4873,6 +4878,9 @@ class ProcessExecutionBroker extends EventEmitter {
4873
4878
  cwd: path.resolve(optsForSpawn.cwd || cwd),
4874
4879
  env: optsForSpawn.env || process.env,
4875
4880
  graceMs: request.graceMs,
4881
+ ...(ownershipLease?.beforeLaunch
4882
+ ? { beforeLaunch: ownershipLease.beforeLaunch }
4883
+ : {}),
4876
4884
  helper,
4877
4885
  },
4878
4886
  {
@@ -96,10 +96,22 @@ export function spawnLinuxSubreaper(command, args, options, native) {
96
96
  if (
97
97
  Object.keys(options).some(
98
98
  (key) =>
99
- !["cwd", "env", "graceMs", "helperPath", "helper"].includes(key),
99
+ ![
100
+ "cwd",
101
+ "env",
102
+ "graceMs",
103
+ "helperPath",
104
+ "helper",
105
+ "beforeLaunch",
106
+ ].includes(key),
100
107
  )
101
108
  )
102
109
  throw new TypeError("Unsupported Linux subreaper option");
110
+ if (
111
+ options.beforeLaunch !== undefined &&
112
+ typeof options.beforeLaunch !== "function"
113
+ )
114
+ throw new TypeError("Invalid trusted before-launch hook");
103
115
  launch = encodeLinuxSubreaperLaunch(command, args, options);
104
116
  child = native.spawn(helper ? "/proc/self/fd/4" : options.helperPath, [], {
105
117
  cwd: options.cwd,
@@ -249,7 +261,17 @@ export function spawnLinuxSubreaper(command, args, options, native) {
249
261
  });
250
262
  });
251
263
  });
252
- control.write(launch);
264
+ try {
265
+ const result = options.beforeLaunch?.(child);
266
+ if (result && typeof result.then === "function")
267
+ throw new TypeError("Before-launch hook must be synchronous");
268
+ control.write(launch);
269
+ } catch (error) {
270
+ // No launch frame was sent. Closing the private control channel releases
271
+ // the blocked supervisor without executing the target.
272
+ control.destroy();
273
+ throw error;
274
+ }
253
275
  return Object.freeze({ child, terminate, completion });
254
276
  }
255
277
 
@@ -4,11 +4,17 @@ import { randomUUID } from "node:crypto";
4
4
  import { withFileLock } from "../with-file-lock.js";
5
5
  import { writeSecurityStore } from "../durable-security-store.js";
6
6
  import { ensurePrivateDirectory } from "../secure-fs.js";
7
+ import { performance } from "node:perf_hooks";
8
+ import {
9
+ openRecoveryCgroup,
10
+ validateRecoveryCgroup,
11
+ } from "./process-recovery-cgroup.js";
7
12
 
8
13
  export const PROCESS_OWNERSHIP_PENDING = "BROKER_PROCESS_OWNERSHIP_PENDING";
9
14
  export const PROCESS_OWNERSHIP_JOURNAL_UNAVAILABLE =
10
15
  "BROKER_PROCESS_OWNERSHIP_JOURNAL_UNAVAILABLE";
11
16
  const SCHEMA = "chainlesschain.process-ownership-journal/v1";
17
+ const RECOVERY_SCHEMA = "chainlesschain.process-ownership-journal/v2";
12
18
  const MAX_BYTES = 1024 * 1024;
13
19
  const MAX_PENDING = 1024;
14
20
  const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
@@ -27,8 +33,12 @@ function unavailable(cause) {
27
33
  function validate(state) {
28
34
  if (
29
35
  !state ||
30
- Object.keys(state).sort().join() !== "pending,schema" ||
31
- state.schema !== SCHEMA ||
36
+ !(
37
+ (state.schema === SCHEMA &&
38
+ Object.keys(state).sort().join() === "pending,schema") ||
39
+ (state.schema === RECOVERY_SCHEMA &&
40
+ Object.keys(state).sort().join() === "pending,recoveries,schema")
41
+ ) ||
32
42
  !Array.isArray(state.pending) ||
33
43
  state.pending.length > MAX_PENDING
34
44
  )
@@ -37,7 +47,12 @@ function validate(state) {
37
47
  for (const entry of state.pending) {
38
48
  if (
39
49
  !entry ||
40
- Object.keys(entry).sort().join() !== "executionId,ownerPid,token" ||
50
+ ![
51
+ "executionId,ownerPid,token",
52
+ ...(state.schema === RECOVERY_SCHEMA
53
+ ? ["executionId,ownerPid,recovery,token"]
54
+ : []),
55
+ ].includes(Object.keys(entry).sort().join()) ||
41
56
  !UUID.test(entry.executionId) ||
42
57
  !UUID.test(entry.token) ||
43
58
  !Number.isSafeInteger(entry.ownerPid) ||
@@ -45,8 +60,31 @@ function validate(state) {
45
60
  ids.has(entry.executionId)
46
61
  )
47
62
  throw new Error("Invalid ownership journal entry");
63
+ if (Object.hasOwn(entry, "recovery"))
64
+ validateRecoveryCgroup(entry.recovery, entry.executionId);
48
65
  ids.add(entry.executionId);
49
66
  }
67
+ if (state.schema === RECOVERY_SCHEMA) {
68
+ if (
69
+ !Array.isArray(state.recoveries) ||
70
+ state.recoveries.length > MAX_PENDING
71
+ )
72
+ throw new Error("Invalid recovery audit inventory");
73
+ for (const receipt of state.recoveries) {
74
+ if (
75
+ !receipt ||
76
+ Object.keys(receipt).sort().join() !==
77
+ "completedAt,executionId,group,killIssued,populated,token" ||
78
+ !UUID.test(receipt.executionId) ||
79
+ !UUID.test(receipt.token) ||
80
+ receipt.killIssued !== true ||
81
+ receipt.populated !== false ||
82
+ !Number.isFinite(Date.parse(receipt.completedAt))
83
+ )
84
+ throw new Error("Invalid recovery audit receipt");
85
+ validateRecoveryCgroup(receipt.group, receipt.executionId);
86
+ }
87
+ }
50
88
  return state;
51
89
  }
52
90
 
@@ -211,6 +249,13 @@ export class ProcessOwnershipJournal {
211
249
  }
212
250
  }
213
251
 
252
+ #write(file, state, label = "Process ownership") {
253
+ validate(state);
254
+ if (Buffer.byteLength(`${JSON.stringify(state, null, 2)}\n`) > MAX_BYTES)
255
+ throw new Error("Ownership journal byte capacity is exhausted");
256
+ writeSecurityStore(file, label, state);
257
+ }
258
+
214
259
  assertAvailable() {
215
260
  if (this.#fault) throw this.#fault;
216
261
  this.#withDirectory(false, (file) => this.#assertState(this.#read(file)));
@@ -222,6 +267,9 @@ export class ProcessOwnershipJournal {
222
267
  const state = this.#read(file);
223
268
  return {
224
269
  pendingExecutionIds: state.pending.map((entry) => entry.executionId),
270
+ recoverableExecutionIds: state.pending
271
+ .filter((entry) => entry.recovery)
272
+ .map((entry) => entry.executionId),
225
273
  blocked: state.pending.some(
226
274
  (entry) => this.#local.get(entry.executionId)?.token !== entry.token,
227
275
  ),
@@ -229,24 +277,34 @@ export class ProcessOwnershipJournal {
229
277
  });
230
278
  }
231
279
 
232
- prepare(executionId) {
280
+ prepare(executionId, recovery = null) {
233
281
  if (!UUID.test(executionId))
234
282
  throw new TypeError("Invalid ownership execution id");
235
283
  if (this.#fault) throw this.#fault;
236
- const entry = { executionId, ownerPid: process.pid, token: randomUUID() };
284
+ if (recovery) validateRecoveryCgroup(recovery, executionId);
285
+ const entry = {
286
+ executionId,
287
+ ownerPid: process.pid,
288
+ token: randomUUID(),
289
+ ...(recovery ? { recovery: { ...recovery } } : {}),
290
+ };
237
291
  this.#withDirectory(true, (file) =>
238
292
  withFileLock(
239
293
  file,
240
294
  () => {
241
295
  const state = this.#read(file);
242
296
  this.#assertState(state);
297
+ if (recovery && state.schema === SCHEMA) {
298
+ state.schema = RECOVERY_SCHEMA;
299
+ state.recoveries = [];
300
+ }
243
301
  if (
244
302
  state.pending.length >= MAX_PENDING ||
245
303
  state.pending.some((item) => item.executionId === executionId)
246
304
  )
247
305
  throw new Error("Ownership journal admission limit");
248
306
  state.pending.push(entry);
249
- writeSecurityStore(file, "Process ownership", state);
307
+ this.#write(file, state);
250
308
  this.#initialized = true;
251
309
  this.#local.set(executionId, entry);
252
310
  },
@@ -284,7 +342,7 @@ export class ProcessOwnershipJournal {
284
342
  state.pending = state.pending.filter(
285
343
  (item) => item.executionId !== executionId,
286
344
  );
287
- writeSecurityStore(file, "Process ownership", state);
345
+ this.#write(file, state);
288
346
  this.#local.delete(executionId);
289
347
  settled = true;
290
348
  },
@@ -294,4 +352,107 @@ export class ProcessOwnershipJournal {
294
352
  },
295
353
  });
296
354
  }
355
+
356
+ /** Explicitly cancel an orphaned, identity-bound cgroup. Old PID-only records
357
+ * remain quarantined. Neither process death nor age is a cleanup authority.
358
+ */
359
+ async recover(executionId, { timeoutMs = 5000, pollMs = 10 } = {}) {
360
+ if (
361
+ !UUID.test(executionId) ||
362
+ !Number.isSafeInteger(timeoutMs) ||
363
+ timeoutMs < 1 ||
364
+ timeoutMs > 30000 ||
365
+ !Number.isSafeInteger(pollMs) ||
366
+ pollMs < 1 ||
367
+ pollMs > 1000
368
+ )
369
+ throw new TypeError("Invalid process recovery request");
370
+ if (this.#fault) throw this.#fault;
371
+ if (this.#local.has(executionId))
372
+ throw unavailable(
373
+ new Error(
374
+ "A locally owned live launch must use its normal cancellation fence",
375
+ ),
376
+ );
377
+ const entry = this.#withDirectory(false, (file) => {
378
+ const state = this.#read(file);
379
+ const found = state.pending.find(
380
+ (item) => item.executionId === executionId,
381
+ );
382
+ if (!found?.recovery) {
383
+ const error = new Error(
384
+ "This pending execution has no recoverable kernel group identity",
385
+ );
386
+ error.code = PROCESS_OWNERSHIP_PENDING;
387
+ error.recoveryRequired = true;
388
+ throw error;
389
+ }
390
+ if (state.recoveries.length >= MAX_PENDING)
391
+ throw new Error("Recovery audit capacity is exhausted");
392
+ return { ...found, recovery: { ...found.recovery } };
393
+ });
394
+ const group = openRecoveryCgroup(entry.recovery, executionId);
395
+ let settled = false;
396
+ try {
397
+ group.kill();
398
+ const deadline = performance.now() + timeoutMs;
399
+ while (group.populated()) {
400
+ if (performance.now() >= deadline) {
401
+ const error = new Error(
402
+ "Recovered process group has not reached its empty kernel fence",
403
+ );
404
+ error.code = PROCESS_OWNERSHIP_PENDING;
405
+ error.recoveryRequired = true;
406
+ throw error;
407
+ }
408
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
409
+ }
410
+ const receipt = {
411
+ executionId,
412
+ token: entry.token,
413
+ group: entry.recovery,
414
+ killIssued: true,
415
+ populated: false,
416
+ completedAt: new Date().toISOString(),
417
+ };
418
+ this.#withDirectory(false, (file) =>
419
+ withFileLock(
420
+ file,
421
+ () => {
422
+ const state = this.#read(file);
423
+ const current = state.pending.find(
424
+ (item) => item.executionId === executionId,
425
+ );
426
+ if (
427
+ !current ||
428
+ current.token !== entry.token ||
429
+ JSON.stringify(current.recovery) !==
430
+ JSON.stringify(entry.recovery)
431
+ )
432
+ throw new Error("Ownership recovery lost its original record");
433
+ if (state.recoveries.length >= MAX_PENDING)
434
+ throw new Error("Recovery audit capacity is exhausted");
435
+ // Check the kernel fence again while committing the durable receipt.
436
+ group.confirmEmpty();
437
+ state.pending = state.pending.filter(
438
+ (item) => item.executionId !== executionId,
439
+ );
440
+ state.recoveries.push(receipt);
441
+ this.#write(file, state, "Process ownership recovery");
442
+ settled = true;
443
+ },
444
+ { failIfUnavailable: true, timeoutMs: 2000 },
445
+ ),
446
+ );
447
+ return { ...receipt, cleanupConfirmed: true, executionResumed: false };
448
+ } finally {
449
+ // Publish settlement before removing the empty group. If journal IO
450
+ // fails, its kernel identity remains available for a safe retry.
451
+ try {
452
+ group.close({ remove: settled });
453
+ } catch {
454
+ group.close(); /* Empty directory leftovers never certify a run. */
455
+ }
456
+ }
457
+ }
297
458
  }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { getMachineSecurityAnchorDir } from "../paths.js";
3
3
  import { ProcessOwnershipJournal } from "./process-ownership-journal.js";
4
+ import { prepareRecoveryCgroup } from "./process-recovery-cgroup.js";
4
5
 
5
6
  // Shared by every Broker in this JS runtime. A new Broker, different cwd or
6
7
  // cleared audit history cannot turn a lost process owner into cleanup evidence.
@@ -18,7 +19,60 @@ function durableJournal() {
18
19
  }
19
20
 
20
21
  export function prepareProcessOwnership(executionId) {
21
- return durableJournal()?.prepare(executionId) ?? null;
22
+ const authority = durableJournal();
23
+ if (!authority) return null;
24
+ const group = prepareRecoveryCgroup(
25
+ executionId,
26
+ process.env.CHAINLESSCHAIN_PROCESS_RECOVERY_CGROUP_ROOT,
27
+ );
28
+ if (!group) return authority.prepare(executionId);
29
+ let lease;
30
+ try {
31
+ lease = authority.prepare(executionId, group.identity);
32
+ } catch (error) {
33
+ group.close({ remove: true });
34
+ throw error;
35
+ }
36
+ let settled = false,
37
+ retained = false;
38
+ return Object.freeze({
39
+ beforeLaunch: (proc) => group.attachBeforeLaunch(proc),
40
+ settle() {
41
+ if (settled) return;
42
+ if (retained)
43
+ throw new Error("Retained recovery group requires explicit recovery");
44
+ try {
45
+ group.confirmEmpty();
46
+ lease.settle();
47
+ } catch (error) {
48
+ retained = true;
49
+ lease.retain();
50
+ group.close();
51
+ throw error;
52
+ }
53
+ settled = true;
54
+ try {
55
+ group.close({ remove: true });
56
+ } catch {
57
+ group.close();
58
+ }
59
+ },
60
+ retain() {
61
+ if (settled || retained) return;
62
+ retained = true;
63
+ lease.retain();
64
+ group.close();
65
+ },
66
+ });
67
+ }
68
+
69
+ export async function recoverProcessOwnership(executionId, options) {
70
+ const authority = durableJournal();
71
+ if (!authority)
72
+ throw new Error("Kernel process ownership recovery requires Linux");
73
+ const receipt = await authority.recover(executionId, options);
74
+ unresolvedOwners.delete(executionId);
75
+ return receipt;
22
76
  }
23
77
 
24
78
  export const PROCESS_OWNERSHIP_UNCONFIRMED =
@@ -73,6 +127,7 @@ export function getProcessOwnershipStatus() {
73
127
  !!durable &&
74
128
  [...unresolvedOwners.values()].every((owner) => owner.durable),
75
129
  restartSafe: false,
130
+ recoverableExecutionIds: durable?.recoverableExecutionIds || [],
76
131
  };
77
132
  }
78
133
 
@@ -0,0 +1,272 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const PROCESS_RECOVERY_CGROUP_SCHEMA =
5
+ "chainlesschain.process-recovery-cgroup/v1";
6
+ const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u;
7
+ const CGROUP2_MAGIC = 0x63677270n;
8
+ const BOOT_ID = "/proc/sys/kernel/random/boot_id";
9
+
10
+ function denied(message, cause) {
11
+ return Object.assign(
12
+ new Error(
13
+ `Process recovery cgroup is unavailable: ${message}`,
14
+ cause ? { cause } : undefined,
15
+ ),
16
+ {
17
+ code: "BROKER_PROCESS_RECOVERY_UNAVAILABLE",
18
+ recoveryRequired: true,
19
+ },
20
+ );
21
+ }
22
+ function requireValue(value, reason) {
23
+ if (!value) throw denied(reason);
24
+ }
25
+ function identity(stat) {
26
+ return { dev: String(stat.dev), ino: String(stat.ino) };
27
+ }
28
+ function same(stat, expected, prefix = "") {
29
+ return (
30
+ String(stat.dev) === expected[`${prefix}dev`] &&
31
+ String(stat.ino) === expected[`${prefix}ino`]
32
+ );
33
+ }
34
+ function rootPath(root) {
35
+ requireValue(
36
+ typeof root === "string" &&
37
+ path.posix.isAbsolute(root) &&
38
+ root !== "/" &&
39
+ !root.includes("\0") &&
40
+ !root.split("/").includes(".."),
41
+ "invalid delegated root",
42
+ );
43
+ return path.posix.normalize(root).replace(/\/+$/u, "");
44
+ }
45
+
46
+ export function validateRecoveryCgroup(value, executionId) {
47
+ requireValue(
48
+ value &&
49
+ Object.keys(value).sort().join() ===
50
+ "bootId,dev,ino,name,root,rootdev,rootino,schema" &&
51
+ value.schema === PROCESS_RECOVERY_CGROUP_SCHEMA &&
52
+ UUID.test(value.bootId) &&
53
+ UUID.test(executionId) &&
54
+ value.name === `cc-owner-${executionId}` &&
55
+ rootPath(value.root) === value.root &&
56
+ [value.dev, value.ino, value.rootdev, value.rootino].every(
57
+ (part) => typeof part === "string" && /^\d{1,30}$/u.test(part),
58
+ ),
59
+ "invalid persisted group identity",
60
+ );
61
+ return value;
62
+ }
63
+
64
+ function openDirectory(io, directory) {
65
+ const before = io.lstatSync(directory, { bigint: true });
66
+ requireValue(
67
+ before.isDirectory() && !before.isSymbolicLink(),
68
+ "cgroup path is not a directory",
69
+ );
70
+ requireValue(
71
+ io.realpathSync(directory) === directory,
72
+ "cgroup path traverses a link",
73
+ );
74
+ const fd = io.openSync(
75
+ directory,
76
+ io.constants.O_RDONLY | io.constants.O_DIRECTORY | io.constants.O_NOFOLLOW,
77
+ );
78
+ try {
79
+ const opened = io.fstatSync(fd, { bigint: true });
80
+ requireValue(
81
+ same(opened, identity(before)),
82
+ "cgroup directory changed while opening",
83
+ );
84
+ requireValue(
85
+ BigInt(io.statfsSync(`/proc/self/fd/${fd}`, { bigint: true }).type) ===
86
+ CGROUP2_MAGIC,
87
+ "not a kernel cgroup v2 filesystem",
88
+ );
89
+ return { fd, stat: opened, pinned: `/proc/self/fd/${fd}` };
90
+ } catch (error) {
91
+ io.closeSync(fd);
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ /** Kernel object identities, not PIDs, establish the recovery target. The root
97
+ * must be explicitly delegated by the host. This is cooperative lifecycle
98
+ * recovery, not confinement against same-UID code that can move cgroup members.
99
+ */
100
+ export function openRecoveryCgroup(value, executionId, runtime = {}) {
101
+ const io = runtime.fs || fs;
102
+ requireValue(
103
+ (runtime.platform || process.platform) === "linux",
104
+ "Linux cgroup v2 is required",
105
+ );
106
+ validateRecoveryCgroup(value, executionId);
107
+ requireValue(
108
+ String(io.readFileSync(BOOT_ID, "utf8")).trim() === value.bootId,
109
+ "boot identity changed",
110
+ );
111
+ const root = openDirectory(io, value.root);
112
+ let group;
113
+ try {
114
+ requireValue(
115
+ same(root.stat, value, "root"),
116
+ "delegated root identity changed",
117
+ );
118
+ group = openDirectory(io, path.posix.join(value.root, value.name));
119
+ requireValue(same(group.stat, value), "recovery group identity changed");
120
+ } catch (error) {
121
+ if (group) io.closeSync(group.fd);
122
+ io.closeSync(root.fd);
123
+ throw error;
124
+ }
125
+ let closed = false;
126
+ const assertIdentity = () => {
127
+ requireValue(!closed, "group lease was closed");
128
+ requireValue(
129
+ String(io.readFileSync(BOOT_ID, "utf8")).trim() === value.bootId,
130
+ "boot identity changed",
131
+ );
132
+ const currentRoot = io.lstatSync(value.root, { bigint: true });
133
+ const currentGroup = io.lstatSync(path.posix.join(value.root, value.name), {
134
+ bigint: true,
135
+ });
136
+ requireValue(
137
+ currentRoot.isDirectory() &&
138
+ !currentRoot.isSymbolicLink() &&
139
+ same(currentRoot, value, "root") &&
140
+ currentGroup.isDirectory() &&
141
+ !currentGroup.isSymbolicLink() &&
142
+ same(currentGroup, value),
143
+ "cgroup authority was replaced",
144
+ );
145
+ };
146
+ const populated = () => {
147
+ assertIdentity();
148
+ const bytes = io.readFileSync(`${group.pinned}/cgroup.events`, "utf8");
149
+ requireValue(
150
+ typeof bytes === "string" && bytes.length <= 4096,
151
+ "invalid cgroup event payload",
152
+ );
153
+ const lines = bytes
154
+ .trim()
155
+ .split(/\n/u)
156
+ .map((line) => line.trim().split(/\s+/u));
157
+ const entries = lines.filter(([key]) => key === "populated");
158
+ requireValue(
159
+ entries.length === 1 &&
160
+ entries[0].length === 2 &&
161
+ ["0", "1"].includes(entries[0][1]),
162
+ "missing or invalid populated fence",
163
+ );
164
+ return entries[0][1] === "1";
165
+ };
166
+ return Object.freeze({
167
+ identity: Object.freeze({ ...value }),
168
+ attachBeforeLaunch(proc) {
169
+ assertIdentity();
170
+ requireValue(
171
+ Number.isSafeInteger(proc?.pid) && proc.pid > 0,
172
+ "supervisor PID is unavailable",
173
+ );
174
+ requireValue(!populated(), "new recovery group was already populated");
175
+ // The native supervisor is still blocked on its private launch socket.
176
+ // No target can fork before this write and membership check complete.
177
+ io.writeFileSync(`${group.pinned}/cgroup.procs`, `${proc.pid}\n`, "utf8");
178
+ const members = String(
179
+ io.readFileSync(`${group.pinned}/cgroup.procs`, "utf8"),
180
+ )
181
+ .trim()
182
+ .split(/\s+/u);
183
+ requireValue(
184
+ members.includes(String(proc.pid)),
185
+ "supervisor membership was not observed",
186
+ );
187
+ },
188
+ kill() {
189
+ assertIdentity();
190
+ io.writeFileSync(`${group.pinned}/cgroup.kill`, "1\n", "utf8");
191
+ },
192
+ populated,
193
+ confirmEmpty() {
194
+ requireValue(!populated(), "recovery group remains populated");
195
+ return true;
196
+ },
197
+ close({ remove = false } = {}) {
198
+ if (closed) return;
199
+ try {
200
+ if (remove) {
201
+ requireValue(
202
+ !populated(),
203
+ "cannot remove a populated recovery group",
204
+ );
205
+ io.rmdirSync(`${root.pinned}/${value.name}`);
206
+ }
207
+ } finally {
208
+ closed = true;
209
+ io.closeSync(group.fd);
210
+ io.closeSync(root.fd);
211
+ }
212
+ },
213
+ });
214
+ }
215
+
216
+ export function prepareRecoveryCgroup(executionId, rootInput, runtime = {}) {
217
+ if (!rootInput) return null;
218
+ const io = runtime.fs || fs;
219
+ requireValue(
220
+ (runtime.platform || process.platform) === "linux",
221
+ "Linux cgroup v2 is required",
222
+ );
223
+ requireValue(UUID.test(executionId), "invalid execution identity");
224
+ const root = rootPath(rootInput);
225
+ const opened = openDirectory(io, root);
226
+ const name = `cc-owner-${executionId}`;
227
+ let created = false;
228
+ try {
229
+ const bootId = String(io.readFileSync(BOOT_ID, "utf8")).trim();
230
+ requireValue(UUID.test(bootId), "boot identity is unavailable");
231
+ // The unique group is created through the pinned delegated root.
232
+ io.mkdirSync(`${opened.pinned}/${name}`, { mode: 0o700 });
233
+ created = true;
234
+ const directory = path.posix.join(root, name);
235
+ const stat = io.lstatSync(directory, { bigint: true });
236
+ const value = {
237
+ schema: PROCESS_RECOVERY_CGROUP_SCHEMA,
238
+ root,
239
+ name,
240
+ bootId,
241
+ rootdev: String(opened.stat.dev),
242
+ rootino: String(opened.stat.ino),
243
+ ...identity(stat),
244
+ };
245
+ const group = openRecoveryCgroup(value, executionId, runtime);
246
+ try {
247
+ group.confirmEmpty();
248
+ // Require the real kernel primitive before a recoverable launch is armed.
249
+ const killFd = io.openSync(
250
+ `${directory}/cgroup.kill`,
251
+ io.constants.O_WRONLY | io.constants.O_NOFOLLOW,
252
+ );
253
+ io.closeSync(killFd);
254
+ return group;
255
+ } catch (error) {
256
+ group.close({ remove: true });
257
+ created = false;
258
+ throw error;
259
+ }
260
+ } catch (error) {
261
+ if (created) {
262
+ try {
263
+ io.rmdirSync(`${opened.pinned}/${name}`);
264
+ } catch {
265
+ /* No process was launched into this group. */
266
+ }
267
+ }
268
+ throw denied("preparation failed", error);
269
+ } finally {
270
+ io.closeSync(opened.fd);
271
+ }
272
+ }