@dharma-ai-labs/agent-fabric 0.2.152 → 0.2.154

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 (309) 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 +208 -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 +456 -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/containerRelayLifecycle.test.js +13 -0
  159. package/dist/containerRelayLifecycle.test.js.map +1 -1
  160. package/dist/cooperativeInboxSession.d.ts +2 -2
  161. package/dist/cooperativeInboxSession.d.ts.map +1 -1
  162. package/dist/cooperativeInboxSession.js +21 -13
  163. package/dist/cooperativeInboxSession.js.map +1 -1
  164. package/dist/cooperativeInboxSession.test.js +65 -0
  165. package/dist/cooperativeInboxSession.test.js.map +1 -1
  166. package/dist/demoWatchControl.d.ts +3 -3
  167. package/dist/index.d.ts +55 -14
  168. package/dist/index.d.ts.map +1 -1
  169. package/dist/index.js +1650 -557
  170. package/dist/index.js.map +1 -1
  171. package/dist/namedCodexSkillDiscovery.d.ts +15 -0
  172. package/dist/namedCodexSkillDiscovery.d.ts.map +1 -0
  173. package/dist/namedCodexSkillDiscovery.js +90 -0
  174. package/dist/namedCodexSkillDiscovery.js.map +1 -0
  175. package/dist/namedCodexSkillDiscovery.test.d.ts +2 -0
  176. package/dist/namedCodexSkillDiscovery.test.d.ts.map +1 -0
  177. package/dist/namedCodexSkillDiscovery.test.js +37 -0
  178. package/dist/namedCodexSkillDiscovery.test.js.map +1 -0
  179. package/dist/namedCodexThread.d.ts +2 -0
  180. package/dist/namedCodexThread.d.ts.map +1 -1
  181. package/dist/namedCodexThread.js +13 -4
  182. package/dist/namedCodexThread.js.map +1 -1
  183. package/dist/namedSessionChildOwner.d.ts +17 -0
  184. package/dist/namedSessionChildOwner.d.ts.map +1 -0
  185. package/dist/namedSessionChildOwner.js +122 -0
  186. package/dist/namedSessionChildOwner.js.map +1 -0
  187. package/dist/namedSessionChildOwner.test.d.ts +2 -0
  188. package/dist/namedSessionChildOwner.test.d.ts.map +1 -0
  189. package/dist/namedSessionChildOwner.test.js +203 -0
  190. package/dist/namedSessionChildOwner.test.js.map +1 -0
  191. package/dist/namedSessionEvidence.d.ts +2 -2
  192. package/dist/namedSessionEvidence.d.ts.map +1 -1
  193. package/dist/namedSessionEvidence.js +2 -2
  194. package/dist/namedSessionEvidence.js.map +1 -1
  195. package/dist/namedSessionEvidence.test.js +24 -1
  196. package/dist/namedSessionEvidence.test.js.map +1 -1
  197. package/dist/namedSessionRepositoryState.d.ts +2 -2
  198. package/dist/namedSessionRepositoryState.d.ts.map +1 -1
  199. package/dist/namedSessionService.d.ts +4 -2
  200. package/dist/namedSessionService.d.ts.map +1 -1
  201. package/dist/namedSessionService.js +18 -1
  202. package/dist/namedSessionService.js.map +1 -1
  203. package/dist/namedSessionService.test.js +31 -2
  204. package/dist/namedSessionService.test.js.map +1 -1
  205. package/dist/namedSessionTaskExportSync.d.ts +3 -3
  206. package/dist/namedSessionTaskExportSync.d.ts.map +1 -1
  207. package/dist/namedSessionTaskExportSync.js +6 -6
  208. package/dist/namedSessionTaskExportSync.js.map +1 -1
  209. package/dist/namedSessionTrust.d.ts +2 -2
  210. package/dist/namedSessionTrust.d.ts.map +1 -1
  211. package/dist/namedSessionTrust.js +3 -2
  212. package/dist/namedSessionTrust.js.map +1 -1
  213. package/dist/namedSessionTrust.test.js +2 -1
  214. package/dist/namedSessionTrust.test.js.map +1 -1
  215. package/dist/ownedChildLifecycle.d.ts +13 -0
  216. package/dist/ownedChildLifecycle.d.ts.map +1 -0
  217. package/dist/ownedChildLifecycle.js +87 -0
  218. package/dist/ownedChildLifecycle.js.map +1 -0
  219. package/dist/ownedChildLifecycle.test.d.ts +2 -0
  220. package/dist/ownedChildLifecycle.test.d.ts.map +1 -0
  221. package/dist/ownedChildLifecycle.test.js +133 -0
  222. package/dist/ownedChildLifecycle.test.js.map +1 -0
  223. package/dist/pidLock.test.js +242 -9
  224. package/dist/pidLock.test.js.map +1 -1
  225. package/dist/providerSessionChannel.d.ts +1 -1
  226. package/dist/providerSessionChannel.d.ts.map +1 -1
  227. package/dist/providerSessionChannel.js +7 -3
  228. package/dist/providerSessionChannel.js.map +1 -1
  229. package/dist/providerSessionChannel.test.js +20 -0
  230. package/dist/providerSessionChannel.test.js.map +1 -1
  231. package/dist/providerSessionReplyRecovery.d.ts +2 -2
  232. package/dist/providerSessionReplyRecovery.d.ts.map +1 -1
  233. package/dist/providerSessionReplyRecovery.js +3 -3
  234. package/dist/providerSessionReplyRecovery.js.map +1 -1
  235. package/dist/relayAutostart.d.ts.map +1 -1
  236. package/dist/relayAutostart.js +12 -9
  237. package/dist/relayAutostart.js.map +1 -1
  238. package/dist/relayRuntimeUpgrade.d.ts +1 -1
  239. package/dist/relayRuntimeUpgrade.d.ts.map +1 -1
  240. package/dist/relayRuntimeUpgrade.js +3 -1
  241. package/dist/relayRuntimeUpgrade.js.map +1 -1
  242. package/dist/relayRuntimeUpgrade.test.js +113 -0
  243. package/dist/relayRuntimeUpgrade.test.js.map +1 -1
  244. package/dist/relayRuntimeUpgradeCaller.test.js +12 -1
  245. package/dist/relayRuntimeUpgradeCaller.test.js.map +1 -1
  246. package/dist/relaySupervisor.d.ts.map +1 -1
  247. package/dist/relaySupervisor.js +19 -14
  248. package/dist/relaySupervisor.js.map +1 -1
  249. package/dist/relaySupervisor.test.js +26 -0
  250. package/dist/relaySupervisor.test.js.map +1 -1
  251. package/dist/relaySupervisorCleanup.test.d.ts +2 -0
  252. package/dist/relaySupervisorCleanup.test.d.ts.map +1 -0
  253. package/dist/relaySupervisorCleanup.test.js +69 -0
  254. package/dist/relaySupervisorCleanup.test.js.map +1 -0
  255. package/dist/repositoryInstallerFiles.d.ts +19 -1
  256. package/dist/repositoryInstallerFiles.d.ts.map +1 -1
  257. package/dist/repositoryInstallerFiles.js +205 -29
  258. package/dist/repositoryInstallerFiles.js.map +1 -1
  259. package/dist/repositoryInstallerScope.test.d.ts +2 -0
  260. package/dist/repositoryInstallerScope.test.d.ts.map +1 -0
  261. package/dist/repositoryInstallerScope.test.js +513 -0
  262. package/dist/repositoryInstallerScope.test.js.map +1 -0
  263. package/dist/repositoryKnowledge.d.ts.map +1 -1
  264. package/dist/repositoryKnowledge.js +177 -37
  265. package/dist/repositoryKnowledge.js.map +1 -1
  266. package/dist/repositoryKnowledgeScope.test.d.ts +2 -0
  267. package/dist/repositoryKnowledgeScope.test.d.ts.map +1 -0
  268. package/dist/repositoryKnowledgeScope.test.js +316 -0
  269. package/dist/repositoryKnowledgeScope.test.js.map +1 -0
  270. package/dist/repositoryOnboardingCaller.test.js +61 -4
  271. package/dist/repositoryOnboardingCaller.test.js.map +1 -1
  272. package/dist/repositoryPackage.d.ts.map +1 -1
  273. package/dist/repositoryPackage.js +340 -59
  274. package/dist/repositoryPackage.js.map +1 -1
  275. package/dist/repositoryPackageDelivery.test.js +65 -0
  276. package/dist/repositoryPackageDelivery.test.js.map +1 -1
  277. package/dist/repositoryPackageScope.test.d.ts +2 -0
  278. package/dist/repositoryPackageScope.test.d.ts.map +1 -0
  279. package/dist/repositoryPackageScope.test.js +578 -0
  280. package/dist/repositoryPackageScope.test.js.map +1 -0
  281. package/dist/schemas/codex-setup-child-start.schema.json +12 -0
  282. package/dist/schemas/codex-setup-intent.schema.json +27 -0
  283. package/dist/schemas/local-codex-setup-session.schema.json +39 -0
  284. package/dist/schemas/setup-claim-failure.schema.json +15 -0
  285. package/dist/skillInstallationScope.test.d.ts +2 -0
  286. package/dist/skillInstallationScope.test.d.ts.map +1 -0
  287. package/dist/skillInstallationScope.test.js +64 -0
  288. package/dist/skillInstallationScope.test.js.map +1 -0
  289. package/dist/skillPreparationCache.test.js +12 -4
  290. package/dist/skillPreparationCache.test.js.map +1 -1
  291. package/dist/skillPreparationCaller.test.js +31 -1
  292. package/dist/skillPreparationCaller.test.js.map +1 -1
  293. package/dist/startupCommitScope.test.d.ts +2 -0
  294. package/dist/startupCommitScope.test.d.ts.map +1 -0
  295. package/dist/startupCommitScope.test.js +172 -0
  296. package/dist/startupCommitScope.test.js.map +1 -0
  297. package/dist/workspaceRegistry.d.ts.map +1 -1
  298. package/dist/workspaceRegistry.js +7 -3
  299. package/dist/workspaceRegistry.js.map +1 -1
  300. package/dist/workspaceRegistry.test.js +51 -0
  301. package/dist/workspaceRegistry.test.js.map +1 -1
  302. package/dist/workspaceRegistryRecovery.d.ts.map +1 -1
  303. package/dist/workspaceRegistryRecovery.js +171 -64
  304. package/dist/workspaceRegistryRecovery.js.map +1 -1
  305. package/dist/workspaceRegistryRecoveryScope.test.d.ts +2 -0
  306. package/dist/workspaceRegistryRecoveryScope.test.d.ts.map +1 -0
  307. package/dist/workspaceRegistryRecoveryScope.test.js +416 -0
  308. package/dist/workspaceRegistryRecoveryScope.test.js.map +1 -0
  309. 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.152';
