@cortexkit/antigravity-auth-core 2.2.1 → 3.0.1

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 (253) 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 +2284 -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-request-metadata.d.ts.map +1 -1
  38. package/dist/agy-request-metadata.js +8 -21
  39. package/dist/agy-request-metadata.js.map +1 -1
  40. package/dist/agy-transport.d.ts.map +1 -1
  41. package/dist/agy-transport.js +21 -35
  42. package/dist/agy-transport.js.map +1 -1
  43. package/dist/antigravity-command-menu.d.ts +171 -0
  44. package/dist/antigravity-command-menu.d.ts.map +1 -0
  45. package/dist/antigravity-command-menu.js +523 -0
  46. package/dist/antigravity-command-menu.js.map +1 -0
  47. package/dist/atomic-write.d.ts +18 -1
  48. package/dist/atomic-write.d.ts.map +1 -1
  49. package/dist/atomic-write.js +15 -3
  50. package/dist/atomic-write.js.map +1 -1
  51. package/dist/common-auth-embedded/NOTICE.txt +21 -0
  52. package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
  53. package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
  54. package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
  55. package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
  56. package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
  57. package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
  58. package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
  59. package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
  60. package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
  61. package/dist/common-auth-embedded/auth-menu/index.js +12 -0
  62. package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
  63. package/dist/common-auth-embedded/auth-menu/login.js +83 -0
  64. package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
  65. package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
  66. package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
  67. package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
  68. package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
  69. package/dist/common-auth-embedded/auth-menu/select.js +159 -0
  70. package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
  71. package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
  72. package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
  73. package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
  74. package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
  75. package/dist/common-auth-embedded/claustrum/custody.js +354 -0
  76. package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
  77. package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
  78. package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
  79. package/dist/common-auth-embedded/claustrum/errors.js +8 -0
  80. package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
  81. package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
  82. package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
  83. package/dist/common-auth-embedded/claustrum/index.js +22 -0
  84. package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
  85. package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
  86. package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
  87. package/dist/common-auth-embedded/claustrum/roster.js +536 -0
  88. package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
  89. package/dist/common-auth-embedded/commands/builtins.js +534 -0
  90. package/dist/common-auth-embedded/commands/index.d.ts +10 -0
  91. package/dist/common-auth-embedded/commands/index.js +5 -0
  92. package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
  93. package/dist/common-auth-embedded/commands/menu.js +300 -0
  94. package/dist/common-auth-embedded/commands/model.d.ts +238 -0
  95. package/dist/common-auth-embedded/commands/model.js +16 -0
  96. package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
  97. package/dist/common-auth-embedded/commands/pi.js +206 -0
  98. package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
  99. package/dist/common-auth-embedded/commands/seam.js +301 -0
  100. package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
  101. package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
  102. package/dist/common-auth-embedded/fs/index.d.ts +7 -0
  103. package/dist/common-auth-embedded/fs/index.js +6 -0
  104. package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
  105. package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
  106. package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
  107. package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
  108. package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
  109. package/dist/common-auth-embedded/fs/with-lock.js +48 -0
  110. package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
  111. package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
  112. package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
  113. package/dist/common-auth-embedded/logger/engine.js +246 -0
  114. package/dist/common-auth-embedded/logger/index.d.ts +6 -0
  115. package/dist/common-auth-embedded/logger/index.js +3 -0
  116. package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
  117. package/dist/common-auth-embedded/logger/redact.js +83 -0
  118. package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
  119. package/dist/common-auth-embedded/pi-slot/index.js +2 -0
  120. package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
  121. package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
  122. package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
  123. package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
  124. package/dist/common-auth-embedded/quota/codec.js +12 -0
  125. package/dist/common-auth-embedded/quota/format.d.ts +46 -0
  126. package/dist/common-auth-embedded/quota/format.js +138 -0
  127. package/dist/common-auth-embedded/quota/index.d.ts +10 -0
  128. package/dist/common-auth-embedded/quota/index.js +5 -0
  129. package/dist/common-auth-embedded/quota/map.d.ts +63 -0
  130. package/dist/common-auth-embedded/quota/map.js +105 -0
  131. package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
  132. package/dist/common-auth-embedded/quota/merge.js +162 -0
  133. package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
  134. package/dist/common-auth-embedded/quota/projection.js +134 -0
  135. package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
  136. package/dist/common-auth-embedded/routing/admission.js +142 -0
  137. package/dist/common-auth-embedded/routing/index.d.ts +8 -0
  138. package/dist/common-auth-embedded/routing/index.js +4 -0
  139. package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
  140. package/dist/common-auth-embedded/routing/ordered.js +58 -0
  141. package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
  142. package/dist/common-auth-embedded/routing/pins.js +34 -0
  143. package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
  144. package/dist/common-auth-embedded/routing/sticky.js +339 -0
  145. package/dist/common-auth-embedded/source-output.json +1090 -0
  146. package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
  147. package/dist/common-auth-embedded/store/attribution.js +60 -0
  148. package/dist/common-auth-embedded/store/errors.d.ts +55 -0
  149. package/dist/common-auth-embedded/store/errors.js +38 -0
  150. package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
  151. package/dist/common-auth-embedded/store/hooks.js +34 -0
  152. package/dist/common-auth-embedded/store/identity.d.ts +39 -0
  153. package/dist/common-auth-embedded/store/identity.js +79 -0
  154. package/dist/common-auth-embedded/store/index.d.ts +21 -0
  155. package/dist/common-auth-embedded/store/index.js +7 -0
  156. package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
  157. package/dist/common-auth-embedded/store/mutate.js +500 -0
  158. package/dist/common-auth-embedded/store/pool.d.ts +196 -0
  159. package/dist/common-auth-embedded/store/pool.js +120 -0
  160. package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
  161. package/dist/common-auth-embedded/store/provider-state.js +226 -0
  162. package/dist/common-auth-embedded/store/publication.d.ts +47 -0
  163. package/dist/common-auth-embedded/store/publication.js +304 -0
  164. package/dist/common-auth-embedded/store/pull.d.ts +36 -0
  165. package/dist/common-auth-embedded/store/pull.js +143 -0
  166. package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
  167. package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
  168. package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
  169. package/dist/common-auth-embedded/store/refresh.js +225 -0
  170. package/dist/common-auth-embedded/store/reserved.js +13 -0
  171. package/dist/common-auth-embedded/store/rows.d.ts +313 -0
  172. package/dist/common-auth-embedded/store/rows.js +1258 -0
  173. package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
  174. package/dist/common-auth-embedded/store/runtime.js +68 -0
  175. package/dist/common-auth-embedded/store/schema.d.ts +443 -0
  176. package/dist/common-auth-embedded/store/schema.js +754 -0
  177. package/dist/common-auth-embedded/store/settings.d.ts +63 -0
  178. package/dist/common-auth-embedded/store/settings.js +120 -0
  179. package/dist/common-auth-embedded/store/torn.d.ts +78 -0
  180. package/dist/common-auth-embedded/store/torn.js +294 -0
  181. package/dist/common-auth-embedded/util/any-signal.js +42 -0
  182. package/dist/common-auth-runtime.d.ts +73 -0
  183. package/dist/common-auth-runtime.d.ts.map +1 -0
  184. package/dist/common-auth-runtime.js +65 -0
  185. package/dist/common-auth-runtime.js.map +1 -0
  186. package/dist/credential-redaction.d.ts +13 -0
  187. package/dist/credential-redaction.d.ts.map +1 -0
  188. package/dist/credential-redaction.js +230 -0
  189. package/dist/credential-redaction.js.map +1 -0
  190. package/dist/fetch-timeout.d.ts +4 -2
  191. package/dist/fetch-timeout.d.ts.map +1 -1
  192. package/dist/fetch-timeout.js +121 -14
  193. package/dist/fetch-timeout.js.map +1 -1
  194. package/dist/fingerprint.d.ts +3 -3
  195. package/dist/fingerprint.d.ts.map +1 -1
  196. package/dist/fingerprint.js +4 -4
  197. package/dist/fingerprint.js.map +1 -1
  198. package/dist/index.d.ts +16 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +15 -0
  201. package/dist/index.js.map +1 -1
  202. package/dist/model-registry.d.ts +16 -0
  203. package/dist/model-registry.d.ts.map +1 -1
  204. package/dist/model-registry.js +242 -30
  205. package/dist/model-registry.js.map +1 -1
  206. package/dist/quota-manager.d.ts.map +1 -1
  207. package/dist/quota-manager.js +35 -10
  208. package/dist/quota-manager.js.map +1 -1
  209. package/dist/quota-presentation.d.ts +10 -0
  210. package/dist/quota-presentation.d.ts.map +1 -0
  211. package/dist/quota-presentation.js +60 -0
  212. package/dist/quota-presentation.js.map +1 -0
  213. package/dist/quota-types.d.ts +2 -0
  214. package/dist/quota-types.d.ts.map +1 -1
  215. package/dist/rotation.d.ts +8 -0
  216. package/dist/rotation.d.ts.map +1 -1
  217. package/dist/rotation.js +24 -0
  218. package/dist/rotation.js.map +1 -1
  219. package/dist/transform/agy-generation.d.ts +3 -0
  220. package/dist/transform/agy-generation.d.ts.map +1 -0
  221. package/dist/transform/agy-generation.js +28 -0
  222. package/dist/transform/agy-generation.js.map +1 -0
  223. package/dist/transform/claude.d.ts +7 -3
  224. package/dist/transform/claude.d.ts.map +1 -1
  225. package/dist/transform/claude.js +8 -2
  226. package/dist/transform/claude.js.map +1 -1
  227. package/dist/transform/index.d.ts +1 -0
  228. package/dist/transform/index.d.ts.map +1 -1
  229. package/dist/transform/index.js +1 -0
  230. package/dist/transform/index.js.map +1 -1
  231. package/dist/transform/model-resolver.d.ts +2 -1
  232. package/dist/transform/model-resolver.d.ts.map +1 -1
  233. package/dist/transform/model-resolver.js +46 -5
  234. package/dist/transform/model-resolver.js.map +1 -1
  235. package/dist/transform/types.d.ts +1 -1
  236. package/dist/transform/types.d.ts.map +1 -1
  237. package/dist/vault-account-source.d.ts +183 -0
  238. package/dist/vault-account-source.d.ts.map +1 -0
  239. package/dist/vault-account-source.js +296 -0
  240. package/dist/vault-account-source.js.map +1 -0
  241. package/dist/vault-enrollment-authority.d.ts +16 -0
  242. package/dist/vault-enrollment-authority.d.ts.map +1 -0
  243. package/dist/vault-enrollment-authority.js +19 -0
  244. package/dist/vault-enrollment-authority.js.map +1 -0
  245. package/dist/vault-family.d.ts +11 -0
  246. package/dist/vault-family.d.ts.map +1 -0
  247. package/dist/vault-family.js +24 -0
  248. package/dist/vault-family.js.map +1 -0
  249. package/dist/vault-provider-state.d.ts +162 -0
  250. package/dist/vault-provider-state.d.ts.map +1 -0
  251. package/dist/vault-provider-state.js +422 -0
  252. package/dist/vault-provider-state.js.map +1 -0
  253. package/package.json +39 -2
