@modelprofile.com/authswitch 7.0.0 → 8.1.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 (86) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/authority-contract.d.ts +1 -3
  3. package/dist_ts/authority-contract.js +1 -1
  4. package/dist_ts/authority-import-contract.d.ts +207 -0
  5. package/dist_ts/authority-import-contract.js +18 -0
  6. package/dist_ts/classes.authoritybroker.js +1 -5
  7. package/dist_ts/classes.authoritycli.d.ts +4 -0
  8. package/dist_ts/classes.authoritycli.js +149 -12
  9. package/dist_ts/classes.authorityclient.d.ts +7 -0
  10. package/dist_ts/classes.authorityclient.js +13 -1
  11. package/dist_ts/classes.authoritydaemon.d.ts +4 -0
  12. package/dist_ts/classes.authoritydaemon.js +75 -9
  13. package/dist_ts/classes.authoritydatabase.d.ts +10 -26
  14. package/dist_ts/classes.authoritydatabase.js +68 -133
  15. package/dist_ts/classes.authorityimport.d.ts +161 -0
  16. package/dist_ts/classes.authorityimport.js +638 -0
  17. package/dist_ts/classes.authoritymodels.d.ts +39 -43
  18. package/dist_ts/classes.authoritymodels.js +209 -243
  19. package/dist_ts/classes.authoritypreuse.js +1 -2
  20. package/dist_ts/classes.authorityregistry.d.ts +2 -17
  21. package/dist_ts/classes.authorityregistry.js +18 -74
  22. package/dist_ts/classes.authoritysecrets.d.ts +1 -1
  23. package/dist_ts/classes.authoritysecrets.js +2 -2
  24. package/dist_ts/classes.authorityusage.d.ts +3 -3
  25. package/dist_ts/classes.claudeauthority.js +3 -7
  26. package/dist_ts/classes.claudenative.d.ts +1 -8
  27. package/dist_ts/classes.claudenative.js +2 -25
  28. package/dist_ts/classes.codexmanaged.d.ts +1 -1
  29. package/dist_ts/index.d.ts +1 -0
  30. package/dist_ts/index.js +6 -3
  31. package/dist_ts/plugins.d.ts +7 -7
  32. package/dist_ts/plugins.js +8 -8
  33. package/dist_ts/ts_migration/0001_authority_meta.d.ts +13 -3
  34. package/dist_ts/ts_migration/0001_authority_meta.js +29 -17
  35. package/dist_ts/ts_migration/0002_remove_backup_records.d.ts +17 -0
  36. package/dist_ts/ts_migration/0002_remove_backup_records.js +48 -0
  37. package/dist_ts/ts_migration/index.d.ts +6 -3
  38. package/dist_ts/ts_migration/index.js +10 -4
  39. package/dist_ts/ts_migration/legacysources/authswitchstores.d.ts +2 -0
  40. package/dist_ts/ts_migration/legacysources/authswitchstores.js +244 -0
  41. package/dist_ts/ts_migration/legacysources/index.d.ts +18 -0
  42. package/dist_ts/ts_migration/legacysources/index.js +24 -0
  43. package/dist_ts/ts_migration/legacysources/material.d.ts +37 -0
  44. package/dist_ts/ts_migration/legacysources/material.js +120 -0
  45. package/dist_ts/ts_migration/legacysources/nativestores.d.ts +2 -0
  46. package/dist_ts/ts_migration/legacysources/nativestores.js +207 -0
  47. package/dist_ts/ts_migration/legacysources/shared.d.ts +124 -0
  48. package/dist_ts/ts_migration/legacysources/shared.js +142 -0
  49. package/package.json +6 -5
  50. package/readme.md +68 -26
  51. package/ts/00_commitinfo_data.ts +1 -1
  52. package/ts/authority-contract.ts +1 -3
  53. package/ts/authority-import-contract.ts +216 -0
  54. package/ts/classes.authoritybroker.ts +0 -4
  55. package/ts/classes.authoritycli.ts +165 -11
  56. package/ts/classes.authorityclient.ts +22 -0
  57. package/ts/classes.authoritydaemon.ts +75 -8
  58. package/ts/classes.authoritydatabase.ts +70 -145
  59. package/ts/classes.authorityimport.ts +718 -0
  60. package/ts/classes.authoritymodels.ts +253 -269
  61. package/ts/classes.authoritypreuse.ts +0 -1
  62. package/ts/classes.authorityregistry.ts +20 -91
  63. package/ts/classes.authoritysecrets.ts +1 -1
  64. package/ts/classes.authorityusage.ts +3 -3
  65. package/ts/classes.claudeauthority.ts +2 -6
  66. package/ts/classes.claudenative.ts +1 -32
  67. package/ts/classes.codexmanaged.ts +1 -1
  68. package/ts/index.ts +5 -2
  69. package/ts/plugins.ts +7 -7
  70. package/ts/ts_migration/0001_authority_meta.ts +29 -17
  71. package/ts/ts_migration/0002_remove_backup_records.ts +49 -0
  72. package/ts/ts_migration/index.ts +10 -5
  73. package/ts/ts_migration/legacysources/authswitchstores.ts +211 -0
  74. package/ts/ts_migration/legacysources/index.ts +35 -0
  75. package/ts/ts_migration/legacysources/material.ts +134 -0
  76. package/ts/ts_migration/legacysources/nativestores.ts +204 -0
  77. package/ts/ts_migration/legacysources/shared.ts +220 -0
  78. package/dist_ts/authorityrehearsal.child.d.ts +0 -1
  79. package/dist_ts/authorityrehearsal.child.js +0 -395
  80. package/dist_ts/classes.authoritybackup.d.ts +0 -134
  81. package/dist_ts/classes.authoritybackup.js +0 -769
  82. package/dist_ts/classes.authorityrehearsal.d.ts +0 -127
  83. package/dist_ts/classes.authorityrehearsal.js +0 -632
  84. package/ts/authorityrehearsal.child.ts +0 -330
  85. package/ts/classes.authoritybackup.ts +0 -780
  86. package/ts/classes.authorityrehearsal.ts +0 -625
