@dharma-ai-labs/agent-fabric 0.2.119 → 0.2.121
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/AGENT_FABRIC_ONBOARDING.md +4 -0
- package/README.md +46 -0
- package/dist/demoEnrollment.d.ts.map +1 -1
- package/dist/demoEnrollment.js +81 -9
- package/dist/demoEnrollment.js.map +1 -1
- package/dist/demoEnrollment.test.js +185 -0
- package/dist/demoEnrollment.test.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -378
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +18 -2
- package/dist/index.test.js.map +1 -1
- package/dist/legacyInstallerRecovery.d.ts +30 -0
- package/dist/legacyInstallerRecovery.d.ts.map +1 -0
- package/dist/legacyInstallerRecovery.js +107 -0
- package/dist/legacyInstallerRecovery.js.map +1 -0
- package/dist/legacyInstallerRecovery.test.d.ts +2 -0
- package/dist/legacyInstallerRecovery.test.d.ts.map +1 -0
- package/dist/legacyInstallerRecovery.test.js +126 -0
- package/dist/legacyInstallerRecovery.test.js.map +1 -0
- package/dist/relayAutostart.d.ts +1 -0
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +12 -3
- package/dist/relayAutostart.js.map +1 -1
- package/dist/relayAutostart.test.js +37 -0
- package/dist/relayAutostart.test.js.map +1 -1
- package/dist/relayRuntimeUpgrade.test.js +13 -2
- package/dist/relayRuntimeUpgrade.test.js.map +1 -1
- package/dist/repositoryInstallerFiles.d.ts +2 -1
- package/dist/repositoryInstallerFiles.d.ts.map +1 -1
- package/dist/repositoryInstallerFiles.js +6 -3
- package/dist/repositoryInstallerFiles.js.map +1 -1
- package/dist/repositoryOnboardingCaller.test.js +87 -1
- package/dist/repositoryOnboardingCaller.test.js.map +1 -1
- package/dist/repositoryOnboardingFlow.test.js +4 -3
- package/dist/repositoryOnboardingFlow.test.js.map +1 -1
- package/dist/repositoryRelaySupervisor.d.ts +39 -0
- package/dist/repositoryRelaySupervisor.d.ts.map +1 -0
- package/dist/repositoryRelaySupervisor.js +145 -0
- package/dist/repositoryRelaySupervisor.js.map +1 -0
- package/dist/repositoryRelaySupervisor.test.d.ts +2 -0
- package/dist/repositoryRelaySupervisor.test.d.ts.map +1 -0
- package/dist/repositoryRelaySupervisor.test.js +191 -0
- package/dist/repositoryRelaySupervisor.test.js.map +1 -0
- package/dist/skillPreparationCache.test.js +8 -4
- package/dist/skillPreparationCache.test.js.map +1 -1
- package/dist/skillPreparationCaller.test.js +4 -2
- package/dist/skillPreparationCaller.test.js.map +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/usage.js.map +1 -1
- package/dist/workspaceRegistry.d.ts +6 -0
- package/dist/workspaceRegistry.d.ts.map +1 -0
- package/dist/workspaceRegistry.js +24 -0
- package/dist/workspaceRegistry.js.map +1 -0
- package/dist/workspaceRegistry.test.d.ts +2 -0
- package/dist/workspaceRegistry.test.d.ts.map +1 -0
- package/dist/workspaceRegistry.test.js +57 -0
- package/dist/workspaceRegistry.test.js.map +1 -0
- package/dist/workspaceRegistryRecovery.d.ts +60 -0
- package/dist/workspaceRegistryRecovery.d.ts.map +1 -0
- package/dist/workspaceRegistryRecovery.js +167 -0
- package/dist/workspaceRegistryRecovery.js.map +1 -0
- package/dist/workspaceRegistryRecovery.test.d.ts +2 -0
- package/dist/workspaceRegistryRecovery.test.d.ts.map +1 -0
- package/dist/workspaceRegistryRecovery.test.js +111 -0
- package/dist/workspaceRegistryRecovery.test.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,7 +17,10 @@ import { contentHash, getActiveSkillBundleAuthorization, getExpiredSkillBundleAu
|
|
|
17
17
|
import { executeTask, FileActionExecutionJournal, FileTaskReceiptStore, providerInstructionsForTask, } from '@dharma-ai-labs/agent-fabric-task-runner';
|
|
18
18
|
import { CLI_USAGE } from './usage.js';
|
|
19
19
|
import { superviseRelay } from './relaySupervisor.js';
|
|
20
|
+
import { repositoryRelayObservationReady, runRegisteredRepositoryRelays, selectRepositoryRelayRegistrations, serializeRelayWork, waitForRelayRefresh } from './repositoryRelaySupervisor.js';
|
|
20
21
|
import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
22
|
+
import { readWorkspaceRegistry } from './workspaceRegistry.js';
|
|
23
|
+
import { appendRecoveredWorkspace, applyRegistryRecoveryFile, inspectRegistryRecoveryFile, resolveRegistryRecoveryProjection } from './workspaceRegistryRecovery.js';
|
|
21
24
|
import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
|
|
22
25
|
import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
|
|
23
26
|
import { initializeRepositoryKnowledge, readRepositoryKnowledgeSource } from './repositoryKnowledge.js';
|
|
@@ -25,6 +28,7 @@ import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readReposito
|
|
|
25
28
|
import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
|
|
26
29
|
import { advanceRepositorySourceBaseline, BlockedRepositorySourceRetry, fetchRepositorySourceAuthorization, recoverPublishedLocalSourceBaseline, RepositorySourceWatcher, scanRepositorySourceChanges, seedRepositorySourceWatcher } from './repositorySourceSync.js';
|
|
27
30
|
import { assertRepositoryInstallerOwnership, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
|
|
31
|
+
import { recoverLegacyRepositoryInstaller, selectLegacyInstallerRecoveryWorkspace } from './legacyInstallerRecovery.js';
|
|
28
32
|
import { onboardingResumeCommand, selectDeviceWorkspace, workspaceIdForDevice } from './onboardingWorkspace.js';
|
|
29
33
|
import { receiveRepositoryPackageDelivery } from './repositoryPackageDelivery.js';
|
|
30
34
|
import { selectInstalledRepositoryKnowledge } from './repositoryInstalledKnowledge.js';
|
|
@@ -56,7 +60,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
|
|
|
56
60
|
import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
|
|
57
61
|
import { startNamedCodexThread } from './namedCodexThread.js';
|
|
58
62
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
59
|
-
const VERSION = '0.2.
|
|
63
|
+
const VERSION = '0.2.121';
|
|
60
64
|
const USAGE = CLI_USAGE;
|
|
61
65
|
const execFileAsync = promisify(execFile);
|
|
62
66
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -1294,21 +1298,28 @@ async function platform() {
|
|
|
1294
1298
|
throw new Error(`Unsupported device platform: ${process.platform}`);
|
|
1295
1299
|
}
|
|
1296
1300
|
async function registry() {
|
|
1297
|
-
|
|
1298
|
-
return JSON.parse(await readFile(workspaceRegistryPath(), 'utf8'));
|
|
1299
|
-
}
|
|
1300
|
-
catch {
|
|
1301
|
-
return [];
|
|
1302
|
-
}
|
|
1301
|
+
return (await readWorkspaceRegistry(workspaceRegistryPath())).records;
|
|
1303
1302
|
}
|
|
1304
1303
|
async function saveRegistry(items) {
|
|
1305
1304
|
await mkdir(resolve(dharmaHome(), 'registry'), { recursive: true, mode: 0o700 });
|
|
1306
|
-
await
|
|
1305
|
+
await writeJsonAtomic(workspaceRegistryPath(), items);
|
|
1307
1306
|
}
|
|
1308
1307
|
async function saveWorkspaceRecord(entry) {
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1308
|
+
const release = await acquirePidLock(`${workspaceRegistryPath()}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
|
|
1309
|
+
try {
|
|
1310
|
+
const value = await readFile(workspaceRegistryPath(), 'utf8').catch(error => {
|
|
1311
|
+
if (error.code === 'ENOENT')
|
|
1312
|
+
return '[]';
|
|
1313
|
+
throw error;
|
|
1314
|
+
});
|
|
1315
|
+
const items = JSON.parse(value);
|
|
1316
|
+
if (!Array.isArray(items))
|
|
1317
|
+
throw new Error('Workspace registry is invalid; preserve it for recovery.');
|
|
1318
|
+
await saveRegistry([...items.filter(item => item.workspaceId !== entry.workspaceId), entry]);
|
|
1319
|
+
}
|
|
1320
|
+
finally {
|
|
1321
|
+
await release();
|
|
1322
|
+
}
|
|
1312
1323
|
}
|
|
1313
1324
|
async function gitValue(workspace, argv) {
|
|
1314
1325
|
try {
|
|
@@ -1656,6 +1667,15 @@ export async function waitForRelayReadiness(options = {}) {
|
|
|
1656
1667
|
throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
|
|
1657
1668
|
}
|
|
1658
1669
|
async function startRelayDaemon(policyPath) {
|
|
1670
|
+
const config = await readDeviceConfig();
|
|
1671
|
+
if (!config)
|
|
1672
|
+
throw new Error('Relay startup requires current device enrollment.');
|
|
1673
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
1674
|
+
const workspaceId = policy.serverAuthorization?.workspaceId;
|
|
1675
|
+
const selected = (await registry()).find(row => row.workspaceId === workspaceId
|
|
1676
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
1677
|
+
if (!selected)
|
|
1678
|
+
throw new Error('Relay policy must belong to this enrolled repository.');
|
|
1659
1679
|
const alreadyRunning = await relayProcessState() === 'running';
|
|
1660
1680
|
const supervisorState = await relaySupervisorProcessState();
|
|
1661
1681
|
if (supervisorState === 'unknown')
|
|
@@ -1677,8 +1697,11 @@ async function startRelayDaemon(policyPath) {
|
|
|
1677
1697
|
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
1678
1698
|
.then(value => JSON.parse(value)).catch(() => null);
|
|
1679
1699
|
if (binding?.pid === pid) {
|
|
1680
|
-
if (binding.
|
|
1681
|
-
throw new Error('
|
|
1700
|
+
if (binding.version !== VERSION || !binding.standardRepositories) {
|
|
1701
|
+
throw new Error('relay_runtime_upgrade_required: safely stop and upgrade the existing startup-anchor relay; do not retarget a working receiver.');
|
|
1702
|
+
}
|
|
1703
|
+
if (binding.organizationId !== config.organizationId || binding.deviceId !== config.deviceId) {
|
|
1704
|
+
throw new Error('relay_workspace_conflict: the active supervisor belongs to another enrollment.');
|
|
1682
1705
|
}
|
|
1683
1706
|
supervisorReady = true;
|
|
1684
1707
|
break;
|
|
@@ -1689,7 +1712,14 @@ async function startRelayDaemon(policyPath) {
|
|
|
1689
1712
|
}
|
|
1690
1713
|
if (!supervisorReady)
|
|
1691
1714
|
throw new Error('The relay supervisor did not become ready after bootstrap.');
|
|
1692
|
-
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION
|
|
1715
|
+
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
|
|
1716
|
+
if (await relayProcessState() !== 'running')
|
|
1717
|
+
return 'stopped';
|
|
1718
|
+
const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
|
|
1719
|
+
const poll = await readFile(resolve(dharmaHome(), 'relay', 'repositories', selected.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
1720
|
+
return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
|
|
1721
|
+
version: VERSION, pid }) ? 'running' : 'stopped';
|
|
1722
|
+
} });
|
|
1693
1723
|
return { started: !alreadyRunning, supervisor: 'running', ...readiness };
|
|
1694
1724
|
}
|
|
1695
1725
|
async function supervisedDemoCycle(registration, signal) {
|
|
@@ -1752,7 +1782,8 @@ async function relaySupervise(flags) {
|
|
|
1752
1782
|
const health = createDemoWatchHealthRecorder({ home: dharmaHome(), pid: process.pid, version: VERSION });
|
|
1753
1783
|
try {
|
|
1754
1784
|
await writeJsonAtomic(bindingPath, { pid: process.pid, workspaceId: selectedWorkspace?.workspaceId ?? null,
|
|
1755
|
-
policyPath, demoWatches: true, version: VERSION
|
|
1785
|
+
policyPath, demoWatches: true, version: VERSION, standardRepositories: Boolean(policyPath),
|
|
1786
|
+
organizationId: config?.organizationId ?? null, deviceId: config?.deviceId ?? null });
|
|
1756
1787
|
const demo = runDemoSupervisor({ signal: controller.signal,
|
|
1757
1788
|
list: () => listDemoWatchRegistrations(dharmaHome()), cycle: (registration, signal) => {
|
|
1758
1789
|
if (!health.available(demoWatchRegistrationKey(registration))) {
|
|
@@ -2123,11 +2154,36 @@ async function bootstrap(flags) {
|
|
|
2123
2154
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
2124
2155
|
const autostart = flags.has('no-relay-daemon')
|
|
2125
2156
|
? { state: 'disabled', backend: null }
|
|
2126
|
-
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(() =>
|
|
2127
|
-
home
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2157
|
+
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(async () => {
|
|
2158
|
+
const home = dharmaHome();
|
|
2159
|
+
const existing = await relayAutostartStatus({ home });
|
|
2160
|
+
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
2161
|
+
const anchor = await inspectOwnedRelayAutostart({ home });
|
|
2162
|
+
if (anchor.policy !== null) {
|
|
2163
|
+
const current = await readDeviceConfig();
|
|
2164
|
+
if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
|
|
2165
|
+
throw new Error('relay_workspace_conflict: startup enrollment changed.');
|
|
2166
|
+
}
|
|
2167
|
+
const selected = selectDeviceWorkspace(await registry(), { organizationId,
|
|
2168
|
+
deviceId: config.deviceId, path: anchor.workspace });
|
|
2169
|
+
if (!selected)
|
|
2170
|
+
throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
|
|
2171
|
+
if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
|
|
2172
|
+
throw new Error('relay_workspace_conflict: startup policy is not canonical.');
|
|
2173
|
+
}
|
|
2174
|
+
const authorized = await loadOrganizationPolicy(anchor.policy);
|
|
2175
|
+
const canonical = (await registry()).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
|
|
2176
|
+
&& row.organizationId === organizationId && row.path === anchor.workspace
|
|
2177
|
+
&& row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
|
|
2178
|
+
if (canonical.length !== 1)
|
|
2179
|
+
throw new Error('relay_workspace_conflict: startup policy route changed.');
|
|
2180
|
+
await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
|
|
2184
|
+
launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
|
|
2185
|
+
version: VERSION, preserveStandardAnchor: true });
|
|
2186
|
+
}));
|
|
2131
2187
|
let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
2132
2188
|
const completionRequested = flags.has('complete');
|
|
2133
2189
|
if (!completionRequested) {
|
|
@@ -3006,6 +3062,65 @@ async function workspaceAdd(flags, positional) {
|
|
|
3006
3062
|
pathDisclosedToServer: false,
|
|
3007
3063
|
};
|
|
3008
3064
|
}
|
|
3065
|
+
async function workspaceRecoverRegistry(flags) {
|
|
3066
|
+
if (flags.has('apply') && flags.has('dry-run'))
|
|
3067
|
+
throw new Error('registry_recovery_flags_conflict');
|
|
3068
|
+
const config = await readDeviceConfig();
|
|
3069
|
+
if (!config)
|
|
3070
|
+
throw new Error('registry_recovery_enrollment_required');
|
|
3071
|
+
const enrollment = await loadDeviceEnrollmentAnchor({ config });
|
|
3072
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
3073
|
+
const home = dharmaHome();
|
|
3074
|
+
const startup = await inspectOwnedRelayAutostart({ home });
|
|
3075
|
+
const policyPath = resolve(workspace, '.dharma', 'approved-policy.json');
|
|
3076
|
+
const launcher = resolve(workspace, '.dharma', 'bin', process.platform === 'win32' ? 'dharma.cmd' : 'dharma');
|
|
3077
|
+
if (startup.workspace !== workspace || startup.policy !== policyPath || startup.launcher !== launcher) {
|
|
3078
|
+
throw new Error('registry_recovery_startup_anchor_mismatch');
|
|
3079
|
+
}
|
|
3080
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
3081
|
+
const workspaceId = policy.serverAuthorization?.workspaceId;
|
|
3082
|
+
if (!workspaceId || policy.organizationId !== config.organizationId) {
|
|
3083
|
+
throw new Error('registry_recovery_policy_scope_mismatch');
|
|
3084
|
+
}
|
|
3085
|
+
verifyServerAuthorizedPolicy({ policy, publicKeyEd25519: enrollment.serverPublicKeyEd25519,
|
|
3086
|
+
organizationId: config.organizationId, workspaceId });
|
|
3087
|
+
await assertWorkspaceAuthorizationCurrent(workspaceId, policy.serverAuthorization);
|
|
3088
|
+
const remote = await gitValue(workspace, ['config', '--get', 'remote.origin.url']);
|
|
3089
|
+
if (!remote)
|
|
3090
|
+
throw new Error('registry_recovery_repository_identity_missing');
|
|
3091
|
+
const repositoryFingerprint = sourceRepositoryFingerprint(remote).fingerprint;
|
|
3092
|
+
const api = await organizationApi(new Map());
|
|
3093
|
+
const projection = resolveRegistryRecoveryProjection({ organizationId: config.organizationId,
|
|
3094
|
+
deviceId: config.deviceId, workspaceId, policyRevision: policy.revision, repositoryFingerprint,
|
|
3095
|
+
workspaces: await api.listWorkspaces(), repositoryAgents: await api.listRepositoryAgents() });
|
|
3096
|
+
const entry = {
|
|
3097
|
+
workspaceId, organizationId: config.organizationId,
|
|
3098
|
+
name: projection.name || basename(workspace), path: workspace,
|
|
3099
|
+
routeHash: `sha256:${createHash('sha256').update(workspace).digest('hex')}`,
|
|
3100
|
+
repositoryRemoteHash: repositoryFingerprint, repositoryIdentityVersion: 'normalized-v1',
|
|
3101
|
+
repositoryAgentId: projection.repositoryAgentId, repositoryBindingId: projection.repositoryBindingId,
|
|
3102
|
+
endpointId: projection.endpointId, repositoryAgentKey: projection.repositoryAgentKey,
|
|
3103
|
+
controlBranch: projection.controlBranch, defaultBranch: projection.defaultBranch, status: 'active',
|
|
3104
|
+
};
|
|
3105
|
+
const path = workspaceRegistryPath();
|
|
3106
|
+
const original = await inspectRegistryRecoveryFile(path);
|
|
3107
|
+
const planned = appendRecoveredWorkspace(original.records, entry);
|
|
3108
|
+
if (!flags.has('apply'))
|
|
3109
|
+
return { ok: true, dryRun: true, workspaceId, organizationId: config.organizationId,
|
|
3110
|
+
deviceId: config.deviceId, registryState: original.kind, registryHash: original.hash,
|
|
3111
|
+
backupRequired: original.bytes !== null && !planned.alreadyPresent, alreadyPresent: planned.alreadyPresent,
|
|
3112
|
+
serverMutation: false, newEnrollment: false, startupMutation: false, signedPackageMutation: false };
|
|
3113
|
+
const release = await acquirePidLock(`${path}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
|
|
3114
|
+
try {
|
|
3115
|
+
const recovered = await applyRegistryRecoveryFile({ home, path,
|
|
3116
|
+
expectedKind: original.kind, expectedHash: original.hash, entry });
|
|
3117
|
+
return { ok: true, ...recovered, workspaceId, registryStateBefore: original.kind,
|
|
3118
|
+
serverMutation: false, newEnrollment: false, startupMutation: false, signedPackageMutation: false };
|
|
3119
|
+
}
|
|
3120
|
+
finally {
|
|
3121
|
+
await release();
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3009
3124
|
async function discoverRepositoryAt(path) {
|
|
3010
3125
|
const canonical = await realpath(path);
|
|
3011
3126
|
const topLevel = await gitValue(canonical, ['rev-parse', '--show-toplevel']);
|
|
@@ -3885,6 +4000,38 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
3885
4000
|
knowledge,
|
|
3886
4001
|
};
|
|
3887
4002
|
}
|
|
4003
|
+
async function repositoryInstallerRecoveryCommand(flags) {
|
|
4004
|
+
if (flags.has('apply') === flags.has('dry-run'))
|
|
4005
|
+
throw new Error('Choose exactly one of --apply or --dry-run.');
|
|
4006
|
+
const fromWorkspaceId = required(flags, 'from-workspace-id');
|
|
4007
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
4008
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
4009
|
+
const organizationId = required(flags, 'organization-id');
|
|
4010
|
+
const config = await readDeviceConfig();
|
|
4011
|
+
if (!config || config.organizationId !== organizationId)
|
|
4012
|
+
throw new Error('Recovery requires current organization enrollment.');
|
|
4013
|
+
await loadDeviceEnrollmentAnchor({ config });
|
|
4014
|
+
const identity = await preflightBootstrapWorkspaceIdentity(workspace);
|
|
4015
|
+
const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
|
|
4016
|
+
path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
|
|
4017
|
+
const repositoryAgentKey = `repo:${identity.fingerprint.slice('sha256:'.length, 'sha256:'.length + 24)}`;
|
|
4018
|
+
if (!registered?.repositoryBindingId || !registered.repositoryAgentId
|
|
4019
|
+
|| registered.repositoryAgentKey !== repositoryAgentKey) {
|
|
4020
|
+
throw new Error('Recovery requires the enrolled device canonical repository binding.');
|
|
4021
|
+
}
|
|
4022
|
+
const policy = await loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), registered.workspaceId);
|
|
4023
|
+
if (policy.organizationId !== organizationId || policy.serverAuthorization?.workspaceId !== registered.workspaceId) {
|
|
4024
|
+
throw new Error('Recovery requires a current signed workspace policy.');
|
|
4025
|
+
}
|
|
4026
|
+
await assertWorkspaceAuthorizationCurrent(registered.workspaceId, policy.serverAuthorization);
|
|
4027
|
+
const scope = { organizationId, workspaceId: registered.workspaceId,
|
|
4028
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId };
|
|
4029
|
+
validateRepositorySourceAuthorization(await fetchRepositorySourceAuthorization(await client(), scope), scope, new Date());
|
|
4030
|
+
const recovery = await recoverLegacyRepositoryInstaller({ workspace, fromWorkspaceId,
|
|
4031
|
+
workspaceId: registered.workspaceId, repositoryAgentKey, apply: flags.has('apply') });
|
|
4032
|
+
return { ok: true, dryRun: !recovery.applied, serverMutation: false, recovery,
|
|
4033
|
+
nextAction: 'Resume pinned onboarding; recovery is not signed skill installation or activation.' };
|
|
4034
|
+
}
|
|
3888
4035
|
async function repositorySnapshotCommand(flags, outputs) {
|
|
3889
4036
|
if (flags.has('apply') && flags.has('dry-run'))
|
|
3890
4037
|
throw new Error('Choose --apply or --dry-run, not both.');
|
|
@@ -3956,6 +4103,15 @@ async function onboard(flags) {
|
|
|
3956
4103
|
: typeof flags.get('provider') === 'string' ? [String(flags.get('provider'))] : []);
|
|
3957
4104
|
const repositoryKey = typeof flags.get('repository-key') === 'string' ? String(flags.get('repository-key')) : null;
|
|
3958
4105
|
const repositoryIdentity = await preflightBootstrapWorkspaceIdentity(workspace, repositoryKey);
|
|
4106
|
+
const registryState = await inspectRegistryRecoveryFile(workspaceRegistryPath());
|
|
4107
|
+
if (registryState.kind === 'corrupt_zero') {
|
|
4108
|
+
const anchor = await inspectOwnedRelayAutostart({ home: dharmaHome() });
|
|
4109
|
+
if (anchor.policy === null)
|
|
4110
|
+
throw new Error('registry_recovery_startup_anchor_mismatch');
|
|
4111
|
+
await workspaceRecoverRegistry(new Map([
|
|
4112
|
+
['workspace', anchor.workspace], ['apply', true],
|
|
4113
|
+
]));
|
|
4114
|
+
}
|
|
3959
4115
|
let registered = selectDeviceWorkspace(await registry(), {
|
|
3960
4116
|
organizationId,
|
|
3961
4117
|
deviceId: config.deviceId,
|
|
@@ -5972,6 +6128,52 @@ async function takeCachedSkillUpdate(input) {
|
|
|
5972
6128
|
}
|
|
5973
6129
|
}
|
|
5974
6130
|
async function relayStart(flags) {
|
|
6131
|
+
const anchor = resolve(required(flags, 'policy'));
|
|
6132
|
+
const config = await readDeviceConfig();
|
|
6133
|
+
if (!config)
|
|
6134
|
+
throw new Error('Relay requires current enrollment.');
|
|
6135
|
+
const declared = await loadOrganizationPolicy(anchor);
|
|
6136
|
+
const rows = await registry();
|
|
6137
|
+
const selected = rows.find(row => row.workspaceId === declared.serverAuthorization?.workspaceId
|
|
6138
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === anchor);
|
|
6139
|
+
if (!selected)
|
|
6140
|
+
throw new Error('Relay policy must be the canonical policy of one registered workspace.');
|
|
6141
|
+
const authorized = await selectRepositoryRelayRegistrations(rows, config, loadOrganizationPolicy);
|
|
6142
|
+
if (!authorized.some(row => row.workspaceId === selected.workspaceId && row.policyPath === anchor)) {
|
|
6143
|
+
throw new Error('Relay policy is not registered to this enrolled device.');
|
|
6144
|
+
}
|
|
6145
|
+
await loadVerifiedWorkspacePolicy(anchor, selected.workspaceId);
|
|
6146
|
+
const release = await acquireRelayProcessLease();
|
|
6147
|
+
const controller = new AbortController();
|
|
6148
|
+
const stop = () => controller.abort();
|
|
6149
|
+
process.once('SIGINT', stop);
|
|
6150
|
+
process.once('SIGTERM', stop);
|
|
6151
|
+
const serialized = serializeRelayWork();
|
|
6152
|
+
try {
|
|
6153
|
+
if (flags.has('once'))
|
|
6154
|
+
return await relayWorkspaceLoop(flags, controller.signal, serialized);
|
|
6155
|
+
await runRegisteredRepositoryRelays({ signal: controller.signal, list: async () => {
|
|
6156
|
+
const currentConfig = await readDeviceConfig();
|
|
6157
|
+
if (currentConfig?.organizationId !== config.organizationId || currentConfig.deviceId !== config.deviceId) {
|
|
6158
|
+
throw new Error('Relay enrollment changed.');
|
|
6159
|
+
}
|
|
6160
|
+
const records = JSON.parse(await readFile(workspaceRegistryPath(), 'utf8'));
|
|
6161
|
+
if (!Array.isArray(records))
|
|
6162
|
+
throw new Error('Invalid workspace registry.');
|
|
6163
|
+
return selectRepositoryRelayRegistrations(records, config, loadOrganizationPolicy);
|
|
6164
|
+
}, run: (row, signal) => relayWorkspaceLoop(new Map([...flags, ['policy', row.policyPath]]), signal, serialized),
|
|
6165
|
+
observe: event => process.stderr.write(`${JSON.stringify({ event: 'repository_relay', ...event })}\n`) });
|
|
6166
|
+
return { ok: true, stopped: true };
|
|
6167
|
+
}
|
|
6168
|
+
finally {
|
|
6169
|
+
controller.abort();
|
|
6170
|
+
process.removeListener('SIGINT', stop);
|
|
6171
|
+
process.removeListener('SIGTERM', stop);
|
|
6172
|
+
await release();
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
6176
|
+
signal.throwIfAborted();
|
|
5975
6177
|
const policyPath = resolve(required(flags, 'policy'));
|
|
5976
6178
|
const declaredPolicy = await loadOrganizationPolicy(policyPath);
|
|
5977
6179
|
const declaredWorkspaceId = declaredPolicy.serverAuthorization?.workspaceId;
|
|
@@ -5986,400 +6188,411 @@ async function relayStart(flags) {
|
|
|
5986
6188
|
}
|
|
5987
6189
|
let canonicalWorkspace = selectedWorkspace;
|
|
5988
6190
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6191
|
+
signal.throwIfAborted();
|
|
5989
6192
|
const fabric = await client();
|
|
5990
6193
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
5991
6194
|
const pollMs = Math.min(Math.max(Number(flags.get('poll-seconds') || 3), 1), 60) * 1_000;
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
6025
|
-
publishedLocalSnapshotHash: recovered,
|
|
6026
|
-
} };
|
|
6027
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
catch {
|
|
6031
|
-
repositorySourceBaselineAvailable = false;
|
|
6195
|
+
let stopping = signal.aborted;
|
|
6196
|
+
let stopPreparation;
|
|
6197
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
6198
|
+
let tasksCompleted = 0;
|
|
6199
|
+
let lastProviderFailureCategory = null;
|
|
6200
|
+
let taskTrajectoriesRecovered = 0;
|
|
6201
|
+
let evidenceResponsesCompleted = 0;
|
|
6202
|
+
let trajectorySyncsCompleted = 0;
|
|
6203
|
+
let nextPolicyRefreshAt = 0;
|
|
6204
|
+
let lastPollReceiptAt = 0;
|
|
6205
|
+
let evidencePolicyFresh = false;
|
|
6206
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
6207
|
+
const blockedRepositorySourceRetry = new BlockedRepositorySourceRetry();
|
|
6208
|
+
let repositorySourceBaselineAvailable = true;
|
|
6209
|
+
let publishedLocalSnapshotHash = canonicalWorkspace.repositoryPackage?.publishedLocalSnapshotHash
|
|
6210
|
+
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
6211
|
+
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
6212
|
+
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
6213
|
+
if (canonicalWorkspace.repositoryPackage && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6214
|
+
try {
|
|
6215
|
+
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
6216
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
6217
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6218
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, record: canonicalWorkspace.repositoryPackage,
|
|
6219
|
+
});
|
|
6220
|
+
if (recovered && recovered !== publishedLocalSnapshotHash) {
|
|
6221
|
+
publishedLocalSnapshotHash = recovered;
|
|
6222
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: {
|
|
6223
|
+
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
6224
|
+
publishedLocalSnapshotHash: recovered,
|
|
6225
|
+
} };
|
|
6226
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6032
6227
|
}
|
|
6033
6228
|
}
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6229
|
+
catch {
|
|
6230
|
+
repositorySourceBaselineAvailable = false;
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
if (publishedLocalSnapshotHash) {
|
|
6234
|
+
try {
|
|
6235
|
+
await readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash);
|
|
6236
|
+
const localHash = canonicalWorkspace.repositoryPackage?.localBaselineSnapshotHash;
|
|
6237
|
+
if (localHash && (localHash === publishedLocalSnapshotHash
|
|
6238
|
+
|| canonicalWorkspace.repositoryPackage?.pendingLocalSnapshotHash === localHash)) {
|
|
6239
|
+
await seedRepositorySourceWatcher({ workspace: canonicalWorkspace.path,
|
|
6240
|
+
organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6241
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6242
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId,
|
|
6243
|
+
publishedHash: publishedLocalSnapshotHash,
|
|
6244
|
+
localHash,
|
|
6245
|
+
watcher: repositorySourceWatcher });
|
|
6051
6246
|
}
|
|
6052
6247
|
}
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6248
|
+
catch {
|
|
6249
|
+
repositorySourceBaselineAvailable = false;
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
let nextRepositorySourceScanAt = 0;
|
|
6253
|
+
let repositorySourceCandidates = 0;
|
|
6254
|
+
let repositorySourceFailures = 0;
|
|
6255
|
+
let repositorySourceState = 'not_scanned';
|
|
6256
|
+
let sourceScanFlight = null;
|
|
6257
|
+
let sourceScanReady = null;
|
|
6258
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
6259
|
+
const vault = await LocalVault.open({
|
|
6260
|
+
root: resolve(dharmaHome(), 'vault'),
|
|
6261
|
+
masterKey: await loadOrCreateVaultMasterKey(),
|
|
6262
|
+
rawLocalDays: rawLocalRetentionDays(policy),
|
|
6263
|
+
});
|
|
6264
|
+
if (signal.aborted)
|
|
6265
|
+
vault.close();
|
|
6266
|
+
signal.throwIfAborted();
|
|
6267
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
6268
|
+
if (signal.aborted)
|
|
6269
|
+
stop();
|
|
6270
|
+
let skillPreparationsCompleted = 0;
|
|
6271
|
+
let skillPreparationFailures = 0;
|
|
6272
|
+
let skillActivationsCompleted = 0;
|
|
6273
|
+
let skillActivationFailures = 0;
|
|
6274
|
+
const skillActivationFailuresByProvider = {};
|
|
6275
|
+
let nextSkillActivationAt = 0;
|
|
6276
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
6277
|
+
prepare: async (assertRunning) => {
|
|
6278
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
6279
|
+
assertRunning();
|
|
6280
|
+
const capability = await adapter.capability();
|
|
6281
|
+
assertRunning();
|
|
6282
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6283
|
+
return;
|
|
6284
|
+
const provider = adapter.providerId;
|
|
6285
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6286
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
6287
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6288
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
6289
|
+
if (!prepared) {
|
|
6290
|
+
assertRunning();
|
|
6078
6291
|
return;
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
if (!
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
prepared.
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6292
|
+
}
|
|
6293
|
+
let retained = false;
|
|
6294
|
+
try {
|
|
6295
|
+
prepared.assertCurrent();
|
|
6296
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
6297
|
+
if (!repositoryAgentId)
|
|
6298
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
6299
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
6300
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
6301
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
6302
|
+
repositoryAgentId,
|
|
6303
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
6304
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
6305
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
6306
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
6307
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
6308
|
+
});
|
|
6309
|
+
prepared.assertCurrent();
|
|
6310
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
6311
|
+
prepared.assertCurrent();
|
|
6312
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6313
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
6314
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
6097
6315
|
repositoryAgentId,
|
|
6098
|
-
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
6316
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
6099
6317
|
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
prepared.
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6318
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
6319
|
+
onCommitted: () => { retained = true; } });
|
|
6320
|
+
skillPreparationsCompleted += 1;
|
|
6321
|
+
}
|
|
6322
|
+
finally {
|
|
6323
|
+
if (!retained)
|
|
6324
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
6325
|
+
}
|
|
6326
|
+
});
|
|
6327
|
+
}, () => { skillPreparationFailures += 1; });
|
|
6328
|
+
},
|
|
6329
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
6330
|
+
});
|
|
6331
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
6332
|
+
if (stopping)
|
|
6333
|
+
stopPreparation();
|
|
6334
|
+
const applyReadySourceScan = async () => {
|
|
6335
|
+
if (!sourceScanReady)
|
|
6336
|
+
return;
|
|
6337
|
+
const ready = sourceScanReady;
|
|
6338
|
+
sourceScanReady = null;
|
|
6339
|
+
sourceScanFlight = null;
|
|
6340
|
+
if ('error' in ready) {
|
|
6341
|
+
repositorySourceWatcher.invalidate();
|
|
6342
|
+
repositorySourceFailures += 1;
|
|
6343
|
+
repositorySourceState = 'blocked';
|
|
6344
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6345
|
+
return;
|
|
6346
|
+
}
|
|
6347
|
+
if ('state' in ready) {
|
|
6348
|
+
repositorySourceState = ready.state;
|
|
6349
|
+
nextRepositorySourceScanAt = performance.now() + 15_000;
|
|
6350
|
+
return;
|
|
6351
|
+
}
|
|
6352
|
+
const sourceCycle = ready.cycle;
|
|
6353
|
+
repositorySourceState = sourceCycle.state;
|
|
6354
|
+
if (sourceCycle.localMutation) {
|
|
6355
|
+
repositorySourceCandidates += 1;
|
|
6356
|
+
if (canonicalWorkspace.repositoryPackage && sourceCycle.persisted) {
|
|
6357
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6358
|
+
if (sourceCycle.state === 'source_already_published') {
|
|
6359
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6360
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6361
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6362
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6363
|
+
}
|
|
6364
|
+
else if (sourceCycle.candidate) {
|
|
6365
|
+
const receipt = sourceCycle.candidate;
|
|
6366
|
+
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
6367
|
+
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
6368
|
+
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
6369
|
+
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
6370
|
+
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
6371
|
+
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
6372
|
+
consolidationMode: 'repository_update',
|
|
6373
|
+
});
|
|
6374
|
+
}
|
|
6375
|
+
if (receipt.state === 'published') {
|
|
6154
6376
|
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6155
6377
|
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6156
6378
|
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6157
6379
|
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6158
6380
|
}
|
|
6159
|
-
else if (
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6381
|
+
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
6382
|
+
&& typeof receipt.operationId === 'string') {
|
|
6383
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6384
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
6385
|
+
}
|
|
6386
|
+
else if (receipt.state === 'blocked') {
|
|
6387
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
6388
|
+
repositorySourceWatcher.invalidate();
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
nextRepositorySourceScanAt = performance.now() + (sourceCycle.state === 'debouncing' ? 15_000 : 60_000);
|
|
6395
|
+
};
|
|
6396
|
+
const consumeSourceScan = async () => {
|
|
6397
|
+
try {
|
|
6398
|
+
await applyReadySourceScan();
|
|
6399
|
+
}
|
|
6400
|
+
catch {
|
|
6401
|
+
repositorySourceWatcher.invalidate();
|
|
6402
|
+
repositorySourceFailures += 1;
|
|
6403
|
+
repositorySourceState = 'blocked';
|
|
6404
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6405
|
+
}
|
|
6406
|
+
};
|
|
6407
|
+
try {
|
|
6408
|
+
signal.throwIfAborted();
|
|
6409
|
+
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
6410
|
+
if (recoveredTaskTrajectories.state === 'completed') {
|
|
6411
|
+
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
6412
|
+
}
|
|
6413
|
+
do {
|
|
6414
|
+
if (stopping)
|
|
6415
|
+
break;
|
|
6416
|
+
await consumeSourceScan();
|
|
6417
|
+
if (Date.now() >= nextPolicyRefreshAt) {
|
|
6418
|
+
try {
|
|
6419
|
+
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
6420
|
+
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6421
|
+
evidencePolicyFresh = true;
|
|
6422
|
+
}
|
|
6423
|
+
catch {
|
|
6424
|
+
evidencePolicyFresh = false;
|
|
6425
|
+
}
|
|
6426
|
+
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6427
|
+
}
|
|
6428
|
+
if (!sourceScanFlight && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6429
|
+
try {
|
|
6430
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
6431
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6432
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6433
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6434
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6435
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6436
|
+
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6437
|
+
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6438
|
+
throw new Error('Repository candidate status changed identity while polling.');
|
|
6180
6439
|
}
|
|
6181
|
-
|
|
6182
|
-
|
|
6440
|
+
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6441
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6442
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6443
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6444
|
+
...baseline,
|
|
6445
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6446
|
+
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6447
|
+
if (current.state === 'blocked')
|
|
6183
6448
|
repositorySourceWatcher.invalidate();
|
|
6184
|
-
|
|
6449
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6185
6450
|
}
|
|
6186
|
-
|
|
6451
|
+
}
|
|
6452
|
+
catch {
|
|
6453
|
+
repositorySourceFailures += 1;
|
|
6187
6454
|
}
|
|
6188
6455
|
}
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6456
|
+
let evidenceRequestId;
|
|
6457
|
+
if (evidencePolicyFresh && !stopping) {
|
|
6458
|
+
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6459
|
+
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6460
|
+
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6461
|
+
return {
|
|
6462
|
+
synced,
|
|
6463
|
+
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6464
|
+
};
|
|
6465
|
+
});
|
|
6466
|
+
if (evidenceCycle.state === 'deferred') {
|
|
6467
|
+
evidencePolicyFresh = false;
|
|
6468
|
+
}
|
|
6469
|
+
else {
|
|
6470
|
+
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6471
|
+
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6472
|
+
if (evidenceRequestId)
|
|
6473
|
+
evidenceResponsesCompleted += 1;
|
|
6474
|
+
}
|
|
6200
6475
|
}
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6476
|
+
const result = await serialized(async () => {
|
|
6477
|
+
if (stopping)
|
|
6478
|
+
return { taskId: null };
|
|
6479
|
+
return executeOneTask(fabric, leaseSeconds);
|
|
6480
|
+
});
|
|
6481
|
+
if (!stopping && Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6482
|
+
const observation = {
|
|
6483
|
+
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6484
|
+
};
|
|
6485
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'repositories', canonicalWorkspace.workspaceId, 'last-successful-poll.json'), observation);
|
|
6486
|
+
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
6487
|
+
.then(value => JSON.parse(value)).catch(() => null);
|
|
6488
|
+
if (!binding || binding.workspaceId === canonicalWorkspace.workspaceId) {
|
|
6489
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), observation);
|
|
6490
|
+
}
|
|
6491
|
+
lastPollReceiptAt = Date.now();
|
|
6206
6492
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6493
|
+
if (result.taskId)
|
|
6494
|
+
tasksCompleted += 1;
|
|
6495
|
+
if (typeof result.failureCategory === 'string')
|
|
6496
|
+
lastProviderFailureCategory = result.failureCategory;
|
|
6497
|
+
if (!stopping && !result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6498
|
+
for (const adapter of providerAdapters) {
|
|
6210
6499
|
try {
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6500
|
+
const capability = await adapter.capability();
|
|
6501
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6502
|
+
continue;
|
|
6503
|
+
const provider = adapter.providerId;
|
|
6504
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6505
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6506
|
+
provider, assertCurrent: () => { if (stopping)
|
|
6507
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6508
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6509
|
+
provider, policyPath, fabric });
|
|
6510
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6511
|
+
provider, policyPath, fabric, automatic: true });
|
|
6512
|
+
if (!prepared)
|
|
6513
|
+
return;
|
|
6514
|
+
if (!cached)
|
|
6515
|
+
skillPreparationsCompleted += 1;
|
|
6516
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6517
|
+
provider, policyPath, flags: new Map() });
|
|
6518
|
+
skillActivationsCompleted += 1;
|
|
6519
|
+
});
|
|
6520
|
+
});
|
|
6214
6521
|
}
|
|
6215
6522
|
catch {
|
|
6216
|
-
|
|
6523
|
+
skillActivationFailures += 1;
|
|
6524
|
+
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
6217
6525
|
}
|
|
6218
|
-
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6219
6526
|
}
|
|
6220
|
-
|
|
6527
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
6528
|
+
}
|
|
6529
|
+
if (!stopping && !sourceScanFlight && performance.now() >= nextRepositorySourceScanAt) {
|
|
6530
|
+
const sourceWorkspace = canonicalWorkspace;
|
|
6531
|
+
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6532
|
+
sourceScanFlight = (async () => {
|
|
6221
6533
|
try {
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6225
|
-
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6226
|
-
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6227
|
-
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6228
|
-
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6229
|
-
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6230
|
-
throw new Error('Repository candidate status changed identity while polling.');
|
|
6231
|
-
}
|
|
6232
|
-
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6233
|
-
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6234
|
-
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6235
|
-
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6236
|
-
...baseline,
|
|
6237
|
-
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6238
|
-
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6239
|
-
if (current.state === 'blocked')
|
|
6240
|
-
repositorySourceWatcher.invalidate();
|
|
6241
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6534
|
+
if (!repositorySourceBaselineAvailable) {
|
|
6535
|
+
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6242
6536
|
}
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
repositorySourceFailures += 1;
|
|
6246
|
-
}
|
|
6247
|
-
}
|
|
6248
|
-
let evidenceRequestId;
|
|
6249
|
-
if (evidencePolicyFresh) {
|
|
6250
|
-
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6251
|
-
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6252
|
-
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6253
|
-
return {
|
|
6254
|
-
synced,
|
|
6255
|
-
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6256
|
-
};
|
|
6257
|
-
});
|
|
6258
|
-
if (evidenceCycle.state === 'deferred') {
|
|
6259
|
-
evidencePolicyFresh = false;
|
|
6260
|
-
}
|
|
6261
|
-
else {
|
|
6262
|
-
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6263
|
-
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6264
|
-
if (evidenceRequestId)
|
|
6265
|
-
evidenceResponsesCompleted += 1;
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6268
|
-
const result = await executeOneTask(fabric, leaseSeconds);
|
|
6269
|
-
if (Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6270
|
-
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), {
|
|
6271
|
-
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6272
|
-
});
|
|
6273
|
-
lastPollReceiptAt = Date.now();
|
|
6274
|
-
}
|
|
6275
|
-
if (result.taskId)
|
|
6276
|
-
tasksCompleted += 1;
|
|
6277
|
-
if (typeof result.failureCategory === 'string')
|
|
6278
|
-
lastProviderFailureCategory = result.failureCategory;
|
|
6279
|
-
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6280
|
-
for (const adapter of providerAdapters) {
|
|
6281
|
-
try {
|
|
6282
|
-
const capability = await adapter.capability();
|
|
6283
|
-
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6284
|
-
continue;
|
|
6285
|
-
const provider = adapter.providerId;
|
|
6286
|
-
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6287
|
-
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6288
|
-
provider, assertCurrent: () => { if (stopping)
|
|
6289
|
-
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6290
|
-
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6291
|
-
provider, policyPath, fabric });
|
|
6292
|
-
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6293
|
-
provider, policyPath, fabric, automatic: true });
|
|
6294
|
-
if (!prepared)
|
|
6295
|
-
return;
|
|
6296
|
-
if (!cached)
|
|
6297
|
-
skillPreparationsCompleted += 1;
|
|
6298
|
-
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6299
|
-
provider, policyPath, flags: new Map() });
|
|
6300
|
-
skillActivationsCompleted += 1;
|
|
6301
|
-
});
|
|
6302
|
-
});
|
|
6537
|
+
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6538
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6303
6539
|
}
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6540
|
+
const installedKnowledge = await installedRepositoryKnowledge(sourceWorkspace);
|
|
6541
|
+
if (!installedKnowledge && (sourceWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
6542
|
+
sourceScanReady = { state: 'awaiting_signed_baseline' };
|
|
6307
6543
|
}
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6314
|
-
sourceScanFlight = (async () => {
|
|
6315
|
-
try {
|
|
6316
|
-
if (!repositorySourceBaselineAvailable) {
|
|
6317
|
-
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6318
|
-
}
|
|
6319
|
-
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6320
|
-
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6544
|
+
else {
|
|
6545
|
+
if (sourceWorkspace.repositoryPackage?.state === 'blocked' && sourceWorkspace.repositoryPackage.snapshotHash) {
|
|
6546
|
+
const blocked = await readRepositorySourceBaselineSnapshot(sourceWorkspace.path, sourceWorkspace.repositoryPackage.snapshotHash);
|
|
6547
|
+
if (blockedRepositorySourceRetry.consider(sourceWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
6548
|
+
repositorySourceWatcher.invalidate();
|
|
6321
6549
|
}
|
|
6322
|
-
const
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6339
|
-
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6340
|
-
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6341
|
-
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6342
|
-
workspaceId: sourceWorkspace.workspaceId,
|
|
6343
|
-
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6344
|
-
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6345
|
-
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6346
|
-
});
|
|
6347
|
-
sourceScanReady = { cycle: sourceCycle };
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
catch (error) {
|
|
6351
|
-
sourceScanReady = { error };
|
|
6550
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
6551
|
+
workspace: sourceWorkspace.path, organizationId: sourceWorkspace.organizationId,
|
|
6552
|
+
workspaceId: sourceWorkspace.workspaceId, repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6553
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
6554
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(sourceWorkspace),
|
|
6555
|
+
loadPublishedLocalBaseline: () => sourceBaselineHash
|
|
6556
|
+
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6557
|
+
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6558
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6559
|
+
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6560
|
+
workspaceId: sourceWorkspace.workspaceId,
|
|
6561
|
+
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6562
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6563
|
+
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6564
|
+
});
|
|
6565
|
+
sourceScanReady = { cycle: sourceCycle };
|
|
6352
6566
|
}
|
|
6353
|
-
}
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
await consumeSourceScan();
|
|
6365
|
-
await skillPreparationPump.stop();
|
|
6366
|
-
vault.close();
|
|
6367
|
-
process.removeListener('SIGINT', stop);
|
|
6368
|
-
process.removeListener('SIGTERM', stop);
|
|
6369
|
-
}
|
|
6370
|
-
return {
|
|
6371
|
-
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6372
|
-
lastProviderFailureCategory,
|
|
6373
|
-
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6374
|
-
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6375
|
-
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6376
|
-
skillActivationFailuresByProvider,
|
|
6377
|
-
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6378
|
-
};
|
|
6567
|
+
}
|
|
6568
|
+
catch (error) {
|
|
6569
|
+
sourceScanReady = { error };
|
|
6570
|
+
}
|
|
6571
|
+
})();
|
|
6572
|
+
}
|
|
6573
|
+
if (flags.has('once'))
|
|
6574
|
+
break;
|
|
6575
|
+
if (!result.taskId && !evidenceRequestId)
|
|
6576
|
+
await waitForRelayRefresh(pollMs, signal);
|
|
6577
|
+
} while (!stopping);
|
|
6379
6578
|
}
|
|
6380
6579
|
finally {
|
|
6381
|
-
|
|
6580
|
+
if (sourceScanFlight)
|
|
6581
|
+
await sourceScanFlight;
|
|
6582
|
+
await consumeSourceScan();
|
|
6583
|
+
await skillPreparationPump.stop();
|
|
6584
|
+
vault.close();
|
|
6585
|
+
signal.removeEventListener('abort', stop);
|
|
6382
6586
|
}
|
|
6587
|
+
return {
|
|
6588
|
+
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6589
|
+
lastProviderFailureCategory,
|
|
6590
|
+
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6591
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6592
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6593
|
+
skillActivationFailuresByProvider,
|
|
6594
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6595
|
+
};
|
|
6383
6596
|
}
|
|
6384
6597
|
export async function run(argv) {
|
|
6385
6598
|
const { positional, flags, repeated } = parseCliOptions(argv);
|
|
@@ -6571,6 +6784,8 @@ export async function run(argv) {
|
|
|
6571
6784
|
}
|
|
6572
6785
|
if (command === 'repositories' && subcommand === 'list')
|
|
6573
6786
|
return repositoriesList(flags);
|
|
6787
|
+
if (command === 'repositories' && subcommand === 'recover-installer')
|
|
6788
|
+
return repositoryInstallerRecoveryCommand(flags);
|
|
6574
6789
|
if (command === 'repositories' && subcommand === 'snapshot') {
|
|
6575
6790
|
return repositorySnapshotCommand(flags, repeated.get('approved-output') || []);
|
|
6576
6791
|
}
|
|
@@ -6588,6 +6803,8 @@ export async function run(argv) {
|
|
|
6588
6803
|
}
|
|
6589
6804
|
if (command === 'workspace' && subcommand === 'add')
|
|
6590
6805
|
return workspaceAdd(flags, positional.slice(2));
|
|
6806
|
+
if (command === 'workspace' && subcommand === 'recover-registry')
|
|
6807
|
+
return workspaceRecoverRegistry(flags);
|
|
6591
6808
|
if (command === 'workspace' && subcommand === 'sync')
|
|
6592
6809
|
return workspaceSync(flags, positional.slice(2));
|
|
6593
6810
|
if (command === 'capture' || (command === 'evidence' && subcommand === 'capture'))
|
|
@@ -6624,6 +6841,37 @@ export async function run(argv) {
|
|
|
6624
6841
|
try {
|
|
6625
6842
|
const config = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
6626
6843
|
const status = { version: VERSION, enrolled: true, relay, supervisor, autostart, reconnect };
|
|
6844
|
+
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6845
|
+
const relayPid = await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')
|
|
6846
|
+
.then(value => Number(value.trim())).catch(() => 0);
|
|
6847
|
+
try {
|
|
6848
|
+
const registryRead = await readWorkspaceRegistry(workspaceRegistryPath());
|
|
6849
|
+
status.workspaceRegistry = { state: registryRead.state, count: registryRead.records.length };
|
|
6850
|
+
if (registryRead.state === 'absent') {
|
|
6851
|
+
status.repositoryRelays = { state: 'unavailable', repositories: [], reason: 'workspace_registry_missing' };
|
|
6852
|
+
}
|
|
6853
|
+
else {
|
|
6854
|
+
const registered = await selectRepositoryRelayRegistrations(registryRead.records, config, loadOrganizationPolicy);
|
|
6855
|
+
const observations = [];
|
|
6856
|
+
for (const row of registered) {
|
|
6857
|
+
const observation = await readFile(resolve(dharmaHome(), 'relay', 'repositories', row.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
6858
|
+
observations.push({ workspaceId: row.workspaceId,
|
|
6859
|
+
state: relay === 'running' && repositoryRelayObservationReady({ observation,
|
|
6860
|
+
workspaceId: row.workspaceId, version: VERSION, pid: relayPid }) ? 'acknowledged_recently' : 'pending',
|
|
6861
|
+
lastSuccessfulPollAt: typeof observation?.at === 'string' ? observation.at : null });
|
|
6862
|
+
}
|
|
6863
|
+
status.repositoryRelays = { state: 'observed', repositories: observations };
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
catch (error) {
|
|
6867
|
+
const message = error instanceof Error ? error.message : '';
|
|
6868
|
+
const reason = ['workspace_registry_read_failed', 'workspace_registry_invalid'].includes(message)
|
|
6869
|
+
? message : 'repository_relay_state_unavailable';
|
|
6870
|
+
if (!status.workspaceRegistry)
|
|
6871
|
+
status.workspaceRegistry = { state: 'unavailable', reason };
|
|
6872
|
+
status.repositoryRelays = { state: 'unavailable', repositories: [], reason };
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6627
6875
|
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6628
6876
|
Object.assign(status, { home: dharmaHome(), organizationId: config.organizationId, deviceId: config.deviceId });
|
|
6629
6877
|
}
|