@modelprofile.com/authswitch 8.2.0 → 9.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 (64) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/authority-contract.d.ts +84 -1
  3. package/dist_ts/authority-contract.js +14 -2
  4. package/dist_ts/authority-import-contract.d.ts +6 -0
  5. package/dist_ts/authority-paths.d.ts +37 -0
  6. package/dist_ts/authority-paths.js +46 -0
  7. package/dist_ts/authority-runtime-contract.d.ts +11 -1
  8. package/dist_ts/classes.authoritybroker.d.ts +23 -8
  9. package/dist_ts/classes.authoritybroker.js +155 -32
  10. package/dist_ts/classes.authorityclient.d.ts +22 -2
  11. package/dist_ts/classes.authorityclient.js +98 -13
  12. package/dist_ts/classes.authoritydaemon.d.ts +21 -3
  13. package/dist_ts/classes.authoritydaemon.js +77 -32
  14. package/dist_ts/classes.authoritydatabase.d.ts +21 -3
  15. package/dist_ts/classes.authoritydatabase.js +98 -12
  16. package/dist_ts/classes.authorityimport.d.ts +16 -4
  17. package/dist_ts/classes.authorityimport.js +75 -24
  18. package/dist_ts/classes.authoritymodels.js +5 -3
  19. package/dist_ts/classes.authoritypreuse.js +8 -3
  20. package/dist_ts/classes.authorityservice.d.ts +10 -11
  21. package/dist_ts/classes.authorityservice.js +14 -23
  22. package/dist_ts/classes.cli.d.ts +10 -2
  23. package/dist_ts/classes.cli.js +12 -4
  24. package/dist_ts/classes.codexmanaged.d.ts +0 -1
  25. package/dist_ts/classes.codexmanaged.js +13 -26
  26. package/dist_ts/classes.legacyfence.d.ts +53 -0
  27. package/dist_ts/classes.legacyfence.js +189 -0
  28. package/dist_ts/classes.operations.d.ts +15 -3
  29. package/dist_ts/classes.operations.js +22 -4
  30. package/dist_ts/classes.service.d.ts +21 -2
  31. package/dist_ts/classes.service.js +35 -8
  32. package/dist_ts/classes.tui.d.ts +2 -1
  33. package/dist_ts/classes.tui.js +3 -2
  34. package/dist_ts/codexcontract.d.ts +30 -0
  35. package/dist_ts/codexcontract.js +174 -0
  36. package/dist_ts/ts_migration/0004_container_setup_owner.d.ts +12 -0
  37. package/dist_ts/ts_migration/0004_container_setup_owner.js +19 -0
  38. package/dist_ts/ts_migration/index.js +3 -1
  39. package/dist_ts/ts_migration/legacysources/authswitchstores.js +5 -2
  40. package/package.json +11 -11
  41. package/readme.md +194 -24
  42. package/ts/00_commitinfo_data.ts +1 -1
  43. package/ts/authority-contract.ts +90 -4
  44. package/ts/authority-import-contract.ts +6 -0
  45. package/ts/authority-paths.ts +69 -0
  46. package/ts/authority-runtime-contract.ts +11 -1
  47. package/ts/classes.authoritybroker.ts +153 -33
  48. package/ts/classes.authorityclient.ts +102 -15
  49. package/ts/classes.authoritydaemon.ts +89 -27
  50. package/ts/classes.authoritydatabase.ts +98 -12
  51. package/ts/classes.authorityimport.ts +102 -25
  52. package/ts/classes.authoritymodels.ts +4 -1
  53. package/ts/classes.authoritypreuse.ts +7 -1
  54. package/ts/classes.authorityservice.ts +15 -30
  55. package/ts/classes.cli.ts +14 -3
  56. package/ts/classes.codexmanaged.ts +10 -19
  57. package/ts/classes.legacyfence.ts +219 -0
  58. package/ts/classes.operations.ts +22 -3
  59. package/ts/classes.service.ts +45 -8
  60. package/ts/classes.tui.ts +3 -1
  61. package/ts/codexcontract.ts +200 -0
  62. package/ts/ts_migration/0004_container_setup_owner.ts +19 -0
  63. package/ts/ts_migration/index.ts +2 -0
  64. package/ts/ts_migration/legacysources/authswitchstores.ts +4 -1
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/authswitch',
6
- version: '8.2.0',
6
+ version: '9.1.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=
@@ -8,7 +8,21 @@ export type { IAuthSwitchUsageSnapshot } from './classes.authorityusage.js';
8
8
  * the owner does instead. Everything else stays a fault whose text the transport replaces, and a caller