@@ -8,7 +8,7 @@ import {
8
8
  assertStoredAuthorityBinding, assertStoredAuthorityClaudeHandoff, assertStoredAuthorityClaudeHome,
9
9
  assertStoredAuthorityEnrollment, assertStoredAuthorityGrant,
10
10
  assertStoredAuthorityHandoff, assertStoredAuthorityMigrationLedger, assertStoredAuthorityOperation,
11
- assertStoredAuthorityUsage,
11
+ assertStoredAuthorityUsage, grantRefresherTransferRefusal, transfersGrantRefresher,
12
12
  type IStoredAuthorityAccount, type IStoredAuthorityBinding, type IStoredAuthorityClaudeHandoff,
13
13
  type IStoredAuthorityClaudeHome, type IStoredAuthorityCodexHome, type IStoredAuthorityEnrollment,
14
14
  type IStoredAuthorityEvent, type IStoredAuthorityGrant, type IStoredAuthorityHandoff,
@@ -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 { authorityModel, authorityModelRegistry, authorityStoredRecords,
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.smartdata.SmartdataSession;
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.smartdb.LocalSmartDb;
76
- private database?: plugins.smartdata.SmartdataDb;
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(options: { mode?: 'normal' | 'closed_restore' } = {}): Promise<void> {
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.smartdb.LocalSmartDb({ folderPath: this.options.dataDirectory, socketPath: this.options.socketPath });
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.smartdata.SmartdataDb({ mongoDbUrl: connection.connectionUri, mongoDbName: 'authswitch' });
89
+ const database = new plugins.nosqldb.SmartdataDb({ mongoDbUrl: connection.connectionUri, mongoDbName: 'authswitch' });
102
90
  this.database = database;
103
91
  await database.init();
104
- // The LocalSmartDb root lease excludes another engine during this catalog admission.
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 (mode === 'closed_restore' && !fresh) {
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 (mode === 'normal') {
126
- if (fresh) {
127
- const initial: IStoredAuthorityMeta = {
128
- id: 'authswitch-authority', schemaVersion: 2, admission: 'open',
129
- epoch: plugins.crypto.randomUUID(), revision: 0, updateId: plugins.crypto.randomUUID(),
130
- };
131
- const result = await AuthSwitchAuthorityMetaModel.exact.insert(initial);
132
- if (result.status !== 'inserted') throw new Error('Authswitch authority could not initialize.');
133
- } else if (!await AuthSwitchAuthorityMetaModel.exact.findStoredOne({ id: 'authswitch-authority' })) {
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.smartdata.SmartdataDb {
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 SmartData. */
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.smartdata.SmartdataExactPersistenceError
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
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.');
@@ -665,6 +573,19 @@ export class AuthSwitchAuthorityDatabase {
665
573
  return stored.map(item => AuthSwitchAuthorityMigrationLedgerModel.exact.toPersisted(item));
666
574
  }
667
575
 
576
+ /** Every migration ledger row in id order, one bounded page at a time. */
577
+ public async migrationLedgerPage(after: string | null,
578
+ limit = 128): Promise<{ rows: IStoredAuthorityMigrationLedger[]; nextCursor: string | null }> {
579
+ this.db();
580
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 256) throw new Error('Invalid migration page size.');
581
+ const stored = await AuthSwitchAuthorityMigrationLedgerModel.exact.findStored({
582
+ filter: after ? { id: { $gt: after } } : {}, sort: { id: 1 }, limit: limit + 1,
583
+ });
584
+ const rows = stored.slice(0, limit)
585
+ .map(item => AuthSwitchAuthorityMigrationLedgerModel.exact.toPersisted(item));
586
+ return { rows, nextCursor: stored.length > limit ? rows[rows.length - 1].id : null };
587
+ }
588
+
668
589
  public async readAccountAndPrimaryGrant(id: string): Promise<{
669
590
  account: IStoredAuthorityAccount | null; grant: IStoredAuthorityGrant | null;
670
591
  }> {
@@ -806,7 +727,7 @@ export class AuthSwitchAuthorityDatabase {
806
727
  return true;
807
728
  });
808
729
  } catch (error) {
809
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError)) throw error;
730
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError)) throw error;
810
731
  if (error.code === 'unique_conflict') return false;
811
732
  if (error.code !== 'ambiguous_write') throw error;
812
733
  const [stored, event] = await Promise.all([
@@ -1065,9 +986,8 @@ export class AuthSwitchAuthorityDatabase {
1065
986
  if (accountStored && accountStored.primaryGrantId !== grantId) {
1066
987
  throw new Error('Primary grant identity cannot be replaced.');
1067
988
  }
1068
- if (grantStored && ['legacy_native', 'claude_native'].includes(grantStored.owner)
1069
- && draft.grant.owner === 'daemon') {
1070
- throw new Error('Native ownership requires a verified handoff.');
989
+ if (grantStored && transfersGrantRefresher(grantStored.owner, draft.grant.owner)) {
990
+ throw new Error(grantRefresherTransferRefusal(grantStored.owner, draft.grant.owner));
1071
991
  }
1072
992
  await this.persistAccount(session, accountStored, draft.account);
1073
993
  await this.persistGrant(session, grantStored, draft.grant);
@@ -1076,7 +996,7 @@ export class AuthSwitchAuthorityDatabase {
1076
996
  return { meta, ...draft };
1077
997
  });
1078
998
  } catch (error) {
1079
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
999
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1080
1000
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1081
1001
  const { account, grant } = await this.readAccountAndPrimaryGrant(accountId);
1082
1002
  if (event && account?.updateId === updateId && grant?.updateId === updateId) {
@@ -1141,7 +1061,7 @@ export class AuthSwitchAuthorityDatabase {
1141
1061
  return { ...draft, operation: completed };
1142
1062
  });
1143
1063
  } catch (error) {
1144
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1064
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1145
1065
  const [event, operation, pair] = await Promise.all([
1146
1066
  AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId }),
1147
1067
  this.readOperation(operationId), this.readAccountAndPrimaryGrant(accountId),
@@ -1176,7 +1096,7 @@ export class AuthSwitchAuthorityDatabase {
1176
1096
  if (grant.purpose === 'claude_host_native' && draft.state === 'exchange_may_have_been_sent'
1177
1097
  && grant.state !== 'exchange_may_have_been_sent'
1178
1098
  && !(await this.hasVerifiedClaudeGrantReceipt(account.id, grant.id, session))) {
1179
- throw new Error('Claude refresh requires a verified import and backup receipt.');
1099
+ throw new Error('Claude refresh requires a verified import receipt.');
1180
1100
  }
1181
1101
  const accountDraft: IStoredAuthorityAccount = { ...account, revision: account.revision + 1,
1182
1102
  statusObservedAt: draft.statusObservedAt, updateId };
@@ -1187,7 +1107,7 @@ export class AuthSwitchAuthorityDatabase {
1187
1107
  return { meta, account: accountDraft, grant: draft };
1188
1108
  });
1189
1109
  } catch (error) {
1190
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1110
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1191
1111
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1192
1112
  const grant = await this.readGrant(grantId);
1193
1113
  const account = grant ? await this.readAccount(grant.accountId) : null;
@@ -1216,6 +1136,10 @@ export class AuthSwitchAuthorityDatabase {
1216
1136
  || draft.providerId !== account.providerId || draft.updateId !== updateId) {
1217
1137
  throw new Error('Secondary grant identity is invalid.');
1218
1138
  }
1139
+ // A secondary grant transfers its refresher by a verified handoff as well; one token, one refresher.
1140
+ if (grant && transfersGrantRefresher(grant.owner, draft.owner)) {
1141
+ throw new Error(grantRefresherTransferRefusal(grant.owner, draft.owner));
1142
+ }
1219
1143
  await this.persistGrant(session, grantStored, draft);
1220
1144
  const updatedAccount = { ...account, revision: account.revision + 1,
1221
1145
  statusObservedAt: draft.statusObservedAt, updateId };
@@ -1225,7 +1149,7 @@ export class AuthSwitchAuthorityDatabase {
1225
1149
  return { account: updatedAccount, grant: draft };
1226
1150
  });
1227
1151
  } catch (error) {
1228
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1152
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1229
1153
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1230
1154
  const account = await this.readAccount(accountId);
1231
1155
  const grant = await this.readGrant(grantId);
@@ -1315,7 +1239,7 @@ export class AuthSwitchAuthorityDatabase {
1315
1239
  return removed;
1316
1240
  });
1317
1241
  } catch (error) {
1318
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1242
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1319
1243
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1320
1244
  const account = await this.readAccount(accountId);
1321
1245
  if (event && account?.removed && account.updateId === updateId) return account;
@@ -1341,7 +1265,7 @@ export class AuthSwitchAuthorityDatabase {
1341
1265
  return { meta, account: draft };
1342
1266
  });
1343
1267
  } catch (error) {
1344
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1268
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1345
1269
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1346
1270
  const account = await this.readAccount(accountId);
1347
1271
  if (event && account?.updateId === updateId) return { meta: await this.readMeta(), account };
@@ -1386,7 +1310,7 @@ export class AuthSwitchAuthorityDatabase {
1386
1310
  return { meta, binding: draft };
1387
1311
  });
1388
1312
  } catch (error) {
1389
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1313
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1390
1314
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1391
1315
  const binding = await this.readBinding(bindingId);
1392
1316
  if (event && binding?.updateId === updateId) return { meta: await this.readMeta(), binding };
@@ -1410,7 +1334,7 @@ export class AuthSwitchAuthorityDatabase {
1410
1334
  return true;
1411
1335
  });
