modelrig 0.0.1 → 0.1.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/LICENSE +201 -0
- package/README.md +115 -8
- package/dist/adapters/anthropic.d.ts +46 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +202 -0
- package/dist/adapters/deepseek.d.ts +41 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +163 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +222 -0
- package/dist/adapters/grok.d.ts +44 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +168 -0
- package/dist/adapters/openai-compat.d.ts +74 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +187 -0
- package/dist/adapters/openai.d.ts +49 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +222 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/samples.d.ts +62 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +128 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +572 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/init/finder.d.ts +79 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +297 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +126 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +192 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +72 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +120 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +41 -0
- package/dist/registry/pricing.d.ts +19 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +69 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +26 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +436 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +26 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +81 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +41 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +0 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +56 -0
- package/dist/savings/emit.d.ts +25 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +75 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +44 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +87 -0
- package/dist/savings/swaps-client.d.ts +41 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +81 -0
- package/dist/telemetry/capture.d.ts +28 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +74 -0
- package/dist/telemetry/exporter.d.ts +62 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +233 -0
- package/dist/telemetry/ingest-protocol.d.ts +161 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +163 -0
- package/dist/telemetry/sinks.d.ts +54 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +89 -0
- package/dist/telemetry/store.d.ts +38 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +332 -0
- package/dist/telemetry/supabase-exporter.d.ts +64 -0
- package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
- package/dist/telemetry/supabase-exporter.js +211 -0
- package/dist/tenancy/api-key-format.d.ts +67 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +91 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +79 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +20 -0
- package/dist/types/route.d.ts +127 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +89 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +66 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +94 -0
- package/dist/watch/proposals.d.ts +73 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +169 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +40 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +44 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +50 -14
- package/index.js +0 -3
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Matrix aggregation job — Phase 4 WS6 (plan §4). Pure fold: telemetry
|
|
4
|
+
* rows → priors cells keyed (fingerprint_cell × model), each carrying
|
|
5
|
+
* outcome stats. Writes go to priors_cells ONLY (deny-all RLS, no UI, no
|
|
6
|
+
* cross-account use — standing decision 7; the gate stays off).
|
|
7
|
+
*
|
|
8
|
+
* Contribution discipline (spec WS6): the flag decides whether a cell may
|
|
9
|
+
* ever aggregate beyond its own account — respected here in code even
|
|
10
|
+
* though today's deployment is single-tenant. Non-contributing sources
|
|
11
|
+
* produce "account"-scoped cells, full stop.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.aggregationScope = aggregationScope;
|
|
15
|
+
exports.aggregatePairwiseFromTelemetry = aggregatePairwiseFromTelemetry;
|
|
16
|
+
exports.aggregatePriors = aggregatePriors;
|
|
17
|
+
exports.writePriorsCells = writePriorsCells;
|
|
18
|
+
/**
|
|
19
|
+
* Whether these rows may become community knowledge.
|
|
20
|
+
*
|
|
21
|
+
* One function so the two-veto rule has a single home and a name, rather than
|
|
22
|
+
* living as a `&&` inside a ternary inside a map.
|
|
23
|
+
*/
|
|
24
|
+
function aggregationScope(opts) {
|
|
25
|
+
const posture = opts.telemetryPosture ?? "pure_router";
|
|
26
|
+
return opts.contribution && posture === "optimized" ? "community" : "account";
|
|
27
|
+
}
|
|
28
|
+
/** A2: fold paired replay outcomes from telemetry. A pair exists where the
|
|
29
|
+
* SAME captureId was replayed through two different models within a cell.
|
|
30
|
+
* Sample outcome = did ANY attempt of the latest replay run for that
|
|
31
|
+
* (capture, model) conform — sample-level, retry-inclusive, matching the
|
|
32
|
+
* bake-off's own conformance notion. Single-source deployment folds
|
|
33
|
+
* sources: 1; cross-account distinct counts arrive with the community job. */
|
|
34
|
+
function aggregatePairwiseFromTelemetry(rows) {
|
|
35
|
+
const samples = new Map();
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
if (row.fingerprintCell === null || row.provider === "search")
|
|
38
|
+
continue;
|
|
39
|
+
let tags;
|
|
40
|
+
try {
|
|
41
|
+
tags = JSON.parse(row.tagsJson);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const sampleTag = tags.replay_capture;
|
|
47
|
+
const replayId = tags.replay;
|
|
48
|
+
if (typeof sampleTag !== "string" || typeof replayId !== "string")
|
|
49
|
+
continue;
|
|
50
|
+
const captureId = sampleTag.slice(replayId.length + 1);
|
|
51
|
+
if (captureId === "")
|
|
52
|
+
continue;
|
|
53
|
+
const modelKey = `${row.provider}/${row.model}`;
|
|
54
|
+
const key = `${row.fingerprintCell}|${captureId}|${modelKey}`;
|
|
55
|
+
const entry = samples.get(key) ?? {
|
|
56
|
+
key: { cell: row.fingerprintCell, captureId, modelKey },
|
|
57
|
+
runs: new Map(),
|
|
58
|
+
};
|
|
59
|
+
const run = entry.runs.get(replayId) ?? { replayId, latestTs: row.ts, conform: false };
|
|
60
|
+
if (row.ts > run.latestTs)
|
|
61
|
+
run.latestTs = row.ts;
|
|
62
|
+
if (row.failureClass === null)
|
|
63
|
+
run.conform = true;
|
|
64
|
+
entry.runs.set(replayId, run);
|
|
65
|
+
samples.set(key, entry);
|
|
66
|
+
}
|
|
67
|
+
// Latest run per sample → (cell, capture) → model outcomes.
|
|
68
|
+
const byCapture = new Map(); // cell|capture → model → conform
|
|
69
|
+
for (const { key, runs } of samples.values()) {
|
|
70
|
+
const latest = [...runs.values()].sort((a, b) => (a.latestTs < b.latestTs ? 1 : -1))[0];
|
|
71
|
+
const captureKey = `${key.cell}|${key.captureId}`;
|
|
72
|
+
const models = byCapture.get(captureKey) ?? new Map();
|
|
73
|
+
models.set(key.modelKey, latest.conform);
|
|
74
|
+
byCapture.set(captureKey, models);
|
|
75
|
+
}
|
|
76
|
+
const out = new Map();
|
|
77
|
+
function record(cell, a, b, outcome) {
|
|
78
|
+
const byModel = out.get(cell) ?? new Map();
|
|
79
|
+
const opponents = byModel.get(a) ?? new Map();
|
|
80
|
+
const prev = opponents.get(b) ?? { wins: 0, losses: 0, ties: 0, samples: 0, sources: 1 };
|
|
81
|
+
opponents.set(b, {
|
|
82
|
+
wins: prev.wins + (outcome === "win" ? 1 : 0),
|
|
83
|
+
losses: prev.losses + (outcome === "loss" ? 1 : 0),
|
|
84
|
+
ties: prev.ties + (outcome === "tie" ? 1 : 0),
|
|
85
|
+
samples: prev.samples + 1,
|
|
86
|
+
sources: 1,
|
|
87
|
+
});
|
|
88
|
+
byModel.set(a, opponents);
|
|
89
|
+
out.set(cell, byModel);
|
|
90
|
+
}
|
|
91
|
+
for (const [captureKey, models] of byCapture) {
|
|
92
|
+
if (models.size < 2)
|
|
93
|
+
continue;
|
|
94
|
+
const cell = captureKey.slice(0, captureKey.indexOf("|"));
|
|
95
|
+
const entries = [...models.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
96
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
97
|
+
for (let j = i + 1; j < entries.length; j += 1) {
|
|
98
|
+
const [modelA, conformA] = entries[i];
|
|
99
|
+
const [modelB, conformB] = entries[j];
|
|
100
|
+
if (conformA === conformB) {
|
|
101
|
+
record(cell, modelA, modelB, "tie");
|
|
102
|
+
record(cell, modelB, modelA, "tie");
|
|
103
|
+
}
|
|
104
|
+
else if (conformA) {
|
|
105
|
+
record(cell, modelA, modelB, "win");
|
|
106
|
+
record(cell, modelB, modelA, "loss");
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
record(cell, modelA, modelB, "loss");
|
|
110
|
+
record(cell, modelB, modelA, "win");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
function aggregatePriors(rows, opts) {
|
|
118
|
+
const byCell = new Map();
|
|
119
|
+
for (const row of rows) {
|
|
120
|
+
if (row.fingerprintCell === null)
|
|
121
|
+
continue;
|
|
122
|
+
const key = `${row.fingerprintCell} ${row.provider}/${row.model}`;
|
|
123
|
+
const agg = byCell.get(key) ?? { conform: 0, repaired: 0, samples: 0, pricedCost: 0, priced: 0 };
|
|
124
|
+
agg.samples += 1;
|
|
125
|
+
if (row.failureClass === null)
|
|
126
|
+
agg.conform += 1;
|
|
127
|
+
if (row.repairedBy !== null)
|
|
128
|
+
agg.repaired += 1;
|
|
129
|
+
if (!row.pricingMissing) {
|
|
130
|
+
agg.pricedCost += row.costEstimateUsd;
|
|
131
|
+
agg.priced += 1;
|
|
132
|
+
}
|
|
133
|
+
byCell.set(key, agg);
|
|
134
|
+
}
|
|
135
|
+
// A2: the pairwise fold rides the same pass — records attach to their
|
|
136
|
+
// (cell, model) row's stats; scope/RLS/contribution treatment identical.
|
|
137
|
+
const pairwiseByModel = aggregatePairwiseFromTelemetry(rows);
|
|
138
|
+
return [...byCell.entries()]
|
|
139
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
140
|
+
.map(([key, agg]) => {
|
|
141
|
+
const [cellKey, modelKey] = key.split(" ");
|
|
142
|
+
const opponents = pairwiseByModel.get(cellKey)?.get(modelKey);
|
|
143
|
+
return {
|
|
144
|
+
cellKey,
|
|
145
|
+
modelKey,
|
|
146
|
+
stats: {
|
|
147
|
+
samples: agg.samples,
|
|
148
|
+
conform_rate: agg.samples === 0 ? 0 : agg.conform / agg.samples,
|
|
149
|
+
repair_rate: agg.samples === 0 ? 0 : agg.repaired / agg.samples,
|
|
150
|
+
mean_cost_usd: agg.priced === 0 ? null : agg.pricedCost / agg.priced,
|
|
151
|
+
scope: aggregationScope(opts),
|
|
152
|
+
...(opponents !== undefined && opponents.size > 0
|
|
153
|
+
? {
|
|
154
|
+
pairwise: Object.fromEntries([...opponents.entries()].sort(([a], [b]) => a.localeCompare(b))),
|
|
155
|
+
}
|
|
156
|
+
: {}),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Upsert cells into priors_cells (service role — the ONLY principal that
|
|
162
|
+
* can touch the table; RLS denies everyone else including anon). */
|
|
163
|
+
async function writePriorsCells(cells, config) {
|
|
164
|
+
if (cells.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
167
|
+
const base = config.supabaseUrl.replace(/\/$/, "");
|
|
168
|
+
const response = await fetchFn(`${base}/rest/v1/priors_cells?on_conflict=cell_key,model_key`, {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: {
|
|
171
|
+
"content-type": "application/json",
|
|
172
|
+
apikey: config.serviceRoleKey,
|
|
173
|
+
authorization: `Bearer ${config.serviceRoleKey}`,
|
|
174
|
+
prefer: "resolution=merge-duplicates,return=minimal",
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify(cells.map((c) => ({
|
|
177
|
+
cell_key: c.cellKey,
|
|
178
|
+
model_key: c.modelKey,
|
|
179
|
+
stats: c.stats,
|
|
180
|
+
updated_at: new Date().toISOString(),
|
|
181
|
+
}))),
|
|
182
|
+
});
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new Error(`priors_cells upsert → ${response.status} ${await response.text().catch(() => "")}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The telemetry query grammar — Phase 4 WS5 ("your exhaust is your data").
|
|
3
|
+
* A hand-rolled recursive-descent parser over a DELIBERATELY tiny SQL-ish
|
|
4
|
+
* language:
|
|
5
|
+
*
|
|
6
|
+
* SELECT item (, item)* FROM inferences
|
|
7
|
+
* [WHERE cond (AND cond)*] [GROUP BY col (, col)*] [LIMIT n]
|
|
8
|
+
* item := col | count(*) | sum(m)|avg(m)|min(m)|max(m)
|
|
9
|
+
* col := route|provider|model|ts|failure_class|served_variant|tag.<name>
|
|
10
|
+
* m := cost|tokens_in|tokens_out|tokens_cached|latency_ms|ttfb_ms
|
|
11
|
+
* cond := col op literal op := = != > < >= <=
|
|
12
|
+
*
|
|
13
|
+
* Security model = the allowlist: unknown tokens throw, string literals
|
|
14
|
+
* only ever travel as bind parameters, the output is a single read-only
|
|
15
|
+
* SELECT. There is no raw-SQL path — by construction, not by filtering.
|
|
16
|
+
* Row scoping: a token's scope is ANDed into WHERE as one more bind
|
|
17
|
+
* parameter; the caller cannot opt out of it.
|
|
18
|
+
*/
|
|
19
|
+
export declare class QueryGrammarError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/** What a query token is allowed to see. */
|
|
23
|
+
export type QueryScope = {
|
|
24
|
+
readonly kind: "all";
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "tag";
|
|
27
|
+
readonly tag: string;
|
|
28
|
+
readonly value: string;
|
|
29
|
+
};
|
|
30
|
+
export interface CompiledQuery {
|
|
31
|
+
readonly sql: string;
|
|
32
|
+
readonly params: ReadonlyArray<string | number>;
|
|
33
|
+
}
|
|
34
|
+
export declare function compileQuery(input: string, scope: QueryScope): CompiledQuery;
|
|
35
|
+
//# sourceMappingURL=grammar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/query/grammar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,4CAA4C;AAC5C,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CACjD;AAwHD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,aAAa,CAkJ5E"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The telemetry query grammar — Phase 4 WS5 ("your exhaust is your data").
|
|
4
|
+
* A hand-rolled recursive-descent parser over a DELIBERATELY tiny SQL-ish
|
|
5
|
+
* language:
|
|
6
|
+
*
|
|
7
|
+
* SELECT item (, item)* FROM inferences
|
|
8
|
+
* [WHERE cond (AND cond)*] [GROUP BY col (, col)*] [LIMIT n]
|
|
9
|
+
* item := col | count(*) | sum(m)|avg(m)|min(m)|max(m)
|
|
10
|
+
* col := route|provider|model|ts|failure_class|served_variant|tag.<name>
|
|
11
|
+
* m := cost|tokens_in|tokens_out|tokens_cached|latency_ms|ttfb_ms
|
|
12
|
+
* cond := col op literal op := = != > < >= <=
|
|
13
|
+
*
|
|
14
|
+
* Security model = the allowlist: unknown tokens throw, string literals
|
|
15
|
+
* only ever travel as bind parameters, the output is a single read-only
|
|
16
|
+
* SELECT. There is no raw-SQL path — by construction, not by filtering.
|
|
17
|
+
* Row scoping: a token's scope is ANDed into WHERE as one more bind
|
|
18
|
+
* parameter; the caller cannot opt out of it.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.QueryGrammarError = void 0;
|
|
22
|
+
exports.compileQuery = compileQuery;
|
|
23
|
+
class QueryGrammarError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "QueryGrammarError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.QueryGrammarError = QueryGrammarError;
|
|
30
|
+
const PLAIN_COLUMNS = {
|
|
31
|
+
route: "route",
|
|
32
|
+
provider: "provider",
|
|
33
|
+
model: "model",
|
|
34
|
+
ts: "ts",
|
|
35
|
+
failure_class: "failure_class",
|
|
36
|
+
served_variant: "served_variant",
|
|
37
|
+
};
|
|
38
|
+
const METRICS = {
|
|
39
|
+
cost: "cost_estimate_usd",
|
|
40
|
+
tokens_in: "tokens_in",
|
|
41
|
+
tokens_out: "tokens_out",
|
|
42
|
+
tokens_cached: "tokens_cached",
|
|
43
|
+
latency_ms: "latency_ms",
|
|
44
|
+
ttfb_ms: "ttfb_ms",
|
|
45
|
+
};
|
|
46
|
+
const FUNCTIONS = new Set(["sum", "avg", "min", "max", "count"]);
|
|
47
|
+
const OPERATORS = new Set(["=", "!=", ">", "<", ">=", "<="]);
|
|
48
|
+
const TAG_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
49
|
+
const DEFAULT_LIMIT = 1000;
|
|
50
|
+
const MAX_LIMIT = 10000;
|
|
51
|
+
function tokenize(input) {
|
|
52
|
+
const tokens = [];
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < input.length) {
|
|
55
|
+
const ch = input[i];
|
|
56
|
+
if (/\s/.test(ch)) {
|
|
57
|
+
i += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ch === "'") {
|
|
61
|
+
// Single-quoted string; '' escapes a quote. Anything else inside is data.
|
|
62
|
+
let value = "";
|
|
63
|
+
i += 1;
|
|
64
|
+
for (;;) {
|
|
65
|
+
if (i >= input.length)
|
|
66
|
+
throw new QueryGrammarError("unterminated string literal");
|
|
67
|
+
if (input[i] === "'") {
|
|
68
|
+
if (input[i + 1] === "'") {
|
|
69
|
+
value += "'";
|
|
70
|
+
i += 2;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
i += 1;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
value += input[i];
|
|
77
|
+
i += 1;
|
|
78
|
+
}
|
|
79
|
+
tokens.push({ kind: "string", text: value });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (/[0-9]/.test(ch) || (ch === "." && /[0-9]/.test(input[i + 1] ?? ""))) {
|
|
83
|
+
let num = "";
|
|
84
|
+
while (i < input.length && /[0-9.]/.test(input[i])) {
|
|
85
|
+
num += input[i];
|
|
86
|
+
i += 1;
|
|
87
|
+
}
|
|
88
|
+
if (!/^\d+(\.\d+)?$/.test(num))
|
|
89
|
+
throw new QueryGrammarError(`bad number: ${num}`);
|
|
90
|
+
tokens.push({ kind: "number", text: num });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
94
|
+
let word = "";
|
|
95
|
+
while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
|
|
96
|
+
word += input[i];
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
tokens.push({ kind: "word", text: word });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (ch === ">" || ch === "<" || ch === "!") {
|
|
103
|
+
const two = input.slice(i, i + 2);
|
|
104
|
+
if (two === ">=" || two === "<=" || two === "!=") {
|
|
105
|
+
tokens.push({ kind: "punct", text: two });
|
|
106
|
+
i += 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === "!")
|
|
110
|
+
throw new QueryGrammarError("unexpected '!'");
|
|
111
|
+
tokens.push({ kind: "punct", text: ch });
|
|
112
|
+
i += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (ch === "=" || ch === "," || ch === "(" || ch === ")" || ch === "*") {
|
|
116
|
+
tokens.push({ kind: "punct", text: ch });
|
|
117
|
+
i += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
throw new QueryGrammarError(`unexpected character: ${JSON.stringify(ch)}`);
|
|
121
|
+
}
|
|
122
|
+
return tokens;
|
|
123
|
+
}
|
|
124
|
+
/** Column reference → SQL expression. tag.<name> compiles to a
|
|
125
|
+
* json_extract over tags_json with a VALIDATED identifier (never
|
|
126
|
+
* interpolated user text beyond the identifier alphabet). */
|
|
127
|
+
function columnSql(word) {
|
|
128
|
+
if (word.startsWith("tag.")) {
|
|
129
|
+
const tag = word.slice(4);
|
|
130
|
+
if (!TAG_NAME.test(tag))
|
|
131
|
+
throw new QueryGrammarError(`bad tag name: ${tag}`);
|
|
132
|
+
return `json_extract(tags_json, '$.${tag}')`;
|
|
133
|
+
}
|
|
134
|
+
const column = PLAIN_COLUMNS[word];
|
|
135
|
+
if (column === undefined) {
|
|
136
|
+
throw new QueryGrammarError(`unknown column "${word}" — allowed: ${Object.keys(PLAIN_COLUMNS).join(", ")}, tag.<name>`);
|
|
137
|
+
}
|
|
138
|
+
return column;
|
|
139
|
+
}
|
|
140
|
+
function compileQuery(input, scope) {
|
|
141
|
+
const tokens = tokenize(input);
|
|
142
|
+
let pos = 0;
|
|
143
|
+
const peek = () => tokens[pos];
|
|
144
|
+
function next() {
|
|
145
|
+
const token = tokens[pos];
|
|
146
|
+
if (token === undefined)
|
|
147
|
+
throw new QueryGrammarError("unexpected end of query");
|
|
148
|
+
pos += 1;
|
|
149
|
+
return token;
|
|
150
|
+
}
|
|
151
|
+
function expectWord(word) {
|
|
152
|
+
const token = next();
|
|
153
|
+
if (token.kind !== "word" || token.text.toLowerCase() !== word) {
|
|
154
|
+
throw new QueryGrammarError(`expected ${word.toUpperCase()}, got "${token.text}"`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function expectPunct(text) {
|
|
158
|
+
const token = next();
|
|
159
|
+
if (token.kind !== "punct" || token.text !== text) {
|
|
160
|
+
throw new QueryGrammarError(`expected "${text}", got "${token.text}"`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
expectWord("select");
|
|
164
|
+
const selectSql = [];
|
|
165
|
+
for (;;) {
|
|
166
|
+
const token = next();
|
|
167
|
+
if (token.kind !== "word")
|
|
168
|
+
throw new QueryGrammarError(`bad select item: "${token.text}"`);
|
|
169
|
+
const lower = token.text.toLowerCase();
|
|
170
|
+
if (FUNCTIONS.has(lower) && peek()?.kind === "punct" && peek()?.text === "(") {
|
|
171
|
+
expectPunct("(");
|
|
172
|
+
const arg = next();
|
|
173
|
+
expectPunct(")");
|
|
174
|
+
if (lower === "count") {
|
|
175
|
+
if (arg.kind !== "punct" || arg.text !== "*") {
|
|
176
|
+
throw new QueryGrammarError("count takes exactly count(*)");
|
|
177
|
+
}
|
|
178
|
+
selectSql.push(`COUNT(*) AS "count"`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
if (arg.kind !== "word")
|
|
182
|
+
throw new QueryGrammarError(`bad metric in ${lower}()`);
|
|
183
|
+
const metric = METRICS[arg.text.toLowerCase()];
|
|
184
|
+
if (metric === undefined) {
|
|
185
|
+
throw new QueryGrammarError(`unknown metric "${arg.text}" — allowed: ${Object.keys(METRICS).join(", ")}`);
|
|
186
|
+
}
|
|
187
|
+
selectSql.push(`${lower.toUpperCase()}(${metric}) AS "${lower}_${arg.text.toLowerCase()}"`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// tag.client aliases to "client" — result keys read naturally.
|
|
192
|
+
const alias = token.text.startsWith("tag.") ? token.text.slice(4) : token.text;
|
|
193
|
+
selectSql.push(`${columnSql(token.text)} AS "${alias}"`);
|
|
194
|
+
}
|
|
195
|
+
if (peek()?.kind === "punct" && peek()?.text === ",") {
|
|
196
|
+
pos += 1;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
expectWord("from");
|
|
202
|
+
const table = next();
|
|
203
|
+
if (table.kind !== "word" || table.text.toLowerCase() !== "inferences") {
|
|
204
|
+
throw new QueryGrammarError(`only "inferences" is queryable, got "${table.text}"`);
|
|
205
|
+
}
|
|
206
|
+
const whereSql = [];
|
|
207
|
+
const params = [];
|
|
208
|
+
if (peek()?.kind === "word" && peek()?.text.toLowerCase() === "where") {
|
|
209
|
+
pos += 1;
|
|
210
|
+
for (;;) {
|
|
211
|
+
const column = next();
|
|
212
|
+
if (column.kind !== "word")
|
|
213
|
+
throw new QueryGrammarError(`bad condition column`);
|
|
214
|
+
const columnExpr = METRICS[column.text.toLowerCase()] !== undefined
|
|
215
|
+
? METRICS[column.text.toLowerCase()]
|
|
216
|
+
: columnSql(column.text);
|
|
217
|
+
const op = next();
|
|
218
|
+
if (op.kind !== "punct" || !OPERATORS.has(op.text)) {
|
|
219
|
+
throw new QueryGrammarError(`bad operator "${op.text}" — allowed: ${[...OPERATORS].join(" ")}`);
|
|
220
|
+
}
|
|
221
|
+
const value = next();
|
|
222
|
+
if (value.kind === "string") {
|
|
223
|
+
params.push(value.text);
|
|
224
|
+
}
|
|
225
|
+
else if (value.kind === "number") {
|
|
226
|
+
params.push(Number(value.text));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
throw new QueryGrammarError(`condition value must be a 'string' or number, got "${value.text}"`);
|
|
230
|
+
}
|
|
231
|
+
whereSql.push(`${columnExpr} ${op.text} ?`);
|
|
232
|
+
if (peek()?.kind === "word" && peek()?.text.toLowerCase() === "and") {
|
|
233
|
+
pos += 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Row scoping — ANDed in unconditionally; the caller cannot remove it.
|
|
240
|
+
if (scope.kind === "tag") {
|
|
241
|
+
if (!TAG_NAME.test(scope.tag))
|
|
242
|
+
throw new QueryGrammarError(`bad scope tag: ${scope.tag}`);
|
|
243
|
+
whereSql.push(`json_extract(tags_json, '$.${scope.tag}') = ?`);
|
|
244
|
+
params.push(scope.value);
|
|
245
|
+
}
|
|
246
|
+
const groupSql = [];
|
|
247
|
+
if (peek()?.kind === "word" && peek()?.text.toLowerCase() === "group") {
|
|
248
|
+
pos += 1;
|
|
249
|
+
expectWord("by");
|
|
250
|
+
for (;;) {
|
|
251
|
+
const column = next();
|
|
252
|
+
if (column.kind !== "word")
|
|
253
|
+
throw new QueryGrammarError("bad GROUP BY column");
|
|
254
|
+
groupSql.push(columnSql(column.text));
|
|
255
|
+
if (peek()?.kind === "punct" && peek()?.text === ",") {
|
|
256
|
+
pos += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
let limit = DEFAULT_LIMIT;
|
|
263
|
+
if (peek()?.kind === "word" && peek()?.text.toLowerCase() === "limit") {
|
|
264
|
+
pos += 1;
|
|
265
|
+
const n = next();
|
|
266
|
+
if (n.kind !== "number" || !/^\d+$/.test(n.text)) {
|
|
267
|
+
throw new QueryGrammarError("LIMIT takes a bare integer");
|
|
268
|
+
}
|
|
269
|
+
limit = Math.min(Number(n.text), MAX_LIMIT);
|
|
270
|
+
}
|
|
271
|
+
if (pos !== tokens.length) {
|
|
272
|
+
throw new QueryGrammarError(`unexpected trailing input: "${tokens[pos].text}"`);
|
|
273
|
+
}
|
|
274
|
+
const sql = `SELECT ${selectSql.join(", ")} FROM inferences` +
|
|
275
|
+
(whereSql.length > 0 ? ` WHERE ${whereSql.join(" AND ")}` : "") +
|
|
276
|
+
(groupSql.length > 0 ? ` GROUP BY ${groupSql.join(", ")}` : "") +
|
|
277
|
+
` LIMIT ${limit}`;
|
|
278
|
+
return { sql, params };
|
|
279
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query execution — Phase 4 WS5. Opens the telemetry file READONLY and runs
|
|
3
|
+
* one compiled (grammar-validated, parameterized) SELECT. The readonly
|
|
4
|
+
* handle is defense in depth under the grammar allowlist.
|
|
5
|
+
*/
|
|
6
|
+
import type { CompiledQuery } from "./grammar";
|
|
7
|
+
export declare function runTelemetryQuery(dbPath: string, compiled: CompiledQuery): Array<Record<string, unknown>>;
|
|
8
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/query/run.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,aAAa,GACtB,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOhC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Query execution — Phase 4 WS5. Opens the telemetry file READONLY and runs
|
|
4
|
+
* one compiled (grammar-validated, parameterized) SELECT. The readonly
|
|
5
|
+
* handle is defense in depth under the grammar allowlist.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.runTelemetryQuery = runTelemetryQuery;
|
|
12
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
13
|
+
function runTelemetryQuery(dbPath, compiled) {
|
|
14
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true, fileMustExist: true });
|
|
15
|
+
try {
|
|
16
|
+
return db.prepare(compiled.sql).all(...compiled.params);
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
db.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry v0 build logic — spec WS3. Pure functions (script entrypoints in
|
|
3
|
+
* registry/ do the IO): declared from the vendored pricing map + adapter
|
|
4
|
+
* capability maps; probed from probe result files; observed from telemetry
|
|
5
|
+
* aggregates; merge + discrepancy detection.
|
|
6
|
+
*
|
|
7
|
+
* PRIVACY (spec WS6 / plan step-4 review): the observed layer publishes
|
|
8
|
+
* AGGREGATES ONLY — no tags, no payloads, no fingerprint joins.
|
|
9
|
+
*/
|
|
10
|
+
import type { ModelFacts } from "./claims";
|
|
11
|
+
export interface DeclaredLayer {
|
|
12
|
+
readonly as_of: string;
|
|
13
|
+
readonly source: string;
|
|
14
|
+
readonly pricing: {
|
|
15
|
+
readonly input_usd_per_mtok: number;
|
|
16
|
+
readonly output_usd_per_mtok: number;
|
|
17
|
+
readonly cached_input_usd_per_mtok: number | null;
|
|
18
|
+
} | null;
|
|
19
|
+
readonly flags: {
|
|
20
|
+
readonly supports_response_schema: boolean | null;
|
|
21
|
+
readonly supports_web_search: boolean | null;
|
|
22
|
+
readonly supports_prompt_caching: boolean | null;
|
|
23
|
+
/** What OUR adapter actually drives for this model (static map v0). */
|
|
24
|
+
readonly adapter_structured_native: boolean | null;
|
|
25
|
+
readonly adapter_grounded_native: boolean | null;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface ProbedSchemaSummary {
|
|
29
|
+
readonly samples: number;
|
|
30
|
+
readonly parse_rate: number;
|
|
31
|
+
readonly conform_rate: number | null;
|
|
32
|
+
readonly conform_ci95: readonly [number, number] | null;
|
|
33
|
+
readonly value_accuracy_mean: number | null;
|
|
34
|
+
readonly native_rung_rate: number | null;
|
|
35
|
+
readonly mean_cost_usd: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ProbedGroundingSummary {
|
|
38
|
+
readonly samples: number;
|
|
39
|
+
readonly grounded_rate: number;
|
|
40
|
+
readonly mean_citations: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ProbedCachingSummary {
|
|
43
|
+
readonly samples: number;
|
|
44
|
+
readonly cache_hit_rate: number;
|
|
45
|
+
readonly mean_cached_tokens: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ProbedLayer {
|
|
48
|
+
readonly as_of: string;
|
|
49
|
+
readonly source: string;
|
|
50
|
+
readonly schema: ProbedSchemaSummary | null;
|
|
51
|
+
readonly grounding: ProbedGroundingSummary | null;
|
|
52
|
+
readonly caching: ProbedCachingSummary | null;
|
|
53
|
+
}
|
|
54
|
+
export interface ObservedLayer {
|
|
55
|
+
readonly as_of: string;
|
|
56
|
+
readonly source: string;
|
|
57
|
+
readonly inferences: number;
|
|
58
|
+
readonly p50_latency_ms: number | null;
|
|
59
|
+
/** TTFB p50 (Phase 4 WS3) — optional: aggregators populate it where
|
|
60
|
+
* ttfb_ms rows exist; older observed layers lack the field. */
|
|
61
|
+
readonly p50_ttfb_ms?: number | null;
|
|
62
|
+
readonly served_tier_rates: Readonly<Record<string, number>>;
|
|
63
|
+
readonly cache_hit_rate: number | null;
|
|
64
|
+
readonly failure_class_rates: Readonly<Record<string, number>>;
|
|
65
|
+
}
|
|
66
|
+
export interface Discrepancy {
|
|
67
|
+
readonly kind: string;
|
|
68
|
+
readonly message: string;
|
|
69
|
+
}
|
|
70
|
+
export interface RegistryEntry {
|
|
71
|
+
readonly model_key: string;
|
|
72
|
+
readonly declared: DeclaredLayer | null;
|
|
73
|
+
readonly probed: ProbedLayer | null;
|
|
74
|
+
readonly observed: ObservedLayer | null;
|
|
75
|
+
readonly discrepancies: readonly Discrepancy[];
|
|
76
|
+
/** Per-model call-shaping knowledge (addendum A1 / patch P1) — seeded from
|
|
77
|
+
* the adapters' documented constraints; see call-notes.ts. */
|
|
78
|
+
readonly callNotes: readonly string[];
|
|
79
|
+
/** Credential-scoped {value, scope, source, as_of} facts (Phase 4 WS3) —
|
|
80
|
+
* null when nothing is cited; absence IS the conservative claim. */
|
|
81
|
+
readonly facts: ModelFacts | null;
|
|
82
|
+
}
|
|
83
|
+
export interface Registry {
|
|
84
|
+
readonly generated_at: string;
|
|
85
|
+
readonly license: string;
|
|
86
|
+
readonly models: readonly RegistryEntry[];
|
|
87
|
+
}
|
|
88
|
+
/** Minimal shape of a probe result file this builder consumes (kept
|
|
89
|
+
* structural so the core package does not depend on modelrig-probes). */
|
|
90
|
+
export interface ProbeResultFile {
|
|
91
|
+
readonly modelKey: string;
|
|
92
|
+
readonly class: "schema" | "grounding" | "caching";
|
|
93
|
+
readonly date: string;
|
|
94
|
+
readonly harnessVersion: string;
|
|
95
|
+
readonly raw: Readonly<Record<string, ReadonlyArray<{
|
|
96
|
+
readonly rung?: "native" | "json_mode";
|
|
97
|
+
readonly parseOk: boolean;
|
|
98
|
+
readonly schemaConform: boolean | null;
|
|
99
|
+
readonly valueAccuracy: number | null;
|
|
100
|
+
readonly grounded: boolean | null;
|
|
101
|
+
readonly citationCount: number | null;
|
|
102
|
+
readonly cachedTokens: number | null;
|
|
103
|
+
readonly costUsd: number;
|
|
104
|
+
readonly failureClass: string | null;
|
|
105
|
+
}>>>;
|
|
106
|
+
}
|
|
107
|
+
/** Latest result per class wins (results are dated). */
|
|
108
|
+
export declare function latestPerClass(results: readonly ProbeResultFile[]): Partial<Record<ProbeResultFile["class"], ProbeResultFile>>;
|
|
109
|
+
export declare function summarizeSchemaResult(result: ProbeResultFile): ProbedSchemaSummary;
|
|
110
|
+
export declare function summarizeGroundingResult(result: ProbeResultFile): ProbedGroundingSummary;
|
|
111
|
+
export declare function summarizeCachingResult(result: ProbeResultFile): ProbedCachingSummary;
|
|
112
|
+
export declare function buildProbedLayer(results: readonly ProbeResultFile[]): ProbedLayer | null;
|
|
113
|
+
/** Declared-vs-probed disagreement detection — plan §4 (the rule pair) plus
|
|
114
|
+
* the rung gap that motivated the whole exercise (DeepSeek json_object). */
|
|
115
|
+
export declare function detectDiscrepancies(declared: DeclaredLayer | null, probed: ProbedLayer | null): Discrepancy[];
|
|
116
|
+
export declare function buildRegistry(entries: ReadonlyArray<{
|
|
117
|
+
readonly model_key: string;
|
|
118
|
+
readonly declared: DeclaredLayer | null;
|
|
119
|
+
readonly probed: ProbedLayer | null;
|
|
120
|
+
readonly observed: ObservedLayer | null;
|
|
121
|
+
}>, generatedAt: string): Registry;
|
|
122
|
+
/** Leaderboard ranking metric — spec WS3: "conformance per dollar".
|
|
123
|
+
* Effective $ per 1K conformant outputs: mean sample cost ÷ conform rate
|
|
124
|
+
* × 1000. Null when unjudged or never conformant. */
|
|
125
|
+
export declare function effectiveCostPer1kConformant(schema: ProbedSchemaSummary | null): number | null;
|
|
126
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/registry/build.ts"],"names":[],"mappings":"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"}
|