modelrig 0.1.0 → 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/README.md +41 -11
- 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 +50 -6
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +123 -29
- 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 +6 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +54 -1
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +46 -6
- package/dist/adapters/grok.d.ts +9 -0
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +67 -3
- package/dist/adapters/openai-compat.d.ts +6 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +58 -1
- 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 +6 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +60 -1
- 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/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/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 +15 -0
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +20 -0
- package/dist/cli.d.ts +1 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +134 -116
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -0
- package/dist/deprecations.d.ts +32 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +59 -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 +105 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +266 -138
- package/dist/init/finder.d.ts +15 -0
- package/dist/init/finder.d.ts.map +1 -1
- package/dist/init/finder.js +46 -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/registry/build.d.ts +94 -1
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/build.js +125 -4
- 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.map +1 -1
- package/dist/registry/call-notes.js +40 -2
- 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.map +1 -1
- package/dist/registry/facts.js +16 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -1
- package/dist/registry/parity.js +43 -2
- 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 +25 -1
- package/dist/registry/pricing.d.ts.map +1 -1
- package/dist/registry/pricing.js +64 -6
- 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/loader.d.ts +37 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +138 -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/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.map +1 -1
- package/dist/run/cost.js +1 -1
- 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/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/resolve.d.ts +75 -2
- package/dist/run/resolve.d.ts.map +1 -1
- package/dist/run/resolve.js +126 -4
- package/dist/run/run.d.ts +14 -0
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +0 -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.map +1 -1
- package/dist/run/validate.js +6 -7
- 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 +13 -0
- package/dist/savings/emit.d.ts.map +1 -1
- package/dist/savings/emit.js +25 -0
- package/dist/savings/swap-execute.d.ts +14 -0
- package/dist/savings/swap-execute.d.ts.map +1 -1
- package/dist/savings/swap-execute.js +44 -1
- package/dist/savings/swaps-client.d.ts +14 -0
- package/dist/savings/swaps-client.d.ts.map +1 -1
- package/dist/savings/swaps-client.js +16 -2
- 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 +3 -0
- package/dist/telemetry/capture.d.ts.map +1 -1
- package/dist/telemetry/capture.js +15 -8
- 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 +75 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +324 -47
- 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 +152 -3
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +204 -4
- 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 +37 -1
- package/dist/telemetry/sinks.d.ts.map +1 -1
- package/dist/telemetry/sinks.js +121 -30
- package/dist/telemetry/store.d.ts +28 -1
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +218 -3
- package/dist/tenancy/api-key-format.d.ts +15 -5
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +24 -5
- 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/provider.d.ts +122 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +21 -1
- package/dist/types/route.d.ts +79 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +117 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- 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/evaluate.d.ts +162 -3
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +61 -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 +20 -4
- package/dist/watch/proposals.d.ts.map +1 -1
- package/dist/watch/proposals.js +31 -3
- package/dist/watch/run.d.ts +33 -0
- package/dist/watch/run.d.ts.map +1 -1
- package/dist/watch/run.js +39 -0
- package/package.json +7 -1
- package/registry/LICENSE +20 -0
- package/registry/leaderboard.json +3639 -0
- package/registry/registry.json +7263 -0
- package/dist/telemetry/supabase-exporter.d.ts +0 -64
- package/dist/telemetry/supabase-exporter.d.ts.map +0 -1
- package/dist/telemetry/supabase-exporter.js +0 -211
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Build-and-start the telemetry exporter from resolved config. Extracted from
|
|
4
|
+
* index.ts (the SDK entrypoint) so that file stays under the LOC cap while
|
|
5
|
+
* Phase 2 adds the settings resolver + boot banner beside it — the exporter
|
|
6
|
+
* wiring is mechanical and has one caller, so it lives here rather than inline.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.startTelemetryExporter = startTelemetryExporter;
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const exporter_1 = require("./exporter");
|
|
12
|
+
const sinks_1 = require("./sinks");
|
|
13
|
+
/**
|
|
14
|
+
* Construct the sink for the configured mode, wire the exporter, and start its
|
|
15
|
+
* flush loop. Returns the running exporter (the caller stops it on close()).
|
|
16
|
+
*/
|
|
17
|
+
function startTelemetryExporter(telemetry, exportConfig, extras) {
|
|
18
|
+
const sourceId = exportConfig.sourceId ?? (0, node_os_1.hostname)();
|
|
19
|
+
const sink = (0, sinks_1.buildTelemetrySink)(exportConfig, sourceId);
|
|
20
|
+
const exporter = (0, exporter_1.createTelemetryExporter)(telemetry, {
|
|
21
|
+
sink,
|
|
22
|
+
sourceId,
|
|
23
|
+
...(exportConfig.batchSize !== undefined ? { batchSize: exportConfig.batchSize } : {}),
|
|
24
|
+
...(exportConfig.intervalMs !== undefined ? { intervalMs: exportConfig.intervalMs } : {}),
|
|
25
|
+
routes: extras.routes,
|
|
26
|
+
manifest: extras.manifest,
|
|
27
|
+
serveability: extras.serveability,
|
|
28
|
+
artifacts: extras.artifactsEnabled,
|
|
29
|
+
...(extras.artifactStore !== null ? { artifactStore: extras.artifactStore } : {}),
|
|
30
|
+
...(extras.onArtifactsFlushed !== undefined ? { onArtifactsFlushed: extras.onArtifactsFlushed } : {}),
|
|
31
|
+
});
|
|
32
|
+
exporter.start();
|
|
33
|
+
return exporter;
|
|
34
|
+
}
|
|
@@ -17,8 +17,13 @@
|
|
|
17
17
|
* holds the one mapper both of them write rows through.
|
|
18
18
|
*/
|
|
19
19
|
import type { RigManifest } from "../routes/manifest";
|
|
20
|
+
import type { ResolvedRouteBundle } from "../routes/resolved-bundle";
|
|
21
|
+
import type { RouteBundle } from "../types/route";
|
|
20
22
|
import type { ServeabilityWarning } from "../routes/serveability";
|
|
23
|
+
import type { ConfigFailedState } from "../types/config-teaching";
|
|
24
|
+
import type { IngestBatch } from "./ingest-protocol";
|
|
21
25
|
import type { TelemetrySink } from "./sinks";
|
|
26
|
+
import type { ArtifactStore } from "./artifact-store";
|
|
22
27
|
import type { ExportableTelemetryStore } from "./store";
|
|
23
28
|
export interface ExportedRouteMirror {
|
|
24
29
|
readonly route: string;
|
|
@@ -30,7 +35,21 @@ export interface ExportedRouteMirror {
|
|
|
30
35
|
/** Capture opt-in FLAG (Phase 3 WS4) — status only; capture content never
|
|
31
36
|
* has a cloud surface. */
|
|
32
37
|
readonly capture?: boolean;
|
|
38
|
+
/** Phase D (spec §13): the RESOLVED bundle (schema + prompt template inlined)
|
|
39
|
+
* the hosted gateway serves, and its content hash. Built by `index.ts` from
|
|
40
|
+
* the already-loaded (file-resolved) bundle via `resolveRouteBundle`. */
|
|
41
|
+
readonly bundle?: ResolvedRouteBundle;
|
|
42
|
+
readonly bundleSha?: string;
|
|
33
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the `routes` mirror payload from loaded bundles (Phase D, spec §13).
|
|
46
|
+
* Each `b` is ALREADY file-resolved — the loader inlined the schema + prompt
|
|
47
|
+
* files at createRig() and FAILED THERE (teaching RouteConfigError) if one was
|
|
48
|
+
* missing, so nothing unresolved reaches here and the gateway never needs the
|
|
49
|
+
* customer's filesystem (§13.1 rule 6). Lives here rather than in index.ts to
|
|
50
|
+
* keep that file under the LOC cap.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildExportedRouteMirrors(bundles: readonly RouteBundle[]): ExportedRouteMirror[];
|
|
34
53
|
export interface ExporterConfig {
|
|
35
54
|
/** Where batches go. `sinks.ts` builds one of these from config. */
|
|
36
55
|
readonly sink: TelemetrySink;
|
|
@@ -47,6 +66,43 @@ export interface ExporterConfig {
|
|
|
47
66
|
* XAI_API_KEY" instead of the fact living only in a boot log. Names only:
|
|
48
67
|
* route, provider, model, env var. */
|
|
49
68
|
readonly serveability?: readonly ServeabilityWarning[];
|
|
69
|
+
/** Load-time RouteConfigError diagnostics for THIS deployment (follow-up #2)
|
|
70
|
+
* — mirrored onto the manifest row so /setup's "Define your first route" step
|
|
71
|
+
* renders `failed` with what we looked for and found, instead of silence.
|
|
72
|
+
* Absent/empty means the load was clean, and sending `[]` clears a stale
|
|
73
|
+
* failure from a prior broken deploy. */
|
|
74
|
+
readonly loadErrors?: readonly ConfigFailedState[];
|
|
75
|
+
/**
|
|
76
|
+
* Artifact / execution-graph namespace active (Expansion E2). When true the
|
|
77
|
+
* exporter announces protocol v2 and ships the `runs`/`steps` streams; when
|
|
78
|
+
* false (the default) it announces v1 and ships nothing new — byte-identical
|
|
79
|
+
* to before v2 existed (spec §1.3/§1.4). The wire version is a property of the
|
|
80
|
+
* EXPORTER, not the batch: once artifacts are on, every batch is v2, so an old
|
|
81
|
+
* server 422s loudly rather than silently dropping artifact sections.
|
|
82
|
+
*/
|
|
83
|
+
readonly artifacts?: boolean;
|
|
84
|
+
/** The local artifact-metadata buffer (chunk B). A SEPARATE handle from the
|
|
85
|
+
* telemetry store; present only when the namespace is enabled. Its rows ride
|
|
86
|
+
* the `:artifacts`/`:edges`/`:evals` watermark streams (watermarks live in the
|
|
87
|
+
* main store's export_state, rows are read from here). */
|
|
88
|
+
readonly artifactStore?: ArtifactStore;
|
|
89
|
+
/**
|
|
90
|
+
* Post-flush hook fired AFTER a confirmed export of the artifacts stream (E4
|
|
91
|
+
* chunk 0, flag 2). The artifact api wires this to `retryPendingContent`: a
|
|
92
|
+
* content upload that 404'd because it raced its own metadata export is now
|
|
93
|
+
* safe to re-attempt, because the metadata row just landed in the control
|
|
94
|
+
* plane. It runs only once the artifact drains have all returned (a throw in
|
|
95
|
+
* any of them aborts the flush before this, so "confirmed export" holds), and
|
|
96
|
+
* it must never throw — a rejection here is swallowed like any flush failure.
|
|
97
|
+
*/
|
|
98
|
+
readonly onArtifactsFlushed?: () => Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
/** Poison-pill counters (E2 hardening Phase 2, spec §2.3.5): per-stream row
|
|
101
|
+
* counts the exporter has SKIPPED past because the batch was rejected
|
|
102
|
+
* deterministically K times running. In-memory, per instance; empty `{}` on a
|
|
103
|
+
* healthy exporter. Mirrors the `IngestOutcome.reasons` map shape. */
|
|
104
|
+
export interface ExporterStats {
|
|
105
|
+
readonly poisonSkipped: Readonly<Record<string, number>>;
|
|
50
106
|
}
|
|
51
107
|
export interface Exporter {
|
|
52
108
|
/** Which lane this rig's telemetry is travelling in. Read by the boot
|
|
@@ -57,6 +113,25 @@ export interface Exporter {
|
|
|
57
113
|
/** Flush pending rows now (used by tests and process-exit hooks). */
|
|
58
114
|
flush(): Promise<void>;
|
|
59
115
|
stop(): Promise<void>;
|
|
116
|
+
/** Poison batches this exporter has skipped past, per stream (additive; E2
|
|
117
|
+
* hardening Phase 2). The `/status` snapshot and any operator dashboard read
|
|
118
|
+
* it; a healthy exporter returns `{ poisonSkipped: {} }`. */
|
|
119
|
+
stats(): ExporterStats;
|
|
60
120
|
}
|
|
121
|
+
/** The flush cadence. Exported so the settings resolver (§1.5) can beat on the
|
|
122
|
+
* SAME interval — "refreshes on the exporter interval" without a second knob. */
|
|
123
|
+
export declare const DEFAULT_INTERVAL_MS = 10000;
|
|
61
124
|
export declare function createTelemetryExporter(store: ExportableTelemetryStore, config: ExporterConfig): Exporter;
|
|
125
|
+
/**
|
|
126
|
+
* The one-shot manifest batch that carries a load-time config failure to the
|
|
127
|
+
* console (follow-up #2), for the case `createRig` throws BEFORE the exporter
|
|
128
|
+
* loop exists (a route bundle that fails to load never reaches the loop).
|
|
129
|
+
*
|
|
130
|
+
* It is a manifest-only batch: no inferences, no watermark. The manifest carries
|
|
131
|
+
* whatever `rig.yaml` could still provide (name/dimensions/contribution — the
|
|
132
|
+
* routes failing to load does not stop rig.yaml loading) plus the load errors;
|
|
133
|
+
* `serveability` is `[]` because it cannot be computed without loaded routes.
|
|
134
|
+
* A caller sends it fire-and-forget through the same sink the run loop uses.
|
|
135
|
+
*/
|
|
136
|
+
export declare function buildLoadErrorBatch(sourceId: string, manifest: Pick<RigManifest, "name" | "dimensions" | "contribution"> | null, loadErrors: readonly ConfigFailedState[]): IngestBatch;
|
|
62
137
|
//# sourceMappingURL=exporter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../../src/telemetry/exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../../src/telemetry/exporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,13 +18,51 @@
|
|
|
18
18
|
* holds the one mapper both of them write rows through.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.DEFAULT_INTERVAL_MS = void 0;
|
|
22
|
+
exports.buildExportedRouteMirrors = buildExportedRouteMirrors;
|
|
21
23
|
exports.createTelemetryExporter = createTelemetryExporter;
|
|
24
|
+
exports.buildLoadErrorBatch = buildLoadErrorBatch;
|
|
25
|
+
const resolved_bundle_1 = require("../routes/resolved-bundle");
|
|
22
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
|
+
}
|
|
23
49
|
const DEFAULT_BATCH_SIZE = 200;
|
|
24
|
-
|
|
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;
|
|
25
53
|
/** After a sink failure, skip this many intervals, doubling per consecutive
|
|
26
54
|
* failure (simple exponential backoff, capped). */
|
|
27
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;
|
|
28
66
|
function toWireInference(row) {
|
|
29
67
|
return {
|
|
30
68
|
id: row.id,
|
|
@@ -48,6 +86,92 @@ function toWireInference(row) {
|
|
|
48
86
|
served_variant: row.servedVariant,
|
|
49
87
|
repaired_by: row.repairedBy,
|
|
50
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,
|
|
51
175
|
};
|
|
52
176
|
}
|
|
53
177
|
function toWireFeedback(row) {
|
|
@@ -69,14 +193,25 @@ function toWireFeedback(row) {
|
|
|
69
193
|
}
|
|
70
194
|
function createTelemetryExporter(store, config) {
|
|
71
195
|
const batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
72
|
-
const intervalMs = config.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
196
|
+
const intervalMs = config.intervalMs ?? exports.DEFAULT_INTERVAL_MS;
|
|
73
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;
|
|
74
201
|
let timer = null;
|
|
75
202
|
let flushing = false;
|
|
76
203
|
let skipsRemaining = 0;
|
|
77
204
|
let consecutiveFailures = 0;
|
|
78
205
|
let routesMirrored = false;
|
|
79
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 = {};
|
|
80
215
|
/**
|
|
81
216
|
* The once-per-process mirrors, folded into whichever batch goes out first.
|
|
82
217
|
*
|
|
@@ -92,6 +227,9 @@ function createTelemetryExporter(store, config) {
|
|
|
92
227
|
version: r.version,
|
|
93
228
|
candidates: r.candidates,
|
|
94
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 } : {}),
|
|
95
233
|
}));
|
|
96
234
|
}
|
|
97
235
|
if (!manifestMirrored && config.manifest !== undefined && config.manifest !== null) {
|
|
@@ -106,6 +244,9 @@ function createTelemetryExporter(store, config) {
|
|
|
106
244
|
serveable_count: w.serveableCount,
|
|
107
245
|
fix_hint: w.fixHint,
|
|
108
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 ?? [])],
|
|
109
250
|
};
|
|
110
251
|
}
|
|
111
252
|
return header;
|
|
@@ -118,56 +259,161 @@ function createTelemetryExporter(store, config) {
|
|
|
118
259
|
if (header.manifest !== undefined)
|
|
119
260
|
manifestMirrored = true;
|
|
120
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
|
+
}
|
|
121
317
|
async function flushOnce() {
|
|
122
318
|
let header = pendingHeader();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
354
|
}
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
406
|
}
|
|
164
|
-
// Envelopes: small table, full upsert each flush. This send
|
|
165
|
-
// any header the
|
|
166
|
-
// routes but has not run yet must still appear in the console.
|
|
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.
|
|
167
413
|
const envelopes = store.listEnvelopes();
|
|
168
414
|
if (envelopes.length > 0 || Object.keys(header).length > 0) {
|
|
169
|
-
await
|
|
170
|
-
v:
|
|
415
|
+
const outcome = await sendOrPoison(`${sourceId}:envelopes`, 0, Math.max(envelopes.length, 1), {
|
|
416
|
+
v: wireVersion,
|
|
171
417
|
source: sourceId,
|
|
172
418
|
...header,
|
|
173
419
|
...(envelopes.length > 0
|
|
@@ -181,7 +427,8 @@ function createTelemetryExporter(store, config) {
|
|
|
181
427
|
}
|
|
182
428
|
: {}),
|
|
183
429
|
});
|
|
184
|
-
|
|
430
|
+
if (outcome === "sent")
|
|
431
|
+
markHeaderDelivered(header);
|
|
185
432
|
}
|
|
186
433
|
}
|
|
187
434
|
async function guardedFlush() {
|
|
@@ -229,5 +476,35 @@ function createTelemetryExporter(store, config) {
|
|
|
229
476
|
}
|
|
230
477
|
await guardedFlush();
|
|
231
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
|
+
},
|
|
232
509
|
};
|
|
233
510
|
}
|