1412
1336
  } catch (error) {
1413
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1337
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1414
1338
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1415
1339
  const binding = await this.readBinding(bindingId);
1416
1340
  // Every bind mints a fresh random capability. An event plus absence or a different
@@ -1458,7 +1382,7 @@ export class AuthSwitchAuthorityDatabase {
1458
1382
  return draft;
1459
1383
  });
1460
1384
  } catch (error) {
1461
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1385
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1462
1386
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1463
1387
  const enrollment = await this.readEnrollment(enrollmentId);
1464
1388
  if (event && enrollment?.updateId === updateId) return enrollment;
@@ -1540,7 +1464,7 @@ export class AuthSwitchAuthorityDatabase {
1540
1464
  return draft;
1541
1465
  });
1542
1466
  } catch (error) {
1543
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1467
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1544
1468
  const event = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: updateId });
1545
1469
  const handoff = await this.readHandoff(handoffId);
1546
1470
  const grant = await this.readGrant(grantId);
@@ -1563,7 +1487,7 @@ export class AuthSwitchAuthorityDatabase {
1563
1487
  return stored ? AuthSwitchAuthorityClaudeHandoffModel.exact.toPersisted(stored) : null;
1564
1488
  }
1565
1489
 
1566
- /** A backed-up backend import receipt, not an account snapshot, authorizes an inactive Claude grant. */
1490
+ /** A verified backend import receipt, not an account snapshot, authorizes an inactive Claude grant. */
1567
1491
  public async hasVerifiedClaudeGrantReceipt(accountId: string, grantId: string,
1568
1492
  session?: TSession): Promise<boolean> {
1569
1493
  this.db();
@@ -1575,7 +1499,6 @@ export class AuthSwitchAuthorityDatabase {
1575
1499
  filter: { grantId, status }, sort: { id: 1 }, limit: 128, session,
1576
1500
  });