91
+ const VERSION = '0.2.154';
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';
678
821
  }
679
- throw renameError;
680
822
  }
681
- throw renameError;
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;
833
+ }
834
+ }
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;
1226
+ }
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
+ }
1044
1243
  }
1045
- previous = parsed;
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) {
@@ -1600,10 +1881,7 @@ async function relayUpgrade(flags) {
1600
1881
  || startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
1601
1882
  throw new Error('relay_upgrade_workspace_conflict');
1602
1883
  }
1603
- const assertStopped = async () => {
1604
- if (await relaySupervisorProcessState(home) !== 'stopped' || await relayProcessState(home) !== 'stopped') {
1605
- throw new Error('relay_upgrade_runtime_busy: finish current work, then stop the owned relay before upgrading.');
1606
- }
1884
+ const assertSessionsStopped = async () => {
1607
1885
  const entries = await readdir(resolve(home, 'sessions'), { withFileTypes: true }).catch(error => {
1608
1886
  if (error.code === 'ENOENT')
1609
1887
  return [];
@@ -1617,6 +1895,12 @@ async function relayUpgrade(flags) {
1617
1895
  }
1618
1896
  }
1619
1897
  };
1898
+ const assertStopped = async () => {
1899
+ if (await relaySupervisorProcessState(home) !== 'stopped' || await relayProcessState(home) !== 'stopped') {
1900
+ throw new Error('relay_upgrade_runtime_busy: finish current work, then stop the owned relay before upgrading.');
1901
+ }
1902
+ await assertSessionsStopped();
1903
+ };
1620
1904
  return withRelayStartupMutation(async () => {
1621
1905
  const receipt = await upgradeRelayRuntime({ home, workspace, version: VERSION,
1622
1906
  organizationId: config.organizationId, deviceId: config.deviceId, workspaceId: selected.workspaceId,
@@ -1637,6 +1921,7 @@ async function relayUpgrade(flags) {
1637
1921
  launcher: startup.launcher, policy: startup.policy, version }),
1638
1922
  start: () => startRelayAutostart(startupOptions),
1639
1923
  stop: async () => {
1924
+ await assertSessionsStopped();
1640
1925
  const activationLocks = resolve(home, 'registry', 'skill-activation-locks');
1641
1926
  const locks = await readdir(activationLocks).catch(error => {
1642
1927
  if (error.code === 'ENOENT')
@@ -1714,6 +1999,9 @@ export async function waitForRelayReadiness(options = {}) {
1714
1999
  throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
1715
2000
  }
1716
2001
  async function startRelayDaemon(policyPath) {
2002
+ const hostScope = currentBootstrapHostScope();
2003
+ const step = (operation) => hostScope ? hostScope.step(operation) : operation();
2004
+ await hostScope?.assert();
1717
2005
  const config = await readDeviceConfig();
1718
2006
  if (!config)
1719
2007
  throw new Error('Relay startup requires current device enrollment.');
@@ -1723,23 +2011,32 @@ async function startRelayDaemon(policyPath) {
1723
2011
  && row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
1724
2012
  if (!selected)
1725
2013
  throw new Error('Relay policy must belong to this enrolled repository.');
2014
+ await assertBootstrapHostSource(selected.path, String(selected.repositoryRemoteHash || ''));
1726
2015
  const alreadyRunning = await relayProcessState() === 'running';
1727
2016
  const supervisorState = await relaySupervisorProcessState();
1728
2017
  if (supervisorState === 'unknown')
1729
2018
  throw new Error('Relay supervisor process state is unknown.');
1730
2019
  if (supervisorState === 'stopped') {
1731
- if ((await relayAutostartStatus({ home: dharmaHome() })).backend === 'container-entrypoint') {
1732
- 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() }));
1733
2024
  }
1734
2025
  else {
1735
- const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', policyPath], {
1736
- 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();
1737
2034
  });
1738
- child.unref();
1739
2035
  }
1740
2036
  }
1741
2037
  let supervisorReady = false;
1742
2038
  for (let attempt = 0; attempt < 80; attempt += 1) {
2039
+ await hostScope?.assert();
1743
2040
  if (await relaySupervisorProcessState() === 'running') {
1744
2041
  const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'supervisor.pid'), 'utf8')
1745
2042
  .catch(() => '0')).trim());
@@ -1762,6 +2059,7 @@ async function startRelayDaemon(policyPath) {
1762
2059
  if (!supervisorReady)
1763
2060
  throw new Error('The relay supervisor did not become ready after bootstrap.');
1764
2061
  const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
2062
+ await hostScope?.assert();
1765
2063
  if (await relayProcessState() !== 'running')
1766
2064
  return 'stopped';
1767
2065
  const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
@@ -1769,6 +2067,7 @@ async function startRelayDaemon(policyPath) {
1769
2067
  return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
1770
2068
  version: VERSION, pid }) ? 'running' : 'stopped';
1771
2069
  } });
2070
+ await hostScope?.assert();
1772
2071
  return { started: !alreadyRunning, supervisor: 'running', ...readiness };
1773
2072
  }
