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