@cortexkit/antigravity-auth-core 2.2.1 → 3.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 (230) hide show
  1. package/README.md +24 -2
  2. package/dist/abort-forwarding.d.ts +3 -0
  3. package/dist/abort-forwarding.d.ts.map +1 -0
  4. package/dist/abort-forwarding.js +19 -0
  5. package/dist/abort-forwarding.js.map +1 -0
  6. package/dist/account-identity.d.ts +52 -0
  7. package/dist/account-identity.d.ts.map +1 -0
  8. package/dist/account-identity.js +66 -0
  9. package/dist/account-identity.js.map +1 -0
  10. package/dist/account-manager.d.ts +228 -43
  11. package/dist/account-manager.d.ts.map +1 -1
  12. package/dist/account-manager.js +942 -903
  13. package/dist/account-manager.js.map +1 -1
  14. package/dist/account-migration.d.ts +309 -0
  15. package/dist/account-migration.d.ts.map +1 -0
  16. package/dist/account-migration.js +2282 -0
  17. package/dist/account-migration.js.map +1 -0
  18. package/dist/account-repository-codecs.d.ts +82 -0
  19. package/dist/account-repository-codecs.d.ts.map +1 -0
  20. package/dist/account-repository-codecs.js +609 -0
  21. package/dist/account-repository-codecs.js.map +1 -0
  22. package/dist/account-repository-types.d.ts +831 -0
  23. package/dist/account-repository-types.d.ts.map +1 -0
  24. package/dist/account-repository-types.js +137 -0
  25. package/dist/account-repository-types.js.map +1 -0
  26. package/dist/account-repository.d.ts +442 -0
  27. package/dist/account-repository.d.ts.map +1 -0
  28. package/dist/account-repository.js +2253 -0
  29. package/dist/account-repository.js.map +1 -0
  30. package/dist/account-selector.d.ts +470 -0
  31. package/dist/account-selector.d.ts.map +1 -0
  32. package/dist/account-selector.js +1383 -0
  33. package/dist/account-selector.js.map +1 -0
  34. package/dist/account-storage.d.ts.map +1 -1
  35. package/dist/account-storage.js +5 -0
  36. package/dist/account-storage.js.map +1 -1
  37. package/dist/agy-transport.d.ts.map +1 -1
  38. package/dist/agy-transport.js +21 -35
  39. package/dist/agy-transport.js.map +1 -1
  40. package/dist/antigravity-command-menu.d.ts +171 -0
  41. package/dist/antigravity-command-menu.d.ts.map +1 -0
  42. package/dist/antigravity-command-menu.js +523 -0
  43. package/dist/antigravity-command-menu.js.map +1 -0
  44. package/dist/atomic-write.d.ts +18 -1
  45. package/dist/atomic-write.d.ts.map +1 -1
  46. package/dist/atomic-write.js +15 -3
  47. package/dist/atomic-write.js.map +1 -1
  48. package/dist/common-auth-embedded/NOTICE.txt +21 -0
  49. package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
  50. package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
  51. package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
  52. package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
  53. package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
  54. package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
  55. package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
  56. package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
  57. package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
  58. package/dist/common-auth-embedded/auth-menu/index.js +12 -0
  59. package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
  60. package/dist/common-auth-embedded/auth-menu/login.js +83 -0
  61. package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
  62. package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
  63. package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
  64. package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
  65. package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
  66. package/dist/common-auth-embedded/auth-menu/select.js +159 -0
  67. package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
  68. package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
  69. package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
  70. package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
  71. package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
  72. package/dist/common-auth-embedded/claustrum/custody.js +354 -0
  73. package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
  74. package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
  75. package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
  76. package/dist/common-auth-embedded/claustrum/errors.js +8 -0
  77. package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
  78. package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
  79. package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
  80. package/dist/common-auth-embedded/claustrum/index.js +22 -0
  81. package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
  82. package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
  83. package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
  84. package/dist/common-auth-embedded/claustrum/roster.js +536 -0
  85. package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
  86. package/dist/common-auth-embedded/commands/builtins.js +534 -0
  87. package/dist/common-auth-embedded/commands/index.d.ts +10 -0
  88. package/dist/common-auth-embedded/commands/index.js +5 -0
  89. package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
  90. package/dist/common-auth-embedded/commands/menu.js +300 -0
  91. package/dist/common-auth-embedded/commands/model.d.ts +238 -0
  92. package/dist/common-auth-embedded/commands/model.js +16 -0
  93. package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
  94. package/dist/common-auth-embedded/commands/pi.js +206 -0
  95. package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
  96. package/dist/common-auth-embedded/commands/seam.js +301 -0
  97. package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
  98. package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
  99. package/dist/common-auth-embedded/fs/index.d.ts +7 -0
  100. package/dist/common-auth-embedded/fs/index.js +6 -0
  101. package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
  102. package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
  103. package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
  104. package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
  105. package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
  106. package/dist/common-auth-embedded/fs/with-lock.js +48 -0
  107. package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
  108. package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
  109. package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
  110. package/dist/common-auth-embedded/logger/engine.js +246 -0
  111. package/dist/common-auth-embedded/logger/index.d.ts +6 -0
  112. package/dist/common-auth-embedded/logger/index.js +3 -0
  113. package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
  114. package/dist/common-auth-embedded/logger/redact.js +83 -0
  115. package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
  116. package/dist/common-auth-embedded/pi-slot/index.js +2 -0
  117. package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
  118. package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
  119. package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
  120. package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
  121. package/dist/common-auth-embedded/quota/codec.js +12 -0
  122. package/dist/common-auth-embedded/quota/format.d.ts +46 -0
  123. package/dist/common-auth-embedded/quota/format.js +138 -0
  124. package/dist/common-auth-embedded/quota/index.d.ts +10 -0
  125. package/dist/common-auth-embedded/quota/index.js +5 -0
  126. package/dist/common-auth-embedded/quota/map.d.ts +63 -0
  127. package/dist/common-auth-embedded/quota/map.js +105 -0
  128. package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
  129. package/dist/common-auth-embedded/quota/merge.js +162 -0
  130. package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
  131. package/dist/common-auth-embedded/quota/projection.js +134 -0
  132. package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
  133. package/dist/common-auth-embedded/routing/admission.js +142 -0
  134. package/dist/common-auth-embedded/routing/index.d.ts +8 -0
  135. package/dist/common-auth-embedded/routing/index.js +4 -0
  136. package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
  137. package/dist/common-auth-embedded/routing/ordered.js +58 -0
  138. package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
  139. package/dist/common-auth-embedded/routing/pins.js +34 -0
  140. package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
  141. package/dist/common-auth-embedded/routing/sticky.js +339 -0
  142. package/dist/common-auth-embedded/source-output.json +1090 -0
  143. package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
  144. package/dist/common-auth-embedded/store/attribution.js +60 -0
  145. package/dist/common-auth-embedded/store/errors.d.ts +55 -0
  146. package/dist/common-auth-embedded/store/errors.js +38 -0
  147. package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
  148. package/dist/common-auth-embedded/store/hooks.js +34 -0
  149. package/dist/common-auth-embedded/store/identity.d.ts +39 -0
  150. package/dist/common-auth-embedded/store/identity.js +79 -0
  151. package/dist/common-auth-embedded/store/index.d.ts +21 -0
  152. package/dist/common-auth-embedded/store/index.js +7 -0
  153. package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
  154. package/dist/common-auth-embedded/store/mutate.js +500 -0
  155. package/dist/common-auth-embedded/store/pool.d.ts +196 -0
  156. package/dist/common-auth-embedded/store/pool.js +120 -0
  157. package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
  158. package/dist/common-auth-embedded/store/provider-state.js +226 -0
  159. package/dist/common-auth-embedded/store/publication.d.ts +47 -0
  160. package/dist/common-auth-embedded/store/publication.js +304 -0
  161. package/dist/common-auth-embedded/store/pull.d.ts +36 -0
  162. package/dist/common-auth-embedded/store/pull.js +143 -0
  163. package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
  164. package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
  165. package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
  166. package/dist/common-auth-embedded/store/refresh.js +225 -0
  167. package/dist/common-auth-embedded/store/reserved.js +13 -0
  168. package/dist/common-auth-embedded/store/rows.d.ts +313 -0
  169. package/dist/common-auth-embedded/store/rows.js +1258 -0
  170. package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
  171. package/dist/common-auth-embedded/store/runtime.js +68 -0
  172. package/dist/common-auth-embedded/store/schema.d.ts +443 -0
  173. package/dist/common-auth-embedded/store/schema.js +754 -0
  174. package/dist/common-auth-embedded/store/settings.d.ts +63 -0
  175. package/dist/common-auth-embedded/store/settings.js +120 -0
  176. package/dist/common-auth-embedded/store/torn.d.ts +78 -0
  177. package/dist/common-auth-embedded/store/torn.js +294 -0
  178. package/dist/common-auth-embedded/util/any-signal.js +42 -0
  179. package/dist/common-auth-runtime.d.ts +73 -0
  180. package/dist/common-auth-runtime.d.ts.map +1 -0
  181. package/dist/common-auth-runtime.js +65 -0
  182. package/dist/common-auth-runtime.js.map +1 -0
  183. package/dist/credential-redaction.d.ts +13 -0
  184. package/dist/credential-redaction.d.ts.map +1 -0
  185. package/dist/credential-redaction.js +230 -0
  186. package/dist/credential-redaction.js.map +1 -0
  187. package/dist/fetch-timeout.d.ts +4 -2
  188. package/dist/fetch-timeout.d.ts.map +1 -1
  189. package/dist/fetch-timeout.js +121 -14
  190. package/dist/fetch-timeout.js.map +1 -1
  191. package/dist/index.d.ts +16 -1
  192. package/dist/index.d.ts.map +1 -1
  193. package/dist/index.js +15 -0
  194. package/dist/index.js.map +1 -1
  195. package/dist/quota-manager.d.ts.map +1 -1
  196. package/dist/quota-manager.js +35 -10
  197. package/dist/quota-manager.js.map +1 -1
  198. package/dist/quota-presentation.d.ts +10 -0
  199. package/dist/quota-presentation.d.ts.map +1 -0
  200. package/dist/quota-presentation.js +60 -0
  201. package/dist/quota-presentation.js.map +1 -0
  202. package/dist/quota-types.d.ts +2 -0
  203. package/dist/quota-types.d.ts.map +1 -1
  204. package/dist/rotation.d.ts +8 -0
  205. package/dist/rotation.d.ts.map +1 -1
  206. package/dist/rotation.js +24 -0
  207. package/dist/rotation.js.map +1 -1
  208. package/dist/transform/claude.d.ts +7 -3
  209. package/dist/transform/claude.d.ts.map +1 -1
  210. package/dist/transform/claude.js +6 -2
  211. package/dist/transform/claude.js.map +1 -1
  212. package/dist/transform/types.d.ts +1 -1
  213. package/dist/transform/types.d.ts.map +1 -1
  214. package/dist/vault-account-source.d.ts +183 -0
  215. package/dist/vault-account-source.d.ts.map +1 -0
  216. package/dist/vault-account-source.js +296 -0
  217. package/dist/vault-account-source.js.map +1 -0
  218. package/dist/vault-enrollment-authority.d.ts +16 -0
  219. package/dist/vault-enrollment-authority.d.ts.map +1 -0
  220. package/dist/vault-enrollment-authority.js +19 -0
  221. package/dist/vault-enrollment-authority.js.map +1 -0
  222. package/dist/vault-family.d.ts +11 -0
  223. package/dist/vault-family.d.ts.map +1 -0
  224. package/dist/vault-family.js +24 -0
  225. package/dist/vault-family.js.map +1 -0
  226. package/dist/vault-provider-state.d.ts +162 -0
  227. package/dist/vault-provider-state.d.ts.map +1 -0
  228. package/dist/vault-provider-state.js +422 -0
  229. package/dist/vault-provider-state.js.map +1 -0
  230. package/package.json +39 -2
