@apifuse/provider-sdk 2.2.0-beta.2 → 2.2.0-beta.21

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 (291) hide show
  1. package/AUTHORING.md +487 -0
  2. package/CHANGELOG.md +90 -0
  3. package/README.md +49 -3
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +36 -81
  10. package/bin/apifuse-pack-types.ts +305 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +659 -111
  13. package/bin/apifuse-submit-check.ts +546 -48
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +65 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +176 -8
  32. package/dist/config/loader.js +424 -95
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +9 -1
  39. package/dist/define.js +304 -119
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/error-resolution.d.ts +2 -0
  43. package/dist/error-resolution.js +90 -0
  44. package/dist/errors.d.ts +22 -1
  45. package/dist/errors.js +88 -0
  46. package/dist/fixture-sanitization.d.ts +26 -0
  47. package/dist/fixture-sanitization.js +216 -0
  48. package/dist/i18n/catalog.d.ts +2 -2
  49. package/dist/i18n/catalog.js +4 -10
  50. package/dist/i18n/index.d.ts +2 -2
  51. package/dist/i18n/index.js +2 -2
  52. package/dist/i18n/keys.d.ts +2 -2
  53. package/dist/index.d.ts +46 -42
  54. package/dist/index.js +41 -37
  55. package/dist/lint.d.ts +1 -1
  56. package/dist/lint.js +8 -15
  57. package/dist/native-address.d.ts +43 -0
  58. package/dist/native-address.js +281 -0
  59. package/dist/native-egress-policy.d.ts +31 -0
  60. package/dist/native-egress-policy.js +288 -0
  61. package/dist/observability.d.ts +5 -2
  62. package/dist/observability.js +48 -1
  63. package/dist/provider.d.ts +12 -11
  64. package/dist/provider.js +10 -9
  65. package/dist/public-schema-field-lint.d.ts +1 -1
  66. package/dist/recipes/gov-api.js +1 -1
  67. package/dist/runtime/auth-flow.d.ts +2 -1
  68. package/dist/runtime/auth-flow.js +4 -3
  69. package/dist/runtime/browser.d.ts +1 -1
  70. package/dist/runtime/browser.js +15 -29
  71. package/dist/runtime/cache.d.ts +1 -1
  72. package/dist/runtime/cache.js +4 -8
  73. package/dist/runtime/choice.d.ts +1 -1
  74. package/dist/runtime/choice.js +31 -35
  75. package/dist/runtime/credential.d.ts +1 -1
  76. package/dist/runtime/credential.js +1 -1
  77. package/dist/runtime/env.d.ts +1 -1
  78. package/dist/runtime/executor.d.ts +1 -1
  79. package/dist/runtime/executor.js +31 -4
  80. package/dist/runtime/http.d.ts +2 -2
  81. package/dist/runtime/http.js +387 -47
  82. package/dist/runtime/insights.d.ts +1 -1
  83. package/dist/runtime/insights.js +6 -13
  84. package/dist/runtime/instrumentation.d.ts +2 -2
  85. package/dist/runtime/instrumentation.js +345 -22
  86. package/dist/runtime/keyring.js +1 -1
  87. package/dist/runtime/namespace.js +1 -1
  88. package/dist/runtime/native-network.d.ts +127 -0
  89. package/dist/runtime/native-network.js +1298 -0
  90. package/dist/runtime/otlp.d.ts +1 -1
  91. package/dist/runtime/perf.d.ts +1 -1
  92. package/dist/runtime/provider.d.ts +1 -1
  93. package/dist/runtime/provider.js +1 -2
  94. package/dist/runtime/proxy-errors.d.ts +1 -1
  95. package/dist/runtime/proxy-errors.js +9 -7
  96. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  97. package/dist/runtime/proxy-nodemaven.js +146 -0
  98. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  99. package/dist/runtime/proxy-retry-policy.js +2 -2
  100. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  101. package/dist/runtime/proxy-telemetry.js +55 -52
  102. package/dist/runtime/redirects.d.ts +29 -0
  103. package/dist/runtime/redirects.js +36 -0
  104. package/dist/runtime/redis.d.ts +1 -1
  105. package/dist/runtime/redis.js +2 -4
  106. package/dist/runtime/request-options.d.ts +68 -1
  107. package/dist/runtime/request-options.js +548 -0
  108. package/dist/runtime/secrets.d.ts +27 -0
  109. package/dist/runtime/secrets.js +51 -0
  110. package/dist/runtime/state.d.ts +2 -2
  111. package/dist/runtime/state.js +238 -26
  112. package/dist/runtime/stealth.d.ts +5 -3
  113. package/dist/runtime/stealth.js +423 -143
  114. package/dist/runtime/stt.d.ts +1 -1
  115. package/dist/runtime/stt.js +11 -15
  116. package/dist/runtime/trace.d.ts +2 -2
  117. package/dist/runtime/trace.js +2 -4
  118. package/dist/runtime/waterfall.d.ts +1 -1
  119. package/dist/schema.d.ts +1 -1
  120. package/dist/schema.js +7 -15
  121. package/dist/serve.d.ts +1 -1
  122. package/dist/serve.js +1 -1
  123. package/dist/server/index.d.ts +7 -7
  124. package/dist/server/index.js +6 -6
  125. package/dist/server/self-test-input-tokens.d.ts +2 -1
  126. package/dist/server/self-test-input-tokens.js +18 -14
  127. package/dist/server/self-test-redaction.d.ts +1 -1
  128. package/dist/server/self-test-redaction.js +1 -1
  129. package/dist/server/self-test.d.ts +104 -3
  130. package/dist/server/self-test.js +673 -115
  131. package/dist/server/serve.d.ts +116 -4
  132. package/dist/server/serve.js +799 -128
  133. package/dist/server/types.d.ts +34 -9
  134. package/dist/server/types.js +8 -1
  135. package/dist/stateful/errors.d.ts +14 -0
  136. package/dist/stateful/errors.js +14 -0
  137. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  138. package/dist/stateful/http-provider-event-emitter.js +237 -0
  139. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  140. package/dist/stateful/http-session-owner-registry.js +210 -0
  141. package/dist/stateful/index.d.ts +18 -0
  142. package/dist/stateful/index.js +18 -0
  143. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  144. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  145. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  146. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  147. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  148. package/dist/stateful/provider-event-pipeline.js +1 -0
  149. package/dist/stateful/provider-events.d.ts +101 -0
  150. package/dist/stateful/provider-events.js +289 -0
  151. package/dist/stateful/session-key.d.ts +15 -0
  152. package/dist/stateful/session-key.js +86 -0
  153. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  154. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  155. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  156. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  157. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  158. package/dist/stateful/stateful-provider-adapter.js +287 -0
  159. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  160. package/dist/stateful/stateful-provider-observability.js +161 -0
  161. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  162. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  163. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  164. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  165. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  166. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  167. package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
  168. package/dist/stateful/stateful-provider-session-routing.js +353 -0
  169. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  170. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  171. package/dist/stateful-signing.d.ts +18 -0
  172. package/dist/stateful-signing.js +27 -0
  173. package/dist/stealth/profiles.d.ts +1 -1
  174. package/dist/stealth/profiles.js +5 -14
  175. package/dist/stream-evidence.d.ts +74 -0
  176. package/dist/stream-evidence.js +785 -0
  177. package/dist/stream.d.ts +1 -1
  178. package/dist/testing/index.d.ts +2 -2
  179. package/dist/testing/index.js +2 -2
  180. package/dist/testing/run.d.ts +32 -2
  181. package/dist/testing/run.js +478 -28
  182. package/dist/types.d.ts +342 -14
  183. package/dist/types.js +1 -0
  184. package/dist/user-input.d.ts +30 -0
  185. package/dist/user-input.js +66 -0
  186. package/package.json +16 -5
  187. package/src/auth-turn/index.ts +1 -1
  188. package/src/auth.ts +28 -86
  189. package/src/ceremonies/index.ts +103 -78
  190. package/src/cli/commands.ts +10 -0
  191. package/src/cli/create.ts +42 -35
  192. package/src/cli/prompt-assets.ts +865 -0
  193. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  194. package/src/config/loader.ts +652 -204
  195. package/src/contract-serialization.ts +5 -11
  196. package/src/contract-types.ts +2 -2
  197. package/src/contract.ts +12 -28
  198. package/src/define.ts +499 -498
  199. package/src/dev.ts +4 -9
  200. package/src/error-resolution.ts +91 -0
  201. package/src/errors.ts +110 -5
  202. package/src/fixture-sanitization.ts +247 -0
  203. package/src/i18n/catalog.ts +10 -32
  204. package/src/i18n/index.ts +2 -2
  205. package/src/i18n/keys.ts +5 -11
  206. package/src/index.ts +112 -42
  207. package/src/lint.ts +88 -152
  208. package/src/native-address.ts +340 -0
  209. package/src/native-egress-policy.ts +358 -0
  210. package/src/observability.ts +51 -1
  211. package/src/provider.ts +66 -11
  212. package/src/public-schema-field-lint.ts +7 -33
  213. package/src/recipes/gov-api.ts +2 -5
  214. package/src/runtime/auth-flow.ts +7 -7
  215. package/src/runtime/browser.ts +64 -187
  216. package/src/runtime/cache.ts +20 -67
  217. package/src/runtime/choice.ts +79 -132
  218. package/src/runtime/credential.ts +2 -2
  219. package/src/runtime/env.ts +1 -1
  220. package/src/runtime/executor.ts +43 -20
  221. package/src/runtime/http.ts +494 -57
  222. package/src/runtime/insights.ts +15 -53
  223. package/src/runtime/instrumentation.ts +495 -66
  224. package/src/runtime/keyring.ts +7 -19
  225. package/src/runtime/namespace.ts +2 -7
  226. package/src/runtime/native-network.ts +1686 -0
  227. package/src/runtime/otlp.ts +12 -23
  228. package/src/runtime/perf.ts +1 -1
  229. package/src/runtime/provider.ts +4 -9
  230. package/src/runtime/proxy-errors.ts +29 -42
  231. package/src/runtime/proxy-nodemaven.ts +221 -0
  232. package/src/runtime/proxy-retry-policy.ts +3 -3
  233. package/src/runtime/proxy-telemetry.ts +79 -77
  234. package/src/runtime/redirects.ts +66 -0
  235. package/src/runtime/redis.ts +4 -12
  236. package/src/runtime/request-options.ts +679 -9
  237. package/src/runtime/secrets.ts +64 -0
  238. package/src/runtime/state.ts +353 -133
  239. package/src/runtime/stealth.ts +505 -154
  240. package/src/runtime/stt.ts +38 -94
  241. package/src/runtime/trace.ts +14 -44
  242. package/src/runtime/waterfall.ts +5 -18
  243. package/src/schema.ts +23 -84
  244. package/src/serve.ts +1 -1
  245. package/src/server/index.ts +29 -7
  246. package/src/server/self-test-input-tokens.ts +29 -14
  247. package/src/server/self-test-redaction.ts +2 -2
  248. package/src/server/self-test.ts +857 -132
  249. package/src/server/serve.ts +1151 -328
  250. package/src/server/types.ts +12 -13
  251. package/src/stateful/README.md +146 -0
  252. package/src/stateful/errors.ts +23 -0
  253. package/src/stateful/http-provider-event-emitter.ts +314 -0
  254. package/src/stateful/http-session-owner-registry.ts +306 -0
  255. package/src/stateful/index.ts +18 -0
  256. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  257. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  258. package/src/stateful/provider-event-pipeline.ts +61 -0
  259. package/src/stateful/provider-events.ts +462 -0
  260. package/src/stateful/session-key.ts +111 -0
  261. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  262. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  263. package/src/stateful/stateful-provider-adapter.ts +562 -0
  264. package/src/stateful/stateful-provider-observability.ts +261 -0
  265. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  266. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  267. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  268. package/src/stateful/stateful-provider-session-routing.ts +555 -0
  269. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  270. package/src/stateful-signing.ts +46 -0
  271. package/src/stealth/profiles.ts +10 -26
  272. package/src/stream-evidence.ts +988 -0
  273. package/src/stream.ts +8 -19
  274. package/src/testing/index.ts +10 -2
  275. package/src/testing/run.ts +653 -74
  276. package/src/types.ts +408 -28
  277. package/src/user-input.ts +118 -0
  278. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  279. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  280. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  281. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  282. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  283. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  284. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  285. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  286. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  287. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  288. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  289. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  290. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  291. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