1774
2073
  async function supervisedDemoCycle(registration, signal) {
@@ -1869,10 +2168,15 @@ async function relaySupervise(flags) {
1869
2168
  }
1870
2169
  finally {
1871
2170
  controller.abort();
1872
- await Promise.allSettled(services);
2171
+ const settled = await Promise.allSettled(services);
1873
2172
  await health.drain();
1874
2173
  process.removeListener('SIGINT', stop);
1875
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
+ }
1876
2180
  const binding = await readFile(bindingPath, 'utf8')
1877
2181
  .then(value => JSON.parse(value)).catch(() => null);
1878
2182
  if (binding?.pid === process.pid)
@@ -1883,37 +2187,160 @@ async function relaySupervise(flags) {
1883
2187
  async function superviseNamedSessions(signal) {
1884
2188
  if (process.platform !== 'linux')
1885
2189
  return;
1886
- while (!signal.aborted) {
1887
- const root = resolve(dharmaHome(), 'sessions');
1888
- const entries = await readdir(root, { withFileTypes: true }).catch(error => {
1889
- if (error.code === 'ENOENT')
1890
- return [];
1891
- throw error;
1892
- });
1893
- for (const entry of entries.slice(0, 50)) {
1894
- if (signal.aborted)
1895
- break;
1896
- if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
1897
- continue;
1898
- try {
1899
- const registration = await readNamedSession(dharmaHome(), entry.name);
1900
- if (!registration?.enabled)
1901
- continue;
1902
- await namedSessionCommand('start', new Map([
1903
- ['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
1904
- ]));
1905
- }
1906
- catch {
1907
- 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
+ });
1908
2260
  }
1909
2261
  }
1910
- await new Promise(resolveWait => {
1911
- const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
1912
- const timer = setTimeout(finish, 10_000);
1913
- signal.addEventListener('abort', finish, { once: true });
1914
- if (signal.aborted)
1915
- finish();
1916
- });
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;
1917
2344
  }
1918
2345
  }
1919
2346
  async function relayStop() {
@@ -2057,7 +2484,472 @@ export function assertBootstrapResumeAuthority(input) {
2057
2484
  throw new Error('Bootstrap resume requires an existing device enrolled to this organization and portal.');
2058
2485
  }
2059
2486
  }
2060
- 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();
2061
2953
  const hqUrl = normalizeHqUrl(portalUrl(flags));
2062
2954
  const organizationId = required(flags, 'organization-id');
2063
2955
  const resuming = flags.has('resume');
@@ -2084,10 +2976,21 @@ async function bootstrap(flags) {
2084
2976
  if (!/^sha256:[a-f0-9]{64}$/.test(required(flags, key)))
2085
2977
  throw new Error('setup_claim_context_invalid');
2086
2978
  }
2087
- const installedContract = await loadAgentFabricOnboardingContract();
2979
+ const installedContract = await step(() => loadAgentFabricOnboardingContract());
2088
2980
  if (required(flags, 'setup-contract-digest') !== `sha256:${installedContract.sha256}`) {
2089
2981
  throw new Error('setup_claim_contract_mismatch: the portal setup must match this published client operating contract.');
2090
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
+ }
2091
2994
  if (flags.has('dry-run'))
2092
2995
  return { ok: true, stage: 'plan', setupTransport: 'public_claim_v1',
2093
2996
  organizationId, portalUrl: hqUrl, setupReference: required(flags, 'setup-reference'),
@@ -2111,15 +3014,15 @@ async function bootstrap(flags) {
2111
3014
  repositorySelection = joinedBindingId
2112
3015
  ? { workspace: resolve(dharmaHome(), 'joined-repositories', joinedBindingId), selection: 'existing_organization_binding' }
2113
3016
  : typeof selectedRemote === 'string'
2114
- ? await resolveBootstrapRepositoryWorkspace({
3017
+ ? await step(() => resolveBootstrapRepositoryWorkspace({
2115
3018
  workspace: String(flags.get('workspace') || '.'),
2116
3019
  selectedRemoteBase64url: selectedRemote,
2117
3020
  organizationId,
2118
3021
  home: dharmaHome(),
2119
3022
  normalizeRemote: normalizeGitRemoteIdentity,
2120
3023
  withLock: (path) => acquirePidLock(path, 30_000, 'repository_checkout_busy: another setup is selecting this repository; retry after it finishes.'),
2121
- })
2122
- : { workspace: await realpath(String(flags.get('workspace') || '.')), selection: 'provided' };
3024
+ }))
3025
+ : { workspace: await step(() => realpath(String(flags.get('workspace') || '.'))), selection: 'provided' };
2123
3026
  }
2124
3027
  catch (error) {
2125
3028
  const message = error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
@@ -2136,15 +3039,16 @@ async function bootstrap(flags) {
2136
3039
  const policyRevision = required(flags, 'policy-revision');
2137
3040
  const repositoryIdentity = joinedBindingId
2138
3041
  ? { fingerprint: joinedFingerprint }
2139
- : 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));
2140
3043
  const requestedProvider = String(flags.get('provider') || 'auto').trim().toLowerCase();
3044
+ await assertBootstrapHostSource(workspace, repositoryIdentity.fingerprint);
2141
3045
  const provider = requestedProvider === 'auto'
2142
- ? await detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace)
3046
+ ? await step(() => detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace))
2143
3047
  : requestedProvider;
2144
3048
  if (!isLocalProviderId(provider)) {
2145
3049
  throw new Error('Bootstrap provider must be auto, codex, claude, agy, or hermes.');
2146
3050
  }
2147
- let existing = await readDeviceConfig();
3051
+ let existing = await step(() => readDeviceConfig());
2148
3052
  const enrollmentMismatch = Boolean(existing
2149
3053
  && (existing.organizationId !== organizationId || normalizeHqUrl(existing.hqUrl) !== hqUrl));
2150
3054
  if (resuming)
@@ -2167,20 +3071,27 @@ async function bootstrap(flags) {
2167
3071
  config = existing;
2168
3072
  }
2169
3073
  else if (grantMode === 'reference') {
2170
- 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,
2171
3077
  setupReference: required(flags, 'setup-reference'),
2172
3078
  recipientMembershipId: required(flags, 'setup-recipient-membership-id'),
2173
3079
  repositoryFingerprint: repositoryIdentity.fingerprint, policyRevision,
2174
3080
  scopeDigest: required(flags, 'setup-scope-digest'), contractDigest: required(flags, 'setup-contract-digest'),
2175
3081
  name: String(flags.get('device-name') || `${process.env.USER || process.env.USERNAME || 'developer'} device`),
2176
- platform: await platform(), installationId: existing?.installationId ?? await loadOrCreateInstallationId(),
3082
+ platform: claimPlatform, installationId: claimInstallationId,
2177
3083
  existingConfig: existing, configPath: configPath(),
3084
+ hostScope,
3085
+ onFailureDiagnostic: diagnostic => {
3086
+ process.stderr.write(`${JSON.stringify(diagnostic)}\n`);
3087
+ },
2178
3088
  onRecipientApprovalRequired: async (approval) => {
2179
3089
  recipientApproval.required = true;
2180
- 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();
2181
3092
  process.stderr.write(`Approve this exact device in the authenticated portal before ${approval.expiresAt}: ${approval.url}\n`);
2182
3093
  },
2183
- });
3094
+ }));
2184
3095
  config = result.config;
2185
3096
  scopes = result.scopes;
2186
3097
  }
@@ -2250,22 +3161,24 @@ async function bootstrap(flags) {
2250
3161
  config.serverSigningKeyset = redeemed.serverSigningKeyset;
2251
3162
  }
2252
3163
  await saveDeviceConfig(configPath(), config);
2253
- await saveDeviceEnrollmentAnchor({ config });
3164
+ await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
2254
3165
  await saveOrganizationApiToken({
2255
3166
  hqUrl,
2256
3167
  organizationId,
2257
3168
  installationId: config.installationId,
2258
3169
  token: redeemed.organizationApiToken,
3170
+ hostScope: currentBootstrapHostScope(),
2259
3171
  });
2260
3172
  scopes = redeemed.organizationApiTokenScopes;
2261
3173
  }
2262
3174
  let organizationApiTokenStored = false;
2263
3175
  let credentialFailure = null;
2264
3176
  try {
2265
- organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
3177
+ organizationApiTokenStored = Boolean(await step(() => loadOrganizationApiToken({
2266
3178
  hqUrl, organizationId, installationId: config.installationId,
2267
- }));
2268
- 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())) {
2269
3182
  credentialFailure = 'organization_api_credentials_required';
2270
3183
  }
2271
3184
  }
@@ -2297,9 +3210,9 @@ async function bootstrap(flags) {
2297
3210
  onboardFlags.set('workspace', workspace);
2298
3211
  onboardFlags.set('policy-revision', policyRevision);
2299
3212
  onboardFlags.set('provider', provider);
2300
- const onboarded = await retryBootstrapOnboarding(async () => joinedBindingId
2301
- ? await joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint)
2302
- : await onboard(onboardFlags));
3213
+ const onboarded = await step(() => retryBootstrapOnboarding(async () => joinedBindingId
3214
+ ? await step(() => joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint))
3215
+ : await step(() => onboard(onboardFlags))));
2303
3216
  if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
2304
3217
  return {
2305
3218
  ok: onboarded.ok === true,
@@ -2319,40 +3232,40 @@ async function bootstrap(flags) {
2319
3232
  repository: onboarded,
2320
3233
  };
2321
3234
  }
2322
- const launcher = await installStableRepositoryLauncher(workspace);
3235
+ const launcher = await step(() => installStableRepositoryLauncher(workspace));
2323
3236
  const autostart = flags.has('no-relay-daemon')
2324
3237
  ? { state: 'disabled', backend: null }
