@modelprofile.com/authswitch 9.0.0 → 9.2.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 (38) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/authority-contract.d.ts +71 -1
  3. package/dist_ts/authority-contract.js +13 -2
  4. package/dist_ts/authority-import-contract.d.ts +6 -0
  5. package/dist_ts/authority-runtime-contract.d.ts +29 -0
  6. package/dist_ts/classes.authoritybroker.d.ts +37 -15
  7. package/dist_ts/classes.authoritybroker.js +121 -35
  8. package/dist_ts/classes.authorityclient.d.ts +28 -3
  9. package/dist_ts/classes.authorityclient.js +101 -18
  10. package/dist_ts/classes.authoritydaemon.d.ts +24 -0
  11. package/dist_ts/classes.authoritydaemon.js +87 -40
  12. package/dist_ts/classes.authoritydatabase.d.ts +29 -4
  13. package/dist_ts/classes.authoritydatabase.js +98 -13
  14. package/dist_ts/classes.authorityimport.js +2 -2
  15. package/dist_ts/classes.authoritymodels.js +5 -3
  16. package/dist_ts/classes.codexmanaged.d.ts +0 -9
  17. package/dist_ts/classes.codexmanaged.js +8 -28
  18. package/dist_ts/codexcontract.d.ts +30 -0
  19. package/dist_ts/codexcontract.js +174 -0
  20. package/dist_ts/ts_migration/0004_container_setup_owner.d.ts +12 -0
  21. package/dist_ts/ts_migration/0004_container_setup_owner.js +19 -0
  22. package/dist_ts/ts_migration/index.js +3 -1
  23. package/package.json +8 -8
  24. package/readme.md +102 -25
  25. package/ts/00_commitinfo_data.ts +1 -1
  26. package/ts/authority-contract.ts +75 -4
  27. package/ts/authority-import-contract.ts +6 -0
  28. package/ts/authority-runtime-contract.ts +30 -0
  29. package/ts/classes.authoritybroker.ts +122 -36
  30. package/ts/classes.authorityclient.ts +107 -21
  31. package/ts/classes.authoritydaemon.ts +91 -33
  32. package/ts/classes.authoritydatabase.ts +100 -14
  33. package/ts/classes.authorityimport.ts +1 -1
  34. package/ts/classes.authoritymodels.ts +4 -1
  35. package/ts/classes.codexmanaged.ts +6 -26
  36. package/ts/codexcontract.ts +200 -0
  37. package/ts/ts_migration/0004_container_setup_owner.ts +19 -0
  38. package/ts/ts_migration/index.ts +2 -0
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/authswitch',
6
- version: '9.0.0',
6
+ version: '9.2.0',
7
7
  description: 'Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSw4QkFBOEI7SUFDcEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDZGQUE2RjtDQUMzRyxDQUFBIn0=
@@ -14,7 +14,15 @@ export type TAuthSwitchRefusalCode = 'authority_closing' | 'not_found' | 'accoun
14
14
  /** The authority holds this login, so the legacy path that used to move it no longer may. */
15
15
  | 'authority_holds_login'
16
16
  /** An authority is installed on this host and is not answering, so nothing may assume it holds nothing. */
17
- | 'authority_unavailable' | 'native_owner_holds_login' | 'claude_home_unregistered' | 'claude_handoff_pending' | 'claude_receipt_missing' | 'import_refusal';
17
+ | 'authority_unavailable' | 'native_owner_holds_login' | 'claude_home_unregistered' | 'claude_handoff_pending' | 'claude_receipt_missing' | 'import_refusal'
18
+ /** The Codex on this host is older than managed Codex runs, or no longer offers a surface it uses. */
19
+ | 'codex_unsupported'
20
+ /** A value the owner typed breaks a rule the instruction states, such as an account label. */
21
+ | 'invalid_input'
22
+ /** The account's login has ended and only a new device sign-in brings it back. */
23
+ | 'login_needs_reauth'
24
+ /** The login is intact, but the provider could not renew its access yet; the same request can succeed later. */
25
+ | 'access_not_fresh';
18
26
  /** The marker the importer has published since 8.1.0; it keeps its own value on the wire. */