- import { providerCacheRedisUrlFromEnv } from "../config/loader";
3
+ import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
4
4
  import type {
5
5
  ProviderCache,
6
6
  ProviderCacheGetOrSetOptions,
@@ -8,13 +8,13 @@ import type {
8
8
  ProviderCacheLookupMeta,
9
9
  ProviderCacheResponseMeta,
10
10
  ProviderCacheResult,
11
- } from "../types";
11
+ } from "../types.js";
12
12
  import {
13
13
  createProviderRedisClient,
14
14
  ensureRedisReady,
15
15
  type ProviderRedisClient,
16
16
  withRedisTimeout,
17
- } from "./redis";
17
+ } from "./redis.js";
18
18
 
19
19
  type CacheSource = ProviderCacheLookupMeta["source"];
20
20
 
@@ -123,10 +123,7 @@ function normalizeKeyPart(value: unknown, extra: Set<string>): unknown {
123
123
  }
124
124
 
125
125
  function stableHash(value: unknown): string {
126
- return createHash("sha256")
127
- .update(JSON.stringify(value))
128
- .digest("hex")
129
- .slice(0, 32);
126
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 32);
130
127
  }
131
128
 
132
129
  function jitteredTtlMs(ttlMs: number, jitterPct: number | undefined): number {
@@ -161,10 +158,7 @@ function safeParseEnvelope(raw: string | null): CacheEnvelope | null {
161
158
  }
162
159
  }