9
9
  * must never present a fault as if it said what to do next.
10
10
  */
11
- export type TAuthSwitchRefusalCode = 'authority_closing' | 'not_found' | 'account_changed' | 'account_busy' | 'login_unavailable' | 'native_owner_holds_login' | 'claude_home_unregistered' | 'claude_handoff_pending' | 'claude_receipt_missing' | 'import_refusal';
11
+ export type TAuthSwitchRefusalCode = 'authority_closing' | 'not_found' | 'account_changed' | 'account_busy' | 'login_unavailable'
12
+ /** A runtime capability that is missing, wrong or superseded: deliberately one answer, and one repair. */
13
+ | 'binding_unauthorized'
14
+ /** The authority holds this login, so the legacy path that used to move it no longer may. */
15
+ | 'authority_holds_login'
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'
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';
12
26
  /** The marker the importer has published since 8.1.0; it keeps its own value on the wire. */
13
27
  export declare const authSwitchImportRefusalMarker = "authswitch_import_refusal";
14
28
  export declare const authSwitchRefusalReason = "authswitch_refusal";
@@ -47,6 +61,14 @@ export declare class AuthSwitchRefusal extends Error {
47
61
  * refusal that release could answer with -- which is why it is read rather than treated as a fault.
48
62
  */
49
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;
50
72
  /** Credential-free account management contract. Safe to import in browser code. */
51
73
  export type TAuthSwitchLoginPurpose = 'openai_managed' | 'claude_host_native' | 'claude_container_setup' | 'opencode_native';
52
74
  export type TAuthSwitchLoginHealth = 'ready' | 'refreshing' | 'retry_wait' | 'needs_reauth' | 'unverified' | 'pending_handoff' | 'handoff_quarantined' | 'removed';
@@ -60,6 +82,15 @@ export interface IAuthSwitchAccount {
60
82
  revision: number;
61
83
  statusObservedAt: string;
62
84
  }
85
+ /**
86
+ * Which tool refreshes a natively owned login, named rather than inferred.
87
+ *
88
+ * `owner` says that a native tool holds the login; this says which one, so a consumer presents "Claude Code"
89
+ * or "Codex" without deriving it from `purpose` -- a derivation that would be wrong the moment two purposes
90
+ * share a tool, and that belongs to the side that decides ownership in the first place. It is `null` exactly
91
+ * when no native tool refreshes the login: the authority does, or nothing does.
92
+ */
93
+ export type TAuthSwitchLoginOwnerTool = 'claude_code' | 'codex' | 'opencode';
63
94
  /** One independently owned login. Native ownership does not imply observed health. */
64
95
  export interface IAuthSwitchLogin {
65
96
  id: string;
@@ -67,6 +98,8 @@ export interface IAuthSwitchLogin {
67
98
  providerId: string;
68
99
  purpose: TAuthSwitchLoginPurpose;
69
100
  owner: 'daemon' | 'claude_native' | 'legacy_native' | 'none';
101
+ /** The native tool that refreshes this login, or null when the authority or nobody does. */
102
+ ownerTool: TAuthSwitchLoginOwnerTool | null;
70
103
  health: TAuthSwitchLoginHealth;
71
104
  problem: 'none' | 'provider_unavailable' | 'exchange_uncertain' | 'provider_rejected' | 'native_owner';
72
105
  grantGeneration: number;
@@ -83,11 +116,33 @@ export interface IAuthSwitchLogin {
83
116
  export interface IAuthSwitchBinding {
84
117
  id: string;
85
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
+ */
86
124
  runtime: 'flex' | 'codex' | 'opencode' | 'claude';
87
125
  scopeId: string;
88
126
  incarnationId: string;
89
127
  revision: number;
90
128
  }
129
+ /**
130
+ * Which account a vendor tool's own home on this host runs on. Credential-free: the home is named by the
131
+ * hash the authority registered it under, never by its path.
132
+ *
133
+ * Only homes the authority itself switches are recorded: Claude Code homes adopted by a verified import.
134
+ * A Codex or OpenCode store that a native tool still refreshes appears as its login instead, with
135
+ * `owner: 'legacy_native'` and its `ownerTool`, because the authority does not decide what that store holds.
136
+ */
137
+ export interface IAuthSwitchNativeAssignment {
138
+ id: string;
139
+ tool: TAuthSwitchLoginOwnerTool;
140
+ accountId: string;
141
+ loginId: string;
142
+ /** `switching` while a handoff to another account is in flight; `quarantined` until it is resolved. */
143
+ state: 'ready' | 'switching' | 'quarantined';
144
+ revision: number;
145
+ }
91
146
  export interface IAuthSwitchSnapshot {
92
147
  schemaVersion: 2;
93
148
  epoch: string;
@@ -96,9 +151,11 @@ export interface IAuthSwitchSnapshot {
96
151
  accounts: IAuthSwitchAccount[];
97
152
  logins: IAuthSwitchLogin[];
98
153
  bindings: IAuthSwitchBinding[];
154
+ nativeAssignments: IAuthSwitchNativeAssignment[];
99
155
  nextAccountCursor: string | null;
100
156
  nextLoginCursor: string | null;
101
157
  nextBindingCursor: string | null;
158
+ nextNativeAssignmentCursor: string | null;
102
159
  }
103
160
  /** Persisted account evidence. No provider request is made while collecting diagnostics. */
104
161
  export interface IAuthSwitchDoctorAccountEvidence {
@@ -335,7 +392,13 @@ export interface IReq_AuthSwitchSnapshot extends ITypedRequest {
335
392
  accountAfter?: string;
336
393
  loginAfter?: string;
337
394
  bindingAfter?: string;
395
+ nativeAssignmentAfter?: string;
338
396
  limit?: number;
397
+ /**
398
+ * Also publish removed accounts (`removed: true`) and their removed logins (`health: 'removed'`), so a
399
+ * consumer can show them apart. Absent or false keeps the snapshot to what is live.
400
+ */
401
+ includeRemoved?: boolean;
339
402
  };
340
403
  response: {
341
404
  snapshot: IAuthSwitchSnapshot;
@@ -418,10 +481,17 @@ export interface IReq_AuthSwitchListOperations extends ITypedRequest {
418
481
  nextCursor: string | null;
419
482
  };
420
483
  }
484
+ /**
485
+ * One device sign-in. `afterRevision` and `waitMs` come together or not at all: with them the read is a long
486
+ * poll that answers once the operation's revision passes `afterRevision`, at once for a finished sign-in, or
487
+ * with the unchanged operation after `waitMs` (at most 30000).
488
+ */
421
489
  export interface IReq_AuthSwitchGetOperation extends ITypedRequest {
422
490
  method: 'authswitch.authority.operation';
423
491
  request: {
424
492
  operationId: string;
493
+ afterRevision?: number;
494
+ waitMs?: number;
425
495
  };
426
496
  response: {
427
497
  operation: IAuthSwitchOperation;
@@ -479,6 +549,19 @@ export interface IReq_AuthSwitchRenameAccount extends ITypedRequest {
479
549
  account: IAuthSwitchAccount;
480
550
  };
481
551
  }
552
+ /**
553
+ * One binding by the id `bind` returned, credential-free, or `null` when this authority holds no binding by
554
+ * that id. A backend checks that its binding still stands without reading the whole snapshot.
555
+ */
556
+ export interface IReq_AuthSwitchGetBinding extends ITypedRequest {
557
+ method: 'authswitch.authority.binding';
558
+ request: {
559
+ bindingId: string;
560
+ };
561
+ response: {
562
+ binding: IAuthSwitchBinding | null;
563
+ };
564
+ }
482
565
  export interface IReq_AuthSwitchRemoveAccount extends ITypedRequest {
483
566
  method: 'authswitch.authority.remove';
484
567
  request: {
@@ -3,8 +3,10 @@ export const authSwitchImportRefusalMarker = 'authswitch_import_refusal';
3
3
  export const authSwitchRefusalReason = 'authswitch_refusal';
4
4
  const refusalCodes = new Set([
5
5
  'authority_closing', 'not_found', 'account_changed', 'account_busy',
6
- 'login_unavailable', 'native_owner_holds_login', 'claude_home_unregistered',
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', 'codex_unsupported', 'invalid_input',
9
+ 'login_needs_reauth', 'access_not_fresh',
8
10
  ]);
9
11
  const isRefusalCode = (value) => typeof value === 'string' && refusalCodes.has(value);
10
12
  /** The importer keeps the marker it published; every other code shares the authority's own. */
@@ -51,5 +53,15 @@ export const asAuthSwitchRefusal = (error) => {
51
53
  return null;
52
54
  return { code, instruction };
53
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);
54
66
  /** Runtime capability is returned only to trusted backend callers; do not expose this response in a browser API. */
55
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5LWNvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5LWNvbnRyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXVCQSw2RkFBNkY7QUFDN0YsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsMkJBQTJCLENBQUM7QUFDekUsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsb0JBQW9CLENBQUM7QUFpQjVELE1BQU0sWUFBWSxHQUF3QixJQUFJLEdBQUcsQ0FBeUI7SUFDeEUsbUJBQW1CLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLGNBQWM7SUFDbkUsbUJBQW1CLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCO0lBQzNFLHdCQUF3QixFQUFFLHdCQUF3QixFQUFFLGdCQUFnQjtDQUNyRSxDQUFDLENBQUM7QUFFSCxNQUFNLGFBQWEsR0FBRyxDQUFDLEtBQWMsRUFBbUMsRUFBRSxDQUN4RSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksWUFBWSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2RCwrRkFBK0Y7QUFDL0YsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUE0QixFQUE0QixFQUFFLENBQzFFLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsNkJBQTZCLENBQUMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDO0FBRXRGOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGlCQUFrQixTQUFRLEtBQUs7SUFDMUIsSUFBSSxDQUF5QjtJQUU3QyxZQUFZLElBQTRCLEVBQUUsV0FBbUI7UUFDM0QsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ25CLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsSUFBVyxJQUFJLEtBQTZCLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztDQUN2RztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLENBQUMsS0FBYyxFQUE2QixFQUFFO0lBQy9FLElBQUksS0FBSyxZQUFZLGlCQUFpQjtRQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxXQUFXLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hHLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFdBQVcsSUFBSSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN4RixNQUFNLElBQUksR0FBWSxLQUFLLENBQUMsU0FBUyxDQUFDO0lBQ3RDLElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNsRixNQUFNLElBQUksR0FBWSxNQUFNLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDN0QsTUFBTSxXQUFXLEdBQUcsU0FBUyxJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDakcsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLDZCQUE2QixFQUFFLENBQUM7UUFDbEQsT0FBTyxJQUFJLEtBQUssU0FBUyxJQUFJLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUMxRyxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLHVCQUF1QixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ2pGLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFDL0IsQ0FBQyxDQUFDO0FBb1hGLG9IQUFvSCJ9
67
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5LWNvbnRyYWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5LWNvbnRyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXFDQSw2RkFBNkY7QUFDN0YsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsMkJBQTJCLENBQUM7QUFDekUsTUFBTSxDQUFDLE1BQU0sdUJBQXVCLEdBQUcsb0JBQW9CLENBQUM7QUFpQjVELE1BQU0sWUFBWSxHQUF3QixJQUFJLEdBQUcsQ0FBeUI7SUFDeEUsbUJBQW1CLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLGNBQWM7SUFDbkUsbUJBQW1CLEVBQUUsc0JBQXNCLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCO0lBQ25HLHdCQUF3QixFQUFFLHdCQUF3QixFQUFFLGdCQUFnQjtJQUNwRSx1QkFBdUIsRUFBRSx1QkFBdUIsRUFBRSxtQkFBbUIsRUFBRSxlQUFlO0lBQ3RGLG9CQUFvQixFQUFFLGtCQUFrQjtDQUN6QyxDQUFDLENBQUM7QUFFSCxNQUFNLGFBQWEsR0FBRyxDQUFDLEtBQWMsRUFBbUMsRUFBRSxDQUN4RSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksWUFBWSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2RCwrRkFBK0Y7QUFDL0YsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUE0QixFQUE0QixFQUFFLENBQzFFLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsNkJBQTZCLENBQUMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDO0FBRXRGOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGlCQUFrQixTQUFRLEtBQUs7SUFDMUIsSUFBSSxDQUF5QjtJQUU3QyxZQUFZLElBQTRCLEVBQUUsV0FBbUI7UUFDM0QsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ25CLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsSUFBVyxJQUFJLEtBQTZCLE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztDQUN2RztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLENBQUMsS0FBYyxFQUE2QixFQUFFO0lBQy9FLElBQUksS0FBSyxZQUFZLGlCQUFpQjtRQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxXQUFXLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hHLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFdBQVcsSUFBSSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN4RixNQUFNLElBQUksR0FBWSxLQUFLLENBQUMsU0FBUyxDQUFDO0lBQ3RDLElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNsRixNQUFNLElBQUksR0FBWSxNQUFNLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDN0QsTUFBTSxXQUFXLEdBQUcsU0FBUyxJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDakcsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLDZCQUE2QixFQUFFLENBQUM7UUFDbEQsT0FBTyxJQUFJLEtBQUssU0FBUyxJQUFJLElBQUksS0FBSyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUMxRyxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLHVCQUF1QixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ2pGLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFDL0IsQ0FBQyxDQUFDO0FBRUYsdURBQXVEO0FBQ3ZELE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLEdBQUcsQ0FBQztBQUVuRDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsQ0FBQyxLQUFjLEVBQW1CLEVBQUUsQ0FBQyxPQUFPLEtBQUssS0FBSyxRQUFRO09BQ2pHLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxLQUFLLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sSUFBSSwrQkFBK0I7T0FDN0YsQ0FBQyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7QUE4YTFDLG9IQUFvSCJ9
@@ -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;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Where this user's authority lives: its sockets, its store and the service unit that runs it.
3
+ *
4
+ * These rules sit in a leaf module because two sides read them. `AuthSwitchAuthorityService` installs and
5
+ * runs the daemon there, and the legacy fence (`./classes.legacyfence`) asks the same socket and looks for
6
+ * the same unit -- without importing the daemon, whose module graph reaches back to the legacy operations
7
+ * the fence guards.
8
+ */
9
+ export interface IAuthSwitchAuthorityPaths {
10
+ runtimeDirectory: string;
11
+ dataDirectory: string;
12
+ databaseSocketPath: string;
13
+ authoritySocketPath: string;
14
+ runtimeSocketPath: string;
15
+ runtimeSocketDirectory: string;
16
+ }
17
+ /** The authority's sockets. */
18
+ export type TAuthSwitchAuthoritySocketPaths = Omit<IAuthSwitchAuthorityPaths, 'dataDirectory'>;
19
+ /**
20
+ * The sockets under one runtime directory, derived from it alone.
21
+ *
22
+ * The legacy fence asks the socket a caller states and must not depend on anything this process holds --
23
+ * its home or its `XDG_DATA_HOME`, which only the store's directory needs.
24
+ */
25
+ export declare const authSwitchAuthoritySocketPaths: (runtimeDirectory: string | undefined) => TAuthSwitchAuthoritySocketPaths;
26
+ /** Canonical per-user paths. An explicit runtime directory is also usable by a trusted container host. */
27
+ export declare const resolveAuthSwitchAuthorityPaths: (runtimeDirectory?: string | undefined) => IAuthSwitchAuthorityPaths;
28
+ /** The user service `authswitch authority service install` writes. */
29
+ export declare const authSwitchAuthorityUnitName = "authswitch-authority.service";
30
+ /**
31
+ * The directory that unit is installed into, from a stated environment and home directory.
32
+ *
33
+ * It is the rule smartdaemon applies to a user unit when it is given no directory --
34
+ * `$XDG_DATA_HOME/systemd/user`, default `~/.local/share/systemd/user` -- stated here so the installer passes
35
+ * it explicitly and the fence derives it from the locations its caller states rather than from this process.
36
+ */
37
+ export declare const authSwitchAuthorityUnitDirectory: (env: NodeJS.ProcessEnv, homeDirectory: string) => string;
@@ -0,0 +1,46 @@
1
+ import * as plugins from './plugins.js';
2
+ /**
3
+ * The sockets under one runtime directory, derived from it alone.
4
+ *
5
+ * The legacy fence asks the socket a caller states and must not depend on anything this process holds --
6
+ * its home or its `XDG_DATA_HOME`, which only the store's directory needs.
7
+ */
8
+ export const authSwitchAuthoritySocketPaths = (runtimeDirectory) => {
9
+ if (!runtimeDirectory || !plugins.path.isAbsolute(runtimeDirectory)) {
10
+ throw new Error('A private XDG_RUNTIME_DIR is required for authswitch authority.');
11
+ }
12
+ const socketDirectory = plugins.path.join(runtimeDirectory, 'authswitch');
13
+ const runtimeSocketDirectory = plugins.path.join(socketDirectory, 'runtime');
14
+ return {
15
+ runtimeDirectory,
16
+ databaseSocketPath: plugins.path.join(socketDirectory, 'internal', 'db.sock'),
17
+ authoritySocketPath: plugins.path.join(socketDirectory, 'management', 'authority.sock'),
18
+ runtimeSocketPath: plugins.path.join(runtimeSocketDirectory, 'runtime.sock'),
19
+ runtimeSocketDirectory,
20
+ };
21
+ };
22
+ /** Canonical per-user paths. An explicit runtime directory is also usable by a trusted container host. */
23
+ export const resolveAuthSwitchAuthorityPaths = (runtimeDirectory = process.env.XDG_RUNTIME_DIR) => {
24
+ const sockets = authSwitchAuthoritySocketPaths(runtimeDirectory);
25
+ const home = plugins.os.userInfo().homedir;
26
+ const dataHome = process.env.XDG_DATA_HOME || plugins.path.join(home, '.local/share');
27
+ if (!plugins.path.isAbsolute(dataHome))
28
+ throw new Error('XDG_DATA_HOME must be an absolute path.');
29
+ return { ...sockets, dataDirectory: plugins.path.join(dataHome, 'authswitch', 'authority') };
30
+ };
31
+ /** The user service `authswitch authority service install` writes. */
32
+ export const authSwitchAuthorityUnitName = 'authswitch-authority.service';
33
+ /**
34
+ * The directory that unit is installed into, from a stated environment and home directory.
35
+ *
36
+ * It is the rule smartdaemon applies to a user unit when it is given no directory --
37
+ * `$XDG_DATA_HOME/systemd/user`, default `~/.local/share/systemd/user` -- stated here so the installer passes
38
+ * it explicitly and the fence derives it from the locations its caller states rather than from this process.
39
+ */
40
+ export const authSwitchAuthorityUnitDirectory = (env, homeDirectory) => {
41
+ const dataHome = env.XDG_DATA_HOME || plugins.path.join(homeDirectory, '.local/share');
42
+ if (!plugins.path.isAbsolute(dataHome))
43
+ throw new Error('XDG_DATA_HOME must be an absolute path.');
44
+ return plugins.path.join(dataHome, 'systemd/user');
45
+ };
46
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5LXBhdGhzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5LXBhdGhzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBdUJ4Qzs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLENBQzVDLGdCQUFvQyxFQUFtQyxFQUFFO0lBQ3pFLElBQUksQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQztRQUNwRSxNQUFNLElBQUksS0FBSyxDQUFDLGlFQUFpRSxDQUFDLENBQUM7SUFDckYsQ0FBQztJQUNELE1BQU0sZUFBZSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGdCQUFnQixFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQzFFLE1BQU0sc0JBQXNCLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQzdFLE9BQU87UUFDTCxnQkFBZ0I7UUFDaEIsa0JBQWtCLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFVBQVUsRUFBRSxTQUFTLENBQUM7UUFDN0UsbUJBQW1CLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsQ0FBQztRQUN2RixpQkFBaUIsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxjQUFjLENBQUM7UUFDNUUsc0JBQXNCO0tBQ3ZCLENBQUM7QUFDSixDQUFDLENBQUM7QUFFRiwwR0FBMEc7QUFDMUcsTUFBTSxDQUFDLE1BQU0sK0JBQStCLEdBQUcsQ0FBQyxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBNkIsRUFBRTtJQUMzSCxNQUFNLE9BQU8sR0FBRyw4QkFBOEIsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ2pFLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsT0FBTyxDQUFDO0lBQzNDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsQ0FBQztJQUN0RixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFDO0lBQ25HLE9BQU8sRUFBRSxHQUFHLE9BQU8sRUFBRSxhQUFhLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFlBQVksRUFBRSxXQUFXLENBQUMsRUFBRSxDQUFDO0FBQy9GLENBQUMsQ0FBQztBQUVGLHNFQUFzRTtBQUN0RSxNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FBRyw4QkFBOEIsQ0FBQztBQUUxRTs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRyxDQUFDLEdBQXNCLEVBQUUsYUFBcUIsRUFBVSxFQUFFO0lBQ3hHLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxhQUFhLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQ3ZGLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUM7UUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLHlDQUF5QyxDQUFDLENBQUM7SUFDbkcsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsY0FBYyxDQUFDLENBQUM7QUFDckQsQ0FBQyxDQUFDIn0=
@@ -1,6 +1,16 @@
1
1
  import type { ITypedRequest } from '@api.global/typedrequest-interfaces';
2
2
  import type { IAuthSwitchBinding } from './authority-contract.js';
3
- /** Backend-only binding operation. The capability must never enter a browser-facing response. */
3
+ /**
4
+ * Backend-only binding operation, served on the MANAGEMENT socket. Not a misplacement -- the rule.
5
+ *
6
+ * The other two requests in this file are answered on the runtime socket, whose directory a container may
7
+ * be given. This one is not, because it mints the capability those two spend: minting is the act that
8
+ * authorizes a runtime, so it belongs to a caller already trusted with account management. A container
9
+ * holding the runtime directory can therefore use the binding it was handed, and can never create one --
10
+ * not for itself, and not for another account.
11
+ *
12
+ * The capability must never enter a browser-facing response.
13
+ */
4
14
  export interface IReq_AuthSwitchBindAccount extends ITypedRequest {
5
15
  method: 'authswitch.authority.bind';
6
16
  request: {
@@ -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,11 +89,20 @@ 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'>;
88
95
  resolveAccess(bindingId: string, capability: string, minValidityMs: number, rejectedGrantGeneration?: number): Promise<IAuthSwitchResolvedAccess>;
89
96
  /** Backend-only access for usage; it never creates or bypasses a runtime binding. */
90
97
  resolveUsageAccess(context: IAuthSwitchUsageContext, rejectedGrantGeneration: number | undefined, minValidityMs: number): Promise<IAuthSwitchResolvedAccess>;
98
+ /**
99
+ * The shared managed-access loop. What it decides about the login itself is a marked refusal both callers
100
+ * read the same way: `login_needs_reauth` when only a new sign-in brings the login back, `access_not_fresh`
101
+ * when the login is intact and the provider could not renew it yet. The usage reader still folds either
102
+ * into its own problem; a bound runtime shows the instruction. What only concerns a binding is decided
103
+ * before this point, in the view `resolveAccess` supplies, and a changed identity or a view that keeps
104
+ * moving stays an unmarked fault, because nobody decided it.
105
+ */
91
106
  private resolveManagedAccess;
92
107
  private refreshAccount;
93
108
  private performRefresh;