@cortexkit/opencode-openai-auth 0.11.0 → 1.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 (204) hide show
  1. package/README.md +15 -26
  2. package/dist/index-766sfrh5.js +1 -0
  3. package/dist/index-8kj7sa1p.js +12 -0
  4. package/dist/index-8rqfbhfw.js +221 -0
  5. package/dist/index-b3q3j39x.js +2 -0
  6. package/dist/index-bmd7x5aq.js +2 -0
  7. package/dist/index-eqh53k8v.js +2 -0
  8. package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
  9. package/dist/index-jyr8y8k4.js +7 -0
  10. package/dist/index-rv08jmd4.js +2 -0
  11. package/dist/index.d.ts +578 -145
  12. package/dist/index.js +1 -110
  13. package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
  14. package/dist/port-file-Do3lQIEk.d.ts +29 -0
  15. package/dist/protocol-BOBdkqqm.d.ts +198 -0
  16. package/dist/rpc/port-file.d.ts +4 -17
  17. package/dist/rpc/port-file.js +1 -1
  18. package/dist/rpc/protocol.d.ts +2 -2
  19. package/dist/rpc/protocol.js +1 -0
  20. package/dist/rpc/rpc-client.d.ts +5 -4
  21. package/dist/rpc/rpc-client.js +1 -1
  22. package/dist/rpc/rpc-dir.d.ts +2 -0
  23. package/dist/rpc/rpc-dir.js +1 -1
  24. package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
  25. package/dist/sidebar-state.d.ts +2 -2
  26. package/dist/sidebar-state.js +1 -1
  27. package/dist/token-util-wdza526f.js +1 -0
  28. package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
  29. package/dist/tui-preferences.js +5 -2
  30. package/dist/tui.d.ts +9 -4
  31. package/dist/v2/server.d.ts +54 -0
  32. package/dist/v2/server.js +4 -0
  33. package/package.json +21 -9
  34. package/src/tui/entry.mjs +26 -25
  35. package/src/tui-compiled/raw/logger.js +24 -0
  36. package/src/tui-compiled/raw/rpc/protocol.js +3 -0
  37. package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
  38. package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
  39. package/src/tui-compiled/raw/selector.js +21 -0
  40. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
  41. package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
  42. package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
  43. package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
  44. package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
  45. package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
  46. package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
  47. package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
  48. package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
  49. package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
  50. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  51. package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
  52. package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
  53. package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
  54. package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
  55. package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
  56. package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
  57. package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
  58. package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
  59. package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
  60. package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
  61. package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
  62. package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
  63. package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
  64. package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  65. package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
  66. package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
  67. package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
  68. package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
  69. package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
  70. package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
  71. package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
  72. package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  73. package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
  74. package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
  75. package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  76. package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
  77. package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
  78. package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
  79. package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
  80. package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  81. package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
  82. package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
  83. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
  84. package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
  85. package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
  86. package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
  87. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
  88. package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
  89. package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
  90. package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
  91. package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
  92. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  93. package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
  94. package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
  95. package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  96. package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  97. package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
  98. package/src/tui-compiled/raw/sidebar-state.js +960 -0
  99. package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
  100. package/src/tui-compiled/raw/tui-preferences.js +135 -0
  101. package/src/tui-compiled/raw/tui.tsx +135 -143
  102. package/src/tui-compiled/runtime/logger.js +24 -0
  103. package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
  104. package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
  105. package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
  106. package/src/tui-compiled/runtime/selector.js +21 -0
  107. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
  108. package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
  109. package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
  110. package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
  111. package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
  112. package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
  113. package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
  114. package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
  115. package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
  116. package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
  117. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  118. package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
  119. package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
  120. package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
  121. package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
  122. package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
  123. package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
  124. package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
  125. package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
  126. package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
  127. package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
  128. package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
  129. package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
  130. package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
  131. package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  132. package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
  133. package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
  134. package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
  135. package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
  136. package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
  137. package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
  138. package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
  139. package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  140. package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
  141. package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
  142. package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  143. package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
  144. package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
  145. package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
  146. package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
  147. package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  148. package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
  149. package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
  150. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
  151. package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
  152. package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
  153. package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
  154. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
  155. package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
  156. package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
  157. package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
  158. package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
  159. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  160. package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
  161. package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
  162. package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  163. package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  164. package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
  165. package/src/tui-compiled/runtime/sidebar-state.js +960 -0
  166. package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
  167. package/src/tui-compiled/runtime/tui-preferences.js +135 -0
  168. package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
  169. package/dist/index-4effjmr6.js +0 -2
  170. package/dist/index-9ehhb051.js +0 -2
  171. package/dist/index-g3hsdjh4.js +0 -2
  172. package/dist/index-hh52v8sb.js +0 -2
  173. package/dist/index-jwn4r3sw.js +0 -1
  174. package/dist/index-sq36b120.js +0 -211
  175. package/dist/internal-BTndPmW2.d.ts +0 -914
  176. package/dist/quota-normalize-08hpp51v.js +0 -1
  177. package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
  178. package/dist/token-util-ypgz7rge.js +0 -1
  179. package/src/tui-compiled/raw/core-account-paths.ts +0 -114
  180. package/src/tui-compiled/raw/core-custody-state.ts +0 -243
  181. package/src/tui-compiled/raw/logger.ts +0 -53
  182. package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
  183. package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
  184. package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
  185. package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
  186. package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
  187. package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
  188. package/src/tui-compiled/raw/tui-preferences.ts +0 -328
  189. package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
  190. package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
  191. package/src/tui-compiled/runtime/logger.ts +0 -53
  192. package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
  193. package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
  194. package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
  195. package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
  196. package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
  197. package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
  198. package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
  199. package/src/tui-compiled/shared/internal.ts +0 -6
  200. package/src/tui-compiled/shared/logger.ts +0 -264
  201. package/src/tui-compiled/shared/paths.ts +0 -33
  202. package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
  203. package/src/tui-compiled/shared/util/error.ts +0 -9
  204. package/src/tui-compiled/shared/util/open-url.ts +0 -30
