@koed-labs/server 0.0.0-bootstrap.0 → 0.10.3

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.
Files changed (56) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +65 -0
  3. package/bin/koed.js +7 -0
  4. package/package.json +22 -4
  5. package/third-party-notices.json +92 -0
  6. package/vendor/chunk-2BWTLF7A.js +13883 -0
  7. package/vendor/chunk-2DA7EFZV.js +924 -0
  8. package/vendor/chunk-2S3OGPUC.js +58 -0
  9. package/vendor/chunk-473OA62O.js +26827 -0
  10. package/vendor/chunk-6JRWGOQG.js +236 -0
  11. package/vendor/chunk-7NOQSX4T.js +14 -0
  12. package/vendor/chunk-7NXKPDF3.js +1586 -0
  13. package/vendor/chunk-A4NKL2PK.js +153 -0
  14. package/vendor/chunk-A6WMU2GG.js +48 -0
  15. package/vendor/chunk-AFLKCQHF.js +73 -0
  16. package/vendor/chunk-BTS4ZTF3.js +43 -0
  17. package/vendor/chunk-C3N4N5HR.js +108 -0
  18. package/vendor/chunk-DXTAWHY3.js +25 -0
  19. package/vendor/chunk-G4IIVVJO.js +56 -0
  20. package/vendor/chunk-GELDEJJ2.js +4337 -0
  21. package/vendor/chunk-HFM6ROJL.js +188 -0
  22. package/vendor/chunk-HK2H3MVE.js +328 -0
  23. package/vendor/chunk-HZJK5RWC.js +5088 -0
  24. package/vendor/chunk-J2L726BC.js +2497 -0
  25. package/vendor/chunk-JBGD4JLR.js +3779 -0
  26. package/vendor/chunk-JJNLH5GA.js +2470 -0
  27. package/vendor/chunk-JKOG52PE.js +128 -0
  28. package/vendor/chunk-K2EIFEQF.js +89 -0
  29. package/vendor/chunk-KUIWH33N.js +16 -0
  30. package/vendor/chunk-NEWIOOM6.js +34 -0
  31. package/vendor/chunk-NM5NXCVM.js +2404 -0
  32. package/vendor/chunk-PT3AUPGE.js +459 -0
  33. package/vendor/chunk-PY5LYLV7.js +143 -0
  34. package/vendor/chunk-QIIKMG3M.js +103 -0
  35. package/vendor/chunk-QT24SAW6.js +110 -0
  36. package/vendor/chunk-RVYJKKWA.js +22 -0
  37. package/vendor/chunk-SKVYP7NY.js +1647 -0
  38. package/vendor/chunk-TSRPHKAC.js +780 -0
  39. package/vendor/chunk-UI5RLBKQ.js +30 -0
  40. package/vendor/chunk-VPC5NLZG.js +259 -0
  41. package/vendor/chunk-W2HNI6AA.js +75 -0
  42. package/vendor/chunk-X6M525ZJ.js +20 -0
  43. package/vendor/chunk-XIZVLPHZ.js +18 -0
  44. package/vendor/chunk-XNFVSVBS.js +242 -0
  45. package/vendor/chunk-YCDEMYY6.js +2076 -0
  46. package/vendor/chunk-YJ6XUZT6.js +292 -0
  47. package/vendor/chunk-YJWREXN6.js +26020 -0
  48. package/vendor/chunk-YK4F4VY2.js +173 -0
  49. package/vendor/chunk-YOFI4PCH.js +36 -0
  50. package/vendor/chunk-ZUGNCQZE.js +247 -0
  51. package/vendor/chunk-ZXX4ZNJY.js +21 -0
  52. package/vendor/cli.js +7 -0
  53. package/vendor/component-manifest-catalog.js +8 -0
  54. package/vendor/control-plane.js +23 -0
  55. package/vendor/package.json +1 -0
  56. package/vendor/privacy-service-bootstrap.js +6 -0
