@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,1647 @@
1
+ import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
2
+ import {
3
+ beginUpstreamDisconnectCleanup,
4
+ clearUpstreamBackendCapabilities,
5
+ collectUpstreamRegistryStatus,
6
+ createSecureUpstreamFetch,
7
+ getActiveUpstreamBackend,
8
+ registeredPrivateNetworkPolicy,
9
+ setActiveUpstreamBackend,
10
+ updateUpstreamBackendCredential,
11
+ updateUpstreamBackendRoutePolicy,
12
+ updateUpstreamDisconnectCleanup,
13
+ upstreamBackendAdvertisesCapability,
14
+ upstreamBackendSupportsTeamMemoryAnswerTasks,
15
+ upstreamDisconnectCleanupPending
16
+ } from "./chunk-YJWREXN6.js";
17
+ import {
18
+ ensureDeviceIdentity
19
+ } from "./chunk-XNFVSVBS.js";
20
+ import {
21
+ require_proper_lockfile
22
+ } from "./chunk-7NXKPDF3.js";
23
+ import {
24
+ assertSecureHttpTransport,
25
+ deleteLocalEdgeClientCredential,
26
+ deleteUpstreamCredentialSecret,
27
+ readLocalEdgeUpstreamEnrollmentBinding,
28
+ readUpstreamCredentialAuthorization,
29
+ storeEnrollmentCredentialCustody,
30
+ storeLocalEdgeClientCredential,
31
+ storeUpstreamCredentialSecret,
32
+ upstreamCredentialReferenceFor
33
+ } from "./chunk-473OA62O.js";
34
+ import {
35
+ __toESM
36
+ } from "./chunk-BTS4ZTF3.js";
37
+
38
+ // packages/koed-server/dist/upstream-enrollment.js
39
+ import { createHash as createHash2, randomBytes, randomUUID } from "node:crypto";
40
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
41
+ import { mkdirSync as mkdirSync2 } from "node:fs";
42
+ import { dirname } from "node:path";
43
+
44
+ // packages/koed-server/dist/upstream-enrollment-lock.js
45
+ var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
46
+ import { createHash } from "node:crypto";
47
+ import { mkdirSync } from "node:fs";
48
+ import { resolve } from "node:path";
49
+ var LOCK_STALE_MS = 3e4;
50
+ var upstreamEnrollmentLockTarget = (paths, backendId) => {
51
+ const key = createHash("sha256").update(backendId).digest("hex").slice(0, 24);
52
+ return resolve(paths.runDir, `upstream-enrollment-${key}`);
53
+ };
54
+ var withUpstreamEnrollmentLock = async (paths, backendId, mutation) => {
55
+ mkdirSync(paths.runDir, { recursive: true, mode: 448 });
56
+ const release = await import_proper_lockfile.default.lock(upstreamEnrollmentLockTarget(paths, backendId), {
57
+ realpath: false,
58
+ stale: LOCK_STALE_MS,
59
+ update: LOCK_STALE_MS / 3,
60
+ retries: {
61
+ retries: 100,
62
+ factor: 1,
63
+ minTimeout: 25,
64
+ maxTimeout: 100,
65
+ randomize: true
66
+ }
67
+ });
68
+ try {
69
+ return await mutation();
70
+ } finally {
71
+ await release();
72
+ }
73
+ };
74
+
75
+ // packages/koed-server/dist/upstream-enrollment-transaction.js
76
+ var executeUpstreamEnrollmentTransactionEffect = (decision2, handlers) => {
77
+ if (decision2.effect === "none")
78
+ return null;
79
+ const handler = handlers[decision2.effect];
80
+ if (!handler) {
81
+ throw new Error(`Enrollment transaction effect ${decision2.effect} has no handler.`);
82
+ }
83
+ return handler();
84
+ };
85
+ var decision = (current, update, effect = "none", temporary = false) => ({
86
+ next: { ...current, ...update },
87
+ effect,
88
+ temporary
89
+ });
90
+ var beginEffect = (current, phase, state, effect) => decision(current, { phase, state, pendingEffect: effect }, effect);
91
+ var createUpstreamEnrollmentTransaction = (input) => {
92
+ if (!input.id.trim())
93
+ throw new Error("Enrollment transaction id is required.");
94
+ if (!Number.isSafeInteger(input.generation) || input.generation < 1) {
95
+ throw new Error("Enrollment transaction generation must be positive.");
96
+ }
97
+ return {
98
+ id: input.id,
99
+ generation: input.generation,
100
+ kind: input.kind,
101
+ phase: "prepared",
102
+ state: "pending",
103
+ pendingEffect: "stage_pending_custody"
104
+ };
105
+ };
106
+ var upstreamEnrollmentObservationApplies = (current, observation) => current.id === observation.transactionId && current.generation === observation.generation;
107
+ var decideUpstreamEnrollmentTransaction = (current, event) => {
108
+ if (event.type === "prepare") {
109
+ return current.phase === "prepared" ? decision(current, {}, "stage_pending_custody") : decision(current, {});
110
+ }
111
+ if (event.type === "effect_failed") {
112
+ return current.pendingEffect ? decision(current, { phase: "recovery_required" }) : decision(current, {});
113
+ }
114
+ if (event.type === "recover") {
115
+ if (!current.pendingEffect) {
116
+ return decision(current, {});
117
+ }
118
+ if (current.phase !== "recovery_required") {
119
+ return current.phase === "prepared" || current.phase === "awaiting_remote" || current.phase === "committing" || current.phase === "aborting" ? decision(current, {}, current.pendingEffect) : decision(current, {});
120
+ }
121
+ if (current.pendingEffect === "stage_pending_custody") {
122
+ return beginEffect(current, "aborting", "failed", "compensate_pending_custody");
123
+ }
124
+ const phase = current.pendingEffect === "commit_successor" ? "committing" : current.pendingEffect === "abort_pending" || current.pendingEffect === "revoke_active" ? "aborting" : "awaiting_remote";
125
+ return decision(current, { phase }, current.pendingEffect);
126
+ }
127
+ if (event.type === "effect_succeeded") {
128
+ if (current.phase === "committing") {
129
+ return decision(current, {
130
+ phase: "committed",
131
+ state: "exchanged",
132
+ pendingEffect: null
133
+ });
134
+ }
135
+ if (current.phase === "aborting") {
136
+ return decision(current, { phase: "aborted", pendingEffect: null });
137
+ }
138
+ if (current.phase === "prepared" && current.pendingEffect === "stage_pending_custody") {
139
+ return decision(current, {
140
+ phase: "awaiting_remote",
141
+ pendingEffect: null
142
+ });
143
+ }
144
+ if (current.phase === "awaiting_remote" && current.pendingEffect === "record_challenge") {
145
+ return decision(current, { pendingEffect: null });
146
+ }
147
+ return decision(current, {});
148
+ }
149
+ if (event.type === "challenge_created") {
150
+ return current.phase === "prepared" || current.phase === "awaiting_remote" ? decision(current, {
151
+ phase: "awaiting_remote",
152
+ state: "pending",
153
+ pendingEffect: "record_challenge"
154
+ }, "record_challenge") : decision(current, {});
155
+ }
156
+ if (event.type === "challenge_observed") {
157
+ if (event.status === "unknown")
158
+ return decision(current, {}, "none", true);
159
+ if (event.status === "pending") {
160
+ return decision(current, {
161
+ phase: "awaiting_remote",
162
+ state: "pending",
163
+ pendingEffect: null
164
+ });
165
+ }
166
+ if (event.status === "approved") {
167
+ return decision(current, {
168
+ phase: "awaiting_exchange",
169
+ state: "approved",
170
+ pendingEffect: null
171
+ });
172
+ }
173
+ return beginEffect(current, "aborting", event.status, "abort_pending");
174
+ }
175
+ if (event.type === "credential_observed") {
176
+ if (event.status === "unknown")
177
+ return decision(current, {}, "none", true);
178
+ if (event.status === "active") {
179
+ return beginEffect(current, "committing", "exchanged", "commit_successor");
180
+ }
181
+ return current.phase === "committed" || current.state === "exchanged" ? beginEffect(current, "aborting", "failed", "revoke_active") : decision(current, {});
182
+ }
183
+ if (event.type === "cancel") {
184
+ return current.phase === "committed" || current.phase === "aborted" ? decision(current, {}) : beginEffect(current, "aborting", "canceled", "abort_pending");
185
+ }
186
+ return current.phase === "aborted" ? decision(current, {}) : beginEffect(current, "aborting", "revoked", "revoke_active");
187
+ };
188
+
189
+ // packages/koed-server/dist/upstream-enrollment.js
190
+ var depsWithDefaults = (deps = {}) => ({
191
+ existsSync: deps.existsSync ?? existsSync,
192
+ readFileSync: deps.readFileSync ?? readFileSync,
193
+ writeFileSync: deps.writeFileSync ?? writeFileSync,
194
+ renameSync: deps.renameSync ?? renameSync,
195
+ fetch: deps.fetch ?? globalThis.fetch.bind(globalThis),
196
+ now: deps.now ?? (() => /* @__PURE__ */ new Date()),
197
+ randomId: deps.randomId ?? randomUUID,
198
+ randomBytes: deps.randomBytes ?? randomBytes,
199
+ beforeEnrollmentEffect: deps.beforeEnrollmentEffect ?? (() => void 0),
200
+ sourceOwnerPrincipalId: deps.sourceOwnerPrincipalId ?? "",
201
+ memoryAnswers: deps.memoryAnswers ?? false
202
+ });
203
+ var defaultStore = (now) => ({
204
+ schemaVersion: 1,
205
+ updatedAt: now,
206
+ enrollments: []
207
+ });
208
+ var validateBackendId = (id) => {
209
+ const trimmed = id.trim();
210
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}$/.test(trimmed)) {
211
+ throw new Error("Upstream backend id must be 2-64 characters of letters, numbers, hyphen, or underscore.");
212
+ }
213
+ return trimmed;
214
+ };
215
+ var readStore = (paths, deps) => {
216
+ const now = deps.now().toISOString();
217
+ if (!deps.existsSync(paths.upstreamEnrollmentsPath)) {
218
+ return defaultStore(now);
219
+ }
220
+ try {
221
+ const parsed = JSON.parse(deps.readFileSync(paths.upstreamEnrollmentsPath, "utf8"));
222
+ return {
223
+ schemaVersion: 1,
224
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : now,
225
+ enrollments: Array.isArray(parsed.enrollments) ? parsed.enrollments.map((record) => normalizeRecord(record, now)) : []
226
+ };
227
+ } catch {
228
+ throw new Error("Upstream enrollment state is malformed.");
229
+ }
230
+ };
231
+ var normalizeRecord = (record, now) => {
232
+ const requestId = typeof record.requestId === "string" && record.requestId.trim() ? record.requestId.trim().slice(0, 120) : "unknown";
233
+ const state = normalizeEnrollmentState(record.state);
234
+ const transactionKind = record.transaction?.kind === "replacement" || typeof record.rotationCredentialId === "string" && uuidPattern.test(record.rotationCredentialId) && typeof record.rotationCredentialReference === "string" && record.rotationCredentialReference.trim() ? "replacement" : "initial";
235
+ const candidateTransaction = record.transactionState;
236
+ const phase = candidateTransaction?.phase === "prepared" || candidateTransaction?.phase === "awaiting_remote" || candidateTransaction?.phase === "awaiting_exchange" || candidateTransaction?.phase === "committing" || candidateTransaction?.phase === "committed" || candidateTransaction?.phase === "aborting" || candidateTransaction?.phase === "aborted" || candidateTransaction?.phase === "recovery_required" ? candidateTransaction.phase : state === "exchanged" ? "committed" : state === "denied" || state === "expired" || state === "canceled" || state === "revoked" || state === "failed" ? "aborted" : state === "approved" ? "awaiting_exchange" : "awaiting_remote";
237
+ const pendingEffect = candidateTransaction?.pendingEffect === "stage_pending_custody" || candidateTransaction?.pendingEffect === "compensate_pending_custody" || candidateTransaction?.pendingEffect === "record_challenge" || candidateTransaction?.pendingEffect === "commit_successor" || candidateTransaction?.pendingEffect === "abort_pending" || candidateTransaction?.pendingEffect === "revoke_active" ? candidateTransaction.pendingEffect : null;
238
+ const transactionTargetState = candidateTransaction?.state === "pending" || candidateTransaction?.state === "approved" || candidateTransaction?.state === "exchanged" || candidateTransaction?.state === "denied" || candidateTransaction?.state === "expired" || candidateTransaction?.state === "canceled" || candidateTransaction?.state === "revoked" || candidateTransaction?.state === "failed" ? candidateTransaction.state : state;
239
+ const transactionState = {
240
+ id: typeof candidateTransaction?.id === "string" && candidateTransaction.id.trim() ? candidateTransaction.id.trim().slice(0, 120) : requestId,
241
+ generation: Number.isSafeInteger(candidateTransaction?.generation) && candidateTransaction.generation > 0 ? candidateTransaction.generation : 1,
242
+ kind: transactionKind,
243
+ phase,
244
+ state: transactionTargetState,
245
+ pendingEffect
246
+ };
247
+ return {
248
+ backendId: typeof record.backendId === "string" ? validateBackendId(record.backendId) : "missing",
249
+ requestId,
250
+ state,
251
+ activationUrl: typeof record.activationUrl === "string" ? sanitizeActivationUrl(record.activationUrl) : null,
252
+ requestedOperationFamilies: Array.isArray(record.requestedOperationFamilies) ? record.requestedOperationFamilies.filter(isOperationFamily) : [],
253
+ transaction: record.transaction?.kind === "replacement" && uuidPattern.test(record.transaction.predecessorCredentialId) && record.transaction.predecessorCredentialReference.trim() ? {
254
+ kind: "replacement",
255
+ predecessorCredentialId: record.transaction.predecessorCredentialId.toLowerCase(),
256
+ predecessorCredentialReference: record.transaction.predecessorCredentialReference.trim().slice(0, 180)
257
+ } : typeof record.rotationCredentialId === "string" && uuidPattern.test(record.rotationCredentialId) && typeof record.rotationCredentialReference === "string" && record.rotationCredentialReference.trim() ? {
258
+ kind: "replacement",
259
+ predecessorCredentialId: record.rotationCredentialId.toLowerCase(),
260
+ predecessorCredentialReference: record.rotationCredentialReference.trim().slice(0, 180)
261
+ } : { kind: "initial" },
262
+ transactionState,
263
+ ...typeof record.activeCredentialReference === "string" && record.activeCredentialReference.trim() ? {
264
+ activeCredentialReference: record.activeCredentialReference.trim().slice(0, 180)
265
+ } : {},
266
+ ...typeof record.pendingCredentialReference === "string" && record.pendingCredentialReference.trim() ? {
267
+ pendingCredentialReference: record.pendingCredentialReference.trim().slice(0, 180)
268
+ } : {},
269
+ ...typeof record.challengeHash === "string" && /^[0-9a-f]{64}$/.test(record.challengeHash) ? { challengeHash: record.challengeHash } : {},
270
+ ...typeof record.challengeId === "string" && record.challengeId.trim() ? { challengeId: record.challengeId.trim().slice(0, 120) } : {},
271
+ ...typeof record.credentialKeyId === "string" && record.credentialKeyId.trim() ? { credentialKeyId: record.credentialKeyId.trim().slice(0, 160) } : {},
272
+ ...typeof record.credentialReference === "string" && record.credentialReference.trim() ? { credentialReference: record.credentialReference.trim().slice(0, 180) } : {},
273
+ ...typeof record.localClientCredentialReference === "string" && record.localClientCredentialReference.trim() ? {
274
+ localClientCredentialReference: record.localClientCredentialReference.trim().slice(0, 200)
275
+ } : {},
276
+ ...typeof record.deviceCredentialId === "string" && uuidPattern.test(record.deviceCredentialId) ? { deviceCredentialId: record.deviceCredentialId.toLowerCase() } : {},
277
+ ...typeof record.principalUserId === "string" && uuidPattern.test(record.principalUserId) ? { principalUserId: record.principalUserId.toLowerCase() } : {},
278
+ createdAt: typeof record.createdAt === "string" ? record.createdAt : now,
279
+ updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : now,
280
+ expiresAt: typeof record.expiresAt === "string" ? record.expiresAt : null,
281
+ ...typeof record.failureReason === "string" ? { failureReason: record.failureReason.slice(0, 80) } : {},
282
+ ...typeof record.failureMessage === "string" ? { failureMessage: record.failureMessage.slice(0, 240) } : {},
283
+ credential: sanitizeCredential(record.credential)
284
+ };
285
+ };
286
+ var replacementTransaction = (record) => record.transaction.kind === "replacement" ? record.transaction : null;
287
+ var normalizeEnrollmentState = (value) => {
288
+ if (value === "approved" || value === "exchanged" || value === "denied" || value === "expired" || value === "canceled" || value === "revoked" || value === "failed") {
289
+ return value;
290
+ }
291
+ return "pending";
292
+ };
293
+ var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
294
+ var sanitizeActivationUrl = (value) => {
295
+ try {
296
+ const parsed = new URL(value);
297
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
298
+ return null;
299
+ }
300
+ assertSecureHttpTransport(parsed, "Activation URL");
301
+ parsed.username = "";
302
+ parsed.password = "";
303
+ for (const key of [...parsed.searchParams.keys()]) {
304
+ if (isSecretKey(key)) {
305
+ parsed.searchParams.set(key, "[redacted]");
306
+ }
307
+ }
308
+ return parsed.toString();
309
+ } catch {
310
+ return null;
311
+ }
312
+ };
313
+ var sanitizeCredential = (credential) => {
314
+ if (credential?.status === "configured" || credential?.status === "revoked" || credential?.status === "unknown") {
315
+ return {
316
+ status: credential.status,
317
+ ...typeof credential.reference === "string" && credential.reference.trim() && !isSecretKey(credential.reference) ? { reference: credential.reference.trim().slice(0, 240) } : {}
318
+ };
319
+ }
320
+ return { status: "not_configured" };
321
+ };
322
+ var isSecretKey = (value) => /(?:token|secret|password|bearer|cookie|authorization|verifier|challenge)/i.test(value);
323
+ var isOperationFamily = (value) => typeof value === "string" && /^[a-z0-9_.:-]{1,80}$/i.test(value) && !isSecretKey(value);
324
+ var writeStore = (paths, store, deps) => {
325
+ mkdirSync2(dirname(paths.upstreamEnrollmentsPath), {
326
+ recursive: true,
327
+ mode: 448
328
+ });
329
+ const tempPath = `${paths.upstreamEnrollmentsPath}.tmp`;
330
+ deps.writeFileSync(tempPath, `${JSON.stringify(store, null, 2)}
331
+ `, {
332
+ mode: 384
333
+ });
334
+ deps.renameSync(tempPath, paths.upstreamEnrollmentsPath);
335
+ };
336
+ var summarizeEnrollment = (record, backend) => {
337
+ const credential = record.state === "revoked" ? record.credential ?? { status: "revoked" } : backend?.credential.status === "configured" || backend?.credential.status === "revoked" ? backend.credential : record.credential ?? backend?.credential ?? { status: "unknown" };
338
+ return {
339
+ backendId: record.backendId,
340
+ requestId: record.requestId,
341
+ state: record.state,
342
+ activationUrl: record.activationUrl,
343
+ requestedOperationFamilies: record.requestedOperationFamilies,
344
+ ...record.challengeId ? { challengeId: record.challengeId } : {},
345
+ ...record.deviceCredentialId ? { deviceCredentialId: record.deviceCredentialId } : {},
346
+ ...record.principalUserId ? { principalUserId: record.principalUserId } : {},
347
+ createdAt: record.createdAt,
348
+ updatedAt: record.updatedAt,
349
+ expiresAt: record.expiresAt,
350
+ ...record.failureReason ? { failureReason: record.failureReason } : {},
351
+ ...record.failureMessage ? { failureMessage: record.failureMessage } : {},
352
+ credential
353
+ };
354
+ };
355
+ var routePolicyOperationFamilies = (routePolicy, options = { collaboration: false }) => {
356
+ const entries = [
357
+ ["personalMemoryRead", "personal_memory_read"],
358
+ ...options.collaboration ? [
359
+ ["personalCollaboration", "personal_collaboration_read"],
360
+ ["personalCollaboration", "personal_collaboration_write"]
361
+ ] : [],
362
+ ["teamWorkspaceRead", "team_workspace_read"],
363
+ ["memoryAnswerExecution", "memory_answer_execution"],
364
+ ...options.collaboration ? [
365
+ ["teamWorkspaceRead", "team_chat_read"],
366
+ ["teamWorkspaceRead", "team_chat_write"]
367
+ ] : [],
368
+ ["shareGrantManagement", "share_grant_management"],
369
+ ["captureWrites", "capture_writes"],
370
+ ["sync", "sync"],
371
+ ["managedExecution", "managed_execution"],
372
+ ["managedExecution", "managed_file_read"],
373
+ ["managedExecution", "managed_terminal"],
374
+ ["admin", "action_grant"]
375
+ ];
376
+ return entries.filter(([key]) => routePolicy[key] === "enabled").map(([, family]) => family);
377
+ };
378
+ var browserEnrollmentOperationFamilies = (routePolicy, options) => routePolicyOperationFamilies({
379
+ ...routePolicy,
380
+ ...options.memoryAnswers ? { memoryAnswerExecution: "enabled" } : {}
381
+ }, options);
382
+ var localClientOperationFamiliesFor = (operationFamilies) => operationFamilies.filter((family) => family === "personal_collaboration_read" || family === "personal_collaboration_write" || family === "team_workspace_read" || family === "memory_answer_execution" || family === "team_chat_read" || family === "team_chat_write" || family === "share_grant_management" || family === "managed_execution" || family === "managed_file_read" || family === "managed_terminal");
383
+ var expiresAtFor = (now) => new Date(now.getTime() + 10 * 60 * 1e3).toISOString();
384
+ var randomSecret = (deps, bytes = 32) => deps.randomBytes(bytes).toString("base64url");
385
+ var challengeHashFor = (backendId, requestId, secret) => createHash2("sha256").update(`${backendId}:${requestId}:${secret}`).digest("hex");
386
+ var approvalUrlFor = (backend, challengeId) => new URL(`device-enrollment/${encodeURIComponent(challengeId)}`, `${backend.baseUrl}/`).toString();
387
+ var jsonFetch = async (deps, url, init) => {
388
+ const response = await deps.fetch(url, {
389
+ ...init,
390
+ redirect: "error",
391
+ headers: {
392
+ accept: "application/json",
393
+ ...init.body === void 0 ? {} : { "content-type": "application/json" },
394
+ ...init.headers
395
+ }
396
+ });
397
+ const payload = await response.json().catch(() => ({}));
398
+ return { status: response.status, ok: response.ok, payload };
399
+ };
400
+ var createUpstreamChallenge = async (backend, input, deps) => {
401
+ const result = await jsonFetch(deps, new URL("v1/local-edge/device-enrollments/challenges", `${backend.baseUrl}/`), {
402
+ method: "POST",
403
+ headers: input.rotation ? { authorization: input.rotation.authorization } : void 0,
404
+ body: JSON.stringify({
405
+ challenge_hash: input.challengeHash,
406
+ upstream_backend_id: input.backendId,
407
+ device_instance_id: input.deviceInstanceId,
408
+ protocol_deployment_id: input.protocolDeploymentId,
409
+ ...input.sourceOwnerPrincipalId ? { source_owner_principal_id: input.sourceOwnerPrincipalId } : {},
410
+ ...input.rotation ? { rotate_credential_id: input.rotation.credentialId } : {},
411
+ device_label: "Koed local edge",
412
+ requested_operation_families: input.operationFamilies,
413
+ pending_credential: {
414
+ credential_key_id: input.credentialKeyId,
415
+ verifier_kind: "secret_hash",
416
+ verifier_secret: input.verifierSecret,
417
+ operation_families: input.operationFamilies
418
+ },
419
+ metadata: {
420
+ source: "koed-server-upstream-enroll",
421
+ requestId: input.requestId
422
+ },
423
+ ttl_seconds: 600
424
+ })
425
+ });
426
+ if (!result.ok) {
427
+ throw new Error(typeof result.payload.error === "string" ? result.payload.error : `Upstream challenge creation failed with HTTP ${result.status}.`);
428
+ }
429
+ const challenge = result.payload.challenge;
430
+ const rawChallengeId = challenge && typeof challenge === "object" && "id" in challenge ? challenge.id : "";
431
+ const challengeId = typeof rawChallengeId === "string" ? rawChallengeId : "";
432
+ if (!challengeId) {
433
+ throw new Error("Upstream challenge response did not include a challenge id.");
434
+ }
435
+ const rawActivationUrl = result.payload.activationUrl;
436
+ const activationUrl = typeof rawActivationUrl === "string" ? sanitizeActivationUrl(rawActivationUrl) : null;
437
+ return {
438
+ challengeId,
439
+ activationUrl: activationUrl ?? approvalUrlFor(backend, challengeId)
440
+ };
441
+ };
442
+ var readUpstreamChallengeStatus = async (backend, challengeId, deviceInstanceId, deps) => {
443
+ const result = await jsonFetch(deps, new URL(`v1/local-edge/device-enrollments/challenges/${encodeURIComponent(challengeId)}`, `${backend.baseUrl}/`), {
444
+ method: "GET",
445
+ headers: { "x-koed-device-instance-id": deviceInstanceId }
446
+ });
447
+ if (!result.ok) {
448
+ return "unknown";
449
+ }
450
+ const challenge = result.payload.challenge;
451
+ const rawStatus = challenge && typeof challenge === "object" && "status" in challenge ? challenge.status : "";
452
+ const status = typeof rawStatus === "string" ? rawStatus : "";
453
+ return status === "pending" || status === "approved" || status === "denied" || status === "expired" ? status : "unknown";
454
+ };
455
+ var remoteCredentialStatus = async (paths, backend, reference, deps) => {
456
+ const authorization = readUpstreamCredentialAuthorization(paths.koedHome, reference);
457
+ if (!authorization) {
458
+ return { state: "unknown" };
459
+ }
460
+ try {
461
+ const result = await jsonFetch(deps, new URL("v1/local-edge/device-credentials/status", `${backend.baseUrl}/`), {
462
+ method: "GET",
463
+ headers: { authorization }
464
+ });
465
+ if (result.status === 401 || result.status === 403) {
466
+ return { state: "rejected" };
467
+ }
468
+ const credential = result.payload.credential;
469
+ const user = result.payload.user;
470
+ const deviceCredentialId = credential && typeof credential === "object" && "id" in credential ? credential.id : null;
471
+ const principalUserId = user && typeof user === "object" && "id" in user ? user.id : null;
472
+ return result.ok && result.payload.ok === true && typeof deviceCredentialId === "string" && uuidPattern.test(deviceCredentialId) && typeof principalUserId === "string" && uuidPattern.test(principalUserId) ? {
473
+ state: "active",
474
+ deviceCredentialId: deviceCredentialId.toLowerCase(),
475
+ principalUserId: principalUserId.toLowerCase()
476
+ } : { state: "unknown" };
477
+ } catch {
478
+ return { state: "unknown" };
479
+ }
480
+ };
481
+ var discoverRotationCredential = async (paths, backend, store, deps) => {
482
+ const candidates = [...store.enrollments].reverse().filter((record) => record.backendId === backend.id && typeof record.credentialReference === "string");
483
+ for (const candidate of candidates) {
484
+ const credentialReference = candidate.credentialReference;
485
+ const authorization = readUpstreamCredentialAuthorization(paths.koedHome, credentialReference);
486
+ if (!authorization)
487
+ continue;
488
+ const status = await remoteCredentialStatus(paths, backend, credentialReference, deps);
489
+ if (status.state === "active") {
490
+ return {
491
+ state: "active",
492
+ credentialId: status.deviceCredentialId,
493
+ principalUserId: status.principalUserId,
494
+ credentialReference,
495
+ authorization
496
+ };
497
+ }
498
+ if (status.state === "unknown")
499
+ return { state: "unavailable" };
500
+ }
501
+ return { state: "none" };
502
+ };
503
+ var revokeRemoteCredential = async (paths, backend, reference, deps) => {
504
+ const authorization = readUpstreamCredentialAuthorization(paths.koedHome, reference);
505
+ if (!authorization)
506
+ return "unavailable";
507
+ try {
508
+ const result = await jsonFetch(deps, new URL("v1/local-edge/device-credentials/current", `${backend.baseUrl}/`), {
509
+ method: "DELETE",
510
+ headers: { authorization }
511
+ });
512
+ if (result.ok && result.payload.revoked === true)
513
+ return "revoked";
514
+ if (result.status === 401)
515
+ return "already_inactive";
516
+ return "unavailable";
517
+ } catch {
518
+ return "unavailable";
519
+ }
520
+ };
521
+ var latestEnrollment = (store, backendId) => [...store.enrollments].reverse().find((enrollment) => enrollment.backendId === backendId);
522
+ var readUpstreamEnrollmentBinding = (paths, id, deps = {}) => readLocalEdgeUpstreamEnrollmentBinding(paths.upstreamEnrollmentsPath, validateBackendId(id), {
523
+ existsSync: deps.existsSync,
524
+ readFileSync: deps.readFileSync
525
+ });
526
+ var backendById = (paths, backendId, deps) => {
527
+ const registry = collectUpstreamRegistryStatus(paths, {
528
+ existsSync: deps.existsSync,
529
+ readFileSync: deps.readFileSync,
530
+ now: deps.now
531
+ });
532
+ return {
533
+ backend: registry.backends.find((backend) => backend.id === backendId),
534
+ parseError: registry.parseError
535
+ };
536
+ };
537
+ var enrollmentCanReceiveRemoteStatus = (record) => record.state === "pending" || record.state === "approved" || record.state === "exchanged";
538
+ var sameEnrollmentIdentity = (current, expected) => current?.requestId === expected.requestId && current.credentialReference === expected.credentialReference && upstreamEnrollmentObservationApplies(current.transactionState, {
539
+ transactionId: expected.transactionState.id,
540
+ generation: expected.transactionState.generation
541
+ });
542
+ var enrollmentResultFromSnapshot = (backendId, record, backend) => record ? {
543
+ ok: true,
544
+ state: record.state,
545
+ backend,
546
+ enrollment: summarizeEnrollment(record, backend),
547
+ message: `Upstream enrollment for ${backendId} is ${record.state}.`
548
+ } : {
549
+ ok: true,
550
+ state: "missing",
551
+ backend,
552
+ message: `No upstream enrollment has been started for ${backendId}.`
553
+ };
554
+ var materializeState = (record, now, backend) => {
555
+ const replacement = replacementTransaction(record);
556
+ if (backend?.credential.status === "configured" && (!replacement || backend.credential.reference === record.credentialReference)) {
557
+ return {
558
+ ...record,
559
+ state: "exchanged",
560
+ credential: backend.credential
561
+ };
562
+ }
563
+ if (backend?.credential.status === "revoked") {
564
+ return {
565
+ ...record,
566
+ state: "revoked",
567
+ credential: backend.credential
568
+ };
569
+ }
570
+ if (record.state === "exchanged" && backend?.credential.status === "not_configured") {
571
+ return {
572
+ ...record,
573
+ state: "failed",
574
+ updatedAt: now.toISOString(),
575
+ failureReason: "credential_reset",
576
+ failureMessage: "Upstream backend credential is no longer configured; restart enrollment.",
577
+ credential: backend.credential
578
+ };
579
+ }
580
+ if (record.state === "denied" || record.state === "canceled" || record.state === "expired" || record.state === "failed") {
581
+ return record;
582
+ }
583
+ if ((record.state === "pending" || record.state === "approved") && record.expiresAt && Date.parse(record.expiresAt) <= now.getTime()) {
584
+ return {
585
+ ...record,
586
+ state: "expired",
587
+ updatedAt: now.toISOString()
588
+ };
589
+ }
590
+ return record;
591
+ };
592
+ var withSecureFetchForBackend = async (paths, id, deps, operation) => {
593
+ if (deps.fetch)
594
+ return operation(deps);
595
+ const backend = backendById(paths, validateBackendId(id), depsWithDefaults(deps)).backend;
596
+ if (!backend)
597
+ return operation(deps);
598
+ const secureFetch = createSecureUpstreamFetch({
599
+ allowPrivateNetworkForUrl: registeredPrivateNetworkPolicy(() => [
600
+ { baseUrl: backend.baseUrl, profile: backend.profile }
601
+ ])
602
+ });
603
+ try {
604
+ return await operation({ ...deps, fetch: secureFetch });
605
+ } finally {
606
+ await secureFetch.close();
607
+ }
608
+ };
609
+ var startUpstreamEnrollmentWithFetch = async (paths, id, deps = {}) => {
610
+ const resolvedDeps = depsWithDefaults(deps);
611
+ const backendId = validateBackendId(id);
612
+ const identity = await ensureDeviceIdentity(paths);
613
+ if (!identity.remoteOperationsAllowed) {
614
+ return {
615
+ ok: false,
616
+ state: "failed",
617
+ message: `Local device identity is ${identity.health}; upstream enrollment is blocked until explicit identity rotation.`
618
+ };
619
+ }
620
+ const now = resolvedDeps.now();
621
+ const nowIso = now.toISOString();
622
+ const { backend, parseError } = backendById(paths, backendId, resolvedDeps);
623
+ if (parseError) {
624
+ return {
625
+ ok: false,
626
+ state: "failed",
627
+ message: "Upstream backend registry is malformed."
628
+ };
629
+ }
630
+ if (!backend) {
631
+ return {
632
+ ok: false,
633
+ state: "missing",
634
+ message: `Upstream backend ${backendId} is not registered.`
635
+ };
636
+ }
637
+ if (upstreamDisconnectCleanupPending(paths, backendId)) {
638
+ return {
639
+ ok: false,
640
+ state: "failed",
641
+ backend,
642
+ message: `Upstream backend ${backendId} cannot enroll until disconnect cleanup completes.`
643
+ };
644
+ }
645
+ if (backend.capabilities.state !== "validated") {
646
+ return {
647
+ ok: false,
648
+ state: "failed",
649
+ backend,
650
+ message: `Upstream backend ${backendId} capabilities are not validated. Run koed upstream refresh --id ${backendId} --json.`
651
+ };
652
+ }
653
+ if (resolvedDeps.memoryAnswers || backend.routePolicy.memoryAnswerExecution === "enabled") {
654
+ if (backend.routePolicy.teamWorkspaceRead !== "enabled") {
655
+ return {
656
+ ok: false,
657
+ state: "failed",
658
+ backend,
659
+ message: "Memory Answer enrollment requires explicit Team Workspace read routing first."
660
+ };
661
+ }
662
+ if (!upstreamBackendSupportsTeamMemoryAnswerTasks(paths, backendId, resolvedDeps)) {
663
+ return {
664
+ ok: false,
665
+ state: "failed",
666
+ backend,
667
+ message: `The backend must advertise memory.answerTasks version 2 with Team support. Upgrade it, run koed upstream refresh --id ${backendId} --json, then re-enroll with --memory-answers. Team read alone does not authorize execution.`
668
+ };
669
+ }
670
+ }
671
+ const configuredOperationFamilies = routePolicyOperationFamilies(backend.routePolicy);
672
+ const collaboration = upstreamBackendAdvertisesCapability(paths, backendId, "memory.collaboration", {
673
+ existsSync: resolvedDeps.existsSync,
674
+ readFileSync: resolvedDeps.readFileSync,
675
+ now: resolvedDeps.now
676
+ });
677
+ const operationFamilies = browserEnrollmentOperationFamilies(backend.routePolicy, { collaboration, memoryAnswers: resolvedDeps.memoryAnswers });
678
+ if (configuredOperationFamilies.length === 0) {
679
+ return {
680
+ ok: false,
681
+ state: "failed",
682
+ backend,
683
+ message: `Upstream backend ${backendId} has no enabled route-policy families.`
684
+ };
685
+ }
686
+ if (operationFamilies.length === 0) {
687
+ return {
688
+ ok: false,
689
+ state: "failed",
690
+ backend,
691
+ message: `Upstream backend ${backendId} has no browser-enrollable route-policy families.`
692
+ };
693
+ }
694
+ if (operationFamilies.includes("share_grant_management") && !uuidPattern.test(resolvedDeps.sourceOwnerPrincipalId)) {
695
+ return {
696
+ ok: false,
697
+ state: "failed",
698
+ backend,
699
+ message: "Share Grant enrollment requires the authenticated local owner principal."
700
+ };
701
+ }
702
+ let store = readStore(paths, resolvedDeps);
703
+ let expectedRecord = latestEnrollment(store, backendId);
704
+ if (expectedRecord) {
705
+ const current = await getUpstreamEnrollmentStatus(paths, backendId, resolvedDeps);
706
+ if (current.state === "pending" || current.state === "approved" || current.state === "exchanged" && JSON.stringify(current.enrollment?.requestedOperationFamilies ?? []) === JSON.stringify(operationFamilies)) {
707
+ if (resolvedDeps.memoryAnswers && !current.enrollment?.requestedOperationFamilies.includes("memory_answer_execution")) {
708
+ return {
709
+ ...current,
710
+ ok: false,
711
+ message: `Cancel the existing enrollment, then run koed upstream enroll start --id ${backendId} --memory-answers to request Memory Answer execution explicitly.`
712
+ };
713
+ }
714
+ return current;
715
+ }
716
+ store = readStore(paths, resolvedDeps);
717
+ expectedRecord = latestEnrollment(store, backendId);
718
+ }
719
+ const rotation = await discoverRotationCredential(paths, backend, store, resolvedDeps);
720
+ if (rotation.state === "unavailable") {
721
+ return {
722
+ ok: false,
723
+ state: "failed",
724
+ backend,
725
+ message: "Could not verify the existing upstream device credential required for authenticated rotation. Retry when the Team Backend is available."
726
+ };
727
+ }
728
+ const requestId = resolvedDeps.randomId();
729
+ const deviceInstanceId = identity.deviceInstanceId;
730
+ const verifierSecret = randomSecret(resolvedDeps);
731
+ const challengeSecret = randomSecret(resolvedDeps);
732
+ const challengeHash = challengeHashFor(backendId, requestId, challengeSecret);
733
+ const credentialKeyId = `koed_${createHash2("sha256").update(`${backendId}:${requestId}:${verifierSecret}`).digest("hex").slice(0, 40)}`;
734
+ const expiresAt = expiresAtFor(now);
735
+ const localClientOperationFamilies = localClientOperationFamiliesFor(operationFamilies);
736
+ const localClientSecret = randomSecret(resolvedDeps);
737
+ const staged = await withUpstreamEnrollmentLock(paths, backendId, () => {
738
+ store = readStore(paths, resolvedDeps);
739
+ const currentRecord = latestEnrollment(store, backendId);
740
+ const currentBackend = backendById(paths, backendId, resolvedDeps).backend;
741
+ const expectedStillCurrent = expectedRecord ? sameEnrollmentIdentity(currentRecord, expectedRecord) && currentRecord.state === expectedRecord.state : currentRecord === void 0;
742
+ const currentCollaboration = upstreamBackendAdvertisesCapability(paths, backendId, "memory.collaboration", {
743
+ existsSync: resolvedDeps.existsSync,
744
+ readFileSync: resolvedDeps.readFileSync,
745
+ now: resolvedDeps.now
746
+ });
747
+ const currentOperationFamilies = currentBackend ? browserEnrollmentOperationFamilies(currentBackend.routePolicy, {
748
+ collaboration: currentCollaboration,
749
+ memoryAnswers: resolvedDeps.memoryAnswers
750
+ }) : [];
751
+ if (!expectedStillCurrent || !currentBackend || currentBackend.capabilities.state !== "validated" || JSON.stringify(currentOperationFamilies) !== JSON.stringify(operationFamilies)) {
752
+ return {
753
+ kind: "result",
754
+ result: currentRecord ? enrollmentResultFromSnapshot(backendId, currentRecord, currentBackend) : {
755
+ ok: false,
756
+ state: "failed",
757
+ backend: currentBackend,
758
+ message: `Upstream backend ${backendId} changed while enrollment was starting; retry with its current configuration.`
759
+ }
760
+ };
761
+ }
762
+ const reference = upstreamCredentialReferenceFor({
763
+ backendId,
764
+ credentialKeyId
765
+ });
766
+ const transactionGeneration = Math.max(0, ...store.enrollments.filter((entry) => entry.backendId === backendId).map((entry) => entry.transactionState.generation)) + 1;
767
+ const preparedTransaction = createUpstreamEnrollmentTransaction({
768
+ id: requestId,
769
+ generation: transactionGeneration,
770
+ kind: rotation.state === "active" ? "replacement" : "initial"
771
+ });
772
+ const stageDecision = decideUpstreamEnrollmentTransaction(preparedTransaction, { type: "prepare" });
773
+ const record = {
774
+ backendId,
775
+ requestId,
776
+ state: "pending",
777
+ activationUrl: null,
778
+ requestedOperationFamilies: operationFamilies,
779
+ transaction: rotation.state === "active" ? {
780
+ kind: "replacement",
781
+ predecessorCredentialId: rotation.credentialId,
782
+ predecessorCredentialReference: rotation.credentialReference
783
+ } : { kind: "initial" },
784
+ transactionState: stageDecision.next,
785
+ ...rotation.state === "active" ? { activeCredentialReference: rotation.credentialReference } : {},
786
+ pendingCredentialReference: reference,
787
+ challengeHash,
788
+ credentialKeyId,
789
+ credentialReference: reference,
790
+ createdAt: nowIso,
791
+ updatedAt: nowIso,
792
+ expiresAt,
793
+ credential: { status: "unknown", reference }
794
+ };
795
+ if (rotation.state === "active") {
796
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === backendId && entry.credentialReference === rotation.credentialReference ? {
797
+ ...entry,
798
+ state: "exchanged",
799
+ updatedAt: nowIso,
800
+ deviceCredentialId: rotation.credentialId,
801
+ principalUserId: rotation.principalUserId,
802
+ credential: {
803
+ status: "configured",
804
+ reference: rotation.credentialReference
805
+ }
806
+ } : entry);
807
+ }
808
+ store.enrollments.push(record);
809
+ store.updatedAt = nowIso;
810
+ writeStore(paths, store, resolvedDeps);
811
+ try {
812
+ let stagedRecord = record;
813
+ executeUpstreamEnrollmentTransactionEffect(stageDecision, {
814
+ stage_pending_custody: () => {
815
+ resolvedDeps.beforeEnrollmentEffect("stage_upstream_credential");
816
+ if (rotation.state !== "active" && localClientOperationFamilies.length > 0) {
817
+ resolvedDeps.beforeEnrollmentEffect("stage_local_client_credential");
818
+ const custody = storeEnrollmentCredentialCustody(paths.koedHome, {
819
+ upstream: { backendId, credentialKeyId, secret: verifierSecret },
820
+ localEdgeClient: {
821
+ backendId,
822
+ secret: localClientSecret,
823
+ operationFamilies: localClientOperationFamilies
824
+ }
825
+ });
826
+ if (custody.upstreamReference !== reference) {
827
+ throw new Error("Staged upstream credential reference changed.");
828
+ }
829
+ stagedRecord = {
830
+ ...stagedRecord,
831
+ localClientCredentialReference: custody.localEdgeClientReference
832
+ };
833
+ } else {
834
+ const stored = storeUpstreamCredentialSecret(paths.koedHome, {
835
+ backendId,
836
+ credentialKeyId,
837
+ secret: verifierSecret
838
+ });
839
+ if (stored.reference !== reference) {
840
+ throw new Error("Staged upstream credential reference changed.");
841
+ }
842
+ }
843
+ resolvedDeps.beforeEnrollmentEffect("stage_registry_credential");
844
+ updateUpstreamBackendCredential(paths, backendId, rotation.state === "active" ? {
845
+ status: "configured",
846
+ reference: rotation.credentialReference
847
+ } : { status: "unknown", reference }, {
848
+ existsSync: resolvedDeps.existsSync,
849
+ readFileSync: resolvedDeps.readFileSync,
850
+ writeFileSync: resolvedDeps.writeFileSync,
851
+ renameSync: resolvedDeps.renameSync,
852
+ now: resolvedDeps.now
853
+ });
854
+ }
855
+ });
856
+ stagedRecord = {
857
+ ...stagedRecord,
858
+ transactionState: decideUpstreamEnrollmentTransaction(stageDecision.next, { type: "effect_succeeded" }).next,
859
+ updatedAt: resolvedDeps.now().toISOString()
860
+ };
861
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === record.backendId && entry.requestId === record.requestId ? stagedRecord : entry);
862
+ store.updatedAt = stagedRecord.updatedAt;
863
+ resolvedDeps.beforeEnrollmentEffect("commit_prepared_state");
864
+ writeStore(paths, store, resolvedDeps);
865
+ return { kind: "record", record: stagedRecord };
866
+ } catch (error) {
867
+ const recoveryRequired = {
868
+ ...record,
869
+ transactionState: decideUpstreamEnrollmentTransaction(stageDecision.next, { type: "effect_failed" }).next,
870
+ updatedAt: resolvedDeps.now().toISOString(),
871
+ failureReason: "prepared_effect_interrupted",
872
+ failureMessage: error instanceof Error ? error.message.slice(0, 240) : "Enrollment preparation was interrupted."
873
+ };
874
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === record.backendId && entry.requestId === record.requestId ? recoveryRequired : entry);
875
+ store.updatedAt = recoveryRequired.updatedAt;
876
+ writeStore(paths, store, resolvedDeps);
877
+ return {
878
+ kind: "result",
879
+ result: {
880
+ ok: false,
881
+ state: "failed",
882
+ backend: backendById(paths, backendId, resolvedDeps).backend,
883
+ enrollment: summarizeEnrollment(recoveryRequired, backendById(paths, backendId, resolvedDeps).backend),
884
+ message: "Upstream enrollment preparation was interrupted and requires recovery."
885
+ }
886
+ };
887
+ }
888
+ });
889
+ if (staged.kind === "result")
890
+ return staged.result;
891
+ expectedRecord = staged.record;
892
+ let challenge;
893
+ try {
894
+ challenge = await createUpstreamChallenge(backend, {
895
+ backendId,
896
+ deviceInstanceId,
897
+ protocolDeploymentId: identity.deploymentId,
898
+ sourceOwnerPrincipalId: resolvedDeps.sourceOwnerPrincipalId,
899
+ challengeHash,
900
+ credentialKeyId,
901
+ verifierSecret,
902
+ operationFamilies,
903
+ requestId,
904
+ expiresAt,
905
+ ...rotation.state === "active" ? { rotation } : {}
906
+ }, resolvedDeps);
907
+ } catch (error) {
908
+ await withUpstreamEnrollmentLock(paths, backendId, () => {
909
+ const failedStore = readStore(paths, resolvedDeps);
910
+ let current = latestEnrollment(failedStore, backendId);
911
+ if (!sameEnrollmentIdentity(current, staged.record))
912
+ return;
913
+ const abortDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "cancel" });
914
+ const aborting = abortDecision.next;
915
+ current = {
916
+ ...current,
917
+ transactionState: aborting,
918
+ updatedAt: resolvedDeps.now().toISOString(),
919
+ failureReason: "challenge_creation_failed",
920
+ failureMessage: error instanceof Error ? error.message.slice(0, 240) : "Failed to create upstream enrollment challenge."
921
+ };
922
+ failedStore.enrollments = failedStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
923
+ failedStore.updatedAt = current.updatedAt;
924
+ writeStore(paths, failedStore, resolvedDeps);
925
+ let aborted;
926
+ try {
927
+ aborted = executeUpstreamEnrollmentTransactionEffect(abortDecision, {
928
+ abort_pending: () => abortPendingEnrollmentCredential(paths, backendId, current, resolvedDeps)
929
+ });
930
+ } catch (abortError) {
931
+ const recoveryRequired = {
932
+ ...current,
933
+ transactionState: decideUpstreamEnrollmentTransaction(aborting, {
934
+ type: "effect_failed"
935
+ }).next,
936
+ updatedAt: resolvedDeps.now().toISOString(),
937
+ failureReason: "challenge_abort_interrupted",
938
+ failureMessage: abortError instanceof Error ? abortError.message.slice(0, 240) : "Challenge cleanup was interrupted."
939
+ };
940
+ failedStore.enrollments = failedStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? recoveryRequired : entry);
941
+ failedStore.updatedAt = recoveryRequired.updatedAt;
942
+ writeStore(paths, failedStore, resolvedDeps);
943
+ return;
944
+ }
945
+ const transactionState = {
946
+ ...decideUpstreamEnrollmentTransaction(aborting, {
947
+ type: "effect_succeeded"
948
+ }).next,
949
+ state: "failed"
950
+ };
951
+ const failed = {
952
+ ...current,
953
+ state: "failed",
954
+ activationUrl: null,
955
+ updatedAt: resolvedDeps.now().toISOString(),
956
+ transactionState,
957
+ credential: aborted.credential,
958
+ failureReason: "challenge_creation_failed",
959
+ failureMessage: current.failureMessage
960
+ };
961
+ delete failed.pendingCredentialReference;
962
+ failedStore.enrollments = failedStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? failed : entry);
963
+ failedStore.updatedAt = failed.updatedAt;
964
+ writeStore(paths, failedStore, resolvedDeps);
965
+ });
966
+ return {
967
+ ok: false,
968
+ state: "failed",
969
+ backend: backendById(paths, backendId, resolvedDeps).backend,
970
+ message: error instanceof Error ? error.message : "Failed to create upstream enrollment challenge."
971
+ };
972
+ }
973
+ return withUpstreamEnrollmentLock(paths, backendId, () => {
974
+ const currentStore = readStore(paths, resolvedDeps);
975
+ const current = latestEnrollment(currentStore, backendId);
976
+ const currentBackend = backendById(paths, backendId, resolvedDeps).backend;
977
+ if (!sameEnrollmentIdentity(current, staged.record)) {
978
+ return enrollmentResultFromSnapshot(backendId, current, currentBackend);
979
+ }
980
+ const challengeDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "challenge_created" });
981
+ const pending = executeUpstreamEnrollmentTransactionEffect(challengeDecision, {
982
+ record_challenge: () => ({
983
+ ...current,
984
+ challengeId: challenge.challengeId,
985
+ activationUrl: challenge.activationUrl,
986
+ transactionState: decideUpstreamEnrollmentTransaction(challengeDecision.next, { type: "effect_succeeded" }).next,
987
+ updatedAt: resolvedDeps.now().toISOString()
988
+ })
989
+ });
990
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? pending : entry);
991
+ currentStore.updatedAt = pending.updatedAt;
992
+ writeStore(paths, currentStore, resolvedDeps);
993
+ return {
994
+ ok: true,
995
+ state: "pending",
996
+ backend: currentBackend,
997
+ enrollment: summarizeEnrollment(pending, currentBackend),
998
+ message: `Started upstream enrollment for ${backendId}. Open the activation URL to approve this local edge.`
999
+ };
1000
+ });
1001
+ };
1002
+ var startUpstreamEnrollment = (paths, id, deps = {}) => withSecureFetchForBackend(paths, id, deps, (resolvedDeps) => startUpstreamEnrollmentWithFetch(paths, id, resolvedDeps));
1003
+ var abortPendingEnrollmentCredential = (paths, backendId, record, deps) => {
1004
+ const replacement = replacementTransaction(record);
1005
+ deps.beforeEnrollmentEffect("abort_delete_pending_credential");
1006
+ deleteUpstreamCredentialSecret(paths.koedHome, record.credentialReference);
1007
+ if (replacement && replacement.predecessorCredentialReference !== record.credentialReference) {
1008
+ deps.beforeEnrollmentEffect("abort_restore_predecessor_registry");
1009
+ updateUpstreamBackendCredential(paths, backendId, {
1010
+ status: "configured",
1011
+ reference: replacement.predecessorCredentialReference
1012
+ }, {
1013
+ existsSync: deps.existsSync,
1014
+ readFileSync: deps.readFileSync,
1015
+ writeFileSync: deps.writeFileSync,
1016
+ renameSync: deps.renameSync,
1017
+ now: deps.now
1018
+ });
1019
+ return {
1020
+ credential: {
1021
+ status: "configured",
1022
+ reference: replacement.predecessorCredentialReference
1023
+ },
1024
+ predecessorRestored: true
1025
+ };
1026
+ }
1027
+ deps.beforeEnrollmentEffect("abort_delete_local_client_credential");
1028
+ deleteLocalEdgeClientCredential(paths.koedHome, backendId);
1029
+ deps.beforeEnrollmentEffect("abort_clear_registry_credential");
1030
+ updateUpstreamBackendCredential(paths, backendId, { status: "not_configured" }, {
1031
+ existsSync: deps.existsSync,
1032
+ readFileSync: deps.readFileSync,
1033
+ writeFileSync: deps.writeFileSync,
1034
+ renameSync: deps.renameSync,
1035
+ now: deps.now
1036
+ });
1037
+ return {
1038
+ credential: { status: "not_configured" },
1039
+ predecessorRestored: false
1040
+ };
1041
+ };
1042
+ var getUpstreamEnrollmentStatusWithFetch = async (paths, id, deps = {}) => {
1043
+ const resolvedDeps = depsWithDefaults(deps);
1044
+ const backendId = validateBackendId(id);
1045
+ const identity = await ensureDeviceIdentity(paths);
1046
+ if (!identity.remoteOperationsAllowed || !identity.deviceInstanceId) {
1047
+ return {
1048
+ ok: false,
1049
+ state: "failed",
1050
+ message: `Local device identity is ${identity.health}; upstream enrollment is blocked until explicit identity rotation.`
1051
+ };
1052
+ }
1053
+ const now = resolvedDeps.now();
1054
+ const { backend } = backendById(paths, backendId, resolvedDeps);
1055
+ const store = readStore(paths, resolvedDeps);
1056
+ const record = latestEnrollment(store, backendId);
1057
+ if (!record) {
1058
+ return {
1059
+ ok: true,
1060
+ state: "missing",
1061
+ backend,
1062
+ message: `No upstream enrollment has been started for ${backendId}.`
1063
+ };
1064
+ }
1065
+ if (record.transactionState.phase === "recovery_required" && record.transactionState.pendingEffect === "stage_pending_custody") {
1066
+ return withUpstreamEnrollmentLock(paths, backendId, () => {
1067
+ const recoveryStore = readStore(paths, resolvedDeps);
1068
+ const current = latestEnrollment(recoveryStore, backendId);
1069
+ const currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1070
+ if (!sameEnrollmentIdentity(current, record)) {
1071
+ return enrollmentResultFromSnapshot(backendId, current, currentBackend);
1072
+ }
1073
+ const recoveryDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "recover" });
1074
+ const aborted = executeUpstreamEnrollmentTransactionEffect(recoveryDecision, {
1075
+ compensate_pending_custody: () => abortPendingEnrollmentCredential(paths, backendId, current, resolvedDeps)
1076
+ });
1077
+ const recovered = {
1078
+ ...current,
1079
+ state: "failed",
1080
+ activationUrl: null,
1081
+ updatedAt: resolvedDeps.now().toISOString(),
1082
+ transactionState: decideUpstreamEnrollmentTransaction(recoveryDecision.next, { type: "effect_succeeded" }).next,
1083
+ credential: aborted.credential,
1084
+ failureReason: "prepared_effect_compensated",
1085
+ failureMessage: "Interrupted enrollment preparation was compensated; start enrollment again."
1086
+ };
1087
+ delete recovered.pendingCredentialReference;
1088
+ recoveryStore.enrollments = recoveryStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? recovered : entry);
1089
+ recoveryStore.updatedAt = recovered.updatedAt;
1090
+ writeStore(paths, recoveryStore, resolvedDeps);
1091
+ return {
1092
+ ok: false,
1093
+ state: "failed",
1094
+ backend: backendById(paths, backendId, resolvedDeps).backend,
1095
+ enrollment: summarizeEnrollment(recovered, backendById(paths, backendId, resolvedDeps).backend),
1096
+ message: recovered.failureMessage
1097
+ };
1098
+ });
1099
+ }
1100
+ if ((record.transactionState.phase === "aborting" || record.transactionState.phase === "recovery_required") && record.transactionState.pendingEffect === "abort_pending") {
1101
+ return withUpstreamEnrollmentLock(paths, backendId, () => {
1102
+ const recoveryStore = readStore(paths, resolvedDeps);
1103
+ const current = latestEnrollment(recoveryStore, backendId);
1104
+ let currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1105
+ if (!sameEnrollmentIdentity(current, record)) {
1106
+ return enrollmentResultFromSnapshot(backendId, current, currentBackend);
1107
+ }
1108
+ const replacement = replacementTransaction(current);
1109
+ const resumed = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "recover" });
1110
+ const aborted = executeUpstreamEnrollmentTransactionEffect(resumed, {
1111
+ abort_pending: () => abortPendingEnrollmentCredential(paths, backendId, current, resolvedDeps)
1112
+ });
1113
+ const recovered = {
1114
+ ...current,
1115
+ state: current.transactionState.state,
1116
+ activationUrl: null,
1117
+ updatedAt: resolvedDeps.now().toISOString(),
1118
+ transactionState: decideUpstreamEnrollmentTransaction(resumed.next, {
1119
+ type: "effect_succeeded"
1120
+ }).next,
1121
+ credential: aborted.credential,
1122
+ ...aborted.predecessorRestored && replacement ? {
1123
+ credentialReference: replacement.predecessorCredentialReference,
1124
+ activeCredentialReference: replacement.predecessorCredentialReference
1125
+ } : {}
1126
+ };
1127
+ delete recovered.pendingCredentialReference;
1128
+ recoveryStore.enrollments = recoveryStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? recovered : entry);
1129
+ recoveryStore.updatedAt = recovered.updatedAt;
1130
+ writeStore(paths, recoveryStore, resolvedDeps);
1131
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1132
+ return {
1133
+ ok: true,
1134
+ state: recovered.state,
1135
+ backend: currentBackend,
1136
+ enrollment: summarizeEnrollment(recovered, currentBackend),
1137
+ message: `Upstream enrollment for ${backendId} is ${recovered.state}.`
1138
+ };
1139
+ });
1140
+ }
1141
+ const materialized = materializeState(record, now, backend);
1142
+ let credentialStatus = null;
1143
+ let upstreamStatus = null;
1144
+ if (backend && enrollmentCanReceiveRemoteStatus(materialized) && materialized.credentialReference) {
1145
+ credentialStatus = await remoteCredentialStatus(paths, backend, materialized.credentialReference, resolvedDeps);
1146
+ if (credentialStatus?.state !== "active" && !(materialized.state === "exchanged" && (credentialStatus?.state === "rejected" || credentialStatus?.state === "unknown")) && materialized.challengeId) {
1147
+ upstreamStatus = await readUpstreamChallengeStatus(backend, materialized.challengeId, identity.deviceInstanceId, resolvedDeps);
1148
+ }
1149
+ }
1150
+ return withUpstreamEnrollmentLock(paths, backendId, () => {
1151
+ const currentStore = readStore(paths, resolvedDeps);
1152
+ const currentRecord = latestEnrollment(currentStore, backendId);
1153
+ let currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1154
+ if (!sameEnrollmentIdentity(currentRecord, record)) {
1155
+ return enrollmentResultFromSnapshot(backendId, currentRecord, currentBackend);
1156
+ }
1157
+ let current = materializeState(currentRecord, now, currentBackend);
1158
+ let temporaryCredentialStatusFailure = false;
1159
+ const recordReplacement = replacementTransaction(record);
1160
+ const currentReplacement = replacementTransaction(current);
1161
+ const remoteResultStillApplies = currentBackend && enrollmentCanReceiveRemoteStatus(current) && current.credentialReference === record.credentialReference && currentBackend.credential.status !== "revoked" && currentBackend.credential.status !== "not_configured" && (currentBackend.credential.reference === record.credentialReference || recordReplacement !== null && currentBackend.credential.reference === recordReplacement.predecessorCredentialReference);
1162
+ if (remoteResultStillApplies && credentialStatus?.state === "active") {
1163
+ const commitDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "credential_observed", status: "active" });
1164
+ current = {
1165
+ ...current,
1166
+ transactionState: commitDecision.next,
1167
+ updatedAt: now.toISOString()
1168
+ };
1169
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
1170
+ currentStore.updatedAt = current.updatedAt;
1171
+ writeStore(paths, currentStore, resolvedDeps);
1172
+ try {
1173
+ executeUpstreamEnrollmentTransactionEffect(commitDecision, {
1174
+ commit_successor: () => {
1175
+ if (currentReplacement) {
1176
+ const localFamilies = localClientOperationFamiliesFor(current.requestedOperationFamilies);
1177
+ if (localFamilies.length > 0) {
1178
+ resolvedDeps.beforeEnrollmentEffect("commit_local_client_credential");
1179
+ const localClient = storeLocalEdgeClientCredential(paths.koedHome, {
1180
+ backendId,
1181
+ secret: randomSecret(resolvedDeps),
1182
+ operationFamilies: localFamilies
1183
+ });
1184
+ current = {
1185
+ ...current,
1186
+ localClientCredentialReference: localClient.reference
1187
+ };
1188
+ } else {
1189
+ resolvedDeps.beforeEnrollmentEffect("commit_delete_local_client_credential");
1190
+ deleteLocalEdgeClientCredential(paths.koedHome, backendId);
1191
+ }
1192
+ }
1193
+ resolvedDeps.beforeEnrollmentEffect("commit_registry_credential");
1194
+ updateUpstreamBackendCredential(paths, backendId, {
1195
+ status: "configured",
1196
+ reference: current.credentialReference
1197
+ }, {
1198
+ existsSync: resolvedDeps.existsSync,
1199
+ readFileSync: resolvedDeps.readFileSync,
1200
+ writeFileSync: resolvedDeps.writeFileSync,
1201
+ renameSync: resolvedDeps.renameSync,
1202
+ now: resolvedDeps.now
1203
+ });
1204
+ if (current.requestedOperationFamilies.includes("memory_answer_execution")) {
1205
+ resolvedDeps.beforeEnrollmentEffect("commit_memory_answer_route_policy");
1206
+ updateUpstreamBackendRoutePolicy(paths, backendId, { memoryAnswerExecution: "enabled" }, {
1207
+ existsSync: resolvedDeps.existsSync,
1208
+ readFileSync: resolvedDeps.readFileSync,
1209
+ writeFileSync: resolvedDeps.writeFileSync,
1210
+ renameSync: resolvedDeps.renameSync,
1211
+ now: resolvedDeps.now
1212
+ });
1213
+ }
1214
+ if (currentReplacement && currentReplacement.predecessorCredentialReference !== current.credentialReference) {
1215
+ resolvedDeps.beforeEnrollmentEffect("commit_delete_predecessor_credential");
1216
+ deleteUpstreamCredentialSecret(paths.koedHome, currentReplacement.predecessorCredentialReference);
1217
+ }
1218
+ }
1219
+ });
1220
+ } catch (error) {
1221
+ current = {
1222
+ ...current,
1223
+ transactionState: decideUpstreamEnrollmentTransaction(commitDecision.next, { type: "effect_failed" }).next,
1224
+ updatedAt: now.toISOString(),
1225
+ failureReason: "commit_effect_interrupted",
1226
+ failureMessage: error instanceof Error ? error.message.slice(0, 240) : "Enrollment commit was interrupted."
1227
+ };
1228
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
1229
+ currentStore.updatedAt = current.updatedAt;
1230
+ writeStore(paths, currentStore, resolvedDeps);
1231
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1232
+ return {
1233
+ ok: false,
1234
+ state: current.state,
1235
+ backend: currentBackend,
1236
+ enrollment: summarizeEnrollment(current, currentBackend),
1237
+ message: "Upstream enrollment commit was interrupted and will recover on retry."
1238
+ };
1239
+ }
1240
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1241
+ current = {
1242
+ ...current,
1243
+ state: "exchanged",
1244
+ updatedAt: now.toISOString(),
1245
+ failureReason: void 0,
1246
+ failureMessage: void 0,
1247
+ transactionState: decideUpstreamEnrollmentTransaction(commitDecision.next, { type: "effect_succeeded" }).next,
1248
+ activeCredentialReference: current.credentialReference,
1249
+ credential: {
1250
+ status: "configured",
1251
+ reference: current.credentialReference
1252
+ },
1253
+ deviceCredentialId: credentialStatus.deviceCredentialId,
1254
+ principalUserId: credentialStatus.principalUserId
1255
+ };
1256
+ delete current.pendingCredentialReference;
1257
+ } else if (remoteResultStillApplies && credentialStatus?.state === "rejected" && current.state === "exchanged") {
1258
+ const revokeDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "credential_observed", status: "rejected" });
1259
+ current = {
1260
+ ...current,
1261
+ transactionState: revokeDecision.next,
1262
+ updatedAt: now.toISOString()
1263
+ };
1264
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
1265
+ currentStore.updatedAt = current.updatedAt;
1266
+ writeStore(paths, currentStore, resolvedDeps);
1267
+ executeUpstreamEnrollmentTransactionEffect(revokeDecision, {
1268
+ revoke_active: () => {
1269
+ deleteUpstreamCredentialSecret(paths.koedHome, current.credentialReference);
1270
+ deleteLocalEdgeClientCredential(paths.koedHome, backendId);
1271
+ updateUpstreamBackendCredential(paths, backendId, { status: "not_configured" }, {
1272
+ existsSync: resolvedDeps.existsSync,
1273
+ readFileSync: resolvedDeps.readFileSync,
1274
+ writeFileSync: resolvedDeps.writeFileSync,
1275
+ renameSync: resolvedDeps.renameSync,
1276
+ now: resolvedDeps.now
1277
+ });
1278
+ }
1279
+ });
1280
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1281
+ current = {
1282
+ ...current,
1283
+ state: "failed",
1284
+ updatedAt: now.toISOString(),
1285
+ transactionState: decideUpstreamEnrollmentTransaction(revokeDecision.next, { type: "effect_succeeded" }).next,
1286
+ failureReason: "credential_rejected",
1287
+ failureMessage: "Upstream backend rejected the stored device credential; restart enrollment.",
1288
+ credential: { status: "not_configured" }
1289
+ };
1290
+ delete current.activeCredentialReference;
1291
+ } else if (remoteResultStillApplies && credentialStatus?.state === "unknown" && current.state === "exchanged") {
1292
+ temporaryCredentialStatusFailure = true;
1293
+ current = {
1294
+ ...current,
1295
+ updatedAt: now.toISOString(),
1296
+ failureReason: "credential_status_unavailable",
1297
+ failureMessage: "Could not verify the upstream device credential. Stored credentials were kept; retry when the Team Backend is available."
1298
+ };
1299
+ } else if (remoteResultStillApplies) {
1300
+ if (upstreamStatus === "denied" || upstreamStatus === "expired") {
1301
+ const abortDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "challenge_observed", status: upstreamStatus });
1302
+ current = {
1303
+ ...current,
1304
+ transactionState: abortDecision.next,
1305
+ updatedAt: now.toISOString()
1306
+ };
1307
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
1308
+ currentStore.updatedAt = current.updatedAt;
1309
+ writeStore(paths, currentStore, resolvedDeps);
1310
+ const aborted = executeUpstreamEnrollmentTransactionEffect(abortDecision, {
1311
+ abort_pending: () => abortPendingEnrollmentCredential(paths, backendId, current, resolvedDeps)
1312
+ });
1313
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1314
+ current = {
1315
+ ...current,
1316
+ state: upstreamStatus,
1317
+ updatedAt: now.toISOString(),
1318
+ transactionState: decideUpstreamEnrollmentTransaction(abortDecision.next, { type: "effect_succeeded" }).next,
1319
+ credential: aborted.credential,
1320
+ ...aborted.predecessorRestored ? {
1321
+ credentialReference: currentReplacement.predecessorCredentialReference,
1322
+ activeCredentialReference: currentReplacement.predecessorCredentialReference
1323
+ } : {}
1324
+ };
1325
+ delete current.pendingCredentialReference;
1326
+ } else if (upstreamStatus === "approved") {
1327
+ const approval = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "challenge_observed", status: "approved" });
1328
+ current = {
1329
+ ...current,
1330
+ state: "approved",
1331
+ transactionState: approval.next,
1332
+ updatedAt: now.toISOString()
1333
+ };
1334
+ }
1335
+ }
1336
+ if (current.state === "expired" && current.credential?.status !== "configured" && current.transactionState.phase !== "aborted") {
1337
+ const expiringReplacement = replacementTransaction(current);
1338
+ const abortDecision = decideUpstreamEnrollmentTransaction(current.transactionState, { type: "challenge_observed", status: "expired" });
1339
+ current = {
1340
+ ...current,
1341
+ transactionState: abortDecision.next,
1342
+ updatedAt: now.toISOString()
1343
+ };
1344
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === current.backendId && entry.requestId === current.requestId ? current : entry);
1345
+ currentStore.updatedAt = current.updatedAt;
1346
+ writeStore(paths, currentStore, resolvedDeps);
1347
+ const aborted = executeUpstreamEnrollmentTransactionEffect(abortDecision, {
1348
+ abort_pending: () => abortPendingEnrollmentCredential(paths, backendId, current, resolvedDeps)
1349
+ });
1350
+ currentBackend = backendById(paths, backendId, resolvedDeps).backend;
1351
+ current = {
1352
+ ...current,
1353
+ transactionState: decideUpstreamEnrollmentTransaction(abortDecision.next, { type: "effect_succeeded" }).next,
1354
+ credential: aborted.credential,
1355
+ ...aborted.predecessorRestored ? {
1356
+ credentialReference: expiringReplacement.predecessorCredentialReference,
1357
+ activeCredentialReference: expiringReplacement.predecessorCredentialReference
1358
+ } : {}
1359
+ };
1360
+ delete current.pendingCredentialReference;
1361
+ }
1362
+ if (JSON.stringify(current) !== JSON.stringify(currentRecord)) {
1363
+ currentStore.enrollments = currentStore.enrollments.map((entry) => entry.backendId === currentRecord.backendId && entry.requestId === currentRecord.requestId ? current : entry);
1364
+ currentStore.updatedAt = now.toISOString();
1365
+ resolvedDeps.beforeEnrollmentEffect("commit_enrollment_state");
1366
+ writeStore(paths, currentStore, resolvedDeps);
1367
+ }
1368
+ return {
1369
+ ok: !temporaryCredentialStatusFailure,
1370
+ state: current.state,
1371
+ backend: currentBackend,
1372
+ enrollment: summarizeEnrollment(current, currentBackend),
1373
+ message: temporaryCredentialStatusFailure ? current.failureMessage : `Upstream enrollment for ${backendId} is ${current.state}.`
1374
+ };
1375
+ });
1376
+ };
1377
+ var getUpstreamEnrollmentStatus = (paths, id, deps = {}) => withSecureFetchForBackend(paths, id, deps, (resolvedDeps) => getUpstreamEnrollmentStatusWithFetch(paths, id, resolvedDeps));
1378
+ var cancelUpstreamEnrollment = async (paths, id, deps = {}) => {
1379
+ const resolvedDeps = depsWithDefaults(deps);
1380
+ const backendId = validateBackendId(id);
1381
+ return withUpstreamEnrollmentLock(paths, backendId, () => {
1382
+ const nowDate = resolvedDeps.now();
1383
+ const now = nowDate.toISOString();
1384
+ const { backend } = backendById(paths, backendId, resolvedDeps);
1385
+ const store = readStore(paths, resolvedDeps);
1386
+ const record = latestEnrollment(store, backendId);
1387
+ if (!record) {
1388
+ return {
1389
+ ok: true,
1390
+ state: "missing",
1391
+ backend,
1392
+ message: `No upstream enrollment has been started for ${backendId}.`
1393
+ };
1394
+ }
1395
+ const materialized = materializeState(record, nowDate, backend);
1396
+ if (materialized.transactionState.pendingEffect === "commit_successor") {
1397
+ return {
1398
+ ok: false,
1399
+ state: materialized.state,
1400
+ backend,
1401
+ enrollment: summarizeEnrollment(materialized, backend),
1402
+ message: "The successor credential is already active and its local commit must recover before enrollment can be canceled."
1403
+ };
1404
+ }
1405
+ if (materialized.state === "exchanged" || materialized.state === "revoked" || materialized.state === "expired" || materialized.state === "failed" || materialized.state === "denied") {
1406
+ return {
1407
+ ok: true,
1408
+ state: materialized.state,
1409
+ backend,
1410
+ enrollment: summarizeEnrollment(materialized, backend),
1411
+ message: `Upstream enrollment for ${backendId} is already ${materialized.state}.`
1412
+ };
1413
+ }
1414
+ const abortDecision = decideUpstreamEnrollmentTransaction(materialized.transactionState, { type: "cancel" });
1415
+ const aborting = {
1416
+ ...materialized,
1417
+ transactionState: abortDecision.next,
1418
+ updatedAt: now
1419
+ };
1420
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === record.backendId && entry.requestId === record.requestId ? aborting : entry);
1421
+ store.updatedAt = now;
1422
+ writeStore(paths, store, resolvedDeps);
1423
+ const aborted = executeUpstreamEnrollmentTransactionEffect(abortDecision, {
1424
+ abort_pending: () => abortPendingEnrollmentCredential(paths, backendId, aborting, resolvedDeps)
1425
+ });
1426
+ const canceledReplacement = replacementTransaction(aborting);
1427
+ const canceled = {
1428
+ ...aborting,
1429
+ state: "canceled",
1430
+ updatedAt: now,
1431
+ transactionState: decideUpstreamEnrollmentTransaction(abortDecision.next, { type: "effect_succeeded" }).next,
1432
+ credential: aborted.credential,
1433
+ ...aborted.predecessorRestored ? {
1434
+ credentialReference: canceledReplacement.predecessorCredentialReference,
1435
+ activeCredentialReference: canceledReplacement.predecessorCredentialReference
1436
+ } : {}
1437
+ };
1438
+ delete canceled.pendingCredentialReference;
1439
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === record.backendId && entry.requestId === record.requestId ? canceled : entry);
1440
+ store.updatedAt = now;
1441
+ writeStore(paths, store, resolvedDeps);
1442
+ if (!aborted.predecessorRestored && getActiveUpstreamBackend(paths)?.id === backendId) {
1443
+ setActiveUpstreamBackend(paths, null, {
1444
+ existsSync: resolvedDeps.existsSync,
1445
+ readFileSync: resolvedDeps.readFileSync,
1446
+ writeFileSync: resolvedDeps.writeFileSync,
1447
+ renameSync: resolvedDeps.renameSync,
1448
+ now: resolvedDeps.now
1449
+ });
1450
+ }
1451
+ return {
1452
+ ok: true,
1453
+ state: "canceled",
1454
+ backend,
1455
+ enrollment: summarizeEnrollment(canceled, backend),
1456
+ message: `Canceled upstream enrollment for ${backendId}.`
1457
+ };
1458
+ });
1459
+ };
1460
+ var invalidateUpstreamEnrollmentReferences = async (paths, deps = {}) => {
1461
+ const resolvedDeps = depsWithDefaults(deps);
1462
+ const registry = collectUpstreamRegistryStatus(paths, {
1463
+ existsSync: resolvedDeps.existsSync,
1464
+ readFileSync: resolvedDeps.readFileSync,
1465
+ now: resolvedDeps.now
1466
+ });
1467
+ if (registry.parseError) {
1468
+ throw new Error("Upstream backend registry is malformed.");
1469
+ }
1470
+ let pendingRemoteRevocation = false;
1471
+ for (const backend of registry.backends) {
1472
+ await withUpstreamEnrollmentLock(paths, backend.id, () => {
1473
+ const now = resolvedDeps.now().toISOString();
1474
+ const current = backendById(paths, backend.id, resolvedDeps).backend;
1475
+ if (!current)
1476
+ return;
1477
+ if (current.credential.reference && current.credential.status !== "revoked" && current.credential.status !== "not_configured") {
1478
+ pendingRemoteRevocation = true;
1479
+ }
1480
+ updateUpstreamBackendRoutePolicy(paths, backend.id, {
1481
+ personalMemoryRead: "disabled",
1482
+ personalCollaboration: "disabled",
1483
+ teamWorkspaceRead: "disabled",
1484
+ memoryAnswerExecution: "disabled",
1485
+ shareGrantManagement: "disabled",
1486
+ captureWrites: "disabled",
1487
+ sync: "disabled",
1488
+ admin: "disabled"
1489
+ }, {
1490
+ existsSync: resolvedDeps.existsSync,
1491
+ readFileSync: resolvedDeps.readFileSync,
1492
+ writeFileSync: resolvedDeps.writeFileSync,
1493
+ renameSync: resolvedDeps.renameSync,
1494
+ now: resolvedDeps.now
1495
+ });
1496
+ updateUpstreamBackendCredential(paths, backend.id, { status: "revoked", reference: current.credential.reference }, {
1497
+ existsSync: resolvedDeps.existsSync,
1498
+ readFileSync: resolvedDeps.readFileSync,
1499
+ writeFileSync: resolvedDeps.writeFileSync,
1500
+ renameSync: resolvedDeps.renameSync,
1501
+ now: resolvedDeps.now
1502
+ });
1503
+ deleteUpstreamCredentialSecret(paths.koedHome, current.credential.reference);
1504
+ deleteLocalEdgeClientCredential(paths.koedHome, backend.id);
1505
+ const store = readStore(paths, resolvedDeps);
1506
+ store.enrollments = store.enrollments.map((record) => record.backendId === backend.id ? {
1507
+ ...record,
1508
+ state: "revoked",
1509
+ updatedAt: now,
1510
+ credential: {
1511
+ status: "revoked",
1512
+ reference: record.credentialReference
1513
+ }
1514
+ } : record);
1515
+ store.updatedAt = now;
1516
+ writeStore(paths, store, resolvedDeps);
1517
+ });
1518
+ }
1519
+ return { pendingRemoteRevocation };
1520
+ };
1521
+ var disconnectUpstreamBackendEnrollmentWithFetch = async (paths, id, deps = {}) => {
1522
+ const resolvedDeps = depsWithDefaults(deps);
1523
+ const backendId = validateBackendId(id);
1524
+ return withUpstreamEnrollmentLock(paths, backendId, async () => {
1525
+ const now = resolvedDeps.now().toISOString();
1526
+ const { backend } = backendById(paths, backendId, resolvedDeps);
1527
+ if (!backend) {
1528
+ return {
1529
+ ok: false,
1530
+ state: "missing",
1531
+ message: `Upstream backend ${backendId} is not registered.`
1532
+ };
1533
+ }
1534
+ beginUpstreamDisconnectCleanup(paths, backendId, {
1535
+ now: resolvedDeps.now
1536
+ });
1537
+ if (backend.credential.status !== "revoked" && backend.credential.status !== "not_configured") {
1538
+ const remoteRevocation = await revokeRemoteCredential(paths, backend, backend.credential.reference, resolvedDeps);
1539
+ if (remoteRevocation === "unavailable") {
1540
+ updateUpstreamDisconnectCleanup(paths, backendId, {
1541
+ phase: "remote_revocation_pending",
1542
+ lastFailureCategory: "remote_unavailable"
1543
+ }, { now: resolvedDeps.now });
1544
+ return {
1545
+ ok: false,
1546
+ state: "failed",
1547
+ backend,
1548
+ message: "The upstream device credential could not be revoked. Local credentials and routes were kept so disconnect can be retried safely."
1549
+ };
1550
+ }
1551
+ }
1552
+ updateUpstreamDisconnectCleanup(paths, backendId, {
1553
+ phase: "local_cleanup_pending",
1554
+ lastFailureCategory: null
1555
+ }, { now: resolvedDeps.now });
1556
+ updateUpstreamBackendRoutePolicy(paths, backendId, {
1557
+ personalMemoryRead: "disabled",
1558
+ personalCollaboration: "disabled",
1559
+ teamWorkspaceRead: "disabled",
1560
+ memoryAnswerExecution: "disabled",
1561
+ shareGrantManagement: "disabled",
1562
+ captureWrites: "disabled",
1563
+ sync: "disabled",
1564
+ admin: "disabled"
1565
+ }, {
1566
+ existsSync: resolvedDeps.existsSync,
1567
+ readFileSync: resolvedDeps.readFileSync,
1568
+ writeFileSync: resolvedDeps.writeFileSync,
1569
+ renameSync: resolvedDeps.renameSync,
1570
+ now: resolvedDeps.now
1571
+ });
1572
+ updateUpstreamBackendCredential(paths, backendId, { status: "revoked" }, {
1573
+ existsSync: resolvedDeps.existsSync,
1574
+ readFileSync: resolvedDeps.readFileSync,
1575
+ writeFileSync: resolvedDeps.writeFileSync,
1576
+ renameSync: resolvedDeps.renameSync,
1577
+ now: resolvedDeps.now
1578
+ });
1579
+ clearUpstreamBackendCapabilities(paths, backendId, {
1580
+ existsSync: resolvedDeps.existsSync,
1581
+ readFileSync: resolvedDeps.readFileSync,
1582
+ writeFileSync: resolvedDeps.writeFileSync,
1583
+ renameSync: resolvedDeps.renameSync,
1584
+ now: resolvedDeps.now
1585
+ });
1586
+ deleteUpstreamCredentialSecret(paths.koedHome, backend.credential.reference);
1587
+ deleteLocalEdgeClientCredential(paths.koedHome, backendId);
1588
+ const refreshedBackend = backendById(paths, backendId, resolvedDeps).backend;
1589
+ const store = readStore(paths, resolvedDeps);
1590
+ const existing = latestEnrollment(store, backendId);
1591
+ const record = {
1592
+ backendId,
1593
+ requestId: existing?.requestId ?? resolvedDeps.randomId(),
1594
+ state: "revoked",
1595
+ activationUrl: null,
1596
+ requestedOperationFamilies: [],
1597
+ transaction: { kind: "initial" },
1598
+ transactionState: {
1599
+ ...createUpstreamEnrollmentTransaction({
1600
+ id: existing?.transactionState.id ?? existing?.requestId ?? "revoked",
1601
+ generation: existing?.transactionState.generation ?? 1,
1602
+ kind: "initial"
1603
+ }),
1604
+ phase: "aborted",
1605
+ state: "revoked",
1606
+ pendingEffect: null
1607
+ },
1608
+ updatedAt: now,
1609
+ createdAt: existing?.createdAt ?? now,
1610
+ expiresAt: null,
1611
+ credential: { status: "revoked" }
1612
+ };
1613
+ if (existing) {
1614
+ store.enrollments = store.enrollments.map((entry) => entry.backendId === existing.backendId && entry.requestId === existing.requestId ? record : entry);
1615
+ } else {
1616
+ store.enrollments.push(record);
1617
+ }
1618
+ store.updatedAt = now;
1619
+ writeStore(paths, store, resolvedDeps);
1620
+ if (getActiveUpstreamBackend(paths)?.id === backendId) {
1621
+ setActiveUpstreamBackend(paths, null, {
1622
+ existsSync: resolvedDeps.existsSync,
1623
+ readFileSync: resolvedDeps.readFileSync,
1624
+ writeFileSync: resolvedDeps.writeFileSync,
1625
+ renameSync: resolvedDeps.renameSync,
1626
+ now: resolvedDeps.now
1627
+ });
1628
+ }
1629
+ return {
1630
+ ok: true,
1631
+ state: "revoked",
1632
+ backend: refreshedBackend,
1633
+ enrollment: summarizeEnrollment(record, refreshedBackend),
1634
+ message: `Disconnected upstream backend ${backendId} and revoked its exchanged device credential.`
1635
+ };
1636
+ });
1637
+ };
1638
+ var disconnectUpstreamBackendEnrollment = (paths, id, deps = {}) => withSecureFetchForBackend(paths, id, deps, (resolvedDeps) => disconnectUpstreamBackendEnrollmentWithFetch(paths, id, resolvedDeps));
1639
+
1640
+ export {
1641
+ readUpstreamEnrollmentBinding,
1642
+ startUpstreamEnrollment,
1643
+ getUpstreamEnrollmentStatus,
1644
+ cancelUpstreamEnrollment,
1645
+ invalidateUpstreamEnrollmentReferences,
1646
+ disconnectUpstreamBackendEnrollment
1647
+ };