@dharma-ai-labs/agent-fabric 0.2.118 → 0.2.120
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 +5 -0
- package/README.md +23 -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/demoPackage.d.ts +1 -1
- package/dist/demoTransportFetch.d.ts.map +1 -1
- package/dist/demoTransportFetch.js +3 -1
- package/dist/demoTransportFetch.js.map +1 -1
- package/dist/demoTransportFetch.test.js +62 -0
- package/dist/demoTransportFetch.test.js.map +1 -1
- package/dist/evidencePreviewDisclosureCaller.test.d.ts +2 -0
- package/dist/evidencePreviewDisclosureCaller.test.d.ts.map +1 -0
- package/dist/evidencePreviewDisclosureCaller.test.js +56 -0
- package/dist/evidencePreviewDisclosureCaller.test.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +683 -377
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -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 +9 -1
- package/dist/relayAutostart.d.ts.map +1 -1
- package/dist/relayAutostart.js +39 -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.d.ts +58 -0
- package/dist/relayRuntimeUpgrade.d.ts.map +1 -0
- package/dist/relayRuntimeUpgrade.js +193 -0
- package/dist/relayRuntimeUpgrade.js.map +1 -0
- package/dist/relayRuntimeUpgrade.test.d.ts +2 -0
- package/dist/relayRuntimeUpgrade.test.d.ts.map +1 -0
- package/dist/relayRuntimeUpgrade.test.js +173 -0
- package/dist/relayRuntimeUpgrade.test.js.map +1 -0
- package/dist/relayRuntimeUpgradeCaller.test.d.ts +2 -0
- package/dist/relayRuntimeUpgradeCaller.test.d.ts.map +1 -0
- package/dist/relayRuntimeUpgradeCaller.test.js +116 -0
- package/dist/relayRuntimeUpgradeCaller.test.js.map +1 -0
- 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 +94 -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 +189 -0
- package/dist/repositoryRelaySupervisor.test.js.map +1 -0
- package/dist/schemas/local-relay-upgrade.schema.json +32 -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/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -17,13 +17,16 @@ 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 {
|
|
20
|
+
import { repositoryRelayObservationReady, runRegisteredRepositoryRelays, selectRepositoryRelayRegistrations, serializeRelayWork, waitForRelayRefresh } from './repositoryRelaySupervisor.js';
|
|
21
|
+
import { disableRelayAutostart, enableRelayAutostart, inspectOwnedRelayAutostart, relayAutostartStatus, startRelayAutostart, stopRelayAutostart } from './relayAutostart.js';
|
|
22
|
+
import { relayRuntimeObservationReady, upgradeRelayRuntime } from './relayRuntimeUpgrade.js';
|
|
21
23
|
import { assertStudyExactPoll, studyTaskIdFromFlags } from './studyExactTaskSelector.js';
|
|
22
24
|
import { initializeRepositoryKnowledge, readRepositoryKnowledgeSource } from './repositoryKnowledge.js';
|
|
23
25
|
import { inventoryRepositoryPackage, readRepositoryPackageSnapshot, readRepositorySourceBaselineSnapshot, writeRepositoryPackageSnapshot } from './repositoryPackage.js';
|
|
24
26
|
import { validateRepositorySourceAuthorization } from './repositorySourceAuthorization.js';
|
|
25
27
|
import { advanceRepositorySourceBaseline, BlockedRepositorySourceRetry, fetchRepositorySourceAuthorization, recoverPublishedLocalSourceBaseline, RepositorySourceWatcher, scanRepositorySourceChanges, seedRepositorySourceWatcher } from './repositorySourceSync.js';
|
|
26
28
|
import { assertRepositoryInstallerOwnership, writeRepositoryInstallerFile } from './repositoryInstallerFiles.js';
|
|
29
|
+
import { recoverLegacyRepositoryInstaller, selectLegacyInstallerRecoveryWorkspace } from './legacyInstallerRecovery.js';
|
|
27
30
|
import { onboardingResumeCommand, selectDeviceWorkspace, workspaceIdForDevice } from './onboardingWorkspace.js';
|
|
28
31
|
import { receiveRepositoryPackageDelivery } from './repositoryPackageDelivery.js';
|
|
29
32
|
import { selectInstalledRepositoryKnowledge } from './repositoryInstalledKnowledge.js';
|
|
@@ -55,7 +58,7 @@ import { openCodexAppServerTransport } from '@dharma-ai-labs/agent-fabric-provid
|
|
|
55
58
|
import { createNamedSessionTrust, isNamedSessionOwnerReceipt, renewNamedSessionLifetime } from './namedSessionTrust.js';
|
|
56
59
|
import { startNamedCodexThread } from './namedCodexThread.js';
|
|
57
60
|
export { openCooperativeInboxSession } from './cooperativeInboxSession.js';
|
|
58
|
-
const VERSION = '0.2.
|
|
61
|
+
const VERSION = '0.2.120';
|
|
59
62
|
const USAGE = CLI_USAGE;
|
|
60
63
|
const execFileAsync = promisify(execFile);
|
|
61
64
|
const LOCAL_PROVIDER_IDS = ['codex', 'claude', 'agy', 'hermes'];
|
|
@@ -1302,12 +1305,24 @@ async function registry() {
|
|
|
1302
1305
|
}
|
|
1303
1306
|
async function saveRegistry(items) {
|
|
1304
1307
|
await mkdir(resolve(dharmaHome(), 'registry'), { recursive: true, mode: 0o700 });
|
|
1305
|
-
await
|
|
1308
|
+
await writeJsonAtomic(workspaceRegistryPath(), items);
|
|
1306
1309
|
}
|
|
1307
1310
|
async function saveWorkspaceRecord(entry) {
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
+
const release = await acquirePidLock(`${workspaceRegistryPath()}.mutation.lock`, 10_000, 'Workspace registry is busy; preserve it for recovery.');
|
|
1312
|
+
try {
|
|
1313
|
+
const value = await readFile(workspaceRegistryPath(), 'utf8').catch(error => {
|
|
1314
|
+
if (error.code === 'ENOENT')
|
|
1315
|
+
return '[]';
|
|
1316
|
+
throw error;
|
|
1317
|
+
});
|
|
1318
|
+
const items = JSON.parse(value);
|
|
1319
|
+
if (!Array.isArray(items))
|
|
1320
|
+
throw new Error('Workspace registry is invalid; preserve it for recovery.');
|
|
1321
|
+
await saveRegistry([...items.filter(item => item.workspaceId !== entry.workspaceId), entry]);
|
|
1322
|
+
}
|
|
1323
|
+
finally {
|
|
1324
|
+
await release();
|
|
1325
|
+
}
|
|
1311
1326
|
}
|
|
1312
1327
|
async function gitValue(workspace, argv) {
|
|
1313
1328
|
try {
|
|
@@ -1525,6 +1540,103 @@ async function installStableRepositoryLauncher(workspace) {
|
|
|
1525
1540
|
await writeFile(cmdPath, contents.windows, { mode: 0o600 });
|
|
1526
1541
|
return { shell: '.dharma/bin/dharma', windows: '.dharma/bin/dharma.cmd' };
|
|
1527
1542
|
}
|
|
1543
|
+
async function relayUpgrade(flags) {
|
|
1544
|
+
const config = await readDeviceConfig();
|
|
1545
|
+
if (!config)
|
|
1546
|
+
throw new Error('relay_upgrade_enrollment_required');
|
|
1547
|
+
if (flags.has('dry-run') && flags.has('apply'))
|
|
1548
|
+
throw new Error('relay_upgrade_flags_conflict');
|
|
1549
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
1550
|
+
const selected = selectDeviceWorkspace(await registry(), {
|
|
1551
|
+
organizationId: config.organizationId, deviceId: config.deviceId, path: workspace,
|
|
1552
|
+
});
|
|
1553
|
+
if (!selected)
|
|
1554
|
+
throw new Error('relay_upgrade_workspace_unregistered');
|
|
1555
|
+
const home = dharmaHome();
|
|
1556
|
+
const startup = await inspectOwnedRelayAutostart({ home });
|
|
1557
|
+
const expectedLauncher = resolve(workspace, '.dharma', 'bin', process.platform === 'win32' ? 'dharma.cmd' : 'dharma');
|
|
1558
|
+
if (startup.workspace !== workspace || startup.launcher !== expectedLauncher
|
|
1559
|
+
|| startup.policy !== resolve(workspace, '.dharma', 'approved-policy.json')) {
|
|
1560
|
+
throw new Error('relay_upgrade_workspace_conflict');
|
|
1561
|
+
}
|
|
1562
|
+
const assertStopped = async () => {
|
|
1563
|
+
if (await relaySupervisorProcessState(home) !== 'stopped' || await relayProcessState(home) !== 'stopped') {
|
|
1564
|
+
throw new Error('relay_upgrade_runtime_busy: finish current work, then stop the owned relay before upgrading.');
|
|
1565
|
+
}
|
|
1566
|
+
const entries = await readdir(resolve(home, 'sessions'), { withFileTypes: true }).catch(error => {
|
|
1567
|
+
if (error.code === 'ENOENT')
|
|
1568
|
+
return [];
|
|
1569
|
+
throw error;
|
|
1570
|
+
});
|
|
1571
|
+
for (const entry of entries) {
|
|
1572
|
+
if (!entry.isDirectory() || !/^[a-z][a-z0-9-]{0,47}$/.test(entry.name))
|
|
1573
|
+
continue;
|
|
1574
|
+
if (await pidProcessState(resolve(home, 'sessions', entry.name, 'service.lock')) !== 'stopped') {
|
|
1575
|
+
throw new Error('relay_upgrade_session_busy: finish and stop the named session before upgrading.');
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
return withRelayStartupMutation(async () => {
|
|
1580
|
+
const receipt = await upgradeRelayRuntime({ home, workspace, version: VERSION,
|
|
1581
|
+
organizationId: config.organizationId, deviceId: config.deviceId, workspaceId: selected.workspaceId,
|
|
1582
|
+
dryRun: !flags.has('apply'), rollback: flags.has('rollback') }, {
|
|
1583
|
+
assertStopped, inspectStartup: async () => {
|
|
1584
|
+
const current = await inspectOwnedRelayAutostart({ home });
|
|
1585
|
+
if (current.launcher !== expectedLauncher || current.policy !== startup.policy
|
|
1586
|
+
|| current.workspace !== workspace)
|
|
1587
|
+
throw new Error('relay_upgrade_workspace_conflict');
|
|
1588
|
+
return current;
|
|
1589
|
+
},
|
|
1590
|
+
launcherContents: version => stableRepositoryLauncherContents(version, { platform: process.platform, nodeDirectory: dirname(process.execPath) }),
|
|
1591
|
+
legacyLauncherContents: version => stableRepositoryLauncherContents(version),
|
|
1592
|
+
configureStartup: version => enableRelayAutostart({ home, workspace,
|
|
1593
|
+
launcher: startup.launcher, policy: startup.policy, version }),
|
|
1594
|
+
start: () => startRelayAutostart({ home }),
|
|
1595
|
+
stop: async () => {
|
|
1596
|
+
const activationLocks = resolve(home, 'registry', 'skill-activation-locks');
|
|
1597
|
+
const locks = await readdir(activationLocks).catch(error => {
|
|
1598
|
+
if (error.code === 'ENOENT')
|
|
1599
|
+
return [];
|
|
1600
|
+
throw error;
|
|
1601
|
+
});
|
|
1602
|
+
for (const lock of locks) {
|
|
1603
|
+
if (lock.endsWith('.lock') && await pidProcessState(resolve(activationLocks, lock)) !== 'stopped') {
|
|
1604
|
+
throw new Error('relay_upgrade_work_in_progress: recovery must wait for a safe task boundary.');
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const binding = await readFile(resolve(home, 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
1608
|
+
.then(value => JSON.parse(value)).catch(() => null);
|
|
1609
|
+
if (await relaySupervisorProcessState(home) !== 'stopped'
|
|
1610
|
+
&& binding?.workspaceId !== selected.workspaceId)
|
|
1611
|
+
throw new Error('relay_upgrade_process_unverified');
|
|
1612
|
+
await stopRelayAutostart({ home });
|
|
1613
|
+
const stopped = await relayStop();
|
|
1614
|
+
if (stopped.ok !== true)
|
|
1615
|
+
throw new Error('relay_upgrade_stop_unconfirmed');
|
|
1616
|
+
},
|
|
1617
|
+
verify: async (version, since) => {
|
|
1618
|
+
for (let attempt = 0; attempt < 90; attempt++) {
|
|
1619
|
+
const read = (name) => readFile(resolve(home, 'relay', name), 'utf8').catch(() => 'null');
|
|
1620
|
+
const supervisorPid = Number((await read('supervisor.pid')).trim());
|
|
1621
|
+
const relayPid = Number((await read('relay.pid')).trim());
|
|
1622
|
+
const supervisor = JSON.parse(await read('supervisor-workspace.json'));
|
|
1623
|
+
const lastPoll = JSON.parse(await read('last-successful-poll.json'));
|
|
1624
|
+
if (await relaySupervisorProcessState(home) === 'running' && await relayProcessState(home) === 'running'
|
|
1625
|
+
&& relayRuntimeObservationReady({ version, workspaceId: selected.workspaceId, since,
|
|
1626
|
+
supervisorPid, relayPid, supervisor, lastPoll, allowLegacyPoll: version !== VERSION }))
|
|
1627
|
+
return;
|
|
1628
|
+
if (attempt < 89)
|
|
1629
|
+
await new Promise(resolveWait => setTimeout(resolveWait, 1000));
|
|
1630
|
+
}
|
|
1631
|
+
throw new Error('relay_upgrade_receiver_unconfirmed');
|
|
1632
|
+
},
|
|
1633
|
+
});
|
|
1634
|
+
const validation = await validateContract(resolve(fileURLToPath(new URL('.', import.meta.url)), 'schemas'), 'https://schemas.dharma-ai.io/local-relay-upgrade/v1', receipt);
|
|
1635
|
+
if (!validation.ok)
|
|
1636
|
+
throw new Error('relay_upgrade_receipt_invalid');
|
|
1637
|
+
return receipt;
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1528
1640
|
export async function waitForRelayReadiness(options = {}) {
|
|
1529
1641
|
const processState = options.processState || relayProcessState;
|
|
1530
1642
|
const probe = options.probe || probeRelayConnection;
|
|
@@ -1558,6 +1670,15 @@ export async function waitForRelayReadiness(options = {}) {
|
|
|
1558
1670
|
throw new Error(`The outbound relay did not become ready after bootstrap (${reason}).`);
|
|
1559
1671
|
}
|
|
1560
1672
|
async function startRelayDaemon(policyPath) {
|
|
1673
|
+
const config = await readDeviceConfig();
|
|
1674
|
+
if (!config)
|
|
1675
|
+
throw new Error('Relay startup requires current device enrollment.');
|
|
1676
|
+
const policy = await loadOrganizationPolicy(policyPath);
|
|
1677
|
+
const workspaceId = policy.serverAuthorization?.workspaceId;
|
|
1678
|
+
const selected = (await registry()).find(row => row.workspaceId === workspaceId
|
|
1679
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === policyPath);
|
|
1680
|
+
if (!selected)
|
|
1681
|
+
throw new Error('Relay policy must belong to this enrolled repository.');
|
|
1561
1682
|
const alreadyRunning = await relayProcessState() === 'running';
|
|
1562
1683
|
const supervisorState = await relaySupervisorProcessState();
|
|
1563
1684
|
if (supervisorState === 'unknown')
|
|
@@ -1579,8 +1700,11 @@ async function startRelayDaemon(policyPath) {
|
|
|
1579
1700
|
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
1580
1701
|
.then(value => JSON.parse(value)).catch(() => null);
|
|
1581
1702
|
if (binding?.pid === pid) {
|
|
1582
|
-
if (binding.
|
|
1583
|
-
throw new Error('
|
|
1703
|
+
if (binding.version !== VERSION || !binding.standardRepositories) {
|
|
1704
|
+
throw new Error('relay_runtime_upgrade_required: safely stop and upgrade the existing startup-anchor relay; do not retarget a working receiver.');
|
|
1705
|
+
}
|
|
1706
|
+
if (binding.organizationId !== config.organizationId || binding.deviceId !== config.deviceId) {
|
|
1707
|
+
throw new Error('relay_workspace_conflict: the active supervisor belongs to another enrollment.');
|
|
1584
1708
|
}
|
|
1585
1709
|
supervisorReady = true;
|
|
1586
1710
|
break;
|
|
@@ -1591,7 +1715,14 @@ async function startRelayDaemon(policyPath) {
|
|
|
1591
1715
|
}
|
|
1592
1716
|
if (!supervisorReady)
|
|
1593
1717
|
throw new Error('The relay supervisor did not become ready after bootstrap.');
|
|
1594
|
-
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION
|
|
1718
|
+
const readiness = await waitForRelayReadiness({ expectedVersion: VERSION, attempts: 120, delayMs: 1000, processState: async () => {
|
|
1719
|
+
if (await relayProcessState() !== 'running')
|
|
1720
|
+
return 'stopped';
|
|
1721
|
+
const pid = Number((await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')).trim());
|
|
1722
|
+
const poll = await readFile(resolve(dharmaHome(), 'relay', 'repositories', selected.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
1723
|
+
return repositoryRelayObservationReady({ observation: poll, workspaceId: selected.workspaceId,
|
|
1724
|
+
version: VERSION, pid }) ? 'running' : 'stopped';
|
|
1725
|
+
} });
|
|
1595
1726
|
return { started: !alreadyRunning, supervisor: 'running', ...readiness };
|
|
1596
1727
|
}
|
|
1597
1728
|
async function supervisedDemoCycle(registration, signal) {
|
|
@@ -1654,7 +1785,8 @@ async function relaySupervise(flags) {
|
|
|
1654
1785
|
const health = createDemoWatchHealthRecorder({ home: dharmaHome(), pid: process.pid, version: VERSION });
|
|
1655
1786
|
try {
|
|
1656
1787
|
await writeJsonAtomic(bindingPath, { pid: process.pid, workspaceId: selectedWorkspace?.workspaceId ?? null,
|
|
1657
|
-
policyPath, demoWatches: true, version: VERSION
|
|
1788
|
+
policyPath, demoWatches: true, version: VERSION, standardRepositories: Boolean(policyPath),
|
|
1789
|
+
organizationId: config?.organizationId ?? null, deviceId: config?.deviceId ?? null });
|
|
1658
1790
|
const demo = runDemoSupervisor({ signal: controller.signal,
|
|
1659
1791
|
list: () => listDemoWatchRegistrations(dharmaHome()), cycle: (registration, signal) => {
|
|
1660
1792
|
if (!health.available(demoWatchRegistrationKey(registration))) {
|
|
@@ -1969,6 +2101,33 @@ async function bootstrap(flags) {
|
|
|
1969
2101
|
});
|
|
1970
2102
|
scopes = redeemed.organizationApiTokenScopes;
|
|
1971
2103
|
}
|
|
2104
|
+
let organizationApiTokenStored = false;
|
|
2105
|
+
let credentialFailure = null;
|
|
2106
|
+
try {
|
|
2107
|
+
organizationApiTokenStored = Boolean(await loadOrganizationApiToken({
|
|
2108
|
+
hqUrl, organizationId, installationId: config.installationId,
|
|
2109
|
+
}));
|
|
2110
|
+
if (!organizationApiTokenStored && !String(process.env.DHARMA_ORG_API_TOKEN || '').trim()) {
|
|
2111
|
+
credentialFailure = 'organization_api_credentials_required';
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
catch {
|
|
2115
|
+
// Secure-store diagnostics can contain secrets. Emit only the failed stage.
|
|
2116
|
+
credentialFailure = 'organization_api_credential_store_unavailable';
|
|
2117
|
+
}
|
|
2118
|
+
if (credentialFailure) {
|
|
2119
|
+
return {
|
|
2120
|
+
ok: false,
|
|
2121
|
+
stage: 'organization_api_credentials',
|
|
2122
|
+
code: credentialFailure,
|
|
2123
|
+
message: 'The enrolled device is preserved, but its organization API credential is unavailable. Contact Dharma support for device-bound credential recovery; do not repeat enrollment or use another member\'s token.',
|
|
2124
|
+
sharedRepositoryReady: false,
|
|
2125
|
+
enrollment: {
|
|
2126
|
+
organizationId, deviceId: config.deviceId, status: 'approved', organizationApiTokenStored,
|
|
2127
|
+
scopes, recipientApproval, rebind,
|
|
2128
|
+
},
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
1972
2131
|
const onboardFlags = new Map(flags);
|
|
1973
2132
|
onboardFlags.delete('grant');
|
|
1974
2133
|
onboardFlags.set('portal-url', hqUrl);
|
|
@@ -1987,7 +2146,7 @@ async function bootstrap(flags) {
|
|
|
1987
2146
|
organizationId,
|
|
1988
2147
|
deviceId: config.deviceId,
|
|
1989
2148
|
status: 'approved',
|
|
1990
|
-
organizationApiTokenStored
|
|
2149
|
+
organizationApiTokenStored,
|
|
1991
2150
|
scopes,
|
|
1992
2151
|
recipientApproval,
|
|
1993
2152
|
rebind,
|
|
@@ -1998,11 +2157,36 @@ async function bootstrap(flags) {
|
|
|
1998
2157
|
const launcher = await installStableRepositoryLauncher(workspace);
|
|
1999
2158
|
const autostart = flags.has('no-relay-daemon')
|
|
2000
2159
|
? { state: 'disabled', backend: null }
|
|
2001
|
-
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(() =>
|
|
2002
|
-
home
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2160
|
+
: await withOnboardingStage('autostart', String(onboarded.workspaceId || ''), `dharma bootstrap --resume --complete --portal-url ${hqUrl} --organization-id ${organizationId} --workspace . --policy-revision ${policyRevision}`, () => withRelayStartupMutation(async () => {
|
|
2161
|
+
const home = dharmaHome();
|
|
2162
|
+
const existing = await relayAutostartStatus({ home });
|
|
2163
|
+
if (existing.backend !== null || existing.state !== 'disabled') {
|
|
2164
|
+
const anchor = await inspectOwnedRelayAutostart({ home });
|
|
2165
|
+
if (anchor.policy !== null) {
|
|
2166
|
+
const current = await readDeviceConfig();
|
|
2167
|
+
if (!current || current.organizationId !== organizationId || current.deviceId !== config.deviceId) {
|
|
2168
|
+
throw new Error('relay_workspace_conflict: startup enrollment changed.');
|
|
2169
|
+
}
|
|
2170
|
+
const selected = selectDeviceWorkspace(await registry(), { organizationId,
|
|
2171
|
+
deviceId: config.deviceId, path: anchor.workspace });
|
|
2172
|
+
if (!selected)
|
|
2173
|
+
throw new Error('relay_workspace_conflict: startup anchor is not registered to this device.');
|
|
2174
|
+
if (anchor.policy !== resolve(anchor.workspace, '.dharma', 'approved-policy.json')) {
|
|
2175
|
+
throw new Error('relay_workspace_conflict: startup policy is not canonical.');
|
|
2176
|
+
}
|
|
2177
|
+
const authorized = await loadOrganizationPolicy(anchor.policy);
|
|
2178
|
+
const canonical = (await registry()).filter(row => row.workspaceId === authorized.serverAuthorization?.workspaceId
|
|
2179
|
+
&& row.organizationId === organizationId && row.path === anchor.workspace
|
|
2180
|
+
&& row.routeHash === selected.routeHash && row.repositoryRemoteHash === selected.repositoryRemoteHash);
|
|
2181
|
+
if (canonical.length !== 1)
|
|
2182
|
+
throw new Error('relay_workspace_conflict: startup policy route changed.');
|
|
2183
|
+
await loadVerifiedWorkspacePolicy(anchor.policy, authorized.serverAuthorization?.workspaceId ?? '');
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return enableRelayAutostart({ home, workspace, policy: resolve(workspace, '.dharma', 'approved-policy.json'),
|
|
2187
|
+
launcher: resolve(workspace, process.platform === 'win32' ? launcher.windows : launcher.shell),
|
|
2188
|
+
version: VERSION, preserveStandardAnchor: true });
|
|
2189
|
+
}));
|
|
2006
2190
|
let sharedRepositoryReady = onboarded.sharedRepositoryReady === true;
|
|
2007
2191
|
const completionRequested = flags.has('complete');
|
|
2008
2192
|
if (!completionRequested) {
|
|
@@ -2015,7 +2199,7 @@ async function bootstrap(flags) {
|
|
|
2015
2199
|
organizationId,
|
|
2016
2200
|
deviceId: config.deviceId,
|
|
2017
2201
|
status: 'approved',
|
|
2018
|
-
organizationApiTokenStored
|
|
2202
|
+
organizationApiTokenStored,
|
|
2019
2203
|
scopes,
|
|
2020
2204
|
recipientApproval,
|
|
2021
2205
|
rebind,
|
|
@@ -2106,7 +2290,7 @@ async function bootstrap(flags) {
|
|
|
2106
2290
|
organizationId,
|
|
2107
2291
|
deviceId: config.deviceId,
|
|
2108
2292
|
status: 'approved',
|
|
2109
|
-
organizationApiTokenStored
|
|
2293
|
+
organizationApiTokenStored,
|
|
2110
2294
|
scopes,
|
|
2111
2295
|
recipientApproval,
|
|
2112
2296
|
rebind,
|
|
@@ -2791,8 +2975,18 @@ async function evidencePreview(flags) {
|
|
|
2791
2975
|
captureProvenance: captureProvenance(session.endedAt),
|
|
2792
2976
|
});
|
|
2793
2977
|
});
|
|
2978
|
+
const disclosureFailures = capsules.map((capsule) => {
|
|
2979
|
+
try {
|
|
2980
|
+
return containsDisallowedLocalPath(capsule) ? 'local_path_disclosure_forbidden' : null;
|
|
2981
|
+
}
|
|
2982
|
+
catch {
|
|
2983
|
+
return 'local_path_inspection_depth_exceeded';
|
|
2984
|
+
}
|
|
2985
|
+
}).filter((reason) => reason !== null);
|
|
2794
2986
|
automaticDisclosure = {
|
|
2795
|
-
ready:
|
|
2987
|
+
ready: disclosureFailures.length === 0,
|
|
2988
|
+
blockedCapsuleCount: disclosureFailures.length,
|
|
2989
|
+
reasonCodes: [...new Set(disclosureFailures)].sort(),
|
|
2796
2990
|
disclosureClass: 'automatic_capsule',
|
|
2797
2991
|
disclosureMode: capsules[0]?.automaticDisclosureMode ?? policy.evidence.automaticDisclosure?.mode ?? 'local_analysis',
|
|
2798
2992
|
consentReceiptId: policy.evidence.automaticDisclosure?.consentReceiptId ?? null,
|
|
@@ -3750,6 +3944,38 @@ The CLI enrolls this device through browser-confirmed Clerk organization consent
|
|
|
3750
3944
|
knowledge,
|
|
3751
3945
|
};
|
|
3752
3946
|
}
|
|
3947
|
+
async function repositoryInstallerRecoveryCommand(flags) {
|
|
3948
|
+
if (flags.has('apply') === flags.has('dry-run'))
|
|
3949
|
+
throw new Error('Choose exactly one of --apply or --dry-run.');
|
|
3950
|
+
const fromWorkspaceId = required(flags, 'from-workspace-id');
|
|
3951
|
+
const workspaceId = required(flags, 'workspace-id');
|
|
3952
|
+
const workspace = await realpath(String(flags.get('workspace') || '.'));
|
|
3953
|
+
const organizationId = required(flags, 'organization-id');
|
|
3954
|
+
const config = await readDeviceConfig();
|
|
3955
|
+
if (!config || config.organizationId !== organizationId)
|
|
3956
|
+
throw new Error('Recovery requires current organization enrollment.');
|
|
3957
|
+
await loadDeviceEnrollmentAnchor({ config });
|
|
3958
|
+
const identity = await preflightBootstrapWorkspaceIdentity(workspace);
|
|
3959
|
+
const registered = selectLegacyInstallerRecoveryWorkspace(await registry(), { organizationId, deviceId: config.deviceId,
|
|
3960
|
+
path: workspace, repositoryRemoteHash: identity.fingerprint, workspaceId });
|
|
3961
|
+
const repositoryAgentKey = `repo:${identity.fingerprint.slice('sha256:'.length, 'sha256:'.length + 24)}`;
|
|
3962
|
+
if (!registered?.repositoryBindingId || !registered.repositoryAgentId
|
|
3963
|
+
|| registered.repositoryAgentKey !== repositoryAgentKey) {
|
|
3964
|
+
throw new Error('Recovery requires the enrolled device canonical repository binding.');
|
|
3965
|
+
}
|
|
3966
|
+
const policy = await loadVerifiedWorkspacePolicy(resolve(workspace, '.dharma', 'approved-policy.json'), registered.workspaceId);
|
|
3967
|
+
if (policy.organizationId !== organizationId || policy.serverAuthorization?.workspaceId !== registered.workspaceId) {
|
|
3968
|
+
throw new Error('Recovery requires a current signed workspace policy.');
|
|
3969
|
+
}
|
|
3970
|
+
await assertWorkspaceAuthorizationCurrent(registered.workspaceId, policy.serverAuthorization);
|
|
3971
|
+
const scope = { organizationId, workspaceId: registered.workspaceId,
|
|
3972
|
+
repositoryBindingId: registered.repositoryBindingId, repositoryAgentId: registered.repositoryAgentId };
|
|
3973
|
+
validateRepositorySourceAuthorization(await fetchRepositorySourceAuthorization(await client(), scope), scope, new Date());
|
|
3974
|
+
const recovery = await recoverLegacyRepositoryInstaller({ workspace, fromWorkspaceId,
|
|
3975
|
+
workspaceId: registered.workspaceId, repositoryAgentKey, apply: flags.has('apply') });
|
|
3976
|
+
return { ok: true, dryRun: !recovery.applied, serverMutation: false, recovery,
|
|
3977
|
+
nextAction: 'Resume pinned onboarding; recovery is not signed skill installation or activation.' };
|
|
3978
|
+
}
|
|
3753
3979
|
async function repositorySnapshotCommand(flags, outputs) {
|
|
3754
3980
|
if (flags.has('apply') && flags.has('dry-run'))
|
|
3755
3981
|
throw new Error('Choose --apply or --dry-run, not both.');
|
|
@@ -5837,6 +6063,52 @@ async function takeCachedSkillUpdate(input) {
|
|
|
5837
6063
|
}
|
|
5838
6064
|
}
|
|
5839
6065
|
async function relayStart(flags) {
|
|
6066
|
+
const anchor = resolve(required(flags, 'policy'));
|
|
6067
|
+
const config = await readDeviceConfig();
|
|
6068
|
+
if (!config)
|
|
6069
|
+
throw new Error('Relay requires current enrollment.');
|
|
6070
|
+
const declared = await loadOrganizationPolicy(anchor);
|
|
6071
|
+
const rows = await registry();
|
|
6072
|
+
const selected = rows.find(row => row.workspaceId === declared.serverAuthorization?.workspaceId
|
|
6073
|
+
&& row.organizationId === config.organizationId && resolve(row.path, '.dharma', 'approved-policy.json') === anchor);
|
|
6074
|
+
if (!selected)
|
|
6075
|
+
throw new Error('Relay policy must be the canonical policy of one registered workspace.');
|
|
6076
|
+
const authorized = await selectRepositoryRelayRegistrations(rows, config, loadOrganizationPolicy);
|
|
6077
|
+
if (!authorized.some(row => row.workspaceId === selected.workspaceId && row.policyPath === anchor)) {
|
|
6078
|
+
throw new Error('Relay policy is not registered to this enrolled device.');
|
|
6079
|
+
}
|
|
6080
|
+
await loadVerifiedWorkspacePolicy(anchor, selected.workspaceId);
|
|
6081
|
+
const release = await acquireRelayProcessLease();
|
|
6082
|
+
const controller = new AbortController();
|
|
6083
|
+
const stop = () => controller.abort();
|
|
6084
|
+
process.once('SIGINT', stop);
|
|
6085
|
+
process.once('SIGTERM', stop);
|
|
6086
|
+
const serialized = serializeRelayWork();
|
|
6087
|
+
try {
|
|
6088
|
+
if (flags.has('once'))
|
|
6089
|
+
return await relayWorkspaceLoop(flags, controller.signal, serialized);
|
|
6090
|
+
await runRegisteredRepositoryRelays({ signal: controller.signal, list: async () => {
|
|
6091
|
+
const currentConfig = await readDeviceConfig();
|
|
6092
|
+
if (currentConfig?.organizationId !== config.organizationId || currentConfig.deviceId !== config.deviceId) {
|
|
6093
|
+
throw new Error('Relay enrollment changed.');
|
|
6094
|
+
}
|
|
6095
|
+
const records = JSON.parse(await readFile(workspaceRegistryPath(), 'utf8'));
|
|
6096
|
+
if (!Array.isArray(records))
|
|
6097
|
+
throw new Error('Invalid workspace registry.');
|
|
6098
|
+
return selectRepositoryRelayRegistrations(records, config, loadOrganizationPolicy);
|
|
6099
|
+
}, run: (row, signal) => relayWorkspaceLoop(new Map([...flags, ['policy', row.policyPath]]), signal, serialized),
|
|
6100
|
+
observe: event => process.stderr.write(`${JSON.stringify({ event: 'repository_relay', ...event })}\n`) });
|
|
6101
|
+
return { ok: true, stopped: true };
|
|
6102
|
+
}
|
|
6103
|
+
finally {
|
|
6104
|
+
controller.abort();
|
|
6105
|
+
process.removeListener('SIGINT', stop);
|
|
6106
|
+
process.removeListener('SIGTERM', stop);
|
|
6107
|
+
await release();
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
async function relayWorkspaceLoop(flags, signal, serialized) {
|
|
6111
|
+
signal.throwIfAborted();
|
|
5840
6112
|
const policyPath = resolve(required(flags, 'policy'));
|
|
5841
6113
|
const declaredPolicy = await loadOrganizationPolicy(policyPath);
|
|
5842
6114
|
const declaredWorkspaceId = declaredPolicy.serverAuthorization?.workspaceId;
|
|
@@ -5851,400 +6123,411 @@ async function relayStart(flags) {
|
|
|
5851
6123
|
}
|
|
5852
6124
|
let canonicalWorkspace = selectedWorkspace;
|
|
5853
6125
|
let policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6126
|
+
signal.throwIfAborted();
|
|
5854
6127
|
const fabric = await client();
|
|
5855
6128
|
const leaseSeconds = Number(flags.get('lease-seconds') || 120);
|
|
5856
6129
|
const pollMs = Math.min(Math.max(Number(flags.get('poll-seconds') || 3), 1), 60) * 1_000;
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
5890
|
-
publishedLocalSnapshotHash: recovered,
|
|
5891
|
-
} };
|
|
5892
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5893
|
-
}
|
|
5894
|
-
}
|
|
5895
|
-
catch {
|
|
5896
|
-
repositorySourceBaselineAvailable = false;
|
|
6130
|
+
let stopping = signal.aborted;
|
|
6131
|
+
let stopPreparation;
|
|
6132
|
+
const stop = () => { stopping = true; stopPreparation?.(); };
|
|
6133
|
+
let tasksCompleted = 0;
|
|
6134
|
+
let lastProviderFailureCategory = null;
|
|
6135
|
+
let taskTrajectoriesRecovered = 0;
|
|
6136
|
+
let evidenceResponsesCompleted = 0;
|
|
6137
|
+
let trajectorySyncsCompleted = 0;
|
|
6138
|
+
let nextPolicyRefreshAt = 0;
|
|
6139
|
+
let lastPollReceiptAt = 0;
|
|
6140
|
+
let evidencePolicyFresh = false;
|
|
6141
|
+
const repositorySourceWatcher = new RepositorySourceWatcher();
|
|
6142
|
+
const blockedRepositorySourceRetry = new BlockedRepositorySourceRetry();
|
|
6143
|
+
let repositorySourceBaselineAvailable = true;
|
|
6144
|
+
let publishedLocalSnapshotHash = canonicalWorkspace.repositoryPackage?.publishedLocalSnapshotHash
|
|
6145
|
+
|| (canonicalWorkspace.repositoryPackage?.state === 'published'
|
|
6146
|
+
&& canonicalWorkspace.repositoryPackage.snapshotHash === canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash
|
|
6147
|
+
? canonicalWorkspace.repositoryPackage.snapshotHash : null);
|
|
6148
|
+
if (canonicalWorkspace.repositoryPackage && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6149
|
+
try {
|
|
6150
|
+
const recovered = await recoverPublishedLocalSourceBaseline({
|
|
6151
|
+
workspace: canonicalWorkspace.path, organizationId: canonicalWorkspace.organizationId,
|
|
6152
|
+
workspaceId: canonicalWorkspace.workspaceId, repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6153
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId, record: canonicalWorkspace.repositoryPackage,
|
|
6154
|
+
});
|
|
6155
|
+
if (recovered && recovered !== publishedLocalSnapshotHash) {
|
|
6156
|
+
publishedLocalSnapshotHash = recovered;
|
|
6157
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: {
|
|
6158
|
+
...canonicalWorkspace.repositoryPackage, localBaselineSnapshotHash: recovered,
|
|
6159
|
+
publishedLocalSnapshotHash: recovered,
|
|
6160
|
+
} };
|
|
6161
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
5897
6162
|
}
|
|
5898
6163
|
}
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
6164
|
+
catch {
|
|
6165
|
+
repositorySourceBaselineAvailable = false;
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
if (publishedLocalSnapshotHash) {
|
|
6169
|
+
try {
|
|
6170
|
+
await readRepositoryPackageSnapshot(canonicalWorkspace.path, publishedLocalSnapshotHash);
|
|
6171
|
+
const localHash = canonicalWorkspace.repositoryPackage?.localBaselineSnapshotHash;
|
|
6172
|
+
if (localHash && (localHash === publishedLocalSnapshotHash
|
|
6173
|
+
|| canonicalWorkspace.repositoryPackage?.pendingLocalSnapshotHash === localHash)) {
|
|
6174
|
+
await seedRepositorySourceWatcher({ workspace: canonicalWorkspace.path,
|
|
6175
|
+
organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6176
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6177
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId,
|
|
6178
|
+
publishedHash: publishedLocalSnapshotHash,
|
|
6179
|
+
localHash,
|
|
6180
|
+
watcher: repositorySourceWatcher });
|
|
5916
6181
|
}
|
|
5917
6182
|
}
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
6183
|
+
catch {
|
|
6184
|
+
repositorySourceBaselineAvailable = false;
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
let nextRepositorySourceScanAt = 0;
|
|
6188
|
+
let repositorySourceCandidates = 0;
|
|
6189
|
+
let repositorySourceFailures = 0;
|
|
6190
|
+
let repositorySourceState = 'not_scanned';
|
|
6191
|
+
let sourceScanFlight = null;
|
|
6192
|
+
let sourceScanReady = null;
|
|
6193
|
+
const { LocalVault, loadOrCreateVaultMasterKey } = await loadVaultModule();
|
|
6194
|
+
const vault = await LocalVault.open({
|
|
6195
|
+
root: resolve(dharmaHome(), 'vault'),
|
|
6196
|
+
masterKey: await loadOrCreateVaultMasterKey(),
|
|
6197
|
+
rawLocalDays: rawLocalRetentionDays(policy),
|
|
6198
|
+
});
|
|
6199
|
+
if (signal.aborted)
|
|
6200
|
+
vault.close();
|
|
6201
|
+
signal.throwIfAborted();
|
|
6202
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
6203
|
+
if (signal.aborted)
|
|
6204
|
+
stop();
|
|
6205
|
+
let skillPreparationsCompleted = 0;
|
|
6206
|
+
let skillPreparationFailures = 0;
|
|
6207
|
+
let skillActivationsCompleted = 0;
|
|
6208
|
+
let skillActivationFailures = 0;
|
|
6209
|
+
const skillActivationFailuresByProvider = {};
|
|
6210
|
+
let nextSkillActivationAt = 0;
|
|
6211
|
+
const skillPreparationPump = startSkillPreparationPump({
|
|
6212
|
+
prepare: async (assertRunning) => {
|
|
6213
|
+
await prepareProvidersIndependently(providerAdapters, assertRunning, async (adapter) => {
|
|
6214
|
+
assertRunning();
|
|
6215
|
+
const capability = await adapter.capability();
|
|
6216
|
+
assertRunning();
|
|
6217
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6218
|
+
return;
|
|
6219
|
+
const provider = adapter.providerId;
|
|
6220
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6221
|
+
provider, assertCurrent: assertRunning }, async () => {
|
|
6222
|
+
const prepared = await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6223
|
+
provider, policyPath, fabric, automatic: true, assertRunning });
|
|
6224
|
+
if (!prepared) {
|
|
6225
|
+
assertRunning();
|
|
5943
6226
|
return;
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
if (!
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
prepared.
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
6227
|
+
}
|
|
6228
|
+
let retained = false;
|
|
6229
|
+
try {
|
|
6230
|
+
prepared.assertCurrent();
|
|
6231
|
+
const repositoryAgentId = prepared.workspace.repositoryAgentId;
|
|
6232
|
+
if (!repositoryAgentId)
|
|
6233
|
+
throw new Error('Preparation cache requires an enrolled repository agent.');
|
|
6234
|
+
const pendingRecord = await serializeSkillPreparationRecord({
|
|
6235
|
+
schema: 'dharma.skill-preparation/v1', organizationId: prepared.config.organizationId,
|
|
6236
|
+
deviceId: prepared.config.deviceId, workspaceId: prepared.workspace.workspaceId,
|
|
6237
|
+
repositoryAgentId,
|
|
6238
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null, provider,
|
|
6239
|
+
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
6240
|
+
bundle: prepared.bundle, repositoryPackage: prepared.repositoryDelivery
|
|
6241
|
+
? { envelope: prepared.repositoryDelivery.envelope, index: prepared.repositoryDelivery.index } : null,
|
|
6242
|
+
preparedAt: new Date().toISOString(), activationAuthorized: false,
|
|
6243
|
+
});
|
|
6244
|
+
prepared.assertCurrent();
|
|
6245
|
+
await writeFile(resolve(prepared.sourceRoot, 'PREPARED.json'), pendingRecord, { mode: 0o600, flag: 'wx' });
|
|
6246
|
+
prepared.assertCurrent();
|
|
6247
|
+
await publishSkillPreparationCache({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6248
|
+
provider, sourceRoot: prepared.sourceRoot, recordBytes: pendingRecord, assertCurrent: prepared.assertCurrent,
|
|
6249
|
+
expected: { organizationId: prepared.config.organizationId, deviceId: prepared.config.deviceId,
|
|
5962
6250
|
repositoryAgentId,
|
|
5963
|
-
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
6251
|
+
repositoryBindingId: prepared.workspace.repositoryBindingId ?? null,
|
|
5964
6252
|
policyHash: sha256(canonicalize(prepared.policy)), rolloutId: prepared.rollout.id,
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
prepared.
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
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
|
-
|
|
6253
|
+
bundleId: prepared.bundle.bundleId, bundleHash: prepared.bundle.bundleHash },
|
|
6254
|
+
onCommitted: () => { retained = true; } });
|
|
6255
|
+
skillPreparationsCompleted += 1;
|
|
6256
|
+
}
|
|
6257
|
+
finally {
|
|
6258
|
+
if (!retained)
|
|
6259
|
+
await rm(prepared.sourceRoot, { recursive: true, force: true });
|
|
6260
|
+
}
|
|
6261
|
+
});
|
|
6262
|
+
}, () => { skillPreparationFailures += 1; });
|
|
6263
|
+
},
|
|
6264
|
+
onError: () => { skillPreparationFailures += 1; },
|
|
6265
|
+
});
|
|
6266
|
+
stopPreparation = skillPreparationPump.requestStop;
|
|
6267
|
+
if (stopping)
|
|
6268
|
+
stopPreparation();
|
|
6269
|
+
const applyReadySourceScan = async () => {
|
|
6270
|
+
if (!sourceScanReady)
|
|
6271
|
+
return;
|
|
6272
|
+
const ready = sourceScanReady;
|
|
6273
|
+
sourceScanReady = null;
|
|
6274
|
+
sourceScanFlight = null;
|
|
6275
|
+
if ('error' in ready) {
|
|
6276
|
+
repositorySourceWatcher.invalidate();
|
|
6277
|
+
repositorySourceFailures += 1;
|
|
6278
|
+
repositorySourceState = 'blocked';
|
|
6279
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6280
|
+
return;
|
|
6281
|
+
}
|
|
6282
|
+
if ('state' in ready) {
|
|
6283
|
+
repositorySourceState = ready.state;
|
|
6284
|
+
nextRepositorySourceScanAt = performance.now() + 15_000;
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
const sourceCycle = ready.cycle;
|
|
6288
|
+
repositorySourceState = sourceCycle.state;
|
|
6289
|
+
if (sourceCycle.localMutation) {
|
|
6290
|
+
repositorySourceCandidates += 1;
|
|
6291
|
+
if (canonicalWorkspace.repositoryPackage && sourceCycle.persisted) {
|
|
6292
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6293
|
+
if (sourceCycle.state === 'source_already_published') {
|
|
6294
|
+
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6295
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6296
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6297
|
+
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6298
|
+
}
|
|
6299
|
+
else if (sourceCycle.candidate) {
|
|
6300
|
+
const receipt = sourceCycle.candidate;
|
|
6301
|
+
if (typeof receipt.candidateId === 'string' && typeof receipt.operationId === 'string'
|
|
6302
|
+
&& typeof receipt.snapshotHash === 'string' && sourceCycle.submittedManifestHash) {
|
|
6303
|
+
Object.assign(canonicalWorkspace.repositoryPackage, {
|
|
6304
|
+
state: receipt.state, candidateId: receipt.candidateId, operationId: receipt.operationId,
|
|
6305
|
+
snapshotHash: receipt.snapshotHash, sourceManifestHash: sourceCycle.submittedManifestHash,
|
|
6306
|
+
releaseId: typeof receipt.releaseId === 'string' ? receipt.releaseId : null,
|
|
6307
|
+
consolidationMode: 'repository_update',
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
if (receipt.state === 'published') {
|
|
6019
6311
|
canonicalWorkspace.repositoryPackage.publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6020
6312
|
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = null;
|
|
6021
6313
|
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = null;
|
|
6022
6314
|
publishedLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6023
6315
|
}
|
|
6024
|
-
else if (
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6316
|
+
else if ((receipt.state === 'accepted' || receipt.state === 'processing')
|
|
6317
|
+
&& typeof receipt.operationId === 'string') {
|
|
6318
|
+
canonicalWorkspace.repositoryPackage.pendingLocalSnapshotHash = sourceCycle.persisted.snapshotHash;
|
|
6319
|
+
canonicalWorkspace.repositoryPackage.pendingLocalOperationId = receipt.operationId;
|
|
6320
|
+
}
|
|
6321
|
+
else if (receipt.state === 'blocked') {
|
|
6322
|
+
canonicalWorkspace.repositoryPackage.localBaselineSnapshotHash = publishedLocalSnapshotHash;
|
|
6323
|
+
repositorySourceWatcher.invalidate();
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
nextRepositorySourceScanAt = performance.now() + (sourceCycle.state === 'debouncing' ? 15_000 : 60_000);
|
|
6330
|
+
};
|
|
6331
|
+
const consumeSourceScan = async () => {
|
|
6332
|
+
try {
|
|
6333
|
+
await applyReadySourceScan();
|
|
6334
|
+
}
|
|
6335
|
+
catch {
|
|
6336
|
+
repositorySourceWatcher.invalidate();
|
|
6337
|
+
repositorySourceFailures += 1;
|
|
6338
|
+
repositorySourceState = 'blocked';
|
|
6339
|
+
nextRepositorySourceScanAt = performance.now() + 60_000;
|
|
6340
|
+
}
|
|
6341
|
+
};
|
|
6342
|
+
try {
|
|
6343
|
+
signal.throwIfAborted();
|
|
6344
|
+
const recoveredTaskTrajectories = await deferUnavailableRelayRetention(async () => finalizeRecoveredSignedTaskTrajectories(fabric, policy));
|
|
6345
|
+
if (recoveredTaskTrajectories.state === 'completed') {
|
|
6346
|
+
taskTrajectoriesRecovered += recoveredTaskTrajectories.value.length;
|
|
6347
|
+
}
|
|
6348
|
+
do {
|
|
6349
|
+
if (stopping)
|
|
6350
|
+
break;
|
|
6351
|
+
await consumeSourceScan();
|
|
6352
|
+
if (Date.now() >= nextPolicyRefreshAt) {
|
|
6353
|
+
try {
|
|
6354
|
+
await syncWorkspacePolicy(fabric, canonicalWorkspace, policy.revision, true);
|
|
6355
|
+
policy = await loadVerifiedWorkspacePolicy(policyPath, canonicalWorkspace.workspaceId);
|
|
6356
|
+
evidencePolicyFresh = true;
|
|
6357
|
+
}
|
|
6358
|
+
catch {
|
|
6359
|
+
evidencePolicyFresh = false;
|
|
6360
|
+
}
|
|
6361
|
+
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6362
|
+
}
|
|
6363
|
+
if (!sourceScanFlight && canonicalWorkspace.repositoryBindingId && canonicalWorkspace.repositoryAgentId) {
|
|
6364
|
+
try {
|
|
6365
|
+
const current = await pollRepositoryCandidate({ transport: fabric,
|
|
6366
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6367
|
+
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6368
|
+
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6369
|
+
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6370
|
+
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6371
|
+
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6372
|
+
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6373
|
+
throw new Error('Repository candidate status changed identity while polling.');
|
|
6045
6374
|
}
|
|
6046
|
-
|
|
6047
|
-
|
|
6375
|
+
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6376
|
+
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6377
|
+
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6378
|
+
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6379
|
+
...baseline,
|
|
6380
|
+
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6381
|
+
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6382
|
+
if (current.state === 'blocked')
|
|
6048
6383
|
repositorySourceWatcher.invalidate();
|
|
6049
|
-
|
|
6384
|
+
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6050
6385
|
}
|
|
6051
|
-
|
|
6386
|
+
}
|
|
6387
|
+
catch {
|
|
6388
|
+
repositorySourceFailures += 1;
|
|
6052
6389
|
}
|
|
6053
6390
|
}
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6391
|
+
let evidenceRequestId;
|
|
6392
|
+
if (evidencePolicyFresh && !stopping) {
|
|
6393
|
+
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6394
|
+
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6395
|
+
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6396
|
+
return {
|
|
6397
|
+
synced,
|
|
6398
|
+
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6399
|
+
};
|
|
6400
|
+
});
|
|
6401
|
+
if (evidenceCycle.state === 'deferred') {
|
|
6402
|
+
evidencePolicyFresh = false;
|
|
6403
|
+
}
|
|
6404
|
+
else {
|
|
6405
|
+
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6406
|
+
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6407
|
+
if (evidenceRequestId)
|
|
6408
|
+
evidenceResponsesCompleted += 1;
|
|
6409
|
+
}
|
|
6065
6410
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6411
|
+
const result = await serialized(async () => {
|
|
6412
|
+
if (stopping)
|
|
6413
|
+
return { taskId: null };
|
|
6414
|
+
return executeOneTask(fabric, leaseSeconds);
|
|
6415
|
+
});
|
|
6416
|
+
if (!stopping && Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6417
|
+
const observation = {
|
|
6418
|
+
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION, pid: process.pid,
|
|
6419
|
+
};
|
|
6420
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'repositories', canonicalWorkspace.workspaceId, 'last-successful-poll.json'), observation);
|
|
6421
|
+
const binding = await readFile(resolve(dharmaHome(), 'relay', 'supervisor-workspace.json'), 'utf8')
|
|
6422
|
+
.then(value => JSON.parse(value)).catch(() => null);
|
|
6423
|
+
if (!binding || binding.workspaceId === canonicalWorkspace.workspaceId) {
|
|
6424
|
+
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), observation);
|
|
6425
|
+
}
|
|
6426
|
+
lastPollReceiptAt = Date.now();
|
|
6071
6427
|
}
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6428
|
+
if (result.taskId)
|
|
6429
|
+
tasksCompleted += 1;
|
|
6430
|
+
if (typeof result.failureCategory === 'string')
|
|
6431
|
+
lastProviderFailureCategory = result.failureCategory;
|
|
6432
|
+
if (!stopping && !result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6433
|
+
for (const adapter of providerAdapters) {
|
|
6075
6434
|
try {
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6435
|
+
const capability = await adapter.capability();
|
|
6436
|
+
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6437
|
+
continue;
|
|
6438
|
+
const provider = adapter.providerId;
|
|
6439
|
+
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6440
|
+
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6441
|
+
provider, assertCurrent: () => { if (stopping)
|
|
6442
|
+
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6443
|
+
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6444
|
+
provider, policyPath, fabric });
|
|
6445
|
+
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6446
|
+
provider, policyPath, fabric, automatic: true });
|
|
6447
|
+
if (!prepared)
|
|
6448
|
+
return;
|
|
6449
|
+
if (!cached)
|
|
6450
|
+
skillPreparationsCompleted += 1;
|
|
6451
|
+
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6452
|
+
provider, policyPath, flags: new Map() });
|
|
6453
|
+
skillActivationsCompleted += 1;
|
|
6454
|
+
});
|
|
6455
|
+
});
|
|
6079
6456
|
}
|
|
6080
6457
|
catch {
|
|
6081
|
-
|
|
6458
|
+
skillActivationFailures += 1;
|
|
6459
|
+
skillActivationFailuresByProvider[adapter.providerId] = (skillActivationFailuresByProvider[adapter.providerId] || 0) + 1;
|
|
6082
6460
|
}
|
|
6083
|
-
nextPolicyRefreshAt = Date.now() + 60_000;
|
|
6084
6461
|
}
|
|
6085
|
-
|
|
6462
|
+
nextSkillActivationAt = performance.now() + 60_000;
|
|
6463
|
+
}
|
|
6464
|
+
if (!stopping && !sourceScanFlight && performance.now() >= nextRepositorySourceScanAt) {
|
|
6465
|
+
const sourceWorkspace = canonicalWorkspace;
|
|
6466
|
+
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6467
|
+
sourceScanFlight = (async () => {
|
|
6086
6468
|
try {
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
scope: { organizationId: canonicalWorkspace.organizationId, workspaceId: canonicalWorkspace.workspaceId,
|
|
6090
|
-
repositoryBindingId: canonicalWorkspace.repositoryBindingId,
|
|
6091
|
-
repositoryAgentId: canonicalWorkspace.repositoryAgentId } });
|
|
6092
|
-
if (current && canonicalWorkspace.repositoryPackage) {
|
|
6093
|
-
if (current.candidateId !== canonicalWorkspace.repositoryPackage.candidateId
|
|
6094
|
-
|| current.operationId !== canonicalWorkspace.repositoryPackage.operationId) {
|
|
6095
|
-
throw new Error('Repository candidate status changed identity while polling.');
|
|
6096
|
-
}
|
|
6097
|
-
const baseline = advanceRepositorySourceBaseline(canonicalWorkspace.repositoryPackage, current);
|
|
6098
|
-
canonicalWorkspace = { ...canonicalWorkspace, repositoryPackage: { ...canonicalWorkspace.repositoryPackage,
|
|
6099
|
-
state: current.state, candidateId: current.candidateId, operationId: current.operationId,
|
|
6100
|
-
snapshotHash: current.snapshotHash, releaseId: current.releaseId,
|
|
6101
|
-
...baseline,
|
|
6102
|
-
generation: current.state === 'published' ? Math.max(1, canonicalWorkspace.repositoryPackage.generation) : canonicalWorkspace.repositoryPackage.generation } };
|
|
6103
|
-
publishedLocalSnapshotHash = baseline.publishedLocalSnapshotHash;
|
|
6104
|
-
if (current.state === 'blocked')
|
|
6105
|
-
repositorySourceWatcher.invalidate();
|
|
6106
|
-
await saveWorkspaceRecord(canonicalWorkspace);
|
|
6469
|
+
if (!repositorySourceBaselineAvailable) {
|
|
6470
|
+
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6107
6471
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
repositorySourceFailures += 1;
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
let evidenceRequestId;
|
|
6114
|
-
if (evidencePolicyFresh) {
|
|
6115
|
-
const evidenceCycle = await deferUnavailableRelayRetention(async () => {
|
|
6116
|
-
const synced = await syncPendingRetentionCapsules(vault, fabric, policy, canonicalWorkspace.workspaceId);
|
|
6117
|
-
const evidence = await processEvidenceRequest(fabric, policy, canonicalWorkspace.workspaceId);
|
|
6118
|
-
return {
|
|
6119
|
-
synced,
|
|
6120
|
-
evidenceRequestId: typeof evidence.requestId === 'string' ? evidence.requestId : undefined,
|
|
6121
|
-
};
|
|
6122
|
-
});
|
|
6123
|
-
if (evidenceCycle.state === 'deferred') {
|
|
6124
|
-
evidencePolicyFresh = false;
|
|
6125
|
-
}
|
|
6126
|
-
else {
|
|
6127
|
-
trajectorySyncsCompleted += evidenceCycle.value.synced;
|
|
6128
|
-
evidenceRequestId = evidenceCycle.value.evidenceRequestId;
|
|
6129
|
-
if (evidenceRequestId)
|
|
6130
|
-
evidenceResponsesCompleted += 1;
|
|
6131
|
-
}
|
|
6132
|
-
}
|
|
6133
|
-
const result = await executeOneTask(fabric, leaseSeconds);
|
|
6134
|
-
if (Date.now() - lastPollReceiptAt >= 30_000) {
|
|
6135
|
-
await writeJsonAtomic(resolve(dharmaHome(), 'relay', 'last-successful-poll.json'), {
|
|
6136
|
-
at: new Date().toISOString(), workspaceId: canonicalWorkspace.workspaceId, version: VERSION,
|
|
6137
|
-
});
|
|
6138
|
-
lastPollReceiptAt = Date.now();
|
|
6139
|
-
}
|
|
6140
|
-
if (result.taskId)
|
|
6141
|
-
tasksCompleted += 1;
|
|
6142
|
-
if (typeof result.failureCategory === 'string')
|
|
6143
|
-
lastProviderFailureCategory = result.failureCategory;
|
|
6144
|
-
if (!result.taskId && performance.now() >= nextSkillActivationAt) {
|
|
6145
|
-
for (const adapter of providerAdapters) {
|
|
6146
|
-
try {
|
|
6147
|
-
const capability = await adapter.capability();
|
|
6148
|
-
if (capability.skillInstall === 'unavailable' || !isLocalProviderId(adapter.providerId))
|
|
6149
|
-
continue;
|
|
6150
|
-
const provider = adapter.providerId;
|
|
6151
|
-
await withWorkspaceSkillActivationLock(canonicalWorkspace.workspaceId, provider, async () => {
|
|
6152
|
-
await withSkillPreparationTransaction({ home: dharmaHome(), workspaceId: canonicalWorkspace.workspaceId,
|
|
6153
|
-
provider, assertCurrent: () => { if (stopping)
|
|
6154
|
-
throw new Error('Relay is stopping.'); } }, async () => {
|
|
6155
|
-
const cached = await takeCachedSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6156
|
-
provider, policyPath, fabric });
|
|
6157
|
-
const prepared = cached || await prepareSkillUpdate({ workspaceId: canonicalWorkspace.workspaceId,
|
|
6158
|
-
provider, policyPath, fabric, automatic: true });
|
|
6159
|
-
if (!prepared)
|
|
6160
|
-
return;
|
|
6161
|
-
if (!cached)
|
|
6162
|
-
skillPreparationsCompleted += 1;
|
|
6163
|
-
await activatePreparedSkillUpdate({ prepared, workspaceId: canonicalWorkspace.workspaceId,
|
|
6164
|
-
provider, policyPath, flags: new Map() });
|
|
6165
|
-
skillActivationsCompleted += 1;
|
|
6166
|
-
});
|
|
6167
|
-
});
|
|
6472
|
+
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6473
|
+
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6168
6474
|
}
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6475
|
+
const installedKnowledge = await installedRepositoryKnowledge(sourceWorkspace);
|
|
6476
|
+
if (!installedKnowledge && (sourceWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
6477
|
+
sourceScanReady = { state: 'awaiting_signed_baseline' };
|
|
6172
6478
|
}
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
const sourceBaselineHash = publishedLocalSnapshotHash;
|
|
6179
|
-
sourceScanFlight = (async () => {
|
|
6180
|
-
try {
|
|
6181
|
-
if (!repositorySourceBaselineAvailable) {
|
|
6182
|
-
throw new Error('Repository source baseline is unavailable; refusing an unanchored update.');
|
|
6183
|
-
}
|
|
6184
|
-
if (!sourceWorkspace.repositoryBindingId || !sourceWorkspace.repositoryAgentId) {
|
|
6185
|
-
throw new Error('Repository source synchronization requires a complete repository binding.');
|
|
6186
|
-
}
|
|
6187
|
-
const installedKnowledge = await installedRepositoryKnowledge(sourceWorkspace);
|
|
6188
|
-
if (!installedKnowledge && (sourceWorkspace.repositoryPackage?.generation ?? 0) > 0) {
|
|
6189
|
-
sourceScanReady = { state: 'awaiting_signed_baseline' };
|
|
6190
|
-
}
|
|
6191
|
-
else {
|
|
6192
|
-
if (sourceWorkspace.repositoryPackage?.state === 'blocked' && sourceWorkspace.repositoryPackage.snapshotHash) {
|
|
6193
|
-
const blocked = await readRepositorySourceBaselineSnapshot(sourceWorkspace.path, sourceWorkspace.repositoryPackage.snapshotHash);
|
|
6194
|
-
if (blockedRepositorySourceRetry.consider(sourceWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
6195
|
-
repositorySourceWatcher.invalidate();
|
|
6196
|
-
}
|
|
6197
|
-
const sourceCycle = await scanRepositorySourceChanges({
|
|
6198
|
-
workspace: sourceWorkspace.path, organizationId: sourceWorkspace.organizationId,
|
|
6199
|
-
workspaceId: sourceWorkspace.workspaceId, repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6200
|
-
repositoryAgentId: sourceWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
6201
|
-
loadRetainedKnowledge: () => installedRepositoryKnowledge(sourceWorkspace),
|
|
6202
|
-
loadPublishedLocalBaseline: () => sourceBaselineHash
|
|
6203
|
-
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6204
|
-
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6205
|
-
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6206
|
-
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6207
|
-
workspaceId: sourceWorkspace.workspaceId,
|
|
6208
|
-
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6209
|
-
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6210
|
-
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6211
|
-
});
|
|
6212
|
-
sourceScanReady = { cycle: sourceCycle };
|
|
6479
|
+
else {
|
|
6480
|
+
if (sourceWorkspace.repositoryPackage?.state === 'blocked' && sourceWorkspace.repositoryPackage.snapshotHash) {
|
|
6481
|
+
const blocked = await readRepositorySourceBaselineSnapshot(sourceWorkspace.path, sourceWorkspace.repositoryPackage.snapshotHash);
|
|
6482
|
+
if (blockedRepositorySourceRetry.consider(sourceWorkspace.repositoryPackage.candidateId ?? '', blocked.manifest.knowledge, installedKnowledge))
|
|
6483
|
+
repositorySourceWatcher.invalidate();
|
|
6213
6484
|
}
|
|
6485
|
+
const sourceCycle = await scanRepositorySourceChanges({
|
|
6486
|
+
workspace: sourceWorkspace.path, organizationId: sourceWorkspace.organizationId,
|
|
6487
|
+
workspaceId: sourceWorkspace.workspaceId, repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6488
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId, transport: fabric, watcher: repositorySourceWatcher,
|
|
6489
|
+
loadRetainedKnowledge: () => installedRepositoryKnowledge(sourceWorkspace),
|
|
6490
|
+
loadPublishedLocalBaseline: () => sourceBaselineHash
|
|
6491
|
+
? readRepositoryPackageSnapshot(sourceWorkspace.path, sourceBaselineHash) : Promise.resolve(null),
|
|
6492
|
+
submitCandidate: (snapshot, expectedLatestSourceFingerprint) => synchronizeRepositoryCandidate({ transport: fabric,
|
|
6493
|
+
outboxRoot: resolve(dharmaHome(), 'relay', 'repository-candidates'),
|
|
6494
|
+
scope: { organizationId: sourceWorkspace.organizationId,
|
|
6495
|
+
workspaceId: sourceWorkspace.workspaceId,
|
|
6496
|
+
repositoryBindingId: sourceWorkspace.repositoryBindingId,
|
|
6497
|
+
repositoryAgentId: sourceWorkspace.repositoryAgentId },
|
|
6498
|
+
snapshot, initialRepository: false, expectedLatestSourceFingerprint }),
|
|
6499
|
+
});
|
|
6500
|
+
sourceScanReady = { cycle: sourceCycle };
|
|
6214
6501
|
}
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
}
|
|
6218
|
-
}
|
|
6219
|
-
}
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
}
|
|
6226
|
-
finally {
|
|
6227
|
-
if (sourceScanFlight)
|
|
6228
|
-
await sourceScanFlight;
|
|
6229
|
-
await consumeSourceScan();
|
|
6230
|
-
await skillPreparationPump.stop();
|
|
6231
|
-
vault.close();
|
|
6232
|
-
process.removeListener('SIGINT', stop);
|
|
6233
|
-
process.removeListener('SIGTERM', stop);
|
|
6234
|
-
}
|
|
6235
|
-
return {
|
|
6236
|
-
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6237
|
-
lastProviderFailureCategory,
|
|
6238
|
-
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6239
|
-
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6240
|
-
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6241
|
-
skillActivationFailuresByProvider,
|
|
6242
|
-
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6243
|
-
};
|
|
6502
|
+
}
|
|
6503
|
+
catch (error) {
|
|
6504
|
+
sourceScanReady = { error };
|
|
6505
|
+
}
|
|
6506
|
+
})();
|
|
6507
|
+
}
|
|
6508
|
+
if (flags.has('once'))
|
|
6509
|
+
break;
|
|
6510
|
+
if (!result.taskId && !evidenceRequestId)
|
|
6511
|
+
await waitForRelayRefresh(pollMs, signal);
|
|
6512
|
+
} while (!stopping);
|
|
6244
6513
|
}
|
|
6245
6514
|
finally {
|
|
6246
|
-
|
|
6515
|
+
if (sourceScanFlight)
|
|
6516
|
+
await sourceScanFlight;
|
|
6517
|
+
await consumeSourceScan();
|
|
6518
|
+
await skillPreparationPump.stop();
|
|
6519
|
+
vault.close();
|
|
6520
|
+
signal.removeEventListener('abort', stop);
|
|
6247
6521
|
}
|
|
6522
|
+
return {
|
|
6523
|
+
ok: true, stopped: true, tasksCompleted, taskTrajectoriesRecovered,
|
|
6524
|
+
lastProviderFailureCategory,
|
|
6525
|
+
evidenceResponsesCompleted, trajectorySyncsCompleted,
|
|
6526
|
+
repositorySourceCandidates, repositorySourceFailures, repositorySourceState,
|
|
6527
|
+
skillPreparationsCompleted, skillPreparationFailures, skillActivationsCompleted, skillActivationFailures,
|
|
6528
|
+
skillActivationFailuresByProvider,
|
|
6529
|
+
sharedRepositoryReady: await repositorySharedReady(canonicalWorkspace),
|
|
6530
|
+
};
|
|
6248
6531
|
}
|
|
6249
6532
|
export async function run(argv) {
|
|
6250
6533
|
const { positional, flags, repeated } = parseCliOptions(argv);
|
|
@@ -6436,6 +6719,8 @@ export async function run(argv) {
|
|
|
6436
6719
|
}
|
|
6437
6720
|
if (command === 'repositories' && subcommand === 'list')
|
|
6438
6721
|
return repositoriesList(flags);
|
|
6722
|
+
if (command === 'repositories' && subcommand === 'recover-installer')
|
|
6723
|
+
return repositoryInstallerRecoveryCommand(flags);
|
|
6439
6724
|
if (command === 'repositories' && subcommand === 'snapshot') {
|
|
6440
6725
|
return repositorySnapshotCommand(flags, repeated.get('approved-output') || []);
|
|
6441
6726
|
}
|
|
@@ -6489,6 +6774,25 @@ export async function run(argv) {
|
|
|
6489
6774
|
try {
|
|
6490
6775
|
const config = JSON.parse(await readFile(configPath(), 'utf8'));
|
|
6491
6776
|
const status = { version: VERSION, enrolled: true, relay, supervisor, autostart, reconnect };
|
|
6777
|
+
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6778
|
+
const relayPid = await readFile(resolve(dharmaHome(), 'relay', 'relay.pid'), 'utf8')
|
|
6779
|
+
.then(value => Number(value.trim())).catch(() => 0);
|
|
6780
|
+
try {
|
|
6781
|
+
const registered = await selectRepositoryRelayRegistrations(await registry(), config, loadOrganizationPolicy);
|
|
6782
|
+
const observations = [];
|
|
6783
|
+
for (const row of registered) {
|
|
6784
|
+
const observation = await readFile(resolve(dharmaHome(), 'relay', 'repositories', row.workspaceId, 'last-successful-poll.json'), 'utf8').then(value => JSON.parse(value)).catch(() => null);
|
|
6785
|
+
observations.push({ workspaceId: row.workspaceId,
|
|
6786
|
+
state: relay === 'running' && repositoryRelayObservationReady({ observation,
|
|
6787
|
+
workspaceId: row.workspaceId, version: VERSION, pid: relayPid }) ? 'acknowledged_recently' : 'pending',
|
|
6788
|
+
lastSuccessfulPollAt: typeof observation?.at === 'string' ? observation.at : null });
|
|
6789
|
+
}
|
|
6790
|
+
status.repositoryRelays = { state: 'observed', repositories: observations };
|
|
6791
|
+
}
|
|
6792
|
+
catch {
|
|
6793
|
+
status.repositoryRelays = { state: 'unavailable', repositories: [] };
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6492
6796
|
if (flags.has('verbose') || flags.has('diagnostic')) {
|
|
6493
6797
|
Object.assign(status, { home: dharmaHome(), organizationId: config.organizationId, deviceId: config.deviceId });
|
|
6494
6798
|
}
|
|
@@ -6522,6 +6826,8 @@ export async function run(argv) {
|
|
|
6522
6826
|
return probeRelayConnection();
|
|
6523
6827
|
if (command === 'relay' && subcommand === 'stop')
|
|
6524
6828
|
return relayStop();
|
|
6829
|
+
if (command === 'relay' && subcommand === 'upgrade')
|
|
6830
|
+
return relayUpgrade(flags);
|
|
6525
6831
|
if (command === 'relay' && subcommand === 'autostart') {
|
|
6526
6832
|
if (positional[2] === 'status')
|
|
6527
6833
|
return relayAutostartStatus({ home: dharmaHome() });
|