@@ -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 "./7211cfec06a298bc-atomic-write.js";
4
+ import { LockContentionError, LockOwnershipError } from "./4825e03209d6ae3e-with-lock.js";
5
+ import { PoolOperationError, } from "./04a6357627de5ed0-errors.js";
6
+ import { callFailureHook } from "./9deae3c4da4fd63b-hooks.js";
7
+ import { LockStack, } from "./43fc9e6b994519c7-refresh-lock.js";
8
+ import { assertNotReserved } from "./08eef45626a94992-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 "./cd94316a888b0a8e-schema.js";
10
+ import { completeTornRows, loadRows } from "./fd45e82f2f9c0a0d-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
+ }
@@ -0,0 +1,142 @@
1
+ // Admission: which candidate rows may be dispatched for one request.
2
+ //
3
+ // Pure: the caller passes the candidate rows with their quota maps, the
4
+ // request's scope, the window labels that scope requires, and the rate-limit
5
+ // marks and refresh backoff it holds. Nothing is read or written here.
6
+ //
7
+ // A marked or backed-off row is excluded before the gates. Stage 1 then
8
+ // judges each remaining row alone, in gate order:
9
+ // 1. an API-key row is admitted without consulting quota (a provider that
10
+ // offers paid rows only after its OAuth rows are spent leaves them out
11
+ // of `rows` until then);
12
+ // 2. an OAuth row whose projection resolves no entry for the scope needs a
13
+ // first reading: refused, pull requested;
14
+ // 3. a required label with no entry is unknown: refused, pull requested;
15
+ // 4. a reading at or beyond 100% whose reset is missing, unparsable or not
16
+ // after `now` cannot confirm the exhaustion, so the window is unknown:
17
+ // refused, pull requested;
18
+ // 5. a reading at or beyond 100% with a future reset is exhausted: refused.
19
+ // Gates 4 and 5 look at every projected limit, not only the required labels.
20
+ // Stage 2 judges the credit budget across the stage-1 survivors: a row whose
21
+ // budget is known spent (reached, with a parsable future reset) is refused
22
+ // unless every survivor's budget is spent, in which case all of them stay
23
+ // admitted as the last path. Every missing, malformed or passed budget reset
24
+ // fails open. Stage-1 refusals have no last-path exception.
25
+ import { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, } from "./905bffc0220f43d0-map.js";
26
+ import { budgetExhaustedResetAt, futureResetAt, projectQuota, readsExhausted, } from "./39c3cb05738fcf6d-projection.js";
27
+ /** The exclusion that applies to `id` at `now`, if any. */
28
+ export function exclusionFor(id, inputs, now) {
29
+ const markedUntil = inputs.rateLimitMarks?.get(id);
30
+ if (markedUntil !== undefined && now < markedUntil) {
31
+ return { id, reason: 'rate-limited', until: markedUntil };
32
+ }
33
+ const backoffUntil = inputs.refreshBackoff?.get(id);
34
+ if (backoffUntil !== undefined && now < backoffUntil) {
35
+ return { id, reason: 'refresh-backoff', until: backoffUntil };
36
+ }
37
+ return undefined;
38
+ }
39
+ function judgeStageOne(id, projection, requiredLabels, now) {
40
+ if (projection.limits.length === 0) {
41
+ return {
42
+ id,
43
+ stage: 1,
44
+ gate: 2,
45
+ reason: 'needs-first-reading',
46
+ pullRequested: true,
47
+ };
48
+ }
49
+ for (const label of requiredLabels) {
50
+ if (!projection.limits.some((limit) => limit.label === label)) {
51
+ return {
52
+ id,
53
+ stage: 1,
54
+ gate: 3,
55
+ reason: 'unknown-window',
56
+ window: { scope: projection.scope, label },
57
+ pullRequested: true,
58
+ };
59
+ }
60
+ }
61
+ const exhausted = projection.limits.filter(readsExhausted);
62
+ for (const limit of exhausted) {
63
+ if (futureResetAt(limit, now) === undefined) {
64
+ return {
65
+ id,
66
+ stage: 1,
67
+ gate: 4,
68
+ reason: 'unknown-reset',
69
+ window: { scope: limit.scope, label: limit.label },
70
+ pullRequested: true,
71
+ };
72
+ }
73
+ }
74
+ for (const limit of exhausted) {
75
+ const resetAtMs = futureResetAt(limit, now);
76
+ if (resetAtMs !== undefined && typeof limit.resetsAt === 'string') {
77
+ return {
78
+ id,
79
+ stage: 1,
80
+ gate: 5,
81
+ reason: 'exhausted',
82
+ window: { scope: limit.scope, label: limit.label },
83
+ resetsAt: limit.resetsAt,
84
+ resetAtMs,
85
+ };
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+ /** Runs both admission stages over `input.rows`. */
91
+ export function admit(input) {
92
+ const scope = input.scope ?? ALL_SCOPE;
93
+ const requiredLabels = input.requiredLabels ?? DEFAULT_REQUIRED_LABELS;
94
+ const excluded = [];
95
+ const refused = [];
96
+ const survivors = [];
97
+ for (const row of input.rows) {
98
+ const exclusion = exclusionFor(row.id, input, input.now);
99
+ if (exclusion) {
100
+ excluded.push(exclusion);
101
+ continue;
102
+ }
103
+ if (row.kind === 'api-key') {
104
+ survivors.push({ id: row.id, kind: row.kind });
105
+ continue;
106
+ }
107
+ const projection = projectQuota(row.quota, scope);
108
+ const refusal = judgeStageOne(row.id, projection, requiredLabels, input.now);
109
+ if (refusal) {
110
+ refused.push(refusal);
111
+ continue;
112
+ }
113
+ survivors.push({ id: row.id, kind: row.kind, projection });
114
+ }
115
+ const spent = new Map(survivors.flatMap((row) => {
116
+ const reset = budgetExhaustedResetAt(row.projection, input.now);
117
+ return reset ? [[row.id, reset]] : [];
118
+ }));
119
+ const lastPath = spent.size > 0 && spent.size === survivors.length;
120
+ const admitted = [];
121
+ for (const row of survivors) {
122
+ const reset = spent.get(row.id);
123
+ if (reset === undefined) {
124
+ admitted.push(row);
125
+ }
126
+ else if (lastPath) {
127
+ admitted.push({ ...row, lastPath: true });
128
+ }
129
+ else {
130
+ refused.push({ id: row.id, stage: 2, reason: 'budget-spent', ...reset });
131
+ }
132
+ }
133
+ const order = new Map(input.rows.map((row, index) => [row.id, index]));
134
+ const byInput = (left, right) => (order.get(left.id) ?? 0) - (order.get(right.id) ?? 0);
135
+ refused.sort(byInput);
136
+ const pulls = refused
137
+ .filter((refusal) => 'pullRequested' in refusal)
138
+ .map((refusal) => refusal.id);
139
+ for (const id of pulls)
140
+ input.requestPull?.(id);
141
+ return { admitted, refused, excluded, pulls };
142
+ }
@@ -0,0 +1,2 @@
1
+ export { discoverPortFile, } from "./c14988621dc60a75-port-file.js";
2
+ export * from "./fbcdcd0f925e74c0-rpc-client.js";
@@ -0,0 +1,48 @@
1
+ import { acquireRefreshFileLock } from "./e8cd4929507508d8-refresh-file-lock.js";
2
+ export function lockPathFor(target, name) {
3
+ return `${target}.${name}.lock`;
4
+ }
5
+ export class LockContentionError extends Error {
6
+ details;
7
+ constructor(details) {
8
+ super(`Timed out acquiring ${details.name} lock for ${details.target}`);
9
+ this.name = 'LockContentionError';
10
+ this.details = details;
11
+ }
12
+ }
13
+ export class LockOwnershipError extends Error {
14
+ details;
15
+ constructor(details) {
16
+ super(`Lost ${details.name} lock for ${details.target}`);
17
+ this.name = 'LockOwnershipError';
18
+ this.details = details;
19
+ }
20
+ }
21
+ export async function withLock(target, options, fn) {
22
+ const started = performance.now();
23
+ for (;;) {
24
+ const lock = await acquireRefreshFileLock({
25
+ path: target,
26
+ name: options.name,
27
+ ttlMs: options.ttlMs,
28
+ renew: options.renew ?? false,
29
+ });
30
+ if (lock) {
31
+ try {
32
+ return await fn(lock);
33
+ }
34
+ finally {
35
+ await lock.release();
36
+ }
37
+ }
38
+ const remaining = options.timeoutMs - (performance.now() - started);
39
+ if (remaining <= 0) {
40
+ throw new LockContentionError({
41
+ target,
42
+ name: options.name,
43
+ timeoutMs: options.timeoutMs,
44
+ });
45
+ }
46
+ await new Promise((resolve) => setTimeout(resolve, Math.min(25, Math.ceil(remaining))));
47
+ }
48
+ }