163
160
 
164
- function resultWithValue<T>(
165
- value: unknown,
166
- meta: ProviderCacheLookupMeta,
167
- ): ProviderCacheResult<T> {
161
+ function resultWithValue<T>(value: unknown, meta: ProviderCacheLookupMeta): ProviderCacheResult<T> {
168
162
  return {
169
163
  value: <T>value,
170
164
  meta,
@@ -187,18 +181,14 @@ function resultFromEnvelope<T>(
187
181
  });
188
182
  }
189
183
 
190
- function sourceSummary(
191
- events: ProviderCacheLookupMeta[],
192
- ): ProviderCacheResponseMeta["source"] {
184
+ function sourceSummary(events: ProviderCacheLookupMeta[]): ProviderCacheResponseMeta["source"] {
193
185
  const sources = new Set(events.map((event) => event.source));
194
186
  if (sources.size === 0) return undefined;
195
187
  if (sources.size === 1) return events[0]?.source;
196
188
  return "mixed";
197
189
  }
198
190
 
199
- async function withRedisFallback<T>(
200
- operation: () => Promise<T>,
201
- ): Promise<T | undefined> {
191
+ async function withRedisFallback<T>(operation: () => Promise<T>): Promise<T | undefined> {
202
192
  return await withRedisTimeout(operation, {
203
193
  timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
204
194
  onTimeout: () => undefined,
@@ -206,15 +196,10 @@ async function withRedisFallback<T>(
206
196
  });
207
197
  }
208
198
 
209
- export function createProviderCache(
210
- options: ProviderCacheOptions,
211
- ): ProviderCache {
199
+ export function createProviderCache(options: ProviderCacheOptions): ProviderCache {
212
200
  const redisUrl = options.redisUrl ?? providerCacheRedisUrlFromEnv();
213
201
  const backend = getSharedBackend(redisUrl);
214
- const memoryMaxEntries = Math.max(
215
- 1,
216
- options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES,
217
- );
202
+ const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
218
203
  const now = options.now ?? Date.now;
219
204
  const events: ProviderCacheLookupMeta[] = [];
220
205
 
@@ -238,11 +223,7 @@ export function createProviderCache(
238
223
  }
239
224
  }
240
225
 
241
- function rememberEnvelope(
242
- key: string,
243
- envelope: CacheEnvelope,
244
- currentTime: number,
245
- ): void {
226
+ function rememberEnvelope(key: string, envelope: CacheEnvelope, currentTime: number): void {
246
227
  sweepMemory(currentTime);
247
228
  backend.memory.delete(key);
248
229
  backend.memory.set(key, {
@@ -253,11 +234,7 @@ export function createProviderCache(
253
234
  enforceMemoryLimit();
254
235
  }
255
236
 
256
- function touchMemory(
257
- key: string,
258
- entry: MemoryEntry,
259
- currentTime: number,
260
- ): void {
237
+ function touchMemory(key: string, entry: MemoryEntry, currentTime: number): void {
261
238
  backend.memory.delete(key);
262
239
  backend.memory.set(key, { ...entry, lastAccessedAt: currentTime });
263
240
  }
@@ -298,12 +275,7 @@ export function createProviderCache(
298
275
  if (memoryEntry.expiresAt <= currentTime) {
299
276
  backend.memory.delete(key);
300
277
  } else {
301
- const memoryResult = resultFromEnvelope<T>(
302
- key,
303
- memoryEntry,
304
- currentTime,
305
- "memory",
306
- );
278
+ const memoryResult = resultFromEnvelope<T>(key, memoryEntry, currentTime, "memory");
307
279
  if (memoryResult && !memoryResult.meta.stale) {
308
280
  touchMemory(key, memoryEntry, currentTime);
309
281
  return memoryResult;
@@ -334,10 +306,7 @@ export function createProviderCache(
334
306
  cacheOptions: ProviderCacheGetOrSetOptions,
335
307
  ): Promise<void> {
336
308
  const currentTime = now();
337
- const freshTtlMs = jitteredTtlMs(
338
- cacheOptions.ttlMs,
339
- cacheOptions.jitterPct,
340
- );
309
+ const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
341
310
  const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
342
311
  const staleTtlMs = freshTtlMs + staleIfErrorMs;
343
312
  const envelope: CacheEnvelope = {
@@ -352,9 +321,7 @@ export function createProviderCache(
352
321
  if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
353
322
  return;
354
323
  }
355
- await withRedisFallback(() =>
356
- redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs),
357
- );
324
+ await withRedisFallback(() => redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs));
358
325
  }
359
326
 
360
327
  async function loadAndStore<T>(
@@ -385,16 +352,12 @@ export function createProviderCache(
385
352
 
386
353
  return {
387
354
  key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
388
- const extra = new Set(
389
- (keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
390
- );
355
+ const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
391
356
  const normalized = normalizeKeyPart(parts, extra);
392
357
  return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
393
358
  },
394
359
 
395
- async get<T = unknown>(
396
- key: string,
397
- ): Promise<ProviderCacheResult<T> | null> {
360
+ async get<T = unknown>(key: string): Promise<ProviderCacheResult<T> | null> {
398
361
  const result = await read<T>(key);
399
362
  if (result) record(result.meta);
400
363
  return result;
@@ -405,10 +368,7 @@ export function createProviderCache(
405
368
  async delete(key: string): Promise<void> {
406
369
  backend.memory.delete(key);
407
370
  const redis = backend.redis;
408
- if (
409
- !redis ||
410
- !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))
411
- ) {
371
+ if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
412
372
  return;
413
373
  }
414
374
  await withRedisFallback(() => redis.del(key));
@@ -428,10 +388,7 @@ export function createProviderCache(
428
388
  const existingInflight = backend.inflight.get(key);
429
389
  if (existingInflight) {
430
390
  const inflightResult = await existingInflight;
431
- const result = resultWithValue<T>(
432
- inflightResult.value,
433
- inflightResult.meta,
434
- );
391
+ const result = resultWithValue<T>(inflightResult.value, inflightResult.meta);
435
392
  record(result.meta);
436
393
  return result;
437
394
  }
@@ -470,16 +427,12 @@ export function createBypassProviderCache(
470
427
 
471
428
  return {
472
429
  key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
473
- const extra = new Set(
474
- (keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
475
- );
430
+ const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
476
431
  const normalized = normalizeKeyPart(parts, extra);
477
432
  return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
478
433
  },
479
434
 
480
- async get<T = unknown>(
481
- _key: string,
482
- ): Promise<ProviderCacheResult<T> | null> {
435
+ async get<T = unknown>(_key: string): Promise<ProviderCacheResult<T> | null> {
483
436
  return null;
484
437
  },
485
438
 
@@ -10,8 +10,8 @@ import {
10
10
  assertFreshProviderChoiceIssuedAt,
11
11
  ProviderChoiceTokenError,
12
12
  type ProviderChoiceTokenPayload,
13
- } from "../choice-token";
14
- import { ProviderError } from "../errors";
13
+ } from "../choice-token.js";
14
+ import { isProviderError, ProviderError } from "../errors.js";
15
15
  import type {
16
16
  CredentialContext,
17
17
  EnvContext,
@@ -23,7 +23,8 @@ import type {
23
23
  ProviderRequestContext,
24
24
  ProviderRuntimeState,
25
25
  ProviderStateDurationString,
26
- } from "../types";
26
+ StateValue,
27
+ } from "../types.js";
27
28
 
28
29
  export const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV =
29
30
  "APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
@@ -74,18 +75,12 @@ export function createProviderChoiceContext(
74
75
  ): string;
75
76
  function issue<TPayload extends ProviderChoiceTokenPayload>(
76
77
  issueOptions: ProviderChoiceIssueOptions<TPayload> & {
77
- readonly storage: Extract<
78
- ProviderChoiceStorageOptions,
79
- { readonly mode: "server" }
80
- >;
78
+ readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "server" }>;
81
79
  },
82
80
  ): Promise<string>;
83
81
  function issue<TPayload extends ProviderChoiceTokenPayload>(
84
82
  issueOptions: ProviderChoiceIssueOptions<TPayload> & {
85
- readonly storage: Extract<
86
- ProviderChoiceStorageOptions,
87
- { readonly mode: "auto" }
88
- >;
83
+ readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "auto" }>;
89
84
  },
90
85
  ): string | Promise<string>;
91
86
  function issue<TPayload extends ProviderChoiceTokenPayload>(
@@ -112,10 +107,7 @@ export function createProviderChoiceContext(
112
107
  required: true,
113
108
  }),
114
109
  };
115
- const resolvedStorage = resolveIssueStorage(
116
- issueOptions.storage,
117
- issueOptions.payload,
118
- );
110
+ const resolvedStorage = resolveIssueStorage(issueOptions.storage, issueOptions.payload);
119
111
  if (resolvedStorage.mode === "server") {
120
112
  return issueServerStoredChoice({
121
113
  baseEnvelope,
@@ -146,31 +138,19 @@ export function createProviderChoiceContext(
146
138
  ): ProviderChoiceTokenPayload;
147
139
  function parse(
148
140
  parseOptions: ProviderChoiceParseOptions & {
149
- readonly storage: Extract<
150
- ProviderChoiceStorageOptions,
151
- { readonly mode: "server" }
152
- >;
141
+ readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "server" }>;
153
142
  },
154
143
  ): Promise<ProviderChoiceTokenPayload>;
155
144
  function parse(
156
145
  parseOptions: ProviderChoiceParseOptions & {
157
- readonly storage: Extract<
158
- ProviderChoiceStorageOptions,
159
- { readonly mode: "auto" }
160
- >;
146
+ readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "auto" }>;
161
147
  },
162
148
  ): ProviderChoiceTokenPayload | Promise<ProviderChoiceTokenPayload>;
163
149
  function parse(
164
150
  parseOptions: ProviderChoiceParseOptions,
165
151
  ): ProviderChoiceTokenPayload | Promise<ProviderChoiceTokenPayload> {
166
- const [
167
- actualPrefix,
168
- tokenKid,
169
- encodedIv,
170
- encryptedPayload,
171
- authTag,
172
- signature,
173
- ] = parseManagedChoiceTokenParts(parseOptions.token);
152
+ const [actualPrefix, tokenKid, encodedIv, encryptedPayload, authTag, signature] =
153
+ parseManagedChoiceTokenParts(parseOptions.token);
174
154
  if (
175
155
  actualPrefix !== parseOptions.prefix ||
176
156
  tokenKid !== kid ||
@@ -191,13 +171,9 @@ export function createProviderChoiceContext(
191
171
  purpose: parseOptions.purpose,
192
172
  kid: tokenKid,
193
173
  });
194
- const signedBody = [
195
- parseOptions.prefix,
196
- tokenKid,
197
- encodedIv,
198
- encryptedPayload,
199
- authTag,
200
- ].join(".");
174
+ const signedBody = [parseOptions.prefix, tokenKid, encodedIv, encryptedPayload, authTag].join(
175
+ ".",
176
+ );
201
177
  assertManagedChoiceSignature({
202
178
  signedBody,
203
179
  signature,
@@ -247,30 +223,23 @@ export function createTestProviderChoiceContext(
247
223
  return createProviderChoiceContext({
248
224
  ...options,
249
225
  masterSecret:
250
- options.masterSecret ??
251
- "apifuse-test-provider-runtime-choice-token-master-secret",
226
+ options.masterSecret ?? "apifuse-test-provider-runtime-choice-token-master-secret",
252
227
  });
253
228
  }
254
229
 
255
- function resolveChoiceMasterSecret(
256
- options: CreateProviderChoiceContextOptions,
257
- ): string {
230
+ function resolveChoiceMasterSecret(options: CreateProviderChoiceContextOptions): string {
258
231
  const configured =
259
- options.masterSecret ??
260
- options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
232
+ options.masterSecret ?? options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
261
233
  const trimmed = configured?.trim();
262
234
  if (trimmed) return trimmed;
263
- throw new ProviderError(
264
- "Provider runtime choice-token master secret is not configured.",
265
- {
266
- code: "CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
267
- category: "internal_error",
268
- retryable: false,
269
- details: {
270
- secret: PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
271
- },
235
+ throw new ProviderError("Provider runtime choice-token master secret is not configured.", {
236
+ code: "CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
237
+ category: "internal_error",
238
+ retryable: false,
239
+ details: {
240
+ secret: PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
272
241
  },
273
- );
242
+ });
274
243
  }
275
244
 
276
245
  type ManagedChoiceKeyInput = {
@@ -286,9 +255,7 @@ type ManagedChoiceKeys = {
286
255
  readonly binding: Buffer;
287
256
  };
288
257
 
289
- function deriveManagedChoiceKeys(
290
- input: ManagedChoiceKeyInput,
291
- ): ManagedChoiceKeys {
258
+ function deriveManagedChoiceKeys(input: ManagedChoiceKeyInput): ManagedChoiceKeys {
292
259
  return {
293
260
  encryption: deriveManagedChoiceKey(input, "encryption"),
294
261
  signing: deriveManagedChoiceKey(input, "signing"),
@@ -327,22 +294,14 @@ function encryptManagedChoiceToken(options: {
327
294
  ]).toString("base64url");
328
295
  const authTag = cipher.getAuthTag().toString("base64url");
329
296
  const encodedIv = iv.toString("base64url");
330
- const signedBody = [
331
- options.prefix,
332
- options.kid,
333
- encodedIv,
334
- encryptedPayload,
335
- authTag,
336
- ].join(".");
297
+ const signedBody = [options.prefix, options.kid, encodedIv, encryptedPayload, authTag].join(".");
337
298
  const signature = createHmac("sha256", options.keys.signing)
338
299
  .update(signedBody)
339
300
  .digest("base64url");
340
301
  return `${signedBody}.${signature}`;
341
302
  }
342
303
 
343
- async function issueServerStoredChoice<
344
- TPayload extends ProviderChoiceTokenPayload,
345
- >(options: {
304
+ async function issueServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
346
305
  readonly baseEnvelope: Omit<ManagedChoiceEnvelope, "payload">;
347
306
  readonly issueOptions: ProviderChoiceIssueOptions<TPayload>;
348
307
  readonly storage: ServerProviderChoiceStorageOptions;
@@ -351,23 +310,18 @@ async function issueServerStoredChoice<
351
310
  readonly keys: ManagedChoiceKeys;
352
311
  readonly issuedAtMs: number;
353
312
  }): Promise<string> {
354
- const serializedPayload = serializeChoicePayload(
355
- options.issueOptions.payload,
356
- );
313
+ const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
357
314
  const payloadBytes = Buffer.byteLength(serializedPayload, "utf8");
358
315
  if (payloadBytes > options.storage.maxValueBytes) {
359
- throw new ProviderError(
360
- "Provider choice payload exceeds state storage policy.",
361
- {
362
- code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
363
- category: "input_validation",
364
- retryable: false,
365
- details: {
366
- maxValueBytes: options.storage.maxValueBytes,
367
- payloadBytes,
368
- },
316
+ throw new ProviderError("Provider choice payload exceeds state storage policy.", {
317
+ code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
318
+ category: "input_validation",
319
+ retryable: false,
320
+ details: {
321
+ maxValueBytes: options.storage.maxValueBytes,
322
+ payloadBytes,
369
323
  },
370
- );
324
+ });
371
325
  }
372
326
  const stateId = `choice_${randomBytes(16).toString("base64url")}`;
373
327
  const digest = digestChoicePayload(serializedPayload);
@@ -406,9 +360,29 @@ async function parseServerStoredChoice(options: {
406
360
  storage,
407
361
  contextState: options.contextState,
408
362
  });
409
- const record = await namespace.get<ProviderChoiceTokenPayload>(
410
- optionsStateKey(options.handle.state_id),
411
- );
363
+ // Reading a server-stored choice back deserializes a persisted value. A
364
+ // corrupt/undecodable value would otherwise surface as a raw JSON.parse
365
+ // SyntaxError (or another unexpected throwable) that escapes the choice error
366
+ // taxonomy, gets masked as internal_error 500, and is treated as retryable by
367
+ // the hub -> reservation restart loop (2026-07-22 catchtable RCA, candidate A).
368
+ // Convert any non-branded throwable into a branded invalid_payload so it maps
369
+ // to a clean, non-retryable 400. Branded ProviderChoiceTokenError and genuine
370
+ // ProviderError (e.g. Redis-unavailable / state-unavailable) pass through so
371
+ // their category/retryable semantics are preserved.
372
+ let record: StateValue<ProviderChoiceTokenPayload> | null;
373
+ try {
374
+ record = await namespace.get<ProviderChoiceTokenPayload>(
375
+ optionsStateKey(options.handle.state_id),
376
+ );
377
+ } catch (error) {
378
+ if (error instanceof ProviderChoiceTokenError || isProviderError(error)) {
379
+ throw error;
380
+ }
381
+ throw new ProviderChoiceTokenError(
382
+ "invalid_payload",
383
+ "Provider choice token state payload could not be decoded.",
384
+ );
385
+ }
412
386
  if (!record) {
413
387
  throw new ProviderChoiceTokenError(
414
388
  "invalid_payload",
@@ -439,10 +413,7 @@ function resolveIssueStorage<TPayload extends ProviderChoiceTokenPayload>(
439
413
  } {
440
414
  if (!storage || storage.mode === "inline") return { mode: "inline" };
441
415
  if (storage.mode === "server") return { mode: "server", storage };
442
- const payloadBytes = Buffer.byteLength(
443
- serializeChoicePayload(payload),
444
- "utf8",
445
- );
416
+ const payloadBytes = Buffer.byteLength(serializeChoicePayload(payload), "utf8");
446
417
  if (payloadBytes <= storage.maxInlineBytes) return { mode: "inline" };
447
418
  return { mode: "server", storage };
448
419
  }
@@ -536,10 +507,7 @@ function parseManagedChoiceTokenParts(
536
507
  ] {
537
508
  const parts = token.split(".");
538
509
  if (parts.length !== 6) {
539
- throw new ProviderChoiceTokenError(
540
- "invalid_shape",
541
- "Provider choice token shape is invalid.",
542
- );
510
+ throw new ProviderChoiceTokenError("invalid_shape", "Provider choice token shape is invalid.");
543
511
  }
544
512
  return [parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]];
545
513
  }
@@ -601,9 +569,7 @@ function decryptManagedChoiceToken(options: {
601
569
  }
602
570
  }
603
571
 
604
- function isManagedChoiceEnvelope(
605
- value: unknown,
606
- ): value is ManagedChoiceEnvelope {
572
+ function isManagedChoiceEnvelope(value: unknown): value is ManagedChoiceEnvelope {
607
573
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
608
574
  if (!("payload" in value) || !isChoicePayload(value.payload)) return false;
609
575
  return (
@@ -617,9 +583,7 @@ function isManagedChoiceEnvelope(
617
583
  typeof value.issued_at_ms === "number" &&
618
584
  "ttl_ms" in value &&
619
585
  typeof value.ttl_ms === "number" &&
620
- (!("binding" in value) ||
621
- value.binding === undefined ||
622
- isChoiceBinding(value.binding))
586
+ (!("binding" in value) || value.binding === undefined || isChoiceBinding(value.binding))
623
587
  );
624
588
  }
625
589
 
@@ -627,13 +591,10 @@ function isChoicePayload(value: unknown): value is ProviderChoiceTokenPayload {
627
591
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
628
592
  }
629
593
 
630
- function isChoiceBinding(
631
- value: unknown,
632
- ): value is ManagedChoiceEnvelope["binding"] {
594
+ function isChoiceBinding(value: unknown): value is ManagedChoiceEnvelope["binding"] {
633
595
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
634
596
  return (
635
- (!("connection_hash" in value) ||
636
- typeof value.connection_hash === "string") &&
597
+ (!("connection_hash" in value) || typeof value.connection_hash === "string") &&
637
598
  (!("credential_hash" in value) || typeof value.credential_hash === "string")
638
599
  );
639
600
  }
@@ -648,10 +609,7 @@ function assertManagedChoiceEnvelope(
648
609
  readonly futureToleranceMs?: number;
649
610
  },
650
611
  ): void {
651
- if (
652
- envelope.provider_id !== options.providerId ||
653
- envelope.purpose !== options.purpose
654
- ) {
612
+ if (envelope.provider_id !== options.providerId || envelope.purpose !== options.purpose) {
655
613
  throw new ProviderChoiceTokenError(
656
614
  "invalid_payload",
657
615
  "Provider choice token payload is invalid.",
@@ -660,10 +618,7 @@ function assertManagedChoiceEnvelope(
660
618
  assertFreshProviderChoiceIssuedAt(envelope.issued_at_ms, {
661
619
  // Clamp to the issuer's embedded TTL so a caller-supplied value cannot
662
620
  // silently extend token validity past the deadline the issuer intended.
663
- ttlMs:
664
- options.ttlMs != null
665
- ? Math.min(options.ttlMs, envelope.ttl_ms)
666
- : envelope.ttl_ms,
621
+ ttlMs: options.ttlMs != null ? Math.min(options.ttlMs, envelope.ttl_ms) : envelope.ttl_ms,
667
622
  nowMs: options.nowMs,
668
623
  futureToleranceMs: options.futureToleranceMs,
669
624
  });
@@ -676,9 +631,7 @@ function createChoiceBinding(options: {
676
631
  readonly credential?: CredentialContext;
677
632
  readonly required: boolean;
678
633
  }): ManagedChoiceEnvelope["binding"] {
679
- const connectionHash = options.options?.connection
680
- ? hashRequiredConnection(options)
681
- : undefined;
634
+ const connectionHash = options.options?.connection ? hashRequiredConnection(options) : undefined;
682
635
  const credentialHash = options.options?.credentialKeys?.length
683
636
  ? hashCredentialKeys(options)
684
637
  : undefined;
@@ -697,14 +650,11 @@ function hashRequiredConnection(options: {
697
650
  const connectionId = options.request?.connectionId;
698
651
  if (!connectionId) {
699
652
  if (!options.required) return undefined;
700
- throw new ProviderError(
701
- "Provider choice tokens require connection context.",
702
- {
703
- code: "CHOICE_CONTEXT_REQUIRED",
704
- category: "input_validation",
705
- retryable: false,
706
- },
707
- );
653
+ throw new ProviderError("Provider choice tokens require connection context.", {
654
+ code: "CHOICE_CONTEXT_REQUIRED",
655
+ category: "input_validation",
656
+ retryable: false,
657
+ });
708
658
  }
709
659
  return createHmac("sha256", options.keys.binding)
710
660
  .update("connection")
@@ -722,15 +672,12 @@ function hashCredentialKeys(options: {
722
672
  const material = credentialKeys.map((key) => {
723
673
  const value = options.credential?.get(key);
724
674
  if (typeof value !== "string" || value.length === 0) {
725
- throw new ProviderError(
726
- "Provider choice tokens require configured credential binding.",
727
- {
728
- code: "CHOICE_CONTEXT_REQUIRED",
729
- category: "input_validation",
730
- retryable: false,
731
- details: { credentialKey: key },
732
- },
733
- );
675
+ throw new ProviderError("Provider choice tokens require configured credential binding.", {
676
+ code: "CHOICE_CONTEXT_REQUIRED",
677
+ category: "input_validation",
678
+ retryable: false,
679
+ details: { credentialKey: key },
680
+ });
734
681
  }
735
682
  return [key, value];
736
683
  });
@@ -1,5 +1,5 @@
1
- import { CredentialModeError } from "../errors";
2
- import type { AuthMode, CredentialContext } from "../types";
1
+ import { CredentialModeError } from "../errors.js";
2
+ import type { AuthMode, CredentialContext } from "../types.js";
3
3
 
4
4
  export interface CreateCredentialContextOptions {
5
5
  allowedKeys?: string[];
@@ -1,4 +1,4 @@
1
- import type { EnvContext } from "../types";
1
+ import type { EnvContext } from "../types.js";
2
2
 
3
3
  export function createEnvContext(allowedKeys?: string[]): EnvContext {
4
4
  return {