2325
- : 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}`
2326
3239
  + (joinedBindingId ? ` --join-repository-binding-id ${joinedBindingId} --join-source-fingerprint ${joinedFingerprint}` : ''), () => withRelayStartupMutation(async () => {
2327
3240
  const home = dharmaHome();
2328
- const existing = await relayAutostartStatus({ home });
3241
+ const existing = await step(() => relayAutostartStatus({ home }));
2329
3242
  if (existing.backend !== null || existing.state !== 'disabled') {
2330
- const anchor = await inspectOwnedRelayAutostart({ home });
3243
+ const anchor = await step(() => inspectOwnedRelayAutostart({ home }));
2331
3244
  if (anchor.policy !== null) {
2332
- const current = await readDeviceConfig();
3245
+ const current = await step(() => readDeviceConfig());
2333
3246
  if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
2334
3247
  throw new Error('relay_workspace_conflict: startup enrollment changed.');
2335
3248
  }
2336
- const selected = selectDeviceWorkspace(await registry(), { organizationId,
3249
+ const selected = selectDeviceWorkspace(await step(() => registry()), { organizationId,
2337
3250
  deviceId: config.deviceId, path: anchor.workspace });
2338
3251
  if (!selected)
2339
3252
  throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
2340
3253
  if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
2341
3254
  throw new Error('relay_workspace_conflict: startup policy is not canonical.');
2342
3255
  }
2343
- const authorized = await loadOrganizationPolicy(anchor.policy);
2344
- 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
2345
3258
  && row.organizationId === organizationId && row.path === anchor.workspace
2346
3259
  && row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
2347
3260
  if (canonical.length !== 1)
2348
3261
  throw new Error('relay_workspace_conflict: startup policy route changed.');
2349
- await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
3262
+ await step(() => loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? ''));
2350
3263
  }
2351
3264
  }
2352
- return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
3265
+ return step(() => enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
2353
3266
  launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
2354
- version: VERSION, preserveStandardAnchor: true });
2355
- }));
3267
+ version: VERSION, preserveStandardAnchor: true }));
3268
+ })));
2356
3269
  let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
2357
3270
  const completionRequested = flags.has('complete');
2358
3271
  if (!completionRequested) {
@@ -2376,7 +3289,7 @@ async function bootstrap(flags) {
2376
3289
  autostart,
2377
3290
  };
2378
3291
  }
2379
- const skill = await verifyAgentFabricSkillInstallation({ provider, workspace });
3292
+ const skill = await step(() => verifyAgentFabricSkillInstallation({ provider, workspace }));
2380
3293
  if (!skill.ready)
2381
3294
  throw new Error(`Provider skill did not become ready: ${JSON.stringify(skill)}`);
2382
3295
  const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
@@ -2385,13 +3298,13 @@ async function bootstrap(flags) {
2385
3298
  || joinedBindingId !== null && firstLearning?.state === 'shared_package_inherited';
2386
3299
  const relay = flags.has('no-relay-daemon')
2387
3300
  ? { started: false, ...(await waitForRelayReadiness()) }
2388
- : await startRelayDaemon(policyPath);
3301
+ : await step(() => startRelayDaemon(policyPath));
2389
3302
  let repositoryReadiness = null;
2390
3303
  if (!sharedRepositoryReady && relay.state === 'running') {
2391
3304
  const workspaceId = String(onboarded.workspaceId || '');
2392
3305
  const resumeCommand = `dharma onboard --resume --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision} --provider ${provider}`;
2393
- repositoryReadiness = await withOnboardingStage('readiness', workspaceId, resumeCommand, () => waitForRepositoryReadiness(async () => {
2394
- 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
2395
3308
  && item.organizationId === organizationId && item.path === workspace);
2396
3309
  if (!current)
2397
3310
  throw new Error('Enrolled repository workspace disappeared during publication.');
@@ -2400,20 +3313,20 @@ async function bootstrap(flags) {
2400
3313
  ready: await repositorySharedReady(current),
2401
3314
  candidateId: current.repositoryPackage?.candidateId || null,
2402
3315
  };
2403
- }));
3316
+ })));
2404
3317
  sharedRepositoryReady = repositoryReadiness.outcome === 'ready';
2405
3318
  }
2406
3319
  const apiFlags = new Map([
2407
3320
  ['organization-id', organizationId], ['portal-url', hqUrl],
2408
3321
  ]);
2409
3322
  const [organization, agents, experiments, failures, remediations, skills, usage] = await Promise.all([
2410
- runOrganizationCommand('organization', 'status', apiFlags),
2411
- runOrganizationCommand('agents', 'list', apiFlags),
2412
- runOrganizationCommand('experiments', 'list', apiFlags),
2413
- runOrganizationCommand('failures', 'list', apiFlags),
2414
- runOrganizationCommand('remediations', 'list', apiFlags),
2415
- runOrganizationCommand('skills', 'list', apiFlags),
2416
- 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)),
2417
3330
  ]);
2418
3331
  const organizationApi = summarizeBootstrapOrganizationApi({
2419
3332
  organizationId,
@@ -2425,23 +3338,35 @@ async function bootstrap(flags) {
2425
3338
  skills,
2426
3339
  usage,
2427
3340
  });
2428
- const operatingContract = await loadAgentFabricOnboardingContract();
2429
- const namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
2430
- ? 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([
2431
3344
  ['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
2432
3345
  ['workspace-id', String(onboarded.workspaceId)], ['apply', true],
2433
3346
  ...(typeof flags.get('session-budget-cents') === 'string'
2434
3347
  ? [['session-budget-cents', String(flags.get('session-budget-cents'))]] : []),
2435
- ])))
3348
+ ]))))
2436
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
+ }
2437
3361
  const repositoryReceipt = onboarded;
2438
3362
  const namedSessionReady = Boolean(joinedBindingId) || provider !== 'codex' || process.platform !== 'linux'
2439
- || Boolean(namedSession?.ok === true && ['running', 'executing'].includes(String(namedSession.state)));
3363
+ || Boolean(namedSession?.ok === true && namedSession.nativeSkill
3364
+ && ['running', 'executing'].includes(String(namedSession.state)));
2440
3365
  const role = repositoryReceipt.repositoryRole;
2441
3366
  const roleReady = Boolean(role);
2442
3367
  const relayReady = relay.state === 'running';
2443
3368
  const observedAutostart = autostart.backend === 'container-entrypoint'
2444
- ? await relayAutostartStatus({ home: dharmaHome() }) : autostart;
3369
+ ? await step(() => relayAutostartStatus({ home: dharmaHome() })) : autostart;
2445
3370
  const startupReady = !['win32', 'linux', 'darwin'].includes(process.platform)
2446
3371
  || observedAutostart.state === 'enabled'
2447
3372
  && (observedAutostart.backend !== 'container-entrypoint' || observedAutostart.lifecycle === 'running');
@@ -2900,7 +3825,7 @@ async function login(flags) {
2900
3825
  config.serverSigningKeyset = candidate;
2901
3826
  }
2902
3827
  await saveDeviceConfig(configPath(), config);
2903
- await saveDeviceEnrollmentAnchor({ config });
3828
+ await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
2904
3829
  await rm(pendingEnrollmentPath(), { force: true });
2905
3830
  return { ok: true, status: 'approved', deviceId: config.deviceId, organizationId: pending.organizationId, relayUrl: config.relayUrl };
2906
3831
  }
@@ -2975,7 +3900,6 @@ async function capture(flags, batch = false) {
2975
3900
  taskReceiptHash: null,
2976
3901
  });
2977
3902
  let policy = await loadVerifiedWorkspacePolicy(policyPath, registered.workspaceId);
2978
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
2979
3903
  const fabric = flags.has('sync')
2980
3904
  ? await client()
2981
3905
  : null;
@@ -2985,9 +3909,8 @@ async function capture(flags, batch = false) {
2985
3909
  }
2986
3910
  if (fabric)
2987
3911
  policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, registered.workspaceId, fabric);
2988
- const vault = await LocalVault.open({
3912
+ const vault = await openBootstrapVault({
2989
3913
  root: resolve(dharmaHome(), 'vault'),
2990
- masterKey: await loadOrCreateVaultMasterKey(),
2991
3914
  rawLocalDays: rawLocalRetentionDays(policy),
2992
3915
  });
2993
3916
  try {
@@ -3012,7 +3935,7 @@ async function capture(flags, batch = false) {
3012
3935
  if (fabric) {
3013
3936
  await vault.discardCapsuleRevisionsAfter(firstRevision.trajectoryId, serverHead?.revision || 0);
3014
3937
  }
3015
- const latestMetadata = vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
3938
+ const latestMetadata = await vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
3016
3939
  const latestCapsule = latestMetadata
3017
3940
  ? await vault.getLatestCapsule(firstRevision.trajectoryId)
3018
3941
  : null;
@@ -3055,7 +3978,7 @@ async function capture(flags, batch = false) {
3055
3978
  }
3056
3979
  capsules.push(capsule);
3057
3980
  if (fabric) {
3058
- vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
3981
+ await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
3059
3982
  try {
3060
3983
  await reserveDailyContentUpload(capsule, policy);
3061
3984
  // An unknown delivery outcome retains the local advisory reservation.
@@ -3064,11 +3987,11 @@ async function capture(flags, batch = false) {
3064
3987
  }
3065
3988
  catch (error) {
3066
3989
  if (isDefinitiveSecretDisclosureRejection(error)) {
3067
- vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
3990
+ await vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
3068
3991
  }
3069
3992
  throw error;
3070
3993
  }
3071
- vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
3994
+ await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
3072
3995
  }
3073
3996
  }
3074
3997
  const output = flags.get('output');
@@ -3102,7 +4025,7 @@ async function capture(flags, batch = false) {
3102
4025
  return manifest;
3103
4026
  }
3104
4027
  finally {
3105
- vault.close();
4028
+ await vault.close();
3106
4029
  }
3107
4030
  }
3108
4031
  async function evidencePreview(flags) {
@@ -3255,7 +4178,7 @@ async function workspaceRecoverRegistry(flags) {
3255
4178
  const config = await readDeviceConfig();
3256
4179
  if (!config)
3257
4180
  throw new Error('registry_recovery_enrollment_required');
3258
- const enrollment = await loadDeviceEnrollmentAnchor({ config });
4181
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
3259
4182
  const workspace = await realpath(String(flags.get('workspace') || '.'));
3260
4183
  const home = dharmaHome();
3261
4184
  const startup = await inspectOwnedRelayAutostart({ home });
@@ -3455,14 +4378,17 @@ function selectedProviderAdapters(providerIds) {
3455
4378
  : providerAdapters;
3456
4379
  }
3457
4380
  export async function receiptAwareProviderCapabilities(providers, now = new Date()) {
4381
+ const hostScope = currentBootstrapHostScope();
4382
+ await hostScope?.assert();
3458
4383
  const selfTestedAt = now.toISOString();
3459
4384
  let freshUntil = now.toISOString();
3460
4385
  let receiverState = 'unavailable';
3461
4386
  let reason = 'device_not_enrolled';
3462
4387
  let trustedKeyVersions = [];
3463
4388
  try {
3464
- const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now });
3465
- await loadDeviceEnrollmentAnchor({ config });
4389
+ const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now,
4390
+ hostScope });
4391
+ await loadDeviceEnrollmentAnchor({ config, hostScope });
3466
4392
  const keyset = config.serverSigningKeyset;
3467
4393
  if (!keyset)
3468
4394
  throw new Error('trusted_server_signing_keyset_unavailable');
@@ -3477,13 +4403,15 @@ export async function receiptAwareProviderCapabilities(providers, now = new Date
3477
4403
  if (trustedKeyVersions.length === 0)
3478
4404
  throw new Error('trusted_server_signing_keys_unavailable');
3479
4405
  freshUntil = actionDecisionCapabilityFreshUntil(keyset, trustedKeyVersions, now);
3480
- await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest();
4406
+ await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest(hostScope);
3481
4407
  receiverState = 'available';
3482
4408
  reason = '';
3483
4409
  }
3484
4410
  catch (error) {
3485
- 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);
3486
4413
  }
4414
+ await hostScope?.assert();
3487
4415
  return providers.map((provider) => {
3488
4416
  const available = provider.taskExecution === 'available' && receiverState === 'available';
3489
4417
  return {
@@ -3695,18 +4623,95 @@ async function repositoryRoleCommand(action, flags) {
3695
4623
  targetEndpointId: typeof flags.get('target-endpoint-id') === 'string' ? String(flags.get('target-endpoint-id')) : undefined });
3696
4624
  return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
3697
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
+ }
3698
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
+ }
3699
4692
  const name = String(flags.get('name') || 'codex');
3700
4693
  const paths = namedSessionPaths(dharmaHome(), name);
3701
4694
  if (process.platform !== 'linux')
3702
4695
  throw new Error('codex_session_sandbox_unqualified');
3703
4696
  const workspaceId = required(flags, 'workspace-id');
3704
- const item = (await registry()).find(row => row.workspaceId === workspaceId);
3705
- 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());
3706
4707
  if (!item || !config || item.organizationId !== config.organizationId)
3707
4708
  throw new Error('named_session_scope_mismatch');
4709
+ await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
3708
4710
  const scope = repositoryRoleScope(item);
3709
- 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');
3710
4715
  if (existing && (existing.identity.workspaceId !== item.workspaceId
3711
4716
  || existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
3712
4717
  || existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
@@ -3721,6 +4726,18 @@ async function namedSessionCommand(action, flags) {
3721
4726
  bindingId: existing?.bindingId ?? null };
3722
4727
  }
3723
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
+ }
3724
4741
  if (action === 'work')
3725
4742
  return namedSessionRequest(dharmaHome(), name, { action: 'work',
3726
4743
  workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
@@ -3741,101 +4758,155 @@ async function namedSessionCommand(action, flags) {
3741
4758
  if (!flags.has('apply'))
3742
4759
  return { ok: true, planned: true, name, workspaceId,
3743
4760
  provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
3744
- 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)));
3745
4762
  if (action === 'start') {
3746
4763
  if (existing && !existing.enabled)
3747
- await saveNamedSession(dharmaHome(), { ...existing, enabled: true });
4764
+ await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
3748
4765
  try {
3749
- return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
4766
+ return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
3750
4767
  }
3751
4768
  catch { /* Start an owned worker below. */ }
3752
4769
  const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
3753
4770
  '--workspace-id', workspaceId, '--apply'];
4771
+ if (setupSessionScope)
4772
+ args.push('--setup-handoff');
3754
4773
  for (const key of ['session-budget-cents', 'turn-budget-cents']) {
3755
4774
  if (flags.has(key))
3756
4775
  args.push(`--${key}`, required(flags, key));
3757
4776
  }
3758
- const child = spawn(process.execPath, args, { cwd: item.path, detached: true, stdio: 'ignore', env: process.env });
3759
- child.on('error', () => { });
3760
- 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
+ });
3761
4794
  for (let attempt = 0; attempt < 80; attempt++) {
3762
- await new Promise(resolveWait => setTimeout(resolveWait, 250));
4795
+ await step(() => new Promise(resolveWait => setTimeout(resolveWait, 250)));
4796
+ await hostScope?.assert();
3763
4797
  try {
3764
- return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
4798
+ return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
3765
4799
  }
3766
4800
  catch { /* Await service initialization. */ }
3767
4801
  }
3768
4802
  throw new Error('named_session_startup_failed');
3769
4803
  }
3770
4804
  await mkdir(paths.root, { recursive: true, mode: 0o700 });
3771
- const releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
3772
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
3773
- const vault = await LocalVault.open({ root: resolve(dharmaHome(), 'vault'), masterKey: await loadOrCreateVaultMasterKey() });
4805
+ const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
4806
+ let releaseLock;
3774
4807
  const controller = new AbortController(), stop = () => controller.abort();
3775
4808
  process.once('SIGTERM', stop);
3776
4809
  process.once('SIGINT', stop);
4810
+ if (childMessage)
4811
+ process.once('disconnect', stop);
3777
4812
  let transport;
3778
4813
  try {
3779
- const fabric = await AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json') });
3780
- if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
3781
- || fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
3782
- throw new Error('named_session_trust_scope_mismatch');
3783
- const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
3784
- await trust.refresh();
3785
- await fabric.openSession(VERSION);
3786
- const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
3787
- const policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3788
- const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
3789
- .map(path => path.slice(0, -3));
3790
- if (!writeRoots.length)
3791
- throw new Error('named_session_workspace_write_not_authorized');
3792
- const environment = namedCodexEnvironment(process.env);
3793
- const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
3794
- if (environment.XDG_RUNTIME_DIR)
3795
- privateRoots.push(environment.XDG_RUNTIME_DIR);
3796
- if (environment.DBUS_SESSION_BUS_ADDRESS)
3797
- privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
3798
- const filesystem = await namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots });
3799
- transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
3800
- environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
3801
- argv: ['-c', 'default_permissions="dharma_bridge"',
3802
- '-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
3803
- '-c', 'permissions.dharma_bridge.network={enabled=false}',
3804
- '-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
3805
- '-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
3806
- const account = await transport.request('account/read', { refreshToken: false });
3807
- if (!account.account)
3808
- throw new Error('named_session_provider_authentication_required');
3809
- let registration = existing;
3810
- if (!registration) {
3811
- const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
3812
- const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
3813
- const threadId = await startNamedCodexThread(transport, item.path, name);
3814
- const bindingId = randomUUID();
3815
- const response = await fabric.signedPost('/agent-fabric/provider-sessions', {
3816
- schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
3817
- workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
3818
- 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;
3819
4891
  });
3820
- const remote = response.registration;
3821
- if (response.ok !== true || response.organizationId !== config.organizationId || !remote
3822
- || remote.bindingId !== bindingId || remote.workspaceId !== workspaceId || remote.endpointId !== scope.endpointId
3823
- || remote.repositoryBindingId !== scope.repositoryBindingId || remote.deviceId !== config.deviceId
3824
- || !UUID_PATTERN.test(String(remote.membershipId)) || remote.provider !== 'codex'
3825
- || remote.mode !== 'bridge_owned' || remote.state !== 'attached' || remote.revision !== 1) {
3826
- throw new Error('named_session_registration_invalid');
3827
- }
3828
- const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
3829
- workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
3830
- provider: 'codex' };
3831
- vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...identity,
3832
- bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
3833
- createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
3834
- maximumProviderCostCents: maximumTurnCostCents });
3835
- registration = { schema: 'dharma.named-session/v1', name, bindingId, identity,
3836
- maximumCostCents, maximumTurnCostCents, enabled: true };
3837
- await saveNamedSession(dharmaHome(), registration);
3838
- }
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();
3839
4910
  const authorizeLifetime = async () => {
3840
4911
  const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
3841
4912
  if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
@@ -3867,6 +4938,17 @@ async function namedSessionCommand(action, flags) {
3867
4938
  };
3868
4939
  return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
3869
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
+ },
3870
4952
  syncTaskExports: () => syncNamedSessionTaskExports({ vault, bindingId: registration.bindingId,
3871
4953
  identity: registration.identity,
3872
4954
  loadPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
@@ -3884,7 +4966,7 @@ async function namedSessionCommand(action, flags) {
3884
4966
  if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
3885
4967
  try {
3886
4968
  taskStateFailure = 'public_context_unavailable';
3887
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
4969
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3888
4970
  if (!binding)
3889
4971
  throw new Error('named_session_binding_unavailable');
3890
4972
  const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
@@ -3935,7 +5017,7 @@ async function namedSessionCommand(action, flags) {
3935
5017
  || current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
3936
5018
  throw new Error('named_session_repository_state_consent_changed');
3937
5019
  }
3938
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
5020
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3939
5021
  if (!binding)
3940
5022
  throw new Error('named_session_binding_unavailable');
3941
5023
  const receipt = await retainNamedSessionRepositoryState({ vault, binding, capture, workId: taskState.workId,
@@ -3952,7 +5034,7 @@ async function namedSessionCommand(action, flags) {
3952
5034
  },
3953
5035
  queueEvidence: async (capture) => {
3954
5036
  const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
3955
- const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
5037
+ const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
3956
5038
  if (!binding)
3957
5039
  throw new Error('named_session_binding_unavailable');
3958
5040
  // The existing relay rechecks current authority, disclosure limits and device binding before upload.
@@ -3971,10 +5053,16 @@ async function namedSessionCommand(action, flags) {
3971
5053
  await transport.close();
3972
5054
  }
3973
5055
  finally {
3974
- vault.close();
3975
- await releaseLock();
3976
- process.removeListener('SIGTERM', stop);
3977
- 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
+ }
3978
5066
  }
3979
5067
  }
3980
5068
  }
@@ -4168,13 +5256,22 @@ async function joinExistingRepository(flags, bindingId, sourceFingerprint) {
4168
5256
  workspaceSync: synchronized, relay };
4169
5257
  }
4170
5258
  async function readDeviceConfig() {
5259
+ const scope = currentBootstrapHostScope();
4171
5260
  try {
4172
- 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);
4173
5263
  }
4174
5264
  catch {
5265
+ await scope?.assert();
4175
5266
  return null;
4176
5267
  }
4177
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
+ }
4178
5275
  async function activeSkillAuthorization(provider, workspaceId, organizationAgentId, config) {
4179
5276
  const root = await nativeSkillDirectoryForWorkspace({ provider, workspaceId });
4180
5277
  const pointer = resolve(root, '.dharma-managed', 'workspaces', workspaceId, 'ACTIVE_BUNDLE');
@@ -4183,13 +5280,9 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4183
5280
  if (!organizationAgentId)
4184
5281
  throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
4185
5282
  const [identity, enrollment, active] = await Promise.all([
4186
- loadOrCreateDeviceIdentity({
4187
- hqUrl: config.hqUrl,
4188
- organizationId: config.organizationId,
4189
- installationId: config.installationId,
4190
- }),
4191
- loadDeviceEnrollmentAnchor({ config }),
4192
- loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
5283
+ enrolledDeviceIdentity(config),
5284
+ loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
5285
+ loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
4193
5286
  ]);
4194
5287
  if (!active)
4195
5288
  throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
@@ -4197,6 +5290,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4197
5290
  throw new Error('Active skill device identity does not match secure enrollment state.');
4198
5291
  }
4199
5292
  const authorizationInput = (expectedReceiptHash) => ({
5293
+ hostScope: currentBootstrapHostScope(),
4200
5294
  nativeSkillDirectory: root,
4201
5295
  workspaceId,
4202
5296
  provider,
@@ -4222,6 +5316,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
4222
5316
  throw error;
4223
5317
  const refreshed = await loadActiveSkillAuthorizationAnchor({
4224
5318
  config, workspaceId, organizationAgentId, provider, fresh: true,
5319
+ hostScope: currentBootstrapHostScope(),
4225
5320
  });
4226
5321
  if (!refreshed)
4227
5322
  throw error;
@@ -4233,13 +5328,9 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4233
5328
  if (!organizationAgentId)
4234
5329
  throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
4235
5330
  const [identity, enrollment, active] = await Promise.all([
4236
- loadOrCreateDeviceIdentity({
4237
- hqUrl: config.hqUrl,
4238
- organizationId: config.organizationId,
4239
- installationId: config.installationId,
4240
- }),
4241
- loadDeviceEnrollmentAnchor({ config }),
4242
- loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
5331
+ enrolledDeviceIdentity(config),
5332
+ loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
5333
+ loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
4243
5334
  ]);
4244
5335
  if (!active)
4245
5336
  throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
@@ -4247,6 +5338,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4247
5338
  throw new Error('Active skill device identity does not match secure enrollment state.');
4248
5339
  }
4249
5340
  const authorizationInput = (expectedReceiptHash) => ({
5341
+ hostScope: currentBootstrapHostScope(),
4250
5342
  nativeSkillDirectory: root,
4251
5343
  workspaceId,
4252
5344
  provider,
@@ -4272,6 +5364,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
4272
5364
  throw error;
4273
5365
  const refreshed = await loadActiveSkillAuthorizationAnchor({
4274
5366
  config, workspaceId, organizationAgentId, provider, fresh: true,
5367
+ hostScope: currentBootstrapHostScope(),
4275
5368
  });
4276
5369
  if (!refreshed)
4277
5370
  throw error;
@@ -4295,22 +5388,18 @@ async function loadVerifiedWorkspacePolicy(path, workspaceId) {
4295
5388
  return verified;
4296
5389
  }
4297
5390
  export async function installRepositoryAgentFabricSkill(input) {
4298
- if (input.sourceAuthorization !== undefined)
4299
- input = { ...input,
4300
- sourceAuthorization: validateRepositorySourceAuthorization(input.sourceAuthorization, input, new Date()) };
4301
- await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
4302
- input = { ...input, workspace: await realpath(input.workspace) };
4303
- const ownership = await assertRepositoryInstallerOwnership(input.workspace, input.workspaceId);
4304
- const skillRoot = resolve(input.workspace, '.agents', 'skills', 'dharma-agent-fabric');
4305
- if (ownership !== 'signed')
4306
- await mkdir(resolve(skillRoot, 'references'), { recursive: true, mode: 0o700 });
4307
- 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();
4308
5397
  const skill = `---
