@viewportai/daemon 0.5.3 → 0.6.1
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 +575 -38
- package/dist/cli/context-command.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 +2 -8
- package/dist/cli/lifecycle-commands.js.map +1 -1
- package/dist/cli/skills-command.js +3 -3
- 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-auto-sync.d.ts +17 -0
- package/dist/context/local-edge-auto-sync.d.ts.map +1 -0
- package/dist/context/local-edge-auto-sync.js +94 -0
- package/dist/context/local-edge-auto-sync.js.map +1 -0
- package/dist/context/local-edge-store.d.ts +11 -0
- package/dist/context/local-edge-store.d.ts.map +1 -1
- package/dist/context/local-edge-store.js +25 -0
- package/dist/context/local-edge-store.js.map +1 -1
- package/dist/context/local-edge-sync.d.ts +2 -15
- package/dist/context/local-edge-sync.d.ts.map +1 -1
- package/dist/context/local-edge-sync.js +306 -86
- package/dist/context/local-edge-sync.js.map +1 -1
- package/dist/context/local-edge-types.d.ts +12 -0
- package/dist/context/local-edge-types.d.ts.map +1 -1
- package/dist/context-providers/viewport-vault-provider.d.ts.map +1 -1
- package/dist/context-providers/viewport-vault-provider.js +11 -0
- package/dist/context-providers/viewport-vault-provider.js.map +1 -1
- package/dist/core/session-context-prompt.d.ts.map +1 -1
- package/dist/core/session-context-prompt.js +8 -0
- package/dist/core/session-context-prompt.js.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts +30 -27
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.js +71 -89
- package/dist/hooks/trusted-edge-plan-artifacts.js.map +1 -1
- 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-daemon-key-registration.d.ts.map +1 -1
- package/dist/relay/bridge-daemon-key-registration.js +27 -7
- package/dist/relay/bridge-daemon-key-registration.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-grant-payloads.d.ts +44 -0
- package/dist/security/team-epoch-grant-payloads.d.ts.map +1 -0
- package/dist/security/team-epoch-grant-payloads.js +100 -0
- package/dist/security/team-epoch-grant-payloads.js.map +1 -0
- package/dist/security/team-epoch-grants.d.ts +31 -0
- package/dist/security/team-epoch-grants.d.ts.map +1 -0
- package/dist/security/team-epoch-grants.js +194 -0
- package/dist/security/team-epoch-grants.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 +57 -15
- 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 +2 -1
- package/dist/server/rate-limiter.js.map +1 -1
- package/dist/server/trusted-edge-command-capability.d.ts +2 -1
- package/dist/server/trusted-edge-command-capability.d.ts.map +1 -1
- package/dist/server/trusted-edge-command-capability.js +15 -0
- package/dist/server/trusted-edge-command-capability.js.map +1 -1
- package/dist/server/ws-command-handlers.d.ts.map +1 -1
- package/dist/server/ws-command-handlers.js +200 -28
- package/dist/server/ws-command-handlers.js.map +1 -1
- package/dist/server/ws-protocol.d.ts +281 -44
- package/dist/server/ws-protocol.d.ts.map +1 -1
- package/dist/server/ws-protocol.js +89 -19
- package/dist/server/ws-protocol.js.map +1 -1
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +0 -17
- package/dist/startup.js.map +1 -1
- package/docs/README.md +18 -0
- package/docs/configuration.md +3 -3
- package/docs/protocol-matrix.json +53 -8
- package/docs/security.md +11 -8
- package/node_modules/@viewportai/context-engine/src/repo/identities.js +7 -3
- 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,4 +1,5 @@
|
|
|
1
1
|
import { transportFetch, type TlsVerifyMode } from '../cli/network.js';
|
|
2
|
+
import { type SignedContextGrantMaterialization } from '../security/epoch-protocol.js';
|
|
2
3
|
import type { ContextCredentials } from './local-edge-types.js';
|
|
3
4
|
export declare function pushContextEvents(options: {
|
|
4
5
|
contextResourceId: string;
|
|
@@ -42,7 +43,7 @@ export declare function recordContextGrantMaterialization(options: {
|
|
|
42
43
|
serverUrl: string;
|
|
43
44
|
credential: string;
|
|
44
45
|
contextResourceId: string;
|
|
45
|
-
|
|
46
|
+
receipts: SignedContextGrantMaterialization[];
|
|
46
47
|
tlsVerify?: TlsVerifyMode;
|
|
47
48
|
caCertPath?: string;
|
|
48
49
|
tlsPins?: string[];
|
|
@@ -64,20 +65,6 @@ export declare function recordContextCandidatePreviewProof(options: {
|
|
|
64
65
|
previewProofId: string;
|
|
65
66
|
expiresAt: string | null;
|
|
66
67
|
}>;
|
|
67
|
-
export declare function publishContextPublicIdentity(options: {
|
|
68
|
-
workspaceId: string;
|
|
69
|
-
serverUrl: string;
|
|
70
|
-
credential: string;
|
|
71
|
-
identityName: string;
|
|
72
|
-
tlsVerify?: TlsVerifyMode;
|
|
73
|
-
caCertPath?: string;
|
|
74
|
-
tlsPins?: string[];
|
|
75
|
-
home?: string;
|
|
76
|
-
fetchImpl?: typeof transportFetch;
|
|
77
|
-
}): Promise<{
|
|
78
|
-
identityId: string;
|
|
79
|
-
fingerprint: string | null;
|
|
80
|
-
}>;
|
|
81
68
|
export declare function processPendingContextGrants(options: {
|
|
82
69
|
contextResourceId: string;
|
|
83
70
|
workspaceId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-edge-sync.d.ts","sourceRoot":"","sources":["../../src/context/local-edge-sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-edge-sync.d.ts","sourceRoot":"","sources":["../../src/context/local-edge-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { configDir } from '../core/config.js';
|
|
2
3
|
import { transportFetch } from '../cli/network.js';
|
|
3
4
|
import { assertCredentialsOrApprovedDevice, createVault, ensureUserOrApprovedDevice, } from './local-edge-engine.js';
|
|
@@ -5,13 +6,23 @@ import { applyContextCandidateDecision } from './local-edge-candidates.js';
|
|
|
5
6
|
import { readCandidateDecisionApplications } from './local-edge-decision-applications.js';
|
|
6
7
|
import { verifyContextCandidateDecision } from './local-edge-decision-signature.js';
|
|
7
8
|
import { readContextMetadata, touchContextMetadata } from './local-edge-metadata.js';
|
|
8
|
-
import {
|
|
9
|
+
import { grantContextHpkeRecipient, revokeContextUser } from './local-edge-store.js';
|
|
10
|
+
import { validateAndPinPublicEpoch } from '../security/epoch-public-pins.js';
|
|
11
|
+
import { getActiveLocalUserEpoch, listActiveLocalTeamEpochs } from '../security/epoch-store.js';
|
|
12
|
+
import { TRUSTED_EDGE_CRYPTO_PROTOCOL_HEADER, TRUSTED_EDGE_CRYPTO_PROTOCOL_VERSION, contextGrantMaterializationPayload, signContextGrantMaterialization, } from '../security/epoch-protocol.js';
|
|
9
13
|
const CONTEXT_GRANT_EVENT_TYPES = new Set(['member.granted', 'key.rotated']);
|
|
10
14
|
export async function pushContextEvents(options) {
|
|
11
15
|
const home = options.home ?? configDir();
|
|
12
16
|
const metadata = await readContextMetadata(options.contextResourceId, home);
|
|
13
17
|
const vault = createVault(home, metadata.keyStore);
|
|
14
18
|
const events = vault.listSyncEvents({ repoId: metadata.repoId });
|
|
19
|
+
const publicIdentities = collectPublicIdentities(vault, [
|
|
20
|
+
metadata.userName,
|
|
21
|
+
metadata.deviceName,
|
|
22
|
+
...events
|
|
23
|
+
.map((event) => nullableStringField(objectValue(event), 'actorName'))
|
|
24
|
+
.filter((name) => !!name),
|
|
25
|
+
]);
|
|
15
26
|
const candidateDecisionApplications = await readCandidateDecisionApplications({
|
|
16
27
|
home,
|
|
17
28
|
contextResourceId: options.contextResourceId,
|
|
@@ -23,6 +34,7 @@ export async function pushContextEvents(options) {
|
|
|
23
34
|
credential: options.credential,
|
|
24
35
|
...(options.workspaceId ? { target_workspace_id: options.workspaceId } : {}),
|
|
25
36
|
events,
|
|
37
|
+
...(publicIdentities.length > 0 ? { public_identities: publicIdentities } : {}),
|
|
26
38
|
...(candidateDecisionApplications.length > 0
|
|
27
39
|
? { candidate_decision_applications: candidateDecisionApplications }
|
|
28
40
|
: {}),
|
|
@@ -65,22 +77,31 @@ export async function pullContextEvents(options) {
|
|
|
65
77
|
tlsPins: options.tlsPins,
|
|
66
78
|
});
|
|
67
79
|
const records = extractPulledRecords(response);
|
|
80
|
+
importPulledPublicIdentities(vault, response);
|
|
68
81
|
const events = records.map((record) => record.signedEvent);
|
|
82
|
+
const grantIdentities = await contextGrantIdentitiesForWorkspace({
|
|
83
|
+
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
84
|
+
home,
|
|
85
|
+
});
|
|
69
86
|
const imported = await vault.importSyncEvents({
|
|
70
87
|
repoId: metadata.repoId,
|
|
71
88
|
events,
|
|
72
89
|
actorName: options.actorName,
|
|
90
|
+
grantIdentities,
|
|
91
|
+
});
|
|
92
|
+
const materializationReceipts = contextGrantMaterializationReceipts({
|
|
93
|
+
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
94
|
+
contextResourceId: options.contextResourceId,
|
|
95
|
+
events,
|
|
96
|
+
grantIdentities,
|
|
73
97
|
});
|
|
74
|
-
const
|
|
75
|
-
.filter((event) => isGrantEventForUser(event, metadata.userName))
|
|
76
|
-
.map((event) => event.id);
|
|
77
|
-
const materializedGrants = materializedGrantEventIds.length > 0
|
|
98
|
+
const materializedGrants = materializationReceipts.length > 0
|
|
78
99
|
? await recordContextGrantMaterialization({
|
|
79
100
|
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
80
101
|
serverUrl: options.serverUrl,
|
|
81
102
|
credential: options.credential,
|
|
82
103
|
contextResourceId: options.contextResourceId,
|
|
83
|
-
|
|
104
|
+
receipts: materializationReceipts,
|
|
84
105
|
tlsVerify: options.tlsVerify,
|
|
85
106
|
caCertPath: options.caCertPath,
|
|
86
107
|
tlsPins: options.tlsPins,
|
|
@@ -115,14 +136,67 @@ export async function pullContextEvents(options) {
|
|
|
115
136
|
repoId: metadata.repoId,
|
|
116
137
|
};
|
|
117
138
|
}
|
|
139
|
+
function collectPublicIdentities(vault, names) {
|
|
140
|
+
const identities = new Map();
|
|
141
|
+
for (const name of names) {
|
|
142
|
+
if (!name)
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
const publicIdentity = objectValue(vault.exportPublicIdentity(name));
|
|
146
|
+
const identityName = stringField(publicIdentity, 'name');
|
|
147
|
+
identities.set(identityName, {
|
|
148
|
+
name: identityName,
|
|
149
|
+
fingerprint: publicIdentityFingerprint(publicIdentity),
|
|
150
|
+
public_identity: publicIdentity,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (!isUnknownIdentityError(error)) {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return [...identities.values()];
|
|
160
|
+
}
|
|
161
|
+
function importPulledPublicIdentities(vault, response) {
|
|
162
|
+
const object = objectValue(response);
|
|
163
|
+
const identities = Array.isArray(object.public_identities) ? object.public_identities : [];
|
|
164
|
+
for (const item of identities) {
|
|
165
|
+
const record = objectValue(item);
|
|
166
|
+
const identity = objectField(record, 'public_identity');
|
|
167
|
+
vault.importPublicIdentity(identity);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function publicIdentityFingerprint(identity) {
|
|
171
|
+
const canonical = JSON.stringify(sortJson(identity));
|
|
172
|
+
return `sha256:${createHash('sha256').update(canonical).digest('base64url')}`;
|
|
173
|
+
}
|
|
174
|
+
function sortJson(value) {
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
return value.map(sortJson);
|
|
177
|
+
}
|
|
178
|
+
if (value && typeof value === 'object') {
|
|
179
|
+
return Object.fromEntries(Object.entries(value)
|
|
180
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
181
|
+
.map(([key, child]) => [key, sortJson(child)]));
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function isUnknownIdentityError(error) {
|
|
186
|
+
return error instanceof Error && error.message.startsWith('Unknown identity:');
|
|
187
|
+
}
|
|
118
188
|
export async function recordContextGrantMaterialization(options) {
|
|
119
|
-
if (options.
|
|
189
|
+
if (options.receipts.length === 0) {
|
|
120
190
|
return 0;
|
|
121
191
|
}
|
|
122
192
|
const response = await postJson(options.fetchImpl ?? transportFetch, contextGrantMaterializedUrl(options.serverUrl, options.workspaceId), {
|
|
123
193
|
credential: options.credential,
|
|
124
194
|
context_resource_id: options.contextResourceId,
|
|
125
|
-
|
|
195
|
+
receipts: options.receipts.map((receipt) => ({
|
|
196
|
+
payload: receipt.payload,
|
|
197
|
+
signature: receipt.signature,
|
|
198
|
+
signed_by_epoch_fingerprint: receipt.signedByEpochFingerprint,
|
|
199
|
+
})),
|
|
126
200
|
}, {
|
|
127
201
|
tlsVerify: options.tlsVerify,
|
|
128
202
|
caCertPath: options.caCertPath,
|
|
@@ -155,26 +229,6 @@ export async function recordContextCandidatePreviewProof(options) {
|
|
|
155
229
|
expiresAt: typeof expiresAt === 'string' ? expiresAt : null,
|
|
156
230
|
};
|
|
157
231
|
}
|
|
158
|
-
export async function publishContextPublicIdentity(options) {
|
|
159
|
-
const publicIdentity = exportContextIdentity({
|
|
160
|
-
name: options.identityName,
|
|
161
|
-
home: options.home,
|
|
162
|
-
});
|
|
163
|
-
const response = await postJson(options.fetchImpl ?? transportFetch, contextPublicIdentityUrl(options.serverUrl, options.workspaceId), {
|
|
164
|
-
credential: options.credential,
|
|
165
|
-
name: options.identityName,
|
|
166
|
-
public_identity: publicIdentity,
|
|
167
|
-
}, {
|
|
168
|
-
tlsVerify: options.tlsVerify,
|
|
169
|
-
caCertPath: options.caCertPath,
|
|
170
|
-
tlsPins: options.tlsPins,
|
|
171
|
-
});
|
|
172
|
-
const identity = objectField(response, 'identity');
|
|
173
|
-
return {
|
|
174
|
-
identityId: stringField(identity, 'id'),
|
|
175
|
-
fingerprint: nullableStringField(identity, 'fingerprint'),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
232
|
export async function processPendingContextGrants(options) {
|
|
179
233
|
const fetchImpl = options.fetchImpl ?? transportFetch;
|
|
180
234
|
const response = await postJson(fetchImpl, contextPendingGrantsUrl(options.serverUrl, options.workspaceId), {
|
|
@@ -191,49 +245,137 @@ export async function processPendingContextGrants(options) {
|
|
|
191
245
|
let pushed = 0;
|
|
192
246
|
for (const grant of grants) {
|
|
193
247
|
const record = objectValue(grant);
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
196
|
-
|
|
248
|
+
const userEpoch = objectField(record, 'user_epoch', false);
|
|
249
|
+
if (userEpoch) {
|
|
250
|
+
const userEpochId = String(numberOrStringField(userEpoch, 'id'));
|
|
251
|
+
const userId = String(numberOrStringField(userEpoch, 'user_id'));
|
|
252
|
+
const epoch = numberField(userEpoch, 'epoch');
|
|
253
|
+
const fingerprint = stringField(userEpoch, 'fingerprint');
|
|
254
|
+
const encryptionPublicKeyJwk = objectField(userEpoch, 'encryption_public_key_jwk');
|
|
255
|
+
const signingPublicKeyJwk = objectField(userEpoch, 'signing_public_key_jwk');
|
|
256
|
+
await validateAndPinPublicEpoch({
|
|
257
|
+
platformEpochId: userEpochId,
|
|
258
|
+
workspaceId: options.workspaceId,
|
|
259
|
+
subjectType: 'user',
|
|
260
|
+
subjectId: userId,
|
|
261
|
+
epoch,
|
|
262
|
+
schema: 'viewport.user_crypto_epoch/v1',
|
|
263
|
+
fingerprint,
|
|
264
|
+
encryptionPublicKeyJwk: encryptionPublicKeyJwk,
|
|
265
|
+
signingPublicKeyJwk: signingPublicKeyJwk,
|
|
266
|
+
previousEpochFingerprint: nullableStringField(userEpoch, 'previous_epoch_fingerprint'),
|
|
267
|
+
continuityPayload: objectField(userEpoch, 'continuity_payload', false),
|
|
268
|
+
continuitySignature: nullableStringField(userEpoch, 'continuity_signature'),
|
|
269
|
+
signedByEpochFingerprint: nullableStringField(userEpoch, 'signed_by_epoch_fingerprint'),
|
|
270
|
+
}, options.home);
|
|
271
|
+
const recipientName = contextUserEpochRecipientName({ userEpochId, fingerprint });
|
|
272
|
+
const result = await grantContextHpkeRecipient({
|
|
273
|
+
contextResourceId: options.contextResourceId,
|
|
274
|
+
actorName: options.actorName,
|
|
275
|
+
recipientName,
|
|
276
|
+
recipientHpkePublicKey: jwkPublicXToBase64(encryptionPublicKeyJwk),
|
|
277
|
+
credentials: options.credentials,
|
|
278
|
+
home: options.home,
|
|
279
|
+
});
|
|
280
|
+
const event = objectValue(result.event);
|
|
281
|
+
const grantEventId = stringField(event, 'id');
|
|
282
|
+
const grantPayload = objectField(event, 'grant', false);
|
|
283
|
+
const keyEpoch = grantPayload ? numberField(grantPayload, 'keyEpoch', false) : null;
|
|
284
|
+
const pushResult = await pushContextEvents({
|
|
285
|
+
contextResourceId: options.contextResourceId,
|
|
286
|
+
workspaceId: options.workspaceId,
|
|
287
|
+
serverUrl: options.serverUrl,
|
|
288
|
+
credential: options.credential,
|
|
289
|
+
tlsVerify: options.tlsVerify,
|
|
290
|
+
caCertPath: options.caCertPath,
|
|
291
|
+
tlsPins: options.tlsPins,
|
|
292
|
+
home: options.home,
|
|
293
|
+
fetchImpl,
|
|
294
|
+
});
|
|
295
|
+
pushed += pushResult.accepted;
|
|
296
|
+
await postJson(fetchImpl, contextMarkGrantEmittedUrl(options.serverUrl, options.workspaceId), {
|
|
297
|
+
credential: options.credential,
|
|
298
|
+
crypto_grant_id: stringField(record, 'id'),
|
|
299
|
+
grant_event_id: grantEventId,
|
|
300
|
+
recipient_identity_name: recipientName,
|
|
301
|
+
recipient_type: 'user_epoch',
|
|
302
|
+
recipient_epoch_id: userEpochId,
|
|
303
|
+
recipient_fingerprint: fingerprint,
|
|
304
|
+
...(keyEpoch !== null ? { key_epoch: keyEpoch } : {}),
|
|
305
|
+
}, {
|
|
306
|
+
tlsVerify: options.tlsVerify,
|
|
307
|
+
caCertPath: options.caCertPath,
|
|
308
|
+
tlsPins: options.tlsPins,
|
|
309
|
+
});
|
|
310
|
+
emitted++;
|
|
197
311
|
continue;
|
|
198
312
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
313
|
+
const teamEpoch = objectField(record, 'team_epoch', false);
|
|
314
|
+
if (teamEpoch) {
|
|
315
|
+
const teamEpochId = String(numberOrStringField(teamEpoch, 'id'));
|
|
316
|
+
const teamId = String(numberOrStringField(teamEpoch, 'team_id'));
|
|
317
|
+
const epoch = numberField(teamEpoch, 'epoch');
|
|
318
|
+
const fingerprint = stringField(teamEpoch, 'fingerprint');
|
|
319
|
+
const encryptionPublicKeyJwk = objectField(teamEpoch, 'encryption_public_key_jwk');
|
|
320
|
+
const signingPublicKeyJwk = objectField(teamEpoch, 'signing_public_key_jwk');
|
|
321
|
+
await validateAndPinPublicEpoch({
|
|
322
|
+
platformEpochId: teamEpochId,
|
|
323
|
+
workspaceId: options.workspaceId,
|
|
324
|
+
subjectType: 'team',
|
|
325
|
+
subjectId: teamId,
|
|
326
|
+
epoch,
|
|
327
|
+
schema: 'viewport.team_crypto_epoch/v1',
|
|
328
|
+
fingerprint,
|
|
329
|
+
encryptionPublicKeyJwk: encryptionPublicKeyJwk,
|
|
330
|
+
signingPublicKeyJwk: signingPublicKeyJwk,
|
|
331
|
+
previousEpochFingerprint: nullableStringField(teamEpoch, 'previous_epoch_fingerprint'),
|
|
332
|
+
continuityPayload: objectField(teamEpoch, 'continuity_payload', false),
|
|
333
|
+
continuitySignature: nullableStringField(teamEpoch, 'continuity_signature'),
|
|
334
|
+
signedByEpochFingerprint: nullableStringField(teamEpoch, 'signed_by_epoch_fingerprint'),
|
|
335
|
+
}, options.home);
|
|
336
|
+
const recipientName = contextTeamEpochRecipientName({ teamEpochId, fingerprint });
|
|
337
|
+
const result = await grantContextHpkeRecipient({
|
|
338
|
+
contextResourceId: options.contextResourceId,
|
|
339
|
+
actorName: options.actorName,
|
|
340
|
+
recipientName,
|
|
341
|
+
recipientHpkePublicKey: jwkPublicXToBase64(encryptionPublicKeyJwk),
|
|
342
|
+
credentials: options.credentials,
|
|
343
|
+
home: options.home,
|
|
344
|
+
});
|
|
345
|
+
const event = objectValue(result.event);
|
|
346
|
+
const grantEventId = stringField(event, 'id');
|
|
347
|
+
const grantPayload = objectField(event, 'grant', false);
|
|
348
|
+
const keyEpoch = grantPayload ? numberField(grantPayload, 'keyEpoch', false) : null;
|
|
349
|
+
const pushResult = await pushContextEvents({
|
|
350
|
+
contextResourceId: options.contextResourceId,
|
|
351
|
+
workspaceId: options.workspaceId,
|
|
352
|
+
serverUrl: options.serverUrl,
|
|
353
|
+
credential: options.credential,
|
|
354
|
+
tlsVerify: options.tlsVerify,
|
|
355
|
+
caCertPath: options.caCertPath,
|
|
356
|
+
tlsPins: options.tlsPins,
|
|
357
|
+
home: options.home,
|
|
358
|
+
fetchImpl,
|
|
359
|
+
});
|
|
360
|
+
pushed += pushResult.accepted;
|
|
361
|
+
await postJson(fetchImpl, contextMarkGrantEmittedUrl(options.serverUrl, options.workspaceId), {
|
|
362
|
+
credential: options.credential,
|
|
363
|
+
crypto_grant_id: stringField(record, 'id'),
|
|
364
|
+
grant_event_id: grantEventId,
|
|
365
|
+
recipient_identity_name: recipientName,
|
|
366
|
+
recipient_type: 'team_epoch',
|
|
367
|
+
recipient_epoch_id: teamEpochId,
|
|
368
|
+
recipient_fingerprint: fingerprint,
|
|
369
|
+
...(keyEpoch !== null ? { key_epoch: keyEpoch } : {}),
|
|
370
|
+
}, {
|
|
371
|
+
tlsVerify: options.tlsVerify,
|
|
372
|
+
caCertPath: options.caCertPath,
|
|
373
|
+
tlsPins: options.tlsPins,
|
|
374
|
+
});
|
|
375
|
+
emitted++;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
missingIdentity++;
|
|
237
379
|
}
|
|
238
380
|
return { emitted, missingIdentity, pushed };
|
|
239
381
|
}
|
|
@@ -253,9 +395,7 @@ export async function processPendingContextRevocations(options) {
|
|
|
253
395
|
let pushed = 0;
|
|
254
396
|
for (const revocation of revocations) {
|
|
255
397
|
const record = objectValue(revocation);
|
|
256
|
-
const
|
|
257
|
-
const recipientName = nullableStringField(record, 'recipient_identity_name') ??
|
|
258
|
-
(recipient ? nullableStringField(recipient, 'name') : null);
|
|
398
|
+
const recipientName = nullableStringField(record, 'recipient_identity_name');
|
|
259
399
|
if (!recipientName) {
|
|
260
400
|
missingIdentity++;
|
|
261
401
|
continue;
|
|
@@ -317,6 +457,84 @@ function contextGrantRotationReceipt(event) {
|
|
|
317
457
|
...(recipientName ? { recipient_identity_name: recipientName } : {}),
|
|
318
458
|
};
|
|
319
459
|
}
|
|
460
|
+
async function contextGrantIdentitiesForWorkspace(options) {
|
|
461
|
+
const userEpoch = await getActiveLocalUserEpoch(options.workspaceId, options.home);
|
|
462
|
+
const teamEpochs = await listActiveLocalTeamEpochs(options.workspaceId, options.home);
|
|
463
|
+
return [
|
|
464
|
+
...(userEpoch?.platformEpochId
|
|
465
|
+
? [
|
|
466
|
+
{
|
|
467
|
+
kind: 'user_epoch',
|
|
468
|
+
name: contextUserEpochRecipientName({
|
|
469
|
+
userEpochId: userEpoch.platformEpochId,
|
|
470
|
+
fingerprint: userEpoch.fingerprint,
|
|
471
|
+
}),
|
|
472
|
+
hpkePrivateKey: jwkPrivateDToBase64(objectValue(userEpoch.encryptionPrivateKeyJwk)),
|
|
473
|
+
signingPrivateKeyJwk: userEpoch.signingPrivateKeyJwk,
|
|
474
|
+
signerFingerprint: userEpoch.fingerprint,
|
|
475
|
+
},
|
|
476
|
+
]
|
|
477
|
+
: []),
|
|
478
|
+
...teamEpochs.map((epoch) => ({
|
|
479
|
+
kind: 'team_epoch',
|
|
480
|
+
name: contextTeamEpochRecipientName({
|
|
481
|
+
teamEpochId: epoch.platformEpochId ?? `${epoch.platformTeamId ?? epoch.teamId}:${epoch.epoch}`,
|
|
482
|
+
fingerprint: epoch.fingerprint,
|
|
483
|
+
}),
|
|
484
|
+
hpkePrivateKey: jwkPrivateDToBase64(objectValue(epoch.encryptionPrivateKeyJwk)),
|
|
485
|
+
signingPrivateKeyJwk: epoch.signingPrivateKeyJwk,
|
|
486
|
+
signerFingerprint: epoch.fingerprint,
|
|
487
|
+
})),
|
|
488
|
+
];
|
|
489
|
+
}
|
|
490
|
+
function contextGrantMaterializationReceipts(options) {
|
|
491
|
+
const receipts = [];
|
|
492
|
+
for (const event of options.events) {
|
|
493
|
+
const match = grantEventRecipientMatch(event, options.grantIdentities);
|
|
494
|
+
if (!match)
|
|
495
|
+
continue;
|
|
496
|
+
const keyEpoch = numberField(event, 'keyEpoch', false);
|
|
497
|
+
const payload = contextGrantMaterializationPayload({
|
|
498
|
+
workspaceId: options.workspaceId,
|
|
499
|
+
contextResourceId: options.contextResourceId,
|
|
500
|
+
grantEventId: stringField(event, 'id'),
|
|
501
|
+
recipientName: match.identity.name,
|
|
502
|
+
keyEpoch,
|
|
503
|
+
});
|
|
504
|
+
receipts.push(signContextGrantMaterialization({
|
|
505
|
+
payload,
|
|
506
|
+
signingPrivateKeyJwk: match.identity.signingPrivateKeyJwk,
|
|
507
|
+
signedByEpochFingerprint: match.identity.signerFingerprint,
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
return receipts;
|
|
511
|
+
}
|
|
512
|
+
function grantEventRecipientMatch(event, grantIdentities) {
|
|
513
|
+
if (!CONTEXT_GRANT_EVENT_TYPES.has(event.type))
|
|
514
|
+
return null;
|
|
515
|
+
const grant = event.grant;
|
|
516
|
+
if (!grant || typeof grant !== 'object' || Array.isArray(grant))
|
|
517
|
+
return null;
|
|
518
|
+
const recipientName = grant.recipientName;
|
|
519
|
+
if (typeof recipientName !== 'string' || recipientName === '')
|
|
520
|
+
return null;
|
|
521
|
+
const identity = grantIdentities.find((candidate) => candidate.name === recipientName);
|
|
522
|
+
return identity ? { grant: grant, identity } : null;
|
|
523
|
+
}
|
|
524
|
+
function contextUserEpochRecipientName(input) {
|
|
525
|
+
return `user-epoch:${input.userEpochId}:${input.fingerprint}`;
|
|
526
|
+
}
|
|
527
|
+
function contextTeamEpochRecipientName(input) {
|
|
528
|
+
return `team-epoch:${input.teamEpochId}:${input.fingerprint}`;
|
|
529
|
+
}
|
|
530
|
+
function jwkPublicXToBase64(jwk) {
|
|
531
|
+
const x = stringField(jwk, 'x');
|
|
532
|
+
return Buffer.from(x, 'base64url').toString('base64');
|
|
533
|
+
}
|
|
534
|
+
function jwkPrivateDToBase64(jwk) {
|
|
535
|
+
const d = stringField(jwk, 'd');
|
|
536
|
+
return Buffer.from(d, 'base64url').toString('base64');
|
|
537
|
+
}
|
|
320
538
|
function contextRuntimeUrl(serverUrl, workspaceId, operation) {
|
|
321
539
|
const base = serverUrl.replace(/\/+$/, '');
|
|
322
540
|
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/events/${operation}`;
|
|
@@ -325,10 +543,6 @@ function contextCandidatePreviewProofUrl(serverUrl, workspaceId) {
|
|
|
325
543
|
const base = serverUrl.replace(/\/+$/, '');
|
|
326
544
|
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/candidates/preview-proof`;
|
|
327
545
|
}
|
|
328
|
-
function contextPublicIdentityUrl(serverUrl, workspaceId) {
|
|
329
|
-
const base = serverUrl.replace(/\/+$/, '');
|
|
330
|
-
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/identities`;
|
|
331
|
-
}
|
|
332
546
|
function contextPendingGrantsUrl(serverUrl, workspaceId) {
|
|
333
547
|
const base = serverUrl.replace(/\/+$/, '');
|
|
334
548
|
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/pending`;
|
|
@@ -352,7 +566,11 @@ function contextGrantMaterializedUrl(serverUrl, workspaceId) {
|
|
|
352
566
|
async function postJson(fetchImpl, url, body, transportOptions = {}) {
|
|
353
567
|
const response = await fetchImpl(url, {
|
|
354
568
|
method: 'POST',
|
|
355
|
-
headers: {
|
|
569
|
+
headers: {
|
|
570
|
+
'content-type': 'application/json',
|
|
571
|
+
accept: 'application/json',
|
|
572
|
+
[TRUSTED_EDGE_CRYPTO_PROTOCOL_HEADER]: TRUSTED_EDGE_CRYPTO_PROTOCOL_VERSION,
|
|
573
|
+
},
|
|
356
574
|
body: JSON.stringify(body),
|
|
357
575
|
timeoutMs: 5_000,
|
|
358
576
|
...transportOptions,
|
|
@@ -367,7 +585,9 @@ async function postJson(fetchImpl, url, body, transportOptions = {}) {
|
|
|
367
585
|
if (!response.ok) {
|
|
368
586
|
const reason = typeof payload === 'object' && payload && 'reason' in payload
|
|
369
587
|
? String(payload.reason)
|
|
370
|
-
:
|
|
588
|
+
: typeof payload === 'object' && payload && 'message' in payload
|
|
589
|
+
? String(payload.message)
|
|
590
|
+
: `${response.status} ${response.statusText}`;
|
|
371
591
|
throw new Error(`Context sync request failed: ${reason}`);
|
|
372
592
|
}
|
|
373
593
|
return payload;
|
|
@@ -458,14 +678,6 @@ function extractPulledCandidateDecisions(response, trustedDecisionKeys) {
|
|
|
458
678
|
return record;
|
|
459
679
|
});
|
|
460
680
|
}
|
|
461
|
-
function isGrantEventForUser(event, userName) {
|
|
462
|
-
if (!CONTEXT_GRANT_EVENT_TYPES.has(event.type))
|
|
463
|
-
return false;
|
|
464
|
-
const grant = event.grant;
|
|
465
|
-
if (!grant || typeof grant !== 'object' || Array.isArray(grant))
|
|
466
|
-
return false;
|
|
467
|
-
return grant.recipientName === userName;
|
|
468
|
-
}
|
|
469
681
|
function latestReceivedAt(records, decisions = []) {
|
|
470
682
|
return [
|
|
471
683
|
...records
|
|
@@ -491,4 +703,12 @@ function numberField(response, field, required = true) {
|
|
|
491
703
|
}
|
|
492
704
|
return value;
|
|
493
705
|
}
|
|
706
|
+
function numberOrStringField(response, field) {
|
|
707
|
+
const object = objectValue(response);
|
|
708
|
+
const value = object[field];
|
|
709
|
+
if (typeof value !== 'number' && typeof value !== 'string') {
|
|
710
|
+
throw new Error(`Context sync response ${field} must be a number or string`);
|
|
711
|
+
}
|
|
712
|
+
return value;
|
|
713
|
+
}
|
|
494
714
|
//# sourceMappingURL=local-edge-sync.js.map
|