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,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OTLP span emitter — E1 (spec §2.3, §4). A SIBLING of the control-plane
|
|
4
|
+
* exporter, not a sink inside it: it reads the same `inferences` table behind
|
|
5
|
+
* its OWN watermark (`${sourceId}:otlp`), so the OTLP stream and the
|
|
6
|
+
* control-plane export never block or drain each other — exactly the pattern
|
|
7
|
+
* the feedback stream already uses (`:feedback`).
|
|
8
|
+
*
|
|
9
|
+
* ── DOCTRINE CLONED FROM exporter.ts (verbatim, on purpose) ───────────────
|
|
10
|
+
* Fire-and-forget from the run loop; at-least-once with idempotent span ids
|
|
11
|
+
* (otel-map derives them deterministically, so a re-emit after a crash is a
|
|
12
|
+
* no-op at the sink); the watermark advances ONLY after a confirmed 2xx;
|
|
13
|
+
* sink errors are swallowed-and-logged with exponential skip backoff; the
|
|
14
|
+
* interval timer is `unref()`ed so telemetry never keeps a process alive.
|
|
15
|
+
*
|
|
16
|
+
* ── OFF BY DEFAULT ────────────────────────────────────────────────────────
|
|
17
|
+
* This module is only constructed when `MODELRIG_OTLP_ENDPOINT` is set
|
|
18
|
+
* (config.ts). No endpoint ⇒ no emitter ⇒ zero behaviour change (spec §6a).
|
|
19
|
+
*
|
|
20
|
+
* ── C2 (never leak credentials) ───────────────────────────────────────────
|
|
21
|
+
* `cfg.headers` may carry sink auth (e.g. Langfuse Basic auth) and `endpoint`
|
|
22
|
+
* itself may embed a token. NEITHER is ever logged or put in an error. Failure
|
|
23
|
+
* text is run through the shared secret scrubber before it reaches a warning.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createOtlpEmitter = createOtlpEmitter;
|
|
27
|
+
exports.startOtlpEmitterIfConfigured = startOtlpEmitterIfConfigured;
|
|
28
|
+
const node_zlib_1 = require("node:zlib");
|
|
29
|
+
const otel_map_1 = require("./otel-map");
|
|
30
|
+
const scrub_1 = require("../tenancy/scrub");
|
|
31
|
+
const DEFAULT_BATCH_SIZE = 200;
|
|
32
|
+
const DEFAULT_INTERVAL_MS = 10000;
|
|
33
|
+
const BACKOFF_MAX_SKIPS = 32;
|
|
34
|
+
/** Below this, gzip costs more than it saves (mirrors the api sink). */
|
|
35
|
+
const GZIP_MIN_BYTES = 1024;
|
|
36
|
+
function createOtlpEmitter(store, config) {
|
|
37
|
+
const batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
38
|
+
const intervalMs = config.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
39
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
40
|
+
const watermarkKey = `${config.sourceId}:otlp`;
|
|
41
|
+
const resource = { serviceName: config.sourceId };
|
|
42
|
+
let timer = null;
|
|
43
|
+
let flushing = false;
|
|
44
|
+
let skipsRemaining = 0;
|
|
45
|
+
let consecutiveFailures = 0;
|
|
46
|
+
async function postSpans(rows) {
|
|
47
|
+
const spans = rows.map(({ row }) => (0, otel_map_1.inferenceToSpan)(row));
|
|
48
|
+
const json = JSON.stringify((0, otel_map_1.buildExportTraceRequest)(spans, resource));
|
|
49
|
+
const compress = Buffer.byteLength(json) >= GZIP_MIN_BYTES;
|
|
50
|
+
const response = await fetchFn(config.endpoint, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
...config.headers,
|
|
55
|
+
...(compress ? { "content-encoding": "gzip" } : {}),
|
|
56
|
+
},
|
|
57
|
+
body: compress ? (0, node_zlib_1.gzipSync)(json) : json,
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
// C2: neither the endpoint nor the headers ever appear here — status plus
|
|
61
|
+
// a SCRUBBED body only. A collector's error body is the likeliest place a
|
|
62
|
+
// reflected credential could reach someone else's log aggregator.
|
|
63
|
+
const body = (0, scrub_1.scrubSecrets)(await response.text().catch(() => ""));
|
|
64
|
+
throw new Error(`otlp export → ${response.status} ${body}`.trim());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function flushOnce() {
|
|
68
|
+
// Inferences past the OTLP watermark, in batches. The watermark advances
|
|
69
|
+
// ONLY after a confirmed send (at-least-once; deterministic span ids make
|
|
70
|
+
// the duplicate a no-op at the sink).
|
|
71
|
+
for (;;) {
|
|
72
|
+
const watermark = store.getExportWatermark(watermarkKey);
|
|
73
|
+
const pending = store.readInferencesAfter(watermark, batchSize);
|
|
74
|
+
if (pending.length === 0)
|
|
75
|
+
break;
|
|
76
|
+
await postSpans(pending);
|
|
77
|
+
store.setExportWatermark(watermarkKey, pending[pending.length - 1].rowid);
|
|
78
|
+
if (pending.length < batchSize)
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function guardedFlush() {
|
|
83
|
+
if (flushing)
|
|
84
|
+
return;
|
|
85
|
+
flushing = true;
|
|
86
|
+
try {
|
|
87
|
+
await flushOnce();
|
|
88
|
+
consecutiveFailures = 0;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
// Swallow-and-log, ALWAYS — an OTLP sink failure must never reach the run
|
|
92
|
+
// loop, and the scrubber guards the one place a secret could ride out.
|
|
93
|
+
consecutiveFailures += 1;
|
|
94
|
+
skipsRemaining = Math.min(2 ** consecutiveFailures, BACKOFF_MAX_SKIPS);
|
|
95
|
+
console.warn(`[modelrig otlp] export failed (attempt ${consecutiveFailures}, ` +
|
|
96
|
+
`backing off ${skipsRemaining} intervals): ` +
|
|
97
|
+
(0, scrub_1.scrubSecrets)(err instanceof Error ? err.message : String(err)));
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
flushing = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
start() {
|
|
105
|
+
if (timer !== null)
|
|
106
|
+
return;
|
|
107
|
+
timer = setInterval(() => {
|
|
108
|
+
if (skipsRemaining > 0) {
|
|
109
|
+
skipsRemaining -= 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
void guardedFlush();
|
|
113
|
+
}, intervalMs);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
},
|
|
116
|
+
async flushNow() {
|
|
117
|
+
await guardedFlush();
|
|
118
|
+
},
|
|
119
|
+
async stop() {
|
|
120
|
+
if (timer !== null) {
|
|
121
|
+
clearInterval(timer);
|
|
122
|
+
timer = null;
|
|
123
|
+
}
|
|
124
|
+
await guardedFlush();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Construct + start the OTLP emitter iff an endpoint is configured, else null
|
|
130
|
+
* (spec §6a). Lives here rather than index.ts so createRig's wiring stays one
|
|
131
|
+
* line and this file keeps the LOC cap on index.ts — the same reason
|
|
132
|
+
* `buildTelemetrySink` moved to sinks.ts.
|
|
133
|
+
*/
|
|
134
|
+
function startOtlpEmitterIfConfigured(store, otlp, sourceId) {
|
|
135
|
+
if (otlp == null)
|
|
136
|
+
return null;
|
|
137
|
+
const emitter = createOtlpEmitter(store, {
|
|
138
|
+
endpoint: otlp.endpoint,
|
|
139
|
+
headers: otlp.headers,
|
|
140
|
+
sourceId,
|
|
141
|
+
});
|
|
142
|
+
emitter.start();
|
|
143
|
+
return emitter;
|
|
144
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal OTLP/protobuf trace decoder — Expansion E3-Harvest (spec §3.2).
|
|
3
|
+
* PURE. Decodes an `ExportTraceServiceRequest` / `TracesData` protobuf body
|
|
4
|
+
* into the SAME loose OTLP-JSON-ish structure `otel-normalize.ts` consumes, so
|
|
5
|
+
* both transports (JSON + protobuf) converge on one normalizer.
|
|
6
|
+
*
|
|
7
|
+
* ── WHY HAND-ROLLED (§5.5 dependency firewall) ────────────────────────────
|
|
8
|
+
* Adding `@opentelemetry/*` or a generated proto runtime would be a new npm
|
|
9
|
+
* dependency, which needs an explicit addendum + G2 pack-safety review. We are
|
|
10
|
+
* decoding a fixed, small message set (trace/span/keyvalue), not implementing
|
|
11
|
+
* protobuf generally, so a focused wire reader is the right, dependency-free
|
|
12
|
+
* tool (implementation-plan "risks"). It reads only structure/identity/usage
|
|
13
|
+
* fields; content-adjacent fields (kvlist/bytes AnyValue) are skipped.
|
|
14
|
+
*
|
|
15
|
+
* Wire format: tag = (field << 3) | wireType; 0=varint, 1=64-bit, 2=length-
|
|
16
|
+
* delimited, 5=32-bit. int64 attribute + fixed64 timestamp values are emitted
|
|
17
|
+
* as DECIMAL STRINGS (what the normalizer's coercion expects), trace/span ids
|
|
18
|
+
* as lowercase hex.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Decode an OTLP `ExportTraceServiceRequest` / `TracesData` protobuf body into
|
|
22
|
+
* the `{ resourceSpans: [...] }` structure the normalizer reads. Throws on a
|
|
23
|
+
* truncated/garbled body (the caller turns that into a 400) — never returns
|
|
24
|
+
* partial garbage.
|
|
25
|
+
*/
|
|
26
|
+
export declare function decodeOtlpProtobufTraces(body: Buffer): {
|
|
27
|
+
resourceSpans: unknown[];
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=otlp-proto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp-proto.d.ts","sourceRoot":"","sources":["../../src/telemetry/otlp-proto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA4OH;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,aAAa,EAAE,OAAO,EAAE,CAAA;CAAE,CAUnF"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal OTLP/protobuf trace decoder — Expansion E3-Harvest (spec §3.2).
|
|
4
|
+
* PURE. Decodes an `ExportTraceServiceRequest` / `TracesData` protobuf body
|
|
5
|
+
* into the SAME loose OTLP-JSON-ish structure `otel-normalize.ts` consumes, so
|
|
6
|
+
* both transports (JSON + protobuf) converge on one normalizer.
|
|
7
|
+
*
|
|
8
|
+
* ── WHY HAND-ROLLED (§5.5 dependency firewall) ────────────────────────────
|
|
9
|
+
* Adding `@opentelemetry/*` or a generated proto runtime would be a new npm
|
|
10
|
+
* dependency, which needs an explicit addendum + G2 pack-safety review. We are
|
|
11
|
+
* decoding a fixed, small message set (trace/span/keyvalue), not implementing
|
|
12
|
+
* protobuf generally, so a focused wire reader is the right, dependency-free
|
|
13
|
+
* tool (implementation-plan "risks"). It reads only structure/identity/usage
|
|
14
|
+
* fields; content-adjacent fields (kvlist/bytes AnyValue) are skipped.
|
|
15
|
+
*
|
|
16
|
+
* Wire format: tag = (field << 3) | wireType; 0=varint, 1=64-bit, 2=length-
|
|
17
|
+
* delimited, 5=32-bit. int64 attribute + fixed64 timestamp values are emitted
|
|
18
|
+
* as DECIMAL STRINGS (what the normalizer's coercion expects), trace/span ids
|
|
19
|
+
* as lowercase hex.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.decodeOtlpProtobufTraces = decodeOtlpProtobufTraces;
|
|
23
|
+
const WIRE_VARINT = 0;
|
|
24
|
+
const WIRE_64BIT = 1;
|
|
25
|
+
const WIRE_LEN = 2;
|
|
26
|
+
const WIRE_32BIT = 5;
|
|
27
|
+
class Reader {
|
|
28
|
+
constructor(buf) {
|
|
29
|
+
this.buf = buf;
|
|
30
|
+
this.pos = 0;
|
|
31
|
+
}
|
|
32
|
+
get done() {
|
|
33
|
+
return this.pos >= this.buf.length;
|
|
34
|
+
}
|
|
35
|
+
/** LEB128 varint → bigint (values can exceed 2^53). */
|
|
36
|
+
varint() {
|
|
37
|
+
let result = 0n;
|
|
38
|
+
let shift = 0n;
|
|
39
|
+
for (;;) {
|
|
40
|
+
if (this.pos >= this.buf.length)
|
|
41
|
+
throw new Error("varint overrun");
|
|
42
|
+
const byte = this.buf[this.pos++];
|
|
43
|
+
result |= BigInt(byte & 0x7f) << shift;
|
|
44
|
+
if ((byte & 0x80) === 0)
|
|
45
|
+
break;
|
|
46
|
+
shift += 7n;
|
|
47
|
+
if (shift > 70n)
|
|
48
|
+
throw new Error("varint too long");
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/** A field tag → {field, wireType}. */
|
|
53
|
+
tag() {
|
|
54
|
+
const t = Number(this.varint());
|
|
55
|
+
return { field: t >>> 3, wireType: t & 0x7 };
|
|
56
|
+
}
|
|
57
|
+
bytes() {
|
|
58
|
+
const len = Number(this.varint());
|
|
59
|
+
if (this.pos + len > this.buf.length)
|
|
60
|
+
throw new Error("length-delimited overrun");
|
|
61
|
+
const slice = this.buf.subarray(this.pos, this.pos + len);
|
|
62
|
+
this.pos += len;
|
|
63
|
+
return slice;
|
|
64
|
+
}
|
|
65
|
+
fixed64() {
|
|
66
|
+
if (this.pos + 8 > this.buf.length)
|
|
67
|
+
throw new Error("fixed64 overrun");
|
|
68
|
+
const slice = this.buf.subarray(this.pos, this.pos + 8);
|
|
69
|
+
this.pos += 8;
|
|
70
|
+
return slice;
|
|
71
|
+
}
|
|
72
|
+
/** Skip a field of the given wire type we do not consume. */
|
|
73
|
+
skip(wireType) {
|
|
74
|
+
if (wireType === WIRE_VARINT)
|
|
75
|
+
this.varint();
|
|
76
|
+
else if (wireType === WIRE_64BIT)
|
|
77
|
+
this.pos += 8;
|
|
78
|
+
else if (wireType === WIRE_LEN)
|
|
79
|
+
this.bytes();
|
|
80
|
+
else if (wireType === WIRE_32BIT)
|
|
81
|
+
this.pos += 4;
|
|
82
|
+
else
|
|
83
|
+
throw new Error(`unknown wire type ${wireType}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function fixed64ToDecimalString(b) {
|
|
87
|
+
// Little-endian uint64 → decimal string.
|
|
88
|
+
let v = 0n;
|
|
89
|
+
for (let i = 7; i >= 0; i--)
|
|
90
|
+
v = (v << 8n) | BigInt(b[i]);
|
|
91
|
+
return v.toString();
|
|
92
|
+
}
|
|
93
|
+
function fixed64ToDouble(b) {
|
|
94
|
+
return b.readDoubleLE(0);
|
|
95
|
+
}
|
|
96
|
+
function decodeAnyValue(buf) {
|
|
97
|
+
const r = new Reader(buf);
|
|
98
|
+
const out = {};
|
|
99
|
+
while (!r.done) {
|
|
100
|
+
const { field, wireType } = r.tag();
|
|
101
|
+
// Guard the wire type on EVERY field: a field whose wire type does not match
|
|
102
|
+
// its schema (malformed input, or a wrong-message-type body such as a logs
|
|
103
|
+
// record decoded as a span) is skipped, never mis-read. Without this a
|
|
104
|
+
// fixed64 log timestamp at field 1 would be read as a length-delimited
|
|
105
|
+
// string and corrupt the parse.
|
|
106
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
107
|
+
out.stringValue = r.bytes().toString("utf8");
|
|
108
|
+
else if (field === 2 && wireType === WIRE_VARINT)
|
|
109
|
+
out.boolValue = r.varint() !== 0n;
|
|
110
|
+
else if (field === 3 && wireType === WIRE_VARINT)
|
|
111
|
+
out.intValue = r.varint().toString();
|
|
112
|
+
else if (field === 4 && wireType === WIRE_64BIT)
|
|
113
|
+
out.doubleValue = fixed64ToDouble(r.fixed64());
|
|
114
|
+
else if (field === 5 && wireType === WIRE_LEN)
|
|
115
|
+
out.arrayValue = { values: decodeArrayValue(r.bytes()) };
|
|
116
|
+
// 6 kvlist_value / 7 bytes_value are content-adjacent — skipped in v0.
|
|
117
|
+
else
|
|
118
|
+
r.skip(wireType);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
function decodeArrayValue(buf) {
|
|
123
|
+
const r = new Reader(buf);
|
|
124
|
+
const values = [];
|
|
125
|
+
while (!r.done) {
|
|
126
|
+
const { field, wireType } = r.tag();
|
|
127
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
128
|
+
values.push(decodeAnyValue(r.bytes()));
|
|
129
|
+
else
|
|
130
|
+
r.skip(wireType);
|
|
131
|
+
}
|
|
132
|
+
return values;
|
|
133
|
+
}
|
|
134
|
+
function decodeKeyValue(buf) {
|
|
135
|
+
const r = new Reader(buf);
|
|
136
|
+
const kv = {};
|
|
137
|
+
while (!r.done) {
|
|
138
|
+
const { field, wireType } = r.tag();
|
|
139
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
140
|
+
kv.key = r.bytes().toString("utf8");
|
|
141
|
+
else if (field === 2 && wireType === WIRE_LEN)
|
|
142
|
+
kv.value = decodeAnyValue(r.bytes());
|
|
143
|
+
else
|
|
144
|
+
r.skip(wireType);
|
|
145
|
+
}
|
|
146
|
+
return kv;
|
|
147
|
+
}
|
|
148
|
+
function decodeSpan(buf) {
|
|
149
|
+
const r = new Reader(buf);
|
|
150
|
+
const span = { attributes: [] };
|
|
151
|
+
while (!r.done) {
|
|
152
|
+
const { field, wireType } = r.tag();
|
|
153
|
+
// Wire-type-guarded (see decodeAnyValue): a LogRecord decoded here as a Span
|
|
154
|
+
// has a fixed64 at field 1 where a Span expects bytes; guarding makes that a
|
|
155
|
+
// skip (→ an id-less span the normalizer drops / logs-detection catches),
|
|
156
|
+
// never a corrupt read.
|
|
157
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
158
|
+
span.traceId = r.bytes().toString("hex");
|
|
159
|
+
else if (field === 2 && wireType === WIRE_LEN)
|
|
160
|
+
span.spanId = r.bytes().toString("hex");
|
|
161
|
+
else if (field === 4 && wireType === WIRE_LEN)
|
|
162
|
+
span.parentSpanId = r.bytes().toString("hex");
|
|
163
|
+
else if (field === 5 && wireType === WIRE_LEN)
|
|
164
|
+
span.name = r.bytes().toString("utf8");
|
|
165
|
+
else if (field === 6 && wireType === WIRE_VARINT)
|
|
166
|
+
span.kind = Number(r.varint());
|
|
167
|
+
else if (field === 7 && wireType === WIRE_64BIT)
|
|
168
|
+
span.startTimeUnixNano = fixed64ToDecimalString(r.fixed64());
|
|
169
|
+
else if (field === 8 && wireType === WIRE_64BIT)
|
|
170
|
+
span.endTimeUnixNano = fixed64ToDecimalString(r.fixed64());
|
|
171
|
+
else if (field === 9 && wireType === WIRE_LEN)
|
|
172
|
+
span.attributes.push(decodeKeyValue(r.bytes()));
|
|
173
|
+
else if (field === 15 && wireType === WIRE_LEN)
|
|
174
|
+
span.status = decodeStatus(r.bytes());
|
|
175
|
+
else
|
|
176
|
+
r.skip(wireType);
|
|
177
|
+
}
|
|
178
|
+
return span;
|
|
179
|
+
}
|
|
180
|
+
function decodeStatus(buf) {
|
|
181
|
+
const r = new Reader(buf);
|
|
182
|
+
const status = {};
|
|
183
|
+
while (!r.done) {
|
|
184
|
+
const { field, wireType } = r.tag();
|
|
185
|
+
if (field === 2 && wireType === WIRE_LEN)
|
|
186
|
+
status.message = r.bytes().toString("utf8");
|
|
187
|
+
else if (field === 3 && wireType === WIRE_VARINT)
|
|
188
|
+
status.code = Number(r.varint());
|
|
189
|
+
else
|
|
190
|
+
r.skip(wireType);
|
|
191
|
+
}
|
|
192
|
+
return status;
|
|
193
|
+
}
|
|
194
|
+
function decodeScope(buf) {
|
|
195
|
+
const r = new Reader(buf);
|
|
196
|
+
const scope = {};
|
|
197
|
+
while (!r.done) {
|
|
198
|
+
const { field, wireType } = r.tag();
|
|
199
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
200
|
+
scope.name = r.bytes().toString("utf8");
|
|
201
|
+
else if (field === 2 && wireType === WIRE_LEN)
|
|
202
|
+
scope.version = r.bytes().toString("utf8");
|
|
203
|
+
else
|
|
204
|
+
r.skip(wireType);
|
|
205
|
+
}
|
|
206
|
+
return scope;
|
|
207
|
+
}
|
|
208
|
+
function decodeScopeSpans(buf) {
|
|
209
|
+
const r = new Reader(buf);
|
|
210
|
+
const out = { spans: [] };
|
|
211
|
+
while (!r.done) {
|
|
212
|
+
const { field, wireType } = r.tag();
|
|
213
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
214
|
+
out.scope = decodeScope(r.bytes());
|
|
215
|
+
else if (field === 2 && wireType === WIRE_LEN)
|
|
216
|
+
out.spans.push(decodeSpan(r.bytes()));
|
|
217
|
+
else
|
|
218
|
+
r.skip(wireType);
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
function decodeResource(buf) {
|
|
223
|
+
const r = new Reader(buf);
|
|
224
|
+
const attributes = [];
|
|
225
|
+
while (!r.done) {
|
|
226
|
+
const { field, wireType } = r.tag();
|
|
227
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
228
|
+
attributes.push(decodeKeyValue(r.bytes()));
|
|
229
|
+
else
|
|
230
|
+
r.skip(wireType);
|
|
231
|
+
}
|
|
232
|
+
return { attributes };
|
|
233
|
+
}
|
|
234
|
+
function decodeResourceSpans(buf) {
|
|
235
|
+
const r = new Reader(buf);
|
|
236
|
+
const out = { scopeSpans: [] };
|
|
237
|
+
while (!r.done) {
|
|
238
|
+
const { field, wireType } = r.tag();
|
|
239
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
240
|
+
out.resource = decodeResource(r.bytes());
|
|
241
|
+
else if (field === 2 && wireType === WIRE_LEN)
|
|
242
|
+
out.scopeSpans.push(decodeScopeSpans(r.bytes()));
|
|
243
|
+
else
|
|
244
|
+
r.skip(wireType);
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Decode an OTLP `ExportTraceServiceRequest` / `TracesData` protobuf body into
|
|
250
|
+
* the `{ resourceSpans: [...] }` structure the normalizer reads. Throws on a
|
|
251
|
+
* truncated/garbled body (the caller turns that into a 400) — never returns
|
|
252
|
+
* partial garbage.
|
|
253
|
+
*/
|
|
254
|
+
function decodeOtlpProtobufTraces(body) {
|
|
255
|
+
const r = new Reader(body);
|
|
256
|
+
const resourceSpans = [];
|
|
257
|
+
while (!r.done) {
|
|
258
|
+
const { field, wireType } = r.tag();
|
|
259
|
+
// TracesData.resource_spans = 1; ExportTraceServiceRequest.resource_spans = 1.
|
|
260
|
+
if (field === 1 && wireType === WIRE_LEN)
|
|
261
|
+
resourceSpans.push(decodeResourceSpans(r.bytes()));
|
|
262
|
+
else
|
|
263
|
+
r.skip(wireType);
|
|
264
|
+
}
|
|
265
|
+
return { resourceSpans };
|
|
266
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two telemetry transports — Phase 6 WS2.
|
|
3
|
+
*
|
|
4
|
+
* telemetry-ingestion-boundary.md §2.4 names three modes. Two of them are
|
|
5
|
+
* sinks and live here; the third (local-only) is the absence of a sink.
|
|
6
|
+
*
|
|
7
|
+
* direct self-host + our own single-tenant deployments · their database
|
|
8
|
+
* api every hosted customer · their `rig_sk_` key only
|
|
9
|
+
*
|
|
10
|
+
* Both implement `TelemetrySink`, both are driven by the SAME exporter loop,
|
|
11
|
+
* and both produce their database rows through `stampBatch` — so the promise
|
|
12
|
+
* that "self-host ↔ hosted migration is a config change, not a data model
|
|
13
|
+
* change" is enforced by construction rather than by review.
|
|
14
|
+
*
|
|
15
|
+
* ── WHY THE API SINK GZIPS AND THE DIRECT ONE DOES NOT ───────────────────
|
|
16
|
+
* The api sink crosses the public internet with one batch per flush, and a
|
|
17
|
+
* batch is highly repetitive JSON. The direct sink talks to PostgREST, which
|
|
18
|
+
* would have to decompress it again on the way into the same database the
|
|
19
|
+
* caller already holds a service-role key for. Compression there would be
|
|
20
|
+
* cost with no payer.
|
|
21
|
+
*/
|
|
22
|
+
import type { IngestBatch, IngestOutcome, Tenancy } from "./ingest-protocol";
|
|
23
|
+
import type { ApiExportConfig, DirectExportConfig } from "../config";
|
|
24
|
+
export interface TelemetrySink {
|
|
25
|
+
/** Named so a boot banner and `/status` can say which lane is live without
|
|
26
|
+
* inspecting config — the "which mode am I in?" question got asked enough
|
|
27
|
+
* during WS1 to be worth answering in the object. */
|
|
28
|
+
readonly mode: "direct" | "api";
|
|
29
|
+
/** Deliver one batch. Throws `SinkSendError` on failure; the exporter owns
|
|
30
|
+
* retry/backoff and reads `retriable` to tell a transient blip apart from a
|
|
31
|
+
* deterministic rejection it must poison-skip (E2 hardening Phase 2). */
|
|
32
|
+
send(batch: IngestBatch): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A sink send failed — the typed error both transports throw (E2 hardening
|
|
36
|
+
* Phase 2, spec §2.3.1). It carries the HTTP status the sink observed (absent
|
|
37
|
+
* for a network-level throw) and a `retriable` verdict the exporter branches on:
|
|
38
|
+
*
|
|
39
|
+
* retriable network throw (no status) · any 5xx · a 429 (overload)
|
|
40
|
+
* → the same batch may land next time; retry forever, never drop.
|
|
41
|
+
* NOT every other 4xx, including the server's deterministic 422
|
|
42
|
+
* → the same bytes will be rejected identically forever; the
|
|
43
|
+
* exporter poison-skips it after a bounded self-heal window.
|
|
44
|
+
*
|
|
45
|
+
* The message is leak-safe BY CONSTRUCTION — status + table only, NEVER the
|
|
46
|
+
* PostgREST/response body. An exporter's warnings are the likeliest place for a
|
|
47
|
+
* secret or a foreign row to reach someone else's log aggregator (C2's leak
|
|
48
|
+
* gate), so the body a failed write returns is logged nowhere.
|
|
49
|
+
*/
|
|
50
|
+
export declare class SinkSendError extends Error {
|
|
51
|
+
/** The HTTP status observed, or undefined for a network-level throw. */
|
|
52
|
+
readonly status?: number;
|
|
53
|
+
/** True ⇔ a transient failure the exporter should retry forever (never a
|
|
54
|
+
* poison-skip candidate). Derived once, from the status, so the classification
|
|
55
|
+
* lives in exactly one place. */
|
|
56
|
+
readonly retriable: boolean;
|
|
57
|
+
constructor(message: string, status: number | undefined);
|
|
58
|
+
}
|
|
59
|
+
export interface DirectSinkConfig {
|
|
60
|
+
readonly supabaseUrl: string;
|
|
61
|
+
/** Service-role key — server-side only; never reaches a browser. */
|
|
62
|
+
readonly serviceRoleKey: string;
|
|
63
|
+
/** The tenant this deployment stamps. Every row it writes lands here. */
|
|
64
|
+
readonly tenancy: Tenancy;
|
|
65
|
+
readonly fetchFn?: typeof fetch;
|
|
66
|
+
}
|
|
67
|
+
export declare function createDirectSink(config: DirectSinkConfig): TelemetrySink;
|
|
68
|
+
export interface ApiSinkConfig {
|
|
69
|
+
/** Base URL of the ingest service, e.g. https://api.modelrig.ai */
|
|
70
|
+
readonly ingestUrl: string;
|
|
71
|
+
/** `rig_sk_…` with the `ingest` scope. The ONLY credential a hosted
|
|
72
|
+
* customer holds — no database URL, no service role (§2's one rule). */
|
|
73
|
+
readonly apiKey: string;
|
|
74
|
+
readonly fetchFn?: typeof fetch;
|
|
75
|
+
/** Called with each batch's outcome. The exporter logs dropped rows through
|
|
76
|
+
* this: a batch the server partly refused is a 200, and a customer whose
|
|
77
|
+
* rows are being dropped must hear about it from their own process rather
|
|
78
|
+
* than by noticing a gap in a chart weeks later. */
|
|
79
|
+
readonly onOutcome?: (outcome: IngestOutcome) => void;
|
|
80
|
+
}
|
|
81
|
+
export declare function createApiSink(config: ApiSinkConfig): TelemetrySink;
|
|
82
|
+
/**
|
|
83
|
+
* Build the telemetry transport for the configured export mode. One place, so
|
|
84
|
+
* the run-loop exporter and the load-error reporter build it the same way —
|
|
85
|
+
* telemetry-ingestion-boundary.md §4's "one loop, two transports". (Lived in
|
|
86
|
+
* index.ts until Phase D; moved here — its natural home — to keep that file
|
|
87
|
+
* under the LOC cap.)
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildTelemetrySink(exportConfig: ApiExportConfig | DirectExportConfig, sourceId: string): TelemetrySink;
|
|
90
|
+
//# sourceMappingURL=sinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../../src/telemetry/sinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGrE,MAAM,WAAW,aAAa;IAC5B;;yDAEqD;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAChC;;6EAEyE;IACzE,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;qCAEiC;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS;CAMxD;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CA0CxE;AAMD,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;4EACwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC;;;wDAGoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CACvD;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CA4ClE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,eAAe,GAAG,kBAAkB,EAClD,QAAQ,EAAE,MAAM,GACf,aAAa,CA0Bf"}
|