4309
5398
  name: dharma-agent-fabric
4310
5399
  description: Connect this repository to Dharma Agent Fabric for shared knowledge, signed skills, and team coordination.
4311
5400
  ---
4312
5401
 
4313
- ${(await loadAgentFabricOnboardingContract()).markdown}
5402
+ ${contract.markdown}
4314
5403
  `;
4315
5404
  const reference = `# Organization connection
4316
5405
 
@@ -4369,6 +5458,7 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
4369
5458
  });
4370
5459
  await writeRepositoryInstallerFile(input.workspace, '.dharma/agent-fabric.json', `${JSON.stringify(connection, null, 2)}\n`);
4371
5460
  await writeRepositoryInstallerFile(input.workspace, '.dharma/repository-agent.json', `${JSON.stringify(repositoryAgent, null, 2)}\n`);
5461
+ await scope?.assert();
4372
5462
  return {
4373
5463
  skillPath: '.agents/skills/dharma-agent-fabric/SKILL.md',
4374
5464
  connectionPath: '.dharma/agent-fabric.json',
@@ -4387,7 +5477,7 @@ async function repositoryInstallerRecoveryCommand(flags) {
4387
5477
  const config = await readDeviceConfig();
4388
5478
  if (!config || config.organizationId !== organizationId)
4389
5479
  throw new Error('Recovery requires current organization enrollment.');
4390
- await loadDeviceEnrollmentAnchor({ config });
5480
+ await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
4391
5481
  const identity = await preflightBootstrapWorkspaceIdentity(workspace);
4392
5482
  const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
4393
5483
  path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
@@ -4451,6 +5541,8 @@ async function onboard(flags) {
4451
5541
  const requestedHqUrl = normalizeHqUrl(portalUrl(flags));
4452
5542
  let config = await readDeviceConfig();
4453
5543
  if (!config) {
5544
+ if (currentBootstrapHostScope())
5545
+ throw new Error('codex_setup_host_enrollment_missing');
4454
5546
  const loginFlags = new Map(flags);
4455
5547
  loginFlags.set('hq-url', requestedHqUrl);
4456
5548
  loginFlags.set('organization-id', organizationId);
@@ -4722,10 +5814,8 @@ async function evidenceSync(flags) {
4722
5814
  assertCapsuleIntegrity(capsule);
4723
5815
  const trajectoryId = String(capsule.trajectoryId || '');
4724
5816
  const revision = Number(capsule.revision);
4725
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
4726
- const vault = await LocalVault.open({
5817
+ const vault = await openBootstrapVault({
4727
5818
  root: resolve(dharmaHome(), 'vault'),
4728
- masterKey: await loadOrCreateVaultMasterKey(),
4729
5819
  });
4730
5820
  try {
4731
5821
  const storedCapsule = await vault.getCapsule(trajectoryId, revision);
@@ -4739,7 +5829,7 @@ async function evidenceSync(flags) {
4739
5829
  return fabric.syncTrajectory(storedCapsule);
4740
5830
  }
4741
5831
  finally {
4742
- vault.close();
5832
+ await vault.close();
4743
5833
  }
4744
5834
  }
4745
5835
  async function processEvidenceRequest(fabric, policy, workspaceId) {
@@ -4797,10 +5887,8 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4797
5887
  finally {
4798
5888
  await requestHandle?.close().catch(() => undefined);
4799
5889
  }
4800
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
4801
- const vault = await LocalVault.open({
5890
+ const vault = await openBootstrapVault({
4802
5891
  root: resolve(dharmaHome(), 'vault'),
4803
- masterKey: await loadOrCreateVaultMasterKey(),
4804
5892
  rawLocalDays: rawLocalRetentionDays(policy),
4805
5893
  });
4806
5894
  try {
@@ -4906,7 +5994,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4906
5994
  const receipt = accepted.receipt && typeof accepted.receipt === 'object' ? accepted.receipt : {};
4907
5995
  const receiptHash = typeof receipt.hash === 'string' && /^sha256:[a-f0-9]{64}$/.test(receipt.hash)
4908
5996
  ? receipt.hash : response.responseHash;
4909
- vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
5997
+ await vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
4910
5998
  await writeJsonAtomic(requestReceipt, {
4911
5999
  schema: 'dharma.evidence-request-receipt/v1',
4912
6000
  requestId: request.requestId,
@@ -4922,7 +6010,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
4922
6010
  };
4923
6011
  }
4924
6012
  finally {
4925
- vault.close();
6013
+ await vault.close();
4926
6014
  }
4927
6015
  }
4928
6016
  async function runOneEvidenceRequest(flags) {
@@ -5034,14 +6122,12 @@ async function syncSignedTaskTrajectory(input) {
5034
6122
  if (!validation.ok)
5035
6123
  throw new Error(`Signed task capsule failed schema validation: ${JSON.stringify(validation.errors)}`);
5036
6124
  assertCapsuleAuthorizedByCurrentPolicy(capsule, input.policy);
5037
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5038
- const vault = await LocalVault.open({
6125
+ const vault = await openBootstrapVault({
5039
6126
  root: resolve(dharmaHome(), 'vault'),
5040
- masterKey: await loadOrCreateVaultMasterKey(),
5041
6127
  rawLocalDays: rawLocalRetentionDays(input.policy),
5042
6128
  });
5043
6129
  try {
5044
- const existing = vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
6130
+ const existing = await vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
5045
6131
  if (!existing) {
5046
6132
  await vault.commitCapture({
5047
6133
  raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: rawContentId },
@@ -5058,14 +6144,14 @@ async function syncSignedTaskTrajectory(input) {
5058
6144
  else if (existing.capsuleHash !== capsule.capsuleHash) {
5059
6145
  throw new Error('Signed task evidence already exists with different immutable content.');
5060
6146
  }
5061
- vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
6147
+ await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
5062
6148
  await reserveDailyContentUpload(capsule, input.policy);
5063
6149
  const synced = await input.fabric.syncTrajectory(capsule);
5064
- vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
6150
+ await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
5065
6151
  return synced;
5066
6152
  }
5067
6153
  finally {
5068
- vault.close();
6154
+ await vault.close();
5069
6155
  }
5070
6156
  }
5071
6157
  export function assertRecoveredTaskWorkspacePolicy(input) {
@@ -5108,10 +6194,8 @@ export async function postTaskOutcome(input) {
5108
6194
  return input.postEvent(input.task.taskId, input.receipt.status, input.payload);
5109
6195
  }
5110
6196
  async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, prepared) {
5111
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5112
- const vault = await LocalVault.open({
6197
+ const vault = await openBootstrapVault({
5113
6198
  root: resolve(dharmaHome(), 'vault'),
5114
- masterKey: await loadOrCreateVaultMasterKey(),
5115
6199
  rawLocalDays: rawLocalRetentionDays(policy),
5116
6200
  });
5117
6201
  try {
@@ -5127,7 +6211,7 @@ async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, pr
5127
6211
  await vault.stageTaskCompletionRecovery(taskId, Buffer.from(JSON.stringify(recovery)));
5128
6212
  }
5129
6213
  finally {
5130
- vault.close();
6214
+ await vault.close();
5131
6215
  }
5132
6216
  }
5133
6217
  async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, onlyTaskId) {
@@ -5135,10 +6219,8 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5135
6219
  .filter((item) => !onlyTaskId || item.taskId === onlyTaskId);
5136
6220
  const finalized = [];
5137
6221
  for (const completion of completions) {
5138
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
5139
- const vault = await LocalVault.open({
6222
+ const vault = await openBootstrapVault({
5140
6223
  root: resolve(dharmaHome(), 'vault'),
5141
- masterKey: await loadOrCreateVaultMasterKey(),
5142
6224
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5143
6225
  });
5144
6226
  let recovery;
@@ -5146,7 +6228,7 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5146
6228
  recovery = await vault.getTaskCompletionRecovery(completion.taskId);
5147
6229
  }
5148
6230
  finally {
5149
- vault.close();
6231
+ await vault.close();
5150
6232
  }
5151
6233
  if (!recovery || recovery.schema !== 'dharma.signed-task-trajectory-recovery/v1'
5152
6234
  || recovery.taskId !== completion.taskId) {
@@ -5177,13 +6259,12 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5177
6259
  };
5178
6260
  assertCapsuleIntegrity(capsule);
5179
6261
  if (recoveredTaskPolicyWasSuperseded(capsule, recoveryPolicy)) {
5180
- const supersededVault = await LocalVault.open({
6262
+ const supersededVault = await openBootstrapVault({
5181
6263
  root: resolve(dharmaHome(), 'vault'),
5182
- masterKey: await loadOrCreateVaultMasterKey(),
5183
6264
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5184
6265
  });
5185
6266
  try {
5186
- const existing = supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
6267
+ const existing = await supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
5187
6268
  if (!existing) {
5188
6269
  await supersededVault.commitCapture({
5189
6270
  raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: recovery.prepared.rawContentId },
@@ -5204,22 +6285,21 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5204
6285
  else if (existing.capsuleHash !== capsule.capsuleHash) {
5205
6286
  throw new Error('Recovered task evidence already exists with different immutable content.');
5206
6287
  }
5207
- supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
6288
+ await supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
5208
6289
  }
5209
6290
  finally {
5210
- supersededVault.close();
6291
+ await supersededVault.close();
5211
6292
  }
5212
6293
  await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
5213
- const acknowledgedVault = await LocalVault.open({
6294
+ const acknowledgedVault = await openBootstrapVault({
5214
6295
  root: resolve(dharmaHome(), 'vault'),
5215
- masterKey: await loadOrCreateVaultMasterKey(),
5216
6296
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5217
6297
  });
5218
6298
  try {
5219
6299
  await acknowledgedVault.clearTaskCompletionRecovery(completion.taskId);
5220
6300
  }
5221
6301
  finally {
5222
- acknowledgedVault.close();
6302
+ await acknowledgedVault.close();
5223
6303
  }
5224
6304
  finalized.push({
5225
6305
  taskId: completion.taskId,
@@ -5243,16 +6323,15 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
5243
6323
  },
5244
6324
  });
5245
6325
  await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
5246
- const cleanupVault = await LocalVault.open({
6326
+ const cleanupVault = await openBootstrapVault({
5247
6327
  root: resolve(dharmaHome(), 'vault'),
5248
- masterKey: await loadOrCreateVaultMasterKey(),
5249
6328
  rawLocalDays: rawLocalRetentionDays(vaultPolicy),
5250
6329
  });
5251
6330
  try {
5252
6331
  await cleanupVault.clearTaskCompletionRecovery(completion.taskId);
5253
6332
  }
5254
6333
  finally {
5255
- cleanupVault.close();
6334
+ await cleanupVault.close();
5256
6335
  }
5257
6336
  finalized.push({ taskId: completion.taskId, trajectory });
5258
6337
  }
@@ -5463,6 +6542,7 @@ export async function nativeSkillDirectoryForWorkspace(input) {
5463
6542
  const anchor = await loadActiveSkillAuthorizationAnchor({
5464
6543
  config, workspaceId: input.workspaceId, organizationAgentId: registered.repositoryAgentId,
5465
6544
  provider: input.provider, fresh: true,
6545
+ hostScope: currentBootstrapHostScope(),
5466
6546
  });
5467
6547
  if (anchor) {
5468
6548
  const matching = await nativeSkillRootMatchingAuthorization({
@@ -6104,6 +7184,7 @@ export function validateSkillBundleSources(bundle) {
6104
7184
  }
6105
7185
  export async function recoverLegacySkillBundleIdAfterAuthorizationFailure(input) {
6106
7186
  const legacyBundleId = await getLegacySkillBundleIdForUpgrade({
7187
+ hostScope: currentBootstrapHostScope(),
6107
7188
  nativeSkillDirectory: input.nativeSkillDirectory,
6108
7189
  workspaceId: input.workspaceId,
6109
7190
  });
@@ -6134,7 +7215,7 @@ export async function loadSkillSynchronizationPolicy(policyPath, workspaceId, se
6134
7215
  verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
6135
7216
  organizationId: config.organizationId, workspaceId });
6136
7217
  await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
6137
- const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore });
7218
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore, hostScope: currentBootstrapHostScope() });
6138
7219
  await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
6139
7220
  verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
6140
7221
  organizationId: config.organizationId, workspaceId });
@@ -6174,7 +7255,8 @@ export async function prepareSkillUpdate(input) {
6174
7255
  let legacyBaselineMigrationRequested = false;
6175
7256
  if (input.automatic) {
6176
7257
  activeBundleId = (await loadActiveSkillAuthorizationAnchor({ config, workspaceId,
6177
- 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;
6178
7260
  assertRunning();
6179
7261
  }
6180
7262
  else {
@@ -6294,7 +7376,7 @@ export async function prepareSkillUpdate(input) {
6294
7376
  assertCurrent();
6295
7377
  }
6296
7378
  for (const skill of bundle.skills) {
6297
- const actual = await contentHash(containedInlinePath(sourceRoot, skill.path));
7379
+ const actual = await contentHash(containedInlinePath(sourceRoot, skill.path), currentBootstrapHostScope());
6298
7380
  assertCurrent();
6299
7381
  if (actual !== skill.contentHash)
6300
7382
  throw new Error('Prepared skill tree does not match the signed content hash.');
@@ -6319,12 +7401,9 @@ async function activatePreparedSkillUpdate(input) {
6319
7401
  workspaceId,
6320
7402
  organizationAgentId: String(workspace.repositoryAgentId || ''),
6321
7403
  provider,
7404
+ hostScope: currentBootstrapHostScope(),
6322
7405
  });
6323
- const identity = await loadOrCreateDeviceIdentity({
6324
- hqUrl: config.hqUrl,
6325
- organizationId: config.organizationId,
6326
- installationId: config.installationId,
6327
- });
7406
+ const identity = await enrolledDeviceIdentity(config);
6328
7407
  repositoryDelivery?.assertCurrent();
6329
7408
  const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId);
6330
7409
  if (canonicalize(current.policy) !== canonicalize(policy)
@@ -6334,6 +7413,7 @@ async function activatePreparedSkillUpdate(input) {
6334
7413
  }
6335
7414
  repositoryDelivery?.assertCurrent();
6336
7415
  const receipt = await installSkillBundle({
7416
+ hostScope: currentBootstrapHostScope(),
6337
7417
  bundle,
6338
7418
  sourceDirectory: sourceRoot,
6339
7419
  nativeSkillDirectory: destination,
@@ -6371,16 +7451,18 @@ async function activatePreparedSkillUpdate(input) {
6371
7451
  receiptHash: previousAnchor.receiptHash,
6372
7452
  activatedAt: previousAnchor.activatedAt,
6373
7453
  expiresAt: previousAnchor.expiresAt,
7454
+ hostScope: currentBootstrapHostScope(),
6374
7455
  });
6375
7456
  }
6376
7457
  else {
6377
- await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider });
7458
+ await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider, hostScope: currentBootstrapHostScope() });
6378
7459
  }
6379
7460
  };
6380
7461
  const recoverLocalInstallation = async (error) => {
6381
7462
  const recoveryErrors = [];
6382
7463
  try {
6383
7464
  await rollbackUnconfirmedSkillBundle({
7465
+ hostScope: currentBootstrapHostScope(),
6384
7466
  nativeSkillDirectory: destination,
6385
7467
  workspaceId,
6386
7468
  receipt,
@@ -6411,6 +7493,7 @@ async function activatePreparedSkillUpdate(input) {
6411
7493
  receiptHash: receipt.receiptHash,
6412
7494
  activatedAt: receipt.completedAt,
6413
7495
  expiresAt: bundle.expiresAt ?? null,
7496
+ hostScope: currentBootstrapHostScope(),
6414
7497
  });
6415
7498
  }
6416
7499
  }
@@ -6458,12 +7541,8 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
6458
7541
  if (!config || config.organizationId !== workspace.organizationId || !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
6459
7542
  throw new Error('Installed repository knowledge requires current enrolled workspace scope.');
6460
7543
  }
6461
- const identity = await loadOrCreateDeviceIdentity({
6462
- hqUrl: config.hqUrl,
6463
- organizationId: config.organizationId,
6464
- installationId: config.installationId,
6465
- });
6466
- const enrollment = await loadDeviceEnrollmentAnchor({ config });
7544
+ const identity = await enrolledDeviceIdentity(config);
7545
+ const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
6467
7546
  if (identity.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
6468
7547
  throw new Error('Installed knowledge device identity mismatch.');
6469
7548
  const organizationAgentId = workspace.repositoryAgentId;
@@ -6477,11 +7556,13 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
6477
7556
  });
6478
7557
  if (!await pathExistsOrThrow(resolve(root, '.dharma-managed/workspaces', workspace.workspaceId, 'ACTIVE_BUNDLE')))
6479
7558
  return null;
6480
- const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider };
7559
+ const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider,
7560
+ hostScope: currentBootstrapHostScope() };
6481
7561
  const active = await loadActiveSkillAuthorizationAnchor(anchorInput);
6482
7562
  if (!active)
6483
7563
  throw new Error('Installed knowledge release lacks a protected authorization anchor.');
6484
7564
  const read = (expectedReceiptHash) => readVerifiedRepositoryKnowledge({
7565
+ hostScope: currentBootstrapHostScope(),
6485
7566
  nativeSkillDirectory: root, workspaceId: workspace.workspaceId, provider,
6486
7567
  organizationId: config.organizationId, organizationAgentId, deviceId: config.deviceId,
6487
7568
  serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: enrollment.serverPublicKeyEd25519 }, format: 'jwk' }),
@@ -6519,7 +7600,7 @@ async function takeCachedSkillUpdate(input) {
6519
7600
  const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
6520
7601
  verifySkillBundle(bundle, serverPublicKey);
6521
7602
  for (const skill of bundle.skills) {
6522
- if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path)) !== skill.contentHash) {
7603
+ if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path), currentBootstrapHostScope()) !== skill.contentHash) {
6523
7604
  throw new Error('Cached skill tree does not match the signed content hash.');
6524
7605
  }
6525
7606
  }
@@ -6686,14 +7767,12 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
6686
7767
  let repositorySourceState = 'not_scanned';
6687
7768
  let sourceScanFlight = null;
6688
7769
  let sourceScanReady = null;
6689
- const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
6690
- const vault = await LocalVault.open({
7770
+ const vault = await openBootstrapVault({
6691
7771
  root: resolve(dharmaHome(), 'vault'),
6692
- masterKey: await loadOrCreateVaultMasterKey(),
6693
7772
  rawLocalDays: rawLocalRetentionDays(policy),
6694
7773
  });
6695
7774
  if (signal.aborted)
6696
- vault.close();
7775
+ await vault.close();
6697
7776
  signal.throwIfAborted();
6698
7777
  signal.addEventListener('abort', stop, { once: true });
6699
7778
  if (signal.aborted)
@@ -7019,7 +8098,7 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
7019
8098
  await sourceScanFlight;
7020
8099
  await consumeSourceScan();
7021
8100
  await skillPreparationPump.stop();
7022
- vault.close();
8101
+ await vault.close();
7023
8102
  signal.removeEventListener('abort', stop);
7024
8103
  }
7025
8104
  return {
@@ -7035,6 +8114,20 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
7035
8114
  export async function run(argv) {
7036
8115
  const { positional, flags, repeated } = parseCliOptions(argv);
7037
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
+ }
7038
8131
  if (command === 'relay' && subcommand === 'container-entrypoint') {
7039
8132
  if (positional.length === 2 && flags.size === 1 && flags.get('dry-run') === true) {
7040
8133
  return { ok: true, stage: 'container_entrypoint_plan', started: false,