1577
1501
  if (stored.some(item => item.accountId === accountId
1578
- && item.backupDigest !== null
1579
1502
  && ['authswitch_stash', 'authswitch_backup', 'claude_native'].includes(item.sourceKind))) return true;
1580
1503
  if (stored.length === 128) throw new Error('Claude grant has too many migration receipts to verify.');
1581
1504
  }
@@ -1623,7 +1546,6 @@ export class AuthSwitchAuthorityDatabase {
1623
1546
  const grantStored = await AuthSwitchAuthorityGrantModel.exact.findStoredOne({ id: grantId }, { session });
1624
1547
  const current = await AuthSwitchAuthorityClaudeHomeModel.exact.findStoredOne({ id: homeId }, { session });
1625
1548
  if (!receiptStored || !['verified', 'complete'].includes(receiptStored.status)
1626
- || receiptStored.backupDigest === null
1627
1549
  || receiptStored.sourceKind !== 'claude_native' || receiptStored.sourcePathHash !== homeId
1628
1550
  || receiptStored.sourceDigest !== sourceDigest || receiptStored.accountId !== accountId
1629
1551
  || receiptStored.grantId !== grantId || !accountStored || accountStored.removed
@@ -1649,7 +1571,7 @@ export class AuthSwitchAuthorityDatabase {
1649
1571
  return home;
1650
1572
  });
1651
1573
  } catch (error) {
1652
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError)
1574
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError)
1653
1575
  || !['ambiguous_write', 'unique_conflict'].includes(error.code)) throw error;