@@ -0,0 +1,500 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { setTimeout as snapshotDelay } from 'node:timers/promises';
3
+ import { AtomicSyncError, syncJsonFile, writeJsonAtomic, writeJsonAtomicTracked, } from '../fs/atomic-write.js';
4
+ import { LockContentionError, LockOwnershipError } from '../fs/with-lock.js';
5
+ import { PoolOperationError, } from './errors.js';
6
+ import { callFailureHook } from './hooks.js';
7
+ import { LockStack, } from './refresh-lock.js';
8
+ import { assertNotReserved } from './reserved.js';
9
+ import { CREDENTIAL_STAMP_KEY, classifyConfig, classifyState, ensureEntries, entryIn, isRecord, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, retireEpochsIn, rosterOf, rosterRowIn, setEntryIn, } from './schema.js';
10
+ import { completeTornRows, loadRows } from './torn.js';
11
+ async function readText(path) {
12
+ try {
13
+ return await readFile(path, 'utf8');
14
+ }
15
+ catch (error) {
16
+ if (error.code === 'ENOENT')
17
+ return undefined;
18
+ throw error;
19
+ }
20
+ }
21
+ function parseFile(text) {
22
+ if (text === undefined)
23
+ return { exists: false };
24
+ try {
25
+ return { exists: true, value: JSON.parse(text) };
26
+ }
27
+ catch (parseError) {
28
+ return { exists: true, parseError };
29
+ }
30
+ }
31
+ /**
32
+ * Validate config/state/config bytes without locks. Roster replacement writes
33
+ * config before deleting old state credentials. Re-read a changed config so an
34
+ * old roster cannot be paired with state from which its accounts were removed.
35
+ */
36
+ export async function readPool(ctx) {
37
+ for (let attempt = 0; attempt < 3; attempt++) {
38
+ const configText = await readText(ctx.configPath);
39
+ const stateText = await readText(ctx.statePath);
40
+ const checkedConfigText = await readText(ctx.configPath);
41
+ if (configText !== checkedConfigText) {
42
+ if (attempt < 2)
43
+ await snapshotDelay(2 + Math.floor(Math.random() * 4));
44
+ continue;
45
+ }
46
+ const config = classifyConfig(parseFile(configText));
47
+ if (config.status === 'error')
48
+ return { status: 'error', file: 'config', reason: config.reason };
49
+ const state = classifyState(parseFile(stateText));
50
+ if (state.status === 'error')
51
+ return { status: 'error', file: 'state', reason: state.reason };
52
+ if (config.status === 'pending-migration')
53
+ return { status: 'pending-migration', config: config.config };
54
+ return {
55
+ status: 'ready',
56
+ configExists: config.exists,
57
+ stateExists: state.exists,
58
+ config: config.config,
59
+ state: state.state,
60
+ rows: loadRows(config.config, state.state, ctx.codec, {
61
+ requireCredentialStamps: ctx.requireCredentialStamps === true,
62
+ ...(ctx.providerState ? { providerState: ctx.providerState } : {}),
63
+ }),
64
+ };
65
+ }
66
+ return {
67
+ status: 'error',
68
+ file: 'config',
69
+ kind: 'snapshot-contended',
70
+ retryable: true,
71
+ reason: 'The store was changing; retry the operation once writes settle.',
72
+ };
73
+ }
74
+ /** The refusal for a pool that is not ready, as a failure value. */
75
+ export function notReadyError(result, operation, rowId, phase = 'before-first-write') {
76
+ if (result.status === 'error' && result.kind === 'snapshot-contended')
77
+ return new PoolOperationError({
78
+ operation,
79
+ ...(rowId !== undefined ? { rowId } : {}),
80
+ phase,
81
+ kind: 'snapshot-contended',
82
+ retryable: true,
83
+ message: result.reason,
84
+ });
85
+ return new PoolOperationError({
86
+ operation,
87
+ ...(rowId !== undefined ? { rowId } : {}),
88
+ phase,
89
+ retryable: false,
90
+ kind: result.status === 'pending-migration'
91
+ ? 'pending-migration'
92
+ : 'load-error',
93
+ message: result.status === 'pending-migration'
94
+ ? 'the config holds a legacy roster that has not been migrated into the pool'
95
+ : `${result.file} file cannot be loaded: ${result.reason}`,
96
+ });
97
+ }
98
+ /**
99
+ * One locked read-modify-write. The store locks are pushed onto the caller's
100
+ * lock stack, so the ownership assertion before each write covers the outer
101
+ * row, provider-wide and extra locks as well; they are released when the
102
+ * transaction ends, whatever happens.
103
+ */
104
+ export class Transaction {
105
+ ctx;
106
+ snapshot;
107
+ locks;
108
+ progress;
109
+ info;
110
+ config;
111
+ state;
112
+ constructor(ctx, snapshot, locks, progress, info) {
113
+ this.ctx = ctx;
114
+ this.snapshot = snapshot;
115
+ this.locks = locks;
116
+ this.progress = progress;
117
+ this.info = info;
118
+ this.config = structuredClone(snapshot.config);
119
+ this.state = structuredClone(snapshot.state);
120
+ }
121
+ /**
122
+ * The rows as every reader loads them: a row torn between the writes of a
123
+ * replace is shown as that replace leaves it once completed (see
124
+ * `PoolRow.torn`).
125
+ */
126
+ rows() {
127
+ return loadRows(this.config, this.state, this.ctx.codec, {
128
+ requireCredentialStamps: this.ctx.requireCredentialStamps === true,
129
+ ...(this.ctx.providerState
130
+ ? { providerState: this.ctx.providerState }
131
+ : {}),
132
+ });
133
+ }
134
+ row(id) {
135
+ return this.rows().find((row) => row.id === id);
136
+ }
137
+ /** Read the config marker, or an orphan's stamp; never use a caller's reservation. */
138
+ reservation(id) {
139
+ if (this.rosterRow(id)) {
140
+ const entry = this.entry(id);
141
+ return entry && Object.hasOwn(entry, 'staged') ? entry.staged : undefined;
142
+ }
143
+ const stamp = this.stateAccount(id)?.[CREDENTIAL_STAMP_KEY];
144
+ return isRecord(stamp) && Object.hasOwn(stamp, 'staged')
145
+ ? stamp.staged
146
+ : undefined;
147
+ }
148
+ assertNotStaged(id) {
149
+ assertNotReserved(this.info.operation, id, this.reservation(id));
150
+ }
151
+ roster() {
152
+ if (!Array.isArray(this.config.accounts))
153
+ this.config.accounts = [];
154
+ return this.config.accounts;
155
+ }
156
+ /** The first roster row with this id (the one the pool loads). */
157
+ rosterRow(id) {
158
+ this.roster();
159
+ return rosterRowIn(this.config, id);
160
+ }
161
+ /**
162
+ * Drops every roster row carrying this id. The row's per-row entry goes with
163
+ * it on the next `commitConfig`, which drops entries for ids no longer in
164
+ * the roster. Returns how many roster rows were dropped.
165
+ */
166
+ dropRosterRows(id) {
167
+ const roster = this.roster();
168
+ const kept = roster.filter((raw) => !(isRecord(raw) && raw.id === id));
169
+ this.config.accounts = kept;
170
+ return roster.length - kept.length;
171
+ }
172
+ entries() {
173
+ return ensureEntries(this.config);
174
+ }
175
+ entry(id) {
176
+ this.entries();
177
+ return entryIn(this.config, id);
178
+ }
179
+ setEntry(id, entry) {
180
+ setEntryIn(this.config, id, entry);
181
+ }
182
+ /**
183
+ * Writes the config of every row torn between the writes of a replace, or
184
+ * of a write giving it its first identity, as that write would have left it
185
+ * (see `completeTornRows`), in one config
186
+ * write ahead of the operation's own. The write is counted apart from the
187
+ * operation's: it is setup, like a pull giving a row its entry, so a later
188
+ * refusal still reports `before-first-write`.
189
+ */
190
+ async completeTorn() {
191
+ const { config, torn } = completeTornRows(this.config, this.state, this.ctx.codec, { requireCredentialStamps: this.ctx.requireCredentialStamps === true });
192
+ if (torn.length === 0)
193
+ return;
194
+ this.config = config;
195
+ await this.commitConfig({ counted: false });
196
+ }
197
+ stateAccount(id) {
198
+ const accounts = isRecord(this.state.accounts) ? this.state.accounts : {};
199
+ const entry = Object.hasOwn(accounts, id) ? accounts[id] : undefined;
200
+ return isRecord(entry) ? entry : undefined;
201
+ }
202
+ /** Drops the row's credential and runtime fields from the state file's accounts. */
203
+ dropStateAccount(id) {
204
+ if (isRecord(this.state.accounts) && Object.hasOwn(this.state.accounts, id))
205
+ delete this.state.accounts[id];
206
+ }
207
+ setStateAccount(id, fields) {
208
+ if (!isRecord(this.state.accounts))
209
+ this.state.accounts = {};
210
+ Object.defineProperty(this.state.accounts, id, {
211
+ value: fields,
212
+ enumerable: true,
213
+ writable: true,
214
+ configurable: true,
215
+ });
216
+ }
217
+ /**
218
+ * Writes the config: legacy `version: 1` and the legacy roster beside
219
+ * `commonAuthPool`, every other top-level key and every unrecognised pool
220
+ * key untouched. Entries for ids no longer in the roster are dropped here,
221
+ * and remembered so the id is not reused in this process. Every id the
222
+ * write drops (a roster row the files held when the transaction read them,
223
+ * or an entry left without one) has its epoch recorded in the config (see
224
+ * `retireEpochsIn`), which is what keeps a later `add` of the id, from any
225
+ * process, past every epoch an attribution could name.
226
+ */
227
+ async commitConfig(options = {}) {
228
+ const roster = this.roster();
229
+ const rosterIds = new Set();
230
+ for (const raw of roster)
231
+ if (isRecord(raw) && typeof raw.id === 'string')
232
+ rosterIds.add(raw.id);
233
+ const dropped = new Set();
234
+ for (const raw of rosterOf(this.snapshot.config))
235
+ if (isRecord(raw) && typeof raw.id === 'string' && !rosterIds.has(raw.id))
236
+ dropped.add(raw.id);
237
+ for (const id of Object.keys(this.entries()))
238
+ if (!rosterIds.has(id))
239
+ dropped.add(id);
240
+ retireEpochsIn(this.config, dropped);
241
+ const entries = this.entries();
242
+ const kept = {};
243
+ for (const [id, entry] of Object.entries(entries)) {
244
+ if (rosterIds.has(id)) {
245
+ Object.defineProperty(kept, id, {
246
+ value: entry,
247
+ enumerable: true,
248
+ writable: true,
249
+ configurable: true,
250
+ });
251
+ }
252
+ }
253
+ const pool = this.config[POOL_KEY];
254
+ const next = {
255
+ ...this.config,
256
+ version: LEGACY_STORE_VERSION,
257
+ accounts: roster,
258
+ [POOL_KEY]: {
259
+ ...pool,
260
+ schemaVersion: POOL_SCHEMA_VERSION,
261
+ [POOL_ROWS_KEY]: kept,
262
+ },
263
+ };
264
+ await this.write(this.ctx.configPath, next, 'config', options.counted ?? true, options.durable ?? false, () => {
265
+ if (options.publicationDecision)
266
+ this.markPublicationDecision();
267
+ for (const id of dropped)
268
+ this.ctx.removedIds.add(id);
269
+ this.config = next;
270
+ });
271
+ this.config = next;
272
+ }
273
+ /** Writes the state: every unrecognised top-level and per-row key kept. */
274
+ async commitState(committed, options = {}) {
275
+ const next = {
276
+ ...this.state,
277
+ version: LEGACY_STORE_VERSION,
278
+ accounts: isRecord(this.state.accounts) ? this.state.accounts : {},
279
+ };
280
+ await this.write(this.ctx.statePath, next, 'state', true, options.durable ?? false);
281
+ this.state = next;
282
+ if (committed)
283
+ this.progress.committed = committed;
284
+ }
285
+ async assertAll() {
286
+ await this.locks.assertAll();
287
+ }
288
+ async syncState() {
289
+ await this.assertAll();
290
+ try {
291
+ if (this.snapshot.stateExists)
292
+ await syncJsonFile(this.ctx.statePath);
293
+ }
294
+ catch (cause) {
295
+ throw new PoolOperationError({
296
+ operation: this.info.operation,
297
+ rowId: this.info.rowId,
298
+ phase: 'before-first-write',
299
+ kind: 'publication-sync',
300
+ retryable: true,
301
+ message: 'Publication sync failed before the decision; nothing was written. Retry the same plan.',
302
+ cause,
303
+ });
304
+ }
305
+ }
306
+ markPublicationDecision() {
307
+ this.progress.publicationDecided = true;
308
+ }
309
+ async syncConfig() {
310
+ await this.assertAll();
311
+ await syncJsonFile(this.ctx.configPath);
312
+ }
313
+ async write(path, value, file, counted = true, durable = false, onRenamed) {
314
+ await writeJsonAtomicTracked(path, value, {
315
+ durable,
316
+ beforeRename: async () => {
317
+ await this.ctx.onStep?.(`before-${file}-write`, this.info);
318
+ // Ownership is proved immediately before the rename, on every lease.
319
+ await this.locks.assertAll();
320
+ },
321
+ }, () => {
322
+ if (counted)
323
+ this.progress.writes++;
324
+ onRenamed?.();
325
+ });
326
+ await this.ctx.onStep?.(`after-${file}-write`, this.info);
327
+ }
328
+ }
329
+ /**
330
+ * Adds the pool key to a config that holds a legacy roster without one, under
331
+ * the store-lock list, in one config write: `commonAuthPool` with the schema
332
+ * version and no row entries, the legacy roster and every other top-level key
333
+ * kept, except the keys named in `dropKeys`. The state file is not touched.
334
+ * This is the only write the store makes to a pending-migration config; it is
335
+ * where a plugin's migration starts, and every later row goes through the
336
+ * ordinary operations. A config that is already a pool is left alone; a load
337
+ * error refuses.
338
+ */
339
+ export async function initializePool(ctx, dropKeys) {
340
+ const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
341
+ const progress = { writes: 0 };
342
+ try {
343
+ for (const spec of ctx.storeLocks)
344
+ await locks.acquire(spec);
345
+ const result = await readPool(ctx);
346
+ if (result.status === 'error')
347
+ throw notReadyError(result, 'initialize', undefined);
348
+ if (result.status === 'ready')
349
+ return 'already-ready';
350
+ const next = {};
351
+ for (const [key, value] of Object.entries(result.config))
352
+ if (!dropKeys.includes(key))
353
+ next[key] = value;
354
+ next.version = LEGACY_STORE_VERSION;
355
+ next[POOL_KEY] = { schemaVersion: POOL_SCHEMA_VERSION, [POOL_ROWS_KEY]: {} };
356
+ const info = { operation: 'initialize', rowId: undefined };
357
+ await writeJsonAtomic(ctx.configPath, next, {
358
+ beforeRename: async () => {
359
+ await ctx.onStep?.('before-config-write', info);
360
+ await locks.assertAll();
361
+ },
362
+ });
363
+ progress.writes++;
364
+ await ctx.onStep?.('after-config-write', info);
365
+ return 'initialized';
366
+ }
367
+ catch (error) {
368
+ throw toFailure(error, 'initialize', undefined, progress);
369
+ }
370
+ finally {
371
+ await locks.releaseAll();
372
+ }
373
+ }
374
+ /**
375
+ * Runs `fn` under the store-lock list. The pool must be ready: a pending
376
+ * migration or a load error refuses before anything is written. Unless
377
+ * `completeTorn` is false, rows torn between the writes of a replace are
378
+ * completed first, so `fn` never sees one; the writes that only record
379
+ * readings or reorder the roster opt out and leave such rows as they are.
380
+ */
381
+ export async function withTransaction(ctx, locks, progress, info, fn, options = {}) {
382
+ const mark = locks.held.length;
383
+ try {
384
+ for (const spec of ctx.storeLocks)
385
+ await locks.acquire(spec);
386
+ const result = await readPool(ctx);
387
+ if (result.status !== 'ready')
388
+ throw notReadyError(result, info.operation, info.rowId, progress.writes > 0 ? 'after-first-write' : 'before-first-write');
389
+ const tx = new Transaction(ctx, result, locks, progress, info);
390
+ if (info.rowId !== undefined &&
391
+ !['add', 'remove', 'pull', 'publishRoster', 'enable', 'disable'].includes(info.operation))
392
+ tx.assertNotStaged(info.rowId);
393
+ if (options.completeTorn ?? true)
394
+ await tx.completeTorn();
395
+ return await fn(tx);
396
+ }
397
+ finally {
398
+ await locks.releaseTo(mark);
399
+ }
400
+ }
401
+ /** Maps anything thrown inside an operation onto the one failure value. */
402
+ export function toFailure(error, operation, rowId, progress) {
403
+ const phase = operation === 'pull'
404
+ ? 'pull'
405
+ : progress.writes > 0
406
+ ? 'after-first-write'
407
+ : 'before-first-write';
408
+ const committed = phase === 'after-first-write' ? progress.committed : undefined;
409
+ if (operation === 'publishRoster' && progress.publicationDecided)
410
+ return new PoolOperationError({
411
+ operation,
412
+ ...(rowId !== undefined ? { rowId } : {}),
413
+ phase,
414
+ kind: 'publication-incomplete',
415
+ retryable: true,
416
+ message: 'The publication may already be decided. Replay the same plan or check publication(operationId).',
417
+ cause: error,
418
+ });
419
+ if (operation === 'publishRoster' && error instanceof AtomicSyncError)
420
+ return new PoolOperationError({
421
+ operation,
422
+ ...(rowId !== undefined ? { rowId } : {}),
423
+ phase,
424
+ kind: 'publication-sync',
425
+ retryable: true,
426
+ message: 'Publication sync failed before the decision; nothing was written. Retry the same plan.',
427
+ cause: error,
428
+ });
429
+ if (error instanceof PoolOperationError) {
430
+ if (error.phase === phase &&
431
+ error.rowId === rowId &&
432
+ error.operation === operation &&
433
+ error.committed === committed)
434
+ return error;
435
+ return new PoolOperationError({
436
+ operation,
437
+ ...(rowId !== undefined ? { rowId } : {}),
438
+ phase: error.kind === 'after-persist-hook' ? error.phase : phase,
439
+ retryable: error.retryable,
440
+ kind: error.kind,
441
+ ...((error.committed ?? committed)
442
+ ? { committed: error.committed ?? committed }
443
+ : {}),
444
+ message: error.message,
445
+ ...(error.cause !== undefined ? { cause: error.cause } : {}),
446
+ });
447
+ }
448
+ const base = {
449
+ operation,
450
+ ...(rowId !== undefined ? { rowId } : {}),
451
+ phase,
452
+ ...(committed ? { committed } : {}),
453
+ cause: error,
454
+ };
455
+ if (error instanceof LockOwnershipError)
456
+ return new PoolOperationError({
457
+ ...base,
458
+ retryable: true,
459
+ kind: 'lock-ownership',
460
+ message: phase === 'after-first-write'
461
+ ? `${operation} lost a lease after its first write; the intermediate stays on disk`
462
+ : `${operation} lost a lease before writing; nothing was written`,
463
+ });
464
+ if (error instanceof LockContentionError)
465
+ return new PoolOperationError({
466
+ ...base,
467
+ retryable: true,
468
+ kind: 'lock-contention',
469
+ message: error.message,
470
+ });
471
+ return new PoolOperationError({
472
+ ...base,
473
+ retryable: false,
474
+ kind: 'unexpected',
475
+ message: error instanceof Error ? error.message : String(error),
476
+ });
477
+ }
478
+ /**
479
+ * The frame every lock-holding operation runs in: failures are mapped onto
480
+ * the failure value, handed to the failure hook while the outer locks are
481
+ * still held (the store locks are already released), and rethrown; every
482
+ * lock is released afterwards. `rowId` is undefined for an operation that
483
+ * names no row (`reorder`).
484
+ */
485
+ export async function runOperation(ctx, operation, rowId, onFailure, body) {
486
+ const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
487
+ const progress = { writes: 0 };
488
+ try {
489
+ return await body(locks, progress);
490
+ }
491
+ catch (error) {
492
+ const failure = toFailure(error, operation, rowId, progress);
493
+ if (failure.kind !== 'after-persist-hook')
494
+ await callFailureHook(operation, onFailure, rowId, failure, ctx.logger);
495
+ throw failure;
496
+ }
497
+ finally {
498
+ await locks.releaseAll();
499
+ }
500
+ }