@@ -0,0 +1,2497 @@
1
+ import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
2
+ import {
3
+ runApplicationSecretProvider
4
+ } from "./chunk-2S3OGPUC.js";
5
+ import {
6
+ ensureDeviceIdentity
7
+ } from "./chunk-XNFVSVBS.js";
8
+ import {
9
+ loadRepoEnv,
10
+ resolveApiUrl
11
+ } from "./chunk-W2HNI6AA.js";
12
+ import {
13
+ PDS_PROTOCOL,
14
+ canonicalizePdsJson,
15
+ createPdsAuthorizedKeyBundle,
16
+ createPdsSessionPackageRuntimeContext,
17
+ decryptPdsKeyBundleSecretSet,
18
+ isLoopbackHostname,
19
+ isPrivateNetworkIpv4Address,
20
+ parseCanonicalPdsJson,
21
+ pdsEd25519PrivateKey,
22
+ pdsPublicKeyCommitment,
23
+ pdsRepositoryKeyHistorySchema,
24
+ pdsSha256,
25
+ readDesktopLocalCredentialAuthorization,
26
+ signPdsGroupDraft,
27
+ signPdsRecord,
28
+ validatePdsGroupStatement,
29
+ validatePdsKeyBundle
30
+ } from "./chunk-473OA62O.js";
31
+
32
+ // packages/koed-server/dist/personal-sync.js
33
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHash as createHash2, generateKeyPairSync, randomBytes as randomBytes2, randomUUID as randomUUID2, scryptSync, sign } from "node:crypto";
34
+ import { spawn } from "node:child_process";
35
+ import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
36
+ import { dirname, resolve } from "node:path";
37
+
38
+ // packages/koed-server/dist/personal-device-pairing-client.js
39
+ import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes, randomUUID } from "node:crypto";
40
+ var isPersonalDevicePairingUuid = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
41
+ var PERSONAL_DEVICE_PAIRING_PROTOCOL = "koed/pds-lan-pair/v1";
42
+ var MAX_PLAINTEXT_BYTES = 256 * 1024;
43
+ var MAX_RESPONSE_BYTES = 1048576;
44
+ var COMPLETION_ATTEMPTS = 3;
45
+ var AmbiguousTransportFailure = class extends Error {
46
+ originalError;
47
+ constructor(originalError) {
48
+ super("Pairing exchange transport failed.");
49
+ this.name = "AmbiguousTransportFailure";
50
+ this.originalError = originalError;
51
+ }
52
+ };
53
+ var exactKeys = (value, expected) => {
54
+ const actual = Object.keys(value).sort();
55
+ const sortedExpected = [...expected].sort();
56
+ return actual.length === sortedExpected.length && actual.every((key, index) => key === sortedExpected[index]);
57
+ };
58
+ var strictBase64url = (value, bytes) => {
59
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) {
60
+ throw new Error("Pairing message is invalid.");
61
+ }
62
+ const decoded = Buffer.from(value, "base64url");
63
+ if (bytes !== void 0 && decoded.length !== bytes || decoded.toString("base64url") !== value) {
64
+ throw new Error("Pairing message is invalid.");
65
+ }
66
+ return decoded;
67
+ };
68
+ var pairingKey = (invitationId, token) => {
69
+ if (!isPersonalDevicePairingUuid(invitationId) || !/^[A-Za-z0-9_-]{43}$/.test(token)) {
70
+ throw new Error("Pairing credentials are invalid.");
71
+ }
72
+ const tokenBytes = strictBase64url(token, 32);
73
+ const salt = createHash("sha256").update(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}\0${invitationId}`, "utf8").digest();
74
+ return Buffer.from(hkdfSync("sha256", tokenBytes, salt, Buffer.from(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}/transport-key`, "utf8"), 32));
75
+ };
76
+ var additionalData = (direction, invitationId, messageId) => Buffer.from(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}/${direction}
77
+ ${invitationId}
78
+ ${messageId}`, "utf8");
79
+ var parseEnvelope = (value) => {
80
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
81
+ throw new Error("Pairing message is invalid.");
82
+ }
83
+ const envelope = value;
84
+ if (!exactKeys(envelope, [
85
+ "protocol",
86
+ "message_id",
87
+ "nonce",
88
+ "ciphertext",
89
+ "tag"
90
+ ]) || envelope.protocol !== PERSONAL_DEVICE_PAIRING_PROTOCOL || !isPersonalDevicePairingUuid(envelope.message_id)) {
91
+ throw new Error("Pairing message is invalid.");
92
+ }
93
+ strictBase64url(envelope.nonce, 12);
94
+ strictBase64url(envelope.tag, 16);
95
+ const ciphertext = strictBase64url(envelope.ciphertext);
96
+ if (ciphertext.length > MAX_PLAINTEXT_BYTES) {
97
+ throw new Error("Pairing message is too large.");
98
+ }
99
+ return envelope;
100
+ };
101
+ var encrypt = (input, options) => {
102
+ const plaintext = Buffer.from(JSON.stringify(input), "utf8");
103
+ if (plaintext.length === 0 || plaintext.length > MAX_PLAINTEXT_BYTES) {
104
+ throw new Error("Pairing message is too large.");
105
+ }
106
+ const messageId = randomUUID();
107
+ const nonce = randomBytes(12);
108
+ const cipher = createCipheriv("aes-256-gcm", pairingKey(options.invitationId, options.token), nonce);
109
+ cipher.setAAD(additionalData(options.direction, options.invitationId, messageId));
110
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
111
+ return {
112
+ protocol: PERSONAL_DEVICE_PAIRING_PROTOCOL,
113
+ message_id: messageId,
114
+ nonce: nonce.toString("base64url"),
115
+ ciphertext: ciphertext.toString("base64url"),
116
+ tag: cipher.getAuthTag().toString("base64url")
117
+ };
118
+ };
119
+ var decrypt = (input, options) => {
120
+ const envelope = parseEnvelope(input);
121
+ try {
122
+ const decipher = createDecipheriv("aes-256-gcm", pairingKey(options.invitationId, options.token), strictBase64url(envelope.nonce, 12));
123
+ decipher.setAAD(additionalData(options.direction, options.invitationId, envelope.message_id));
124
+ decipher.setAuthTag(strictBase64url(envelope.tag, 16));
125
+ const plaintext = Buffer.concat([
126
+ decipher.update(strictBase64url(envelope.ciphertext)),
127
+ decipher.final()
128
+ ]);
129
+ if (plaintext.length === 0 || plaintext.length > MAX_PLAINTEXT_BYTES) {
130
+ throw new Error("Pairing message is too large.");
131
+ }
132
+ const value = JSON.parse(plaintext.toString("utf8"));
133
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
134
+ throw new Error("Pairing message is invalid.");
135
+ }
136
+ return {
137
+ messageId: envelope.message_id,
138
+ value
139
+ };
140
+ } catch {
141
+ throw new Error("Pairing message authentication failed.");
142
+ }
143
+ };
144
+ var parseLink = (value) => {
145
+ if (typeof value !== "string" || value.length > 4096) {
146
+ throw new Error("Pairing link is invalid.");
147
+ }
148
+ const normalized = value.trim();
149
+ const match = /^http:\/\/([^/:?#]+):([1-9][0-9]{0,4})(\/pair\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}))#token=([A-Za-z0-9_-]{43})$/.exec(normalized);
150
+ if (!match)
151
+ throw new Error("Pairing link is invalid.");
152
+ let url;
153
+ try {
154
+ url = new URL(normalized);
155
+ } catch {
156
+ throw new Error("Pairing link is invalid.");
157
+ }
158
+ if (match[1] !== url.hostname || !isPrivateNetworkIpv4Address(url.hostname) || url.protocol !== "http:" || url.username || url.password || url.search || url.pathname !== match[3] || url.hash !== `#token=${match[5]}`) {
159
+ throw new Error("Same-network pairing requires a private-network or Tailscale link issued by Koed.");
160
+ }
161
+ return {
162
+ invitationUrl: url,
163
+ invitationId: match[4],
164
+ token: match[5]
165
+ };
166
+ };
167
+ var parseLoopbackOrigin = (value) => {
168
+ if (typeof value !== "string" || value.length > 2048) {
169
+ throw new Error("Local control URL must be an exact loopback origin.");
170
+ }
171
+ let url;
172
+ try {
173
+ url = new URL(value.trim());
174
+ } catch {
175
+ throw new Error("Local control URL must be an exact loopback origin.");
176
+ }
177
+ if (url.protocol !== "http:" && url.protocol !== "https:" || !isLoopbackHostname(url.hostname) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
178
+ throw new Error("Local control URL must be an exact loopback origin.");
179
+ }
180
+ return url.origin;
181
+ };
182
+ var readBoundedResponseText = async (response, classifyAmbiguousTransportFailure = false) => {
183
+ const rawContentLength = response.headers.get("content-length");
184
+ let declaredLength;
185
+ if (rawContentLength !== null) {
186
+ const contentLength = rawContentLength.trim();
187
+ if (!/^\d+$/.test(contentLength)) {
188
+ await response.body?.cancel().catch(() => void 0);
189
+ throw new Error("Pairing response is invalid.");
190
+ }
191
+ declaredLength = Number(contentLength);
192
+ if (!Number.isSafeInteger(declaredLength)) {
193
+ await response.body?.cancel().catch(() => void 0);
194
+ throw new Error("Pairing response is invalid.");
195
+ }
196
+ if (declaredLength > MAX_RESPONSE_BYTES) {
197
+ await response.body?.cancel().catch(() => void 0);
198
+ throw new Error("Pairing response exceeds maximum size.");
199
+ }
200
+ }
201
+ if (!response.body) {
202
+ if (classifyAmbiguousTransportFailure && declaredLength !== 0) {
203
+ throw new AmbiguousTransportFailure(new Error("Pairing response body was dropped."));
204
+ }
205
+ return "";
206
+ }
207
+ const reader = response.body.getReader();
208
+ const chunks = [];
209
+ let bytes = 0;
210
+ let overflow = false;
211
+ try {
212
+ while (true) {
213
+ const next = await reader.read();
214
+ if (next.done)
215
+ break;
216
+ if (next.value.byteLength > MAX_RESPONSE_BYTES - bytes) {
217
+ overflow = true;
218
+ await reader.cancel().catch(() => void 0);
219
+ throw new Error("Pairing response exceeds maximum size.");
220
+ }
221
+ bytes += next.value.byteLength;
222
+ chunks.push(next.value);
223
+ }
224
+ } catch (error) {
225
+ if (!overflow)
226
+ await reader.cancel().catch(() => void 0);
227
+ if (overflow || !classifyAmbiguousTransportFailure)
228
+ throw error;
229
+ throw new AmbiguousTransportFailure(error);
230
+ } finally {
231
+ reader.releaseLock();
232
+ }
233
+ if (declaredLength !== void 0 && bytes !== declaredLength) {
234
+ if (bytes < declaredLength && classifyAmbiguousTransportFailure) {
235
+ throw new AmbiguousTransportFailure(new Error("Pairing response body was truncated."));
236
+ }
237
+ throw new Error("Pairing response is invalid.");
238
+ }
239
+ if (bytes === 0 && classifyAmbiguousTransportFailure && declaredLength !== 0) {
240
+ throw new AmbiguousTransportFailure(new Error("Pairing response body was dropped."));
241
+ }
242
+ return Buffer.concat(chunks, bytes).toString("utf8");
243
+ };
244
+ var isTruncatedJson = (text, error) => {
245
+ const trimmed = text.trim();
246
+ if (!(error instanceof SyntaxError) || !trimmed.startsWith("{") && !trimmed.startsWith("[")) {
247
+ return false;
248
+ }
249
+ if (/unexpected end|unterminated string/i.test(error.message))
250
+ return true;
251
+ const position = /position (\d+)/i.exec(error.message)?.[1];
252
+ return position !== void 0 && Number(position) >= text.length;
253
+ };
254
+ var responseJson = async (response, classifyAmbiguousTransportFailure = false) => {
255
+ const text = await readBoundedResponseText(response, classifyAmbiguousTransportFailure && response.ok);
256
+ let value;
257
+ try {
258
+ value = JSON.parse(text);
259
+ } catch (error) {
260
+ if (classifyAmbiguousTransportFailure && response.ok && isTruncatedJson(text, error)) {
261
+ throw new AmbiguousTransportFailure(error);
262
+ }
263
+ throw new Error("Pairing response is invalid.", { cause: error });
264
+ }
265
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
266
+ throw new Error("Pairing response is invalid.");
267
+ }
268
+ if (!response.ok) {
269
+ const error = value;
270
+ throw new Error(typeof error.error === "string" ? error.error : "Pairing exchange failed.");
271
+ }
272
+ return value;
273
+ };
274
+ var redeemPersonalDevicePairing = async (options) => {
275
+ const { invitationUrl, invitationId, token } = parseLink(options.link);
276
+ const fetcher = options.fetch ?? globalThis.fetch;
277
+ if (!fetcher)
278
+ throw new Error("Pairing network access is unavailable.");
279
+ const exchange = async (payload, timeoutMs = 1e4, classifyTransportFailure = false) => {
280
+ const encrypted = encrypt(payload, {
281
+ invitationId,
282
+ token,
283
+ direction: "request"
284
+ });
285
+ let response;
286
+ try {
287
+ response = await fetcher(new URL(`/v1/pair/${invitationId}/exchange`, invitationUrl.origin), {
288
+ method: "POST",
289
+ headers: {
290
+ accept: "application/json",
291
+ "content-type": "application/json"
292
+ },
293
+ body: JSON.stringify(encrypted),
294
+ redirect: "error",
295
+ signal: AbortSignal.timeout(timeoutMs)
296
+ });
297
+ } catch (error) {
298
+ if (classifyTransportFailure)
299
+ throw new AmbiguousTransportFailure(error);
300
+ throw error;
301
+ }
302
+ const decrypted = decrypt(await responseJson(response, classifyTransportFailure), {
303
+ invitationId,
304
+ token,
305
+ direction: "response"
306
+ });
307
+ if (decrypted.messageId !== encrypted.message_id) {
308
+ throw new Error("Pairing response binding is invalid.");
309
+ }
310
+ return decrypted.value;
311
+ };
312
+ const invitationResponse = await exchange({ operation: "invitation" });
313
+ const invitation = invitationResponse.invitation;
314
+ if (!invitation || typeof invitation !== "object" || Array.isArray(invitation)) {
315
+ throw new Error("Pairing invitation is invalid.");
316
+ }
317
+ const typedInvitation = invitation;
318
+ const controlUrl = new URL(typedInvitation.control_url);
319
+ const relayUrl = new URL(typedInvitation.relay_url);
320
+ if (typedInvitation.protocol !== PERSONAL_DEVICE_PAIRING_PROTOCOL || controlUrl.origin !== invitationUrl.origin || controlUrl.pathname !== `/v1/pair/${invitationId}/exchange` || relayUrl.origin !== invitationUrl.origin || relayUrl.pathname !== "/pds") {
321
+ throw new Error("Pairing invitation endpoint binding is invalid.");
322
+ }
323
+ const controlFetch = (async (input, init) => {
324
+ const requestedUrl = new URL(input instanceof URL ? input.toString() : typeof input === "string" ? input : input.url);
325
+ const prefix = controlUrl.toString().replace(/\/$/, "");
326
+ if (requestedUrl.origin !== controlUrl.origin || !requestedUrl.toString().startsWith(`${prefix}/`)) {
327
+ throw new Error("Pairing control request escaped its invitation.");
328
+ }
329
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
330
+ if (method !== "GET" && method !== "POST")
331
+ throw new Error("Pairing control method is invalid.");
332
+ const body = init?.body === void 0 ? void 0 : typeof init.body === "string" ? init.body : (() => {
333
+ throw new Error("Pairing control body is invalid.");
334
+ })();
335
+ const headers = new Headers(init?.headers);
336
+ const result = await exchange({
337
+ operation: "control",
338
+ method,
339
+ path: requestedUrl.toString().slice(prefix.length),
340
+ headers: Object.fromEntries(["accept", "content-type"].flatMap((name) => {
341
+ const value = headers.get(name);
342
+ return value ? [[name, value]] : [];
343
+ })),
344
+ ...body === void 0 ? {} : { body }
345
+ }, 1e4);
346
+ if (typeof result.status !== "number" || typeof result.body !== "string") {
347
+ throw new Error("Pairing control response is invalid.");
348
+ }
349
+ return new Response(result.body, {
350
+ status: result.status,
351
+ headers: result.headers && typeof result.headers === "object" && !Array.isArray(result.headers) ? result.headers : { "content-type": "application/json; charset=utf-8" }
352
+ });
353
+ });
354
+ const join = await options.withJsonFd(typedInvitation, async (fd) => await options.runPersonalSync([
355
+ "join",
356
+ "request",
357
+ "--group-id",
358
+ typedInvitation.group_id,
359
+ "--invitation-fd",
360
+ String(fd)
361
+ ], {
362
+ environment: {
363
+ ...options.environment,
364
+ PDS_CONTROL_URL: controlUrl.toString(),
365
+ PDS_RUNTIME_SECRET_REF: options.environment.PDS_RUNTIME_SECRET_REF?.trim() || "pds-runtime"
366
+ },
367
+ pairingToken: token,
368
+ fetch: controlFetch
369
+ }));
370
+ const request = join.request;
371
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
372
+ throw new Error("Koed could not create the signed pairing request.");
373
+ }
374
+ const joinPairing = join.pairing;
375
+ if (!joinPairing || typeof joinPairing !== "object" || Array.isArray(joinPairing) || joinPairing.challengeId !== typedInvitation.challenge_id) {
376
+ throw new Error("Koed could not verify the pairing challenge binding.");
377
+ }
378
+ const submitted = await exchange({
379
+ operation: "request",
380
+ request,
381
+ device_label: options.deviceLabel.trim().slice(0, 80) || "SSH device"
382
+ }, 10 * 60 * 1e3);
383
+ if (submitted.approved !== true)
384
+ throw new Error("Pairing approval was not completed.");
385
+ const completed = await options.runPersonalSync([
386
+ "join",
387
+ "complete",
388
+ "--group-id",
389
+ typedInvitation.group_id,
390
+ "--challenge-id",
391
+ typedInvitation.challenge_id
392
+ ], {
393
+ environment: {
394
+ ...options.environment,
395
+ PDS_CONTROL_URL: controlUrl.toString(),
396
+ PDS_RUNTIME_SECRET_REF: options.environment.PDS_RUNTIME_SECRET_REF?.trim() || "pds-runtime"
397
+ },
398
+ pairingToken: token,
399
+ fetch: controlFetch
400
+ });
401
+ const reconciliation = completed.localGroupReconciliation;
402
+ if (!reconciliation || typeof reconciliation !== "object" || Array.isArray(reconciliation)) {
403
+ throw new Error("Koed could not verify the joining device's local sync state.");
404
+ }
405
+ const localCredential = readDesktopLocalCredentialAuthorization(options.koedHome);
406
+ if (!localCredential) {
407
+ throw new Error("Koed local Desktop credential is unavailable for SSH enrollment.");
408
+ }
409
+ const localRequest = async (path, body) => {
410
+ const localOrigin = parseLoopbackOrigin(options.localControlUrl);
411
+ const response = await fetcher(new URL(path, `${localOrigin}/`), {
412
+ method: "POST",
413
+ headers: {
414
+ accept: "application/json",
415
+ authorization: localCredential.authorization,
416
+ ...body ? { "content-type": "application/json" } : {}
417
+ },
418
+ ...body ? { body: JSON.stringify(body) } : {},
419
+ redirect: "error",
420
+ signal: AbortSignal.timeout(3e4)
421
+ });
422
+ return await responseJson(response);
423
+ };
424
+ const localGroup = await localRequest("/v1/personal-device-sync/local-group-reconciliation", reconciliation);
425
+ const localUserId = localGroup.local_user_id;
426
+ if (typeof localUserId !== "string")
427
+ throw new Error("Local Personal identity binding failed.");
428
+ await options.runPersonalSync([
429
+ "join",
430
+ "bind-local-user",
431
+ "--group-id",
432
+ typedInvitation.group_id,
433
+ "--user-id",
434
+ localUserId,
435
+ "--challenge-id",
436
+ typedInvitation.challenge_id
437
+ ], {
438
+ environment: {
439
+ ...options.environment,
440
+ PDS_CONTROL_URL: controlUrl.toString(),
441
+ PDS_RUNTIME_SECRET_REF: options.environment.PDS_RUNTIME_SECRET_REF?.trim() || "pds-runtime"
442
+ },
443
+ pairingToken: token,
444
+ fetch: controlFetch
445
+ });
446
+ await localRequest("/v1/personal-device-sync/local-runtime-wake");
447
+ let completion;
448
+ for (let attempt = 0; attempt < COMPLETION_ATTEMPTS; attempt += 1) {
449
+ try {
450
+ completion = await exchange({ operation: "complete" }, 1e4, true);
451
+ break;
452
+ } catch (error) {
453
+ if (!(error instanceof AmbiguousTransportFailure))
454
+ throw error;
455
+ if (attempt === COMPLETION_ATTEMPTS - 1)
456
+ throw error.originalError;
457
+ }
458
+ }
459
+ if (!completion || completion.completed !== true) {
460
+ throw new Error("Pairing invitation was not closed after enrollment.");
461
+ }
462
+ const publicResult = { ...completed };
463
+ delete publicResult.localGroupReconciliation;
464
+ return publicResult;
465
+ };
466
+
467
+ // packages/koed-server/dist/personal-sync.js
468
+ var KIT_FORMAT = "koed/pds-recovery-kit/v1";
469
+ var PENDING_VERSION = 1;
470
+ var MAX_PASSWORD_BYTES = 4096;
471
+ var MAX_CONTROL_RESPONSE_BYTES = 1048576;
472
+ var MAX_PENDING_REQUESTS = 32;
473
+ var fail = (message) => {
474
+ throw new Error(message);
475
+ };
476
+ var strictString = (value) => typeof value === "string" && value.length > 0 && value.length <= 1048576;
477
+ var b64 = (value) => value.toString("base64url");
478
+ var relayId = () => b64(randomBytes2(16));
479
+ var isRelayId = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{22}$/.test(value) && Buffer.from(value, "base64url").length === 16 && Buffer.from(value, "base64url").toString("base64url") === value;
480
+ var hash = (value) => createHash2("sha256").update(value).digest("base64url");
481
+ var fingerprint = (value) => hash(value).slice(0, 26);
482
+ var now = (deps) => (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
483
+ var pendingPath = (paths) => resolve(paths.configDir, "personal-sync-pending.json");
484
+ var exactKeys2 = (value, keys) => {
485
+ const actual = Object.keys(value).sort();
486
+ const expected = [...keys].sort();
487
+ return actual.length === expected.length && actual.every((key, i) => key === expected[i]);
488
+ };
489
+ var strictBase64url2 = (value, bytes) => {
490
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value))
491
+ fail("Recovery kit encoding is invalid.");
492
+ const encoded = value;
493
+ const decoded = Buffer.from(encoded, "base64url");
494
+ if (decoded.length !== bytes || decoded.toString("base64url") !== encoded)
495
+ fail("Recovery kit encoding is invalid.");
496
+ return decoded;
497
+ };
498
+ var recoveryKitMetadata = (kit) => JSON.stringify({
499
+ cipher: { name: kit.cipher.name, nonce: kit.cipher.nonce },
500
+ format: kit.format,
501
+ kdf: {
502
+ N: kit.kdf.N,
503
+ name: kit.kdf.name,
504
+ p: kit.kdf.p,
505
+ r: kit.kdf.r,
506
+ salt: kit.kdf.salt
507
+ },
508
+ version: kit.version
509
+ });
510
+ var validateRecoveryKit = (kit) => {
511
+ if (!kit || typeof kit !== "object" || Array.isArray(kit))
512
+ fail("Recovery kit format is invalid.");
513
+ const value = kit;
514
+ if (!exactKeys2(value, ["format", "version", "kdf", "cipher", "fingerprint"]))
515
+ fail("Recovery kit format is invalid.");
516
+ const kdf = value.kdf;
517
+ const cipher = value.cipher;
518
+ if (value.format !== KIT_FORMAT || value.version !== 1 || typeof value.fingerprint !== "string" || value.fingerprint.length !== 26 || !kdf || typeof kdf !== "object" || Array.isArray(kdf) || !cipher || typeof cipher !== "object" || Array.isArray(cipher) || !exactKeys2(kdf, [
519
+ "name",
520
+ "N",
521
+ "r",
522
+ "p",
523
+ "salt"
524
+ ]) || !exactKeys2(cipher, [
525
+ "name",
526
+ "nonce",
527
+ "ciphertext",
528
+ "tag"
529
+ ]) || kdf.name !== "scrypt" || kdf.N !== 32768 || kdf.r !== 8 || kdf.p !== 1 || cipher.name !== "aes-256-gcm")
530
+ fail("Recovery kit format is invalid.");
531
+ const parsed = value;
532
+ strictBase64url2(parsed.kdf.salt, 16);
533
+ strictBase64url2(parsed.cipher.nonce, 12);
534
+ strictBase64url2(parsed.cipher.tag, 16);
535
+ const ciphertext = Buffer.from(parsed.cipher.ciphertext, "base64url");
536
+ if (!/^[A-Za-z0-9_-]+$/.test(parsed.cipher.ciphertext) || ciphertext.length < 1 || ciphertext.length > MAX_CONTROL_RESPONSE_BYTES || ciphertext.toString("base64url") !== parsed.cipher.ciphertext)
537
+ fail("Recovery kit encoding is invalid.");
538
+ return parsed;
539
+ };
540
+ var deriveKey = (password, salt) => scryptSync(password, salt, 32, {
541
+ N: 32768,
542
+ r: 8,
543
+ p: 1,
544
+ maxmem: 64 * 1024 * 1024
545
+ });
546
+ var encryptRecoveryKit = (plaintext, password) => {
547
+ if (!password.length || password.length > MAX_PASSWORD_BYTES)
548
+ fail("Recovery password length is invalid.");
549
+ const kit = {
550
+ format: KIT_FORMAT,
551
+ version: 1,
552
+ kdf: { name: "scrypt", N: 32768, r: 8, p: 1, salt: b64(randomBytes2(16)) },
553
+ cipher: {
554
+ name: "aes-256-gcm",
555
+ nonce: b64(randomBytes2(12)),
556
+ ciphertext: "",
557
+ tag: ""
558
+ },
559
+ fingerprint: fingerprint(plaintext)
560
+ };
561
+ const cipher = createCipheriv2("aes-256-gcm", deriveKey(password, strictBase64url2(kit.kdf.salt, 16)), strictBase64url2(kit.cipher.nonce, 12));
562
+ cipher.setAAD(Buffer.from(recoveryKitMetadata(kit), "utf8"));
563
+ kit.cipher.ciphertext = b64(Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]));
564
+ kit.cipher.tag = b64(cipher.getAuthTag());
565
+ return kit;
566
+ };
567
+ var decryptRecoveryKit = (input, password) => {
568
+ const kit = validateRecoveryKit(input);
569
+ if (!password.length || password.length > MAX_PASSWORD_BYTES)
570
+ fail("Recovery password length is invalid.");
571
+ try {
572
+ const decipher = createDecipheriv2("aes-256-gcm", deriveKey(password, strictBase64url2(kit.kdf.salt, 16)), strictBase64url2(kit.cipher.nonce, 12));
573
+ decipher.setAAD(Buffer.from(recoveryKitMetadata(kit), "utf8"));
574
+ decipher.setAuthTag(strictBase64url2(kit.cipher.tag, 16));
575
+ const plaintext = Buffer.concat([
576
+ decipher.update(Buffer.from(kit.cipher.ciphertext, "base64url")),
577
+ decipher.final()
578
+ ]).toString("utf8");
579
+ if (fingerprint(plaintext) !== kit.fingerprint)
580
+ fail("Recovery kit fingerprint is invalid.");
581
+ return plaintext;
582
+ } catch (error) {
583
+ if (error instanceof Error && error.message === "Recovery kit fingerprint is invalid.")
584
+ throw error;
585
+ return fail("Recovery kit password or authentication tag is invalid.");
586
+ }
587
+ };
588
+ var rejectPasswordArguments = (args) => {
589
+ if (args.some((arg) => arg === "--password" || arg.startsWith("--password=")))
590
+ fail("Recovery passwords must use --password-stdin or --password-fd; password arguments are forbidden.");
591
+ };
592
+ var flag = (args, name) => {
593
+ const index = args.indexOf(name);
594
+ return index < 0 ? void 0 : args[index + 1];
595
+ };
596
+ var requiredFlag = (args, name) => flag(args, name)?.trim() || fail(`${name} is required.`);
597
+ var readBoundedFd = (raw, label, maximum = MAX_CONTROL_RESPONSE_BYTES) => {
598
+ const fd = Number.parseInt(raw ?? "", 10);
599
+ if (!Number.isInteger(fd) || fd < 0)
600
+ fail(`${label} must be a non-negative file descriptor.`);
601
+ const buffer = Buffer.allocUnsafe(maximum + 1);
602
+ let offset = 0;
603
+ while (offset < buffer.length) {
604
+ const count = readSync(fd, buffer, offset, buffer.length - offset, null);
605
+ if (count === 0)
606
+ break;
607
+ offset += count;
608
+ }
609
+ if (offset > maximum)
610
+ fail(`${label} exceeds maximum size.`);
611
+ return Buffer.from(buffer.subarray(0, offset));
612
+ };
613
+ var passwordFrom = (args) => {
614
+ rejectPasswordArguments(args);
615
+ const fd = flag(args, "--password-fd");
616
+ const stdin = args.includes("--password-stdin");
617
+ if (Boolean(fd) === stdin)
618
+ fail("Use exactly one of --password-stdin or --password-fd.");
619
+ const raw = readBoundedFd(stdin ? "0" : fd, "--password-fd", MAX_PASSWORD_BYTES);
620
+ const password = Buffer.from(raw.toString("utf8").replace(/\r?\n$/, ""));
621
+ raw.fill(0);
622
+ if (!password.length || password.length > MAX_PASSWORD_BYTES) {
623
+ password.fill(0);
624
+ fail("Recovery password length is invalid.");
625
+ }
626
+ return password;
627
+ };
628
+ var secureRegularFile = (path) => {
629
+ try {
630
+ const stat = lstatSync(path);
631
+ return stat.isFile() && !stat.isSymbolicLink() && (stat.mode & 63) === 0;
632
+ } catch {
633
+ return false;
634
+ }
635
+ };
636
+ var fsyncDirectory = (path) => {
637
+ const fd = openSync(path, "r");
638
+ try {
639
+ fsyncSync(fd);
640
+ } finally {
641
+ closeSync(fd);
642
+ }
643
+ };
644
+ var stageRecoveryKit = (path, kit) => {
645
+ const output = resolve(path);
646
+ const directory = dirname(output);
647
+ mkdirSync(directory, { recursive: true, mode: 448 });
648
+ if (existsSync(output))
649
+ fail("Recovery kit destination already exists.");
650
+ const temporary = `${output}.${process.pid}.${b64(randomBytes2(8))}.tmp`;
651
+ const fd = openSync(temporary, "wx", 384);
652
+ try {
653
+ writeFileSync(fd, `${JSON.stringify(kit, null, 2)}
654
+ `, "utf8");
655
+ fsyncSync(fd);
656
+ } finally {
657
+ closeSync(fd);
658
+ }
659
+ return {
660
+ output,
661
+ finalize: () => {
662
+ if (existsSync(output))
663
+ fail("Recovery kit destination already exists.");
664
+ renameSync(temporary, output);
665
+ fsyncDirectory(directory);
666
+ if (!secureRegularFile(output))
667
+ fail("Recovery kit permissions are unsafe; require 0600.");
668
+ },
669
+ abort: () => {
670
+ if (!existsSync(temporary))
671
+ return;
672
+ unlinkSync(temporary);
673
+ }
674
+ };
675
+ };
676
+ var writeRecoveryKit = (path, kit) => {
677
+ const staged = stageRecoveryKit(path, kit);
678
+ try {
679
+ staged.finalize();
680
+ } catch (error) {
681
+ try {
682
+ staged.abort();
683
+ } catch {
684
+ }
685
+ throw error;
686
+ }
687
+ };
688
+ var readPending = (paths) => {
689
+ const path = pendingPath(paths);
690
+ if (!existsSync(path))
691
+ return { version: PENDING_VERSION, pending: [] };
692
+ if (!secureRegularFile(path))
693
+ fail("Personal Sync pending state permissions are unsafe.");
694
+ try {
695
+ const value = JSON.parse(readFileSync(path, "utf8"));
696
+ if (value.version !== PENDING_VERSION || !Array.isArray(value.pending))
697
+ throw new Error();
698
+ const pending = value.pending.flatMap((entry) => {
699
+ if (!entry || typeof entry !== "object")
700
+ return [];
701
+ const request = entry;
702
+ return typeof request.requestId === "string" && typeof request.groupId === "string" && typeof request.createdAt === "string" ? [
703
+ {
704
+ requestId: request.requestId,
705
+ groupId: request.groupId,
706
+ createdAt: request.createdAt
707
+ }
708
+ ] : [];
709
+ });
710
+ if (pending.length !== value.pending.length || pending.length > MAX_PENDING_REQUESTS)
711
+ throw new Error();
712
+ return { version: PENDING_VERSION, pending };
713
+ } catch {
714
+ return fail("Personal Sync pending state is unreadable.");
715
+ }
716
+ };
717
+ var writePending = (paths, state) => {
718
+ const path = pendingPath(paths);
719
+ const directory = dirname(path);
720
+ mkdirSync(directory, { recursive: true, mode: 448 });
721
+ const temporary = `${path}.${process.pid}.${b64(randomBytes2(8))}.tmp`;
722
+ const fd = openSync(temporary, "wx", 384);
723
+ try {
724
+ writeFileSync(fd, `${JSON.stringify(state)}
725
+ `, "utf8");
726
+ fsyncSync(fd);
727
+ } finally {
728
+ closeSync(fd);
729
+ }
730
+ renameSync(temporary, path);
731
+ fsyncDirectory(directory);
732
+ };
733
+ var controlOrigin = (environment) => {
734
+ const configured = environment.PDS_CONTROL_URL?.trim();
735
+ const origin = configured ?? fail("Personal Sync control API is not configured (PDS_CONTROL_URL). ");
736
+ try {
737
+ const url = new URL(origin);
738
+ if (!/^https?:$/.test(url.protocol) || !url.hostname || url.username || url.password || url.search || url.hash)
739
+ throw new Error();
740
+ return url.toString().replace(/\/$/, "");
741
+ } catch {
742
+ return fail("PDS_CONTROL_URL must be an HTTP(S) origin without credentials.");
743
+ }
744
+ };
745
+ var browserOrigin = (environment) => {
746
+ const configured = environment.PDS_BROWSER_ORIGIN?.trim();
747
+ if (!configured)
748
+ fail("Personal Sync browser origin is not configured (PDS_BROWSER_ORIGIN).");
749
+ try {
750
+ const url = new URL(configured);
751
+ if (!/^https?:$/.test(url.protocol) || !url.hostname || url.username || url.password || url.pathname !== "/" || url.search || url.hash)
752
+ throw new Error();
753
+ return url.origin;
754
+ } catch {
755
+ return fail("PDS_BROWSER_ORIGIN must be an HTTP(S) origin.");
756
+ }
757
+ };
758
+ var browserSession = (environment) => {
759
+ const raw = environment.PDS_BROWSER_SESSION_FD;
760
+ if (raw === void 0)
761
+ fail("PDS browser session FD is required; API Tokens and legacy credentials are rejected.");
762
+ const bytes = readBoundedFd(raw, "PDS_BROWSER_SESSION_FD", 16384);
763
+ const value = bytes.toString("utf8").trim();
764
+ bytes.fill(0);
765
+ if (!value || /[\r\n\0]/.test(value))
766
+ fail("PDS browser session FD is invalid.");
767
+ return value;
768
+ };
769
+ var readBoundedResponseText2 = async (response) => {
770
+ const rawContentLength = response.headers.get("content-length");
771
+ if (rawContentLength !== null) {
772
+ const contentLength = rawContentLength.trim();
773
+ if (!/^\d+$/.test(contentLength)) {
774
+ await response.body?.cancel().catch(() => void 0);
775
+ fail("PDS control response is invalid.");
776
+ }
777
+ const declaredLength = Number(contentLength);
778
+ if (!Number.isSafeInteger(declaredLength)) {
779
+ await response.body?.cancel().catch(() => void 0);
780
+ fail("PDS control response is invalid.");
781
+ }
782
+ if (declaredLength > MAX_CONTROL_RESPONSE_BYTES) {
783
+ await response.body?.cancel().catch(() => void 0);
784
+ fail("PDS control response exceeds maximum size.");
785
+ }
786
+ }
787
+ if (!response.body)
788
+ return "";
789
+ const reader = response.body.getReader();
790
+ const chunks = [];
791
+ let bytes = 0;
792
+ let overflow = false;
793
+ try {
794
+ while (true) {
795
+ const next = await reader.read();
796
+ if (next.done)
797
+ break;
798
+ if (next.value.byteLength > MAX_CONTROL_RESPONSE_BYTES - bytes) {
799
+ overflow = true;
800
+ await reader.cancel().catch(() => void 0);
801
+ fail("PDS control response exceeds maximum size.");
802
+ }
803
+ bytes += next.value.byteLength;
804
+ chunks.push(next.value);
805
+ }
806
+ } catch (error) {
807
+ if (!overflow)
808
+ await reader.cancel().catch(() => void 0);
809
+ throw error;
810
+ } finally {
811
+ reader.releaseLock();
812
+ }
813
+ return Buffer.concat(chunks, bytes).toString("utf8");
814
+ };
815
+ var strictResponse = async (response) => {
816
+ const text = await readBoundedResponseText2(response);
817
+ let value;
818
+ try {
819
+ value = JSON.parse(text);
820
+ } catch {
821
+ fail("PDS control response is invalid.");
822
+ }
823
+ if (!value || typeof value !== "object" || Array.isArray(value))
824
+ fail("PDS control response is invalid.");
825
+ if (!response.ok) {
826
+ const body = value;
827
+ const message = typeof body.message === "string" ? body.message : typeof body.error === "string" ? body.error : null;
828
+ fail(message ? `PDS control API rejected request: ${message}` : `PDS control API rejected request (${response.status}).`);
829
+ }
830
+ return value;
831
+ };
832
+ var control = async (input) => {
833
+ const fetcher = input.deps.fetch ?? globalThis.fetch;
834
+ if (!fetcher)
835
+ fail("PDS control API is unavailable.");
836
+ const desktopAuthorization = input.deps.desktopAuthorization?.trim();
837
+ const pairingToken = input.deps.pairingToken?.trim();
838
+ const sessionCookie = desktopAuthorization || pairingToken ? null : input.deps.sessionCookie ?? browserSession(input.environment);
839
+ const timeout = AbortSignal.timeout(1e4);
840
+ const response = await fetcher(`${controlOrigin(input.environment)}${input.path}`, {
841
+ method: input.method,
842
+ headers: {
843
+ accept: "application/json",
844
+ ...desktopAuthorization ? { authorization: desktopAuthorization } : pairingToken ? {} : {
845
+ cookie: sessionCookie,
846
+ origin: browserOrigin(input.environment)
847
+ },
848
+ ...input.body ? { "content-type": "application/json" } : {}
849
+ },
850
+ ...input.body ? { body: JSON.stringify(input.body) } : {},
851
+ signal: timeout,
852
+ redirect: "error"
853
+ });
854
+ return strictResponse(response);
855
+ };
856
+ var generatedKey = (kind) => {
857
+ const pair = kind === "ed25519" ? generateKeyPairSync("ed25519") : generateKeyPairSync("x25519");
858
+ const publicJwk = pair.publicKey.export({ format: "jwk" });
859
+ const privateJwk = pair.privateKey.export({ format: "jwk" });
860
+ const publicKey = publicJwk.x;
861
+ const privateSeed = privateJwk.d;
862
+ if (typeof publicKey !== "string" || typeof privateSeed !== "string" || publicKey.length !== 43 || privateSeed.length !== 43)
863
+ fail(`Generated ${kind} key material is invalid.`);
864
+ return {
865
+ publicKey,
866
+ privateSeed,
867
+ privateKey: pair.privateKey
868
+ };
869
+ };
870
+ var personalSyncProviderEnvironment = (environment) => ({
871
+ PATH: environment.PATH,
872
+ HOME: environment.HOME,
873
+ USER: environment.USER,
874
+ LANG: environment.LANG,
875
+ LC_ALL: environment.LC_ALL,
876
+ KOED_HOME: environment.KOED_HOME,
877
+ ELECTRON_RUN_AS_NODE: environment.ELECTRON_RUN_AS_NODE
878
+ });
879
+ var providerArgs = (environment) => {
880
+ const raw = environment.PDS_SECRET_PROVIDER_COMMAND_ARGS_JSON;
881
+ if (!raw)
882
+ return [];
883
+ try {
884
+ const parsed = JSON.parse(raw);
885
+ if (!Array.isArray(parsed) || parsed.length > 8 || parsed.some((value) => typeof value !== "string" || value.length > 4096)) {
886
+ throw new Error();
887
+ }
888
+ return parsed;
889
+ } catch {
890
+ fail("PDS secret provider arguments are invalid.");
891
+ return [];
892
+ }
893
+ };
894
+ var validSecretProvider = (environment) => environment.PDS_SECRET_PROVIDER?.trim() === "headless";
895
+ var runSecretProvider = async (operation, reference, environment, value) => {
896
+ const provider = environment.PDS_SECRET_PROVIDER?.trim();
897
+ const command = environment.PDS_SECRET_PROVIDER_COMMAND?.trim();
898
+ if (!provider && !command) {
899
+ const result = await runApplicationSecretProvider(operation, reference, value, environment);
900
+ return { ok: result.ok, stdout: result.value ?? "" };
901
+ }
902
+ if (!validSecretProvider(environment) || !command || !/^[^\s\r\n\0]+$/.test(command) || !/^[^\r\n\0]{1,240}$/.test(reference))
903
+ fail("A valid PDS secret provider is required.");
904
+ return await new Promise((resolvePromise) => {
905
+ const child = spawn(command, [...providerArgs(environment), operation, reference], {
906
+ env: personalSyncProviderEnvironment(environment),
907
+ stdio: ["pipe", "pipe", "ignore"],
908
+ windowsHide: true
909
+ });
910
+ let stdout = "";
911
+ let settled = false;
912
+ const finish = (ok) => {
913
+ if (settled)
914
+ return;
915
+ settled = true;
916
+ clearTimeout(timeout);
917
+ resolvePromise({ ok, stdout });
918
+ };
919
+ const timeout = setTimeout(() => {
920
+ child.kill();
921
+ finish(false);
922
+ }, 1e4);
923
+ child.stdout.setEncoding("utf8");
924
+ child.stdout.on("data", (chunk) => {
925
+ stdout += chunk;
926
+ if (Buffer.byteLength(stdout, "utf8") > MAX_CONTROL_RESPONSE_BYTES) {
927
+ child.kill();
928
+ finish(false);
929
+ }
930
+ });
931
+ child.once("error", () => finish(false));
932
+ child.once("close", (code) => finish(code === 0));
933
+ child.stdin.end(value ?? "");
934
+ });
935
+ };
936
+ var putProviderSecret = async (reference, value, environment) => {
937
+ const result = await runSecretProvider("put", reference, environment, value);
938
+ if (!result.ok)
939
+ fail("PDS secret provider rejected runtime material.");
940
+ };
941
+ var getProviderSecret = async (reference, environment) => {
942
+ const result = await runSecretProvider("get", reference, environment);
943
+ return result.ok && result.stdout.length > 0 ? result.stdout : null;
944
+ };
945
+ var deleteProviderSecret = async (reference, environment) => {
946
+ const result = await runSecretProvider("delete", reference, environment);
947
+ if (!result.ok)
948
+ fail("PDS secret provider could not delete pending material.");
949
+ };
950
+ var pendingSecretReference = (runtimeReference, challengeId) => `pds-pending-${hash(runtimeReference).slice(0, 20)}-${challengeId}`;
951
+ var parsedSecret = (raw, expectedKeys, label) => {
952
+ if (!raw || Buffer.byteLength(raw, "utf8") > MAX_CONTROL_RESPONSE_BYTES)
953
+ fail(`${label} is unavailable.`);
954
+ try {
955
+ const value = JSON.parse(raw);
956
+ if (!value || typeof value !== "object" || Array.isArray(value) || !(Array.isArray(expectedKeys[0]) ? expectedKeys.some((keys) => exactKeys2(value, keys)) : exactKeys2(value, expectedKeys)))
957
+ fail(`${label} is invalid.`);
958
+ return value;
959
+ } catch (error) {
960
+ if (error instanceof Error && error.message === `${label} is invalid.`)
961
+ throw error;
962
+ return fail(`${label} is invalid.`);
963
+ }
964
+ };
965
+ var runtimeSecret = async (reference, environment, deps) => {
966
+ const value = parsedSecret(await (deps.getSecret ?? getProviderSecret)(reference, environment), [
967
+ [
968
+ "version",
969
+ "userId",
970
+ "relayUrl",
971
+ "groupId",
972
+ "device",
973
+ "authority",
974
+ "recovery",
975
+ "certificate",
976
+ "recipientCertificates",
977
+ "groupSecrets"
978
+ ],
979
+ [
980
+ "version",
981
+ "userId",
982
+ "relayUrl",
983
+ "groupId",
984
+ "device",
985
+ "authority",
986
+ "recovery",
987
+ "certificate",
988
+ "recipientCertificates",
989
+ "historicalOriginCertificates",
990
+ "groupSecrets"
991
+ ]
992
+ ].flatMap((fields) => [fields, [...fields, "historicalProjectKeys"]]), "PDS runtime secret");
993
+ if (value.version !== 1 || !Object.values(value.device).every(strictString) || !Object.values(value.authority).every(strictString) || !Object.values(value.recovery).every(strictString) || value.historicalProjectKeys !== void 0 && !pdsRepositoryKeyHistorySchema.safeParse(value.historicalProjectKeys).success || !Object.values(value.groupSecrets).every(strictString) || !Array.isArray(value.recipientCertificates) || !value.recipientCertificates.every(strictString) || value.historicalOriginCertificates !== void 0 && (!Array.isArray(value.historicalOriginCertificates) || !value.historicalOriginCertificates.every(strictString)))
994
+ fail("PDS runtime secret is invalid.");
995
+ return value;
996
+ };
997
+ var optionalRuntimeSecret = async (reference, environment, deps) => {
998
+ const raw = await (deps.getSecret ?? getProviderSecret)(reference, environment);
999
+ if (raw === null)
1000
+ return null;
1001
+ return await runtimeSecret(reference, environment, {
1002
+ ...deps,
1003
+ getSecret: () => raw
1004
+ });
1005
+ };
1006
+ var certificateDeviceId = (certificate) => {
1007
+ const parsed = parseCanonicalPdsJson(certificate);
1008
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1009
+ fail("PDS membership certificate is invalid.");
1010
+ }
1011
+ const deviceId = parsed.deviceId;
1012
+ if (typeof deviceId !== "string") {
1013
+ fail("PDS membership certificate is invalid.");
1014
+ }
1015
+ return deviceId;
1016
+ };
1017
+ var validatedRuntimeSecret = (runtime, certificates) => {
1018
+ if (!isRelayId(runtime.device.originDeploymentId)) {
1019
+ fail("PDS origin deployment identity is invalid.");
1020
+ }
1021
+ const ordered = certificates.map((certificate) => ({
1022
+ certificate,
1023
+ deviceId: certificateDeviceId(certificate)
1024
+ })).sort((left, right) => Buffer.from(left.deviceId, "ascii").compare(Buffer.from(right.deviceId, "ascii")));
1025
+ if (ordered.length === 0 || new Set(ordered.map(({ deviceId }) => deviceId)).size !== ordered.length) {
1026
+ fail("PDS membership certificate set is invalid.");
1027
+ }
1028
+ const own = ordered.find(({ deviceId }) => deviceId === runtime.device.id) ?? fail("PDS local membership certificate is unavailable.");
1029
+ const candidate = {
1030
+ ...runtime,
1031
+ certificate: own.certificate,
1032
+ recipientCertificates: ordered.map(({ certificate }) => certificate)
1033
+ };
1034
+ createPdsSessionPackageRuntimeContext({
1035
+ authorityPublicKey: candidate.authority.publicKey,
1036
+ authorityKeyId: candidate.authority.keyId,
1037
+ groupId: candidate.groupId,
1038
+ authorityHead: candidate.authority.head,
1039
+ currentEpoch: candidate.groupSecrets.currentEpoch,
1040
+ servingCertificate: candidate.certificate,
1041
+ recipientCertificate: candidate.certificate,
1042
+ recipientCertificates: candidate.recipientCertificates,
1043
+ historicalOriginCertificates: candidate.historicalOriginCertificates
1044
+ });
1045
+ return candidate;
1046
+ };
1047
+ var pendingEnrollmentSecret = async (reference, environment, deps) => {
1048
+ const value = parsedSecret(await (deps.getSecret ?? getProviderSecret)(reference, environment), [
1049
+ "version",
1050
+ "groupId",
1051
+ "userId",
1052
+ "browserDeploymentId",
1053
+ "localDeploymentId",
1054
+ "relayUrl",
1055
+ "originDeploymentId",
1056
+ "deviceId",
1057
+ "signingKeyId",
1058
+ "signingPublicKey",
1059
+ "signingPrivateSeed",
1060
+ "kemKeyId",
1061
+ "kemPublicKey",
1062
+ "kemPrivateSeed",
1063
+ "authorityKeyId",
1064
+ "authorityPublicKey",
1065
+ "challengeId",
1066
+ "challenge",
1067
+ "expiresAt"
1068
+ ], "PDS pending enrollment secret");
1069
+ if (value.version !== 1 || !Object.entries(value).filter(([key]) => key !== "version").every(([, field]) => strictString(field)) || !isRelayId(value.originDeploymentId))
1070
+ fail("PDS pending enrollment secret is invalid.");
1071
+ return value;
1072
+ };
1073
+ var challengeBinding = async (input) => {
1074
+ const response = await control({
1075
+ environment: input.environment,
1076
+ deps: input.deps,
1077
+ method: "POST",
1078
+ path: "/v1/personal-device-sync/challenges",
1079
+ body: {
1080
+ ...input.groupId ? { group_id: input.groupId } : {},
1081
+ challenge_hash: pdsSha256(Buffer.from(input.challenge, "base64url"))
1082
+ }
1083
+ });
1084
+ const challenge = object(response.challenge, "challenge");
1085
+ const authority = object(challenge.authority, "challenge.authority");
1086
+ const values = {
1087
+ id: challenge.id,
1088
+ expiresAt: challenge.expires_at,
1089
+ browserSubjectId: challenge.browser_subject_id,
1090
+ browserDeploymentId: challenge.browser_deployment_id,
1091
+ authorityKeyId: authority.key_id,
1092
+ authorityPublicKey: authority.public_key
1093
+ };
1094
+ if (!Object.values(values).every((value) => typeof value === "string"))
1095
+ fail("PDS control response challenge binding is invalid.");
1096
+ strictBase64url2(values.authorityPublicKey, 32);
1097
+ return values;
1098
+ };
1099
+ var pairingInvitationBinding = (invitation, groupId) => {
1100
+ if (!exactKeys2(invitation, [
1101
+ "protocol",
1102
+ "group_id",
1103
+ "challenge_id",
1104
+ "challenge",
1105
+ "expires_at",
1106
+ "browser_subject_id",
1107
+ "browser_deployment_id",
1108
+ "authority",
1109
+ "control_url",
1110
+ "relay_url"
1111
+ ]) || invitation.protocol !== "koed/pds-lan-pair/v1" || invitation.group_id !== groupId) {
1112
+ fail("PDS pairing invitation is invalid.");
1113
+ }
1114
+ const authority = object(invitation.authority, "pairing invitation authority");
1115
+ if (!exactKeys2(authority, ["key_id", "public_key"])) {
1116
+ fail("PDS pairing invitation authority is invalid.");
1117
+ }
1118
+ const values = {
1119
+ challenge: invitation.challenge,
1120
+ id: invitation.challenge_id,
1121
+ expiresAt: invitation.expires_at,
1122
+ browserSubjectId: invitation.browser_subject_id,
1123
+ browserDeploymentId: invitation.browser_deployment_id,
1124
+ authorityKeyId: authority.key_id,
1125
+ authorityPublicKey: authority.public_key
1126
+ };
1127
+ if (!Object.values(values).every(strictString)) {
1128
+ fail("PDS pairing invitation is invalid.");
1129
+ }
1130
+ strictBase64url2(values.challenge, 32);
1131
+ strictBase64url2(values.authorityPublicKey, 32);
1132
+ const expiresAt = new Date(values.expiresAt);
1133
+ const remaining = expiresAt.getTime() - Date.now();
1134
+ if (!Number.isFinite(expiresAt.getTime()) || remaining <= 0 || remaining > 10 * 60 * 1e3) {
1135
+ fail("PDS pairing invitation is expired or has an invalid lifetime.");
1136
+ }
1137
+ for (const [field, value] of [
1138
+ ["control", invitation.control_url],
1139
+ ["relay", invitation.relay_url]
1140
+ ]) {
1141
+ try {
1142
+ const url = new URL(value);
1143
+ if (url.protocol !== "http:" && url.protocol !== "https:" || !url.hostname || url.username || url.password || url.search || url.hash) {
1144
+ throw new Error();
1145
+ }
1146
+ } catch {
1147
+ fail(`PDS pairing invitation ${field} URL is invalid.`);
1148
+ }
1149
+ }
1150
+ return values;
1151
+ };
1152
+ var createPairingInvitation = async (args, environment, deps) => {
1153
+ const groupId = groupIdFrom(args);
1154
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for pairing.");
1155
+ const runtime = await runtimeSecret(runtimeReference, environment, deps);
1156
+ if (runtime.groupId !== groupId) {
1157
+ fail("PDS pairing invitation targets another group.");
1158
+ }
1159
+ const challenge = b64(randomBytes2(32));
1160
+ const binding = await challengeBinding({
1161
+ groupId,
1162
+ challenge,
1163
+ environment,
1164
+ deps
1165
+ });
1166
+ return {
1167
+ ok: true,
1168
+ state: "inviting",
1169
+ message: "Pairing invitation created.",
1170
+ invitation: {
1171
+ protocol: "koed/pds-lan-pair/v1",
1172
+ group_id: groupId,
1173
+ challenge_id: binding.id,
1174
+ challenge,
1175
+ expires_at: binding.expiresAt,
1176
+ browser_subject_id: binding.browserSubjectId,
1177
+ browser_deployment_id: binding.browserDeploymentId,
1178
+ authority: {
1179
+ key_id: binding.authorityKeyId,
1180
+ public_key: binding.authorityPublicKey
1181
+ },
1182
+ relay_url: controlOrigin(environment)
1183
+ }
1184
+ };
1185
+ };
1186
+ var enrollmentProof = (input) => {
1187
+ const unsigned = {
1188
+ challengeId: input.challengeId,
1189
+ challenge: input.challenge,
1190
+ groupId: input.groupId ?? null,
1191
+ deviceId: input.deviceId,
1192
+ deviceSigningKeyId: input.signingKeyId,
1193
+ deviceSigningPublicKey: input.signingPublicKey,
1194
+ deviceKemKeyId: input.kemKeyId,
1195
+ deviceKemPublicKey: input.kemPublicKey,
1196
+ browserSubjectId: input.browserSubjectId,
1197
+ browserDeploymentId: input.browserDeploymentId,
1198
+ expiresAt: input.expiresAt
1199
+ };
1200
+ return {
1201
+ challenge_id: input.challengeId,
1202
+ challenge: input.challenge,
1203
+ device_id: input.deviceId,
1204
+ signature: sign(null, Buffer.from(`${PDS_PROTOCOL}/enrollment-proof
1205
+ ${canonicalizePdsJson(unsigned)}`, "utf8"), input.signingPrivateKey).toString("base64url"),
1206
+ expires_at: input.expiresAt
1207
+ };
1208
+ };
1209
+ var bootstrapGroup = async (args, paths, environment, deps) => {
1210
+ const controlDeps = deps.desktopAuthorization || deps.pairingToken ? deps : {
1211
+ ...deps,
1212
+ sessionCookie: deps.sessionCookie ?? browserSession(environment)
1213
+ };
1214
+ const recoveryKitPath = flag(args, "--recovery-kit");
1215
+ const runtimeSecretRef = flag(args, "--runtime-secret-ref")?.trim() || environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF or --runtime-secret-ref is required for bootstrap.");
1216
+ const password = recoveryKitPath ? passwordFrom(args) : randomBytes2(32);
1217
+ const identity = deps.identity ?? await ensureDeviceIdentity(paths, { environment });
1218
+ if (!identity.remoteOperationsAllowed || !identity.deploymentId || !identity.deviceInstanceId) {
1219
+ password.fill(0);
1220
+ fail("A healthy Koed device identity is required for bootstrap.");
1221
+ }
1222
+ const deviceInstanceId = relayId();
1223
+ const groupId = relayId();
1224
+ const signingKey = generatedKey("ed25519");
1225
+ const kemKey = generatedKey("x25519");
1226
+ const recoverySigningKey = generatedKey("ed25519");
1227
+ const recoveryKemKey = generatedKey("x25519");
1228
+ const challengeValue = b64(randomBytes2(32));
1229
+ const binding = await challengeBinding({
1230
+ challenge: challengeValue,
1231
+ environment,
1232
+ deps: controlDeps
1233
+ });
1234
+ const signingKeyId = relayId();
1235
+ const kemKeyId = relayId();
1236
+ const recoverySigningKeyId = relayId();
1237
+ const recoveryKemKeyId = relayId();
1238
+ const groupSecrets = {
1239
+ currentEpoch: "1",
1240
+ contentKey: b64(randomBytes2(32)),
1241
+ sourceFingerprintKey: b64(randomBytes2(32)),
1242
+ tombstoneFloorKey: b64(randomBytes2(32)),
1243
+ projectAliasKey: b64(randomBytes2(32))
1244
+ };
1245
+ const recoveryPayload = {
1246
+ version: 1,
1247
+ groupId,
1248
+ recoverySigningKeyId,
1249
+ recoverySigningPublicKey: recoverySigningKey.publicKey,
1250
+ recoverySigningPrivateSeed: recoverySigningKey.privateSeed,
1251
+ recoveryKemKeyId,
1252
+ recoveryKemPublicKey: recoveryKemKey.publicKey,
1253
+ recoveryKemPrivateSeed: recoveryKemKey.privateSeed,
1254
+ groupSecrets
1255
+ };
1256
+ const recoveryPlaintext = JSON.stringify(recoveryPayload);
1257
+ const kit = encryptRecoveryKit(recoveryPlaintext, password);
1258
+ let stagedRecoveryKit;
1259
+ try {
1260
+ if (decryptRecoveryKit(kit, password) !== recoveryPlaintext)
1261
+ fail("Recovery kit round-trip verification failed.");
1262
+ if (recoveryKitPath)
1263
+ stagedRecoveryKit = stageRecoveryKit(recoveryKitPath, kit);
1264
+ } finally {
1265
+ password.fill(0);
1266
+ }
1267
+ const draft = {
1268
+ protocol: PDS_PROTOCOL,
1269
+ kind: "genesis",
1270
+ groupId,
1271
+ sequence: "1",
1272
+ previousHash: null,
1273
+ body: {
1274
+ authorityKeyId: binding.authorityKeyId,
1275
+ authorityPublicKey: binding.authorityPublicKey,
1276
+ recoverySigningKeyId,
1277
+ recoverySigningPublicKey: recoverySigningKey.publicKey,
1278
+ recoveryKemKeyId,
1279
+ recoveryKemPublicKey: recoveryKemKey.publicKey,
1280
+ recoveryKitHash: pdsSha256(recoveryPlaintext),
1281
+ recoveryKitVerified: true,
1282
+ initialDeviceId: deviceInstanceId,
1283
+ initialDeviceSigningKeyId: signingKeyId,
1284
+ initialDeviceSigningPublicKey: signingKey.publicKey,
1285
+ initialDeviceKemKeyId: kemKeyId,
1286
+ initialDeviceKemPublicKey: kemKey.publicKey,
1287
+ operationFamilies: ["pds_relay"],
1288
+ initialEpoch: "1",
1289
+ initialKeyCommitment: pdsSha256(Buffer.from(groupSecrets.contentKey, "base64url"))
1290
+ }
1291
+ };
1292
+ const statement = {
1293
+ draft,
1294
+ authorization: {
1295
+ signerKeyId: signingKeyId,
1296
+ signature: signPdsGroupDraft(draft, signingKey.privateKey)
1297
+ }
1298
+ };
1299
+ validatePdsGroupStatement(statement, {
1300
+ authorizationPublicKey: signingKey.publicKey,
1301
+ expectedAuthorizationKeyId: signingKeyId,
1302
+ expectedGroupId: groupId,
1303
+ expectedPreviousHash: null,
1304
+ expectedSequence: "1"
1305
+ });
1306
+ const proof = enrollmentProof({
1307
+ challengeId: binding.id,
1308
+ challenge: challengeValue,
1309
+ groupId,
1310
+ deviceId: deviceInstanceId,
1311
+ signingKeyId,
1312
+ signingPublicKey: signingKey.publicKey,
1313
+ kemKeyId,
1314
+ kemPublicKey: kemKey.publicKey,
1315
+ browserSubjectId: binding.browserSubjectId,
1316
+ browserDeploymentId: binding.browserDeploymentId,
1317
+ expiresAt: binding.expiresAt,
1318
+ signingPrivateKey: signingKey.privateKey
1319
+ });
1320
+ let created;
1321
+ try {
1322
+ created = await control({
1323
+ environment,
1324
+ deps: controlDeps,
1325
+ method: "POST",
1326
+ path: "/v1/personal-device-sync/groups/genesis",
1327
+ body: {
1328
+ statement: canonicalizePdsJson(statement),
1329
+ proof,
1330
+ first_device: {
1331
+ device_id: deviceInstanceId,
1332
+ signing_key_id: signingKeyId,
1333
+ signing_public_key: signingKey.publicKey,
1334
+ kem_key_id: kemKeyId,
1335
+ kem_public_key: kemKey.publicKey,
1336
+ operation_families: ["pds_relay"]
1337
+ }
1338
+ }
1339
+ });
1340
+ stagedRecoveryKit?.finalize();
1341
+ } catch (error) {
1342
+ try {
1343
+ stagedRecoveryKit?.abort();
1344
+ } catch {
1345
+ }
1346
+ throw error;
1347
+ }
1348
+ const group = object(created.group, "group");
1349
+ const head = object(group.head, "group.head");
1350
+ if (group.group_id !== groupId || typeof head.hash !== "string" || group.current_epoch !== "1")
1351
+ fail("PDS genesis response is invalid.");
1352
+ const headHash = head.hash;
1353
+ const certificateResponse = await control({
1354
+ environment,
1355
+ deps: controlDeps,
1356
+ method: "GET",
1357
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/certificates/${encodeURIComponent(deviceInstanceId)}`
1358
+ });
1359
+ const certificate = object(certificateResponse.certificate, "certificate");
1360
+ const runtime = validatedRuntimeSecret({
1361
+ version: 1,
1362
+ userId: binding.browserSubjectId,
1363
+ relayUrl: controlOrigin(environment),
1364
+ groupId,
1365
+ device: {
1366
+ id: deviceInstanceId,
1367
+ originDeploymentId: relayId(),
1368
+ signingKeyId,
1369
+ signingPrivateSeed: signingKey.privateSeed,
1370
+ kemKeyId,
1371
+ kemPrivateSeed: kemKey.privateSeed
1372
+ },
1373
+ authority: {
1374
+ keyId: binding.authorityKeyId,
1375
+ publicKey: binding.authorityPublicKey,
1376
+ head: headHash
1377
+ },
1378
+ recovery: {
1379
+ signingKeyId: recoverySigningKeyId,
1380
+ signingPublicKey: recoverySigningKey.publicKey
1381
+ },
1382
+ groupSecrets
1383
+ }, [canonicalizePdsJson(certificate)]);
1384
+ await (deps.putSecret ?? putProviderSecret)(runtimeSecretRef, JSON.stringify(runtime), environment);
1385
+ await control({
1386
+ environment,
1387
+ deps: controlDeps,
1388
+ method: "PUT",
1389
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/policy`,
1390
+ body: {
1391
+ enabled: true,
1392
+ future_closed_sessions_only: true,
1393
+ historical_backfill_enabled: false
1394
+ }
1395
+ });
1396
+ return {
1397
+ ok: true,
1398
+ state: "active",
1399
+ message: "Personal Device Group created. The secure runtime is active.",
1400
+ groupId,
1401
+ deviceId: deviceInstanceId,
1402
+ ...recoveryKitPath ? { recoveryKit: resolve(recoveryKitPath) } : {}
1403
+ };
1404
+ };
1405
+ var groupIdFrom = (args) => {
1406
+ const groupId = requiredFlag(args, "--group-id");
1407
+ if (!/^[\x21-\x7e]{1,240}$/.test(groupId))
1408
+ fail("--group-id is invalid.");
1409
+ return groupId;
1410
+ };
1411
+ var object = (value, field) => {
1412
+ if (!value || typeof value !== "object" || Array.isArray(value))
1413
+ fail(`PDS control response ${field} is invalid.`);
1414
+ return value;
1415
+ };
1416
+ var status = async (environment, deps) => {
1417
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim();
1418
+ const runtime = runtimeReference ? await optionalRuntimeSecret(runtimeReference, environment, deps) : null;
1419
+ try {
1420
+ const response = await control({
1421
+ environment,
1422
+ deps,
1423
+ method: "GET",
1424
+ path: "/v1/personal-device-sync/groups"
1425
+ });
1426
+ if (!Array.isArray(response.groups))
1427
+ fail("PDS control response groups is invalid.");
1428
+ if (!Array.isArray(response.pairing_invitation_group_ids) || response.pairing_invitation_group_ids.some((groupId) => typeof groupId !== "string")) {
1429
+ fail("PDS control response pairing capability is invalid.");
1430
+ }
1431
+ const groups = await Promise.all(response.groups.map(async (entry) => {
1432
+ const group = object(entry, "group");
1433
+ if (typeof group.group_id !== "string")
1434
+ return group;
1435
+ try {
1436
+ const local = await control({
1437
+ environment,
1438
+ deps,
1439
+ method: "GET",
1440
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(group.group_id)}/local-status`
1441
+ });
1442
+ return { ...group, local_sync: local.status ?? null };
1443
+ } catch {
1444
+ return { ...group, local_sync: null };
1445
+ }
1446
+ }));
1447
+ if (groups.length || !runtime) {
1448
+ return {
1449
+ ok: true,
1450
+ state: "backend",
1451
+ message: "Personal Sync status is Authority-owned.",
1452
+ local_device_id: runtime?.device.id ?? null,
1453
+ groups,
1454
+ pairing_invitation_group_ids: response.pairing_invitation_group_ids
1455
+ };
1456
+ }
1457
+ return {
1458
+ ok: true,
1459
+ state: "local_binding_missing",
1460
+ message: "A protected Personal Sync runtime exists, but this local Personal database is not enrolled in its group.",
1461
+ groups: [],
1462
+ pairing_invitation_group_ids: [],
1463
+ recoveryRequired: true
1464
+ };
1465
+ } catch (error) {
1466
+ if (!runtime)
1467
+ throw error;
1468
+ }
1469
+ const verified = createPdsSessionPackageRuntimeContext({
1470
+ authorityPublicKey: runtime.authority.publicKey,
1471
+ authorityKeyId: runtime.authority.keyId,
1472
+ groupId: runtime.groupId,
1473
+ authorityHead: runtime.authority.head,
1474
+ currentEpoch: runtime.groupSecrets.currentEpoch,
1475
+ servingCertificate: runtime.certificate,
1476
+ recipientCertificate: runtime.certificate,
1477
+ recipientCertificates: runtime.recipientCertificates,
1478
+ historicalOriginCertificates: runtime.historicalOriginCertificates
1479
+ });
1480
+ return {
1481
+ ok: true,
1482
+ state: "runtime_cached",
1483
+ local_device_id: runtime.device.id,
1484
+ message: "Personal Sync status is using the last cryptographically verified Authority state.",
1485
+ pairing_invitation_group_ids: [],
1486
+ groups: [
1487
+ {
1488
+ group_id: runtime.groupId,
1489
+ authority_key_id: runtime.authority.keyId,
1490
+ authority_public_key: runtime.authority.publicKey,
1491
+ current_epoch: runtime.groupSecrets.currentEpoch,
1492
+ pending_epoch: null,
1493
+ head: { hash: runtime.authority.head },
1494
+ state: "active",
1495
+ members: verified.recipients.map((member) => ({
1496
+ device_id: member.deviceId,
1497
+ signing_key_id: member.signingKeyId,
1498
+ signing_public_key: member.signingPublicKey,
1499
+ kem_key_id: member.kemKeyId,
1500
+ kem_public_key: member.kemPublicKey,
1501
+ operation_families: ["pds_relay"],
1502
+ status: "active"
1503
+ })),
1504
+ policy: {
1505
+ enabled: true,
1506
+ future_closed_sessions_only: true,
1507
+ historical_backfill_enabled: false
1508
+ }
1509
+ }
1510
+ ]
1511
+ };
1512
+ };
1513
+ var createJoinChallenge = async (args, paths, environment, deps) => {
1514
+ const groupId = groupIdFrom(args);
1515
+ const runtimeSecretRef = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for enrollment.");
1516
+ const identity = deps.identity ?? await ensureDeviceIdentity(paths, { environment });
1517
+ if (!identity.remoteOperationsAllowed || !identity.deviceInstanceId || !identity.deploymentId)
1518
+ fail("A healthy Koed device identity is required for enrollment.");
1519
+ const deploymentId = identity.deploymentId;
1520
+ const invitationFd = flag(args, "--invitation-fd");
1521
+ const suppliedInvitation = invitationFd ? readJsonFd(args, "--invitation-fd") : null;
1522
+ const generatedChallenge = suppliedInvitation ? null : b64(randomBytes2(32));
1523
+ const binding = suppliedInvitation ? pairingInvitationBinding(suppliedInvitation, groupId) : await challengeBinding({
1524
+ groupId,
1525
+ challenge: generatedChallenge,
1526
+ environment,
1527
+ deps
1528
+ });
1529
+ const challengeValue = "challenge" in binding ? binding.challenge : generatedChallenge;
1530
+ const pendingSecretRef = pendingSecretReference(runtimeSecretRef, binding.id);
1531
+ const existingPendingRaw = suppliedInvitation ? await (deps.getSecret ?? getProviderSecret)(pendingSecretRef, environment) : null;
1532
+ const existingPending = existingPendingRaw ? await pendingEnrollmentSecret(pendingSecretRef, environment, deps) : null;
1533
+ if (existingPending && (existingPending.groupId !== groupId || existingPending.userId !== binding.browserSubjectId || existingPending.browserDeploymentId !== binding.browserDeploymentId || existingPending.localDeploymentId !== deploymentId || existingPending.authorityKeyId !== binding.authorityKeyId || existingPending.authorityPublicKey !== binding.authorityPublicKey || existingPending.challengeId !== binding.id || existingPending.challenge !== challengeValue || existingPending.expiresAt !== binding.expiresAt)) {
1534
+ fail("PDS pending enrollment does not match the pairing invitation.");
1535
+ }
1536
+ const deviceInstanceId = existingPending?.deviceId ?? relayId();
1537
+ const originDeploymentId = existingPending?.originDeploymentId ?? relayId();
1538
+ const signingKey = existingPending ? {
1539
+ publicKey: existingPending.signingPublicKey,
1540
+ privateSeed: existingPending.signingPrivateSeed,
1541
+ privateKey: pdsEd25519PrivateKey(existingPending.signingPrivateSeed, existingPending.signingPublicKey)
1542
+ } : generatedKey("ed25519");
1543
+ const kemKey = existingPending ? {
1544
+ publicKey: existingPending.kemPublicKey,
1545
+ privateSeed: existingPending.kemPrivateSeed
1546
+ } : generatedKey("x25519");
1547
+ const signingKeyId = existingPending?.signingKeyId ?? relayId();
1548
+ const kemKeyId = existingPending?.kemKeyId ?? relayId();
1549
+ const proof = enrollmentProof({
1550
+ challengeId: binding.id,
1551
+ challenge: challengeValue,
1552
+ groupId,
1553
+ deviceId: deviceInstanceId,
1554
+ signingKeyId,
1555
+ signingPublicKey: signingKey.publicKey,
1556
+ kemKeyId,
1557
+ kemPublicKey: kemKey.publicKey,
1558
+ browserSubjectId: binding.browserSubjectId,
1559
+ browserDeploymentId: binding.browserDeploymentId,
1560
+ expiresAt: binding.expiresAt,
1561
+ signingPrivateKey: signingKey.privateKey
1562
+ });
1563
+ await (deps.putSecret ?? putProviderSecret)(pendingSecretRef, JSON.stringify({
1564
+ version: 1,
1565
+ groupId,
1566
+ userId: binding.browserSubjectId,
1567
+ browserDeploymentId: binding.browserDeploymentId,
1568
+ localDeploymentId: deploymentId,
1569
+ relayUrl: suppliedInvitation && typeof suppliedInvitation.relay_url === "string" ? suppliedInvitation.relay_url : controlOrigin(environment),
1570
+ originDeploymentId,
1571
+ deviceId: deviceInstanceId,
1572
+ signingKeyId,
1573
+ signingPublicKey: signingKey.publicKey,
1574
+ signingPrivateSeed: signingKey.privateSeed,
1575
+ kemKeyId,
1576
+ kemPublicKey: kemKey.publicKey,
1577
+ kemPrivateSeed: kemKey.privateSeed,
1578
+ authorityKeyId: binding.authorityKeyId,
1579
+ authorityPublicKey: binding.authorityPublicKey,
1580
+ challengeId: binding.id,
1581
+ challenge: challengeValue,
1582
+ expiresAt: binding.expiresAt
1583
+ }), environment);
1584
+ const pending = readPending(paths);
1585
+ const next = pending.pending.filter((entry) => entry.requestId !== binding.id);
1586
+ next.push({ requestId: binding.id, groupId, createdAt: now(deps) });
1587
+ writePending(paths, {
1588
+ version: PENDING_VERSION,
1589
+ pending: next.slice(-MAX_PENDING_REQUESTS)
1590
+ });
1591
+ return {
1592
+ ok: true,
1593
+ state: "pending",
1594
+ message: "Pairing request is pending backend approval.",
1595
+ pairing: {
1596
+ challengeId: binding.id
1597
+ },
1598
+ request: {
1599
+ group_id: groupId,
1600
+ device_id: deviceInstanceId,
1601
+ signing_key_id: signingKeyId,
1602
+ signing_public_key: signingKey.publicKey,
1603
+ kem_key_id: kemKeyId,
1604
+ kem_public_key: kemKey.publicKey,
1605
+ operation_families: ["pds_relay"],
1606
+ proof
1607
+ }
1608
+ };
1609
+ };
1610
+ var groupMembers = (group) => {
1611
+ if (!Array.isArray(group.members))
1612
+ fail("PDS control response group members are invalid.");
1613
+ return group.members.map((member) => object(member, "group member"));
1614
+ };
1615
+ var responseString = (value, field) => strictString(value[field]) ? value[field] : fail(`PDS control response ${field} is invalid.`);
1616
+ var groupMember = (group, deviceId) => groupMembers(group).find((member) => member.device_id === deviceId && member.status === "active") ?? fail("PDS active device is not a current group member.");
1617
+ var epochAck = (input) => {
1618
+ const unsigned = {
1619
+ protocol: PDS_PROTOCOL,
1620
+ groupId: input.groupId,
1621
+ bundleHash: input.bundleHash,
1622
+ deviceId: input.deviceId,
1623
+ recipientKemKeyId: input.kemKeyId,
1624
+ recipientKemPublicKeyCommitment: pdsPublicKeyCommitment(input.kemPublicKey),
1625
+ epoch: input.epoch,
1626
+ acknowledgedAt: input.acknowledgedAt
1627
+ };
1628
+ return canonicalizePdsJson({
1629
+ ...unsigned,
1630
+ signature: {
1631
+ signerKeyId: input.signingKeyId,
1632
+ signature: signPdsRecord("key-bundle-ack", unsigned, input.signingPrivateKey)
1633
+ }
1634
+ });
1635
+ };
1636
+ var requestDeviceMatches = (body, request) => body.deviceId === request.device_id && body.deviceSigningKeyId === request.signing_key_id && body.deviceSigningPublicKey === request.signing_public_key && body.deviceKemKeyId === request.kem_key_id && body.deviceKemPublicKey === request.kem_public_key && Array.isArray(body.operationFamilies) && body.operationFamilies.length === 1 && body.operationFamilies[0] === "pds_relay";
1637
+ var pendingApprovalStatement = async (input) => {
1638
+ const sequence = responseString(input.current, "pending_statement_sequence");
1639
+ const statementResponse = await control({
1640
+ environment: input.environment,
1641
+ deps: input.controlDeps,
1642
+ method: "GET",
1643
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(input.groupId)}/log`
1644
+ });
1645
+ if (!Array.isArray(statementResponse.statements))
1646
+ fail("PDS pending transition statement log is invalid.");
1647
+ const entry = statementResponse.statements.map((value) => object(value, "group statement log entry")).find((value) => value.sequence === sequence) ?? fail("PDS pending transition statement is unavailable.");
1648
+ const statement = parseCanonicalPdsJson(responseString(entry, "canonicalStatement"));
1649
+ const head = object(input.current.head, "group.head");
1650
+ validatePdsGroupStatement(statement, {
1651
+ authorizationPublicKey: responseString(input.sourceMember, "signing_public_key"),
1652
+ authorityPublicKey: input.activeRuntime.authority.publicKey,
1653
+ expectedAuthorizationKeyId: input.activeRuntime.device.signingKeyId,
1654
+ expectedAuthorityKeyId: input.activeRuntime.authority.keyId,
1655
+ expectedGroupId: input.groupId
1656
+ });
1657
+ const statementDraft = object(statement.draft, "pending statement draft");
1658
+ const statementBody = object(statementDraft.body, "pending statement body");
1659
+ const epoch = responseString(input.current, "pending_epoch");
1660
+ const pendingMember = groupMembers(input.current).find((member) => member.device_id === input.request.device_id);
1661
+ const memberMatches = pendingMember?.status === "active" ? requestDeviceMatches({
1662
+ deviceId: pendingMember.device_id,
1663
+ deviceSigningKeyId: pendingMember.signing_key_id,
1664
+ deviceSigningPublicKey: pendingMember.signing_public_key,
1665
+ deviceKemKeyId: pendingMember.kem_key_id,
1666
+ deviceKemPublicKey: pendingMember.kem_public_key,
1667
+ operationFamilies: pendingMember.operation_families
1668
+ }, input.request) : false;
1669
+ if (statementDraft.kind !== "add-device" || statementDraft.sequence !== sequence || statementDraft.groupId !== input.groupId || statementBody.nextEpoch !== epoch || statementBody.previousEpoch !== input.current.current_epoch || statementBody.keyBundleHash !== input.current.pending_bundle_hash || responseString(entry, "statementHash") !== responseString(head, "hash") || responseString(head, "sequence") !== sequence || !requestDeviceMatches(statementBody, input.request) || !memberMatches) {
1670
+ fail("PDS pending membership transition does not match pairing request.");
1671
+ }
1672
+ const bundleResponse = await control({
1673
+ environment: input.environment,
1674
+ deps: input.controlDeps,
1675
+ method: "GET",
1676
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(input.groupId)}/key-bundles/${encodeURIComponent(epoch)}`
1677
+ });
1678
+ const bundle = object(bundleResponse.key_bundle, "key_bundle");
1679
+ const metadata = validatePdsKeyBundle(bundle, {
1680
+ authorizationPublicKey: responseString(input.sourceMember, "signing_public_key"),
1681
+ authorityPublicKey: input.activeRuntime.authority.publicKey,
1682
+ expectedAuthorizationKeyId: input.activeRuntime.device.signingKeyId,
1683
+ expectedAuthorityKeyId: input.activeRuntime.authority.keyId
1684
+ });
1685
+ if (metadata.authorizationHash !== input.current.pending_bundle_hash || metadata.draft.groupId !== input.groupId || metadata.draft.epoch !== epoch || metadata.draft.transitionKind !== "add-device") {
1686
+ fail("PDS pending key bundle does not match pairing request.");
1687
+ }
1688
+ return { statement, bundle, epoch };
1689
+ };
1690
+ var approveActiveDevice = async (args, environment, deps) => {
1691
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for approval.");
1692
+ const activeRuntime = await runtimeSecret(runtimeReference, environment, deps);
1693
+ const requestContainer = readJsonFd(args, "--request-fd");
1694
+ const request = object(requestContainer.request ?? requestContainer, "pairing request");
1695
+ const groupId = typeof request.group_id === "string" ? request.group_id.trim() : "";
1696
+ if (groupId !== activeRuntime.groupId)
1697
+ fail("PDS pairing request targets another group.");
1698
+ const controlDeps = deps.desktopAuthorization || deps.pairingToken ? deps : {
1699
+ ...deps,
1700
+ sessionCookie: deps.sessionCookie ?? browserSession(environment)
1701
+ };
1702
+ const currentResponse = await control({
1703
+ environment,
1704
+ deps: controlDeps,
1705
+ method: "GET",
1706
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}`
1707
+ });
1708
+ const current = object(currentResponse.group, "group");
1709
+ if (current.state !== "active" || typeof current.current_epoch !== "string")
1710
+ fail("PDS group is not ready for membership transition.");
1711
+ const sourceMember = groupMember(current, activeRuntime.device.id);
1712
+ const recovery = object(current.recovery, "group.recovery");
1713
+ const newDevice = {
1714
+ deviceId: responseString(request, "device_id"),
1715
+ signingKeyId: responseString(request, "signing_key_id"),
1716
+ signingPublicKey: responseString(request, "signing_public_key"),
1717
+ kemKeyId: responseString(request, "kem_key_id"),
1718
+ kemPublicKey: responseString(request, "kem_public_key")
1719
+ };
1720
+ if (!Object.values(newDevice).every(strictString) || !request.proof || typeof request.proof !== "object" || Array.isArray(request.proof))
1721
+ fail("PDS pairing request is invalid.");
1722
+ const existingMember = groupMembers(current).find((member) => member.device_id === newDevice.deviceId && member.status === "active");
1723
+ if (current.pending_epoch === null && existingMember) {
1724
+ if (!requestDeviceMatches({
1725
+ deviceId: existingMember.device_id,
1726
+ deviceSigningKeyId: existingMember.signing_key_id,
1727
+ deviceSigningPublicKey: existingMember.signing_public_key,
1728
+ deviceKemKeyId: existingMember.kem_key_id,
1729
+ deviceKemPublicKey: existingMember.kem_public_key,
1730
+ operationFamilies: existingMember.operation_families
1731
+ }, request)) {
1732
+ fail("PDS active membership does not match pairing request.");
1733
+ }
1734
+ return {
1735
+ ok: true,
1736
+ state: "active",
1737
+ message: "Device approval was already completed.",
1738
+ groupId,
1739
+ deviceId: newDevice.deviceId,
1740
+ epoch: current.current_epoch
1741
+ };
1742
+ }
1743
+ if (current.pending_epoch !== null) {
1744
+ const pending = await pendingApprovalStatement({
1745
+ groupId,
1746
+ current,
1747
+ request,
1748
+ activeRuntime,
1749
+ sourceMember,
1750
+ environment,
1751
+ controlDeps
1752
+ });
1753
+ const sourceSigningKey2 = pdsEd25519PrivateKey(activeRuntime.device.signingPrivateSeed, responseString(sourceMember, "signing_public_key"));
1754
+ const decrypted2 = decryptPdsKeyBundleSecretSet({
1755
+ bundle: pending.bundle,
1756
+ authorizationPublicKey: responseString(sourceMember, "signing_public_key"),
1757
+ authorityPublicKey: activeRuntime.authority.publicKey,
1758
+ recipientId: activeRuntime.device.id,
1759
+ recipientKemKeyId: activeRuntime.device.kemKeyId,
1760
+ recipientKemPublicKey: responseString(sourceMember, "kem_public_key"),
1761
+ recipientKemPrivateSeed: activeRuntime.device.kemPrivateSeed
1762
+ });
1763
+ if (!decrypted2 || typeof decrypted2 !== "object")
1764
+ fail("PDS source device decrypted an invalid group secret set.");
1765
+ const acknowledged2 = await control({
1766
+ environment,
1767
+ deps: controlDeps,
1768
+ method: "POST",
1769
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/epoch-acks`,
1770
+ body: {
1771
+ ack: epochAck({
1772
+ groupId,
1773
+ bundleHash: responseString(current, "pending_bundle_hash"),
1774
+ epoch: pending.epoch,
1775
+ deviceId: activeRuntime.device.id,
1776
+ kemKeyId: activeRuntime.device.kemKeyId,
1777
+ kemPublicKey: responseString(sourceMember, "kem_public_key"),
1778
+ signingKeyId: activeRuntime.device.signingKeyId,
1779
+ signingPrivateKey: sourceSigningKey2,
1780
+ acknowledgedAt: now(deps)
1781
+ })
1782
+ }
1783
+ });
1784
+ return {
1785
+ ok: true,
1786
+ state: acknowledged2.activated ? "active" : "pending_joining_device",
1787
+ message: acknowledged2.activated ? "Device approval was already completed." : "Device approval was resumed and source epoch acknowledged.",
1788
+ groupId,
1789
+ deviceId: newDevice.deviceId,
1790
+ epoch: pending.epoch
1791
+ };
1792
+ }
1793
+ const nextEpoch = (BigInt(current.current_epoch) + 1n).toString();
1794
+ const nextSecrets = {
1795
+ epochSecret: b64(randomBytes2(32)),
1796
+ sourceFingerprintKey: b64(randomBytes2(32)),
1797
+ tombstoneFloorKey: b64(randomBytes2(32)),
1798
+ projectAliasKey: b64(randomBytes2(32))
1799
+ };
1800
+ const recipients = [
1801
+ ...groupMembers(current).map((member) => ({
1802
+ recipientId: responseString(member, "device_id"),
1803
+ recipientKind: "device",
1804
+ recipientKemKeyId: responseString(member, "kem_key_id"),
1805
+ recipientKemPublicKey: responseString(member, "kem_public_key")
1806
+ })),
1807
+ {
1808
+ recipientId: newDevice.deviceId,
1809
+ recipientKind: "device",
1810
+ recipientKemKeyId: newDevice.kemKeyId,
1811
+ recipientKemPublicKey: newDevice.kemPublicKey
1812
+ },
1813
+ {
1814
+ recipientId: responseString(recovery, "kem_key_id"),
1815
+ recipientKind: "recovery",
1816
+ recipientKemKeyId: responseString(recovery, "kem_key_id"),
1817
+ recipientKemPublicKey: responseString(recovery, "kem_public_key")
1818
+ }
1819
+ ];
1820
+ const sourceSigningKey = pdsEd25519PrivateKey(activeRuntime.device.signingPrivateSeed, responseString(sourceMember, "signing_public_key"));
1821
+ const authorizedBundle = createPdsAuthorizedKeyBundle({
1822
+ groupId,
1823
+ epoch: nextEpoch,
1824
+ transitionKind: "add-device",
1825
+ recipients,
1826
+ secrets: nextSecrets,
1827
+ authorizationKeyId: activeRuntime.device.signingKeyId,
1828
+ authorizationPrivateKey: sourceSigningKey
1829
+ });
1830
+ const head = object(current.head, "group.head");
1831
+ if (typeof head.hash !== "string" || typeof head.sequence !== "string")
1832
+ fail("PDS group head is invalid.");
1833
+ const headHash = head.hash;
1834
+ const headSequence = head.sequence;
1835
+ const draft = {
1836
+ protocol: PDS_PROTOCOL,
1837
+ kind: "add-device",
1838
+ groupId,
1839
+ sequence: (BigInt(headSequence) + 1n).toString(),
1840
+ previousHash: headHash,
1841
+ body: {
1842
+ deviceId: newDevice.deviceId,
1843
+ deviceSigningKeyId: newDevice.signingKeyId,
1844
+ deviceSigningPublicKey: newDevice.signingPublicKey,
1845
+ deviceKemKeyId: newDevice.kemKeyId,
1846
+ deviceKemPublicKey: newDevice.kemPublicKey,
1847
+ operationFamilies: ["pds_relay"],
1848
+ previousEpoch: current.current_epoch,
1849
+ nextEpoch,
1850
+ keyBundleHash: authorizedBundle.authorizationHash
1851
+ }
1852
+ };
1853
+ const statement = {
1854
+ draft,
1855
+ authorization: {
1856
+ signerKeyId: activeRuntime.device.signingKeyId,
1857
+ signature: signPdsGroupDraft(draft, sourceSigningKey)
1858
+ }
1859
+ };
1860
+ const transitioned = await control({
1861
+ environment,
1862
+ deps: controlDeps,
1863
+ method: "POST",
1864
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/transitions`,
1865
+ body: {
1866
+ statement: canonicalizePdsJson(statement),
1867
+ key_bundle: canonicalizePdsJson(authorizedBundle.bundle),
1868
+ proof: request.proof
1869
+ }
1870
+ });
1871
+ const pendingGroup = object(transitioned.group, "group");
1872
+ const finalizedBundle = object(transitioned.key_bundle, "key_bundle");
1873
+ if (pendingGroup.pending_epoch !== nextEpoch || pendingGroup.pending_bundle_hash !== authorizedBundle.authorizationHash)
1874
+ fail("PDS transition did not enter the expected pending epoch.");
1875
+ const decrypted = decryptPdsKeyBundleSecretSet({
1876
+ bundle: finalizedBundle,
1877
+ authorizationPublicKey: responseString(sourceMember, "signing_public_key"),
1878
+ authorityPublicKey: activeRuntime.authority.publicKey,
1879
+ recipientId: activeRuntime.device.id,
1880
+ recipientKemKeyId: activeRuntime.device.kemKeyId,
1881
+ recipientKemPublicKey: responseString(sourceMember, "kem_public_key"),
1882
+ recipientKemPrivateSeed: activeRuntime.device.kemPrivateSeed
1883
+ });
1884
+ if (canonicalizePdsJson(decrypted) !== canonicalizePdsJson(nextSecrets))
1885
+ fail("PDS source device decrypted an unexpected group secret set.");
1886
+ const acknowledged = await control({
1887
+ environment,
1888
+ deps: controlDeps,
1889
+ method: "POST",
1890
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/epoch-acks`,
1891
+ body: {
1892
+ ack: epochAck({
1893
+ groupId,
1894
+ bundleHash: authorizedBundle.authorizationHash,
1895
+ epoch: nextEpoch,
1896
+ deviceId: activeRuntime.device.id,
1897
+ kemKeyId: activeRuntime.device.kemKeyId,
1898
+ kemPublicKey: responseString(sourceMember, "kem_public_key"),
1899
+ signingKeyId: activeRuntime.device.signingKeyId,
1900
+ signingPrivateKey: sourceSigningKey,
1901
+ acknowledgedAt: now(deps)
1902
+ })
1903
+ }
1904
+ });
1905
+ if (acknowledged.activated !== false)
1906
+ fail("PDS epoch activated before the joining device acknowledged it.");
1907
+ return {
1908
+ ok: true,
1909
+ state: "pending_joining_device",
1910
+ message: "Device approved and source epoch acknowledged. Complete enrollment on the joining device.",
1911
+ groupId,
1912
+ deviceId: newDevice.deviceId,
1913
+ epoch: nextEpoch
1914
+ };
1915
+ };
1916
+ var completeDeviceJoin = async (args, paths, environment, deps) => {
1917
+ const groupId = groupIdFrom(args);
1918
+ const challengeId = requiredFlag(args, "--challenge-id");
1919
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for enrollment.");
1920
+ const pendingReference = pendingSecretReference(runtimeReference, challengeId);
1921
+ const pending = await pendingEnrollmentSecret(pendingReference, environment, deps);
1922
+ if (pending.groupId !== groupId || pending.challengeId !== challengeId)
1923
+ fail("PDS pending enrollment does not match completion request.");
1924
+ const controlDeps = deps.desktopAuthorization || deps.pairingToken ? deps : {
1925
+ ...deps,
1926
+ sessionCookie: deps.sessionCookie ?? browserSession(environment)
1927
+ };
1928
+ const groupResponse = await control({
1929
+ environment,
1930
+ deps: controlDeps,
1931
+ method: "GET",
1932
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}`
1933
+ });
1934
+ const group = object(groupResponse.group, "group");
1935
+ const member = groupMember(group, pending.deviceId);
1936
+ const alreadyActivated = group.state === "active" && group.pending_epoch === null && typeof group.current_epoch === "string" && member.status === "active";
1937
+ let pendingEpoch;
1938
+ let activeGroup;
1939
+ let nextRuntime;
1940
+ if (alreadyActivated) {
1941
+ pendingEpoch = group.current_epoch;
1942
+ const currentRuntime = await runtimeSecret(runtimeReference, environment, deps);
1943
+ if (currentRuntime.groupId !== groupId || currentRuntime.device.id !== pending.deviceId || currentRuntime.groupSecrets.currentEpoch !== pendingEpoch) {
1944
+ fail("PDS activated enrollment cannot be resumed safely.");
1945
+ }
1946
+ activeGroup = group;
1947
+ nextRuntime = currentRuntime;
1948
+ } else {
1949
+ if (typeof group.pending_epoch !== "string" || typeof group.pending_bundle_hash !== "string") {
1950
+ fail("PDS group has no pending enrollment epoch.");
1951
+ }
1952
+ pendingEpoch = group.pending_epoch;
1953
+ const pendingBundleHash = group.pending_bundle_hash;
1954
+ const bundleResponse = await control({
1955
+ environment,
1956
+ deps: controlDeps,
1957
+ method: "GET",
1958
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/key-bundles/${encodeURIComponent(pendingEpoch)}`
1959
+ });
1960
+ const bundle = object(bundleResponse.key_bundle, "key_bundle");
1961
+ const authorization = object(bundle.authorization, "key_bundle.authorization");
1962
+ const author = groupMembers(group).find((candidate) => candidate.signing_key_id === authorization.signerKeyId && candidate.status === "active");
1963
+ if (!author)
1964
+ fail("PDS Key Bundle author is not an active member.");
1965
+ const secrets = decryptPdsKeyBundleSecretSet({
1966
+ bundle,
1967
+ authorizationPublicKey: responseString(author, "signing_public_key"),
1968
+ authorityPublicKey: pending.authorityPublicKey,
1969
+ recipientId: pending.deviceId,
1970
+ recipientKemKeyId: pending.kemKeyId,
1971
+ recipientKemPublicKey: pending.kemPublicKey,
1972
+ recipientKemPrivateSeed: pending.kemPrivateSeed
1973
+ });
1974
+ const joiningSigningKey = pdsEd25519PrivateKey(pending.signingPrivateSeed, pending.signingPublicKey);
1975
+ const ackResponse = await control({
1976
+ environment,
1977
+ deps: controlDeps,
1978
+ method: "POST",
1979
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/epoch-acks`,
1980
+ body: {
1981
+ ack: epochAck({
1982
+ groupId,
1983
+ bundleHash: pendingBundleHash,
1984
+ epoch: pendingEpoch,
1985
+ deviceId: pending.deviceId,
1986
+ kemKeyId: pending.kemKeyId,
1987
+ kemPublicKey: pending.kemPublicKey,
1988
+ signingKeyId: pending.signingKeyId,
1989
+ signingPrivateKey: joiningSigningKey,
1990
+ acknowledgedAt: now(deps)
1991
+ })
1992
+ }
1993
+ });
1994
+ if (ackResponse.activated !== true)
1995
+ fail("PDS epoch did not activate after all device acknowledgements.");
1996
+ activeGroup = object(ackResponse.group, "group");
1997
+ nextRuntime = {
1998
+ version: 1,
1999
+ userId: pending.userId,
2000
+ relayUrl: pending.relayUrl,
2001
+ groupId,
2002
+ device: {
2003
+ id: pending.deviceId,
2004
+ originDeploymentId: pending.originDeploymentId,
2005
+ signingKeyId: pending.signingKeyId,
2006
+ signingPrivateSeed: pending.signingPrivateSeed,
2007
+ kemKeyId: pending.kemKeyId,
2008
+ kemPrivateSeed: pending.kemPrivateSeed
2009
+ },
2010
+ authority: {
2011
+ keyId: pending.authorityKeyId,
2012
+ publicKey: pending.authorityPublicKey,
2013
+ head: ""
2014
+ },
2015
+ recovery: {
2016
+ signingKeyId: responseString(object(activeGroup.recovery, "group.recovery"), "signing_key_id"),
2017
+ signingPublicKey: responseString(object(activeGroup.recovery, "group.recovery"), "signing_public_key")
2018
+ },
2019
+ groupSecrets: {
2020
+ currentEpoch: pendingEpoch,
2021
+ contentKey: secrets.epochSecret,
2022
+ sourceFingerprintKey: secrets.sourceFingerprintKey,
2023
+ tombstoneFloorKey: secrets.tombstoneFloorKey,
2024
+ projectAliasKey: secrets.projectAliasKey
2025
+ },
2026
+ certificate: "",
2027
+ recipientCertificates: []
2028
+ };
2029
+ }
2030
+ const activeHead = object(activeGroup.head, "group.head");
2031
+ if (activeGroup.current_epoch !== pendingEpoch || activeGroup.pending_epoch !== null || typeof activeHead.hash !== "string")
2032
+ fail("PDS activated group response is invalid.");
2033
+ const activeHeadHash = activeHead.hash;
2034
+ const certificates = await Promise.all(groupMembers(activeGroup).map(async (activeMember) => {
2035
+ const response = await control({
2036
+ environment,
2037
+ deps: controlDeps,
2038
+ method: "GET",
2039
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/certificates/${encodeURIComponent(responseString(activeMember, "device_id"))}`
2040
+ });
2041
+ return canonicalizePdsJson(object(response.certificate, "certificate"));
2042
+ }));
2043
+ const logResponse = await control({
2044
+ environment,
2045
+ deps: controlDeps,
2046
+ method: "GET",
2047
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/log`
2048
+ });
2049
+ const statementEntries = logResponse.statements;
2050
+ if (!Array.isArray(statementEntries)) {
2051
+ return fail("PDS activated group statement log is invalid.");
2052
+ }
2053
+ const statements = statementEntries.map((value) => {
2054
+ const statement = object(value, "group statement log entry");
2055
+ if (!exactKeys2(statement, [
2056
+ "sequence",
2057
+ "statementHash",
2058
+ "canonicalStatement"
2059
+ ]) || !["sequence", "statementHash", "canonicalStatement"].every((field) => strictString(statement[field]))) {
2060
+ fail("PDS activated group statement log is invalid.");
2061
+ }
2062
+ return {
2063
+ sequence: statement.sequence,
2064
+ statementHash: statement.statementHash,
2065
+ canonicalStatement: statement.canonicalStatement
2066
+ };
2067
+ });
2068
+ nextRuntime = validatedRuntimeSecret({
2069
+ ...nextRuntime,
2070
+ authority: {
2071
+ keyId: pending.authorityKeyId,
2072
+ publicKey: pending.authorityPublicKey,
2073
+ head: activeHeadHash
2074
+ }
2075
+ }, certificates);
2076
+ await (deps.putSecret ?? putProviderSecret)(runtimeReference, JSON.stringify(nextRuntime), environment);
2077
+ return {
2078
+ ok: true,
2079
+ state: "active",
2080
+ message: "Joining device activated. The secure runtime is active.",
2081
+ groupId,
2082
+ deviceId: pending.deviceId,
2083
+ epoch: pendingEpoch,
2084
+ localGroupReconciliation: {
2085
+ local_device_id: pending.deviceId,
2086
+ group: activeGroup,
2087
+ statements,
2088
+ certificates
2089
+ }
2090
+ };
2091
+ };
2092
+ var bindLocalRuntimeUser = async (args, paths, environment, deps) => {
2093
+ const groupId = groupIdFrom(args);
2094
+ const userId = requiredFlag(args, "--user-id");
2095
+ const challengeId = flag(args, "--challenge-id")?.trim();
2096
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(userId)) {
2097
+ fail("--user-id is invalid.");
2098
+ }
2099
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for local identity binding.");
2100
+ const runtime = await runtimeSecret(runtimeReference, environment, deps);
2101
+ if (runtime.groupId !== groupId) {
2102
+ fail("PDS local identity binding targets another group.");
2103
+ }
2104
+ const rebound = validatedRuntimeSecret({
2105
+ version: runtime.version,
2106
+ userId,
2107
+ relayUrl: runtime.relayUrl,
2108
+ groupId: runtime.groupId,
2109
+ device: runtime.device,
2110
+ authority: runtime.authority,
2111
+ recovery: runtime.recovery,
2112
+ ...runtime.historicalOriginCertificates ? {
2113
+ historicalOriginCertificates: runtime.historicalOriginCertificates
2114
+ } : {},
2115
+ groupSecrets: runtime.groupSecrets
2116
+ }, runtime.recipientCertificates);
2117
+ await (deps.putSecret ?? putProviderSecret)(runtimeReference, JSON.stringify(rebound), environment);
2118
+ if (challengeId) {
2119
+ await (deps.deleteSecret ?? deleteProviderSecret)(pendingSecretReference(runtimeReference, challengeId), environment);
2120
+ const pendingState = readPending(paths);
2121
+ writePending(paths, {
2122
+ version: PENDING_VERSION,
2123
+ pending: pendingState.pending.filter((entry) => entry.requestId !== challengeId)
2124
+ });
2125
+ }
2126
+ return {
2127
+ ok: true,
2128
+ state: "active",
2129
+ message: "Personal Device Sync is bound to the local User.",
2130
+ groupId,
2131
+ deviceId: runtime.device.id
2132
+ };
2133
+ };
2134
+ var refreshActiveDevice = async (environment, deps) => {
2135
+ const runtimeReference = environment.PDS_RUNTIME_SECRET_REF?.trim() || fail("PDS_RUNTIME_SECRET_REF is required for refresh.");
2136
+ const runtime = await runtimeSecret(runtimeReference, environment, deps);
2137
+ const controlDeps = deps.desktopAuthorization || deps.pairingToken ? deps : {
2138
+ ...deps,
2139
+ sessionCookie: deps.sessionCookie ?? browserSession(environment)
2140
+ };
2141
+ const groupResponse = await control({
2142
+ environment,
2143
+ deps: controlDeps,
2144
+ method: "GET",
2145
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(runtime.groupId)}`
2146
+ });
2147
+ const group = object(groupResponse.group, "group");
2148
+ if (group.state !== "active" || group.pending_epoch !== null || typeof group.current_epoch !== "string")
2149
+ fail("PDS group is not ready for runtime refresh.");
2150
+ const currentEpoch = group.current_epoch;
2151
+ if (BigInt(currentEpoch) < BigInt(runtime.groupSecrets.currentEpoch))
2152
+ fail("PDS Authority epoch is behind the local runtime.");
2153
+ if (currentEpoch === runtime.groupSecrets.currentEpoch)
2154
+ return {
2155
+ ok: true,
2156
+ state: "current",
2157
+ message: "PDS runtime already uses the current epoch.",
2158
+ groupId: runtime.groupId,
2159
+ deviceId: runtime.device.id,
2160
+ epoch: currentEpoch
2161
+ };
2162
+ if (responseString(group, "authority_key_id") !== runtime.authority.keyId || responseString(group, "authority_public_key") !== runtime.authority.publicKey)
2163
+ fail("PDS Authority identity changed unexpectedly.");
2164
+ const member = groupMember(group, runtime.device.id);
2165
+ const bundleResponse = await control({
2166
+ environment,
2167
+ deps: controlDeps,
2168
+ method: "GET",
2169
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(runtime.groupId)}/key-bundles/${encodeURIComponent(currentEpoch)}`
2170
+ });
2171
+ const bundle = object(bundleResponse.key_bundle, "key_bundle");
2172
+ const authorization = object(bundle.authorization, "key_bundle.authorization");
2173
+ const author = groupMembers(group).find((candidate) => candidate.signing_key_id === authorization.signerKeyId && candidate.status === "active");
2174
+ const activeAuthor = author ?? fail("PDS Key Bundle author is not an active member.");
2175
+ const secrets = decryptPdsKeyBundleSecretSet({
2176
+ bundle,
2177
+ authorizationPublicKey: responseString(activeAuthor, "signing_public_key"),
2178
+ authorityPublicKey: runtime.authority.publicKey,
2179
+ recipientId: runtime.device.id,
2180
+ recipientKemKeyId: runtime.device.kemKeyId,
2181
+ recipientKemPublicKey: responseString(member, "kem_public_key"),
2182
+ recipientKemPrivateSeed: runtime.device.kemPrivateSeed
2183
+ });
2184
+ const members = groupMembers(group);
2185
+ const certificates = await Promise.all(members.map(async (activeMember) => {
2186
+ const response = await control({
2187
+ environment,
2188
+ deps: controlDeps,
2189
+ method: "GET",
2190
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(runtime.groupId)}/certificates/${encodeURIComponent(responseString(activeMember, "device_id"))}`
2191
+ });
2192
+ return canonicalizePdsJson(object(response.certificate, "certificate"));
2193
+ }));
2194
+ const head = object(group.head, "group.head");
2195
+ if (typeof head.hash !== "string")
2196
+ fail("PDS refreshed group response is invalid.");
2197
+ const runtimeWithoutCertificates = Object.fromEntries(Object.entries(runtime).filter(([key]) => key !== "certificate" && key !== "recipientCertificates"));
2198
+ const nextRuntime = validatedRuntimeSecret({
2199
+ ...runtimeWithoutCertificates,
2200
+ historicalOriginCertificates: [
2201
+ .../* @__PURE__ */ new Set([
2202
+ ...runtime.historicalOriginCertificates ?? [],
2203
+ ...runtime.recipientCertificates
2204
+ ])
2205
+ ],
2206
+ historicalProjectKeys: {
2207
+ ...runtime.historicalProjectKeys,
2208
+ [runtime.groupSecrets.currentEpoch]: runtime.groupSecrets.projectAliasKey
2209
+ },
2210
+ authority: { ...runtime.authority, head: head.hash },
2211
+ groupSecrets: {
2212
+ currentEpoch,
2213
+ contentKey: secrets.epochSecret,
2214
+ sourceFingerprintKey: secrets.sourceFingerprintKey,
2215
+ tombstoneFloorKey: secrets.tombstoneFloorKey,
2216
+ projectAliasKey: secrets.projectAliasKey
2217
+ }
2218
+ }, certificates);
2219
+ await (deps.putSecret ?? putProviderSecret)(runtimeReference, JSON.stringify(nextRuntime), environment);
2220
+ return {
2221
+ ok: true,
2222
+ state: "refreshed",
2223
+ message: "PDS runtime refreshed to the current Authority epoch.",
2224
+ groupId: runtime.groupId,
2225
+ deviceId: runtime.device.id,
2226
+ epoch: currentEpoch
2227
+ };
2228
+ };
2229
+ var readJsonFd = (args, name) => {
2230
+ const raw = readBoundedFd(requiredFlag(args, name), name);
2231
+ try {
2232
+ const parsed = JSON.parse(raw.toString("utf8"));
2233
+ return object(parsed, name);
2234
+ } finally {
2235
+ raw.fill(0);
2236
+ }
2237
+ };
2238
+ var withTransientJsonFd = async (paths, payload, operation) => {
2239
+ mkdirSync(paths.runDir, { recursive: true, mode: 448 });
2240
+ const path = resolve(paths.runDir, `pds-cli-${process.pid}-${randomUUID2()}.json`);
2241
+ writeFileSync(path, JSON.stringify(payload), { flag: "wx", mode: 384 });
2242
+ const fd = openSync(path, "r");
2243
+ unlinkSync(path);
2244
+ try {
2245
+ return await operation(fd);
2246
+ } finally {
2247
+ closeSync(fd);
2248
+ }
2249
+ };
2250
+ var submitTransition = async (args, environment, deps) => {
2251
+ const groupId = groupIdFrom(args);
2252
+ const body = {
2253
+ statement: requiredFlag(args, "--statement-fd") && readJsonFd(args, "--statement-fd").statement,
2254
+ key_bundle: flag(args, "--key-bundle-fd") ? readJsonFd(args, "--key-bundle-fd").key_bundle : void 0,
2255
+ proof: flag(args, "--proof-fd") ? readJsonFd(args, "--proof-fd").proof : void 0
2256
+ };
2257
+ if (typeof body.statement !== "string")
2258
+ fail("--statement-fd must contain a statement field.");
2259
+ if (body.key_bundle !== void 0 && typeof body.key_bundle !== "string")
2260
+ fail("--key-bundle-fd must contain a key_bundle field.");
2261
+ if (body.proof !== void 0 && (!body.proof || typeof body.proof !== "object" || Array.isArray(body.proof)))
2262
+ fail("--proof-fd must contain a proof object.");
2263
+ const response = await control({
2264
+ environment,
2265
+ deps,
2266
+ method: "POST",
2267
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/transitions`,
2268
+ body
2269
+ });
2270
+ return {
2271
+ ok: true,
2272
+ state: response.group ? "pending_activation" : "backend",
2273
+ message: "Authority accepted transition; poll backend durable status.",
2274
+ ...response
2275
+ };
2276
+ };
2277
+ var pairingLinkFromArgs = (args) => {
2278
+ if (args.some((arg) => arg === "--link" || arg.startsWith("--link=")))
2279
+ fail("--link is obsolete; use --link-stdin or --link-fd.");
2280
+ const stdin = args.includes("--link-stdin");
2281
+ const fd = flag(args, "--link-fd");
2282
+ if (Number(stdin) + Number(fd !== void 0) !== 1) {
2283
+ fail("Use exactly one of --link-stdin or --link-fd.");
2284
+ }
2285
+ const raw = readBoundedFd(stdin ? "0" : fd, "--link-fd", 4096);
2286
+ try {
2287
+ return raw.toString("utf8").trim();
2288
+ } finally {
2289
+ raw.fill(0);
2290
+ }
2291
+ };
2292
+ var redeemPairingFromCli = async (args, paths, environment, deps) => {
2293
+ return redeemPairingLink(pairingLinkFromArgs(args), flag(args, "--device-label")?.trim() || "SSH device", paths, environment, deps);
2294
+ };
2295
+ var redeemPairingLink = async (link, deviceLabel, paths, environment, deps = {}) => {
2296
+ if (deviceLabel.length > 80 || /[\r\n\0]/.test(deviceLabel)) {
2297
+ fail("--device-label is invalid.");
2298
+ }
2299
+ const localControlUrl = parseLoopbackOrigin(environment.PDS_LOCAL_CONTROL_URL?.trim() || resolveApiUrl(environment, loadRepoEnv(paths.repoRoot, environment)));
2300
+ return await redeemPersonalDevicePairing({
2301
+ link,
2302
+ deviceLabel,
2303
+ requestId: randomUUID2(),
2304
+ localControlUrl,
2305
+ koedHome: paths.koedHome,
2306
+ environment,
2307
+ fetch: deps.fetch,
2308
+ runPersonalSync: async (nestedArgs, options) => await runPersonalSyncCommand(nestedArgs, paths, options.environment, {
2309
+ ...deps,
2310
+ desktopAuthorization: void 0,
2311
+ pairingToken: options.pairingToken,
2312
+ fetch: options.fetch
2313
+ }),
2314
+ withJsonFd: async (payload, operation) => await withTransientJsonFd(paths, payload, operation)
2315
+ });
2316
+ };
2317
+ var runPersonalSyncCommand = async (args, paths, environment = process.env, deps = {}) => {
2318
+ rejectPasswordArguments(args);
2319
+ const [area, action] = args;
2320
+ if (area === "group" && action === "bootstrap")
2321
+ return bootstrapGroup(args.slice(2), paths, environment, deps);
2322
+ if (area === "status") {
2323
+ if (!deps.desktopAuthorization && !deps.pairingToken && !deps.sessionCookie && environment.PDS_BROWSER_SESSION_FD === void 0) {
2324
+ let runtime;
2325
+ try {
2326
+ runtime = JSON.parse(readFileSync(paths.runtimeStatePath, "utf8"));
2327
+ } catch {
2328
+ return fail("Start Koed first with koed start --daemon, then retry personal-sync status.");
2329
+ }
2330
+ if (runtime.runtimeMode !== "local-personal" || !runtime.apiUrl)
2331
+ return fail("Automatic Personal Sync status requires a local Personal installation. Advanced remote access requires a browser session FD.");
2332
+ const origin = parseLoopbackOrigin(runtime.apiUrl);
2333
+ if (environment.PDS_CONTROL_URL?.trim() && parseLoopbackOrigin(environment.PDS_CONTROL_URL.trim()) !== origin)
2334
+ return fail("PDS_CONTROL_URL does not match this installation\u2019s local API. Local credentials cannot be sent there.");
2335
+ const credential = readDesktopLocalCredentialAuthorization(paths.koedHome);
2336
+ if (!credential)
2337
+ return fail("Local Personal credentials are unavailable. Run koed setup core and retry.");
2338
+ return status({
2339
+ ...environment,
2340
+ PDS_CONTROL_URL: origin,
2341
+ PDS_RUNTIME_SECRET_REF: environment.PDS_RUNTIME_SECRET_REF?.trim() || "pds-runtime"
2342
+ }, { ...deps, desktopAuthorization: credential.authorization });
2343
+ }
2344
+ return status(environment, deps);
2345
+ }
2346
+ if (area === "invite" && action === "create")
2347
+ return createPairingInvitation(args.slice(2), environment, deps);
2348
+ if (area === "join" && action === "request")
2349
+ return createJoinChallenge(args.slice(2), paths, environment, deps);
2350
+ if (area === "join" && action === "redeem")
2351
+ return redeemPairingFromCli(args.slice(2), paths, environment, deps);
2352
+ if (area === "join" && action === "complete")
2353
+ return completeDeviceJoin(args.slice(2), paths, environment, deps);
2354
+ if (area === "join" && action === "bind-local-user")
2355
+ return bindLocalRuntimeUser(args.slice(2), paths, environment, deps);
2356
+ if (area === "join" && action === "challenge")
2357
+ return {
2358
+ ok: true,
2359
+ state: "pending",
2360
+ message: "Pending pairing artifacts are local redacted request IDs only.",
2361
+ requests: readPending(paths).pending
2362
+ };
2363
+ if (area === "active-device" && action === "approve")
2364
+ return approveActiveDevice(args.slice(2), environment, deps);
2365
+ if (area === "active-device" && action === "refresh")
2366
+ return refreshActiveDevice(environment, deps);
2367
+ if (area === "recovery" && action === "approve")
2368
+ return submitTransition(args.slice(2), environment, deps);
2369
+ if (area === "device" && action === "list") {
2370
+ const groupId = groupIdFrom(args.slice(2));
2371
+ const response = await control({
2372
+ environment,
2373
+ deps,
2374
+ method: "GET",
2375
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}`
2376
+ });
2377
+ const group = object(response.group, "group");
2378
+ return {
2379
+ ok: true,
2380
+ state: "backend",
2381
+ message: "Devices are Authority-owned.",
2382
+ devices: group.members ?? []
2383
+ };
2384
+ }
2385
+ if (area === "device" && action === "revoke")
2386
+ return submitTransition(args.slice(2), environment, deps);
2387
+ if (area === "policy" && ["enable", "pause", "resume"].includes(action ?? "")) {
2388
+ const groupId = groupIdFrom(args.slice(2));
2389
+ const enabled = action === "enable" || action === "resume";
2390
+ const response = action === "pause" || action === "resume" ? await control({
2391
+ environment,
2392
+ deps,
2393
+ method: "PUT",
2394
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/pause`,
2395
+ body: { paused: !enabled }
2396
+ }) : await control({
2397
+ environment,
2398
+ deps,
2399
+ method: "PUT",
2400
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/policy`,
2401
+ body: {
2402
+ enabled,
2403
+ future_closed_sessions_only: true,
2404
+ historical_backfill_enabled: false
2405
+ }
2406
+ });
2407
+ return {
2408
+ ok: true,
2409
+ state: "backend",
2410
+ message: "Policy result is backend-owned; poll status for durable outcome.",
2411
+ ...response
2412
+ };
2413
+ }
2414
+ if (area === "retry") {
2415
+ const groupId = groupIdFrom(args.slice(1));
2416
+ const response = await control({
2417
+ environment,
2418
+ deps,
2419
+ method: "POST",
2420
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/retry`
2421
+ });
2422
+ return {
2423
+ ok: true,
2424
+ state: "backend",
2425
+ message: "Retry submitted to backend.",
2426
+ ...response
2427
+ };
2428
+ }
2429
+ if (area === "replica" && action === "status") {
2430
+ const groupId = groupIdFrom(args.slice(2));
2431
+ const response = await control({
2432
+ environment,
2433
+ deps,
2434
+ method: "GET",
2435
+ path: `/v1/personal-device-sync/groups/${encodeURIComponent(groupId)}/local-status`
2436
+ });
2437
+ return {
2438
+ ok: true,
2439
+ state: "backend",
2440
+ message: "Replica status is backend-owned.",
2441
+ ...response
2442
+ };
2443
+ }
2444
+ if (area === "recovery" && action === "guidance")
2445
+ return {
2446
+ ok: true,
2447
+ state: "guidance",
2448
+ message: "Use active-device signed transition when possible. Otherwise use recovery signer through protected FD. Browser sessions only bind requests; API Tokens and legacy credentials are rejected."
2449
+ };
2450
+ if (area === "recovery-kit" && action === "verify") {
2451
+ const kitPath = requiredFlag(args.slice(2), "--recovery-kit");
2452
+ if (!secureRegularFile(kitPath))
2453
+ fail("Recovery kit permissions are unsafe; require 0600.");
2454
+ const password = passwordFrom(args.slice(2));
2455
+ try {
2456
+ const plaintext = decryptRecoveryKit(JSON.parse(readFileSync(kitPath, "utf8")), password);
2457
+ return {
2458
+ ok: true,
2459
+ state: "verified",
2460
+ message: "Recovery kit cryptographically verified. Backend transition remains required.",
2461
+ fingerprint: fingerprint(plaintext)
2462
+ };
2463
+ } finally {
2464
+ password.fill(0);
2465
+ }
2466
+ }
2467
+ if (area === "recovery-kit" && action === "create") {
2468
+ const output = requiredFlag(args.slice(2), "--output");
2469
+ const payload = readJsonFd(args.slice(2), "--payload-fd");
2470
+ const password = passwordFrom(args.slice(2));
2471
+ try {
2472
+ const plaintext = JSON.stringify(payload);
2473
+ const kit = encryptRecoveryKit(plaintext, password);
2474
+ if (decryptRecoveryKit(kit, password) !== plaintext)
2475
+ fail("Recovery kit round-trip verification failed.");
2476
+ writeRecoveryKit(output, kit);
2477
+ return {
2478
+ ok: true,
2479
+ state: "created",
2480
+ message: "Recovery kit encrypted and verified.",
2481
+ fingerprint: kit.fingerprint
2482
+ };
2483
+ } finally {
2484
+ password.fill(0);
2485
+ }
2486
+ }
2487
+ return fail("personal-sync command is invalid or requires backend signed transition input.");
2488
+ };
2489
+
2490
+ export {
2491
+ parseLoopbackOrigin,
2492
+ encryptRecoveryKit,
2493
+ decryptRecoveryKit,
2494
+ personalSyncProviderEnvironment,
2495
+ redeemPairingLink,
2496
+ runPersonalSyncCommand
2497
+ };