@modelprofile.com/authswitch 6.4.0 → 7.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.
Files changed (98) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/authority-contract.d.ts +302 -6
  3. package/dist_ts/authority-contract.js +1 -1
  4. package/dist_ts/authority-runtime-contract.d.ts +15 -0
  5. package/dist_ts/authorityrehearsal.child.d.ts +1 -0
  6. package/dist_ts/authorityrehearsal.child.js +395 -0
  7. package/dist_ts/classes.accountlist.js +4 -3
  8. package/dist_ts/classes.authoritybackup.d.ts +134 -0
  9. package/dist_ts/classes.authoritybackup.js +769 -0
  10. package/dist_ts/classes.authoritybroker.d.ts +29 -6
  11. package/dist_ts/classes.authoritybroker.js +413 -122
  12. package/dist_ts/classes.authoritycli.d.ts +24 -0
  13. package/dist_ts/classes.authoritycli.js +675 -0
  14. package/dist_ts/classes.authorityclient.d.ts +34 -9
  15. package/dist_ts/classes.authorityclient.js +136 -38
  16. package/dist_ts/classes.authoritydaemon.d.ts +47 -0
  17. package/dist_ts/classes.authoritydaemon.js +705 -16
  18. package/dist_ts/classes.authoritydatabase.d.ts +182 -8
  19. package/dist_ts/classes.authoritydatabase.js +1188 -41
  20. package/dist_ts/classes.authoritymodels.d.ts +247 -1
  21. package/dist_ts/classes.authoritymodels.js +870 -6
  22. package/dist_ts/classes.authoritypreuse.d.ts +47 -0
  23. package/dist_ts/classes.authoritypreuse.js +278 -0
  24. package/dist_ts/classes.authorityregistry.d.ts +24 -0
  25. package/dist_ts/classes.authorityregistry.js +88 -0
  26. package/dist_ts/classes.authorityrehearsal.d.ts +127 -0
  27. package/dist_ts/classes.authorityrehearsal.js +632 -0
  28. package/dist_ts/classes.authorityusage.d.ts +160 -0
  29. package/dist_ts/classes.authorityusage.js +429 -0
  30. package/dist_ts/classes.claudeauthority.d.ts +66 -0
  31. package/dist_ts/classes.claudeauthority.js +532 -0
  32. package/dist_ts/classes.claudecodeharness.js +3 -2
  33. package/dist_ts/classes.claudecodelocks.d.ts +13 -0
  34. package/dist_ts/classes.claudecodelocks.js +75 -3
  35. package/dist_ts/classes.claudenative.d.ts +137 -0
  36. package/dist_ts/classes.claudenative.js +731 -0
  37. package/dist_ts/classes.claudestatus.d.ts +9 -0
  38. package/dist_ts/classes.claudestatus.js +26 -9
  39. package/dist_ts/classes.claudetokenrefresh.d.ts +15 -1
  40. package/dist_ts/classes.claudetokenrefresh.js +74 -21
  41. package/dist_ts/classes.codexharness.js +4 -2
  42. package/dist_ts/classes.codexmanaged.d.ts +91 -0
  43. package/dist_ts/classes.codexmanaged.js +404 -0
  44. package/dist_ts/classes.codexpreuse.d.ts +8 -0
  45. package/dist_ts/classes.codexpreuse.js +24 -6
  46. package/dist_ts/classes.fileharness.d.ts +2 -0
  47. package/dist_ts/classes.fileharness.js +6 -4
  48. package/dist_ts/classes.limits.js +12 -7
  49. package/dist_ts/classes.opencodeharness.js +2 -2
  50. package/dist_ts/claudehttp.d.ts +10 -0
  51. package/dist_ts/claudehttp.js +29 -3
  52. package/dist_ts/index.d.ts +2 -0
  53. package/dist_ts/index.js +8 -1
  54. package/dist_ts/interfaces.harness.d.ts +6 -0
  55. package/dist_ts/interfaces.list.d.ts +9 -3
  56. package/dist_ts/plugins.d.ts +7 -3
  57. package/dist_ts/plugins.js +9 -4
  58. package/dist_ts/ts_migration/0001_authority_meta.d.ts +3 -0
  59. package/dist_ts/ts_migration/0001_authority_meta.js +32 -0
  60. package/dist_ts/ts_migration/index.d.ts +3 -0
  61. package/dist_ts/ts_migration/index.js +6 -0
  62. package/package.json +12 -2
  63. package/readme.md +205 -13
  64. package/ts/00_commitinfo_data.ts +1 -1
  65. package/ts/authority-contract.ts +264 -9
  66. package/ts/authority-runtime-contract.ts +21 -1
  67. package/ts/authorityrehearsal.child.ts +330 -0
  68. package/ts/classes.accountlist.ts +3 -2
  69. package/ts/classes.authoritybackup.ts +780 -0
  70. package/ts/classes.authoritybroker.ts +410 -120
  71. package/ts/classes.authoritycli.ts +719 -0
  72. package/ts/classes.authorityclient.ts +164 -38
  73. package/ts/classes.authoritydaemon.ts +614 -17
  74. package/ts/classes.authoritydatabase.ts +1229 -44
  75. package/ts/classes.authoritymodels.ts +579 -4
  76. package/ts/classes.authoritypreuse.ts +297 -0
  77. package/ts/classes.authorityregistry.ts +118 -0
  78. package/ts/classes.authorityrehearsal.ts +625 -0
  79. package/ts/classes.authorityusage.ts +527 -0
  80. package/ts/classes.claudeauthority.ts +541 -0
  81. package/ts/classes.claudecodeharness.ts +2 -1
  82. package/ts/classes.claudecodelocks.ts +70 -1
  83. package/ts/classes.claudenative.ts +816 -0
  84. package/ts/classes.claudestatus.ts +38 -9
  85. package/ts/classes.claudetokenrefresh.ts +82 -19
  86. package/ts/classes.codexharness.ts +3 -1
  87. package/ts/classes.codexmanaged.ts +402 -0
  88. package/ts/classes.codexpreuse.ts +33 -5
  89. package/ts/classes.fileharness.ts +7 -3
  90. package/ts/classes.limits.ts +11 -6
  91. package/ts/classes.opencodeharness.ts +1 -1
  92. package/ts/claudehttp.ts +31 -2
  93. package/ts/index.ts +7 -0
  94. package/ts/interfaces.harness.ts +6 -0
  95. package/ts/interfaces.list.ts +9 -3
  96. package/ts/plugins.ts +9 -3
  97. package/ts/ts_migration/0001_authority_meta.ts +33 -0
  98. package/ts/ts_migration/index.ts +7 -0
