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,510 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Async batch exporter: SQLite telemetry → wherever this rig's telemetry goes.
|
|
4
|
+
* Spec §5 (cloud sink) + §6 criteria 8–9. Fire-and-forget from the run loop:
|
|
5
|
+
* the inference path NEVER blocks on, or fails because of, this sink.
|
|
6
|
+
* At-least-once delivery with idempotent ids; unexported rows queue locally in
|
|
7
|
+
* SQLite (the inferences table itself, behind a rowid watermark) and flush on
|
|
8
|
+
* recovery; watermark bookkeeping per source instance.
|
|
9
|
+
*
|
|
10
|
+
* ── ONE LOOP, TWO TRANSPORTS (Phase 6 WS2) ───────────────────────────────
|
|
11
|
+
* Everything below is transport-agnostic. Batching, watermarks, backoff,
|
|
12
|
+
* once-per-process mirrors — all of it runs identically whether the batch
|
|
13
|
+
* leaves as a service-role PostgREST write (self-host) or as a `rig_sk_`-
|
|
14
|
+
* authenticated POST to api.modelrig.ai (hosted). That is not a tidiness
|
|
15
|
+
* preference: telemetry-ingestion-boundary.md §4 makes it a requirement, so
|
|
16
|
+
* that moving between self-host and hosted is a config change rather than a
|
|
17
|
+
* data-model change. `sinks.ts` holds the two transports; `ingest-protocol.ts`
|
|
18
|
+
* holds the one mapper both of them write rows through.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.DEFAULT_INTERVAL_MS = void 0;
|
|
22
|
+
exports.buildExportedRouteMirrors = buildExportedRouteMirrors;
|
|
23
|
+
exports.createTelemetryExporter = createTelemetryExporter;
|
|
24
|
+
exports.buildLoadErrorBatch = buildLoadErrorBatch;
|
|
25
|
+
const resolved_bundle_1 = require("../routes/resolved-bundle");
|
|
26
|
+
const ingest_protocol_1 = require("./ingest-protocol");
|
|
27
|
+
const sinks_1 = require("./sinks");
|
|
28
|
+
/**
|
|
29
|
+
* Build the `routes` mirror payload from loaded bundles (Phase D, spec §13).
|
|
30
|
+
* Each `b` is ALREADY file-resolved — the loader inlined the schema + prompt
|
|
31
|
+
* files at createRig() and FAILED THERE (teaching RouteConfigError) if one was
|
|
32
|
+
* missing, so nothing unresolved reaches here and the gateway never needs the
|
|
33
|
+
* customer's filesystem (§13.1 rule 6). Lives here rather than in index.ts to
|
|
34
|
+
* keep that file under the LOC cap.
|
|
35
|
+
*/
|
|
36
|
+
function buildExportedRouteMirrors(bundles) {
|
|
37
|
+
return bundles.map((b) => {
|
|
38
|
+
const bundle = (0, resolved_bundle_1.resolveRouteBundle)(b);
|
|
39
|
+
return {
|
|
40
|
+
route: b.route,
|
|
41
|
+
version: b.version,
|
|
42
|
+
candidates: b.candidates.map((c) => ({ provider: c.provider, model: c.model })),
|
|
43
|
+
capture: b.capture,
|
|
44
|
+
bundle,
|
|
45
|
+
bundleSha: (0, resolved_bundle_1.hashResolvedBundle)(bundle),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_BATCH_SIZE = 200;
|
|
50
|
+
/** The flush cadence. Exported so the settings resolver (§1.5) can beat on the
|
|
51
|
+
* SAME interval — "refreshes on the exporter interval" without a second knob. */
|
|
52
|
+
exports.DEFAULT_INTERVAL_MS = 10000;
|
|
53
|
+
/** After a sink failure, skip this many intervals, doubling per consecutive
|
|
54
|
+
* failure (simple exponential backoff, capped). */
|
|
55
|
+
const BACKOFF_MAX_SKIPS = 32;
|
|
56
|
+
/**
|
|
57
|
+
* Consecutive DETERMINISTIC failures on the SAME stuck batch before the
|
|
58
|
+
* exporter gives up and skips past it (E2 hardening Phase 2, spec §2.3.3).
|
|
59
|
+
* Five failures with the global exponential backoff between them is a
|
|
60
|
+
* multi-minute self-heal window — long enough for a still-deploying migration
|
|
61
|
+
* that adds a missing column to land, short enough to bound the wedge. A
|
|
62
|
+
* TRANSIENT failure never counts toward this, so an outage of any length can
|
|
63
|
+
* never trip it.
|
|
64
|
+
*/
|
|
65
|
+
const POISON_SKIP_THRESHOLD = 5;
|
|
66
|
+
function toWireInference(row) {
|
|
67
|
+
return {
|
|
68
|
+
id: row.id,
|
|
69
|
+
ts: row.ts,
|
|
70
|
+
route: row.route,
|
|
71
|
+
route_version: row.routeVersion,
|
|
72
|
+
provider: row.provider,
|
|
73
|
+
model: row.model,
|
|
74
|
+
requested_tier: row.requestedTier,
|
|
75
|
+
served_tier: row.servedTier,
|
|
76
|
+
tokens_in: row.tokensIn,
|
|
77
|
+
tokens_out: row.tokensOut,
|
|
78
|
+
tokens_cached: row.tokensCached,
|
|
79
|
+
cost_estimate_usd: row.costEstimateUsd,
|
|
80
|
+
latency_ms: row.latencyMs,
|
|
81
|
+
ttfb_ms: row.ttfbMs,
|
|
82
|
+
failure_class: row.failureClass,
|
|
83
|
+
attempts: JSON.parse(row.attemptsJson),
|
|
84
|
+
tags: JSON.parse(row.tagsJson),
|
|
85
|
+
fingerprint_cell: row.fingerprintCell,
|
|
86
|
+
served_variant: row.servedVariant,
|
|
87
|
+
repaired_by: row.repairedBy,
|
|
88
|
+
pricing_missing: row.pricingMissing,
|
|
89
|
+
// Cache accounting (WS-A) — always emitted by a current SDK. Requires the
|
|
90
|
+
// `inferences` cache columns (migration 00049) to exist server-side; apply
|
|
91
|
+
// 00049 BEFORE deploying this build (migrate-first), else the insert 4xx's
|
|
92
|
+
// and the exporter's poison-skip drops the batch after its self-heal window.
|
|
93
|
+
tokens_cache_write: row.tokensCacheWrite ?? 0,
|
|
94
|
+
tokens_cache_write_1h: row.tokensCacheWrite1h ?? 0,
|
|
95
|
+
cache_key: row.cacheKey ?? null,
|
|
96
|
+
prefix_fingerprint: row.prefixFingerprint ?? null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function toWireRun(row) {
|
|
100
|
+
return {
|
|
101
|
+
id: row.id,
|
|
102
|
+
pipeline: row.pipeline,
|
|
103
|
+
pipeline_version: row.pipelineVersion,
|
|
104
|
+
environment: row.environment,
|
|
105
|
+
episode_key: row.episodeKey,
|
|
106
|
+
started_at: row.startedAt,
|
|
107
|
+
ended_at: row.endedAt,
|
|
108
|
+
status: row.status,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function toWireStep(row) {
|
|
112
|
+
return {
|
|
113
|
+
run_id: row.runId,
|
|
114
|
+
step_key: row.stepKey,
|
|
115
|
+
attempt_no: row.attemptNo,
|
|
116
|
+
task: row.task,
|
|
117
|
+
provenance: row.provenance,
|
|
118
|
+
inference_id: row.inferenceId,
|
|
119
|
+
span_trace_id: row.spanTraceId,
|
|
120
|
+
span_id: row.spanId,
|
|
121
|
+
started_at: row.startedAt,
|
|
122
|
+
ended_at: row.endedAt,
|
|
123
|
+
failure_class: row.failureClass,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function toWireArtifact(row) {
|
|
127
|
+
return {
|
|
128
|
+
id: row.id,
|
|
129
|
+
run_id: row.runId,
|
|
130
|
+
step_key: row.stepKey,
|
|
131
|
+
attempt_no: row.attemptNo,
|
|
132
|
+
task: row.task,
|
|
133
|
+
name: row.name,
|
|
134
|
+
type: row.type,
|
|
135
|
+
version: row.version,
|
|
136
|
+
supersedes_artifact_id: row.supersedesArtifactId,
|
|
137
|
+
declared_schema: row.declaredSchema ?? null,
|
|
138
|
+
fingerprint_cell: row.fingerprintCell,
|
|
139
|
+
content_ref: row.contentRef,
|
|
140
|
+
content_hash: row.contentHash,
|
|
141
|
+
content_bytes: row.contentBytes,
|
|
142
|
+
data_classification: row.dataClassification,
|
|
143
|
+
expires_at: row.expiresAt,
|
|
144
|
+
// Per-row scrub findings (E4 chunk 0) — spread-iff-present so a null/absent
|
|
145
|
+
// scrub never widens the batch beyond what older servers accept.
|
|
146
|
+
...(row.scrub !== undefined && row.scrub !== null ? { scrub: row.scrub } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function toWireEdge(row) {
|
|
150
|
+
return { from_artifact_id: row.fromArtifactId, to_artifact_id: row.toArtifactId, kind: row.kind };
|
|
151
|
+
}
|
|
152
|
+
function toWireEvalCase(row) {
|
|
153
|
+
return {
|
|
154
|
+
id: row.id,
|
|
155
|
+
task: row.task,
|
|
156
|
+
source_artifact_id: row.sourceArtifactId,
|
|
157
|
+
corrected_artifact_id: row.correctedArtifactId,
|
|
158
|
+
origin: row.origin,
|
|
159
|
+
note: row.note,
|
|
160
|
+
created_by: row.createdBy,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function toWireEvaluation(row) {
|
|
164
|
+
return {
|
|
165
|
+
id: row.id,
|
|
166
|
+
artifact_id: row.artifactId,
|
|
167
|
+
run_id: row.runId,
|
|
168
|
+
evaluator: row.evaluator,
|
|
169
|
+
evaluator_kind: row.evaluatorKind,
|
|
170
|
+
score: row.score,
|
|
171
|
+
passed: row.passed,
|
|
172
|
+
label: row.label,
|
|
173
|
+
explanation: row.explanation,
|
|
174
|
+
created_by: row.createdBy,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function toWireFeedback(row) {
|
|
178
|
+
return {
|
|
179
|
+
id: row.id,
|
|
180
|
+
ts: row.ts,
|
|
181
|
+
inference_id: row.inferenceId,
|
|
182
|
+
episode_key: row.episodeKey,
|
|
183
|
+
verdict: row.verdict,
|
|
184
|
+
score: row.score,
|
|
185
|
+
note: row.note,
|
|
186
|
+
source: row.source,
|
|
187
|
+
created_by: row.createdBy,
|
|
188
|
+
// Present only when the verdict names its own org. Omitted (not null) so
|
|
189
|
+
// that `stampBatch` can fall back to the exporter's tenant without having
|
|
190
|
+
// to distinguish "no org" from "explicitly no org".
|
|
191
|
+
...(row.orgId !== null ? { org_id: row.orgId } : {}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function createTelemetryExporter(store, config) {
|
|
195
|
+
const batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
196
|
+
const intervalMs = config.intervalMs ?? exports.DEFAULT_INTERVAL_MS;
|
|
197
|
+
const { sink, sourceId } = config;
|
|
198
|
+
// The wire version this exporter announces (spec §1.3). v2 iff the artifact
|
|
199
|
+
// namespace is active; otherwise v1, byte-identical to before v2 existed.
|
|
200
|
+
const wireVersion = config.artifacts === true ? ingest_protocol_1.INGEST_PROTOCOL_VERSION : ingest_protocol_1.INGEST_PROTOCOL_VERSION_V1;
|
|
201
|
+
let timer = null;
|
|
202
|
+
let flushing = false;
|
|
203
|
+
let skipsRemaining = 0;
|
|
204
|
+
let consecutiveFailures = 0;
|
|
205
|
+
let routesMirrored = false;
|
|
206
|
+
let manifestMirrored = false;
|
|
207
|
+
// Poison-pill state (E2 hardening Phase 2). Per stream key, the deterministic
|
|
208
|
+
// failure count for the CURRENTLY-STUCK batch — which is identified by its
|
|
209
|
+
// watermark, since the read is always "rows after W, limit N", so an
|
|
210
|
+
// unadvanced watermark IS the same batch. Reset the instant the watermark
|
|
211
|
+
// moves (a success OR a skip). `poisonSkipped` is the cumulative, per-stream
|
|
212
|
+
// count of rows dropped this way — the never-silent surface `stats()` reads.
|
|
213
|
+
const poisonState = new Map();
|
|
214
|
+
const poisonSkipped = {};
|
|
215
|
+
/**
|
|
216
|
+
* The once-per-process mirrors, folded into whichever batch goes out first.
|
|
217
|
+
*
|
|
218
|
+
* They ride along rather than travelling alone because in api mode a batch
|
|
219
|
+
* is an HTTP request, and three requests where one would do is three times
|
|
220
|
+
* the chance of a partial flush.
|
|
221
|
+
*/
|
|
222
|
+
function pendingHeader() {
|
|
223
|
+
const header = {};
|
|
224
|
+
if (!routesMirrored && config.routes !== undefined && config.routes.length > 0) {
|
|
225
|
+
header.routes = config.routes.map((r) => ({
|
|
226
|
+
route: r.route,
|
|
227
|
+
version: r.version,
|
|
228
|
+
candidates: r.candidates,
|
|
229
|
+
capture: r.capture ?? false,
|
|
230
|
+
// Phase D: carry the resolved bundle + hash when the caller built them
|
|
231
|
+
// (index.ts always does; a caller that doesn't stays metadata-only).
|
|
232
|
+
...(r.bundle !== undefined ? { bundle: r.bundle, bundle_sha: r.bundleSha } : {}),
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
if (!manifestMirrored && config.manifest !== undefined && config.manifest !== null) {
|
|
236
|
+
header.manifest = {
|
|
237
|
+
name: config.manifest.name,
|
|
238
|
+
dimensions: config.manifest.dimensions,
|
|
239
|
+
contribution: config.manifest.contribution,
|
|
240
|
+
// F1: names only — route, provider/model, and the env var to set.
|
|
241
|
+
serveability: (config.serveability ?? []).map((w) => ({
|
|
242
|
+
route: w.route,
|
|
243
|
+
inert: w.inert.map((c) => ({ provider: c.provider, model: c.model })),
|
|
244
|
+
serveable_count: w.serveableCount,
|
|
245
|
+
fix_hint: w.fixHint,
|
|
246
|
+
})),
|
|
247
|
+
// Follow-up #2: the describeConfigFailure shape, or [] on a clean load
|
|
248
|
+
// (which clears a stale failure from a prior broken deploy).
|
|
249
|
+
load_errors: [...(config.loadErrors ?? [])],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return header;
|
|
253
|
+
}
|
|
254
|
+
/** Mark the mirrors delivered — called ONLY after a confirmed send, so a
|
|
255
|
+
* failed first batch re-sends them rather than silently losing them. */
|
|
256
|
+
function markHeaderDelivered(header) {
|
|
257
|
+
if (header.routes !== undefined)
|
|
258
|
+
routesMirrored = true;
|
|
259
|
+
if (header.manifest !== undefined)
|
|
260
|
+
manifestMirrored = true;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Send one batch for a stream, applying the poison-pill state machine
|
|
264
|
+
* (spec §2.3.3). Returns:
|
|
265
|
+
* "sent" the send succeeded — the caller advances the watermark and marks
|
|
266
|
+
* the header delivered.
|
|
267
|
+
* "skip" a deterministic poison was skipped after K failures — the caller
|
|
268
|
+
* advances the watermark PAST this batch without it ever landing
|
|
269
|
+
* (the rows are counted + logged, never retried).
|
|
270
|
+
* THROWS the underlying error when the caller must abort this flush and let
|
|
271
|
+
* the global backoff apply: a transient failure (retry forever, never counted)
|
|
272
|
+
* OR a deterministic one still inside its self-heal window (failCount < K).
|
|
273
|
+
*
|
|
274
|
+
* A poison in one stream never blocks another: the skip breaks only its own
|
|
275
|
+
* stuck batch, and the caller then continues to the next batch and the next
|
|
276
|
+
* stream (spec §2.3.3).
|
|
277
|
+
*/
|
|
278
|
+
async function sendOrPoison(stream, watermark, rowCount, batch) {
|
|
279
|
+
try {
|
|
280
|
+
await sink.send(batch);
|
|
281
|
+
// A success advances the watermark → this stream is unstuck; drop its
|
|
282
|
+
// failure record so a later, unrelated batch starts fresh.
|
|
283
|
+
poisonState.delete(stream);
|
|
284
|
+
return "sent";
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
// Anything that is not a classified SinkSendError is treated as TRANSIENT
|
|
288
|
+
// — we never poison-skip (drop data) on an error we cannot reason about.
|
|
289
|
+
const retriable = err instanceof sinks_1.SinkSendError ? err.retriable : true;
|
|
290
|
+
if (retriable) {
|
|
291
|
+
// Transient: do NOT touch failCount. Rethrow so the existing global
|
|
292
|
+
// backoff owns it and the stream retries forever (at-least-once).
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
// Deterministic. Count failures against THIS stuck batch (keyed by W); a
|
|
296
|
+
// watermark that no longer matches means a different batch, so reset.
|
|
297
|
+
const prior = poisonState.get(stream);
|
|
298
|
+
const failCount = (prior !== undefined && prior.watermark === watermark ? prior.failCount : 0) + 1;
|
|
299
|
+
if (failCount < POISON_SKIP_THRESHOLD) {
|
|
300
|
+
// Self-heal window: rethrow (retry next flush, with backoff). A
|
|
301
|
+
// still-deploying migration that adds the missing column lands here.
|
|
302
|
+
poisonState.set(stream, { watermark, failCount });
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
// POISON. Advance past this batch WITHOUT sending, count it, log LOUDLY,
|
|
306
|
+
// reset — the stream (and every later stream) keeps flowing.
|
|
307
|
+
poisonState.delete(stream);
|
|
308
|
+
poisonSkipped[stream] = (poisonSkipped[stream] ?? 0) + rowCount;
|
|
309
|
+
const status = err instanceof sinks_1.SinkSendError ? err.status : undefined;
|
|
310
|
+
console.warn(`[modelrig exporter] POISON-SKIP: stream "${stream}" batch at watermark ${watermark} ` +
|
|
311
|
+
`(${rowCount} row(s), status ${status ?? "network"}) was rejected deterministically ` +
|
|
312
|
+
`${POISON_SKIP_THRESHOLD}× running — advancing the watermark PAST it WITHOUT writing. ` +
|
|
313
|
+
`These rows are dropped and will NOT be retried; investigate the rejection and backfill.`);
|
|
314
|
+
return "skip";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function flushOnce() {
|
|
318
|
+
let header = pendingHeader();
|
|
319
|
+
/**
|
|
320
|
+
* Drain one watermarked stream to exhaustion, batch by batch, through the
|
|
321
|
+
* poison-pill state machine. Every stream (inferences, feedback, runs,
|
|
322
|
+
* steps, artifacts, edges, evals) is the SAME at-least-once dance behind its
|
|
323
|
+
* own export_state key — factored here so the watermark/header/poison
|
|
324
|
+
* bookkeeping lives in exactly one place and no stream can drift from
|
|
325
|
+
* another. A "skip" advances the watermark past a poison batch WITHOUT
|
|
326
|
+
* having sent it; a "sent" advances it and delivers the once-per-flush
|
|
327
|
+
* header. Reassigning the captured `header` is intentional: the header rides
|
|
328
|
+
* the first CONFIRMED send, so a skipped (unsent) batch leaves it for the
|
|
329
|
+
* next one.
|
|
330
|
+
*/
|
|
331
|
+
async function drainStream(streamKey, read, toSection) {
|
|
332
|
+
for (;;) {
|
|
333
|
+
const watermark = store.getExportWatermark(streamKey);
|
|
334
|
+
const pending = read(watermark, batchSize);
|
|
335
|
+
if (pending.length === 0)
|
|
336
|
+
break;
|
|
337
|
+
const outcome = await sendOrPoison(streamKey, watermark, pending.length, {
|
|
338
|
+
v: wireVersion,
|
|
339
|
+
source: sourceId,
|
|
340
|
+
...header,
|
|
341
|
+
...toSection(pending.map((p) => p.row)),
|
|
342
|
+
});
|
|
343
|
+
if (outcome === "sent") {
|
|
344
|
+
markHeaderDelivered(header);
|
|
345
|
+
header = {};
|
|
346
|
+
}
|
|
347
|
+
// Advance past this batch on BOTH a send and a poison-skip — a skip's
|
|
348
|
+
// whole purpose is to move the watermark past the batch that will never
|
|
349
|
+
// land, so the stream keeps flowing (spec §2.3.3).
|
|
350
|
+
store.setExportWatermark(streamKey, pending[pending.length - 1].rowid);
|
|
351
|
+
if (pending.length < batchSize)
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Inferences past the watermark. The watermark advances after a confirmed
|
|
356
|
+
// send (at-least-once; the sink's upsert makes the duplicate harmless) OR a
|
|
357
|
+
// poison-skip (the batch will never land). Inferences also carry watermarkAt.
|
|
358
|
+
await drainStream(sourceId, (w, n) => store.readInferencesAfter(w, n), (rows) => ({
|
|
359
|
+
inferences: rows.map((row) => toWireInference(row)),
|
|
360
|
+
watermarkAt: new Date().toISOString(),
|
|
361
|
+
}));
|
|
362
|
+
// Feedback, runs, steps, artifacts, edges, evals: the same dance, each
|
|
363
|
+
// behind its own export_state key so no stream ever blocks another — a
|
|
364
|
+
// poison in one is skipped past without touching the rest.
|
|
365
|
+
await drainStream(`${sourceId}:feedback`, (w, n) => store.readFeedbackAfter(w, n), (rows) => ({
|
|
366
|
+
feedback: rows.map((row) => toWireFeedback(row)),
|
|
367
|
+
}));
|
|
368
|
+
// Execution graph (Expansion E2 chunk A) — ONLY when the artifact namespace
|
|
369
|
+
// is active. When artifacts are off these streams never drain and no
|
|
370
|
+
// runs/steps section is ever built (byte-identical to before v2 existed).
|
|
371
|
+
if (config.artifacts === true) {
|
|
372
|
+
await drainStream(`${sourceId}:runs`, (w, n) => store.readRunsAfter(w, n), (rows) => ({
|
|
373
|
+
runs: rows.map((row) => toWireRun(row)),
|
|
374
|
+
}));
|
|
375
|
+
await drainStream(`${sourceId}:steps`, (w, n) => store.readStepsAfter(w, n), (rows) => ({
|
|
376
|
+
steps: rows.map((row) => toWireStep(row)),
|
|
377
|
+
}));
|
|
378
|
+
// Artifact metadata (chunk B): rows live in the SEPARATE artifact store;
|
|
379
|
+
// watermarks live in the main store's export_state, keyed `:artifacts` etc.
|
|
380
|
+
const artStore = config.artifactStore;
|
|
381
|
+
if (artStore !== undefined) {
|
|
382
|
+
await drainStream(`${sourceId}:artifacts`, (w, n) => artStore.readArtifactsAfter(w, n), (rows) => ({
|
|
383
|
+
artifacts: rows.map((row) => toWireArtifact(row)),
|
|
384
|
+
}));
|
|
385
|
+
await drainStream(`${sourceId}:edges`, (w, n) => artStore.readEdgesAfter(w, n), (rows) => ({
|
|
386
|
+
artifact_edges: rows.map((row) => toWireEdge(row)),
|
|
387
|
+
}));
|
|
388
|
+
await drainStream(`${sourceId}:evals`, (w, n) => artStore.readEvaluationsAfter(w, n), (rows) => ({
|
|
389
|
+
artifact_evaluations: rows.map((row) => toWireEvaluation(row)),
|
|
390
|
+
}));
|
|
391
|
+
// Promoted eval cases (E4 chunk A) — same at-least-once dance behind its
|
|
392
|
+
// own watermark key.
|
|
393
|
+
await drainStream(`${sourceId}:eval_cases`, (w, n) => artStore.readEvalCasesAfter(w, n), (rows) => ({
|
|
394
|
+
eval_cases: rows.map((row) => toWireEvalCase(row)),
|
|
395
|
+
}));
|
|
396
|
+
// Post-flush hook (E4 chunk 0, flag 2): the artifacts stream is now
|
|
397
|
+
// confirmed exported (every drainStream above returned without throwing),
|
|
398
|
+
// so any content upload that 404'd racing its own metadata can re-attempt
|
|
399
|
+
// — the row is durable. Reached ONLY on a clean artifact flush; a poison
|
|
400
|
+
// rethrow above skips it, so we never retry content while metadata export
|
|
401
|
+
// is failing. Never throws (its rejection would be swallowed anyway).
|
|
402
|
+
if (config.onArtifactsFlushed !== undefined) {
|
|
403
|
+
await config.onArtifactsFlushed();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Envelopes: small table, full upsert each flush (no watermark). This send
|
|
408
|
+
// also carries any header the streams above never got the chance to — a rig
|
|
409
|
+
// that has routes but has not run yet must still appear in the console.
|
|
410
|
+
// A deterministic failure here poison-skips too (keyed by a synthetic 0
|
|
411
|
+
// watermark, since there is no cursor to advance): after K it is counted +
|
|
412
|
+
// logged and this flush moves on, rather than wedging the header forever.
|
|
413
|
+
const envelopes = store.listEnvelopes();
|
|
414
|
+
if (envelopes.length > 0 || Object.keys(header).length > 0) {
|
|
415
|
+
const outcome = await sendOrPoison(`${sourceId}:envelopes`, 0, Math.max(envelopes.length, 1), {
|
|
416
|
+
v: wireVersion,
|
|
417
|
+
source: sourceId,
|
|
418
|
+
...header,
|
|
419
|
+
...(envelopes.length > 0
|
|
420
|
+
? {
|
|
421
|
+
envelopes: envelopes.map((e) => ({
|
|
422
|
+
envelope_key: e.envelopeKey,
|
|
423
|
+
budget_usd: e.budgetUsd,
|
|
424
|
+
spent_usd: e.spentUsd,
|
|
425
|
+
status: e.status,
|
|
426
|
+
})),
|
|
427
|
+
}
|
|
428
|
+
: {}),
|
|
429
|
+
});
|
|
430
|
+
if (outcome === "sent")
|
|
431
|
+
markHeaderDelivered(header);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function guardedFlush() {
|
|
435
|
+
if (flushing)
|
|
436
|
+
return;
|
|
437
|
+
flushing = true;
|
|
438
|
+
try {
|
|
439
|
+
await flushOnce();
|
|
440
|
+
consecutiveFailures = 0;
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
// Swallow-and-log, ALWAYS — sink failures must never reach the run loop.
|
|
444
|
+
consecutiveFailures += 1;
|
|
445
|
+
skipsRemaining = Math.min(2 ** consecutiveFailures, BACKOFF_MAX_SKIPS);
|
|
446
|
+
console.warn(`[modelrig exporter] ${sink.mode} sink flush failed (attempt ${consecutiveFailures}, ` +
|
|
447
|
+
`backing off ${skipsRemaining} intervals): ` +
|
|
448
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
flushing = false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
mode: sink.mode,
|
|
456
|
+
start() {
|
|
457
|
+
if (timer !== null)
|
|
458
|
+
return;
|
|
459
|
+
timer = setInterval(() => {
|
|
460
|
+
if (skipsRemaining > 0) {
|
|
461
|
+
skipsRemaining -= 1;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
void guardedFlush();
|
|
465
|
+
}, intervalMs);
|
|
466
|
+
// Never keep the process alive for telemetry export.
|
|
467
|
+
timer.unref?.();
|
|
468
|
+
},
|
|
469
|
+
async flush() {
|
|
470
|
+
await guardedFlush();
|
|
471
|
+
},
|
|
472
|
+
async stop() {
|
|
473
|
+
if (timer !== null) {
|
|
474
|
+
clearInterval(timer);
|
|
475
|
+
timer = null;
|
|
476
|
+
}
|
|
477
|
+
await guardedFlush();
|
|
478
|
+
},
|
|
479
|
+
stats() {
|
|
480
|
+
// A COPY, so a reader can never mutate the exporter's live counters.
|
|
481
|
+
return { poisonSkipped: { ...poisonSkipped } };
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* The one-shot manifest batch that carries a load-time config failure to the
|
|
487
|
+
* console (follow-up #2), for the case `createRig` throws BEFORE the exporter
|
|
488
|
+
* loop exists (a route bundle that fails to load never reaches the loop).
|
|
489
|
+
*
|
|
490
|
+
* It is a manifest-only batch: no inferences, no watermark. The manifest carries
|
|
491
|
+
* whatever `rig.yaml` could still provide (name/dimensions/contribution — the
|
|
492
|
+
* routes failing to load does not stop rig.yaml loading) plus the load errors;
|
|
493
|
+
* `serveability` is `[]` because it cannot be computed without loaded routes.
|
|
494
|
+
* A caller sends it fire-and-forget through the same sink the run loop uses.
|
|
495
|
+
*/
|
|
496
|
+
function buildLoadErrorBatch(sourceId, manifest, loadErrors) {
|
|
497
|
+
return {
|
|
498
|
+
// A manifest-only batch carries no artifact sections — announce v1 so it is
|
|
499
|
+
// accepted by any server vintage, even one predating protocol v2.
|
|
500
|
+
v: ingest_protocol_1.INGEST_PROTOCOL_VERSION_V1,
|
|
501
|
+
source: sourceId,
|
|
502
|
+
manifest: {
|
|
503
|
+
name: manifest?.name ?? sourceId,
|
|
504
|
+
dimensions: manifest?.dimensions ?? [],
|
|
505
|
+
contribution: manifest?.contribution ?? false,
|
|
506
|
+
serveability: [],
|
|
507
|
+
load_errors: [...loadErrors],
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observed-health aggregation — G-4 (observed-health/spec.md §3.1, §4.1).
|
|
3
|
+
*
|
|
4
|
+
* One home for the per-endpoint latency/TTFB percentile + error-rate + min-n
|
|
5
|
+
* math, shared by the live store reader (store.ts `readEndpointHealth`) and the
|
|
6
|
+
* observed-layer rollup (registry/build-observed.ts). Keeping the math here —
|
|
7
|
+
* not in run.ts (at the 700-LOC cap) — is deliberate.
|
|
8
|
+
*
|
|
9
|
+
* THE RIGINDEX WALL (spec §4.4, load-bearing): this module feeds routing +
|
|
10
|
+
* console surfaces ONLY. It must NEVER be imported by registry/build-leaderboard.ts
|
|
11
|
+
* or the matrix/Bradley-Terry path — production telemetry never produces rank
|
|
12
|
+
* (eval-methodology-and-rigindex.md:19). The import-guard test
|
|
13
|
+
* (health-rigindex-wall.test.ts) is the tripwire; do not weaken it.
|
|
14
|
+
*/
|
|
15
|
+
import type { ProviderId } from "../types/provider";
|
|
16
|
+
/** Spec §4.1 example: below 20 samples, percentiles/error-rate surface as null. */
|
|
17
|
+
export declare const DEFAULT_HEALTH_MIN_N = 20;
|
|
18
|
+
export interface HealthPercentiles {
|
|
19
|
+
readonly p50: number;
|
|
20
|
+
readonly p90: number;
|
|
21
|
+
readonly p99: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Per-endpoint observed health over a rolling window. Percentiles + errorRate
|
|
25
|
+
* are `null` under the min-n gate (thin data must not read as a confident zero);
|
|
26
|
+
* `sampleN` is always reported so the surface can say "n too low", not "healthy".
|
|
27
|
+
*/
|
|
28
|
+
export interface EndpointHealth {
|
|
29
|
+
readonly provider: ProviderId;
|
|
30
|
+
readonly model: string;
|
|
31
|
+
readonly windowStart: string;
|
|
32
|
+
readonly sampleN: number;
|
|
33
|
+
readonly latencyMs: HealthPercentiles | null;
|
|
34
|
+
readonly ttfbMs: HealthPercentiles | null;
|
|
35
|
+
readonly errorRate: number | null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The minimal row shape the aggregation needs. Both the local SQLite store and
|
|
39
|
+
* the hosted rollup can project their rows onto this, so the math is identical
|
|
40
|
+
* on both lanes (spec §3.1).
|
|
41
|
+
*/
|
|
42
|
+
export interface HealthInputRow {
|
|
43
|
+
readonly provider: string;
|
|
44
|
+
readonly model: string;
|
|
45
|
+
readonly ts: string;
|
|
46
|
+
readonly latency_ms: number;
|
|
47
|
+
readonly ttfb_ms: number | null;
|
|
48
|
+
readonly failure_class: string | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Nearest-rank percentile over a pre-sorted ascending array. Matches the
|
|
52
|
+
* convention already used by build-observed.ts so the two lanes agree.
|
|
53
|
+
*/
|
|
54
|
+
export declare function percentile(sorted: readonly number[], p: number): number | null;
|
|
55
|
+
/**
|
|
56
|
+
* p50/p90/p99 over a set of values, dropping non-positive samples (a zero or
|
|
57
|
+
* negative latency/TTFB is a non-measurement, not a fast call). `null` when no
|
|
58
|
+
* positive sample remains.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computePercentiles(values: readonly number[]): HealthPercentiles | null;
|
|
61
|
+
export interface HealthWindowOptions {
|
|
62
|
+
readonly windowMinutes: number;
|
|
63
|
+
readonly minN: number;
|
|
64
|
+
/** Reference "now" in epoch ms — injected for deterministic windowing/tests. */
|
|
65
|
+
readonly now: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Aggregate windowed telemetry rows into per-(provider, model) health.
|
|
69
|
+
*
|
|
70
|
+
* Window: rows whose ts is within [now - windowMinutes, now]. Rows with an
|
|
71
|
+
* unparseable ts are dropped, never thrown on. Output is sorted by provider then
|
|
72
|
+
* model for deterministic rollups. Below `minN`, percentiles + errorRate are
|
|
73
|
+
* `null` (min-n gate) while `sampleN` is still reported.
|
|
74
|
+
*/
|
|
75
|
+
export declare function aggregateEndpointHealth(rows: readonly HealthInputRow[], opts: HealthWindowOptions): EndpointHealth[];
|
|
76
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/telemetry/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,IAAI,CAStF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAMD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,cAAc,EAAE,EAC/B,IAAI,EAAE,mBAAmB,GACxB,cAAc,EAAE,CAoClB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Observed-health aggregation — G-4 (observed-health/spec.md §3.1, §4.1).
|
|
4
|
+
*
|
|
5
|
+
* One home for the per-endpoint latency/TTFB percentile + error-rate + min-n
|
|
6
|
+
* math, shared by the live store reader (store.ts `readEndpointHealth`) and the
|
|
7
|
+
* observed-layer rollup (registry/build-observed.ts). Keeping the math here —
|
|
8
|
+
* not in run.ts (at the 700-LOC cap) — is deliberate.
|
|
9
|
+
*
|
|
10
|
+
* THE RIGINDEX WALL (spec §4.4, load-bearing): this module feeds routing +
|
|
11
|
+
* console surfaces ONLY. It must NEVER be imported by registry/build-leaderboard.ts
|
|
12
|
+
* or the matrix/Bradley-Terry path — production telemetry never produces rank
|
|
13
|
+
* (eval-methodology-and-rigindex.md:19). The import-guard test
|
|
14
|
+
* (health-rigindex-wall.test.ts) is the tripwire; do not weaken it.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DEFAULT_HEALTH_MIN_N = void 0;
|
|
18
|
+
exports.percentile = percentile;
|
|
19
|
+
exports.computePercentiles = computePercentiles;
|
|
20
|
+
exports.aggregateEndpointHealth = aggregateEndpointHealth;
|
|
21
|
+
/** Spec §4.1 example: below 20 samples, percentiles/error-rate surface as null. */
|
|
22
|
+
exports.DEFAULT_HEALTH_MIN_N = 20;
|
|
23
|
+
/**
|
|
24
|
+
* Nearest-rank percentile over a pre-sorted ascending array. Matches the
|
|
25
|
+
* convention already used by build-observed.ts so the two lanes agree.
|
|
26
|
+
*/
|
|
27
|
+
function percentile(sorted, p) {
|
|
28
|
+
if (sorted.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
const index = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
|
|
31
|
+
return sorted[index];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* p50/p90/p99 over a set of values, dropping non-positive samples (a zero or
|
|
35
|
+
* negative latency/TTFB is a non-measurement, not a fast call). `null` when no
|
|
36
|
+
* positive sample remains.
|
|
37
|
+
*/
|
|
38
|
+
function computePercentiles(values) {
|
|
39
|
+
const sorted = values.filter((v) => v > 0).sort((a, b) => a - b);
|
|
40
|
+
if (sorted.length === 0)
|
|
41
|
+
return null;
|
|
42
|
+
return {
|
|
43
|
+
// percentile() only returns null for an empty array, ruled out above.
|
|
44
|
+
p50: percentile(sorted, 50),
|
|
45
|
+
p90: percentile(sorted, 90),
|
|
46
|
+
p99: percentile(sorted, 99),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Grouping-key separator: neither a ProviderId nor a model id contains a pipe,
|
|
50
|
+
// so the split back into (provider, model) is unambiguous.
|
|
51
|
+
const KEY_SEP = "|";
|
|
52
|
+
/**
|
|
53
|
+
* Aggregate windowed telemetry rows into per-(provider, model) health.
|
|
54
|
+
*
|
|
55
|
+
* Window: rows whose ts is within [now - windowMinutes, now]. Rows with an
|
|
56
|
+
* unparseable ts are dropped, never thrown on. Output is sorted by provider then
|
|
57
|
+
* model for deterministic rollups. Below `minN`, percentiles + errorRate are
|
|
58
|
+
* `null` (min-n gate) while `sampleN` is still reported.
|
|
59
|
+
*/
|
|
60
|
+
function aggregateEndpointHealth(rows, opts) {
|
|
61
|
+
const windowStartMs = opts.now - opts.windowMinutes * 60000;
|
|
62
|
+
const windowStart = new Date(windowStartMs).toISOString();
|
|
63
|
+
const byEndpoint = new Map();
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
const t = Date.parse(row.ts);
|
|
66
|
+
if (Number.isNaN(t) || t < windowStartMs || t > opts.now)
|
|
67
|
+
continue;
|
|
68
|
+
const key = `${row.provider}${KEY_SEP}${row.model}`;
|
|
69
|
+
const list = byEndpoint.get(key) ?? [];
|
|
70
|
+
list.push(row);
|
|
71
|
+
byEndpoint.set(key, list);
|
|
72
|
+
}
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const [key, group] of byEndpoint) {
|
|
75
|
+
const [provider, model] = key.split(KEY_SEP);
|
|
76
|
+
const sampleN = group.length;
|
|
77
|
+
const gated = sampleN < opts.minN;
|
|
78
|
+
const errorCount = group.filter((r) => r.failure_class !== null).length;
|
|
79
|
+
out.push({
|
|
80
|
+
provider: provider,
|
|
81
|
+
model,
|
|
82
|
+
windowStart,
|
|
83
|
+
sampleN,
|
|
84
|
+
latencyMs: gated ? null : computePercentiles(group.map((r) => r.latency_ms)),
|
|
85
|
+
ttfbMs: gated
|
|
86
|
+
? null
|
|
87
|
+
: computePercentiles(group.map((r) => r.ttfb_ms).filter((t) => t !== null)),
|
|
88
|
+
errorRate: gated ? null : errorCount / sampleN,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return out.sort((a, b) => a.provider === b.provider ? a.model.localeCompare(b.model) : a.provider.localeCompare(b.provider));
|
|
92
|
+
}
|