@cotal-ai/workspace 0.68.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/auth-paths.d.ts +16 -19
  2. package/dist/auth-paths.d.ts.map +1 -1
  3. package/dist/auth-paths.js +126 -170
  4. package/dist/auth-paths.js.map +1 -1
  5. package/dist/bin-path.d.ts +7 -6
  6. package/dist/bin-path.d.ts.map +1 -1
  7. package/dist/bin-path.js +11 -7
  8. package/dist/bin-path.js.map +1 -1
  9. package/dist/connect.d.ts.map +1 -1
  10. package/dist/connect.js +11 -11
  11. package/dist/connect.js.map +1 -1
  12. package/dist/default-agent.d.ts +9 -0
  13. package/dist/default-agent.d.ts.map +1 -1
  14. package/dist/default-agent.js +8 -0
  15. package/dist/default-agent.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/join-target.d.ts +3 -50
  21. package/dist/join-target.d.ts.map +1 -1
  22. package/dist/join-target.js +5 -18
  23. package/dist/join-target.js.map +1 -1
  24. package/dist/local-process.d.ts +5 -1
  25. package/dist/local-process.d.ts.map +1 -1
  26. package/dist/local-process.js +14 -11
  27. package/dist/local-process.js.map +1 -1
  28. package/dist/manager-shutdown.d.ts.map +1 -1
  29. package/dist/manager-shutdown.js +5 -5
  30. package/dist/manager-shutdown.js.map +1 -1
  31. package/dist/mesh-registry.d.ts +11 -0
  32. package/dist/mesh-registry.d.ts.map +1 -1
  33. package/dist/mesh-registry.js +69 -37
  34. package/dist/mesh-registry.js.map +1 -1
  35. package/dist/registration-policy.d.ts +5 -0
  36. package/dist/registration-policy.d.ts.map +1 -1
  37. package/dist/registration-policy.js +12 -4
  38. package/dist/registration-policy.js.map +1 -1
  39. package/dist/secret-store-fs.d.ts +17 -2
  40. package/dist/secret-store-fs.d.ts.map +1 -1
  41. package/dist/secret-store-fs.js +85 -6
  42. package/dist/secret-store-fs.js.map +1 -1
  43. package/dist/web-session.d.ts +24 -0
  44. package/dist/web-session.d.ts.map +1 -0
  45. package/dist/web-session.js +25 -0
  46. package/dist/web-session.js.map +1 -0
  47. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { type BrokerAuth, type SecretStore, type SpaceAccountAuth, type SpaceAuth } from "@cotal-ai/core";
1
+ import { type BrokerAuth, type Identity, type SecretStore, type SpaceAccountAuth, type SpaceAuth } from "@cotal-ai/core";
2
2
  export declare function authDir(root: string): string;
