@dharma-ai-labs/agent-fabric 0.2.153 → 0.2.155

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 (300) hide show
  1. package/dist/bootstrapGitScope.test.d.ts +2 -0
  2. package/dist/bootstrapGitScope.test.d.ts.map +1 -0
  3. package/dist/bootstrapGitScope.test.js +115 -0
  4. package/dist/bootstrapGitScope.test.js.map +1 -0
  5. package/dist/bootstrapHostFiles.d.ts +5 -0
  6. package/dist/bootstrapHostFiles.d.ts.map +1 -0
  7. package/dist/bootstrapHostFiles.js +150 -0
  8. package/dist/bootstrapHostFiles.js.map +1 -0
  9. package/dist/bootstrapHostFiles.test.d.ts +2 -0
  10. package/dist/bootstrapHostFiles.test.d.ts.map +1 -0
  11. package/dist/bootstrapHostFiles.test.js +127 -0
  12. package/dist/bootstrapHostFiles.test.js.map +1 -0
  13. package/dist/bootstrapHostScope.d.ts +42 -0
  14. package/dist/bootstrapHostScope.d.ts.map +1 -0
  15. package/dist/bootstrapHostScope.js +224 -0
  16. package/dist/bootstrapHostScope.js.map +1 -0
  17. package/dist/bootstrapHostScope.test.d.ts +2 -0
  18. package/dist/bootstrapHostScope.test.d.ts.map +1 -0
  19. package/dist/bootstrapHostScope.test.js +577 -0
  20. package/dist/bootstrapHostScope.test.js.map +1 -0
  21. package/dist/bootstrapInstallationScope.test.d.ts +2 -0
  22. package/dist/bootstrapInstallationScope.test.d.ts.map +1 -0
  23. package/dist/bootstrapInstallationScope.test.js +68 -0
  24. package/dist/bootstrapInstallationScope.test.js.map +1 -0
  25. package/dist/bootstrapLauncherCaller.test.d.ts +2 -0
  26. package/dist/bootstrapLauncherCaller.test.d.ts.map +1 -0
  27. package/dist/bootstrapLauncherCaller.test.js +91 -0
  28. package/dist/bootstrapLauncherCaller.test.js.map +1 -0
  29. package/dist/bootstrapOwnedChildren.test.d.ts +2 -0
  30. package/dist/bootstrapOwnedChildren.test.d.ts.map +1 -0
  31. package/dist/bootstrapOwnedChildren.test.js +93 -0
  32. package/dist/bootstrapOwnedChildren.test.js.map +1 -0
  33. package/dist/bootstrapPolicyScope.test.d.ts +2 -0
  34. package/dist/bootstrapPolicyScope.test.d.ts.map +1 -0
  35. package/dist/bootstrapPolicyScope.test.js +261 -0
  36. package/dist/bootstrapPolicyScope.test.js.map +1 -0
  37. package/dist/bootstrapProcessCaller.test.d.ts +2 -0
  38. package/dist/bootstrapProcessCaller.test.d.ts.map +1 -0
  39. package/dist/bootstrapProcessCaller.test.js +262 -0
  40. package/dist/bootstrapProcessCaller.test.js.map +1 -0
  41. package/dist/bootstrapRegistryCaller.test.d.ts +2 -0
  42. package/dist/bootstrapRegistryCaller.test.d.ts.map +1 -0
  43. package/dist/bootstrapRegistryCaller.test.js +128 -0
  44. package/dist/bootstrapRegistryCaller.test.js.map +1 -0
  45. package/dist/bootstrapVaultScope.test.d.ts +2 -0
  46. package/dist/bootstrapVaultScope.test.d.ts.map +1 -0
  47. package/dist/bootstrapVaultScope.test.js +105 -0
  48. package/dist/bootstrapVaultScope.test.js.map +1 -0
  49. package/dist/codexBootstrapCompletion.test.d.ts +2 -0
  50. package/dist/codexBootstrapCompletion.test.d.ts.map +1 -0
  51. package/dist/codexBootstrapCompletion.test.js +630 -0
  52. package/dist/codexBootstrapCompletion.test.js.map +1 -0
  53. package/dist/codexBoundSession.d.ts +2 -2
  54. package/dist/codexBoundSession.d.ts.map +1 -1
  55. package/dist/codexBoundSession.js +23 -17
  56. package/dist/codexBoundSession.js.map +1 -1
  57. package/dist/codexBoundSession.test.js +3 -0
  58. package/dist/codexBoundSession.test.js.map +1 -1
  59. package/dist/codexInboxSession.d.ts.map +1 -1
  60. package/dist/codexInboxSession.js +3 -3
  61. package/dist/codexInboxSession.js.map +1 -1
  62. package/dist/codexScopedDispatch.test.d.ts +2 -0
  63. package/dist/codexScopedDispatch.test.d.ts.map +1 -0
  64. package/dist/codexScopedDispatch.test.js +56 -0
  65. package/dist/codexScopedDispatch.test.js.map +1 -0
  66. package/dist/codexSetupAdmission.d.ts +108 -0
  67. package/dist/codexSetupAdmission.d.ts.map +1 -0
  68. package/dist/codexSetupAdmission.js +214 -0
  69. package/dist/codexSetupAdmission.js.map +1 -0
  70. package/dist/codexSetupAdmission.test.d.ts +2 -0
  71. package/dist/codexSetupAdmission.test.d.ts.map +1 -0
  72. package/dist/codexSetupAdmission.test.js +578 -0
  73. package/dist/codexSetupAdmission.test.js.map +1 -0
  74. package/dist/codexSetupChildProvider.test.d.ts +2 -0
  75. package/dist/codexSetupChildProvider.test.d.ts.map +1 -0
  76. package/dist/codexSetupChildProvider.test.js +210 -0
  77. package/dist/codexSetupChildProvider.test.js.map +1 -0
  78. package/dist/codexSetupChildStartup.d.ts +38 -0
  79. package/dist/codexSetupChildStartup.d.ts.map +1 -0
  80. package/dist/codexSetupChildStartup.js +115 -0
  81. package/dist/codexSetupChildStartup.js.map +1 -0
  82. package/dist/codexSetupChildStartup.test.d.ts +2 -0
  83. package/dist/codexSetupChildStartup.test.d.ts.map +1 -0
  84. package/dist/codexSetupChildStartup.test.js +141 -0
  85. package/dist/codexSetupChildStartup.test.js.map +1 -0
  86. package/dist/codexSetupCliDispatch.test.d.ts +2 -0
  87. package/dist/codexSetupCliDispatch.test.d.ts.map +1 -0
  88. package/dist/codexSetupCliDispatch.test.js +138 -0
  89. package/dist/codexSetupCliDispatch.test.js.map +1 -0
  90. package/dist/codexSetupJournalOpen.test.d.ts +2 -0
  91. package/dist/codexSetupJournalOpen.test.d.ts.map +1 -0
  92. package/dist/codexSetupJournalOpen.test.js +101 -0
  93. package/dist/codexSetupJournalOpen.test.js.map +1 -0
  94. package/dist/codexSetupNativeHost.d.ts +31 -0
  95. package/dist/codexSetupNativeHost.d.ts.map +1 -0
  96. package/dist/codexSetupNativeHost.js +211 -0
  97. package/dist/codexSetupNativeHost.js.map +1 -0
  98. package/dist/codexSetupNativeHost.test.d.ts +2 -0
  99. package/dist/codexSetupNativeHost.test.d.ts.map +1 -0
  100. package/dist/codexSetupNativeHost.test.js +479 -0
  101. package/dist/codexSetupNativeHost.test.js.map +1 -0
  102. package/dist/codexSetupNativeWire.test.d.ts +2 -0
  103. package/dist/codexSetupNativeWire.test.d.ts.map +1 -0
  104. package/dist/codexSetupNativeWire.test.js +243 -0
  105. package/dist/codexSetupNativeWire.test.js.map +1 -0
  106. package/dist/codexSetupOwnedConnection.d.ts +14 -0
  107. package/dist/codexSetupOwnedConnection.d.ts.map +1 -0
  108. package/dist/codexSetupOwnedConnection.js +64 -0
  109. package/dist/codexSetupOwnedConnection.js.map +1 -0
  110. package/dist/codexSetupOwnedConnection.test.d.ts +2 -0
  111. package/dist/codexSetupOwnedConnection.test.d.ts.map +1 -0
  112. package/dist/codexSetupOwnedConnection.test.js +192 -0
  113. package/dist/codexSetupOwnedConnection.test.js.map +1 -0
  114. package/dist/codexSetupPortalCommand.test.d.ts +2 -0
  115. package/dist/codexSetupPortalCommand.test.d.ts.map +1 -0
  116. package/dist/codexSetupPortalCommand.test.js +146 -0
  117. package/dist/codexSetupPortalCommand.test.js.map +1 -0
  118. package/dist/codexSetupReadiness.d.ts +23 -0
  119. package/dist/codexSetupReadiness.d.ts.map +1 -0
  120. package/dist/codexSetupReadiness.js +74 -0
  121. package/dist/codexSetupReadiness.js.map +1 -0
  122. package/dist/codexSetupReadiness.test.d.ts +2 -0
  123. package/dist/codexSetupReadiness.test.d.ts.map +1 -0
  124. package/dist/codexSetupReadiness.test.js +145 -0
  125. package/dist/codexSetupReadiness.test.js.map +1 -0
  126. package/dist/codexSetupSessionCaller.test.d.ts +2 -0
  127. package/dist/codexSetupSessionCaller.test.d.ts.map +1 -0
  128. package/dist/codexSetupSessionCaller.test.js +352 -0
  129. package/dist/codexSetupSessionCaller.test.js.map +1 -0
  130. package/dist/codexSetupSessionHandoff.d.ts +48 -0
  131. package/dist/codexSetupSessionHandoff.d.ts.map +1 -0
  132. package/dist/codexSetupSessionHandoff.js +146 -0
  133. package/dist/codexSetupSessionHandoff.js.map +1 -0
  134. package/dist/codexSetupSessionHandoff.test.d.ts +2 -0
  135. package/dist/codexSetupSessionHandoff.test.d.ts.map +1 -0
  136. package/dist/codexSetupSessionHandoff.test.js +289 -0
  137. package/dist/codexSetupSessionHandoff.test.js.map +1 -0
  138. package/dist/codexSetupTransport.test.d.ts +2 -0
  139. package/dist/codexSetupTransport.test.d.ts.map +1 -0
  140. package/dist/codexSetupTransport.test.js +206 -0
  141. package/dist/codexSetupTransport.test.js.map +1 -0
  142. package/dist/codexSetupVaultJournal.d.ts +13 -0
  143. package/dist/codexSetupVaultJournal.d.ts.map +1 -0
  144. package/dist/codexSetupVaultJournal.js +28 -0
  145. package/dist/codexSetupVaultJournal.js.map +1 -0
  146. package/dist/codexSetupVaultJournal.test.d.ts +2 -0
  147. package/dist/codexSetupVaultJournal.test.d.ts.map +1 -0
  148. package/dist/codexSetupVaultJournal.test.js +104 -0
  149. package/dist/codexSetupVaultJournal.test.js.map +1 -0
  150. package/dist/containerChildCleanup.test.d.ts +2 -0
  151. package/dist/containerChildCleanup.test.d.ts.map +1 -0
  152. package/dist/containerChildCleanup.test.js +145 -0
  153. package/dist/containerChildCleanup.test.js.map +1 -0
  154. package/dist/containerRelayLifecycle.d.ts +1 -1
  155. package/dist/containerRelayLifecycle.d.ts.map +1 -1
  156. package/dist/containerRelayLifecycle.js +29 -37
  157. package/dist/containerRelayLifecycle.js.map +1 -1
  158. package/dist/cooperativeInboxSession.d.ts +2 -2
  159. package/dist/cooperativeInboxSession.d.ts.map +1 -1
  160. package/dist/cooperativeInboxSession.js +21 -13
  161. package/dist/cooperativeInboxSession.js.map +1 -1
  162. package/dist/cooperativeInboxSession.test.js +65 -0
  163. package/dist/cooperativeInboxSession.test.js.map +1 -1
  164. package/dist/demoWatchControl.d.ts +3 -3
  165. package/dist/index.d.ts +55 -14
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +1642 -553
  168. package/dist/index.js.map +1 -1
  169. package/dist/namedCodexSkillDiscovery.d.ts +15 -0
  170. package/dist/namedCodexSkillDiscovery.d.ts.map +1 -0
  171. package/dist/namedCodexSkillDiscovery.js +90 -0
  172. package/dist/namedCodexSkillDiscovery.js.map +1 -0
  173. package/dist/namedCodexSkillDiscovery.test.d.ts +2 -0
  174. package/dist/namedCodexSkillDiscovery.test.d.ts.map +1 -0
  175. package/dist/namedCodexSkillDiscovery.test.js +37 -0
  176. package/dist/namedCodexSkillDiscovery.test.js.map +1 -0
  177. package/dist/namedCodexThread.d.ts +2 -0
  178. package/dist/namedCodexThread.d.ts.map +1 -1
  179. package/dist/namedCodexThread.js +13 -4
  180. package/dist/namedCodexThread.js.map +1 -1
  181. package/dist/namedSessionChildOwner.d.ts +17 -0
  182. package/dist/namedSessionChildOwner.d.ts.map +1 -0
  183. package/dist/namedSessionChildOwner.js +122 -0
  184. package/dist/namedSessionChildOwner.js.map +1 -0
  185. package/dist/namedSessionChildOwner.test.d.ts +2 -0
  186. package/dist/namedSessionChildOwner.test.d.ts.map +1 -0
  187. package/dist/namedSessionChildOwner.test.js +203 -0
  188. package/dist/namedSessionChildOwner.test.js.map +1 -0
  189. package/dist/namedSessionEvidence.d.ts +2 -2
  190. package/dist/namedSessionEvidence.d.ts.map +1 -1
  191. package/dist/namedSessionEvidence.js +2 -2
  192. package/dist/namedSessionEvidence.js.map +1 -1
  193. package/dist/namedSessionEvidence.test.js +24 -1
  194. package/dist/namedSessionEvidence.test.js.map +1 -1
  195. package/dist/namedSessionRepositoryState.d.ts +2 -2
  196. package/dist/namedSessionRepositoryState.d.ts.map +1 -1
  197. package/dist/namedSessionService.d.ts +4 -2
  198. package/dist/namedSessionService.d.ts.map +1 -1
  199. package/dist/namedSessionService.js +18 -1
  200. package/dist/namedSessionService.js.map +1 -1
  201. package/dist/namedSessionService.test.js +31 -2
  202. package/dist/namedSessionService.test.js.map +1 -1
  203. package/dist/namedSessionTaskExportSync.d.ts +3 -3
  204. package/dist/namedSessionTaskExportSync.d.ts.map +1 -1
  205. package/dist/namedSessionTaskExportSync.js +6 -6
  206. package/dist/namedSessionTaskExportSync.js.map +1 -1
  207. package/dist/namedSessionTrust.d.ts +2 -2
  208. package/dist/namedSessionTrust.d.ts.map +1 -1
  209. package/dist/namedSessionTrust.js +3 -2
  210. package/dist/namedSessionTrust.js.map +1 -1
  211. package/dist/namedSessionTrust.test.js +2 -1
  212. package/dist/namedSessionTrust.test.js.map +1 -1
  213. package/dist/ownedChildLifecycle.d.ts +13 -0
  214. package/dist/ownedChildLifecycle.d.ts.map +1 -0
  215. package/dist/ownedChildLifecycle.js +87 -0
  216. package/dist/ownedChildLifecycle.js.map +1 -0
  217. package/dist/ownedChildLifecycle.test.d.ts +2 -0
  218. package/dist/ownedChildLifecycle.test.d.ts.map +1 -0
  219. package/dist/ownedChildLifecycle.test.js +133 -0
  220. package/dist/ownedChildLifecycle.test.js.map +1 -0
  221. package/dist/pidLock.test.js +242 -9
  222. package/dist/pidLock.test.js.map +1 -1
  223. package/dist/providerSessionChannel.d.ts +1 -1
  224. package/dist/providerSessionChannel.d.ts.map +1 -1
  225. package/dist/providerSessionChannel.js +7 -3
  226. package/dist/providerSessionChannel.js.map +1 -1
  227. package/dist/providerSessionChannel.test.js +20 -0
  228. package/dist/providerSessionChannel.test.js.map +1 -1
  229. package/dist/providerSessionReplyRecovery.d.ts +2 -2
  230. package/dist/providerSessionReplyRecovery.d.ts.map +1 -1
  231. package/dist/providerSessionReplyRecovery.js +3 -3
  232. package/dist/providerSessionReplyRecovery.js.map +1 -1
  233. package/dist/relayAutostart.d.ts.map +1 -1
  234. package/dist/relayAutostart.js +12 -9
  235. package/dist/relayAutostart.js.map +1 -1
  236. package/dist/relayRuntimeUpgrade.d.ts +1 -1
  237. package/dist/relaySupervisor.d.ts.map +1 -1
  238. package/dist/relaySupervisor.js +19 -14
  239. package/dist/relaySupervisor.js.map +1 -1
  240. package/dist/relaySupervisor.test.js +26 -0
  241. package/dist/relaySupervisor.test.js.map +1 -1
  242. package/dist/relaySupervisorCleanup.test.d.ts +2 -0
  243. package/dist/relaySupervisorCleanup.test.d.ts.map +1 -0
  244. package/dist/relaySupervisorCleanup.test.js +69 -0
  245. package/dist/relaySupervisorCleanup.test.js.map +1 -0
  246. package/dist/repositoryInstallerFiles.d.ts +19 -1
  247. package/dist/repositoryInstallerFiles.d.ts.map +1 -1
  248. package/dist/repositoryInstallerFiles.js +205 -29
  249. package/dist/repositoryInstallerFiles.js.map +1 -1
  250. package/dist/repositoryInstallerScope.test.d.ts +2 -0
  251. package/dist/repositoryInstallerScope.test.d.ts.map +1 -0
  252. package/dist/repositoryInstallerScope.test.js +513 -0
  253. package/dist/repositoryInstallerScope.test.js.map +1 -0
  254. package/dist/repositoryKnowledge.d.ts.map +1 -1
  255. package/dist/repositoryKnowledge.js +177 -37
  256. package/dist/repositoryKnowledge.js.map +1 -1
  257. package/dist/repositoryKnowledgeScope.test.d.ts +2 -0
  258. package/dist/repositoryKnowledgeScope.test.d.ts.map +1 -0
  259. package/dist/repositoryKnowledgeScope.test.js +316 -0
  260. package/dist/repositoryKnowledgeScope.test.js.map +1 -0
  261. package/dist/repositoryOnboardingCaller.test.js +61 -4
  262. package/dist/repositoryOnboardingCaller.test.js.map +1 -1
  263. package/dist/repositoryPackage.d.ts.map +1 -1
  264. package/dist/repositoryPackage.js +340 -59
  265. package/dist/repositoryPackage.js.map +1 -1
  266. package/dist/repositoryPackageDelivery.test.js +65 -0
  267. package/dist/repositoryPackageDelivery.test.js.map +1 -1
  268. package/dist/repositoryPackageScope.test.d.ts +2 -0
  269. package/dist/repositoryPackageScope.test.d.ts.map +1 -0
  270. package/dist/repositoryPackageScope.test.js +578 -0
  271. package/dist/repositoryPackageScope.test.js.map +1 -0
  272. package/dist/schemas/codex-setup-child-start.schema.json +12 -0
  273. package/dist/schemas/codex-setup-intent.schema.json +27 -0
  274. package/dist/schemas/local-codex-setup-session.schema.json +39 -0
  275. package/dist/schemas/setup-claim-failure.schema.json +15 -0
  276. package/dist/skillInstallationScope.test.d.ts +2 -0
  277. package/dist/skillInstallationScope.test.d.ts.map +1 -0
  278. package/dist/skillInstallationScope.test.js +64 -0
  279. package/dist/skillInstallationScope.test.js.map +1 -0
  280. package/dist/skillPreparationCache.test.js +12 -4
  281. package/dist/skillPreparationCache.test.js.map +1 -1
  282. package/dist/skillPreparationCaller.test.js +31 -1
  283. package/dist/skillPreparationCaller.test.js.map +1 -1
  284. package/dist/startupCommitScope.test.d.ts +2 -0
  285. package/dist/startupCommitScope.test.d.ts.map +1 -0
  286. package/dist/startupCommitScope.test.js +172 -0
  287. package/dist/startupCommitScope.test.js.map +1 -0
  288. package/dist/workspaceRegistry.d.ts.map +1 -1
  289. package/dist/workspaceRegistry.js +7 -3
  290. package/dist/workspaceRegistry.js.map +1 -1
  291. package/dist/workspaceRegistry.test.js +51 -0
  292. package/dist/workspaceRegistry.test.js.map +1 -1
  293. package/dist/workspaceRegistryRecovery.d.ts.map +1 -1
  294. package/dist/workspaceRegistryRecovery.js +171 -64
  295. package/dist/workspaceRegistryRecovery.js.map +1 -1
  296. package/dist/workspaceRegistryRecoveryScope.test.d.ts +2 -0
  297. package/dist/workspaceRegistryRecoveryScope.test.d.ts.map +1 -0
  298. package/dist/workspaceRegistryRecoveryScope.test.js +416 -0
  299. package/dist/workspaceRegistryRecoveryScope.test.js.map +1 -0
  300. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFile, spawn } from 'node:child_process';
3
3
  import { createHash, createPrivateKey, createPublicKey, randomUUID } from 'node:crypto';
4
- import { realpathSync } from 'node:fs';
4
+ import { constants as fsConstants, realpathSync } from 'node:fs';
5
5
  import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
6
6
  import { homedir } from 'node:os';
7
7
  import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
@@ -12,7 +12,7 @@ import { canonicalize, createActionDecisionPublicKeyResolver, sha256, validateCo
12
12
  import { buildTrajectoryCapsule, containsDisallowedLocalPath, redactValue, referencesExcludedPath, trajectoryCapsuleHash } from '@dharma-ai-labs/agent-fabric-evidence-reduction';
13
13
  import { assertPolicy, loadOrganizationPolicy, verifyServerAuthorizedPolicy } from '@dharma-ai-labs/agent-fabric-policy';
14
14
  import { agyAdapter, claudeAdapter, codexAdapter, hermesAdapter, providerAdapters, providerExecutionRecords, providerProcessEnvironment } from '@dharma-ai-labs/agent-fabric-provider-adapters';
15
- import { AgentFabricClient, beginEnrollment, loadOrCreateDeviceIdentity, normalizeHqUrl, pollEnrollment, deleteActiveSkillAuthorizationAnchor, loadActiveSkillAuthorizationAnchor, loadDeviceEnrollmentAnchor, saveActiveSkillAuthorizationAnchor, isDefinitiveAgentFabricRejection, recoverDeviceEnrollmentConsistency, loadOrganizationApiToken, redeemBootstrapGrant, claimSetupReference, setupClaimSourceRegistration, saveDeviceConfig, saveDeviceEnrollmentAnchor, saveOrganizationApiToken, } from '@dharma-ai-labs/agent-fabric-relay-client';
15
+ import { AgentFabricClient, beginEnrollment, loadDeviceIdentity, loadOrCreateDeviceIdentity, normalizeHqUrl, pollEnrollment, deleteActiveSkillAuthorizationAnchor, loadActiveSkillAuthorizationAnchor, loadDeviceEnrollmentAnchor, saveActiveSkillAuthorizationAnchor, isDefinitiveAgentFabricRejection, recoverDeviceEnrollmentConsistency, loadOrganizationApiToken, redeemBootstrapGrant, claimSetupReference, setupClaimSourceRegistration, saveDeviceConfig, saveDeviceEnrollmentAnchor, saveOrganizationApiToken, } from '@dharma-ai-labs/agent-fabric-relay-client';
16
16
  import { AgentFabricClient as AgentFabricApiClient, } from '@dharma-ai-labs/agent-fabric-sdk';
17
17
  import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAuthorizationForReplacement, getLegacySkillBundleIdForUpgrade, installSkillBundle, readVerifiedRepositoryKnowledge, rollbackUnconfirmedSkillBundle, verifySkillBundle } from '@dharma-ai-labs/agent-fabric-skill-manager';
18
18
  import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, providerInstructionsForTask, } from '@dharma-ai-labs/agent-fabric-task-runner';
@@ -20,7 +20,7 @@ import { CLI_USAGE } from './usage.js';
20
20
  import { superviseRelay } from './relaySupervisor.js';
