@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70
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.
- package/AUTHORING.md +943 -51
- package/CHANGELOG.md +273 -1
- package/LICENSE +21 -0
- package/README.md +117 -29
- package/SUBMISSION.md +3 -2
- package/bin/apifuse-check.ts +293 -13
- package/bin/apifuse-dev.ts +42 -19
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +19 -6
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +736 -38
- package/bin/apifuse-perf.ts +59 -23
- package/bin/apifuse-record.ts +923 -94
- package/bin/apifuse-submit-check.ts +2313 -281
- package/bin/apifuse-test.ts +3 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +30 -0
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +8 -2
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/local-connection.d.ts +111 -0
- package/dist/cli/local-connection.js +151 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-operation-declaration.d.ts +70 -0
- package/dist/cli/migrate-operation-declaration.js +2206 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +56 -0
- package/dist/cli/migrate-provider-shape.js +580 -0
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +41 -19
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +202 -16
- package/dist/config/loader.js +491 -141
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +34 -0
- package/dist/declaration-validation.js +305 -0
- package/dist/define.d.ts +71 -29
- package/dist/define.js +807 -69
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +8 -1
- package/dist/engine.d.ts +129 -0
- package/dist/engine.js +209 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +18 -0
- package/dist/error-resolution.js +243 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +99 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +371 -0
- package/dist/handle-meta.d.ts +90 -0
- package/dist/handle-meta.js +73 -0
- package/dist/handle.d.ts +226 -0
- package/dist/handle.js +345 -0
- package/dist/health-scenario.d.ts +1901 -0
- package/dist/health-scenario.js +653 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/i18n/operation-locale-namespace.d.ts +2 -0
- package/dist/i18n/operation-locale-namespace.js +7 -0
- package/dist/index.d.ts +40 -15
- package/dist/index.js +29 -11
- package/dist/lint.d.ts +43 -11
- package/dist/lint.js +2512 -177
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +18 -8
- package/dist/provider.js +11 -6
- package/dist/runtime/auth-flow.d.ts +6 -1
- package/dist/runtime/auth-flow.js +8 -0
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +4 -0
- package/dist/runtime/browser.js +688 -83
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.d.ts +3 -0
- package/dist/runtime/cache.js +245 -23
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +14 -0
- package/dist/runtime/diagnostic-env.js +44 -0
- package/dist/runtime/diagnostic-redactor.d.ts +54 -0
- package/dist/runtime/diagnostic-redactor.js +571 -0
- package/dist/runtime/egress-lease.d.ts +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/env.js +14 -1
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +23 -26
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/handle-wordlist.js +256 -0
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +908 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +34 -0
- package/dist/runtime/http.js +680 -121
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +462 -19
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +105 -0
- package/dist/runtime/native-network.js +1433 -0
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +468 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +398 -16
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +147 -0
- package/dist/runtime/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/proxy-telemetry.d.ts +133 -3
- package/dist/runtime/proxy-telemetry.js +218 -70
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +61 -0
- package/dist/runtime/redirects.js +149 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +75 -1
- package/dist/runtime/request-options.js +588 -0
- package/dist/runtime/request-telemetry.d.ts +70 -0
- package/dist/runtime/request-telemetry.js +229 -0
- package/dist/runtime/resolver-config.d.ts +13 -0
- package/dist/runtime/resolver-config.js +14 -0
- package/dist/runtime/resolver-public.d.ts +3 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +24 -0
- package/dist/runtime/resolver-shared.js +69 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-telemetry.d.ts +135 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +32 -0
- package/dist/runtime/resolver-usage.js +54 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
- package/dist/runtime/resolver-vendors/bindings.js +45 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +423 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +544 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
- package/dist/runtime/resolver-vendors/hosts.js +68 -0
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
- package/dist/runtime/resolver-vendors/types.d.ts +125 -0
- package/dist/runtime/resolver-vendors/types.js +106 -0
- package/dist/runtime/resolver.d.ts +79 -0
- package/dist/runtime/resolver.js +1094 -0
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +10 -1
- package/dist/runtime/state.js +322 -82
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth-cookies.d.ts +24 -0
- package/dist/runtime/stealth-cookies.js +131 -0
- package/dist/runtime/stealth-owned-headers.d.ts +39 -0
- package/dist/runtime/stealth-owned-headers.js +57 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +45 -6
- package/dist/runtime/stealth.js +1775 -320
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +18 -19
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +17 -0
- package/dist/runtime/trace.js +176 -25
- package/dist/runtime-boundary-lint.d.ts +137 -0
- package/dist/runtime-boundary-lint.js +590 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +72 -2
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +21 -2
- package/dist/server/index.js +11 -2
- package/dist/server/runtime-diagnostics.d.ts +165 -0
- package/dist/server/runtime-diagnostics.js +360 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +18 -3
- package/dist/server/self-test.js +139 -76
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +137 -0
- package/dist/server/serve-implementation.d.ts +285 -0
- package/dist/server/serve-implementation.js +3172 -0
- package/dist/server/serve.d.ts +1 -75
- package/dist/server/serve.js +1 -1170
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/server/trace-output.d.ts +7 -0
- package/dist/server/trace-output.js +111 -0
- package/dist/server/types.d.ts +37 -5
- package/dist/server/types.js +34 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
- package/dist/stateful/stateful-provider-runtime-context.js +61 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +15 -4
- package/dist/stealth/profiles.js +169 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +509 -25
- package/dist/trace-sanitization.d.ts +6 -0
- package/dist/trace-sanitization.js +68 -0
- package/dist/types.d.ts +899 -210
- package/dist/types.js +1 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +52 -6
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/commands.ts +37 -0
- package/src/cli/create.ts +8 -2
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/local-connection.ts +221 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-operation-declaration.ts +3026 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +778 -0
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +41 -19
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +774 -181
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +376 -0
- package/src/define.ts +1105 -131
- package/src/dev.ts +13 -1
- package/src/engine.ts +390 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +269 -0
- package/src/errors.ts +157 -0
- package/src/fixture-sanitization.ts +422 -0
- package/src/handle-meta.ts +143 -0
- package/src/handle.ts +694 -0
- package/src/health-scenario.ts +960 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/i18n/operation-locale-namespace.ts +10 -0
- package/src/index.ts +390 -23
- package/src/lint.ts +2975 -234
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +207 -27
- package/src/runtime/auth-flow.ts +15 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +882 -99
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +258 -22
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +65 -0
- package/src/runtime/diagnostic-redactor.ts +668 -0
- package/src/runtime/egress-lease.ts +341 -0
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/env.ts +15 -1
- package/src/runtime/executor.ts +39 -32
- package/src/runtime/handle-wordlist.ts +266 -0
- package/src/runtime/handle.ts +1180 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +896 -150
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +628 -28
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1853 -0
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +564 -0
- package/src/runtime/otlp.ts +468 -21
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +222 -0
- package/src/runtime/proxy-retry-policy.ts +12 -1
- package/src/runtime/proxy-telemetry.ts +358 -103
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +215 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +728 -1
- package/src/runtime/request-telemetry.ts +375 -0
- package/src/runtime/resolver-config.ts +18 -0
- package/src/runtime/resolver-public.ts +44 -0
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +113 -0
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +99 -0
- package/src/runtime/resolver-vendors/bindings.ts +61 -0
- package/src/runtime/resolver-vendors/browser.ts +591 -0
- package/src/runtime/resolver-vendors/capsolver.ts +727 -0
- package/src/runtime/resolver-vendors/hosts.ts +81 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
- package/src/runtime/resolver-vendors/types.ts +261 -0
- package/src/runtime/resolver.ts +1543 -0
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +435 -89
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth-cookies.ts +155 -0
- package/src/runtime/stealth-owned-headers.ts +67 -0
- package/src/runtime/stealth-telemetry.ts +309 -0
- package/src/runtime/stealth.ts +2275 -372
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +96 -91
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +257 -35
- package/src/runtime-boundary-lint.ts +707 -0
- package/src/schema.ts +80 -2
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +184 -2
- package/src/server/runtime-diagnostics.ts +522 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +205 -89
- package/src/server/sensitive-values.ts +211 -0
- package/src/server/serve-implementation.ts +4596 -0
- package/src/server/serve.ts +1 -1685
- package/src/server/state-backend-report.ts +185 -0
- package/src/server/trace-output.ts +189 -0
- package/src/server/types.ts +34 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
- package/src/stateful/stateful-provider-runtime-context.ts +96 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +212 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +685 -19
- package/src/trace-sanitization.ts +110 -0
- package/src/types.ts +1014 -273
- package/src/typescript-module.ts +100 -0
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice.d.ts +0 -15
- package/dist/runtime/choice.js +0 -413
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -686
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ProviderCacheLookupMeta } from "../types.js";
|
|
3
|
+
import {
|
|
4
|
+
closedEnum,
|
|
5
|
+
type ClosedEnum,
|
|
6
|
+
type SpanIndex,
|
|
7
|
+
type TelemetryContributor,
|
|
8
|
+
} from "./request-telemetry.js";
|
|
9
|
+
import { createTelemetryObserver } from "./http-telemetry-guard.js";
|
|
10
|
+
|
|
11
|
+
export type CacheTelemetrySource = "memory" | "redis" | "loader";
|
|
12
|
+
|
|
13
|
+
export type CacheTelemetrySample = {
|
|
14
|
+
key: string;
|
|
15
|
+
hit: boolean;
|
|
16
|
+
stale: boolean;
|
|
17
|
+
ageMs?: number;
|
|
18
|
+
source: CacheTelemetrySource;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CacheTelemetryLogPayload = {
|
|
22
|
+
telemetryFailed?: true;
|
|
23
|
+
lookups: number;
|
|
24
|
+
hits: number;
|
|
25
|
+
stale: number;
|
|
26
|
+
misses: number;
|
|
27
|
+
writes: number;
|
|
28
|
+
loaderErrors: number;
|
|
29
|
+
writeErrors: number;
|
|
30
|
+
source: { memory: number; redis: number; loader: number };
|
|
31
|
+
redisFallbacks: number;
|
|
32
|
+
redisMode: ClosedEnum<"not_configured" | "configured" | "degraded">;
|
|
33
|
+
/** Successful Redis commands, including reads, writes, and deletes; memory-only work counts zero. */
|
|
34
|
+
redisRoundTrips: number;
|
|
35
|
+
ageMs?: { p50: number; max: number };
|
|
36
|
+
samples: CacheTelemetrySample[];
|
|
37
|
+
dropped: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type CacheTelemetryHeaderPayload = Omit<
|
|
41
|
+
CacheTelemetryLogPayload,
|
|
42
|
+
"samples" | "ageMs" | "telemetryFailed"
|
|
43
|
+
> & {
|
|
44
|
+
ageP50?: number;
|
|
45
|
+
ageMax?: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface CacheTelemetrySink {
|
|
49
|
+
markTelemetryFailed?(): void;
|
|
50
|
+
setRedisConfigured?(configured: boolean): void;
|
|
51
|
+
recordLookup(meta: ProviderCacheLookupMeta): void;
|
|
52
|
+
recordWrite(): void;
|
|
53
|
+
recordLoaderError?(): void;
|
|
54
|
+
recordWriteError?(): void;
|
|
55
|
+
recordRedisFallback(): void;
|
|
56
|
+
recordRedisRoundTrip?(): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cacheTelemetryBindings = new WeakMap<object, CacheTelemetrySink>();
|
|
60
|
+
|
|
61
|
+
/** Internal construction binding; keeps sink plumbing off ProviderCacheOptions. */
|
|
62
|
+
export function bindCacheTelemetry<T extends object>(
|
|
63
|
+
options: T,
|
|
64
|
+
sink: CacheTelemetrySink | undefined,
|
|
65
|
+
): T {
|
|
66
|
+
if (sink) cacheTelemetryBindings.set(options, sink);
|
|
67
|
+
return options;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Internal lookup used only by the cache factory. */
|
|
71
|
+
export function cacheTelemetryFor(options: object): CacheTelemetrySink | undefined {
|
|
72
|
+
return cacheTelemetryBindings.get(options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function guardCacheTelemetry(sink: CacheTelemetrySink | undefined): CacheTelemetrySink {
|
|
76
|
+
const observe = createTelemetryObserver(sink);
|
|
77
|
+
return {
|
|
78
|
+
setRedisConfigured: (configured) => observe(() => sink?.setRedisConfigured?.(configured)),
|
|
79
|
+
recordLookup: (meta) => observe(() => sink?.recordLookup(meta)),
|
|
80
|
+
recordWrite: () => observe(() => sink?.recordWrite()),
|
|
81
|
+
recordLoaderError: () => observe(() => sink?.recordLoaderError?.()),
|
|
82
|
+
recordWriteError: () => observe(() => sink?.recordWriteError?.()),
|
|
83
|
+
recordRedisFallback: () => observe(() => sink?.recordRedisFallback()),
|
|
84
|
+
recordRedisRoundTrip: () => observe(() => sink?.recordRedisRoundTrip?.()),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const MAX_SAMPLES = 24;
|
|
89
|
+
const MAX_TEXT = 300;
|
|
90
|
+
|
|
91
|
+
function integer(value: number): number {
|
|
92
|
+
return Number.isFinite(value)
|
|
93
|
+
? Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)))
|
|
94
|
+
: Number.MAX_SAFE_INTEGER;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function add(a: number, b: number): number {
|
|
98
|
+
return Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function boundedText(value: string, redact?: (text: string) => string): string {
|
|
102
|
+
let result: string;
|
|
103
|
+
try {
|
|
104
|
+
result = redact?.(value) ?? value;
|
|
105
|
+
} catch {
|
|
106
|
+
result = "[REDACTION_FAILED]";
|
|
107
|
+
}
|
|
108
|
+
const length = Math.min(result.length, MAX_TEXT);
|
|
109
|
+
const units = Buffer.alloc(length * 2);
|
|
110
|
+
for (let index = 0; index < length; index += 1)
|
|
111
|
+
units.writeUInt16LE(result.charCodeAt(index), index * 2);
|
|
112
|
+
return units.toString("utf16le");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class CacheTelemetryCollector
|
|
116
|
+
implements
|
|
117
|
+
TelemetryContributor<CacheTelemetryLogPayload, CacheTelemetryHeaderPayload>,
|
|
118
|
+
CacheTelemetrySink
|
|
119
|
+
{
|
|
120
|
+
readonly key = "cache" as const;
|
|
121
|
+
readonly #redact?: (text: string) => string;
|
|
122
|
+
#telemetryFailed = false;
|
|
123
|
+
#lookups = 0;
|
|
124
|
+
#hits = 0;
|
|
125
|
+
#stale = 0;
|
|
126
|
+
#misses = 0;
|
|
127
|
+
#writes = 0;
|
|
128
|
+
#loaderErrors = 0;
|
|
129
|
+
#writeErrors = 0;
|
|
130
|
+
#source = { memory: 0, redis: 0, loader: 0 };
|
|
131
|
+
#redisFallbacks = 0;
|
|
132
|
+
#configured: "not_configured" | "configured" | "degraded";
|
|
133
|
+
#redisRoundTrips = 0;
|
|
134
|
+
#ages: number[] = [];
|
|
135
|
+
#maxAge = 0;
|
|
136
|
+
#samples: CacheTelemetrySample[] = [];
|
|
137
|
+
#dropped = 0;
|
|
138
|
+
|
|
139
|
+
constructor(options: { redact?: (text: string) => string } = {}) {
|
|
140
|
+
this.#redact = options.redact;
|
|
141
|
+
this.#configured = "not_configured";
|
|
142
|
+
}
|
|
143
|
+
setRedisConfigured(configured: boolean): void {
|
|
144
|
+
if (configured && this.#configured === "not_configured") this.#configured = "configured";
|
|
145
|
+
}
|
|
146
|
+
markTelemetryFailed(): void {
|
|
147
|
+
this.#telemetryFailed = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
recordLookup(meta: ProviderCacheLookupMeta): void {
|
|
151
|
+
if (!["memory", "redis", "loader"].includes(meta.source)) {
|
|
152
|
+
this.#telemetryFailed = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.#lookups = add(this.#lookups, 1);
|
|
156
|
+
if (meta.hit) this.#hits = add(this.#hits, 1);
|
|
157
|
+
else this.#misses = add(this.#misses, 1);
|
|
158
|
+
if (meta.stale) this.#stale = add(this.#stale, 1);
|
|
159
|
+
this.#source[meta.source] = add(this.#source[meta.source], 1);
|
|
160
|
+
if (meta.source === "redis" && this.#configured === "not_configured")
|
|
161
|
+
this.#configured = "configured";
|
|
162
|
+
if (meta.ageMs !== undefined) {
|
|
163
|
+
const age = integer(meta.ageMs);
|
|
164
|
+
if (this.#ages.length < MAX_SAMPLES) this.#ages.push(age);
|
|
165
|
+
this.#maxAge = Math.max(this.#maxAge, age);
|
|
166
|
+
}
|
|
167
|
+
if (this.#samples.length < MAX_SAMPLES) {
|
|
168
|
+
this.#samples.push({
|
|
169
|
+
key: boundedText(meta.key, this.#redact),
|
|
170
|
+
hit: meta.hit,
|
|
171
|
+
stale: meta.stale,
|
|
172
|
+
...(meta.ageMs === undefined ? {} : { ageMs: integer(meta.ageMs) }),
|
|
173
|
+
source: meta.source,
|
|
174
|
+
});
|
|
175
|
+
} else this.#dropped = add(this.#dropped, 1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
recordWrite(): void {
|
|
179
|
+
this.#writes = add(this.#writes, 1);
|
|
180
|
+
}
|
|
181
|
+
recordLoaderError(): void {
|
|
182
|
+
this.#loaderErrors = add(this.#loaderErrors, 1);
|
|
183
|
+
}
|
|
184
|
+
recordWriteError(): void {
|
|
185
|
+
this.#writeErrors = add(this.#writeErrors, 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
recordRedisFallback(): void {
|
|
189
|
+
this.#redisFallbacks = add(this.#redisFallbacks, 1);
|
|
190
|
+
this.#configured = "degraded";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
recordRedisRoundTrip(): void {
|
|
194
|
+
this.#redisRoundTrips = add(this.#redisRoundTrips, 1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
toLogPayload(_spans: SpanIndex): CacheTelemetryLogPayload | undefined {
|
|
198
|
+
if (
|
|
199
|
+
this.#lookups === 0 &&
|
|
200
|
+
this.#writes === 0 &&
|
|
201
|
+
this.#redisFallbacks === 0 &&
|
|
202
|
+
!this.#telemetryFailed
|
|
203
|
+
)
|
|
204
|
+
return undefined;
|
|
205
|
+
const sorted = [...this.#ages].sort((a, b) => a - b);
|
|
206
|
+
const p50 = sorted.length ? sorted[Math.floor((sorted.length - 1) / 2)]! : undefined;
|
|
207
|
+
return {
|
|
208
|
+
...(this.#telemetryFailed ? { telemetryFailed: true as const } : {}),
|
|
209
|
+
lookups: this.#lookups,
|
|
210
|
+
hits: this.#hits,
|
|
211
|
+
stale: this.#stale,
|
|
212
|
+
misses: this.#misses,
|
|
213
|
+
writes: this.#writes,
|
|
214
|
+
loaderErrors: this.#loaderErrors,
|
|
215
|
+
writeErrors: this.#writeErrors,
|
|
216
|
+
source: { ...this.#source },
|
|
217
|
+
redisFallbacks: this.#redisFallbacks,
|
|
218
|
+
redisMode: closedEnum(this.#configured),
|
|
219
|
+
redisRoundTrips: this.#redisRoundTrips,
|
|
220
|
+
...(p50 === undefined ? {} : { ageMs: { p50, max: this.#maxAge } }),
|
|
221
|
+
samples: this.#samples.map((sample) => ({ ...sample })),
|
|
222
|
+
dropped: this.#dropped,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
toHeaderPayload(log: CacheTelemetryLogPayload): CacheTelemetryHeaderPayload {
|
|
227
|
+
return {
|
|
228
|
+
lookups: log.lookups,
|
|
229
|
+
hits: log.hits,
|
|
230
|
+
stale: log.stale,
|
|
231
|
+
misses: log.misses,
|
|
232
|
+
writes: log.writes,
|
|
233
|
+
loaderErrors: log.loaderErrors,
|
|
234
|
+
writeErrors: log.writeErrors,
|
|
235
|
+
source: {
|
|
236
|
+
memory: log.source.memory,
|
|
237
|
+
redis: log.source.redis,
|
|
238
|
+
loader: log.source.loader,
|
|
239
|
+
},
|
|
240
|
+
redisFallbacks: log.redisFallbacks,
|
|
241
|
+
redisMode: log.redisMode,
|
|
242
|
+
redisRoundTrips: log.redisRoundTrips,
|
|
243
|
+
...(log.ageMs ? { ageP50: log.ageMs.p50, ageMax: log.ageMs.max } : {}),
|
|
244
|
+
dropped: log.dropped,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/runtime/cache.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readDiagnosticEnv } from "./diagnostic-env.js";
|
|
2
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
|
|
5
|
+
import { ProviderError } from "../errors.js";
|
|
4
6
|
import type {
|
|
5
7
|
ProviderCache,
|
|
6
8
|
ProviderCacheGetOrSetOptions,
|
|
@@ -9,6 +11,7 @@ import type {
|
|
|
9
11
|
ProviderCacheResponseMeta,
|
|
10
12
|
ProviderCacheResult,
|
|
11
13
|
} from "../types.js";
|
|
14
|
+
import { cacheTelemetryFor, guardCacheTelemetry } from "./cache-telemetry.js";
|
|
12
15
|
import {
|
|
13
16
|
createProviderRedisClient,
|
|
14
17
|
ensureRedisReady,
|
|
@@ -43,9 +46,12 @@ export type ProviderCacheOptions = {
|
|
|
43
46
|
now?: () => number;
|
|
44
47
|
};
|
|
45
48
|
|
|
49
|
+
export const APIFUSE__CACHE__KEY_PEPPER_ENV = "APIFUSE__CACHE__KEY_PEPPER";
|
|
50
|
+
|
|
46
51
|
const DEFAULT_PREFIX = "apifuse:provider-cache:v1";
|
|
47
52
|
const DEFAULT_MEMORY_MAX_ENTRIES = 1_000;
|
|
48
53
|
const DEFAULT_REDIS_TIMEOUT_MS = 150;
|
|
54
|
+
const SECRET_SCOPED_KEY_MARKER = "[secret-scoped";
|
|
49
55
|
const SECRET_FIELD_NAMES = new Set([
|
|
50
56
|
"authorization",
|
|
51
57
|
"cookie",
|
|
@@ -61,6 +67,7 @@ const SECRET_FIELD_NAMES = new Set([
|
|
|
61
67
|
]);
|
|
62
68
|
|
|
63
69
|
const sharedBackends = new Map<string, SharedCacheBackend>();
|
|
70
|
+
let warnedAboutUnpepperedSecretKeys = false;
|
|
64
71
|
|
|
65
72
|
function backendKey(redisUrl: string | undefined): string {
|
|
66
73
|
return redisUrl ?? "memory";
|
|
@@ -107,19 +114,172 @@ function shouldRedactField(name: string, extra: Set<string>): boolean {
|
|
|
107
114
|
);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
type NormalizedKeyPart = {
|
|
118
|
+
value: unknown;
|
|
119
|
+
secretScoped: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function unsupportedSecretValue(path: string, reason: string): never {
|
|
123
|
+
throw new ProviderError(`Secret cache-key values must be JSON-safe; ${reason} at ${path}.`, {
|
|
124
|
+
code: "CACHE_KEY_SECRET_VALUE_UNSUPPORTED",
|
|
125
|
+
fix: "Convert the secret cache-key selector to JSON-safe primitives, arrays, or plain objects.",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assertJsonSafeSecretValue(
|
|
130
|
+
value: unknown,
|
|
131
|
+
reportedPath: string,
|
|
132
|
+
ancestors = new Set<object>(),
|
|
133
|
+
): void {
|
|
134
|
+
if (value === undefined) return;
|
|
135
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
136
|
+
if (typeof value === "number") {
|
|
137
|
+
if (!Number.isFinite(value))
|
|
138
|
+
unsupportedSecretValue(reportedPath, "non-finite numbers are unsupported");
|
|
139
|
+
if (Object.is(value, -0)) unsupportedSecretValue(reportedPath, "negative zero is unsupported");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (typeof value !== "object") {
|
|
143
|
+
unsupportedSecretValue(reportedPath, `${typeof value} values are unsupported`);
|
|
144
|
+
}
|
|
145
|
+
if (ancestors.has(value)) unsupportedSecretValue(reportedPath, "cyclic values are unsupported");
|
|
146
|
+
|
|
147
|
+
ancestors.add(value);
|
|
148
|
+
try {
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
151
|
+
unsupportedSecretValue(reportedPath, "symbol-keyed array properties are unsupported");
|
|
152
|
+
}
|
|
153
|
+
const expectedNames = new Set(["length"]);
|
|
154
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
155
|
+
const key = String(index);
|
|
156
|
+
expectedNames.add(key);
|
|
157
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
158
|
+
if (!descriptor) unsupportedSecretValue(reportedPath, "sparse arrays are unsupported");
|
|
159
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
160
|
+
unsupportedSecretValue(reportedPath, "array accessors are unsupported");
|
|
161
|
+
}
|
|
162
|
+
assertJsonSafeSecretValue(descriptor.value, reportedPath, ancestors);
|
|
163
|
+
}
|
|
164
|
+
if (Object.getOwnPropertyNames(value).some((name) => !expectedNames.has(name))) {
|
|
165
|
+
unsupportedSecretValue(reportedPath, "custom array properties are unsupported");
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const prototype = Object.getPrototypeOf(value);
|
|
171
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
172
|
+
unsupportedSecretValue(reportedPath, "non-plain objects are unsupported");
|
|
173
|
+
}
|
|
174
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
175
|
+
unsupportedSecretValue(reportedPath, "symbol-keyed properties are unsupported");
|
|
176
|
+
}
|
|
177
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
178
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
179
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
180
|
+
unsupportedSecretValue(
|
|
181
|
+
reportedPath,
|
|
182
|
+
"non-enumerable properties and accessors are unsupported",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
assertJsonSafeSecretValue(descriptor.value, reportedPath, ancestors);
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
ancestors.delete(value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function containsUndefined(value: unknown): boolean {
|
|
193
|
+
if (value === undefined) return true;
|
|
194
|
+
if (Array.isArray(value)) return value.some(containsUndefined);
|
|
195
|
+
if (isRecord(value)) return Object.values(value).some(containsUndefined);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function tagSecretValue(value: unknown): unknown {
|
|
200
|
+
if (value === undefined) return ["undefined"];
|
|
201
|
+
if (value === null) return ["null"];
|
|
202
|
+
if (typeof value === "string") return ["string", value];
|
|
203
|
+
if (typeof value === "number") return ["number", value];
|
|
204
|
+
if (typeof value === "boolean") return ["boolean", value];
|
|
205
|
+
if (Array.isArray(value)) return ["array", value.map(tagSecretValue)];
|
|
206
|
+
return [
|
|
207
|
+
"object",
|
|
208
|
+
Object.entries(value as Record<string, unknown>).map(([key, entry]) => [
|
|
209
|
+
key,
|
|
210
|
+
tagSecretValue(entry),
|
|
211
|
+
]),
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function serializeSecretValue(value: unknown): string {
|
|
216
|
+
if (!containsUndefined(value)) return JSON.stringify([value]);
|
|
217
|
+
return `undefined-v1:${JSON.stringify(tagSecretValue(value))}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function warnAboutUnpepperedSecretKey(): void {
|
|
221
|
+
if (warnedAboutUnpepperedSecretKeys) return;
|
|
222
|
+
warnedAboutUnpepperedSecretKeys = true;
|
|
223
|
+
console.warn(
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
level: "warn",
|
|
226
|
+
event: "provider_cache_secret_key_unpeppered",
|
|
227
|
+
message: `Secret-bearing cache keys are using unkeyed SHA-256 because ${APIFUSE__CACHE__KEY_PEPPER_ENV} is not configured.`,
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeKeyPart(
|
|
233
|
+
value: unknown,
|
|
234
|
+
extra: Set<string>,
|
|
235
|
+
pepper: string | undefined,
|
|
236
|
+
): NormalizedKeyPart {
|
|
111
237
|
if (Array.isArray(value)) {
|
|
112
|
-
|
|
238
|
+
const entries = value.map((entry) => normalizeKeyPart(entry, extra, pepper));
|
|
239
|
+
return {
|
|
240
|
+
value: entries.map((entry) => entry.value),
|
|
241
|
+
secretScoped: entries.some((entry) => entry.secretScoped),
|
|
242
|
+
};
|
|
113
243
|
}
|
|
114
244
|
if (isRecord(value)) {
|
|
115
|
-
const normalized: Record<string, unknown> =
|
|
245
|
+
const normalized: Record<string, unknown> = Object.create(null);
|
|
246
|
+
let secretScoped = false;
|
|
116
247
|
for (const key of Object.keys(value).sort()) {
|
|
117
|
-
|
|
118
|
-
|
|
248
|
+
const part = shouldRedactField(key, extra)
|
|
249
|
+
? hashSecretValue(value[key], key, extra, pepper)
|
|
250
|
+
: normalizeKeyPart(value[key], extra, pepper);
|
|
251
|
+
normalized[key] = part.value;
|
|
252
|
+
secretScoped ||= part.secretScoped;
|
|
119
253
|
}
|
|
120
|
-
return normalized;
|
|
254
|
+
return { value: normalized, secretScoped };
|
|
121
255
|
}
|
|
122
|
-
return value;
|
|
256
|
+
return { value, secretScoped: false };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function hashSecretValue(
|
|
260
|
+
value: unknown,
|
|
261
|
+
fieldName: string,
|
|
262
|
+
extra: Set<string>,
|
|
263
|
+
pepper: string | undefined,
|
|
264
|
+
): NormalizedKeyPart {
|
|
265
|
+
assertJsonSafeSecretValue(value, `${fieldName} (inside secret value)`);
|
|
266
|
+
const canonical = serializeSecretValue(normalizeKeyPart(value, extra, pepper).value);
|
|
267
|
+
if (pepper === undefined) {
|
|
268
|
+
warnAboutUnpepperedSecretKey();
|
|
269
|
+
const digest = createHash("sha256").update(canonical).digest("hex");
|
|
270
|
+
return { value: `sha256:${digest}`, secretScoped: true };
|
|
271
|
+
}
|
|
272
|
+
const digest = createHmac("sha256", pepper).update(canonical).digest("hex");
|
|
273
|
+
return { value: `hmac-sha256:${digest}`, secretScoped: true };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function metadataKeys(events: ProviderCacheLookupMeta[], secretScopedKeys: Set<string>): string[] {
|
|
277
|
+
let secretScopedIndex = 0;
|
|
278
|
+
return Array.from(new Set(events.map((event) => event.key))).map((key) => {
|
|
279
|
+
if (!secretScopedKeys.has(key)) return key;
|
|
280
|
+
secretScopedIndex += 1;
|
|
281
|
+
return `${SECRET_SCOPED_KEY_MARKER}#${secretScopedIndex}]`;
|
|
282
|
+
});
|
|
123
283
|
}
|
|
124
284
|
|
|
125
285
|
function stableHash(value: unknown): string {
|
|
@@ -198,13 +358,20 @@ async function withRedisFallback<T>(operation: () => Promise<T>): Promise<T | un
|
|
|
198
358
|
|
|
199
359
|
export function createProviderCache(options: ProviderCacheOptions): ProviderCache {
|
|
200
360
|
const redisUrl = options.redisUrl ?? providerCacheRedisUrlFromEnv();
|
|
361
|
+
const configuredPepper = readDiagnosticEnv(APIFUSE__CACHE__KEY_PEPPER_ENV);
|
|
362
|
+
const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
|
|
201
363
|
const backend = getSharedBackend(redisUrl);
|
|
202
364
|
const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
|
|
203
365
|
const now = options.now ?? Date.now;
|
|
204
366
|
const events: ProviderCacheLookupMeta[] = [];
|
|
367
|
+
const telemetry = guardCacheTelemetry(cacheTelemetryFor(options));
|
|
368
|
+
telemetry.setRedisConfigured?.(!!redisUrl);
|
|
369
|
+
let redisUnavailable = false;
|
|
370
|
+
const secretScopedKeys = new Set<string>();
|
|
205
371
|
|
|
206
372
|
function record(meta: ProviderCacheLookupMeta): void {
|
|
207
373
|
events.push(meta);
|
|
374
|
+
telemetry?.recordLookup(meta);
|
|
208
375
|
}
|
|
209
376
|
|
|
210
377
|
function sweepMemory(currentTime: number): void {
|
|
@@ -248,13 +415,22 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
248
415
|
} | null> {
|
|
249
416
|
const redis = backend.redis;
|
|
250
417
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
418
|
+
if (redis) {
|
|
419
|
+
redisUnavailable = true;
|
|
420
|
+
telemetry?.recordRedisFallback();
|
|
421
|
+
}
|
|
251
422
|
return null;
|
|
252
423
|
}
|
|
253
424
|
|
|
254
425
|
const raw = await withRedisFallback(async () => {
|
|
255
426
|
return await redis.get(key);
|
|
256
427
|
});
|
|
257
|
-
if (typeof raw !== "string" && raw !== null)
|
|
428
|
+
if (typeof raw !== "string" && raw !== null) {
|
|
429
|
+
redisUnavailable = true;
|
|
430
|
+
telemetry.recordRedisFallback();
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
telemetry?.recordRedisRoundTrip?.();
|
|
258
434
|
|
|
259
435
|
const envelope = safeParseEnvelope(raw);
|
|
260
436
|
if (!envelope) return null;
|
|
@@ -305,6 +481,7 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
305
481
|
value: T,
|
|
306
482
|
cacheOptions: ProviderCacheGetOrSetOptions,
|
|
307
483
|
): Promise<void> {
|
|
484
|
+
telemetry?.recordWrite();
|
|
308
485
|
const currentTime = now();
|
|
309
486
|
const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
|
|
310
487
|
const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
|
|
@@ -319,9 +496,21 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
319
496
|
|
|
320
497
|
const redis = backend.redis;
|
|
321
498
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
499
|
+
if (redis) {
|
|
500
|
+
redisUnavailable = true;
|
|
501
|
+
telemetry?.recordRedisFallback();
|
|
502
|
+
telemetry?.recordWriteError?.();
|
|
503
|
+
}
|
|
322
504
|
return;
|
|
323
505
|
}
|
|
324
|
-
await withRedisFallback(() =>
|
|
506
|
+
const written = await withRedisFallback(() =>
|
|
507
|
+
redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs),
|
|
508
|
+
);
|
|
509
|
+
if (written === undefined) {
|
|
510
|
+
redisUnavailable = true;
|
|
511
|
+
telemetry?.recordRedisFallback();
|
|
512
|
+
telemetry?.recordWriteError?.();
|
|
513
|
+
} else telemetry?.recordRedisRoundTrip?.();
|
|
325
514
|
}
|
|
326
515
|
|
|
327
516
|
async function loadAndStore<T>(
|
|
@@ -346,6 +535,13 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
346
535
|
if (staleCandidate?.meta.stale) {
|
|
347
536
|
return staleCandidate;
|
|
348
537
|
}
|
|
538
|
+
telemetry?.recordLookup({
|
|
539
|
+
key,
|
|
540
|
+
hit: false,
|
|
541
|
+
stale: false,
|
|
542
|
+
source: "loader",
|
|
543
|
+
});
|
|
544
|
+
telemetry?.recordLoaderError?.();
|
|
349
545
|
throw error;
|
|
350
546
|
}
|
|
351
547
|
}
|
|
@@ -353,25 +549,38 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
353
549
|
return {
|
|
354
550
|
key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
|
|
355
551
|
const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
|
|
356
|
-
const normalized = normalizeKeyPart(parts, extra);
|
|
357
|
-
|
|
552
|
+
const normalized = normalizeKeyPart(parts, extra, pepper);
|
|
553
|
+
const key = `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized.value)}`;
|
|
554
|
+
if (normalized.secretScoped) secretScopedKeys.add(key);
|
|
555
|
+
return key;
|
|
358
556
|
},
|
|
359
557
|
|
|
360
558
|
async get<T = unknown>(key: string): Promise<ProviderCacheResult<T> | null> {
|
|
361
559
|
const result = await read<T>(key);
|
|
362
560
|
if (result) record(result.meta);
|
|
561
|
+
else
|
|
562
|
+
telemetry.recordLookup({
|
|
563
|
+
key,
|
|
564
|
+
hit: false,
|
|
565
|
+
stale: false,
|
|
566
|
+
source: backend.redis && !redisUnavailable ? "redis" : "memory",
|
|
567
|
+
});
|
|
363
568
|
return result;
|
|
364
569
|
},
|
|
365
570
|
|
|
366
571
|
set: write,
|
|
367
572
|
|
|
368
573
|
async delete(key: string): Promise<void> {
|
|
574
|
+
telemetry?.recordWrite();
|
|
369
575
|
backend.memory.delete(key);
|
|
370
576
|
const redis = backend.redis;
|
|
371
577
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
578
|
+
if (redis) telemetry?.recordRedisFallback();
|
|
372
579
|
return;
|
|
373
580
|
}
|
|
374
|
-
await withRedisFallback(() => redis.del(key));
|
|
581
|
+
const deleted = await withRedisFallback(() => redis.del(key));
|
|
582
|
+
if (deleted === undefined) telemetry?.recordRedisFallback();
|
|
583
|
+
else telemetry?.recordRedisRoundTrip?.();
|
|
375
584
|
},
|
|
376
585
|
|
|
377
586
|
async getOrSet<T = unknown>(
|
|
@@ -387,10 +596,22 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
387
596
|
|
|
388
597
|
const existingInflight = backend.inflight.get(key);
|
|
389
598
|
if (existingInflight) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
599
|
+
try {
|
|
600
|
+
const inflightResult = await existingInflight;
|
|
601
|
+
const result = resultWithValue<T>(inflightResult.value, inflightResult.meta);
|
|
602
|
+
record(result.meta);
|
|
603
|
+
return result;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const meta: ProviderCacheLookupMeta = {
|
|
606
|
+
key,
|
|
607
|
+
hit: false,
|
|
608
|
+
stale: false,
|
|
609
|
+
source: "loader",
|
|
610
|
+
};
|
|
611
|
+
telemetry.recordLookup(meta);
|
|
612
|
+
telemetry?.recordLoaderError?.();
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
394
615
|
}
|
|
395
616
|
|
|
396
617
|
const promise: Promise<ProviderCacheResult<unknown>> = loadAndStore(
|
|
@@ -413,23 +634,35 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
413
634
|
return {
|
|
414
635
|
hit: events.some((event) => event.hit),
|
|
415
636
|
stale: events.some((event) => event.stale),
|
|
416
|
-
keys:
|
|
637
|
+
keys: metadataKeys(events, secretScopedKeys),
|
|
417
638
|
source: sourceSummary(events),
|
|
418
639
|
};
|
|
419
640
|
},
|
|
420
641
|
};
|
|
421
642
|
}
|
|
422
643
|
|
|
644
|
+
const bypassCaches = new WeakSet<ProviderCache>();
|
|
645
|
+
|
|
646
|
+
/** Internal cache capability, separate from hit/miss metadata. */
|
|
647
|
+
export function isProviderCacheBypassed(cache: ProviderCache): boolean {
|
|
648
|
+
return bypassCaches.has(cache);
|
|
649
|
+
}
|
|
650
|
+
|
|
423
651
|
export function createBypassProviderCache(
|
|
424
652
|
options: Pick<ProviderCacheOptions, "providerId">,
|
|
425
653
|
): ProviderCache {
|
|
654
|
+
const configuredPepper = readDiagnosticEnv(APIFUSE__CACHE__KEY_PEPPER_ENV);
|
|
655
|
+
const pepper = configuredPepper && configuredPepper.length > 0 ? configuredPepper : undefined;
|
|
426
656
|
const events: ProviderCacheLookupMeta[] = [];
|
|
657
|
+
const secretScopedKeys = new Set<string>();
|
|
427
658
|
|
|
428
|
-
|
|
659
|
+
const cache: ProviderCache = {
|
|
429
660
|
key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
|
|
430
661
|
const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
|
|
431
|
-
const normalized = normalizeKeyPart(parts, extra);
|
|
432
|
-
|
|
662
|
+
const normalized = normalizeKeyPart(parts, extra, pepper);
|
|
663
|
+
const key = `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized.value)}`;
|
|
664
|
+
if (normalized.secretScoped) secretScopedKeys.add(key);
|
|
665
|
+
return key;
|
|
433
666
|
},
|
|
434
667
|
|
|
435
668
|
async get<T = unknown>(_key: string): Promise<ProviderCacheResult<T> | null> {
|
|
@@ -464,11 +697,13 @@ export function createBypassProviderCache(
|
|
|
464
697
|
return {
|
|
465
698
|
hit: false,
|
|
466
699
|
stale: false,
|
|
467
|
-
keys:
|
|
700
|
+
keys: metadataKeys(events, secretScopedKeys),
|
|
468
701
|
source: sourceSummary(events),
|
|
469
702
|
};
|
|
470
703
|
},
|
|
471
704
|
};
|
|
705
|
+
bypassCaches.add(cache);
|
|
706
|
+
return cache;
|
|
472
707
|
}
|
|
473
708
|
|
|
474
709
|
export function resetProviderCacheForTests(): void {
|
|
@@ -478,4 +713,5 @@ export function resetProviderCacheForTests(): void {
|
|
|
478
713
|
backend.redis?.disconnect();
|
|
479
714
|
}
|
|
480
715
|
sharedBackends.clear();
|
|
716
|
+
warnedAboutUnpepperedSecretKeys = false;
|
|
481
717
|
}
|