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,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrub v1 — deterministic PII/PHI detection over artifact content BEFORE upload
|
|
3
|
+
* (E2-C chunk B, spec §3.1 / Expansion spec §7.4).
|
|
4
|
+
*
|
|
5
|
+
* Four modes, per route (default `detect` when content custody is on):
|
|
6
|
+
* off — no scan; upload verbatim.
|
|
7
|
+
* detect — scan; record FINDINGS as counts-by-type; upload verbatim. The
|
|
8
|
+
* findings are metadata (how many SSNs, how many emails), NEVER the
|
|
9
|
+
* matched values — recording the value would move the PII into the
|
|
10
|
+
* control-plane DB, the exact thing custody is meant to contain.
|
|
11
|
+
* redact — scan; upload a MASKED copy; the finding counts still travel.
|
|
12
|
+
* block — scan; if anything is found, REFUSE the upload (counted); the
|
|
13
|
+
* artifact keeps its metadata + hash, it simply stores no content.
|
|
14
|
+
*
|
|
15
|
+
* ── DETERMINISTIC ≠ DLP (honest limits) ───────────────────────────────────
|
|
16
|
+
* These are regex/checksum detectors, not a model. They catch well-formed SSNs,
|
|
17
|
+
* Luhn-valid PANs, emails, E.164/US phones, and labelled MRNs. They WILL miss
|
|
18
|
+
* obfuscated or free-form PII (that is scrub v2, model-assisted, out of scope).
|
|
19
|
+
* The custody docs page states this plainly — a caller must not read a clean
|
|
20
|
+
* `detect` result as a guarantee of no PII.
|
|
21
|
+
*
|
|
22
|
+
* NEVER logs a matched value. Findings are `{type, count}` only.
|
|
23
|
+
*/
|
|
24
|
+
export type ScrubMode = "off" | "detect" | "redact" | "block";
|
|
25
|
+
/** A finding is a COUNT by type — never a value (the load-bearing invariant). */
|
|
26
|
+
export interface ScrubFinding {
|
|
27
|
+
readonly type: ScrubDetectorType;
|
|
28
|
+
readonly count: number;
|
|
29
|
+
}
|
|
30
|
+
export type ScrubDetectorType = "ssn" | "pan" | "email" | "phone" | "mrn";
|
|
31
|
+
export interface ScrubResult {
|
|
32
|
+
/** Findings as counts-by-type, sorted by type. Empty when nothing matched. */
|
|
33
|
+
readonly findings: readonly ScrubFinding[];
|
|
34
|
+
/** The text to upload: identical to input for off/detect, masked for redact,
|
|
35
|
+
* and null for block-with-findings (nothing is uploaded). */
|
|
36
|
+
readonly text: string | null;
|
|
37
|
+
/** True when `block` mode refused because findings were present. */
|
|
38
|
+
readonly blocked: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** The per-artifact scrub record persisted on `artifacts.scrub` (migration
|
|
41
|
+
* 00039). COUNTS ONLY — the `findings` are `{type,count}`, never a matched
|
|
42
|
+
* value (the load-bearing invariant, same as the SDK counters). `mode` records
|
|
43
|
+
* WHICH policy ran; `blocked` whether `block` mode refused the upload. */
|
|
44
|
+
export interface ScrubSummary {
|
|
45
|
+
readonly mode: ScrubMode;
|
|
46
|
+
readonly blocked: boolean;
|
|
47
|
+
readonly findings: readonly ScrubFinding[];
|
|
48
|
+
}
|
|
49
|
+
/** Build the persisted summary from a scrub result. The only producer of the
|
|
50
|
+
* `artifacts.scrub` value, so the counts-not-values invariant lives in one
|
|
51
|
+
* place — it simply forwards `ScrubResult.findings`, which are already counts. */
|
|
52
|
+
export declare function summarizeScrub(mode: ScrubMode, result: ScrubResult): ScrubSummary;
|
|
53
|
+
/** Luhn checksum — the difference between "16 digits" and "a card number". */
|
|
54
|
+
export declare function luhnValid(digits: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Scan `text` and produce a scrub result for `mode`. Pure and deterministic.
|
|
57
|
+
* Never returns or logs a matched value.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scrubContent(text: string, mode: ScrubMode): ScrubResult;
|
|
60
|
+
//# sourceMappingURL=scrub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub.d.ts","sourceRoot":"","sources":["../../src/artifacts/scrub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C;iEAC6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;0EAG0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED;;kFAEkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY,CAEjF;AAWD,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAejD;AA0CD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CA6CvE"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scrub v1 — deterministic PII/PHI detection over artifact content BEFORE upload
|
|
4
|
+
* (E2-C chunk B, spec §3.1 / Expansion spec §7.4).
|
|
5
|
+
*
|
|
6
|
+
* Four modes, per route (default `detect` when content custody is on):
|
|
7
|
+
* off — no scan; upload verbatim.
|
|
8
|
+
* detect — scan; record FINDINGS as counts-by-type; upload verbatim. The
|
|
9
|
+
* findings are metadata (how many SSNs, how many emails), NEVER the
|
|
10
|
+
* matched values — recording the value would move the PII into the
|
|
11
|
+
* control-plane DB, the exact thing custody is meant to contain.
|
|
12
|
+
* redact — scan; upload a MASKED copy; the finding counts still travel.
|
|
13
|
+
* block — scan; if anything is found, REFUSE the upload (counted); the
|
|
14
|
+
* artifact keeps its metadata + hash, it simply stores no content.
|
|
15
|
+
*
|
|
16
|
+
* ── DETERMINISTIC ≠ DLP (honest limits) ───────────────────────────────────
|
|
17
|
+
* These are regex/checksum detectors, not a model. They catch well-formed SSNs,
|
|
18
|
+
* Luhn-valid PANs, emails, E.164/US phones, and labelled MRNs. They WILL miss
|
|
19
|
+
* obfuscated or free-form PII (that is scrub v2, model-assisted, out of scope).
|
|
20
|
+
* The custody docs page states this plainly — a caller must not read a clean
|
|
21
|
+
* `detect` result as a guarantee of no PII.
|
|
22
|
+
*
|
|
23
|
+
* NEVER logs a matched value. Findings are `{type, count}` only.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.summarizeScrub = summarizeScrub;
|
|
27
|
+
exports.luhnValid = luhnValid;
|
|
28
|
+
exports.scrubContent = scrubContent;
|
|
29
|
+
/** Build the persisted summary from a scrub result. The only producer of the
|
|
30
|
+
* `artifacts.scrub` value, so the counts-not-values invariant lives in one
|
|
31
|
+
* place — it simply forwards `ScrubResult.findings`, which are already counts. */
|
|
32
|
+
function summarizeScrub(mode, result) {
|
|
33
|
+
return { mode, blocked: result.blocked, findings: result.findings };
|
|
34
|
+
}
|
|
35
|
+
/** Luhn checksum — the difference between "16 digits" and "a card number". */
|
|
36
|
+
function luhnValid(digits) {
|
|
37
|
+
const only = digits.replace(/\D/g, "");
|
|
38
|
+
if (only.length < 13 || only.length > 19)
|
|
39
|
+
return false;
|
|
40
|
+
let sum = 0;
|
|
41
|
+
let dbl = false;
|
|
42
|
+
for (let i = only.length - 1; i >= 0; i--) {
|
|
43
|
+
let d = only.charCodeAt(i) - 48;
|
|
44
|
+
if (dbl) {
|
|
45
|
+
d *= 2;
|
|
46
|
+
if (d > 9)
|
|
47
|
+
d -= 9;
|
|
48
|
+
}
|
|
49
|
+
sum += d;
|
|
50
|
+
dbl = !dbl;
|
|
51
|
+
}
|
|
52
|
+
return sum % 10 === 0;
|
|
53
|
+
}
|
|
54
|
+
// Order matters: PAN before phone/SSN so a 16-digit run is claimed as a card,
|
|
55
|
+
// not split. Each pattern is intentionally conservative — a false positive that
|
|
56
|
+
// masks a non-secret is cheaper than a false negative, but a pattern loose
|
|
57
|
+
// enough to match ordinary prose would make `block` unusable.
|
|
58
|
+
const DETECTORS = [
|
|
59
|
+
{
|
|
60
|
+
// PAN: 13–16 digits, optionally split by spaces/dashes in 4s, Luhn-valid.
|
|
61
|
+
type: "pan",
|
|
62
|
+
re: /\b(?:\d[ -]?){12,18}\d\b/g,
|
|
63
|
+
accept: luhnValid,
|
|
64
|
+
mask: "[REDACTED-PAN]",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
// US SSN: AAA-GG-SSSS with dashes (the delimited form; bare 9-digit runs are
|
|
68
|
+
// too ambiguous to claim without a false-positive flood).
|
|
69
|
+
type: "ssn",
|
|
70
|
+
re: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
71
|
+
mask: "[REDACTED-SSN]",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "email",
|
|
75
|
+
re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
76
|
+
mask: "[REDACTED-EMAIL]",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
// Phone: E.164 (+1…) or US formats (parens/dashes/dots/spaces). Requires a
|
|
80
|
+
// grouping delimiter or leading + so bare 10-digit ids don't all match.
|
|
81
|
+
type: "phone",
|
|
82
|
+
re: /(?:\+\d{1,3}[ -]?)?(?:\(\d{3}\)|\d{3})[ .-]\d{3}[ .-]\d{4}\b/g,
|
|
83
|
+
mask: "[REDACTED-PHONE]",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
// MRN-ish: an explicit label followed by an id. A heuristic by nature — the
|
|
87
|
+
// label is what makes it claimable without a false-positive flood.
|
|
88
|
+
type: "mrn",
|
|
89
|
+
re: /\bMRN\b\s*[:#]?\s*[A-Za-z0-9-]{4,}/gi,
|
|
90
|
+
mask: "[REDACTED-MRN]",
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
/**
|
|
94
|
+
* Scan `text` and produce a scrub result for `mode`. Pure and deterministic.
|
|
95
|
+
* Never returns or logs a matched value.
|
|
96
|
+
*/
|
|
97
|
+
function scrubContent(text, mode) {
|
|
98
|
+
if (mode === "off") {
|
|
99
|
+
return { findings: [], text, blocked: false };
|
|
100
|
+
}
|
|
101
|
+
const counts = new Map();
|
|
102
|
+
let masked = text;
|
|
103
|
+
for (const det of DETECTORS) {
|
|
104
|
+
// Fresh regex state per pass (global flag carries lastIndex otherwise).
|
|
105
|
+
const re = new RegExp(det.re.source, det.re.flags);
|
|
106
|
+
let count = 0;
|
|
107
|
+
const matched = [];
|
|
108
|
+
for (const m of text.matchAll(re)) {
|
|
109
|
+
const value = m[0];
|
|
110
|
+
if (det.accept && !det.accept(value))
|
|
111
|
+
continue;
|
|
112
|
+
count += 1;
|
|
113
|
+
if (mode === "redact")
|
|
114
|
+
matched.push(value);
|
|
115
|
+
}
|
|
116
|
+
if (count > 0) {
|
|
117
|
+
counts.set(det.type, count);
|
|
118
|
+
if (mode === "redact") {
|
|
119
|
+
for (const value of matched) {
|
|
120
|
+
// Replace literal occurrences with the mask. split/join avoids
|
|
121
|
+
// regex-escaping the value AND avoids putting it in any log.
|
|
122
|
+
masked = masked.split(value).join(det.mask);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const findings = [...counts.entries()]
|
|
128
|
+
.map(([type, count]) => ({ type, count }))
|
|
129
|
+
.sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0));
|
|
130
|
+
const hasFindings = findings.length > 0;
|
|
131
|
+
if (mode === "block" && hasFindings) {
|
|
132
|
+
return { findings, text: null, blocked: true };
|
|
133
|
+
}
|
|
134
|
+
if (mode === "redact") {
|
|
135
|
+
return { findings, text: masked, blocked: false };
|
|
136
|
+
}
|
|
137
|
+
// detect (or block with no findings): upload verbatim, report counts.
|
|
138
|
+
return { findings, text, blocked: false };
|
|
139
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@step` / `step()` — execution scope for NON-LLM stages (spec §1.4, §9).
|
|
3
|
+
*
|
|
4
|
+
* A step is an execution operation ("what happened"). For LLM calls `rig.run()`
|
|
5
|
+
* auto-creates a ground-truth step (its inference row already exists); this
|
|
6
|
+
* wrapper is for the OTHER stages — retrieval, transforms, tool calls, external
|
|
7
|
+
* APIs — which have no inference row.
|
|
8
|
+
*
|
|
9
|
+
* ── WHY THIS WRITES NO STEP ROW THIS MISSION ──────────────────────────────
|
|
10
|
+
* The `steps` CHECK requires EXACTLY ONE reference: ground-truth (an inference)
|
|
11
|
+
* XOR attested (a foreign OTLP span). A pure non-LLM stage has neither — its
|
|
12
|
+
* span reference arrives with OTLP INGEST (E3), and nothing writes `attested`
|
|
13
|
+
* rows this mission. So `step()` establishes a NAMED execution scope inside the
|
|
14
|
+
* active run context (chunk B attributes artifact save/load lineage to it) and
|
|
15
|
+
* runs the body; it does not persist a row until it has a valid reference.
|
|
16
|
+
*
|
|
17
|
+
* Instrumentation never changes the wrapped function's return value, and never
|
|
18
|
+
* throws into it — a disabled or absent run context just runs the body.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Run `fn` as a named execution step within the active run context. Returns the
|
|
22
|
+
* body's value unchanged (a promise if the body is async). Outside a run context
|
|
23
|
+
* (namespace disabled, or no run.start on this path) it simply runs the body.
|
|
24
|
+
*/
|
|
25
|
+
export declare function step<T>(key: string, fn: () => T): T;
|
|
26
|
+
//# sourceMappingURL=step.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/artifacts/step.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAOnD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@step` / `step()` — execution scope for NON-LLM stages (spec §1.4, §9).
|
|
4
|
+
*
|
|
5
|
+
* A step is an execution operation ("what happened"). For LLM calls `rig.run()`
|
|
6
|
+
* auto-creates a ground-truth step (its inference row already exists); this
|
|
7
|
+
* wrapper is for the OTHER stages — retrieval, transforms, tool calls, external
|
|
8
|
+
* APIs — which have no inference row.
|
|
9
|
+
*
|
|
10
|
+
* ── WHY THIS WRITES NO STEP ROW THIS MISSION ──────────────────────────────
|
|
11
|
+
* The `steps` CHECK requires EXACTLY ONE reference: ground-truth (an inference)
|
|
12
|
+
* XOR attested (a foreign OTLP span). A pure non-LLM stage has neither — its
|
|
13
|
+
* span reference arrives with OTLP INGEST (E3), and nothing writes `attested`
|
|
14
|
+
* rows this mission. So `step()` establishes a NAMED execution scope inside the
|
|
15
|
+
* active run context (chunk B attributes artifact save/load lineage to it) and
|
|
16
|
+
* runs the body; it does not persist a row until it has a valid reference.
|
|
17
|
+
*
|
|
18
|
+
* Instrumentation never changes the wrapped function's return value, and never
|
|
19
|
+
* throws into it — a disabled or absent run context just runs the body.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.step = step;
|
|
23
|
+
const run_context_1 = require("./run-context");
|
|
24
|
+
/**
|
|
25
|
+
* Run `fn` as a named execution step within the active run context. Returns the
|
|
26
|
+
* body's value unchanged (a promise if the body is async). Outside a run context
|
|
27
|
+
* (namespace disabled, or no run.start on this path) it simply runs the body.
|
|
28
|
+
*/
|
|
29
|
+
function step(key, fn) {
|
|
30
|
+
const ctx = (0, run_context_1.getActiveRunContext)();
|
|
31
|
+
if (ctx === null)
|
|
32
|
+
return fn();
|
|
33
|
+
// Reserve a unique step key so chunk B can attribute this scope's artifact
|
|
34
|
+
// saves/loads. No row is written (see header).
|
|
35
|
+
ctx.nextStepKey(key);
|
|
36
|
+
return fn();
|
|
37
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint propagation — downstream closure over the artifact graph (Expansion E4
|
|
3
|
+
* chunk C, spec §Chunk C). "If THIS artifact is bad, what derived from it?" —
|
|
4
|
+
* the condemn-or-clear question. Follows lineage edges forward (an edge means
|
|
5
|
+
* `from` was produced from / consumed `to`, so `from` is DOWNSTREAM of `to`) and
|
|
6
|
+
* `supersedes` chains forward (a version that supersedes a tainted one inherits
|
|
7
|
+
* the taint). Served as a bounded DTO the artifact page renders and the replay
|
|
8
|
+
* affordance (chunk A) links into.
|
|
9
|
+
*
|
|
10
|
+
* ── BOUNDED + HONEST ──────────────────────────────────────────────────────
|
|
11
|
+
* Real graphs can be deep, wide, or cyclic (a redaction supersedes an artifact
|
|
12
|
+
* that a later step also consumed). So the closure is:
|
|
13
|
+
* - depth-bounded (default 20) — `depthLimited` says more may exist deeper;
|
|
14
|
+
* - cycle-guarded (a visited set — a node is expanded once);
|
|
15
|
+
* - count-capped (default 500) — `truncated` says "≥ cap", never a silent cut.
|
|
16
|
+
* The production path is a recursive CTE with the SAME bounds (server); this pure
|
|
17
|
+
* function is the algorithm both the CTE golden-tests against and a caller can
|
|
18
|
+
* run over fetched edges without a database.
|
|
19
|
+
*/
|
|
20
|
+
/** A stored lineage edge: `from` depends on `to` (kind = how). */
|
|
21
|
+
export interface TaintEdge {
|
|
22
|
+
readonly from: string;
|
|
23
|
+
readonly to: string;
|
|
24
|
+
readonly kind: "consumed" | "derived_from";
|
|
25
|
+
}
|
|
26
|
+
/** A supersession: `artifactId` supersedes `supersedesArtifactId` (a newer
|
|
27
|
+
* version). The superseder is downstream of what it supersedes. */
|
|
28
|
+
export interface Supersession {
|
|
29
|
+
readonly artifactId: string;
|
|
30
|
+
readonly supersedesArtifactId: string;
|
|
31
|
+
}
|
|
32
|
+
export interface TaintNode {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/** Hops from the root (1 = a direct descendant). */
|
|
35
|
+
readonly depth: number;
|
|
36
|
+
}
|
|
37
|
+
export interface TaintClosure {
|
|
38
|
+
readonly root: string;
|
|
39
|
+
/** Downstream artifacts, breadth-first (nearest first), deduped, capped. */
|
|
40
|
+
readonly downstream: readonly TaintNode[];
|
|
41
|
+
/** `downstream.length` — the count actually returned. */
|
|
42
|
+
readonly count: number;
|
|
43
|
+
/** True when the closure hit the count cap — the honest "≥ count" signal. */
|
|
44
|
+
readonly truncated: boolean;
|
|
45
|
+
/** True when the frontier reached `maxDepth` — more may exist deeper. */
|
|
46
|
+
readonly depthLimited: boolean;
|
|
47
|
+
readonly maxDepth: number;
|
|
48
|
+
readonly cap: number;
|
|
49
|
+
}
|
|
50
|
+
export interface TaintOptions {
|
|
51
|
+
/** Max hops from the root (default 20). */
|
|
52
|
+
readonly maxDepth?: number;
|
|
53
|
+
/** Max downstream nodes returned before truncation (default 500). */
|
|
54
|
+
readonly cap?: number;
|
|
55
|
+
}
|
|
56
|
+
export declare const TAINT_DEFAULT_MAX_DEPTH = 20;
|
|
57
|
+
export declare const TAINT_DEFAULT_CAP = 500;
|
|
58
|
+
/**
|
|
59
|
+
* Downstream closure from `root`. Pure and deterministic. Cycle-guarded via a
|
|
60
|
+
* visited set; depth-bounded; count-capped with an honest `truncated` flag.
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeDownstreamClosure(root: string, edges: readonly TaintEdge[], supersessions: readonly Supersession[], options?: TaintOptions): TaintClosure;
|
|
63
|
+
//# sourceMappingURL=taint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taint.d.ts","sourceRoot":"","sources":["../../src/artifacts/taint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC;CAC5C;AAED;mEACmE;AACnE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,aAAa,EAAE,SAAS,YAAY,EAAE,EACtC,OAAO,GAAE,YAAiB,GACzB,YAAY,CA2Cd"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Taint propagation — downstream closure over the artifact graph (Expansion E4
|
|
4
|
+
* chunk C, spec §Chunk C). "If THIS artifact is bad, what derived from it?" —
|
|
5
|
+
* the condemn-or-clear question. Follows lineage edges forward (an edge means
|
|
6
|
+
* `from` was produced from / consumed `to`, so `from` is DOWNSTREAM of `to`) and
|
|
7
|
+
* `supersedes` chains forward (a version that supersedes a tainted one inherits
|
|
8
|
+
* the taint). Served as a bounded DTO the artifact page renders and the replay
|
|
9
|
+
* affordance (chunk A) links into.
|
|
10
|
+
*
|
|
11
|
+
* ── BOUNDED + HONEST ──────────────────────────────────────────────────────
|
|
12
|
+
* Real graphs can be deep, wide, or cyclic (a redaction supersedes an artifact
|
|
13
|
+
* that a later step also consumed). So the closure is:
|
|
14
|
+
* - depth-bounded (default 20) — `depthLimited` says more may exist deeper;
|
|
15
|
+
* - cycle-guarded (a visited set — a node is expanded once);
|
|
16
|
+
* - count-capped (default 500) — `truncated` says "≥ cap", never a silent cut.
|
|
17
|
+
* The production path is a recursive CTE with the SAME bounds (server); this pure
|
|
18
|
+
* function is the algorithm both the CTE golden-tests against and a caller can
|
|
19
|
+
* run over fetched edges without a database.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.TAINT_DEFAULT_CAP = exports.TAINT_DEFAULT_MAX_DEPTH = void 0;
|
|
23
|
+
exports.computeDownstreamClosure = computeDownstreamClosure;
|
|
24
|
+
exports.TAINT_DEFAULT_MAX_DEPTH = 20;
|
|
25
|
+
exports.TAINT_DEFAULT_CAP = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Downstream closure from `root`. Pure and deterministic. Cycle-guarded via a
|
|
28
|
+
* visited set; depth-bounded; count-capped with an honest `truncated` flag.
|
|
29
|
+
*/
|
|
30
|
+
function computeDownstreamClosure(root, edges, supersessions, options = {}) {
|
|
31
|
+
const maxDepth = options.maxDepth ?? exports.TAINT_DEFAULT_MAX_DEPTH;
|
|
32
|
+
const cap = options.cap ?? exports.TAINT_DEFAULT_CAP;
|
|
33
|
+
// Adjacency: node → its direct downstream nodes. An edge (from → to) makes
|
|
34
|
+
// `from` downstream of `to`; a supersession makes the superseder downstream of
|
|
35
|
+
// what it supersedes. Both collapse into the same "who depends on X" map.
|
|
36
|
+
const downstreamOf = new Map();
|
|
37
|
+
const add = (parent, child) => {
|
|
38
|
+
const cur = downstreamOf.get(parent);
|
|
39
|
+
if (cur === undefined)
|
|
40
|
+
downstreamOf.set(parent, [child]);
|
|
41
|
+
else
|
|
42
|
+
cur.push(child);
|
|
43
|
+
};
|
|
44
|
+
for (const e of edges)
|
|
45
|
+
add(e.to, e.from);
|
|
46
|
+
for (const s of supersessions)
|
|
47
|
+
add(s.supersedesArtifactId, s.artifactId);
|
|
48
|
+
const visited = new Set([root]); // root is never listed as its own downstream
|
|
49
|
+
const downstream = [];
|
|
50
|
+
let truncated = false;
|
|
51
|
+
let depthLimited = false;
|
|
52
|
+
// BFS by depth so `downstream` is nearest-first and depth is well-defined.
|
|
53
|
+
let frontier = [root];
|
|
54
|
+
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
55
|
+
const next = [];
|
|
56
|
+
for (const node of frontier) {
|
|
57
|
+
for (const child of downstreamOf.get(node) ?? []) {
|
|
58
|
+
if (visited.has(child))
|
|
59
|
+
continue; // cycle / diamond guard: expand once
|
|
60
|
+
visited.add(child);
|
|
61
|
+
if (downstream.length >= cap) {
|
|
62
|
+
truncated = true;
|
|
63
|
+
continue; // keep draining `visited` so we don't re-count, but add no more
|
|
64
|
+
}
|
|
65
|
+
downstream.push({ id: child, depth });
|
|
66
|
+
next.push(child);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
frontier = next;
|
|
70
|
+
}
|
|
71
|
+
// If we stopped because we hit maxDepth but there was still a frontier, more
|
|
72
|
+
// may exist deeper than we looked.
|
|
73
|
+
if (frontier.length > 0)
|
|
74
|
+
depthLimited = true;
|
|
75
|
+
return { root, downstream, count: downstream.length, truncated, depthLimited, maxDepth, cap };
|
|
76
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Virtual cache simulator — cache-aware-routing spec §5.4, [Briefing §26]. PURE:
|
|
3
|
+
* no store/clock/network. THE fix for the independent-turn evaluation fallacy:
|
|
4
|
+
* replay currently scores every sample COLD, so a bake-off report can never see
|
|
5
|
+
* that a candidate would run warm at production cadence. This walks a candidate's
|
|
6
|
+
* call sequence in timestamp order, maintaining a per-candidate VIRTUAL cache
|
|
7
|
+
* from the POLICY REGISTRY (TTL + min-cacheable + refresh-on-read), and emits a
|
|
8
|
+
* per-call {read, write, uncached} token split.
|
|
9
|
+
*
|
|
10
|
+
* ── SIMULATOR HONESTY (spec §5.4, §10 credibility risk) ───────────────────
|
|
11
|
+
* • State comes ONLY from the policy registry — never from a guessed model of
|
|
12
|
+
* how a provider caches. An undocumented TTL uses a CONSERVATIVE fallback
|
|
13
|
+
* (short), so the sim UNDER-credits rather than over-credits warmth.
|
|
14
|
+
* • The output is always LABELLED simulation. The odometer stays measured-only
|
|
15
|
+
* (this module never feeds it) — simulator output is never "verified savings".
|
|
16
|
+
* • Cache identity is the caller-supplied `prefixFingerprint`. A template
|
|
17
|
+
* mutation or a tool-order change is, by construction, a DIFFERENT
|
|
18
|
+
* fingerprint — so a mutation forces a miss/rewrite with no special-casing
|
|
19
|
+
* here (the synthetic tests exercise exactly that).
|
|
20
|
+
*
|
|
21
|
+
* A "switch-and-return" is modelled by a time GAP in this candidate's sequence:
|
|
22
|
+
* if the gap is within TTL the return is a read (still warm); if it exceeds TTL
|
|
23
|
+
* the cache has expired and the return rewrites. Nothing about the OTHER
|
|
24
|
+
* candidate is needed — each candidate is simulated over its own calls.
|
|
25
|
+
*/
|
|
26
|
+
import type { CachePolicy } from "../registry/cache-policy";
|
|
27
|
+
import type { EffectiveRates } from "../registry/effective-rate";
|
|
28
|
+
import type { ProviderId } from "../types/provider";
|
|
29
|
+
/** One call in a candidate's sequence. `prefixTokens` is the cacheable prefix
|
|
30
|
+
* length; `promptTokens` the whole input (prefix + volatile tail). */
|
|
31
|
+
export interface SimCall {
|
|
32
|
+
readonly tsMs: number;
|
|
33
|
+
readonly prefixFingerprint: string;
|
|
34
|
+
readonly prefixTokens: number;
|
|
35
|
+
readonly promptTokens: number;
|
|
36
|
+
}
|
|
37
|
+
/** Per-call token split — a disjoint partition of `promptTokens`:
|
|
38
|
+
* `simulated_read + simulated_write + simulated_uncached === promptTokens`. */
|
|
39
|
+
export interface SimCallResult {
|
|
40
|
+
readonly simulated_read: number;
|
|
41
|
+
readonly simulated_write: number;
|
|
42
|
+
readonly simulated_uncached: number;
|
|
43
|
+
/** True when the cacheable prefix was served from a warm virtual cache. */
|
|
44
|
+
readonly hit: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** The policy assumptions the sim ran under — PRINTED with every report so the
|
|
47
|
+
* simulation's basis is never hidden (spec §5.4 "assumptions printed"). */
|
|
48
|
+
export interface CacheSimAssumptions {
|
|
49
|
+
readonly provider: string;
|
|
50
|
+
readonly model: string;
|
|
51
|
+
readonly cacheSupported: boolean;
|
|
52
|
+
/** TTL used to expire virtual entries (seconds). */
|
|
53
|
+
readonly ttlSeconds: number;
|
|
54
|
+
/** false ⇒ `ttlSeconds` is the conservative fallback, not a published value. */
|
|
55
|
+
readonly ttlSourced: boolean;
|
|
56
|
+
readonly minCacheableTokens: number;
|
|
57
|
+
readonly refreshOnRead: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface CacheSimResult {
|
|
60
|
+
readonly perCall: readonly SimCallResult[];
|
|
61
|
+
readonly totalRead: number;
|
|
62
|
+
readonly totalWrite: number;
|
|
63
|
+
readonly totalUncached: number;
|
|
64
|
+
readonly hitCount: number;
|
|
65
|
+
readonly assumptions: CacheSimAssumptions;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Conservative TTL (seconds) for a provider that publishes none
|
|
69
|
+
* (`default_ttl_seconds: null`). Deliberately SHORT — the shortest real registry
|
|
70
|
+
* TTL (Anthropic 5m) — so the simulator never over-credits warmth for a provider
|
|
71
|
+
* whose true window is longer but undocumented (spec §5.4 honesty).
|
|
72
|
+
*/
|
|
73
|
+
export declare const FALLBACK_SIM_TTL_SECONDS = 300;
|
|
74
|
+
export interface CacheSimOptions {
|
|
75
|
+
/** Policy override — injected for tests; defaults to the live registry. */
|
|
76
|
+
readonly policy?: CachePolicy;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Simulate one candidate's cache trajectory over `calls`. Calls are processed in
|
|
80
|
+
* ascending timestamp order (a copy is sorted; the input is not mutated). The
|
|
81
|
+
* per-call results are returned in that same timestamp order.
|
|
82
|
+
*/
|
|
83
|
+
export declare function simulateCache(provider: ProviderId, model: string, calls: readonly SimCall[], opts?: CacheSimOptions): CacheSimResult;
|
|
84
|
+
/**
|
|
85
|
+
* Cache-ADJUSTED input cost of a simulated trajectory (spec §5.4): the read /
|
|
86
|
+
* write / uncached token buckets each priced at their own rate, with the
|
|
87
|
+
* fail-closed fallback to the input rate (dev-rule L5) when a cache rate is
|
|
88
|
+
* unverified. This is what the candidate WOULD pay at production cadence — the
|
|
89
|
+
* right-hand column of the bake-off's side-by-side.
|
|
90
|
+
*/
|
|
91
|
+
export declare function simulatedInputCostUsd(sim: CacheSimResult, rates: EffectiveRates): number;
|
|
92
|
+
/**
|
|
93
|
+
* Measured-COLD input cost (spec §5.4): every input token at the list input
|
|
94
|
+
* rate — what independent-turn replay actually pays. The left-hand column. This
|
|
95
|
+
* equals `simulatedInputCostUsd` exactly when the sim produced zero reads and
|
|
96
|
+
* zero writes (the regression pin — no cache activity, no divergence).
|
|
97
|
+
*/
|
|
98
|
+
export declare function coldInputCostUsd(sim: CacheSimResult, rates: EffectiveRates): number;
|
|
99
|
+
//# sourceMappingURL=cache-sim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-sim.d.ts","sourceRoot":"","sources":["../../src/bakeoff/cache-sim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;sEACsE;AACtE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;+EAC+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AAED;2EAC2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AASD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,IAAI,GAAE,eAAoB,GACzB,cAAc,CAkEhB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAIxF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAEnF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Virtual cache simulator — cache-aware-routing spec §5.4, [Briefing §26]. PURE:
|
|
4
|
+
* no store/clock/network. THE fix for the independent-turn evaluation fallacy:
|
|
5
|
+
* replay currently scores every sample COLD, so a bake-off report can never see
|
|
6
|
+
* that a candidate would run warm at production cadence. This walks a candidate's
|
|
7
|
+
* call sequence in timestamp order, maintaining a per-candidate VIRTUAL cache
|
|
8
|
+
* from the POLICY REGISTRY (TTL + min-cacheable + refresh-on-read), and emits a
|
|
9
|
+
* per-call {read, write, uncached} token split.
|
|
10
|
+
*
|
|
11
|
+
* ── SIMULATOR HONESTY (spec §5.4, §10 credibility risk) ───────────────────
|
|
12
|
+
* • State comes ONLY from the policy registry — never from a guessed model of
|
|
13
|
+
* how a provider caches. An undocumented TTL uses a CONSERVATIVE fallback
|
|
14
|
+
* (short), so the sim UNDER-credits rather than over-credits warmth.
|
|
15
|
+
* • The output is always LABELLED simulation. The odometer stays measured-only
|
|
16
|
+
* (this module never feeds it) — simulator output is never "verified savings".
|
|
17
|
+
* • Cache identity is the caller-supplied `prefixFingerprint`. A template
|
|
18
|
+
* mutation or a tool-order change is, by construction, a DIFFERENT
|
|
19
|
+
* fingerprint — so a mutation forces a miss/rewrite with no special-casing
|
|
20
|
+
* here (the synthetic tests exercise exactly that).
|
|
21
|
+
*
|
|
22
|
+
* A "switch-and-return" is modelled by a time GAP in this candidate's sequence:
|
|
23
|
+
* if the gap is within TTL the return is a read (still warm); if it exceeds TTL
|
|
24
|
+
* the cache has expired and the return rewrites. Nothing about the OTHER
|
|
25
|
+
* candidate is needed — each candidate is simulated over its own calls.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.FALLBACK_SIM_TTL_SECONDS = void 0;
|
|
29
|
+
exports.simulateCache = simulateCache;
|
|
30
|
+
exports.simulatedInputCostUsd = simulatedInputCostUsd;
|
|
31
|
+
exports.coldInputCostUsd = coldInputCostUsd;
|
|
32
|
+
const cache_policy_1 = require("../registry/cache-policy");
|
|
33
|
+
/**
|
|
34
|
+
* Conservative TTL (seconds) for a provider that publishes none
|
|
35
|
+
* (`default_ttl_seconds: null`). Deliberately SHORT — the shortest real registry
|
|
36
|
+
* TTL (Anthropic 5m) — so the simulator never over-credits warmth for a provider
|
|
37
|
+
* whose true window is longer but undocumented (spec §5.4 honesty).
|
|
38
|
+
*/
|
|
39
|
+
exports.FALLBACK_SIM_TTL_SECONDS = 300;
|
|
40
|
+
/**
|
|
41
|
+
* Simulate one candidate's cache trajectory over `calls`. Calls are processed in
|
|
42
|
+
* ascending timestamp order (a copy is sorted; the input is not mutated). The
|
|
43
|
+
* per-call results are returned in that same timestamp order.
|
|
44
|
+
*/
|
|
45
|
+
function simulateCache(provider, model, calls, opts = {}) {
|
|
46
|
+
const policy = opts.policy ?? (0, cache_policy_1.getCachePolicy)(provider);
|
|
47
|
+
const ttlSourced = policy.cache_supported && policy.default_ttl_seconds !== null;
|
|
48
|
+
const ttlSeconds = policy.cache_supported && policy.default_ttl_seconds !== null
|
|
49
|
+
? policy.default_ttl_seconds
|
|
50
|
+
: exports.FALLBACK_SIM_TTL_SECONDS;
|
|
51
|
+
const ttlMs = ttlSeconds * 1000;
|
|
52
|
+
const minCacheable = policy.min_cacheable_tokens ?? 0;
|
|
53
|
+
const assumptions = {
|
|
54
|
+
provider,
|
|
55
|
+
model,
|
|
56
|
+
cacheSupported: policy.cache_supported,
|
|
57
|
+
ttlSeconds,
|
|
58
|
+
ttlSourced,
|
|
59
|
+
minCacheableTokens: minCacheable,
|
|
60
|
+
refreshOnRead: policy.ttl_refresh_on_read,
|
|
61
|
+
};
|
|
62
|
+
const ordered = [...calls].sort((a, b) => a.tsMs - b.tsMs);
|
|
63
|
+
const cache = new Map();
|
|
64
|
+
const perCall = [];
|
|
65
|
+
let totalRead = 0;
|
|
66
|
+
let totalWrite = 0;
|
|
67
|
+
let totalUncached = 0;
|
|
68
|
+
let hitCount = 0;
|
|
69
|
+
for (const call of ordered) {
|
|
70
|
+
const prompt = Math.max(call.promptTokens, 0);
|
|
71
|
+
const prefix = Math.min(Math.max(call.prefixTokens, 0), prompt);
|
|
72
|
+
// Provider does not cache on our path, or the prefix is below the minimum
|
|
73
|
+
// cacheable size: caching does not engage — the whole prompt is uncached and
|
|
74
|
+
// NO virtual state is written (spec §5.4: min-cacheable gates the write).
|
|
75
|
+
if (!policy.cache_supported || prefix < minCacheable || prefix === 0) {
|
|
76
|
+
perCall.push({ simulated_read: 0, simulated_write: 0, simulated_uncached: prompt, hit: false });
|
|
77
|
+
totalUncached += prompt;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const entry = cache.get(call.prefixFingerprint);
|
|
81
|
+
const warm = entry !== undefined && call.tsMs < entry.expiresAtMs;
|
|
82
|
+
if (warm) {
|
|
83
|
+
// HIT: read the cached prefix; the volatile tail is uncached.
|
|
84
|
+
const read = Math.min(prefix, entry.tokens);
|
|
85
|
+
const uncached = prompt - read;
|
|
86
|
+
perCall.push({ simulated_read: read, simulated_write: 0, simulated_uncached: uncached, hit: true });
|
|
87
|
+
totalRead += read;
|
|
88
|
+
totalUncached += uncached;
|
|
89
|
+
hitCount += 1;
|
|
90
|
+
// TTL base: a read refreshes the window only when the policy says so;
|
|
91
|
+
// otherwise the entry keeps expiring from its WRITE time.
|
|
92
|
+
if (policy.ttl_refresh_on_read)
|
|
93
|
+
entry.expiresAtMs = call.tsMs + ttlMs;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// MISS (cold or expired): WRITE the prefix into cache; the tail is uncached.
|
|
97
|
+
const uncached = prompt - prefix;
|
|
98
|
+
perCall.push({ simulated_read: 0, simulated_write: prefix, simulated_uncached: uncached, hit: false });
|
|
99
|
+
totalWrite += prefix;
|
|
100
|
+
totalUncached += uncached;
|
|
101
|
+
cache.set(call.prefixFingerprint, { tokens: prefix, expiresAtMs: call.tsMs + ttlMs });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { perCall, totalRead, totalWrite, totalUncached, hitCount, assumptions };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Cache-ADJUSTED input cost of a simulated trajectory (spec §5.4): the read /
|
|
108
|
+
* write / uncached token buckets each priced at their own rate, with the
|
|
109
|
+
* fail-closed fallback to the input rate (dev-rule L5) when a cache rate is
|
|
110
|
+
* unverified. This is what the candidate WOULD pay at production cadence — the
|
|
111
|
+
* right-hand column of the bake-off's side-by-side.
|
|
112
|
+
*/
|
|
113
|
+
function simulatedInputCostUsd(sim, rates) {
|
|
114
|
+
const readRate = rates.cacheRead ?? rates.input;
|
|
115
|
+
const writeRate = rates.cacheWrite ?? rates.input;
|
|
116
|
+
return sim.totalRead * readRate + sim.totalWrite * writeRate + sim.totalUncached * rates.input;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Measured-COLD input cost (spec §5.4): every input token at the list input
|
|
120
|
+
* rate — what independent-turn replay actually pays. The left-hand column. This
|
|
121
|
+
* equals `simulatedInputCostUsd` exactly when the sim produced zero reads and
|
|
122
|
+
* zero writes (the regression pin — no cache activity, no divergence).
|
|
123
|
+
*/
|
|
124
|
+
function coldInputCostUsd(sim, rates) {
|
|
125
|
+
return (sim.totalRead + sim.totalWrite + sim.totalUncached) * rates.input;
|
|
126
|
+
}
|