@cortexkit/anthropic-auth-core 1.22.0 → 2.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 (133) hide show
  1. package/README.md +2 -0
  2. package/dist/accounts.d.ts +52 -8
  3. package/dist/cachekeep.d.ts +27 -1
  4. package/dist/cch.d.ts +11 -1
  5. package/dist/claude-code.d.ts +23 -1
  6. package/dist/claustrum-enrollment.d.ts +72 -0
  7. package/dist/claustrum-scoped-roster.d.ts +27 -0
  8. package/dist/claustrum-scoped-runtime.d.ts +31 -0
  9. package/dist/claustrum-scoped.d.ts +53 -0
  10. package/dist/claustrum.d.ts +16 -82
  11. package/dist/commands/account.d.ts +38 -11
  12. package/dist/constants.d.ts +3 -2
  13. package/dist/custom-headers.d.ts +3 -0
  14. package/dist/index.d.ts +50 -0
  15. package/dist/index.js +29700 -31
  16. package/dist/internal-types/LICENSE +21 -0
  17. package/dist/internal-types/commands/builtins.d.ts +71 -0
  18. package/dist/internal-types/commands/index.d.ts +10 -0
  19. package/dist/internal-types/commands/menu.d.ts +61 -0
  20. package/dist/internal-types/commands/model.d.ts +238 -0
  21. package/dist/internal-types/commands/pi.d.ts +23 -0
  22. package/dist/internal-types/commands/seam.d.ts +72 -0
  23. package/dist/internal-types/fs/refresh-file-lock.d.ts +33 -0
  24. package/dist/internal-types/logger/capture-sink.d.ts +14 -0
  25. package/dist/internal-types/logger/engine.d.ts +100 -0
  26. package/dist/internal-types/logger/index.d.ts +6 -0
  27. package/dist/internal-types/logger/redact.d.ts +11 -0
  28. package/dist/internal-types/pi-slot/index.d.ts +3 -0
  29. package/dist/internal-types/pi-slot/slot.d.ts +49 -0
  30. package/dist/internal-types/quota/codec.d.ts +11 -0
  31. package/dist/internal-types/quota/format.d.ts +46 -0
  32. package/dist/internal-types/quota/index.d.ts +10 -0
  33. package/dist/internal-types/quota/map.d.ts +63 -0
  34. package/dist/internal-types/quota/merge.d.ts +48 -0
  35. package/dist/internal-types/quota/projection.d.ts +64 -0
  36. package/dist/internal-types/store/attribution.d.ts +28 -0
  37. package/dist/internal-types/store/errors.d.ts +55 -0
  38. package/dist/internal-types/store/hooks.d.ts +12 -0
  39. package/dist/internal-types/store/identity.d.ts +39 -0
  40. package/dist/internal-types/store/index.d.ts +21 -0
  41. package/dist/internal-types/store/mutate.d.ts +189 -0
  42. package/dist/internal-types/store/pool.d.ts +196 -0
  43. package/dist/internal-types/store/provider-state.d.ts +131 -0
  44. package/dist/internal-types/store/publication.d.ts +47 -0
  45. package/dist/internal-types/store/pull.d.ts +36 -0
  46. package/dist/internal-types/store/refresh-lock.d.ts +63 -0
  47. package/dist/internal-types/store/refresh.d.ts +72 -0
  48. package/dist/internal-types/store/rows.d.ts +313 -0
  49. package/dist/internal-types/store/runtime.d.ts +36 -0
  50. package/dist/internal-types/store/schema.d.ts +443 -0
  51. package/dist/internal-types/store/settings.d.ts +63 -0
  52. package/dist/internal-types/store/torn.d.ts +78 -0
  53. package/dist/migration-source.d.ts +33 -0
  54. package/dist/model-remap.d.ts +27 -0
  55. package/dist/models.d.ts +60 -0
  56. package/dist/native-account-runtime.d.ts +121 -0
  57. package/dist/native-account-view.d.ts +126 -0
  58. package/dist/native-command-effects.d.ts +59 -0
  59. package/dist/native-credential-validation.d.ts +42 -0
  60. package/dist/native-custody-activation.d.ts +45 -0
  61. package/dist/native-custody.d.ts +114 -0
  62. package/dist/native-enrollment-authority.d.ts +2 -0
  63. package/dist/native-host-auth-read.d.ts +3 -0
  64. package/dist/native-host-auth-write.d.ts +51 -0
  65. package/dist/native-host-auth.d.ts +12 -0
  66. package/dist/native-local-credential-service.d.ts +76 -0
  67. package/dist/native-local-runtime-readers.d.ts +22 -0
  68. package/dist/native-lock-observer.d.ts +12 -0
  69. package/dist/native-menu-executor.d.ts +243 -0
  70. package/dist/native-menu-model.d.ts +142 -0
  71. package/dist/native-migration-proof.d.ts +28 -0
  72. package/dist/native-migration-source.d.ts +47 -0
  73. package/dist/native-migration.d.ts +66 -0
  74. package/dist/native-quota-codec.d.ts +32 -0
  75. package/dist/native-refresh-coordinator.d.ts +240 -0
  76. package/dist/native-roster-seed.d.ts +115 -0
  77. package/dist/native-runtime-registry.d.ts +19 -0
  78. package/dist/native-runtime.d.ts +204 -0
  79. package/dist/native-transient-account.d.ts +38 -0
  80. package/dist/native-ui.d.ts +45 -0
  81. package/dist/native-usage-publication.d.ts +11 -0
  82. package/dist/native-vault-admission.d.ts +8 -0
  83. package/dist/native-vault-connection.d.ts +33 -0
  84. package/dist/native-vault-paths.d.ts +13 -0
  85. package/dist/native-vault-runtime.d.ts +102 -0
  86. package/dist/native-vault-seed.d.ts +16 -0
  87. package/dist/oauth-profile.d.ts +2 -1
  88. package/dist/pool-authority.d.ts +159 -0
  89. package/dist/pool-binding.d.ts +32 -0
  90. package/dist/pool-paths.d.ts +21 -0
  91. package/dist/pool-store.d.ts +32 -0
  92. package/dist/prime.d.ts +11 -3
  93. package/dist/provider-error.d.ts +139 -0
  94. package/dist/quota-header-feed.d.ts +13 -0
  95. package/dist/quota-manager.d.ts +30 -5
  96. package/dist/relay.d.ts +22 -1
  97. package/dist/request-history.d.ts +34 -0
  98. package/dist/sse.d.ts +81 -0
  99. package/dist/sticky-routing.d.ts +29 -0
  100. package/package.json +9 -3
  101. package/dist/accounts.js +0 -3330
  102. package/dist/auth.js +0 -182
  103. package/dist/cache1h.js +0 -97
  104. package/dist/cachekeep-registry.js +0 -124
  105. package/dist/cachekeep.js +0 -578
  106. package/dist/cch.js +0 -127
  107. package/dist/claude-code.js +0 -388
  108. package/dist/claustrum.js +0 -529
  109. package/dist/commands/account.js +0 -239
  110. package/dist/constants.js +0 -82
  111. package/dist/dump.js +0 -616
  112. package/dist/fast.js +0 -66
  113. package/dist/json.js +0 -10
  114. package/dist/killswitch.js +0 -132
  115. package/dist/logger.js +0 -286
  116. package/dist/logging.js +0 -59
  117. package/dist/mid-conversation-output-config.js +0 -89
  118. package/dist/models.js +0 -141
  119. package/dist/network-errors.js +0 -27
  120. package/dist/oauth-profile.js +0 -54
  121. package/dist/pkce.js +0 -17
  122. package/dist/prime.js +0 -823
  123. package/dist/provider.js +0 -0
  124. package/dist/quota-header-feed.js +0 -279
  125. package/dist/quota-headers.js +0 -121
  126. package/dist/quota-manager.js +0 -805
  127. package/dist/quotas.js +0 -141
  128. package/dist/relay.js +0 -1243
  129. package/dist/routing.js +0 -94
  130. package/dist/start.js +0 -29
  131. package/dist/sticky-routing.js +0 -523
  132. package/dist/thinking-binding.js +0 -54
  133. package/dist/token-fingerprint.js +0 -8