21
21
  import { currentRepositoryRelayFailure, repositoryRelayObservationReady, runRegisteredRepositoryRelays, selectRepositoryRelayRegistrations, serializeRelayWork, waitForRelayRefresh, withRepositoryRelayStage } from './repositoryRelaySupervisor.js';
22
22
  import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
23
- import { runOwnedContainerEntrypoint } from './containerRelayLifecycle.js';
23
+ import { runOwnedContainerEntrypoint, readContainerProcessIdentity } from './containerRelayLifecycle.js';
24
24
  import { readWorkspaceRegistry } from './workspaceRegistry.js';
25
25
  import { appendRecoveredWorkspace, applyRegistryRecoveryFile, inspectRegistryRecoveryFile, resolveRegistryRecoveryProjection } from './workspaceRegistryRecovery.js';
26
26
  import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
@@ -31,7 +31,7 @@ import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readReposito
31
31
  import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
32
32
  import { createNamedPeerContentAuthorization } from './namedPeerContentAuthorization.js';
33
33
  import { advanceRepositorySourceBaseline, BlockedRepositorySourceRetry, fetchRepositorySourceAuthorization, recoverPublishedLocalSourceBaseline, repositorySourcePollInvalidatesWatcher, RepositorySourceWatcher, scanRepositorySourceChanges, seedRepositorySourceWatcher } from './repositorySourceSync.js';
34
- import { assertRepositoryInstallerOwnership, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
34
+ import { captureRepositoryInstallerInput, prepareRepositoryInstallerWorkspace, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
35
35
  import { installRepositoryJoinConnection } from './repositoryJoinConnection.js';
36
36
  import { recoverLegacyRepositoryInstaller, selectLegacyInstallerRecoveryWorkspace } from './legacyInstallerRecovery.js';
37
37
  import { onboardingResumeCommand, selectDeviceWorkspace, workspaceIdForDevice } from './onboardingWorkspace.js';
@@ -70,10 +70,25 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
70
70
  import { readCodexPublicContext } from '@dharma-ai-labs/agent-fabric-provider-adapters/experimental/codex-session';
71
71
  import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
72
72
  import { startNamedCodexThread } from './namedCodexThread.js';
73
+ import { observeNamedCodexSkill, parseNamedCodexSkillObservation } from './namedCodexSkillDiscovery.js';
73
74
  import { namedCodexEnvironment } from './namedCodexEnvironment.js';
74
75
  import { namedCodexFilesystem } from './namedCodexFilesystem.js';
76
+ import { createNamedSessionChildOwner, currentNamedSessionChildOwner } from './namedSessionChildOwner.js';
77
+ import { awaitCodexSetupSession, consumeCodexSetupSessions, currentAcceptedSetupSessionScope, originalCodexSetupSessionSender, withCodexSetupSessionSender } from './codexSetupSessionHandoff.js';
78
+ import { receiveCodexSetupChildStart, runCodexSetupChildStartup, sendCodexSetupChildStart } from './codexSetupChildStartup.js';
79
+ import { assertBootstrapHostSource, captureBootstrapHostChild, currentBootstrapHostScope, inspectCodexBootstrapHostPreparation, prepareCodexBootstrapHost, runCodexBootstrapHost, runCodexBootstrapHostScope } from './bootstrapHostScope.js';
80
+ // Trusted runtime composition only; these exports do not enable effectful setup.
81
+ import { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
82
+ import { openCodexSetupOwnedConnection } from './codexSetupOwnedConnection.js';
83
+ export { startCodexSetupNativeHost } from './codexSetupNativeHost.js';
84
+ export { openCodexSetupVaultJournal } from './codexSetupVaultJournal.js';
85
+ import { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
86
+ export { createCodexSetupReadinessOwner } from './codexSetupReadiness.js';
87
+ import { assertCodexSetupExecutionLease } from './codexSetupAdmission.js';
88
+ import { parseLocalCodexSetupReadiness } from '@dharma-ai-labs/agent-fabric-local-vault/setup-readiness';
89
+ import { writeBootstrapHostJson, writeBootstrapHostText } from './bootstrapHostFiles.js';
75
90
  export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
76
- const VERSION = '0.2.153';
91
+ const VERSION = '0.2.155';
77
92
  const USAGE = CLI_USAGE;
78
93
  const execFileAsync = promisify(execFile);
79
94
  const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
@@ -157,6 +172,36 @@ async function loadVaultModule() {
157
172
  process.emitWarning = original;
158
173
  }
159
174
  }
175
+ async function openBootstrapVault(options) {
176
+ const scope = currentBootstrapHostScope(), snapshot = { root: options.root, rawLocalDays: options.rawLocalDays };
177
+ let key;
178
+ let vault;
179
+ try {
180
+ await scope?.assert();
181
+ const { LocalVault, loadOrCreateVaultMasterKey } = scope
182
+ ? await scope.step(() => loadVaultModule()) : await loadVaultModule();
183
+ key = await loadOrCreateVaultMasterKey(undefined, scope);
184
+ await scope?.assert();
185
+ vault = scope ? await LocalVault.open({ ...snapshot, masterKey: key }, scope)
186
+ : await LocalVault.open({ ...snapshot, masterKey: key });
187
+ await scope?.assert();
188
+ return vault;
189
+ }
190
+ catch (error) {
191
+ if (vault) {
192
+ try {
193
+ await vault.close();
194
+ }
195
+ catch {
196
+ throw new Error('vault_cleanup_unconfirmed');
197
+ }
198
+ }
199
+ throw error;
200
+ }
201
+ finally {
202
+ key?.fill(0);
203
+ }
204
+ }
160
205
  export function isDirectExecution(argvPath, moduleUrl) {
161
206
  if (!argvPath)
162
207
  return false;
@@ -176,43 +221,56 @@ function workspaceRegistryPath() { return resolve(dharmaHome(), 'registry', 'wor
176
221
  function evidenceUploadLedgerPath() { return resolve(dharmaHome(), 'relay', 'evidence-upload-ledger.json'); }
177
222
  function evidenceRequestReceiptPath(requestId) { return resolve(dharmaHome(), 'relay', 'evidence-requests', `${requestId}.json`); }
178
223
  export async function loadOrCreateInstallationId(path = installationIdentityPath()) {
224
+ const scope = currentBootstrapHostScope();
225
+ const step = (operation) => scope ? scope.step(operation) : operation();
179
226
  const readExisting = async () => {
180
- const value = JSON.parse(await readFile(path, 'utf8'));
227
+ const value = JSON.parse(await step(() => readFile(path, 'utf8')));
181
228
  if (value.schema !== 'dharma.installation-identity/v1'
182
229
  || typeof value.installationId !== 'string'
183
230
  || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.installationId)) {
184
231
  throw new Error('Installation identity is invalid. Preserve the file and re-enroll this installation.');
185
232
  }
233
+ await scope?.assert();
186
234
  return value.installationId;
187
235
  };
188
236
  try {
189
- return await readExisting();
190
- }
191
- catch (error) {
192
- if (error.code !== 'ENOENT')
193
- throw error;
194
- }
195
- await mkdir(dirname(path), { recursive: true, mode: 0o700 });
196
- const installationId = randomUUID();
197
- let handle;
198
- try {
199
- handle = await open(path, 'wx', 0o600);
200
- await handle.writeFile(`${JSON.stringify({
201
- schema: 'dharma.installation-identity/v1',
202
- installationId,
203
- createdAt: new Date().toISOString(),
204
- }, null, 2)}\n`);
205
- await handle.sync();
237
+ try {
238
+ return await readExisting();
239
+ }
240
+ catch (error) {
241
+ if (error.code !== 'ENOENT')
242
+ throw error;
243
+ }
244
+ await step(() => mkdir(dirname(path), { recursive: true, mode: 0o700 }));
245
+ const installationId = randomUUID();
246
+ let handle;
247
+ try {
248
+ // Capture ownership before the post-acquisition fence can refuse continuation.
249
+ await step(async () => { handle = await open(path, 'wx', 0o600); });
250
+ await step(() => handle.writeFile(`${JSON.stringify({
251
+ schema: 'dharma.installation-identity/v1',
252
+ installationId,
253
+ createdAt: new Date().toISOString(),
254
+ }, null, 2)}\n`));
255
+ await step(() => handle.sync());
256
+ }
257
+ catch (error) {
258
+ if (error.code !== 'EEXIST')
259
+ throw error;
260
+ return await readExisting();
261
+ }
262
+ finally {
263
+ await handle?.close();
264
+ }
265
+ await scope?.assert();
266
+ return installationId;
206
267
  }
207
268
  catch (error) {
208
- if (error.code !== 'EEXIST')
269
+ if (!scope)
209
270
  throw error;
210
- return await readExisting();
211
- }
212
- finally {
213
- await handle?.close();
271
+ await scope.assert();
272
+ throw new Error('codex_setup_host_installation_failed');
214
273
  }
215
- return installationId;
216
274
  }
217
275
  function newEvidenceUploadLedger(day) {
218
276
  return { schema: 'dharma.evidence-upload-ledger/v2', day, totalBytes: 0, capsuleHashes: [], capsuleBytes: {} };
@@ -328,12 +386,12 @@ export async function syncPendingRetentionCapsules(vault, fabric, policy, worksp
328
386
  assertCapsuleIntegrity(item.capsule);
329
387
  }
330
388
  catch {
331
- vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'capsule_integrity_failed');
389
+ await vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'capsule_integrity_failed');
332
390
  continue;
333
391
  }
334
392
  if (item.capsule.organizationId === fabric.config.organizationId
335
393
  && item.capsule.deviceId !== fabric.config.deviceId) {
336
- vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'device_binding_changed');
394
+ await vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'device_binding_changed');
337
395
  if (!warnedPreviousDevice) {
338
396
  process.stderr.write('Queued trajectory capsules from a previous device were preserved locally and excluded from upload. Recapture eligible provider sessions under the current device.\n');
339
397
  warnedPreviousDevice = true;
@@ -350,7 +408,7 @@ export async function syncPendingRetentionCapsules(vault, fabric, policy, worksp
350
408
  assertPolicy(policy);
351
409
  // A successfully refreshed policy is authoritative. Retire only the
352
410
  // superseded capsule so it cannot permanently block later valid work.
353
- vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'authorization_superseded');
411
+ await vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'authorization_superseded');
354
412
  continue;
355
413
  }
356
414
  const oversized = Buffer.byteLength(canonicalize(item.capsule)) > policy.evidence.maximumCapsuleBytes;
@@ -361,7 +419,7 @@ export async function syncPendingRetentionCapsules(vault, fabric, policy, worksp
361
419
  }), item.trajectoryId);
362
420
  if (serverHead && serverHead.revision === item.revision
363
421
  && serverHead.capsuleHash === item.capsule.capsuleHash) {
364
- vault.markCapsuleSynced(item.trajectoryId, item.revision);
422
+ await vault.markCapsuleSynced(item.trajectoryId, item.revision);
365
423
  synced += 1;
366
424
  continue;
367
425
  }
@@ -370,7 +428,7 @@ export async function syncPendingRetentionCapsules(vault, fabric, policy, worksp
370
428
  : item.revision !== 1 || item.capsule.previousRevisionHash !== null) {
371
429
  throw new Error('Rejected pending trajectory revision conflicts with the accepted server head.');
372
430
  }
373
- vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, containsLocalPath ? 'local_path_disclosure_superseded' : 'capsule_size_limit_superseded');
431
+ await vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, containsLocalPath ? 'local_path_disclosure_superseded' : 'capsule_size_limit_superseded');
374
432
  process.stderr.write(containsLocalPath
375
433
  ? 'An unsent trajectory capsule containing a local path was retired from the upload queue. Encrypted raw evidence remains local and eligible sessions can be recaptured under the current policy.\n'
376
434
  : 'An unsent oversized trajectory capsule was retired from the upload queue. Encrypted raw evidence remains local and eligible sessions can be recaptured under the current policy.\n');
@@ -383,10 +441,10 @@ export async function syncPendingRetentionCapsules(vault, fabric, policy, worksp
383
441
  catch (error) {
384
442
  if (!isDefinitiveSecretDisclosureRejection(error))
385
443
  throw error;
386
- vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'secret_disclosure_forbidden');
444
+ await vault.discardPendingCapsuleSync(item.trajectoryId, item.revision, 'secret_disclosure_forbidden');
387
445
  continue;
388
446
  }
389
- vault.markCapsuleSynced(item.trajectoryId, item.revision);
447
+ await vault.markCapsuleSynced(item.trajectoryId, item.revision);
390
448
  synced += 1;
391
449
  }
392
450
  if (matched === 0) {
@@ -618,93 +676,198 @@ export function assertCapsuleAuthorizedByCurrentPolicy(capsule, policy) {
618
676
  }
619
677
  }