19
27
  export declare const authSwitchImportRefusalMarker = "authswitch_import_refusal";
20
28
  export declare const authSwitchRefusalReason = "authswitch_refusal";
@@ -53,6 +61,14 @@ export declare class AuthSwitchRefusal extends Error {
53
61
  * refusal that release could answer with -- which is why it is read rather than treated as a fault.
54
62
  */
55
63
  export declare const asAuthSwitchRefusal: (error: unknown) => IAuthSwitchRefusal | null;
64
+ /** The longest account label, in UTF-16 code units. */
65
+ export declare const authSwitchAccountLabelMaxLength = 128;
66
+ /**
67
+ * The account label rule, the one the authority applies to a rename: one to `authSwitchAccountLabelMaxLength`
68
+ * characters, no space at either end, no control character. A consumer validates with this rather than
69
+ * restating it; the authority answers a label that breaks it with the `invalid_input` refusal.
70
+ */
71
+ export declare const isAuthSwitchAccountLabel: (value: unknown) => value is string;
56
72
  /** Credential-free account management contract. Safe to import in browser code. */
57
73
  export type TAuthSwitchLoginPurpose = 'openai_managed' | 'claude_host_native' | 'claude_container_setup' | 'opencode_native';
58
74
  export type TAuthSwitchLoginHealth = 'ready' | 'refreshing' | 'retry_wait' | 'needs_reauth' | 'unverified' | 'pending_handoff' | 'handoff_quarantined' | 'removed';
@@ -100,9 +116,35 @@ export interface IAuthSwitchLogin {
100
116
  export interface IAuthSwitchBinding {
101
117
  id: string;
102
118
  accountId: string;
119
+ /**
120
+ * The runtime an OpenAI (ChatGPT) login backs. `flex`, `opencode` and `claude` bindings are held and
121
+ * released by their caller over the runtime socket; `codex` is the daemon's own managed Codex. A Claude
122
+ * account never binds: it reaches Claude Code through the native switch.
123
+ */
103
124
  runtime: 'flex' | 'codex' | 'opencode' | 'claude';
104
125
  scopeId: string;
105
126
  incarnationId: string;
127
+ /**
128
+ * The authority revision at which this runtime and scope were last bound. It grows with every bind and never
129
+ * repeats for them, not even after a release and a new bind, so it names exactly one bind.
130
+ */
131
+ revision: number;
132
+ }
133
+ /**
134
+ * Which account a vendor tool's own home on this host runs on. Credential-free: the home is named by the
135
+ * hash the authority registered it under, never by its path.
136
+ *
137
+ * Only homes the authority itself switches are recorded: Claude Code homes adopted by a verified import.
138
+ * A Codex or OpenCode store that a native tool still refreshes appears as its login instead, with
139
+ * `owner: 'legacy_native'` and its `ownerTool`, because the authority does not decide what that store holds.
140
+ */
141
+ export interface IAuthSwitchNativeAssignment {
142
+ id: string;
143
+ tool: TAuthSwitchLoginOwnerTool;
144
+ accountId: string;
145
+ loginId: string;
146
+ /** `switching` while a handoff to another account is in flight; `quarantined` until it is resolved. */
147
+ state: 'ready' | 'switching' | 'quarantined';
106
148
  revision: number;
107
149
  }
108
150
  export interface IAuthSwitchSnapshot {
@@ -113,9 +155,11 @@ export interface IAuthSwitchSnapshot {
113
155
  accounts: IAuthSwitchAccount[];
114
156
  logins: IAuthSwitchLogin[];
115
157
  bindings: IAuthSwitchBinding[];
158
+ nativeAssignments: IAuthSwitchNativeAssignment[];
116
159
  nextAccountCursor: string | null;
117
160
  nextLoginCursor: string | null;
118
161
  nextBindingCursor: string | null;
162
+ nextNativeAssignmentCursor: string | null;
119
163
  }
120
164
  /** Persisted account evidence. No provider request is made while collecting diagnostics. */
121
165
  export interface IAuthSwitchDoctorAccountEvidence {
@@ -352,7 +396,13 @@ export interface IReq_AuthSwitchSnapshot extends ITypedRequest {
352
396
  accountAfter?: string;
353
397
  loginAfter?: string;
354
398
  bindingAfter?: string;
399
+ nativeAssignmentAfter?: string;
355
400
  limit?: number;
401
+ /**
402
+ * Also publish removed accounts (`removed: true`) and their removed logins (`health: 'removed'`), so a
403
+ * consumer can show them apart. Absent or false keeps the snapshot to what is live.
404
+ */
405
+ includeRemoved?: boolean;
356
406
  };
357
407
  response: {
358
408
  snapshot: IAuthSwitchSnapshot;
@@ -435,10 +485,17 @@ export interface IReq_AuthSwitchListOperations extends ITypedRequest {
435
485
  nextCursor: string | null;
436
486
  };
437
487
  }
488
+ /**
489
+ * One device sign-in. `afterRevision` and `waitMs` come together or not at all: with them the read is a long
490
+ * poll that answers once the operation's revision passes `afterRevision`, at once for a finished sign-in, or
491
+ * with the unchanged operation after `waitMs` (at most 30000).
492
+ */
438
493
  export interface IReq_AuthSwitchGetOperation extends ITypedRequest {
439
494
  method: 'authswitch.authority.operation';
440
495
  request: {
441
496
  operationId: string;
497
+ afterRevision?: number;
498
+ waitMs?: number;
442
499
  };
443
500
  response: {
444
501
  operation: IAuthSwitchOperation;
@@ -496,6 +553,19 @@ export interface IReq_AuthSwitchRenameAccount extends ITypedRequest {
496
553
  account: IAuthSwitchAccount;
497
554
  };
498
555
  }
556
+ /**
557
+ * One binding by the id `bind` returned, credential-free, or `null` when this authority holds no binding by
558
+ * that id. A backend checks that its binding still stands without reading the whole snapshot.
559
+ */
560
+ export interface IReq_AuthSwitchGetBinding extends ITypedRequest {
561
+ method: 'authswitch.authority.binding';
562
+ request: {
563
+ bindingId: string;
564
+ };
565
+ response: {
566
+ binding: IAuthSwitchBinding | null;
567
+ };
568
+ }
499
569
  export interface IReq_AuthSwitchRemoveAccount extends ITypedRequest {
500
570
  method: 'authswitch.authority.remove';
501
571
  request: {
@@ -5,7 +5,8 @@ const refusalCodes = new Set([
5
5
  'authority_closing', 'not_found', 'account_changed', 'account_busy',
6
6
  'login_unavailable', 'binding_unauthorized', 'native_owner_holds_login', 'claude_home_unregistered',
7
7
  'claude_handoff_pending', 'claude_receipt_missing', 'import_refusal',
8
- 'authority_holds_login', 'authority_unavailable',
8
+ 'authority_holds_login', 'authority_unavailable', 'codex_unsupported', 'invalid_input',
9
+ 'login_needs_reauth', 'access_not_fresh',
9
10
  ]);
10
11
  const isRefusalCode = (value) => typeof value === 'string' && refusalCodes.has(value);
11
12
  /** The importer keeps the marker it published; every other code shares the authority's own. */
@@ -52,5 +53,15 @@ export const asAuthSwitchRefusal = (error) => {
52
53
  return null;
53
54
  return { code, instruction };
54
55
  };
56
+ /** The longest account label, in UTF-16 code units. */
57
+ export const authSwitchAccountLabelMaxLength = 128;
58
+ /**
59
+ * The account label rule, the one the authority applies to a rename: one to `authSwitchAccountLabelMaxLength`
60
+ * characters, no space at either end, no control character. A consumer validates with this rather than
61
+ * restating it; the authority answers a label that breaks it with the `invalid_input` refusal.
62
+ */
63
+ export const isAuthSwitchAccountLabel = (value) => typeof value === 'string'
64
+ && value.trim() === value && value.length > 0 && value.length <= authSwitchAccountLabelMaxLength
65
+ && !/[\u0000-\u001f\u007f]/.test(value);
55
66
  /** Runtime capability is returned only to trusted backend callers; do not expose this response in a browser API. */
56
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5LWNvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5LWNvbnRyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQTZCQSw2RkFBNkY7QUFDN0YsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsMkJBQTJCLENBQUM7QUFDekUsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsb0JBQW9CLENBQUM7QUFpQjVELE1BQU0sWUFBWSxHQUF3QixJQUFJLEdBQUcsQ0FBeUI7SUFDeEUsbUJBQW1CLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLGNBQWM7SUFDbkUsbUJBQW1CLEVBQUUsc0JBQXNCLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCO0lBQ25HLHdCQUF3QixFQUFFLHdCQUF3QixFQUFFLGdCQUFnQjtJQUNwRSx1QkFBdUIsRUFBRSx1QkFBdUI7Q0FDakQsQ0FBQyxDQUFDO0FBRUgsTUFBTSxhQUFhLEdBQUcsQ0FBQyxLQUFjLEVBQW1DLEVBQUUsQ0FDeEUsT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLFlBQVksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7QUFFdkQsK0ZBQStGO0FBQy9GLE1BQU0sUUFBUSxHQUFHLENBQUMsSUFBNEIsRUFBNEIsRUFBRSxDQUMxRSxJQUFJLEtBQUssZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLDZCQUE2QixDQUFDLENBQUMsQ0FBQyx1QkFBdUIsQ0FBQztBQUV0Rjs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTyxpQkFBa0IsU0FBUSxLQUFLO0lBQzFCLElBQUksQ0FBeUI7SUFFN0MsWUFBWSxJQUE0QixFQUFFLFdBQW1CO1FBQzNELEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUNuQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUNuQixDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLElBQVcsSUFBSSxLQUE2QixPQUFPLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUM7Q0FDdkc7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLEtBQWMsRUFBNkIsRUFBRTtJQUMvRSxJQUFJLEtBQUssWUFBWSxpQkFBaUI7UUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNoRyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLENBQUMsQ0FBQyxXQUFXLElBQUksS0FBSyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDeEYsTUFBTSxJQUFJLEdBQVksS0FBSyxDQUFDLFNBQVMsQ0FBQztJQUN0QyxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxJQUFJLEtBQUssSUFBSSxJQUFJLENBQUMsQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDbEYsTUFBTSxJQUFJLEdBQVksTUFBTSxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0lBQzdELE1BQU0sV0FBVyxHQUFHLFNBQVMsSUFBSSxLQUFLLElBQUksT0FBTyxLQUFLLENBQUMsT0FBTyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ2pHLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyw2QkFBNkIsRUFBRSxDQUFDO1FBQ2xELE9BQU8sSUFBSSxLQUFLLFNBQVMsSUFBSSxJQUFJLEtBQUssZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDMUcsQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyx1QkFBdUIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNqRixPQUFPLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxDQUFDO0FBQy9CLENBQUMsQ0FBQztBQWdZRixvSEFBb0gifQ==
67
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5LWNvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5LWNvbnRyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXFDQSw2RkFBNkY7QUFDN0YsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsMkJBQTJCLENBQUM7QUFDekUsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsb0JBQW9CLENBQUM7QUFpQjVELE1BQU0sWUFBWSxHQUF3QixJQUFJLEdBQUcsQ0FBeUI7SUFDeEUsbUJBQW1CLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLGNBQWM7SUFDbkUsbUJBQW1CLEVBQUUsc0JBQXNCLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCO0lBQ25HLHdCQUF3QixFQUFFLHdCQUF3QixFQUFFLGdCQUFnQjtJQUNwRSx1QkFBdUIsRUFBRSx1QkFBdUIsRUFBRSxtQkFBbUIsRUFBRSxlQUFlO0lBQ3RGLG9CQUFvQixFQUFFLGtCQUFrQjtDQUN6QyxDQUFDLENBQUM7QUFFSCxNQUFNLGFBQWEsR0FBRyxDQUFDLEtBQWMsRUFBbUMsRUFBRSxDQUN4RSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksWUFBWSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2RCwrRkFBK0Y7QUFDL0YsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUE0QixFQUE0QixFQUFFLENBQzFFLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsNkJBQTZCLENBQUMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDO0FBRXRGOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGlCQUFrQixTQUFRLEtBQUs7SUFDMUIsSUFBSSxDQUF5QjtJQUU3QyxZQUFZLElBQTRCLEVBQUUsV0FBbUI7UUFDM0QsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ25CLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsSUFBVyxJQUFJLEtBQTZCLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztDQUN2RztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLENBQUMsS0FBYyxFQUE2QixFQUFFO0lBQy9FLElBQUksS0FBSyxZQUFZLGlCQUFpQjtRQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxXQUFXLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hHLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFdBQVcsSUFBSSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN4RixNQUFNLElBQUksR0FBWSxLQUFLLENBQUMsU0FBUyxDQUFDO0lBQ3RDLElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNsRixNQUFNLElBQUksR0FBWSxNQUFNLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDN0QsTUFBTSxXQUFXLEdBQUcsU0FBUyxJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDakcsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLDZCQUE2QixFQUFFLENBQUM7UUFDbEQsT0FBTyxJQUFJLEtBQUssU0FBUyxJQUFJLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUMxRyxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLHVCQUF1QixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ2pGLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFDL0IsQ0FBQyxDQUFDO0FBRUYsdURBQXVEO0FBQ3ZELE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLEdBQUcsQ0FBQztBQUVuRDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsQ0FBQyxLQUFjLEVBQW1CLEVBQUUsQ0FBQyxPQUFPLEtBQUssS0FBSyxRQUFRO09BQ2pHLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxLQUFLLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sSUFBSSwrQkFBK0I7T0FDN0YsQ0FBQyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7QUFrYjFDLG9IQUFvSCJ9
@@ -163,6 +163,12 @@ export declare const isAuthSwitchImportRefusal: (error: unknown) => boolean;
163
163
  export interface IAuthSwitchImportStatusEntry {
164
164
  sourceId: string;
165
165
  sourceKind: TAuthSwitchImportSourceKind;
166
+ /**
167
+ * The source location as a hash, exactly as it was submitted or inventoried. A backend that submits an
168
+ * external source finds its own record here by `sourceKind` and this hash, including after a submit whose
169
+ * outcome it never learned.
170
+ */
171
+ sourcePathHash: string;
166
172
  status: TAuthSwitchImportLedgerStatus;
167
173
  accountId: string | null;
168
174
  loginId: string | null;
@@ -26,6 +26,35 @@ export interface IReq_AuthSwitchBindAccount extends ITypedRequest {
26
26
  capability: string;
27
27
  };
28
28
  }
29
+ /**
30
+ * Backend-only release of a binding by the identity its holder bound it under, served on the MANAGEMENT
31
+ * socket beside `bind`, of which it is the inverse.
32
+ *
33
+ * It is how a holder releases a binding it no longer holds the capability for -- one an earlier run of it
34
+ * left behind -- and it needs no ready login: an account whose sign-in has ended can have its leftover
35
+ * bindings released, and then be removed, without signing in again first. It is authorized as `bind` is: a
36
+ * caller trusted with the management socket names a runtime and scope, the namespace a holder binds under
37
+ * and owns. It releases only the binding at exactly that runtime and scope, and only while it is still the
38
+ * incarnation and revision the holder read from the snapshot or `getBinding`; any other binding -- another
39
+ * scope, another runtime, or a successor bound meanwhile -- is left alone and the answer is `inactive`.
40
+ * A binding's revision names one bind and never repeats for its runtime and scope, so a successor is told
41
+ * apart even when it was bound under the same incarnation after the one read was released.
42
+ * The daemon's own managed Codex bindings are never named here. Like the runtime release, it waits for
43
+ * the access resolutions already admitted for that binding.
44
+ */
45
+ export interface IReq_AuthSwitchUnbind extends ITypedRequest {
46
+ method: 'authswitch.authority.unbind';
47
+ request: {
48
+ runtime: Exclude<IAuthSwitchBinding['runtime'], 'codex'>;
49
+ scopeId: string;
50
+ incarnationId: string;
51
+ /** The binding's `revision` as the holder read it. */
52
+ expectedRevision: number;
53
+ };
54
+ response: {
55
+ state: 'released' | 'inactive';
56
+ };
57
+ }
29
58
  /** Backend-only access operation. Refresh grants never cross this interface. */
30
59
  export interface IReq_AuthSwitchResolveAccess extends ITypedRequest {
31
60
  method: 'authswitch.authority.resolveAccess';
@@ -1,5 +1,5 @@
1
1
  import * as plugins from './plugins.js';
2
- import type { IAuthSwitchAccount, IAuthSwitchAccountEvent, IAuthSwitchBinding, IAuthSwitchOperation, IAuthSwitchSnapshot } from './authority-contract.js';
2
+ import type { IAuthSwitchAccount, IAuthSwitchAccountEvent, IAuthSwitchBinding, IAuthSwitchOperation, IAuthSwitchSnapshot, IReq_AuthSwitchSnapshot } from './authority-contract.js';
3
3
  import { AuthSwitchAuthorityDatabase } from './classes.authoritydatabase.js';
4
4
  import type { IAuthSwitchUsageContext } from './classes.authorityusage.js';
5
5
  import { type IAuthSwitchSecretCodec } from './classes.authoritysecrets.js';
@@ -29,6 +29,8 @@ export declare class AuthSwitchAuthorityBroker {
29
29
  private readonly operations;
30
30
  private readonly refreshes;
31
31
  private readonly listeners;
32
+ /** Operation long polls; woken by their operation's own changes, and all of them on close. */
33
+ private readonly operationWaiters;
32
34
  private claudeRefresh?;
33
35
  private maintenance?;
34
36
  private timer?;
@@ -42,12 +44,7 @@ export declare class AuthSwitchAuthorityBroker {
42
44
  refreshDueAccounts(): Promise<void>;
43
45
  private isDue;
44
46
  private publish;
45
- snapshot(options?: {
46
- accountAfter?: string;
47
- loginAfter?: string;
48
- bindingAfter?: string;
49
- limit?: number;
50
- }): Promise<IAuthSwitchSnapshot>;
47
+ snapshot(options?: IReq_AuthSwitchSnapshot['request']): Promise<IAuthSwitchSnapshot>;
51
48
  events(epoch: string, afterRevision: number, waitMs: number, signal?: AbortSignal): Promise<{
52
49
  epoch: string;
53
50
  revision: number;
@@ -62,7 +59,16 @@ export declare class AuthSwitchAuthorityBroker {
62
59
  beginAddOpenAi(operationId: string): Promise<IAuthSwitchOperation>;
63
60
  beginReauthOpenAi(operationId: string, accountId: string, loginId: string, purpose: 'openai_managed', beforeNew?: () => Promise<void>): Promise<IAuthSwitchOperation>;
64
61
  private beginLogin;
65
- getOperation(operationId: string): Promise<IAuthSwitchOperation>;
62
+ /**
63
+ * One device sign-in. With `wait`, the read is a long poll, the way `events` waits: it answers at once when
64
+ * the operation's revision is past `afterRevision` or the sign-in has finished, and otherwise when the
65
+ * operation next changes or `waitMs` runs out, whichever is first -- so a caller follows the prompt and
66
+ * the outcome without polling. A timed-out wait answers with the unchanged operation.
67
+ */
68
+ getOperation(operationId: string, wait?: {
69
+ afterRevision: number;
70
+ waitMs: number;
71
+ }, signal?: AbortSignal): Promise<IAuthSwitchOperation>;
66
72
  listOperations(after: string | null, limit?: number): Promise<{
67
73
  operations: IAuthSwitchOperation[];
68
74
  nextCursor: string | null;
@@ -83,19 +89,35 @@ export declare class AuthSwitchAuthorityBroker {
83
89
  binding: IAuthSwitchBinding;
84
90
  capability: string;
85
91
  }>;
92
+ getBinding(bindingId: string): Promise<IAuthSwitchBinding | null>;
86
93
  revokeBinding(bindingId: string, capability: string): Promise<boolean>;
87
94
  releaseExternalBinding(bindingId: string, capability: string): Promise<'released' | 'inactive'>;
95
+ /** Releases a caller-held binding while it still carries exactly this capability, named by its hash. */
96
+ releaseExternalCapability(bindingId: string, capabilityHash: string): Promise<'released' | 'inactive'>;
97
+ /**
98
+ * The caller-held binding a holder names by the identity it bound under, while it is still exactly the
99
+ * incarnation and revision the holder read, or `null`. Its capability hash is what a release then fences
100
+ * and compares, so a successor bound after this read is never released in its place.
101
+ */
102
+ findHeldBinding(input: {
103
+ runtime: Exclude<IAuthSwitchBinding['runtime'], 'codex'>;
104
+ scopeId: string;
105
+ incarnationId: string;
106
+ expectedRevision: number;
107
+ }): Promise<{
108
+ bindingId: string;
109
+ capabilityHash: string;
110
+ } | null>;
88
111
  resolveAccess(bindingId: string, capability: string, minValidityMs: number, rejectedGrantGeneration?: number): Promise<IAuthSwitchResolvedAccess>;
89
112
  /** Backend-only access for usage; it never creates or bypasses a runtime binding. */
90
113
  resolveUsageAccess(context: IAuthSwitchUsageContext, rejectedGrantGeneration: number | undefined, minValidityMs: number): Promise<IAuthSwitchResolvedAccess>;
91
114
  /**
92
- * The shared managed-access loop, whose refusals are deliberately still unmarked faults.
93
- *
94
- * Its two callers repair differently: a runtime holding a binding binds again, while the usage reader has
95
- * no binding at all and collapses everything into one usage problem. Naming a code here would either say
96
- * "binding" to a caller that has none, or invent a second meaning for one that does, so the decision waits
97
- * for the slice that gives the usage answer its own vocabulary. What a bound runtime can act on is decided
98
- * before this point, in the view `resolveAccess` supplies.
115
+ * The shared managed-access loop. What it decides about the login itself is a marked refusal both callers
116
+ * read the same way: `login_needs_reauth` when only a new sign-in brings the login back, `access_not_fresh`
117
+ * when the login is intact and the provider could not renew it yet. The usage reader still folds either
118
+ * into its own problem; a bound runtime shows the instruction. What only concerns a binding is decided
119
+ * before this point, in the view `resolveAccess` supplies, and a changed identity or a view that keeps
120
+ * moving stays an unmarked fault, because nobody decided it.
99
121
  */
100
122
  private resolveManagedAccess;
101
123
  private refreshAccount;