@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,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;
|
|
@@ -32,8 +33,70 @@ export declare function pullContextEvents(options: {
|
|
|
32
33
|
}): Promise<{
|
|
33
34
|
appliedCandidateDecisions: number;
|
|
34
35
|
imported: number;
|
|
36
|
+
materializedGrants: number;
|
|
35
37
|
pendingCandidateDecisions: number;
|
|
36
38
|
pulled: number;
|
|
37
39
|
repoId: string;
|
|
38
40
|
}>;
|
|
41
|
+
export declare function recordContextGrantMaterialization(options: {
|
|
42
|
+
workspaceId: string;
|
|
43
|
+
serverUrl: string;
|
|
44
|
+
credential: string;
|
|
45
|
+
contextResourceId: string;
|
|
46
|
+
receipts: SignedContextGrantMaterialization[];
|
|
47
|
+
tlsVerify?: TlsVerifyMode;
|
|
48
|
+
caCertPath?: string;
|
|
49
|
+
tlsPins?: string[];
|
|
50
|
+
fetchImpl?: typeof transportFetch;
|
|
51
|
+
}): Promise<number>;
|
|
52
|
+
export declare function recordContextCandidatePreviewProof(options: {
|
|
53
|
+
workspaceId: string;
|
|
54
|
+
serverUrl: string;
|
|
55
|
+
credential: string;
|
|
56
|
+
contextResourceId: string;
|
|
57
|
+
candidateEventId: string;
|
|
58
|
+
payloadDigest?: string | null;
|
|
59
|
+
previewDigest?: string | null;
|
|
60
|
+
tlsVerify?: TlsVerifyMode;
|
|
61
|
+
caCertPath?: string;
|
|
62
|
+
tlsPins?: string[];
|
|
63
|
+
fetchImpl?: typeof transportFetch;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
previewProofId: string;
|
|
66
|
+
expiresAt: string | null;
|
|
67
|
+
}>;
|
|
68
|
+
export declare function processPendingContextGrants(options: {
|
|
69
|
+
contextResourceId: string;
|
|
70
|
+
workspaceId: string;
|
|
71
|
+
serverUrl: string;
|
|
72
|
+
credential: string;
|
|
73
|
+
actorName: string;
|
|
74
|
+
credentials: ContextCredentials;
|
|
75
|
+
tlsVerify?: TlsVerifyMode;
|
|
76
|
+
caCertPath?: string;
|
|
77
|
+
tlsPins?: string[];
|
|
78
|
+
home?: string;
|
|
79
|
+
fetchImpl?: typeof transportFetch;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
emitted: number;
|
|
82
|
+
missingIdentity: number;
|
|
83
|
+
pushed: number;
|
|
84
|
+
}>;
|
|
85
|
+
export declare function processPendingContextRevocations(options: {
|
|
86
|
+
contextResourceId: string;
|
|
87
|
+
workspaceId: string;
|
|
88
|
+
serverUrl: string;
|
|
89
|
+
credential: string;
|
|
90
|
+
actorName: string;
|
|
91
|
+
credentials: ContextCredentials;
|
|
92
|
+
tlsVerify?: TlsVerifyMode;
|
|
93
|
+
caCertPath?: string;
|
|
94
|
+
tlsPins?: string[];
|
|
95
|
+
home?: string;
|
|
96
|
+
fetchImpl?: typeof transportFetch;
|
|
97
|
+
}): Promise<{
|
|
98
|
+
revoked: number;
|
|
99
|
+
missingIdentity: number;
|
|
100
|
+
pushed: number;
|
|
101
|
+
}>;
|
|
39
102
|
//# sourceMappingURL=local-edge-sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-edge-sync.d.ts","sourceRoot":"","sources":["../../src/context/local-edge-sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"local-edge-sync.d.ts","sourceRoot":"","sources":["../../src/context/local-edge-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,11 @@ import { applyContextCandidateDecision } from './local-edge-candidates.js';
|
|
|
5
5
|
import { readCandidateDecisionApplications } from './local-edge-decision-applications.js';
|
|
6
6
|
import { verifyContextCandidateDecision } from './local-edge-decision-signature.js';
|
|
7
7
|
import { readContextMetadata, touchContextMetadata } from './local-edge-metadata.js';
|
|
8
|
+
import { grantContextHpkeRecipient, revokeContextUser } from './local-edge-store.js';
|
|
9
|
+
import { validateAndPinPublicEpoch } from '../security/epoch-public-pins.js';
|
|
10
|
+
import { getActiveLocalUserEpoch, listActiveLocalTeamEpochs } from '../security/epoch-store.js';
|
|
11
|
+
import { TRUSTED_EDGE_CRYPTO_PROTOCOL_HEADER, TRUSTED_EDGE_CRYPTO_PROTOCOL_VERSION, contextGrantMaterializationPayload, signContextGrantMaterialization, } from '../security/epoch-protocol.js';
|
|
12
|
+
const CONTEXT_GRANT_EVENT_TYPES = new Set(['member.granted', 'key.rotated']);
|
|
8
13
|
export async function pushContextEvents(options) {
|
|
9
14
|
const home = options.home ?? configDir();
|
|
10
15
|
const metadata = await readContextMetadata(options.contextResourceId, home);
|
|
@@ -64,11 +69,35 @@ export async function pullContextEvents(options) {
|
|
|
64
69
|
});
|
|
65
70
|
const records = extractPulledRecords(response);
|
|
66
71
|
const events = records.map((record) => record.signedEvent);
|
|
72
|
+
const grantIdentities = await contextGrantIdentitiesForWorkspace({
|
|
73
|
+
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
74
|
+
home,
|
|
75
|
+
});
|
|
67
76
|
const imported = await vault.importSyncEvents({
|
|
68
77
|
repoId: metadata.repoId,
|
|
69
78
|
events,
|
|
70
79
|
actorName: options.actorName,
|
|
80
|
+
grantIdentities,
|
|
81
|
+
});
|
|
82
|
+
const materializationReceipts = contextGrantMaterializationReceipts({
|
|
83
|
+
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
84
|
+
contextResourceId: options.contextResourceId,
|
|
85
|
+
events,
|
|
86
|
+
grantIdentities,
|
|
71
87
|
});
|
|
88
|
+
const materializedGrants = materializationReceipts.length > 0
|
|
89
|
+
? await recordContextGrantMaterialization({
|
|
90
|
+
workspaceId: options.workspaceId ?? options.contextResourceId,
|
|
91
|
+
serverUrl: options.serverUrl,
|
|
92
|
+
credential: options.credential,
|
|
93
|
+
contextResourceId: options.contextResourceId,
|
|
94
|
+
receipts: materializationReceipts,
|
|
95
|
+
tlsVerify: options.tlsVerify,
|
|
96
|
+
caCertPath: options.caCertPath,
|
|
97
|
+
tlsPins: options.tlsPins,
|
|
98
|
+
fetchImpl: options.fetchImpl,
|
|
99
|
+
})
|
|
100
|
+
: 0;
|
|
72
101
|
const candidateDecisions = extractPulledCandidateDecisions(response, options.trustedDecisionKeys);
|
|
73
102
|
const candidateDecisionResults = [];
|
|
74
103
|
for (const decision of candidateDecisions) {
|
|
@@ -91,19 +120,398 @@ export async function pullContextEvents(options) {
|
|
|
91
120
|
return {
|
|
92
121
|
appliedCandidateDecisions,
|
|
93
122
|
imported: imported.imported.length,
|
|
123
|
+
materializedGrants,
|
|
94
124
|
pendingCandidateDecisions,
|
|
95
125
|
pulled: events.length,
|
|
96
126
|
repoId: metadata.repoId,
|
|
97
127
|
};
|
|
98
128
|
}
|
|
129
|
+
export async function recordContextGrantMaterialization(options) {
|
|
130
|
+
if (options.receipts.length === 0) {
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
const response = await postJson(options.fetchImpl ?? transportFetch, contextGrantMaterializedUrl(options.serverUrl, options.workspaceId), {
|
|
134
|
+
credential: options.credential,
|
|
135
|
+
context_resource_id: options.contextResourceId,
|
|
136
|
+
receipts: options.receipts.map((receipt) => ({
|
|
137
|
+
payload: receipt.payload,
|
|
138
|
+
signature: receipt.signature,
|
|
139
|
+
signed_by_epoch_fingerprint: receipt.signedByEpochFingerprint,
|
|
140
|
+
})),
|
|
141
|
+
}, {
|
|
142
|
+
tlsVerify: options.tlsVerify,
|
|
143
|
+
caCertPath: options.caCertPath,
|
|
144
|
+
tlsPins: options.tlsPins,
|
|
145
|
+
});
|
|
146
|
+
return numberField(response, 'materialized');
|
|
147
|
+
}
|
|
148
|
+
export async function recordContextCandidatePreviewProof(options) {
|
|
149
|
+
const response = await postJson(options.fetchImpl ?? transportFetch, contextCandidatePreviewProofUrl(options.serverUrl, options.workspaceId), {
|
|
150
|
+
credential: options.credential,
|
|
151
|
+
context_resource_id: options.contextResourceId,
|
|
152
|
+
candidate_event_id: options.candidateEventId,
|
|
153
|
+
...(options.payloadDigest ? { payload_digest: options.payloadDigest } : {}),
|
|
154
|
+
...(options.previewDigest ? { preview_digest: options.previewDigest } : {}),
|
|
155
|
+
}, {
|
|
156
|
+
tlsVerify: options.tlsVerify,
|
|
157
|
+
caCertPath: options.caCertPath,
|
|
158
|
+
tlsPins: options.tlsPins,
|
|
159
|
+
});
|
|
160
|
+
if (!response || typeof response !== 'object') {
|
|
161
|
+
throw new Error('Context preview proof response was not an object');
|
|
162
|
+
}
|
|
163
|
+
const previewProofId = response.preview_proof_id;
|
|
164
|
+
if (typeof previewProofId !== 'string' || previewProofId === '') {
|
|
165
|
+
throw new Error('Context preview proof response did not include preview_proof_id');
|
|
166
|
+
}
|
|
167
|
+
const expiresAt = response.expires_at;
|
|
168
|
+
return {
|
|
169
|
+
previewProofId,
|
|
170
|
+
expiresAt: typeof expiresAt === 'string' ? expiresAt : null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export async function processPendingContextGrants(options) {
|
|
174
|
+
const fetchImpl = options.fetchImpl ?? transportFetch;
|
|
175
|
+
const response = await postJson(fetchImpl, contextPendingGrantsUrl(options.serverUrl, options.workspaceId), {
|
|
176
|
+
credential: options.credential,
|
|
177
|
+
context_resource_id: options.contextResourceId,
|
|
178
|
+
}, {
|
|
179
|
+
tlsVerify: options.tlsVerify,
|
|
180
|
+
caCertPath: options.caCertPath,
|
|
181
|
+
tlsPins: options.tlsPins,
|
|
182
|
+
});
|
|
183
|
+
const grants = arrayField(response, 'grants');
|
|
184
|
+
let emitted = 0;
|
|
185
|
+
let missingIdentity = 0;
|
|
186
|
+
let pushed = 0;
|
|
187
|
+
for (const grant of grants) {
|
|
188
|
+
const record = objectValue(grant);
|
|
189
|
+
const userEpoch = objectField(record, 'user_epoch', false);
|
|
190
|
+
if (userEpoch) {
|
|
191
|
+
const userEpochId = String(numberOrStringField(userEpoch, 'id'));
|
|
192
|
+
const userId = String(numberOrStringField(userEpoch, 'user_id'));
|
|
193
|
+
const epoch = numberField(userEpoch, 'epoch');
|
|
194
|
+
const fingerprint = stringField(userEpoch, 'fingerprint');
|
|
195
|
+
const encryptionPublicKeyJwk = objectField(userEpoch, 'encryption_public_key_jwk');
|
|
196
|
+
const signingPublicKeyJwk = objectField(userEpoch, 'signing_public_key_jwk');
|
|
197
|
+
await validateAndPinPublicEpoch({
|
|
198
|
+
platformEpochId: userEpochId,
|
|
199
|
+
workspaceId: options.workspaceId,
|
|
200
|
+
subjectType: 'user',
|
|
201
|
+
subjectId: userId,
|
|
202
|
+
epoch,
|
|
203
|
+
schema: 'viewport.user_crypto_epoch/v1',
|
|
204
|
+
fingerprint,
|
|
205
|
+
encryptionPublicKeyJwk: encryptionPublicKeyJwk,
|
|
206
|
+
signingPublicKeyJwk: signingPublicKeyJwk,
|
|
207
|
+
previousEpochFingerprint: nullableStringField(userEpoch, 'previous_epoch_fingerprint'),
|
|
208
|
+
continuityPayload: objectField(userEpoch, 'continuity_payload', false),
|
|
209
|
+
continuitySignature: nullableStringField(userEpoch, 'continuity_signature'),
|
|
210
|
+
signedByEpochFingerprint: nullableStringField(userEpoch, 'signed_by_epoch_fingerprint'),
|
|
211
|
+
}, options.home);
|
|
212
|
+
const recipientName = contextUserEpochRecipientName({ userEpochId, fingerprint });
|
|
213
|
+
const result = await grantContextHpkeRecipient({
|
|
214
|
+
contextResourceId: options.contextResourceId,
|
|
215
|
+
actorName: options.actorName,
|
|
216
|
+
recipientName,
|
|
217
|
+
recipientHpkePublicKey: jwkPublicXToBase64(encryptionPublicKeyJwk),
|
|
218
|
+
credentials: options.credentials,
|
|
219
|
+
home: options.home,
|
|
220
|
+
});
|
|
221
|
+
const event = objectValue(result.event);
|
|
222
|
+
const grantEventId = stringField(event, 'id');
|
|
223
|
+
const grantPayload = objectField(event, 'grant', false);
|
|
224
|
+
const keyEpoch = grantPayload ? numberField(grantPayload, 'keyEpoch', false) : null;
|
|
225
|
+
const pushResult = await pushContextEvents({
|
|
226
|
+
contextResourceId: options.contextResourceId,
|
|
227
|
+
workspaceId: options.workspaceId,
|
|
228
|
+
serverUrl: options.serverUrl,
|
|
229
|
+
credential: options.credential,
|
|
230
|
+
tlsVerify: options.tlsVerify,
|
|
231
|
+
caCertPath: options.caCertPath,
|
|
232
|
+
tlsPins: options.tlsPins,
|
|
233
|
+
home: options.home,
|
|
234
|
+
fetchImpl,
|
|
235
|
+
});
|
|
236
|
+
pushed += pushResult.accepted;
|
|
237
|
+
await postJson(fetchImpl, contextMarkGrantEmittedUrl(options.serverUrl, options.workspaceId), {
|
|
238
|
+
credential: options.credential,
|
|
239
|
+
crypto_grant_id: stringField(record, 'id'),
|
|
240
|
+
grant_event_id: grantEventId,
|
|
241
|
+
recipient_identity_name: recipientName,
|
|
242
|
+
recipient_type: 'user_epoch',
|
|
243
|
+
recipient_epoch_id: userEpochId,
|
|
244
|
+
recipient_fingerprint: fingerprint,
|
|
245
|
+
...(keyEpoch !== null ? { key_epoch: keyEpoch } : {}),
|
|
246
|
+
}, {
|
|
247
|
+
tlsVerify: options.tlsVerify,
|
|
248
|
+
caCertPath: options.caCertPath,
|
|
249
|
+
tlsPins: options.tlsPins,
|
|
250
|
+
});
|
|
251
|
+
emitted++;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const teamEpoch = objectField(record, 'team_epoch', false);
|
|
255
|
+
if (teamEpoch) {
|
|
256
|
+
const teamEpochId = String(numberOrStringField(teamEpoch, 'id'));
|
|
257
|
+
const teamId = String(numberOrStringField(teamEpoch, 'team_id'));
|
|
258
|
+
const epoch = numberField(teamEpoch, 'epoch');
|
|
259
|
+
const fingerprint = stringField(teamEpoch, 'fingerprint');
|
|
260
|
+
const encryptionPublicKeyJwk = objectField(teamEpoch, 'encryption_public_key_jwk');
|
|
261
|
+
const signingPublicKeyJwk = objectField(teamEpoch, 'signing_public_key_jwk');
|
|
262
|
+
await validateAndPinPublicEpoch({
|
|
263
|
+
platformEpochId: teamEpochId,
|
|
264
|
+
workspaceId: options.workspaceId,
|
|
265
|
+
subjectType: 'team',
|
|
266
|
+
subjectId: teamId,
|
|
267
|
+
epoch,
|
|
268
|
+
schema: 'viewport.team_crypto_epoch/v1',
|
|
269
|
+
fingerprint,
|
|
270
|
+
encryptionPublicKeyJwk: encryptionPublicKeyJwk,
|
|
271
|
+
signingPublicKeyJwk: signingPublicKeyJwk,
|
|
272
|
+
previousEpochFingerprint: nullableStringField(teamEpoch, 'previous_epoch_fingerprint'),
|
|
273
|
+
continuityPayload: objectField(teamEpoch, 'continuity_payload', false),
|
|
274
|
+
continuitySignature: nullableStringField(teamEpoch, 'continuity_signature'),
|
|
275
|
+
signedByEpochFingerprint: nullableStringField(teamEpoch, 'signed_by_epoch_fingerprint'),
|
|
276
|
+
}, options.home);
|
|
277
|
+
const recipientName = contextTeamEpochRecipientName({ teamEpochId, fingerprint });
|
|
278
|
+
const result = await grantContextHpkeRecipient({
|
|
279
|
+
contextResourceId: options.contextResourceId,
|
|
280
|
+
actorName: options.actorName,
|
|
281
|
+
recipientName,
|
|
282
|
+
recipientHpkePublicKey: jwkPublicXToBase64(encryptionPublicKeyJwk),
|
|
283
|
+
credentials: options.credentials,
|
|
284
|
+
home: options.home,
|
|
285
|
+
});
|
|
286
|
+
const event = objectValue(result.event);
|
|
287
|
+
const grantEventId = stringField(event, 'id');
|
|
288
|
+
const grantPayload = objectField(event, 'grant', false);
|
|
289
|
+
const keyEpoch = grantPayload ? numberField(grantPayload, 'keyEpoch', false) : null;
|
|
290
|
+
const pushResult = await pushContextEvents({
|
|
291
|
+
contextResourceId: options.contextResourceId,
|
|
292
|
+
workspaceId: options.workspaceId,
|
|
293
|
+
serverUrl: options.serverUrl,
|
|
294
|
+
credential: options.credential,
|
|
295
|
+
tlsVerify: options.tlsVerify,
|
|
296
|
+
caCertPath: options.caCertPath,
|
|
297
|
+
tlsPins: options.tlsPins,
|
|
298
|
+
home: options.home,
|
|
299
|
+
fetchImpl,
|
|
300
|
+
});
|
|
301
|
+
pushed += pushResult.accepted;
|
|
302
|
+
await postJson(fetchImpl, contextMarkGrantEmittedUrl(options.serverUrl, options.workspaceId), {
|
|
303
|
+
credential: options.credential,
|
|
304
|
+
crypto_grant_id: stringField(record, 'id'),
|
|
305
|
+
grant_event_id: grantEventId,
|
|
306
|
+
recipient_identity_name: recipientName,
|
|
307
|
+
recipient_type: 'team_epoch',
|
|
308
|
+
recipient_epoch_id: teamEpochId,
|
|
309
|
+
recipient_fingerprint: fingerprint,
|
|
310
|
+
...(keyEpoch !== null ? { key_epoch: keyEpoch } : {}),
|
|
311
|
+
}, {
|
|
312
|
+
tlsVerify: options.tlsVerify,
|
|
313
|
+
caCertPath: options.caCertPath,
|
|
314
|
+
tlsPins: options.tlsPins,
|
|
315
|
+
});
|
|
316
|
+
emitted++;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
missingIdentity++;
|
|
320
|
+
}
|
|
321
|
+
return { emitted, missingIdentity, pushed };
|
|
322
|
+
}
|
|
323
|
+
export async function processPendingContextRevocations(options) {
|
|
324
|
+
const fetchImpl = options.fetchImpl ?? transportFetch;
|
|
325
|
+
const response = await postJson(fetchImpl, contextPendingRevocationsUrl(options.serverUrl, options.workspaceId), {
|
|
326
|
+
credential: options.credential,
|
|
327
|
+
context_resource_id: options.contextResourceId,
|
|
328
|
+
}, {
|
|
329
|
+
tlsVerify: options.tlsVerify,
|
|
330
|
+
caCertPath: options.caCertPath,
|
|
331
|
+
tlsPins: options.tlsPins,
|
|
332
|
+
});
|
|
333
|
+
const revocations = arrayField(response, 'revocations');
|
|
334
|
+
let revoked = 0;
|
|
335
|
+
let missingIdentity = 0;
|
|
336
|
+
let pushed = 0;
|
|
337
|
+
for (const revocation of revocations) {
|
|
338
|
+
const record = objectValue(revocation);
|
|
339
|
+
const recipientName = nullableStringField(record, 'recipient_identity_name');
|
|
340
|
+
if (!recipientName) {
|
|
341
|
+
missingIdentity++;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const result = await revokeContextUser({
|
|
345
|
+
contextResourceId: options.contextResourceId,
|
|
346
|
+
actorName: options.actorName,
|
|
347
|
+
recipientName,
|
|
348
|
+
credentials: options.credentials,
|
|
349
|
+
home: options.home,
|
|
350
|
+
});
|
|
351
|
+
const revokeEventId = stringField(objectValue(result.revokeEvent), 'id');
|
|
352
|
+
const rotationEventIds = result.rotateEvents.map((event) => stringField(objectValue(event), 'id'));
|
|
353
|
+
const rotationEvents = result.rotateEvents.map((event) => contextGrantRotationReceipt(event));
|
|
354
|
+
const maxKeyEpoch = maxEventEpoch([result.revokeEvent, ...result.rotateEvents]);
|
|
355
|
+
const pushResult = await pushContextEvents({
|
|
356
|
+
contextResourceId: options.contextResourceId,
|
|
357
|
+
workspaceId: options.workspaceId,
|
|
358
|
+
serverUrl: options.serverUrl,
|
|
359
|
+
credential: options.credential,
|
|
360
|
+
tlsVerify: options.tlsVerify,
|
|
361
|
+
caCertPath: options.caCertPath,
|
|
362
|
+
tlsPins: options.tlsPins,
|
|
363
|
+
home: options.home,
|
|
364
|
+
fetchImpl,
|
|
365
|
+
});
|
|
366
|
+
pushed += pushResult.accepted;
|
|
367
|
+
await postJson(fetchImpl, contextMarkRevokedUrl(options.serverUrl, options.workspaceId), {
|
|
368
|
+
credential: options.credential,
|
|
369
|
+
crypto_grant_id: stringField(record, 'id'),
|
|
370
|
+
revoke_event_id: revokeEventId,
|
|
371
|
+
rotation_event_ids: rotationEventIds,
|
|
372
|
+
rotation_events: rotationEvents,
|
|
373
|
+
...(maxKeyEpoch !== null ? { key_epoch: maxKeyEpoch } : {}),
|
|
374
|
+
}, {
|
|
375
|
+
tlsVerify: options.tlsVerify,
|
|
376
|
+
caCertPath: options.caCertPath,
|
|
377
|
+
tlsPins: options.tlsPins,
|
|
378
|
+
});
|
|
379
|
+
revoked++;
|
|
380
|
+
}
|
|
381
|
+
return { revoked, missingIdentity, pushed };
|
|
382
|
+
}
|
|
383
|
+
function maxEventEpoch(events) {
|
|
384
|
+
let max = null;
|
|
385
|
+
for (const event of events) {
|
|
386
|
+
const epoch = numberField(objectValue(event), 'keyEpoch', false);
|
|
387
|
+
if (epoch !== null)
|
|
388
|
+
max = max === null ? epoch : Math.max(max, epoch);
|
|
389
|
+
}
|
|
390
|
+
return max;
|
|
391
|
+
}
|
|
392
|
+
function contextGrantRotationReceipt(event) {
|
|
393
|
+
const object = objectValue(event);
|
|
394
|
+
const grant = objectField(object, 'grant', false);
|
|
395
|
+
const recipientName = grant ? nullableStringField(grant, 'recipientName') : null;
|
|
396
|
+
return {
|
|
397
|
+
event_id: stringField(object, 'id'),
|
|
398
|
+
...(recipientName ? { recipient_identity_name: recipientName } : {}),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
async function contextGrantIdentitiesForWorkspace(options) {
|
|
402
|
+
const userEpoch = await getActiveLocalUserEpoch(options.workspaceId, options.home);
|
|
403
|
+
const teamEpochs = await listActiveLocalTeamEpochs(options.workspaceId, options.home);
|
|
404
|
+
return [
|
|
405
|
+
...(userEpoch?.platformEpochId
|
|
406
|
+
? [
|
|
407
|
+
{
|
|
408
|
+
kind: 'user_epoch',
|
|
409
|
+
name: contextUserEpochRecipientName({
|
|
410
|
+
userEpochId: userEpoch.platformEpochId,
|
|
411
|
+
fingerprint: userEpoch.fingerprint,
|
|
412
|
+
}),
|
|
413
|
+
hpkePrivateKey: jwkPrivateDToBase64(objectValue(userEpoch.encryptionPrivateKeyJwk)),
|
|
414
|
+
signingPrivateKeyJwk: userEpoch.signingPrivateKeyJwk,
|
|
415
|
+
signerFingerprint: userEpoch.fingerprint,
|
|
416
|
+
},
|
|
417
|
+
]
|
|
418
|
+
: []),
|
|
419
|
+
...teamEpochs.map((epoch) => ({
|
|
420
|
+
kind: 'team_epoch',
|
|
421
|
+
name: contextTeamEpochRecipientName({
|
|
422
|
+
teamEpochId: epoch.platformEpochId ?? `${epoch.platformTeamId ?? epoch.teamId}:${epoch.epoch}`,
|
|
423
|
+
fingerprint: epoch.fingerprint,
|
|
424
|
+
}),
|
|
425
|
+
hpkePrivateKey: jwkPrivateDToBase64(objectValue(epoch.encryptionPrivateKeyJwk)),
|
|
426
|
+
signingPrivateKeyJwk: epoch.signingPrivateKeyJwk,
|
|
427
|
+
signerFingerprint: epoch.fingerprint,
|
|
428
|
+
})),
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
function contextGrantMaterializationReceipts(options) {
|
|
432
|
+
const receipts = [];
|
|
433
|
+
for (const event of options.events) {
|
|
434
|
+
const match = grantEventRecipientMatch(event, options.grantIdentities);
|
|
435
|
+
if (!match)
|
|
436
|
+
continue;
|
|
437
|
+
const keyEpoch = numberField(event, 'keyEpoch', false);
|
|
438
|
+
const payload = contextGrantMaterializationPayload({
|
|
439
|
+
workspaceId: options.workspaceId,
|
|
440
|
+
contextResourceId: options.contextResourceId,
|
|
441
|
+
grantEventId: stringField(event, 'id'),
|
|
442
|
+
recipientName: match.identity.name,
|
|
443
|
+
keyEpoch,
|
|
444
|
+
});
|
|
445
|
+
receipts.push(signContextGrantMaterialization({
|
|
446
|
+
payload,
|
|
447
|
+
signingPrivateKeyJwk: match.identity.signingPrivateKeyJwk,
|
|
448
|
+
signedByEpochFingerprint: match.identity.signerFingerprint,
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
return receipts;
|
|
452
|
+
}
|
|
453
|
+
function grantEventRecipientMatch(event, grantIdentities) {
|
|
454
|
+
if (!CONTEXT_GRANT_EVENT_TYPES.has(event.type))
|
|
455
|
+
return null;
|
|
456
|
+
const grant = event.grant;
|
|
457
|
+
if (!grant || typeof grant !== 'object' || Array.isArray(grant))
|
|
458
|
+
return null;
|
|
459
|
+
const recipientName = grant.recipientName;
|
|
460
|
+
if (typeof recipientName !== 'string' || recipientName === '')
|
|
461
|
+
return null;
|
|
462
|
+
const identity = grantIdentities.find((candidate) => candidate.name === recipientName);
|
|
463
|
+
return identity ? { grant: grant, identity } : null;
|
|
464
|
+
}
|
|
465
|
+
function contextUserEpochRecipientName(input) {
|
|
466
|
+
return `user-epoch:${input.userEpochId}:${input.fingerprint}`;
|
|
467
|
+
}
|
|
468
|
+
function contextTeamEpochRecipientName(input) {
|
|
469
|
+
return `team-epoch:${input.teamEpochId}:${input.fingerprint}`;
|
|
470
|
+
}
|
|
471
|
+
function jwkPublicXToBase64(jwk) {
|
|
472
|
+
const x = stringField(jwk, 'x');
|
|
473
|
+
return Buffer.from(x, 'base64url').toString('base64');
|
|
474
|
+
}
|
|
475
|
+
function jwkPrivateDToBase64(jwk) {
|
|
476
|
+
const d = stringField(jwk, 'd');
|
|
477
|
+
return Buffer.from(d, 'base64url').toString('base64');
|
|
478
|
+
}
|
|
99
479
|
function contextRuntimeUrl(serverUrl, workspaceId, operation) {
|
|
100
480
|
const base = serverUrl.replace(/\/+$/, '');
|
|
101
481
|
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/events/${operation}`;
|
|
102
482
|
}
|
|
483
|
+
function contextCandidatePreviewProofUrl(serverUrl, workspaceId) {
|
|
484
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
485
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/candidates/preview-proof`;
|
|
486
|
+
}
|
|
487
|
+
function contextPendingGrantsUrl(serverUrl, workspaceId) {
|
|
488
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
489
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/pending`;
|
|
490
|
+
}
|
|
491
|
+
function contextMarkGrantEmittedUrl(serverUrl, workspaceId) {
|
|
492
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
493
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/mark-emitted`;
|
|
494
|
+
}
|
|
495
|
+
function contextPendingRevocationsUrl(serverUrl, workspaceId) {
|
|
496
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
497
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/revocations/pending`;
|
|
498
|
+
}
|
|
499
|
+
function contextMarkRevokedUrl(serverUrl, workspaceId) {
|
|
500
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
501
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/mark-revoked`;
|
|
502
|
+
}
|
|
503
|
+
function contextGrantMaterializedUrl(serverUrl, workspaceId) {
|
|
504
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
505
|
+
return `${base}/api/runtime/workspaces/${encodeURIComponent(workspaceId)}/context-vault/grants/materialized`;
|
|
506
|
+
}
|
|
103
507
|
async function postJson(fetchImpl, url, body, transportOptions = {}) {
|
|
104
508
|
const response = await fetchImpl(url, {
|
|
105
509
|
method: 'POST',
|
|
106
|
-
headers: {
|
|
510
|
+
headers: {
|
|
511
|
+
'content-type': 'application/json',
|
|
512
|
+
accept: 'application/json',
|
|
513
|
+
[TRUSTED_EDGE_CRYPTO_PROTOCOL_HEADER]: TRUSTED_EDGE_CRYPTO_PROTOCOL_VERSION,
|
|
514
|
+
},
|
|
107
515
|
body: JSON.stringify(body),
|
|
108
516
|
timeoutMs: 5_000,
|
|
109
517
|
...transportOptions,
|
|
@@ -144,6 +552,47 @@ function extractPulledRecords(response) {
|
|
|
144
552
|
};
|
|
145
553
|
});
|
|
146
554
|
}
|
|
555
|
+
function objectValue(value) {
|
|
556
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
557
|
+
throw new Error('Expected object value');
|
|
558
|
+
}
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
function objectField(value, field, required = true) {
|
|
562
|
+
const object = objectValue(value);
|
|
563
|
+
const child = object[field];
|
|
564
|
+
if (child === undefined || child === null) {
|
|
565
|
+
if (!required)
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
return objectValue(child);
|
|
569
|
+
}
|
|
570
|
+
function arrayField(value, field) {
|
|
571
|
+
const object = objectValue(value);
|
|
572
|
+
const child = object[field];
|
|
573
|
+
if (!Array.isArray(child)) {
|
|
574
|
+
throw new Error(`Expected ${field} to be an array`);
|
|
575
|
+
}
|
|
576
|
+
return child;
|
|
577
|
+
}
|
|
578
|
+
function nullableStringField(value, field) {
|
|
579
|
+
const object = objectValue(value);
|
|
580
|
+
const child = object[field];
|
|
581
|
+
if (child === undefined || child === null)
|
|
582
|
+
return null;
|
|
583
|
+
if (typeof child !== 'string') {
|
|
584
|
+
throw new Error(`Expected ${field} to be a string`);
|
|
585
|
+
}
|
|
586
|
+
return child;
|
|
587
|
+
}
|
|
588
|
+
function stringField(value, field) {
|
|
589
|
+
const object = objectValue(value);
|
|
590
|
+
const child = object[field];
|
|
591
|
+
if (typeof child !== 'string' || child.length === 0) {
|
|
592
|
+
throw new Error(`Expected ${field} to be a non-empty string`);
|
|
593
|
+
}
|
|
594
|
+
return child;
|
|
595
|
+
}
|
|
147
596
|
function extractPulledCandidateDecisions(response, trustedDecisionKeys) {
|
|
148
597
|
if (!response ||
|
|
149
598
|
typeof response !== 'object' ||
|
|
@@ -180,14 +629,25 @@ function latestReceivedAt(records, decisions = []) {
|
|
|
180
629
|
.sort()
|
|
181
630
|
.at(-1);
|
|
182
631
|
}
|
|
183
|
-
function numberField(response, field) {
|
|
184
|
-
|
|
632
|
+
function numberField(response, field, required = true) {
|
|
633
|
+
const object = objectValue(response);
|
|
634
|
+
const value = object[field];
|
|
635
|
+
if (value === undefined || value === null) {
|
|
636
|
+
if (!required)
|
|
637
|
+
return null;
|
|
185
638
|
throw new Error(`Context sync response did not include ${field}`);
|
|
186
639
|
}
|
|
187
|
-
const value = response[field];
|
|
188
640
|
if (typeof value !== 'number') {
|
|
189
641
|
throw new Error(`Context sync response ${field} must be a number`);
|
|
190
642
|
}
|
|
191
643
|
return value;
|
|
192
644
|
}
|
|
645
|
+
function numberOrStringField(response, field) {
|
|
646
|
+
const object = objectValue(response);
|
|
647
|
+
const value = object[field];
|
|
648
|
+
if (typeof value !== 'number' && typeof value !== 'string') {
|
|
649
|
+
throw new Error(`Context sync response ${field} must be a number or string`);
|
|
650
|
+
}
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
193
653
|
//# sourceMappingURL=local-edge-sync.js.map
|