620
678
  async function acquirePidLock(lockPath, timeoutMs, timeoutMessage) {
621
- await mkdir(dirname(lockPath), { recursive: true, mode: 0o700 });
622
- const deadline = Date.now() + timeoutMs;
623
- for (;;) {
624
- const candidate = `${lockPath}.${process.pid}.${randomUUID()}.candidate`;
625
- try {
626
- await writeFile(candidate, `${process.pid}\n`, { mode: 0o600, flag: 'wx' });
627
- await link(candidate, lockPath);
628
- await unlink(candidate);
629
- return async () => { await unlink(lockPath).catch(() => undefined); };
679
+ const scope = currentBootstrapHostScope();
680
+ const step = (operation) => scope ? scope.step(operation) : operation();
681
+ const owned = new Map();
682
+ const recordOwned = async (path) => {
683
+ if (!scope)
684
+ return;
685
+ owned.set(path, null);
686
+ // Metadata for cooperative cleanup is captured even if creation withdrew authority.
687
+ const metadata = await lstat(path, { bigint: true });
688
+ if (metadata.isSymbolicLink() || !metadata.isFile() && !metadata.isDirectory()) {
689
+ throw new Error('codex_setup_host_lock_cleanup_unconfirmed');
630
690
  }
631
- catch (error) {
632
- await unlink(candidate).catch(() => undefined);
633
- if (error.code !== 'EEXIST')
691
+ owned.set(path, { dev: metadata.dev, ino: metadata.ino, directory: metadata.isDirectory() });
692
+ };
693
+ const cleanupOwned = async (path, directory = false) => {
694
+ if (!scope) {
695
+ if (directory)
696
+ await rm(path, { recursive: true, force: true });
697
+ else
698
+ await unlink(path).catch(() => undefined);
699
+ return;
700
+ }
701
+ if (!owned.has(path))
702
+ return;
703
+ try {
704
+ const expected = owned.get(path);
705
+ if (!expected)
706
+ throw new Error();
707
+ const metadata = await lstat(path, { bigint: true }).catch(error => {
708
+ if (error.code === 'ENOENT')
709
+ return null;
634
710
  throw error;
635
- const recoveryPath = `${lockPath}.recovery`;
636
- const recoveryCandidate = `${recoveryPath}.${process.pid}.${randomUUID()}.candidate`;
637
- let windowsRecoveryOwner;
711
+ });
712
+ if (metadata) {
713
+ if (metadata.isSymbolicLink() || metadata.dev !== expected.dev || metadata.ino !== expected.ino
714
+ || metadata.isDirectory() !== expected.directory || !expected.directory && !metadata.isFile())
715
+ throw new Error();
716
+ if (expected.directory)
717
+ await rm(path, { recursive: true, force: true });
718
+ else
719
+ await unlink(path);
720
+ }
721
+ owned.delete(path);
722
+ }
723
+ catch {
724
+ throw new Error('codex_setup_host_lock_cleanup_unconfirmed');
725
+ }
726
+ };
727
+ try {
728
+ await step(() => mkdir(dirname(lockPath), { recursive: true, mode: 0o700 }));
729
+ const deadline = Date.now() + timeoutMs;
730
+ for (;;) {
731
+ const candidate = `${lockPath}.${process.pid}.${randomUUID()}.candidate`;
638
732
  try {
639
- await mkdir(recoveryCandidate);
640
- await writeFile(resolve(recoveryCandidate, 'owner'), `${process.pid}\n`, { mode: 0o600 });
641
- for (;;) {
642
- try {
643
- await rename(recoveryCandidate, recoveryPath);
644
- break;
645
- }
646
- catch (renameError) {
647
- if (process.platform !== 'win32' || renameError.code !== 'EPERM')
648
- throw renameError;
649
- // Windows uses EPERM for an existing destination. Its owner can
650
- // finish during inspection; retry publication, never ignore denial.
733
+ await step(async () => {
734
+ await writeFile(candidate, `${process.pid}\n`, { mode: 0o600, flag: 'wx' });
735
+ await recordOwned(candidate);
736
+ });
737
+ await step(async () => {
738
+ await link(candidate, lockPath);
739
+ if (scope)
740
+ owned.set(lockPath, owned.get(candidate));
741
+ });
742
+ await step(() => cleanupOwned(candidate));
743
+ let release;
744
+ return () => release ??= cleanupOwned(lockPath);
745
+ }
746
+ catch (error) {
747
+ await cleanupOwned(candidate);
748
+ if (error.code !== 'EEXIST')
749
+ throw error;
750
+ const recoveryPath = `${lockPath}.recovery`;
751
+ const recoveryCandidate = `${recoveryPath}.${process.pid}.${randomUUID()}.candidate`;
752
+ let windowsRecoveryOwner;
753
+ try {
754
+ await step(async () => { await mkdir(recoveryCandidate); await recordOwned(recoveryCandidate); });
755
+ await step(() => writeFile(resolve(recoveryCandidate, 'owner'), `${process.pid}\n`, { mode: 0o600 }));
756
+ for (;;) {
651
757
  try {
652
- const directory = await lstat(recoveryPath);
653
- const ownerPath = resolve(recoveryPath, 'owner');
654
- const ownerFile = await lstat(ownerPath);
655
- const ownerText = (await readFile(ownerPath, 'utf8')).trim();
656
- const owner = Number(ownerText);
657
- if (!directory.isDirectory() || directory.isSymbolicLink()
658
- || !ownerFile.isFile() || ownerFile.isSymbolicLink()
659
- || !/^[1-9][0-9]*$/.test(ownerText) || !Number.isSafeInteger(owner))
660
- throw renameError;
661
- windowsRecoveryOwner = owner;
758
+ await step(async () => {
759
+ await rename(recoveryCandidate, recoveryPath);
760
+ if (scope) {
761
+ owned.set(recoveryPath, owned.get(recoveryCandidate));
762
+ owned.delete(recoveryCandidate);
763
+ }
764
+ });
765
+ break;
662
766
  }
663
- catch (inspectionError) {
664
- let disappeared = inspectionError.code === 'ENOENT';
665
- if (!disappeared && inspectionError.code === 'EPERM') {
666
- // Windows can deny owner reads while its directory is being
667
- // deleted. Retry only when fresh metadata confirms absence.
668
- try {
669
- await lstat(recoveryPath);
767
+ catch (renameError) {
768
+ if (process.platform !== 'win32' || renameError.code !== 'EPERM')
769
+ throw renameError;
770
+ // Windows uses EPERM for an existing destination. Its owner can
771
+ // finish during inspection; retry publication, never ignore denial.
772
+ try {
773
+ const directory = await step(() => lstat(recoveryPath));
774
+ const ownerPath = resolve(recoveryPath, 'owner');
775
+ const ownerFile = await step(() => lstat(ownerPath));
776
+ const ownerText = (await step(() => readFile(ownerPath, 'utf8'))).trim();
777
+ const owner = Number(ownerText);
778
+ if (!directory.isDirectory() || directory.isSymbolicLink()
779
+ || !ownerFile.isFile() || ownerFile.isSymbolicLink()
780
+ || !/^[1-9][0-9]*$/.test(ownerText) || !Number.isSafeInteger(owner))
781
+ throw renameError;
782
+ windowsRecoveryOwner = owner;
783
+ }
784
+ catch (inspectionError) {
785
+ let disappeared = inspectionError.code === 'ENOENT';
786
+ if (!disappeared && inspectionError.code === 'EPERM') {
787
+ // Windows can deny owner reads while its directory is being
788
+ // deleted. Retry only when fresh metadata confirms absence.
789
+ try {
790
+ await step(() => lstat(recoveryPath));
791
+ }
792
+ catch (readbackError) {
793
+ disappeared = readbackError.code === 'ENOENT';
794
+ }
670
795
  }
671
- catch (readbackError) {
672
- disappeared = readbackError.code === 'ENOENT';
796
+ if (disappeared && Date.now() < deadline) {
797
+ await step(() => new Promise((resolvePromise) => setTimeout(resolvePromise, 25)));
798
+ continue;
673
799
  }
800
+ throw renameError;
674
801
  }
675
- if (disappeared && Date.now() < deadline) {
676
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 25));
802
+ throw renameError;
803
+ }
804
+ }
805
+ try {
806
+ let ownerPid = 0;
807
+ try {
808
+ ownerPid = Number((await step(() => readFile(lockPath, 'utf8'))).trim());
809
+ }
810
+ catch (readError) {
811
+ if (readError.code === 'ENOENT')
677
812
  continue;
813
+ }
814
+ let ownerAlive = Number.isSafeInteger(ownerPid) && ownerPid > 0;
815
+ if (ownerAlive) {
816
+ try {
817
+ await step(async () => { process.kill(ownerPid, 0); });
818
+ }
819
+ catch (killError) {
820
+ ownerAlive = killError.code === 'EPERM';
821
+ }
822
+ }
823
+ if (!ownerAlive) {
824
+ const quarantine = `${lockPath}.dead.${randomUUID()}`;
825
+ try {
826
+ await step(() => rename(lockPath, quarantine));
827
+ await step(() => unlink(quarantine));
828
+ continue;
829
+ }
830
+ catch (renameError) {
831
+ if (renameError.code !== 'ENOENT')
832
+ throw renameError;
678
833
  }
679
- throw renameError;
680
834
  }
681
- throw renameError;
835
+ }
836
+ finally {
837
+ await cleanupOwned(recoveryPath, true);
682
838
  }
683
839
  }
684
- try {
685
- let ownerPid = 0;
686
- try {
687
- ownerPid = Number((await readFile(lockPath, 'utf8')).trim());
840
+ catch (recoveryError) {
841
+ await cleanupOwned(recoveryCandidate, true);
842
+ const recoveryCode = recoveryError.code || '';
843
+ let recoveryOwner = 0;
844
+ if (process.platform === 'win32' && recoveryCode === 'EPERM') {
845
+ if (windowsRecoveryOwner === undefined)
846
+ throw recoveryError;
847
+ recoveryOwner = windowsRecoveryOwner;
688
848
  }
689
- catch (readError) {
690
- if (readError.code === 'ENOENT')
691
- continue;
849
+ else {
850
+ if (!['EEXIST', 'ENOTEMPTY'].includes(recoveryCode))
851
+ throw recoveryError;
852
+ try {
853
+ recoveryOwner = Number((await step(() => readFile(resolve(recoveryPath, 'owner'), 'utf8'))).trim());
854
+ }
855
+ catch { }
692
856
  }
693
- let ownerAlive = Number.isSafeInteger(ownerPid) && ownerPid > 0;
694
- if (ownerAlive) {
857
+ let recoveryOwnerAlive = Number.isSafeInteger(recoveryOwner) && recoveryOwner > 0;
858
+ if (recoveryOwnerAlive) {
695
859
  try {
696
- process.kill(ownerPid, 0);
860
+ await step(async () => { process.kill(recoveryOwner, 0); });
697
861
  }
698
862
  catch (killError) {
699
- ownerAlive = killError.code === 'EPERM';
863
+ recoveryOwnerAlive = killError.code === 'EPERM';
700
864
  }
701
865
  }
702
- if (!ownerAlive) {
703
- const quarantine = `${lockPath}.dead.${randomUUID()}`;
866
+ if (!recoveryOwnerAlive) {
867
+ const quarantine = `${recoveryPath}.dead.${randomUUID()}`;
704
868
  try {
705
- await rename(lockPath, quarantine);
706
- await unlink(quarantine);
707
- continue;
869
+ await step(() => rename(recoveryPath, quarantine));
870
+ await step(() => rm(quarantine, { recursive: true, force: true }));
708
871
  }
709
872
  catch (renameError) {
710
873
  if (renameError.code !== 'ENOENT')
@@ -712,64 +875,49 @@ async function acquirePidLock(lockPath, timeoutMs, timeoutMessage) {
712
875
  }
713
876
  }
714
877
  }
715
- finally {
716
- await rm(recoveryPath, { recursive: true, force: true });
717
- }
718
- }
719
- catch (recoveryError) {
720
- await rm(recoveryCandidate, { recursive: true, force: true });
721
- const recoveryCode = recoveryError.code || '';
722
- let recoveryOwner = 0;
723
- if (process.platform === 'win32' && recoveryCode === 'EPERM') {
724
- if (windowsRecoveryOwner === undefined)
725
- throw recoveryError;
726
- recoveryOwner = windowsRecoveryOwner;
727
- }
728
- else {
729
- if (!['EEXIST', 'ENOTEMPTY'].includes(recoveryCode))
730
- throw recoveryError;
731
- try {
732
- recoveryOwner = Number((await readFile(resolve(recoveryPath, 'owner'), 'utf8')).trim());
733
- }
734
- catch { }
735
- }
736
- let recoveryOwnerAlive = Number.isSafeInteger(recoveryOwner) && recoveryOwner > 0;
737
- if (recoveryOwnerAlive) {
738
- try {
739
- process.kill(recoveryOwner, 0);
740
- }
741
- catch (killError) {
742
- recoveryOwnerAlive = killError.code === 'EPERM';
743
- }
744
- }
745
- if (!recoveryOwnerAlive) {
746
- const quarantine = `${recoveryPath}.dead.${randomUUID()}`;
747
- try {
748
- await rename(recoveryPath, quarantine);
749
- await rm(quarantine, { recursive: true, force: true });
750
- }
751
- catch (renameError) {
752
- if (renameError.code !== 'ENOENT')
753
- throw renameError;
754
- }
755
- }
878
+ if (Date.now() >= deadline)
879
+ throw new Error(scope ? 'codex_setup_host_lock_timeout' : timeoutMessage);
880
+ await step(() => new Promise((resolvePromise) => setTimeout(resolvePromise, 25)));
756
881
  }
757
- if (Date.now() >= deadline)
758
- throw new Error(timeoutMessage);
759
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 25));
760
882
  }
761
883
  }
884
+ catch (error) {
885
+ let cleanupFailed = false;
886
+ for (const path of owned.keys())
887
+ try {
888
+ await cleanupOwned(path);
889
+ }
890
+ catch {
891
+ cleanupFailed = true;
892
+ }
893
+ if (cleanupFailed)
894
+ throw new Error('codex_setup_host_lock_cleanup_unconfirmed');
895
+ if (!scope)
896
+ throw error;
897
+ await scope.assert();
898
+ if (error instanceof Error && error.message === 'codex_setup_host_lock_timeout')
899
+ throw error;
900
+ throw new Error('codex_setup_host_lock_failed');
901
+ }
762
902
  }
763
903
  async function withEvidenceLedgerLock(operation) {
764
- const release = await acquirePidLock(`${evidenceUploadLedgerPath()}.lock`, 5_000, 'Timed out waiting for the evidence upload ledger lock.');
904
+ const scope = currentBootstrapHostScope();
905
+ const step = (effect) => scope ? scope.step(effect) : effect();
906
+ let release;
765
907
  try {
766
- return await operation();
908
+ await step(async () => {
909
+ release = await acquirePidLock(`${evidenceUploadLedgerPath()}.lock`, 5_000, 'Timed out waiting for the evidence upload ledger lock.');
910
+ });
911
+ return await step(operation);
767
912
  }
768
913
  finally {
769
- await release();
914
+ await release?.();
770
915
  }
771
916
  }
772
917
  async function writeJsonAtomic(path, value) {
918
+ const hostScope = currentBootstrapHostScope();
919
+ if (hostScope)
920
+ return writeBootstrapHostJson(path, value, hostScope);
773
921
  const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
774
922
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
775
923
  await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
@@ -918,104 +1066,130 @@ export async function acquireRelaySupervisorLease(home = dharmaHome()) {
918
1066
  };
919
1067
  }
920
1068
  export async function materializeWorkspacePolicy(input) {
921
- const allowedCommands = {};
1069
+ const scope = currentBootstrapHostScope();
1070
+ const step = (effect) => scope ? scope.step(effect) : effect();
1071
+ const exists = (path) => step(() => scope ? pathExistsOrThrow(path) : pathExists(path));
922
1072
  try {
923
- const packageJson = JSON.parse(await readFile(resolve(input.workspace, 'package.json'), 'utf8'));
924
- const scripts = packageJson.scripts || {};
925
- for (const [script, commandId, timeoutSeconds] of [
926
- ['test', 'repo.test', 1_200],
927
- ['lint', 'repo.lint', 600],
928
- ['typecheck', 'repo.typecheck', 600],
929
- ['type-check', 'repo.typecheck', 600],
930
- ['build', 'repo.build', 1_200],
931
- ]) {
932
- if (typeof scripts[script] === 'string' && !allowedCommands[commandId]) {
933
- allowedCommands[commandId] = { argv: ['npm', 'run', script], timeoutSeconds };
1073
+ if (scope)
1074
+ input = { ...input, serverPolicyAuthorization: structuredClone(input.serverPolicyAuthorization) };
1075
+ await scope?.assert();
1076
+ const allowedCommands = {};
1077
+ try {
1078
+ const packageJson = JSON.parse(await step(() => readFile(resolve(input.workspace, 'package.json'), 'utf8')));
1079
+ const scripts = packageJson.scripts || {};
1080
+ for (const [script, commandId, timeoutSeconds] of [
1081
+ ['test', 'repo.test', 1_200],
1082
+ ['lint', 'repo.lint', 600],
1083
+ ['typecheck', 'repo.typecheck', 600],
1084
+ ['type-check', 'repo.typecheck', 600],
1085
+ ['build', 'repo.build', 1_200],
1086
+ ]) {
1087
+ if (typeof scripts[script] === 'string' && !allowedCommands[commandId]) {
1088
+ allowedCommands[commandId] = { argv: ['npm', 'run', script], timeoutSeconds };
1089
+ }
934
1090
  }
935
1091
  }
936
- }
937
- catch { }
938
- const writePaths = [];
939
- for (const candidate of ['src', 'app', 'apps', 'lib', 'packages', 'test', 'tests', 'docs']) {
940
- if (await pathExists(resolve(input.workspace, candidate)))
941
- writePaths.push(`${candidate}/**`);
942
- }
943
- let policy = {
944
- schema: 'dharma.organization-policy/v2',
945
- organizationId: input.organizationId,
946
- revision: input.revision,
947
- evidence: {
948
- defaultMode: 'deep',
949
- automaticDisclosure: { mode: 'local_analysis' },
950
- registeredWorkspaceOnly: true,
951
- excludePaths: ['.env', '.env.*', '.git/**', 'node_modules/**', 'dist/**', 'build/**', '**/*.pem', '**/*.key'],
952
- maximumCapsuleBytes: 1_000_000,
953
- maximumDailyUploadBytes: 50_000_000,
954
- maximumExpansionBytes: 65_536,
955
- pseudonymizeIdentity: true,
956
- },
957
- tasks: {
958
- defaultNetwork: 'deny',
959
- defaultGit: 'task_branch',
960
- allowedCommands,
961
- writePaths,
962
- requireLocalConfirmationFor: ['network.allowlisted_domains', 'git.push', 'merge', 'deploy'],
963
- },
964
- skills: { automaticInstall: true, automaticPromotionMaxRisk: 'R2', canaryPercent: 10 },
965
- retention: { rawLocalDays: 30, capsuleServerDays: 90 },
966
- budgets: { dailyAnalysisCents: 1_000 },
967
- };
968
- const existingPath = resolve(input.workspace, '.dharma', 'approved-policy.json');
969
- if (await pathExists(existingPath)) {
970
- const existing = await loadOrganizationPolicy(existingPath);
971
- if (existing.organizationId === input.organizationId)
972
- policy = existing;
973
- }
974
- if (input.serverPolicyAuthorization !== undefined && input.serverPolicyAuthorization !== null) {
975
- if (!input.serverPublicKeyEd25519 || !input.workspaceId) {
976
- throw new Error('Server policy authorization requires the enrolled server key and workspace ID.');
977
- }
978
- policy = applyServerEvidencePolicy(policy, input.serverPolicyAuthorization, input.serverPublicKeyEd25519, input.organizationId, input.workspaceId);
979
- if (!input.dryRun) {
980
- await applyWorkspaceAuthorizationAtomically({
981
- workspaceId: input.workspaceId,
982
- authorization: policy.serverAuthorization,
983
- policyPath: existingPath,
984
- policy,
985
- secureStore: input.secureStore,
986
- });
1092
+ catch {
1093
+ await scope?.assert();
1094
+ }
1095
+ const writePaths = [];
1096
+ for (const candidate of ['src', 'app', 'apps', 'lib', 'packages', 'test', 'tests', 'docs']) {
1097
+ if (await exists(resolve(input.workspace, candidate)))
1098
+ writePaths.push(`${candidate}/**`);
1099
+ }
1100
+ let policy = {
1101
+ schema: 'dharma.organization-policy/v2',
1102
+ organizationId: input.organizationId,
1103
+ revision: input.revision,
1104
+ evidence: {
1105
+ defaultMode: 'deep',
1106
+ automaticDisclosure: { mode: 'local_analysis' },
1107
+ registeredWorkspaceOnly: true,
1108
+ excludePaths: ['.env', '.env.*', '.git/**', 'node_modules/**', 'dist/**', 'build/**', '**/*.pem', '**/*.key'],
1109
+ maximumCapsuleBytes: 1_000_000,
1110
+ maximumDailyUploadBytes: 50_000_000,
1111
+ maximumExpansionBytes: 65_536,
1112
+ pseudonymizeIdentity: true,
1113
+ },
1114
+ tasks: {
1115
+ defaultNetwork: 'deny',
1116
+ defaultGit: 'task_branch',
1117
+ allowedCommands,
1118
+ writePaths,
1119
+ requireLocalConfirmationFor: ['network.allowlisted_domains', 'git.push', 'merge', 'deploy'],
1120
+ },
1121
+ skills: { automaticInstall: true, automaticPromotionMaxRisk: 'R2', canaryPercent: 10 },
1122
+ retention: { rawLocalDays: 30, capsuleServerDays: 90 },
1123
+ budgets: { dailyAnalysisCents: 1_000 },
1124
+ };
1125
+ const existingPath = resolve(input.workspace, '.dharma', 'approved-policy.json');
1126
+ if (await exists(existingPath)) {
1127
+ const existing = await step(() => loadOrganizationPolicy(existingPath));
1128
+ if (existing.organizationId === input.organizationId)
1129
+ policy = existing;
1130
+ }
1131
+ if (input.serverPolicyAuthorization !== undefined && input.serverPolicyAuthorization !== null) {
1132
+ const workspaceId = input.workspaceId;
1133
+ if (!input.serverPublicKeyEd25519 || !workspaceId) {
1134
+ throw new Error('Server policy authorization requires the enrolled server key and workspace ID.');
1135
+ }
1136
+ policy = applyServerEvidencePolicy(policy, input.serverPolicyAuthorization, input.serverPublicKeyEd25519, input.organizationId, workspaceId);
1137
+ if (!input.dryRun) {
1138
+ await step(() => applyWorkspaceAuthorizationAtomically({
1139
+ workspaceId,
1140
+ authorization: policy.serverAuthorization,
1141
+ policyPath: existingPath,
1142
+ policy,
1143
+ secureStore: input.secureStore,
1144
+ }));
1145
+ }
1146
+ else {
1147
+ await step(() => assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization, false));
1148
+ }
987
1149
  }
988
- else {
989
- await assertWorkspaceAuthorizationCurrent(input.workspaceId, policy.serverAuthorization, false);
1150
+ assertPolicy(policy);
1151
+ const relativePath = '.dharma/approved-policy.json';
1152
+ if (!input.dryRun && !policy.serverAuthorization) {
1153
+ await step(() => mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 }));
1154
+ await step(() => writeJsonAtomic(resolve(input.workspace, relativePath), policy));
990
1155
  }
1156
+ await scope?.assert();
1157
+ return { relativePath, policy, applied: !input.dryRun };
991
1158
  }
992
- assertPolicy(policy);
993
- const relativePath = '.dharma/approved-policy.json';
994
- if (!input.dryRun && !policy.serverAuthorization) {
995
- await mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 });
996
- await writeJsonAtomic(resolve(input.workspace, relativePath), policy);
1159
+ catch (error) {
1160
+ if (!scope)
1161
+ throw error;
1162
+ await scope.assert();
1163
+ throw new Error('workspace_policy_materialization_failed');
997
1164
  }
998
- return { relativePath, policy, applied: !input.dryRun };
999
1165
  }
1000
1166
  function workspaceAuthorizationStatePath(workspaceId) {
1001
1167
  return resolve(dharmaHome(), 'registry', 'workspace-authorizations', `${workspaceId}.json`);
1002
1168
  }
1003
1169
  async function withFileLock(lockPath, operation, timeoutMessage = 'Timed out waiting for the workspace authorization lock.') {
1004
- const release = await acquirePidLock(lockPath, 10_000, timeoutMessage);
1170
+ const scope = currentBootstrapHostScope();
1171
+ const step = (effect) => scope ? scope.step(effect) : effect();
1172
+ let release;
1005
1173
  try {
1006
- return await operation();
1174
+ await step(async () => { release = await acquirePidLock(lockPath, 10_000, timeoutMessage); });
1175
+ return await step(operation);
1007
1176
  }
1008
1177
  finally {
1009
- await release();
1178
+ await release?.();
1010
1179
  }
1011
1180
  }
1012
1181
  export async function withRelayStartupMutation(operation, home = dharmaHome()) {
1013
- const release = await acquirePidLock(resolve(home, 'autostart-mutation.lock'), 30_000, 'demo_watch_startup_busy: another startup operation is still running.');
1182
+ const scope = currentBootstrapHostScope();
1183
+ const step = (effect) => scope ? scope.step(effect) : effect();
1184
+ let release;
1014
1185
  try {
1015
- return await operation();
1186
+ await step(async () => {
1187
+ release = await acquirePidLock(resolve(home, 'autostart-mutation.lock'), 30_000, 'demo_watch_startup_busy: another startup operation is still running.');
1188
+ });
1189
+ return await step(operation);
1016
1190
  }
1017
1191
  finally {
1018
- await release();
1192
+ await release?.();
1019
1193
  }
1020
1194
  }
1021
1195
  export async function withWorkspacePolicyRefreshLock(workspaceId, operation) {
@@ -1031,67 +1205,102 @@ export async function withWorkspaceSkillActivationLock(workspaceId, provider, op
1031
1205
  return withFileLock(resolve(dharmaHome(), 'registry', 'skill-activation-locks', `${key}.lock`), operation, 'Timed out waiting for the workspace skill activation lock.');
1032
1206
  }
1033
1207
  async function assertWorkspaceAuthorizationCurrent(workspaceId, authorization, requireExisting = true) {
1034
- const statePath = workspaceAuthorizationStatePath(workspaceId);
1035
- let previous = null;
1208
+ const scope = currentBootstrapHostScope();
1209
+ const step = (effect) => scope ? scope.step(effect) : effect();
1036
1210
  try {
1037
- const parsed = JSON.parse(await readFile(statePath, 'utf8'));
1038
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
1039
- || typeof parsed.issuedAt !== 'string'
1040
- || !Number.isFinite(Date.parse(parsed.issuedAt))
1041
- || typeof parsed.signature !== 'string'
1042
- || !parsed.signature) {
1043
- throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1211
+ if (scope)
1212
+ authorization = structuredClone(authorization);
1213
+ await scope?.assert();
1214
+ const statePath = workspaceAuthorizationStatePath(workspaceId);
1215
+ let previous = null;
1216
+ try {
1217
+ const parsed = JSON.parse(await step(() => readFile(statePath, 'utf8')));
1218
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
1219
+ || typeof parsed.issuedAt !== 'string'
1220
+ || !Number.isFinite(Date.parse(parsed.issuedAt))
1221
+ || typeof parsed.signature !== 'string'
1222
+ || !parsed.signature) {
1223
+ throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1224
+ }
1225
+ previous = parsed;
1044
1226
  }
1045
- previous = parsed;
1227
+ catch (error) {
1228
+ await scope?.assert();
1229
+ if (error.code === 'ENOENT' && !requireExisting)
1230
+ return;
1231
+ throw new Error(scope ? 'workspace_authorization_replay_state_invalid'
1232
+ : 'Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1233
+ }
1234
+ if (previous) {
1235
+ const incomingTime = Date.parse(authorization.issuedAt);
1236
+ const previousTime = Date.parse(previous.issuedAt);
1237
+ if (!Number.isFinite(previousTime)
1238
+ || incomingTime < previousTime
1239
+ || (incomingTime === previousTime && authorization.signature !== previous.signature)) {
1240
+ throw new Error(scope ? 'workspace_authorization_replay_superseded'
1241
+ : 'Server workspace policy authorization is older than the last accepted authorization.');
1242
+ }
1243
+ }
1244
+ await scope?.assert();
1046
1245
  }
1047
1246
  catch (error) {
1048
- if (error.code === 'ENOENT' && !requireExisting)
1049
- return;
1050
- throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1051
- }
1052
- if (previous) {
1053
- const incomingTime = Date.parse(authorization.issuedAt);
1054
- const previousTime = Date.parse(previous.issuedAt);
1055
- if (!Number.isFinite(previousTime)
1056
- || incomingTime < previousTime
1057
- || (incomingTime === previousTime && authorization.signature !== previous.signature)) {
1058
- throw new Error('Server workspace policy authorization is older than the last accepted authorization.');
1059
- }
1247
+ if (!scope)
1248
+ throw error;
1249
+ await scope.assert();
1250
+ if (error instanceof Error && ['workspace_authorization_replay_state_invalid',
1251
+ 'workspace_authorization_replay_superseded'].includes(error.message))
1252
+ throw error;
1253
+ throw new Error('workspace_authorization_replay_failed');
1060
1254
  }
1061
1255
  }
1062
1256
  async function applyWorkspaceAuthorizationAtomically(input) {
1063
- const statePath = workspaceAuthorizationStatePath(input.workspaceId);
1064
- await withFileLock(`${statePath}.lock`, async () => {
1065
- await assertWorkspaceAuthorizationCurrent(input.workspaceId, input.authorization, false);
1066
- let previous = null;
1067
- try {
1068
- previous = JSON.parse(await readFile(statePath, 'utf8'));
1069
- }
1070
- catch (error) {
1071
- if (error.code !== 'ENOENT') {
1072
- throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1257
+ const scope = currentBootstrapHostScope();
1258
+ const step = (effect) => scope ? scope.step(effect) : effect();
1259
+ try {
1260
+ if (scope)
1261
+ input = { ...input, authorization: structuredClone(input.authorization), policy: structuredClone(input.policy) };
1262
+ await scope?.assert();
1263
+ const statePath = workspaceAuthorizationStatePath(input.workspaceId);
1264
+ await step(() => withFileLock(`${statePath}.lock`, async () => {
1265
+ await step(() => assertWorkspaceAuthorizationCurrent(input.workspaceId, input.authorization, false));
1266
+ let previous = null;
1267
+ try {
1268
+ previous = JSON.parse(await step(() => readFile(statePath, 'utf8')));
1073
1269
  }
1074
- }
1075
- const contentAuthorized = input.policy.evidence.automaticDisclosure?.mode === 'customer_authorized_content';
1076
- const ledgerExists = await pathExists(evidenceUploadLedgerPath());
1077
- if (contentAuthorized && ledgerExists) {
1078
- const current = JSON.parse(await readFile(evidenceUploadLedgerPath(), 'utf8'));
1079
- const ledger = evidenceLedgerForPolicyActivation(current, new Date().toISOString().slice(0, 10));
1080
- if (JSON.stringify(current) !== JSON.stringify(ledger)) {
1081
- await writeJsonAtomic(evidenceUploadLedgerPath(), ledger);
1270
+ catch (error) {
1271
+ await scope?.assert();
1272
+ if (error.code !== 'ENOENT') {
1273
+ throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
1274
+ }
1082
1275
  }
1083
- }
1084
- if (contentAuthorized && !ledgerExists) {
1085
- const ledger = newEvidenceUploadLedger(new Date().toISOString().slice(0, 10));
1086
- await writeJsonAtomic(evidenceUploadLedgerPath(), ledger);
1087
- }
1088
- await writeJsonAtomic(statePath, {
1089
- issuedAt: input.authorization.issuedAt,
1090
- signature: input.authorization.signature,
1091
- contentLedgerInitialized: previous?.contentLedgerInitialized === true || contentAuthorized,
1092
- });
1093
- await writeJsonAtomic(input.policyPath, input.policy);
1094
- });
1276
+ const contentAuthorized = input.policy.evidence.automaticDisclosure?.mode === 'customer_authorized_content';
1277
+ const ledgerExists = await step(() => scope ? pathExistsOrThrow(evidenceUploadLedgerPath()) : pathExists(evidenceUploadLedgerPath()));
1278
+ if (contentAuthorized && ledgerExists) {
1279
+ const current = JSON.parse(await step(() => readFile(evidenceUploadLedgerPath(), 'utf8')));
1280
+ const ledger = evidenceLedgerForPolicyActivation(current, new Date().toISOString().slice(0, 10));
1281
+ if (JSON.stringify(current) !== JSON.stringify(ledger)) {
1282
+ await step(() => writeJsonAtomic(evidenceUploadLedgerPath(), ledger));
1283
+ }
1284
+ }
1285
+ if (contentAuthorized && !ledgerExists) {
1286
+ const ledger = newEvidenceUploadLedger(new Date().toISOString().slice(0, 10));
1287
+ await step(() => writeJsonAtomic(evidenceUploadLedgerPath(), ledger));
1288
+ }
1289
+ await step(() => writeJsonAtomic(statePath, {
1290
+ issuedAt: input.authorization.issuedAt,
1291
+ signature: input.authorization.signature,
1292
+ contentLedgerInitialized: previous?.contentLedgerInitialized === true || contentAuthorized,
1293
+ }));
1294
+ await step(() => writeJsonAtomic(input.policyPath, input.policy));
1295
+ }));
1296
+ await scope?.assert();
1297
+ }
1298
+ catch (error) {
1299
+ if (!scope)
1300
+ throw error;
1301
+ await scope.assert();
1302
+ throw new Error('workspace_policy_activation_failed');
1303
+ }
1095
1304
  }
1096
1305
  export function applyServerEvidencePolicy(base, value, serverPublicKeyEd25519, organizationId, workspaceId, now = new Date()) {
1097
1306
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -1348,13 +1557,35 @@ async function registry() {
1348
1557
  return (await readWorkspaceRegistry(workspaceRegistryPath())).records;
1349
1558
  }
1350
1559
  async function saveRegistry(items) {
1351
- await mkdir(resolve(dharmaHome(), 'registry'), { recursive: true, mode: 0o700 });
1352
- await writeJsonAtomic(workspaceRegistryPath(), items);
1560
+ const scope = currentBootstrapHostScope();
1561
+ try {
1562
+ await scope?.assert();
1563
+ const snapshot = scope ? structuredClone(items) : items;
1564
+ const directory = resolve(dharmaHome(), 'registry');
1565
+ if (scope)
1566
+ await scope.step(() => mkdir(directory, { recursive: true, mode: 0o700 }));
1567
+ else
1568
+ await mkdir(directory, { recursive: true, mode: 0o700 });
1569
+ await writeJsonAtomic(workspaceRegistryPath(), snapshot);
1570
+ }
1571
+ catch (error) {
1572
+ if (!scope)
1573
+ throw error;
1574
+ await scope.assert();
1575
+ throw new Error('workspace_registry_write_failed');
1576
+ }
1353
1577
  }
1354
1578
  async function saveWorkspaceRecord(entry) {
1355
- const release = await acquirePidLock(`${workspaceRegistryPath()}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
1579
+ const scope = currentBootstrapHostScope();
1580
+ const step = (operation) => scope ? scope.step(operation) : operation();
1581
+ let release;
1356
1582
  try {
1357
- const value = await readFile(workspaceRegistryPath(), 'utf8').catch(error => {
1583
+ await scope?.assert();
1584
+ const snapshot = scope ? structuredClone(entry) : entry;
1585
+ await step(async () => {
1586
+ release = await acquirePidLock(`${workspaceRegistryPath()}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
1587
+ });
1588
+ const value = await step(() => readFile(workspaceRegistryPath(), 'utf8')).catch(error => {
1358
1589
  if (error.code === 'ENOENT')
1359
1590
  return '[]';
1360
1591
  throw error;
@@ -1362,28 +1593,49 @@ async function saveWorkspaceRecord(entry) {
1362
1593
  const items = JSON.parse(value);
1363
1594
  if (!Array.isArray(items))
1364
1595
  throw new Error('Workspace registry is invalid; preserve it for recovery.');
1365
- await saveRegistry([...items.filter(item => item.workspaceId !== entry.workspaceId), entry]);
1596
+ await saveRegistry([...items.filter(item => item.workspaceId !== snapshot.workspaceId), snapshot]);
1597
+ }
1598
+ catch (error) {
1599
+ if (!scope)
1600
+ throw error;
1601
+ if (error instanceof Error && error.message === 'codex_setup_host_lock_cleanup_unconfirmed')
1602
+ throw error;
1603
+ await scope.assert();
1604
+ if (error instanceof Error && error.message === 'Workspace registry is invalid; preserve it for recovery.') {
1605
+ throw new Error('workspace_registry_invalid');
1606
+ }
1607
+ throw new Error('workspace_registry_write_failed');
1366
1608
  }
1367
1609
  finally {
1368
- await release();
1610
+ await release?.();
1369
1611
  }
1370
1612
  }
1371
1613
  async function gitValue(workspace, argv) {
1614
+ const scope = currentBootstrapHostScope(), args = ['-C', workspace, ...argv];
1615
+ const operation = () => execFileAsync('git', args, scope ? {
1616
+ timeout: 10_000, maxBuffer: 65_536, windowsHide: true, signal: scope.signal,
1617
+ env: providerProcessEnvironment(process.env),
1618
+ } : { timeout: 10_000 });
1372
1619
  try {
1373
- return (await execFileAsync('git', ['-C', workspace, ...argv], { timeout: 10_000 })).stdout.trim() || null;
1620
+ const result = scope ? await scope.step(operation) : await operation();
1621
+ return result.stdout.trim() || null;
1374
1622
  }
1375
1623
  catch {
1624
+ // An optional Git value may be absent; withdrawn setup authority may not.
1625
+ await scope?.assert();
1376
1626
  return null;
1377
1627
  }
1378
1628
  }
1379
1629
  async function client() {
1380
- const instance = await AgentFabricClient.open({ configPath: configPath(), statePath: protocolStatePath() });
1630
+ const instance = await AgentFabricClient.open({ configPath: configPath(), statePath: protocolStatePath(),
1631
+ hostScope: currentBootstrapHostScope() });
1381
1632
  await instance.openSession(VERSION);
1382
1633
  return instance;
1383
1634
  }
1384
1635
  export async function probeRelayConnection(openClient = async () => AgentFabricClient.open({
1385
1636
  configPath: configPath(),
1386
1637
  statePath: protocolStatePath(),
1638
+ hostScope: currentBootstrapHostScope(),
1387
1639
  })) {
1388
1640
  const instance = await openClient();
1389
1641
  const acknowledgement = await instance.openSession(VERSION);
@@ -1398,6 +1650,8 @@ export async function probeRelayConnection(openClient = async () => AgentFabricC
1398
1650
  };
1399
1651
  }
1400
1652
  async function organizationApi(flags) {
1653
+ const hostScope = currentBootstrapHostScope();
1654
+ await hostScope?.assert();
1401
1655
  let enrolled = null;
1402
1656
  try {
1403
1657
  enrolled = JSON.parse(await readFile(configPath(), 'utf8'));
@@ -1406,11 +1660,12 @@ async function organizationApi(flags) {
1406
1660
  const organizationId = String(flags.get('organization-id') || enrolled?.organizationId || '').trim();
1407
1661
  if (!organizationId)
1408
1662
  throw new Error('Organization command requires --organization-id or an enrolled device.');
1409
- const token = String(process.env.DHARMA_ORG_API_TOKEN || '').trim()
1663
+ const token = (hostScope ? '' : String(process.env.DHARMA_ORG_API_TOKEN || '').trim())
1410
1664
  || await loadOrganizationApiToken({
1411
1665
  hqUrl: enrolled?.hqUrl || portalUrl(flags),
1412
1666
  organizationId,
1413
1667
  installationId: enrolled?.installationId,
1668
+ hostScope,
1414
1669
  }) || '';
1415
1670
  if (!token)
1416
1671
  throw new Error('Organization command requires a token in the OS credential store or DHARMA_ORG_API_TOKEN. Tokens are not accepted on the command line.');
@@ -1570,14 +1825,40 @@ export function stableRepositoryLauncherContents(version = VERSION, runtime) {
1570
1825
  return repositoryLauncherContents(version, runtime);
1571
1826
  }
1572
1827
  async function installStableRepositoryLauncher(workspace) {
1828
+ const scope = currentBootstrapHostScope();
1573
1829
  const launcherRoot = resolve(workspace, '.dharma', 'bin');
1574
- await mkdir(launcherRoot, { recursive: true, mode: 0o700 });
1575
1830
  const shellPath = resolve(launcherRoot, 'dharma');
1576
1831
  const cmdPath = resolve(launcherRoot, 'dharma.cmd');
1577
1832
  const contents = stableRepositoryLauncherContents(VERSION, { platform: process.platform, nodeDirectory: dirname(process.execPath) });
1578
- await writeFile(shellPath, contents.shell, { mode: 0o700 });
1579
- await chmod(shellPath, 0o700);
1580
- await writeFile(cmdPath, contents.windows, { mode: 0o600 });
1833
+ if (scope) {
1834
+ await scope.assert();
1835
+ if (await scope.step(() => realpath(workspace)) !== workspace)
1836
+ throw new Error('codex_setup_host_launcher_path_invalid');
1837
+ for (const directory of [resolve(workspace, '.dharma'), launcherRoot]) {
1838
+ await scope.step(async () => {
1839
+ try {
1840
+ await mkdir(directory, { mode: 0o700 });
1841
+ }
1842
+ catch (error) {
1843
+ if (error.code !== 'EEXIST')
1844
+ throw error;
1845
+ }
1846
+ });
1847
+ const entry = await scope.step(() => lstat(directory));
1848
+ if (!entry.isDirectory() || entry.isSymbolicLink() || await scope.step(() => realpath(directory)) !== directory
1849
+ || process.platform !== 'win32' && (entry.uid !== process.getuid() || (entry.mode & 0o022))) {
1850
+ throw new Error('codex_setup_host_launcher_path_invalid');
1851
+ }
1852
+ }
1853
+ await writeBootstrapHostText(shellPath, contents.shell, scope, 0o700);
1854
+ await writeBootstrapHostText(cmdPath, contents.windows, scope, 0o600);
1855
+ }
1856
+ else {
1857
+ await mkdir(launcherRoot, { recursive: true, mode: 0o700 });
1858
+ await writeFile(shellPath, contents.shell, { mode: 0o700 });
1859
+ await chmod(shellPath, 0o700);
1860
+ await writeFile(cmdPath, contents.windows, { mode: 0o600 });
1861
+ }
1581
1862
  return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
1582
1863
  }
1583
1864
  async function relayUpgrade(flags) {
@@ -1718,6 +1999,9 @@ export async function waitForRelayReadiness(options = {}) {
1718
1999
  throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
1719
2000
  }
1720
2001
  async function startRelayDaemon(policyPath) {
2002
+ const hostScope = currentBootstrapHostScope();
2003
+ const step = (operation) => hostScope ? hostScope.step(operation) : operation();
2004
+ await hostScope?.assert();
1721
2005
  const config = await readDeviceConfig();
1722
2006
  if (!config)
1723
2007
  throw new Error('Relay startup requires current device enrollment.');
@@ -1727,23 +2011,32 @@ async function startRelayDaemon(policyPath) {
1727
2011
  && row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
1728
2012
  if (!selected)
1729
2013
  throw new Error('Relay policy must belong to this enrolled repository.');
2014
+ await assertBootstrapHostSource(selected.path, String(selected.repositoryRemoteHash || ''));
1730
2015
  const alreadyRunning = await relayProcessState() === 'running';
1731
2016
  const supervisorState = await relaySupervisorProcessState();
1732
2017
  if (supervisorState === 'unknown')
1733
2018
  throw new Error('Relay supervisor process state is unknown.');
1734
2019
  if (supervisorState === 'stopped') {
1735
- if ((await relayAutostartStatus({ home: dharmaHome() })).backend === 'container-entrypoint') {
1736
- await startRelayAutostart({ home: dharmaHome() });
2020
+ const startupBackend = (await relayAutostartStatus({ home: dharmaHome() })).backend;
2021
+ // A setup turn cannot own a supervisor intended to outlive that turn.
2022
+ if (hostScope || startupBackend === 'container-entrypoint') {
2023
+ await step(() => startRelayAutostart({ home: dharmaHome() }));
1737
2024
  }
1738
2025
  else {
1739
- const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', policyPath], {
1740
- cwd: dirname(dirname(policyPath)), detached: true, stdio: 'ignore', env: process.env,
2026
+ await step(async () => {
2027
+ const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', policyPath], {
2028
+ cwd: dirname(dirname(policyPath)), detached: !hostScope, stdio: 'ignore', env: process.env,
2029
+ });
2030
+ if (hostScope)
2031
+ captureBootstrapHostChild(hostScope, child);
2032
+ else
2033
+ child.unref();
1741
2034
  });
1742
- child.unref();
1743
2035
  }
1744
2036
  }
1745
2037
  let supervisorReady = false;
1746
2038
  for (let attempt = 0; attempt < 80; attempt += 1) {
2039
+ await hostScope?.assert();
1747
2040
  if (await relaySupervisorProcessState() === 'running') {
1748
2041
  const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'supervisor.pid'), 'utf8')
1749
2042
  .catch(() => '0')).trim());
@@ -1766,6 +2059,7 @@ async function startRelayDaemon(policyPath) {
1766
2059
  if (!supervisorReady)
1767
2060
  throw new Error('The relay supervisor did not become ready after bootstrap.');
1768
2061
  const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
2062
+ await hostScope?.assert();
1769
2063
  if (await relayProcessState() !== 'running')
1770
2064
  return 'stopped';
1771
2065
  const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
@@ -1773,6 +2067,7 @@ async function startRelayDaemon(policyPath) {
1773
2067
  return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
1774
2068
  version: VERSION, pid }) ? 'running' : 'stopped';
1775
2069
  } });
2070
+ await hostScope?.assert();
1776
2071
  return { started: !alreadyRunning, supervisor: 'running', ...readiness };
1777
2072
  }
1778
2073
  async function supervisedDemoCycle(registration, signal) {
@@ -1873,10 +2168,15 @@ async function relaySupervise(flags) {
1873
2168
  }
1874
2169
  finally {
1875
2170
  controller.abort();
1876
- await Promise.allSettled(services);
2171
+ const settled = await Promise.allSettled(services);
1877
2172
  await health.drain();
1878
2173
  process.removeListener('SIGINT', stop);
1879
2174
  process.removeListener('SIGTERM', stop);
2175
+ // Preserve ownership records when an owned child may still be running.
2176
+ if (settled.some(result => result.status === 'rejected'
2177
+ && result.reason instanceof Error && result.reason.message === 'owned_child_stop_unconfirmed')) {
2178
+ throw new Error('relay_supervisor_child_stop_unconfirmed');
2179
+ }
1880
2180
  const binding = await readFile(bindingPath, 'utf8')
1881
2181
  .then(value => JSON.parse(value)).catch(() => null);
1882
2182
  if (binding?.pid === process.pid)
@@ -1887,37 +2187,160 @@ async function relaySupervise(flags) {
1887
2187
  async function superviseNamedSessions(signal) {
1888
2188
  if (process.platform !== 'linux')
1889
2189
  return;
1890
- while (!signal.aborted) {
1891
- const root = resolve(dharmaHome(), 'sessions');
1892
- const entries = await readdir(root, { withFileTypes: true }).catch(error => {
1893
- if (error.code === 'ENOENT')
1894
- return [];
1895
- throw error;
1896
- });
1897
- for (const entry of entries.slice(0, 50)) {
1898
- if (signal.aborted)
1899
- break;
1900
- if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
1901
- continue;
1902
- try {
1903
- const registration = await readNamedSession(dharmaHome(), entry.name);
1904
- if (!registration?.enabled)
1905
- continue;
1906
- await namedSessionCommand('start', new Map([
1907
- ['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
1908
- ]));
1909
- }
1910
- catch {
1911
- process.stderr.write(`${JSON.stringify({ event: 'named_session_reconnect_pending', name: entry.name })}\n`);
2190
+ const owner = createNamedSessionChildOwner(signal);
2191
+ return owner.run(async () => {
2192
+ let vault;
2193
+ try {
2194
+ while (!signal.aborted) {
2195
+ try {
2196
+ // The original setup initializes the protected vault before staging.
2197
+ // A missing/unavailable store is not permission for a plaintext fallback.
2198
+ await access(resolve(dharmaHome(), 'vault', 'vault.sqlite'));
2199
+ owner.assert();
2200
+ vault ??= await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
2201
+ owner.assert();
2202
+ await consumeCodexSetupSessions({ vault, owner, signal,
2203
+ authorize: qualifyCodexSetupSessionRequest,
2204
+ start: async (scope) => {
2205
+ const request = scope.request;
2206
+ const status = await namedSessionCommand('start', new Map([
2207
+ ['name', request.name], ['workspace-id', request.workspaceId], ['apply', true]
2208
+ ]));
2209
+ const pid = owner.ownedPid(request.name);
2210
+ if (!pid || status.ok !== true || status.membershipId !== request.membershipId
2211
+ || status.deviceId !== request.deviceId || status.organizationId !== request.organizationId
2212
+ || status.workspaceId !== request.workspaceId || status.repositoryBindingId !== request.repositoryBindingId
2213
+ || status.endpointId !== request.endpointId || status.provider !== 'codex') {
2214
+ throw new Error('setup_session_owner_unconfirmed');
2215
+ }
2216
+ const [child, supervisor] = await scope.step(() => Promise.all([
2217
+ readContainerProcessIdentity(pid), readContainerProcessIdentity(process.pid)
2218
+ ]));
2219
+ if (child.parentPid !== process.pid || child.uid !== process.getuid() || supervisor.uid !== process.getuid()) {
2220
+ throw new Error('setup_session_owner_unconfirmed');
2221
+ }
2222
+ return { state: 'started', bindingId: String(status.bindingId), sessionId: String(status.sessionId),
2223
+ sessionPid: pid, supervisorPid: process.pid, sessionStartTicks: child.startTicks, supervisorStartTicks: supervisor.startTicks };
2224
+ } });
2225
+ }
2226
+ catch {
2227
+ if (!signal.aborted)
2228
+ process.stderr.write(`${JSON.stringify({ event: 'named_session_setup_pending' })}\n`);
2229
+ }
2230
+ const root = resolve(dharmaHome(), 'sessions');
2231
+ const entries = await readdir(root, { withFileTypes: true }).catch(error => {
2232
+ if (error.code === 'ENOENT')
2233
+ return [];
2234
+ throw error;
2235
+ });
2236
+ for (const entry of entries.slice(0, 50)) {
2237
+ if (signal.aborted)
2238
+ break;
2239
+ if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
2240
+ continue;
2241
+ try {
2242
+ const registration = await readNamedSession(dharmaHome(), entry.name);
2243
+ if (!registration?.enabled)
2244
+ continue;
2245
+ await namedSessionCommand('start', new Map([
2246
+ ['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
2247
+ ]));
2248
+ }
2249
+ catch {
2250
+ process.stderr.write(`${JSON.stringify({ event: 'named_session_reconnect_pending', name: entry.name })}\n`);
2251
+ }
2252
+ }
2253
+ await new Promise(resolveWait => {
2254
+ const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
2255
+ const timer = setTimeout(finish, 10_000);
2256
+ signal.addEventListener('abort', finish, { once: true });
2257
+ if (signal.aborted)
2258
+ finish();
2259
+ });
1912
2260
  }
1913
2261
  }
1914
- await new Promise(resolveWait => {
1915
- const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
1916
- const timer = setTimeout(finish, 10_000);
1917
- signal.addEventListener('abort', finish, { once: true });
1918
- if (signal.aborted)
1919
- finish();
1920
- });
2262
+ finally {
2263
+ vault?.close();
2264
+ }
2265
+ });
2266
+ }
2267
+ /** Standing owner rechecks the exact approved source/device without adopting
2268
+ * a caller PID, changing enrollment or contacting another member's device. */
2269
+ async function qualifyCodexSetupSessionRequest(request) {
2270
+ const owner = currentNamedSessionChildOwner();
2271
+ if (!owner)
2272
+ return false;
2273
+ try {
2274
+ owner.assert();
2275
+ const qualified = await qualifyCodexSetupSessionSource(request);
2276
+ owner.assert();
2277
+ return qualified;
2278
+ }
2279
+ catch {
2280
+ return false;
2281
+ }
2282
+ }
2283
+ async function qualifyCodexSetupSessionSource(request) {
2284
+ try {
2285
+ if (process.platform !== 'linux' || request.maximumCostCents !== 1000 || request.maximumTurnCostCents !== 25)
2286
+ return false;
2287
+ const config = await readDeviceConfig();
2288
+ if (!config || config.deviceId !== request.deviceId || config.organizationId !== request.organizationId
2289
+ || normalizeHqUrl(config.hqUrl) !== request.origin || config.setupClaimReference !== request.setupReference
2290
+ || config.setupClaimRepositoryFingerprint !== request.repositoryFingerprint)
2291
+ return false;
2292
+ const sender = await readContainerProcessIdentity(request.senderPid);
2293
+ if (sender.uid !== process.getuid() || sender.startTicks !== request.senderStartTicks)
2294
+ return false;
2295
+ const rows = (await registry()).filter(row => row.path === request.workspaceRoot && row.workspaceId === request.workspaceId
2296
+ && row.organizationId === request.organizationId && row.repositoryRemoteHash === request.repositoryFingerprint && row.status === 'active');
2297
+ if (rows.length !== 1 || request.name !== `codex-${request.workspaceId.slice(0, 8)}`)
2298
+ return false;
2299
+ const item = rows[0], role = repositoryRoleScope(item);
2300
+ if (role.repositoryBindingId !== request.repositoryBindingId || role.endpointId !== request.endpointId)
2301
+ return false;
2302
+ const source = await preflightBootstrapWorkspaceIdentity(request.workspaceRoot);
2303
+ if (source.fingerprint !== request.repositoryFingerprint || await realpath(request.workspaceRoot) !== request.workspaceRoot)
2304
+ return false;
2305
+ const contract = await loadAgentFabricOnboardingContract();
2306
+ if (`sha256:${contract.sha256}` !== request.contractDigest)
2307
+ return false;
2308
+ const enrollment = await loadDeviceEnrollmentAnchor({ config });
2309
+ const device = await enrolledDeviceIdentity(config);
2310
+ if (device.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
2311
+ return false;
2312
+ const policy = await loadVerifiedWorkspacePolicy(resolve(request.workspaceRoot, '.dharma', 'approved-policy.json'), request.workspaceId);
2313
+ verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
2314
+ organizationId: request.organizationId, workspaceId: request.workspaceId });
2315
+ await assertWorkspaceAuthorizationCurrent(request.workspaceId, policy.serverAuthorization);
2316
+ if (policy.revision !== request.policyRevision || sha256(canonicalize(policy)) !== request.policyHash)
2317
+ return false;
2318
+ const fabric = await client();
2319
+ await fetchRepositorySourceAuthorization(fabric, role);
2320
+ const existing = await readNamedSession(dharmaHome(), request.name);
2321
+ if (existing && (existing.identity.membershipId !== request.membershipId || existing.identity.deviceId !== request.deviceId
2322
+ || existing.identity.organizationId !== request.organizationId || existing.identity.workspaceId !== request.workspaceId
2323
+ || existing.identity.repositoryBindingId !== request.repositoryBindingId || existing.identity.endpointId !== request.endpointId))
2324
+ return false;
2325
+ return Date.parse(request.issuedAt) <= Date.now() && Date.now() < Date.parse(request.expiresAt);
2326
+ }
2327
+ catch {
2328
+ return false;
2329
+ }
2330
+ }
2331
+ async function qualifyCodexSetupChildRequest(request) {
2332
+ try {
2333
+ if (!await qualifyCodexSetupSessionSource(request))
2334
+ return false;
2335
+ const startup = await inspectOwnedRelayAutostart({ home: dharmaHome() });
2336
+ const parent = await readContainerProcessIdentity(process.ppid);
2337
+ return ['systemd-user', 'container-entrypoint'].includes(startup.backend) && startup.version === VERSION
2338
+ && parent.uid === process.getuid() && canonicalize(parent.argv) === canonicalize([
2339
+ process.execPath, fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', startup.policy
2340
+ ]);
2341
+ }
2342
+ catch {
2343
+ return false;
1921
2344
  }
1922
2345
  }
1923
2346
  async function relayStop() {
@@ -2061,7 +2484,472 @@ export function assertBootstrapResumeAuthority(input) {
2061
2484
  throw new Error('Bootstrap resume requires an existing device enrolled to this organization and portal.');
2062
2485
  }
2063
2486
  }
2064
- async function bootstrap(flags) {
2487
+ /** Planning-only trusted host entry. Effectful admission remains unavailable
2488
+ * until the full continuation and native owner lifecycle are qualified. */
2489
+ export async function bootstrapFromCodexSetup(input) {
2490
+ try {
2491
+ return await runCodexBootstrapHost(input, prepared => bootstrap(prepared.flags, prepared.scope));
2492
+ }
2493
+ catch (error) {
2494
+ const safe = new Set(['codex_setup_host_scope_invalid', 'codex_setup_host_scope_unavailable', 'codex_setup_host_context_conflict', 'codex_setup_host_source_mismatch']);
2495
+ let code = 'codex_setup_host_operation_failed';
2496
+ try {
2497
+ if (error instanceof Error && safe.has(error.message))
2498
+ code = error.message;
2499
+ }
2500
+ catch { }
2501
+ throw new Error(code);
2502
+ }
2503
+ }
2504
+ /** Native-owner continuation: no caller-selected flags or replacement authority. */
2505
+ export async function bootstrapFromCodexSetupScope(scope) {
2506
+ try {
2507
+ return await runCodexBootstrapHostScope(scope, prepared => run(['bootstrap',
2508
+ ...[...prepared.flags].flatMap(([key, value]) => value === true ? [`--${key}`] : [`--${key}`, String(value)]),
2509
+ ]));
2510
+ }
2511
+ catch (error) {
2512
+ if (error instanceof Error && error.message === 'setup_session_sender_unavailable') {
2513
+ return { ok: false, stage: 'host_setup_unavailable',
2514
+ code: 'codex_setup_host_execution_unqualified', effects: false, grantRedeemed: false };
2515
+ }
2516
+ const safe = new Set(['codex_setup_host_scope_invalid', 'codex_setup_host_scope_unavailable',
2517
+ 'codex_setup_host_context_conflict', 'codex_setup_host_source_mismatch', 'codex_setup_host_command_mismatch']);
2518
+ let code = 'codex_setup_host_operation_failed';
2519
+ try {
2520
+ if (error instanceof Error && safe.has(error.message))
2521
+ code = error.message;
2522
+ }
2523
+ catch { }
2524
+ throw new Error(code);
2525
+ }
2526
+ }
2527
+ async function readBootstrapRuntimeJson(path, scope) {
2528
+ let file;
2529
+ try {
2530
+ const before = await scope.step(() => lstat(path, { bigint: true }));
2531
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > 32768n
2532
+ || before.uid !== BigInt(process.getuid()) || (before.mode & 63n) !== 0n) {
2533
+ throw new Error('setup_runtime_state_unconfirmed');
2534
+ }
2535
+ await scope.step(async () => { file = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK); });
2536
+ const opened = await scope.step(() => file.stat({ bigint: true }));
2537
+ const bytes = Buffer.alloc(Number(before.size) + 1);
2538
+ const read = await scope.step(() => file.read(bytes, 0, bytes.length, 0));
2539
+ const after = await scope.step(() => file.stat({ bigint: true }));
2540
+ const current = await scope.step(() => lstat(path, { bigint: true }));
2541
+ if (read.bytesRead !== Number(before.size) || [opened, after, current].some(value => !value.isFile()
2542
+ || value.nlink !== 1n || value.dev !== before.dev || value.ino !== before.ino || value.size !== before.size
2543
+ || value.mtimeNs !== before.mtimeNs || value.ctimeNs !== before.ctimeNs || value.mode !== before.mode
2544
+ || value.uid !== before.uid || value.gid !== before.gid)) {
2545
+ throw new Error('setup_runtime_state_unconfirmed');
2546
+ }
2547
+ return JSON.parse(bytes.subarray(0, read.bytesRead).toString('utf8'));
2548
+ }
2549
+ finally {
2550
+ await file?.close();
2551
+ }
2552
+ }
2553
+ /** Actual scoped runtime readback. No callback-supplied ready flag or receipt ID. */
2554
+ async function observeCodexBootstrapRuntime(prepared, vault, firstLearning, intentDigest) {
2555
+ const { scope, intent } = prepared, workspace = String(prepared.flags.get('workspace'));
2556
+ const step = (operation) => scope.step(operation);
2557
+ await scope.assert();
2558
+ if (process.platform !== 'linux')
2559
+ throw new Error('setup_runtime_route_unqualified');
2560
+ const source = await step(() => preflightBootstrapWorkspaceIdentity(workspace));
2561
+ await assertBootstrapHostSource(workspace, source.fingerprint);
2562
+ const contract = await step(() => loadAgentFabricOnboardingContract());
2563
+ if (`sha256:${contract.sha256}` !== intent.contractDigest)
2564
+ throw new Error('setup_runtime_contract_changed');
2565
+ const config = await step(() => readDeviceConfig());
2566
+ if (!config || config.organizationId !== intent.organizationId || normalizeHqUrl(config.hqUrl) !== intent.origin) {
2567
+ throw new Error('setup_runtime_identity_unconfirmed');
2568
+ }
2569
+ const enrollment = await step(() => loadDeviceEnrollmentAnchor({ config, hostScope: scope }));
2570
+ const device = await step(() => enrolledDeviceIdentity(config));
2571
+ if (device.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
2572
+ throw new Error('setup_runtime_identity_unconfirmed');
2573
+ const rows = (await step(() => registry())).filter(row => row.path === workspace
2574
+ && row.organizationId === intent.organizationId && row.repositoryRemoteHash === intent.repositoryFingerprint && row.status === 'active');
2575
+ if (rows.length !== 1 || !rows[0].repositoryRole)
2576
+ throw new Error('setup_runtime_repository_unconfirmed');
2577
+ const item = rows[0], roleScope = repositoryRoleScope(item);
2578
+ const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
2579
+ const policy = await step(() => loadVerifiedWorkspacePolicy(policyPath, item.workspaceId));
2580
+ verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
2581
+ organizationId: intent.organizationId, workspaceId: item.workspaceId });
2582
+ await step(() => assertWorkspaceAuthorizationCurrent(item.workspaceId, policy.serverAuthorization));
2583
+ if (policy.revision !== intent.policyRevision)
2584
+ throw new Error('setup_runtime_policy_changed');
2585
+ const signed = await step(() => activeSkillAuthorization('codex', item.workspaceId, roleScope.repositoryAgentId, config));
2586
+ const anchor = await step(() => loadActiveSkillAuthorizationAnchor({ config, workspaceId: item.workspaceId,
2587
+ organizationAgentId: roleScope.repositoryAgentId, provider: 'codex', fresh: true, hostScope: scope }));
2588
+ if (!signed || !anchor || signed.bundleId !== anchor.bundleId)
2589
+ throw new Error('setup_runtime_package_unconfirmed');
2590
+ const installation = await step(() => verifyAgentFabricSkillInstallation({ provider: 'codex', workspace }));
2591
+ const content = await step(() => readNamedSessionPackageContent(installation, true));
2592
+ if (!installation.ready || content.bundleId !== signed.bundleId || content.bundleHash !== signed.bundleHash) {
2593
+ throw new Error('setup_runtime_package_unconfirmed');
2594
+ }
2595
+ const fabric = await step(() => client());
2596
+ const roles = await step(() => discoverRepositoryRoleMetadata(fabric, roleScope));
2597
+ const peers = roles.discovery?.peers;
2598
+ const own = peers?.filter(peer => peer.endpointId === roleScope.endpointId && peer.workspaceId === item.workspaceId);
2599
+ if (own?.length !== 1 || own[0].provider !== 'codex' || own[0].revision !== item.repositoryRole.revision
2600
+ || sha256(canonicalize({ roleName: own[0].roleName, description: own[0].description,
2601
+ questionCategories: own[0].questionCategories })) !== item.repositoryRole.profileHash) {
2602
+ throw new Error('setup_runtime_role_unconfirmed');
2603
+ }
2604
+ const name = `codex-${item.workspaceId.slice(0, 8)}`;
2605
+ const session = await step(() => readNamedSession(dharmaHome(), name));
2606
+ if (!session?.enabled || session.identity.membershipId !== intent.recipientMembershipId
2607
+ || session.identity.deviceId !== config.deviceId || session.identity.organizationId !== intent.organizationId
2608
+ || session.identity.workspaceId !== item.workspaceId || session.identity.endpointId !== roleScope.endpointId
2609
+ || session.identity.repositoryBindingId !== roleScope.repositoryBindingId)
2610
+ throw new Error('setup_runtime_session_unconfirmed');
2611
+ const binding = await step(() => vault.getProviderSessionBinding(session.bindingId, session.identity));
2612
+ if (!binding || binding.owner !== 'dharma_bridge' || binding.workspaceRoot !== workspace
2613
+ || binding.bindingId !== session.bindingId || Object.entries(session.identity).some(([key, value]) => binding[key] !== value)
2614
+ || Date.parse(binding.expiresAt) <= Date.now())
2615
+ throw new Error('setup_runtime_session_unconfirmed');
2616
+ const remote = await step(() => fabric.signedPost('/agent-fabric/provider-sessions', {
2617
+ schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
2618
+ workspaceId: item.workspaceId, endpointId: roleScope.endpointId, repositoryBindingId: roleScope.repositoryBindingId,
2619
+ bindingId: session.bindingId, expectedRevision: 0, leaseSeconds: 60,
2620
+ }));
2621
+ if (!isNamedSessionOwnerReceipt(remote, session.bindingId, session.identity))
2622
+ throw new Error('setup_runtime_session_unconfirmed');
2623
+ const native = await step(() => namedSessionCommand('readiness', new Map([
2624
+ ['name', name], ['workspace-id', item.workspaceId],
2625
+ ])));
2626
+ const nativeSkill = parseNamedCodexSkillObservation(native.nativeSkill);
2627
+ if (native.ok !== true || native.name !== name || native.bindingId !== binding.bindingId || native.sessionId !== binding.sessionId
2628
+ || Object.entries(session.identity).some(([key, value]) => native[key] !== value)
2629
+ || Object.keys(content).some(key => nativeSkill[key] !== content[key])) {
2630
+ throw new Error('setup_runtime_session_unconfirmed');
2631
+ }
2632
+ const startup = await step(() => inspectOwnedRelayAutostart({ home: dharmaHome() }));
2633
+ const startupState = await step(() => relayAutostartStatus({ home: dharmaHome() }));
2634
+ if (!['systemd-user', 'container-entrypoint'].includes(startup.backend) || startupState.state !== 'enabled'
2635
+ || startupState.backend !== startup.backend || startup.version !== VERSION || startupState.version !== VERSION
2636
+ || resolve(startup.workspace) !== startup.workspace
2637
+ || startup.policy !== resolve(startup.workspace, '.dharma', 'approved-policy.json')
2638
+ || startup.launcher !== resolve(startup.workspace, '.dharma', 'bin', 'dharma')
2639
+ || startup.backend === 'container-entrypoint' && startupState.lifecycle !== 'running') {
2640
+ throw new Error('setup_runtime_startup_unconfirmed');
2641
+ }
2642
+ // A standard relay can retain another repository as its same-device anchor.
2643
+ // Verify that anchor independently; selected-repository evidence stays scoped.
2644
+ const anchorRows = await step(() => registry());
2645
+ const startupItem = selectDeviceWorkspace(anchorRows, { organizationId: intent.organizationId,
2646
+ deviceId: config.deviceId, path: startup.workspace });
2647
+ if (!startupItem || startupItem.status !== 'active' || startupItem.routeHash !== sha256(startup.workspace)) {
2648
+ throw new Error('setup_runtime_startup_unconfirmed');
2649
+ }
2650
+ const startupRoute = canonicalize({ workspaceId: startupItem.workspaceId, path: startupItem.path,
2651
+ routeHash: startupItem.routeHash, repositoryRemoteHash: startupItem.repositoryRemoteHash, status: startupItem.status });
2652
+ const startupPolicy = await step(() => loadVerifiedWorkspacePolicy(startup.policy, startupItem.workspaceId));
2653
+ verifyServerAuthorizedPolicy({ policy: startupPolicy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
2654
+ organizationId: intent.organizationId, workspaceId: startupItem.workspaceId });
2655
+ await step(() => assertWorkspaceAuthorizationCurrent(startupItem.workspaceId, startupPolicy.serverAuthorization));
2656
+ const startupSnapshot = canonicalize(startup), startupStateSnapshot = canonicalize(startupState);
2657
+ const startupPolicyHash = sha256(canonicalize(startupPolicy));
2658
+ const pidFrom = async (path) => {
2659
+ const value = await readBootstrapRuntimeJson(path, scope);
2660
+ if (!Number.isSafeInteger(value) || Number(value) < 1 || Number(value) > 2147483647) {
2661
+ throw new Error('setup_runtime_process_unconfirmed');
2662
+ }
2663
+ return Number(value);
2664
+ };
2665
+ const home = dharmaHome(), entry = fileURLToPath(import.meta.url);
2666
+ const supervisorPid = await pidFrom(resolve(home, 'relay', 'supervisor.pid'));
2667
+ const relayPid = await pidFrom(resolve(home, 'relay', 'relay.pid'));
2668
+ const sessionPid = await pidFrom(resolve(namedSessionPaths(home, name).root, 'service.lock'));
2669
+ const processes = await step(() => Promise.all([supervisorPid, relayPid, sessionPid].map(pid => readContainerProcessIdentity(pid))));
2670
+ const argv = [[process.execPath, entry, 'relay', 'supervise', '--policy', startup.policy],
2671
+ [process.execPath, entry, 'relay', 'start', '--policy', startup.policy],
2672
+ [process.execPath, entry, 'sessions', 'serve', '--name', name, '--workspace-id', item.workspaceId, '--apply']];
2673
+ if (processes.some((value, i) => value.uid !== process.getuid() || !/^\d+$/.test(value.startTicks)
2674
+ || canonicalize(value.argv) !== canonicalize(argv[i])
2675
+ && (i !== 2 || canonicalize(value.argv) !== canonicalize([...argv[i], '--setup-handoff']))) || processes[1].parentPid !== supervisorPid
2676
+ || processes[2].parentPid !== supervisorPid) {
2677
+ throw new Error('setup_runtime_process_unconfirmed');
2678
+ }
2679
+ if (intentDigest !== undefined || processes[2].argv.includes('--setup-handoff')) {
2680
+ if (!intentDigest || !/^sha256:[a-f0-9]{64}$(?![\s\S])/.test(intentDigest))
2681
+ throw new Error('setup_runtime_session_unconfirmed');
2682
+ const handoff = await step(() => vault.readCodexSetupSession(intent.operationId, intentDigest));
2683
+ const request = handoff?.request, result = handoff?.result;
2684
+ if (handoff?.state !== 'accepted' || !request || result?.state !== 'started'
2685
+ || request.setupReference !== intent.setupReference || request.membershipId !== intent.recipientMembershipId
2686
+ || request.organizationId !== intent.organizationId || request.deviceId !== config.deviceId
2687
+ || request.workspaceId !== item.workspaceId || request.workspaceRoot !== workspace || request.name !== name
2688
+ || request.repositoryBindingId !== roleScope.repositoryBindingId || request.endpointId !== roleScope.endpointId
2689
+ || request.repositoryFingerprint !== intent.repositoryFingerprint || request.policyRevision !== intent.policyRevision
2690
+ || request.scopeDigest !== intent.scopeDigest || request.contractDigest !== intent.contractDigest
2691
+ || result.bindingId !== binding.bindingId || result.sessionId !== binding.sessionId || result.sessionPid !== sessionPid
2692
+ || result.supervisorPid !== supervisorPid || result.sessionStartTicks !== processes[2].startTicks
2693
+ || result.supervisorStartTicks !== processes[0].startTicks)
2694
+ throw new Error('setup_runtime_session_unconfirmed');
2695
+ }
2696
+ const supervisor = await readBootstrapRuntimeJson(resolve(home, 'relay', 'supervisor-workspace.json'), scope);
2697
+ if (supervisor.pid !== supervisorPid || supervisor.organizationId !== intent.organizationId || supervisor.deviceId !== config.deviceId
2698
+ || supervisor.policyPath !== startup.policy || supervisor.version !== VERSION || supervisor.standardRepositories !== true) {
2699
+ throw new Error('setup_runtime_process_unconfirmed');
2700
+ }
2701
+ const poll = await readBootstrapRuntimeJson(resolve(home, 'relay', 'repositories', item.workspaceId, 'last-successful-poll.json'), scope);
2702
+ if (!repositoryRelayObservationReady({ observation: poll, workspaceId: item.workspaceId, version: VERSION, pid: relayPid })) {
2703
+ throw new Error('setup_runtime_poll_unconfirmed');
2704
+ }
2705
+ const currentContent = await step(async () => readNamedSessionPackageContent(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace }), true));
2706
+ if (Object.keys(content).some(key => currentContent[key] !== content[key])) {
2707
+ throw new Error('setup_runtime_package_changed');
2708
+ }
2709
+ const currentProcesses = await step(() => Promise.all([supervisorPid, relayPid, sessionPid].map(pid => readContainerProcessIdentity(pid))));
2710
+ if (processes.some((value, i) => canonicalize(value) !== canonicalize(currentProcesses[i])))
2711
+ throw new Error('setup_runtime_process_changed');
2712
+ const currentStartup = await step(() => inspectOwnedRelayAutostart({ home }));
2713
+ const currentStartupState = await step(() => relayAutostartStatus({ home }));
2714
+ const currentStartupItem = selectDeviceWorkspace(await step(() => registry()), { organizationId: intent.organizationId,
2715
+ deviceId: config.deviceId, path: startup.workspace });
2716
+ const currentConfig = await step(() => readDeviceConfig());
2717
+ if (canonicalize(currentStartup) !== startupSnapshot || canonicalize(currentStartupState) !== startupStateSnapshot
2718
+ || !currentStartupItem || canonicalize({ workspaceId: currentStartupItem.workspaceId, path: currentStartupItem.path,
2719
+ routeHash: currentStartupItem.routeHash, repositoryRemoteHash: currentStartupItem.repositoryRemoteHash,
2720
+ status: currentStartupItem.status }) !== startupRoute
2721
+ || !currentConfig || currentConfig.deviceId !== config.deviceId || currentConfig.organizationId !== intent.organizationId
2722
+ || normalizeHqUrl(currentConfig.hqUrl) !== intent.origin)
2723
+ throw new Error('setup_runtime_startup_changed');
2724
+ const currentStartupPolicy = await step(() => loadVerifiedWorkspacePolicy(startup.policy, startupItem.workspaceId));
2725
+ if (sha256(canonicalize(currentStartupPolicy)) !== startupPolicyHash)
2726
+ throw new Error('setup_runtime_startup_changed');
2727
+ await step(() => assertWorkspaceAuthorizationCurrent(startupItem.workspaceId, currentStartupPolicy.serverAuthorization));
2728
+ await step(() => assertWorkspaceAuthorizationCurrent(item.workspaceId, policy.serverAuthorization));
2729
+ return parseLocalCodexSetupReadiness({ schema: 'dharma.local-codex-setup-readiness/v1',
2730
+ operationId: intent.operationId, organizationId: intent.organizationId, membershipId: session.identity.membershipId,
2731
+ deviceId: config.deviceId, workspaceId: item.workspaceId, repositoryBindingId: roleScope.repositoryBindingId,
2732
+ repositoryAgentId: roleScope.repositoryAgentId, endpointId: roleScope.endpointId, sessionBindingId: binding.bindingId,
2733
+ sessionId: binding.sessionId, repositoryFingerprint: intent.repositoryFingerprint, policyRevision: policy.revision,
2734
+ policyHash: sha256(canonicalize(policy)), manifestHash: content.manifestHash, catalogHash: content.catalogHash,
2735
+ bundleId: content.bundleId, bundleHash: content.bundleHash, activeReceiptHash: anchor.receiptHash,
2736
+ roleRevision: item.repositoryRole.revision, roleProfileHash: item.repositoryRole.profileHash, nativeSkillHash: content.skillsHash,
2737
+ contractDigest: intent.contractDigest, cliVersion: VERSION, relayPid, relayPolledAt: poll.at,
2738
+ startupBackend: startup.backend, firstLearning, verifiedAt: new Date().toISOString(),
2739
+ expiresAt: new Date(Math.min(Date.parse(intent.expiresAt), Date.parse(policy.serverAuthorization.expiresAt), Date.parse(startupPolicy.serverAuthorization.expiresAt), Date.parse(binding.expiresAt), signed.expiresAt ? Date.parse(signed.expiresAt) : Infinity)).toISOString() });
2740
+ }
2741
+ /** Trusted native host composition, not an MCP/model/peer tool. */
2742
+ export async function createCodexBootstrapCompletionOwner(scope, vault) {
2743
+ const prepared = await runCodexBootstrapHostScope(scope, async (value) => value);
2744
+ let firstLearning;
2745
+ const readiness = createCodexSetupReadinessOwner({ intent: prepared.intent,
2746
+ workspace: String(prepared.flags.get('workspace')), scope, vault,
2747
+ observe: async (_intent, _scope, retained, intentDigest) => {
2748
+ const disposition = firstLearning ?? retained?.firstLearning;
2749
+ if (!disposition)
2750
+ throw new Error('setup_runtime_first_learning_unconfirmed');
2751
+ // Keep the original execution's disposition; reobserve all live state.
2752
+ return runCodexBootstrapHostScope(scope, current => observeCodexBootstrapRuntime(current, vault, disposition, intentDigest));
2753
+ } });
2754
+ return Object.freeze({
2755
+ execute: async (lease) => {
2756
+ await scope.step(() => assertCodexSetupExecutionLease(lease, prepared.intent));
2757
+ const result = await withCodexSetupSessionSender({ scope, vault, lease }, () => bootstrapFromCodexSetupScope(scope));
2758
+ await scope.step(() => assertCodexSetupExecutionLease(lease, prepared.intent));
2759
+ const workflow = result.workflowReadiness;
2760
+ if (result.ok !== true || result.stage !== 'complete' || !workflow
2761
+ || !['synchronized', 'no_eligible_history', 'denied_disclosure'].includes(String(workflow.firstLearning))) {
2762
+ return { state: 'unconfirmed', code: 'setup_execution_unconfirmed' };
2763
+ }
2764
+ firstLearning = workflow.firstLearning;
2765
+ return readiness.record(lease);
2766
+ },
2767
+ verify: readiness.verify,
2768
+ });
2769
+ }
2770
+ /** Controller entry: owns the official connection and never accepts a peer transport. */
2771
+ export async function openCodexBootstrapNativeHost(input) {
2772
+ if (currentBootstrapHostScope())
2773
+ throw new Error('codex_setup_host_context_conflict');
2774
+ const { workspace, name, signal, current, reserve, maximumProviderCostCents, expectedAccountEmail } = input;
2775
+ if (!/^[a-z][a-z0-9-]{0,47}$/.test(name) || typeof reserve !== 'function'
2776
+ || !Number.isInteger(maximumProviderCostCents) || maximumProviderCostCents < 1 || maximumProviderCostCents > 10_000) {
2777
+ throw new Error('codex_setup_owned_input_invalid');
2778
+ }
2779
+ const prepared = prepareCodexBootstrapHost({ intent: input.intent, workspace, signal, current });
2780
+ let connection;
2781
+ let host;
2782
+ try {
2783
+ await prepared.scope.assert();
2784
+ const source = await preflightBootstrapWorkspaceIdentity(workspace);
2785
+ const contract = await loadAgentFabricOnboardingContract();
2786
+ if (source.fingerprint !== prepared.intent.repositoryFingerprint || `sha256:${contract.sha256}` !== prepared.intent.contractDigest) {
2787
+ throw new Error('codex_setup_owned_source_unconfirmed');
2788
+ }
2789
+ await prepared.scope.assert();
2790
+ connection = await openCodexSetupOwnedConnection({ workspace, deviceHome: dharmaHome(),
2791
+ expectedAccountEmail, signal, current });
2792
+ await prepared.scope.assert();
2793
+ host = await startCodexBootstrapNativeHost({ workspace, name,
2794
+ intent: prepared.intent, signal, maximumProviderCostCents,
2795
+ reserve, current: connection.current, transport: connection.transport,
2796
+ additionalFilesystemRules: connection.additionalFilesystemRules });
2797
+ await prepared.scope.assert();
2798
+ const ownedHost = host, ownedConnection = connection;
2799
+ let closing;
2800
+ const close = () => closing ??= (async () => {
2801
+ try {
2802
+ await ownedHost.close();
2803
+ }
2804
+ finally {
2805
+ await ownedConnection.close();
2806
+ }
2807
+ })();
2808
+ // A terminal native turn must also release its owned transport.
2809
+ const settled = ownedHost.settled.then(close, async () => {
2810
+ try {
2811
+ await close();
2812
+ }
2813
+ finally {
2814
+ throw new Error('codex_setup_owned_completion_unconfirmed');
2815
+ }
2816
+ });
2817
+ void settled.catch(() => { });
2818
+ return Object.freeze({ threadId: host.threadId, turnId: host.turnId, close, settled });
2819
+ }
2820
+ catch {
2821
+ let cleanupFailed = false;
2822
+ try {
2823
+ await host?.close();
2824
+ }
2825
+ catch {
2826
+ cleanupFailed = true;
2827
+ }
2828
+ try {
2829
+ await connection?.close();
2830
+ }
2831
+ catch {
2832
+ cleanupFailed = true;
2833
+ }
2834
+ if (cleanupFailed)
2835
+ throw new Error('codex_setup_owned_close_unconfirmed');
2836
+ throw new Error('codex_setup_owned_start_unqualified');
2837
+ }
2838
+ finally {
2839
+ prepared.scope.close();
2840
+ }
2841
+ }
2842
+ /** Prepared controller ingress for the portal's public command, never a shell evaluator. */
2843
+ export async function openCodexBootstrapFromPortalCommand(input) {
2844
+ if (currentBootstrapHostScope())
2845
+ throw new Error('codex_setup_host_context_conflict');
2846
+ const { workspace, name, signal, current, reserve, maximumProviderCostCents, expectedAccountEmail } = input;
2847
+ if (!Array.isArray(input.portalArgs) || input.portalArgs.length > 64) {
2848
+ throw new Error('codex_setup_portal_command_mismatch');
2849
+ }
2850
+ const args = [...input.portalArgs];
2851
+ if (args.some(value => typeof value !== 'string' || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))) {
2852
+ throw new Error('codex_setup_portal_command_mismatch');
2853
+ }
2854
+ const prepared = prepareCodexBootstrapHost({ intent: input.intent, workspace, signal, current });
2855
+ try {
2856
+ const parsed = parseCliOptions(args);
2857
+ const expected = prepared.flags;
2858
+ const allowed = new Set([...expected.keys(), 'repository-url-base64url']);
2859
+ if (parsed.positional.length !== 1 || parsed.positional[0] !== 'bootstrap'
2860
+ || [...parsed.flags.keys()].some(key => !allowed.has(key))
2861
+ || [...parsed.repeated.values()].some(values => values.length !== 1)) {
2862
+ throw new Error('codex_setup_portal_command_mismatch');
2863
+ }
2864
+ for (const [key, value] of expected) {
2865
+ const supplied = parsed.flags.get(key);
2866
+ if (key === 'workspace') {
2867
+ if (supplied !== undefined && (typeof supplied !== 'string' || resolve(workspace, supplied) !== workspace)) {
2868
+ throw new Error('codex_setup_portal_command_mismatch');
2869
+ }
2870
+ }
2871
+ else if (key === 'provider') {
2872
+ if (supplied !== undefined && supplied !== 'auto' && supplied !== 'codex')
2873
+ throw new Error('codex_setup_portal_command_mismatch');
2874
+ }
2875
+ else if (supplied !== value)
2876
+ throw new Error('codex_setup_portal_command_mismatch');
2877
+ }
2878
+ const selected = parsed.flags.get('repository-url-base64url');
2879
+ if (selected !== undefined) {
2880
+ if (typeof selected !== 'string' || !/^[A-Za-z0-9_-]+$/.test(selected))
2881
+ throw new Error('codex_setup_portal_command_mismatch');
2882
+ const bytes = Buffer.from(selected, 'base64url'), decoded = bytes.toString('utf8');
2883
+ let url, fingerprint;
2884
+ try {
2885
+ url = new URL(decoded);
2886
+ fingerprint = sourceRepositoryFingerprint(decoded).fingerprint;
2887
+ }
2888
+ catch {
2889
+ throw new Error('codex_setup_portal_command_mismatch');
2890
+ }
2891
+ if (bytes.toString('base64url') !== selected || !Buffer.from(decoded, 'utf8').equals(bytes)
2892
+ || /[\u0000-\u0020\u007f]/.test(decoded) || url.protocol !== 'https:' || url.username || url.password || url.search || url.hash
2893
+ || fingerprint !== prepared.intent.repositoryFingerprint) {
2894
+ throw new Error('codex_setup_portal_command_mismatch');
2895
+ }
2896
+ }
2897
+ await prepared.scope.assert();
2898
+ return await openCodexBootstrapNativeHost({ workspace, name, signal, current, reserve, maximumProviderCostCents,
2899
+ expectedAccountEmail, intent: prepared.intent });
2900
+ }
2901
+ finally {
2902
+ prepared.scope.close();
2903
+ }
2904
+ }
2905
+ /** Official native-host composition. The caller supplies its already owned
2906
+ * transport and qualification, never a vault, executor or readiness callback. */
2907
+ export async function startCodexBootstrapNativeHost(input) {
2908
+ const root = resolve(dharmaHome(), 'vault');
2909
+ let completion;
2910
+ let ownerScope;
2911
+ return startCodexSetupNativeHost({ ...input,
2912
+ openJournal: async (scope) => {
2913
+ let vault;
2914
+ try {
2915
+ await runCodexBootstrapHostScope(scope, async () => {
2916
+ // Capture the owned handle before the borrower's post-open check.
2917
+ // The original ALS scope selects the scoped LocalVault overload.
2918
+ vault = await openBootstrapVault({ root });
2919
+ });
2920
+ completion = await createCodexBootstrapCompletionOwner(scope, vault);
2921
+ ownerScope = scope;
2922
+ return Object.freeze({
2923
+ claim: (operationId, intentDigest) => vault.claimCodexSetupOperation(operationId, intentDigest),
2924
+ read: (operationId, intentDigest) => vault.readCodexSetupOperation(operationId, intentDigest),
2925
+ finish: (leaseId, intentDigest, result) => vault.finishCodexSetupOperation(leaseId, intentDigest, result),
2926
+ close: async () => { await vault.close(); },
2927
+ });
2928
+ }
2929
+ catch (error) {
2930
+ try {
2931
+ await vault?.close();
2932
+ }
2933
+ catch {
2934
+ throw new Error('codex_setup_native_journal_close_unconfirmed');
2935
+ }
2936
+ throw error;
2937
+ }
2938
+ },
2939
+ execute: async (_intent, _signal, _current, lease, scope) => {
2940
+ if (!completion || scope !== ownerScope)
2941
+ throw new Error('codex_setup_host_scope_unavailable');
2942
+ return completion.execute(lease);
2943
+ },
2944
+ verifyReadiness: async (receiptId, intent, digest) => {
2945
+ if (!completion)
2946
+ return false;
2947
+ return completion.verify(receiptId, intent, digest);
2948
+ }, });
2949
+ }
2950
+ async function bootstrap(flags, hostScope) {
2951
+ const step = (operation) => hostScope ? hostScope.step(operation) : operation();
2952
+ await hostScope?.assert();
2065
2953
  const hqUrl = normalizeHqUrl(portalUrl(flags));
2066
2954
  const organizationId = required(flags, 'organization-id');
2067
2955
  const resuming = flags.has('resume');
@@ -2088,10 +2976,21 @@ async function bootstrap(flags) {
2088
2976
  if (!/^sha256:[a-f0-9]{64}$/.test(required(flags, key)))
2089
2977
  throw new Error('setup_claim_context_invalid');
2090
2978
  }
2091
- const installedContract = await loadAgentFabricOnboardingContract();
2979
+ const installedContract = await step(() => loadAgentFabricOnboardingContract());
2092
2980
  if (required(flags, 'setup-contract-digest') !== `sha256:${installedContract.sha256}`) {
2093
2981
  throw new Error('setup_claim_contract_mismatch: the portal setup must match this published client operating contract.');
2094
2982
  }
2983
+ // A prepared scope alone is not execution admission. Only the original
2984
+ // native turn's still-live private lease may enter this continuation.
2985
+ if (hostScope && !flags.has('dry-run')) {
2986
+ try {
2987
+ await originalCodexSetupSessionSender(hostScope);
2988
+ }
2989
+ catch {
2990
+ return { ok: false, stage: 'host_setup_unavailable',
2991
+ code: 'codex_setup_host_execution_unqualified', effects: false, grantRedeemed: false };
2992
+ }
2993
+ }
2095
2994
  if (flags.has('dry-run'))
2096
2995
  return { ok: true, stage: 'plan', setupTransport: 'public_claim_v1',
2097
2996
  organizationId, portalUrl: hqUrl, setupReference: required(flags, 'setup-reference'),
@@ -2115,15 +3014,15 @@ async function bootstrap(flags) {
2115
3014
  repositorySelection = joinedBindingId
2116
3015
  ? { workspace: resolve(dharmaHome(), 'joined-repositories', joinedBindingId), selection: 'existing_organization_binding' }
2117
3016
  : typeof selectedRemote === 'string'
2118
- ? await resolveBootstrapRepositoryWorkspace({
3017
+ ? await step(() => resolveBootstrapRepositoryWorkspace({
2119
3018
  workspace: String(flags.get('workspace') || '.'),
2120
3019
  selectedRemoteBase64url: selectedRemote,
2121
3020
  organizationId,
2122
3021
  home: dharmaHome(),
2123
3022
  normalizeRemote: normalizeGitRemoteIdentity,
2124
3023
  withLock: (path) => acquirePidLock(path, 30_000, 'repository_checkout_busy: another setup is selecting this repository; retry after it finishes.'),
2125
- })
2126
- : { workspace: await realpath(String(flags.get('workspace') || '.')), selection: 'provided' };
3024
+ }))
3025
+ : { workspace: await step(() => realpath(String(flags.get('workspace') || '.'))), selection: 'provided' };
2127
3026
  }
2128
3027
  catch (error) {
2129
3028
  const message = error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
@@ -2140,15 +3039,16 @@ async function bootstrap(flags) {
2140
3039
  const policyRevision = required(flags, 'policy-revision');
2141
3040
  const repositoryIdentity = joinedBindingId
2142
3041
  ? { fingerprint: joinedFingerprint }
2143
- : await preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null);
3042
+ : await step(() => preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null));
2144
3043
  const requestedProvider = String(flags.get('provider') || 'auto').trim().toLowerCase();
3044
+ await assertBootstrapHostSource(workspace, repositoryIdentity.fingerprint);
2145
3045
  const provider = requestedProvider === 'auto'
2146
- ? await detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace)
3046
+ ? await step(() => detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace))
2147
3047
  : requestedProvider;
2148
3048
  if (!isLocalProviderId(provider)) {
2149
3049
  throw new Error('Bootstrap provider must be auto, codex, claude, agy, or hermes.');
2150
3050
  }
2151
- let existing = await readDeviceConfig();
3051
+ let existing = await step(() => readDeviceConfig());
2152
3052
  const enrollmentMismatch = Boolean(existing
2153
3053
  && (existing.organizationId !== organizationId || normalizeHqUrl(existing.hqUrl) !== hqUrl));
2154
3054
  if (resuming)
@@ -2171,20 +3071,27 @@ async function bootstrap(flags) {
2171
3071
  config = existing;
2172
3072
  }
2173
3073
  else if (grantMode === 'reference') {
2174
- const result = await claimSetupReference({ hqUrl, organizationId,
3074
+ const claimPlatform = await step(() => platform());
3075
+ const claimInstallationId = existing?.installationId ?? await step(() => loadOrCreateInstallationId());
3076
+ const result = await step(() => claimSetupReference({ hqUrl, organizationId,
2175
3077
  setupReference: required(flags, 'setup-reference'),
2176
3078
  recipientMembershipId: required(flags, 'setup-recipient-membership-id'),
2177
3079
  repositoryFingerprint: repositoryIdentity.fingerprint, policyRevision,
2178
3080
  scopeDigest: required(flags, 'setup-scope-digest'), contractDigest: required(flags, 'setup-contract-digest'),
2179
3081
  name: String(flags.get('device-name') || `${process.env.USER || process.env.USERNAME || 'developer'} device`),
2180
- platform: await platform(), installationId: existing?.installationId ?? await loadOrCreateInstallationId(),
3082
+ platform: claimPlatform, installationId: claimInstallationId,
2181
3083
  existingConfig: existing, configPath: configPath(),
3084
+ hostScope,
3085
+ onFailureDiagnostic: diagnostic => {
3086
+ process.stderr.write(`${JSON.stringify(diagnostic)}\n`);
3087
+ },
2182
3088
  onRecipientApprovalRequired: async (approval) => {
2183
3089
  recipientApproval.required = true;
2184
- recipientApproval.browserOpened = flags.has('no-browser') ? false : await openVerificationUri(approval.url);
3090
+ recipientApproval.browserOpened = flags.has('no-browser') ? false : await step(() => openVerificationUri(approval.url));
3091
+ await hostScope?.assert();
2185
3092
  process.stderr.write(`Approve this exact device in the authenticated portal before ${approval.expiresAt}: ${approval.url}\n`);
2186
3093
  },
2187
- });
3094
+ }));
2188
3095
  config = result.config;
2189
3096
  scopes = result.scopes;
2190
3097
  }
@@ -2254,22 +3161,24 @@ async function bootstrap(flags) {
2254
3161
  config.serverSigningKeyset = redeemed.serverSigningKeyset;
2255
3162
  }
2256
3163
  await saveDeviceConfig(configPath(), config);
2257
- await saveDeviceEnrollmentAnchor({ config });
3164
+ await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
2258
3165
  await saveOrganizationApiToken({
2259
3166
  hqUrl,
2260
3167
  organizationId,
2261
3168
  installationId: config.installationId,
2262
3169
  token: redeemed.organizationApiToken,
3170
+ hostScope: currentBootstrapHostScope(),
2263
3171
  });
2264
3172
  scopes = redeemed.organizationApiTokenScopes;
2265
3173
  }
2266
3174
  let organizationApiTokenStored = false;
2267
3175
  let credentialFailure = null;
2268
3176
  try {
2269
- organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
3177
+ organizationApiTokenStored = Boolean(await step(() => loadOrganizationApiToken({
2270
3178
  hqUrl, organizationId, installationId: config.installationId,
2271
- }));
2272
- if (!organizationApiTokenStored && !String(process.env.DHARMA_ORG_API_TOKEN || '').trim()) {
3179
+ hostScope: currentBootstrapHostScope(),
3180
+ })));
3181
+ if (!organizationApiTokenStored && (currentBootstrapHostScope() || !String(process.env.DHARMA_ORG_API_TOKEN || '').trim())) {
2273
3182
  credentialFailure = 'organization_api_credentials_required';
2274
3183
  }
2275
3184
  }
@@ -2301,9 +3210,9 @@ async function bootstrap(flags) {
2301
3210
  onboardFlags.set('workspace', workspace);
2302
3211
  onboardFlags.set('policy-revision', policyRevision);
2303
3212
  onboardFlags.set('provider', provider);
2304
- const onboarded = await retryBootstrapOnboarding(async () => joinedBindingId
2305
- ? await joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint)
2306
- : await onboard(onboardFlags));
3213
+ const onboarded = await step(() => retryBootstrapOnboarding(async () => joinedBindingId
3214
+ ? await step(() => joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint))
3215
+ : await step(() => onboard(onboardFlags))));
2307
3216
  if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
2308
3217
  return {
2309
3218
  ok: onboarded.ok === true,
@@ -2323,40 +3232,40 @@ async function bootstrap(flags) {
2323
3232
  repository: onboarded,
2324
3233
  };
2325
3234
  }
2326
- const launcher = await installStableRepositoryLauncher(workspace);
3235
+ const launcher = await step(() => installStableRepositoryLauncher(workspace));
2327
3236
  const autostart = flags.has('no-relay-daemon')
2328
3237
  ? { state: 'disabled', backend: null }
2329
- : await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
3238
+ : await step(() => withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
2330
3239
  + (joinedBindingId ? ` --join-repository-binding-id ${joinedBindingId} --join-source-fingerprint ${joinedFingerprint}` : ''), () => withRelayStartupMutation(async () => {
2331
3240
  const home = dharmaHome();
2332
- const existing = await relayAutostartStatus({ home });
3241
+ const existing = await step(() => relayAutostartStatus({ home }));
2333
3242
  if (existing.backend !== null || existing.state !== 'disabled') {
2334
- const anchor = await inspectOwnedRelayAutostart({ home });
3243
+ const anchor = await step(() => inspectOwnedRelayAutostart({ home }));
2335
3244
  if (anchor.policy !== null) {
2336
- const current = await readDeviceConfig();
3245
+ const current = await step(() => readDeviceConfig());
2337
3246
  if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
2338
3247
  throw new Error('relay_workspace_conflict: startup enrollment changed.');
2339
3248
  }
2340
- const selected = selectDeviceWorkspace(await registry(), { organizationId,
3249
+ const selected = selectDeviceWorkspace(await step(() => registry()), { organizationId,
2341
3250
  deviceId: config.deviceId, path: anchor.workspace });
2342
3251
  if (!selected)
2343
3252
  throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
2344
3253
  if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
2345
3254
  throw new Error('relay_workspace_conflict: startup policy is not canonical.');
2346
3255
  }
2347
- const authorized = await loadOrganizationPolicy(anchor.policy);
2348
- const canonical = (await registry()).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
3256
+ const authorized = await step(() => loadOrganizationPolicy(anchor.policy));
3257
+ const canonical = (await step(() => registry())).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
2349
3258
  && row.organizationId === organizationId && row.path === anchor.workspace
2350
3259
  && row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
2351
3260
  if (canonical.length !== 1)
2352
3261
  throw new Error('relay_workspace_conflict: startup policy route changed.');
2353
- await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
3262
+ await step(() => loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? ''));
2354
3263
  }
2355
3264
  }
2356
- return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
3265
+ return step(() => enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
2357
3266
  launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
2358
- version: VERSION, preserveStandardAnchor: true });
2359
- }));
3267
+ version: VERSION, preserveStandardAnchor: true }));
3268
+ })));
2360
3269
  let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
2361
3270
  const completionRequested = flags.has('complete');
2362
3271
  if (!completionRequested) {
@@ -2380,7 +3289,7 @@ async function bootstrap(flags) {
2380
3289
  autostart,
2381
3290
  };
2382
3291
  }
2383
- const skill = await verifyAgentFabricSkillInstallation({ provider, workspace });
3292
+ const skill = await step(() => verifyAgentFabricSkillInstallation({ provider, workspace }));
2384
3293
  if (!skill.ready)
2385
3294
  throw new Error(`Provider skill did not become ready: ${JSON.stringify(skill)}`);
2386
3295
  const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
@@ -2389,13 +3298,13 @@ async function bootstrap(flags) {
2389
3298
  || joinedBindingId !== null && firstLearning?.state === 'shared_package_inherited';
2390
3299
  const relay = flags.has('no-relay-daemon')
2391
3300
  ? { started: false, ...(await waitForRelayReadiness()) }
2392
- : await startRelayDaemon(policyPath);
3301
+ : await step(() => startRelayDaemon(policyPath));
2393
3302
  let repositoryReadiness = null;
2394
3303
  if (!sharedRepositoryReady && relay.state === 'running') {
2395
3304
  const workspaceId = String(onboarded.workspaceId || '');
2396
3305
  const resumeCommand = `dharma onboard --resume --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision} --provider ${provider}`;
2397
- repositoryReadiness = await withOnboardingStage('readiness', workspaceId, resumeCommand, () => waitForRepositoryReadiness(async () => {
2398
- const current = (await registry()).find(item => item.workspaceId === workspaceId
3306
+ repositoryReadiness = await step(() => withOnboardingStage('readiness', workspaceId, resumeCommand, () => waitForRepositoryReadiness(async () => {
3307
+ const current = (await step(() => registry())).find(item => item.workspaceId === workspaceId
2399
3308
  && item.organizationId === organizationId && item.path === workspace);
2400
3309
  if (!current)
2401
3310
  throw new Error('Enrolled repository workspace disappeared during publication.');
@@ -2404,20 +3313,20 @@ async function bootstrap(flags) {
2404
3313
  ready: await repositorySharedReady(current),
2405
3314
  candidateId: current.repositoryPackage?.candidateId || null,
2406
3315
  };
2407
- }));
3316
+ })));
2408
3317
  sharedRepositoryReady = repositoryReadiness.outcome === 'ready';
2409
3318
  }
2410
3319
  const apiFlags = new Map([
2411
3320
  ['organization-id', organizationId], ['portal-url', hqUrl],
2412
3321
  ]);
2413
3322
  const [organization, agents, experiments, failures, remediations, skills, usage] = await Promise.all([
2414
- runOrganizationCommand('organization', 'status', apiFlags),
2415
- runOrganizationCommand('agents', 'list', apiFlags),
2416
- runOrganizationCommand('experiments', 'list', apiFlags),
2417
- runOrganizationCommand('failures', 'list', apiFlags),
2418
- runOrganizationCommand('remediations', 'list', apiFlags),
2419
- runOrganizationCommand('skills', 'list', apiFlags),
2420
- runOrganizationCommand('usage', 'list', apiFlags),
3323
+ step(() => runOrganizationCommand('organization', 'status', apiFlags)),
3324
+ step(() => runOrganizationCommand('agents', 'list', apiFlags)),
3325
+ step(() => runOrganizationCommand('experiments', 'list', apiFlags)),
3326
+ step(() => runOrganizationCommand('failures', 'list', apiFlags)),
3327
+ step(() => runOrganizationCommand('remediations', 'list', apiFlags)),
3328
+ step(() => runOrganizationCommand('skills', 'list', apiFlags)),
3329
+ step(() => runOrganizationCommand('usage', 'list', apiFlags)),
2421
3330
  ]);
2422
3331
  const organizationApi = summarizeBootstrapOrganizationApi({
2423
3332
  organizationId,
@@ -2429,23 +3338,35 @@ async function bootstrap(flags) {
2429
3338
  skills,
2430
3339
  usage,
2431
3340
  });
2432
- const operatingContract = await loadAgentFabricOnboardingContract();
2433
- const namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
2434
- ? await withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
3341
+ const operatingContract = await step(() => loadAgentFabricOnboardingContract());
3342
+ let namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
3343
+ ? await step(() => withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
2435
3344
  ['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
2436
3345
  ['workspace-id', String(onboarded.workspaceId)], ['apply', true],
2437
3346
  ...(typeof flags.get('session-budget-cents') === 'string'
2438
3347
  ? [['session-budget-cents', String(flags.get('session-budget-cents'))]] : []),
2439
- ])))
3348
+ ]))))
2440
3349
  : null;
3350
+ if (namedSession) {
3351
+ const native = await step(() => withOnboardingStage('readiness', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('readiness', new Map([
3352
+ ['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
3353
+ ['workspace-id', String(onboarded.workspaceId)],
3354
+ ]))));
3355
+ if (['name', 'bindingId', 'sessionId', 'organizationId', 'repositoryBindingId', 'workspaceId',
3356
+ 'endpointId', 'membershipId', 'deviceId', 'provider'].some(key => native[key] !== namedSession[key])) {
3357
+ throw new Error('named_session_readiness_scope_mismatch');
3358
+ }
3359
+ namedSession = { ...namedSession, nativeSkill: parseNamedCodexSkillObservation(native.nativeSkill) };
3360
+ }
2441
3361
  const repositoryReceipt = onboarded;
2442
3362
  const namedSessionReady = Boolean(joinedBindingId) || provider !== 'codex' || process.platform !== 'linux'
2443
- || Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
3363
+ || Boolean(namedSession?.ok === true && namedSession.nativeSkill
3364
+ && ['running', 'executing'].includes(String(namedSession.state)));
2444
3365
  const role = repositoryReceipt.repositoryRole;
2445
3366
  const roleReady = Boolean(role);
2446
3367
  const relayReady = relay.state === 'running';
2447
3368
  const observedAutostart = autostart.backend === 'container-entrypoint'
2448
- ? await relayAutostartStatus({ home: dharmaHome() }) : autostart;
3369
+ ? await step(() => relayAutostartStatus({ home: dharmaHome() })) : autostart;
2449
3370
  const startupReady = !['win32', 'linux', 'darwin'].includes(process.platform)
2450
3371
  || observedAutostart.state === 'enabled'
2451
3372
  && (observedAutostart.backend !== 'container-entrypoint' || observedAutostart.lifecycle === 'running');
@@ -2904,7 +3825,7 @@ async function login(flags) {
2904
3825
  config.serverSigningKeyset = candidate;
2905
3826
  }
2906
3827
  await saveDeviceConfig(configPath(), config);
2907
- await saveDeviceEnrollmentAnchor({ config });
3828
+ await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
2908
3829
  await rm(pendingEnrollmentPath(), { force: true });
2909
3830
  return { ok: true, status: 'approved', deviceId: config.deviceId, organizationId: pending.organizationId, relayUrl: config.relayUrl };
2910
3831
  }
@@ -2979,7 +3900,6 @@ async function capture(flags, batch = false) {
2979
3900
  taskReceiptHash: null,
2980
3901
  });
