@viewportai/daemon 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +1 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/context-access-command.d.ts +0 -6
- package/dist/cli/context-access-command.d.ts.map +1 -1
- package/dist/cli/context-access-command.js +1 -71
- package/dist/cli/context-access-command.js.map +1 -1
- package/dist/cli/context-command.d.ts.map +1 -1
- package/dist/cli/context-command.js +593 -27
- package/dist/cli/context-command.js.map +1 -1
- package/dist/cli/context-sync-target.d.ts +2 -1
- package/dist/cli/context-sync-target.d.ts.map +1 -1
- package/dist/cli/context-sync-target.js +28 -0
- package/dist/cli/context-sync-target.js.map +1 -1
- package/dist/cli/context-vault-metadata-command.d.ts.map +1 -1
- package/dist/cli/context-vault-metadata-command.js +6 -1
- package/dist/cli/context-vault-metadata-command.js.map +1 -1
- package/dist/cli/lifecycle-commands.d.ts.map +1 -1
- package/dist/cli/lifecycle-commands.js +6 -6
- package/dist/cli/lifecycle-commands.js.map +1 -1
- package/dist/cli/unlock-command.d.ts +2 -0
- package/dist/cli/unlock-command.d.ts.map +1 -0
- package/dist/cli/unlock-command.js +35 -0
- package/dist/cli/unlock-command.js.map +1 -0
- package/dist/context/local-edge-store.d.ts +23 -1
- package/dist/context/local-edge-store.d.ts.map +1 -1
- package/dist/context/local-edge-store.js +51 -0
- package/dist/context/local-edge-store.js.map +1 -1
- package/dist/context/local-edge-sync.d.ts +63 -0
- package/dist/context/local-edge-sync.d.ts.map +1 -1
- package/dist/context/local-edge-sync.js +464 -4
- package/dist/context/local-edge-sync.js.map +1 -1
- package/dist/context/local-edge-types.d.ts +21 -0
- package/dist/context/local-edge-types.d.ts.map +1 -1
- package/dist/hooks/platform-plan-sync.d.ts +4 -1
- package/dist/hooks/platform-plan-sync.d.ts.map +1 -1
- package/dist/hooks/platform-plan-sync.js +20 -5
- package/dist/hooks/platform-plan-sync.js.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts +117 -0
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts.map +1 -0
- package/dist/hooks/trusted-edge-plan-artifacts.js +371 -0
- package/dist/hooks/trusted-edge-plan-artifacts.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/relay/bridge-token-issuer.d.ts +1 -0
- package/dist/relay/bridge-token-issuer.d.ts.map +1 -1
- package/dist/relay/bridge-token-issuer.js +1 -1
- package/dist/relay/bridge-token-issuer.js.map +1 -1
- package/dist/security/epoch-enrollment.d.ts +48 -0
- package/dist/security/epoch-enrollment.d.ts.map +1 -0
- package/dist/security/epoch-enrollment.js +290 -0
- package/dist/security/epoch-enrollment.js.map +1 -0
- package/dist/security/epoch-protocol.d.ts +181 -0
- package/dist/security/epoch-protocol.d.ts.map +1 -0
- package/dist/security/epoch-protocol.js +285 -0
- package/dist/security/epoch-protocol.js.map +1 -0
- package/dist/security/epoch-public-pins.d.ts +19 -0
- package/dist/security/epoch-public-pins.d.ts.map +1 -0
- package/dist/security/epoch-public-pins.js +129 -0
- package/dist/security/epoch-public-pins.js.map +1 -0
- package/dist/security/epoch-recovery.d.ts +56 -0
- package/dist/security/epoch-recovery.d.ts.map +1 -0
- package/dist/security/epoch-recovery.js +314 -0
- package/dist/security/epoch-recovery.js.map +1 -0
- package/dist/security/epoch-store.d.ts +111 -0
- package/dist/security/epoch-store.d.ts.map +1 -0
- package/dist/security/epoch-store.js +224 -0
- package/dist/security/epoch-store.js.map +1 -0
- package/dist/security/epoch-sync.d.ts +47 -0
- package/dist/security/epoch-sync.d.ts.map +1 -0
- package/dist/security/epoch-sync.js +371 -0
- package/dist/security/epoch-sync.js.map +1 -0
- package/dist/security/team-epoch-grants.d.ts +28 -0
- package/dist/security/team-epoch-grants.d.ts.map +1 -0
- package/dist/security/team-epoch-grants.js +256 -0
- package/dist/security/team-epoch-grants.js.map +1 -0
- package/dist/server/context-preview-service.d.ts +26 -0
- package/dist/server/context-preview-service.d.ts.map +1 -0
- package/dist/server/context-preview-service.js +71 -0
- package/dist/server/context-preview-service.js.map +1 -0
- package/dist/server/http-context-routes.d.ts +2 -1
- package/dist/server/http-context-routes.d.ts.map +1 -1
- package/dist/server/http-context-routes.js +65 -30
- package/dist/server/http-context-routes.js.map +1 -1
- package/dist/server/http-server.js +1 -1
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/rate-limiter.d.ts.map +1 -1
- package/dist/server/rate-limiter.js +6 -1
- package/dist/server/rate-limiter.js.map +1 -1
- package/dist/server/trusted-edge-command-capability.d.ts +14 -0
- package/dist/server/trusted-edge-command-capability.d.ts.map +1 -0
- package/dist/server/trusted-edge-command-capability.js +114 -0
- package/dist/server/trusted-edge-command-capability.js.map +1 -0
- package/dist/server/ws-command-handlers.d.ts.map +1 -1
- package/dist/server/ws-command-handlers.js +231 -27
- package/dist/server/ws-command-handlers.js.map +1 -1
- package/dist/server/ws-protocol.d.ts +419 -5
- package/dist/server/ws-protocol.d.ts.map +1 -1
- package/dist/server/ws-protocol.js +141 -4
- package/dist/server/ws-protocol.js.map +1 -1
- package/docs/protocol-matrix.json +93 -5
- package/node_modules/@viewportai/context-engine/src/repo/materializer.js +20 -5
- package/node_modules/@viewportai/context-engine/src/repo/membership.js +15 -0
- package/node_modules/@viewportai/context-engine/src/repo/sync.js +4 -4
- package/node_modules/@viewportai/context-engine/src/repo/vault.js +8 -3
- package/package.json +1 -1
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { getArgs, getFlag, hasFlag } from './args.js';
|
|
2
4
|
import { isJsonMode, printJson } from './command-shared.js';
|
|
3
|
-
import { initContextResource, isResolverPinMismatch, readContextStatus, resolveContextBundle, } from '../context/local-edge-store.js';
|
|
5
|
+
import { initContextResource, isResolverPinMismatch, joinContextResource, readContextStatus, resolveContextBundle, } from '../context/local-edge-store.js';
|
|
4
6
|
import { proposeContextEntry } from '../context/local-edge-candidates.js';
|
|
5
7
|
import { readCandidateDecisionApplications } from '../context/local-edge-decision-applications.js';
|
|
6
|
-
import { pullContextEvents, pushContextEvents } from '../context/local-edge-sync.js';
|
|
8
|
+
import { processPendingContextGrants, processPendingContextRevocations, pullContextEvents, pushContextEvents, } from '../context/local-edge-sync.js';
|
|
7
9
|
import { resolveContextKeyStore } from '../context/local-edge-key-store.js';
|
|
8
|
-
import { resolveContextSyncTarget } from './context-sync-target.js';
|
|
10
|
+
import { resolveContextSyncTarget, resolveWorkspaceSyncTarget } from './context-sync-target.js';
|
|
9
11
|
import { parseLimit, parseMaxItems, parseSince } from './context-command-parsers.js';
|
|
12
|
+
import { transportFetch } from './network.js';
|
|
10
13
|
import { contextAdd } from './context-add-command.js';
|
|
11
14
|
import { contextGet, contextProviderPropose, contextSearch } from './context-provider-command.js';
|
|
12
15
|
import { contextVaultCreate, contextVaultsList } from './context-vault-metadata-command.js';
|
|
13
16
|
import { contextVaultUse } from './context-vault-use-command.js';
|
|
14
17
|
import { contextCandidatePreview } from './context-candidate-preview-command.js';
|
|
15
18
|
import { contextRulesInstall } from './context-rules-command.js';
|
|
16
|
-
import {
|
|
19
|
+
import { acceptDeviceEpochEnrollment, approveDeviceEpochEnrollment, listDeviceEpochEnrollments, requestDeviceEpochEnrollment, } from '../security/epoch-enrollment.js';
|
|
20
|
+
import { ensureTeamCryptoEpoch, ensureUserCryptoEpoch, processPendingCryptoRotationRequests, rotateTeamCryptoEpoch, rotateUserCryptoEpoch, } from '../security/epoch-sync.js';
|
|
21
|
+
import { createUserEpochRecoveryBackup, generateUserEpochRecoveryKey, restoreUserEpochFromRecoveryBackup, } from '../security/epoch-recovery.js';
|
|
22
|
+
import { acceptTeamEpochMemberGrants, grantTeamEpochToUserEpoch, } from '../security/team-epoch-grants.js';
|
|
23
|
+
import { contextJoin, contextUserInit } from './context-access-command.js';
|
|
24
|
+
import { configDir } from '../core/config.js';
|
|
17
25
|
export async function context() {
|
|
18
26
|
const subcommand = getArgs()[1];
|
|
19
27
|
if (!subcommand) {
|
|
@@ -72,54 +80,90 @@ export async function context() {
|
|
|
72
80
|
await contextSyncPull();
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
|
-
if (subcommand === '
|
|
76
|
-
await
|
|
83
|
+
if (subcommand === 'sync-all') {
|
|
84
|
+
await contextSyncAll();
|
|
77
85
|
return;
|
|
78
86
|
}
|
|
79
|
-
if (subcommand === '
|
|
80
|
-
await
|
|
87
|
+
if (subcommand === 'dev-reset-crypto') {
|
|
88
|
+
await contextDevResetCrypto();
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
83
|
-
if (subcommand === '
|
|
84
|
-
await
|
|
91
|
+
if (subcommand === 'epoch-publish') {
|
|
92
|
+
await contextEpochPublish();
|
|
85
93
|
return;
|
|
86
94
|
}
|
|
87
|
-
if (subcommand === '
|
|
88
|
-
await
|
|
95
|
+
if (subcommand === 'epoch-rotate') {
|
|
96
|
+
await contextEpochRotate();
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
|
-
if (subcommand === '
|
|
92
|
-
await
|
|
99
|
+
if (subcommand === 'recovery-backup') {
|
|
100
|
+
await contextRecoveryBackup();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (subcommand === 'recovery-restore') {
|
|
104
|
+
await contextRecoveryRestore();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (subcommand === 'rotations-process') {
|
|
108
|
+
await contextRotationsProcess();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (subcommand === 'device-enroll-request') {
|
|
112
|
+
await contextDeviceEnrollRequest();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (subcommand === 'device-enroll-approve') {
|
|
116
|
+
await contextDeviceEnrollApprove();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (subcommand === 'device-enroll-accept') {
|
|
120
|
+
await contextDeviceEnrollAccept();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (subcommand === 'device-enrollments' || subcommand === 'device-enroll-status') {
|
|
124
|
+
await contextDeviceEnrollments();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (subcommand === 'team-grant-create') {
|
|
128
|
+
await contextTeamGrantCreate();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (subcommand === 'team-grants-accept') {
|
|
132
|
+
await contextTeamGrantsAccept();
|
|
93
133
|
return;
|
|
94
134
|
}
|
|
95
|
-
if (subcommand === '
|
|
96
|
-
await
|
|
135
|
+
if (subcommand === 'grants-process') {
|
|
136
|
+
await contextGrantsProcess();
|
|
97
137
|
return;
|
|
98
138
|
}
|
|
99
|
-
if (subcommand === '
|
|
100
|
-
await
|
|
139
|
+
if (subcommand === 'revokes-process') {
|
|
140
|
+
await contextRevokesProcess();
|
|
101
141
|
return;
|
|
102
142
|
}
|
|
103
|
-
if (subcommand === '
|
|
104
|
-
await
|
|
143
|
+
if (subcommand === 'decisions') {
|
|
144
|
+
await contextDecisions();
|
|
105
145
|
return;
|
|
106
146
|
}
|
|
107
|
-
if (subcommand === '
|
|
108
|
-
await
|
|
147
|
+
if (subcommand === 'candidate-preview') {
|
|
148
|
+
await contextCandidatePreview();
|
|
109
149
|
return;
|
|
110
150
|
}
|
|
111
|
-
if (subcommand === '
|
|
112
|
-
await
|
|
151
|
+
if (subcommand === 'rules' && getArgs()[2] === 'install') {
|
|
152
|
+
await contextRulesInstall();
|
|
113
153
|
return;
|
|
114
154
|
}
|
|
115
|
-
if (subcommand === '
|
|
116
|
-
await
|
|
155
|
+
if (subcommand === 'user-init') {
|
|
156
|
+
await contextUserInit();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (subcommand === 'join') {
|
|
160
|
+
await contextJoin();
|
|
117
161
|
return;
|
|
118
162
|
}
|
|
119
163
|
throw new Error(contextUsage());
|
|
120
164
|
}
|
|
121
165
|
function contextUsage() {
|
|
122
|
-
return 'Usage: vpd context <create|vaults|use|init|status|add|search|get|propose|resolve|sync-push|sync-pull|
|
|
166
|
+
return 'Usage: vpd context <create|vaults|use|init|status|add|search|get|propose|resolve|sync-push|sync-pull|sync-all|dev-reset-crypto --i-understand|epoch-publish [--team <team-id>]|epoch-rotate [--team <team-id>] [--reason <reason>]|recovery-backup [--recovery-key <key>]|recovery-restore --recovery-key <key>|rotations-process|device-enroll-request|device-enroll-approve|device-enroll-accept|device-enrollments|team-grant-create|team-grants-accept|grants-process|revokes-process|decisions|candidate-preview|rules install|user-init|join> ...';
|
|
123
167
|
}
|
|
124
168
|
function showContextHelp() {
|
|
125
169
|
console.log(contextUsage());
|
|
@@ -251,6 +295,528 @@ async function contextSyncPull() {
|
|
|
251
295
|
console.log(`Context events pulled: ${result.imported}/${result.pulled}`);
|
|
252
296
|
console.log(`Repo: ${result.repoId}`);
|
|
253
297
|
}
|
|
298
|
+
async function contextSyncAll() {
|
|
299
|
+
const target = await resolveWorkspaceSyncTarget('sync-all');
|
|
300
|
+
const home = getFlag('home');
|
|
301
|
+
const userName = requiredFlag('user', 'vpd context sync-all --user <name> --device <name>');
|
|
302
|
+
const deviceName = requiredFlag('device', 'vpd context sync-all --user <name> --device <name>');
|
|
303
|
+
const credentials = readCredentials({ required: false });
|
|
304
|
+
const keyStore = parseKeyStore(getFlag('key-store'));
|
|
305
|
+
const syncTarget = {
|
|
306
|
+
workspaceId: target.workspaceId,
|
|
307
|
+
serverUrl: target.serverUrl,
|
|
308
|
+
credential: target.credential,
|
|
309
|
+
tlsVerify: target.tlsVerify,
|
|
310
|
+
caCertPath: target.caCertPath,
|
|
311
|
+
tlsPins: target.tlsPins,
|
|
312
|
+
};
|
|
313
|
+
const rotations = await processPendingCryptoRotationRequests({
|
|
314
|
+
target: syncTarget,
|
|
315
|
+
home,
|
|
316
|
+
});
|
|
317
|
+
const acceptedTeamGrants = await acceptTeamEpochMemberGrants({
|
|
318
|
+
target: syncTarget,
|
|
319
|
+
home,
|
|
320
|
+
});
|
|
321
|
+
const vaults = await fetchVisibleContextVaults(target);
|
|
322
|
+
const results = [];
|
|
323
|
+
for (const vault of vaults) {
|
|
324
|
+
const contextResourceId = vault.vault_id;
|
|
325
|
+
if (!contextResourceId || vault.access?.can_view === false)
|
|
326
|
+
continue;
|
|
327
|
+
const status = await readContextStatus({ contextResourceId, home });
|
|
328
|
+
if (status.contexts.length === 0) {
|
|
329
|
+
await joinContextResource({
|
|
330
|
+
contextResourceId,
|
|
331
|
+
userName,
|
|
332
|
+
deviceName,
|
|
333
|
+
credentials,
|
|
334
|
+
keyStore,
|
|
335
|
+
home,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const pulled = await pullContextEvents({
|
|
339
|
+
contextResourceId,
|
|
340
|
+
workspaceId: target.workspaceId,
|
|
341
|
+
serverUrl: target.serverUrl,
|
|
342
|
+
credential: target.credential,
|
|
343
|
+
tlsVerify: target.tlsVerify,
|
|
344
|
+
caCertPath: target.caCertPath,
|
|
345
|
+
tlsPins: target.tlsPins,
|
|
346
|
+
actorName: deviceName,
|
|
347
|
+
credentials,
|
|
348
|
+
limit: parseLimit(getFlag('limit')),
|
|
349
|
+
home,
|
|
350
|
+
});
|
|
351
|
+
const revoked = await processPendingContextRevocations({
|
|
352
|
+
contextResourceId,
|
|
353
|
+
workspaceId: target.workspaceId,
|
|
354
|
+
serverUrl: target.serverUrl,
|
|
355
|
+
credential: target.credential,
|
|
356
|
+
tlsVerify: target.tlsVerify,
|
|
357
|
+
caCertPath: target.caCertPath,
|
|
358
|
+
tlsPins: target.tlsPins,
|
|
359
|
+
actorName: deviceName,
|
|
360
|
+
credentials,
|
|
361
|
+
home,
|
|
362
|
+
});
|
|
363
|
+
const granted = await processPendingContextGrants({
|
|
364
|
+
contextResourceId,
|
|
365
|
+
workspaceId: target.workspaceId,
|
|
366
|
+
serverUrl: target.serverUrl,
|
|
367
|
+
credential: target.credential,
|
|
368
|
+
tlsVerify: target.tlsVerify,
|
|
369
|
+
caCertPath: target.caCertPath,
|
|
370
|
+
tlsPins: target.tlsPins,
|
|
371
|
+
actorName: deviceName,
|
|
372
|
+
credentials,
|
|
373
|
+
home,
|
|
374
|
+
});
|
|
375
|
+
results.push({ contextResourceId, ...pulled, revoked, granted });
|
|
376
|
+
}
|
|
377
|
+
const summary = {
|
|
378
|
+
vaults: results.length,
|
|
379
|
+
pulled: results.reduce((total, item) => total + item.pulled, 0),
|
|
380
|
+
imported: results.reduce((total, item) => total + item.imported, 0),
|
|
381
|
+
materializedGrants: results.reduce((total, item) => total + item.materializedGrants, 0),
|
|
382
|
+
revocationsProcessed: results.reduce((total, item) => total + item.revoked.revoked, 0),
|
|
383
|
+
grantsEmitted: results.reduce((total, item) => total + item.granted.emitted, 0),
|
|
384
|
+
rotationsProcessed: rotations.processed,
|
|
385
|
+
teamEpochGrantsAccepted: acceptedTeamGrants.accepted,
|
|
386
|
+
};
|
|
387
|
+
if (isJsonMode()) {
|
|
388
|
+
printJson({
|
|
389
|
+
command: 'context sync-all',
|
|
390
|
+
ok: true,
|
|
391
|
+
workspaceId: target.workspaceId,
|
|
392
|
+
...summary,
|
|
393
|
+
rotations,
|
|
394
|
+
acceptedTeamGrants,
|
|
395
|
+
results,
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
console.log(`Context vaults synced: ${summary.vaults}`);
|
|
400
|
+
console.log(`Context events pulled: ${summary.imported}/${summary.pulled}`);
|
|
401
|
+
if (summary.materializedGrants > 0) {
|
|
402
|
+
console.log(`Context grants materialized: ${summary.materializedGrants}`);
|
|
403
|
+
}
|
|
404
|
+
if (summary.rotationsProcessed > 0) {
|
|
405
|
+
console.log(`Crypto rotations processed: ${summary.rotationsProcessed}`);
|
|
406
|
+
}
|
|
407
|
+
if (summary.teamEpochGrantsAccepted > 0) {
|
|
408
|
+
console.log(`Team epoch grants accepted: ${summary.teamEpochGrantsAccepted}`);
|
|
409
|
+
}
|
|
410
|
+
if (summary.revocationsProcessed > 0) {
|
|
411
|
+
console.log(`Context revocations processed: ${summary.revocationsProcessed}`);
|
|
412
|
+
}
|
|
413
|
+
if (summary.grantsEmitted > 0) {
|
|
414
|
+
console.log(`Context grants emitted: ${summary.grantsEmitted}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async function contextDevResetCrypto() {
|
|
418
|
+
if (!hasFlag('i-understand')) {
|
|
419
|
+
throw new Error('vpd context dev-reset-crypto removes local encrypted context, epoch, and plan key material. Re-run with --i-understand to continue.');
|
|
420
|
+
}
|
|
421
|
+
const home = getFlag('home') ?? configDir();
|
|
422
|
+
const targets = [
|
|
423
|
+
path.join(home, 'crypto', 'epochs.json'),
|
|
424
|
+
path.join(home, 'context', 'canonical-resources'),
|
|
425
|
+
path.join(home, 'context', 'candidate-decision-applications'),
|
|
426
|
+
path.join(home, 'repos'),
|
|
427
|
+
path.join(home, 'identities'),
|
|
428
|
+
path.join(home, 'plans', 'trusted-edge-keys.json'),
|
|
429
|
+
];
|
|
430
|
+
const removed = [];
|
|
431
|
+
for (const target of targets) {
|
|
432
|
+
try {
|
|
433
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
434
|
+
removed.push(path.relative(home, target) || target);
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
throw new Error(`Failed to remove local crypto state at ${target}: ${error.message}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (isJsonMode()) {
|
|
441
|
+
printJson({ command: 'context dev-reset-crypto', ok: true, home, removed });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
console.log(`Local encrypted collaboration state reset under ${home}`);
|
|
445
|
+
for (const item of removed) {
|
|
446
|
+
console.log(`Removed: ${item}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function fetchVisibleContextVaults(target) {
|
|
450
|
+
const query = new URLSearchParams({ credential: target.credential });
|
|
451
|
+
const response = await transportFetch(`${target.serverUrl.replace(/\/+$/, '')}/api/runtime/workspaces/${encodeURIComponent(target.workspaceId)}/context-vaults?${query.toString()}`, {
|
|
452
|
+
method: 'GET',
|
|
453
|
+
headers: { accept: 'application/json' },
|
|
454
|
+
timeoutMs: 5_000,
|
|
455
|
+
tlsVerify: target.tlsVerify,
|
|
456
|
+
caCertPath: target.caCertPath,
|
|
457
|
+
tlsPins: target.tlsPins,
|
|
458
|
+
});
|
|
459
|
+
const payload = (await response.json());
|
|
460
|
+
if (!response.ok) {
|
|
461
|
+
throw new Error(`Failed to list context vaults for sync-all: HTTP ${response.status}`);
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(payload.data))
|
|
464
|
+
return [];
|
|
465
|
+
return payload.data
|
|
466
|
+
.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item))
|
|
467
|
+
.map((item) => ({
|
|
468
|
+
vault_id: String(item.vault_id ?? ''),
|
|
469
|
+
access: item.access && typeof item.access === 'object' && !Array.isArray(item.access)
|
|
470
|
+
? item.access
|
|
471
|
+
: null,
|
|
472
|
+
}))
|
|
473
|
+
.filter((item) => item.vault_id.length > 0);
|
|
474
|
+
}
|
|
475
|
+
async function contextEpochPublish() {
|
|
476
|
+
const target = await resolveWorkspaceSyncTarget('epoch-publish');
|
|
477
|
+
const syncTarget = {
|
|
478
|
+
workspaceId: target.workspaceId,
|
|
479
|
+
serverUrl: target.serverUrl,
|
|
480
|
+
credential: target.credential,
|
|
481
|
+
tlsVerify: target.tlsVerify,
|
|
482
|
+
caCertPath: target.caCertPath,
|
|
483
|
+
tlsPins: target.tlsPins,
|
|
484
|
+
};
|
|
485
|
+
const teamId = getFlag('team');
|
|
486
|
+
const epoch = teamId
|
|
487
|
+
? await ensureTeamCryptoEpoch({
|
|
488
|
+
target: syncTarget,
|
|
489
|
+
teamId,
|
|
490
|
+
home: getFlag('home'),
|
|
491
|
+
})
|
|
492
|
+
: await ensureUserCryptoEpoch({
|
|
493
|
+
target: syncTarget,
|
|
494
|
+
home: getFlag('home'),
|
|
495
|
+
});
|
|
496
|
+
if (isJsonMode()) {
|
|
497
|
+
printJson({
|
|
498
|
+
command: 'context epoch-publish',
|
|
499
|
+
ok: true,
|
|
500
|
+
scope: teamId ? 'team' : 'user',
|
|
501
|
+
epoch,
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch ready: ${epoch.fingerprint}`);
|
|
506
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
507
|
+
}
|
|
508
|
+
async function contextEpochRotate() {
|
|
509
|
+
const target = await resolveWorkspaceSyncTarget('epoch-rotate');
|
|
510
|
+
const syncTarget = {
|
|
511
|
+
workspaceId: target.workspaceId,
|
|
512
|
+
serverUrl: target.serverUrl,
|
|
513
|
+
credential: target.credential,
|
|
514
|
+
tlsVerify: target.tlsVerify,
|
|
515
|
+
caCertPath: target.caCertPath,
|
|
516
|
+
tlsPins: target.tlsPins,
|
|
517
|
+
};
|
|
518
|
+
const reason = epochRotationReason(getFlag('reason') ?? 'manual_rotation');
|
|
519
|
+
const teamId = getFlag('team');
|
|
520
|
+
const epoch = teamId
|
|
521
|
+
? await rotateTeamCryptoEpoch({
|
|
522
|
+
target: syncTarget,
|
|
523
|
+
teamId,
|
|
524
|
+
reason,
|
|
525
|
+
home: getFlag('home'),
|
|
526
|
+
})
|
|
527
|
+
: await rotateUserCryptoEpoch({
|
|
528
|
+
target: syncTarget,
|
|
529
|
+
reason,
|
|
530
|
+
home: getFlag('home'),
|
|
531
|
+
});
|
|
532
|
+
if (isJsonMode()) {
|
|
533
|
+
printJson({
|
|
534
|
+
command: 'context epoch-rotate',
|
|
535
|
+
ok: true,
|
|
536
|
+
scope: teamId ? 'team' : 'user',
|
|
537
|
+
reason,
|
|
538
|
+
epoch,
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch rotated: ${epoch.fingerprint}`);
|
|
543
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
544
|
+
console.log(`Reason: ${reason}`);
|
|
545
|
+
}
|
|
546
|
+
async function contextRecoveryBackup() {
|
|
547
|
+
const target = await resolveWorkspaceSyncTarget('recovery-backup');
|
|
548
|
+
const generatedRecoveryKey = getFlag('recovery-key') ? null : generateUserEpochRecoveryKey();
|
|
549
|
+
const recoveryKey = getFlag('recovery-key') ?? generatedRecoveryKey;
|
|
550
|
+
if (!recoveryKey) {
|
|
551
|
+
throw new Error('vpd context recovery-backup requires --recovery-key <key>');
|
|
552
|
+
}
|
|
553
|
+
const backup = await createUserEpochRecoveryBackup({
|
|
554
|
+
target: {
|
|
555
|
+
workspaceId: target.workspaceId,
|
|
556
|
+
serverUrl: target.serverUrl,
|
|
557
|
+
credential: target.credential,
|
|
558
|
+
tlsVerify: target.tlsVerify,
|
|
559
|
+
caCertPath: target.caCertPath,
|
|
560
|
+
tlsPins: target.tlsPins,
|
|
561
|
+
},
|
|
562
|
+
recoveryKey,
|
|
563
|
+
home: getFlag('home'),
|
|
564
|
+
});
|
|
565
|
+
if (isJsonMode()) {
|
|
566
|
+
printJson({
|
|
567
|
+
command: 'context recovery-backup',
|
|
568
|
+
ok: true,
|
|
569
|
+
backup,
|
|
570
|
+
generatedRecoveryKey,
|
|
571
|
+
});
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
console.log(`Recovery backup stored: ${backup.id}`);
|
|
575
|
+
console.log(`User epoch: ${backup.user_crypto_epoch_id}`);
|
|
576
|
+
if (generatedRecoveryKey) {
|
|
577
|
+
console.log('Recovery key generated. Store it somewhere private; Viewport cannot recover it.');
|
|
578
|
+
console.log(generatedRecoveryKey);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function contextRecoveryRestore() {
|
|
582
|
+
const target = await resolveWorkspaceSyncTarget('recovery-restore');
|
|
583
|
+
const recoveryKey = requiredFlag('recovery-key', 'vpd context recovery-restore --recovery-key <key>');
|
|
584
|
+
const result = await restoreUserEpochFromRecoveryBackup({
|
|
585
|
+
target: {
|
|
586
|
+
workspaceId: target.workspaceId,
|
|
587
|
+
serverUrl: target.serverUrl,
|
|
588
|
+
credential: target.credential,
|
|
589
|
+
tlsVerify: target.tlsVerify,
|
|
590
|
+
caCertPath: target.caCertPath,
|
|
591
|
+
tlsPins: target.tlsPins,
|
|
592
|
+
},
|
|
593
|
+
recoveryKey,
|
|
594
|
+
home: getFlag('home'),
|
|
595
|
+
});
|
|
596
|
+
if (isJsonMode()) {
|
|
597
|
+
printJson({ command: 'context recovery-restore', ok: true, ...result });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
console.log(`Recovery backup restored: ${result.backup.id}`);
|
|
601
|
+
console.log(`Recovered epoch: ${result.restoredEpoch.fingerprint}`);
|
|
602
|
+
console.log(`Rotated epoch: ${result.rotatedEpoch.fingerprint}`);
|
|
603
|
+
console.log(`Fresh recovery backup stored: ${result.rotatedBackup.id}`);
|
|
604
|
+
}
|
|
605
|
+
async function contextRotationsProcess() {
|
|
606
|
+
const target = await resolveWorkspaceSyncTarget('rotations-process');
|
|
607
|
+
const result = await processPendingCryptoRotationRequests({
|
|
608
|
+
target: {
|
|
609
|
+
workspaceId: target.workspaceId,
|
|
610
|
+
serverUrl: target.serverUrl,
|
|
611
|
+
credential: target.credential,
|
|
612
|
+
tlsVerify: target.tlsVerify,
|
|
613
|
+
caCertPath: target.caCertPath,
|
|
614
|
+
tlsPins: target.tlsPins,
|
|
615
|
+
},
|
|
616
|
+
home: getFlag('home'),
|
|
617
|
+
});
|
|
618
|
+
if (isJsonMode()) {
|
|
619
|
+
printJson({ command: 'context rotations-process', ok: true, ...result });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
console.log(`Crypto rotation requests processed: ${result.processed}`);
|
|
623
|
+
if (result.userRotations > 0) {
|
|
624
|
+
console.log(`User epoch rotations: ${result.userRotations}`);
|
|
625
|
+
}
|
|
626
|
+
if (result.teamRotations > 0) {
|
|
627
|
+
console.log(`Team epoch rotations: ${result.teamRotations}`);
|
|
628
|
+
}
|
|
629
|
+
if (result.teamMemberGrants > 0) {
|
|
630
|
+
console.log(`Team epoch member grants created: ${result.teamMemberGrants}`);
|
|
631
|
+
}
|
|
632
|
+
if (result.skipped > 0) {
|
|
633
|
+
console.log(`Skipped rotation requests: ${result.skipped}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function epochRotationReason(value) {
|
|
637
|
+
if (value === 'device_revoked' ||
|
|
638
|
+
value === 'member_added' ||
|
|
639
|
+
value === 'member_revoked' ||
|
|
640
|
+
value === 'manual_rotation' ||
|
|
641
|
+
value === 'recovery') {
|
|
642
|
+
return value;
|
|
643
|
+
}
|
|
644
|
+
throw new Error('Epoch rotation reason must be device_revoked, member_added, member_revoked, manual_rotation, or recovery.');
|
|
645
|
+
}
|
|
646
|
+
async function contextDeviceEnrollRequest() {
|
|
647
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-request');
|
|
648
|
+
const deviceId = requiredFlag('device', 'vpd context device-enroll-request --device <id>');
|
|
649
|
+
const enrollment = await requestDeviceEpochEnrollment({
|
|
650
|
+
target: {
|
|
651
|
+
workspaceId: target.workspaceId,
|
|
652
|
+
serverUrl: target.serverUrl,
|
|
653
|
+
credential: target.credential,
|
|
654
|
+
tlsVerify: target.tlsVerify,
|
|
655
|
+
caCertPath: target.caCertPath,
|
|
656
|
+
tlsPins: target.tlsPins,
|
|
657
|
+
},
|
|
658
|
+
deviceId,
|
|
659
|
+
deviceLabel: getFlag('label') ?? deviceId,
|
|
660
|
+
home: getFlag('home'),
|
|
661
|
+
});
|
|
662
|
+
if (isJsonMode()) {
|
|
663
|
+
printJson({ command: 'context device-enroll-request', ok: true, enrollment });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(`Device enrollment requested: ${enrollment.enrollmentId}`);
|
|
667
|
+
console.log(`Fingerprint: ${enrollment.fingerprint}`);
|
|
668
|
+
}
|
|
669
|
+
async function contextDeviceEnrollApprove() {
|
|
670
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-approve');
|
|
671
|
+
const enrollment = await approveDeviceEpochEnrollment({
|
|
672
|
+
target: {
|
|
673
|
+
workspaceId: target.workspaceId,
|
|
674
|
+
serverUrl: target.serverUrl,
|
|
675
|
+
credential: target.credential,
|
|
676
|
+
tlsVerify: target.tlsVerify,
|
|
677
|
+
caCertPath: target.caCertPath,
|
|
678
|
+
tlsPins: target.tlsPins,
|
|
679
|
+
},
|
|
680
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-approve --enrollment <id>'),
|
|
681
|
+
home: getFlag('home'),
|
|
682
|
+
});
|
|
683
|
+
if (isJsonMode()) {
|
|
684
|
+
printJson({ command: 'context device-enroll-approve', ok: true, enrollment });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
console.log(`Device enrollment approved: ${enrollment.id}`);
|
|
688
|
+
console.log(`Status: ${enrollment.status}`);
|
|
689
|
+
}
|
|
690
|
+
async function contextDeviceEnrollAccept() {
|
|
691
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-accept');
|
|
692
|
+
const epoch = await acceptDeviceEpochEnrollment({
|
|
693
|
+
target: {
|
|
694
|
+
workspaceId: target.workspaceId,
|
|
695
|
+
serverUrl: target.serverUrl,
|
|
696
|
+
credential: target.credential,
|
|
697
|
+
tlsVerify: target.tlsVerify,
|
|
698
|
+
caCertPath: target.caCertPath,
|
|
699
|
+
tlsPins: target.tlsPins,
|
|
700
|
+
},
|
|
701
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-accept --enrollment <id>'),
|
|
702
|
+
home: getFlag('home'),
|
|
703
|
+
});
|
|
704
|
+
if (isJsonMode()) {
|
|
705
|
+
printJson({ command: 'context device-enroll-accept', ok: true, epoch });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log(`Device enrollment accepted. User crypto epoch ready: ${epoch.fingerprint}`);
|
|
709
|
+
}
|
|
710
|
+
async function contextDeviceEnrollments() {
|
|
711
|
+
const target = await resolveWorkspaceSyncTarget('device-enrollments');
|
|
712
|
+
const enrollments = await listDeviceEpochEnrollments({
|
|
713
|
+
target: {
|
|
714
|
+
workspaceId: target.workspaceId,
|
|
715
|
+
serverUrl: target.serverUrl,
|
|
716
|
+
credential: target.credential,
|
|
717
|
+
tlsVerify: target.tlsVerify,
|
|
718
|
+
caCertPath: target.caCertPath,
|
|
719
|
+
tlsPins: target.tlsPins,
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
if (isJsonMode()) {
|
|
723
|
+
printJson({ command: 'context device-enrollments', ok: true, enrollments });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (enrollments.length === 0) {
|
|
727
|
+
console.log('No device enrollments found.');
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
for (const enrollment of enrollments) {
|
|
731
|
+
console.log(`${enrollment.id} ${enrollment.status} ${enrollment.device_label} ${enrollment.fingerprint}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async function contextTeamGrantCreate() {
|
|
735
|
+
const target = await resolveWorkspaceSyncTarget('team-grant-create');
|
|
736
|
+
const grant = await grantTeamEpochToUserEpoch({
|
|
737
|
+
target: {
|
|
738
|
+
workspaceId: target.workspaceId,
|
|
739
|
+
serverUrl: target.serverUrl,
|
|
740
|
+
credential: target.credential,
|
|
741
|
+
tlsVerify: target.tlsVerify,
|
|
742
|
+
caCertPath: target.caCertPath,
|
|
743
|
+
tlsPins: target.tlsPins,
|
|
744
|
+
},
|
|
745
|
+
teamCryptoEpochId: requiredFlag('team-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
746
|
+
recipientUserCryptoEpochId: requiredFlag('recipient-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
747
|
+
home: getFlag('home'),
|
|
748
|
+
});
|
|
749
|
+
if (isJsonMode()) {
|
|
750
|
+
printJson({ command: 'context team-grant-create', ok: true, grant });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
console.log(`Team epoch member grant created: ${grant.id}`);
|
|
754
|
+
}
|
|
755
|
+
async function contextTeamGrantsAccept() {
|
|
756
|
+
const target = await resolveWorkspaceSyncTarget('team-grants-accept');
|
|
757
|
+
const result = await acceptTeamEpochMemberGrants({
|
|
758
|
+
target: {
|
|
759
|
+
workspaceId: target.workspaceId,
|
|
760
|
+
serverUrl: target.serverUrl,
|
|
761
|
+
credential: target.credential,
|
|
762
|
+
tlsVerify: target.tlsVerify,
|
|
763
|
+
caCertPath: target.caCertPath,
|
|
764
|
+
tlsPins: target.tlsPins,
|
|
765
|
+
},
|
|
766
|
+
home: getFlag('home'),
|
|
767
|
+
});
|
|
768
|
+
if (isJsonMode()) {
|
|
769
|
+
printJson({ command: 'context team-grants-accept', ok: true, ...result });
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
console.log(`Team epoch grants accepted: ${result.accepted}`);
|
|
773
|
+
}
|
|
774
|
+
async function contextGrantsProcess() {
|
|
775
|
+
const target = await resolveContextSyncTarget('grants-process');
|
|
776
|
+
const result = await processPendingContextGrants({
|
|
777
|
+
contextResourceId: target.contextResourceId,
|
|
778
|
+
workspaceId: target.workspaceId,
|
|
779
|
+
serverUrl: target.serverUrl,
|
|
780
|
+
credential: target.credential,
|
|
781
|
+
tlsVerify: target.tlsVerify,
|
|
782
|
+
caCertPath: target.caCertPath,
|
|
783
|
+
tlsPins: target.tlsPins,
|
|
784
|
+
actorName: getFlag('actor') ?? getFlag('device') ?? 'local-device',
|
|
785
|
+
credentials: readCredentials(),
|
|
786
|
+
home: getFlag('home'),
|
|
787
|
+
});
|
|
788
|
+
if (isJsonMode()) {
|
|
789
|
+
printJson({ command: 'context grants-process', ok: true, ...result });
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
console.log(`Context grants emitted: ${result.emitted}`);
|
|
793
|
+
if (result.missingIdentity > 0) {
|
|
794
|
+
console.log(`Missing recipient identities: ${result.missingIdentity}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async function contextRevokesProcess() {
|
|
798
|
+
const target = await resolveContextSyncTarget('revokes-process');
|
|
799
|
+
const result = await processPendingContextRevocations({
|
|
800
|
+
contextResourceId: target.contextResourceId,
|
|
801
|
+
workspaceId: target.workspaceId,
|
|
802
|
+
serverUrl: target.serverUrl,
|
|
803
|
+
credential: target.credential,
|
|
804
|
+
tlsVerify: target.tlsVerify,
|
|
805
|
+
caCertPath: target.caCertPath,
|
|
806
|
+
tlsPins: target.tlsPins,
|
|
807
|
+
actorName: getFlag('actor') ?? getFlag('device') ?? 'local-device',
|
|
808
|
+
credentials: readCredentials(),
|
|
809
|
+
home: getFlag('home'),
|
|
810
|
+
});
|
|
811
|
+
if (isJsonMode()) {
|
|
812
|
+
printJson({ command: 'context revokes-process', ok: true, ...result });
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
console.log(`Context revocations processed: ${result.revoked}`);
|
|
816
|
+
if (result.missingIdentity > 0) {
|
|
817
|
+
console.log(`Missing recipient identities: ${result.missingIdentity}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
254
820
|
async function contextDecisions() {
|
|
255
821
|
const since = parseSince(getFlag('since'));
|
|
256
822
|
const applications = await readCandidateDecisionApplications({
|