1654
1576
  const home = await this.readClaudeHome(homeId);
1655
1577
  if (home?.updateId === updateId && home.activeAccountId === accountId && home.activeGrantId === grantId) return home;
@@ -1772,7 +1694,6 @@ export class AuthSwitchAuthorityDatabase {
1772
1694
  });
1773
1695
  if (receipts.length === 128) throw new Error('Claude grant receipt set is unbounded.');
1774
1696
  verifiedIncoming ||= receipts.some(item => item.accountId === incoming.accountId
1775
- && item.backupDigest !== null
1776
1697
  && ['authswitch_stash', 'authswitch_backup', 'claude_native'].includes(item.sourceKind));
1777
1698
  }
1778
1699
  if (draft.handoff.phase !== 'reserved' || home.pendingOperationId !== null
@@ -1843,7 +1764,7 @@ export class AuthSwitchAuthorityDatabase {
1843
1764
  return draft;
1844
1765
  });
1845
1766
  } catch (error) {
1846
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1767
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1847
1768
  const context = await this.readClaudeHandoffContext(operationId);
1848
1769
  const first = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: eventIds[0] });
1849
1770
  const second = await AuthSwitchAuthorityEventModel.exact.findStoredOne({ id: eventIds[1] });
@@ -1860,7 +1781,7 @@ export class AuthSwitchAuthorityDatabase {
1860
1781
  }
