modelrig 0.1.0 → 0.3.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 +158 -0
- package/README.md +53 -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 +10 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +78 -7
- 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 +106 -19
- 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 +48 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +167 -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 +130 -0
- package/dist/run/raw.d.ts.map +1 -0
- package/dist/run/raw.js +363 -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 +147 -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
package/dist/telemetry/sinks.js
CHANGED
|
@@ -21,10 +21,39 @@
|
|
|
21
21
|
* cost with no payer.
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.SinkSendError = void 0;
|
|
24
25
|
exports.createDirectSink = createDirectSink;
|
|
25
26
|
exports.createApiSink = createApiSink;
|
|
27
|
+
exports.buildTelemetrySink = buildTelemetrySink;
|
|
26
28
|
const node_zlib_1 = require("node:zlib");
|
|
27
29
|
const ingest_protocol_1 = require("./ingest-protocol");
|
|
30
|
+
const constants_1 = require("../tenancy/constants");
|
|
31
|
+
/**
|
|
32
|
+
* A sink send failed — the typed error both transports throw (E2 hardening
|
|
33
|
+
* Phase 2, spec §2.3.1). It carries the HTTP status the sink observed (absent
|
|
34
|
+
* for a network-level throw) and a `retriable` verdict the exporter branches on:
|
|
35
|
+
*
|
|
36
|
+
* retriable network throw (no status) · any 5xx · a 429 (overload)
|
|
37
|
+
* → the same batch may land next time; retry forever, never drop.
|
|
38
|
+
* NOT every other 4xx, including the server's deterministic 422
|
|
39
|
+
* → the same bytes will be rejected identically forever; the
|
|
40
|
+
* exporter poison-skips it after a bounded self-heal window.
|
|
41
|
+
*
|
|
42
|
+
* The message is leak-safe BY CONSTRUCTION — status + table only, NEVER the
|
|
43
|
+
* PostgREST/response body. An exporter's warnings are the likeliest place for a
|
|
44
|
+
* secret or a foreign row to reach someone else's log aggregator (C2's leak
|
|
45
|
+
* gate), so the body a failed write returns is logged nowhere.
|
|
46
|
+
*/
|
|
47
|
+
class SinkSendError extends Error {
|
|
48
|
+
constructor(message, status) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "SinkSendError";
|
|
51
|
+
if (status !== undefined)
|
|
52
|
+
this.status = status;
|
|
53
|
+
this.retriable = status === undefined || status >= 500 || status === 429;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.SinkSendError = SinkSendError;
|
|
28
57
|
function createDirectSink(config) {
|
|
29
58
|
const base = config.supabaseUrl.replace(/\/$/, "");
|
|
30
59
|
const fetchFn = config.fetchFn ?? fetch;
|
|
@@ -32,23 +61,35 @@ function createDirectSink(config) {
|
|
|
32
61
|
mode: "direct",
|
|
33
62
|
async send(batch) {
|
|
34
63
|
for (const write of (0, ingest_protocol_1.stampBatch)(batch, config.tenancy)) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetchFn(
|
|
67
|
+
// S5: the upsert names a real unique constraint. A single-argument
|
|
68
|
+
// upsert targets the primary key and 23505s later.
|
|
69
|
+
`${base}/rest/v1/${write.table}?on_conflict=${encodeURIComponent(write.onConflict)}`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
"content-type": "application/json",
|
|
73
|
+
apikey: config.serviceRoleKey,
|
|
74
|
+
authorization: `Bearer ${config.serviceRoleKey}`,
|
|
75
|
+
prefer: write.merge
|
|
76
|
+
? "resolution=merge-duplicates,return=minimal"
|
|
77
|
+
: "resolution=ignore-duplicates,return=minimal",
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify(write.rows),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Network-level throw — no status, so TRANSIENT (the exporter retries
|
|
84
|
+
// forever). The raw error can carry the control-plane host, so it is
|
|
85
|
+
// NOT echoed (C2); the table + "network error" is the whole message.
|
|
86
|
+
throw new SinkSendError(`sink POST ${write.table} → network error`, undefined);
|
|
87
|
+
}
|
|
50
88
|
if (!response.ok) {
|
|
51
|
-
|
|
89
|
+
// Status + table only — never the PostgREST body (it carries the
|
|
90
|
+
// control-plane host and, on some failures, header fragments; C2).
|
|
91
|
+
// The status is what the exporter classifies on (spec §2.3.1).
|
|
92
|
+
throw new SinkSendError(`sink POST ${write.table} → ${response.status}`, response.status);
|
|
52
93
|
}
|
|
53
94
|
}
|
|
54
95
|
},
|
|
@@ -62,22 +103,37 @@ function createApiSink(config) {
|
|
|
62
103
|
return {
|
|
63
104
|
mode: "api",
|
|
64
105
|
async send(batch) {
|
|
65
|
-
|
|
106
|
+
// Trust the version the EXPORTER set (spec §1.3): it announces v2 only
|
|
107
|
+
// when the artifact namespace is active, v1 otherwise. Overriding it here
|
|
108
|
+
// would force every batch to the max version and break the v1-byte-
|
|
109
|
+
// identical guarantee (and 422 v1 rigs against a strict old server).
|
|
110
|
+
const json = JSON.stringify(batch);
|
|
66
111
|
const compress = Buffer.byteLength(json) >= GZIP_MIN_BYTES;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
let response;
|
|
113
|
+
try {
|
|
114
|
+
response = await fetchFn(url, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"content-type": "application/json",
|
|
118
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
119
|
+
...(compress ? { "content-encoding": "gzip" } : {}),
|
|
120
|
+
},
|
|
121
|
+
body: compress ? (0, node_zlib_1.gzipSync)(json) : json,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Network-level throw — no status ⇒ TRANSIENT (retry forever). The raw
|
|
126
|
+
// error can carry the endpoint host, so it is not echoed (C2).
|
|
127
|
+
throw new SinkSendError("ingest POST → network error", undefined);
|
|
128
|
+
}
|
|
76
129
|
if (!response.ok) {
|
|
77
|
-
//
|
|
78
|
-
// failure text, and an exporter's
|
|
79
|
-
// a secret to reach someone else's
|
|
80
|
-
|
|
130
|
+
// Status only — never the response body. The key never appears in the
|
|
131
|
+
// message (C2's leak gate covers failure text), and an exporter's
|
|
132
|
+
// warnings are the likeliest place for a secret to reach someone else's
|
|
133
|
+
// log aggregator. The status is the exporter's transient-vs-poison
|
|
134
|
+
// signal: the server sends 422 for a deterministic control-plane
|
|
135
|
+
// rejection, 503 for a transient one (spec §2.2/§2.3.1).
|
|
136
|
+
throw new SinkSendError(`ingest POST → ${response.status}`, response.status);
|
|
81
137
|
}
|
|
82
138
|
if (config.onOutcome !== undefined) {
|
|
83
139
|
const outcome = (await response.json().catch(() => null));
|
|
@@ -87,3 +143,38 @@ function createApiSink(config) {
|
|
|
87
143
|
},
|
|
88
144
|
};
|
|
89
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Build the telemetry transport for the configured export mode. One place, so
|
|
148
|
+
* the run-loop exporter and the load-error reporter build it the same way —
|
|
149
|
+
* telemetry-ingestion-boundary.md §4's "one loop, two transports". (Lived in
|
|
150
|
+
* index.ts until Phase D; moved here — its natural home — to keep that file
|
|
151
|
+
* under the LOC cap.)
|
|
152
|
+
*/
|
|
153
|
+
function buildTelemetrySink(exportConfig, sourceId) {
|
|
154
|
+
return exportConfig.mode === "api"
|
|
155
|
+
? createApiSink({
|
|
156
|
+
ingestUrl: exportConfig.ingestUrl,
|
|
157
|
+
apiKey: exportConfig.apiKey,
|
|
158
|
+
// A batch the server partly refused comes back 200. Without this, a
|
|
159
|
+
// customer whose rows are being dropped would find out by noticing a
|
|
160
|
+
// gap in a chart weeks later.
|
|
161
|
+
onOutcome: (outcome) => {
|
|
162
|
+
const dropped = Object.values(outcome.dropped).reduce((a, b) => a + b, 0);
|
|
163
|
+
if (dropped === 0)
|
|
164
|
+
return;
|
|
165
|
+
const reasons = Object.entries(outcome.reasons)
|
|
166
|
+
.map(([reason, count]) => `${reason}×${count}`)
|
|
167
|
+
.join(", ");
|
|
168
|
+
console.warn(`[modelrig exporter] ingest dropped ${dropped} row(s): ${reasons}`);
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
: createDirectSink({
|
|
172
|
+
supabaseUrl: exportConfig.supabaseUrl,
|
|
173
|
+
serviceRoleKey: exportConfig.serviceRoleKey,
|
|
174
|
+
tenancy: {
|
|
175
|
+
org_id: exportConfig.orgId ?? constants_1.HOUSE_ORG_ID,
|
|
176
|
+
project_id: exportConfig.projectId ?? null,
|
|
177
|
+
source_id: sourceId,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* separate handle. ExportableTelemetryStore MUST NOT gain capture accessors;
|
|
10
10
|
* the export-isolation tests are the tripwire.
|
|
11
11
|
*/
|
|
12
|
-
import type { EnvelopeRow, FeedbackRow, TelemetryRow, TelemetryStore } from "../types/telemetry";
|
|
12
|
+
import type { EnvelopeRow, FeedbackRow, RunRow, StepRow, TelemetryRow, TelemetryStore } from "../types/telemetry";
|
|
13
|
+
import { type EndpointHealth } from "./health";
|
|
13
14
|
/**
|
|
14
15
|
* Superset of the TelemetryStore contract with the enumeration hooks the
|
|
15
16
|
* Supabase exporter needs (rowid-watermarked reads + local export state).
|
|
@@ -27,12 +28,38 @@ export interface ExportableTelemetryStore extends TelemetryStore {
|
|
|
27
28
|
rowid: number;
|
|
28
29
|
row: FeedbackRow;
|
|
29
30
|
}>;
|
|
31
|
+
/** Record a run (E2 chunk A) — run.start/run.end. Upsert: run.end overwrites
|
|
32
|
+
* the running projection with its terminal status/ended_at. */
|
|
33
|
+
recordRun(row: RunRow): void;
|
|
34
|
+
/** Record one step attempt (E2 chunk A) — a fact, insert-or-ignore on the PK. */
|
|
35
|
+
recordStep(row: StepRow): void;
|
|
36
|
+
/** Runs past the watermark, oldest first (E2 exporter stream). */
|
|
37
|
+
readRunsAfter(afterRowid: number, limit: number): Array<{
|
|
38
|
+
rowid: number;
|
|
39
|
+
row: RunRow;
|
|
40
|
+
}>;
|
|
41
|
+
/** Steps past the watermark, oldest first (E2 exporter stream). */
|
|
42
|
+
readStepsAfter(afterRowid: number, limit: number): Array<{
|
|
43
|
+
rowid: number;
|
|
44
|
+
row: StepRow;
|
|
45
|
+
}>;
|
|
46
|
+
/** The ground-truth inference id for a (run, step) — the LATEST attempt with a
|
|
47
|
+
* non-null inference_id, or null (E4 chunk A: bake-off rehydration resolves an
|
|
48
|
+
* eval case's source step to the inference that keys its captured input). */
|
|
49
|
+
getStepInferenceId(runId: string, stepKey: string): string | null;
|
|
30
50
|
/** Newest-first local feedback read (console/dev inspection). */
|
|
31
51
|
listFeedback(limit: number): FeedbackRow[];
|
|
32
52
|
listEnvelopes(): EnvelopeRow[];
|
|
33
53
|
/** Local exporter watermark (per source instance), 0 when unset. */
|
|
34
54
|
getExportWatermark(sourceId: string): number;
|
|
35
55
|
setExportWatermark(sourceId: string, lastRowid: number): void;
|
|
56
|
+
/**
|
|
57
|
+
* Observed-health aggregate (G-4) — per (provider, model) over the last
|
|
58
|
+
* `windowMinutes`: p50/p90/p99 latency + TTFB and recent error-rate, gated to
|
|
59
|
+
* `null` below `minN`. Advisory operational health for routing + the console;
|
|
60
|
+
* NEVER an input to RigIndex rank (spec §4.4).
|
|
61
|
+
*/
|
|
62
|
+
readEndpointHealth(windowMinutes: number, minN: number): EndpointHealth[];
|
|
36
63
|
}
|
|
37
64
|
export declare function openTelemetryStore(dbPath: string): ExportableTelemetryStore;
|
|
38
65
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,MAAM,EACN,OAAO,EACP,YAAY,EACZ,cAAc,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAA2B,KAAK,cAAc,EAAuB,MAAM,UAAU,CAAC;AAE7F;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC9D,qEAAqE;IACrE,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACpG,uEAAuE;IACvE,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACjG;mEAC+D;IAC/D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxF,mEAAmE;IACnE,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1F;;iFAE6E;IAC7E,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClE,iEAAiE;IACjE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;IAC3C,aAAa,IAAI,WAAW,EAAE,CAAC;IAC/B,oEAAoE;IACpE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;CAC3E;AA6RD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CA8V3E"}
|
package/dist/telemetry/store.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.openTelemetryStore = openTelemetryStore;
|
|
|
18
18
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
19
19
|
const node_fs_1 = require("node:fs");
|
|
20
20
|
const node_path_1 = require("node:path");
|
|
21
|
+
const health_1 = require("./health");
|
|
21
22
|
const SCHEMA_SQL = `
|
|
22
23
|
CREATE TABLE IF NOT EXISTS inferences (
|
|
23
24
|
id TEXT PRIMARY KEY,
|
|
@@ -40,9 +41,18 @@ CREATE TABLE IF NOT EXISTS inferences (
|
|
|
40
41
|
fingerprint_cell TEXT,
|
|
41
42
|
served_variant TEXT,
|
|
42
43
|
repaired_by TEXT,
|
|
43
|
-
pricing_missing INTEGER NOT NULL DEFAULT 0
|
|
44
|
+
pricing_missing INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
-- v10 (WS-A cache accounting): cache-write buckets + cache-key/prefix
|
|
46
|
+
-- substrate. Fresh here; v9-and-earlier gain them via the ALTER ladder below.
|
|
47
|
+
tokens_cache_write INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
tokens_cache_write_1h INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
cache_key TEXT,
|
|
50
|
+
prefix_fingerprint TEXT
|
|
44
51
|
);
|
|
45
52
|
CREATE INDEX IF NOT EXISTS idx_inferences_route_ts ON inferences (route, ts);
|
|
53
|
+
-- v8 (G-4 observed-health): per-endpoint windowed aggregation filters by
|
|
54
|
+
-- (provider, model, ts). Index-only; no data change.
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_inferences_provider_model_ts ON inferences (provider, model, ts);
|
|
46
56
|
|
|
47
57
|
CREATE TABLE IF NOT EXISTS captures (
|
|
48
58
|
inference_id TEXT PRIMARY KEY,
|
|
@@ -84,7 +94,77 @@ CREATE TABLE IF NOT EXISTS feedback (
|
|
|
84
94
|
);
|
|
85
95
|
CREATE INDEX IF NOT EXISTS idx_feedback_inference ON feedback (inference_id);
|
|
86
96
|
CREATE INDEX IF NOT EXISTS idx_feedback_episode ON feedback (episode_key);
|
|
97
|
+
|
|
98
|
+
-- v9 (Expansion E2 chunk A): the execution graph's local buffer.
|
|
99
|
+
--
|
|
100
|
+
-- runs is a PROJECTION (PK on id): run.start inserts a running row and run.end
|
|
101
|
+
-- UPDATEs it to a terminal status in place -- a run is genuinely mutable
|
|
102
|
+
-- (start -> end). A rowid watermark cannot re-export an updated row (UPDATE does
|
|
103
|
+
-- not move rowid), so the exporter waters off export_seq, a monotonic counter
|
|
104
|
+
-- bumped on EVERY write (insert or update). run.end bumps it, so the terminal
|
|
105
|
+
-- state re-exports; and because there is one row per id, a batch never carries
|
|
106
|
+
-- the same (org_id,id) twice -- which a merge=true upsert (DO UPDATE) would
|
|
107
|
+
-- reject with "cannot affect row a second time". The server merge collapses
|
|
108
|
+
-- the two exports into the one mutable row.
|
|
109
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
pipeline TEXT NOT NULL,
|
|
112
|
+
pipeline_version TEXT,
|
|
113
|
+
environment TEXT,
|
|
114
|
+
episode_key TEXT,
|
|
115
|
+
started_at TEXT NOT NULL,
|
|
116
|
+
ended_at TEXT,
|
|
117
|
+
status TEXT NOT NULL CHECK (status IN ('running','succeeded','failed','abandoned')),
|
|
118
|
+
export_seq INTEGER NOT NULL
|
|
119
|
+
);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_runs_export_seq ON runs (export_seq);
|
|
121
|
+
|
|
122
|
+
-- steps are FACTS -- one row per attempt. The composite PK makes a re-emit a
|
|
123
|
+
-- harmless INSERT-OR-IGNORE (server is merge=false too). rowid still exists (not
|
|
124
|
+
-- WITHOUT ROWID), so the watermark read works identically to the other streams.
|
|
125
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
126
|
+
run_id TEXT NOT NULL,
|
|
127
|
+
step_key TEXT NOT NULL,
|
|
128
|
+
attempt_no INTEGER NOT NULL,
|
|
129
|
+
task TEXT,
|
|
130
|
+
provenance TEXT NOT NULL CHECK (provenance IN ('ground-truth','attested')),
|
|
131
|
+
inference_id TEXT,
|
|
132
|
+
span_trace_id TEXT,
|
|
133
|
+
span_id TEXT,
|
|
134
|
+
started_at TEXT NOT NULL,
|
|
135
|
+
ended_at TEXT,
|
|
136
|
+
failure_class TEXT,
|
|
137
|
+
PRIMARY KEY (run_id, step_key, attempt_no)
|
|
138
|
+
);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_steps_run ON steps (run_id);
|
|
87
140
|
`;
|
|
141
|
+
function toRunRow(r) {
|
|
142
|
+
return {
|
|
143
|
+
id: r.id,
|
|
144
|
+
pipeline: r.pipeline,
|
|
145
|
+
pipelineVersion: r.pipeline_version,
|
|
146
|
+
environment: r.environment,
|
|
147
|
+
episodeKey: r.episode_key,
|
|
148
|
+
startedAt: r.started_at,
|
|
149
|
+
endedAt: r.ended_at,
|
|
150
|
+
status: r.status,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function toStepRow(r) {
|
|
154
|
+
return {
|
|
155
|
+
runId: r.run_id,
|
|
156
|
+
stepKey: r.step_key,
|
|
157
|
+
attemptNo: r.attempt_no,
|
|
158
|
+
task: r.task,
|
|
159
|
+
provenance: r.provenance,
|
|
160
|
+
inferenceId: r.inference_id,
|
|
161
|
+
spanTraceId: r.span_trace_id,
|
|
162
|
+
spanId: r.span_id,
|
|
163
|
+
startedAt: r.started_at,
|
|
164
|
+
endedAt: r.ended_at,
|
|
165
|
+
failureClass: r.failure_class,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
88
168
|
function toFeedbackRow(r) {
|
|
89
169
|
return {
|
|
90
170
|
id: r.id,
|
|
@@ -127,6 +207,12 @@ function toTelemetryRow(r) {
|
|
|
127
207
|
servedVariant: r.served_variant,
|
|
128
208
|
repairedBy: r.repaired_by,
|
|
129
209
|
pricingMissing: r.pricing_missing !== 0,
|
|
210
|
+
// `?? 0` / `?? null`: a v9-or-earlier database returns undefined for a v10
|
|
211
|
+
// ALTER column, and those rows genuinely predate cache accounting (WS-A).
|
|
212
|
+
tokensCacheWrite: r.tokens_cache_write ?? 0,
|
|
213
|
+
tokensCacheWrite1h: r.tokens_cache_write_1h ?? 0,
|
|
214
|
+
cacheKey: r.cache_key ?? null,
|
|
215
|
+
prefixFingerprint: r.prefix_fingerprint ?? null,
|
|
130
216
|
};
|
|
131
217
|
}
|
|
132
218
|
function toEnvelopeRow(r) {
|
|
@@ -201,15 +287,48 @@ function openTelemetryStore(dbPath) {
|
|
|
201
287
|
}
|
|
202
288
|
db.pragma("user_version = 7");
|
|
203
289
|
}
|
|
290
|
+
// v8 (G-4 observed-health): idx_inferences_provider_model_ts — index-only, no
|
|
291
|
+
// data change. SCHEMA_SQL's CREATE INDEX IF NOT EXISTS handles fresh AND
|
|
292
|
+
// migrating databases identically (it ran above), so the bump only stamps the
|
|
293
|
+
// version. Kept explicit rather than folded into v5's bare-stamp branch so the
|
|
294
|
+
// migration ladder reads one rung per schema epoch.
|
|
295
|
+
if (db.pragma("user_version", { simple: true }) < 8) {
|
|
296
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_inferences_provider_model_ts ON inferences (provider, model, ts)");
|
|
297
|
+
db.pragma("user_version = 8");
|
|
298
|
+
}
|
|
299
|
+
// v9 (Expansion E2 chunk A): runs + steps local outbox. Purely additive —
|
|
300
|
+
// SCHEMA_SQL's CREATE TABLE IF NOT EXISTS handles fresh AND migrating
|
|
301
|
+
// databases identically (it ran above), so the bump only stamps the version.
|
|
302
|
+
// A rig that never enables the artifact namespace simply never writes to them.
|
|
303
|
+
if (db.pragma("user_version", { simple: true }) < 9) {
|
|
304
|
+
db.pragma("user_version = 9");
|
|
305
|
+
}
|
|
306
|
+
// v10 (WS-A cache accounting): cache-write buckets + cache-key/prefix
|
|
307
|
+
// substrate. Additive; old rows read 0 / null (true of them — recorded before
|
|
308
|
+
// caching was accounted). Same ALTER pattern as v2–v6, folded over a list.
|
|
309
|
+
if (db.pragma("user_version", { simple: true }) < 10) {
|
|
310
|
+
const cols = db.prepare("PRAGMA table_info(inferences)").all();
|
|
311
|
+
const add = (name, ddl) => {
|
|
312
|
+
if (!cols.some((c) => c.name === name))
|
|
313
|
+
db.exec(`ALTER TABLE inferences ADD COLUMN ${ddl}`);
|
|
314
|
+
};
|
|
315
|
+
add("tokens_cache_write", "tokens_cache_write INTEGER NOT NULL DEFAULT 0");
|
|
316
|
+
add("tokens_cache_write_1h", "tokens_cache_write_1h INTEGER NOT NULL DEFAULT 0");
|
|
317
|
+
add("cache_key", "cache_key TEXT");
|
|
318
|
+
add("prefix_fingerprint", "prefix_fingerprint TEXT");
|
|
319
|
+
db.pragma("user_version = 10");
|
|
320
|
+
}
|
|
204
321
|
const insertInference = db.prepare(`
|
|
205
322
|
INSERT INTO inferences (
|
|
206
323
|
id, ts, route, route_version, provider, model, requested_tier, served_tier,
|
|
207
324
|
tokens_in, tokens_out, tokens_cached, cost_estimate_usd, latency_ms, ttfb_ms,
|
|
208
|
-
failure_class, attempts_json, tags_json, fingerprint_cell, served_variant, repaired_by, pricing_missing
|
|
325
|
+
failure_class, attempts_json, tags_json, fingerprint_cell, served_variant, repaired_by, pricing_missing,
|
|
326
|
+
tokens_cache_write, tokens_cache_write_1h, cache_key, prefix_fingerprint
|
|
209
327
|
) VALUES (
|
|
210
328
|
@id, @ts, @route, @route_version, @provider, @model, @requested_tier, @served_tier,
|
|
211
329
|
@tokens_in, @tokens_out, @tokens_cached, @cost_estimate_usd, @latency_ms, @ttfb_ms,
|
|
212
|
-
@failure_class, @attempts_json, @tags_json, @fingerprint_cell, @served_variant, @repaired_by, @pricing_missing
|
|
330
|
+
@failure_class, @attempts_json, @tags_json, @fingerprint_cell, @served_variant, @repaired_by, @pricing_missing,
|
|
331
|
+
@tokens_cache_write, @tokens_cache_write_1h, @cache_key, @prefix_fingerprint
|
|
213
332
|
)
|
|
214
333
|
`);
|
|
215
334
|
const selectByTag = db.prepare(`
|
|
@@ -236,6 +355,32 @@ function openTelemetryStore(dbPath) {
|
|
|
236
355
|
SET spent_usd = spent_usd + ?,
|
|
237
356
|
status = CASE WHEN spent_usd + ? >= budget_usd THEN 'exhausted' ELSE status END
|
|
238
357
|
WHERE envelope_key = ?
|
|
358
|
+
`);
|
|
359
|
+
// Upsert the run projection and bump export_seq on every write, so run.end's
|
|
360
|
+
// terminal state re-exports past the `:runs` watermark. better-sqlite3 is
|
|
361
|
+
// synchronous, so the MAX(export_seq)+1 read is race-free.
|
|
362
|
+
const nextRunSeq = db.prepare("SELECT COALESCE(MAX(export_seq), 0) + 1 AS n FROM runs");
|
|
363
|
+
const upsertRun = db.prepare(`
|
|
364
|
+
INSERT INTO runs (id, pipeline, pipeline_version, environment, episode_key, started_at, ended_at, status, export_seq)
|
|
365
|
+
VALUES (@id, @pipeline, @pipeline_version, @environment, @episode_key, @started_at, @ended_at, @status, @export_seq)
|
|
366
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
367
|
+
pipeline = excluded.pipeline,
|
|
368
|
+
pipeline_version = excluded.pipeline_version,
|
|
369
|
+
environment = excluded.environment,
|
|
370
|
+
episode_key = excluded.episode_key,
|
|
371
|
+
started_at = excluded.started_at,
|
|
372
|
+
ended_at = excluded.ended_at,
|
|
373
|
+
status = excluded.status,
|
|
374
|
+
export_seq = excluded.export_seq
|
|
375
|
+
`);
|
|
376
|
+
const insertStep = db.prepare(`
|
|
377
|
+
INSERT OR IGNORE INTO steps (
|
|
378
|
+
run_id, step_key, attempt_no, task, provenance, inference_id, span_trace_id, span_id,
|
|
379
|
+
started_at, ended_at, failure_class
|
|
380
|
+
) VALUES (
|
|
381
|
+
@run_id, @step_key, @attempt_no, @task, @provenance, @inference_id, @span_trace_id, @span_id,
|
|
382
|
+
@started_at, @ended_at, @failure_class
|
|
383
|
+
)
|
|
239
384
|
`);
|
|
240
385
|
return {
|
|
241
386
|
record(row) {
|
|
@@ -261,6 +406,13 @@ function openTelemetryStore(dbPath) {
|
|
|
261
406
|
served_variant: row.servedVariant,
|
|
262
407
|
repaired_by: row.repairedBy,
|
|
263
408
|
pricing_missing: row.pricingMissing ? 1 : 0,
|
|
409
|
+
// `?? 0` / `?? null`: the fields are optional on TelemetryRow, and
|
|
410
|
+
// better-sqlite3 rejects an `undefined` bound param — a row that omits
|
|
411
|
+
// them is a zero-write / no-key row (fail-closed default).
|
|
412
|
+
tokens_cache_write: row.tokensCacheWrite ?? 0,
|
|
413
|
+
tokens_cache_write_1h: row.tokensCacheWrite1h ?? 0,
|
|
414
|
+
cache_key: row.cacheKey ?? null,
|
|
415
|
+
prefix_fingerprint: row.prefixFingerprint ?? null,
|
|
264
416
|
});
|
|
265
417
|
},
|
|
266
418
|
recordFeedback(row) {
|
|
@@ -283,6 +435,57 @@ function openTelemetryStore(dbPath) {
|
|
|
283
435
|
.all(afterRowid, limit);
|
|
284
436
|
return rows.map((r) => ({ rowid: r.__rowid, row: toFeedbackRow(r) }));
|
|
285
437
|
},
|
|
438
|
+
recordRun(row) {
|
|
439
|
+
const seq = nextRunSeq.get().n;
|
|
440
|
+
upsertRun.run({
|
|
441
|
+
id: row.id,
|
|
442
|
+
pipeline: row.pipeline,
|
|
443
|
+
pipeline_version: row.pipelineVersion,
|
|
444
|
+
environment: row.environment,
|
|
445
|
+
episode_key: row.episodeKey,
|
|
446
|
+
started_at: row.startedAt,
|
|
447
|
+
ended_at: row.endedAt,
|
|
448
|
+
status: row.status,
|
|
449
|
+
export_seq: seq,
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
recordStep(row) {
|
|
453
|
+
insertStep.run({
|
|
454
|
+
run_id: row.runId,
|
|
455
|
+
step_key: row.stepKey,
|
|
456
|
+
attempt_no: row.attemptNo,
|
|
457
|
+
task: row.task,
|
|
458
|
+
provenance: row.provenance,
|
|
459
|
+
inference_id: row.inferenceId,
|
|
460
|
+
span_trace_id: row.spanTraceId,
|
|
461
|
+
span_id: row.spanId,
|
|
462
|
+
started_at: row.startedAt,
|
|
463
|
+
ended_at: row.endedAt,
|
|
464
|
+
failure_class: row.failureClass,
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
readRunsAfter(afterSeq, limit) {
|
|
468
|
+
// Watermark is export_seq (bumped on every write), NOT rowid — a run.end
|
|
469
|
+
// UPDATE keeps the rowid but advances export_seq, so its terminal state
|
|
470
|
+
// re-exports. The exporter treats the returned handle opaquely.
|
|
471
|
+
const rows = db
|
|
472
|
+
.prepare("SELECT export_seq AS __seq, * FROM runs WHERE export_seq > ? ORDER BY export_seq LIMIT ?")
|
|
473
|
+
.all(afterSeq, limit);
|
|
474
|
+
return rows.map((r) => ({ rowid: r.__seq, row: toRunRow(r) }));
|
|
475
|
+
},
|
|
476
|
+
readStepsAfter(afterRowid, limit) {
|
|
477
|
+
const rows = db
|
|
478
|
+
.prepare("SELECT rowid AS __rowid, * FROM steps WHERE rowid > ? ORDER BY rowid LIMIT ?")
|
|
479
|
+
.all(afterRowid, limit);
|
|
480
|
+
return rows.map((r) => ({ rowid: r.__rowid, row: toStepRow(r) }));
|
|
481
|
+
},
|
|
482
|
+
getStepInferenceId(runId, stepKey) {
|
|
483
|
+
const r = db
|
|
484
|
+
.prepare("SELECT inference_id FROM steps WHERE run_id = ? AND step_key = ? AND inference_id IS NOT NULL " +
|
|
485
|
+
"ORDER BY attempt_no DESC LIMIT 1")
|
|
486
|
+
.get(runId, stepKey);
|
|
487
|
+
return r === undefined ? null : r.inference_id;
|
|
488
|
+
},
|
|
286
489
|
listFeedback(limit) {
|
|
287
490
|
const rows = db
|
|
288
491
|
.prepare("SELECT * FROM feedback ORDER BY ts DESC, rowid DESC LIMIT ?")
|
|
@@ -325,6 +528,18 @@ function openTelemetryStore(dbPath) {
|
|
|
325
528
|
db.prepare(`INSERT INTO export_state (source_id, last_rowid) VALUES (?, ?)
|
|
326
529
|
ON CONFLICT (source_id) DO UPDATE SET last_rowid = excluded.last_rowid`).run(sourceId, lastRowid);
|
|
327
530
|
},
|
|
531
|
+
readEndpointHealth(windowMinutes, minN) {
|
|
532
|
+
// Capture `now` once so the SQL prefilter and the in-memory window agree.
|
|
533
|
+
// ts is ISO-8601 UTC (…Z), so lexical >= is chronological >= — the prefilter
|
|
534
|
+
// is exact, and aggregateEndpointHealth re-checks the window defensively.
|
|
535
|
+
const now = Date.now();
|
|
536
|
+
const windowStartIso = new Date(now - windowMinutes * 60000).toISOString();
|
|
537
|
+
const rows = db
|
|
538
|
+
.prepare(`SELECT provider, model, ts, latency_ms, ttfb_ms, failure_class
|
|
539
|
+
FROM inferences WHERE ts >= ?`)
|
|
540
|
+
.all(windowStartIso);
|
|
541
|
+
return (0, health_1.aggregateEndpointHealth)(rows, { windowMinutes, minN, now });
|
|
542
|
+
},
|
|
328
543
|
close() {
|
|
329
544
|
db.close();
|
|
330
545
|
},
|
|
@@ -24,12 +24,22 @@ export declare const API_KEY_SECRET_BYTES = 32;
|
|
|
24
24
|
/** base64url length of API_KEY_SECRET_BYTES — the minimum a real key's body has. */
|
|
25
25
|
export declare const API_KEY_BODY_MIN_LENGTH = 43;
|
|
26
26
|
/**
|
|
27
|
-
* The granted authorities. Mirrors migration 00020's CHECK constraint
|
|
28
|
-
* test pins that the two agree, because a
|
|
29
|
-
* there is a key that cannot be written, and one
|
|
30
|
-
* here is an authority nothing enforces.
|
|
27
|
+
* The granted authorities. Mirrors migration 00020's CHECK constraint (widened
|
|
28
|
+
* by 00033 for content custody) — a test pins that the two agree, because a
|
|
29
|
+
* scope that exists here and not there is a key that cannot be written, and one
|
|
30
|
+
* that exists there and not here is an authority nothing enforces.
|
|
31
|
+
*
|
|
32
|
+
* `artifact-write`/`artifact-read` (E2-C, 00033): request a content upload grant
|
|
33
|
+
* / mint a content download URL. Both are ordinary (non-elevated) machine
|
|
34
|
+
* grants — they move an org's own artifact bytes, they do not spend money or
|
|
35
|
+
* approve swaps (contrast decision/oracle-act, gated to admin/owner minters).
|
|
36
|
+
*
|
|
37
|
+
* `task-settings` (settings-plane, 00043): PATCH a task's governance settings
|
|
38
|
+
* (capture / artifacts / content / scrub). Held by a coding agent flipping
|
|
39
|
+
* settings for the user. READING settings needs no scope — any org-scoped key
|
|
40
|
+
* may read, since settings ride the already-readable task row.
|
|
31
41
|
*/
|
|
32
|
-
export declare const API_KEY_SCOPES: readonly ["ingest", "query", "feedback", "decision", "oracle-act"];
|
|
42
|
+
export declare const API_KEY_SCOPES: readonly ["ingest", "query", "feedback", "decision", "oracle-act", "artifact-write", "artifact-read", "task-settings"];
|
|
33
43
|
export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
|
|
34
44
|
/** sha256 of the WHOLE presented string, prefix included — so a caller cannot
|
|
35
45
|
* strip the prefix and land on a different row. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-key-format.d.ts","sourceRoot":"","sources":["../../src/tenancy/api-key-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;GAGG;AACH,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC;;6EAE6E;AAC7E,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,oFAAoF;AACpF,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C
|
|
1
|
+
{"version":3,"file":"api-key-format.d.ts","sourceRoot":"","sources":["../../src/tenancy/api-key-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;GAGG;AACH,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC;;6EAE6E;AAC7E,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,oFAAoF;AACpF,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,wHASjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;mDACmD;AACnD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI1D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,EAAE,CAIvD;AAED;gEACgE;AAChE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,QAAQ,EAAE,WAAW,GAAG,OAAO,CAExF"}
|
|
@@ -33,12 +33,31 @@ exports.API_KEY_SECRET_BYTES = 32;
|
|
|
33
33
|
/** base64url length of API_KEY_SECRET_BYTES — the minimum a real key's body has. */
|
|
34
34
|
exports.API_KEY_BODY_MIN_LENGTH = 43;
|
|
35
35
|
/**
|
|
36
|
-
* The granted authorities. Mirrors migration 00020's CHECK constraint
|
|
37
|
-
* test pins that the two agree, because a
|
|
38
|
-
* there is a key that cannot be written, and one
|
|
39
|
-
* here is an authority nothing enforces.
|
|
36
|
+
* The granted authorities. Mirrors migration 00020's CHECK constraint (widened
|
|
37
|
+
* by 00033 for content custody) — a test pins that the two agree, because a
|
|
38
|
+
* scope that exists here and not there is a key that cannot be written, and one
|
|
39
|
+
* that exists there and not here is an authority nothing enforces.
|
|
40
|
+
*
|
|
41
|
+
* `artifact-write`/`artifact-read` (E2-C, 00033): request a content upload grant
|
|
42
|
+
* / mint a content download URL. Both are ordinary (non-elevated) machine
|
|
43
|
+
* grants — they move an org's own artifact bytes, they do not spend money or
|
|
44
|
+
* approve swaps (contrast decision/oracle-act, gated to admin/owner minters).
|
|
45
|
+
*
|
|
46
|
+
* `task-settings` (settings-plane, 00043): PATCH a task's governance settings
|
|
47
|
+
* (capture / artifacts / content / scrub). Held by a coding agent flipping
|
|
48
|
+
* settings for the user. READING settings needs no scope — any org-scoped key
|
|
49
|
+
* may read, since settings ride the already-readable task row.
|
|
40
50
|
*/
|
|
41
|
-
exports.API_KEY_SCOPES = [
|
|
51
|
+
exports.API_KEY_SCOPES = [
|
|
52
|
+
"ingest",
|
|
53
|
+
"query",
|
|
54
|
+
"feedback",
|
|
55
|
+
"decision",
|
|
56
|
+
"oracle-act",
|
|
57
|
+
"artifact-write",
|
|
58
|
+
"artifact-read",
|
|
59
|
+
"task-settings",
|
|
60
|
+
];
|
|
42
61
|
/** sha256 of the WHOLE presented string, prefix included — so a caller cannot
|
|
43
62
|
* strip the prefix and land on a different row. */
|
|
44
63
|
function hashApiKeySecret(secret) {
|