@@ -0,0 +1,297 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IAuthSwitchPreuseOperation } from './authority-contract.js';
3
+ import { AuthSwitchAuthorityBroker } from './classes.authoritybroker.js';
4
+ import { AuthSwitchAuthorityDatabase } from './classes.authoritydatabase.js';
5
+ import type { IStoredAuthorityPreuseOperation, TStoredAuthorityPreuseProblem } from './classes.authoritymodels.js';
6
+ import { CodexPreuse, type ICodexPreuseLifecycle } from './classes.codexpreuse.js';
7
+ import type { IHarnessPreuseOptions, IHarnessPreuseResult } from './interfaces.harness.js';
8
+ import { validatePreuseOptions } from './preuse.js';
9
+
10
+ export interface IAuthSwitchAuthorityPreuseExecutor {
11
+ runAccess(credential: plugins.flexAccounts.IOpenAiChatGptAccessCredential,
12
+ options: IHarnessPreuseOptions, lifecycle: ICodexPreuseLifecycle): Promise<IHarnessPreuseResult>;
13
+ }
14
+
15
+ export interface IAuthSwitchAuthorityPreuseOptions {
16
+ executor?: IAuthSwitchAuthorityPreuseExecutor;
17
+ now?: () => number;
18
+ minAccessValidityMs?: number;
19
+ }
20
+
21
+ export interface IAuthSwitchAuthorityPreuseRequest {
22
+ operationId: string;
23
+ accountId: string;
24
+ loginId: string;
25
+ purpose: 'openai_managed';
26
+ prompt: string;
27
+ model?: string;
28
+ }
29
+
30
+ export type TAuthSwitchAuthorityPreuseFreshAdmission = () => 'admitted' | 'account_busy';
31
+
32
+ interface IRunningPreuse {
33
+ requestHash: string;
34
+ controller: AbortController;
35
+ task: Promise<void>;
36
+ }
37
+
38
+ const uuid = (value: unknown): value is string => typeof value === 'string' && /^[a-f0-9-]{36}$/.test(value);
39
+ const hash = (value: unknown): value is string => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
40
+ const publicOperation = (operation: IStoredAuthorityPreuseOperation): IAuthSwitchPreuseOperation => ({
41
+ id: operation.id, accountId: operation.accountId, loginId: operation.grantId,
42
+ purpose: operation.purpose, requestedModel: operation.requestedModel, model: operation.model,
43
+ state: operation.state, result: operation.result, problem: operation.problem,
44
+ createdAt: operation.createdAt, updatedAt: operation.updatedAt,
45
+ finishedAt: operation.finishedAt, revision: operation.revision,
46
+ });
47
+
48
+ /** Durable, at-most-once owner of one quota-consuming inference for one exact login. */
49
+ export class AuthSwitchAuthorityPreuse {
50
+ private readonly executor: IAuthSwitchAuthorityPreuseExecutor;
51
+ private readonly now: () => number;
52
+ private readonly minAccessValidityMs: number;
53
+ private readonly starts = new Map<string, { requestHash: string; promise: Promise<IAuthSwitchPreuseOperation> }>();
54
+ private readonly running = new Map<string, IRunningPreuse>();
55
+ private closed = false;
56
+
57
+ constructor(private readonly database: AuthSwitchAuthorityDatabase,
58
+ private readonly broker: AuthSwitchAuthorityBroker, options: IAuthSwitchAuthorityPreuseOptions = {}) {
59
+ this.executor = options.executor ?? new CodexPreuse();
60
+ this.now = options.now ?? Date.now;
61
+ this.minAccessValidityMs = options.minAccessValidityMs ?? 300_000;
62
+ }
63
+
64
+ /** Terminalize every abandoned record before management admission opens. */
65
+ public async start(): Promise<void> {
66
+ for (const state of ['preparing', 'request_may_have_started'] as const) {
67
+ let after: string | null = null;
68
+ while (true) {
69
+ const page = await this.database.preuseOperationsByState(state, after);
70
+ if (!page.length) break;
71
+ for (const operation of page) {
72
+ const updateId = plugins.crypto.randomUUID();
73
+ const now = new Date(this.now()).toISOString();
74
+ await this.database.changePreuseOperation(updateId, operation.id, current => {
75
+ if (current.state !== state) throw new Error('Preuse recovery changed concurrently.');
76
+ return { ...current, state: state === 'preparing' ? 'not_sent' : 'outcome_unknown',
77
+ problem: state === 'preparing' ? 'interrupted' : 'request_uncertain',
78
+ updatedAt: now, finishedAt: now, revision: current.revision + 1, updateId };
79
+ });
80
+ }
81
+ after = page.at(-1)!.id;
82
+ }
83
+ }
84
+ }
85
+
86
+ public begin(input: IAuthSwitchAuthorityPreuseRequest,
87
+ admitFresh: TAuthSwitchAuthorityPreuseFreshAdmission): Promise<IAuthSwitchPreuseOperation> {
88
+ if (this.closed) return Promise.reject(new Error('Authswitch preuse is closing.'));
89
+ if (!uuid(input.operationId) || !hash(input.accountId) || !hash(input.loginId)
90
+ || input.purpose !== 'openai_managed') {
91
+ return Promise.reject(new Error('Invalid preuse target.'));
92
+ }
93
+ validatePreuseOptions({ prompt: input.prompt, model: input.model });
94
+ const requestedModel = input.model ?? null;
95
+ const requestHash = plugins.crypto.createHash('sha256').update(JSON.stringify([
96
+ 'authswitch-preuse-v1', input.accountId, input.loginId, input.purpose, input.prompt, requestedModel,
97
+ ])).digest('hex');
98
+ const active = this.running.get(input.operationId);
99
+ if (active) {
100
+ if (active.requestHash !== requestHash) return Promise.reject(new Error('Preuse operation ID belongs to a different request.'));
101
+ return this.get(input.operationId);
102
+ }
103
+ const starting = this.starts.get(input.operationId);
104
+ if (starting) {
105
+ if (starting.requestHash !== requestHash) return Promise.reject(new Error('Preuse operation ID belongs to a different request.'));
106
+ return starting.promise;
107
+ }
108
+ const promise = this.admit(input, requestHash, requestedModel, admitFresh).finally(() => {
109
+ if (this.starts.get(input.operationId)?.promise === promise) this.starts.delete(input.operationId);
110
+ });
111
+ this.starts.set(input.operationId, { requestHash, promise });
112
+ return promise;
113
+ }
114
+
115
+ private async admit(input: IAuthSwitchAuthorityPreuseRequest, requestHash: string,
116
+ requestedModel: string | null,
117
+ admitFresh: TAuthSwitchAuthorityPreuseFreshAdmission): Promise<IAuthSwitchPreuseOperation> {
118
+ await this.database.assertOpenAdmission();
119
+ if (this.closed) throw new Error('Authswitch preuse is closing.');
120
+ const prior = await this.database.readOperation(input.operationId);
121
+ if (prior) {
122
+ if (prior.kind !== 'preuse_openai' || prior.accountId !== input.accountId || prior.grantId !== input.loginId
123
+ || prior.purpose !== input.purpose || prior.requestHash !== requestHash
124
+ || prior.requestedModel !== requestedModel) {
125
+ throw new Error('Preuse operation ID belongs to a different request.');
126
+ }
127
+ if (prior.state !== 'preparing') return publicOperation(prior);
128
+ const updateId = plugins.crypto.randomUUID();
129
+ const interruptedAt = new Date(this.now()).toISOString();
130
+ return publicOperation(await this.database.changePreuseOperation(updateId, prior.id, current => ({
131
+ ...current, state: 'not_sent', problem: 'interrupted', updatedAt: interruptedAt,
132
+ finishedAt: interruptedAt, revision: current.revision + 1, updateId,
133
+ })));
134
+ }
135
+ const context = await this.database.readUsageContext(input.accountId, input.loginId);
136
+ if (!context || context.providerId !== 'openai' || context.purpose !== input.purpose
137
+ || context.owner !== 'daemon' || context.removed
138
+ || !['ready', 'retry_wait'].includes(context.grantState)) {
139
+ throw new Error('Selected OpenAI login is unavailable for preuse.');
140
+ }
141
+ if (this.closed) throw new Error('Authswitch preuse is closing.');
142
+ const freshAdmission = admitFresh();
143
+ if (freshAdmission === 'admitted' && this.running.size >= 32) {
144
+ throw new Error('Too many active preuse operations.');
145
+ }
146
+ const now = new Date(this.now()).toISOString();
147
+ const admitted = await this.database.createPreuseOperation({
148
+ id: input.operationId, kind: 'preuse_openai', purpose: input.purpose,
149
+ accountId: input.accountId, grantId: input.loginId, requestHash, requestedModel,
150
+ model: null, authorizationGeneration: context.authorizationGeneration, grantGeneration: null,
151
+ state: 'preparing', result: null, problem: 'none', createdAt: now, updatedAt: now,
152
+ finishedAt: null, revision: 1, updateId: plugins.crypto.randomUUID(),
153
+ });
154
+ if (!admitted.created) {
155
+ if (admitted.operation.state !== 'preparing') return publicOperation(admitted.operation);
156
+ const updateId = plugins.crypto.randomUUID();
157
+ const interruptedAt = new Date(this.now()).toISOString();
158
+ const interrupted = await this.database.changePreuseOperation(updateId, admitted.operation.id, current => ({
159
+ ...current, state: 'not_sent', problem: 'interrupted', updatedAt: interruptedAt,
160
+ finishedAt: interruptedAt, revision: current.revision + 1, updateId,
161
+ }));
162
+ return publicOperation(interrupted);
163
+ }
164
+ if (freshAdmission === 'account_busy') {
165
+ const updateId = plugins.crypto.randomUUID();
166
+ const finishedAt = new Date(this.now()).toISOString();
167
+ const busy = await this.database.changePreuseOperation(updateId, admitted.operation.id, current => ({
168
+ ...current, state: 'not_sent', problem: 'account_busy', updatedAt: finishedAt,
169
+ finishedAt, revision: current.revision + 1, updateId,
170
+ }));
171
+ return publicOperation(busy);
172
+ }
173
+ if (this.closed) {
174
+ const updateId = plugins.crypto.randomUUID();
175
+ const closedAt = new Date(this.now()).toISOString();
176
+ const closed = await this.database.changePreuseOperation(updateId, admitted.operation.id, current => ({
177
+ ...current, state: 'not_sent', problem: 'authority_closing', updatedAt: closedAt,
178
+ finishedAt: closedAt, revision: current.revision + 1, updateId,
179
+ }));
180
+ return publicOperation(closed);
181
+ }
182
+ const controller = new AbortController();
183
+ const owned: IRunningPreuse = { requestHash, controller, task: Promise.resolve() };
184
+ this.running.set(input.operationId, owned);
185
+ owned.task = this.perform(admitted.operation, input.prompt, controller.signal).finally(() => {
186
+ if (this.running.get(input.operationId) === owned) this.running.delete(input.operationId);
187
+ });
188
+ void owned.task.catch(() => {});
189
+ return publicOperation(admitted.operation);
190
+ }
191
+
192
+ private async perform(operation: IStoredAuthorityPreuseOperation, prompt: string, signal: AbortSignal): Promise<void> {
193
+ let phase: 'target' | 'access' | 'catalog' = 'target';
194
+ try {
195
+ signal.throwIfAborted();
196
+ const context = await this.database.readUsageContext(operation.accountId, operation.grantId);
197
+ if (!context || context.providerId !== 'openai' || context.purpose !== operation.purpose
198
+ || context.owner !== 'daemon' || context.removed
199
+ || context.authorizationGeneration !== operation.authorizationGeneration
200
+ || !['ready', 'retry_wait'].includes(context.grantState)) {
201
+ throw new Error('Preuse target changed.');
202
+ }
203
+ phase = 'access';
204
+ const access = await this.broker.resolveUsageAccess(context, undefined, this.minAccessValidityMs);
205
+ signal.throwIfAborted();
206
+ phase = operation.requestedModel === null ? 'catalog' : 'access';
207
+ const credential: plugins.flexAccounts.IOpenAiChatGptAccessCredential = {
208
+ kind: 'chatgptAccess', providerId: 'openai', accessToken: access.accessToken,
209
+ accountId: access.accountId, isFedrampAccount: access.isFedrampAccount,
210
+ };
211
+ const result = await this.executor.runAccess(credential,
212
+ { prompt, ...(operation.requestedModel === null ? {} : { model: operation.requestedModel }), signal },
213
+ { beforeRequest: async model => {
214
+ signal.throwIfAborted();
215
+ await this.database.markPreuseRequestMayHaveStarted(operation.id, plugins.crypto.randomUUID(),
216
+ model, access.grantGeneration, new Date(this.now()).toISOString());
217
+ } });
218
+ const current = await this.readStored(operation.id);
219
+ if (current.state !== 'request_may_have_started') return;
220
+ const count = (value: number | undefined): number | null => Number.isSafeInteger(value) && value! >= 0 ? value! : null;
221
+ const updateId = plugins.crypto.randomUUID();
222
+ const now = new Date(this.now()).toISOString();
223
+ await this.database.changePreuseOperation(updateId, operation.id, prior => ({ ...prior,
224
+ state: 'complete', result: { inputTokens: count(result.inputTokens), outputTokens: count(result.outputTokens),
225
+ totalTokens: count(result.totalTokens) }, problem: 'none', updatedAt: now, finishedAt: now,
226
+ revision: prior.revision + 1, updateId }));
227
+ } catch {
228
+ await this.fail(operation.id, phase, signal);
229
+ }
230
+ }
231
+
232
+ private async fail(operationId: string, phase: 'target' | 'access' | 'catalog', signal: AbortSignal): Promise<void> {
233
+ let current: IStoredAuthorityPreuseOperation;
234
+ try { current = await this.readStored(operationId); }
235
+ catch { return; }
236
+ if (!['preparing', 'request_may_have_started'].includes(current.state)) return;
237
+ const marked = current.state === 'request_may_have_started';
238
+ const problem: TStoredAuthorityPreuseProblem = marked
239
+ ? this.closed ? 'authority_closing' : signal.aborted ? 'cancelled' : 'request_uncertain'
240
+ : this.closed ? 'authority_closing' : signal.aborted ? 'cancelled'
241
+ : phase === 'target' ? 'target_unavailable' : phase === 'catalog' ? 'catalog_unavailable' : 'access_unavailable';
242
+ const updateId = plugins.crypto.randomUUID();
243
+ const now = new Date(this.now()).toISOString();
244
+ try {
245
+ await this.database.changePreuseOperation(updateId, operationId, prior => ({ ...prior,
246
+ state: marked ? 'outcome_unknown' : 'not_sent', problem,
247
+ updatedAt: now, finishedAt: now, revision: prior.revision + 1, updateId }));
248
+ } catch { /* A durable request marker remains recoverable as outcome_unknown on restart. */ }
249
+ }
250
+
251
+ private async readStored(operationId: string): Promise<IStoredAuthorityPreuseOperation> {
252
+ if (!uuid(operationId)) throw new Error('Invalid preuse operation ID.');
253
+ const operation = await this.database.readOperation(operationId);
254
+ if (!operation || operation.kind !== 'preuse_openai') throw new Error('Preuse operation was not found.');
255
+ return operation;
256
+ }
257
+
258
+ public async get(operationId: string): Promise<IAuthSwitchPreuseOperation> {
259
+ return publicOperation(await this.readStored(operationId));
260
+ }
261
+
262
+ public async cancel(operationId: string): Promise<IAuthSwitchPreuseOperation> {
263
+ let current = await this.readStored(operationId);
264
+ if (!['preparing', 'request_may_have_started'].includes(current.state)) return publicOperation(current);
265
+ this.running.get(operationId)?.controller.abort(new Error('Preuse cancelled.'));
266
+ const updateId = plugins.crypto.randomUUID();
267
+ const now = new Date(this.now()).toISOString();
268
+ try {
269
+ current = await this.database.changePreuseOperation(updateId, operationId, prior => ({ ...prior,
270
+ state: prior.state === 'preparing' ? 'not_sent' : 'outcome_unknown', problem: 'cancelled',
271
+ updatedAt: now, finishedAt: now, revision: prior.revision + 1, updateId }));
272
+ } catch {
273
+ current = await this.readStored(operationId);
274
+ if (['preparing', 'request_may_have_started'].includes(current.state)) throw new Error('Preuse cancellation outcome is unknown.');
275
+ }
276
+ return publicOperation(current);
277
+ }
278
+
279
+ public async wait(operationId: string): Promise<void> {
280
+ const starting = this.starts.get(operationId);
281
+ if (starting) await starting.promise;
282
+ const running = this.running.get(operationId);
283
+ if (running) await running.task;
284
+ }
285
+
286
+ public hasActive(operationId: string): boolean {
287
+ return this.starts.has(operationId) || this.running.has(operationId);
288
+ }
289
+
290
+ public async close(): Promise<void> {
291
+ if (this.closed) return;
292
+ this.closed = true;
293
+ await Promise.allSettled([...this.starts.values()].map(item => item.promise));
294
+ for (const running of this.running.values()) running.controller.abort(new Error('Authswitch authority is closing.'));
295
+ await Promise.allSettled([...this.running.values()].map(item => item.task));
296
+ }
297
+ }
@@ -0,0 +1,118 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ AuthSwitchAuthorityAccountModel, AuthSwitchAuthorityBindingModel, AuthSwitchAuthorityClaudeHandoffModel,
4
+ AuthSwitchAuthorityClaudeHomeModel, AuthSwitchAuthorityCodexHomeModel, AuthSwitchAuthorityEnrollmentModel,
5
+ AuthSwitchAuthorityEventModel, AuthSwitchAuthorityGrantModel, AuthSwitchAuthorityHandoffModel,
6
+ AuthSwitchAuthorityMetaModel, AuthSwitchAuthorityMigrationLedgerModel, AuthSwitchAuthorityOperationModel,
7
+ AuthSwitchAuthorityUsageModel, assertStoredAuthorityAccount, assertStoredAuthorityBinding,
8
+ assertStoredAuthorityClaudeHandoff, assertStoredAuthorityClaudeHome, assertStoredAuthorityCodexHome,
9
+ assertStoredAuthorityEnrollment, assertStoredAuthorityEvent, assertStoredAuthorityGrant,
10
+ assertStoredAuthorityHandoff, assertStoredAuthorityMeta, assertStoredAuthorityMigrationLedger,
11
+ assertStoredAuthorityOperationDocument, assertStoredAuthorityUsage,
12
+ } from './classes.authoritymodels.js';
13
+
14
+ export interface IAuthorityStoredRecord { model: string; id: string; value: unknown }
15
+
16
+ interface IAuthorityModelEntry {
17
+ readonly name: string;
18
+ initialize(db: plugins.smartdata.SmartdataDb): Promise<void>;
19
+ prepare(): Promise<void>;
20
+ assert(value: unknown): void;
21
+ page(after: string | null, limit: number, session?: plugins.smartdata.SmartdataSession,
22
+ signal?: AbortSignal): Promise<IAuthorityStoredRecord[]>;
23
+ insert(record: IAuthorityStoredRecord, session?: plugins.smartdata.SmartdataSession): Promise<void>;
24
+ }
25
+
26
+ const define = <TPersisted extends { id: string },
27
+ TModel extends TPersisted & plugins.smartdata.SmartDataDbDoc<TModel, TPersisted>>(
28
+ name: string,
29
+ model: { new(): TModel; init(): Promise<void>; exact: plugins.smartdata.TExact<TModel> },
30
+ assertDocument: (value: unknown) => asserts value is TPersisted,
31
+ ): IAuthorityModelEntry => Object.freeze({
32
+ name,
33
+ initialize: async (db: plugins.smartdata.SmartdataDb) => {
34
+ plugins.smartdata.setDefaultManagerForDoc({ db }, model);
35
+ await model.init();
36
+ },
37
+ prepare: () => model.exact.prepare(),
38
+ assert: (value: unknown) => { assertDocument(value); },
39
+ page: async (after: string | null, limit: number, session?: plugins.smartdata.SmartdataSession,
40
+ signal?: AbortSignal): Promise<IAuthorityStoredRecord[]> => {
41
+ const query = {
42
+ filter: after === null ? {} : { id: { $gt: after } }, sort: { id: 1 }, limit, session, signal,
43
+ } as Parameters<typeof model.exact.findStored>[0];
44
+ const stored = await model.exact.findStored(query);
45
+ return stored.map(item => {
46
+ const value = model.exact.toPersisted(item);
47
+ return { model: name, id: value.id, value };
48
+ });
49
+ },
50
+ insert: async (record: IAuthorityStoredRecord, session?: plugins.smartdata.SmartdataSession): Promise<void> => {
51
+ assertDocument(record.value);
52
+ if (record.value.id !== record.id) throw new Error('Authority restore record identity differs from its key.');
53
+ const result = await model.exact.insert(record.value as unknown as Parameters<typeof model.exact.insert>[0],
54
+ { session });
55
+ if (result.status !== 'inserted') throw new Error('Authority restore record already exists.');
56
+ },
57
+ });
58
+
59
+ /** The sole authority collection inventory, in portable model+stable-ID order. */
60
+ export const authorityModelRegistry: readonly IAuthorityModelEntry[] = Object.freeze([
61
+ define('authswitch_authority_accounts', AuthSwitchAuthorityAccountModel, assertStoredAuthorityAccount),
62
+ define('authswitch_authority_bindings', AuthSwitchAuthorityBindingModel, assertStoredAuthorityBinding),
63
+ define('authswitch_authority_claude_handoffs', AuthSwitchAuthorityClaudeHandoffModel, assertStoredAuthorityClaudeHandoff),
64
+ define('authswitch_authority_claude_homes', AuthSwitchAuthorityClaudeHomeModel, assertStoredAuthorityClaudeHome),
65
+ define('authswitch_authority_codex_homes', AuthSwitchAuthorityCodexHomeModel, assertStoredAuthorityCodexHome),
66
+ define('authswitch_authority_enrollments', AuthSwitchAuthorityEnrollmentModel, assertStoredAuthorityEnrollment),
67
+ define('authswitch_authority_events', AuthSwitchAuthorityEventModel, assertStoredAuthorityEvent),
68
+ define('authswitch_authority_grants', AuthSwitchAuthorityGrantModel, assertStoredAuthorityGrant),
69
+ define('authswitch_authority_handoffs', AuthSwitchAuthorityHandoffModel, assertStoredAuthorityHandoff),
70
+ define('authswitch_authority_meta', AuthSwitchAuthorityMetaModel, assertStoredAuthorityMeta),
71
+ define('authswitch_authority_migration_ledger', AuthSwitchAuthorityMigrationLedgerModel, assertStoredAuthorityMigrationLedger),
72
+ define('authswitch_authority_operations', AuthSwitchAuthorityOperationModel, assertStoredAuthorityOperationDocument),
73
+ define('authswitch_authority_usage', AuthSwitchAuthorityUsageModel, assertStoredAuthorityUsage),
74
+ ]);
75
+
76
+ for (let index = 1; index < authorityModelRegistry.length; index++) {
77
+ if (authorityModelRegistry[index - 1].name >= authorityModelRegistry[index].name) {
78
+ throw new Error('Authority model registry must be unique and sorted.');
79
+ }
80
+ }
81
+
82
+ export const authorityModel = (name: string): IAuthorityModelEntry => {
83
+ const entry = authorityModelRegistry.find(item => item.name === name);
84
+ if (!entry) throw new Error('Unclassified authority model cannot be read, backed up or restored.');
85
+ return entry;
86
+ };
87
+
88
+ export const assertAuthorityStoredRecord = (record: IAuthorityStoredRecord): void => {
89
+ if (!record || typeof record.model !== 'string' || typeof record.id !== 'string' || !record.id) {
90
+ throw new Error('Invalid authority record identity.');
91
+ }
92
+ authorityModel(record.model).assert(record.value);
93
+ if (!record.value || typeof record.value !== 'object' || !('id' in record.value)
94
+ || record.value.id !== record.id) throw new Error('Invalid authority record identity.');
95
+ };
96
+
97
+ export async function* authorityStoredRecords(options: {
98
+ session?: plugins.smartdata.SmartdataSession; signal?: AbortSignal; pageSize?: number;
99
+ } = {}): AsyncGenerator<IAuthorityStoredRecord> {
100
+ const pageSize = options.pageSize ?? 64;
101
+ if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 256) {
102
+ throw new Error('Invalid authority record page size.');
103
+ }
104
+ for (const entry of authorityModelRegistry) {
105
+ let after: string | null = null;
106
+ while (true) {
107
+ options.signal?.throwIfAborted();
108
+ const page = await entry.page(after, pageSize, options.session, options.signal);
109
+ if (!page.length) break;
110
+ for (const record of page) {
111
+ assertAuthorityStoredRecord(record);
112
+ yield record;
113
+ }
114
+ after = page[page.length - 1].id;
115
+ if (page.length < pageSize) break;
116
+ }
117
+ }
118
+ }