2981
3902
  let policy = await loadVerifiedWorkspacePolicy(policyPath, registered.workspaceId);
2982
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
2983
3903
  const fabric = flags.has('sync')
2984
3904
  ? await client()
2985
3905
  : null;
@@ -2989,9 +3909,8 @@ async function capture(flags, batch = false) {
2989
3909
  }
2990
3910
  if (fabric)
2991
3911
  policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, registered.workspaceId, fabric);
2992
- const vault = await LocalVault.open({
3912
+ const vault = await openBootstrapVault({
2993
3913
  root: resolve(dharmaHome(), 'vault'),
2994
- masterKey: await loadOrCreateVaultMasterKey(),
2995
3914
  rawLocalDays: rawLocalRetentionDays(policy),
2996
3915
  });
2997
3916
  try {
@@ -3016,7 +3935,7 @@ async function capture(flags, batch = false) {
3016
3935
  if (fabric) {
3017
3936
  await vault.discardCapsuleRevisionsAfter(firstRevision.trajectoryId, serverHead?.revision || 0);
3018
3937
  }
3019
- const latestMetadata = vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
3938
+ const latestMetadata = await vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
3020
3939
  const latestCapsule = latestMetadata
3021
3940
  ? await vault.getLatestCapsule(firstRevision.trajectoryId)
3022
3941
  : null;
@@ -3059,7 +3978,7 @@ async function capture(flags, batch = false) {
3059
3978
  }
3060
3979
  capsules.push(capsule);
3061
3980
  if (fabric) {
3062
- vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
3981
+ await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
3063
3982
  try {
3064
3983
  await reserveDailyContentUpload(capsule, policy);
3065
3984
  // An unknown delivery outcome retains the local advisory reservation.
@@ -3068,11 +3987,11 @@ async function capture(flags, batch = false) {
3068
3987
  }
3069
3988
  catch (error) {
3070
3989
  if (isDefinitiveSecretDisclosureRejection(error)) {
3071
- vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
3990
+ await vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
3072
3991
  }
3073
3992
  throw error;
3074
3993
  }
3075
- vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
3994
+ await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
3076
3995
  }
3077
3996
  }
3078
3997
  const output = flags.get('output');
@@ -3106,7 +4025,7 @@ async function capture(flags, batch = false) {
3106
4025
  return manifest;
3107
4026
  }
3108
4027
  finally {
3109
- vault.close();
4028
+ await vault.close();
3110
4029
  }
3111
4030
  }
3112
4031
  async function evidencePreview(flags) {
@@ -3259,7 +4178,7 @@ async function workspaceRecoverRegistry(flags) {
3259
4178
  const config = await readDeviceConfig();
3260
4179
  if (!config)
3261
4180
  throw new Error('registry_recovery_enrollment_required');
3262
- const enrollment = await loadDeviceEnrollmentAnchor({ config });
4181
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
3263
4182
  const workspace = await realpath(String(flags.get('workspace') || '.'));
3264
4183
  const home = dharmaHome();
3265
4184
  const startup = await inspectOwnedRelayAutostart({ home });
@@ -3459,14 +4378,17 @@ function selectedProviderAdapters(providerIds) {
3459
4378
  : providerAdapters;
3460
4379
  }
3461
4380
  export async function receiptAwareProviderCapabilities(providers, now = new Date()) {
4381
+ const hostScope = currentBootstrapHostScope();
4382
+ await hostScope?.assert();
3462
4383
  const selfTestedAt = now.toISOString();
3463
4384
  let freshUntil = now.toISOString();
3464
4385
  let receiverState = 'unavailable';
3465
4386
  let reason = 'device_not_enrolled';
3466
4387
  let trustedKeyVersions = [];
3467
4388
  try {
3468
- const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now });
3469
- await loadDeviceEnrollmentAnchor({ config });
4389
+ const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now,
4390
+ hostScope });
4391
+ await loadDeviceEnrollmentAnchor({ config, hostScope });
3470
4392
  const keyset = config.serverSigningKeyset;
3471
4393
  if (!keyset)
3472
4394
  throw new Error('trusted_server_signing_keyset_unavailable');
@@ -3481,13 +4403,15 @@ export async function receiptAwareProviderCapabilities(providers, now = new Date
3481
4403
  if (trustedKeyVersions.length === 0)
3482
4404
  throw new Error('trusted_server_signing_keys_unavailable');
3483
4405
  freshUntil = actionDecisionCapabilityFreshUntil(keyset, trustedKeyVersions, now);
3484
- await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest();
4406
+ await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest(hostScope);
3485
4407
  receiverState = 'available';
3486
4408
  reason = '';
3487
4409
  }
3488
4410
  catch (error) {
3489
- reason = error instanceof Error ? error.message : String(error);
4411
+ await hostScope?.assert();
4412
+ reason = hostScope ? 'codex_setup_host_receiver_unavailable' : error instanceof Error ? error.message : String(error);
3490
4413
  }
4414
+ await hostScope?.assert();
3491
4415
  return providers.map((provider) => {
3492
4416
  const available = provider.taskExecution === 'available' && receiverState === 'available';
3493
4417
  return {
@@ -3699,18 +4623,95 @@ async function repositoryRoleCommand(action, flags) {
3699
4623
  targetEndpointId: typeof flags.get('target-endpoint-id') === 'string' ? String(flags.get('target-endpoint-id')) : undefined });
3700
4624
  return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
3701
4625
  }
4626
+ /** Original setup submits to the standing owner; it never spawns its worker. */
4627
+ async function requestBootstrapNamedSession(hostScope, flags) {
4628
+ const sender = await originalCodexSetupSessionSender(hostScope);
4629
+ const prepared = await inspectCodexBootstrapHostPreparation(hostScope);
4630
+ const workspace = String(prepared.flags.get('workspace')), intent = sender.intent;
4631
+ const workspaceId = required(flags, 'workspace-id'), name = `codex-${workspaceId.slice(0, 8)}`;
4632
+ if (flags.get('name') !== name || [...flags.keys()].some(key => !['name', 'workspace-id', 'apply'].includes(key))) {
4633
+ throw new Error('setup_session_scope_changed');
4634
+ }
4635
+ const step = (operation) => hostScope.step(operation);
4636
+ const config = await step(() => readDeviceConfig());
4637
+ const rows = (await step(() => registry())).filter(row => row.workspaceId === workspaceId && row.path === workspace
4638
+ && row.organizationId === intent.organizationId && row.repositoryRemoteHash === intent.repositoryFingerprint && row.status === 'active');
4639
+ if (!config || config.organizationId !== intent.organizationId || normalizeHqUrl(config.hqUrl) !== intent.origin
4640
+ || config.setupClaimReference !== intent.setupReference || config.setupClaimRepositoryFingerprint !== intent.repositoryFingerprint
4641
+ || rows.length !== 1)
4642
+ throw new Error('setup_session_scope_changed');
4643
+ const item = rows[0], role = repositoryRoleScope(item);
4644
+ await step(() => assertBootstrapHostSource(workspace, intent.repositoryFingerprint));
4645
+ const enrollment = await step(() => loadDeviceEnrollmentAnchor({ config, hostScope }));
4646
+ const policy = await step(() => loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), workspaceId));
4647
+ verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
4648
+ organizationId: intent.organizationId, workspaceId });
4649
+ await step(() => assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization));
4650
+ if (policy.revision !== intent.policyRevision)
4651
+ throw new Error('setup_session_scope_changed');
4652
+ const senderProcess = await step(() => readContainerProcessIdentity(process.pid));
4653
+ if (senderProcess.uid !== process.getuid())
4654
+ throw new Error('setup_session_scope_changed');
4655
+ const result = await awaitCodexSetupSession({ vault: sender.vault, scope: hostScope, leaseId: sender.lease.leaseId,
4656
+ request: { schema: 'dharma.local-codex-setup-session/v1', operationId: intent.operationId, intentDigest: sender.lease.intentDigest,
4657
+ setupReference: intent.setupReference, senderPid: process.pid, senderStartTicks: senderProcess.startTicks,
4658
+ organizationId: intent.organizationId, membershipId: intent.recipientMembershipId, deviceId: config.deviceId,
4659
+ workspaceId, repositoryBindingId: role.repositoryBindingId, endpointId: role.endpointId, provider: 'codex',
4660
+ origin: intent.origin, repositoryFingerprint: intent.repositoryFingerprint, policyRevision: policy.revision,
4661
+ policyHash: sha256(canonicalize(policy)), scopeDigest: intent.scopeDigest, contractDigest: intent.contractDigest,
4662
+ name, workspaceRoot: workspace, maximumCostCents: 1000, maximumTurnCostCents: 25,
4663
+ issuedAt: new Date().toISOString(), expiresAt: new Date(Math.min(Date.parse(intent.expiresAt), Date.parse(policy.serverAuthorization.expiresAt))).toISOString() } });
4664
+ await step(() => assertCodexSetupExecutionLease(sender.lease, intent));
4665
+ if (result.state !== 'started')
4666
+ throw new Error('setup_session_start_unconfirmed');
4667
+ const status = await step(() => namedSessionCommand('status', new Map([
4668
+ ['name', name], ['workspace-id', workspaceId]
4669
+ ])));
4670
+ if (status.ok !== true || status.bindingId !== result.bindingId || status.sessionId !== result.sessionId
4671
+ || status.membershipId !== intent.recipientMembershipId || status.deviceId !== config.deviceId
4672
+ || status.organizationId !== intent.organizationId || status.workspaceId !== workspaceId
4673
+ || status.repositoryBindingId !== role.repositoryBindingId || status.endpointId !== role.endpointId || status.provider !== 'codex') {
4674
+ throw new Error('setup_session_scope_changed');
4675
+ }
4676
+ return status;
4677
+ }
3702
4678
  async function namedSessionCommand(action, flags) {
4679
+ const hostScope = currentBootstrapHostScope();
4680
+ const setupSessionScope = currentAcceptedSetupSessionScope();
4681
+ const supervisorOwner = currentNamedSessionChildOwner();
4682
+ if (hostScope && (supervisorOwner || setupSessionScope))
4683
+ throw new Error('named_session_child_owner_conflict');
4684
+ if (setupSessionScope && !['start', 'status', 'readiness'].includes(action))
4685
+ throw new Error('setup_session_scope_changed');
4686
+ const step = (operation) => hostScope ? hostScope.step(operation)
4687
+ : setupSessionScope ? setupSessionScope.step(operation) : operation();
4688
+ await hostScope?.assert();
4689
+ if (action === 'start' && hostScope && flags.has('apply')) {
4690
+ return requestBootstrapNamedSession(hostScope, flags);
4691
+ }
3703
4692
  const name = String(flags.get('name') || 'codex');
3704
4693
  const paths = namedSessionPaths(dharmaHome(), name);
3705
4694
  if (process.platform !== 'linux')
3706
4695
  throw new Error('codex_session_sandbox_unqualified');
3707
4696
  const workspaceId = required(flags, 'workspace-id');
3708
- const item = (await registry()).find(row => row.workspaceId === workspaceId);
3709
- const config = await readDeviceConfig();
4697
+ let childMessage;
4698
+ if (flags.has('setup-handoff')) {
4699
+ if (action !== 'serve' || flags.get('setup-handoff') !== true
4700
+ || [...flags.keys()].some(key => !['name', 'workspace-id', 'apply', 'setup-handoff'].includes(key))) {
4701
+ throw new Error('setup_child_unavailable');
4702
+ }
4703
+ childMessage = await receiveCodexSetupChildStart({ process, signal: new AbortController().signal });
4704
+ }
4705
+ const item = (await step(() => registry())).find(row => row.workspaceId === workspaceId);
4706
+ const config = await step(() => readDeviceConfig());
3710
4707
  if (!item || !config || item.organizationId !== config.organizationId)
3711
4708
  throw new Error('named_session_scope_mismatch');
4709
+ await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
3712
4710
  const scope = repositoryRoleScope(item);
3713
- const existing = await readNamedSession(dharmaHome(), name);
4711
+ const existing = await step(() => readNamedSession(dharmaHome(), name));
4712
+ if (setupSessionScope && (name !== setupSessionScope.request.name || workspaceId !== setupSessionScope.request.workspaceId
4713
+ || existing && existing.identity.membershipId !== setupSessionScope.request.membershipId))
4714
+ throw new Error('setup_session_scope_changed');
3714
4715
  if (existing && (existing.identity.workspaceId !== item.workspaceId
3715
4716
  || existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
3716
4717
  || existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
@@ -3725,6 +4726,18 @@ async function namedSessionCommand(action, flags) {
3725
4726
  bindingId: existing?.bindingId ?? null };
3726
4727
  }
3727
4728
  }
4729
+ if (action === 'readiness') {
4730
+ if (!existing?.enabled)
4731
+ throw new Error('named_session_readiness_unavailable');
4732
+ const result = await namedSessionRequest(dharmaHome(), name, { action: 'readiness' });
4733
+ if (Object.keys(result).sort().join(',') !== 'bindingId,deviceId,endpointId,membershipId,name,nativeSkill,ok,organizationId,provider,repositoryBindingId,schema,sessionId,workspaceId'
4734
+ || result.schema !== 'dharma.named-session-readiness/v1' || result.name !== name || result.bindingId !== existing.bindingId
4735
+ || typeof result.sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(result.sessionId)
4736
+ || Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
4737
+ throw new Error('named_session_readiness_scope_mismatch');
4738
+ }
4739
+ return { ...result, nativeSkill: parseNamedCodexSkillObservation(result.nativeSkill) };
4740
+ }
3728
4741
  if (action === 'work')
3729
4742
  return namedSessionRequest(dharmaHome(), name, { action: 'work',
3730
4743
  workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
@@ -3745,101 +4758,155 @@ async function namedSessionCommand(action, flags) {
3745
4758
  if (!flags.has('apply'))
3746
4759
  return { ok: true, planned: true, name, workspaceId,
3747
4760
  provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
3748
- await verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item));
4761
+ await step(async () => verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item)));
3749
4762
  if (action === 'start') {
3750
4763
  if (existing && !existing.enabled)
3751
- await saveNamedSession(dharmaHome(), { ...existing, enabled: true });
4764
+ await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
3752
4765
  try {
3753
- return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
4766
+ return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
3754
4767
  }
3755
4768
  catch { /* Start an owned worker below. */ }
3756
4769
  const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
3757
4770
  '--workspace-id', workspaceId, '--apply'];
4771
+ if (setupSessionScope)
4772
+ args.push('--setup-handoff');
3758
4773
  for (const key of ['session-budget-cents', 'turn-budget-cents']) {
3759
4774
  if (flags.has(key))
3760
4775
  args.push(`--${key}`, required(flags, key));
3761
4776
  }
3762
- const child = spawn(process.execPath, args, { cwd: item.path, detached: true, stdio: 'ignore', env: process.env });
3763
- child.on('error', () => { });
3764
- child.unref();
4777
+ await step(async () => {
4778
+ const start = () => spawn(process.execPath, args, { cwd: item.path, detached: !hostScope && !supervisorOwner,
4779
+ stdio: setupSessionScope ? ['ignore', 'ignore', 'ignore', 'ipc'] : 'ignore', env: process.env });
4780
+ if (supervisorOwner) {
4781
+ const child = await supervisorOwner.spawn(name, start);
4782
+ if (setupSessionScope)
4783
+ await sendCodexSetupChildStart({ owner: supervisorOwner, child, scope: setupSessionScope });
4784
+ return;
4785
+ }
4786
+ const child = start();
4787
+ if (hostScope)
4788
+ captureBootstrapHostChild(hostScope, child);
4789
+ else {
4790
+ child.on('error', () => { });
4791
+ child.unref();
4792
+ }
4793
+ });
3765
4794
  for (let attempt = 0; attempt < 80; attempt++) {
3766
- await new Promise(resolveWait => setTimeout(resolveWait, 250));
4795
+ await step(() => new Promise(resolveWait => setTimeout(resolveWait, 250)));
4796
+ await hostScope?.assert();
3767
4797
  try {
3768
- return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
4798
+ return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
3769
4799
  }
3770
4800
  catch { /* Await service initialization. */ }
3771
4801
  }
3772
4802
  throw new Error('named_session_startup_failed');
3773
4803
  }
