@dharma-ai-labs/agent-fabric 0.2.153 → 0.2.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrapGitScope.test.d.ts +2 -0
- package/dist/bootstrapGitScope.test.d.ts.map +1 -0
- package/dist/bootstrapGitScope.test.js +115 -0
- package/dist/bootstrapGitScope.test.js.map +1 -0
- package/dist/bootstrapHostFiles.d.ts +5 -0
- package/dist/bootstrapHostFiles.d.ts.map +1 -0
- package/dist/bootstrapHostFiles.js +150 -0
- package/dist/bootstrapHostFiles.js.map +1 -0
- package/dist/bootstrapHostFiles.test.d.ts +2 -0
- package/dist/bootstrapHostFiles.test.d.ts.map +1 -0
- package/dist/bootstrapHostFiles.test.js +127 -0
- package/dist/bootstrapHostFiles.test.js.map +1 -0
- package/dist/bootstrapHostScope.d.ts +42 -0
- package/dist/bootstrapHostScope.d.ts.map +1 -0
- package/dist/bootstrapHostScope.js +224 -0
- package/dist/bootstrapHostScope.js.map +1 -0
- package/dist/bootstrapHostScope.test.d.ts +2 -0
- package/dist/bootstrapHostScope.test.d.ts.map +1 -0
- package/dist/bootstrapHostScope.test.js +577 -0
- package/dist/bootstrapHostScope.test.js.map +1 -0
- package/dist/bootstrapInstallationScope.test.d.ts +2 -0
- package/dist/bootstrapInstallationScope.test.d.ts.map +1 -0
- package/dist/bootstrapInstallationScope.test.js +68 -0
- package/dist/bootstrapInstallationScope.test.js.map +1 -0
- package/dist/bootstrapLauncherCaller.test.d.ts +2 -0
- package/dist/bootstrapLauncherCaller.test.d.ts.map +1 -0
- package/dist/bootstrapLauncherCaller.test.js +91 -0
- package/dist/bootstrapLauncherCaller.test.js.map +1 -0
- package/dist/bootstrapOwnedChildren.test.d.ts +2 -0
- package/dist/bootstrapOwnedChildren.test.d.ts.map +1 -0
- package/dist/bootstrapOwnedChildren.test.js +93 -0
- package/dist/bootstrapOwnedChildren.test.js.map +1 -0
- package/dist/bootstrapPolicyScope.test.d.ts +2 -0
- package/dist/bootstrapPolicyScope.test.d.ts.map +1 -0
- package/dist/bootstrapPolicyScope.test.js +261 -0
- package/dist/bootstrapPolicyScope.test.js.map +1 -0
- package/dist/bootstrapProcessCaller.test.d.ts +2 -0
- package/dist/bootstrapProcessCaller.test.d.ts.map +1 -0
- package/dist/bootstrapProcessCaller.test.js +262 -0
- package/dist/bootstrapProcessCaller.test.js.map +1 -0
- package/dist/bootstrapRegistryCaller.test.d.ts +2 -0
- package/dist/bootstrapRegistryCaller.test.d.ts.map +1 -0
- package/dist/bootstrapRegistryCaller.test.js +128 -0
- package/dist/bootstrapRegistryCaller.test.js.map +1 -0
- package/dist/bootstrapVaultScope.test.d.ts +2 -0
- package/dist/bootstrapVaultScope.test.d.ts.map +1 -0
- package/dist/bootstrapVaultScope.test.js +105 -0
- package/dist/bootstrapVaultScope.test.js.map +1 -0
- package/dist/codexBootstrapCompletion.test.d.ts +2 -0
- package/dist/codexBootstrapCompletion.test.d.ts.map +1 -0
- package/dist/codexBootstrapCompletion.test.js +630 -0
- package/dist/codexBootstrapCompletion.test.js.map +1 -0
- package/dist/codexBoundSession.d.ts +2 -2
- package/dist/codexBoundSession.d.ts.map +1 -1
- package/dist/codexBoundSession.js +23 -17
- package/dist/codexBoundSession.js.map +1 -1
- package/dist/codexBoundSession.test.js +3 -0
- package/dist/codexBoundSession.test.js.map +1 -1
- package/dist/codexInboxSession.d.ts.map +1 -1
- package/dist/codexInboxSession.js +3 -3
- package/dist/codexInboxSession.js.map +1 -1
- package/dist/codexScopedDispatch.test.d.ts +2 -0
- package/dist/codexScopedDispatch.test.d.ts.map +1 -0
- package/dist/codexScopedDispatch.test.js +56 -0
- package/dist/codexScopedDispatch.test.js.map +1 -0
- package/dist/codexSetupAdmission.d.ts +108 -0
- package/dist/codexSetupAdmission.d.ts.map +1 -0
- package/dist/codexSetupAdmission.js +214 -0
- package/dist/codexSetupAdmission.js.map +1 -0
- package/dist/codexSetupAdmission.test.d.ts +2 -0
- package/dist/codexSetupAdmission.test.d.ts.map +1 -0
- package/dist/codexSetupAdmission.test.js +578 -0
- package/dist/codexSetupAdmission.test.js.map +1 -0
- package/dist/codexSetupChildProvider.test.d.ts +2 -0
- package/dist/codexSetupChildProvider.test.d.ts.map +1 -0
- package/dist/codexSetupChildProvider.test.js +210 -0
- package/dist/codexSetupChildProvider.test.js.map +1 -0
- package/dist/codexSetupChildStartup.d.ts +38 -0
- package/dist/codexSetupChildStartup.d.ts.map +1 -0
- package/dist/codexSetupChildStartup.js +115 -0
- package/dist/codexSetupChildStartup.js.map +1 -0
- package/dist/codexSetupChildStartup.test.d.ts +2 -0
- package/dist/codexSetupChildStartup.test.d.ts.map +1 -0
- package/dist/codexSetupChildStartup.test.js +141 -0
- package/dist/codexSetupChildStartup.test.js.map +1 -0
- package/dist/codexSetupCliDispatch.test.d.ts +2 -0
- package/dist/codexSetupCliDispatch.test.d.ts.map +1 -0
- package/dist/codexSetupCliDispatch.test.js +138 -0
- package/dist/codexSetupCliDispatch.test.js.map +1 -0
- package/dist/codexSetupJournalOpen.test.d.ts +2 -0
- package/dist/codexSetupJournalOpen.test.d.ts.map +1 -0
- package/dist/codexSetupJournalOpen.test.js +101 -0
- package/dist/codexSetupJournalOpen.test.js.map +1 -0
- package/dist/codexSetupNativeHost.d.ts +31 -0
- package/dist/codexSetupNativeHost.d.ts.map +1 -0
- package/dist/codexSetupNativeHost.js +211 -0
- package/dist/codexSetupNativeHost.js.map +1 -0
- package/dist/codexSetupNativeHost.test.d.ts +2 -0
- package/dist/codexSetupNativeHost.test.d.ts.map +1 -0
- package/dist/codexSetupNativeHost.test.js +479 -0
- package/dist/codexSetupNativeHost.test.js.map +1 -0
- package/dist/codexSetupNativeWire.test.d.ts +2 -0
- package/dist/codexSetupNativeWire.test.d.ts.map +1 -0
- package/dist/codexSetupNativeWire.test.js +243 -0
- package/dist/codexSetupNativeWire.test.js.map +1 -0
- package/dist/codexSetupOwnedConnection.d.ts +14 -0
- package/dist/codexSetupOwnedConnection.d.ts.map +1 -0
- package/dist/codexSetupOwnedConnection.js +64 -0
- package/dist/codexSetupOwnedConnection.js.map +1 -0
- package/dist/codexSetupOwnedConnection.test.d.ts +2 -0
- package/dist/codexSetupOwnedConnection.test.d.ts.map +1 -0
- package/dist/codexSetupOwnedConnection.test.js +192 -0
- package/dist/codexSetupOwnedConnection.test.js.map +1 -0
- package/dist/codexSetupPortalCommand.test.d.ts +2 -0
- package/dist/codexSetupPortalCommand.test.d.ts.map +1 -0
- package/dist/codexSetupPortalCommand.test.js +146 -0
- package/dist/codexSetupPortalCommand.test.js.map +1 -0
- package/dist/codexSetupReadiness.d.ts +23 -0
- package/dist/codexSetupReadiness.d.ts.map +1 -0
- package/dist/codexSetupReadiness.js +74 -0
- package/dist/codexSetupReadiness.js.map +1 -0
- package/dist/codexSetupReadiness.test.d.ts +2 -0
- package/dist/codexSetupReadiness.test.d.ts.map +1 -0
- package/dist/codexSetupReadiness.test.js +145 -0
- package/dist/codexSetupReadiness.test.js.map +1 -0
- package/dist/codexSetupSessionCaller.test.d.ts +2 -0
- package/dist/codexSetupSessionCaller.test.d.ts.map +1 -0
- package/dist/codexSetupSessionCaller.test.js +352 -0
- package/dist/codexSetupSessionCaller.test.js.map +1 -0
- package/dist/codexSetupSessionHandoff.d.ts +48 -0
- package/dist/codexSetupSessionHandoff.d.ts.map +1 -0
- package/dist/codexSetupSessionHandoff.js +146 -0
- package/dist/codexSetupSessionHandoff.js.map +1 -0
- package/dist/codexSetupSessionHandoff.test.d.ts +2 -0
- package/dist/codexSetupSessionHandoff.test.d.ts.map +1 -0
- package/dist/codexSetupSessionHandoff.test.js +289 -0
- package/dist/codexSetupSessionHandoff.test.js.map +1 -0
- package/dist/codexSetupTransport.test.d.ts +2 -0
- package/dist/codexSetupTransport.test.d.ts.map +1 -0
- package/dist/codexSetupTransport.test.js +206 -0
- package/dist/codexSetupTransport.test.js.map +1 -0
- package/dist/codexSetupVaultJournal.d.ts +13 -0
- package/dist/codexSetupVaultJournal.d.ts.map +1 -0
- package/dist/codexSetupVaultJournal.js +28 -0
- package/dist/codexSetupVaultJournal.js.map +1 -0
- package/dist/codexSetupVaultJournal.test.d.ts +2 -0
- package/dist/codexSetupVaultJournal.test.d.ts.map +1 -0
- package/dist/codexSetupVaultJournal.test.js +104 -0
- package/dist/codexSetupVaultJournal.test.js.map +1 -0
- package/dist/containerChildCleanup.test.d.ts +2 -0
- package/dist/containerChildCleanup.test.d.ts.map +1 -0
- package/dist/containerChildCleanup.test.js +145 -0
- package/dist/containerChildCleanup.test.js.map +1 -0
- package/dist/containerRelayLifecycle.d.ts +1 -1
- package/dist/containerRelayLifecycle.d.ts.map +1 -1
- package/dist/containerRelayLifecycle.js +29 -37
- package/dist/containerRelayLifecycle.js.map +1 -1
- package/dist/cooperativeInboxSession.d.ts +2 -2
- package/dist/cooperativeInboxSession.d.ts.map +1 -1
- package/dist/cooperativeInboxSession.js +21 -13
- package/dist/cooperativeInboxSession.js.map +1 -1
- package/dist/cooperativeInboxSession.test.js +65 -0
- package/dist/cooperativeInboxSession.test.js.map +1 -1
- package/dist/demoWatchControl.d.ts +3 -3
- package/dist/index.d.ts +55 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1642 -553
- package/dist/index.js.map +1 -1
- package/dist/namedCodexSkillDiscovery.d.ts +15 -0
- package/dist/namedCodexSkillDiscovery.d.ts.map +1 -0
- package/dist/namedCodexSkillDiscovery.js +90 -0
- package/dist/namedCodexSkillDiscovery.js.map +1 -0
- package/dist/namedCodexSkillDiscovery.test.d.ts +2 -0
- package/dist/namedCodexSkillDiscovery.test.d.ts.map +1 -0
- package/dist/namedCodexSkillDiscovery.test.js +37 -0
- package/dist/namedCodexSkillDiscovery.test.js.map +1 -0
- package/dist/namedCodexThread.d.ts +2 -0
- package/dist/namedCodexThread.d.ts.map +1 -1
- package/dist/namedCodexThread.js +13 -4
- package/dist/namedCodexThread.js.map +1 -1
- package/dist/namedSessionChildOwner.d.ts +17 -0
- package/dist/namedSessionChildOwner.d.ts.map +1 -0
- package/dist/namedSessionChildOwner.js +122 -0
- package/dist/namedSessionChildOwner.js.map +1 -0
- package/dist/namedSessionChildOwner.test.d.ts +2 -0
- package/dist/namedSessionChildOwner.test.d.ts.map +1 -0
- package/dist/namedSessionChildOwner.test.js +203 -0
- package/dist/namedSessionChildOwner.test.js.map +1 -0
- package/dist/namedSessionEvidence.d.ts +2 -2
- package/dist/namedSessionEvidence.d.ts.map +1 -1
- package/dist/namedSessionEvidence.js +2 -2
- package/dist/namedSessionEvidence.js.map +1 -1
- package/dist/namedSessionEvidence.test.js +24 -1
- package/dist/namedSessionEvidence.test.js.map +1 -1
- package/dist/namedSessionRepositoryState.d.ts +2 -2
- package/dist/namedSessionRepositoryState.d.ts.map +1 -1
- package/dist/namedSessionService.d.ts +4 -2
- package/dist/namedSessionService.d.ts.map +1 -1
- package/dist/namedSessionService.js +18 -1
- package/dist/namedSessionService.js.map +1 -1
- package/dist/namedSessionService.test.js +31 -2
- package/dist/namedSessionService.test.js.map +1 -1
- package/dist/namedSessionTaskExportSync.d.ts +3 -3
- package/dist/namedSessionTaskExportSync.d.ts.map +1 -1
- package/dist/namedSessionTaskExportSync.js +6 -6
- package/dist/namedSessionTaskExportSync.js.map +1 -1
- package/dist/namedSessionTrust.d.ts +2 -2
- package/dist/namedSessionTrust.d.ts.map +1 -1
- package/dist/namedSessionTrust.js +3 -2
- package/dist/namedSessionTrust.js.map +1 -1
- package/dist/namedSessionTrust.test.js +2 -1
- package/dist/namedSessionTrust.test.js.map +1 -1
- package/dist/ownedChildLifecycle.d.ts +13 -0
- package/dist/ownedChildLifecycle.d.ts.map +1 -0
- package/dist/ownedChildLifecycle.js +87 -0
- package/dist/ownedChildLifecycle.js.map +1 -0
- package/dist/ownedChildLifecycle.test.d.ts +2 -0
- package/dist/ownedChildLifecycle.test.d.ts.map +1 -0
- package/dist/ownedChildLifecycle.test.js +133 -0
- package/dist/ownedChildLifecycle.test.js.map +1 -0
- package/dist/pidLock.test.js +242 -9
- package/dist/pidLock.test.js.map +1 -1
- package/dist/providerSessionChannel.d.ts +1 -1
- package/dist/providerSessionChannel.d.ts.map +1 -1
- package/dist/providerSessionChannel.js +7 -3
- package/dist/providerSessionChannel.js.map +1 -1
- package/dist/providerSessionChannel.test.js +20 -0
- package/dist/providerSessionChannel.test.js.map +1 -1
- package/dist/providerSessionReplyRecovery.d.ts +2 -2
- package/dist/providerSessionReplyRecovery.d.ts.map +1 -1
- package/dist/providerSessionReplyRecovery.js +3 -3
- package/dist/providerSessionReplyRecovery.js.map +1 -1
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +12 -9
- package/dist/relayAutostart.js.map +1 -1
- package/dist/relayRuntimeUpgrade.d.ts +1 -1
- package/dist/relaySupervisor.d.ts.map +1 -1
- package/dist/relaySupervisor.js +19 -14
- package/dist/relaySupervisor.js.map +1 -1
- package/dist/relaySupervisor.test.js +26 -0
- package/dist/relaySupervisor.test.js.map +1 -1
- package/dist/relaySupervisorCleanup.test.d.ts +2 -0
- package/dist/relaySupervisorCleanup.test.d.ts.map +1 -0
- package/dist/relaySupervisorCleanup.test.js +69 -0
- package/dist/relaySupervisorCleanup.test.js.map +1 -0
- package/dist/repositoryInstallerFiles.d.ts +19 -1
- package/dist/repositoryInstallerFiles.d.ts.map +1 -1
- package/dist/repositoryInstallerFiles.js +205 -29
- package/dist/repositoryInstallerFiles.js.map +1 -1
- package/dist/repositoryInstallerScope.test.d.ts +2 -0
- package/dist/repositoryInstallerScope.test.d.ts.map +1 -0
- package/dist/repositoryInstallerScope.test.js +513 -0
- package/dist/repositoryInstallerScope.test.js.map +1 -0
- package/dist/repositoryKnowledge.d.ts.map +1 -1
- package/dist/repositoryKnowledge.js +177 -37
- package/dist/repositoryKnowledge.js.map +1 -1
- package/dist/repositoryKnowledgeScope.test.d.ts +2 -0
- package/dist/repositoryKnowledgeScope.test.d.ts.map +1 -0
- package/dist/repositoryKnowledgeScope.test.js +316 -0
- package/dist/repositoryKnowledgeScope.test.js.map +1 -0
- package/dist/repositoryOnboardingCaller.test.js +61 -4
- package/dist/repositoryOnboardingCaller.test.js.map +1 -1
- package/dist/repositoryPackage.d.ts.map +1 -1
- package/dist/repositoryPackage.js +340 -59
- package/dist/repositoryPackage.js.map +1 -1
- package/dist/repositoryPackageDelivery.test.js +65 -0
- package/dist/repositoryPackageDelivery.test.js.map +1 -1
- package/dist/repositoryPackageScope.test.d.ts +2 -0
- package/dist/repositoryPackageScope.test.d.ts.map +1 -0
- package/dist/repositoryPackageScope.test.js +578 -0
- package/dist/repositoryPackageScope.test.js.map +1 -0
- package/dist/schemas/codex-setup-child-start.schema.json +12 -0
- package/dist/schemas/codex-setup-intent.schema.json +27 -0
- package/dist/schemas/local-codex-setup-session.schema.json +39 -0
- package/dist/schemas/setup-claim-failure.schema.json +15 -0
- package/dist/skillInstallationScope.test.d.ts +2 -0
- package/dist/skillInstallationScope.test.d.ts.map +1 -0
- package/dist/skillInstallationScope.test.js +64 -0
- package/dist/skillInstallationScope.test.js.map +1 -0
- package/dist/skillPreparationCache.test.js +12 -4
- package/dist/skillPreparationCache.test.js.map +1 -1
- package/dist/skillPreparationCaller.test.js +31 -1
- package/dist/skillPreparationCaller.test.js.map +1 -1
- package/dist/startupCommitScope.test.d.ts +2 -0
- package/dist/startupCommitScope.test.d.ts.map +1 -0
- package/dist/startupCommitScope.test.js +172 -0
- package/dist/startupCommitScope.test.js.map +1 -0
- package/dist/workspaceRegistry.d.ts.map +1 -1
- package/dist/workspaceRegistry.js +7 -3
- package/dist/workspaceRegistry.js.map +1 -1
- package/dist/workspaceRegistry.test.js +51 -0
- package/dist/workspaceRegistry.test.js.map +1 -1
- package/dist/workspaceRegistryRecovery.d.ts.map +1 -1
- package/dist/workspaceRegistryRecovery.js +171 -64
- package/dist/workspaceRegistryRecovery.js.map +1 -1
- package/dist/workspaceRegistryRecoveryScope.test.d.ts +2 -0
- package/dist/workspaceRegistryRecoveryScope.test.d.ts.map +1 -0
- package/dist/workspaceRegistryRecoveryScope.test.js +416 -0
- package/dist/workspaceRegistryRecoveryScope.test.js.map +1 -0
- 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 {
|
|
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.
|
|
91
|
+
const VERSION = '0.2.155';
|
|
77
92
|
const USAGE = CLI_USAGE;
|
|
78
93
|
const execFileAsync = promisify(execFile);
|
|
79
94
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -157,6 +172,36 @@ async function loadVaultModule() {
|
|
|
157
172
|
process.emitWarning = original;
|
|
158
173
|
}
|
|
159
174
|
}
|
|
175
|
+
async function openBootstrapVault(options) {
|
|
176
|
+
const scope = currentBootstrapHostScope(), snapshot = { root: options.root, rawLocalDays: options.rawLocalDays };
|
|
177
|
+
let key;
|
|
178
|
+
let vault;
|
|
179
|
+
try {
|
|
180
|
+
await scope?.assert();
|
|
181
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = scope
|
|
182
|
+
? await scope.step(() => loadVaultModule()) : await loadVaultModule();
|
|
183
|
+
key = await loadOrCreateVaultMasterKey(undefined, scope);
|
|
184
|
+
await scope?.assert();
|
|
185
|
+
vault = scope ? await LocalVault.open({ ...snapshot, masterKey: key }, scope)
|
|
186
|
+
: await LocalVault.open({ ...snapshot, masterKey: key });
|
|
187
|
+
await scope?.assert();
|
|
188
|
+
return vault;
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (vault) {
|
|
192
|
+
try {
|
|
193
|
+
await vault.close();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw new Error('vault_cleanup_unconfirmed');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
key?.fill(0);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
160
205
|
export function isDirectExecution(argvPath, moduleUrl) {
|
|
161
206
|
if (!argvPath)
|
|
162
207
|
return false;
|
|
@@ -176,43 +221,56 @@ function workspaceRegistryPath() { return resolve(dharmaHome(), 'registry', 'wor
|
|
|
176
221
|
function evidenceUploadLedgerPath() { return resolve(dharmaHome(), 'relay', 'evidence-upload-ledger.json'); }
|
|
177
222
|
function evidenceRequestReceiptPath(requestId) { return resolve(dharmaHome(), 'relay', 'evidence-requests', `${requestId}.json`); }
|
|
178
223
|
export async function loadOrCreateInstallationId(path = installationIdentityPath()) {
|
|
224
|
+
const scope = currentBootstrapHostScope();
|
|
225
|
+
const step = (operation) => scope ? scope.step(operation) : operation();
|
|
179
226
|
const readExisting = async () => {
|
|
180
|
-
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
227
|
+
const value = JSON.parse(await step(() => readFile(path, 'utf8')));
|
|
181
228
|
if (value.schema !== 'dharma.installation-identity/v1'
|
|
182
229
|
|| typeof value.installationId !== 'string'
|
|
183
230
|
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.installationId)) {
|
|
184
231
|
throw new Error('Installation identity is invalid. Preserve the file and re-enroll this installation.');
|
|
185
232
|
}
|
|
233
|
+
await scope?.assert();
|
|
186
234
|
return value.installationId;
|
|
187
235
|
};
|
|
188
236
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 (
|
|
269
|
+
if (!scope)
|
|
209
270
|
throw error;
|
|
210
|
-
|
|
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
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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 (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
802
|
+
throw renameError;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
let ownerPid = 0;
|
|
807
|
+
try {
|
|
808
|
+
ownerPid = Number((await step(() => readFile(lockPath, 'utf8'))).trim());
|
|
809
|
+
}
|
|
810
|
+
catch (readError) {
|
|
811
|
+
if (readError.code === 'ENOENT')
|
|
677
812
|
continue;
|
|
813
|
+
}
|
|
814
|
+
let ownerAlive = Number.isSafeInteger(ownerPid) && ownerPid > 0;
|
|
815
|
+
if (ownerAlive) {
|
|
816
|
+
try {
|
|
817
|
+
await step(async () => { process.kill(ownerPid, 0); });
|
|
818
|
+
}
|
|
819
|
+
catch (killError) {
|
|
820
|
+
ownerAlive = killError.code === 'EPERM';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (!ownerAlive) {
|
|
824
|
+
const quarantine = `${lockPath}.dead.${randomUUID()}`;
|
|
825
|
+
try {
|
|
826
|
+
await step(() => rename(lockPath, quarantine));
|
|
827
|
+
await step(() => unlink(quarantine));
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
catch (renameError) {
|
|
831
|
+
if (renameError.code !== 'ENOENT')
|
|
832
|
+
throw renameError;
|
|
678
833
|
}
|
|
679
|
-
throw renameError;
|
|
680
834
|
}
|
|
681
|
-
|
|
835
|
+
}
|
|
836
|
+
finally {
|
|
837
|
+
await cleanupOwned(recoveryPath, true);
|
|
682
838
|
}
|
|
683
839
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
690
|
-
if (
|
|
691
|
-
|
|
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
|
|
694
|
-
if (
|
|
857
|
+
let recoveryOwnerAlive = Number.isSafeInteger(recoveryOwner) && recoveryOwner > 0;
|
|
858
|
+
if (recoveryOwnerAlive) {
|
|
695
859
|
try {
|
|
696
|
-
process.kill(
|
|
860
|
+
await step(async () => { process.kill(recoveryOwner, 0); });
|
|
697
861
|
}
|
|
698
862
|
catch (killError) {
|
|
699
|
-
|
|
863
|
+
recoveryOwnerAlive = killError.code === 'EPERM';
|
|
700
864
|
}
|
|
701
865
|
}
|
|
702
|
-
if (!
|
|
703
|
-
const quarantine = `${
|
|
866
|
+
if (!recoveryOwnerAlive) {
|
|
867
|
+
const quarantine = `${recoveryPath}.dead.${randomUUID()}`;
|
|
704
868
|
try {
|
|
705
|
-
await rename(
|
|
706
|
-
await
|
|
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
|
-
|
|
716
|
-
|
|
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
|
|
904
|
+
const scope = currentBootstrapHostScope();
|
|
905
|
+
const step = (effect) => scope ? scope.step(effect) : effect();
|
|
906
|
+
let release;
|
|
765
907
|
try {
|
|
766
|
-
|
|
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
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
[
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
989
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
await
|
|
996
|
-
|
|
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
|
|
1170
|
+
const scope = currentBootstrapHostScope();
|
|
1171
|
+
const step = (effect) => scope ? scope.step(effect) : effect();
|
|
1172
|
+
let release;
|
|
1005
1173
|
try {
|
|
1006
|
-
|
|
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
|
|
1182
|
+
const scope = currentBootstrapHostScope();
|
|
1183
|
+
const step = (effect) => scope ? scope.step(effect) : effect();
|
|
1184
|
+
let release;
|
|
1014
1185
|
try {
|
|
1015
|
-
|
|
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
|
|
1035
|
-
|
|
1208
|
+
const scope = currentBootstrapHostScope();
|
|
1209
|
+
const step = (effect) => scope ? scope.step(effect) : effect();
|
|
1036
1210
|
try {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1211
|
+
if (scope)
|
|
1212
|
+
authorization = structuredClone(authorization);
|
|
1213
|
+
await scope?.assert();
|
|
1214
|
+
const statePath = workspaceAuthorizationStatePath(workspaceId);
|
|
1215
|
+
let previous = null;
|
|
1216
|
+
try {
|
|
1217
|
+
const parsed = JSON.parse(await step(() => readFile(statePath, 'utf8')));
|
|
1218
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
|
|
1219
|
+
|| typeof parsed.issuedAt !== 'string'
|
|
1220
|
+
|| !Number.isFinite(Date.parse(parsed.issuedAt))
|
|
1221
|
+
|| typeof parsed.signature !== 'string'
|
|
1222
|
+
|| !parsed.signature) {
|
|
1223
|
+
throw new Error('Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
|
|
1224
|
+
}
|
|
1225
|
+
previous = parsed;
|
|
1044
1226
|
}
|
|
1045
|
-
|
|
1227
|
+
catch (error) {
|
|
1228
|
+
await scope?.assert();
|
|
1229
|
+
if (error.code === 'ENOENT' && !requireExisting)
|
|
1230
|
+
return;
|
|
1231
|
+
throw new Error(scope ? 'workspace_authorization_replay_state_invalid'
|
|
1232
|
+
: 'Workspace authorization replay state is missing or invalid; apply a fresh server policy.');
|
|
1233
|
+
}
|
|
1234
|
+
if (previous) {
|
|
1235
|
+
const incomingTime = Date.parse(authorization.issuedAt);
|
|
1236
|
+
const previousTime = Date.parse(previous.issuedAt);
|
|
1237
|
+
if (!Number.isFinite(previousTime)
|
|
1238
|
+
|| incomingTime < previousTime
|
|
1239
|
+
|| (incomingTime === previousTime && authorization.signature !== previous.signature)) {
|
|
1240
|
+
throw new Error(scope ? 'workspace_authorization_replay_superseded'
|
|
1241
|
+
: 'Server workspace policy authorization is older than the last accepted authorization.');
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
await scope?.assert();
|
|
1046
1245
|
}
|
|
1047
1246
|
catch (error) {
|
|
1048
|
-
if (
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
1352
|
-
|
|
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
|
|
1579
|
+
const scope = currentBootstrapHostScope();
|
|
1580
|
+
const step = (operation) => scope ? scope.step(operation) : operation();
|
|
1581
|
+
let release;
|
|
1356
1582
|
try {
|
|
1357
|
-
|
|
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 !==
|
|
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
|
-
|
|
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
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1833
|
+
if (scope) {
|
|
1834
|
+
await scope.assert();
|
|
1835
|
+
if (await scope.step(() => realpath(workspace)) !== workspace)
|
|
1836
|
+
throw new Error('codex_setup_host_launcher_path_invalid');
|
|
1837
|
+
for (const directory of [resolve(workspace, '.dharma'), launcherRoot]) {
|
|
1838
|
+
await scope.step(async () => {
|
|
1839
|
+
try {
|
|
1840
|
+
await mkdir(directory, { mode: 0o700 });
|
|
1841
|
+
}
|
|
1842
|
+
catch (error) {
|
|
1843
|
+
if (error.code !== 'EEXIST')
|
|
1844
|
+
throw error;
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
const entry = await scope.step(() => lstat(directory));
|
|
1848
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || await scope.step(() => realpath(directory)) !== directory
|
|
1849
|
+
|| process.platform !== 'win32' && (entry.uid !== process.getuid() || (entry.mode & 0o022))) {
|
|
1850
|
+
throw new Error('codex_setup_host_launcher_path_invalid');
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
await writeBootstrapHostText(shellPath, contents.shell, scope, 0o700);
|
|
1854
|
+
await writeBootstrapHostText(cmdPath, contents.windows, scope, 0o600);
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
await mkdir(launcherRoot, { recursive: true, mode: 0o700 });
|
|
1858
|
+
await writeFile(shellPath, contents.shell, { mode: 0o700 });
|
|
1859
|
+
await chmod(shellPath, 0o700);
|
|
1860
|
+
await writeFile(cmdPath, contents.windows, { mode: 0o600 });
|
|
1861
|
+
}
|
|
1581
1862
|
return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
|
|
1582
1863
|
}
|
|
1583
1864
|
async function relayUpgrade(flags) {
|
|
@@ -1718,6 +1999,9 @@ export async function waitForRelayReadiness(options = {}) {
|
|
|
1718
1999
|
throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
|
|
1719
2000
|
}
|
|
1720
2001
|
async function startRelayDaemon(policyPath) {
|
|
2002
|
+
const hostScope = currentBootstrapHostScope();
|
|
2003
|
+
const step = (operation) => hostScope ? hostScope.step(operation) : operation();
|
|
2004
|
+
await hostScope?.assert();
|
|
1721
2005
|
const config = await readDeviceConfig();
|
|
1722
2006
|
if (!config)
|
|
1723
2007
|
throw new Error('Relay startup requires current device enrollment.');
|
|
@@ -1727,23 +2011,32 @@ async function startRelayDaemon(policyPath) {
|
|
|
1727
2011
|
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
1728
2012
|
if (!selected)
|
|
1729
2013
|
throw new Error('Relay policy must belong to this enrolled repository.');
|
|
2014
|
+
await assertBootstrapHostSource(selected.path, String(selected.repositoryRemoteHash || ''));
|
|
1730
2015
|
const alreadyRunning = await relayProcessState() === 'running';
|
|
1731
2016
|
const supervisorState = await relaySupervisorProcessState();
|
|
1732
2017
|
if (supervisorState === 'unknown')
|
|
1733
2018
|
throw new Error('Relay supervisor process state is unknown.');
|
|
1734
2019
|
if (supervisorState === 'stopped') {
|
|
1735
|
-
|
|
1736
|
-
|
|
2020
|
+
const startupBackend = (await relayAutostartStatus({ home: dharmaHome() })).backend;
|
|
2021
|
+
// A setup turn cannot own a supervisor intended to outlive that turn.
|
|
2022
|
+
if (hostScope || startupBackend === 'container-entrypoint') {
|
|
2023
|
+
await step(() => startRelayAutostart({ home: dharmaHome() }));
|
|
1737
2024
|
}
|
|
1738
2025
|
else {
|
|
1739
|
-
|
|
1740
|
-
|
|
2026
|
+
await step(async () => {
|
|
2027
|
+
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', policyPath], {
|
|
2028
|
+
cwd: dirname(dirname(policyPath)), detached: !hostScope, stdio: 'ignore', env: process.env,
|
|
2029
|
+
});
|
|
2030
|
+
if (hostScope)
|
|
2031
|
+
captureBootstrapHostChild(hostScope, child);
|
|
2032
|
+
else
|
|
2033
|
+
child.unref();
|
|
1741
2034
|
});
|
|
1742
|
-
child.unref();
|
|
1743
2035
|
}
|
|
1744
2036
|
}
|
|
1745
2037
|
let supervisorReady = false;
|
|
1746
2038
|
for (let attempt = 0; attempt < 80; attempt += 1) {
|
|
2039
|
+
await hostScope?.assert();
|
|
1747
2040
|
if (await relaySupervisorProcessState() === 'running') {
|
|
1748
2041
|
const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'supervisor.pid'), 'utf8')
|
|
1749
2042
|
.catch(() => '0')).trim());
|
|
@@ -1766,6 +2059,7 @@ async function startRelayDaemon(policyPath) {
|
|
|
1766
2059
|
if (!supervisorReady)
|
|
1767
2060
|
throw new Error('The relay supervisor did not become ready after bootstrap.');
|
|
1768
2061
|
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
|
|
2062
|
+
await hostScope?.assert();
|
|
1769
2063
|
if (await relayProcessState() !== 'running')
|
|
1770
2064
|
return 'stopped';
|
|
1771
2065
|
const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
|
|
@@ -1773,6 +2067,7 @@ async function startRelayDaemon(policyPath) {
|
|
|
1773
2067
|
return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
|
|
1774
2068
|
version: VERSION, pid }) ? 'running' : 'stopped';
|
|
1775
2069
|
} });
|
|
2070
|
+
await hostScope?.assert();
|
|
1776
2071
|
return { started: !alreadyRunning, supervisor: 'running', ...readiness };
|
|
1777
2072
|
}
|
|
1778
2073
|
async function supervisedDemoCycle(registration, signal) {
|
|
@@ -1873,10 +2168,15 @@ async function relaySupervise(flags) {
|
|
|
1873
2168
|
}
|
|
1874
2169
|
finally {
|
|
1875
2170
|
controller.abort();
|
|
1876
|
-
await Promise.allSettled(services);
|
|
2171
|
+
const settled = await Promise.allSettled(services);
|
|
1877
2172
|
await health.drain();
|
|
1878
2173
|
process.removeListener('SIGINT', stop);
|
|
1879
2174
|
process.removeListener('SIGTERM', stop);
|
|
2175
|
+
// Preserve ownership records when an owned child may still be running.
|
|
2176
|
+
if (settled.some(result => result.status === 'rejected'
|
|
2177
|
+
&& result.reason instanceof Error && result.reason.message === 'owned_child_stop_unconfirmed')) {
|
|
2178
|
+
throw new Error('relay_supervisor_child_stop_unconfirmed');
|
|
2179
|
+
}
|
|
1880
2180
|
const binding = await readFile(bindingPath, 'utf8')
|
|
1881
2181
|
.then(value => JSON.parse(value)).catch(() => null);
|
|
1882
2182
|
if (binding?.pid === process.pid)
|
|
@@ -1887,37 +2187,160 @@ async function relaySupervise(flags) {
|
|
|
1887
2187
|
async function superviseNamedSessions(signal) {
|
|
1888
2188
|
if (process.platform !== 'linux')
|
|
1889
2189
|
return;
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2190
|
+
const owner = createNamedSessionChildOwner(signal);
|
|
2191
|
+
return owner.run(async () => {
|
|
2192
|
+
let vault;
|
|
2193
|
+
try {
|
|
2194
|
+
while (!signal.aborted) {
|
|
2195
|
+
try {
|
|
2196
|
+
// The original setup initializes the protected vault before staging.
|
|
2197
|
+
// A missing/unavailable store is not permission for a plaintext fallback.
|
|
2198
|
+
await access(resolve(dharmaHome(), 'vault', 'vault.sqlite'));
|
|
2199
|
+
owner.assert();
|
|
2200
|
+
vault ??= await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
|
|
2201
|
+
owner.assert();
|
|
2202
|
+
await consumeCodexSetupSessions({ vault, owner, signal,
|
|
2203
|
+
authorize: qualifyCodexSetupSessionRequest,
|
|
2204
|
+
start: async (scope) => {
|
|
2205
|
+
const request = scope.request;
|
|
2206
|
+
const status = await namedSessionCommand('start', new Map([
|
|
2207
|
+
['name', request.name], ['workspace-id', request.workspaceId], ['apply', true]
|
|
2208
|
+
]));
|
|
2209
|
+
const pid = owner.ownedPid(request.name);
|
|
2210
|
+
if (!pid || status.ok !== true || status.membershipId !== request.membershipId
|
|
2211
|
+
|| status.deviceId !== request.deviceId || status.organizationId !== request.organizationId
|
|
2212
|
+
|| status.workspaceId !== request.workspaceId || status.repositoryBindingId !== request.repositoryBindingId
|
|
2213
|
+
|| status.endpointId !== request.endpointId || status.provider !== 'codex') {
|
|
2214
|
+
throw new Error('setup_session_owner_unconfirmed');
|
|
2215
|
+
}
|
|
2216
|
+
const [child, supervisor] = await scope.step(() => Promise.all([
|
|
2217
|
+
readContainerProcessIdentity(pid), readContainerProcessIdentity(process.pid)
|
|
2218
|
+
]));
|
|
2219
|
+
if (child.parentPid !== process.pid || child.uid !== process.getuid() || supervisor.uid !== process.getuid()) {
|
|
2220
|
+
throw new Error('setup_session_owner_unconfirmed');
|
|
2221
|
+
}
|
|
2222
|
+
return { state: 'started', bindingId: String(status.bindingId), sessionId: String(status.sessionId),
|
|
2223
|
+
sessionPid: pid, supervisorPid: process.pid, sessionStartTicks: child.startTicks, supervisorStartTicks: supervisor.startTicks };
|
|
2224
|
+
} });
|
|
2225
|
+
}
|
|
2226
|
+
catch {
|
|
2227
|
+
if (!signal.aborted)
|
|
2228
|
+
process.stderr.write(`${JSON.stringify({ event: 'named_session_setup_pending' })}\n`);
|
|
2229
|
+
}
|
|
2230
|
+
const root = resolve(dharmaHome(), 'sessions');
|
|
2231
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(error => {
|
|
2232
|
+
if (error.code === 'ENOENT')
|
|
2233
|
+
return [];
|
|
2234
|
+
throw error;
|
|
2235
|
+
});
|
|
2236
|
+
for (const entry of entries.slice(0, 50)) {
|
|
2237
|
+
if (signal.aborted)
|
|
2238
|
+
break;
|
|
2239
|
+
if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
|
|
2240
|
+
continue;
|
|
2241
|
+
try {
|
|
2242
|
+
const registration = await readNamedSession(dharmaHome(), entry.name);
|
|
2243
|
+
if (!registration?.enabled)
|
|
2244
|
+
continue;
|
|
2245
|
+
await namedSessionCommand('start', new Map([
|
|
2246
|
+
['name', registration.name], ['workspace-id', registration.identity.workspaceId], ['apply', true],
|
|
2247
|
+
]));
|
|
2248
|
+
}
|
|
2249
|
+
catch {
|
|
2250
|
+
process.stderr.write(`${JSON.stringify({ event: 'named_session_reconnect_pending', name: entry.name })}\n`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
await new Promise(resolveWait => {
|
|
2254
|
+
const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', finish); resolveWait(); };
|
|
2255
|
+
const timer = setTimeout(finish, 10_000);
|
|
2256
|
+
signal.addEventListener('abort', finish, { once: true });
|
|
2257
|
+
if (signal.aborted)
|
|
2258
|
+
finish();
|
|
2259
|
+
});
|
|
1912
2260
|
}
|
|
1913
2261
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2262
|
+
finally {
|
|
2263
|
+
vault?.close();
|
|
2264
|
+
}
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
/** Standing owner rechecks the exact approved source/device without adopting
|
|
2268
|
+
* a caller PID, changing enrollment or contacting another member's device. */
|
|
2269
|
+
async function qualifyCodexSetupSessionRequest(request) {
|
|
2270
|
+
const owner = currentNamedSessionChildOwner();
|
|
2271
|
+
if (!owner)
|
|
2272
|
+
return false;
|
|
2273
|
+
try {
|
|
2274
|
+
owner.assert();
|
|
2275
|
+
const qualified = await qualifyCodexSetupSessionSource(request);
|
|
2276
|
+
owner.assert();
|
|
2277
|
+
return qualified;
|
|
2278
|
+
}
|
|
2279
|
+
catch {
|
|
2280
|
+
return false;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
async function qualifyCodexSetupSessionSource(request) {
|
|
2284
|
+
try {
|
|
2285
|
+
if (process.platform !== 'linux' || request.maximumCostCents !== 1000 || request.maximumTurnCostCents !== 25)
|
|
2286
|
+
return false;
|
|
2287
|
+
const config = await readDeviceConfig();
|
|
2288
|
+
if (!config || config.deviceId !== request.deviceId || config.organizationId !== request.organizationId
|
|
2289
|
+
|| normalizeHqUrl(config.hqUrl) !== request.origin || config.setupClaimReference !== request.setupReference
|
|
2290
|
+
|| config.setupClaimRepositoryFingerprint !== request.repositoryFingerprint)
|
|
2291
|
+
return false;
|
|
2292
|
+
const sender = await readContainerProcessIdentity(request.senderPid);
|
|
2293
|
+
if (sender.uid !== process.getuid() || sender.startTicks !== request.senderStartTicks)
|
|
2294
|
+
return false;
|
|
2295
|
+
const rows = (await registry()).filter(row => row.path === request.workspaceRoot && row.workspaceId === request.workspaceId
|
|
2296
|
+
&& row.organizationId === request.organizationId && row.repositoryRemoteHash === request.repositoryFingerprint && row.status === 'active');
|
|
2297
|
+
if (rows.length !== 1 || request.name !== `codex-${request.workspaceId.slice(0, 8)}`)
|
|
2298
|
+
return false;
|
|
2299
|
+
const item = rows[0], role = repositoryRoleScope(item);
|
|
2300
|
+
if (role.repositoryBindingId !== request.repositoryBindingId || role.endpointId !== request.endpointId)
|
|
2301
|
+
return false;
|
|
2302
|
+
const source = await preflightBootstrapWorkspaceIdentity(request.workspaceRoot);
|
|
2303
|
+
if (source.fingerprint !== request.repositoryFingerprint || await realpath(request.workspaceRoot) !== request.workspaceRoot)
|
|
2304
|
+
return false;
|
|
2305
|
+
const contract = await loadAgentFabricOnboardingContract();
|
|
2306
|
+
if (`sha256:${contract.sha256}` !== request.contractDigest)
|
|
2307
|
+
return false;
|
|
2308
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
2309
|
+
const device = await enrolledDeviceIdentity(config);
|
|
2310
|
+
if (device.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
|
|
2311
|
+
return false;
|
|
2312
|
+
const policy = await loadVerifiedWorkspacePolicy(resolve(request.workspaceRoot, '.dharma', 'approved-policy.json'), request.workspaceId);
|
|
2313
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
2314
|
+
organizationId: request.organizationId, workspaceId: request.workspaceId });
|
|
2315
|
+
await assertWorkspaceAuthorizationCurrent(request.workspaceId, policy.serverAuthorization);
|
|
2316
|
+
if (policy.revision !== request.policyRevision || sha256(canonicalize(policy)) !== request.policyHash)
|
|
2317
|
+
return false;
|
|
2318
|
+
const fabric = await client();
|
|
2319
|
+
await fetchRepositorySourceAuthorization(fabric, role);
|
|
2320
|
+
const existing = await readNamedSession(dharmaHome(), request.name);
|
|
2321
|
+
if (existing && (existing.identity.membershipId !== request.membershipId || existing.identity.deviceId !== request.deviceId
|
|
2322
|
+
|| existing.identity.organizationId !== request.organizationId || existing.identity.workspaceId !== request.workspaceId
|
|
2323
|
+
|| existing.identity.repositoryBindingId !== request.repositoryBindingId || existing.identity.endpointId !== request.endpointId))
|
|
2324
|
+
return false;
|
|
2325
|
+
return Date.parse(request.issuedAt) <= Date.now() && Date.now() < Date.parse(request.expiresAt);
|
|
2326
|
+
}
|
|
2327
|
+
catch {
|
|
2328
|
+
return false;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async function qualifyCodexSetupChildRequest(request) {
|
|
2332
|
+
try {
|
|
2333
|
+
if (!await qualifyCodexSetupSessionSource(request))
|
|
2334
|
+
return false;
|
|
2335
|
+
const startup = await inspectOwnedRelayAutostart({ home: dharmaHome() });
|
|
2336
|
+
const parent = await readContainerProcessIdentity(process.ppid);
|
|
2337
|
+
return ['systemd-user', 'container-entrypoint'].includes(startup.backend) && startup.version === VERSION
|
|
2338
|
+
&& parent.uid === process.getuid() && canonicalize(parent.argv) === canonicalize([
|
|
2339
|
+
process.execPath, fileURLToPath(import.meta.url), 'relay', 'supervise', '--policy', startup.policy
|
|
2340
|
+
]);
|
|
2341
|
+
}
|
|
2342
|
+
catch {
|
|
2343
|
+
return false;
|
|
1921
2344
|
}
|
|
1922
2345
|
}
|
|
1923
2346
|
async function relayStop() {
|
|
@@ -2061,7 +2484,472 @@ export function assertBootstrapResumeAuthority(input) {
|
|
|
2061
2484
|
throw new Error('Bootstrap resume requires an existing device enrolled to this organization and portal.');
|
|
2062
2485
|
}
|
|
2063
2486
|
}
|
|
2064
|
-
|
|
2487
|
+
/** Planning-only trusted host entry. Effectful admission remains unavailable
|
|
2488
|
+
* until the full continuation and native owner lifecycle are qualified. */
|
|
2489
|
+
export async function bootstrapFromCodexSetup(input) {
|
|
2490
|
+
try {
|
|
2491
|
+
return await runCodexBootstrapHost(input, prepared => bootstrap(prepared.flags, prepared.scope));
|
|
2492
|
+
}
|
|
2493
|
+
catch (error) {
|
|
2494
|
+
const safe = new Set(['codex_setup_host_scope_invalid', 'codex_setup_host_scope_unavailable', 'codex_setup_host_context_conflict', 'codex_setup_host_source_mismatch']);
|
|
2495
|
+
let code = 'codex_setup_host_operation_failed';
|
|
2496
|
+
try {
|
|
2497
|
+
if (error instanceof Error && safe.has(error.message))
|
|
2498
|
+
code = error.message;
|
|
2499
|
+
}
|
|
2500
|
+
catch { }
|
|
2501
|
+
throw new Error(code);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/** Native-owner continuation: no caller-selected flags or replacement authority. */
|
|
2505
|
+
export async function bootstrapFromCodexSetupScope(scope) {
|
|
2506
|
+
try {
|
|
2507
|
+
return await runCodexBootstrapHostScope(scope, prepared => run(['bootstrap',
|
|
2508
|
+
...[...prepared.flags].flatMap(([key, value]) => value === true ? [`--${key}`] : [`--${key}`, String(value)]),
|
|
2509
|
+
]));
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
if (error instanceof Error && error.message === 'setup_session_sender_unavailable') {
|
|
2513
|
+
return { ok: false, stage: 'host_setup_unavailable',
|
|
2514
|
+
code: 'codex_setup_host_execution_unqualified', effects: false, grantRedeemed: false };
|
|
2515
|
+
}
|
|
2516
|
+
const safe = new Set(['codex_setup_host_scope_invalid', 'codex_setup_host_scope_unavailable',
|
|
2517
|
+
'codex_setup_host_context_conflict', 'codex_setup_host_source_mismatch', 'codex_setup_host_command_mismatch']);
|
|
2518
|
+
let code = 'codex_setup_host_operation_failed';
|
|
2519
|
+
try {
|
|
2520
|
+
if (error instanceof Error && safe.has(error.message))
|
|
2521
|
+
code = error.message;
|
|
2522
|
+
}
|
|
2523
|
+
catch { }
|
|
2524
|
+
throw new Error(code);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
async function readBootstrapRuntimeJson(path, scope) {
|
|
2528
|
+
let file;
|
|
2529
|
+
try {
|
|
2530
|
+
const before = await scope.step(() => lstat(path, { bigint: true }));
|
|
2531
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size > 32768n
|
|
2532
|
+
|| before.uid !== BigInt(process.getuid()) || (before.mode & 63n) !== 0n) {
|
|
2533
|
+
throw new Error('setup_runtime_state_unconfirmed');
|
|
2534
|
+
}
|
|
2535
|
+
await scope.step(async () => { file = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK); });
|
|
2536
|
+
const opened = await scope.step(() => file.stat({ bigint: true }));
|
|
2537
|
+
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
2538
|
+
const read = await scope.step(() => file.read(bytes, 0, bytes.length, 0));
|
|
2539
|
+
const after = await scope.step(() => file.stat({ bigint: true }));
|
|
2540
|
+
const current = await scope.step(() => lstat(path, { bigint: true }));
|
|
2541
|
+
if (read.bytesRead !== Number(before.size) || [opened, after, current].some(value => !value.isFile()
|
|
2542
|
+
|| value.nlink !== 1n || value.dev !== before.dev || value.ino !== before.ino || value.size !== before.size
|
|
2543
|
+
|| value.mtimeNs !== before.mtimeNs || value.ctimeNs !== before.ctimeNs || value.mode !== before.mode
|
|
2544
|
+
|| value.uid !== before.uid || value.gid !== before.gid)) {
|
|
2545
|
+
throw new Error('setup_runtime_state_unconfirmed');
|
|
2546
|
+
}
|
|
2547
|
+
return JSON.parse(bytes.subarray(0, read.bytesRead).toString('utf8'));
|
|
2548
|
+
}
|
|
2549
|
+
finally {
|
|
2550
|
+
await file?.close();
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
/** Actual scoped runtime readback. No callback-supplied ready flag or receipt ID. */
|
|
2554
|
+
async function observeCodexBootstrapRuntime(prepared, vault, firstLearning, intentDigest) {
|
|
2555
|
+
const { scope, intent } = prepared, workspace = String(prepared.flags.get('workspace'));
|
|
2556
|
+
const step = (operation) => scope.step(operation);
|
|
2557
|
+
await scope.assert();
|
|
2558
|
+
if (process.platform !== 'linux')
|
|
2559
|
+
throw new Error('setup_runtime_route_unqualified');
|
|
2560
|
+
const source = await step(() => preflightBootstrapWorkspaceIdentity(workspace));
|
|
2561
|
+
await assertBootstrapHostSource(workspace, source.fingerprint);
|
|
2562
|
+
const contract = await step(() => loadAgentFabricOnboardingContract());
|
|
2563
|
+
if (`sha256:${contract.sha256}` !== intent.contractDigest)
|
|
2564
|
+
throw new Error('setup_runtime_contract_changed');
|
|
2565
|
+
const config = await step(() => readDeviceConfig());
|
|
2566
|
+
if (!config || config.organizationId !== intent.organizationId || normalizeHqUrl(config.hqUrl) !== intent.origin) {
|
|
2567
|
+
throw new Error('setup_runtime_identity_unconfirmed');
|
|
2568
|
+
}
|
|
2569
|
+
const enrollment = await step(() => loadDeviceEnrollmentAnchor({ config, hostScope: scope }));
|
|
2570
|
+
const device = await step(() => enrolledDeviceIdentity(config));
|
|
2571
|
+
if (device.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
|
|
2572
|
+
throw new Error('setup_runtime_identity_unconfirmed');
|
|
2573
|
+
const rows = (await step(() => registry())).filter(row => row.path === workspace
|
|
2574
|
+
&& row.organizationId === intent.organizationId && row.repositoryRemoteHash === intent.repositoryFingerprint && row.status === 'active');
|
|
2575
|
+
if (rows.length !== 1 || !rows[0].repositoryRole)
|
|
2576
|
+
throw new Error('setup_runtime_repository_unconfirmed');
|
|
2577
|
+
const item = rows[0], roleScope = repositoryRoleScope(item);
|
|
2578
|
+
const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
|
|
2579
|
+
const policy = await step(() => loadVerifiedWorkspacePolicy(policyPath, item.workspaceId));
|
|
2580
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
2581
|
+
organizationId: intent.organizationId, workspaceId: item.workspaceId });
|
|
2582
|
+
await step(() => assertWorkspaceAuthorizationCurrent(item.workspaceId, policy.serverAuthorization));
|
|
2583
|
+
if (policy.revision !== intent.policyRevision)
|
|
2584
|
+
throw new Error('setup_runtime_policy_changed');
|
|
2585
|
+
const signed = await step(() => activeSkillAuthorization('codex', item.workspaceId, roleScope.repositoryAgentId, config));
|
|
2586
|
+
const anchor = await step(() => loadActiveSkillAuthorizationAnchor({ config, workspaceId: item.workspaceId,
|
|
2587
|
+
organizationAgentId: roleScope.repositoryAgentId, provider: 'codex', fresh: true, hostScope: scope }));
|
|
2588
|
+
if (!signed || !anchor || signed.bundleId !== anchor.bundleId)
|
|
2589
|
+
throw new Error('setup_runtime_package_unconfirmed');
|
|
2590
|
+
const installation = await step(() => verifyAgentFabricSkillInstallation({ provider: 'codex', workspace }));
|
|
2591
|
+
const content = await step(() => readNamedSessionPackageContent(installation, true));
|
|
2592
|
+
if (!installation.ready || content.bundleId !== signed.bundleId || content.bundleHash !== signed.bundleHash) {
|
|
2593
|
+
throw new Error('setup_runtime_package_unconfirmed');
|
|
2594
|
+
}
|
|
2595
|
+
const fabric = await step(() => client());
|
|
2596
|
+
const roles = await step(() => discoverRepositoryRoleMetadata(fabric, roleScope));
|
|
2597
|
+
const peers = roles.discovery?.peers;
|
|
2598
|
+
const own = peers?.filter(peer => peer.endpointId === roleScope.endpointId && peer.workspaceId === item.workspaceId);
|
|
2599
|
+
if (own?.length !== 1 || own[0].provider !== 'codex' || own[0].revision !== item.repositoryRole.revision
|
|
2600
|
+
|| sha256(canonicalize({ roleName: own[0].roleName, description: own[0].description,
|
|
2601
|
+
questionCategories: own[0].questionCategories })) !== item.repositoryRole.profileHash) {
|
|
2602
|
+
throw new Error('setup_runtime_role_unconfirmed');
|
|
2603
|
+
}
|
|
2604
|
+
const name = `codex-${item.workspaceId.slice(0, 8)}`;
|
|
2605
|
+
const session = await step(() => readNamedSession(dharmaHome(), name));
|
|
2606
|
+
if (!session?.enabled || session.identity.membershipId !== intent.recipientMembershipId
|
|
2607
|
+
|| session.identity.deviceId !== config.deviceId || session.identity.organizationId !== intent.organizationId
|
|
2608
|
+
|| session.identity.workspaceId !== item.workspaceId || session.identity.endpointId !== roleScope.endpointId
|
|
2609
|
+
|| session.identity.repositoryBindingId !== roleScope.repositoryBindingId)
|
|
2610
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2611
|
+
const binding = await step(() => vault.getProviderSessionBinding(session.bindingId, session.identity));
|
|
2612
|
+
if (!binding || binding.owner !== 'dharma_bridge' || binding.workspaceRoot !== workspace
|
|
2613
|
+
|| binding.bindingId !== session.bindingId || Object.entries(session.identity).some(([key, value]) => binding[key] !== value)
|
|
2614
|
+
|| Date.parse(binding.expiresAt) <= Date.now())
|
|
2615
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2616
|
+
const remote = await step(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
2617
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
2618
|
+
workspaceId: item.workspaceId, endpointId: roleScope.endpointId, repositoryBindingId: roleScope.repositoryBindingId,
|
|
2619
|
+
bindingId: session.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
2620
|
+
}));
|
|
2621
|
+
if (!isNamedSessionOwnerReceipt(remote, session.bindingId, session.identity))
|
|
2622
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2623
|
+
const native = await step(() => namedSessionCommand('readiness', new Map([
|
|
2624
|
+
['name', name], ['workspace-id', item.workspaceId],
|
|
2625
|
+
])));
|
|
2626
|
+
const nativeSkill = parseNamedCodexSkillObservation(native.nativeSkill);
|
|
2627
|
+
if (native.ok !== true || native.name !== name || native.bindingId !== binding.bindingId || native.sessionId !== binding.sessionId
|
|
2628
|
+
|| Object.entries(session.identity).some(([key, value]) => native[key] !== value)
|
|
2629
|
+
|| Object.keys(content).some(key => nativeSkill[key] !== content[key])) {
|
|
2630
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2631
|
+
}
|
|
2632
|
+
const startup = await step(() => inspectOwnedRelayAutostart({ home: dharmaHome() }));
|
|
2633
|
+
const startupState = await step(() => relayAutostartStatus({ home: dharmaHome() }));
|
|
2634
|
+
if (!['systemd-user', 'container-entrypoint'].includes(startup.backend) || startupState.state !== 'enabled'
|
|
2635
|
+
|| startupState.backend !== startup.backend || startup.version !== VERSION || startupState.version !== VERSION
|
|
2636
|
+
|| resolve(startup.workspace) !== startup.workspace
|
|
2637
|
+
|| startup.policy !== resolve(startup.workspace, '.dharma', 'approved-policy.json')
|
|
2638
|
+
|| startup.launcher !== resolve(startup.workspace, '.dharma', 'bin', 'dharma')
|
|
2639
|
+
|| startup.backend === 'container-entrypoint' && startupState.lifecycle !== 'running') {
|
|
2640
|
+
throw new Error('setup_runtime_startup_unconfirmed');
|
|
2641
|
+
}
|
|
2642
|
+
// A standard relay can retain another repository as its same-device anchor.
|
|
2643
|
+
// Verify that anchor independently; selected-repository evidence stays scoped.
|
|
2644
|
+
const anchorRows = await step(() => registry());
|
|
2645
|
+
const startupItem = selectDeviceWorkspace(anchorRows, { organizationId: intent.organizationId,
|
|
2646
|
+
deviceId: config.deviceId, path: startup.workspace });
|
|
2647
|
+
if (!startupItem || startupItem.status !== 'active' || startupItem.routeHash !== sha256(startup.workspace)) {
|
|
2648
|
+
throw new Error('setup_runtime_startup_unconfirmed');
|
|
2649
|
+
}
|
|
2650
|
+
const startupRoute = canonicalize({ workspaceId: startupItem.workspaceId, path: startupItem.path,
|
|
2651
|
+
routeHash: startupItem.routeHash, repositoryRemoteHash: startupItem.repositoryRemoteHash, status: startupItem.status });
|
|
2652
|
+
const startupPolicy = await step(() => loadVerifiedWorkspacePolicy(startup.policy, startupItem.workspaceId));
|
|
2653
|
+
verifyServerAuthorizedPolicy({ policy: startupPolicy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
2654
|
+
organizationId: intent.organizationId, workspaceId: startupItem.workspaceId });
|
|
2655
|
+
await step(() => assertWorkspaceAuthorizationCurrent(startupItem.workspaceId, startupPolicy.serverAuthorization));
|
|
2656
|
+
const startupSnapshot = canonicalize(startup), startupStateSnapshot = canonicalize(startupState);
|
|
2657
|
+
const startupPolicyHash = sha256(canonicalize(startupPolicy));
|
|
2658
|
+
const pidFrom = async (path) => {
|
|
2659
|
+
const value = await readBootstrapRuntimeJson(path, scope);
|
|
2660
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1 || Number(value) > 2147483647) {
|
|
2661
|
+
throw new Error('setup_runtime_process_unconfirmed');
|
|
2662
|
+
}
|
|
2663
|
+
return Number(value);
|
|
2664
|
+
};
|
|
2665
|
+
const home = dharmaHome(), entry = fileURLToPath(import.meta.url);
|
|
2666
|
+
const supervisorPid = await pidFrom(resolve(home, 'relay', 'supervisor.pid'));
|
|
2667
|
+
const relayPid = await pidFrom(resolve(home, 'relay', 'relay.pid'));
|
|
2668
|
+
const sessionPid = await pidFrom(resolve(namedSessionPaths(home, name).root, 'service.lock'));
|
|
2669
|
+
const processes = await step(() => Promise.all([supervisorPid, relayPid, sessionPid].map(pid => readContainerProcessIdentity(pid))));
|
|
2670
|
+
const argv = [[process.execPath, entry, 'relay', 'supervise', '--policy', startup.policy],
|
|
2671
|
+
[process.execPath, entry, 'relay', 'start', '--policy', startup.policy],
|
|
2672
|
+
[process.execPath, entry, 'sessions', 'serve', '--name', name, '--workspace-id', item.workspaceId, '--apply']];
|
|
2673
|
+
if (processes.some((value, i) => value.uid !== process.getuid() || !/^\d+$/.test(value.startTicks)
|
|
2674
|
+
|| canonicalize(value.argv) !== canonicalize(argv[i])
|
|
2675
|
+
&& (i !== 2 || canonicalize(value.argv) !== canonicalize([...argv[i], '--setup-handoff']))) || processes[1].parentPid !== supervisorPid
|
|
2676
|
+
|| processes[2].parentPid !== supervisorPid) {
|
|
2677
|
+
throw new Error('setup_runtime_process_unconfirmed');
|
|
2678
|
+
}
|
|
2679
|
+
if (intentDigest !== undefined || processes[2].argv.includes('--setup-handoff')) {
|
|
2680
|
+
if (!intentDigest || !/^sha256:[a-f0-9]{64}$(?![\s\S])/.test(intentDigest))
|
|
2681
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2682
|
+
const handoff = await step(() => vault.readCodexSetupSession(intent.operationId, intentDigest));
|
|
2683
|
+
const request = handoff?.request, result = handoff?.result;
|
|
2684
|
+
if (handoff?.state !== 'accepted' || !request || result?.state !== 'started'
|
|
2685
|
+
|| request.setupReference !== intent.setupReference || request.membershipId !== intent.recipientMembershipId
|
|
2686
|
+
|| request.organizationId !== intent.organizationId || request.deviceId !== config.deviceId
|
|
2687
|
+
|| request.workspaceId !== item.workspaceId || request.workspaceRoot !== workspace || request.name !== name
|
|
2688
|
+
|| request.repositoryBindingId !== roleScope.repositoryBindingId || request.endpointId !== roleScope.endpointId
|
|
2689
|
+
|| request.repositoryFingerprint !== intent.repositoryFingerprint || request.policyRevision !== intent.policyRevision
|
|
2690
|
+
|| request.scopeDigest !== intent.scopeDigest || request.contractDigest !== intent.contractDigest
|
|
2691
|
+
|| result.bindingId !== binding.bindingId || result.sessionId !== binding.sessionId || result.sessionPid !== sessionPid
|
|
2692
|
+
|| result.supervisorPid !== supervisorPid || result.sessionStartTicks !== processes[2].startTicks
|
|
2693
|
+
|| result.supervisorStartTicks !== processes[0].startTicks)
|
|
2694
|
+
throw new Error('setup_runtime_session_unconfirmed');
|
|
2695
|
+
}
|
|
2696
|
+
const supervisor = await readBootstrapRuntimeJson(resolve(home, 'relay', 'supervisor-workspace.json'), scope);
|
|
2697
|
+
if (supervisor.pid !== supervisorPid || supervisor.organizationId !== intent.organizationId || supervisor.deviceId !== config.deviceId
|
|
2698
|
+
|| supervisor.policyPath !== startup.policy || supervisor.version !== VERSION || supervisor.standardRepositories !== true) {
|
|
2699
|
+
throw new Error('setup_runtime_process_unconfirmed');
|
|
2700
|
+
}
|
|
2701
|
+
const poll = await readBootstrapRuntimeJson(resolve(home, 'relay', 'repositories', item.workspaceId, 'last-successful-poll.json'), scope);
|
|
2702
|
+
if (!repositoryRelayObservationReady({ observation: poll, workspaceId: item.workspaceId, version: VERSION, pid: relayPid })) {
|
|
2703
|
+
throw new Error('setup_runtime_poll_unconfirmed');
|
|
2704
|
+
}
|
|
2705
|
+
const currentContent = await step(async () => readNamedSessionPackageContent(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace }), true));
|
|
2706
|
+
if (Object.keys(content).some(key => currentContent[key] !== content[key])) {
|
|
2707
|
+
throw new Error('setup_runtime_package_changed');
|
|
2708
|
+
}
|
|
2709
|
+
const currentProcesses = await step(() => Promise.all([supervisorPid, relayPid, sessionPid].map(pid => readContainerProcessIdentity(pid))));
|
|
2710
|
+
if (processes.some((value, i) => canonicalize(value) !== canonicalize(currentProcesses[i])))
|
|
2711
|
+
throw new Error('setup_runtime_process_changed');
|
|
2712
|
+
const currentStartup = await step(() => inspectOwnedRelayAutostart({ home }));
|
|
2713
|
+
const currentStartupState = await step(() => relayAutostartStatus({ home }));
|
|
2714
|
+
const currentStartupItem = selectDeviceWorkspace(await step(() => registry()), { organizationId: intent.organizationId,
|
|
2715
|
+
deviceId: config.deviceId, path: startup.workspace });
|
|
2716
|
+
const currentConfig = await step(() => readDeviceConfig());
|
|
2717
|
+
if (canonicalize(currentStartup) !== startupSnapshot || canonicalize(currentStartupState) !== startupStateSnapshot
|
|
2718
|
+
|| !currentStartupItem || canonicalize({ workspaceId: currentStartupItem.workspaceId, path: currentStartupItem.path,
|
|
2719
|
+
routeHash: currentStartupItem.routeHash, repositoryRemoteHash: currentStartupItem.repositoryRemoteHash,
|
|
2720
|
+
status: currentStartupItem.status }) !== startupRoute
|
|
2721
|
+
|| !currentConfig || currentConfig.deviceId !== config.deviceId || currentConfig.organizationId !== intent.organizationId
|
|
2722
|
+
|| normalizeHqUrl(currentConfig.hqUrl) !== intent.origin)
|
|
2723
|
+
throw new Error('setup_runtime_startup_changed');
|
|
2724
|
+
const currentStartupPolicy = await step(() => loadVerifiedWorkspacePolicy(startup.policy, startupItem.workspaceId));
|
|
2725
|
+
if (sha256(canonicalize(currentStartupPolicy)) !== startupPolicyHash)
|
|
2726
|
+
throw new Error('setup_runtime_startup_changed');
|
|
2727
|
+
await step(() => assertWorkspaceAuthorizationCurrent(startupItem.workspaceId, currentStartupPolicy.serverAuthorization));
|
|
2728
|
+
await step(() => assertWorkspaceAuthorizationCurrent(item.workspaceId, policy.serverAuthorization));
|
|
2729
|
+
return parseLocalCodexSetupReadiness({ schema: 'dharma.local-codex-setup-readiness/v1',
|
|
2730
|
+
operationId: intent.operationId, organizationId: intent.organizationId, membershipId: session.identity.membershipId,
|
|
2731
|
+
deviceId: config.deviceId, workspaceId: item.workspaceId, repositoryBindingId: roleScope.repositoryBindingId,
|
|
2732
|
+
repositoryAgentId: roleScope.repositoryAgentId, endpointId: roleScope.endpointId, sessionBindingId: binding.bindingId,
|
|
2733
|
+
sessionId: binding.sessionId, repositoryFingerprint: intent.repositoryFingerprint, policyRevision: policy.revision,
|
|
2734
|
+
policyHash: sha256(canonicalize(policy)), manifestHash: content.manifestHash, catalogHash: content.catalogHash,
|
|
2735
|
+
bundleId: content.bundleId, bundleHash: content.bundleHash, activeReceiptHash: anchor.receiptHash,
|
|
2736
|
+
roleRevision: item.repositoryRole.revision, roleProfileHash: item.repositoryRole.profileHash, nativeSkillHash: content.skillsHash,
|
|
2737
|
+
contractDigest: intent.contractDigest, cliVersion: VERSION, relayPid, relayPolledAt: poll.at,
|
|
2738
|
+
startupBackend: startup.backend, firstLearning, verifiedAt: new Date().toISOString(),
|
|
2739
|
+
expiresAt: new Date(Math.min(Date.parse(intent.expiresAt), Date.parse(policy.serverAuthorization.expiresAt), Date.parse(startupPolicy.serverAuthorization.expiresAt), Date.parse(binding.expiresAt), signed.expiresAt ? Date.parse(signed.expiresAt) : Infinity)).toISOString() });
|
|
2740
|
+
}
|
|
2741
|
+
/** Trusted native host composition, not an MCP/model/peer tool. */
|
|
2742
|
+
export async function createCodexBootstrapCompletionOwner(scope, vault) {
|
|
2743
|
+
const prepared = await runCodexBootstrapHostScope(scope, async (value) => value);
|
|
2744
|
+
let firstLearning;
|
|
2745
|
+
const readiness = createCodexSetupReadinessOwner({ intent: prepared.intent,
|
|
2746
|
+
workspace: String(prepared.flags.get('workspace')), scope, vault,
|
|
2747
|
+
observe: async (_intent, _scope, retained, intentDigest) => {
|
|
2748
|
+
const disposition = firstLearning ?? retained?.firstLearning;
|
|
2749
|
+
if (!disposition)
|
|
2750
|
+
throw new Error('setup_runtime_first_learning_unconfirmed');
|
|
2751
|
+
// Keep the original execution's disposition; reobserve all live state.
|
|
2752
|
+
return runCodexBootstrapHostScope(scope, current => observeCodexBootstrapRuntime(current, vault, disposition, intentDigest));
|
|
2753
|
+
} });
|
|
2754
|
+
return Object.freeze({
|
|
2755
|
+
execute: async (lease) => {
|
|
2756
|
+
await scope.step(() => assertCodexSetupExecutionLease(lease, prepared.intent));
|
|
2757
|
+
const result = await withCodexSetupSessionSender({ scope, vault, lease }, () => bootstrapFromCodexSetupScope(scope));
|
|
2758
|
+
await scope.step(() => assertCodexSetupExecutionLease(lease, prepared.intent));
|
|
2759
|
+
const workflow = result.workflowReadiness;
|
|
2760
|
+
if (result.ok !== true || result.stage !== 'complete' || !workflow
|
|
2761
|
+
|| !['synchronized', 'no_eligible_history', 'denied_disclosure'].includes(String(workflow.firstLearning))) {
|
|
2762
|
+
return { state: 'unconfirmed', code: 'setup_execution_unconfirmed' };
|
|
2763
|
+
}
|
|
2764
|
+
firstLearning = workflow.firstLearning;
|
|
2765
|
+
return readiness.record(lease);
|
|
2766
|
+
},
|
|
2767
|
+
verify: readiness.verify,
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
/** Controller entry: owns the official connection and never accepts a peer transport. */
|
|
2771
|
+
export async function openCodexBootstrapNativeHost(input) {
|
|
2772
|
+
if (currentBootstrapHostScope())
|
|
2773
|
+
throw new Error('codex_setup_host_context_conflict');
|
|
2774
|
+
const { workspace, name, signal, current, reserve, maximumProviderCostCents, expectedAccountEmail } = input;
|
|
2775
|
+
if (!/^[a-z][a-z0-9-]{0,47}$/.test(name) || typeof reserve !== 'function'
|
|
2776
|
+
|| !Number.isInteger(maximumProviderCostCents) || maximumProviderCostCents < 1 || maximumProviderCostCents > 10_000) {
|
|
2777
|
+
throw new Error('codex_setup_owned_input_invalid');
|
|
2778
|
+
}
|
|
2779
|
+
const prepared = prepareCodexBootstrapHost({ intent: input.intent, workspace, signal, current });
|
|
2780
|
+
let connection;
|
|
2781
|
+
let host;
|
|
2782
|
+
try {
|
|
2783
|
+
await prepared.scope.assert();
|
|
2784
|
+
const source = await preflightBootstrapWorkspaceIdentity(workspace);
|
|
2785
|
+
const contract = await loadAgentFabricOnboardingContract();
|
|
2786
|
+
if (source.fingerprint !== prepared.intent.repositoryFingerprint || `sha256:${contract.sha256}` !== prepared.intent.contractDigest) {
|
|
2787
|
+
throw new Error('codex_setup_owned_source_unconfirmed');
|
|
2788
|
+
}
|
|
2789
|
+
await prepared.scope.assert();
|
|
2790
|
+
connection = await openCodexSetupOwnedConnection({ workspace, deviceHome: dharmaHome(),
|
|
2791
|
+
expectedAccountEmail, signal, current });
|
|
2792
|
+
await prepared.scope.assert();
|
|
2793
|
+
host = await startCodexBootstrapNativeHost({ workspace, name,
|
|
2794
|
+
intent: prepared.intent, signal, maximumProviderCostCents,
|
|
2795
|
+
reserve, current: connection.current, transport: connection.transport,
|
|
2796
|
+
additionalFilesystemRules: connection.additionalFilesystemRules });
|
|
2797
|
+
await prepared.scope.assert();
|
|
2798
|
+
const ownedHost = host, ownedConnection = connection;
|
|
2799
|
+
let closing;
|
|
2800
|
+
const close = () => closing ??= (async () => {
|
|
2801
|
+
try {
|
|
2802
|
+
await ownedHost.close();
|
|
2803
|
+
}
|
|
2804
|
+
finally {
|
|
2805
|
+
await ownedConnection.close();
|
|
2806
|
+
}
|
|
2807
|
+
})();
|
|
2808
|
+
// A terminal native turn must also release its owned transport.
|
|
2809
|
+
const settled = ownedHost.settled.then(close, async () => {
|
|
2810
|
+
try {
|
|
2811
|
+
await close();
|
|
2812
|
+
}
|
|
2813
|
+
finally {
|
|
2814
|
+
throw new Error('codex_setup_owned_completion_unconfirmed');
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2817
|
+
void settled.catch(() => { });
|
|
2818
|
+
return Object.freeze({ threadId: host.threadId, turnId: host.turnId, close, settled });
|
|
2819
|
+
}
|
|
2820
|
+
catch {
|
|
2821
|
+
let cleanupFailed = false;
|
|
2822
|
+
try {
|
|
2823
|
+
await host?.close();
|
|
2824
|
+
}
|
|
2825
|
+
catch {
|
|
2826
|
+
cleanupFailed = true;
|
|
2827
|
+
}
|
|
2828
|
+
try {
|
|
2829
|
+
await connection?.close();
|
|
2830
|
+
}
|
|
2831
|
+
catch {
|
|
2832
|
+
cleanupFailed = true;
|
|
2833
|
+
}
|
|
2834
|
+
if (cleanupFailed)
|
|
2835
|
+
throw new Error('codex_setup_owned_close_unconfirmed');
|
|
2836
|
+
throw new Error('codex_setup_owned_start_unqualified');
|
|
2837
|
+
}
|
|
2838
|
+
finally {
|
|
2839
|
+
prepared.scope.close();
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
/** Prepared controller ingress for the portal's public command, never a shell evaluator. */
|
|
2843
|
+
export async function openCodexBootstrapFromPortalCommand(input) {
|
|
2844
|
+
if (currentBootstrapHostScope())
|
|
2845
|
+
throw new Error('codex_setup_host_context_conflict');
|
|
2846
|
+
const { workspace, name, signal, current, reserve, maximumProviderCostCents, expectedAccountEmail } = input;
|
|
2847
|
+
if (!Array.isArray(input.portalArgs) || input.portalArgs.length > 64) {
|
|
2848
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2849
|
+
}
|
|
2850
|
+
const args = [...input.portalArgs];
|
|
2851
|
+
if (args.some(value => typeof value !== 'string' || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))) {
|
|
2852
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2853
|
+
}
|
|
2854
|
+
const prepared = prepareCodexBootstrapHost({ intent: input.intent, workspace, signal, current });
|
|
2855
|
+
try {
|
|
2856
|
+
const parsed = parseCliOptions(args);
|
|
2857
|
+
const expected = prepared.flags;
|
|
2858
|
+
const allowed = new Set([...expected.keys(), 'repository-url-base64url']);
|
|
2859
|
+
if (parsed.positional.length !== 1 || parsed.positional[0] !== 'bootstrap'
|
|
2860
|
+
|| [...parsed.flags.keys()].some(key => !allowed.has(key))
|
|
2861
|
+
|| [...parsed.repeated.values()].some(values => values.length !== 1)) {
|
|
2862
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2863
|
+
}
|
|
2864
|
+
for (const [key, value] of expected) {
|
|
2865
|
+
const supplied = parsed.flags.get(key);
|
|
2866
|
+
if (key === 'workspace') {
|
|
2867
|
+
if (supplied !== undefined && (typeof supplied !== 'string' || resolve(workspace, supplied) !== workspace)) {
|
|
2868
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
else if (key === 'provider') {
|
|
2872
|
+
if (supplied !== undefined && supplied !== 'auto' && supplied !== 'codex')
|
|
2873
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2874
|
+
}
|
|
2875
|
+
else if (supplied !== value)
|
|
2876
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2877
|
+
}
|
|
2878
|
+
const selected = parsed.flags.get('repository-url-base64url');
|
|
2879
|
+
if (selected !== undefined) {
|
|
2880
|
+
if (typeof selected !== 'string' || !/^[A-Za-z0-9_-]+$/.test(selected))
|
|
2881
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2882
|
+
const bytes = Buffer.from(selected, 'base64url'), decoded = bytes.toString('utf8');
|
|
2883
|
+
let url, fingerprint;
|
|
2884
|
+
try {
|
|
2885
|
+
url = new URL(decoded);
|
|
2886
|
+
fingerprint = sourceRepositoryFingerprint(decoded).fingerprint;
|
|
2887
|
+
}
|
|
2888
|
+
catch {
|
|
2889
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2890
|
+
}
|
|
2891
|
+
if (bytes.toString('base64url') !== selected || !Buffer.from(decoded, 'utf8').equals(bytes)
|
|
2892
|
+
|| /[\u0000-\u0020\u007f]/.test(decoded) || url.protocol !== 'https:' || url.username || url.password || url.search || url.hash
|
|
2893
|
+
|| fingerprint !== prepared.intent.repositoryFingerprint) {
|
|
2894
|
+
throw new Error('codex_setup_portal_command_mismatch');
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
await prepared.scope.assert();
|
|
2898
|
+
return await openCodexBootstrapNativeHost({ workspace, name, signal, current, reserve, maximumProviderCostCents,
|
|
2899
|
+
expectedAccountEmail, intent: prepared.intent });
|
|
2900
|
+
}
|
|
2901
|
+
finally {
|
|
2902
|
+
prepared.scope.close();
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
/** Official native-host composition. The caller supplies its already owned
|
|
2906
|
+
* transport and qualification, never a vault, executor or readiness callback. */
|
|
2907
|
+
export async function startCodexBootstrapNativeHost(input) {
|
|
2908
|
+
const root = resolve(dharmaHome(), 'vault');
|
|
2909
|
+
let completion;
|
|
2910
|
+
let ownerScope;
|
|
2911
|
+
return startCodexSetupNativeHost({ ...input,
|
|
2912
|
+
openJournal: async (scope) => {
|
|
2913
|
+
let vault;
|
|
2914
|
+
try {
|
|
2915
|
+
await runCodexBootstrapHostScope(scope, async () => {
|
|
2916
|
+
// Capture the owned handle before the borrower's post-open check.
|
|
2917
|
+
// The original ALS scope selects the scoped LocalVault overload.
|
|
2918
|
+
vault = await openBootstrapVault({ root });
|
|
2919
|
+
});
|
|
2920
|
+
completion = await createCodexBootstrapCompletionOwner(scope, vault);
|
|
2921
|
+
ownerScope = scope;
|
|
2922
|
+
return Object.freeze({
|
|
2923
|
+
claim: (operationId, intentDigest) => vault.claimCodexSetupOperation(operationId, intentDigest),
|
|
2924
|
+
read: (operationId, intentDigest) => vault.readCodexSetupOperation(operationId, intentDigest),
|
|
2925
|
+
finish: (leaseId, intentDigest, result) => vault.finishCodexSetupOperation(leaseId, intentDigest, result),
|
|
2926
|
+
close: async () => { await vault.close(); },
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
catch (error) {
|
|
2930
|
+
try {
|
|
2931
|
+
await vault?.close();
|
|
2932
|
+
}
|
|
2933
|
+
catch {
|
|
2934
|
+
throw new Error('codex_setup_native_journal_close_unconfirmed');
|
|
2935
|
+
}
|
|
2936
|
+
throw error;
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
2939
|
+
execute: async (_intent, _signal, _current, lease, scope) => {
|
|
2940
|
+
if (!completion || scope !== ownerScope)
|
|
2941
|
+
throw new Error('codex_setup_host_scope_unavailable');
|
|
2942
|
+
return completion.execute(lease);
|
|
2943
|
+
},
|
|
2944
|
+
verifyReadiness: async (receiptId, intent, digest) => {
|
|
2945
|
+
if (!completion)
|
|
2946
|
+
return false;
|
|
2947
|
+
return completion.verify(receiptId, intent, digest);
|
|
2948
|
+
}, });
|
|
2949
|
+
}
|
|
2950
|
+
async function bootstrap(flags, hostScope) {
|
|
2951
|
+
const step = (operation) => hostScope ? hostScope.step(operation) : operation();
|
|
2952
|
+
await hostScope?.assert();
|
|
2065
2953
|
const hqUrl = normalizeHqUrl(portalUrl(flags));
|
|
2066
2954
|
const organizationId = required(flags, 'organization-id');
|
|
2067
2955
|
const resuming = flags.has('resume');
|
|
@@ -2088,10 +2976,21 @@ async function bootstrap(flags) {
|
|
|
2088
2976
|
if (!/^sha256:[a-f0-9]{64}$/.test(required(flags, key)))
|
|
2089
2977
|
throw new Error('setup_claim_context_invalid');
|
|
2090
2978
|
}
|
|
2091
|
-
const installedContract = await loadAgentFabricOnboardingContract();
|
|
2979
|
+
const installedContract = await step(() => loadAgentFabricOnboardingContract());
|
|
2092
2980
|
if (required(flags, 'setup-contract-digest') !== `sha256:${installedContract.sha256}`) {
|
|
2093
2981
|
throw new Error('setup_claim_contract_mismatch: the portal setup must match this published client operating contract.');
|
|
2094
2982
|
}
|
|
2983
|
+
// A prepared scope alone is not execution admission. Only the original
|
|
2984
|
+
// native turn's still-live private lease may enter this continuation.
|
|
2985
|
+
if (hostScope && !flags.has('dry-run')) {
|
|
2986
|
+
try {
|
|
2987
|
+
await originalCodexSetupSessionSender(hostScope);
|
|
2988
|
+
}
|
|
2989
|
+
catch {
|
|
2990
|
+
return { ok: false, stage: 'host_setup_unavailable',
|
|
2991
|
+
code: 'codex_setup_host_execution_unqualified', effects: false, grantRedeemed: false };
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2095
2994
|
if (flags.has('dry-run'))
|
|
2096
2995
|
return { ok: true, stage: 'plan', setupTransport: 'public_claim_v1',
|
|
2097
2996
|
organizationId, portalUrl: hqUrl, setupReference: required(flags, 'setup-reference'),
|
|
@@ -2115,15 +3014,15 @@ async function bootstrap(flags) {
|
|
|
2115
3014
|
repositorySelection = joinedBindingId
|
|
2116
3015
|
? { workspace: resolve(dharmaHome(), 'joined-repositories', joinedBindingId), selection: 'existing_organization_binding' }
|
|
2117
3016
|
: typeof selectedRemote === 'string'
|
|
2118
|
-
? await resolveBootstrapRepositoryWorkspace({
|
|
3017
|
+
? await step(() => resolveBootstrapRepositoryWorkspace({
|
|
2119
3018
|
workspace: String(flags.get('workspace') || '.'),
|
|
2120
3019
|
selectedRemoteBase64url: selectedRemote,
|
|
2121
3020
|
organizationId,
|
|
2122
3021
|
home: dharmaHome(),
|
|
2123
3022
|
normalizeRemote: normalizeGitRemoteIdentity,
|
|
2124
3023
|
withLock: (path) => acquirePidLock(path, 30_000, 'repository_checkout_busy: another setup is selecting this repository; retry after it finishes.'),
|
|
2125
|
-
})
|
|
2126
|
-
: { workspace: await realpath(String(flags.get('workspace') || '.')), selection: 'provided' };
|
|
3024
|
+
}))
|
|
3025
|
+
: { workspace: await step(() => realpath(String(flags.get('workspace') || '.'))), selection: 'provided' };
|
|
2127
3026
|
}
|
|
2128
3027
|
catch (error) {
|
|
2129
3028
|
const message = error && typeof error === 'object' && 'message' in error && typeof error.message === 'string'
|
|
@@ -2140,15 +3039,16 @@ async function bootstrap(flags) {
|
|
|
2140
3039
|
const policyRevision = required(flags, 'policy-revision');
|
|
2141
3040
|
const repositoryIdentity = joinedBindingId
|
|
2142
3041
|
? { fingerprint: joinedFingerprint }
|
|
2143
|
-
: await preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null);
|
|
3042
|
+
: await step(() => preflightBootstrapWorkspaceIdentity(workspace, typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null));
|
|
2144
3043
|
const requestedProvider = String(flags.get('provider') || 'auto').trim().toLowerCase();
|
|
3044
|
+
await assertBootstrapHostSource(workspace, repositoryIdentity.fingerprint);
|
|
2145
3045
|
const provider = requestedProvider === 'auto'
|
|
2146
|
-
? await detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace)
|
|
3046
|
+
? await step(() => detectBootstrapProvider(joinedBindingId ? String(flags.get('workspace') || '.') : workspace))
|
|
2147
3047
|
: requestedProvider;
|
|
2148
3048
|
if (!isLocalProviderId(provider)) {
|
|
2149
3049
|
throw new Error('Bootstrap provider must be auto, codex, claude, agy, or hermes.');
|
|
2150
3050
|
}
|
|
2151
|
-
let existing = await readDeviceConfig();
|
|
3051
|
+
let existing = await step(() => readDeviceConfig());
|
|
2152
3052
|
const enrollmentMismatch = Boolean(existing
|
|
2153
3053
|
&& (existing.organizationId !== organizationId || normalizeHqUrl(existing.hqUrl) !== hqUrl));
|
|
2154
3054
|
if (resuming)
|
|
@@ -2171,20 +3071,27 @@ async function bootstrap(flags) {
|
|
|
2171
3071
|
config = existing;
|
|
2172
3072
|
}
|
|
2173
3073
|
else if (grantMode === 'reference') {
|
|
2174
|
-
const
|
|
3074
|
+
const claimPlatform = await step(() => platform());
|
|
3075
|
+
const claimInstallationId = existing?.installationId ?? await step(() => loadOrCreateInstallationId());
|
|
3076
|
+
const result = await step(() => claimSetupReference({ hqUrl, organizationId,
|
|
2175
3077
|
setupReference: required(flags, 'setup-reference'),
|
|
2176
3078
|
recipientMembershipId: required(flags, 'setup-recipient-membership-id'),
|
|
2177
3079
|
repositoryFingerprint: repositoryIdentity.fingerprint, policyRevision,
|
|
2178
3080
|
scopeDigest: required(flags, 'setup-scope-digest'), contractDigest: required(flags, 'setup-contract-digest'),
|
|
2179
3081
|
name: String(flags.get('device-name') || `${process.env.USER || process.env.USERNAME || 'developer'} device`),
|
|
2180
|
-
platform:
|
|
3082
|
+
platform: claimPlatform, installationId: claimInstallationId,
|
|
2181
3083
|
existingConfig: existing, configPath: configPath(),
|
|
3084
|
+
hostScope,
|
|
3085
|
+
onFailureDiagnostic: diagnostic => {
|
|
3086
|
+
process.stderr.write(`${JSON.stringify(diagnostic)}\n`);
|
|
3087
|
+
},
|
|
2182
3088
|
onRecipientApprovalRequired: async (approval) => {
|
|
2183
3089
|
recipientApproval.required = true;
|
|
2184
|
-
recipientApproval.browserOpened = flags.has('no-browser') ? false : await openVerificationUri(approval.url);
|
|
3090
|
+
recipientApproval.browserOpened = flags.has('no-browser') ? false : await step(() => openVerificationUri(approval.url));
|
|
3091
|
+
await hostScope?.assert();
|
|
2185
3092
|
process.stderr.write(`Approve this exact device in the authenticated portal before ${approval.expiresAt}: ${approval.url}\n`);
|
|
2186
3093
|
},
|
|
2187
|
-
});
|
|
3094
|
+
}));
|
|
2188
3095
|
config = result.config;
|
|
2189
3096
|
scopes = result.scopes;
|
|
2190
3097
|
}
|
|
@@ -2254,22 +3161,24 @@ async function bootstrap(flags) {
|
|
|
2254
3161
|
config.serverSigningKeyset = redeemed.serverSigningKeyset;
|
|
2255
3162
|
}
|
|
2256
3163
|
await saveDeviceConfig(configPath(), config);
|
|
2257
|
-
await saveDeviceEnrollmentAnchor({ config });
|
|
3164
|
+
await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
|
|
2258
3165
|
await saveOrganizationApiToken({
|
|
2259
3166
|
hqUrl,
|
|
2260
3167
|
organizationId,
|
|
2261
3168
|
installationId: config.installationId,
|
|
2262
3169
|
token: redeemed.organizationApiToken,
|
|
3170
|
+
hostScope: currentBootstrapHostScope(),
|
|
2263
3171
|
});
|
|
2264
3172
|
scopes = redeemed.organizationApiTokenScopes;
|
|
2265
3173
|
}
|
|
2266
3174
|
let organizationApiTokenStored = false;
|
|
2267
3175
|
let credentialFailure = null;
|
|
2268
3176
|
try {
|
|
2269
|
-
organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
|
|
3177
|
+
organizationApiTokenStored = Boolean(await step(() => loadOrganizationApiToken({
|
|
2270
3178
|
hqUrl, organizationId, installationId: config.installationId,
|
|
2271
|
-
|
|
2272
|
-
|
|
3179
|
+
hostScope: currentBootstrapHostScope(),
|
|
3180
|
+
})));
|
|
3181
|
+
if (!organizationApiTokenStored && (currentBootstrapHostScope() || !String(process.env.DHARMA_ORG_API_TOKEN || '').trim())) {
|
|
2273
3182
|
credentialFailure = 'organization_api_credentials_required';
|
|
2274
3183
|
}
|
|
2275
3184
|
}
|
|
@@ -2301,9 +3210,9 @@ async function bootstrap(flags) {
|
|
|
2301
3210
|
onboardFlags.set('workspace', workspace);
|
|
2302
3211
|
onboardFlags.set('policy-revision', policyRevision);
|
|
2303
3212
|
onboardFlags.set('provider', provider);
|
|
2304
|
-
const onboarded = await retryBootstrapOnboarding(async () => joinedBindingId
|
|
2305
|
-
? await joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint)
|
|
2306
|
-
: await onboard(onboardFlags));
|
|
3213
|
+
const onboarded = await step(() => retryBootstrapOnboarding(async () => joinedBindingId
|
|
3214
|
+
? await step(() => joinExistingRepository(onboardFlags, joinedBindingId, joinedFingerprint))
|
|
3215
|
+
: await step(() => onboard(onboardFlags))));
|
|
2307
3216
|
if (onboarded.ok !== true || onboarded.stage === 'approve_device') {
|
|
2308
3217
|
return {
|
|
2309
3218
|
ok: onboarded.ok === true,
|
|
@@ -2323,40 +3232,40 @@ async function bootstrap(flags) {
|
|
|
2323
3232
|
repository: onboarded,
|
|
2324
3233
|
};
|
|
2325
3234
|
}
|
|
2326
|
-
const launcher = await installStableRepositoryLauncher(workspace);
|
|
3235
|
+
const launcher = await step(() => installStableRepositoryLauncher(workspace));
|
|
2327
3236
|
const autostart = flags.has('no-relay-daemon')
|
|
2328
3237
|
? { state: 'disabled', backend: null }
|
|
2329
|
-
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
|
|
3238
|
+
: await step(() => withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`
|
|
2330
3239
|
+ (joinedBindingId ? ` --join-repository-binding-id ${joinedBindingId} --join-source-fingerprint ${joinedFingerprint}` : ''), () => withRelayStartupMutation(async () => {
|
|
2331
3240
|
const home = dharmaHome();
|
|
2332
|
-
const existing = await relayAutostartStatus({ home });
|
|
3241
|
+
const existing = await step(() => relayAutostartStatus({ home }));
|
|
2333
3242
|
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
2334
|
-
const anchor = await inspectOwnedRelayAutostart({ home });
|
|
3243
|
+
const anchor = await step(() => inspectOwnedRelayAutostart({ home }));
|
|
2335
3244
|
if (anchor.policy !== null) {
|
|
2336
|
-
const current = await readDeviceConfig();
|
|
3245
|
+
const current = await step(() => readDeviceConfig());
|
|
2337
3246
|
if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
|
|
2338
3247
|
throw new Error('relay_workspace_conflict: startup enrollment changed.');
|
|
2339
3248
|
}
|
|
2340
|
-
const selected = selectDeviceWorkspace(await registry(), { organizationId,
|
|
3249
|
+
const selected = selectDeviceWorkspace(await step(() => registry()), { organizationId,
|
|
2341
3250
|
deviceId: config.deviceId, path: anchor.workspace });
|
|
2342
3251
|
if (!selected)
|
|
2343
3252
|
throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
|
|
2344
3253
|
if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
|
|
2345
3254
|
throw new Error('relay_workspace_conflict: startup policy is not canonical.');
|
|
2346
3255
|
}
|
|
2347
|
-
const authorized = await loadOrganizationPolicy(anchor.policy);
|
|
2348
|
-
const canonical = (await registry()).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
|
|
3256
|
+
const authorized = await step(() => loadOrganizationPolicy(anchor.policy));
|
|
3257
|
+
const canonical = (await step(() => registry())).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
|
|
2349
3258
|
&& row.organizationId === organizationId && row.path === anchor.workspace
|
|
2350
3259
|
&& row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
|
|
2351
3260
|
if (canonical.length !== 1)
|
|
2352
3261
|
throw new Error('relay_workspace_conflict: startup policy route changed.');
|
|
2353
|
-
await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
|
|
3262
|
+
await step(() => loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? ''));
|
|
2354
3263
|
}
|
|
2355
3264
|
}
|
|
2356
|
-
return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
|
|
3265
|
+
return step(() => enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
|
|
2357
3266
|
launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
|
|
2358
|
-
version: VERSION, preserveStandardAnchor: true });
|
|
2359
|
-
}));
|
|
3267
|
+
version: VERSION, preserveStandardAnchor: true }));
|
|
3268
|
+
})));
|
|
2360
3269
|
let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
2361
3270
|
const completionRequested = flags.has('complete');
|
|
2362
3271
|
if (!completionRequested) {
|
|
@@ -2380,7 +3289,7 @@ async function bootstrap(flags) {
|
|
|
2380
3289
|
autostart,
|
|
2381
3290
|
};
|
|
2382
3291
|
}
|
|
2383
|
-
const skill = await verifyAgentFabricSkillInstallation({ provider, workspace });
|
|
3292
|
+
const skill = await step(() => verifyAgentFabricSkillInstallation({ provider, workspace }));
|
|
2384
3293
|
if (!skill.ready)
|
|
2385
3294
|
throw new Error(`Provider skill did not become ready: ${JSON.stringify(skill)}`);
|
|
2386
3295
|
const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
|
|
@@ -2389,13 +3298,13 @@ async function bootstrap(flags) {
|
|
|
2389
3298
|
|| joinedBindingId !== null && firstLearning?.state === 'shared_package_inherited';
|
|
2390
3299
|
const relay = flags.has('no-relay-daemon')
|
|
2391
3300
|
? { started: false, ...(await waitForRelayReadiness()) }
|
|
2392
|
-
: await startRelayDaemon(policyPath);
|
|
3301
|
+
: await step(() => startRelayDaemon(policyPath));
|
|
2393
3302
|
let repositoryReadiness = null;
|
|
2394
3303
|
if (!sharedRepositoryReady && relay.state === 'running') {
|
|
2395
3304
|
const workspaceId = String(onboarded.workspaceId || '');
|
|
2396
3305
|
const resumeCommand = `dharma onboard --resume --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision} --provider ${provider}`;
|
|
2397
|
-
repositoryReadiness = await withOnboardingStage('readiness', workspaceId, resumeCommand, () => waitForRepositoryReadiness(async () => {
|
|
2398
|
-
const current = (await registry()).find(item => item.workspaceId === workspaceId
|
|
3306
|
+
repositoryReadiness = await step(() => withOnboardingStage('readiness', workspaceId, resumeCommand, () => waitForRepositoryReadiness(async () => {
|
|
3307
|
+
const current = (await step(() => registry())).find(item => item.workspaceId === workspaceId
|
|
2399
3308
|
&& item.organizationId === organizationId && item.path === workspace);
|
|
2400
3309
|
if (!current)
|
|
2401
3310
|
throw new Error('Enrolled repository workspace disappeared during publication.');
|
|
@@ -2404,20 +3313,20 @@ async function bootstrap(flags) {
|
|
|
2404
3313
|
ready: await repositorySharedReady(current),
|
|
2405
3314
|
candidateId: current.repositoryPackage?.candidateId || null,
|
|
2406
3315
|
};
|
|
2407
|
-
}));
|
|
3316
|
+
})));
|
|
2408
3317
|
sharedRepositoryReady = repositoryReadiness.outcome === 'ready';
|
|
2409
3318
|
}
|
|
2410
3319
|
const apiFlags = new Map([
|
|
2411
3320
|
['organization-id', organizationId], ['portal-url', hqUrl],
|
|
2412
3321
|
]);
|
|
2413
3322
|
const [organization, agents, experiments, failures, remediations, skills, usage] = await Promise.all([
|
|
2414
|
-
runOrganizationCommand('organization', 'status', apiFlags),
|
|
2415
|
-
runOrganizationCommand('agents', 'list', apiFlags),
|
|
2416
|
-
runOrganizationCommand('experiments', 'list', apiFlags),
|
|
2417
|
-
runOrganizationCommand('failures', 'list', apiFlags),
|
|
2418
|
-
runOrganizationCommand('remediations', 'list', apiFlags),
|
|
2419
|
-
runOrganizationCommand('skills', 'list', apiFlags),
|
|
2420
|
-
runOrganizationCommand('usage', 'list', apiFlags),
|
|
3323
|
+
step(() => runOrganizationCommand('organization', 'status', apiFlags)),
|
|
3324
|
+
step(() => runOrganizationCommand('agents', 'list', apiFlags)),
|
|
3325
|
+
step(() => runOrganizationCommand('experiments', 'list', apiFlags)),
|
|
3326
|
+
step(() => runOrganizationCommand('failures', 'list', apiFlags)),
|
|
3327
|
+
step(() => runOrganizationCommand('remediations', 'list', apiFlags)),
|
|
3328
|
+
step(() => runOrganizationCommand('skills', 'list', apiFlags)),
|
|
3329
|
+
step(() => runOrganizationCommand('usage', 'list', apiFlags)),
|
|
2421
3330
|
]);
|
|
2422
3331
|
const organizationApi = summarizeBootstrapOrganizationApi({
|
|
2423
3332
|
organizationId,
|
|
@@ -2429,23 +3338,35 @@ async function bootstrap(flags) {
|
|
|
2429
3338
|
skills,
|
|
2430
3339
|
usage,
|
|
2431
3340
|
});
|
|
2432
|
-
const operatingContract = await loadAgentFabricOnboardingContract();
|
|
2433
|
-
|
|
2434
|
-
? await withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
|
|
3341
|
+
const operatingContract = await step(() => loadAgentFabricOnboardingContract());
|
|
3342
|
+
let namedSession = !joinedBindingId && provider === 'codex' && process.platform === 'linux' && sharedRepositoryReady
|
|
3343
|
+
? await step(() => withOnboardingStage('named_session', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('start', new Map([
|
|
2435
3344
|
['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
|
|
2436
3345
|
['workspace-id', String(onboarded.workspaceId)], ['apply', true],
|
|
2437
3346
|
...(typeof flags.get('session-budget-cents') === 'string'
|
|
2438
3347
|
? [['session-budget-cents', String(flags.get('session-budget-cents'))]] : []),
|
|
2439
|
-
])))
|
|
3348
|
+
]))))
|
|
2440
3349
|
: null;
|
|
3350
|
+
if (namedSession) {
|
|
3351
|
+
const native = await step(() => withOnboardingStage('readiness', String(onboarded.workspaceId), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => namedSessionCommand('readiness', new Map([
|
|
3352
|
+
['name', String(flags.get('session-name') || `codex-${String(onboarded.workspaceId).slice(0, 8)}`)],
|
|
3353
|
+
['workspace-id', String(onboarded.workspaceId)],
|
|
3354
|
+
]))));
|
|
3355
|
+
if (['name', 'bindingId', 'sessionId', 'organizationId', 'repositoryBindingId', 'workspaceId',
|
|
3356
|
+
'endpointId', 'membershipId', 'deviceId', 'provider'].some(key => native[key] !== namedSession[key])) {
|
|
3357
|
+
throw new Error('named_session_readiness_scope_mismatch');
|
|
3358
|
+
}
|
|
3359
|
+
namedSession = { ...namedSession, nativeSkill: parseNamedCodexSkillObservation(native.nativeSkill) };
|
|
3360
|
+
}
|
|
2441
3361
|
const repositoryReceipt = onboarded;
|
|
2442
3362
|
const namedSessionReady = Boolean(joinedBindingId) || provider !== 'codex' || process.platform !== 'linux'
|
|
2443
|
-
|| Boolean(namedSession?.ok === true &&
|
|
3363
|
+
|| Boolean(namedSession?.ok === true && namedSession.nativeSkill
|
|
3364
|
+
&& ['running', 'executing'].includes(String(namedSession.state)));
|
|
2444
3365
|
const role = repositoryReceipt.repositoryRole;
|
|
2445
3366
|
const roleReady = Boolean(role);
|
|
2446
3367
|
const relayReady = relay.state === 'running';
|
|
2447
3368
|
const observedAutostart = autostart.backend === 'container-entrypoint'
|
|
2448
|
-
? await relayAutostartStatus({ home: dharmaHome() }) : autostart;
|
|
3369
|
+
? await step(() => relayAutostartStatus({ home: dharmaHome() })) : autostart;
|
|
2449
3370
|
const startupReady = !['win32', 'linux', 'darwin'].includes(process.platform)
|
|
2450
3371
|
|| observedAutostart.state === 'enabled'
|
|
2451
3372
|
&& (observedAutostart.backend !== 'container-entrypoint' || observedAutostart.lifecycle === 'running');
|
|
@@ -2904,7 +3825,7 @@ async function login(flags) {
|
|
|
2904
3825
|
config.serverSigningKeyset = candidate;
|
|
2905
3826
|
}
|
|
2906
3827
|
await saveDeviceConfig(configPath(), config);
|
|
2907
|
-
await saveDeviceEnrollmentAnchor({ config });
|
|
3828
|
+
await saveDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
|
|
2908
3829
|
await rm(pendingEnrollmentPath(), { force: true });
|
|
2909
3830
|
return { ok: true, status: 'approved', deviceId: config.deviceId, organizationId: pending.organizationId, relayUrl: config.relayUrl };
|
|
2910
3831
|
}
|
|
@@ -2979,7 +3900,6 @@ async function capture(flags, batch = false) {
|
|
|
2979
3900
|
taskReceiptHash: null,
|
|
2980
3901
|
});
|
|
2981
3902
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, registered.workspaceId);
|
|
2982
|
-
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
2983
3903
|
const fabric = flags.has('sync')
|
|
2984
3904
|
? await client()
|
|
2985
3905
|
: null;
|
|
@@ -2989,9 +3909,8 @@ async function capture(flags, batch = false) {
|
|
|
2989
3909
|
}
|
|
2990
3910
|
if (fabric)
|
|
2991
3911
|
policy = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, registered.workspaceId, fabric);
|
|
2992
|
-
const vault = await
|
|
3912
|
+
const vault = await openBootstrapVault({
|
|
2993
3913
|
root: resolve(dharmaHome(), 'vault'),
|
|
2994
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
2995
3914
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
2996
3915
|
});
|
|
2997
3916
|
try {
|
|
@@ -3016,7 +3935,7 @@ async function capture(flags, batch = false) {
|
|
|
3016
3935
|
if (fabric) {
|
|
3017
3936
|
await vault.discardCapsuleRevisionsAfter(firstRevision.trajectoryId, serverHead?.revision || 0);
|
|
3018
3937
|
}
|
|
3019
|
-
const latestMetadata = vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
|
|
3938
|
+
const latestMetadata = await vault.getLatestCapsuleMetadata(firstRevision.trajectoryId);
|
|
3020
3939
|
const latestCapsule = latestMetadata
|
|
3021
3940
|
? await vault.getLatestCapsule(firstRevision.trajectoryId)
|
|
3022
3941
|
: null;
|
|
@@ -3059,7 +3978,7 @@ async function capture(flags, batch = false) {
|
|
|
3059
3978
|
}
|
|
3060
3979
|
capsules.push(capsule);
|
|
3061
3980
|
if (fabric) {
|
|
3062
|
-
vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
|
|
3981
|
+
await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
|
|
3063
3982
|
try {
|
|
3064
3983
|
await reserveDailyContentUpload(capsule, policy);
|
|
3065
3984
|
// An unknown delivery outcome retains the local advisory reservation.
|
|
@@ -3068,11 +3987,11 @@ async function capture(flags, batch = false) {
|
|
|
3068
3987
|
}
|
|
3069
3988
|
catch (error) {
|
|
3070
3989
|
if (isDefinitiveSecretDisclosureRejection(error)) {
|
|
3071
|
-
vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
|
|
3990
|
+
await vault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'secret_disclosure_forbidden');
|
|
3072
3991
|
}
|
|
3073
3992
|
throw error;
|
|
3074
3993
|
}
|
|
3075
|
-
vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
|
|
3994
|
+
await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
|
|
3076
3995
|
}
|
|
3077
3996
|
}
|
|
3078
3997
|
const output = flags.get('output');
|
|
@@ -3106,7 +4025,7 @@ async function capture(flags, batch = false) {
|
|
|
3106
4025
|
return manifest;
|
|
3107
4026
|
}
|
|
3108
4027
|
finally {
|
|
3109
|
-
vault.close();
|
|
4028
|
+
await vault.close();
|
|
3110
4029
|
}
|
|
3111
4030
|
}
|
|
3112
4031
|
async function evidencePreview(flags) {
|
|
@@ -3259,7 +4178,7 @@ async function workspaceRecoverRegistry(flags) {
|
|
|
3259
4178
|
const config = await readDeviceConfig();
|
|
3260
4179
|
if (!config)
|
|
3261
4180
|
throw new Error('registry_recovery_enrollment_required');
|
|
3262
|
-
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
4181
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
|
|
3263
4182
|
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
3264
4183
|
const home = dharmaHome();
|
|
3265
4184
|
const startup = await inspectOwnedRelayAutostart({ home });
|
|
@@ -3459,14 +4378,17 @@ function selectedProviderAdapters(providerIds) {
|
|
|
3459
4378
|
: providerAdapters;
|
|
3460
4379
|
}
|
|
3461
4380
|
export async function receiptAwareProviderCapabilities(providers, now = new Date()) {
|
|
4381
|
+
const hostScope = currentBootstrapHostScope();
|
|
4382
|
+
await hostScope?.assert();
|
|
3462
4383
|
const selfTestedAt = now.toISOString();
|
|
3463
4384
|
let freshUntil = now.toISOString();
|
|
3464
4385
|
let receiverState = 'unavailable';
|
|
3465
4386
|
let reason = 'device_not_enrolled';
|
|
3466
4387
|
let trustedKeyVersions = [];
|
|
3467
4388
|
try {
|
|
3468
|
-
const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now
|
|
3469
|
-
|
|
4389
|
+
const config = await recoverDeviceEnrollmentConsistency({ configPath: configPath(), now,
|
|
4390
|
+
hostScope });
|
|
4391
|
+
await loadDeviceEnrollmentAnchor({ config, hostScope });
|
|
3470
4392
|
const keyset = config.serverSigningKeyset;
|
|
3471
4393
|
if (!keyset)
|
|
3472
4394
|
throw new Error('trusted_server_signing_keyset_unavailable');
|
|
@@ -3481,13 +4403,15 @@ export async function receiptAwareProviderCapabilities(providers, now = new Date
|
|
|
3481
4403
|
if (trustedKeyVersions.length === 0)
|
|
3482
4404
|
throw new Error('trusted_server_signing_keys_unavailable');
|
|
3483
4405
|
freshUntil = actionDecisionCapabilityFreshUntil(keyset, trustedKeyVersions, now);
|
|
3484
|
-
await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest();
|
|
4406
|
+
await new FileActionExecutionJournal(resolve(dharmaHome(), 'relay', 'action-execution-journal')).selfTest(hostScope);
|
|
3485
4407
|
receiverState = 'available';
|
|
3486
4408
|
reason = '';
|
|
3487
4409
|
}
|
|
3488
4410
|
catch (error) {
|
|
3489
|
-
|
|
4411
|
+
await hostScope?.assert();
|
|
4412
|
+
reason = hostScope ? 'codex_setup_host_receiver_unavailable' : error instanceof Error ? error.message : String(error);
|
|
3490
4413
|
}
|
|
4414
|
+
await hostScope?.assert();
|
|
3491
4415
|
return providers.map((provider) => {
|
|
3492
4416
|
const available = provider.taskExecution === 'available' && receiverState === 'available';
|
|
3493
4417
|
return {
|
|
@@ -3699,18 +4623,95 @@ async function repositoryRoleCommand(action, flags) {
|
|
|
3699
4623
|
targetEndpointId: typeof flags.get('target-endpoint-id') === 'string' ? String(flags.get('target-endpoint-id')) : undefined });
|
|
3700
4624
|
return readRepositoryRoleReply({ transport, scope, questionId: required(flags, 'question-id') });
|
|
3701
4625
|
}
|
|
4626
|
+
/** Original setup submits to the standing owner; it never spawns its worker. */
|
|
4627
|
+
async function requestBootstrapNamedSession(hostScope, flags) {
|
|
4628
|
+
const sender = await originalCodexSetupSessionSender(hostScope);
|
|
4629
|
+
const prepared = await inspectCodexBootstrapHostPreparation(hostScope);
|
|
4630
|
+
const workspace = String(prepared.flags.get('workspace')), intent = sender.intent;
|
|
4631
|
+
const workspaceId = required(flags, 'workspace-id'), name = `codex-${workspaceId.slice(0, 8)}`;
|
|
4632
|
+
if (flags.get('name') !== name || [...flags.keys()].some(key => !['name', 'workspace-id', 'apply'].includes(key))) {
|
|
4633
|
+
throw new Error('setup_session_scope_changed');
|
|
4634
|
+
}
|
|
4635
|
+
const step = (operation) => hostScope.step(operation);
|
|
4636
|
+
const config = await step(() => readDeviceConfig());
|
|
4637
|
+
const rows = (await step(() => registry())).filter(row => row.workspaceId === workspaceId && row.path === workspace
|
|
4638
|
+
&& row.organizationId === intent.organizationId && row.repositoryRemoteHash === intent.repositoryFingerprint && row.status === 'active');
|
|
4639
|
+
if (!config || config.organizationId !== intent.organizationId || normalizeHqUrl(config.hqUrl) !== intent.origin
|
|
4640
|
+
|| config.setupClaimReference !== intent.setupReference || config.setupClaimRepositoryFingerprint !== intent.repositoryFingerprint
|
|
4641
|
+
|| rows.length !== 1)
|
|
4642
|
+
throw new Error('setup_session_scope_changed');
|
|
4643
|
+
const item = rows[0], role = repositoryRoleScope(item);
|
|
4644
|
+
await step(() => assertBootstrapHostSource(workspace, intent.repositoryFingerprint));
|
|
4645
|
+
const enrollment = await step(() => loadDeviceEnrollmentAnchor({ config, hostScope }));
|
|
4646
|
+
const policy = await step(() => loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), workspaceId));
|
|
4647
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
4648
|
+
organizationId: intent.organizationId, workspaceId });
|
|
4649
|
+
await step(() => assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization));
|
|
4650
|
+
if (policy.revision !== intent.policyRevision)
|
|
4651
|
+
throw new Error('setup_session_scope_changed');
|
|
4652
|
+
const senderProcess = await step(() => readContainerProcessIdentity(process.pid));
|
|
4653
|
+
if (senderProcess.uid !== process.getuid())
|
|
4654
|
+
throw new Error('setup_session_scope_changed');
|
|
4655
|
+
const result = await awaitCodexSetupSession({ vault: sender.vault, scope: hostScope, leaseId: sender.lease.leaseId,
|
|
4656
|
+
request: { schema: 'dharma.local-codex-setup-session/v1', operationId: intent.operationId, intentDigest: sender.lease.intentDigest,
|
|
4657
|
+
setupReference: intent.setupReference, senderPid: process.pid, senderStartTicks: senderProcess.startTicks,
|
|
4658
|
+
organizationId: intent.organizationId, membershipId: intent.recipientMembershipId, deviceId: config.deviceId,
|
|
4659
|
+
workspaceId, repositoryBindingId: role.repositoryBindingId, endpointId: role.endpointId, provider: 'codex',
|
|
4660
|
+
origin: intent.origin, repositoryFingerprint: intent.repositoryFingerprint, policyRevision: policy.revision,
|
|
4661
|
+
policyHash: sha256(canonicalize(policy)), scopeDigest: intent.scopeDigest, contractDigest: intent.contractDigest,
|
|
4662
|
+
name, workspaceRoot: workspace, maximumCostCents: 1000, maximumTurnCostCents: 25,
|
|
4663
|
+
issuedAt: new Date().toISOString(), expiresAt: new Date(Math.min(Date.parse(intent.expiresAt), Date.parse(policy.serverAuthorization.expiresAt))).toISOString() } });
|
|
4664
|
+
await step(() => assertCodexSetupExecutionLease(sender.lease, intent));
|
|
4665
|
+
if (result.state !== 'started')
|
|
4666
|
+
throw new Error('setup_session_start_unconfirmed');
|
|
4667
|
+
const status = await step(() => namedSessionCommand('status', new Map([
|
|
4668
|
+
['name', name], ['workspace-id', workspaceId]
|
|
4669
|
+
])));
|
|
4670
|
+
if (status.ok !== true || status.bindingId !== result.bindingId || status.sessionId !== result.sessionId
|
|
4671
|
+
|| status.membershipId !== intent.recipientMembershipId || status.deviceId !== config.deviceId
|
|
4672
|
+
|| status.organizationId !== intent.organizationId || status.workspaceId !== workspaceId
|
|
4673
|
+
|| status.repositoryBindingId !== role.repositoryBindingId || status.endpointId !== role.endpointId || status.provider !== 'codex') {
|
|
4674
|
+
throw new Error('setup_session_scope_changed');
|
|
4675
|
+
}
|
|
4676
|
+
return status;
|
|
4677
|
+
}
|
|
3702
4678
|
async function namedSessionCommand(action, flags) {
|
|
4679
|
+
const hostScope = currentBootstrapHostScope();
|
|
4680
|
+
const setupSessionScope = currentAcceptedSetupSessionScope();
|
|
4681
|
+
const supervisorOwner = currentNamedSessionChildOwner();
|
|
4682
|
+
if (hostScope && (supervisorOwner || setupSessionScope))
|
|
4683
|
+
throw new Error('named_session_child_owner_conflict');
|
|
4684
|
+
if (setupSessionScope && !['start', 'status', 'readiness'].includes(action))
|
|
4685
|
+
throw new Error('setup_session_scope_changed');
|
|
4686
|
+
const step = (operation) => hostScope ? hostScope.step(operation)
|
|
4687
|
+
: setupSessionScope ? setupSessionScope.step(operation) : operation();
|
|
4688
|
+
await hostScope?.assert();
|
|
4689
|
+
if (action === 'start' && hostScope && flags.has('apply')) {
|
|
4690
|
+
return requestBootstrapNamedSession(hostScope, flags);
|
|
4691
|
+
}
|
|
3703
4692
|
const name = String(flags.get('name') || 'codex');
|
|
3704
4693
|
const paths = namedSessionPaths(dharmaHome(), name);
|
|
3705
4694
|
if (process.platform !== 'linux')
|
|
3706
4695
|
throw new Error('codex_session_sandbox_unqualified');
|
|
3707
4696
|
const workspaceId = required(flags, 'workspace-id');
|
|
3708
|
-
|
|
3709
|
-
|
|
4697
|
+
let childMessage;
|
|
4698
|
+
if (flags.has('setup-handoff')) {
|
|
4699
|
+
if (action !== 'serve' || flags.get('setup-handoff') !== true
|
|
4700
|
+
|| [...flags.keys()].some(key => !['name', 'workspace-id', 'apply', 'setup-handoff'].includes(key))) {
|
|
4701
|
+
throw new Error('setup_child_unavailable');
|
|
4702
|
+
}
|
|
4703
|
+
childMessage = await receiveCodexSetupChildStart({ process, signal: new AbortController().signal });
|
|
4704
|
+
}
|
|
4705
|
+
const item = (await step(() => registry())).find(row => row.workspaceId === workspaceId);
|
|
4706
|
+
const config = await step(() => readDeviceConfig());
|
|
3710
4707
|
if (!item || !config || item.organizationId !== config.organizationId)
|
|
3711
4708
|
throw new Error('named_session_scope_mismatch');
|
|
4709
|
+
await assertBootstrapHostSource(item.path, String(item.repositoryRemoteHash || ''));
|
|
3712
4710
|
const scope = repositoryRoleScope(item);
|
|
3713
|
-
const existing = await readNamedSession(dharmaHome(), name);
|
|
4711
|
+
const existing = await step(() => readNamedSession(dharmaHome(), name));
|
|
4712
|
+
if (setupSessionScope && (name !== setupSessionScope.request.name || workspaceId !== setupSessionScope.request.workspaceId
|
|
4713
|
+
|| existing && existing.identity.membershipId !== setupSessionScope.request.membershipId))
|
|
4714
|
+
throw new Error('setup_session_scope_changed');
|
|
3714
4715
|
if (existing && (existing.identity.workspaceId !== item.workspaceId
|
|
3715
4716
|
|| existing.identity.organizationId !== config.organizationId || existing.identity.deviceId !== config.deviceId
|
|
3716
4717
|
|| existing.identity.endpointId !== scope.endpointId || existing.identity.repositoryBindingId !== scope.repositoryBindingId)) {
|
|
@@ -3725,6 +4726,18 @@ async function namedSessionCommand(action, flags) {
|
|
|
3725
4726
|
bindingId: existing?.bindingId ?? null };
|
|
3726
4727
|
}
|
|
3727
4728
|
}
|
|
4729
|
+
if (action === 'readiness') {
|
|
4730
|
+
if (!existing?.enabled)
|
|
4731
|
+
throw new Error('named_session_readiness_unavailable');
|
|
4732
|
+
const result = await namedSessionRequest(dharmaHome(), name, { action: 'readiness' });
|
|
4733
|
+
if (Object.keys(result).sort().join(',') !== 'bindingId,deviceId,endpointId,membershipId,name,nativeSkill,ok,organizationId,provider,repositoryBindingId,schema,sessionId,workspaceId'
|
|
4734
|
+
|| result.schema !== 'dharma.named-session-readiness/v1' || result.name !== name || result.bindingId !== existing.bindingId
|
|
4735
|
+
|| typeof result.sessionId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$(?![\s\S])/.test(result.sessionId)
|
|
4736
|
+
|| Object.entries(existing.identity).some(([key, value]) => result[key] !== value)) {
|
|
4737
|
+
throw new Error('named_session_readiness_scope_mismatch');
|
|
4738
|
+
}
|
|
4739
|
+
return { ...result, nativeSkill: parseNamedCodexSkillObservation(result.nativeSkill) };
|
|
4740
|
+
}
|
|
3728
4741
|
if (action === 'work')
|
|
3729
4742
|
return namedSessionRequest(dharmaHome(), name, { action: 'work',
|
|
3730
4743
|
workId: String(flags.get('work-id') || randomUUID()), prompt: required(flags, 'prompt') });
|
|
@@ -3745,101 +4758,155 @@ async function namedSessionCommand(action, flags) {
|
|
|
3745
4758
|
if (!flags.has('apply'))
|
|
3746
4759
|
return { ok: true, planned: true, name, workspaceId,
|
|
3747
4760
|
provider: 'codex', localWork: 'workspace_write', peerQuestions: 'read_only', network: 'deny' };
|
|
3748
|
-
await verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item));
|
|
4761
|
+
await step(async () => verifyNamedSessionVisibleSkill(await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path }), await repositorySharedReady(item)));
|
|
3749
4762
|
if (action === 'start') {
|
|
3750
4763
|
if (existing && !existing.enabled)
|
|
3751
|
-
await saveNamedSession(dharmaHome(), { ...existing, enabled: true });
|
|
4764
|
+
await step(() => saveNamedSession(dharmaHome(), { ...existing, enabled: true }));
|
|
3752
4765
|
try {
|
|
3753
|
-
return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
|
|
4766
|
+
return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
|
|
3754
4767
|
}
|
|
3755
4768
|
catch { /* Start an owned worker below. */ }
|
|
3756
4769
|
const args = [fileURLToPath(import.meta.url), 'sessions', 'serve', '--name', name,
|
|
3757
4770
|
'--workspace-id', workspaceId, '--apply'];
|
|
4771
|
+
if (setupSessionScope)
|
|
4772
|
+
args.push('--setup-handoff');
|
|
3758
4773
|
for (const key of ['session-budget-cents', 'turn-budget-cents']) {
|
|
3759
4774
|
if (flags.has(key))
|
|
3760
4775
|
args.push(`--${key}`, required(flags, key));
|
|
3761
4776
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4777
|
+
await step(async () => {
|
|
4778
|
+
const start = () => spawn(process.execPath, args, { cwd: item.path, detached: !hostScope && !supervisorOwner,
|
|
4779
|
+
stdio: setupSessionScope ? ['ignore', 'ignore', 'ignore', 'ipc'] : 'ignore', env: process.env });
|
|
4780
|
+
if (supervisorOwner) {
|
|
4781
|
+
const child = await supervisorOwner.spawn(name, start);
|
|
4782
|
+
if (setupSessionScope)
|
|
4783
|
+
await sendCodexSetupChildStart({ owner: supervisorOwner, child, scope: setupSessionScope });
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
const child = start();
|
|
4787
|
+
if (hostScope)
|
|
4788
|
+
captureBootstrapHostChild(hostScope, child);
|
|
4789
|
+
else {
|
|
4790
|
+
child.on('error', () => { });
|
|
4791
|
+
child.unref();
|
|
4792
|
+
}
|
|
4793
|
+
});
|
|
3765
4794
|
for (let attempt = 0; attempt < 80; attempt++) {
|
|
3766
|
-
await new Promise(resolveWait => setTimeout(resolveWait, 250));
|
|
4795
|
+
await step(() => new Promise(resolveWait => setTimeout(resolveWait, 250)));
|
|
4796
|
+
await hostScope?.assert();
|
|
3767
4797
|
try {
|
|
3768
|
-
return await namedSessionRequest(dharmaHome(), name, { action: 'status' });
|
|
4798
|
+
return await step(() => namedSessionRequest(dharmaHome(), name, { action: 'status' }));
|
|
3769
4799
|
}
|
|
3770
4800
|
catch { /* Await service initialization. */ }
|
|
3771
4801
|
}
|
|
3772
4802
|
throw new Error('named_session_startup_failed');
|
|
3773
4803
|
}
|
|
3774
4804
|
await mkdir(paths.root, { recursive: true, mode: 0o700 });
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
const vault = await LocalVault.open({ root: resolve(dharmaHome(), 'vault'), masterKey: await loadOrCreateVaultMasterKey() });
|
|
4805
|
+
const vault = await openBootstrapVault({ root: resolve(dharmaHome(), 'vault') });
|
|
4806
|
+
let releaseLock;
|
|
3778
4807
|
const controller = new AbortController(), stop = () => controller.abort();
|
|
3779
4808
|
process.once('SIGTERM', stop);
|
|
3780
4809
|
process.once('SIGINT', stop);
|
|
4810
|
+
if (childMessage)
|
|
4811
|
+
process.once('disconnect', stop);
|
|
3781
4812
|
let transport;
|
|
3782
4813
|
try {
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
'-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
4814
|
+
releaseLock = await acquirePidLock(resolve(paths.root, 'service.lock'), 250, 'named_session_already_running');
|
|
4815
|
+
const startProvider = async (childScope) => {
|
|
4816
|
+
const startupStep = (operation) => childScope ? childScope.step(operation) : operation();
|
|
4817
|
+
if (childScope && existing && existing.identity.membershipId !== childScope.request.membershipId) {
|
|
4818
|
+
throw new Error('setup_child_unavailable');
|
|
4819
|
+
}
|
|
4820
|
+
const fabric = await startupStep(() => AgentFabricClient.open({ configPath: configPath(), statePath: resolve(paths.root, 'protocol-state.json'),
|
|
4821
|
+
hostScope: currentBootstrapHostScope() }));
|
|
4822
|
+
if (fabric.config.organizationId !== config.organizationId || fabric.config.deviceId !== config.deviceId
|
|
4823
|
+
|| fabric.config.publicKeyEd25519 !== config.publicKeyEd25519)
|
|
4824
|
+
throw new Error('named_session_trust_scope_mismatch');
|
|
4825
|
+
const trust = createNamedSessionTrust({ configPath: configPath(), identity: fabric.config });
|
|
4826
|
+
await startupStep(() => trust.refresh());
|
|
4827
|
+
await startupStep(() => fabric.openSession(VERSION));
|
|
4828
|
+
const policyPath = resolve(item.path, '.dharma', 'approved-policy.json');
|
|
4829
|
+
const policy = await startupStep(() => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric));
|
|
4830
|
+
const writeRoots = policy.tasks.writePaths.filter(path => /^[a-zA-Z0-9_-]+\/\*\*$/.test(path))
|
|
4831
|
+
.map(path => path.slice(0, -3));
|
|
4832
|
+
if (!writeRoots.length)
|
|
4833
|
+
throw new Error('named_session_workspace_write_not_authorized');
|
|
4834
|
+
let registration = existing, pending;
|
|
4835
|
+
let ownerReceipt;
|
|
4836
|
+
// Resolve authenticated ownership before opening any provider transport.
|
|
4837
|
+
if (registration) {
|
|
4838
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
4839
|
+
schema: 'dharma.provider-session-registration/v1', action: 'inspect', provider: 'codex', mode: 'bridge_owned',
|
|
4840
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
4841
|
+
bindingId: registration.bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
4842
|
+
}));
|
|
4843
|
+
if (!isNamedSessionOwnerReceipt(response, registration.bindingId, registration.identity)) {
|
|
4844
|
+
throw new Error('named_session_registration_invalid');
|
|
4845
|
+
}
|
|
4846
|
+
ownerReceipt = response;
|
|
4847
|
+
}
|
|
4848
|
+
else {
|
|
4849
|
+
const maximumCostCents = boundedInteger(flags.get('session-budget-cents'), 1000, 1, 10000, '--session-budget-cents');
|
|
4850
|
+
const maximumTurnCostCents = boundedInteger(flags.get('turn-budget-cents'), 25, 1, maximumCostCents, '--turn-budget-cents');
|
|
4851
|
+
const bindingId = randomUUID();
|
|
4852
|
+
const response = await startupStep(() => fabric.signedPost('/agent-fabric/provider-sessions', {
|
|
4853
|
+
schema: 'dharma.provider-session-registration/v1', action: 'attach', provider: 'codex', mode: 'bridge_owned',
|
|
4854
|
+
workspaceId, endpointId: scope.endpointId, repositoryBindingId: scope.repositoryBindingId,
|
|
4855
|
+
bindingId, expectedRevision: 0, leaseSeconds: 60,
|
|
4856
|
+
}));
|
|
4857
|
+
const remote = response.registration;
|
|
4858
|
+
if (!remote || !UUID_PATTERN.test(String(remote.membershipId))
|
|
4859
|
+
|| childScope && remote.membershipId !== childScope.request.membershipId || remote.revision !== 1) {
|
|
4860
|
+
throw new Error('named_session_registration_invalid');
|
|
4861
|
+
}
|
|
4862
|
+
const identity = { organizationId: config.organizationId, repositoryBindingId: scope.repositoryBindingId,
|
|
4863
|
+
workspaceId, endpointId: scope.endpointId, membershipId: String(remote.membershipId), deviceId: config.deviceId,
|
|
4864
|
+
provider: 'codex' };
|
|
4865
|
+
if (!isNamedSessionOwnerReceipt(response, bindingId, identity))
|
|
4866
|
+
throw new Error('named_session_registration_invalid');
|
|
4867
|
+
ownerReceipt = response;
|
|
4868
|
+
pending = { schema: 'dharma.named-session/v1', name, bindingId, identity,
|
|
4869
|
+
maximumCostCents, maximumTurnCostCents, enabled: true };
|
|
4870
|
+
}
|
|
4871
|
+
const environment = namedCodexEnvironment(process.env);
|
|
4872
|
+
const privateRoots = [dharmaHome(), resolve(environment.CODEX_HOME || resolve(homedir(), '.codex'))];
|
|
4873
|
+
if (environment.XDG_RUNTIME_DIR)
|
|
4874
|
+
privateRoots.push(environment.XDG_RUNTIME_DIR);
|
|
4875
|
+
if (environment.DBUS_SESSION_BUS_ADDRESS)
|
|
4876
|
+
privateRoots.push(environment.DBUS_SESSION_BUS_ADDRESS.slice('unix:path='.length));
|
|
4877
|
+
const filesystem = await startupStep(() => namedCodexFilesystem({ environment, workspace: item.path, privateRoots, writeRoots }));
|
|
4878
|
+
await startupStep(async () => {
|
|
4879
|
+
const expectedOwner = registration ?? pending;
|
|
4880
|
+
if (!isNamedSessionOwnerReceipt(ownerReceipt, expectedOwner.bindingId, expectedOwner.identity)) {
|
|
4881
|
+
throw new Error('named_session_registration_invalid');
|
|
4882
|
+
}
|
|
4883
|
+
transport = await openCodexAppServerTransport({ command: 'codex', cwd: item.path,
|
|
4884
|
+
environment, experimentalApi: true, toolCallTimeoutMs: 60_000,
|
|
4885
|
+
argv: ['-c', 'default_permissions="dharma_bridge"',
|
|
4886
|
+
'-c', `permissions.dharma_bridge.filesystem=${filesystem.peer}`,
|
|
4887
|
+
'-c', 'permissions.dharma_bridge.network={enabled=false}',
|
|
4888
|
+
'-c', `permissions.dharma_work.filesystem=${filesystem.work}`,
|
|
4889
|
+
'-c', 'permissions.dharma_work.network={enabled=false}', 'app-server'] });
|
|
4890
|
+
return transport;
|
|
3823
4891
|
});
|
|
3824
|
-
const
|
|
3825
|
-
if (
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
await
|
|
3842
|
-
}
|
|
4892
|
+
const account = await startupStep(() => transport.request('account/read', { refreshToken: false }));
|
|
4893
|
+
if (!account.account)
|
|
4894
|
+
throw new Error('named_session_provider_authentication_required');
|
|
4895
|
+
if (pending) {
|
|
4896
|
+
const threadId = await startupStep(() => startNamedCodexThread(transport, item.path, name));
|
|
4897
|
+
await startupStep(async () => vault.saveProviderSessionBinding({ schema: 'dharma.local-provider-session-binding/v1', ...pending.identity,
|
|
4898
|
+
bindingId: pending.bindingId, owner: 'dharma_bridge', sessionId: threadId, workspaceRoot: item.path,
|
|
4899
|
+
createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
4900
|
+
maximumProviderCostCents: pending.maximumTurnCostCents }));
|
|
4901
|
+
registration = pending;
|
|
4902
|
+
await startupStep(async () => saveNamedSession(dharmaHome(), registration));
|
|
4903
|
+
}
|
|
4904
|
+
return { registration: registration, filesystem, fabric, trust, policyPath, writeRoots };
|
|
4905
|
+
};
|
|
4906
|
+
const { registration, filesystem, fabric, trust, policyPath, writeRoots } = childMessage
|
|
4907
|
+
? await runCodexSetupChildStartup({ vault: vault, message: childMessage, name, workspaceId,
|
|
4908
|
+
signal: controller.signal, authorize: qualifyCodexSetupChildRequest }, startProvider)
|
|
4909
|
+
: await startProvider();
|
|
3843
4910
|
const authorizeLifetime = async () => {
|
|
3844
4911
|
const current = await refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric);
|
|
3845
4912
|
if (!writeRoots.every(root => current.tasks.writePaths.includes(`${root}/**`)))
|
|
@@ -3871,6 +4938,17 @@ async function namedSessionCommand(action, flags) {
|
|
|
3871
4938
|
};
|
|
3872
4939
|
return await runNamedSessionService({ home: dharmaHome(), registration, vault, signal: controller.signal,
|
|
3873
4940
|
localWriteRoots: writeRoots, additionalFilesystemRules: filesystem.additionalFilesystemRules,
|
|
4941
|
+
observeNativeSkill: async () => {
|
|
4942
|
+
const installation = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
4943
|
+
const native = await observeNamedCodexSkill(transport, { workspace: item.path, installation,
|
|
4944
|
+
sharedRepositoryReady: await repositorySharedReady(item) });
|
|
4945
|
+
const current = await verifyAgentFabricSkillInstallation({ provider: 'codex', workspace: item.path });
|
|
4946
|
+
if (!current.signedLifecycleReady || current.activeBundleId !== native.bundleId
|
|
4947
|
+
|| current.activeBundleHash !== native.bundleHash || current.nativeSkillPath !== installation.nativeSkillPath
|
|
4948
|
+
|| !await repositorySharedReady(item))
|
|
4949
|
+
throw new Error('named_session_native_skill_unavailable');
|
|
4950
|
+
return native;
|
|
4951
|
+
},
|
|
3874
4952
|
syncTaskExports: () => syncNamedSessionTaskExports({ vault, bindingId: registration.bindingId,
|
|
3875
4953
|
identity: registration.identity,
|
|
3876
4954
|
loadPolicy: () => refreshVerifiedWorkspacePolicyForTransmission(policyPath, workspaceId, fabric),
|
|
@@ -3888,7 +4966,7 @@ async function namedSessionCommand(action, flags) {
|
|
|
3888
4966
|
if (current.evidence.automaticDisclosure?.mode === 'customer_authorized_content' && skill.activeBundleHash) {
|
|
3889
4967
|
try {
|
|
3890
4968
|
taskStateFailure = 'public_context_unavailable';
|
|
3891
|
-
const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
4969
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
3892
4970
|
if (!binding)
|
|
3893
4971
|
throw new Error('named_session_binding_unavailable');
|
|
3894
4972
|
const retained = await readCodexPublicContext(transport, { threadId: binding.sessionId, workspaceRoot: binding.workspaceRoot });
|
|
@@ -3939,7 +5017,7 @@ async function namedSessionCommand(action, flags) {
|
|
|
3939
5017
|
|| current.evidence.automaticDisclosure.consentReceiptId !== taskState.consentReceiptId) {
|
|
3940
5018
|
throw new Error('named_session_repository_state_consent_changed');
|
|
3941
5019
|
}
|
|
3942
|
-
const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5020
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
3943
5021
|
if (!binding)
|
|
3944
5022
|
throw new Error('named_session_binding_unavailable');
|
|
3945
5023
|
const receipt = await retainNamedSessionRepositoryState({ vault, binding, capture, workId: taskState.workId,
|
|
@@ -3956,7 +5034,7 @@ async function namedSessionCommand(action, flags) {
|
|
|
3956
5034
|
},
|
|
3957
5035
|
queueEvidence: async (capture) => {
|
|
3958
5036
|
const current = await loadVerifiedWorkspacePolicy(policyPath, workspaceId);
|
|
3959
|
-
const binding = vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
5037
|
+
const binding = await vault.getProviderSessionBinding(registration.bindingId, registration.identity);
|
|
3960
5038
|
if (!binding)
|
|
3961
5039
|
throw new Error('named_session_binding_unavailable');
|
|
3962
5040
|
// The existing relay rechecks current authority, disclosure limits and device binding before upload.
|
|
@@ -3975,10 +5053,16 @@ async function namedSessionCommand(action, flags) {
|
|
|
3975
5053
|
await transport.close();
|
|
3976
5054
|
}
|
|
3977
5055
|
finally {
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
5056
|
+
try {
|
|
5057
|
+
await vault.close();
|
|
5058
|
+
}
|
|
5059
|
+
finally {
|
|
5060
|
+
await releaseLock?.();
|
|
5061
|
+
process.removeListener('SIGTERM', stop);
|
|
5062
|
+
process.removeListener('SIGINT', stop);
|
|
5063
|
+
if (childMessage)
|
|
5064
|
+
process.removeListener('disconnect', stop);
|
|
5065
|
+
}
|
|
3982
5066
|
}
|
|
3983
5067
|
}
|
|
3984
5068
|
}
|
|
@@ -4172,13 +5256,22 @@ async function joinExistingRepository(flags, bindingId, sourceFingerprint) {
|
|
|
4172
5256
|
workspaceSync: synchronized, relay };
|
|
4173
5257
|
}
|
|
4174
5258
|
async function readDeviceConfig() {
|
|
5259
|
+
const scope = currentBootstrapHostScope();
|
|
4175
5260
|
try {
|
|
4176
|
-
|
|
5261
|
+
const bytes = scope ? await scope.step(() => readFile(configPath(), 'utf8')) : await readFile(configPath(), 'utf8');
|
|
5262
|
+
return JSON.parse(bytes);
|
|
4177
5263
|
}
|
|
4178
5264
|
catch {
|
|
5265
|
+
await scope?.assert();
|
|
4179
5266
|
return null;
|
|
4180
5267
|
}
|
|
4181
5268
|
}
|
|
5269
|
+
async function enrolledDeviceIdentity(config, store) {
|
|
5270
|
+
const hostScope = currentBootstrapHostScope();
|
|
5271
|
+
return hostScope ? loadDeviceIdentity({ config, store, hostScope }) : loadOrCreateDeviceIdentity({
|
|
5272
|
+
hqUrl: config.hqUrl, organizationId: config.organizationId, installationId: config.installationId, store
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
4182
5275
|
async function activeSkillAuthorization(provider, workspaceId, organizationAgentId, config) {
|
|
4183
5276
|
const root = await nativeSkillDirectoryForWorkspace({ provider, workspaceId });
|
|
4184
5277
|
const pointer = resolve(root, '.dharma-managed', 'workspaces', workspaceId, 'ACTIVE_BUNDLE');
|
|
@@ -4187,13 +5280,9 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
|
|
|
4187
5280
|
if (!organizationAgentId)
|
|
4188
5281
|
throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
|
|
4189
5282
|
const [identity, enrollment, active] = await Promise.all([
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
installationId: config.installationId,
|
|
4194
|
-
}),
|
|
4195
|
-
loadDeviceEnrollmentAnchor({ config }),
|
|
4196
|
-
loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
|
|
5283
|
+
enrolledDeviceIdentity(config),
|
|
5284
|
+
loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
|
|
5285
|
+
loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
|
|
4197
5286
|
]);
|
|
4198
5287
|
if (!active)
|
|
4199
5288
|
throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
|
|
@@ -4201,6 +5290,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
|
|
|
4201
5290
|
throw new Error('Active skill device identity does not match secure enrollment state.');
|
|
4202
5291
|
}
|
|
4203
5292
|
const authorizationInput = (expectedReceiptHash) => ({
|
|
5293
|
+
hostScope: currentBootstrapHostScope(),
|
|
4204
5294
|
nativeSkillDirectory: root,
|
|
4205
5295
|
workspaceId,
|
|
4206
5296
|
provider,
|
|
@@ -4226,6 +5316,7 @@ async function activeSkillAuthorization(provider, workspaceId, organizationAgent
|
|
|
4226
5316
|
throw error;
|
|
4227
5317
|
const refreshed = await loadActiveSkillAuthorizationAnchor({
|
|
4228
5318
|
config, workspaceId, organizationAgentId, provider, fresh: true,
|
|
5319
|
+
hostScope: currentBootstrapHostScope(),
|
|
4229
5320
|
});
|
|
4230
5321
|
if (!refreshed)
|
|
4231
5322
|
throw error;
|
|
@@ -4237,13 +5328,9 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
|
|
|
4237
5328
|
if (!organizationAgentId)
|
|
4238
5329
|
throw new Error('Workspace is not bound to a repository agent. Run dharma workspace sync.');
|
|
4239
5330
|
const [identity, enrollment, active] = await Promise.all([
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
installationId: config.installationId,
|
|
4244
|
-
}),
|
|
4245
|
-
loadDeviceEnrollmentAnchor({ config }),
|
|
4246
|
-
loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider }),
|
|
5331
|
+
enrolledDeviceIdentity(config),
|
|
5332
|
+
loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() }),
|
|
5333
|
+
loadActiveSkillAuthorizationAnchor({ config, workspaceId, organizationAgentId, provider, hostScope: currentBootstrapHostScope() }),
|
|
4247
5334
|
]);
|
|
4248
5335
|
if (!active)
|
|
4249
5336
|
throw new Error('Active skill state is not anchored in secure storage. Run dharma skill sync again.');
|
|
@@ -4251,6 +5338,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
|
|
|
4251
5338
|
throw new Error('Active skill device identity does not match secure enrollment state.');
|
|
4252
5339
|
}
|
|
4253
5340
|
const authorizationInput = (expectedReceiptHash) => ({
|
|
5341
|
+
hostScope: currentBootstrapHostScope(),
|
|
4254
5342
|
nativeSkillDirectory: root,
|
|
4255
5343
|
workspaceId,
|
|
4256
5344
|
provider,
|
|
@@ -4276,6 +5364,7 @@ async function expiredSkillAuthorizationForReplacement(provider, workspaceId, or
|
|
|
4276
5364
|
throw error;
|
|
4277
5365
|
const refreshed = await loadActiveSkillAuthorizationAnchor({
|
|
4278
5366
|
config, workspaceId, organizationAgentId, provider, fresh: true,
|
|
5367
|
+
hostScope: currentBootstrapHostScope(),
|
|
4279
5368
|
});
|
|
4280
5369
|
if (!refreshed)
|
|
4281
5370
|
throw error;
|
|
@@ -4299,22 +5388,18 @@ async function loadVerifiedWorkspacePolicy(path, workspaceId) {
|
|
|
4299
5388
|
return verified;
|
|
4300
5389
|
}
|
|
4301
5390
|
export async function installRepositoryAgentFabricSkill(input) {
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
const
|
|
4308
|
-
const skillRoot = resolve(input.workspace, '.agents', 'skills', 'dharma-agent-fabric');
|
|
4309
|
-
if (ownership !== 'signed')
|
|
4310
|
-
await mkdir(resolve(skillRoot, 'references'), { recursive: true, mode: 0o700 });
|
|
4311
|
-
await mkdir(resolve(input.workspace, '.dharma'), { recursive: true, mode: 0o700 });
|
|
5391
|
+
const scope = currentBootstrapHostScope();
|
|
5392
|
+
input = captureRepositoryInstallerInput(input);
|
|
5393
|
+
const prepared = await prepareRepositoryInstallerWorkspace(input.workspace, input.workspaceId);
|
|
5394
|
+
input = { ...input, workspace: prepared.workspace };
|
|
5395
|
+
const ownership = prepared.ownership;
|
|
5396
|
+
const contract = scope ? await scope.step(() => loadAgentFabricOnboardingContract()) : await loadAgentFabricOnboardingContract();
|
|
4312
5397
|
const skill = `---
|
|
4313
5398
|
name: dharma-agent-fabric
|
|
4314
5399
|
description: Connect this repository to Dharma Agent Fabric for shared knowledge, signed skills, and team coordination.
|
|
4315
5400
|
---
|
|
4316
5401
|
|
|
4317
|
-
${
|
|
5402
|
+
${contract.markdown}
|
|
4318
5403
|
`;
|
|
4319
5404
|
const reference = `# Organization connection
|
|
4320
5405
|
|
|
@@ -4373,6 +5458,7 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
4373
5458
|
});
|
|
4374
5459
|
await writeRepositoryInstallerFile(input.workspace, '.dharma/agent-fabric.json', `${JSON.stringify(connection, null, 2)}\n`);
|
|
4375
5460
|
await writeRepositoryInstallerFile(input.workspace, '.dharma/repository-agent.json', `${JSON.stringify(repositoryAgent, null, 2)}\n`);
|
|
5461
|
+
await scope?.assert();
|
|
4376
5462
|
return {
|
|
4377
5463
|
skillPath: '.agents/skills/dharma-agent-fabric/SKILL.md',
|
|
4378
5464
|
connectionPath: '.dharma/agent-fabric.json',
|
|
@@ -4391,7 +5477,7 @@ async function repositoryInstallerRecoveryCommand(flags) {
|
|
|
4391
5477
|
const config = await readDeviceConfig();
|
|
4392
5478
|
if (!config || config.organizationId !== organizationId)
|
|
4393
5479
|
throw new Error('Recovery requires current organization enrollment.');
|
|
4394
|
-
await loadDeviceEnrollmentAnchor({ config });
|
|
5480
|
+
await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
|
|
4395
5481
|
const identity = await preflightBootstrapWorkspaceIdentity(workspace);
|
|
4396
5482
|
const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
|
|
4397
5483
|
path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
|
|
@@ -4455,6 +5541,8 @@ async function onboard(flags) {
|
|
|
4455
5541
|
const requestedHqUrl = normalizeHqUrl(portalUrl(flags));
|
|
4456
5542
|
let config = await readDeviceConfig();
|
|
4457
5543
|
if (!config) {
|
|
5544
|
+
if (currentBootstrapHostScope())
|
|
5545
|
+
throw new Error('codex_setup_host_enrollment_missing');
|
|
4458
5546
|
const loginFlags = new Map(flags);
|
|
4459
5547
|
loginFlags.set('hq-url', requestedHqUrl);
|
|
4460
5548
|
loginFlags.set('organization-id', organizationId);
|
|
@@ -4726,10 +5814,8 @@ async function evidenceSync(flags) {
|
|
|
4726
5814
|
assertCapsuleIntegrity(capsule);
|
|
4727
5815
|
const trajectoryId = String(capsule.trajectoryId || '');
|
|
4728
5816
|
const revision = Number(capsule.revision);
|
|
4729
|
-
const
|
|
4730
|
-
const vault = await LocalVault.open({
|
|
5817
|
+
const vault = await openBootstrapVault({
|
|
4731
5818
|
root: resolve(dharmaHome(), 'vault'),
|
|
4732
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
4733
5819
|
});
|
|
4734
5820
|
try {
|
|
4735
5821
|
const storedCapsule = await vault.getCapsule(trajectoryId, revision);
|
|
@@ -4743,7 +5829,7 @@ async function evidenceSync(flags) {
|
|
|
4743
5829
|
return fabric.syncTrajectory(storedCapsule);
|
|
4744
5830
|
}
|
|
4745
5831
|
finally {
|
|
4746
|
-
vault.close();
|
|
5832
|
+
await vault.close();
|
|
4747
5833
|
}
|
|
4748
5834
|
}
|
|
4749
5835
|
async function processEvidenceRequest(fabric, policy, workspaceId) {
|
|
@@ -4801,10 +5887,8 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
|
|
|
4801
5887
|
finally {
|
|
4802
5888
|
await requestHandle?.close().catch(() => undefined);
|
|
4803
5889
|
}
|
|
4804
|
-
const
|
|
4805
|
-
const vault = await LocalVault.open({
|
|
5890
|
+
const vault = await openBootstrapVault({
|
|
4806
5891
|
root: resolve(dharmaHome(), 'vault'),
|
|
4807
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
4808
5892
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
4809
5893
|
});
|
|
4810
5894
|
try {
|
|
@@ -4910,7 +5994,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
|
|
|
4910
5994
|
const receipt = accepted.receipt && typeof accepted.receipt === 'object' ? accepted.receipt : {};
|
|
4911
5995
|
const receiptHash = typeof receipt.hash === 'string' && /^sha256:[a-f0-9]{64}$/.test(receipt.hash)
|
|
4912
5996
|
? receipt.hash : response.responseHash;
|
|
4913
|
-
vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
|
|
5997
|
+
await vault.recordDisclosure(unsignedResponse.responseId, receiptHash, bytesPrepared);
|
|
4914
5998
|
await writeJsonAtomic(requestReceipt, {
|
|
4915
5999
|
schema: 'dharma.evidence-request-receipt/v1',
|
|
4916
6000
|
requestId: request.requestId,
|
|
@@ -4926,7 +6010,7 @@ async function processEvidenceRequest(fabric, policy, workspaceId) {
|
|
|
4926
6010
|
};
|
|
4927
6011
|
}
|
|
4928
6012
|
finally {
|
|
4929
|
-
vault.close();
|
|
6013
|
+
await vault.close();
|
|
4930
6014
|
}
|
|
4931
6015
|
}
|
|
4932
6016
|
async function runOneEvidenceRequest(flags) {
|
|
@@ -5038,14 +6122,12 @@ async function syncSignedTaskTrajectory(input) {
|
|
|
5038
6122
|
if (!validation.ok)
|
|
5039
6123
|
throw new Error(`Signed task capsule failed schema validation: ${JSON.stringify(validation.errors)}`);
|
|
5040
6124
|
assertCapsuleAuthorizedByCurrentPolicy(capsule, input.policy);
|
|
5041
|
-
const
|
|
5042
|
-
const vault = await LocalVault.open({
|
|
6125
|
+
const vault = await openBootstrapVault({
|
|
5043
6126
|
root: resolve(dharmaHome(), 'vault'),
|
|
5044
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5045
6127
|
rawLocalDays: rawLocalRetentionDays(input.policy),
|
|
5046
6128
|
});
|
|
5047
6129
|
try {
|
|
5048
|
-
const existing = vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
|
|
6130
|
+
const existing = await vault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
|
|
5049
6131
|
if (!existing) {
|
|
5050
6132
|
await vault.commitCapture({
|
|
5051
6133
|
raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: rawContentId },
|
|
@@ -5062,14 +6144,14 @@ async function syncSignedTaskTrajectory(input) {
|
|
|
5062
6144
|
else if (existing.capsuleHash !== capsule.capsuleHash) {
|
|
5063
6145
|
throw new Error('Signed task evidence already exists with different immutable content.');
|
|
5064
6146
|
}
|
|
5065
|
-
vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
|
|
6147
|
+
await vault.queueCapsuleSync(capsule.trajectoryId, capsule.revision);
|
|
5066
6148
|
await reserveDailyContentUpload(capsule, input.policy);
|
|
5067
6149
|
const synced = await input.fabric.syncTrajectory(capsule);
|
|
5068
|
-
vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
|
|
6150
|
+
await vault.markCapsuleSynced(capsule.trajectoryId, capsule.revision);
|
|
5069
6151
|
return synced;
|
|
5070
6152
|
}
|
|
5071
6153
|
finally {
|
|
5072
|
-
vault.close();
|
|
6154
|
+
await vault.close();
|
|
5073
6155
|
}
|
|
5074
6156
|
}
|
|
5075
6157
|
export function assertRecoveredTaskWorkspacePolicy(input) {
|
|
@@ -5112,10 +6194,8 @@ export async function postTaskOutcome(input) {
|
|
|
5112
6194
|
return input.postEvent(input.task.taskId, input.receipt.status, input.payload);
|
|
5113
6195
|
}
|
|
5114
6196
|
async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, prepared) {
|
|
5115
|
-
const
|
|
5116
|
-
const vault = await LocalVault.open({
|
|
6197
|
+
const vault = await openBootstrapVault({
|
|
5117
6198
|
root: resolve(dharmaHome(), 'vault'),
|
|
5118
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5119
6199
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
5120
6200
|
});
|
|
5121
6201
|
try {
|
|
@@ -5131,7 +6211,7 @@ async function stageSignedTaskTrajectoryRecovery(taskId, workspaceId, policy, pr
|
|
|
5131
6211
|
await vault.stageTaskCompletionRecovery(taskId, Buffer.from(JSON.stringify(recovery)));
|
|
5132
6212
|
}
|
|
5133
6213
|
finally {
|
|
5134
|
-
vault.close();
|
|
6214
|
+
await vault.close();
|
|
5135
6215
|
}
|
|
5136
6216
|
}
|
|
5137
6217
|
async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, onlyTaskId) {
|
|
@@ -5139,10 +6219,8 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
|
|
|
5139
6219
|
.filter((item) => !onlyTaskId || item.taskId === onlyTaskId);
|
|
5140
6220
|
const finalized = [];
|
|
5141
6221
|
for (const completion of completions) {
|
|
5142
|
-
const
|
|
5143
|
-
const vault = await LocalVault.open({
|
|
6222
|
+
const vault = await openBootstrapVault({
|
|
5144
6223
|
root: resolve(dharmaHome(), 'vault'),
|
|
5145
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5146
6224
|
rawLocalDays: rawLocalRetentionDays(vaultPolicy),
|
|
5147
6225
|
});
|
|
5148
6226
|
let recovery;
|
|
@@ -5150,7 +6228,7 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
|
|
|
5150
6228
|
recovery = await vault.getTaskCompletionRecovery(completion.taskId);
|
|
5151
6229
|
}
|
|
5152
6230
|
finally {
|
|
5153
|
-
vault.close();
|
|
6231
|
+
await vault.close();
|
|
5154
6232
|
}
|
|
5155
6233
|
if (!recovery || recovery.schema !== 'dharma.signed-task-trajectory-recovery/v1'
|
|
5156
6234
|
|| recovery.taskId !== completion.taskId) {
|
|
@@ -5181,13 +6259,12 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
|
|
|
5181
6259
|
};
|
|
5182
6260
|
assertCapsuleIntegrity(capsule);
|
|
5183
6261
|
if (recoveredTaskPolicyWasSuperseded(capsule, recoveryPolicy)) {
|
|
5184
|
-
const supersededVault = await
|
|
6262
|
+
const supersededVault = await openBootstrapVault({
|
|
5185
6263
|
root: resolve(dharmaHome(), 'vault'),
|
|
5186
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5187
6264
|
rawLocalDays: rawLocalRetentionDays(vaultPolicy),
|
|
5188
6265
|
});
|
|
5189
6266
|
try {
|
|
5190
|
-
const existing = supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
|
|
6267
|
+
const existing = await supersededVault.getCapsuleMetadata(capsule.trajectoryId, capsule.revision);
|
|
5191
6268
|
if (!existing) {
|
|
5192
6269
|
await supersededVault.commitCapture({
|
|
5193
6270
|
raw: { plaintext: rawTurn, kind: 'raw-provider-turn', expectedContentId: recovery.prepared.rawContentId },
|
|
@@ -5208,22 +6285,21 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
|
|
|
5208
6285
|
else if (existing.capsuleHash !== capsule.capsuleHash) {
|
|
5209
6286
|
throw new Error('Recovered task evidence already exists with different immutable content.');
|
|
5210
6287
|
}
|
|
5211
|
-
supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
|
|
6288
|
+
await supersededVault.discardPendingCapsuleSync(capsule.trajectoryId, capsule.revision, 'policy_revision_superseded');
|
|
5212
6289
|
}
|
|
5213
6290
|
finally {
|
|
5214
|
-
supersededVault.close();
|
|
6291
|
+
await supersededVault.close();
|
|
5215
6292
|
}
|
|
5216
6293
|
await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
|
|
5217
|
-
const acknowledgedVault = await
|
|
6294
|
+
const acknowledgedVault = await openBootstrapVault({
|
|
5218
6295
|
root: resolve(dharmaHome(), 'vault'),
|
|
5219
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5220
6296
|
rawLocalDays: rawLocalRetentionDays(vaultPolicy),
|
|
5221
6297
|
});
|
|
5222
6298
|
try {
|
|
5223
6299
|
await acknowledgedVault.clearTaskCompletionRecovery(completion.taskId);
|
|
5224
6300
|
}
|
|
5225
6301
|
finally {
|
|
5226
|
-
acknowledgedVault.close();
|
|
6302
|
+
await acknowledgedVault.close();
|
|
5227
6303
|
}
|
|
5228
6304
|
finalized.push({
|
|
5229
6305
|
taskId: completion.taskId,
|
|
@@ -5247,16 +6323,15 @@ async function finalizeRecoveredSignedTaskTrajectories(fabric, vaultPolicy, only
|
|
|
5247
6323
|
},
|
|
5248
6324
|
});
|
|
5249
6325
|
await fabric.acknowledgeRecoveredTaskCompletion(completion.taskId, completion.receiptHash);
|
|
5250
|
-
const cleanupVault = await
|
|
6326
|
+
const cleanupVault = await openBootstrapVault({
|
|
5251
6327
|
root: resolve(dharmaHome(), 'vault'),
|
|
5252
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
5253
6328
|
rawLocalDays: rawLocalRetentionDays(vaultPolicy),
|
|
5254
6329
|
});
|
|
5255
6330
|
try {
|
|
5256
6331
|
await cleanupVault.clearTaskCompletionRecovery(completion.taskId);
|
|
5257
6332
|
}
|
|
5258
6333
|
finally {
|
|
5259
|
-
cleanupVault.close();
|
|
6334
|
+
await cleanupVault.close();
|
|
5260
6335
|
}
|
|
5261
6336
|
finalized.push({ taskId: completion.taskId, trajectory });
|
|
5262
6337
|
}
|
|
@@ -5467,6 +6542,7 @@ export async function nativeSkillDirectoryForWorkspace(input) {
|
|
|
5467
6542
|
const anchor = await loadActiveSkillAuthorizationAnchor({
|
|
5468
6543
|
config, workspaceId: input.workspaceId, organizationAgentId: registered.repositoryAgentId,
|
|
5469
6544
|
provider: input.provider, fresh: true,
|
|
6545
|
+
hostScope: currentBootstrapHostScope(),
|
|
5470
6546
|
});
|
|
5471
6547
|
if (anchor) {
|
|
5472
6548
|
const matching = await nativeSkillRootMatchingAuthorization({
|
|
@@ -6108,6 +7184,7 @@ export function validateSkillBundleSources(bundle) {
|
|
|
6108
7184
|
}
|
|
6109
7185
|
export async function recoverLegacySkillBundleIdAfterAuthorizationFailure(input) {
|
|
6110
7186
|
const legacyBundleId = await getLegacySkillBundleIdForUpgrade({
|
|
7187
|
+
hostScope: currentBootstrapHostScope(),
|
|
6111
7188
|
nativeSkillDirectory: input.nativeSkillDirectory,
|
|
6112
7189
|
workspaceId: input.workspaceId,
|
|
6113
7190
|
});
|
|
@@ -6138,7 +7215,7 @@ export async function loadSkillSynchronizationPolicy(policyPath, workspaceId, se
|
|
|
6138
7215
|
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: config.serverPublicKeyEd25519,
|
|
6139
7216
|
organizationId: config.organizationId, workspaceId });
|
|
6140
7217
|
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
6141
|
-
const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore });
|
|
7218
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config, store: secureStore, hostScope: currentBootstrapHostScope() });
|
|
6142
7219
|
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
6143
7220
|
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
6144
7221
|
organizationId: config.organizationId, workspaceId });
|
|
@@ -6178,7 +7255,8 @@ export async function prepareSkillUpdate(input) {
|
|
|
6178
7255
|
let legacyBaselineMigrationRequested = false;
|
|
6179
7256
|
if (input.automatic) {
|
|
6180
7257
|
activeBundleId = (await loadActiveSkillAuthorizationAnchor({ config, workspaceId,
|
|
6181
|
-
organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true
|
|
7258
|
+
organizationAgentId: String(workspace.repositoryAgentId || ''), provider, store: input.store, fresh: true,
|
|
7259
|
+
hostScope: currentBootstrapHostScope() }))?.bundleId ?? null;
|
|
6182
7260
|
assertRunning();
|
|
6183
7261
|
}
|
|
6184
7262
|
else {
|
|
@@ -6298,7 +7376,7 @@ export async function prepareSkillUpdate(input) {
|
|
|
6298
7376
|
assertCurrent();
|
|
6299
7377
|
}
|
|
6300
7378
|
for (const skill of bundle.skills) {
|
|
6301
|
-
const actual = await contentHash(containedInlinePath(sourceRoot, skill.path));
|
|
7379
|
+
const actual = await contentHash(containedInlinePath(sourceRoot, skill.path), currentBootstrapHostScope());
|
|
6302
7380
|
assertCurrent();
|
|
6303
7381
|
if (actual !== skill.contentHash)
|
|
6304
7382
|
throw new Error('Prepared skill tree does not match the signed content hash.');
|
|
@@ -6323,12 +7401,9 @@ async function activatePreparedSkillUpdate(input) {
|
|
|
6323
7401
|
workspaceId,
|
|
6324
7402
|
organizationAgentId: String(workspace.repositoryAgentId || ''),
|
|
6325
7403
|
provider,
|
|
7404
|
+
hostScope: currentBootstrapHostScope(),
|
|
6326
7405
|
});
|
|
6327
|
-
const identity = await
|
|
6328
|
-
hqUrl: config.hqUrl,
|
|
6329
|
-
organizationId: config.organizationId,
|
|
6330
|
-
installationId: config.installationId,
|
|
6331
|
-
});
|
|
7406
|
+
const identity = await enrolledDeviceIdentity(config);
|
|
6332
7407
|
repositoryDelivery?.assertCurrent();
|
|
6333
7408
|
const current = await loadSkillSynchronizationPolicy(policyPath, workspaceId);
|
|
6334
7409
|
if (canonicalize(current.policy) !== canonicalize(policy)
|
|
@@ -6338,6 +7413,7 @@ async function activatePreparedSkillUpdate(input) {
|
|
|
6338
7413
|
}
|
|
6339
7414
|
repositoryDelivery?.assertCurrent();
|
|
6340
7415
|
const receipt = await installSkillBundle({
|
|
7416
|
+
hostScope: currentBootstrapHostScope(),
|
|
6341
7417
|
bundle,
|
|
6342
7418
|
sourceDirectory: sourceRoot,
|
|
6343
7419
|
nativeSkillDirectory: destination,
|
|
@@ -6375,16 +7451,18 @@ async function activatePreparedSkillUpdate(input) {
|
|
|
6375
7451
|
receiptHash: previousAnchor.receiptHash,
|
|
6376
7452
|
activatedAt: previousAnchor.activatedAt,
|
|
6377
7453
|
expiresAt: previousAnchor.expiresAt,
|
|
7454
|
+
hostScope: currentBootstrapHostScope(),
|
|
6378
7455
|
});
|
|
6379
7456
|
}
|
|
6380
7457
|
else {
|
|
6381
|
-
await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider });
|
|
7458
|
+
await deleteActiveSkillAuthorizationAnchor({ config, workspaceId, provider, hostScope: currentBootstrapHostScope() });
|
|
6382
7459
|
}
|
|
6383
7460
|
};
|
|
6384
7461
|
const recoverLocalInstallation = async (error) => {
|
|
6385
7462
|
const recoveryErrors = [];
|
|
6386
7463
|
try {
|
|
6387
7464
|
await rollbackUnconfirmedSkillBundle({
|
|
7465
|
+
hostScope: currentBootstrapHostScope(),
|
|
6388
7466
|
nativeSkillDirectory: destination,
|
|
6389
7467
|
workspaceId,
|
|
6390
7468
|
receipt,
|
|
@@ -6415,6 +7493,7 @@ async function activatePreparedSkillUpdate(input) {
|
|
|
6415
7493
|
receiptHash: receipt.receiptHash,
|
|
6416
7494
|
activatedAt: receipt.completedAt,
|
|
6417
7495
|
expiresAt: bundle.expiresAt ?? null,
|
|
7496
|
+
hostScope: currentBootstrapHostScope(),
|
|
6418
7497
|
});
|
|
6419
7498
|
}
|
|
6420
7499
|
}
|
|
@@ -6462,12 +7541,8 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
|
|
|
6462
7541
|
if (!config || config.organizationId !== workspace.organizationId || !workspace.repositoryBindingId || !workspace.repositoryAgentId) {
|
|
6463
7542
|
throw new Error('Installed repository knowledge requires current enrolled workspace scope.');
|
|
6464
7543
|
}
|
|
6465
|
-
const identity = await
|
|
6466
|
-
|
|
6467
|
-
organizationId: config.organizationId,
|
|
6468
|
-
installationId: config.installationId,
|
|
6469
|
-
});
|
|
6470
|
-
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
7544
|
+
const identity = await enrolledDeviceIdentity(config);
|
|
7545
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config, hostScope: currentBootstrapHostScope() });
|
|
6471
7546
|
if (identity.publicKeyEd25519 !== enrollment.devicePublicKeyEd25519)
|
|
6472
7547
|
throw new Error('Installed knowledge device identity mismatch.');
|
|
6473
7548
|
const organizationAgentId = workspace.repositoryAgentId;
|
|
@@ -6481,11 +7556,13 @@ async function installedRepositoryKnowledge(workspace, selectedProvider) {
|
|
|
6481
7556
|
});
|
|
6482
7557
|
if (!await pathExistsOrThrow(resolve(root, '.dharma-managed/workspaces', workspace.workspaceId, 'ACTIVE_BUNDLE')))
|
|
6483
7558
|
return null;
|
|
6484
|
-
const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider
|
|
7559
|
+
const anchorInput = { config, workspaceId: workspace.workspaceId, organizationAgentId, provider,
|
|
7560
|
+
hostScope: currentBootstrapHostScope() };
|
|
6485
7561
|
const active = await loadActiveSkillAuthorizationAnchor(anchorInput);
|
|
6486
7562
|
if (!active)
|
|
6487
7563
|
throw new Error('Installed knowledge release lacks a protected authorization anchor.');
|
|
6488
7564
|
const read = (expectedReceiptHash) => readVerifiedRepositoryKnowledge({
|
|
7565
|
+
hostScope: currentBootstrapHostScope(),
|
|
6489
7566
|
nativeSkillDirectory: root, workspaceId: workspace.workspaceId, provider,
|
|
6490
7567
|
organizationId: config.organizationId, organizationAgentId, deviceId: config.deviceId,
|
|
6491
7568
|
serverPublicKey: createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: enrollment.serverPublicKeyEd25519 }, format: 'jwk' }),
|
|
@@ -6523,7 +7600,7 @@ async function takeCachedSkillUpdate(input) {
|
|
|
6523
7600
|
const serverPublicKey = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: config.serverPublicKeyEd25519 }, format: 'jwk' });
|
|
6524
7601
|
verifySkillBundle(bundle, serverPublicKey);
|
|
6525
7602
|
for (const skill of bundle.skills) {
|
|
6526
|
-
if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path)) !== skill.contentHash) {
|
|
7603
|
+
if (await contentHash(containedInlinePath(cached.sourceRoot, skill.path), currentBootstrapHostScope()) !== skill.contentHash) {
|
|
6527
7604
|
throw new Error('Cached skill tree does not match the signed content hash.');
|
|
6528
7605
|
}
|
|
6529
7606
|
}
|
|
@@ -6690,14 +7767,12 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
6690
7767
|
let repositorySourceState = 'not_scanned';
|
|
6691
7768
|
let sourceScanFlight = null;
|
|
6692
7769
|
let sourceScanReady = null;
|
|
6693
|
-
const
|
|
6694
|
-
const vault = await LocalVault.open({
|
|
7770
|
+
const vault = await openBootstrapVault({
|
|
6695
7771
|
root: resolve(dharmaHome(), 'vault'),
|
|
6696
|
-
masterKey: await loadOrCreateVaultMasterKey(),
|
|
6697
7772
|
rawLocalDays: rawLocalRetentionDays(policy),
|
|
6698
7773
|
});
|
|
6699
7774
|
if (signal.aborted)
|
|
6700
|
-
vault.close();
|
|
7775
|
+
await vault.close();
|
|
6701
7776
|
signal.throwIfAborted();
|
|
6702
7777
|
signal.addEventListener('abort', stop, { once: true });
|
|
6703
7778
|
if (signal.aborted)
|
|
@@ -7023,7 +8098,7 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
7023
8098
|
await sourceScanFlight;
|
|
7024
8099
|
await consumeSourceScan();
|
|
7025
8100
|
await skillPreparationPump.stop();
|
|
7026
|
-
vault.close();
|
|
8101
|
+
await vault.close();
|
|
7027
8102
|
signal.removeEventListener('abort', stop);
|
|
7028
8103
|
}
|
|
7029
8104
|
return {
|
|
@@ -7039,6 +8114,20 @@ async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
|
7039
8114
|
export async function run(argv) {
|
|
7040
8115
|
const { positional, flags, repeated } = parseCliOptions(argv);
|
|
7041
8116
|
const [command, subcommand] = positional;
|
|
8117
|
+
const setupScope = currentBootstrapHostScope();
|
|
8118
|
+
if (setupScope) {
|
|
8119
|
+
// An admitted native callback keeps its original authority through the
|
|
8120
|
+
// official dispatcher. Closed descendants never use the legacy route.
|
|
8121
|
+
const prepared = await inspectCodexBootstrapHostPreparation(setupScope);
|
|
8122
|
+
if (command !== 'bootstrap' || positional.length !== 1 || flags.size !== prepared.flags.size
|
|
8123
|
+
|| [...repeated.values()].some(values => values.length !== 1)
|
|
8124
|
+
|| [...prepared.flags].some(([key, value]) => flags.get(key) !== value)) {
|
|
8125
|
+
throw new Error('codex_setup_host_command_mismatch');
|
|
8126
|
+
}
|
|
8127
|
+
if (!prepared.flags.has('dry-run'))
|
|
8128
|
+
await originalCodexSetupSessionSender(setupScope);
|
|
8129
|
+
return setupScope.step(() => bootstrap(flags, setupScope));
|
|
8130
|
+
}
|
|
7042
8131
|
if (command === 'relay' && subcommand === 'container-entrypoint') {
|
|
7043
8132
|
if (positional.length === 2 && flags.size === 1 && flags.get('dry-run') === true) {
|
|
7044
8133
|
return { ok: true, stage: 'container_entrypoint_plan', started: false,
|