@modelprofile.com/authswitch 7.0.0 → 8.0.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/authority-contract.d.ts +1 -3
- package/dist_ts/authority-contract.js +1 -1
- package/dist_ts/classes.authoritybroker.js +1 -5
- package/dist_ts/classes.authoritycli.js +2 -2
- package/dist_ts/classes.authoritydaemon.js +4 -9
- package/dist_ts/classes.authoritydatabase.d.ts +5 -26
- package/dist_ts/classes.authoritydatabase.js +49 -129
- package/dist_ts/classes.authoritymodels.d.ts +27 -43
- package/dist_ts/classes.authoritymodels.js +193 -243
- package/dist_ts/classes.authoritypreuse.js +1 -2
- package/dist_ts/classes.authorityregistry.d.ts +2 -17
- package/dist_ts/classes.authorityregistry.js +18 -74
- package/dist_ts/classes.authoritysecrets.d.ts +1 -1
- package/dist_ts/classes.authoritysecrets.js +2 -2
- package/dist_ts/classes.authorityusage.d.ts +3 -3
- package/dist_ts/classes.claudeauthority.js +3 -7
- package/dist_ts/classes.claudenative.d.ts +1 -8
- package/dist_ts/classes.claudenative.js +2 -25
- package/dist_ts/classes.codexmanaged.d.ts +1 -1
- package/dist_ts/plugins.d.ts +7 -7
- package/dist_ts/plugins.js +8 -8
- package/dist_ts/ts_migration/0001_authority_meta.d.ts +13 -3
- package/dist_ts/ts_migration/0001_authority_meta.js +29 -17
- package/dist_ts/ts_migration/0002_remove_backup_records.d.ts +17 -0
- package/dist_ts/ts_migration/0002_remove_backup_records.js +48 -0
- package/dist_ts/ts_migration/index.d.ts +6 -3
- package/dist_ts/ts_migration/index.js +10 -4
- package/package.json +2 -5
- package/readme.md +22 -26
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/authority-contract.ts +1 -3
- package/ts/classes.authoritybroker.ts +0 -4
- package/ts/classes.authoritycli.ts +1 -1
- package/ts/classes.authoritydaemon.ts +3 -8
- package/ts/classes.authoritydatabase.ts +50 -141
- package/ts/classes.authoritymodels.ts +232 -269
- package/ts/classes.authoritypreuse.ts +0 -1
- package/ts/classes.authorityregistry.ts +20 -91
- package/ts/classes.authoritysecrets.ts +1 -1
- package/ts/classes.authorityusage.ts +3 -3
- package/ts/classes.claudeauthority.ts +2 -6
- package/ts/classes.claudenative.ts +1 -32
- package/ts/classes.codexmanaged.ts +1 -1
- package/ts/plugins.ts +7 -7
- package/ts/ts_migration/0001_authority_meta.ts +29 -17
- package/ts/ts_migration/0002_remove_backup_records.ts +49 -0
- package/ts/ts_migration/index.ts +10 -5
- package/dist_ts/authorityrehearsal.child.d.ts +0 -1
- package/dist_ts/authorityrehearsal.child.js +0 -395
- package/dist_ts/classes.authoritybackup.d.ts +0 -134
- package/dist_ts/classes.authoritybackup.js +0 -769
- package/dist_ts/classes.authorityrehearsal.d.ts +0 -127
- package/dist_ts/classes.authorityrehearsal.js +0 -632
- package/ts/authorityrehearsal.child.ts +0 -330
- package/ts/classes.authoritybackup.ts +0 -780
- package/ts/classes.authorityrehearsal.ts +0 -625
|
@@ -16,9 +16,7 @@ import {
|
|
|
16
16
|
type IStoredAuthorityOperation, type IStoredAuthorityPreuseOperation, type IStoredAuthorityUsage,
|
|
17
17
|
} from './classes.authoritymodels.js';
|
|
18
18
|
import type { IAuthSwitchStoredUsage, IAuthSwitchUsageContext } from './classes.authorityusage.js';
|
|
19
|
-
import {
|
|
20
|
-
assertAuthorityStoredRecord, type IAuthorityStoredRecord } from './classes.authorityregistry.js';
|
|
21
|
-
import type { IAuthorityBackupSnapshotAttempt, IAuthorityBackupSourceCapsule } from './classes.authoritybackup.js';
|
|
19
|
+
import { authorityModelRegistry } from './classes.authorityregistry.js';
|
|
22
20
|
import { runAuthorityMigrations } from './ts_migration/index.js';
|
|
23
21
|
|
|
24
22
|
export interface IAuthSwitchAuthorityDatabaseOptions {
|
|
@@ -26,15 +24,8 @@ export interface IAuthSwitchAuthorityDatabaseOptions {
|
|
|
26
24
|
socketPath: string;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
export interface IAuthoritySnapshotCaptureOptions {
|
|
30
|
-
/** Recreate capsules on every transaction retry from a source already held under qualified exclusion. */
|
|
31
|
-
sourceFactory: () => AsyncIterable<IAuthorityBackupSourceCapsule>;
|
|
32
|
-
signal?: AbortSignal;
|
|
33
|
-
pageSize?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
type TEventKind = IStoredAuthorityEvent['kind'];
|
|
37
|
-
type TSession = plugins.
|
|
28
|
+
type TSession = plugins.nosqldb.SmartdataSession;
|
|
38
29
|
type TStoredAuthorityDeviceOperationAdmission = Omit<IStoredAuthorityDeviceOperation, 'expectedGrantGeneration'>;
|
|
39
30
|
type TClaudeHandoffDraft = {
|
|
40
31
|
home: IStoredAuthorityClaudeHome;
|
|
@@ -72,10 +63,9 @@ const storedOperation = (stored: Parameters<typeof AuthSwitchAuthorityOperationM
|
|
|
72
63
|
|
|
73
64
|
/** Separate account, grant, binding and journal records; the only global record is a revision counter. */
|
|
74
65
|
export class AuthSwitchAuthorityDatabase {
|
|
75
|
-
private engine?: plugins.
|
|
76
|
-
private database?: plugins.
|
|
66
|
+
private engine?: plugins.nosqldbEngine.LocalNoSqlDb;
|
|
67
|
+
private database?: plugins.nosqldb.SmartdataDb;
|
|
77
68
|
private started = false;
|
|
78
|
-
private mode: 'normal' | 'closed_restore' | null = null;
|
|
79
69
|
|
|
80
70
|
constructor(private readonly options: IAuthSwitchAuthorityDatabaseOptions) {}
|
|
81
71
|
|
|
@@ -88,20 +78,18 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
88
78
|
await plugins.fs.promises.chmod(pathArg, 0o700);
|
|
89
79
|
}
|
|
90
80
|
|
|
91
|
-
public async start(
|
|
81
|
+
public async start(): Promise<void> {
|
|
92
82
|
if (this.started || this.engine || this.database) throw new Error('Authswitch database is already starting or started.');
|
|
93
|
-
const mode = options.mode ?? 'normal';
|
|
94
|
-
if (mode !== 'normal' && mode !== 'closed_restore') throw new Error('Invalid authority database start mode.');
|
|
95
83
|
await this.privateDirectory(this.options.dataDirectory);
|
|
96
84
|
await this.privateDirectory(plugins.path.dirname(this.options.socketPath));
|
|
97
|
-
const engine = new plugins.
|
|
85
|
+
const engine = new plugins.nosqldbEngine.LocalNoSqlDb({ folderPath: this.options.dataDirectory, socketPath: this.options.socketPath });
|
|
98
86
|
this.engine = engine;
|
|
99
87
|
try {
|
|
100
88
|
const connection = await engine.start();
|
|
101
|
-
const database = new plugins.
|
|
89
|
+
const database = new plugins.nosqldb.SmartdataDb({ mongoDbUrl: connection.connectionUri, mongoDbName: 'authswitch' });
|
|
102
90
|
this.database = database;
|
|
103
91
|
await database.init();
|
|
104
|
-
// The
|
|
92
|
+
// The LocalNoSqlDb root lease excludes another engine during this catalog admission.
|
|
105
93
|
const inventory = await database.inspectNamespace({ maxCollections: 1024,
|
|
106
94
|
documentCountUpTo: 0, timeoutMs: 30_000 });
|
|
107
95
|
const names = new Set(authorityModelRegistry.map(entry => entry.name));
|
|
@@ -111,10 +99,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
111
99
|
}
|
|
112
100
|
}
|
|
113
101
|
const fresh = inventory.collections.length === 0;
|
|
114
|
-
if (
|
|
115
|
-
throw new Error('Closed authority restore requires an empty database.');
|
|
116
|
-
}
|
|
117
|
-
if (mode === 'normal' && !fresh) {
|
|
102
|
+
if (!fresh) {
|
|
118
103
|
if (!inventory.collections.some(item => item.collectionName === 'authswitch_authority_meta')) {
|
|
119
104
|
throw new Error('Existing authswitch authority database has no metadata collection.');
|
|
120
105
|
}
|
|
@@ -122,19 +107,16 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
122
107
|
}
|
|
123
108
|
await Promise.all(authorityModelRegistry.map(entry => entry.initialize(database)));
|
|
124
109
|
await Promise.all(authorityModelRegistry.map(entry => entry.prepare()));
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
throw new Error('Existing authswitch authority metadata is missing.');
|
|
135
|
-
}
|
|
110
|
+
if (fresh) {
|
|
111
|
+
const initial: IStoredAuthorityMeta = {
|
|
112
|
+
id: 'authswitch-authority',
|
|
113
|
+
epoch: plugins.crypto.randomUUID(), revision: 0, updateId: plugins.crypto.randomUUID(),
|
|
114
|
+
};
|
|
115
|
+
const result = await AuthSwitchAuthorityMetaModel.exact.insert(initial);
|
|
116
|
+
if (result.status !== 'inserted') throw new Error('Authswitch authority could not initialize.');
|
|
117
|
+
} else if (!await AuthSwitchAuthorityMetaModel.exact.findStoredOne({ id: 'authswitch-authority' })) {
|
|
118
|
+
throw new Error('Existing authswitch authority metadata is missing.');
|
|
136
119
|
}
|
|
137
|
-
this.mode = mode;
|
|
138
120
|
this.started = true;
|
|
139
121
|
} catch (error) {
|
|
140
122
|
try { await this.stop(); } catch { /* Preserve the original startup error. */ }
|
|
@@ -142,7 +124,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
142
124
|
}
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
private db(): plugins.
|
|
127
|
+
private db(): plugins.nosqldb.SmartdataDb {
|
|
146
128
|
if (!this.started || !this.database) throw new Error('Authswitch database is unavailable.');
|
|
147
129
|
return this.database;
|
|
148
130
|
}
|
|
@@ -156,70 +138,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
156
138
|
} finally { await session.close(); }
|
|
157
139
|
}
|
|
158
140
|
|
|
159
|
-
/** One repeatable SmartData transaction per capture attempt; portable staging happens inside visit. */
|
|
160
|
-
public async captureBackupSnapshot(options: IAuthoritySnapshotCaptureOptions,
|
|
161
|
-
visit: (attempt: IAuthorityBackupSnapshotAttempt) => Promise<void>): Promise<void> {
|
|
162
|
-
if (typeof options.sourceFactory !== 'function' || typeof visit !== 'function') {
|
|
163
|
-
throw new Error('Authority backup capture requires an exact source and visitor.');
|
|
164
|
-
}
|
|
165
|
-
await this.transaction(async session => {
|
|
166
|
-
options.signal?.throwIfAborted();
|
|
167
|
-
const stored = await AuthSwitchAuthorityMetaModel.exact.findStoredOne({ id: 'authswitch-authority' },
|
|
168
|
-
{ session, signal: options.signal });
|
|
169
|
-
if (!stored) throw new Error('Authority backup metadata is absent.');
|
|
170
|
-
const meta = AuthSwitchAuthorityMetaModel.exact.toPersisted(stored);
|
|
171
|
-
if (meta.admission !== 'open') throw new Error('Closed restored authority cannot publish a backup.');
|
|
172
|
-
let recordsComplete = false;
|
|
173
|
-
let sourcesComplete = false;
|
|
174
|
-
const records = async function* (): AsyncGenerator<IAuthorityStoredRecord> {
|
|
175
|
-
yield* authorityStoredRecords({ session, signal: options.signal, pageSize: options.pageSize });
|
|
176
|
-
recordsComplete = true;
|
|
177
|
-
};
|
|
178
|
-
const sources = async function* (): AsyncGenerator<IAuthorityBackupSourceCapsule> {
|
|
179
|
-
for await (const source of options.sourceFactory()) {
|
|
180
|
-
options.signal?.throwIfAborted();
|
|
181
|
-
yield source;
|
|
182
|
-
}
|
|
183
|
-
sourcesComplete = true;
|
|
184
|
-
};
|
|
185
|
-
await visit({ epoch: meta.epoch, revision: meta.revision, records: records(), sources: sources() });
|
|
186
|
-
if (!recordsComplete || !sourcesComplete) {
|
|
187
|
-
throw new Error('Authority backup visitor did not consume the complete pinned inventory.');
|
|
188
|
-
}
|
|
189
|
-
return true;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Reopened closed restore readback uses the same exhaustive registry and stable keyset order. */
|
|
194
|
-
public readStoredRecords(options: { signal?: AbortSignal; pageSize?: number } = {}): AsyncIterable<IAuthorityStoredRecord> {
|
|
195
|
-
this.db();
|
|
196
|
-
return authorityStoredRecords(options);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
public async restoreClosedMeta(meta: IStoredAuthorityMeta): Promise<void> {
|
|
200
|
-
if (this.mode !== 'closed_restore') throw new Error('Authority database is not a closed restore target.');
|
|
201
|
-
const record: IAuthorityStoredRecord = { model: 'authswitch_authority_meta', id: meta.id, value: meta };
|
|
202
|
-
assertAuthorityStoredRecord(record);
|
|
203
|
-
if (meta.admission !== 'closed_restored') throw new Error('Restored authority metadata must remain closed.');
|
|
204
|
-
await authorityModel(record.model).insert(record);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
public async insertClosedRestoredRecord(record: IAuthorityStoredRecord): Promise<void> {
|
|
208
|
-
if (this.mode !== 'closed_restore' || record.model === 'authswitch_authority_meta') {
|
|
209
|
-
throw new Error('Authority database is not admitting this restored record.');
|
|
210
|
-
}
|
|
211
|
-
if ((await this.readMeta()).admission !== 'closed_restored') {
|
|
212
|
-
throw new Error('Closed restore metadata must be installed before records.');
|
|
213
|
-
}
|
|
214
|
-
assertAuthorityStoredRecord(record);
|
|
215
|
-
await authorityModel(record.model).insert(record);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
public async assertOpenAdmission(): Promise<void> {
|
|
219
|
-
const meta = await this.readMeta();
|
|
220
|
-
if (meta.admission !== 'open') throw new Error('Restored authority admission is closed pending ownership reconciliation.');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
141
|
public async readMeta(): Promise<IStoredAuthorityMeta> {
|
|
224
142
|
this.db();
|
|
225
143
|
const stored = await AuthSwitchAuthorityMetaModel.exact.findStoredOne({ id: 'authswitch-authority' });
|
|
@@ -257,7 +175,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
257
175
|
return stored.map(item => AuthSwitchAuthorityCodexHomeModel.exact.toPersisted(item));
|
|
258
176
|
}
|
|
259
177
|
|
|
260
|
-
/** Home path identity is hashed by the caller; no vendor files or raw paths enter
|
|
178
|
+
/** Home path identity is hashed by the caller; no vendor files or raw paths enter NoSQLDB. */
|
|
261
179
|
public async reserveCodexHome(accountId: string, grantId: string, scopeId: string, homeId: string,
|
|
262
180
|
updateId: string): Promise<{ newlyRegistered: boolean }> {
|
|
263
181
|
try {
|
|
@@ -285,7 +203,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
285
203
|
return { newlyRegistered: true };
|
|
286
204
|
});
|
|
287
205
|
} catch (error) {
|
|
288
|
-
if (error instanceof plugins.
|
|
206
|
+
if (error instanceof plugins.nosqldb.SmartdataExactPersistenceError
|
|
289
207
|
&& (error.code === 'ambiguous_write' || error.code === 'unique_conflict')) {
|
|
290
208
|
const assigned = await AuthSwitchAuthorityCodexHomeModel.exact.findStoredOne({ id: homeId });
|
|
291
209
|
if (assigned?.accountId === accountId && assigned.grantId === grantId && assigned.scopeId === scopeId
|
|
@@ -315,7 +233,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
315
233
|
return true;
|
|
316
234
|
});
|
|
317
235
|
} catch (error) {
|
|
318
|
-
if (!(error instanceof plugins.
|
|
236
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
319
237
|
const current = await this.readCodexHome(homeId);
|
|
320
238
|
if (current?.updateId === updateId && JSON.stringify(current.activeRun) === JSON.stringify(activeRun)) return;
|
|
321
239
|
throw new Error('Managed Codex home run outcome is unknown.');
|
|
@@ -428,11 +346,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
428
346
|
if (!sameRequest(existing)) throw new Error('Account operation ID belongs to a different request.');
|
|
429
347
|
return { operation: existing, created: false };
|
|
430
348
|
}
|
|
431
|
-
const metaStored = await AuthSwitchAuthorityMetaModel.exact.findStoredOne(
|
|
432
|
-
{ id: 'authswitch-authority' }, { session });
|
|
433
|
-
if (!metaStored || AuthSwitchAuthorityMetaModel.exact.toPersisted(metaStored).admission !== 'open') {
|
|
434
|
-
throw new Error('Authswitch authority admission is closed.');
|
|
435
|
-
}
|
|
436
349
|
let expectedGrantGeneration: number | null = null;
|
|
437
350
|
if (intent.kind === 'reauth_openai') {
|
|
438
351
|
const accountStored = await AuthSwitchAuthorityAccountModel.exact.findStoredOne(
|
|
@@ -456,7 +369,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
456
369
|
return { operation, created: true };
|
|
457
370
|
});
|
|
458
371
|
} catch (error) {
|
|
459
|
-
if (!(error instanceof plugins.
|
|
372
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
460
373
|
const stored = await this.readOperation(intent.id);
|
|
461
374
|
if (stored && sameRequest(stored)) {
|
|
462
375
|
return { operation: stored, created: stored.updateId === intent.updateId };
|
|
@@ -468,13 +381,8 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
468
381
|
/** Revalidate the durable owner and exact reauthentication generation after mutable runtime preparation. */
|
|
469
382
|
public async confirmOperationMayStart(operationId: string): Promise<IStoredAuthorityDeviceOperation> {
|
|
470
383
|
return this.transaction(async session => {
|
|
471
|
-
const metaStored = await AuthSwitchAuthorityMetaModel.exact.findStoredOne(
|
|
472
|
-
{ id: 'authswitch-authority' }, { session });
|
|
473
384
|
const operationStored = await AuthSwitchAuthorityOperationModel.exact.findStoredOne(
|
|
474
385
|
{ id: operationId }, { session });
|
|
475
|
-
if (!metaStored || AuthSwitchAuthorityMetaModel.exact.toPersisted(metaStored).admission !== 'open') {
|
|
476
|
-
throw new Error('Authswitch authority admission is closed.');
|
|
477
|
-
}
|
|
478
386
|
if (!operationStored) throw new Error('Account operation was not found.');
|
|
479
387
|
const operation = storedOperation(operationStored);
|
|
480
388
|
if (operation.kind === 'preuse_openai' || operation.state !== 'starting') {
|
|
@@ -528,7 +436,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
528
436
|
if (result.status !== 'transitioned') throw new Error('Account operation changed concurrently.');
|
|
529
437
|
return draft;
|
|
530
438
|
} catch (error) {
|
|
531
|
-
if (!(error instanceof plugins.
|
|
439
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
532
440
|
const stored = await this.readOperation(operationId);
|
|
533
441
|
if (stored?.kind !== 'preuse_openai' && stored?.updateId === updateId) return stored;
|
|
534
442
|
throw new Error('Account operation transition outcome is unknown.');
|
|
@@ -569,7 +477,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
569
477
|
return { operation, created: true };
|
|
570
478
|
});
|
|
571
479
|
} catch (error) {
|
|
572
|
-
if (!(error instanceof plugins.
|
|
480
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
573
481
|
const stored = await this.readOperation(operation.id);
|
|
574
482
|
if (stored && sameRequest(stored) && stored.updateId === operation.updateId) {
|
|
575
483
|
return { operation: stored, created: true };
|
|
@@ -611,7 +519,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
611
519
|
return draft;
|
|
612
520
|
});
|
|
613
521
|
} catch (error) {
|
|
614
|
-
if (!(error instanceof plugins.
|
|
522
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
615
523
|
const stored = await this.readOperation(operationId);
|
|
616
524
|
if (stored?.kind === 'preuse_openai' && stored.updateId === updateId
|
|
617
525
|
&& stored.state === 'request_may_have_started') return stored;
|
|
@@ -648,7 +556,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
648
556
|
if (result.status !== 'transitioned') throw new Error('Preuse operation changed concurrently.');
|
|
649
557
|
return draft;
|
|
650
558
|
} catch (error) {
|
|
651
|
-
if (!(error instanceof plugins.
|
|
559
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
652
560
|
const stored = await this.readOperation(operationId);
|
|
653
561
|
if (stored?.kind === 'preuse_openai' && stored.updateId === updateId) return stored;
|
|
654
562
|
throw new Error('Preuse operation transition outcome is unknown.');
|
|
@@ -806,7 +714,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
806
714
|
return true;
|
|
807
715
|
});
|
|
808
716
|
} catch (error) {
|
|
809
|
-
if (!(error instanceof plugins.
|
|
717
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError)) throw error;
|
|
810
718
|
if (error.code === 'unique_conflict') return false;
|
|
811
719
|
if (error.code !== 'ambiguous_write') throw error;
|
|
812
720
|
const [stored, event] = await Promise.all([
|
|
@@ -1076,7 +984,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1076
984
|
return { meta, ...draft };
|
|
1077
985
|
});
|
|
1078
986
|
} catch (error) {
|
|
1079
|
-
if (!(error instanceof plugins.
|
|
987
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1080
988
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1081
989
|
const { account, grant } = await this.readAccountAndPrimaryGrant(accountId);
|
|
1082
990
|
if (event && account?.updateId === updateId && grant?.updateId === updateId) {
|
|
@@ -1141,7 +1049,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1141
1049
|
return { ...draft, operation: completed };
|
|
1142
1050
|
});
|
|
1143
1051
|
} catch (error) {
|
|
1144
|
-
if (!(error instanceof plugins.
|
|
1052
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1145
1053
|
const [event, operation, pair] = await Promise.all([
|
|
1146
1054
|
AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId }),
|
|
1147
1055
|
this.readOperation(operationId), this.readAccountAndPrimaryGrant(accountId),
|
|
@@ -1176,7 +1084,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1176
1084
|
if (grant.purpose === 'claude_host_native' && draft.state === 'exchange_may_have_been_sent'
|
|
1177
1085
|
&& grant.state !== 'exchange_may_have_been_sent'
|
|
1178
1086
|
&& !(await this.hasVerifiedClaudeGrantReceipt(account.id, grant.id, session))) {
|
|
1179
|
-
throw new Error('Claude refresh requires a verified import
|
|
1087
|
+
throw new Error('Claude refresh requires a verified import receipt.');
|
|
1180
1088
|
}
|
|
1181
1089
|
const accountDraft: IStoredAuthorityAccount = { ...account, revision: account.revision + 1,
|
|
1182
1090
|
statusObservedAt: draft.statusObservedAt, updateId };
|
|
@@ -1187,7 +1095,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1187
1095
|
return { meta, account: accountDraft, grant: draft };
|
|
1188
1096
|
});
|
|
1189
1097
|
} catch (error) {
|
|
1190
|
-
if (!(error instanceof plugins.
|
|
1098
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1191
1099
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1192
1100
|
const grant = await this.readGrant(grantId);
|
|
1193
1101
|
const account = grant ? await this.readAccount(grant.accountId) : null;
|
|
@@ -1225,7 +1133,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1225
1133
|
return { account: updatedAccount, grant: draft };
|
|
1226
1134
|
});
|
|
1227
1135
|
} catch (error) {
|
|
1228
|
-
if (!(error instanceof plugins.
|
|
1136
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1229
1137
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1230
1138
|
const account = await this.readAccount(accountId);
|
|
1231
1139
|
const grant = await this.readGrant(grantId);
|
|
@@ -1315,7 +1223,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1315
1223
|
return removed;
|
|
1316
1224
|
});
|
|
1317
1225
|
} catch (error) {
|
|
1318
|
-
if (!(error instanceof plugins.
|
|
1226
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1319
1227
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1320
1228
|
const account = await this.readAccount(accountId);
|
|
1321
1229
|
if (event && account?.removed && account.updateId === updateId) return account;
|
|
@@ -1341,7 +1249,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1341
1249
|
return { meta, account: draft };
|
|
1342
1250
|
});
|
|
1343
1251
|
} catch (error) {
|
|
1344
|
-
if (!(error instanceof plugins.
|
|
1252
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1345
1253
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1346
1254
|
const account = await this.readAccount(accountId);
|
|
1347
1255
|
if (event && account?.updateId === updateId) return { meta: await this.readMeta(), account };
|
|
@@ -1386,7 +1294,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1386
1294
|
return { meta, binding: draft };
|
|
1387
1295
|
});
|
|
1388
1296
|
} catch (error) {
|
|
1389
|
-
if (!(error instanceof plugins.
|
|
1297
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1390
1298
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1391
1299
|
const binding = await this.readBinding(bindingId);
|
|
1392
1300
|
if (event && binding?.updateId === updateId) return { meta: await this.readMeta(), binding };
|
|
@@ -1410,7 +1318,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1410
1318
|
return true;
|
|
1411
1319
|
});
|
|
1412
1320
|
} catch (error) {
|
|
1413
|
-
if (!(error instanceof plugins.
|
|
1321
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1414
1322
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1415
1323
|
const binding = await this.readBinding(bindingId);
|
|
1416
1324
|
// Every bind mints a fresh random capability. An event plus absence or a different
|
|
@@ -1458,7 +1366,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1458
1366
|
return draft;
|
|
1459
1367
|
});
|
|
1460
1368
|
} catch (error) {
|
|
1461
|
-
if (!(error instanceof plugins.
|
|
1369
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1462
1370
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1463
1371
|
const enrollment = await this.readEnrollment(enrollmentId);
|
|
1464
1372
|
if (event && enrollment?.updateId === updateId) return enrollment;
|
|
@@ -1540,7 +1448,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1540
1448
|
return draft;
|
|
1541
1449
|
});
|
|
1542
1450
|
} catch (error) {
|
|
1543
|
-
if (!(error instanceof plugins.
|
|
1451
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1544
1452
|
const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
|
|
1545
1453
|
const handoff = await this.readHandoff(handoffId);
|
|
1546
1454
|
const grant = await this.readGrant(grantId);
|
|
@@ -1563,7 +1471,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1563
1471
|
return stored ? AuthSwitchAuthorityClaudeHandoffModel.exact.toPersisted(stored) : null;
|
|
1564
1472
|
}
|
|
1565
1473
|
|
|
1566
|
-
/** A
|
|
1474
|
+
/** A verified backend import receipt, not an account snapshot, authorizes an inactive Claude grant. */
|
|
1567
1475
|
public async hasVerifiedClaudeGrantReceipt(accountId: string, grantId: string,
|
|
1568
1476
|
session?: TSession): Promise<boolean> {
|
|
1569
1477
|
this.db();
|
|
@@ -1575,7 +1483,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1575
1483
|
filter: { grantId, status }, sort: { id: 1 }, limit: 128, session,
|
|
1576
1484
|
});
|
|
1577
1485
|
if (stored.some(item => item.accountId === accountId
|
|
1578
|
-
&& item.backupDigest !== null
|
|
1579
1486
|
&& ['authswitch_stash', 'authswitch_backup', 'claude_native'].includes(item.sourceKind))) return true;
|
|
1580
1487
|
if (stored.length === 128) throw new Error('Claude grant has too many migration receipts to verify.');
|
|
1581
1488
|
}
|
|
@@ -1623,7 +1530,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1623
1530
|
const grantStored = await AuthSwitchAuthorityGrantModel.exact.findStoredOne({ id: grantId }, { session });
|
|
1624
1531
|
const current = await AuthSwitchAuthorityClaudeHomeModel.exact.findStoredOne({ id: homeId }, { session });
|
|
1625
1532
|
if (!receiptStored || !['verified', 'complete'].includes(receiptStored.status)
|
|
1626
|
-
|| receiptStored.backupDigest === null
|
|
1627
1533
|
|| receiptStored.sourceKind !== 'claude_native' || receiptStored.sourcePathHash !== homeId
|
|
1628
1534
|
|| receiptStored.sourceDigest !== sourceDigest || receiptStored.accountId !== accountId
|
|
1629
1535
|
|| receiptStored.grantId !== grantId || !accountStored || accountStored.removed
|
|
@@ -1649,7 +1555,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1649
1555
|
return home;
|
|
1650
1556
|
});
|
|
1651
1557
|
} catch (error) {
|
|
1652
|
-
if (!(error instanceof plugins.
|
|
1558
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError)
|
|
1653
1559
|
|| !['ambiguous_write', 'unique_conflict'].includes(error.code)) throw error;
|
|
1654
1560
|
const home = await this.readClaudeHome(homeId);
|
|
1655
1561
|
if (home?.updateId === updateId && home.activeAccountId === accountId && home.activeGrantId === grantId) return home;
|
|
@@ -1772,7 +1678,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1772
1678
|
});
|
|
1773
1679
|
if (receipts.length === 128) throw new Error('Claude grant receipt set is unbounded.');
|
|
1774
1680
|
verifiedIncoming ||= receipts.some(item => item.accountId === incoming.accountId
|
|
1775
|
-
&& item.backupDigest !== null
|
|
1776
1681
|
&& ['authswitch_stash', 'authswitch_backup', 'claude_native'].includes(item.sourceKind));
|
|
1777
1682
|
}
|
|
1778
1683
|
if (draft.handoff.phase !== 'reserved' || home.pendingOperationId !== null
|
|
@@ -1843,7 +1748,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1843
1748
|
return draft;
|
|
1844
1749
|
});
|
|
1845
1750
|
} catch (error) {
|
|
1846
|
-
if (!(error instanceof plugins.
|
|
1751
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1847
1752
|
const context = await this.readClaudeHandoffContext(operationId);
|
|
1848
1753
|
const first = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: eventIds[0] });
|
|
1849
1754
|
const second = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: eventIds[1] });
|
|
@@ -1860,7 +1765,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1860
1765
|
}
|
|
1861
1766
|
}
|
|
1862
1767
|
|
|
1863
|
-
/** Ledger transitions never silently replace an active credential or waive
|
|
1768
|
+
/** Ledger transitions never silently replace an active credential or waive the verified import. */
|
|
1864
1769
|
public async changeMigrationLedger(updateId: string, ledgerId: string,
|
|
1865
1770
|
change: (current: IStoredAuthorityMigrationLedger | null) => IStoredAuthorityMigrationLedger): Promise<IStoredAuthorityMigrationLedger> {
|
|
1866
1771
|
try {
|
|
@@ -1869,7 +1774,12 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1869
1774
|
const draft = change(current ? structuredClone(AuthSwitchAuthorityMigrationLedgerModel.exact.toPersisted(current)) : null);
|
|
1870
1775
|
if (draft.id !== ledgerId || draft.updateId !== updateId) throw new Error('Migration ledger identity is invalid.');
|
|
1871
1776
|
assertStoredAuthorityMigrationLedger(draft);
|
|
1872
|
-
|
|
1777
|
+
// A source may only be considered migrated after a verified import bound to an account, a grant and a
|
|
1778
|
+
// source digest. This deliberately tightens a flow that has no production caller and has never run.
|
|
1779
|
+
if (draft.status === 'complete' && (current?.status !== 'verified'
|
|
1780
|
+
|| current.accountId !== draft.accountId || current.grantId !== draft.grantId)) {
|
|
1781
|
+
throw new Error('Migration completion requires a verified import of the same account and grant.');
|
|
1782
|
+
}
|
|
1873
1783
|
if (current) {
|
|
1874
1784
|
if (current.status === 'complete' || current.version !== draft.version
|
|
1875
1785
|
|| current.sourceKind !== draft.sourceKind || current.sourcePathHash !== draft.sourcePathHash
|
|
@@ -1888,7 +1798,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1888
1798
|
return draft;
|
|
1889
1799
|
});
|
|
1890
1800
|
} catch (error) {
|
|
1891
|
-
if (!(error instanceof plugins.
|
|
1801
|
+
if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
|
|
1892
1802
|
const ledger = await this.readMigrationLedger(ledgerId);
|
|
1893
1803
|
if (ledger?.updateId === updateId) return ledger;
|
|
1894
1804
|
throw new Error('Authswitch migration ledger write outcome is unknown.');
|
|
@@ -1897,7 +1807,6 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1897
1807
|
|
|
1898
1808
|
public async stop(): Promise<void> {
|
|
1899
1809
|
this.started = false;
|
|
1900
|
-
this.mode = null;
|
|
1901
1810
|
const database = this.database;
|
|
1902
1811
|
const engine = this.engine;
|
|
1903
1812
|
this.database = undefined;
|