@@ -0,0 +1,3 @@
1
+ /** Move a Pi provider slot aside without changing provider or model identifiers. */
2
+ export type { PiSlot, PiSlotErrorCode, PiSlotInspection, PiSlotOptions, } from './slot.js';
3
+ export { openPiSlot, PiSlotError } from './slot.js';
@@ -0,0 +1,49 @@
1
+ export type PiSlotErrorCode = 'invalid-options' | 'invalid-auth' | 'invalid-stash' | 'unsafe-path' | 'missing-stash' | 'conflict' | 'lock-timeout' | 'lock-compromised' | 'io';
2
+ /** Errors deliberately contain neither paths nor credentials nor underlying error messages. */
3
+ export declare class PiSlotError extends Error {
4
+ readonly code: PiSlotErrorCode;
5
+ constructor(code: PiSlotErrorCode);
6
+ }
7
+ export interface PiSlotOptions {
8
+ authPath: string;
9
+ provider: string;
10
+ /**
11
+ * Where the provider's original Pi login is kept while the vault serves.
12
+ * Put it in a private directory, and give each provider its own file: one
13
+ * stash holds one provider's original login, and a second provider writing
14
+ * to it would overwrite the first one's backup.
15
+ */
16
+ stashPath: string;
17
+ /**
18
+ * The api_key Pi stores in the slot while the vault serves. It is a fixed,
19
+ * non-secret string that no provider accepts. Pi runs a key starting with
20
+ * `!` as a shell command and expands `$NAME` from the environment, so the
21
+ * placeholder may contain neither.
22
+ */
23
+ placeholderKey: string;
24
+ lockTimeoutMs?: number;
25
+ }
26
+ export interface PiSlotInspection {
27
+ slot: 'original' | 'placeholder' | 'foreign' | 'empty';
28
+ stash: 'valid' | 'missing';
29
+ }
30
+ export interface PiSlot {
31
+ inspect(): Promise<PiSlotInspection>;
32
+ enterVault(): Promise<void>;
33
+ exitVault(): Promise<'restored' | 'conflict' | 'nothing-to-do'>;
34
+ }
35
+ /**
36
+ * Production entry point. Pi caches which models are usable; after a
37
+ * successful transition the extension calls `ctx.modelRegistry.refresh()` so
38
+ * Pi re-reads the slot it just changed.
39
+ */
40
+ export declare function openPiSlot(options: PiSlotOptions): PiSlot;
41
+ /**
42
+ * Internal test entry point: pause reads, renewals, or renames to simulate races,
43
+ * and observe durable writes/deletion to simulate crashes. Not a public export.
44
+ */
45
+ export declare function createPiSlot(options: PiSlotOptions, onStep?: (step: 'stash-written' | 'auth-written' | 'stash-deleted') => void, hooks?: {
46
+ afterReadCheck?: (path: string) => Promise<void>;
47
+ onRenew?: (renew: () => Promise<void>) => void;
48
+ beforeRename?: (path: string) => Promise<void>;
49
+ }): PiSlot;
@@ -0,0 +1,11 @@
1
+ export interface QuotaCodec {
2
+ readonly validate: (value: unknown) => boolean;
3
+ readonly merge: (stored: unknown, observation: unknown) => unknown;
4
+ }
5
+ /**
6
+ * The quota codec the account-pool store (the `/store` subpath) takes when it
7
+ * is opened: `validate` checks a stored per-row quota map and `merge` applies
8
+ * an observation to one, so the store persists the map without interpreting
9
+ * it.
10
+ */
11
+ export declare const quotaCodec: QuotaCodec;
@@ -0,0 +1,46 @@
1
+ import type { ProjectedLimit, ProjectedQuota } from './projection.js';
2
+ /** A reading older than this is marked with its age. */
3
+ export declare const QUOTA_STALE_AFTER_MS: number;
4
+ /** What `formatQuota` returns when there is no reading at all. */
5
+ export declare const NO_QUOTA_READING = "no quota reading yet";
6
+ /**
7
+ * What `formatQuota` returns when the provider answered but reported no
8
+ * limit for any window (every window is an absence record or a tombstone).
9
+ */
10
+ export declare const NO_LIMITS_REPORTED = "no limits reported";
11
+ /**
12
+ * `compact` fits a footer or a sidebar row: each window's figure, the credit
13
+ * budget and the stale marker, without reset times. `full` adds each reset.
14
+ */
15
+ export type QuotaTextForm = 'compact' | 'full';
16
+ export interface QuotaTextOptions {
17
+ /** The current time in ms, for reset countdowns and the stale marker. */
18
+ now: number;
19
+ /** Defaults to `full`. */
20
+ form?: QuotaTextForm;
21
+ /** Defaults to `QUOTA_STALE_AFTER_MS`. */
22
+ staleAfterMs?: number;
23
+ }
24
+ /**
25
+ * The name a window is shown under. A general window (scope `all`) is named
26
+ * by its stored length; a model-scoped window keeps the label the plugin
27
+ * stored it under, which already names the model (`seven_day_opus`). A
28
+ * general window stored without a length (a snapshot from before lengths
29
+ * were recorded) falls back to its label.
30
+ */
31
+ export declare function quotaWindowName(limit: Pick<ProjectedLimit, 'scope' | 'label' | 'windowMinutes'>): string;
32
+ /**
33
+ * The pieces of a quota text, in order: one per reported window
34
+ * (`5h 58% left, resets 2h`), then the credit budget when there is one, then
35
+ * `checked 3h ago` when the oldest piece shown is stale. Empty when nothing
36
+ * was reported; `formatQuota` turns that into a sentence. A caller printing
37
+ * one line per window (the terminal auth menu) uses these directly.
38
+ */
39
+ export declare function quotaTextParts(quota: ProjectedQuota | undefined, options: QuotaTextOptions): string[];
40
+ /**
41
+ * A row's quota as one line: `5h 58% left · 7d 90% left` in the compact
42
+ * form, with `, resets 2h` after each window in the full form. With nothing
43
+ * reported it is `NO_QUOTA_READING`, or `NO_LIMITS_REPORTED` when the
44
+ * provider answered without a limit for any window.
45
+ */
46
+ export declare function formatQuota(quota: ProjectedQuota | undefined, options: QuotaTextOptions): string;
@@ -0,0 +1,10 @@
1
+ export type { QuotaCodec } from './codec.js';
2
+ export { quotaCodec } from './codec.js';
3
+ export type { QuotaTextForm, QuotaTextOptions } from './format.js';
4
+ export { formatQuota, NO_LIMITS_REPORTED, NO_QUOTA_READING, QUOTA_STALE_AFTER_MS, quotaTextParts, quotaWindowName, } from './format.js';
5
+ export type { CreditBudgetCleared, CreditBudgetEntry, CreditBudgetReading, QuotaAbsentEntry, QuotaEntry, QuotaMap, QuotaReadingEntry, QuotaRetiredEntry, } from './map.js';
6
+ export { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, emptyQuotaMap, isQuotaMap, } from './map.js';
7
+ export type { ObservedBudget, ObservedPair, ObservedReading, QuotaObservation, } from './merge.js';
8
+ export { isQuotaObservation, mergeQuotaObservation, QuotaCodecError, } from './merge.js';
9
+ export type { ExhaustionReset, ProjectedBudget, ProjectedLimit, ProjectedQuota, } from './projection.js';
10
+ export { budgetExhaustedResetAt, futureResetAt, projectQuota, readsExhausted, } from './projection.js';
@@ -0,0 +1,63 @@
1
+ /** The scope of a limit that binds every request regardless of model. */
2
+ export declare const ALL_SCOPE = "all";
3
+ /** The window label required when an admission call supplies none. */
4
+ export declare const DEFAULT_REQUIRED_LABELS: readonly string[];
5
+ export interface QuotaReadingEntry {
6
+ scope: string;
7
+ label: string;
8
+ kind: 'reading';
9
+ checkedAt: number;
10
+ usedPercent: number;
11
+ /** ISO timestamp at which the window resets, as the provider reported it. */
12
+ resetsAt?: string;
13
+ /** Window length in minutes, when the provider reported one. */
14
+ windowMinutes?: number;
15
+ }
16
+ export interface QuotaRetiredEntry {
17
+ scope: string;
18
+ label: string;
19
+ kind: 'retired';
20
+ retiredAt: number;
21
+ }
22
+ export interface QuotaAbsentEntry {
23
+ scope: string;
24
+ label: string;
25
+ kind: 'absent';
26
+ checkedAt: number;
27
+ }
28
+ export type QuotaEntry = QuotaReadingEntry | QuotaRetiredEntry | QuotaAbsentEntry;
29
+ export interface CreditBudgetReading {
30
+ kind: 'reading';
31
+ checkedAt: number;
32
+ /** The provider's own verdict that the budget is spent. */
33
+ reached: boolean;
34
+ remainingPercent?: number;
35
+ usedPercent?: number;
36
+ resetsAt?: string;
37
+ limit?: number;
38
+ used?: number;
39
+ remaining?: number;
40
+ unit?: string;
41
+ }
42
+ export interface CreditBudgetCleared {
43
+ kind: 'cleared';
44
+ checkedAt: number;
45
+ }
46
+ export type CreditBudgetEntry = CreditBudgetReading | CreditBudgetCleared;
47
+ export interface QuotaMap {
48
+ limits: QuotaEntry[];
49
+ /** Absent means no observation has reported on the budget yet. */
50
+ budget?: CreditBudgetEntry;
51
+ }
52
+ export declare function emptyQuotaMap(): QuotaMap;
53
+ /** The time an entry speaks for: its reading, retirement or absence time. */
54
+ export declare function entryTime(entry: QuotaEntry): number;
55
+ export declare function limitKey(scope: string, label: string): string;
56
+ export declare function isCreditBudgetEntry(value: unknown): value is CreditBudgetEntry;
57
+ /**
58
+ * True when `value` is a well-formed quota map. Unrecognised top-level keys
59
+ * are tolerated (and preserved by merge) so a newer writer's additions
60
+ * survive; a malformed entry, a duplicated (scope, label) key or a malformed
61
+ * budget makes the whole map invalid.
62
+ */
63
+ export declare function isQuotaMap(value: unknown): value is QuotaMap;
@@ -0,0 +1,48 @@
1
+ import { type QuotaMap } from './map.js';
2
+ export interface ObservedReading {
3
+ /** Defaults to `all`. */
4
+ scope?: string;
5
+ label: string;
6
+ usedPercent: number;
7
+ resetsAt?: string;
8
+ windowMinutes?: number;
9
+ }
10
+ export interface ObservedPair {
11
+ /** Defaults to `all`. */
12
+ scope?: string;
13
+ label: string;
14
+ }
15
+ export type ObservedBudget = {
16
+ kind: 'reading';
17
+ reached: boolean;
18
+ remainingPercent?: number;
19
+ usedPercent?: number;
20
+ resetsAt?: string;
21
+ limit?: number;
22
+ used?: number;
23
+ remaining?: number;
24
+ unit?: string;
25
+ } | {
26
+ kind: 'cleared';
27
+ };
28
+ export interface QuotaObservation {
29
+ checkedAt: number;
30
+ readings?: ObservedReading[];
31
+ /** Pairs reported on with authority besides the ones carrying a reading. */
32
+ coverage?: ObservedPair[];
33
+ /** Absent means the observation says nothing about the budget. */
34
+ budget?: ObservedBudget;
35
+ }
36
+ /** Thrown by `mergeQuotaObservation` when either argument is malformed. */
37
+ export declare class QuotaCodecError extends TypeError {
38
+ constructor(message: string);
39
+ }
40
+ /** True when `value` is a well-formed observation; a pair read twice is not. */
41
+ export declare function isQuotaObservation(value: unknown): value is QuotaObservation;
42
+ /**
43
+ * Applies `observation` to `stored` (undefined for a row with no map yet) and
44
+ * returns the new map. Pure: neither argument is modified. Throws
45
+ * `QuotaCodecError` when either argument is malformed, so a store refuses the
46
+ * write rather than persisting a map it could not read back.
47
+ */
48
+ export declare function mergeQuotaObservation(stored: unknown, observation: unknown): QuotaMap;
@@ -0,0 +1,64 @@
1
+ import { type QuotaMap } from './map.js';
2
+ export interface ProjectedLimit {
3
+ scope: string;
4
+ label: string;
5
+ kind: 'reading' | 'retired' | 'absent';
6
+ /** The reading, retirement or absence time. */
7
+ checkedAt: number;
8
+ /** The stored window length; undefined when the provider reported none. */
9
+ windowMinutes?: number;
10
+ /** Present on readings only. */
11
+ usedPercent?: number;
12
+ /** Present on readings only: `100 - usedPercent`. */
13
+ remainingPercent?: number;
14
+ resetsAt?: string;
15
+ }
16
+ export interface ProjectedBudget {
17
+ checkedAt: number;
18
+ reached: boolean;
19
+ remainingPercent?: number;
20
+ resetsAt?: string;
21
+ }
22
+ export interface ProjectedQuota {
23
+ scope: string;
24
+ /** One entry per label: longest known window first, unknown lengths last. */
25
+ limits: readonly ProjectedLimit[];
26
+ /**
27
+ * The oldest reading time among the limits (or, with no reading, the oldest
28
+ * evidence time), so a projection is only as fresh as its stalest limit.
29
+ */
30
+ checkedAt?: number;
31
+ /** Present only when the stored budget is a reading, not cleared. */
32
+ budget?: ProjectedBudget;
33
+ }
34
+ /**
35
+ * Resolves `map` for a request in `scope` (`all` or a model family). A family
36
+ * request sees only its own family's keys and the `all` keys.
37
+ *
38
+ * The label decides how a family limit relates to a general one. Under the
39
+ * same label, the family reading replaces the `all` reading: use this only
40
+ * when the provider's family figure is a more specific view of the same cap.
41
+ * Under different labels both limits are projected and every consumer judges
42
+ * both, so a family cap that applies on top of a general cap must be stored
43
+ * under its own label; otherwise the general cap is hidden from a family
44
+ * request. A family tombstone or absence record says only that no
45
+ * family-specific limit exists, so it does not hide an `all` entry for the
46
+ * same label and is used only when there is none.
47
+ */
48
+ export declare function projectQuota(map: QuotaMap | undefined, scope?: string): ProjectedQuota;
49
+ export interface ExhaustionReset {
50
+ resetsAt: string;
51
+ resetAtMs: number;
52
+ }
53
+ /**
54
+ * The credit budget's own exhaustion signal. Admission and the sticky
55
+ * routing decision to move a session off its pinned row both use it, so the
56
+ * two agree on what "spent" means. `reached` is the provider's
57
+ * verdict (the percentage is only a display value), and the check fails open
58
+ * on a missing, unparsable or already-passed reset.
59
+ */
60
+ export declare function budgetExhaustedResetAt(quota: Pick<ProjectedQuota, 'budget'> | null | undefined, now: number): ExhaustionReset | undefined;
61
+ /** True for a reading at or beyond its whole window. */
62
+ export declare function readsExhausted(limit: ProjectedLimit): boolean;
63
+ /** The reset time of a limit when it parses and lies after `now`. */
64
+ export declare function futureResetAt(limit: ProjectedLimit, now: number): number | undefined;
@@ -0,0 +1,28 @@
1
+ import { type StoreRuntime } from './runtime.js';
2
+ /**
3
+ * What a pull or refresh captured about its row (named by id alongside) when
4
+ * it was issued. A result applies only while the row with that id still has
5
+ * this credential epoch and this recorded identity; a replaced credential
6
+ * bumps the epoch, so work issued for the old one is discarded.
7
+ *
8
+ * The epoch names one credential lineage of the row: a credential given by
9
+ * `add` or `replace`, through every refresh and `rotate` of it, which keep
10
+ * the epoch. Since 0.8.0 that holds across removal too: a row added under an
11
+ * id the pool held before starts past every epoch that id held (the store
12
+ * records them when it drops a row, in the config file, so every process
13
+ * sees it), so an attribution taken for a removed row is refused once the id
14
+ * is added again, even with the same identity. Writers older than 0.8.0
15
+ * start a re-added id at epoch 1 again, and a writer that does not know the
16
+ * pool can remove and re-add a row without the store seeing it; work
17
+ * attributed across either may still apply to the new credential.
18
+ */
19
+ export interface Attribution {
20
+ credentialEpoch: number;
21
+ identity?: string;
22
+ }
23
+ /**
24
+ * Merges a quota observation into a row's stored map under the store locks,
25
+ * after attribution passes. Needs no row lock, so it is permitted from
26
+ * inside hooks. Clears needs-first-reading on success.
27
+ */
28
+ export declare function recordQuota(rt: StoreRuntime, id: string, attribution: Attribution, observation: unknown): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import type { StoredCredential } from './schema.js';
2
+ /** Every library operation that can fail, as named in the failure value. */
3
+ export type PoolOperation = 'initialize' | 'add' | 'replace' | 'rotate' | 'disable' | 'enable' | 'remove' | 'reorder' | 'updateSettings' | 'recordIdentity' | 'updateProviderState' | 'refresh' | 'pull' | 'publishRoster';
4
+ /**
5
+ * How far an operation got before it failed.
6
+ *
7
+ * `before-first-write`: nothing was written; both files are as they were.
8
+ * `after-first-write`: the operation's first file write landed and a later one
9
+ * did not; what that first write left is on disk and is never rolled back
10
+ * (add: a state entry no roster row names, which no reader loads; replace:
11
+ * the new credential stamped ahead of the config, which readers show as a
12
+ * torn row and the next store write on it completes; rotate: the rotated
13
+ * credential beside the old per-row entry). `pull`: a quota pull, or the
14
+ * recording of its result, failed.
15
+ */
16
+ export type PoolFailurePhase = 'before-first-write' | 'after-first-write' | 'pull';
17
+ /**
18
+ * Why an operation failed. `lock-contention` and `lock-ownership` are the two
19
+ * lock outcomes (a wait that ran out, and a lease found lost); the rest are
20
+ * refusals and failures of the operation itself.
21
+ */
22
+ export type PoolFailureKind = 'lock-contention' | 'lock-ownership' | 'pending-migration' | 'load-error' | 'snapshot-contended' | 'publication-sync' | 'publication-incomplete' | 'unknown-row' | 'invalid-row' | 'invalid-input' | 'id-exists' | 'id-removed' | 'type-mismatch' | 'no-credential' | 'row-disabled' | 'row-protected' | 'row-staged' | 'credential-exists' | 'publication-mismatch' | 'duplicate-identity' | 'identity-mismatch' | 'identity-contradicted' | 'endpoint-mismatch' | 'row-key-changed' | 'invalid-order' | 'refresh-stamp-ahead' | 'unbound-credential' | 'attribution' | 'provider' | 'pull' | 'invalid-quota' | 'invalid-provider-state' | 'after-persist-hook' | 'unexpected';
23
+ /**
24
+ * The single failure value of every store operation. `committed` is present
25
+ * only when the operation had already written a credential to the state file
26
+ * before it failed (a partial rotation or refresh): it is the credential now
27
+ * on disk, so a caller never has to re-read the files to learn it.
28
+ */
29
+ export declare class PoolOperationError extends Error {
30
+ readonly operation: PoolOperation;
31
+ readonly rowId: string | undefined;
32
+ readonly phase: PoolFailurePhase;
33
+ readonly retryable: boolean;
34
+ readonly kind: PoolFailureKind;
35
+ readonly committed: StoredCredential | undefined;
36
+ constructor(details: {
37
+ operation: PoolOperation;
38
+ rowId?: string;
39
+ phase: PoolFailurePhase;
40
+ retryable: boolean;
41
+ kind: PoolFailureKind;
42
+ committed?: StoredCredential;
43
+ message?: string;
44
+ cause?: unknown;
45
+ });
46
+ }
47
+ /**
48
+ * Thrown when a row operation or a refresh is called from inside a hook of a
49
+ * lock-holding operation (or from any continuation created inside one). It is
50
+ * thrown before any lock is taken or waited for.
51
+ */
52
+ export declare class PoolReentryError extends Error {
53
+ readonly operation: PoolOperation;
54
+ constructor(operation: PoolOperation);
55
+ }
@@ -0,0 +1,12 @@
1
+ import { type PoolOperation } from './errors.js';
2
+ /** Refuses a row operation or refresh called from inside a hook. */
3
+ export declare function assertNotInsideHook(operation: PoolOperation): void;
4
+ export declare function runInsideHook<T>(operation: PoolOperation, fn: () => Promise<T> | T): Promise<T>;
5
+ export interface PoolLogger {
6
+ warn(message: string, data?: unknown): void;
7
+ }
8
+ /**
9
+ * Runs a failure hook. A hook that throws never replaces the failure it was
10
+ * handed: its exception is logged and discarded.
11
+ */
12
+ export declare function callFailureHook<E, R extends string | undefined>(operation: PoolOperation, hook: ((rowId: R, error: E) => void | Promise<void>) | undefined, rowId: R, error: E, logger: PoolLogger | undefined): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import type { PoolRow } from './schema.js';
2
+ /**
3
+ * What the identity rules edit: a transaction, or a config being completed
4
+ * in memory.
5
+ */
6
+ export interface RowEditor {
7
+ rows(): PoolRow[];
8
+ rosterRow(id: string): Record<string, unknown> | undefined;
9
+ entry(id: string): Record<string, unknown> | undefined;
10
+ setEntry(id: string, entry: Record<string, unknown>): void;
11
+ }
12
+ /** Prefix of a refresh quarantine reason, followed by JSON of the expected and returned identities. */
13
+ export declare const IDENTITY_CONTRADICTED_REASON_PREFIX = "identity-contradicted: ";
14
+ /** The reason recorded on a row disabled because an earlier row is the same account. */
15
+ export declare const DUPLICATE_IDENTITY_REASON = "duplicate-identity";
16
+ /**
17
+ * Enabled OAuth rows holding a credential whose wire identity is not yet
18
+ * known. API-key rows and disabled rows never count.
19
+ */
20
+ export declare function countUnknownIdentityRows(rows: readonly PoolRow[]): number;
21
+ /**
22
+ * Marks a row disabled with a reason: `enabled: false` in the roster row,
23
+ * which older readers honour, and the reason in the per-row entry. A row
24
+ * without an entry gets one at epoch 1. Nothing is ever deleted.
25
+ */
26
+ export declare function disableIn(tx: RowEditor, id: string, reason: string): void;
27
+ /**
28
+ * Marks a row enabled: `enabled: true` in the roster row and no
29
+ * `disabledReason` in its entry. A row without an entry is not given one.
30
+ */
31
+ export declare function enableIn(tx: RowEditor, id: string): void;
32
+ /**
33
+ * Two enabled OAuth rows with one wire identity are the same account: the
34
+ * earlier row in roster order stays enabled and every later one is disabled
35
+ * with a reason. Returns the ids it disabled.
36
+ */
37
+ export declare function disableIdentityDuplicates(tx: RowEditor, identity: string): string[];
38
+ /** Records a row's wire identity in its roster row, then applies dedupe. */
39
+ export declare function recordIdentityIn(tx: RowEditor, id: string, identity: string): string[];
@@ -0,0 +1,21 @@
1
+ export type { Attribution } from './attribution.js';
2
+ export type { PoolFailureKind, PoolFailurePhase, PoolOperation, } from './errors.js';
3
+ export { PoolOperationError, PoolReentryError } from './errors.js';
4
+ export type { PoolLogger } from './hooks.js';
5
+ export { countUnknownIdentityRows, DUPLICATE_IDENTITY_REASON, } from './identity.js';
6
+ export type { HoldPoint, InitializeOutcome, WriteStep } from './mutate.js';
7
+ export type { OpenPoolStoreOptions, PoolLoad, PoolStore, } from './pool.js';
8
+ export { openPoolStore } from './pool.js';
9
+ export type { ProviderStateMutator, RowTransitionMutator, UpdateProviderStateResult, } from './provider-state.js';
10
+ export { DECLINE_TRANSITION } from './provider-state.js';
11
+ export type { PublicationReceipt, PublishOptions, PublishPlan, PublishResult, } from './publication.js';
12
+ export type { PullHook, PullRequest } from './pull.js';
13
+ export type { ProviderRefresh, ProviderRefreshResult, RefreshOptions, RefreshOutcome, } from './refresh.js';
14
+ export type { LockEvent, PoolLockOptions, PoolLockSpec, } from './refresh-lock.js';
15
+ export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
16
+ export type { AddInput, AddOptions, AddResult, CredentialWriteInput, FailureHook, ProtectFn, ProtectView, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowProjection, RowToggleOptions, RowTransitionOptions, RowTransitionResult, RowWriteOptions, } from './rows.js';
17
+ export type { PullReason } from './runtime.js';
18
+ export type { ApiKeyCredential, CredentialStampStatus, OAuthCredential, PoolCredential, PoolRow, ProviderStateCodec, ProviderStateDrop, ProviderStateReplacement, QuotaCodec, RotateCredential, StoredCredential, } from './schema.js';
19
+ export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, PROVIDER_STATE_KEY, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
20
+ export type { PoolSettings, SettingsMutator, SettingsRead, UpdateSettingsOptions, UpdateSettingsResult, } from './settings.js';
21
+ export { POOL_OWNED_KEYS } from './settings.js';
@@ -0,0 +1,189 @@
1
+ import { type PoolFailurePhase, type PoolOperation, PoolOperationError } from './errors.js';
2
+ import { type PoolLogger } from './hooks.js';
3
+ import { type LockEnvironment, LockStack, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
4
+ import { type PoolRow, type ProviderStateCodec, type QuotaCodec, type StoredCredential } from './schema.js';
5
+ /** Named points on the write path, for crash and ownership injection. */
6
+ export type WriteStep = 'before-config-write' | 'after-config-write' | 'before-state-write' | 'after-state-write';
7
+ /** Awaitable pause points on the pull and refresh paths. */
8
+ export type HoldPoint = 'refresh-before-provider' | 'pull-before-request';
9
+ export interface StoreContext {
10
+ provider: string;
11
+ configPath: string;
12
+ statePath: string;
13
+ codec: QuotaCodec;
14
+ /** The provider-state codec; without one no row shows a provider state. */
15
+ providerState?: ProviderStateCodec;
16
+ now: () => number;
17
+ storeLocks: readonly PoolLockSpec[];
18
+ lockDefaults: PoolLockOptions;
19
+ lockEnv: LockEnvironment;
20
+ logger?: PoolLogger;
21
+ onStep?: (step: WriteStep, info: {
22
+ operation: PoolOperation;
23
+ rowId: string | undefined;
24
+ }) => void | Promise<void>;
25
+ hold?: (point: HoldPoint, rowId: string) => void | Promise<void>;
26
+ /** Ids whose per-row entry a library write dropped in this process. */
27
+ removedIds: Set<string>;
28
+ /**
29
+ * When true, rows whose credential stamp is not bound load `unbound` and
30
+ * the operations that use a credential refuse them (see
31
+ * `OpenPoolStoreOptions.requireCredentialStamps`).
32
+ */
33
+ requireCredentialStamps?: boolean;
34
+ requireRemovedFingerprint?: boolean;
35
+ }
36
+ export interface Snapshot {
37
+ configExists: boolean;
38
+ stateExists: boolean;
39
+ config: Record<string, unknown>;
40
+ state: Record<string, unknown>;
41
+ rows: PoolRow[];
42
+ }
43
+ export type ReadResult = ({
44
+ status: 'ready';
45
+ } & Snapshot) | {
46
+ status: 'pending-migration';
47
+ config: Record<string, unknown>;
48
+ } | {
49
+ status: 'error';
50
+ file: 'config' | 'state';
51
+ reason: string;
52
+ kind?: 'snapshot-contended';
53
+ retryable?: true;
54
+ };
55
+ /**
56
+ * Validate config/state/config bytes without locks. Roster replacement writes
57
+ * config before deleting old state credentials. Re-read a changed config so an
58
+ * old roster cannot be paired with state from which its accounts were removed.
59
+ */
60
+ export declare function readPool(ctx: StoreContext): Promise<ReadResult>;
61
+ /** The refusal for a pool that is not ready, as a failure value. */
62
+ export declare function notReadyError(result: Exclude<ReadResult, {
63
+ status: 'ready';
64
+ }>, operation: PoolOperation, rowId: string | undefined, phase?: PoolFailurePhase): PoolOperationError;
65
+ /** What an operation has written so far; decides the failure phase. */
66
+ export interface Progress {
67
+ writes: number;
68
+ /** The roster config was renamed into place, or its committed receipt was found. */
69
+ publicationDecided?: true;
70
+ /** The credential the operation's state write put on disk, once it has. */
71
+ committed?: StoredCredential;
72
+ }
73
+ /**
74
+ * One locked read-modify-write. The store locks are pushed onto the caller's
75
+ * lock stack, so the ownership assertion before each write covers the outer
76
+ * row, provider-wide and extra locks as well; they are released when the
77
+ * transaction ends, whatever happens.
78
+ */
79
+ export declare class Transaction {
80
+ private readonly ctx;
81
+ readonly snapshot: Snapshot;
82
+ private readonly locks;
83
+ private readonly progress;
84
+ readonly info: {
85
+ operation: PoolOperation;
86
+ rowId: string | undefined;
87
+ };
88
+ config: Record<string, unknown>;
89
+ state: Record<string, unknown>;
90
+ constructor(ctx: StoreContext, snapshot: Snapshot, locks: LockStack, progress: Progress, info: {
91
+ operation: PoolOperation;
92
+ rowId: string | undefined;
93
+ });
94
+ /**
95
+ * The rows as every reader loads them: a row torn between the writes of a
96
+ * replace is shown as that replace leaves it once completed (see
97
+ * `PoolRow.torn`).
98
+ */
99
+ rows(): PoolRow[];
100
+ row(id: string): PoolRow | undefined;
101
+ /** Read the config marker, or an orphan's stamp; never use a caller's reservation. */
102
+ reservation(id: string): unknown;
103
+ assertNotStaged(id: string): void;
104
+ roster(): unknown[];
105
+ /** The first roster row with this id (the one the pool loads). */
106
+ rosterRow(id: string): Record<string, unknown> | undefined;
107
+ /**
108
+ * Drops every roster row carrying this id. The row's per-row entry goes with
109
+ * it on the next `commitConfig`, which drops entries for ids no longer in
110
+ * the roster. Returns how many roster rows were dropped.
111
+ */
112
+ dropRosterRows(id: string): number;
113
+ entries(): Record<string, unknown>;
114
+ entry(id: string): Record<string, unknown> | undefined;
115
+ setEntry(id: string, entry: Record<string, unknown>): void;
116
+ /**
117
+ * Writes the config of every row torn between the writes of a replace, or
118
+ * of a write giving it its first identity, as that write would have left it
119
+ * (see `completeTornRows`), in one config
120
+ * write ahead of the operation's own. The write is counted apart from the
121
+ * operation's: it is setup, like a pull giving a row its entry, so a later
122
+ * refusal still reports `before-first-write`.
123
+ */
124
+ completeTorn(): Promise<void>;
125
+ stateAccount(id: string): Record<string, unknown> | undefined;
126
+ /** Drops the row's credential and runtime fields from the state file's accounts. */
127
+ dropStateAccount(id: string): void;
128
+ setStateAccount(id: string, fields: Record<string, unknown>): void;
129
+ /**
130
+ * Writes the config: legacy `version: 1` and the legacy roster beside
131
+ * `commonAuthPool`, every other top-level key and every unrecognised pool
132
+ * key untouched. Entries for ids no longer in the roster are dropped here,
133
+ * and remembered so the id is not reused in this process. Every id the
134
+ * write drops (a roster row the files held when the transaction read them,
135
+ * or an entry left without one) has its epoch recorded in the config (see
136
+ * `retireEpochsIn`), which is what keeps a later `add` of the id, from any
137
+ * process, past every epoch an attribution could name.
138
+ */
139
+ commitConfig(options?: {
140
+ counted?: boolean;
141
+ durable?: boolean;
142
+ publicationDecision?: boolean;
143
+ }): Promise<void>;
144
+ /** Writes the state: every unrecognised top-level and per-row key kept. */
145
+ commitState(committed?: StoredCredential, options?: {
146
+ durable?: boolean;
147
+ }): Promise<void>;
148
+ assertAll(): Promise<void>;
149
+ syncState(): Promise<void>;
150
+ markPublicationDecision(): void;
151
+ syncConfig(): Promise<void>;
152
+ private write;
153
+ }
154
+ /** What `initializePool` did. */
155
+ export type InitializeOutcome = 'initialized' | 'already-ready';
156
+ /**
157
+ * Adds the pool key to a config that holds a legacy roster without one, under
158
+ * the store-lock list, in one config write: `commonAuthPool` with the schema
159
+ * version and no row entries, the legacy roster and every other top-level key
160
+ * kept, except the keys named in `dropKeys`. The state file is not touched.
161
+ * This is the only write the store makes to a pending-migration config; it is
162
+ * where a plugin's migration starts, and every later row goes through the
163
+ * ordinary operations. A config that is already a pool is left alone; a load
164
+ * error refuses.
165
+ */
166
+ export declare function initializePool(ctx: StoreContext, dropKeys: readonly string[]): Promise<InitializeOutcome>;
167
+ /**
168
+ * Runs `fn` under the store-lock list. The pool must be ready: a pending
169
+ * migration or a load error refuses before anything is written. Unless
170
+ * `completeTorn` is false, rows torn between the writes of a replace are
171
+ * completed first, so `fn` never sees one; the writes that only record
172
+ * readings or reorder the roster opt out and leave such rows as they are.
173
+ */
174
+ export declare function withTransaction<T>(ctx: StoreContext, locks: LockStack, progress: Progress, info: {
175
+ operation: PoolOperation;
176
+ rowId: string | undefined;
177
+ }, fn: (tx: Transaction) => Promise<T>, options?: {
178
+ completeTorn?: boolean;
179
+ }): Promise<T>;
180
+ /** Maps anything thrown inside an operation onto the one failure value. */
181
+ export declare function toFailure(error: unknown, operation: PoolOperation, rowId: string | undefined, progress: Progress): PoolOperationError;
182
+ /**
183
+ * The frame every lock-holding operation runs in: failures are mapped onto
184
+ * the failure value, handed to the failure hook while the outer locks are
185
+ * still held (the store locks are already released), and rethrown; every
186
+ * lock is released afterwards. `rowId` is undefined for an operation that
187
+ * names no row (`reorder`).
188
+ */
189
+ export declare function runOperation<T, R extends string | undefined = string>(ctx: StoreContext, operation: PoolOperation, rowId: R, onFailure: ((rowId: R, error: PoolOperationError) => void | Promise<void>) | undefined, body: (locks: LockStack, progress: Progress) => Promise<T>): Promise<T>;