@@ -0,0 +1,196 @@
1
+ import { type Attribution } from './attribution.js';
2
+ import type { PoolOperationError } from './errors.js';
3
+ import type { PoolLogger } from './hooks.js';
4
+ import { type HoldPoint, type InitializeOutcome, type StoreContext } from './mutate.js';
5
+ import { type ProviderStateMutator, type UpdateProviderStateResult } from './provider-state.js';
6
+ import { type PublicationReceipt, type PublishOptions, type PublishPlan, type PublishResult } from './publication.js';
7
+ import { type PullHook } from './pull.js';
8
+ import { type ProviderRefresh, type RefreshOptions, type RefreshOutcome } from './refresh.js';
9
+ import { type LockEnvironment, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
10
+ import { type AddInput, type AddOptions, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions, type RowTransitionOptions, type RowTransitionResult, type RowWriteOptions } from './rows.js';
11
+ import { type PoolCredential, type PoolRow, type ProviderStateCodec, type QuotaCodec, type RotateCredential, type StoredCredential } from './schema.js';
12
+ import { type SettingsMutator, type SettingsRead, type UpdateSettingsOptions, type UpdateSettingsResult } from './settings.js';
13
+ export interface OpenPoolStoreOptions {
14
+ /** The provider every row of this pool belongs to; keys the provider-wide lock. */
15
+ provider: string;
16
+ configPath: string;
17
+ statePath: string;
18
+ quota: QuotaCodec;
19
+ /**
20
+ * The codec of the provider state kept beside each row's credential (see
21
+ * `ProviderStateCodec`). Without it no row shows a provider state, and
22
+ * every write that would set one refuses (`invalid-input`); writes that
23
+ * leave it alone keep the value on disk as it is, and `replace` clears it.
24
+ */
25
+ providerState?: ProviderStateCodec;
26
+ /**
27
+ * Refuse every credential this store did not stamp (default false, which
28
+ * loads unstamped and mis-stamped credentials as older writers left them).
29
+ * When true, a row whose `stamp` is not `bound` loads `unbound` and is no
30
+ * candidate, and every operation that would use or keep its credential or
31
+ * what was observed about it (`refresh`, quota pulls, `recordQuota`,
32
+ * `recordIdentity`, `rotate`, and an `add` of the same secret or onto the
33
+ * same credential-less row) refuses with `unbound-credential` before any
34
+ * provider call or write, checked again under the locks and at commit.
35
+ * Nothing makes such a row bound except `replace`, which starts a new
36
+ * credential epoch and drops what was observed about the old one.
37
+ * An interrupted add's state-only orphan is recovered only when its stamp
38
+ * binds the stored and incoming material, including a supplied identity and
39
+ * an API key's endpoint/header. Otherwise add refuses `unbound-credential`
40
+ * without writing; `remove(id)` explicitly discards the orphan.
41
+ */
42
+ requireCredentialStamps?: boolean;
43
+ /** Require an exact secret fingerprint for every roster-publication removal. */
44
+ requireRemovedFingerprint?: boolean;
45
+ /** Injected clock for leases, refresh stamps and `addedAt`. */
46
+ now?: () => number;
47
+ /**
48
+ * Ordered store-lock list held across every read-modify-write. Defaults to
49
+ * the older writers' `save` lock at the config path, then at the state path.
50
+ */
51
+ storeLocks?: readonly PoolLockSpec[];
52
+ /** Defaults for every lock the store takes (see `POOL_LOCK_DEFAULTS`). */
53
+ lockOptions?: Partial<PoolLockOptions>;
54
+ /** Overrides for the row locks only. */
55
+ rowLockOptions?: Partial<PoolLockOptions>;
56
+ /** The provider-wide lock; defaults to `provider-<provider>` beside the state file. */
57
+ providerLock?: PoolLockSpec;
58
+ /** Quota pull hook, fired without being awaited. */
59
+ pull?: PullHook;
60
+ /** Receives every pull failure, since no caller awaits a pull. */
61
+ onPullFailure?: (rowId: string, error: PoolOperationError) => void | Promise<void>;
62
+ logger?: PoolLogger;
63
+ /** Named write steps, awaited; a test seam for crash and ownership rows. */
64
+ onStep?: StoreContext['onStep'];
65
+ /** Awaitable hold points on the refresh and pull paths; a test seam. */
66
+ hold?: (point: HoldPoint, rowId: string) => void | Promise<void>;
67
+ onLockEvent?: LockEnvironment['onLockEvent'];
68
+ onLockStep?: LockEnvironment['onLockStep'];
69
+ }
70
+ export type PoolLoad = {
71
+ status: 'ready';
72
+ schemaVersion: number;
73
+ rows: PoolRow[];
74
+ } | {
75
+ status: 'pending-migration';
76
+ roster: unknown[];
77
+ } | {
78
+ status: 'error';
79
+ file: 'config' | 'state';
80
+ reason: string;
81
+ kind?: 'snapshot-contended';
82
+ retryable?: true;
83
+ };
84
+ export interface PoolStore {
85
+ /** Reads the pool and fires first-reading pulls; never writes a file itself. */
86
+ load(): Promise<PoolLoad>;
87
+ /** Reads the pool without firing anything. */
88
+ read(): Promise<PoolLoad>;
89
+ /**
90
+ * Turns a pending-migration config into an empty pool (the pool key with no
91
+ * row entries) in one locked config write, dropping the named top-level
92
+ * keys and keeping the rest. The start of a plugin's migration; a ready
93
+ * pool is left alone. Failures carry operation `initialize`.
94
+ */
95
+ initialize(input?: {
96
+ dropKeys?: readonly string[];
97
+ }): Promise<{
98
+ status: InitializeOutcome;
99
+ }>;
100
+ /**
101
+ * Adds a row, or completes or rotates the row already holding the id or
102
+ * the secret. A new row starts at credential epoch 1; since 0.8.0 one
103
+ * whose id the pool held before starts one past the highest epoch that id
104
+ * held (see `Attribution`), and an id that held `Number.MAX_SAFE_INTEGER`
105
+ * refuses (`id-removed`) before writing.
106
+ */
107
+ add(input: AddInput, options?: AddOptions): Promise<AddResult>;
108
+ publishRoster(plan: PublishPlan, options?: PublishOptions): Promise<PublishResult>;
109
+ publication(operationId: string): Promise<PublicationReceipt | undefined>;
110
+ /**
111
+ * Gives a row a new credential and a new credential epoch. Since 0.6.0 the
112
+ * row's provider state is whatever `ProviderStateCodec.onReplace` returns;
113
+ * without that hook it is `input.providerState`, else cleared.
114
+ * Optional `attribution` fences the write on the prior credential under the
115
+ * locks, before any write or replacement hook; see `RowWriteOptions`.
116
+ */
117
+ replace(id: string, credential: PoolCredential, input?: CredentialWriteInput, options?: RowWriteOptions): Promise<{
118
+ id: string;
119
+ credential: StoredCredential;
120
+ credentialEpoch: number;
121
+ }>;
122
+ /**
123
+ * Refreshes the secret a row holds without changing its account or
124
+ * endpoint. Since 0.4.1 an API key may leave out `baseURL` and `authHeader`
125
+ * to keep the row's, and one that gives another is refused
126
+ * (`endpoint-mismatch`) before writing: that is a `replace`.
127
+ * Takes the same optional attribution fence as `replace` (`RowWriteOptions`).
128
+ */
129
+ rotate(id: string, credential: RotateCredential, input?: CredentialWriteInput, options?: RowWriteOptions): Promise<{
130
+ id: string;
131
+ credential: StoredCredential;
132
+ }>;
133
+ /**
134
+ * Changes a row's provider state without touching its credential (since
135
+ * 0.6.0), under the row lock, `extraLocks` and the store locks. Refuses
136
+ * (`attribution`) once the row has moved off the credential epoch or
137
+ * identity in `fence`, and (`unknown-row`) once it is removed.
138
+ */
139
+ updateProviderState(id: string, fence: Attribution, mutator: ProviderStateMutator, options?: RowToggleOptions): Promise<UpdateProviderStateResult>;
140
+ /**
141
+ * Sets `enabled: false` and the entry's `disabledReason`. Takes the row
142
+ * lock, then `extraLocks`, then the store locks (the row lock and
143
+ * `extraLocks` since 0.2.3). Since 0.7.0 it may be fenced on the
144
+ * credential the caller's evidence is about (`attribution`) and carry a
145
+ * provider-state change that lands with it (`providerState`); see
146
+ * `RowTransitionOptions`.
147
+ */
148
+ disable(id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
149
+ /**
150
+ * Clears `enabled: false` and `disabledReason` (since 0.2.3); refuses with
151
+ * `duplicate-identity` when another enabled OAuth row holds the row's
152
+ * identity. Locks as `disable`, and takes the same options since 0.7.0.
153
+ */
154
+ enable(id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
155
+ /**
156
+ * Deletes the roster row, its per-row entry and its state-file credential
157
+ * (since 0.2.3). Locks as `disable`; `protect` can refuse the id.
158
+ */
159
+ remove(id: string, options?: RemoveOptions): Promise<RemoveResult>;
160
+ /**
161
+ * Sets the roster order (since 0.2.4) in one config write. `ids` must name
162
+ * every roster id exactly once; anything else refuses with `invalid-order`
163
+ * and writes nothing. Takes `extraLocks`, then the store locks; no row or
164
+ * provider-wide lock. Roster rows and their entries are left unchanged.
165
+ */
166
+ reorder(ids: readonly string[], options?: ReorderOptions): Promise<ReorderResult>;
167
+ /**
168
+ * The plugin's settings (since 0.2.6): every top-level key of the config
169
+ * file except the pool-owned ones (`POOL_OWNED_KEYS`). Never writes.
170
+ */
171
+ readSettings(): Promise<SettingsRead>;
172
+ /**
173
+ * One locked write of the plugin's settings (since 0.2.6) beside the pool,
174
+ * in the config file. Refuses a result that sets a pool-owned key
175
+ * (`invalid-input`). Takes `extraLocks`, then the store locks.
176
+ */
177
+ updateSettings(mutator: SettingsMutator, options?: UpdateSettingsOptions): Promise<UpdateSettingsResult>;
178
+ /**
179
+ * Records the identity a lookup found for the row's credential. Since
180
+ * 0.3.1 it takes the credential epoch the lookup was issued for, and
181
+ * refuses a lookup that completes after the row was replaced
182
+ * (`attribution`) or a row recorded for another account
183
+ * (`identity-mismatch`).
184
+ */
185
+ recordIdentity(id: string, identity: string, attribution: Pick<Attribution, 'credentialEpoch'>, options?: RowOperationOptions): Promise<{
186
+ id: string;
187
+ disabled: string[];
188
+ }>;
189
+ refresh(id: string, provider: ProviderRefresh, options?: RefreshOptions): Promise<RefreshOutcome>;
190
+ recordQuota(id: string, attribution: Attribution, observation: unknown): Promise<void>;
191
+ /** Fires a pull for a row admission refused for want of a reading. */
192
+ requestReading(id: string): void;
193
+ /** Resolves once every pull fired so far has settled. */
194
+ pullsSettled(): Promise<void>;
195
+ }
196
+ export declare function openPoolStore(options: OpenPoolStoreOptions): PoolStore;
@@ -0,0 +1,120 @@
1
+ import { recordQuota } from './attribution.js';
2
+ import { initializePool, readPool, } from './mutate.js';
3
+ import { updateProviderStateRow, } from './provider-state.js';
4
+ import { publication, publishRoster, } from './publication.js';
5
+ import { PullScheduler } from './pull.js';
6
+ import { refreshRow, } from './refresh.js';
7
+ import { POOL_LOCK_DEFAULTS, } from './refresh-lock.js';
8
+ import { addRow, disableRow, enableRow, recordRowIdentity, removeRow, reorderRows, replaceRow, rotateRow, } from './rows.js';
9
+ import { POOL_SCHEMA_VERSION, } from './schema.js';
10
+ import { readPoolSettings, updatePoolSettings, } from './settings.js';
11
+ /**
12
+ * Process-wide memory per config file: ids whose per-row entry a library
13
+ * write dropped (not reused in this process), and rows a load-time pull
14
+ * already fired for.
15
+ */
16
+ const processMemory = new Map();
17
+ function memoryFor(configPath) {
18
+ let memory = processMemory.get(configPath);
19
+ if (!memory) {
20
+ memory = { removedIds: new Set(), firedAtLoad: new Set() };
21
+ processMemory.set(configPath, memory);
22
+ }
23
+ return memory;
24
+ }
25
+ function toLoad(result) {
26
+ if (result.status === 'ready')
27
+ return {
28
+ status: 'ready',
29
+ schemaVersion: POOL_SCHEMA_VERSION,
30
+ rows: result.rows,
31
+ };
32
+ if (result.status === 'pending-migration')
33
+ return {
34
+ status: 'pending-migration',
35
+ roster: Array.isArray(result.config.accounts)
36
+ ? result.config.accounts
37
+ : [],
38
+ };
39
+ return result;
40
+ }
41
+ export function openPoolStore(options) {
42
+ const memory = memoryFor(options.configPath);
43
+ const lockDefaults = {
44
+ ...POOL_LOCK_DEFAULTS,
45
+ ...options.lockOptions,
46
+ };
47
+ const ctx = {
48
+ provider: options.provider,
49
+ configPath: options.configPath,
50
+ statePath: options.statePath,
51
+ codec: options.quota,
52
+ ...(options.providerState ? { providerState: options.providerState } : {}),
53
+ now: options.now ?? Date.now,
54
+ storeLocks: options.storeLocks ?? [
55
+ { name: 'save', path: options.configPath },
56
+ { name: 'save', path: options.statePath },
57
+ ],
58
+ lockDefaults,
59
+ lockEnv: {
60
+ now: options.now ?? Date.now,
61
+ ...(options.onLockEvent ? { onLockEvent: options.onLockEvent } : {}),
62
+ ...(options.onLockStep ? { onLockStep: options.onLockStep } : {}),
63
+ },
64
+ removedIds: memory.removedIds,
65
+ requireCredentialStamps: options.requireCredentialStamps === true,
66
+ requireRemovedFingerprint: options.requireRemovedFingerprint === true,
67
+ ...(options.logger ? { logger: options.logger } : {}),
68
+ ...(options.onStep ? { onStep: options.onStep } : {}),
69
+ ...(options.hold ? { hold: options.hold } : {}),
70
+ };
71
+ const pulls = new PullScheduler(() => rt, options.pull, options.onPullFailure, memory.firedAtLoad, options.logger);
72
+ const rt = {
73
+ ctx,
74
+ providerLock: options.providerLock ?? {
75
+ name: `provider-${encodeURIComponent(options.provider)}`,
76
+ path: options.statePath,
77
+ },
78
+ rowLockOptions: options.rowLockOptions ?? {},
79
+ firePull: (id, reason) => pulls.fire(id, reason),
80
+ };
81
+ return {
82
+ async load() {
83
+ const result = await readPool(ctx);
84
+ if (result.status === 'ready') {
85
+ // A torn row is no candidate, but its pull is fired too: the pull
86
+ // completes the row on disk before reading it, so an interrupted
87
+ // replace of an OAuth row heals at the next load.
88
+ for (const row of result.rows)
89
+ if ((row.candidate || (row.torn && row.enabled)) &&
90
+ row.type === 'oauth' &&
91
+ row.needsFirstReading)
92
+ pulls.fire(row.id, 'load');
93
+ }
94
+ return toLoad(result);
95
+ },
96
+ async read() {
97
+ return toLoad(await readPool(ctx));
98
+ },
99
+ async initialize(input = {}) {
100
+ return { status: await initializePool(ctx, input.dropKeys ?? []) };
101
+ },
102
+ add: (input, callOptions) => addRow(rt, input, callOptions),
103
+ publishRoster: (plan, callOptions) => publishRoster(rt, plan, callOptions),
104
+ publication: (operationId) => publication(rt, operationId),
105
+ replace: (id, credential, input, callOptions) => replaceRow(rt, id, credential, input, callOptions),
106
+ rotate: (id, credential, input, callOptions) => rotateRow(rt, id, credential, input, callOptions),
107
+ updateProviderState: (id, fence, mutator, callOptions) => updateProviderStateRow(rt, id, fence, mutator, callOptions),
108
+ disable: (id, reason, callOptions) => disableRow(rt, id, reason, callOptions),
109
+ enable: (id, callOptions) => enableRow(rt, id, callOptions),
110
+ remove: (id, callOptions) => removeRow(rt, id, callOptions),
111
+ reorder: (ids, callOptions) => reorderRows(rt, ids, callOptions),
112
+ readSettings: () => readPoolSettings(rt),
113
+ updateSettings: (mutator, callOptions) => updatePoolSettings(rt, mutator, callOptions),
114
+ recordIdentity: (id, identity, attribution, callOptions) => recordRowIdentity(rt, id, identity, attribution, callOptions),
115
+ refresh: (id, provider, callOptions) => refreshRow(rt, id, provider, callOptions),
116
+ recordQuota: (id, attribution, observation) => recordQuota(rt, id, attribution, observation),
117
+ requestReading: (id) => pulls.fire(id, 'admission'),
118
+ pullsSettled: () => pulls.settled(),
119
+ };
120
+ }
@@ -0,0 +1,131 @@
1
+ import type { Attribution } from './attribution.js';
2
+ import type { PoolOperation } from './errors.js';
3
+ import { type Transaction } from './mutate.js';
4
+ import type { RowToggleOptions } from './rows.js';
5
+ import { type StoreRuntime } from './runtime.js';
6
+ import { type PoolRow, type ProviderStateCodec } from './schema.js';
7
+ /**
8
+ * What a credential write does to the provider state beside it: `keep`
9
+ * leaves the value on disk as it is (bound by the new stamp only if the old
10
+ * stamp bound it to the same row, epoch and identity), `set` stores a value
11
+ * the codec accepted, and `clear` deletes it.
12
+ */
13
+ export type ProviderStateWrite = {
14
+ kind: 'keep';
15
+ } | {
16
+ kind: 'set';
17
+ value: unknown;
18
+ } | {
19
+ kind: 'clear';
20
+ };
21
+ /**
22
+ * A provider state as the store will store it: a JSON round trip of it (so
23
+ * its digest is the same once read back from disk), accepted by the codec.
24
+ * Refused before anything is written when the store has no codec, the value
25
+ * is not JSON, or the codec rejects it.
26
+ */
27
+ export declare function acceptProviderState(codec: ProviderStateCodec | undefined, operation: PoolOperation, id: string, value: unknown, what?: string): unknown;
28
+ /**
29
+ * The write for a value a credential write brings (`add` of a secret the
30
+ * pool holds, `rotate`, a refresh): merged with the row's value on disk by
31
+ * the codec's `merge` when both exist, else the incoming value as is.
32
+ */
33
+ export declare function mergedProviderState(codec: ProviderStateCodec | undefined, operation: PoolOperation, id: string, onDisk: unknown, incoming: unknown): ProviderStateWrite;
34
+ /**
35
+ * The provider state a replace leaves on the row, decided before anything is
36
+ * written: whatever the codec's `onReplace` returns (undefined clears it), or
37
+ * without that hook the value handed to `replace`, else nothing. The old
38
+ * credential's value is never kept by default: it describes the account the
39
+ * replaced credential belonged to.
40
+ */
41
+ export declare function replacementProviderState(codec: ProviderStateCodec | undefined, row: PoolRow, credentialEpoch: number, identity: string | undefined, incoming: unknown): ProviderStateWrite;
42
+ /**
43
+ * The provider-state digest the stamp of a credential write carries, and the
44
+ * state-file fields it changes. `set` binds the new value; `keep` carries the
45
+ * old stamp's digest forward only when that stamp bound it to this row as it
46
+ * stood before the write (same credential lineage, the epoch being written,
47
+ * the identity recorded before the write); `clear` binds nothing.
48
+ */
49
+ export declare function providerStateCoverage(codec: ProviderStateCodec | undefined, write: ProviderStateWrite, prior: Record<string, unknown> | undefined, priorRow: PoolRow | undefined, credentialEpoch: number): string | undefined;
50
+ /**
51
+ * Receives a private copy of the row's provider state (undefined when the row
52
+ * shows none) and the row as loaded under the locks, and returns the next
53
+ * provider state; returning undefined clears it, so a mutator that means to
54
+ * keep the value returns it. It runs under the row lock and the store locks,
55
+ * so it must not call back into the store (`PoolReentryError`).
56
+ */
57
+ export type ProviderStateMutator = (current: unknown | undefined, row: PoolRow) => unknown | Promise<unknown>;
58
+ export type UpdateProviderStateResult = {
59
+ id: string;
60
+ /** The provider state now on disk; absent when the row has none. */
61
+ providerState?: unknown;
62
+ /**
63
+ * `unchanged`: the mutator returned what the row already shows (or cleared
64
+ * a row that holds none); nothing was written.
65
+ */
66
+ outcome: 'updated' | 'cleared' | 'unchanged';
67
+ };
68
+ /**
69
+ * Changes a row's provider state without touching its credential, in one
70
+ * state-file write under the row lock, the caller's extra locks and the
71
+ * store locks. `fence` is what the caller read the row at: the write is
72
+ * refused (`attribution`, retryable) when the row has since moved to another
73
+ * credential epoch or recorded identity, and (`unknown-row`) once it is
74
+ * removed, so a writer that read the row before a replace or a removal never
75
+ * lands its value on the new credential or brings a removed row's state back.
76
+ *
77
+ * The value is bound by the stamp already beside the credential. When its
78
+ * credential-bound part is unchanged the stamp is left byte for byte as it
79
+ * is; otherwise only the stamp's provider-state digest changes, so the
80
+ * credential's stamp status never moves. With `requireCredentialStamps`, an
81
+ * unbound row refuses (`unbound-credential`) as every other strict path
82
+ * does. Without it, a row whose stamp was not written by this store with this
83
+ * credential at the row's epoch and identity refuses the same way: no stamp
84
+ * could bind the value, so no reader would ever show it. A `rotate` or
85
+ * `replace` stamps such a row.
86
+ */
87
+ export declare function updateProviderStateRow(rt: StoreRuntime, id: string, fence: Attribution, mutator: ProviderStateMutator, options?: RowToggleOptions): Promise<UpdateProviderStateResult>;
88
+ /**
89
+ * Returned by the provider-state mutator of an attributed `disable` or
90
+ * `enable` to decline the whole transition: nothing is written, neither the
91
+ * provider state nor the row's enabled flag, and the call resolves with
92
+ * `declined: true`. A mutator declines when the state it is shown is newer
93
+ * than what its caller saw, such as an eligibility recorded after the
94
+ * request whose refusal is being acted on. It is a value of its own because
95
+ * `undefined` already means "clear the provider state". `Symbol.for` keeps it
96
+ * equal across two copies of this module loaded in one process.
97
+ */
98
+ export declare const DECLINE_TRANSITION: unique symbol;
99
+ /**
100
+ * The provider-state mutator of an attributed `disable` or `enable`: as
101
+ * `ProviderStateMutator`, and it may also return `DECLINE_TRANSITION`.
102
+ */
103
+ export type RowTransitionMutator = (current: unknown | undefined, row: PoolRow) => unknown | typeof DECLINE_TRANSITION | Promise<unknown | typeof DECLINE_TRANSITION>;
104
+ /**
105
+ * What a provider-state mutator asks of a row, worked out under the locks
106
+ * before anything is written. `changed` carries the row's whole next
107
+ * state-file account entry (the value, and the stamp rebound to it when its
108
+ * credential-bound part moved); `value` is the next value, absent when it is
109
+ * cleared.
110
+ */
111
+ export type ProviderStatePlan = {
112
+ kind: 'declined';
113
+ } | {
114
+ kind: 'unchanged';
115
+ value?: unknown;
116
+ } | {
117
+ kind: 'changed';
118
+ value?: unknown;
119
+ account: Record<string, unknown>;
120
+ };
121
+ /**
122
+ * Runs a provider-state mutator for a row loaded under every lock and
123
+ * already checked by the caller (present, valid, inside its attribution
124
+ * fence), and plans the write. Refuses (`no-credential`) a row holding no
125
+ * credential, and (`unbound-credential`) one whose credential carries no
126
+ * stamp of this store at the row's epoch and identity: no stamp could bind
127
+ * the value, so no reader would ever show it. `DECLINE_TRANSITION` is
128
+ * honoured only when `declinable` is set; elsewhere it is not JSON and is
129
+ * refused as such.
130
+ */
131
+ export declare function planProviderStateIn(tx: Transaction, codec: ProviderStateCodec, operation: PoolOperation, row: PoolRow, mutator: ProviderStateMutator | RowTransitionMutator, declinable?: boolean): Promise<ProviderStatePlan>;
@@ -0,0 +1,226 @@
1
+ import { assertNotInsideHook, runInsideHook } from './hooks.js';
2
+ import { runOperation, withTransaction } from './mutate.js';
3
+ import { readRow, refusal, requireBound, rowLockSpec, unknownRow, } from './runtime.js';
4
+ import { boundProviderStateDigest, CREDENTIAL_STAMP_KEY, credentialDigest, isCredentialEpoch, isRecord, PROVIDER_STATE_KEY, parseStamp, providerStateDigest, rowLockKey, } from './schema.js';
5
+ /**
6
+ * A provider state as the store will store it: a JSON round trip of it (so
7
+ * its digest is the same once read back from disk), accepted by the codec.
8
+ * Refused before anything is written when the store has no codec, the value
9
+ * is not JSON, or the codec rejects it.
10
+ */
11
+ export function acceptProviderState(codec, operation, id, value, what = 'the provider state') {
12
+ if (!codec)
13
+ throw refusal(operation, id, 'invalid-input', 'the store was opened without a provider-state codec');
14
+ let normalized;
15
+ try {
16
+ const text = JSON.stringify(value);
17
+ normalized = text === undefined ? undefined : JSON.parse(text);
18
+ }
19
+ catch {
20
+ normalized = undefined;
21
+ }
22
+ if (normalized === undefined)
23
+ throw refusal(operation, id, 'invalid-provider-state', `${what} cannot be stored as JSON`);
24
+ if (!codec.validate(normalized))
25
+ throw refusal(operation, id, 'invalid-provider-state', `the provider-state codec rejected ${what}`);
26
+ return normalized;
27
+ }
28
+ /**
29
+ * The write for a value a credential write brings (`add` of a secret the
30
+ * pool holds, `rotate`, a refresh): merged with the row's value on disk by
31
+ * the codec's `merge` when both exist, else the incoming value as is.
32
+ */
33
+ export function mergedProviderState(codec, operation, id, onDisk, incoming) {
34
+ if (onDisk === undefined || !codec?.merge)
35
+ return { kind: 'set', value: incoming };
36
+ return {
37
+ kind: 'set',
38
+ value: acceptProviderState(codec, operation, id, codec.merge(structuredClone(onDisk), structuredClone(incoming)), 'the merged provider state'),
39
+ };
40
+ }
41
+ /**
42
+ * The provider state a replace leaves on the row, decided before anything is
43
+ * written: whatever the codec's `onReplace` returns (undefined clears it), or
44
+ * without that hook the value handed to `replace`, else nothing. The old
45
+ * credential's value is never kept by default: it describes the account the
46
+ * replaced credential belonged to.
47
+ */
48
+ export function replacementProviderState(codec, row, credentialEpoch, identity, incoming) {
49
+ const hook = codec?.onReplace;
50
+ const next = hook
51
+ ? hook(row.providerState === undefined
52
+ ? undefined
53
+ : structuredClone(row.providerState), {
54
+ id: row.id,
55
+ credentialEpoch,
56
+ ...(row.identity !== undefined
57
+ ? { previousIdentity: row.identity }
58
+ : {}),
59
+ ...(identity !== undefined ? { identity } : {}),
60
+ ...(incoming !== undefined
61
+ ? { incoming: structuredClone(incoming) }
62
+ : {}),
63
+ })
64
+ : incoming;
65
+ if (next === undefined)
66
+ return { kind: 'clear' };
67
+ if (!hook)
68
+ return { kind: 'set', value: next };
69
+ return {
70
+ kind: 'set',
71
+ value: acceptProviderState(codec, 'replace', row.id, next, 'the provider state onReplace returned'),
72
+ };
73
+ }
74
+ /**
75
+ * The provider-state digest the stamp of a credential write carries, and the
76
+ * state-file fields it changes. `set` binds the new value; `keep` carries the
77
+ * old stamp's digest forward only when that stamp bound it to this row as it
78
+ * stood before the write (same credential lineage, the epoch being written,
79
+ * the identity recorded before the write); `clear` binds nothing.
80
+ */
81
+ export function providerStateCoverage(codec, write, prior, priorRow, credentialEpoch) {
82
+ if (write.kind === 'set')
83
+ return providerStateDigest(codec, write.value);
84
+ if (write.kind === 'clear')
85
+ return undefined;
86
+ return boundProviderStateDigest(prior, priorRow?.credential, credentialEpoch, priorRow?.identity);
87
+ }
88
+ /**
89
+ * Changes a row's provider state without touching its credential, in one
90
+ * state-file write under the row lock, the caller's extra locks and the
91
+ * store locks. `fence` is what the caller read the row at: the write is
92
+ * refused (`attribution`, retryable) when the row has since moved to another
93
+ * credential epoch or recorded identity, and (`unknown-row`) once it is
94
+ * removed, so a writer that read the row before a replace or a removal never
95
+ * lands its value on the new credential or brings a removed row's state back.
96
+ *
97
+ * The value is bound by the stamp already beside the credential. When its
98
+ * credential-bound part is unchanged the stamp is left byte for byte as it
99
+ * is; otherwise only the stamp's provider-state digest changes, so the
100
+ * credential's stamp status never moves. With `requireCredentialStamps`, an
101
+ * unbound row refuses (`unbound-credential`) as every other strict path
102
+ * does. Without it, a row whose stamp was not written by this store with this
103
+ * credential at the row's epoch and identity refuses the same way: no stamp
104
+ * could bind the value, so no reader would ever show it. A `rotate` or
105
+ * `replace` stamps such a row.
106
+ */
107
+ export async function updateProviderStateRow(rt, id, fence, mutator, options = {}) {
108
+ assertNotInsideHook('updateProviderState');
109
+ const { ctx } = rt;
110
+ const operation = 'updateProviderState';
111
+ return runOperation(ctx, operation, id, options.onFailure, async (locks, progress) => {
112
+ const codec = ctx.providerState;
113
+ if (!codec)
114
+ throw refusal(operation, id, 'invalid-input', 'the store was opened without a provider-state codec');
115
+ if (typeof mutator !== 'function')
116
+ throw refusal(operation, id, 'invalid-input', 'a mutator is required');
117
+ const captured = isRecord(fence) ? fence.credentialEpoch : undefined;
118
+ if (!isCredentialEpoch(captured))
119
+ throw refusal(operation, id, 'invalid-input', 'the credential epoch the provider state was read at is required');
120
+ const { row: seen } = await readRow(rt, operation, id);
121
+ await locks.acquire(rowLockSpec(rt, seen));
122
+ for (const extra of options.extraLocks ?? [])
123
+ await locks.acquire(extra);
124
+ return withTransaction(ctx, locks, progress, { operation, rowId: id }, async (tx) => {
125
+ const row = tx.row(id);
126
+ if (!row)
127
+ throw unknownRow(operation, id);
128
+ if (rowLockKey(row) !== rowLockKey(seen))
129
+ throw refusal(operation, id, 'row-key-changed', `row ${id}'s wire identity changed while its lock was being taken`, true);
130
+ if (row.invalid)
131
+ throw refusal(operation, id, 'invalid-row', `row ${id} failed validation`);
132
+ if (!row.credential)
133
+ throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
134
+ requireBound(operation, row);
135
+ const epoch = row.credentialEpoch ?? 1;
136
+ if (epoch !== captured || row.identity !== fence.identity)
137
+ throw refusal(operation, id, 'attribution', `the provider state for ${id} was read for a credential or account the row no longer holds`, true);
138
+ const plan = await planProviderStateIn(tx, codec, operation, row, mutator);
139
+ if (plan.kind !== 'changed')
140
+ return {
141
+ id,
142
+ outcome: 'unchanged',
143
+ ...(plan.kind === 'unchanged' && plan.value !== undefined
144
+ ? { providerState: plan.value }
145
+ : {}),
146
+ };
147
+ tx.setStateAccount(id, plan.account);
148
+ await tx.commitState();
149
+ return plan.value === undefined
150
+ ? { id, outcome: 'cleared' }
151
+ : { id, outcome: 'updated', providerState: plan.value };
152
+ });
153
+ });
154
+ }
155
+ /**
156
+ * Returned by the provider-state mutator of an attributed `disable` or
157
+ * `enable` to decline the whole transition: nothing is written, neither the
158
+ * provider state nor the row's enabled flag, and the call resolves with
159
+ * `declined: true`. A mutator declines when the state it is shown is newer
160
+ * than what its caller saw, such as an eligibility recorded after the
161
+ * request whose refusal is being acted on. It is a value of its own because
162
+ * `undefined` already means "clear the provider state". `Symbol.for` keeps it
163
+ * equal across two copies of this module loaded in one process.
164
+ */
165
+ export const DECLINE_TRANSITION = Symbol.for('@cortexkit/common-auth/store/decline-transition');
166
+ /**
167
+ * Runs a provider-state mutator for a row loaded under every lock and
168
+ * already checked by the caller (present, valid, inside its attribution
169
+ * fence), and plans the write. Refuses (`no-credential`) a row holding no
170
+ * credential, and (`unbound-credential`) one whose credential carries no
171
+ * stamp of this store at the row's epoch and identity: no stamp could bind
172
+ * the value, so no reader would ever show it. `DECLINE_TRANSITION` is
173
+ * honoured only when `declinable` is set; elsewhere it is not JSON and is
174
+ * refused as such.
175
+ */
176
+ export async function planProviderStateIn(tx, codec, operation, row, mutator, declinable = false) {
177
+ const id = row.id;
178
+ const credential = row.credential;
179
+ if (!credential)
180
+ throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
181
+ const epoch = row.credentialEpoch ?? 1;
182
+ const account = tx.stateAccount(id);
183
+ const rawStamp = account?.[CREDENTIAL_STAMP_KEY];
184
+ const stamp = parseStamp(rawStamp);
185
+ if (!isRecord(rawStamp) ||
186
+ !stamp?.binding ||
187
+ stamp.digest !== credentialDigest(credential) ||
188
+ stamp.credentialEpoch !== epoch ||
189
+ stamp.binding.identity !== row.identity)
190
+ throw refusal(operation, id, 'unbound-credential', `row ${id}'s credential carries no stamp of this store to bind a provider state to (stamp ${row.stamp}); rotate or replace it first`);
191
+ const current = row.providerState === undefined
192
+ ? undefined
193
+ : structuredClone(row.providerState);
194
+ const returned = await runInsideHook(operation, () => mutator(current, row));
195
+ if (declinable && returned === DECLINE_TRANSITION)
196
+ return { kind: 'declined' };
197
+ const next = returned === undefined
198
+ ? undefined
199
+ : acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
200
+ const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
201
+ if (next === undefined
202
+ ? !held
203
+ : row.providerState !== undefined &&
204
+ JSON.stringify(row.providerState) === JSON.stringify(next))
205
+ return { kind: 'unchanged', ...(next !== undefined ? { value: next } : {}) };
206
+ const nextAccount = { ...account };
207
+ if (next === undefined) {
208
+ delete nextAccount[PROVIDER_STATE_KEY];
209
+ const nextStamp = { ...rawStamp };
210
+ delete nextStamp.providerState;
211
+ nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
212
+ }
213
+ else {
214
+ nextAccount[PROVIDER_STATE_KEY] = next;
215
+ const digest = providerStateDigest(codec, next);
216
+ // A change confined to the part the codec does not bind to the
217
+ // credential leaves the stamp exactly as it was.
218
+ if (stamp.providerState !== digest)
219
+ nextAccount[CREDENTIAL_STAMP_KEY] = { ...rawStamp, providerState: digest };
220
+ }
221
+ return {
222
+ kind: 'changed',
223
+ ...(next !== undefined ? { value: next } : {}),
224
+ account: nextAccount,
225
+ };
226
+ }