3774
4804
  await mkdir(paths.root, { recursive: true, mode: 0o700 });
3775
- const releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
3776
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
3777
- const vault = await LocalVault.open({ root: resolve(dharmaHome(), 'vault'), masterKey: await loadOrCreateVaultMasterKey() });
4805
+ const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
4806
+ let releaseLock;
3778
4807
  const controller = new AbortController(), stop = () => controller.abort();
3779
4808
  process.once('SIGTERM', stop);
3780
4809
  process.once('SIGINT', stop);
4810
+ if (childMessage)
4811
+ process.once('disconnect', stop);
3781
4812
  let transport;
3782
4813
  try {
3783
- const fabric = await AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json') });
3784
- if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
3785
- || fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
3786
- throw new Error('named_session_trust_scope_mismatch');
3787
- const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
3788
- await trust.refresh();
3789
- await fabric.openSession(VERSION);
3790
- const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
3791
- const policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3792
- const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
3793
- .map(path => path.slice(0, -3));
3794
- if (!writeRoots.length)
3795
- throw new Error('named_session_workspace_write_not_authorized');
3796
- const environment = namedCodexEnvironment(process.env);
3797
- const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
3798
- if (environment.XDG_RUNTIME_DIR)
3799
- privateRoots.push(environment.XDG_RUNTIME_DIR);
3800
- if (environment.DBUS_SESSION_BUS_ADDRESS)
3801
- privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
3802
- const filesystem = await namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots });
3803
- transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
3804
- environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
3805
- argv: ['-c', 'default_permissions="dharma_bridge"',
3806
- '-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
3807
- '-c', 'permissions.dharma_bridge.network={enabled=false}',
3808
- '-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
3809
- '-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
3810
- const account = await transport.request('account/read', { refreshToken: false });
3811
- if (!account.account)
3812
- throw new Error('named_session_provider_authentication_required');
3813
- let registration = existing;
3814
- if (!registration) {
3815
- const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
3816
- const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
3817
- const threadId = await startNamedCodexThread(transport, item.path, name);
3818
- const bindingId = randomUUID();
3819
- const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3820
- schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
3821
- workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3822
- bindingId, expectedRevision: 0, leaseSeconds: 60,
4814
+ releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
4815
+ const startProvider = async (childScope) => {
4816
+ const startupStep = (operation) => childScope ? childScope.step(operation) : operation();
4817
+ if (childScope && existing && existing.identity.membershipId !== childScope.request.membershipId) {
4818
+ throw new Error('setup_child_unavailable');
4819
+ }
4820
+ const fabric = await startupStep(() => AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json'),
4821
+ hostScope: currentBootstrapHostScope() }));
4822
+ if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
4823
+ || fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
4824
+ throw new Error('named_session_trust_scope_mismatch');
4825
+ const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
4826
+ await startupStep(() => trust.refresh());
4827
+ await startupStep(() => fabric.openSession(VERSION));
4828
+ const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
4829
+ const policy = await startupStep(() => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric));
4830
+ const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
4831
+ .map(path => path.slice(0, -3));
4832
+ if (!writeRoots.length)
4833
+ throw new Error('named_session_workspace_write_not_authorized');
4834
+ let registration = existing, pending;
4835
+ let ownerReceipt;
4836
+ // Resolve authenticated ownership before opening any provider transport.
4837
+ if (registration) {
4838
+ const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
4839
+ schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
4840
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
4841
+ bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
4842
+ }));
4843
+ if (!isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity)) {
4844
+ throw new Error('named_session_registration_invalid');
4845
+ }
4846
+ ownerReceipt = response;
4847
+ }
4848
+ else {
4849
+ const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
4850
+ const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
4851
+ const bindingId = randomUUID();
4852
+ const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
4853
+ schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
4854
+ workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
4855
+ bindingId, expectedRevision: 0, leaseSeconds: 60,
4856
+ }));
4857
+ const remote = response.registration;
4858
+ if (!remote || !UUID_PATTERN.test(String(remote.membershipId))
4859
+ || childScope && remote.membershipId !== childScope.request.membershipId || remote.revision !== 1) {
4860
+ throw new Error('named_session_registration_invalid');
4861
+ }
4862
+ const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
4863
+ workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
4864
+ provider: 'codex' };
4865
+ if (!isNamedSessionOwnerReceipt(response, bindingId, identity))
4866
+ throw new Error('named_session_registration_invalid');
4867
+ ownerReceipt = response;
4868
+ pending = { schema: 'dharma.named-session/v1', name, bindingId, identity,
4869
+ maximumCostCents, maximumTurnCostCents, enabled: true };
4870
+ }
4871
+ const environment = namedCodexEnvironment(process.env);
4872
+ const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
4873
+ if (environment.XDG_RUNTIME_DIR)
4874
+ privateRoots.push(environment.XDG_RUNTIME_DIR);
4875
+ if (environment.DBUS_SESSION_BUS_ADDRESS)
4876
+ privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
4877
+ const filesystem = await startupStep(() => namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots }));
4878
+ await startupStep(async () => {
4879
+ const expectedOwner = registration ?? pending;
4880
+ if (!isNamedSessionOwnerReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity)) {
4881
+ throw new Error('named_session_registration_invalid');
4882
+ }
4883
+ transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
4884
+ environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
4885
+ argv: ['-c', 'default_permissions="dharma_bridge"',
4886
+ '-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
4887
+ '-c', 'permissions.dharma_bridge.network={enabled=false}',
4888
+ '-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
4889
+ '-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
4890
+ return transport;
3823
4891
  });
