modelrig 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/LICENSE +201 -0
- package/README.md +145 -8
- package/data/PROVENANCE.md +19 -0
- package/data/pricing-manual.json +22 -0
- package/data/pricing-map.json +46362 -0
- package/data/pricing-overlay.json +419 -0
- package/dist/adapters/anthropic.d.ts +90 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +296 -0
- package/dist/adapters/build-adapters.d.ts +11 -0
- package/dist/adapters/build-adapters.d.ts.map +1 -0
- package/dist/adapters/build-adapters.js +45 -0
- package/dist/adapters/deepseek.d.ts +47 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +216 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +262 -0
- package/dist/adapters/grok.d.ts +53 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +232 -0
- package/dist/adapters/openai-compat.d.ts +80 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +244 -0
- package/dist/adapters/openai-stream.d.ts +58 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -0
- package/dist/adapters/openai-stream.js +120 -0
- package/dist/adapters/openai.d.ts +55 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +281 -0
- package/dist/adapters/sampling.d.ts +28 -0
- package/dist/adapters/sampling.d.ts.map +1 -0
- package/dist/adapters/sampling.js +34 -0
- package/dist/adapters/tool-mapping.d.ts +92 -0
- package/dist/adapters/tool-mapping.d.ts.map +1 -0
- package/dist/adapters/tool-mapping.js +143 -0
- package/dist/artifacts/artifact.d.ts +137 -0
- package/dist/artifacts/artifact.d.ts.map +1 -0
- package/dist/artifacts/artifact.js +377 -0
- package/dist/artifacts/content-uploader.d.ts +87 -0
- package/dist/artifacts/content-uploader.d.ts.map +1 -0
- package/dist/artifacts/content-uploader.js +124 -0
- package/dist/artifacts/index.d.ts +63 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +80 -0
- package/dist/artifacts/run-context.d.ts +129 -0
- package/dist/artifacts/run-context.d.ts.map +1 -0
- package/dist/artifacts/run-context.js +208 -0
- package/dist/artifacts/scrub.d.ts +60 -0
- package/dist/artifacts/scrub.d.ts.map +1 -0
- package/dist/artifacts/scrub.js +139 -0
- package/dist/artifacts/step.d.ts +26 -0
- package/dist/artifacts/step.d.ts.map +1 -0
- package/dist/artifacts/step.js +37 -0
- package/dist/artifacts/taint.d.ts +63 -0
- package/dist/artifacts/taint.d.ts.map +1 -0
- package/dist/artifacts/taint.js +76 -0
- package/dist/bakeoff/cache-sim.d.ts +99 -0
- package/dist/bakeoff/cache-sim.d.ts.map +1 -0
- package/dist/bakeoff/cache-sim.js +126 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/eval-cases.d.ts +96 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases.js +105 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/report.d.ts +41 -0
- package/dist/bakeoff/report.d.ts.map +1 -0
- package/dist/bakeoff/report.js +142 -0
- package/dist/bakeoff/samples.d.ts +77 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +148 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +590 -0
- package/dist/config.d.ts +204 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +252 -0
- package/dist/deprecations.d.ts +32 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +59 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/gateway/http-protocol.d.ts +175 -0
- package/dist/gateway/http-protocol.d.ts.map +1 -0
- package/dist/gateway/http-protocol.js +105 -0
- package/dist/index.d.ts +242 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +576 -0
- package/dist/init/finder.d.ts +94 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +343 -0
- package/dist/init/init-cli.d.ts +10 -0
- package/dist/init/init-cli.d.ts.map +1 -0
- package/dist/init/init-cli.js +86 -0
- package/dist/init/observe-cli.d.ts +19 -0
- package/dist/init/observe-cli.d.ts.map +1 -0
- package/dist/init/observe-cli.js +73 -0
- package/dist/init/observe.d.ts +81 -0
- package/dist/init/observe.d.ts.map +1 -0
- package/dist/init/observe.js +245 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +219 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +313 -0
- package/dist/registry/cache-policy.d.ts +94 -0
- package/dist/registry/cache-policy.d.ts.map +1 -0
- package/dist/registry/cache-policy.js +238 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +110 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/effective-rate.d.ts +151 -0
- package/dist/registry/effective-rate.d.ts.map +1 -0
- package/dist/registry/effective-rate.js +156 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +136 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +52 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +82 -0
- package/dist/registry/pricing-shape.d.ts +113 -0
- package/dist/registry/pricing-shape.d.ts.map +1 -0
- package/dist/registry/pricing-shape.js +123 -0
- package/dist/registry/pricing.d.ts +43 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +127 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/rigindex/bootstrap.d.ts +36 -0
- package/dist/rigindex/bootstrap.d.ts.map +1 -0
- package/dist/rigindex/bootstrap.js +97 -0
- package/dist/rigindex/fit.d.ts +24 -0
- package/dist/rigindex/fit.d.ts.map +1 -0
- package/dist/rigindex/fit.js +76 -0
- package/dist/rigindex/gates.d.ts +52 -0
- package/dist/rigindex/gates.d.ts.map +1 -0
- package/dist/rigindex/gates.js +109 -0
- package/dist/rigindex/graph.d.ts +31 -0
- package/dist/rigindex/graph.d.ts.map +1 -0
- package/dist/rigindex/graph.js +120 -0
- package/dist/rigindex/index.d.ts +23 -0
- package/dist/rigindex/index.d.ts.map +1 -0
- package/dist/rigindex/index.js +112 -0
- package/dist/rigindex/types.d.ts +76 -0
- package/dist/rigindex/types.d.ts.map +1 -0
- package/dist/rigindex/types.js +8 -0
- package/dist/routes/cache-lint.d.ts +41 -0
- package/dist/routes/cache-lint.d.ts.map +1 -0
- package/dist/routes/cache-lint.js +81 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +63 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +574 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/resolved-bundle.d.ts +79 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -0
- package/dist/routes/resolved-bundle.js +148 -0
- package/dist/routes/sampling-config.d.ts +32 -0
- package/dist/routes/sampling-config.d.ts.map +1 -0
- package/dist/routes/sampling-config.js +91 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/ajv.d.ts +22 -0
- package/dist/run/ajv.d.ts.map +1 -0
- package/dist/run/ajv.js +32 -0
- package/dist/run/cache-handle.d.ts +58 -0
- package/dist/run/cache-handle.d.ts.map +1 -0
- package/dist/run/cache-handle.js +54 -0
- package/dist/run/cache-stamp.d.ts +65 -0
- package/dist/run/cache-stamp.d.ts.map +1 -0
- package/dist/run/cache-stamp.js +0 -0
- package/dist/run/capture-write.d.ts +33 -0
- package/dist/run/capture-write.d.ts.map +1 -0
- package/dist/run/capture-write.js +55 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/dispatch.d.ts +99 -0
- package/dist/run/dispatch.d.ts.map +1 -0
- package/dist/run/dispatch.js +191 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/raw.d.ts +88 -0
- package/dist/run/raw.d.ts.map +1 -0
- package/dist/run/raw.js +286 -0
- package/dist/run/record-attempt.d.ts +40 -0
- package/dist/run/record-attempt.d.ts.map +1 -0
- package/dist/run/record-attempt.js +99 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +99 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +203 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +55 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +540 -0
- package/dist/run/stream.d.ts +49 -0
- package/dist/run/stream.d.ts.map +1 -0
- package/dist/run/stream.js +87 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +55 -0
- package/dist/savings/cache-ledger.d.ts +108 -0
- package/dist/savings/cache-ledger.d.ts.map +1 -0
- package/dist/savings/cache-ledger.js +174 -0
- package/dist/savings/cache-reconcile.d.ts +64 -0
- package/dist/savings/cache-reconcile.d.ts.map +1 -0
- package/dist/savings/cache-reconcile.js +59 -0
- package/dist/savings/cache-stats.d.ts +63 -0
- package/dist/savings/cache-stats.d.ts.map +1 -0
- package/dist/savings/cache-stats.js +108 -0
- package/dist/savings/emit.d.ts +38 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +100 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +58 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +130 -0
- package/dist/savings/swaps-client.d.ts +55 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +95 -0
- package/dist/settings/api-fetcher.d.ts +39 -0
- package/dist/settings/api-fetcher.d.ts.map +1 -0
- package/dist/settings/api-fetcher.js +92 -0
- package/dist/settings/index.d.ts +10 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/task-settings.d.ts +91 -0
- package/dist/settings/task-settings.d.ts.map +1 -0
- package/dist/settings/task-settings.js +116 -0
- package/dist/status.d.ts +77 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/telemetry/artifact-store.d.ts +48 -0
- package/dist/telemetry/artifact-store.d.ts.map +1 -0
- package/dist/telemetry/artifact-store.js +189 -0
- package/dist/telemetry/cache-miss-streak.d.ts +79 -0
- package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
- package/dist/telemetry/cache-miss-streak.js +99 -0
- package/dist/telemetry/capture.d.ts +31 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +81 -0
- package/dist/telemetry/exporter-setup.d.ts +28 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -0
- package/dist/telemetry/exporter-setup.js +34 -0
- package/dist/telemetry/exporter.d.ts +137 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +510 -0
- package/dist/telemetry/health.d.ts +76 -0
- package/dist/telemetry/health.d.ts.map +1 -0
- package/dist/telemetry/health.js +92 -0
- package/dist/telemetry/ingest-protocol.d.ts +310 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +363 -0
- package/dist/telemetry/load-error-report.d.ts +24 -0
- package/dist/telemetry/load-error-report.d.ts.map +1 -0
- package/dist/telemetry/load-error-report.js +50 -0
- package/dist/telemetry/otel-map.d.ts +121 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -0
- package/dist/telemetry/otel-map.js +224 -0
- package/dist/telemetry/otel-normalize.d.ts +104 -0
- package/dist/telemetry/otel-normalize.d.ts.map +1 -0
- package/dist/telemetry/otel-normalize.js +530 -0
- package/dist/telemetry/otlp-emitter.d.ts +52 -0
- package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
- package/dist/telemetry/otlp-emitter.js +144 -0
- package/dist/telemetry/otlp-proto.d.ts +29 -0
- package/dist/telemetry/otlp-proto.d.ts.map +1 -0
- package/dist/telemetry/otlp-proto.js +266 -0
- package/dist/telemetry/sinks.d.ts +90 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +180 -0
- package/dist/telemetry/store.d.ts +65 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +547 -0
- package/dist/tenancy/api-key-format.d.ts +77 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +110 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/config-teaching.d.ts +62 -0
- package/dist/types/config-teaching.d.ts.map +1 -0
- package/dist/types/config-teaching.js +183 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +200 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +40 -0
- package/dist/types/route.d.ts +205 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +206 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/validate-cli.d.ts +12 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-cli.js +57 -0
- package/dist/verify/capture.d.ts +66 -0
- package/dist/verify/capture.d.ts.map +1 -0
- package/dist/verify/capture.js +180 -0
- package/dist/verify/cli-run.d.ts +23 -0
- package/dist/verify/cli-run.d.ts.map +1 -0
- package/dist/verify/cli-run.js +57 -0
- package/dist/verify/diff.d.ts +48 -0
- package/dist/verify/diff.d.ts.map +1 -0
- package/dist/verify/diff.js +157 -0
- package/dist/verify/index.d.ts +17 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +24 -0
- package/dist/verify/report.d.ts +14 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/report.js +88 -0
- package/dist/verify/types.d.ts +101 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/types.js +17 -0
- package/dist/verify/verify.d.ts +29 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/dist/verify/verify.js +148 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +225 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +155 -0
- package/dist/watch/monitor.d.ts +116 -0
- package/dist/watch/monitor.d.ts.map +1 -0
- package/dist/watch/monitor.js +199 -0
- package/dist/watch/proposals.d.ts +89 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +197 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +73 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +83 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +56 -14
- package/registry/LICENSE +20 -0
- package/registry/leaderboard.json +3639 -0
- package/registry/registry.json +7263 -0
- package/index.js +0 -3
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-adjusted route economics — cache-aware-routing spec §4.2–4.3, WS-B.
|
|
3
|
+
* PURE: no store, no clock, no network. Every function here is total and
|
|
4
|
+
* deterministic; the whole module is the math the architect reviews pre-merge.
|
|
5
|
+
*
|
|
6
|
+
* ── THE REGRESSION PIN (load-bearing, WS-A discipline) ────────────────────
|
|
7
|
+
* With observed hit rate h = 0 and write rate w = 0, `effectiveInputRate`
|
|
8
|
+
* returns the LIST input rate BYTE-IDENTICALLY, and every per-call saving
|
|
9
|
+
* collapses to 0. A route with no cache activity therefore prices exactly as it
|
|
10
|
+
* did before this module existed. The property test locks this: the cache-aware
|
|
11
|
+
* lens NEVER changes a number until real cache tokens appear.
|
|
12
|
+
*
|
|
13
|
+
* ── FAIL-CLOSED RATES (dev-rule L5) ───────────────────────────────────────
|
|
14
|
+
* An unverified cache-read or cache-write rate degrades to the INPUT rate
|
|
15
|
+
* (1.0×), never a guessed premium/discount — the same posture as
|
|
16
|
+
* `estimateCostUsd`. So an unpriced cache component can only make caching look
|
|
17
|
+
* NEUTRAL, never spuriously attractive.
|
|
18
|
+
*
|
|
19
|
+
* Units: every rate is USD PER TOKEN (matching `ModelPricing.inputUsdPerTok` and
|
|
20
|
+
* `pricing-shape.ts` `TokenRates`), so callers pass either shape unwidened.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The rate components the effective-rate math needs, USD per token. A thin,
|
|
24
|
+
* self-contained shape so this module imports neither `ModelPricing` nor
|
|
25
|
+
* `pricing-shape` (both can build one) and — critically — does NOT touch the
|
|
26
|
+
* UNRELATED local `TokenRates` in `watch/evaluate.ts` (QA name-collision note:
|
|
27
|
+
* that one is a flat `{inputUsdPerTok, outputUsdPerTok}` blend input; keep them
|
|
28
|
+
* separate). `cacheRead`/`cacheWrite` null ⇒ fall back to `input` (L5).
|
|
29
|
+
*/
|
|
30
|
+
export interface EffectiveRates {
|
|
31
|
+
readonly input: number;
|
|
32
|
+
readonly cacheRead: number | null;
|
|
33
|
+
readonly cacheWrite: number | null;
|
|
34
|
+
}
|
|
35
|
+
/** Observed cache activity over a trailing window — the aggregate form (spec
|
|
36
|
+
* §4.2): `h = Σcached/Σin`, `w = Σwrite/Σin`. Both in [0,1]; `h + w ≤ 1` holds
|
|
37
|
+
* because cached and write tokens are disjoint subsets of input tokens. */
|
|
38
|
+
export interface ObservedCacheRates {
|
|
39
|
+
readonly h: number;
|
|
40
|
+
readonly w: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Cache-adjusted effective INPUT rate (spec §4.2, aggregate form):
|
|
44
|
+
*
|
|
45
|
+
* p_eff_in = h·p_read + w·p_write + (1 − h − w)·p_in
|
|
46
|
+
*
|
|
47
|
+
* The blend of what a token of input ACTUALLY costs given the window's cache
|
|
48
|
+
* behavior. Clamps h,w to non-negative and their sum to ≤ 1 (defensive — a
|
|
49
|
+
* malformed rollup must never produce a negative uncached weight). At h = w = 0
|
|
50
|
+
* this is exactly `rates.input` (the regression pin).
|
|
51
|
+
*/
|
|
52
|
+
export declare function effectiveInputRate(obs: ObservedCacheRates, rates: EffectiveRates): number;
|
|
53
|
+
/**
|
|
54
|
+
* Expected per-call saving from READING a warm prefix instead of paying full
|
|
55
|
+
* input on it (spec §4.2, steady-state form): the cached prefix tokens cost
|
|
56
|
+
* `p_read` rather than `p_in`, so the saving per call is
|
|
57
|
+
*
|
|
58
|
+
* E[T_read]·(p_in − p_read), E[T_read] = matchingPrefixTokens · P(hit)
|
|
59
|
+
*
|
|
60
|
+
* The one-time write premium is NOT here — it is the switch cost (§4.3). At
|
|
61
|
+
* P(hit) = 0, or when the read rate has degraded to the input rate (L5), this is
|
|
62
|
+
* 0: a candidate with no verified read discount shows no per-call saving.
|
|
63
|
+
*/
|
|
64
|
+
export declare function perCallCacheSavingsUsd(matchingPrefixTokens: number, pHit: number, rates: EffectiveRates): number;
|
|
65
|
+
/**
|
|
66
|
+
* Switch cost of moving a warm workflow from `oldRates` to `newRates` (spec
|
|
67
|
+
* §4.3):
|
|
68
|
+
*
|
|
69
|
+
* switch_cost = prefix_tok·p_write(new) − prefix_tok·p_read(old)
|
|
70
|
+
*
|
|
71
|
+
* The new candidate must WRITE the prefix into its (cold) cache at its write
|
|
72
|
+
* rate; the incumbent would have READ it at its read rate. Positive ⇒ switching
|
|
73
|
+
* costs money up front; negative ⇒ the incumbent's read was already dearer than
|
|
74
|
+
* the challenger's write (rare, but real when the incumbent has no read
|
|
75
|
+
* discount). With `prefixTokens = 0` this is exactly 0 (the regression pin for a
|
|
76
|
+
* route with no cacheable prefix).
|
|
77
|
+
*/
|
|
78
|
+
export declare function switchCostUsd(prefixTokens: number, newRates: EffectiveRates, oldRates: EffectiveRates): number;
|
|
79
|
+
/**
|
|
80
|
+
* Cache option value (spec §4.3): the total warmth advantage a candidate is
|
|
81
|
+
* expected to accrue over its remaining life — `E[remaining_calls] ×
|
|
82
|
+
* per_call_cache_savings`. This is the benefit side the switch cost is weighed
|
|
83
|
+
* against. Non-negative inputs; 0 at either factor 0.
|
|
84
|
+
*/
|
|
85
|
+
export declare function cacheOptionValueUsd(remainingCalls: number, perCallSavingsUsd: number): number;
|
|
86
|
+
/** Floor on the denominator of `breakevenCalls` so a vanishing per-call saving
|
|
87
|
+
* yields a very large (but finite) break-even rather than Infinity/NaN. */
|
|
88
|
+
export declare const BREAKEVEN_EPSILON_USD = 1e-9;
|
|
89
|
+
/**
|
|
90
|
+
* Break-even call count (spec §4.3): `switch_cost / max(per_call_savings, ε)` —
|
|
91
|
+
* how many warm calls a swap must serve before its up-front switch cost is
|
|
92
|
+
* repaid by per-call savings. Returns `Infinity` when the switch never pays back
|
|
93
|
+
* (non-positive per-call saving with a positive switch cost) so callers render
|
|
94
|
+
* "never breaks even" rather than a misleading huge integer. A non-positive
|
|
95
|
+
* switch cost (free or profitable to switch) breaks even at 0 calls.
|
|
96
|
+
*/
|
|
97
|
+
export declare function breakevenCalls(switchCostUsd_: number, perCallSavingsUsd: number): number;
|
|
98
|
+
/**
|
|
99
|
+
* The cache-aware verdict on a swap proposal (spec §4.3). Renders on the
|
|
100
|
+
* console, the `swap execute` approval prompt, and the OTLP decision span.
|
|
101
|
+
* • `cache_positive` — cache-adjusted advantage clears BOTH the absolute
|
|
102
|
+
* floor and the relative threshold; recommended.
|
|
103
|
+
* • `cache_marginal` — a real advantage that does not clear the hysteresis
|
|
104
|
+
* bar; created + labeled, NOT recommended (anti-thrash).
|
|
105
|
+
* • `cache_negative` — cache-adjusted, the challenger is WORSE than list
|
|
106
|
+
* would suggest (incumbent warm, challenger cold).
|
|
107
|
+
* • `no_cache_signal` — no observed cache activity to base a cache-aware
|
|
108
|
+
* verdict on; the ordinary list comparison stands.
|
|
109
|
+
*/
|
|
110
|
+
export type CacheVerdict = "cache_positive" | "cache_marginal" | "cache_negative" | "no_cache_signal";
|
|
111
|
+
/**
|
|
112
|
+
* Hysteresis config (transaction-cost analogy, [Briefing §16]). A swap is
|
|
113
|
+
* `cache_positive` only when its advantage clears BOTH bars — an absolute
|
|
114
|
+
* per-call floor AND a relative fraction of window spend. Defaults per spec
|
|
115
|
+
* §4.3; lives in `WatchConfig` (same home as `thresholdPct`), which composes
|
|
116
|
+
* this shape rather than redefining it.
|
|
117
|
+
*/
|
|
118
|
+
export interface CacheHysteresisConfig {
|
|
119
|
+
/** Absolute floor, USD per call-equivalent (spec default $0.03). */
|
|
120
|
+
readonly cacheFloorUsdPerCall: number;
|
|
121
|
+
/** Relative floor, PERCENT of trailing-window spend (spec default 8%). */
|
|
122
|
+
readonly cacheRelativeThresholdPct: number;
|
|
123
|
+
}
|
|
124
|
+
export declare const DEFAULT_CACHE_HYSTERESIS: CacheHysteresisConfig;
|
|
125
|
+
/** The inputs the verdict classifier weighs (all cache-ADJUSTED). */
|
|
126
|
+
export interface CacheVerdictInput {
|
|
127
|
+
/** Whether the window carries ANY cache observation (read or write) on either
|
|
128
|
+
* side — false ⇒ `no_cache_signal` regardless of the numbers below. */
|
|
129
|
+
readonly hasCacheSignal: boolean;
|
|
130
|
+
/** Cache-adjusted per-call advantage of the challenger over the incumbent,
|
|
131
|
+
* USD (incumbent_effective_per_call − challenger_effective_per_call). >0 ⇒
|
|
132
|
+
* challenger cheaper once warmth + switch cost are counted. */
|
|
133
|
+
readonly perCallAdvantageUsd: number;
|
|
134
|
+
/** The same advantage projected over the expected remaining calls, USD — the
|
|
135
|
+
* quantity weighed against the relative (window-spend) bar. */
|
|
136
|
+
readonly totalAdvantageUsd: number;
|
|
137
|
+
/** Trailing-window spend on this route, USD — the base of the relative bar. */
|
|
138
|
+
readonly windowSpendUsd: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Classify a swap proposal's cache-aware verdict with hysteresis (spec §4.3).
|
|
142
|
+
* Pure and total. The ordering is deliberate: no signal short-circuits first
|
|
143
|
+
* (we cannot speak to cache economics we have not observed), then a negative
|
|
144
|
+
* advantage is `cache_negative`, then the two-bar hysteresis gate separates
|
|
145
|
+
* `cache_positive` from `cache_marginal`.
|
|
146
|
+
*/
|
|
147
|
+
export declare function classifyCacheVerdict(input: CacheVerdictInput, config?: CacheHysteresisConfig): CacheVerdict;
|
|
148
|
+
/** Human-readable one-liner for a verdict — reused by the console, the approval
|
|
149
|
+
* prompt, and the definitions page (WS-D) so the phrasing is single-sourced. */
|
|
150
|
+
export declare function describeCacheVerdict(verdict: CacheVerdict): string;
|
|
151
|
+
//# sourceMappingURL=effective-rate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-rate.d.ts","sourceRoot":"","sources":["../../src/registry/effective-rate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;2EAE2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAUzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,cAAc,GACpB,MAAM,CAKR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,cAAc,GACvB,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAE7F;AAED;2EAC2E;AAC3E,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAIxF;AAID;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAEtG;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;CAC5C;AAED,eAAO,MAAM,wBAAwB,EAAE,qBAGtC,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC;2EACuE;IACvE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;mEAE+D;IAC/D,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;mEAC+D;IAC/D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,iBAAiB,EACxB,MAAM,GAAE,qBAAgD,GACvD,YAAY,CAOd;AAED;gFACgF;AAChF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAWlE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cache-adjusted route economics — cache-aware-routing spec §4.2–4.3, WS-B.
|
|
4
|
+
* PURE: no store, no clock, no network. Every function here is total and
|
|
5
|
+
* deterministic; the whole module is the math the architect reviews pre-merge.
|
|
6
|
+
*
|
|
7
|
+
* ── THE REGRESSION PIN (load-bearing, WS-A discipline) ────────────────────
|
|
8
|
+
* With observed hit rate h = 0 and write rate w = 0, `effectiveInputRate`
|
|
9
|
+
* returns the LIST input rate BYTE-IDENTICALLY, and every per-call saving
|
|
10
|
+
* collapses to 0. A route with no cache activity therefore prices exactly as it
|
|
11
|
+
* did before this module existed. The property test locks this: the cache-aware
|
|
12
|
+
* lens NEVER changes a number until real cache tokens appear.
|
|
13
|
+
*
|
|
14
|
+
* ── FAIL-CLOSED RATES (dev-rule L5) ───────────────────────────────────────
|
|
15
|
+
* An unverified cache-read or cache-write rate degrades to the INPUT rate
|
|
16
|
+
* (1.0×), never a guessed premium/discount — the same posture as
|
|
17
|
+
* `estimateCostUsd`. So an unpriced cache component can only make caching look
|
|
18
|
+
* NEUTRAL, never spuriously attractive.
|
|
19
|
+
*
|
|
20
|
+
* Units: every rate is USD PER TOKEN (matching `ModelPricing.inputUsdPerTok` and
|
|
21
|
+
* `pricing-shape.ts` `TokenRates`), so callers pass either shape unwidened.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.DEFAULT_CACHE_HYSTERESIS = exports.BREAKEVEN_EPSILON_USD = void 0;
|
|
25
|
+
exports.effectiveInputRate = effectiveInputRate;
|
|
26
|
+
exports.perCallCacheSavingsUsd = perCallCacheSavingsUsd;
|
|
27
|
+
exports.switchCostUsd = switchCostUsd;
|
|
28
|
+
exports.cacheOptionValueUsd = cacheOptionValueUsd;
|
|
29
|
+
exports.breakevenCalls = breakevenCalls;
|
|
30
|
+
exports.classifyCacheVerdict = classifyCacheVerdict;
|
|
31
|
+
exports.describeCacheVerdict = describeCacheVerdict;
|
|
32
|
+
/** Resolve a possibly-null cache rate to its fail-closed fallback (the input
|
|
33
|
+
* rate). Centralized so every function degrades identically (L5). */
|
|
34
|
+
function readRate(rates) {
|
|
35
|
+
return rates.cacheRead ?? rates.input;
|
|
36
|
+
}
|
|
37
|
+
function writeRate(rates) {
|
|
38
|
+
return rates.cacheWrite ?? rates.input;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Cache-adjusted effective INPUT rate (spec §4.2, aggregate form):
|
|
42
|
+
*
|
|
43
|
+
* p_eff_in = h·p_read + w·p_write + (1 − h − w)·p_in
|
|
44
|
+
*
|
|
45
|
+
* The blend of what a token of input ACTUALLY costs given the window's cache
|
|
46
|
+
* behavior. Clamps h,w to non-negative and their sum to ≤ 1 (defensive — a
|
|
47
|
+
* malformed rollup must never produce a negative uncached weight). At h = w = 0
|
|
48
|
+
* this is exactly `rates.input` (the regression pin).
|
|
49
|
+
*/
|
|
50
|
+
function effectiveInputRate(obs, rates) {
|
|
51
|
+
const h = Math.max(obs.h, 0);
|
|
52
|
+
const w = Math.max(obs.w, 0);
|
|
53
|
+
// Cap the combined cache weight at 1: uncached weight never goes negative.
|
|
54
|
+
const cacheWeight = Math.min(h + w, 1);
|
|
55
|
+
const scale = h + w > 0 ? cacheWeight / (h + w) : 0;
|
|
56
|
+
const hh = h * scale;
|
|
57
|
+
const ww = w * scale;
|
|
58
|
+
const uncachedWeight = 1 - hh - ww;
|
|
59
|
+
return hh * readRate(rates) + ww * writeRate(rates) + uncachedWeight * rates.input;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Expected per-call saving from READING a warm prefix instead of paying full
|
|
63
|
+
* input on it (spec §4.2, steady-state form): the cached prefix tokens cost
|
|
64
|
+
* `p_read` rather than `p_in`, so the saving per call is
|
|
65
|
+
*
|
|
66
|
+
* E[T_read]·(p_in − p_read), E[T_read] = matchingPrefixTokens · P(hit)
|
|
67
|
+
*
|
|
68
|
+
* The one-time write premium is NOT here — it is the switch cost (§4.3). At
|
|
69
|
+
* P(hit) = 0, or when the read rate has degraded to the input rate (L5), this is
|
|
70
|
+
* 0: a candidate with no verified read discount shows no per-call saving.
|
|
71
|
+
*/
|
|
72
|
+
function perCallCacheSavingsUsd(matchingPrefixTokens, pHit, rates) {
|
|
73
|
+
const tokens = Math.max(matchingPrefixTokens, 0);
|
|
74
|
+
const hit = Math.min(Math.max(pHit, 0), 1);
|
|
75
|
+
const perTokenSaving = Math.max(rates.input - readRate(rates), 0);
|
|
76
|
+
return tokens * hit * perTokenSaving;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Switch cost of moving a warm workflow from `oldRates` to `newRates` (spec
|
|
80
|
+
* §4.3):
|
|
81
|
+
*
|
|
82
|
+
* switch_cost = prefix_tok·p_write(new) − prefix_tok·p_read(old)
|
|
83
|
+
*
|
|
84
|
+
* The new candidate must WRITE the prefix into its (cold) cache at its write
|
|
85
|
+
* rate; the incumbent would have READ it at its read rate. Positive ⇒ switching
|
|
86
|
+
* costs money up front; negative ⇒ the incumbent's read was already dearer than
|
|
87
|
+
* the challenger's write (rare, but real when the incumbent has no read
|
|
88
|
+
* discount). With `prefixTokens = 0` this is exactly 0 (the regression pin for a
|
|
89
|
+
* route with no cacheable prefix).
|
|
90
|
+
*/
|
|
91
|
+
function switchCostUsd(prefixTokens, newRates, oldRates) {
|
|
92
|
+
const tokens = Math.max(prefixTokens, 0);
|
|
93
|
+
return tokens * writeRate(newRates) - tokens * readRate(oldRates);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Cache option value (spec §4.3): the total warmth advantage a candidate is
|
|
97
|
+
* expected to accrue over its remaining life — `E[remaining_calls] ×
|
|
98
|
+
* per_call_cache_savings`. This is the benefit side the switch cost is weighed
|
|
99
|
+
* against. Non-negative inputs; 0 at either factor 0.
|
|
100
|
+
*/
|
|
101
|
+
function cacheOptionValueUsd(remainingCalls, perCallSavingsUsd) {
|
|
102
|
+
return Math.max(remainingCalls, 0) * Math.max(perCallSavingsUsd, 0);
|
|
103
|
+
}
|
|
104
|
+
/** Floor on the denominator of `breakevenCalls` so a vanishing per-call saving
|
|
105
|
+
* yields a very large (but finite) break-even rather than Infinity/NaN. */
|
|
106
|
+
exports.BREAKEVEN_EPSILON_USD = 1e-9;
|
|
107
|
+
/**
|
|
108
|
+
* Break-even call count (spec §4.3): `switch_cost / max(per_call_savings, ε)` —
|
|
109
|
+
* how many warm calls a swap must serve before its up-front switch cost is
|
|
110
|
+
* repaid by per-call savings. Returns `Infinity` when the switch never pays back
|
|
111
|
+
* (non-positive per-call saving with a positive switch cost) so callers render
|
|
112
|
+
* "never breaks even" rather than a misleading huge integer. A non-positive
|
|
113
|
+
* switch cost (free or profitable to switch) breaks even at 0 calls.
|
|
114
|
+
*/
|
|
115
|
+
function breakevenCalls(switchCostUsd_, perCallSavingsUsd) {
|
|
116
|
+
if (switchCostUsd_ <= 0)
|
|
117
|
+
return 0;
|
|
118
|
+
if (perCallSavingsUsd <= 0)
|
|
119
|
+
return Number.POSITIVE_INFINITY;
|
|
120
|
+
return switchCostUsd_ / Math.max(perCallSavingsUsd, exports.BREAKEVEN_EPSILON_USD);
|
|
121
|
+
}
|
|
122
|
+
exports.DEFAULT_CACHE_HYSTERESIS = {
|
|
123
|
+
cacheFloorUsdPerCall: 0.03,
|
|
124
|
+
cacheRelativeThresholdPct: 8,
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Classify a swap proposal's cache-aware verdict with hysteresis (spec §4.3).
|
|
128
|
+
* Pure and total. The ordering is deliberate: no signal short-circuits first
|
|
129
|
+
* (we cannot speak to cache economics we have not observed), then a negative
|
|
130
|
+
* advantage is `cache_negative`, then the two-bar hysteresis gate separates
|
|
131
|
+
* `cache_positive` from `cache_marginal`.
|
|
132
|
+
*/
|
|
133
|
+
function classifyCacheVerdict(input, config = exports.DEFAULT_CACHE_HYSTERESIS) {
|
|
134
|
+
if (!input.hasCacheSignal)
|
|
135
|
+
return "no_cache_signal";
|
|
136
|
+
if (input.perCallAdvantageUsd < 0)
|
|
137
|
+
return "cache_negative";
|
|
138
|
+
const clearsFloor = input.perCallAdvantageUsd >= config.cacheFloorUsdPerCall;
|
|
139
|
+
const relativeBar = (config.cacheRelativeThresholdPct / 100) * Math.max(input.windowSpendUsd, 0);
|
|
140
|
+
const clearsRelative = input.totalAdvantageUsd >= relativeBar;
|
|
141
|
+
return clearsFloor && clearsRelative ? "cache_positive" : "cache_marginal";
|
|
142
|
+
}
|
|
143
|
+
/** Human-readable one-liner for a verdict — reused by the console, the approval
|
|
144
|
+
* prompt, and the definitions page (WS-D) so the phrasing is single-sourced. */
|
|
145
|
+
function describeCacheVerdict(verdict) {
|
|
146
|
+
switch (verdict) {
|
|
147
|
+
case "cache_positive":
|
|
148
|
+
return "cache-positive — clears the switch-cost hysteresis; recommended";
|
|
149
|
+
case "cache_marginal":
|
|
150
|
+
return "cache-marginal — a real but sub-threshold advantage; not recommended (anti-thrash)";
|
|
151
|
+
case "cache_negative":
|
|
152
|
+
return "cache-negative — the incumbent is warm and the challenger is cold; switching costs more than it saves";
|
|
153
|
+
case "no_cache_signal":
|
|
154
|
+
return "no cache signal — no observed cache activity; the list-price comparison stands";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry-wired capability facts — Phase 3 spec WS6, plan §4. Replaces the
|
|
3
|
+
* Phase 1 "grant whatever the adapter claims" stub: routing now consumes
|
|
4
|
+
* declared+probed registry facts from the packaged registry.json
|
|
5
|
+
* (env-overridable path), with precedence
|
|
6
|
+
*
|
|
7
|
+
* capabilityOverrides (config) > probed > declared
|
|
8
|
+
*
|
|
9
|
+
* applied PER FLAG. Discrepancy-aware: a probed-false trumps a declared-true
|
|
10
|
+
* — the DeepSeek case (declared supports_response_schema, probed
|
|
11
|
+
* native_rung_rate = 0) finally routes correctly.
|
|
12
|
+
*
|
|
13
|
+
* Conservatism rules (measurement honesty):
|
|
14
|
+
* - grounded_native: probed can only REVOKE (a declared-true with a low
|
|
15
|
+
* probed grounded rate), never grant — high citation rates without
|
|
16
|
+
* declared search may be fabricated URLs.
|
|
17
|
+
* - Unprobed/undeclared dimensions (explicit caches, cache keys, flex tier,
|
|
18
|
+
* trace visibility) fall back to the adapter-static map, which is the
|
|
19
|
+
* declared layer for call-shaping facts.
|
|
20
|
+
*/
|
|
21
|
+
import type { CapabilityFlag, ProviderId } from "../types/provider";
|
|
22
|
+
import type { RegistryEntry } from "./build";
|
|
23
|
+
export interface RegistryFacts {
|
|
24
|
+
/** Capability flags for a (provider, model), or null when the registry has
|
|
25
|
+
* no entry for it (caller falls back to the adapter-static map). */
|
|
26
|
+
flagsFor(provider: ProviderId, model: string, staticFlags: ReadonlySet<CapabilityFlag>): ReadonlySet<CapabilityFlag> | null;
|
|
27
|
+
}
|
|
28
|
+
/** Per-flag fact merge for one registry entry. `staticFlags` is the
|
|
29
|
+
* adapter-static (declared call-shaping) baseline. */
|
|
30
|
+
export declare function capabilityFactsFromEntry(entry: RegistryEntry, staticFlags: ReadonlySet<CapabilityFlag>): Set<CapabilityFlag>;
|
|
31
|
+
export declare function loadRegistryFacts(registryPath?: string): RegistryFacts;
|
|
32
|
+
//# sourceMappingURL=facts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../../src/registry/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,SAAS,CAAC;AAMvD,MAAM,WAAW,aAAa;IAC5B;wEACoE;IACpE,QAAQ,CACN,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,WAAW,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;CACvC;AAED;sDACsD;AACtD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,WAAW,CAAC,cAAc,CAAC,GACvC,GAAG,CAAC,cAAc,CAAC,CAsErB;AAQD,wBAAgB,iBAAiB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CA8BtE"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Registry-wired capability facts — Phase 3 spec WS6, plan §4. Replaces the
|
|
4
|
+
* Phase 1 "grant whatever the adapter claims" stub: routing now consumes
|
|
5
|
+
* declared+probed registry facts from the packaged registry.json
|
|
6
|
+
* (env-overridable path), with precedence
|
|
7
|
+
*
|
|
8
|
+
* capabilityOverrides (config) > probed > declared
|
|
9
|
+
*
|
|
10
|
+
* applied PER FLAG. Discrepancy-aware: a probed-false trumps a declared-true
|
|
11
|
+
* — the DeepSeek case (declared supports_response_schema, probed
|
|
12
|
+
* native_rung_rate = 0) finally routes correctly.
|
|
13
|
+
*
|
|
14
|
+
* Conservatism rules (measurement honesty):
|
|
15
|
+
* - grounded_native: probed can only REVOKE (a declared-true with a low
|
|
16
|
+
* probed grounded rate), never grant — high citation rates without
|
|
17
|
+
* declared search may be fabricated URLs.
|
|
18
|
+
* - Unprobed/undeclared dimensions (explicit caches, cache keys, flex tier,
|
|
19
|
+
* trace visibility) fall back to the adapter-static map, which is the
|
|
20
|
+
* declared layer for call-shaping facts.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.capabilityFactsFromEntry = capabilityFactsFromEntry;
|
|
24
|
+
exports.loadRegistryFacts = loadRegistryFacts;
|
|
25
|
+
const node_fs_1 = require("node:fs");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
/** Same bar the registry's discrepancy detection uses. */
|
|
28
|
+
const GROUNDED_BAR = 0.5;
|
|
29
|
+
const CONFORMANCE_BAR = 0.9;
|
|
30
|
+
/** Per-flag fact merge for one registry entry. `staticFlags` is the
|
|
31
|
+
* adapter-static (declared call-shaping) baseline. */
|
|
32
|
+
function capabilityFactsFromEntry(entry, staticFlags) {
|
|
33
|
+
const flags = new Set(staticFlags);
|
|
34
|
+
const declared = entry.declared;
|
|
35
|
+
const probed = entry.probed;
|
|
36
|
+
// structured_native — probed native_rung_rate is the ground truth when
|
|
37
|
+
// sampled; else declared (adapter fact first, pricing-map flag second).
|
|
38
|
+
const nativeRate = probed?.schema?.native_rung_rate ?? null;
|
|
39
|
+
if (nativeRate !== null) {
|
|
40
|
+
if (nativeRate > 0)
|
|
41
|
+
flags.add("structured_native");
|
|
42
|
+
else
|
|
43
|
+
flags.delete("structured_native"); // the DeepSeek json_object-vs-schema gap
|
|
44
|
+
}
|
|
45
|
+
else if (declared !== null) {
|
|
46
|
+
const declaredNative = declared.flags.adapter_structured_native ?? declared.flags.supports_response_schema;
|
|
47
|
+
if (declaredNative === true)
|
|
48
|
+
flags.add("structured_native");
|
|
49
|
+
else if (declaredNative === false)
|
|
50
|
+
flags.delete("structured_native");
|
|
51
|
+
}
|
|
52
|
+
// json_mode — probed: a sampled schema class with a decent conform rate on
|
|
53
|
+
// ANY rung proves the model can emit parseable schema-shaped JSON.
|
|
54
|
+
const conformRate = probed?.schema?.conform_rate ?? null;
|
|
55
|
+
if (conformRate !== null && conformRate >= CONFORMANCE_BAR) {
|
|
56
|
+
flags.add("json_mode");
|
|
57
|
+
}
|
|
58
|
+
// grounded_native — probed may REVOKE a declared-true, never grant.
|
|
59
|
+
const groundedRate = probed?.grounding?.grounded_rate ?? null;
|
|
60
|
+
const declaredGrounded = declared === null
|
|
61
|
+
? null
|
|
62
|
+
: (declared.flags.adapter_grounded_native ?? declared.flags.supports_web_search);
|
|
63
|
+
if (declaredGrounded === true)
|
|
64
|
+
flags.add("grounded_native");
|
|
65
|
+
else if (declaredGrounded === false)
|
|
66
|
+
flags.delete("grounded_native");
|
|
67
|
+
if (flags.has("grounded_native") &&
|
|
68
|
+
groundedRate !== null &&
|
|
69
|
+
(probed?.grounding?.samples ?? 0) > 0 &&
|
|
70
|
+
groundedRate < GROUNDED_BAR) {
|
|
71
|
+
flags.delete("grounded_native");
|
|
72
|
+
}
|
|
73
|
+
// prefix_cache_implicit — probed realized cache behavior wins when sampled.
|
|
74
|
+
const caching = probed?.caching ?? null;
|
|
75
|
+
if (caching !== null && caching.samples > 0) {
|
|
76
|
+
if (caching.cache_hit_rate > 0)
|
|
77
|
+
flags.add("prefix_cache_implicit");
|
|
78
|
+
else
|
|
79
|
+
flags.delete("prefix_cache_implicit");
|
|
80
|
+
}
|
|
81
|
+
else if (declared?.flags.supports_prompt_caching === true) {
|
|
82
|
+
flags.add("prefix_cache_implicit");
|
|
83
|
+
}
|
|
84
|
+
// zero_retention (G-3) — the opt-in data-governance filter. Authoritative
|
|
85
|
+
// source is the credential-scoped `facts` layer (claims.ts), NEVER the
|
|
86
|
+
// adapter-static baseline: ZDR is a policy designation, not an observable
|
|
87
|
+
// adapter capability. The flag is present only when a fact designates this
|
|
88
|
+
// endpoint zero-retention under our own (managed) account or unconditionally
|
|
89
|
+
// (any). A byok-only designation does NOT grant it — a customer's own key
|
|
90
|
+
// does not inherit our managed-account posture (the credential-scope
|
|
91
|
+
// compliance rule; product-plan v3.2, phase6-verification.md). Absence, a
|
|
92
|
+
// value:false, and a stray baseline flag all fail closed: no cited managed/any
|
|
93
|
+
// ZDR fact => the model is excluded from a `zero_retention` route.
|
|
94
|
+
const zdrFacts = entry.facts?.zero_retention;
|
|
95
|
+
const zdrDesignated = zdrFacts?.some((f) => f.value === true && (f.scope === "managed" || f.scope === "any")) ?? false;
|
|
96
|
+
if (zdrDesignated)
|
|
97
|
+
flags.add("zero_retention");
|
|
98
|
+
else
|
|
99
|
+
flags.delete("zero_retention");
|
|
100
|
+
// context_cache_explicit / prompt_cache_key / tier_flex / trace_visible:
|
|
101
|
+
// unprobed dimensions — the adapter-static baseline stands.
|
|
102
|
+
return flags;
|
|
103
|
+
}
|
|
104
|
+
/** Packaged registry.json (registry/ at the package root; dist/ and src/
|
|
105
|
+
* both sit one level below it). */
|
|
106
|
+
function packagedRegistryPath() {
|
|
107
|
+
return (0, node_path_1.join)(__dirname, "..", "..", "registry", "registry.json");
|
|
108
|
+
}
|
|
109
|
+
function loadRegistryFacts(registryPath) {
|
|
110
|
+
const path = registryPath ?? packagedRegistryPath();
|
|
111
|
+
let byKey = new Map();
|
|
112
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
113
|
+
const registry = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
114
|
+
byKey = new Map(registry.models.map((m) => [m.model_key, m]));
|
|
115
|
+
}
|
|
116
|
+
else if (registryPath !== undefined) {
|
|
117
|
+
// An EXPLICIT path that doesn't exist is a config error worth failing on;
|
|
118
|
+
// a missing packaged file just means static fallback (fresh checkout).
|
|
119
|
+
throw new Error(`registry facts file not found: ${path}`);
|
|
120
|
+
}
|
|
121
|
+
const cache = new Map();
|
|
122
|
+
return {
|
|
123
|
+
flagsFor(provider, model, staticFlags) {
|
|
124
|
+
const key = `${provider}/${model}`;
|
|
125
|
+
const entry = byKey.get(key);
|
|
126
|
+
if (entry === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
let flags = cache.get(key);
|
|
129
|
+
if (flags === undefined) {
|
|
130
|
+
flags = capabilityFactsFromEntry(entry, staticFlags);
|
|
131
|
+
cache.set(key, flags);
|
|
132
|
+
}
|
|
133
|
+
return flags;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema fingerprinting — spec §2 WS6, network-effects instrumentation.
|
|
3
|
+
* Pure function: JSON schema → bucketed structural features + task category
|
|
4
|
+
* + a stable cell key. Computed at route load; stored on every telemetry row.
|
|
5
|
+
* PRIVACY: the fingerprint must be irreversible — features and category only,
|
|
6
|
+
* never field names, titles, descriptions, or enum values.
|
|
7
|
+
*/
|
|
8
|
+
export type TaskCategory = "extraction" | "generation" | "classification" | "analysis";
|
|
9
|
+
export interface SchemaFingerprint {
|
|
10
|
+
readonly depthBucket: "1-2" | "3-4" | "5+";
|
|
11
|
+
readonly fieldCountBucket: "1-5" | "6-20" | "21-60" | "61+";
|
|
12
|
+
readonly enumDensityBucket: "none" | "low" | "high";
|
|
13
|
+
readonly lengthFloorCount: number;
|
|
14
|
+
readonly arrayComplexityBucket: "none" | "flat" | "nested";
|
|
15
|
+
readonly category: TaskCategory;
|
|
16
|
+
/** Stable hash of the buckets above — the Conformance Matrix cell key. */
|
|
17
|
+
readonly cellKey: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function fingerprintSchema(schema: object | null): SchemaFingerprint | null;
|
|
20
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/registry/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,gBAAgB,GAAG,UAAU,CAAC;AAEvF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACpD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AA8GD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,GAAG,IAAI,CAgDjF"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Schema fingerprinting — spec §2 WS6, network-effects instrumentation.
|
|
4
|
+
* Pure function: JSON schema → bucketed structural features + task category
|
|
5
|
+
* + a stable cell key. Computed at route load; stored on every telemetry row.
|
|
6
|
+
* PRIVACY: the fingerprint must be irreversible — features and category only,
|
|
7
|
+
* never field names, titles, descriptions, or enum values.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.fingerprintSchema = fingerprintSchema;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const MAX_WALK_DEPTH = 12;
|
|
13
|
+
/** minLength at or above this marks a prose field (a deliberate length floor,
|
|
14
|
+
* not an id/enum constraint) — the "analysis" signal. */
|
|
15
|
+
const PROSE_FLOOR_MIN_LENGTH = 200;
|
|
16
|
+
const LOW_ENUM_RATIO = 0.2;
|
|
17
|
+
function isRecord(v) {
|
|
18
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19
|
+
}
|
|
20
|
+
const SCALAR_TYPES = new Set(["string", "number", "integer", "boolean", "null"]);
|
|
21
|
+
function isScalarNode(node) {
|
|
22
|
+
return typeof node.type === "string" && SCALAR_TYPES.has(node.type);
|
|
23
|
+
}
|
|
24
|
+
function walk(node, depth, stats) {
|
|
25
|
+
if (!isRecord(node) || depth > MAX_WALK_DEPTH)
|
|
26
|
+
return;
|
|
27
|
+
if (depth > stats.maxDepth)
|
|
28
|
+
stats.maxDepth = depth;
|
|
29
|
+
if (isScalarNode(node)) {
|
|
30
|
+
stats.leafCount += 1;
|
|
31
|
+
if (Array.isArray(node.enum) || node.const !== undefined)
|
|
32
|
+
stats.enumLeafCount += 1;
|
|
33
|
+
if (node.type === "string" &&
|
|
34
|
+
typeof node.minLength === "number" &&
|
|
35
|
+
node.minLength >= PROSE_FLOOR_MIN_LENGTH) {
|
|
36
|
+
stats.proseFloorCount += 1;
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const properties = node.properties;
|
|
41
|
+
if (isRecord(properties)) {
|
|
42
|
+
for (const child of Object.values(properties)) {
|
|
43
|
+
stats.fieldCount += 1;
|
|
44
|
+
walk(child, depth + 1, stats);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const items = node.items;
|
|
48
|
+
if (items !== undefined) {
|
|
49
|
+
const itemNodes = Array.isArray(items) ? items : [items];
|
|
50
|
+
for (const item of itemNodes) {
|
|
51
|
+
if (isRecord(item) && isScalarNode(item)) {
|
|
52
|
+
stats.hasFlatArray = true;
|
|
53
|
+
walk(item, depth + 1, stats);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
stats.hasNestedArray = true;
|
|
57
|
+
walk(item, depth + 1, stats);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (isRecord(node.additionalProperties))
|
|
62
|
+
walk(node.additionalProperties, depth + 1, stats);
|
|
63
|
+
for (const combinator of ["anyOf", "oneOf", "allOf"]) {
|
|
64
|
+
const branches = node[combinator];
|
|
65
|
+
if (Array.isArray(branches)) {
|
|
66
|
+
// Combinator branches describe the SAME position — no depth increase.
|
|
67
|
+
for (const branch of branches)
|
|
68
|
+
walk(branch, depth, stats);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function bucketDepth(depth) {
|
|
73
|
+
if (depth <= 2)
|
|
74
|
+
return "1-2";
|
|
75
|
+
if (depth <= 4)
|
|
76
|
+
return "3-4";
|
|
77
|
+
return "5+";
|
|
78
|
+
}
|
|
79
|
+
function bucketFieldCount(count) {
|
|
80
|
+
if (count <= 5)
|
|
81
|
+
return "1-5";
|
|
82
|
+
if (count <= 20)
|
|
83
|
+
return "6-20";
|
|
84
|
+
if (count <= 60)
|
|
85
|
+
return "21-60";
|
|
86
|
+
return "61+";
|
|
87
|
+
}
|
|
88
|
+
function bucketEnumDensity(ratio) {
|
|
89
|
+
if (ratio === 0)
|
|
90
|
+
return "none";
|
|
91
|
+
if (ratio < LOW_ENUM_RATIO)
|
|
92
|
+
return "low";
|
|
93
|
+
return "high";
|
|
94
|
+
}
|
|
95
|
+
function categorize(stats, enumDensity) {
|
|
96
|
+
// Order matters (v0 heuristic, spec WS6): a deliberate prose floor is the
|
|
97
|
+
// strongest signal (analysis) → enum dominance (classification) → shallow
|
|
98
|
+
// typed scalars (extraction) → everything else (generation).
|
|
99
|
+
if (stats.proseFloorCount > 0)
|
|
100
|
+
return "analysis";
|
|
101
|
+
if (enumDensity === "high")
|
|
102
|
+
return "classification";
|
|
103
|
+
if (stats.maxDepth <= 2 && !stats.hasNestedArray && stats.leafCount > 0)
|
|
104
|
+
return "extraction";
|
|
105
|
+
return "generation";
|
|
106
|
+
}
|
|
107
|
+
function fingerprintSchema(schema) {
|
|
108
|
+
if (schema === null)
|
|
109
|
+
return null;
|
|
110
|
+
const stats = {
|
|
111
|
+
maxDepth: 0,
|
|
112
|
+
fieldCount: 0,
|
|
113
|
+
leafCount: 0,
|
|
114
|
+
enumLeafCount: 0,
|
|
115
|
+
proseFloorCount: 0,
|
|
116
|
+
hasFlatArray: false,
|
|
117
|
+
hasNestedArray: false,
|
|
118
|
+
};
|
|
119
|
+
walk(schema, 1, stats);
|
|
120
|
+
const enumRatio = stats.leafCount === 0 ? 0 : stats.enumLeafCount / stats.leafCount;
|
|
121
|
+
const enumDensityBucket = bucketEnumDensity(enumRatio);
|
|
122
|
+
const arrayComplexityBucket = stats.hasNestedArray
|
|
123
|
+
? "nested"
|
|
124
|
+
: stats.hasFlatArray
|
|
125
|
+
? "flat"
|
|
126
|
+
: "none";
|
|
127
|
+
const category = categorize(stats, enumDensityBucket);
|
|
128
|
+
const depthBucket = bucketDepth(stats.maxDepth);
|
|
129
|
+
const fieldCountBucket = bucketFieldCount(stats.fieldCount);
|
|
130
|
+
const cellKey = (0, node_crypto_1.createHash)("sha256")
|
|
131
|
+
.update(JSON.stringify([
|
|
132
|
+
depthBucket,
|
|
133
|
+
fieldCountBucket,
|
|
134
|
+
enumDensityBucket,
|
|
135
|
+
stats.proseFloorCount,
|
|
136
|
+
arrayComplexityBucket,
|
|
137
|
+
category,
|
|
138
|
+
]))
|
|
139
|
+
.digest("hex")
|
|
140
|
+
.slice(0, 16);
|
|
141
|
+
return {
|
|
142
|
+
depthBucket,
|
|
143
|
+
fieldCountBucket,
|
|
144
|
+
enumDensityBucket,
|
|
145
|
+
lengthFloorCount: stats.proseFloorCount,
|
|
146
|
+
arrayComplexityBucket,
|
|
147
|
+
category,
|
|
148
|
+
cellKey,
|
|
149
|
+
};
|
|
150
|
+
}
|