@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
package/readme.md CHANGED
@@ -92,8 +92,8 @@ digests and sealed values, while managed Codex reports durable, in-memory and ob
92
92
  state independently with fixed problem categories. Continue each independent cursor to inspect
93
93
  the complete inventory. Pages are later observations rather than one atomic global snapshot,
94
94
  and the authority metadata revision is not a change clock for every native-home record. If the
95
- management socket is unavailable, an offline caller must leave database and admission state
96
- unknown instead of starting the database to inspect it.
95
+ management socket is unavailable, an offline caller must leave the database state unknown
96
+ instead of starting the database to inspect it.
97
97
 
98
98
  The authority CLI uses that daemon surface without constructing the legacy native harness adapters:
99
99
 
@@ -110,6 +110,9 @@ authswitch account preuse operation get <operation-id> [--json]
110
110
  authswitch account preuse operation cancel <operation-id> [--json]
111
111
  authswitch account preuse operation resume <operation-id> [--json]
112
112
  authswitch authority doctor [--json]
113
+ authswitch authority import inventory [--json]
114
+ authswitch authority import status [--json]
115
+ authswitch authority import submit <source-id> --digest <source-digest> [--json]
113
116
  ```
114
117
 
115
118
  `account list --json` prints the complete, consistent credential-free authority snapshot.
@@ -164,6 +167,49 @@ If the daemon is unavailable before the first response, it fails with a fixed di
164
167
  stderr and leaves stdout empty. The older native-store commands documented below still coexist until the coordinated
165
168
  major-version migration and removal; these authority routes never use them.
166
169
 
170
+ ## The one-time account import
171
+
172
+ The three `authority import` commands move the accounts that existed before the authority into it.
173
+
174
+ `import inventory` reads this host and changes nothing: no authority record, no legacy file, no
175
+ credential. It lists every Codex stash directory, saved credential record and native Codex, Claude and
176
+ OpenCode store with the authority account it resolves to, derived from the claims the stored token already
177
+ carries -- no provider is contacted. A malformed or unrecognised entry is reported rather than skipped, so
178
+ an empty result is never mistaken for an empty store. The report carries no token, no token digest and no
179
+ source path; a source is named by a hash of its location and a digest of its bytes.
180
+
181
+ Each source is classified by who refreshes its login **after** the import.
182
+
183
+ - **adopt** -- a copy authswitch made for itself, and AGL's own records. Nothing else refreshes such a
184
+ copy, so the authority takes it over outright. It is verified by one real refresh, which rotates the
185
+ token: from that moment the legacy copy is dead. That is the point of no return for that account, and the
186
+ only fallback is one device sign-in.
187
+ - **project** -- a native Codex, Claude or OpenCode store. The vendor tool keeps refreshing its own store,
188
+ so the authority registers the account, leaves the grant with its native owner and holds no copy of the
189
+ credential. It is verified by a provider read with the access token already in the store, which rotates
190
+ nothing. Two refreshers on one rotating refresh token is the failure this avoids.
191
+
192
+ `import submit` takes the source id and the digest the owner read in the inventory. The daemon re-reads the
193
+ source itself and refuses if the digest changed, so approving one source can never import a different one;
194
+ no credential crosses the socket for a source this package can read. It refuses while an authswitch watch, a
195
+ native harness process or Codex's app-server is running, and an app-server it cannot ask counts as running.
196
+ An adopt whose refresh fails, or whose answer is lost, is never retried: the source is quarantined, its grant
197
+ is left needing re-authentication, and the command says so. A projection fails differently, because nothing
198
+ rotating was ever sent for it: a native store that could not be proven live stays a pending native owner and
199
+ is imported by submitting the same source again once its own tool has refreshed it.
200
+
201
+ `import status` reads the migration ledger, the grants and the handoffs -- never the host -- and reports, per
202
+ source, where it stands, which owner refreshes its login now, and what to do next: nothing, submit the same
203
+ source again, or sign in to that account again. Submitting a source again is how an interrupted run resumes,
204
+ and the answer comes from the same durable record the daemon decides on: a source whose sign-in was in
205
+ flight when the run died is reported as needing a device sign-in, not as resumable, because a rotating
206
+ refresh token is never sent a second time. Sources that were never submitted have no ledger row and appear
207
+ only in the inventory.
208
+
209
+ An import deletes nothing: retiring the legacy files is a separate, later step. The authority store is
210
+ deliberately not backed up, so losing it costs one device sign-in per account -- and the remote-control
211
+ enrollments and runtime bindings with it.
212
+
167
213
  `AuthSwitchClient.getUsage(accountId, loginId)` reads OpenAI managed limits or a Claude
168
214
  host login's profile and usage through the management socket. The tagged, credential-free
169
215
  reading preserves Claude's labelled five-hour, weekly and model windows, plan provenance,
@@ -176,7 +222,7 @@ both status requests and publication, and revalidate the exact credential and co
176
222
  source even when serving a cached result. Native grants are never refreshed by the daemon.
177
223
  Pending, quarantined, setup-token-without-profile-scope and other unsupported contexts
178
224
  return explicit unavailable status without a provider request. Production native home
179
- adoption still requires the separate verified migration and backup cutover.
225
+ adoption still requires the separate verified migration cutover.
180
226
 
181
227
  `AuthSwitchClient.startPreuse()` accepts one client-generated operation UUID and one exact
182
228
  OpenAI account/login/purpose. The daemon resolves an access-only grant, optionally reads the
@@ -187,8 +233,6 @@ marker remains `outcome_unknown` and is never replayed. Before-marker refusals a
187
233
  Only the request hash, selected model, timing and safe token counts are stored; the prompt,
188
234
  generated prose and credentials are absent from persistence and management responses. Account
189
235
  removal and targeted reauthentication refuse while that account has an active preuse request.
190
- Historical restore marks every unresolved preuse receipt `outcome_unknown`, including snapshots
191
- captured before the marker, because the original authority may have sent after snapshot capture.
192
236
 
193
237
  A trusted in-process daemon host can call `AuthSwitchAuthorityDaemon.startManagedCodex()`
194
238
  for a bound account and workspace. It owns a private Codex 0.155.1 app-server, passes only
@@ -226,27 +270,25 @@ The backend-only Claude native handoff journals an exact two-file write and tran
226
270
  ownership between two account grants in one SmartData transaction. An inactive Claude
227
271
  grant is refreshed by the daemon only after a durable pre-send marker; pending, native,
228
272
  and quarantined grants cannot enter that exchange. A native home and incoming grant
229
- require source-mapped migration receipts with backup metadata. The ledger field does
230
- not itself verify a SmartBucket backup, so the default daemon does not configure a
231
- Claude native home or run this path against real credentials. One-time migration and
232
- backup verification must precede production activation. Handoff receipts are queryable
233
- after a lost response. `runningEffectiveAuth: unsupported_effective_auth` means the
234
- native store changed, while an already-running Claude process's effective login cannot
235
- be proven externally; qualified live native-store switching still works.
236
-
237
- The authority database uses one versioned collection registry for startup preparation,
238
- ordered backup capture and exact restore readback. Startup rejects unknown collections,
239
- unknown metadata schemas and an incomplete restored database. Published 6.4/6.5 metadata
240
- is upgraded in place while account IDs, sealed grants, enrollments and uncertain states
241
- remain intact. A restored database keeps a durable closed-admission marker across restart;
242
- the daemon refuses provider and runtime work until a separate ownership-reconciliation
243
- step can prove it safe to open. Backup capture pins its paged records in one SmartData
244
- transaction and stages each retry anew before any SmartBucket publication. The closed
245
- restore rehearsal runs in a separate package-owned child, keeps its temporary source and
246
- record stream encrypted, restores metadata closed before any other record, then stops and
247
- reopens the same disposable database for exact stored readback. A receipt is issued only
248
- after the child has closed and its temporary root has been removed. This rehearsal does
249
- not perform a production restore or authorize a new authority instance to take ownership.
273
+ require a verified import receipt: a migration ledger row that binds the exact source
274
+ digest to one account and grant. A source counts as migrated only after such a verified
275
+ import. Nothing in this package writes that ledger yet, so the default daemon does not
276
+ configure a Claude native home or run this path against real credentials. Handoff
277
+ receipts are queryable after a lost response.
278
+ `runningEffectiveAuth: unsupported_effective_auth` means the native store changed, while
279
+ an already-running Claude process's effective login cannot be proven externally;
280
+ qualified live native-store switching still works.
281
+
282
+ The authority database admits exactly the collections of one registry and prepares each
283
+ at startup. Startup rejects unknown collections, unknown metadata schemas and an existing
284
+ store without its metadata document. Published 7.0.0 metadata is upgraded in place to the
285
+ shape 6.4/6.5 already wrote, while account IDs, sealed grants, enrollments and uncertain
286
+ states remain intact. A 7.0.0 store that a restore had marked closed is refused, not opened.
287
+ Startup also removes the backup digest that 6.4.0 to 7.0.0 stored on native handoff and
288
+ migration ledger rows, and refuses a store holding a record only the retired backup and
289
+ restore system could have written.
290
+ The store is deliberately not backed up: its refresh grants are sealed to this host's TPM,
291
+ so a lost store is recovered by one device re-login per account.
250
292
 
251
293
  ## Usage
252
294
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/authswitch',
6
- version: '7.0.0',
6
+ version: '8.1.0',
7
7
  description: 'Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status'
8
8
  }
@@ -169,8 +169,6 @@ export interface IAuthSwitchDoctorPage {
169
169
  database: {
170
170
  state: 'available';
171
171
  observedAt: string;
172
- schemaVersion: 2;
173
- admission: 'open' | 'closed_restored';
174
172
  epoch: string;
175
173
  revision: number;
176
174
  };
@@ -229,7 +227,7 @@ export interface IAuthSwitchPreuseOperation {
229
227
  result: { inputTokens: number | null; outputTokens: number | null; totalTokens: number | null } | null;
230
228
  problem: 'none' | 'target_unavailable' | 'access_unavailable' | 'catalog_unavailable'
231
229
  | 'account_busy' | 'cancelled' | 'authority_closing' | 'interrupted'
232
- | 'request_uncertain' | 'restored_history_uncertain';
230
+ | 'request_uncertain';
233
231
  createdAt: string;
234
232
  updatedAt: string;
235
233
  finishedAt: string | null;
@@ -0,0 +1,216 @@
1
+ import type { ITypedRequest } from '@api.global/typedrequest-interfaces';
2
+
3
+ /**
4
+ * The one-time account import: reading the legacy stores that existed before the authority, and the
5
+ * credential-free report an owner reads before anything is imported.
6
+ *
7
+ * Importing a refresh token is a transfer of ownership, not a copy. The provider rotates a refresh token on
8
+ * use, so the moment one holder refreshes, every other copy of that token is dead. This contract therefore
9
+ * separates the classes of legacy source by who refreshes them after the import, and names the proof each
10
+ * class can offer without rotating anything.
11
+ */
12
+
13
+ /** Mirrors the migration ledger's source kinds, which are the only sources a ledger row can describe. */
14
+ export type TAuthSwitchImportSourceKind = 'authswitch_stash' | 'authswitch_backup' | 'agl_flex'
15
+ | 'codex_native' | 'opencode_native' | 'claude_native';
16
+
17
+ /**
18
+ * The ledger statuses a source can already hold when an inventory runs.
19
+ *
20
+ * - `prepared`: the source is named in the authority and nothing has been sent for it yet.
21
+ * - `pending_native_owner`: the account is registered and its native tool keeps the refresher, but the
22
+ * store could not be proven live. Nothing rotating was ever sent, so it is proven by submitting the
23
+ * source again once that tool has refreshed its own store.
24
+ * - `verified` and `complete`: the import settled; `complete` is the tombstone for that source.
25
+ * - `quarantined`: a rotating refresh may already have been consumed for this source, so it is never
26
+ * submitted again and its account is repaired by a device sign-in.
27
+ */
28
+ export type TAuthSwitchImportLedgerStatus = 'pending_native_owner' | 'prepared' | 'verified' | 'complete'
29
+ | 'quarantined';
30
+
31
+ /**
32
+ * What the ownership rule allows for a source.
33
+ *
34
+ * - `import`: the authority becomes the single refresher. Nothing else owns this copy, so taking it over
35
+ * strands no other holder.
36
+ * - `project`: a native tool keeps refreshing its own store; the authority registers the account and holds
37
+ * the grant as a projection it must never refresh. Two refreshers on one refresh token is the failure this
38
+ * avoids.
39
+ * - `refuse`: the source cannot become an authority grant at all, and the owner signs in again instead.
40
+ */
41
+ export type TAuthSwitchImportTreatment = 'import' | 'project' | 'refuse';
42
+
43
+ /**
44
+ * What can make this source's ledger row `verified`.
45
+ *
46
+ * - `rotating_refresh`: one real refresh through the authority. It proves the refresh token was live and
47
+ * makes the authority its holder in the same step -- and it is the point of no return, because the legacy
48
+ * copy is stale from that moment.
49
+ * - `access_token_read`: a provider read with the stored access token alone, matched against the identity
50
+ * derived locally from the same source bytes. It rotates nothing, so a native refresher keeps its grant.
51
+ * It is available only while that access token is still valid.
52
+ * - `none`: no proof exists for this source; it cannot be verified and cannot be imported.
53
+ */
54
+ export type TAuthSwitchImportProof = 'rotating_refresh' | 'access_token_read' | 'none';
55
+
56
+ /**
57
+ * One legacy source as the inventory found it. Credential-free by construction: identity claims and a digest
58
+ * of the source bytes, never a token, a token digest, or the source path.
59
+ */
60
+ export interface IAuthSwitchImportSource {
61
+ /** The migration ledger row this source would use. Derived from its kind and path, so it is stable. */
62
+ id: string;
63
+ sourceKind: TAuthSwitchImportSourceKind;
64
+ /** The source location as a hash. The ledger never stores a path and this DTO never exposes one. */
65
+ sourcePathHash: string;
66
+ /** Binds a later import to exactly these bytes. Changes whenever the source changes. */
67
+ sourceDigest: string;
68
+ treatment: TAuthSwitchImportTreatment;
69
+ proof: TAuthSwitchImportProof;
70
+ /** The authority account this source resolves to, derived locally from its identity claims. */
71
+ accountId: string | null;
72
+ providerId: string | null;
73
+ email: string | null;
74
+ plan: string | null;
75
+ label: string | null;
76
+ /** Expiry claim of the stored access token. It decides whether a non-rotating proof is available now. */
77
+ accessExpiresAt: string | null;
78
+ /** Remote-control enrollments carried by this source; `null` when they could not be counted. */
79
+ enrollmentCount: number | null;
80
+ /** The ledger row for this source, when one already exists. */
81
+ ledgerStatus: TAuthSwitchImportLedgerStatus | null;
82
+ /** Why this source cannot be submitted as it stands. An empty list means it can. */
83
+ problems: string[];
84
+ }
85
+
86
+ /** The complete credential-free report. `notice` is what the owner must know before importing anything. */
87
+ export interface IAuthSwitchImportInventory {
88
+ generatedAt: string;
89
+ /** Identifies the credential locations this inventory read, as `authSwitchEnvironmentId` computes them. */
90
+ environmentId: string;
91
+ notice: string[];
92
+ sources: IAuthSwitchImportSource[];
93
+ /** Locations that could not be read at all, so an empty result never looks like an empty store. */
94
+ problems: string[];
95
+ }
96
+
97
+ /** Management-only. Reads legacy stores and the ledger; changes nothing, and carries no credential. */
98
+ export interface IReq_AuthSwitchImportInventory extends ITypedRequest {
99
+ method: 'authswitch.authority.import.inventory';
100
+ request: Record<string, never>;
101
+ response: { inventory: IAuthSwitchImportInventory };
102
+ }
103
+
104
+ /** A ChatGPT login held by a store outside this package, submitted by the backend that owns its format. */
105
+ export interface IAuthSwitchImportOpenAiCredential {
106
+ providerId: 'openai';
107
+ accessToken: string;
108
+ refreshToken: string;
109
+ idToken: string;
110
+ }
111
+
112
+ /**
113
+ * What is being imported.
114
+ *
115
+ * A source this daemon can read itself is named only by its id: the daemon re-runs its readers, resolves the
116
+ * location from the hash and re-checks the digest, so no credential crosses the socket and no caller can
117
+ * substitute bytes the owner never approved. Only a store this package cannot read -- AGL's sealed Flex
118
+ * records -- submits material, and that is the whole reason submit lives on the backend-only runtime socket.
119
+ */
120
+ export type TAuthSwitchImportSubmission =
121
+ | { kind: 'local'; sourceId: string; sourceDigest: string }
122
+ | { kind: 'external'; sourceKind: 'agl_flex'; sourcePathHash: string; sourceDigest: string;
123
+ credential: IAuthSwitchImportOpenAiCredential };
124
+
125
+ /** The outcome of one source. `quarantined` always names what to do instead, never a silent failure. */
126
+ export interface IAuthSwitchImportResult {
127
+ sourceId: string;
128
+ sourceKind: TAuthSwitchImportSourceKind;
129
+ treatment: TAuthSwitchImportTreatment;
130
+ status: TAuthSwitchImportLedgerStatus;
131
+ accountId: string | null;
132
+ loginId: string | null;
133
+ problems: string[];
134
+ }
135
+
136
+ /**
137
+ * What the owner does next with one source.
138
+ *
139
+ * - `none`: the import settled; nothing is outstanding.
140
+ * - `resume`: submit the same source again. Nothing rotating has been sent for this source, so a second
141
+ * attempt is safe: an interrupted adopt continues from its durable record, and a native store that could
142
+ * not be proven live is proven the moment its own tool has refreshed it.
143
+ * - `device_login`: this source is finished as a source. Its account needs a fresh device sign-in, because a
144
+ * rotating refresh token whose outcome is unknown is never sent a second time.
145
+ */
146
+ export type TAuthSwitchImportAction = 'none' | 'resume' | 'device_login';
147
+
148
+ /**
149
+ * Marks an error answer whose text is an instruction for the owner, not a report of a fault.
150
+ *
151
+ * The daemon sets it on the refusals the importer authors and on nothing else. Every other failure answers
152
+ * with the transport's own sanitised text, which a caller must never present as if it said what to do next --
153
+ * for a submit in particular, an unmarked failure means the outcome is unknown and the source must be read
154
+ * with `authswitch.authority.import.status` rather than submitted again.
155
+ */
156
+ export const authSwitchImportRefusalReason = 'authswitch_import_refusal';
157
+
158
+ /** True when the daemon answered with an importer refusal, so `error.message` is the instruction to show. */
159
+ export const isAuthSwitchImportRefusal = (error: unknown): boolean => {
160
+ if (typeof error !== 'object' || error === null || !('errorData' in error)) return false;
161
+ const data = error.errorData;
162
+ return typeof data === 'object' && data !== null && 'reason' in data
163
+ && data.reason === authSwitchImportRefusalReason;
164
+ };
165
+
166
+ /**
167
+ * Where one source stands, read from the migration ledger, the grant it produced and its handoff.
168
+ *
169
+ * Only sources the authority has been told about appear here. A source that was never submitted has no
170
+ * ledger row; `authority import inventory` is the read that looks at the host and lists those.
171
+ */
172
+ export interface IAuthSwitchImportStatusEntry {
173
+ sourceId: string;
174
+ sourceKind: TAuthSwitchImportSourceKind;
175
+ status: TAuthSwitchImportLedgerStatus;
176
+ accountId: string | null;
177
+ loginId: string | null;
178
+ /**
179
+ * Who refreshes that login now, or `null` when the ledger row names no grant yet. `none` is the absence of
180
+ * a refresher: the grant needs a device sign-in before anything can use it again.
181
+ */
182
+ owner: 'daemon' | 'claude_native' | 'legacy_native' | 'none' | null;
183
+ action: TAuthSwitchImportAction;
184
+ statusObservedAt: string;
185
+ }
186
+
187
+ export interface IAuthSwitchImportStatus {
188
+ generatedAt: string;
189
+ entries: IAuthSwitchImportStatusEntry[];
190
+ nextCursor: string | null;
191
+ }
192
+
193
+ /** Management-only. Reads durable records only; it opens no legacy store and carries no credential. */
194
+ export interface IReq_AuthSwitchImportStatus extends ITypedRequest {
195
+ method: 'authswitch.authority.import.status';
196
+ request: { after?: string; limit?: number };
197
+ response: { status: IAuthSwitchImportStatus };
198
+ }
199
+
200
+ /**
201
+ * Backend-only. One source, one ownership decision.
202
+ *
203
+ * Both flags are required and both must be `true`. `callerQuiescent` is the caller's own assertion, because
204
+ * this daemon can observe a watch, a native process and Codex's app-server but cannot observe a host that
205
+ * uses the legacy library in its own process. `acknowledgeRunOrder` is the owner's acknowledgement that an
206
+ * imported source's legacy copy is dead and that the everyday commands must already run on the authority.
207
+ */
208
+ export interface IReq_AuthSwitchImportSubmit extends ITypedRequest {
209
+ method: 'authswitch.authority.import.submit';
210
+ request: {
211
+ submission: TAuthSwitchImportSubmission;
212
+ callerQuiescent: true;
213
+ acknowledgeRunOrder: true;
214
+ };
215
+ response: { result: IAuthSwitchImportResult };
216
+ }
@@ -131,7 +131,6 @@ export class AuthSwitchAuthorityBroker {
131
131
  public notifyChanged(): void { this.publish(); }
132
132
 
133
133
  public async start(): Promise<void> {
134
- await this.database.assertOpenAdmission();
135
134
  const probe = plugins.crypto.randomBytes(32);
136
135
  const probeId = idHash('authswitch-custody-probe-v1');
137
136
  try {
@@ -199,7 +198,6 @@ export class AuthSwitchAuthorityBroker {
199
198
 
200
199
  public async refreshDueAccounts(): Promise<void> {
201
200
  if (this.closed) return;
202
- await this.database.assertOpenAdmission();
203
201
  for (const state of ['ready', 'retry_wait'] as const) {
204
202
  let after: string | null = null;
205
203
  while (true) {
@@ -664,7 +662,6 @@ export class AuthSwitchAuthorityBroker {
664
662
  private async resolveManagedAccess(readView: () => Promise<{
665
663
  account: IStoredAuthorityAccount; grant: IStoredAuthorityGrant;
666
664
  }>, minValidityMs: number, rejectedGrantGeneration?: number): Promise<IAuthSwitchResolvedAccess> {
667
- await this.database.assertOpenAdmission();
668
665
  for (let attempt = 0; attempt < 4; attempt++) {
669
666
  const { account, grant } = await readView();
670
667
  if (account.removed || grant.accountId !== account.id || grant.id !== account.primaryGrantId
@@ -732,7 +729,6 @@ export class AuthSwitchAuthorityBroker {
732
729
 
733
730
  private async performRefresh(accountId: string, minValidityMs: number,
734
731
  rejectedGrantGeneration?: number): Promise<void> {
735
- await this.database.assertOpenAdmission();
736
732
  const initial = await this.database.readAccountAndPrimaryGrant(accountId);
737
733
  const initialGrant = initial.grant;
738
734
  if (!initial.account || initial.account.removed || !initialGrant || initialGrant.purpose !== 'openai_managed'