3824
- const remote = response.registration;
3825
- if (response.ok !== true || response.organizationId !== config.organizationId || !remote
3826
- || remote.bindingId !== bindingId || remote.workspaceId !== workspaceId || remote.endpointId !== scope.endpointId
3827
- || remote.repositoryBindingId !== scope.repositoryBindingId || remote.deviceId !== config.deviceId
3828
- || !UUID_PATTERN.test(String(remote.membershipId)) || remote.provider !== 'codex'
3829
- || remote.mode !== 'bridge_owned' || remote.state !== 'attached' || remote.revision !== 1) {
3830
- throw new Error('named_session_registration_invalid');
3831
- }
3832
- const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
3833
- workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
3834
- provider: 'codex' };
3835
- vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...identity,
3836
- bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
3837
- createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
3838
- maximumProviderCostCents: maximumTurnCostCents });
3839
- registration = { schema: 'dharma.named-session/v1', name, bindingId, identity,
3840
- maximumCostCents, maximumTurnCostCents, enabled: true };
3841
- await saveNamedSession(dharmaHome(), registration);
3842
- }
4892
+ const account = await startupStep(() => transport.request('account/read', { refreshToken: false }));
4893
+ if (!account.account)
4894
+ throw new Error('named_session_provider_authentication_required');
4895
+ if (pending) {
4896
+ const threadId = await startupStep(() => startNamedCodexThread(transport, item.path, name));
4897
+ await startupStep(async () => vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...pending.identity,
4898
+ bindingId: pending.bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
4899
+ createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
4900
+ maximumProviderCostCents: pending.maximumTurnCostCents }));
4901
+ registration = pending;
4902
+ await startupStep(async () => saveNamedSession(dharmaHome(), registration));
4903
+ }
4904
+ return { registration: registration, filesystem, fabric, trust, policyPath, writeRoots };
4905
+ };
4906
+ const { registration, filesystem, fabric, trust, policyPath, writeRoots } = childMessage
4907
+ ? await runCodexSetupChildStartup({ vault: vault, message: childMessage, name, workspaceId,
4908
+ signal: controller.signal, authorize: qualifyCodexSetupChildRequest }, startProvider)
4909
+ : await startProvider();
3843
4910
  const authorizeLifetime = async () => {
3844
4911
  const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3845
4912
  if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
@@ -3871,6 +4938,17 @@ async function namedSessionCommand(action, flags) {
3871
4938
  };
3872
4939
  return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
3873
4940
  localWriteRoots: writeRoots, additionalFilesystemRules: filesystem.additionalFilesystemRules,
4941
+ observeNativeSkill: async () => {
4942
+ const installation = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
4943
+ const native = await observeNamedCodexSkill(transport, { workspace: item.path, installation,
4944
+ sharedRepositoryReady: await repositorySharedReady(item) });
4945
+ const current = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
4946
+ if (!current.signedLifecycleReady || current.activeBundleId !== native.bundleId
4947
+ || current.activeBundleHash !== native.bundleHash || current.nativeSkillPath !== installation.nativeSkillPath
4948
+ || !await repositorySharedReady(item))
4949
+ throw new Error('named_session_native_skill_unavailable');
4950
+ return native;
4951
+ },
3874
4952
  syncTaskExports: () => syncNamedSessionTaskExports({ vault, bindingId: registration.bindingId,
3875
4953
  identity: registration.identity,
3876
4954
  loadPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
@@ -3888,7 +4966,7 @@ async function namedSessionCommand(action, flags) {
3888
4966
  if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
3889
4967
  try {
3890
4968
  taskStateFailure = 'public_context_unavailable';
3891
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
4969
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3892
4970
  if (!binding)
3893
4971
  throw new Error('named_session_binding_unavailable');
3894
4972
  const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
@@ -3939,7 +5017,7 @@ async function namedSessionCommand(action, flags) {
3939
5017
  || current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
3940
5018
  throw new Error('named_session_repository_state_consent_changed');
3941
5019
  }
3942
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
5020
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3943
5021
  if (!binding)
3944
5022
  throw new Error('named_session_binding_unavailable');
3945
5023
  const receipt = await retainNamedSessionRepositoryState({ vault, binding, capture, workId: taskState.workId,
@@ -3956,7 +5034,7 @@ async function namedSessionCommand(action, flags) {
3956
5034
  },
3957
5035
  queueEvidence: async (capture) => {
3958
5036
  const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
3959
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
5037
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3960
5038
  if (!binding)
3961
5039
  throw new Error('named_session_binding_unavailable');
3962
5040
  // The existing relay rechecks current authority, disclosure limits and device binding before upload.
@@ -3975,10 +5053,16 @@ async function namedSessionCommand(action, flags) {
3975
5053
  await transport.close();
3976
5054
  }
3977
5055
  finally {
3978
- vault.close();
3979
- await releaseLock();
3980
- process.removeListener('SIGTERM', stop);
3981
- process.removeListener('SIGINT', stop);
5056
+ try {
5057
+ await vault.close();
5058
+ }
5059
+ finally {
5060
+ await releaseLock?.();
5061
+ process.removeListener('SIGTERM', stop);
5062
+ process.removeListener('SIGINT', stop);
5063
+ if (childMessage)
5064
+ process.removeListener('disconnect', stop);
5065
+ }
3982
5066
  }
3983
5067
  }
3984
5068
  }
@@ -4172,13 +5256,22 @@ async function joinExistingRepository(flags, bindingId, sourceFingerprint) {
4172
5256
  workspaceSync: synchronized, relay };
4173
5257
  }
4174
5258
  async function readDeviceConfig() {
5259
+ const scope = currentBootstrapHostScope();
4175
5260
  try {
4176
- return JSON.parse(await readFile(configPath(), 'utf8'));
5261
+ const bytes = scope ? await scope.step(() => readFile(configPath(), 'utf8')) : await readFile(configPath(), 'utf8');
5262
+ return JSON.parse(bytes);
4177
5263
  }
4178
5264
  catch {
5265
+ await scope?.assert();
4179
5266
  return null;
4180
5267
  }
4181
5268
  }
5269
+ async function enrolledDeviceIdentity(config, store) {
5270
+ const hostScope = currentBootstrapHostScope();
5271
+ return hostScope ? loadDeviceIdentity({ config, store, hostScope }) : loadOrCreateDeviceIdentity({
5272
+ hqUrl: config.hqUrl, organizationId: config.organizationId, installationId: config.installationId, store
5273
+ });
5274
+ }
4182
5275
  async function activeSkillAuthorization(provider, workspaceId, organizationAgentId, config) {
4183
5276
  const root = await nativeSkillDirectoryForWorkspace({ provider, workspaceId });
4184
5277
  const pointer = resolve(root, '.dharma-managed', 'workspaces', workspaceId, 'ACTIVE_BUNDLE');
@@ -4187,13 +5280,9 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4187
5280
  if (!organizationAgentId)
4188
5281
  throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
4189
5282
  const [identity, enrollment, active] = await Promise.all([
4190
- loadOrCreateDeviceIdentity({
4191
- hqUrl: config.hqUrl,
4192
- organizationId: config.organizationId,
4193
- installationId: config.installationId,
4194
- }),
4195
- loadDeviceEnrollmentAnchor({ config }),
4196
- loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
5283
+ enrolledDeviceIdentity(config),
5284
+ loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
5285
+ loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
4197
5286
  ]);
4198
5287
  if (!active)
4199
5288
  throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
@@ -4201,6 +5290,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4201
5290
  throw new Error('Active skill device identity does not match secure enrollment state.');
4202
5291
  }
4203
5292
  const authorizationInput = (expectedReceiptHash) => ({
5293
+ hostScope: currentBootstrapHostScope(),
4204
5294
  nativeSkillDirectory: root,
4205
5295
  workspaceId,
4206
5296
  provider,
@@ -4226,6 +5316,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4226
5316
  throw error;
4227
5317
  const refreshed = await loadActiveSkillAuthorizationAnchor({
4228
5318
  config, workspaceId, organizationAgentId, provider, fresh: true,
5319
+ hostScope: currentBootstrapHostScope(),
4229
5320
  });
4230
5321
  if (!refreshed)
4231
5322
  throw error;
@@ -4237,13 +5328,9 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4237
5328
  if (!organizationAgentId)
4238
5329
  throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
4239
5330
  const [identity, enrollment, active] = await Promise.all([
4240
- loadOrCreateDeviceIdentity({
4241
- hqUrl: config.hqUrl,
4242
- organizationId: config.organizationId,
4243
- installationId: config.installationId,
4244
- }),
4245
- loadDeviceEnrollmentAnchor({ config }),
4246
- loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
5331
+ enrolledDeviceIdentity(config),
5332
+ loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
5333
+ loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
4247
5334
  ]);
4248
5335
  if (!active)
4249
5336
  throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
@@ -4251,6 +5338,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4251
5338
  throw new Error('Active skill device identity does not match secure enrollment state.');
4252
5339
  }
4253
5340
  const authorizationInput = (expectedReceiptHash) => ({
5341
+ hostScope: currentBootstrapHostScope(),
4254
5342
  nativeSkillDirectory: root,
4255
5343
  workspaceId,
4256
5344
  provider,
@@ -4276,6 +5364,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4276
5364
  throw error;
4277
5365
  const refreshed = await loadActiveSkillAuthorizationAnchor({
4278
5366
  config, workspaceId, organizationAgentId, provider, fresh: true,
5367
+ hostScope: currentBootstrapHostScope(),
4279
5368
  });
4280
5369
  if (!refreshed)
4281
5370
  throw error;
@@ -4299,22 +5388,18 @@ async function loadVerifiedWorkspacePolicy(path, workspaceId) {
4299
5388
  return verified;
4300
5389
  }
4301
5390
  export async function installRepositoryAgentFabricSkill(input) {
4302
- if (input.sourceAuthorization !== undefined)
4303
- input = { ...input,
4304
- sourceAuthorization: validateRepositorySourceAuthorization(input.sourceAuthorization, input, new Date()) };
4305
- await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
4306
- input = { ...input, workspace: await realpath(input.workspace) };
4307
- const ownership = await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
4308
- const skillRoot = resolve(input.workspace, '.agents', 'skills', 'dharma-agent-fabric');
4309
- if (ownership !== 'signed')
4310
- await mkdir(resolve(skillRoot, 'references'), { recursive: true, mode: 0o700 });
4311
- await mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 });
5391
+ const scope = currentBootstrapHostScope();
5392
+ input = captureRepositoryInstallerInput(input);
5393
+ const prepared = await prepareRepositoryInstallerWorkspace(input.workspace, input.workspaceId);
5394
+ input = { ...input, workspace: prepared.workspace };
5395
+ const ownership = prepared.ownership;
5396
+ const contract = scope ? await scope.step(() => loadAgentFabricOnboardingContract()) : await loadAgentFabricOnboardingContract();
4312
5397
  const skill = `---
4313
5398
  name: dharma-agent-fabric
4314
5399
  description: Connect this repository to Dharma Agent Fabric for shared knowledge, signed skills, and team coordination.
4315
5400
  ---
4316
5401
 
4317
- ${(await loadAgentFabricOnboardingContract()).markdown}
5402
+ ${contract.markdown}
4318
5403
  `;
4319
5404
  const reference = `# Organization connection
4320
5405
 
@@ -4373,6 +5458,7 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
4373
5458
  });
4374
5459
  await writeRepositoryInstallerFile(input.workspace, '.dharma/agent-fabric.json', `${JSON.stringify(connection, null, 2)}\n`);
4375
5460
  await writeRepositoryInstallerFile(input.workspace, '.dharma/repository-agent.json', `${JSON.stringify(repositoryAgent, null, 2)}\n`);
5461
+ await scope?.assert();
4376
5462
  return {
4377
5463
  skillPath: '.agents/skills/dharma-agent-fabric/SKILL.md',
4378
5464
  connectionPath: '.dharma/agent-fabric.json',
@@ -4391,7 +5477,7 @@ async function repositoryInstallerRecoveryCommand(flags) {
4391
5477
  const config = await readDeviceConfig();
4392
5478
  if (!config || config.organizationId !== organizationId)
4393
5479
  throw new Error('Recovery requires current organization enrollment.');
4394
- await loadDeviceEnrollmentAnchor({ config });
5480
+ await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
4395
5481
  const identity = await preflightBootstrapWorkspaceIdentity(workspace);
4396
5482
  const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
4397
5483
  path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
@@ -4455,6 +5541,8 @@ async function onboard(flags) {
4455
5541
  const requestedHqUrl = normalizeHqUrl(portalUrl(flags));
4456
5542
  let config = await readDeviceConfig();
4457
5543
  if (!config) {
5544
+ if (currentBootstrapHostScope())
5545
+ throw new Error('codex_setup_host_enrollment_missing');
4458
5546
  const loginFlags = new Map(flags);
4459
5547
  loginFlags.set('hq-url', requestedHqUrl);
4460
5548
  loginFlags.set('organization-id', organizationId);
@@ -4726,10 +5814,8 @@ async function evidenceSync(flags) {
4726
5814
  assertCapsuleIntegrity(capsule);
4727
5815
  const trajectoryId = String(capsule.trajectoryId || '');
4728
5816
  const revision = Number(capsule.revision);
4729
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
4730
- const vault = await LocalVault.open({
5817
+ const vault = await openBootstrapVault({
4731
5818
  root: resolve(dharmaHome(), 'vault'),
4732
- masterKey: await loadOrCreateVaultMasterKey(),
4733
5819
  });
4734
5820
  try {
4735
5821
  const storedCapsule = await vault.getCapsule(trajectoryId, revision);
@@ -4743,7 +5829,7 @@ async function evidenceSync(flags) {
4743
5829
  return fabric.syncTrajectory(storedCapsule);
4744
5830
  }
4745
5831
  finally {
4746
- vault.close();
5832
+ await vault.close();
4747
5833
  }
4748
5834
  }
4749
5835
  async function processEvidenceRequest(fabric, policy, workspaceId) {
@@ -4801,10 +5887,8 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4801
5887
  finally {
4802
5888
  await requestHandle?.close().catch(() => undefined);
4803
5889
  }
4804
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
4805
- const vault = await LocalVault.open({
5890
+ const vault = await openBootstrapVault({
4806
5891
  root: resolve(dharmaHome(), 'vault'),
4807
- masterKey: await loadOrCreateVaultMasterKey(),
4808
5892
  rawLocalDays: rawLocalRetentionDays(policy),
4809
5893
  });
4810
5894
  try {
@@ -4910,7 +5994,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4910
5994
  const receipt = accepted.receipt && typeof accepted.receipt === 'object' ? accepted.receipt : {};
4911
5995
  const receiptHash = typeof receipt.hash === 'string' && /^sha256:[a-f0-9]{64}$/.test(receipt.hash)
4912
5996
  ? receipt.hash : response.responseHash;
4913
- vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
5997
+ await vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
4914
5998
  await writeJsonAtomic(requestReceipt, {
4915
5999
  schema: 'dharma.evidence-request-receipt/v1',
4916
6000
  requestId: request.requestId,
@@ -4926,7 +6010,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4926
6010
  };
4927
6011
  }
4928
6012
  finally {
4929
- vault.close();
6013
+ await vault.close();
4930
6014
  }
4931
6015
  }
4932
6016
  async function runOneEvidenceRequest(flags) {
@@ -5038,14 +6122,12 @@ async function syncSignedTaskTrajectory(input) {
5038
6122
  if (!validation.ok)
5039
6123
  throw new Error(`Signed task capsule failed schema validation: ${JSON.stringify(validation.errors)}`);
5040
6124
  assertCapsuleAuthorizedByCurrentPolicy(capsule, input.policy);
5041
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5042
- const vault = await LocalVault.open({
6125
+ const vault = await openBootstrapVault({
5043
6126
  root: resolve(dharmaHome(), 'vault'),
5044
- masterKey: await loadOrCreateVaultMasterKey(),
5045
6127
  rawLocalDays: rawLocalRetentionDays(input.policy),
5046
6128
  });
5047
6129
  try {
5048
- const existing = vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
6130
+ const existing = await vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
5049
6131
  if (!existing) {
5050
6132
  await vault.commitCapture({
5051
6133
  raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: rawContentId },
@@ -5062,14 +6144,14 @@ async function syncSignedTaskTrajectory(input) {
5062
6144
  else if (existing.capsuleHash !== capsule.capsuleHash) {
5063
6145
  throw new Error('Signed task evidence already exists with different immutable content.');
5064
6146
  }
5065
- vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
6147
+ await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
5066
6148
  await reserveDailyContentUpload(capsule, input.policy);
5067
6149
  const synced = await input.fabric.syncTrajectory(capsule);
5068
- vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
6150
+ await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
5069
6151
  return synced;
5070
6152
  }
5071
6153
  finally {
5072
- vault.close();
6154
+ await vault.close();
5073
6155
  }
5074
6156
  }
5075
6157
  export function assertRecoveredTaskWorkspacePolicy(input) {
@@ -5112,10 +6194,8 @@ export async function postTaskOutcome(input) {
5112
6194
  return input.postEvent(input.task.taskId, input.receipt.status, input.payload);
5113
6195
  }
5114
6196
  async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, prepared) {
5115
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5116
- const vault = await LocalVault.open({
6197
+ const vault = await openBootstrapVault({
5117
6198
  root: resolve(dharmaHome(), 'vault'),
5118
- masterKey: await loadOrCreateVaultMasterKey(),
5119
6199
  rawLocalDays: rawLocalRetentionDays(policy),
5120
6200
  });
5121
6201
  try {
@@ -5131,7 +6211,7 @@ async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, pr
5131
6211
  await vault.stageTaskCompletionRecovery(taskId, Buffer.from(JSON.stringify(recovery)));
5132
6212
  }
5133
6213
  finally {
5134
- vault.close();
6214
+ await vault.close();
5135
6215
  }
5136
6216
  }
5137
6217
  async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, onlyTaskId) {
@@ -5139,10 +6219,8 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5139
6219
  .filter((item) => !onlyTaskId || item.taskId === onlyTaskId);
5140
6220
  const finalized = [];
5141
6221
  for (const completion of completions) {
5142
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5143
- const vault = await LocalVault.open({
6222
+ const vault = await openBootstrapVault({
5144
6223
  root: resolve(dharmaHome(), 'vault'),
5145
- masterKey: await loadOrCreateVaultMasterKey(),
5146
6224
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5147
6225
  });
5148
6226
  let recovery;
@@ -5150,7 +6228,7 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5150
6228
  recovery = await vault.getTaskCompletionRecovery(completion.taskId);
5151
6229
  }
5152
6230
  finally {
5153
- vault.close();
6231
+ await vault.close();
5154
6232
  }
5155
6233
  if (!recovery || recovery.schema !== 'dharma.signed-task-trajectory-recovery/v1'
5156
6234
  || recovery.taskId !== completion.taskId) {
@@ -5181,13 +6259,12 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5181
6259
  };
5182
6260
  assertCapsuleIntegrity(capsule);
5183
6261
  if (recoveredTaskPolicyWasSuperseded(capsule, recoveryPolicy)) {
5184
- const supersededVault = await LocalVault.open({
6262
+ const supersededVault = await openBootstrapVault({
5185
6263
  root: resolve(dharmaHome(), 'vault'),
5186
- masterKey: await loadOrCreateVaultMasterKey(),
5187
6264
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5188
6265
  });
5189
6266
  try {
5190
- const existing = supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
6267
+ const existing = await supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
5191
6268
  if (!existing) {
5192
6269
  await supersededVault.commitCapture({
5193
6270
  raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: recovery.prepared.rawContentId },
@@ -5208,22 +6285,21 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5208
6285
  else if (existing.capsuleHash !== capsule.capsuleHash) {
5209
6286
  throw new Error('Recovered task evidence already exists with different immutable content.');
5210
6287
  }
5211
- supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
6288
+ await supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
5212
6289
  }
5213
6290
  finally {
5214
- supersededVault.close();
6291
+ await supersededVault.close();
5215
6292
  }
5216
6293
  await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
5217
- const acknowledgedVault = await LocalVault.open({
6294
+ const acknowledgedVault = await openBootstrapVault({
5218
6295
  root: resolve(dharmaHome(), 'vault'),
5219
- masterKey: await loadOrCreateVaultMasterKey(),
5220
6296
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5221
6297
  });
5222
6298
  try {
5223
6299
  await acknowledgedVault.clearTaskCompletionRecovery(completion.taskId);
5224
6300
  }
5225
6301
  finally {
5226
- acknowledgedVault.close();
6302
+ await acknowledgedVault.close();
5227
6303
  }
5228
6304
  finalized.push({
5229
6305
  taskId: completion.taskId,
@@ -5247,16 +6323,15 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5247
6323
  },
5248
6324
  });
5249
6325
  await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
5250
- const cleanupVault = await LocalVault.open({
6326
+ const cleanupVault = await openBootstrapVault({
5251
6327
  root: resolve(dharmaHome(), 'vault'),
5252
- masterKey: await loadOrCreateVaultMasterKey(),
5253
6328
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5254
6329
  });
5255
6330
  try {
5256
6331
  await cleanupVault.clearTaskCompletionRecovery(completion.taskId);
5257
6332
  }
5258
6333
  finally {
5259
- cleanupVault.close();
6334
+ await cleanupVault.close();
5260
6335
  }
5261
6336
  finalized.push({ taskId: completion.taskId, trajectory });
5262
6337
  }
@@ -5467,6 +6542,7 @@ export async function nativeSkillDirectoryForWorkspace(input) {
5467
6542
  const anchor = await loadActiveSkillAuthorizationAnchor({
5468
6543
  config, workspaceId: input.workspaceId, organizationAgentId: registered.repositoryAgentId,
5469
6544
  provider: input.provider, fresh: true,
6545
+ hostScope: currentBootstrapHostScope(),
5470
6546
  });
5471
6547
  if (anchor) {
5472
6548
  const matching = await nativeSkillRootMatchingAuthorization({
@@ -6108,6 +7184,7 @@ export function validateSkillBundleSources(bundle) {
6108
7184
  }
6109
7185
  export async function recoverLegacySkillBundleIdAfterAuthorizationFailure(input) {
6110
7186
  const legacyBundleId = await getLegacySkillBundleIdForUpgrade({
7187
+ hostScope: currentBootstrapHostScope(),
6111
7188
  nativeSkillDirectory: input.nativeSkillDirectory,
6112
7189
  workspaceId: input.workspaceId,
6113
7190
  });
@@ -6138,7 +7215,7 @@ export async function loadSkillSynchronizationPolicy(policyPath, workspaceId, se
6138
7215
  verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
6139
7216
  organizationId: config.organizationId, workspaceId });
6140
7217
  await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
6141
- const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore });
7218
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore, hostScope: currentBootstrapHostScope() });
6142
7219
  await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
6143
7220
  verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
6144
7221
  organizationId: config.organizationId, workspaceId });
@@ -6178,7 +7255,8 @@ export async function prepareSkillUpdate(input) {
6178
7255
  let legacyBaselineMigrationRequested = false;
6179
7256
  if (input.automatic) {
6180
7257
  activeBundleId = (await loadActiveSkillAuthorizationAnchor({ config, workspaceId,
6181
- organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true }))?.bundleId ?? null;
7258
+ organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true,
7259
+ hostScope: currentBootstrapHostScope() }))?.bundleId ?? null;
6182
7260
  assertRunning();
6183
7261
  }
6184
7262
  else {
@@ -6298,7 +7376,7 @@ export async function prepareSkillUpdate(input) {
6298
7376
  assertCurrent();
6299
7377
  }
6300
7378
  for (const skill of bundle.skills) {
6301
- const actual = await contentHash(containedInlinePath(sourceRoot, skill.path));
7379
+ const actual = await contentHash(containedInlinePath(sourceRoot, skill.path), currentBootstrapHostScope());
6302
7380
  assertCurrent();
6303
7381
  if (actual !== skill.contentHash)
6304
7382
  throw new Error('Prepared skill tree does not match the signed content hash.');
@@ -6323,12 +7401,9 @@ async function activatePreparedSkillUpdate(input) {
6323
7401
  workspaceId,
6324
7402
  organizationAgentId: String(workspace.repositoryAgentId || ''),
6325
7403
  provider,
7404
+ hostScope: currentBootstrapHostScope(),
6326
7405
  });
6327
- const identity = await loadOrCreateDeviceIdentity({
6328
- hqUrl: config.hqUrl,
6329
- organizationId: config.organizationId,
6330
- installationId: config.installationId,
6331
- });
7406
+ const identity = await enrolledDeviceIdentity(config);
6332
7407
  repositoryDelivery?.assertCurrent();
6333
7408
  const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId);
6334
7409
  if (canonicalize(current.policy) !== canonicalize(policy)
@@ -6338,6 +7413,7 @@ async function activatePreparedSkillUpdate(input) {
6338
7413
  }
6339
7414
  repositoryDelivery?.assertCurrent();
6340
7415
  const receipt = await installSkillBundle({
7416
+ hostScope: currentBootstrapHostScope(),
6341
7417
  bundle,
6342
7418
  sourceDirectory: sourceRoot,
6343
7419
  nativeSkillDirectory: destination,
@@ -6375,16 +7451,18 @@ async function activatePreparedSkillUpdate(input) {
6375
7451
  receiptHash: previousAnchor.receiptHash,
6376
7452
  activatedAt: previousAnchor.activatedAt,
6377
7453
  expiresAt: previousAnchor.expiresAt,
7454
+ hostScope: currentBootstrapHostScope(),
6378
7455
  });
6379
7456
  }
6380
7457
  else {
6381
- await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider });
7458
+ await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider, hostScope: currentBootstrapHostScope() });
6382
7459
  }
6383
7460
  };
6384
7461
  const recoverLocalInstallation = async (error) => {
6385
7462
  const recoveryErrors = [];
6386
7463
  try {
6387
7464
  await rollbackUnconfirmedSkillBundle({
7465
+ hostScope: currentBootstrapHostScope(),
6388
7466
  nativeSkillDirectory: destination,
6389
7467
  workspaceId,
6390
7468
  receipt,
@@ -6415,6 +7493,7 @@ async function activatePreparedSkillUpdate(input) {
6415
7493
  receiptHash: receipt.receiptHash,
6416
7494
  activatedAt: receipt.completedAt,
6417
7495
  expiresAt: bundle.expiresAt ?? null,
7496
+ hostScope: currentBootstrapHostScope(),
6418
7497
  });
6419
7498
  }
6420
7499
  }
@@ -6462,12 +7541,8 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
6462
7541
  if (!config || config.organizationId !== workspace.organizationId || !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
6463
7542
  throw new Error('Installed repository knowledge requires current enrolled workspace scope.');
6464
7543
  }
6465
- const identity = await loadOrCreateDeviceIdentity({
6466
- hqUrl: config.hqUrl,
6467
- organizationId: config.organizationId,
6468
- installationId: config.installationId,
6469
- });
6470
- const enrollment = await loadDeviceEnrollmentAnchor({ config });
7544
+ const identity = await enrolledDeviceIdentity(config);
7545
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
6471
7546
  if (identity.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
6472
7547
  throw new Error('Installed knowledge device identity mismatch.');
6473
7548
  const organizationAgentId = workspace.repositoryAgentId;
@@ -6481,11 +7556,13 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
6481
7556
  });
6482
7557
  if (!await pathExistsOrThrow(resolve(root, '.dharma-managed/workspaces', workspace.workspaceId, 'ACTIVE_BUNDLE')))
6483
7558
  return null;
6484
- const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider };
7559
+ const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider,
7560
+ hostScope: currentBootstrapHostScope() };
6485
7561
  const active = await loadActiveSkillAuthorizationAnchor(anchorInput);
6486
7562
  if (!active)
6487
7563
  throw new Error('Installed knowledge release lacks a protected authorization anchor.');
6488
7564
  const read = (expectedReceiptHash) => readVerifiedRepositoryKnowledge({
7565
+ hostScope: currentBootstrapHostScope(),
6489
7566
  nativeSkillDirectory: root, workspaceId: workspace.workspaceId, provider,
6490
7567
  organizationId: config.organizationId, organizationAgentId, deviceId: config.deviceId,
6491
7568
  serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: enrollment.serverPublicKeyEd25519 }, format: 'jwk' }),
@@ -6523,7 +7600,7 @@ async function takeCachedSkillUpdate(input) {
6523
7600
  const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
6524
7601
  verifySkillBundle(bundle, serverPublicKey);
6525
7602
  for (const skill of bundle.skills) {
6526
- if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path)) !== skill.contentHash) {
7603
+ if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path), currentBootstrapHostScope()) !== skill.contentHash) {
6527
7604
  throw new Error('Cached skill tree does not match the signed content hash.');
6528
7605
  }
6529
7606
  }
@@ -6690,14 +7767,12 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
6690
7767
  let repositorySourceState = 'not_scanned';
6691
7768
  let sourceScanFlight = null;
6692
7769
  let sourceScanReady = null;
6693
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
6694
- const vault = await LocalVault.open({
7770
+ const vault = await openBootstrapVault({
6695
7771
  root: resolve(dharmaHome(), 'vault'),
6696
- masterKey: await loadOrCreateVaultMasterKey(),
6697
7772
  rawLocalDays: rawLocalRetentionDays(policy),
6698
7773
  });
6699
7774
  if (signal.aborted)
6700
- vault.close();
7775
+ await vault.close();
6701
7776
  signal.throwIfAborted();
6702
7777
  signal.addEventListener('abort', stop, { once: true });
6703
7778
  if (signal.aborted)
@@ -7023,7 +8098,7 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
7023
8098
  await sourceScanFlight;
7024
8099
  await consumeSourceScan();
7025
8100
  await skillPreparationPump.stop();
7026
- vault.close();
8101
+ await vault.close();
7027
8102
  signal.removeEventListener('abort', stop);
7028
8103
  }
7029
8104
  return {
@@ -7039,6 +8114,20 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
7039
8114
  export async function run(argv) {
7040
8115
  const { positional, flags, repeated } = parseCliOptions(argv);
7041
8116
  const [command, subcommand] = positional;
8117
+ const setupScope = currentBootstrapHostScope();
8118
+ if (setupScope) {
8119
+ // An admitted native callback keeps its original authority through the
8120
+ // official dispatcher. Closed descendants never use the legacy route.
8121
+ const prepared = await inspectCodexBootstrapHostPreparation(setupScope);
8122
+ if (command !== 'bootstrap' || positional.length !== 1 || flags.size !== prepared.flags.size
8123
+ || [...repeated.values()].some(values => values.length !== 1)
8124
+ || [...prepared.flags].some(([key, value]) => flags.get(key) !== value)) {
8125
+ throw new Error('codex_setup_host_command_mismatch');
8126
+ }
8127
+ if (!prepared.flags.has('dry-run'))
8128
+ await originalCodexSetupSessionSender(setupScope);
8129
+ return setupScope.step(() => bootstrap(flags, setupScope));
8130
+ }
7042
8131
  if (command === 'relay' && subcommand === 'container-entrypoint') {
7043
8132
  if (positional.length === 2 && flags.size === 1 && flags.get('dry-run') === true) {
7044
8133
  return { ok: true, stage: 'container_entrypoint_plan', started: false,