1861
1782
  }
1862
1783
 
1863
- /** Ledger transitions never silently replace an active credential or waive backup proof. */
1784
+ /** Ledger transitions never silently replace an active credential or waive the verified import. */
1864
1785
  public async changeMigrationLedger(updateId: string, ledgerId: string,
1865
1786
  change: (current: IStoredAuthorityMigrationLedger | null) => IStoredAuthorityMigrationLedger): Promise<IStoredAuthorityMigrationLedger> {
1866
1787
  try {
@@ -1869,7 +1790,12 @@ export class AuthSwitchAuthorityDatabase {
1869
1790
  const draft = change(current ? structuredClone(AuthSwitchAuthorityMigrationLedgerModel.exact.toPersisted(current)) : null);
1870
1791
  if (draft.id !== ledgerId || draft.updateId !== updateId) throw new Error('Migration ledger identity is invalid.');
1871
1792
  assertStoredAuthorityMigrationLedger(draft);
1872
- if (draft.status === 'complete' && draft.backupDigest === null) throw new Error('Migration completion requires verified backup proof.');
1793
+ // A source may only be considered migrated after a verified import bound to an account, a grant and a
1794
+ // source digest. This deliberately tightens a flow that has no production caller and has never run.
1795
+ if (draft.status === 'complete' && (current?.status !== 'verified'
1796
+ || current.accountId !== draft.accountId || current.grantId !== draft.grantId)) {
1797
+ throw new Error('Migration completion requires a verified import of the same account and grant.');
1798
+ }
1873
1799
  if (current) {
1874
1800
  if (current.status === 'complete' || current.version !== draft.version
1875
1801
  || current.sourceKind !== draft.sourceKind || current.sourcePathHash !== draft.sourcePathHash
@@ -1888,7 +1814,7 @@ export class AuthSwitchAuthorityDatabase {
1888
1814
  return draft;
1889
1815
  });
1890
1816
  } catch (error) {
1891
- if (!(error instanceof plugins.smartdata.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1817
+ if (!(error instanceof plugins.nosqldb.SmartdataExactPersistenceError) || error.code !== 'ambiguous_write') throw error;
1892
1818
  const ledger = await this.readMigrationLedger(ledgerId);
1893
1819
  if (ledger?.updateId === updateId) return ledger;
1894
1820
  throw new Error('Authswitch migration ledger write outcome is unknown.');
@@ -1897,7 +1823,6 @@ export class AuthSwitchAuthorityDatabase {
1897
1823
 
1898
1824
  public async stop(): Promise<void> {
1899
1825
  this.started = false;
1900
- this.mode = null;
1901
1826
  const database = this.database;
1902
1827
  const engine = this.engine;
1903
1828
  this.database = undefined;