@cortexkit/opencode-openai-auth 0.11.1 → 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,304 @@
1
+ import { assertNotInsideHook } from "./9deae3c4da4fd63b-hooks.js";
2
+ import { notReadyError, readPool, runOperation, withTransaction, } from "./458b934e92490331-mutate.js";
3
+ import { assertBoundRowAttribution, protectIn, validateAttribution, } from "./fe02abeadc535f77-rows.js";
4
+ import { refusal, rowLockSpec } from "./83bc3ba9d0fe2761-runtime.js";
5
+ import { CREDENTIAL_STAMP_KEY, canonicalDigest, fingerprintOf, idProblem, isCredentialEpoch, isRecord, POOL_KEY, PROVIDER_STATE_KEY, parseStamp, rosterOf, rowLockKey, } from "./cd94316a888b0a8e-schema.js";
6
+ function receiptsIn(config) {
7
+ const pool = config[POOL_KEY];
8
+ return isRecord(pool) && isRecord(pool.publications) ? pool.publications : {};
9
+ }
10
+ function receiptIn(config, operationId) {
11
+ const receipts = receiptsIn(config);
12
+ if (!Object.hasOwn(receipts, operationId))
13
+ return undefined;
14
+ const raw = receipts[operationId];
15
+ if (!isRecord(raw) ||
16
+ raw.operationId !== operationId ||
17
+ typeof raw.planDigest !== 'string' ||
18
+ !/^[a-f0-9]{64}$/.test(raw.planDigest) ||
19
+ !['committed', 'cleaned'].includes(String(raw.phase)) ||
20
+ !Array.isArray(raw.removed) ||
21
+ !Array.isArray(raw.finalized) ||
22
+ raw.removed.some((ref) => !isRecord(ref) ||
23
+ typeof ref.id !== 'string' ||
24
+ !isCredentialEpoch(ref.credentialEpoch)) ||
25
+ raw.finalized.some((ref) => !isRecord(ref) ||
26
+ typeof ref.id !== 'string' ||
27
+ !isCredentialEpoch(ref.credentialEpoch) ||
28
+ typeof ref.reservation !== 'string' ||
29
+ !ref.reservation ||
30
+ typeof ref.enabled !== 'boolean'))
31
+ throw refusal('publishRoster', operationId, 'invalid-input', 'the stored publication receipt is malformed');
32
+ return structuredClone(raw);
33
+ }
34
+ export async function publication(rt, operationId) {
35
+ const result = await readPool(rt.ctx);
36
+ if (result.status !== 'ready')
37
+ throw notReadyError(result, 'publishRoster', operationId);
38
+ return receiptIn(result.config, operationId);
39
+ }
40
+ function validatePlan(rt, plan) {
41
+ const fail = () => {
42
+ throw refusal('publishRoster', plan?.operationId ?? '', 'invalid-input', 'the publication plan is invalid');
43
+ };
44
+ if (!isRecord(plan) ||
45
+ typeof plan.operationId !== 'string' ||
46
+ !plan.operationId ||
47
+ !Array.isArray(plan.remove) ||
48
+ !Array.isArray(plan.finalize) ||
49
+ !Array.isArray(plan.order))
50
+ fail();
51
+ const ids = new Set();
52
+ for (const ref of [...plan.remove, ...plan.finalize]) {
53
+ if (!isRecord(ref) ||
54
+ typeof ref.id !== 'string' ||
55
+ idProblem(ref.id) ||
56
+ ids.has(ref.id) ||
57
+ !ref.attribution)
58
+ fail();
59
+ ids.add(ref.id);
60
+ validateAttribution('publishRoster', ref.id, ref.attribution);
61
+ if (ref.attribution.identity !== undefined &&
62
+ (typeof ref.attribution.identity !== 'string' ||
63
+ !ref.attribution.identity))
64
+ fail();
65
+ }
66
+ for (const ref of plan.remove)
67
+ if ((rt.ctx.requireRemovedFingerprint && !ref.fingerprint) ||
68
+ (ref.fingerprint !== undefined && typeof ref.fingerprint !== 'string'))
69
+ fail();
70
+ for (const ref of plan.finalize)
71
+ if (typeof ref.reservation !== 'string' ||
72
+ !ref.reservation ||
73
+ typeof ref.enabled !== 'boolean' ||
74
+ (ref.enabled
75
+ ? ref.disabledReason !== undefined
76
+ : typeof ref.disabledReason !== 'string'))
77
+ fail();
78
+ if (plan.order.some((id) => typeof id !== 'string' || idProblem(id)) ||
79
+ new Set(plan.order).size !== plan.order.length)
80
+ fail();
81
+ }
82
+ function saveReceipt(tx, receipt) {
83
+ tx.entries();
84
+ const pool = tx.config[POOL_KEY];
85
+ const receipts = { ...receiptsIn(tx.config) };
86
+ Object.defineProperty(receipts, receipt.operationId, {
87
+ value: receipt,
88
+ enumerable: true,
89
+ writable: true,
90
+ configurable: true,
91
+ });
92
+ pool.publications = receipts;
93
+ // Never forget which plan used an operation id or the actual row epochs it
94
+ // changed: otherwise an old replay could publish again against another pool.
95
+ delete pool.publicationOrder;
96
+ }
97
+ function publicationSurvivors(tx, plan, rows) {
98
+ const rosterIds = new Set();
99
+ for (const raw of rosterOf(tx.config)) {
100
+ if (!isRecord(raw) || typeof raw.id !== 'string')
101
+ continue;
102
+ if (rosterIds.has(raw.id))
103
+ throw refusal('publishRoster', plan.operationId, 'invalid-input', 'publication needs a roster of unique named rows');
104
+ rosterIds.add(raw.id);
105
+ }
106
+ const removedIds = new Set(plan.remove.map((ref) => ref.id));
107
+ const survivors = rows.filter((row) => row.invalid !== 'roster' && !removedIds.has(row.id));
108
+ if (survivors.length !== plan.order.length ||
109
+ plan.order.some((id) => !survivors.some((row) => row.id === id)) ||
110
+ survivors.some((row) => !plan.order.includes(row.id)))
111
+ throw refusal('publishRoster', plan.operationId, 'invalid-input', 'order must name exactly the surviving roster');
112
+ return survivors;
113
+ }
114
+ async function cleanup(tx, receipt) {
115
+ await tx.assertAll();
116
+ for (const ref of receipt.removed) {
117
+ const stamp = parseStamp(tx.stateAccount(ref.id)?.[CREDENTIAL_STAMP_KEY]);
118
+ // A later add uses a later epoch. Cleanup never deletes that successor.
119
+ if (stamp?.credentialEpoch === ref.credentialEpoch && !tx.rosterRow(ref.id))
120
+ tx.dropStateAccount(ref.id);
121
+ }
122
+ for (const ref of receipt.finalized) {
123
+ const account = tx.stateAccount(ref.id);
124
+ const stamp = parseStamp(account?.[CREDENTIAL_STAMP_KEY]);
125
+ if (account &&
126
+ stamp?.credentialEpoch === ref.credentialEpoch &&
127
+ stamp.staged?.reservation === ref.reservation) {
128
+ const next = {
129
+ ...account[CREDENTIAL_STAMP_KEY],
130
+ };
131
+ delete next.staged;
132
+ tx.setStateAccount(ref.id, { ...account, [CREDENTIAL_STAMP_KEY]: next });
133
+ }
134
+ }
135
+ await tx.commitState(undefined, { durable: true });
136
+ const cleaned = { ...receipt, phase: 'cleaned' };
137
+ saveReceipt(tx, cleaned);
138
+ await tx.commitConfig({ durable: true });
139
+ return cleaned;
140
+ }
141
+ export async function publishRoster(rt, plan, options = {}) {
142
+ assertNotInsideHook('publishRoster');
143
+ return runOperation(rt.ctx, 'publishRoster', plan?.operationId ?? '', options.onFailure, async (locks, progress) => {
144
+ if (!isRecord(plan) ||
145
+ typeof plan.operationId !== 'string' ||
146
+ !plan.operationId)
147
+ throw refusal('publishRoster', plan?.operationId ?? '', 'invalid-input', 'the publication needs an operation id');
148
+ plan = structuredClone(plan);
149
+ const planDigest = canonicalDigest(plan);
150
+ const named = [
151
+ ...(Array.isArray(plan.remove) ? plan.remove : []),
152
+ ...(Array.isArray(plan.finalize) ? plan.finalize : []),
153
+ ].filter((ref) => isRecord(ref) && typeof ref.id === 'string');
154
+ for (let attempt = 0;; attempt++) {
155
+ const seen = await readPool(rt.ctx);
156
+ if (seen.status !== 'ready')
157
+ throw notReadyError(seen, 'publishRoster', plan.operationId);
158
+ const keys = new Map(named.map(({ id }) => [
159
+ id,
160
+ rowLockKey(seen.rows.find((row) => row.id === id) ?? { id }),
161
+ ]));
162
+ // All row keys precede the provider lock. A refresh owns just one row
163
+ // before that lock, so it cannot wait for a second row this call holds.
164
+ for (const key of [...new Set(keys.values())].sort())
165
+ await locks.acquire(rowLockSpec(rt, { id: key }));
166
+ await locks.acquire(options.providerLock ?? rt.providerLock);
167
+ for (const extra of options.extraLocks ?? [])
168
+ await locks.acquire(extra);
169
+ const result = await withTransaction(rt.ctx, locks, progress, { operation: 'publishRoster', rowId: plan.operationId }, async (tx) => {
170
+ const rows = tx.rows();
171
+ if (named.some(({ id }) => rowLockKey(rows.find((row) => row.id === id) ?? { id }) !==
172
+ keys.get(id)))
173
+ return undefined;
174
+ const prior = receiptIn(tx.config, plan.operationId);
175
+ if (prior) {
176
+ if (prior.planDigest !== planDigest)
177
+ throw refusal('publishRoster', plan.operationId, 'publication-mismatch', 'the operation id belongs to a different publication plan');
178
+ if (prior.phase === 'cleaned')
179
+ return { outcome: 'already-cleaned', receipt: prior };
180
+ tx.markPublicationDecision();
181
+ await tx.syncConfig();
182
+ return { outcome: 'cleaned', receipt: await cleanup(tx, prior) };
183
+ }
184
+ validatePlan(rt, plan);
185
+ const survivors = publicationSurvivors(tx, plan, rows);
186
+ const removed = [];
187
+ for (const ref of plan.remove) {
188
+ tx.assertNotStaged(ref.id);
189
+ const row = rows.find((row) => row.id === ref.id);
190
+ if (!row)
191
+ throw refusal('publishRoster', ref.id, 'attribution', 'the removed row no longer exists');
192
+ assertBoundRowAttribution(ref.id, row, ref.attribution, 'publishRoster');
193
+ if (!row.credential ||
194
+ (ref.fingerprint !== undefined &&
195
+ fingerprintOf(row.credential) !== ref.fingerprint))
196
+ throw refusal('publishRoster', ref.id, 'attribution', 'the removed secret no longer matches');
197
+ removed.push(row);
198
+ }
199
+ const finalized = [];
200
+ for (const ref of plan.finalize) {
201
+ const row = rows.find((row) => row.id === ref.id);
202
+ const stamp = parseStamp(tx.stateAccount(ref.id)?.[CREDENTIAL_STAMP_KEY]);
203
+ if (!row ||
204
+ row.staged?.reservation !== ref.reservation ||
205
+ stamp?.staged?.reservation !== ref.reservation ||
206
+ row.enabled)
207
+ throw refusal('publishRoster', ref.id, 'row-staged', 'the finalized row is not disabled under this reservation');
208
+ const account = tx.stateAccount(ref.id);
209
+ const raw = tx.rosterRow(ref.id);
210
+ const storedProviderState = account?.[PROVIDER_STATE_KEY];
211
+ const fullProviderDigest = storedProviderState === undefined
212
+ ? undefined
213
+ : canonicalDigest(storedProviderState);
214
+ if (!stamp?.staged ||
215
+ stamp.staged.label !== raw?.label ||
216
+ stamp.staged.disabledReason !== row.disabledReason ||
217
+ stamp.staged.providerState !== fullProviderDigest)
218
+ throw refusal('publishRoster', ref.id, 'row-staged', 'the staged label, disabled reason or full provider state no longer matches its stamp');
219
+ assertBoundRowAttribution(ref.id, row, ref.attribution, 'publishRoster');
220
+ finalized.push(row);
221
+ }
222
+ const tornSurvivor = survivors.find((row) => row.torn);
223
+ if (tornSurvivor)
224
+ throw refusal('publishRoster', plan.operationId, 'attribution', `Row ${tornSurvivor.id} has an interrupted write; complete it with a normal store operation before publishing.`, false);
225
+ const identities = new Set();
226
+ for (const row of survivors) {
227
+ const enabled = plan.finalize.find((ref) => ref.id === row.id)?.enabled ??
228
+ row.enabled;
229
+ if (!enabled ||
230
+ row.type !== 'oauth' ||
231
+ row.identity === undefined)
232
+ continue;
233
+ if (identities.has(row.identity))
234
+ throw refusal('publishRoster', row.id, 'invalid-input', 'the final enabled roster repeats a known identity');
235
+ identities.add(row.identity);
236
+ }
237
+ for (const row of survivors) {
238
+ if (row.identity !== undefined &&
239
+ row.fingerprint !== undefined &&
240
+ survivors.some((other) => other.id !== row.id &&
241
+ other.identity !== undefined &&
242
+ other.identity !== row.identity &&
243
+ other.fingerprint === row.fingerprint))
244
+ throw refusal('publishRoster', row.id, 'attribution', 'the resulting pool gives the same secret different known identities');
245
+ }
246
+ for (const row of finalized)
247
+ if (row.identity !== undefined &&
248
+ removed.some((old) => old.identity !== undefined &&
249
+ old.identity !== row.identity &&
250
+ old.fingerprint === row.fingerprint))
251
+ throw refusal('publishRoster', row.id, 'attribution', 'the staged secret belonged to a different known identity');
252
+ await protectIn(tx, plan.operationId, options.protect);
253
+ const receipt = {
254
+ operationId: plan.operationId,
255
+ planDigest,
256
+ phase: 'committed',
257
+ removed: removed.map((row) => ({
258
+ id: row.id,
259
+ credentialEpoch: row.credentialEpoch ?? 1,
260
+ })),
261
+ finalized: plan.finalize.map((ref, index) => ({
262
+ id: ref.id,
263
+ credentialEpoch: finalized[index]?.credentialEpoch ?? 1,
264
+ reservation: ref.reservation,
265
+ enabled: ref.enabled,
266
+ })),
267
+ };
268
+ for (const ref of plan.remove)
269
+ tx.dropRosterRows(ref.id);
270
+ for (const ref of plan.finalize) {
271
+ const raw = tx.rosterRow(ref.id);
272
+ raw.enabled = ref.enabled;
273
+ const entry = { ...tx.entry(ref.id) };
274
+ delete entry.staged;
275
+ if (ref.enabled)
276
+ delete entry.disabledReason;
277
+ else
278
+ entry.disabledReason = ref.disabledReason;
279
+ tx.setEntry(ref.id, entry);
280
+ }
281
+ const preserved = tx
282
+ .roster()
283
+ .filter((raw) => !isRecord(raw) ||
284
+ typeof raw.id !== 'string' ||
285
+ rows.find((row) => row.id === raw.id)?.invalid === 'roster');
286
+ tx.config.accounts = [
287
+ ...plan.order.map((id) => tx.rosterRow(id)),
288
+ ...preserved,
289
+ ];
290
+ saveReceipt(tx, receipt);
291
+ // This durable rename is the irreversible roster decision. Everything
292
+ // after it follows only the receipt, including after a process restart.
293
+ await tx.syncState();
294
+ await tx.commitConfig({ durable: true, publicationDecision: true });
295
+ return { outcome: 'published', receipt: await cleanup(tx, receipt) };
296
+ }, { completeTorn: false });
297
+ if (result)
298
+ return result;
299
+ await locks.releaseAll();
300
+ if (attempt >= 1)
301
+ throw refusal('publishRoster', plan.operationId, 'row-key-changed', 'a named row changed lock key twice during publication', true);
302
+ }
303
+ });
304
+ }
@@ -0,0 +1,120 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { writeJsonAtomic } from "./7211cfec06a298bc-atomic-write.js";
3
+ import { PoolOperationError } from "./04a6357627de5ed0-errors.js";
4
+ import { assertNotInsideHook, runInsideHook } from "./9deae3c4da4fd63b-hooks.js";
5
+ import { notReadyError, readPool, runOperation } from "./458b934e92490331-mutate.js";
6
+ import { isRecord, POOL_KEY } from "./cd94316a888b0a8e-schema.js";
7
+ /**
8
+ * Top-level config keys the pool owns: the legacy `version`, the legacy
9
+ * roster (`accounts`) and the pool's own key. A settings write never reads
10
+ * them into the settings it hands out and refuses a result that sets them,
11
+ * so the roster and the per-row entries change only through row operations.
12
+ */
13
+ export const POOL_OWNED_KEYS = Object.freeze([
14
+ 'version',
15
+ 'accounts',
16
+ POOL_KEY,
17
+ ]);
18
+ function settingsOf(config) {
19
+ const settings = {};
20
+ for (const [key, value] of Object.entries(config))
21
+ if (!POOL_OWNED_KEYS.includes(key))
22
+ defineKey(settings, key, value);
23
+ return settings;
24
+ }
25
+ /** Sets a key as an own data property, so a `__proto__` key stays a plain key. */
26
+ function defineKey(target, key, value) {
27
+ Object.defineProperty(target, key, {
28
+ value,
29
+ enumerable: true,
30
+ writable: true,
31
+ configurable: true,
32
+ });
33
+ }
34
+ /**
35
+ * The next config: the pool-owned keys exactly as read, every settings key
36
+ * from `next` in its existing position, settings keys `next` dropped left
37
+ * out, and new settings keys appended.
38
+ */
39
+ function composeConfig(config, next) {
40
+ const out = {};
41
+ for (const [key, value] of Object.entries(config)) {
42
+ if (POOL_OWNED_KEYS.includes(key))
43
+ defineKey(out, key, value);
44
+ else if (Object.hasOwn(next, key))
45
+ defineKey(out, key, next[key]);
46
+ }
47
+ for (const [key, value] of Object.entries(next))
48
+ if (!Object.hasOwn(config, key))
49
+ defineKey(out, key, value);
50
+ return out;
51
+ }
52
+ function settingsRefusal(message) {
53
+ return new PoolOperationError({
54
+ operation: 'updateSettings',
55
+ phase: 'before-first-write',
56
+ retryable: false,
57
+ kind: 'invalid-input',
58
+ message,
59
+ });
60
+ }
61
+ /** Reads the settings without taking a lock or writing anything. */
62
+ export async function readPoolSettings(rt) {
63
+ const result = await readPool(rt.ctx);
64
+ if (result.status === 'error')
65
+ return result;
66
+ return { status: result.status, settings: settingsOf(result.config) };
67
+ }
68
+ /**
69
+ * One locked read-modify-write of the plugin's settings in the config file
70
+ * the pool lives in. The pool must be ready (a pending migration or a load
71
+ * error refuses before the mutator runs). A mutator result that sets a
72
+ * pool-owned key refuses (`invalid-input`) with nothing written; a result
73
+ * equal to the current settings writes nothing. The state file is never
74
+ * touched, and the pool-owned keys are written back exactly as read.
75
+ */
76
+ export async function updatePoolSettings(rt, mutator, options = {}) {
77
+ assertNotInsideHook('updateSettings');
78
+ const { ctx } = rt;
79
+ const onFailure = options.onFailure;
80
+ return runOperation(ctx, 'updateSettings', undefined, onFailure && ((_rowId, error) => onFailure(error)), async (locks, progress) => {
81
+ for (const extra of options.extraLocks ?? [])
82
+ await locks.acquire(extra);
83
+ // `mark` is where the store locks start on the lock stack. They are
84
+ // released at the end of this block, so `onFailure` runs holding only
85
+ // the caller's extra locks, as it does for every other store operation.
86
+ const mark = locks.held.length;
87
+ try {
88
+ for (const spec of ctx.storeLocks)
89
+ await locks.acquire(spec);
90
+ const result = await readPool(ctx);
91
+ if (result.status !== 'ready')
92
+ throw notReadyError(result, 'updateSettings', undefined);
93
+ const current = settingsOf(result.config);
94
+ const draft = structuredClone(current);
95
+ const returned = await runInsideHook('updateSettings', () => mutator(draft));
96
+ const next = returned === undefined ? draft : returned;
97
+ if (!isRecord(next))
98
+ throw settingsRefusal('the settings mutator must produce an object');
99
+ const owned = POOL_OWNED_KEYS.filter((key) => Object.hasOwn(next, key));
100
+ if (owned.length > 0)
101
+ throw settingsRefusal(`settings cannot set the pool-owned key(s) ${owned.join(', ')}`);
102
+ if (isDeepStrictEqual(next, current))
103
+ return { settings: current, outcome: 'unchanged' };
104
+ const config = composeConfig(result.config, next);
105
+ const info = { operation: 'updateSettings', rowId: undefined };
106
+ await writeJsonAtomic(ctx.configPath, config, {
107
+ beforeRename: async () => {
108
+ await ctx.onStep?.('before-config-write', info);
109
+ await locks.assertAll();
110
+ },
111
+ });
112
+ progress.writes++;
113
+ await ctx.onStep?.('after-config-write', info);
114
+ return { settings: settingsOf(config), outcome: 'updated' };
115
+ }
116
+ finally {
117
+ await locks.releaseTo(mark);
118
+ }
119
+ });
120
+ }
@@ -0,0 +1,68 @@
1
+ import { PoolOperationError } from "./04a6357627de5ed0-errors.js";
2
+ import { notReadyError, readPool, } from "./458b934e92490331-mutate.js";
3
+ import { assertNotReserved } from "./08eef45626a94992-reserved.js";
4
+ import { CREDENTIAL_STAMP_KEY, entryIn, isRecord, rowLockKey, } from "./cd94316a888b0a8e-schema.js";
5
+ /**
6
+ * The row lock's (name, path): keyed by the row's recorded wire identity when
7
+ * known, else its local id, beside the state file. The key is prefixed and
8
+ * URL-encoded so it can never name a store lock or leave the directory.
9
+ */
10
+ export function rowLockSpec(rt, row) {
11
+ return {
12
+ ...rt.rowLockOptions,
13
+ name: `row-${encodeURIComponent(rowLockKey(row))}`,
14
+ path: rt.ctx.statePath,
15
+ };
16
+ }
17
+ /** An unlocked read that must find a ready pool holding the row. */
18
+ export async function readRow(rt, operation, id) {
19
+ const result = await readPool(rt.ctx);
20
+ if (result.status !== 'ready')
21
+ throw notReadyError(result, operation, id);
22
+ const row = result.rows.find((candidate) => candidate.id === id);
23
+ const entry = entryIn(result.config, id);
24
+ const accounts = result.state.accounts;
25
+ const account = isRecord(accounts) && Object.hasOwn(accounts, id) ? accounts[id] : undefined;
26
+ const stamp = isRecord(account) ? account[CREDENTIAL_STAMP_KEY] : undefined;
27
+ if (row &&
28
+ !['enable', 'disable'].includes(operation) &&
29
+ entry &&
30
+ Object.hasOwn(entry, 'staged'))
31
+ assertNotReserved(operation, id, entry.staged);
32
+ if (!row && isRecord(stamp) && Object.hasOwn(stamp, 'staged'))
33
+ assertNotReserved(operation, id, stamp.staged);
34
+ if (!row)
35
+ throw unknownRow(operation, id);
36
+ return { result, row };
37
+ }
38
+ export function unknownRow(operation, id) {
39
+ return new PoolOperationError({
40
+ operation,
41
+ rowId: id,
42
+ phase: operation === 'pull' ? 'pull' : 'before-first-write',
43
+ retryable: false,
44
+ kind: 'unknown-row',
45
+ message: `no row ${id} in the pool`,
46
+ });
47
+ }
48
+ /**
49
+ * Refuses a row the store was told to distrust: opened with
50
+ * `requireCredentialStamps`, a row whose credential stamp is not bound loads
51
+ * `unbound` (see `PoolRow.unbound`). Called on every locked read of the row
52
+ * an operation acts on, before it calls a provider or writes, so a credential
53
+ * another writer swapped in while the operation waited is refused too.
54
+ */
55
+ export function requireBound(operation, row) {
56
+ if (row.unbound)
57
+ throw refusal(operation, row.id, 'unbound-credential', `row ${row.id}'s credential is not the one this store stamped for it (stamp ${row.stamp}); replace it with fresh material`);
58
+ }
59
+ export function refusal(operation, id, kind, message, retryable = false) {
60
+ return new PoolOperationError({
61
+ operation,
62
+ rowId: id,
63
+ phase: operation === 'pull' ? 'pull' : 'before-first-write',
64
+ retryable,
65
+ kind,
66
+ message,
67
+ });
68
+ }