@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.
- package/dist/auth-paths.d.ts +16 -19
- package/dist/auth-paths.d.ts.map +1 -1
- package/dist/auth-paths.js +126 -170
- package/dist/auth-paths.js.map +1 -1
- package/dist/bin-path.d.ts +7 -6
- package/dist/bin-path.d.ts.map +1 -1
- package/dist/bin-path.js +11 -7
- package/dist/bin-path.js.map +1 -1
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +11 -11
- package/dist/connect.js.map +1 -1
- package/dist/default-agent.d.ts +9 -0
- package/dist/default-agent.d.ts.map +1 -1
- package/dist/default-agent.js +8 -0
- package/dist/default-agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/join-target.d.ts +3 -50
- package/dist/join-target.d.ts.map +1 -1
- package/dist/join-target.js +5 -18
- package/dist/join-target.js.map +1 -1
- package/dist/local-process.d.ts +5 -1
- package/dist/local-process.d.ts.map +1 -1
- package/dist/local-process.js +14 -11
- package/dist/local-process.js.map +1 -1
- package/dist/manager-shutdown.d.ts.map +1 -1
- package/dist/manager-shutdown.js +5 -5
- package/dist/manager-shutdown.js.map +1 -1
- package/dist/mesh-registry.d.ts +11 -0
- package/dist/mesh-registry.d.ts.map +1 -1
- package/dist/mesh-registry.js +69 -37
- package/dist/mesh-registry.js.map +1 -1
- package/dist/registration-policy.d.ts +5 -0
- package/dist/registration-policy.d.ts.map +1 -1
- package/dist/registration-policy.js +12 -4
- package/dist/registration-policy.js.map +1 -1
- package/dist/secret-store-fs.d.ts +17 -2
- package/dist/secret-store-fs.d.ts.map +1 -1
- package/dist/secret-store-fs.js +85 -6
- package/dist/secret-store-fs.js.map +1 -1
- package/dist/web-session.d.ts +24 -0
- package/dist/web-session.d.ts.map +1 -0
- package/dist/web-session.js +25 -0
- package/dist/web-session.js.map +1 -0
- package/package.json +2 -2
package/dist/auth-paths.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
118
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
package/dist/auth-paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-paths.d.ts","sourceRoot":"","sources":["../src/auth-paths.ts"],"names":[],"mappings":"AAGA,OAAO,EASL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAqBxB,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;;;;gGAMgG;AAChG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAa9C;AAqBD;;;;;;;;;;;;;;uDAcuD;AACvD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAID;;qFAEqF;AACrF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAED;;;;;;;;;;;;4FAY4F;AAC5F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpE;AAwFD;;;mEAGmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;yDAKyD;AACzD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB1D;AAED;;;;;;2BAM2B;AAC3B,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1G;AAED;;iGAEiG;AACjG,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,CAQnE;AAED;;;iFAGiF;AACjF,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,CAKtE;AAED;;;;;;;;;;;;;;;;;;;6DAmB6D;AAC7D,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAkCrH;AAkBD,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;
|
|
1
|
+
{"version":3,"file":"auth-paths.d.ts","sourceRoot":"","sources":["../src/auth-paths.ts"],"names":[],"mappings":"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"}
|
package/dist/auth-paths.js
CHANGED
|
@@ -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
|
-
|
|
355
|
-
|
|
356
|
-
*
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
420
|
+
return rootIdentityFile(root, space, "manager-siblings.json", join(authDir(root), `manager-siblings.${spaceKey(space)}.json`));
|
|
441
421
|
}
|
|
442
|
-
function
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
533
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
|
|
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
|
|
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. */
|