3
3
  /** The ONE injective, case-safe space key: lowercase hex of the space's UTF-8 bytes. Every
4
4
  * tenant-keyed namespace derives its key from this — the account filename, the user-auth state
@@ -101,7 +101,7 @@ export declare function brokerAuthPath(dir: string): string;
101
101
  * (i) fence bites) + the serve nkey identity (so re-registration reuses the SAME gate principal —
102
102
  * provisionEndpointGateOpen stays idempotent, no core barrier change, and eviction targets a
103
103
  * stable principal). Holds a private seed, so it lands in a HARDENED secret file in the space's
104
- * segment of this root (see {@link managerIdentityFile}). */
104
+ * segment of this root (see {@link rootIdentityFile}). */
105
105
  export interface ManagerInstanceIdentity {
106
106
  instanceId: string;
107
107
  serveIdentity: {
@@ -109,20 +109,16 @@ export interface ManagerInstanceIdentity {
109
109
  seed: string;
110
110
  };
111
111
  }
112
+ /** The nkey identity a persisted record holds at `v`, or undefined when `v` is not one: the check
113
+ * every identity record's `parse` applies to each nkey it holds. */
114
+ export declare function identityOf(v: unknown): Identity | undefined;
112
115
  /** Load this workspace root's persisted manager instance identity for `space`, or undefined if a
113
116
  * manager has never registered here. A present-but-MALFORMED file fails LOUD: minting a fresh id
114
117
  * over it would orphan the prior registration and break the restart-fence guarantee, so a restart
115
118
  * never silently becomes a fresh instance (no-fallbacks). */
116
119
  export declare function loadManagerInstanceIdentity(root: string, space: string): ManagerInstanceIdentity | undefined;
117
- /** Persist this workspace root's manager instance identity for `space` (hardened secret file). */
118
- export declare function saveManagerInstanceIdentity(root: string, space: string, identity: ManagerInstanceIdentity): void;
119
- /**
120
- * First-start identity mint: of N concurrent creators on a fresh root, exactly one creates the
121
- * file and the others adopt the winner. The publication primitive is exclusive create
122
- * (`writeSecretFileCreateOnly` / `link` or `O_EXCL`), not a read-then-write and not an atomic
123
- * rename. A loser never keeps the identity it minted in memory: it re-reads the winner or
124
- * refuses with a named error (`manager-instance-identity-create-lost`).
125
- */
120
+ /** First-start identity mint through {@link claimIdentityRecord}: of N concurrent creators on a
121
+ * fresh root, exactly one creates the file and the others adopt the winner. */
126
122
  export declare function createManagerInstanceIdentity(root: string, space: string, candidate: ManagerInstanceIdentity): ManagerInstanceIdentity;
127
123
  /** The manager's goal-writer and session-ledger nkeys for `space`. Both are siblings of the serve
128
124
  * identity in its §13.1 credential family and persist for the same reason: the family never drops
@@ -139,8 +135,7 @@ export interface ManagerSiblingIdentities {
139
135
  };
140
136
  }
141
137
  /** This root's manager sibling identities for `space`, minted on the first start and read back on
142
- * every later one. The mint publishes by exclusive create, as {@link createManagerInstanceIdentity}
143
- * does, so concurrent first starts adopt one pair. */
138
+ * every later one through {@link claimIdentityRecord}, so concurrent first starts adopt one pair. */
144
139
  export declare function claimManagerSiblingIdentities(root: string, space: string): ManagerSiblingIdentities;
145
140
  /** The AUTH PLANE's persisted instance identity (#399 M2): a stable `instanceId` + serve nkey so
146
141
  * a restart re-registers the SAME `svc.auth.<instanceId>` instance (`registerServiceInstance`
@@ -158,12 +153,7 @@ export interface AuthInstanceIdentity {
158
153
  * the auth plane has never registered here. A present-but-MALFORMED file fails LOUD: minting a
159
154
  * fresh id over it would orphan the prior registration and break the restart-fence guarantee. */
160
155
  export declare function loadAuthInstanceIdentity(root: string, space: string): AuthInstanceIdentity | undefined;
161
- /** Persist this workspace root's auth-plane instance identity for `space` (hardened secret file). */
162
- export declare function saveAuthInstanceIdentity(root: string, space: string, identity: AuthInstanceIdentity): void;
163
- /**
164
- * First-start identity mint for the auth plane: of N concurrent creators on a fresh root, exactly
165
- * one creates the file and the others adopt the winner (mirrors {@link createManagerInstanceIdentity}).
166
- */
156
+ /** First-start identity mint for the auth plane, as {@link createManagerInstanceIdentity}. */
167
157
  export declare function createAuthInstanceIdentity(root: string, space: string, candidate: AuthInstanceIdentity): AuthInstanceIdentity;
168
158
  /** The outcome of {@link retireManagerInstanceIdentity}. `removed` means this call deleted the
169
159
  * record that matched `expected`. `absent` means no record exists for the space: a retry after an
@@ -247,6 +237,13 @@ export declare function loadSeatWriterGeneration(root: string, space: string, na
247
237
  * primitive, opposite disposition.
248
238
  */
249
239
  export declare function advanceSeatWriterGeneration(root: string, claim: SeatWriterGeneration): SeatWriterGeneration;
240
+ /**
241
+ * A persisted identity record of this root: the stored one, or on a fresh root `mint()`'s,
242
+ * published by exclusive create. Of N concurrent first mints exactly one creates the file and the
243
+ * others adopt it, so a loser never keeps the identity it minted in memory; an atomic rename would
244
+ * let it. A loser that cannot read the winner refuses with `identity-record-create-lost`.
245
+ */
246
+ export declare function claimIdentityRecord<T>(path: string, what: string, parse: (raw: unknown) => T | undefined, mint: () => T): T;
250
247
  /** Where one space's own account record lives: a FLAT file beside `broker.json`, keyed by
251
248
  * {@link accountFileKey}. Flat (not `<space>/account.json`) because `<authDir>/<space>/` is
252
249
  * {@link userAuthStateDir}; hex-keyed because a raw space name in the filename both aliased that
@@ -1 +1 @@
1
- {"version":3,"file":"auth-paths.d.ts","sourceRoot":"","sources":["../src/auth-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-paths.d.ts","sourceRoot":"","sources":["../src/auth-paths.ts"],"names":[],"mappings":"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"}
@@ -351,196 +351,109 @@ const SPACE_ACCOUNT_SUFFIX = ".json";
351
351
  export function brokerAuthPath(dir) {
352
352
  return join(dir, BROKER_FILE);
353
353
  }
354
- /** A manager identity record of this root, `<root>/.cotal/space.<hex>/<name>`. It is state of this
355
- * root, so it stays out of `.cotal/auth`: that folder is what an operator copies to give another
356
- * root a mesh's trust, and an identity copied with it made the other root's manager this root's
357
- * logical instance. An older build kept the record in `.cotal/auth` as `legacyName`; the first
358
- * touch moves it here, so an upgraded root restarts as the same instance (SPEC 13.6). */
359
- function managerIdentityFile(root, space, name, legacyName) {
354
+ const MANAGER_INSTANCE = "the manager instance identity";
355
+ /** The nkey identity a persisted record holds at `v`, or undefined when `v` is not one: the check
356
+ * every identity record's `parse` applies to each nkey it holds. */
357
+ export function identityOf(v) {
358
+ const k = v;
359
+ if (typeof k?.id !== "string" || k.id.length === 0 || typeof k.seed !== "string" || k.seed.length === 0)
360
+ return undefined;
361
+ return { id: k.id, seed: k.seed };
362
+ }
363
+ /** The shape both the manager and the auth plane persist as their instance identity. */
364
+ function instanceIdentityOf(raw) {
365
+ const r = raw;
366
+ const serveIdentity = identityOf(r?.serveIdentity);
367
+ if (typeof r?.instanceId !== "string" || r.instanceId.length === 0 || serveIdentity === undefined)
368
+ return undefined;
369
+ return { instanceId: r.instanceId, serveIdentity };
370
+ }
371
+ /** An identity record of this root, `<root>/.cotal/space.<hex>/<name>`. It is state of this root,
372
+ * so it stays out of `.cotal/auth`: that folder is what an operator copies to give another root a
373
+ * mesh's trust, and an identity copied with it made the other root's manager this root's logical
374
+ * instance. An older build kept the record at a `legacy` path; the first touch moves it here, so an
375
+ * upgraded root restarts as the same instance (SPEC 13.6). A `legacy` entry that is not a regular
376
+ * file is refused like the record itself, so a dangling link cannot pass for a fresh root and mint
377
+ * a new identity. */
378
+ function rootIdentityFile(root, space, name, ...legacy) {
360
379
  const path = join(root, ".cotal", spaceSegment(space), name);
361
- const legacy = join(authDir(root), legacyName);
362
- if (!existsSync(legacy))
363
- return path;
364
- mkSecretDir(dirname(path));
365
- // A link, not a rename: it never replaces a record already at `path`, and a concurrent first
366
- // touch that linked first is the same inode, which tells it apart from a second record.
367
- try {
368
- linkSync(legacy, path);
369
- }
370
- catch (e) {
371
- const code = e.code;
372
- if (code === "ENOENT")
373
- return path;
374
- if (code !== "EEXIST")
375
- throw e;
376
- const old = statSync(legacy, { throwIfNoEntry: false });
377
- const current = statSync(path);
378
- if (old && (old.ino !== current.ino || old.dev !== current.dev))
379
- throw new Error(`both ${path} and the older ${legacy} hold a manager identity for space "${space}" - refusing to guess which belongs to this root. Remove the one that did not come from this root, then retry.`);
380
+ for (const from of legacy) {
381
+ if (!hasAuthRecord(from, "an identity record"))
382
+ continue;
383
+ mkSecretDir(dirname(path));
384
+ // A link, not a rename: it never replaces a record already at `path`, and a concurrent first
385
+ // touch that linked first is the same inode, which tells it apart from a second record.
386
+ try {
387
+ linkSync(from, path);
388
+ }
389
+ catch (e) {
390
+ const code = e.code;
391
+ if (code === "ENOENT")
392
+ continue;
393
+ if (code !== "EEXIST")
394
+ throw e;
395
+ const old = statSync(from, { throwIfNoEntry: false });
396
+ const current = statSync(path);
397
+ if (old && (old.ino !== current.ino || old.dev !== current.dev))
398
+ throw new Error(`both ${path} and the older ${from} hold an identity record for space "${space}" - refusing to guess which belongs to this root. Remove the one that did not come from this root, then retry.`);
399
+ }
400
+ rmSync(from, { force: true });
380
401
  }
381
- rmSync(legacy, { force: true });
382
402
  return path;
383
403
  }
384
404
  function managerInstanceFile(root, space) {
385
- return managerIdentityFile(root, space, "manager-instance.json", `manager-instance.${spaceKey(space)}.json`);
405
+ return rootIdentityFile(root, space, "manager-instance.json", join(authDir(root), `manager-instance.${spaceKey(space)}.json`));
386
406
  }
387
407
  /** Load this workspace root's persisted manager instance identity for `space`, or undefined if a
388
408
  * manager has never registered here. A present-but-MALFORMED file fails LOUD: minting a fresh id
389
409
  * over it would orphan the prior registration and break the restart-fence guarantee, so a restart
390
410
  * never silently becomes a fresh instance (no-fallbacks). */
391
411
  export function loadManagerInstanceIdentity(root, space) {
392
- const f = managerInstanceFile(root, space);
393
- if (!existsSync(f))
394
- return undefined;
395
- let parsed;
396
- try {
397
- parsed = JSON.parse(readFileSync(f, "utf8"));
398
- }
399
- catch (e) {
400
- throw new Error(`the persisted manager instance identity at ${f} does not parse (${e.message}); refusing to mint a fresh id over it - a restart must preserve the logical instanceId (SPEC 13.6)`);
401
- }
402
- if (parsed === null || typeof parsed !== "object"
403
- || typeof parsed.instanceId !== "string" || parsed.instanceId.length === 0
404
- || parsed.serveIdentity === null || typeof parsed.serveIdentity !== "object"
405
- || typeof parsed.serveIdentity.id !== "string" || parsed.serveIdentity.id.length === 0
406
- || typeof parsed.serveIdentity.seed !== "string" || parsed.serveIdentity.seed.length === 0)
407
- throw new Error(`the persisted manager instance identity at ${f} is malformed; refusing to mint a fresh id over it - a restart must preserve the logical instanceId (SPEC 13.6)`);
408
- return { instanceId: parsed.instanceId, serveIdentity: { id: parsed.serveIdentity.id, seed: parsed.serveIdentity.seed } };
409
- }
410
- /** Persist this workspace root's manager instance identity for `space` (hardened secret file). */
411
- export function saveManagerInstanceIdentity(root, space, identity) {
412
- const path = managerInstanceFile(root, space);
413
- mkSecretDir(dirname(path)); // harden the dir BEFORE the secret (with its seed) lands
414
- writeSecretFile(path, JSON.stringify(identity, null, 2));
412
+ return readManagerInstanceRecord(managerInstanceFile(root, space));
415
413
  }
416
- /**
417
- * First-start identity mint: of N concurrent creators on a fresh root, exactly one creates the
418
- * file and the others adopt the winner. The publication primitive is exclusive create
419
- * (`writeSecretFileCreateOnly` / `link` or `O_EXCL`), not a read-then-write and not an atomic
420
- * rename. A loser never keeps the identity it minted in memory: it re-reads the winner or
421
- * refuses with a named error (`manager-instance-identity-create-lost`).
422
- */
414
+ /** First-start identity mint through {@link claimIdentityRecord}: of N concurrent creators on a
415
+ * fresh root, exactly one creates the file and the others adopt the winner. */
423
416
  export function createManagerInstanceIdentity(root, space, candidate) {
424
- const path = managerInstanceFile(root, space);
425
- mkSecretDir(dirname(path));
426
- try {
427
- writeSecretFileCreateOnly(path, JSON.stringify(candidate, null, 2));
428
- return candidate;
429
- }
430
- catch (e) {
431
- if (e.code !== "EEXIST")
432
- throw e;
433
- const winner = loadManagerInstanceIdentity(root, space);
434
- if (winner === undefined)
435
- throw new Error(`manager-instance-identity-create-lost: exclusive create for space "${space}" at ${path} lost and the existing file could not be adopted`);
436
- return winner;
437
- }
417
+ return claimIdentityRecord(managerInstanceFile(root, space), MANAGER_INSTANCE, instanceIdentityOf, () => candidate);
438
418
  }
439
419
  function managerSiblingFile(root, space) {
440
- return managerIdentityFile(root, space, "manager-siblings.json", `manager-siblings.${spaceKey(space)}.json`);
420
+ return rootIdentityFile(root, space, "manager-siblings.json", join(authDir(root), `manager-siblings.${spaceKey(space)}.json`));
441
421
  }
442
- function readManagerSiblingRecord(f, space) {
443
- const raw = readAuthRecord(f, `the manager sibling identities for space "${space}"`);
444
- if (raw === undefined)
445
- return undefined;
446
- const nkey = (k) => typeof k?.id === "string" && k.id.length > 0 && typeof k.seed === "string" && k.seed.length > 0;
447
- if (!nkey(raw?.goalWriter) || !nkey(raw?.sessionLedger))
448
- throw new Error(`${f} is malformed; refusing to mint fresh sibling identities over it`);
449
- return {
450
- goalWriter: { id: raw.goalWriter.id, seed: raw.goalWriter.seed },
451
- sessionLedger: { id: raw.sessionLedger.id, seed: raw.sessionLedger.seed },
452
- };
422
+ function siblingIdentitiesOf(raw) {
423
+ const r = raw;
424
+ const goalWriter = identityOf(r?.goalWriter);
425
+ const sessionLedger = identityOf(r?.sessionLedger);
426
+ return goalWriter && sessionLedger ? { goalWriter, sessionLedger } : undefined;
453
427
  }
454
428
  /** This root's manager sibling identities for `space`, minted on the first start and read back on
455
- * every later one. The mint publishes by exclusive create, as {@link createManagerInstanceIdentity}
456
- * does, so concurrent first starts adopt one pair. */
429
+ * every later one through {@link claimIdentityRecord}, so concurrent first starts adopt one pair. */
457
430
  export function claimManagerSiblingIdentities(root, space) {
458
- const path = managerSiblingFile(root, space);
459
- const stored = readManagerSiblingRecord(path, space);
460
- if (stored !== undefined)
461
- return stored;
462
- mkSecretDir(dirname(path));
463
- const candidate = { goalWriter: newIdentity(), sessionLedger: newIdentity() };
464
- try {
465
- writeSecretFileCreateOnly(path, JSON.stringify(candidate, null, 2));
466
- return candidate;
467
- }
468
- catch (e) {
469
- if (e.code !== "EEXIST")
470
- throw e;
471
- const winner = readManagerSiblingRecord(path, space);
472
- if (winner === undefined)
473
- throw new Error(`manager-sibling-identities-create-lost: exclusive create for space "${space}" at ${path} lost and the existing file could not be adopted`);
474
- return winner;
475
- }
431
+ return claimIdentityRecord(managerSiblingFile(root, space), "the manager sibling identities", siblingIdentitiesOf, () => ({ goalWriter: newIdentity(), sessionLedger: newIdentity() }));
476
432
  }
433
+ const AUTH_INSTANCE = "the auth instance identity";
434
+ /** The auth plane's record sits beside the manager's. An older build kept it in `.cotal/auth` under
435
+ * the plane's state dir. For a CLI root that dir is the root's user-auth state dir, inside the folder
436
+ * an operator copies; a hosted context passes its state dir as the root, so its record sat in the
437
+ * root's own `.cotal/auth`. */
477
438
  function authInstanceFile(root, space) {
478
- return join(authDir(root), `auth-instance.${Buffer.from(space, "utf8").toString("hex")}.json`);
439
+ const legacyName = `auth-instance.${spaceKey(space)}.json`;
440
+ return rootIdentityFile(root, space, "auth-instance.json", join(authDir(userAuthStateDir(root, space)), legacyName), join(authDir(root), legacyName));
479
441
  }
480
442
  /** Load this workspace root's persisted auth-plane instance identity for `space`, or undefined if
481
443
  * the auth plane has never registered here. A present-but-MALFORMED file fails LOUD: minting a
482
444
  * fresh id over it would orphan the prior registration and break the restart-fence guarantee. */
483
445
  export function loadAuthInstanceIdentity(root, space) {
484
- const f = authInstanceFile(root, space);
485
- if (!existsSync(f))
486
- return undefined;
487
- let parsed;
488
- try {
489
- parsed = JSON.parse(readFileSync(f, "utf8"));
490
- }
491
- catch (e) {
492
- throw new Error(`the persisted auth instance identity at ${f} does not parse (${e.message}); refusing to mint a fresh id over it - a restart must preserve the logical instanceId (SPEC 13.6)`);
493
- }
494
- if (parsed === null || typeof parsed !== "object"
495
- || typeof parsed.instanceId !== "string" || parsed.instanceId.length === 0
496
- || parsed.serveIdentity === null || typeof parsed.serveIdentity !== "object"
497
- || typeof parsed.serveIdentity.id !== "string" || parsed.serveIdentity.id.length === 0
498
- || typeof parsed.serveIdentity.seed !== "string" || parsed.serveIdentity.seed.length === 0)
499
- throw new Error(`the persisted auth instance identity at ${f} is malformed; refusing to mint a fresh id over it - a restart must preserve the logical instanceId (SPEC 13.6)`);
500
- return { instanceId: parsed.instanceId, serveIdentity: { id: parsed.serveIdentity.id, seed: parsed.serveIdentity.seed } };
501
- }
502
- /** Persist this workspace root's auth-plane instance identity for `space` (hardened secret file). */
503
- export function saveAuthInstanceIdentity(root, space, identity) {
504
- const dir = authDir(root);
505
- mkSecretDir(dir); // harden the auth dir BEFORE the secret (with its seed) lands
506
- writeSecretFile(authInstanceFile(root, space), JSON.stringify(identity, null, 2));
446
+ return readIdentityRecord(authInstanceFile(root, space), AUTH_INSTANCE, instanceIdentityOf);
507
447
  }
508
- /**
509
- * First-start identity mint for the auth plane: of N concurrent creators on a fresh root, exactly
510
- * one creates the file and the others adopt the winner (mirrors {@link createManagerInstanceIdentity}).
511
- */
448
+ /** First-start identity mint for the auth plane, as {@link createManagerInstanceIdentity}. */
512
449
  export function createAuthInstanceIdentity(root, space, candidate) {
513
- const dir = authDir(root);
514
- mkSecretDir(dir);
515
- const path = authInstanceFile(root, space);
516
- try {
517
- writeSecretFileCreateOnly(path, JSON.stringify(candidate, null, 2));
518
- return candidate;
519
- }
520
- catch (e) {
521
- if (e.code !== "EEXIST")
522
- throw e;
523
- const winner = loadAuthInstanceIdentity(root, space);
524
- if (winner === undefined)
525
- throw new Error(`auth-instance-identity-create-lost: exclusive create for space "${space}" at ${path} lost and the existing file could not be adopted`);
526
- return winner;
527
- }
450
+ return claimIdentityRecord(authInstanceFile(root, space), AUTH_INSTANCE, instanceIdentityOf, () => candidate);
528
451
  }
529
452
  function sameManagerInstanceIdentity(a, b) {
530
453
  return a.instanceId === b.instanceId && a.serveIdentity.id === b.serveIdentity.id && a.serveIdentity.seed === b.serveIdentity.seed;
531
454
  }
532
- function readManagerInstanceRecord(f, space) {
533
- const what = `the manager instance identity for space "${space}"`;
534
- const raw = readAuthRecord(f, what);
535
- if (raw === undefined)
536
- return undefined;
537
- if (raw === null || typeof raw !== "object"
538
- || typeof raw.instanceId !== "string" || raw.instanceId.length === 0
539
- || raw.serveIdentity === null || typeof raw.serveIdentity !== "object"
540
- || typeof raw.serveIdentity.id !== "string" || raw.serveIdentity.id.length === 0
541
- || typeof raw.serveIdentity.seed !== "string" || raw.serveIdentity.seed.length === 0)
542
- throw new Error(`${f} is malformed`);
543
- return { instanceId: raw.instanceId, serveIdentity: { id: raw.serveIdentity.id, seed: raw.serveIdentity.seed } };
455
+ function readManagerInstanceRecord(f) {
456
+ return readIdentityRecord(f, MANAGER_INSTANCE, instanceIdentityOf);
544
457
  }
545
458
  /**
546
459
  * Delete this root's persisted manager instance identity for `space`, only when the stored record
@@ -563,7 +476,7 @@ export function retireManagerInstanceIdentity(root, space, expected, opts) {
563
476
  };
564
477
  let current;
565
478
  try {
566
- current = readManagerInstanceRecord(path, space);
479
+ current = readManagerInstanceRecord(path);
567
480
  }
568
481
  catch (e) {
569
482
  return refuse(e.message);
@@ -584,7 +497,7 @@ export function retireManagerInstanceIdentity(root, space, expected, opts) {
584
497
  }
585
498
  let held;
586
499
  try {
587
- held = readManagerInstanceRecord(captured, space);
500
+ held = readManagerInstanceRecord(captured);
588
501
  }
589
502
  catch {
590
503
  held = undefined;
@@ -717,26 +630,32 @@ export function advanceSeatWriterGeneration(root, claim) {
717
630
  throw new Error(`seat-writer-generation-create-lost: generation ${claim.generation} of seat ${JSON.stringify(claim.name)} in space ${JSON.stringify(claim.space)} is already claimed; refusing to resume as a second writer`);
718
631
  }
719
632
  }
720
- /** Read one auth-material record: the file's raw text, or undefined when absent. lstat-disciplined
721
- * and framed, shared by every load/save below so the readers cannot disagree:
722
- * - a non-regular entry at a trust path (symlink, directory, fifo) is REFUSED, never followed —
723
- * nothing in this module writes one, so following it would trust material this module cannot
724
- * vouch for (and enumeration counts the same entry as corrupt: one answer everywhere);
725
- * - only ENOENT means absent; any other errno is uncertainty about trust material and throws;
726
- * - the JSON parse is wrapped so a truncated/hand-edited record surfaces as one legible sentence
727
- * naming the file, never a raw SyntaxError deep in a caller. */
728
- function readAuthRecord(f, what) {
633
+ /** Whether a trust path holds an auth-material record, lstat-disciplined:
634
+ * - a non-regular entry (symlink, directory, fifo) is REFUSED, never followed — nothing in this
635
+ * module writes one, so following it would trust material this module cannot vouch for (and
636
+ * enumeration counts the same entry as corrupt: one answer everywhere);
637
+ * - only ENOENT means absent; any other errno is uncertainty about trust material and throws. */
638
+ function hasAuthRecord(f, what) {
729
639
  let st;
730
640
  try {
731
641
  st = lstatSync(f);
732
642
  }
733
643
  catch (e) {
734
644
  if (e.code === "ENOENT")
735
- return undefined;
645
+ return false;
736
646
  throw e;
737
647
  }
738
648
  if (!st.isFile())
739
649
  throw new Error(`${f} is not a regular file - refusing to read ${what} through it; remove or restore the real record`);
650
+ return true;
651
+ }
652
+ /** Read one auth-material record: the file's raw text, or undefined when absent. Shared by every
653
+ * load/save below so the readers cannot disagree: the entry is checked by {@link hasAuthRecord},
654
+ * and the JSON parse is wrapped so a truncated/hand-edited record surfaces as one legible sentence
655
+ * naming the file, never a raw SyntaxError deep in a caller. */
656
+ function readAuthRecord(f, what) {
657
+ if (!hasAuthRecord(f, what))
658
+ return undefined;
740
659
  try {
741
660
  return JSON.parse(readFileSync(f, "utf8"));
742
661
  }
@@ -744,6 +663,43 @@ function readAuthRecord(f, what) {
744
663
  throw new Error(`${f} does not parse as ${what} (${e instanceof Error ? e.message : String(e)}) - restore it from backup or remove it deliberately`);
745
664
  }
746
665
  }
666
+ /** A persisted identity record, or undefined when absent. Every identity reads through
667
+ * {@link readAuthRecord}, so they refuse the same entries, and a record `parse` rejects fails LOUD:
668
+ * minting over it would orphan the identity it held. */
669
+ function readIdentityRecord(f, what, parse) {
670
+ const raw = readAuthRecord(f, what);
671
+ if (raw === undefined)
672
+ return undefined;
673
+ const record = parse(raw);
674
+ if (record === undefined)
675
+ throw new Error(`${f} is not a well-formed record of ${what} - restore it from backup or remove it deliberately`);
676
+ return record;
677
+ }
678
+ /**
679
+ * A persisted identity record of this root: the stored one, or on a fresh root `mint()`'s,
680
+ * published by exclusive create. Of N concurrent first mints exactly one creates the file and the
681
+ * others adopt it, so a loser never keeps the identity it minted in memory; an atomic rename would
682
+ * let it. A loser that cannot read the winner refuses with `identity-record-create-lost`.
683
+ */
684
+ export function claimIdentityRecord(path, what, parse, mint) {
685
+ const stored = readIdentityRecord(path, what, parse);
686
+ if (stored !== undefined)
687
+ return stored;
688
+ mkSecretDir(dirname(path));
689
+ const candidate = mint();
690
+ try {
691
+ writeSecretFileCreateOnly(path, JSON.stringify(candidate, null, 2));
692
+ return candidate;
693
+ }
694
+ catch (e) {
695
+ if (e.code !== "EEXIST")
696
+ throw e;
697
+ const winner = readIdentityRecord(path, what, parse);
698
+ if (winner === undefined)
699
+ throw new Error(`identity-record-create-lost: exclusive create of ${what} at ${path} lost and the existing record could not be adopted`);
700
+ return winner;
701
+ }
702
+ }
747
703
  /** The space a canonical account filename encodes, or undefined when the name is not one THIS module
748
704
  * wrote (wrong prefix/suffix, non-hex body, or a body that does not round-trip back to the same
749
705
  * filename). Enumeration treats an undefined result as a corrupt/foreign record, never as a tenant. */