@tangle-network/agent-eval 0.120.0 → 0.120.2
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 +12 -0
- package/package.json +1 -1
- package/dist/analyst/index.d.ts +0 -3111
- package/dist/analyst/index.js +0 -403
- package/dist/analyst/index.js.map +0 -1
- package/dist/authenticity/index.d.ts +0 -161
- package/dist/authenticity/index.js +0 -215
- package/dist/authenticity/index.js.map +0 -1
- package/dist/belief-state/index.d.ts +0 -1301
- package/dist/belief-state/index.js +0 -2152
- package/dist/belief-state/index.js.map +0 -1
- package/dist/benchmarks/index.d.ts +0 -974
- package/dist/benchmarks/index.js +0 -60
- package/dist/benchmarks/index.js.map +0 -1
- package/dist/builder-eval/index.d.ts +0 -695
- package/dist/builder-eval/index.js +0 -366
- package/dist/builder-eval/index.js.map +0 -1
- package/dist/campaign/index.d.ts +0 -7454
- package/dist/campaign/index.js +0 -272
- package/dist/campaign/index.js.map +0 -1
- package/dist/chunk-32BZXMSO.js +0 -3878
- package/dist/chunk-32BZXMSO.js.map +0 -1
- package/dist/chunk-3A246TSA.js +0 -998
- package/dist/chunk-3A246TSA.js.map +0 -1
- package/dist/chunk-3RF76KTD.js +0 -84
- package/dist/chunk-3RF76KTD.js.map +0 -1
- package/dist/chunk-3YYRZDON.js +0 -45
- package/dist/chunk-3YYRZDON.js.map +0 -1
- package/dist/chunk-4I2E3LLO.js +0 -1030
- package/dist/chunk-4I2E3LLO.js.map +0 -1
- package/dist/chunk-ARU2PZFM.js +0 -312
- package/dist/chunk-ARU2PZFM.js.map +0 -1
- package/dist/chunk-BOD4O7OF.js +0 -40
- package/dist/chunk-BOD4O7OF.js.map +0 -1
- package/dist/chunk-DPZAEKA6.js +0 -880
- package/dist/chunk-DPZAEKA6.js.map +0 -1
- package/dist/chunk-DTJ6QUQB.js +0 -131
- package/dist/chunk-DTJ6QUQB.js.map +0 -1
- package/dist/chunk-GGE4NNQT.js +0 -65
- package/dist/chunk-GGE4NNQT.js.map +0 -1
- package/dist/chunk-H5UD2323.js +0 -286
- package/dist/chunk-H5UD2323.js.map +0 -1
- package/dist/chunk-HHWE3POT.js +0 -94
- package/dist/chunk-HHWE3POT.js.map +0 -1
- package/dist/chunk-HKUCJ437.js +0 -787
- package/dist/chunk-HKUCJ437.js.map +0 -1
- package/dist/chunk-JHCHEVET.js +0 -274
- package/dist/chunk-JHCHEVET.js.map +0 -1
- package/dist/chunk-JHOJHHU7.js +0 -867
- package/dist/chunk-JHOJHHU7.js.map +0 -1
- package/dist/chunk-JM2SKQMS.js +0 -750
- package/dist/chunk-JM2SKQMS.js.map +0 -1
- package/dist/chunk-JN2FCO5W.js +0 -7958
- package/dist/chunk-JN2FCO5W.js.map +0 -1
- package/dist/chunk-K4DBDHLK.js +0 -158
- package/dist/chunk-K4DBDHLK.js.map +0 -1
- package/dist/chunk-K6N6XJJX.js +0 -306
- package/dist/chunk-K6N6XJJX.js.map +0 -1
- package/dist/chunk-MA6HLL3S.js +0 -65
- package/dist/chunk-MA6HLL3S.js.map +0 -1
- package/dist/chunk-MAZ26DC7.js +0 -99
- package/dist/chunk-MAZ26DC7.js.map +0 -1
- package/dist/chunk-MOXWMGPC.js +0 -577
- package/dist/chunk-MOXWMGPC.js.map +0 -1
- package/dist/chunk-NJC7U437.js +0 -626
- package/dist/chunk-NJC7U437.js.map +0 -1
- package/dist/chunk-NPCTHQIO.js +0 -91
- package/dist/chunk-NPCTHQIO.js.map +0 -1
- package/dist/chunk-ONWEPEDO.js +0 -57
- package/dist/chunk-ONWEPEDO.js.map +0 -1
- package/dist/chunk-OYZAPX5G.js +0 -1526
- package/dist/chunk-OYZAPX5G.js.map +0 -1
- package/dist/chunk-PC4UYEBM.js +0 -166
- package/dist/chunk-PC4UYEBM.js.map +0 -1
- package/dist/chunk-PICTDURQ.js +0 -766
- package/dist/chunk-PICTDURQ.js.map +0 -1
- package/dist/chunk-PJQFMIOX.js +0 -1182
- package/dist/chunk-PJQFMIOX.js.map +0 -1
- package/dist/chunk-PXD6ZFNY.js +0 -1107
- package/dist/chunk-PXD6ZFNY.js.map +0 -1
- package/dist/chunk-PXE2VKMX.js +0 -140
- package/dist/chunk-PXE2VKMX.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-QBRSJK47.js +0 -622
- package/dist/chunk-QBRSJK47.js.map +0 -1
- package/dist/chunk-QWMPPZ3X.js +0 -550
- package/dist/chunk-QWMPPZ3X.js.map +0 -1
- package/dist/chunk-S3UZOQ5Y.js +0 -328
- package/dist/chunk-S3UZOQ5Y.js.map +0 -1
- package/dist/chunk-S5TT5R3L.js +0 -2668
- package/dist/chunk-S5TT5R3L.js.map +0 -1
- package/dist/chunk-T4SQEITX.js +0 -95
- package/dist/chunk-T4SQEITX.js.map +0 -1
- package/dist/chunk-TT4KNT67.js +0 -124
- package/dist/chunk-TT4KNT67.js.map +0 -1
- package/dist/chunk-U5CHZ5M3.js +0 -357
- package/dist/chunk-U5CHZ5M3.js.map +0 -1
- package/dist/chunk-ULOKLHIQ.js +0 -1937
- package/dist/chunk-ULOKLHIQ.js.map +0 -1
- package/dist/chunk-VI2UW6B6.js +0 -162
- package/dist/chunk-VI2UW6B6.js.map +0 -1
- package/dist/chunk-VQMK5FMP.js +0 -247
- package/dist/chunk-VQMK5FMP.js.map +0 -1
- package/dist/chunk-VSMTAMNK.js +0 -53
- package/dist/chunk-VSMTAMNK.js.map +0 -1
- package/dist/chunk-VZSRQ272.js +0 -149
- package/dist/chunk-VZSRQ272.js.map +0 -1
- package/dist/chunk-WW2A73HW.js +0 -159
- package/dist/chunk-WW2A73HW.js.map +0 -1
- package/dist/chunk-X4UCIOTZ.js +0 -136
- package/dist/chunk-X4UCIOTZ.js.map +0 -1
- package/dist/chunk-XDIRG3TO.js +0 -1266
- package/dist/chunk-XDIRG3TO.js.map +0 -1
- package/dist/chunk-XJYR7XFV.js +0 -317
- package/dist/chunk-XJYR7XFV.js.map +0 -1
- package/dist/chunk-ZET2UAYW.js +0 -89
- package/dist/chunk-ZET2UAYW.js.map +0 -1
- package/dist/chunk-ZZUXHH3R.js +0 -99
- package/dist/chunk-ZZUXHH3R.js.map +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -112
- package/dist/cli.js.map +0 -1
- package/dist/contract/index.d.ts +0 -4969
- package/dist/contract/index.js +0 -1653
- package/dist/contract/index.js.map +0 -1
- package/dist/control.d.ts +0 -1013
- package/dist/control.js +0 -34
- package/dist/control.js.map +0 -1
- package/dist/fuzz.d.ts +0 -759
- package/dist/fuzz.js +0 -714
- package/dist/fuzz.js.map +0 -1
- package/dist/hosted/index.d.ts +0 -730
- package/dist/hosted/index.js +0 -14
- package/dist/hosted/index.js.map +0 -1
- package/dist/index.d.ts +0 -16780
- package/dist/index.js +0 -12168
- package/dist/index.js.map +0 -1
- package/dist/matrix/index.d.ts +0 -155
- package/dist/matrix/index.js +0 -8
- package/dist/matrix/index.js.map +0 -1
- package/dist/meta-eval/index.d.ts +0 -1030
- package/dist/meta-eval/index.js +0 -417
- package/dist/meta-eval/index.js.map +0 -1
- package/dist/multishot/index.d.ts +0 -579
- package/dist/multishot/index.js +0 -589
- package/dist/multishot/index.js.map +0 -1
- package/dist/openapi.json +0 -992
- package/dist/pipelines/index.d.ts +0 -567
- package/dist/pipelines/index.js +0 -515
- package/dist/pipelines/index.js.map +0 -1
- package/dist/reporting.d.ts +0 -1277
- package/dist/reporting.js +0 -48
- package/dist/reporting.js.map +0 -1
- package/dist/rl.d.ts +0 -4092
- package/dist/rl.js +0 -1724
- package/dist/rl.js.map +0 -1
- package/dist/run-campaign-HNFPJET4.js +0 -14
- package/dist/run-campaign-HNFPJET4.js.map +0 -1
- package/dist/storyboard/index.d.ts +0 -279
- package/dist/storyboard/index.js +0 -767
- package/dist/storyboard/index.js.map +0 -1
- package/dist/trace-attributes.d.ts +0 -52
- package/dist/trace-attributes.js +0 -62
- package/dist/trace-attributes.js.map +0 -1
- package/dist/traces.d.ts +0 -2343
- package/dist/traces.js +0 -249
- package/dist/traces.js.map +0 -1
- package/dist/wire/index.d.ts +0 -1252
- package/dist/wire/index.js +0 -81
- package/dist/wire/index.js.map +0 -1
package/dist/chunk-XDIRG3TO.js
DELETED
|
@@ -1,1266 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
confidenceInterval
|
|
3
|
-
} from "./chunk-PJQFMIOX.js";
|
|
4
|
-
import {
|
|
5
|
-
CostAccountingIncompleteError,
|
|
6
|
-
CostLedger
|
|
7
|
-
} from "./chunk-JHOJHHU7.js";
|
|
8
|
-
import {
|
|
9
|
-
AgentEvalError,
|
|
10
|
-
ValidationError
|
|
11
|
-
} from "./chunk-ONWEPEDO.js";
|
|
12
|
-
|
|
13
|
-
// src/campaign/run-campaign.ts
|
|
14
|
-
import { join as join3 } from "path";
|
|
15
|
-
|
|
16
|
-
// src/integrity/backend-integrity.ts
|
|
17
|
-
var BackendIntegrityError = class extends AgentEvalError {
|
|
18
|
-
constructor(message, report) {
|
|
19
|
-
super("backend_integrity", message);
|
|
20
|
-
this.report = report;
|
|
21
|
-
}
|
|
22
|
-
report;
|
|
23
|
-
};
|
|
24
|
-
function summarizeBackendIntegrity(records) {
|
|
25
|
-
return summarizeBackendUsage(
|
|
26
|
-
records.map((record) => ({
|
|
27
|
-
inputTokens: record.tokenUsage.input,
|
|
28
|
-
outputTokens: record.tokenUsage.output,
|
|
29
|
-
costUsd: record.costUsd
|
|
30
|
-
}))
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
function summarizeAgentReceiptIntegrity(receipts) {
|
|
34
|
-
return summarizeBackendUsage(
|
|
35
|
-
receipts.filter((receipt) => receipt.channel === "agent").map((receipt) => ({
|
|
36
|
-
inputTokens: receipt.inputTokens,
|
|
37
|
-
outputTokens: receipt.outputTokens,
|
|
38
|
-
costUsd: receipt.costUsd
|
|
39
|
-
}))
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
function summarizeBackendUsage(records) {
|
|
43
|
-
const totalRecords = records.length;
|
|
44
|
-
let stubRecords = 0;
|
|
45
|
-
let realRecords = 0;
|
|
46
|
-
let uncostedRecords = 0;
|
|
47
|
-
let totalInputTokens = 0;
|
|
48
|
-
let totalOutputTokens = 0;
|
|
49
|
-
let totalCostUsd = 0;
|
|
50
|
-
for (const rec of records) {
|
|
51
|
-
totalInputTokens += rec.inputTokens;
|
|
52
|
-
totalOutputTokens += rec.outputTokens;
|
|
53
|
-
totalCostUsd += rec.costUsd;
|
|
54
|
-
if (rec.inputTokens === 0 && rec.outputTokens === 0) stubRecords++;
|
|
55
|
-
else realRecords++;
|
|
56
|
-
if (rec.outputTokens > 0 && rec.costUsd === 0) uncostedRecords++;
|
|
57
|
-
}
|
|
58
|
-
const verdict = totalRecords === 0 ? "stub" : stubRecords === totalRecords ? "stub" : stubRecords === 0 ? "real" : "mixed";
|
|
59
|
-
const diagnosis = buildDiagnosis({
|
|
60
|
-
totalRecords,
|
|
61
|
-
stubRecords,
|
|
62
|
-
realRecords,
|
|
63
|
-
uncostedRecords,
|
|
64
|
-
totalInputTokens,
|
|
65
|
-
totalOutputTokens,
|
|
66
|
-
totalCostUsd,
|
|
67
|
-
verdict
|
|
68
|
-
});
|
|
69
|
-
return {
|
|
70
|
-
totalRecords,
|
|
71
|
-
stubRecords,
|
|
72
|
-
realRecords,
|
|
73
|
-
uncostedRecords,
|
|
74
|
-
totalInputTokens,
|
|
75
|
-
totalOutputTokens,
|
|
76
|
-
totalCostUsd,
|
|
77
|
-
verdict,
|
|
78
|
-
diagnosis
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function buildDiagnosis(r) {
|
|
82
|
-
if (r.totalRecords === 0) {
|
|
83
|
-
return "no records \u2014 eval produced zero runs; backend likely failed before first turn";
|
|
84
|
-
}
|
|
85
|
-
if (r.verdict === "stub") {
|
|
86
|
-
return [
|
|
87
|
-
`all ${r.totalRecords} records have zero token usage \u2014 the LLM backend was never called.`,
|
|
88
|
-
"common causes: --backend sandbox without a sandbox bridge running; stub model returning hard-coded strings;",
|
|
89
|
-
"auth misconfigured so requests were silently dropped before the LLM. Re-run with --backend tcloud and TANGLE_API_KEY set,",
|
|
90
|
-
"or boot the cli-bridge / sandbox before invoking the eval."
|
|
91
|
-
].join(" ");
|
|
92
|
-
}
|
|
93
|
-
if (r.verdict === "mixed") {
|
|
94
|
-
const pct = (r.stubRecords / r.totalRecords * 100).toFixed(0);
|
|
95
|
-
return [
|
|
96
|
-
`${r.stubRecords}/${r.totalRecords} records (${pct}%) have zero token usage \u2014 the backend partially failed.`,
|
|
97
|
-
"common causes: rate-limit cascade (429s after the first N personas);",
|
|
98
|
-
"transient auth expiry mid-run; provider outage. Treat the affected records as missing data, not agent failures."
|
|
99
|
-
].join(" ");
|
|
100
|
-
}
|
|
101
|
-
if (r.uncostedRecords > 0) {
|
|
102
|
-
const pct = (r.uncostedRecords / r.totalRecords * 100).toFixed(0);
|
|
103
|
-
return [
|
|
104
|
-
`${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens).`,
|
|
105
|
-
`${r.uncostedRecords} (${pct}%) have output tokens but costUsd=0. Two distinct roots:`,
|
|
106
|
-
"(a) cost ledger mis-wired \u2014 no usage propagation from the runtime stream into RunRecord; or",
|
|
107
|
-
"(b) the model is unpriced at the source (sandbox/router returned $0 despite real tokens).",
|
|
108
|
-
"For (b), price the measured tokens against the substrate table (estimateCost) instead of leaving $0."
|
|
109
|
-
].join(" ");
|
|
110
|
-
}
|
|
111
|
-
return `${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens, $${r.totalCostUsd.toFixed(4)}).`;
|
|
112
|
-
}
|
|
113
|
-
function assertRealBackend(records, opts = {}) {
|
|
114
|
-
const report = summarizeBackendIntegrity(records);
|
|
115
|
-
return assertBackendReport(report, opts);
|
|
116
|
-
}
|
|
117
|
-
function assertRealAgentReceipts(receipts, opts = {}) {
|
|
118
|
-
const report = summarizeAgentReceiptIntegrity(receipts);
|
|
119
|
-
return assertBackendReport(report, opts);
|
|
120
|
-
}
|
|
121
|
-
function assertBackendReport(report, opts) {
|
|
122
|
-
const allowMixed = opts.allowMixed ?? true;
|
|
123
|
-
if (report.verdict === "stub") {
|
|
124
|
-
throw new BackendIntegrityError(
|
|
125
|
-
`backend-integrity: ran against a stub or unconfigured backend \u2014 ${report.diagnosis}`,
|
|
126
|
-
report
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
if (!allowMixed && report.verdict === "mixed") {
|
|
130
|
-
throw new BackendIntegrityError(
|
|
131
|
-
`backend-integrity: partial backend failure rejected \u2014 ${report.diagnosis}`,
|
|
132
|
-
report
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
return report;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// src/verdict-cache.ts
|
|
139
|
-
import { createHash } from "crypto";
|
|
140
|
-
import { appendFileSync, existsSync, readFileSync } from "fs";
|
|
141
|
-
function canonicalizeAt(value, path) {
|
|
142
|
-
if (value === null) return "null";
|
|
143
|
-
switch (typeof value) {
|
|
144
|
-
case "boolean":
|
|
145
|
-
return value ? "true" : "false";
|
|
146
|
-
case "number":
|
|
147
|
-
if (!Number.isFinite(value)) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`canonicalJson: non-finite number (${value}) at ${path} \u2014 ambiguity is an error, not a coercion`
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
return JSON.stringify(value);
|
|
153
|
-
case "string":
|
|
154
|
-
return JSON.stringify(value);
|
|
155
|
-
case "undefined":
|
|
156
|
-
case "function":
|
|
157
|
-
case "symbol":
|
|
158
|
-
throw new Error(
|
|
159
|
-
`canonicalJson: ${typeof value} at ${path} \u2014 ambiguity is an error, not a coercion`
|
|
160
|
-
);
|
|
161
|
-
case "bigint":
|
|
162
|
-
throw new Error(`canonicalJson: bigint at ${path} \u2014 not representable in JSON`);
|
|
163
|
-
case "object":
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
const obj = value;
|
|
167
|
-
if (typeof obj.toJSON === "function") {
|
|
168
|
-
return canonicalizeAt(obj.toJSON(), path);
|
|
169
|
-
}
|
|
170
|
-
if (Array.isArray(obj)) {
|
|
171
|
-
return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
|
|
172
|
-
}
|
|
173
|
-
if (obj instanceof Map || obj instanceof Set) {
|
|
174
|
-
throw new Error(
|
|
175
|
-
`canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} \u2014 would serialize as '{}'; convert to a plain object/array first`
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
const keys = Object.keys(obj).sort();
|
|
179
|
-
const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`);
|
|
180
|
-
return `{${parts.join(",")}}`;
|
|
181
|
-
}
|
|
182
|
-
function canonicalJson(value) {
|
|
183
|
-
return canonicalizeAt(value, "$");
|
|
184
|
-
}
|
|
185
|
-
function contentHash(value) {
|
|
186
|
-
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
187
|
-
}
|
|
188
|
-
function inMemoryVerdictCache() {
|
|
189
|
-
const entries = /* @__PURE__ */ new Map();
|
|
190
|
-
return {
|
|
191
|
-
get: (key) => entries.get(key),
|
|
192
|
-
set: (key, score) => {
|
|
193
|
-
entries.set(key, score);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function parseCacheLine(line, path, lineNo) {
|
|
198
|
-
let parsed;
|
|
199
|
-
try {
|
|
200
|
-
parsed = JSON.parse(line);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
throw new Error(
|
|
203
|
-
`fileVerdictCache: corrupt JSONL at ${path}:${lineNo} \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
const rec = parsed;
|
|
207
|
-
if (typeof rec !== "object" || rec === null || typeof rec.key !== "string" || typeof rec.score !== "object" || rec.score === null || typeof rec.score.composite !== "number" || typeof rec.score.dimensions !== "object") {
|
|
208
|
-
throw new Error(
|
|
209
|
-
`fileVerdictCache: invalid record shape at ${path}:${lineNo} \u2014 expected {key, score:{dimensions, composite, notes}}`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
return rec;
|
|
213
|
-
}
|
|
214
|
-
function fileVerdictCache(path) {
|
|
215
|
-
const entries = /* @__PURE__ */ new Map();
|
|
216
|
-
if (existsSync(path)) {
|
|
217
|
-
const lines = readFileSync(path, "utf8").split("\n");
|
|
218
|
-
for (let i = 0; i < lines.length; i++) {
|
|
219
|
-
const line = lines[i];
|
|
220
|
-
if (line === void 0 || line.trim() === "") continue;
|
|
221
|
-
const rec = parseCacheLine(line, path, i + 1);
|
|
222
|
-
entries.set(rec.key, rec.score);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
get: (key) => entries.get(key),
|
|
227
|
-
set: (key, score) => {
|
|
228
|
-
appendFileSync(path, `${JSON.stringify({ key, score })}
|
|
229
|
-
`, "utf8");
|
|
230
|
-
entries.set(key, score);
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
function cachedJudge(judge, store, options) {
|
|
235
|
-
if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") {
|
|
236
|
-
throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
|
|
237
|
-
}
|
|
238
|
-
const stats = { hits: 0, misses: 0 };
|
|
239
|
-
const wrapped = {
|
|
240
|
-
name: judge.name,
|
|
241
|
-
dimensions: judge.dimensions,
|
|
242
|
-
judgeVersion: options.judgeVersion,
|
|
243
|
-
async score(input) {
|
|
244
|
-
const key = contentHash({
|
|
245
|
-
artifact: canonicalJson(input.artifact),
|
|
246
|
-
scenarioId: input.scenario.id,
|
|
247
|
-
judgeName: judge.name,
|
|
248
|
-
dimensions: judge.dimensions,
|
|
249
|
-
judgeVersion: options.judgeVersion
|
|
250
|
-
});
|
|
251
|
-
const cached = await store.get(key);
|
|
252
|
-
if (cached !== void 0) {
|
|
253
|
-
stats.hits += 1;
|
|
254
|
-
return cached;
|
|
255
|
-
}
|
|
256
|
-
const score = await judge.score(input);
|
|
257
|
-
await store.set(key, score);
|
|
258
|
-
stats.misses += 1;
|
|
259
|
-
return score;
|
|
260
|
-
},
|
|
261
|
-
stats: () => ({ ...stats })
|
|
262
|
-
};
|
|
263
|
-
if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
|
|
264
|
-
return wrapped;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/campaign/coverage.ts
|
|
268
|
-
function assertCampaignDesign(scenarios, reps) {
|
|
269
|
-
if (!Number.isSafeInteger(reps) || reps < 1) {
|
|
270
|
-
throw new Error("campaign design requires reps to be a positive safe integer");
|
|
271
|
-
}
|
|
272
|
-
const scenarioIds = /* @__PURE__ */ new Set();
|
|
273
|
-
for (const scenario of scenarios) {
|
|
274
|
-
if (typeof scenario.id !== "string" || scenario.id.trim().length === 0) {
|
|
275
|
-
throw new Error("campaign design requires every scenario to have a non-empty id");
|
|
276
|
-
}
|
|
277
|
-
if (scenarioIds.has(scenario.id)) {
|
|
278
|
-
throw new Error(`campaign design contains duplicate scenario id '${scenario.id}'`);
|
|
279
|
-
}
|
|
280
|
-
if (typeof scenario.kind !== "string" || scenario.kind.trim().length === 0) {
|
|
281
|
-
throw new Error("campaign design requires every scenario to have a non-empty kind");
|
|
282
|
-
}
|
|
283
|
-
scenarioIds.add(scenario.id);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function campaignScenarioIdentity(scenario) {
|
|
287
|
-
assertCampaignDesign([scenario], 1);
|
|
288
|
-
return {
|
|
289
|
-
id: scenario.id,
|
|
290
|
-
kind: scenario.kind,
|
|
291
|
-
scenarioDigest: `sha256:${contentHash(scenario)}`
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
function campaignSplitDigestFromIdentities(scenarios, reps) {
|
|
295
|
-
assertCampaignDesign(scenarios, reps);
|
|
296
|
-
for (const scenario of scenarios) {
|
|
297
|
-
if (!/^sha256:[a-f0-9]{64}$/.test(scenario.scenarioDigest)) {
|
|
298
|
-
throw new Error(`campaign scenario '${scenario.id}' has an invalid digest`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return `sha256:${contentHash({
|
|
302
|
-
schema: "tangle.campaign-split",
|
|
303
|
-
scenarios: scenarios.map(({ id, kind, scenarioDigest }) => ({ id, kind, scenarioDigest })),
|
|
304
|
-
reps
|
|
305
|
-
})}`;
|
|
306
|
-
}
|
|
307
|
-
function campaignSplitDigest(scenarios, reps) {
|
|
308
|
-
assertCampaignDesign(scenarios, reps);
|
|
309
|
-
return campaignSplitDigestFromIdentities(scenarios.map(campaignScenarioIdentity), reps);
|
|
310
|
-
}
|
|
311
|
-
function assertCampaignSplitIdentity(scenarios, reps, splitDigest) {
|
|
312
|
-
if (campaignSplitDigestFromIdentities(scenarios, reps) !== splitDigest) {
|
|
313
|
-
throw new Error("campaign split digest does not match its retained scenario identities");
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
|
|
317
|
-
assertCampaignDesign(scenarios, reps);
|
|
318
|
-
const expectedCellIds = designedCellIds(scenarios, reps);
|
|
319
|
-
const cellsById = /* @__PURE__ */ new Map();
|
|
320
|
-
for (const cell of cells) {
|
|
321
|
-
const matches = cellsById.get(cell.cellId) ?? [];
|
|
322
|
-
matches.push(cell);
|
|
323
|
-
cellsById.set(cell.cellId, matches);
|
|
324
|
-
}
|
|
325
|
-
const scorableCellIds = [];
|
|
326
|
-
const unscorableCells = [];
|
|
327
|
-
for (const cellId of expectedCellIds) {
|
|
328
|
-
const matches = cellsById.get(cellId) ?? [];
|
|
329
|
-
if (matches.length === 0) {
|
|
330
|
-
unscorableCells.push({ cellId, reason: "missing campaign cell" });
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
if (matches.length > 1) {
|
|
334
|
-
unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
const cell = matches[0];
|
|
338
|
-
const scoreEntries = Object.entries(cell.judgeScores);
|
|
339
|
-
const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
|
|
340
|
-
const nonFiniteScores = scoreEntries.filter(
|
|
341
|
-
([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
|
|
342
|
-
);
|
|
343
|
-
const reasons = [];
|
|
344
|
-
if (cell.error) reasons.push(cell.error);
|
|
345
|
-
if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
|
|
346
|
-
if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
|
|
347
|
-
reasons.push("no successful finite judge score");
|
|
348
|
-
}
|
|
349
|
-
if (scoreEntries.some(([, score]) => score.failed === true)) {
|
|
350
|
-
reasons.push("judge score marked failed");
|
|
351
|
-
}
|
|
352
|
-
if (nonFiniteScores.length > 0) {
|
|
353
|
-
reasons.push(
|
|
354
|
-
`non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
if (reasons.length > 0) {
|
|
358
|
-
unscorableCells.push({ cellId, reason: reasons.join("; ") });
|
|
359
|
-
} else {
|
|
360
|
-
scorableCellIds.push(cellId);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const expected = new Set(expectedCellIds);
|
|
364
|
-
for (const cell of cells) {
|
|
365
|
-
if (cell.cellId !== `${cell.scenarioId}:${cell.rep}`) {
|
|
366
|
-
unscorableCells.push({
|
|
367
|
-
cellId: cell.cellId,
|
|
368
|
-
reason: "campaign cell id does not match scenario id and rep"
|
|
369
|
-
});
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (!expected.has(cell.cellId)) {
|
|
373
|
-
unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return {
|
|
377
|
-
complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
|
|
378
|
-
expectedCellIds,
|
|
379
|
-
scorableCellIds,
|
|
380
|
-
unscorableCells
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
function formatCoverageFailures(coverage) {
|
|
384
|
-
const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
|
|
385
|
-
const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
|
|
386
|
-
return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
|
|
387
|
-
}
|
|
388
|
-
function designedCellIds(scenarios, reps) {
|
|
389
|
-
const ids = [];
|
|
390
|
-
for (const scenario of scenarios) {
|
|
391
|
-
for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
|
|
392
|
-
}
|
|
393
|
-
return ids;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// src/campaign/run-dir.ts
|
|
397
|
-
import { homedir } from "os";
|
|
398
|
-
import { basename, isAbsolute, join } from "path";
|
|
399
|
-
function tangleTracesRoot() {
|
|
400
|
-
return join(homedir(), ".tangle", "traces");
|
|
401
|
-
}
|
|
402
|
-
function resolveRunDir(runDir, repo) {
|
|
403
|
-
if (isAbsolute(runDir) || runDir.startsWith("mem://")) return runDir;
|
|
404
|
-
const r = repo && repo.trim().length > 0 ? repo : basename(process.cwd());
|
|
405
|
-
return join(tangleTracesRoot(), r, "runs", runDir);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// src/campaign/storage.ts
|
|
409
|
-
import { createRequire } from "module";
|
|
410
|
-
import { join as join2 } from "path";
|
|
411
|
-
|
|
412
|
-
// src/campaign/search-ledger-file.ts
|
|
413
|
-
import { randomUUID } from "crypto";
|
|
414
|
-
import {
|
|
415
|
-
closeSync,
|
|
416
|
-
constants,
|
|
417
|
-
existsSync as existsSync2,
|
|
418
|
-
fsyncSync,
|
|
419
|
-
linkSync,
|
|
420
|
-
mkdirSync,
|
|
421
|
-
openSync,
|
|
422
|
-
readFileSync as readFileSync2,
|
|
423
|
-
renameSync,
|
|
424
|
-
unlinkSync,
|
|
425
|
-
writeSync
|
|
426
|
-
} from "fs";
|
|
427
|
-
import { hostname } from "os";
|
|
428
|
-
import { dirname } from "path";
|
|
429
|
-
import { z } from "zod";
|
|
430
|
-
|
|
431
|
-
// src/campaign/search-ledger-errors.ts
|
|
432
|
-
var SearchLedgerError = class extends ValidationError {
|
|
433
|
-
};
|
|
434
|
-
var SearchLedgerIntegrityError = class extends SearchLedgerError {
|
|
435
|
-
};
|
|
436
|
-
var SearchLedgerConflictError = class extends SearchLedgerError {
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
// src/campaign/search-ledger-file.ts
|
|
440
|
-
function appendSearchLedgerLine(path, line) {
|
|
441
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
442
|
-
const fd = openSync(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND, 384);
|
|
443
|
-
try {
|
|
444
|
-
writeAll(fd, Buffer.from(line, "utf8"));
|
|
445
|
-
fsyncSync(fd);
|
|
446
|
-
} finally {
|
|
447
|
-
closeSync(fd);
|
|
448
|
-
}
|
|
449
|
-
fsyncDirectory(dirname(path));
|
|
450
|
-
}
|
|
451
|
-
function withSearchLedgerFileLock(ledgerPath, run) {
|
|
452
|
-
const result = tryWithSearchLedgerFileLock(ledgerPath, run);
|
|
453
|
-
if (!result.acquired) {
|
|
454
|
-
throw new SearchLedgerIntegrityError(`search ledger lock is held (${ledgerPath})`);
|
|
455
|
-
}
|
|
456
|
-
return result.value;
|
|
457
|
-
}
|
|
458
|
-
function tryWithSearchLedgerFileLock(ledgerPath, run) {
|
|
459
|
-
mkdirSync(dirname(ledgerPath), { recursive: true });
|
|
460
|
-
const lockPath = `${ledgerPath}.lock`;
|
|
461
|
-
const owner = acquireLock(lockPath);
|
|
462
|
-
if (!owner) return { acquired: false };
|
|
463
|
-
try {
|
|
464
|
-
return { acquired: true, value: run() };
|
|
465
|
-
} finally {
|
|
466
|
-
releaseLock(lockPath, owner);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function writeAll(fd, bytes) {
|
|
470
|
-
let offset = 0;
|
|
471
|
-
while (offset < bytes.byteLength) {
|
|
472
|
-
const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
|
|
473
|
-
if (written <= 0) throw new SearchLedgerIntegrityError("filesystem wrote zero bytes");
|
|
474
|
-
offset += written;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function fsyncDirectory(path) {
|
|
478
|
-
const fd = openSync(path, constants.O_RDONLY);
|
|
479
|
-
try {
|
|
480
|
-
fsyncSync(fd);
|
|
481
|
-
} finally {
|
|
482
|
-
closeSync(fd);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
function acquireLock(lockPath) {
|
|
486
|
-
const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
|
|
487
|
-
const ownerBytes = `${canonicalOwner(owner)}
|
|
488
|
-
`;
|
|
489
|
-
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
490
|
-
const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${attempt}.owner`;
|
|
491
|
-
const fd = openSync(ownerPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
492
|
-
try {
|
|
493
|
-
writeAll(fd, Buffer.from(ownerBytes, "utf8"));
|
|
494
|
-
fsyncSync(fd);
|
|
495
|
-
} finally {
|
|
496
|
-
closeSync(fd);
|
|
497
|
-
}
|
|
498
|
-
try {
|
|
499
|
-
linkSync(ownerPath, lockPath);
|
|
500
|
-
unlinkSync(ownerPath);
|
|
501
|
-
return owner;
|
|
502
|
-
} catch (error) {
|
|
503
|
-
unlinkIfExists(ownerPath);
|
|
504
|
-
if (error.code !== "EEXIST") throw error;
|
|
505
|
-
}
|
|
506
|
-
const holder = readOwner(lockPath);
|
|
507
|
-
if (holder.host !== owner.host || isProcessAlive(holder.pid)) {
|
|
508
|
-
return void 0;
|
|
509
|
-
}
|
|
510
|
-
const tombstone = `${lockPath}.stale.${owner.nonce}.${attempt}`;
|
|
511
|
-
try {
|
|
512
|
-
renameSync(lockPath, tombstone);
|
|
513
|
-
unlinkSync(tombstone);
|
|
514
|
-
} catch (error) {
|
|
515
|
-
if (error.code !== "ENOENT") throw error;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
throw new SearchLedgerIntegrityError(`could not acquire search ledger lock ${lockPath}`);
|
|
519
|
-
}
|
|
520
|
-
function releaseLock(lockPath, owner) {
|
|
521
|
-
if (!existsSync2(lockPath)) return;
|
|
522
|
-
const holder = readOwner(lockPath);
|
|
523
|
-
if (canonicalOwner(holder) !== canonicalOwner(owner)) {
|
|
524
|
-
throw new SearchLedgerIntegrityError(
|
|
525
|
-
`search ledger lock owner changed before release (${lockPath})`
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
unlinkSync(lockPath);
|
|
529
|
-
}
|
|
530
|
-
function readOwner(lockPath) {
|
|
531
|
-
let raw;
|
|
532
|
-
try {
|
|
533
|
-
raw = JSON.parse(readFileSync2(lockPath, "utf8"));
|
|
534
|
-
} catch (error) {
|
|
535
|
-
throw new SearchLedgerIntegrityError(`search ledger lock ${lockPath} is malformed`, {
|
|
536
|
-
cause: error
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
const parsed = z.object({
|
|
540
|
-
pid: z.number().int().positive().safe(),
|
|
541
|
-
host: z.string().trim().min(1),
|
|
542
|
-
nonce: z.string().trim().min(1)
|
|
543
|
-
}).strict().safeParse(raw);
|
|
544
|
-
if (!parsed.success) {
|
|
545
|
-
throw new SearchLedgerIntegrityError(
|
|
546
|
-
`search ledger lock ${lockPath} is malformed: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ")}`
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
return parsed.data;
|
|
550
|
-
}
|
|
551
|
-
function canonicalOwner(owner) {
|
|
552
|
-
return JSON.stringify({ host: owner.host, nonce: owner.nonce, pid: owner.pid });
|
|
553
|
-
}
|
|
554
|
-
function isProcessAlive(pid) {
|
|
555
|
-
try {
|
|
556
|
-
process.kill(pid, 0);
|
|
557
|
-
return true;
|
|
558
|
-
} catch (error) {
|
|
559
|
-
return error.code !== "ESRCH";
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
function unlinkIfExists(path) {
|
|
563
|
-
try {
|
|
564
|
-
unlinkSync(path);
|
|
565
|
-
} catch (error) {
|
|
566
|
-
if (error.code !== "ENOENT") throw error;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// src/campaign/storage.ts
|
|
571
|
-
function fsCampaignStorage() {
|
|
572
|
-
const nodeRequire = createRequire(import.meta.url);
|
|
573
|
-
const { existsSync: existsSync3, mkdirSync: mkdirSync2, readFileSync: readFileSync3, statSync, writeFileSync } = nodeRequire(
|
|
574
|
-
"node:fs"
|
|
575
|
-
);
|
|
576
|
-
return {
|
|
577
|
-
ensureDir(dir) {
|
|
578
|
-
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
579
|
-
},
|
|
580
|
-
exists(path) {
|
|
581
|
-
return existsSync3(path);
|
|
582
|
-
},
|
|
583
|
-
read(path) {
|
|
584
|
-
try {
|
|
585
|
-
return readFileSync3(path, "utf8");
|
|
586
|
-
} catch {
|
|
587
|
-
return void 0;
|
|
588
|
-
}
|
|
589
|
-
},
|
|
590
|
-
write(path, content) {
|
|
591
|
-
writeFileSync(path, content);
|
|
592
|
-
},
|
|
593
|
-
append(path, content, expectedBytes) {
|
|
594
|
-
const result = tryWithSearchLedgerFileLock(path, () => {
|
|
595
|
-
let actualBytes = 0;
|
|
596
|
-
try {
|
|
597
|
-
actualBytes = statSync(path).size;
|
|
598
|
-
} catch (error) {
|
|
599
|
-
if (error.code !== "ENOENT") throw error;
|
|
600
|
-
}
|
|
601
|
-
if (actualBytes !== expectedBytes) return void 0;
|
|
602
|
-
appendSearchLedgerLine(path, content);
|
|
603
|
-
return expectedBytes + Buffer.byteLength(content);
|
|
604
|
-
});
|
|
605
|
-
return result.acquired ? result.value : void 0;
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
function inMemoryCampaignStorage() {
|
|
610
|
-
const files = /* @__PURE__ */ new Map();
|
|
611
|
-
const dirs = /* @__PURE__ */ new Set();
|
|
612
|
-
return {
|
|
613
|
-
ensureDir(dir) {
|
|
614
|
-
dirs.add(dir);
|
|
615
|
-
},
|
|
616
|
-
exists(path) {
|
|
617
|
-
return files.has(path) || dirs.has(path);
|
|
618
|
-
},
|
|
619
|
-
read(path) {
|
|
620
|
-
const value = files.get(path);
|
|
621
|
-
if (value === void 0) return void 0;
|
|
622
|
-
return typeof value === "string" ? value : new TextDecoder().decode(value);
|
|
623
|
-
},
|
|
624
|
-
write(path, content) {
|
|
625
|
-
files.set(path, content);
|
|
626
|
-
},
|
|
627
|
-
append(path, content, expectedBytes) {
|
|
628
|
-
const current = files.get(path);
|
|
629
|
-
const currentText = current === void 0 ? "" : typeof current === "string" ? current : new TextDecoder().decode(current);
|
|
630
|
-
const currentBytes = new TextEncoder().encode(currentText).byteLength;
|
|
631
|
-
if (currentBytes !== expectedBytes) return void 0;
|
|
632
|
-
files.set(path, `${currentText}${content}`);
|
|
633
|
-
return currentBytes + new TextEncoder().encode(content).byteLength;
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function createRunCostLedger(input) {
|
|
638
|
-
const path = join2(input.runDir, "cost-ledger.jsonl");
|
|
639
|
-
input.storage.ensureDir(input.runDir);
|
|
640
|
-
return new CostLedger({
|
|
641
|
-
costCeilingUsd: input.costCeilingUsd,
|
|
642
|
-
persistence: {
|
|
643
|
-
read: () => {
|
|
644
|
-
const stored = input.storage.read(path);
|
|
645
|
-
if (stored === void 0 && input.storage.exists(path)) {
|
|
646
|
-
throw new Error(`CostLedger: cannot read existing event log '${path}'`);
|
|
647
|
-
}
|
|
648
|
-
const events = stored ?? "";
|
|
649
|
-
return {
|
|
650
|
-
revision: String(new TextEncoder().encode(events).byteLength),
|
|
651
|
-
events
|
|
652
|
-
};
|
|
653
|
-
},
|
|
654
|
-
append: (expectedRevision, event) => {
|
|
655
|
-
const expectedBytes = Number(expectedRevision);
|
|
656
|
-
if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {
|
|
657
|
-
throw new Error(`CostLedger: invalid storage revision '${expectedRevision}'`);
|
|
658
|
-
}
|
|
659
|
-
if (!input.storage.append) {
|
|
660
|
-
throw new Error("CostLedger: CampaignStorage.append is required for paid calls");
|
|
661
|
-
}
|
|
662
|
-
const next = input.storage.append(path, event, expectedBytes);
|
|
663
|
-
return next === void 0 ? void 0 : String(next);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/campaign/run-campaign.ts
|
|
670
|
-
async function runCampaign(opts) {
|
|
671
|
-
const seed = opts.seed ?? 42;
|
|
672
|
-
const reps = opts.reps ?? 1;
|
|
673
|
-
const resumable = opts.resumable ?? true;
|
|
674
|
-
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
675
|
-
const judges = opts.judges ?? [];
|
|
676
|
-
const storage = opts.storage ?? fsCampaignStorage();
|
|
677
|
-
const costPhase = opts.costPhase ?? "campaign";
|
|
678
|
-
assertCampaignDesign(opts.scenarios, reps);
|
|
679
|
-
if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
|
|
680
|
-
throw new Error("runCampaign: runDir is required and must be a non-empty string");
|
|
681
|
-
}
|
|
682
|
-
opts.runDir = resolveRunDir(opts.runDir, opts.repo);
|
|
683
|
-
storage.ensureDir(opts.runDir);
|
|
684
|
-
const costLedger = opts.costLedger ?? createRunCostLedger({
|
|
685
|
-
storage,
|
|
686
|
-
runDir: opts.runDir,
|
|
687
|
-
costCeilingUsd: opts.costCeiling
|
|
688
|
-
});
|
|
689
|
-
if (opts.costCeiling !== void 0 && costLedger.costCeilingUsd !== opts.costCeiling) {
|
|
690
|
-
throw new Error("runCampaign: costCeiling must match the shared CostLedger ceiling");
|
|
691
|
-
}
|
|
692
|
-
const maxConcurrency = opts.maxConcurrency ?? 2;
|
|
693
|
-
const manifestHash = computeManifestHash({
|
|
694
|
-
scenarios: opts.scenarios,
|
|
695
|
-
judges,
|
|
696
|
-
dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
|
|
697
|
-
seed,
|
|
698
|
-
reps
|
|
699
|
-
});
|
|
700
|
-
const splitDigest = campaignSplitDigest(opts.scenarios, reps);
|
|
701
|
-
const startedAt = now();
|
|
702
|
-
const runAttemptId = globalThis.crypto.randomUUID();
|
|
703
|
-
const cells = [];
|
|
704
|
-
const artifactsByPath = {};
|
|
705
|
-
const schedule = buildCellSchedule(opts.scenarios, seed, reps);
|
|
706
|
-
const abortController = new AbortController();
|
|
707
|
-
const lanes = [];
|
|
708
|
-
let nextIdx = 0;
|
|
709
|
-
const cellsRef = cells;
|
|
710
|
-
for (let i = 0; i < maxConcurrency; i++) {
|
|
711
|
-
lanes.push(
|
|
712
|
-
(async () => {
|
|
713
|
-
while (true) {
|
|
714
|
-
const myIdx = nextIdx++;
|
|
715
|
-
if (myIdx >= schedule.length) return;
|
|
716
|
-
const slot = schedule[myIdx];
|
|
717
|
-
const result = await executeCell({
|
|
718
|
-
slot,
|
|
719
|
-
opts,
|
|
720
|
-
manifestHash,
|
|
721
|
-
resumable,
|
|
722
|
-
now,
|
|
723
|
-
storage,
|
|
724
|
-
buildTraceWriter: opts.buildTraceWriter ?? defaultBuildTraceWriter(storage),
|
|
725
|
-
signal: abortController.signal,
|
|
726
|
-
dispatchTimeoutMs: opts.dispatchTimeoutMs,
|
|
727
|
-
costLedger,
|
|
728
|
-
costPhase,
|
|
729
|
-
runAttemptId
|
|
730
|
-
});
|
|
731
|
-
cellsRef.push(result.cell);
|
|
732
|
-
Object.assign(artifactsByPath, result.artifactsByPath);
|
|
733
|
-
if (opts.labeledStore && opts.labeledStore !== "off" && !result.cell.error) {
|
|
734
|
-
await captureToStore({
|
|
735
|
-
store: opts.labeledStore,
|
|
736
|
-
cell: result.cell,
|
|
737
|
-
scenario: slot.scenario,
|
|
738
|
-
opts,
|
|
739
|
-
now
|
|
740
|
-
}).catch((err) => {
|
|
741
|
-
console.warn(
|
|
742
|
-
`[runCampaign] capture failed for ${result.cell.cellId}: ${err instanceof Error ? err.message : String(err)}`
|
|
743
|
-
);
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
})()
|
|
748
|
-
);
|
|
749
|
-
}
|
|
750
|
-
await Promise.all(lanes);
|
|
751
|
-
const endedAt = now();
|
|
752
|
-
cellsRef.sort((a, b) => a.cellId.localeCompare(b.cellId));
|
|
753
|
-
const campaignCost = costLedger.summary({ tags: { runDir: opts.runDir } });
|
|
754
|
-
const aggregates = computeAggregates(
|
|
755
|
-
cellsRef,
|
|
756
|
-
judges,
|
|
757
|
-
seed,
|
|
758
|
-
campaignCost
|
|
759
|
-
);
|
|
760
|
-
return {
|
|
761
|
-
manifestHash,
|
|
762
|
-
splitDigest,
|
|
763
|
-
seed,
|
|
764
|
-
reps,
|
|
765
|
-
startedAt: startedAt.toISOString(),
|
|
766
|
-
endedAt: endedAt.toISOString(),
|
|
767
|
-
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
768
|
-
cells: cellsRef,
|
|
769
|
-
aggregates,
|
|
770
|
-
runDir: opts.runDir,
|
|
771
|
-
artifactsByPath,
|
|
772
|
-
scenarios: opts.scenarios.map(campaignScenarioIdentity)
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
async function executeCell(args) {
|
|
776
|
-
const storage = args.storage;
|
|
777
|
-
const cellDir = join3(args.opts.runDir, args.slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"));
|
|
778
|
-
storage.ensureDir(cellDir);
|
|
779
|
-
const stableCostTags = {
|
|
780
|
-
runDir: args.opts.runDir,
|
|
781
|
-
cellId: args.slot.cellId,
|
|
782
|
-
scenarioId: args.slot.scenario.id,
|
|
783
|
-
rep: String(args.slot.rep)
|
|
784
|
-
};
|
|
785
|
-
const costTags = { ...stableCostTags, runAttemptId: args.runAttemptId };
|
|
786
|
-
const cachePath = join3(cellDir, "cached-result.json");
|
|
787
|
-
if (args.resumable) {
|
|
788
|
-
const cached = readCachedCell({
|
|
789
|
-
storage,
|
|
790
|
-
cachePath,
|
|
791
|
-
cellId: args.slot.cellId,
|
|
792
|
-
manifestHash: args.manifestHash
|
|
793
|
-
});
|
|
794
|
-
if (cached.status === "hit") {
|
|
795
|
-
enforceDispatchUsage(cached.cell, args.opts.expectUsage ?? "warn");
|
|
796
|
-
const cachedHasUsage = cached.cell.costUsd > 0 || cached.cell.tokenUsage.input > 0 || cached.cell.tokenUsage.output > 0;
|
|
797
|
-
if (cached.cell.costCallIds === void 0) {
|
|
798
|
-
if (cachedHasUsage || Object.keys(cached.cell.judgeScores).length > 0) {
|
|
799
|
-
throw new CostAccountingIncompleteError(
|
|
800
|
-
`runCampaign: cached cell '${args.slot.cellId}' does not identify its ledger receipts`
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
} else if (!Array.isArray(cached.cell.costCallIds) || cached.cell.costCallIds.some(
|
|
804
|
-
(callId) => typeof callId !== "string" || callId.trim().length === 0
|
|
805
|
-
) || new Set(cached.cell.costCallIds).size !== cached.cell.costCallIds.length) {
|
|
806
|
-
throw new CostAccountingIncompleteError(
|
|
807
|
-
`runCampaign: cached cell '${args.slot.cellId}' has invalid ledger receipt IDs`
|
|
808
|
-
);
|
|
809
|
-
} else {
|
|
810
|
-
const restoredCallIds = new Set(
|
|
811
|
-
args.costLedger.list({ tags: stableCostTags }).map((receipt) => receipt.callId)
|
|
812
|
-
);
|
|
813
|
-
const missingCallIds = cached.cell.costCallIds.filter(
|
|
814
|
-
(callId) => !restoredCallIds.has(callId)
|
|
815
|
-
);
|
|
816
|
-
if (missingCallIds.length > 0) {
|
|
817
|
-
throw new CostAccountingIncompleteError(
|
|
818
|
-
`runCampaign: cached cell '${args.slot.cellId}' is missing ledger receipt(s): ${missingCallIds.join(", ")}`
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return { cell: { ...cached.cell, cached: true }, artifactsByPath: {} };
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
const startMs = Date.now();
|
|
826
|
-
const trace = args.buildTraceWriter(args.slot.cellId, cellDir);
|
|
827
|
-
const artifactsByPath = {};
|
|
828
|
-
const artifacts = {
|
|
829
|
-
async write(path, content) {
|
|
830
|
-
const fullPath = join3(cellDir, path);
|
|
831
|
-
storage.ensureDir(join3(fullPath, ".."));
|
|
832
|
-
storage.write(fullPath, content);
|
|
833
|
-
artifactsByPath[`${args.slot.cellId}/${path}`] = fullPath;
|
|
834
|
-
return fullPath;
|
|
835
|
-
},
|
|
836
|
-
async writeJson(path, value) {
|
|
837
|
-
return artifacts.write(path, JSON.stringify(value, null, 2));
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
const cost = {
|
|
841
|
-
async runPaidCall(input) {
|
|
842
|
-
const result = await args.costLedger.runPaidCall({
|
|
843
|
-
...input,
|
|
844
|
-
channel: input.channel ?? "agent",
|
|
845
|
-
phase: args.costPhase,
|
|
846
|
-
actor: input.actor,
|
|
847
|
-
tags: costTags,
|
|
848
|
-
signal: cellAbort.signal
|
|
849
|
-
});
|
|
850
|
-
if (result.receipt) {
|
|
851
|
-
trace.span(`cost.${result.receipt.actor}`, { amountUsd: result.receipt.costUsd }).end();
|
|
852
|
-
}
|
|
853
|
-
return result;
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
const placement = args.opts.cellPlacement?.({
|
|
857
|
-
scenario: args.slot.scenario,
|
|
858
|
-
rep: args.slot.rep
|
|
859
|
-
});
|
|
860
|
-
const cellAbort = new AbortController();
|
|
861
|
-
const onCampaignAbort = () => cellAbort.abort(args.signal.reason);
|
|
862
|
-
if (args.signal.aborted) cellAbort.abort(args.signal.reason);
|
|
863
|
-
else args.signal.addEventListener("abort", onCampaignAbort, { once: true });
|
|
864
|
-
const ctx = {
|
|
865
|
-
cellId: args.slot.cellId,
|
|
866
|
-
rep: args.slot.rep,
|
|
867
|
-
seed: args.slot.cellSeed,
|
|
868
|
-
signal: cellAbort.signal,
|
|
869
|
-
trace,
|
|
870
|
-
artifacts,
|
|
871
|
-
cost,
|
|
872
|
-
placement
|
|
873
|
-
};
|
|
874
|
-
let artifact;
|
|
875
|
-
let errorMessage;
|
|
876
|
-
const timeoutMs = args.dispatchTimeoutMs;
|
|
877
|
-
let timeoutTimer;
|
|
878
|
-
try {
|
|
879
|
-
const dispatched = args.opts.dispatch(args.slot.scenario, ctx);
|
|
880
|
-
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
881
|
-
artifact = await Promise.race([
|
|
882
|
-
dispatched,
|
|
883
|
-
new Promise((_, reject) => {
|
|
884
|
-
timeoutTimer = setTimeout(() => {
|
|
885
|
-
cellAbort.abort(new Error("dispatch timeout"));
|
|
886
|
-
reject(
|
|
887
|
-
new Error(
|
|
888
|
-
`dispatch exceeded ${timeoutMs}ms for cell '${args.slot.cellId}' \u2014 aborted and failed loud (no silent hang)`
|
|
889
|
-
)
|
|
890
|
-
);
|
|
891
|
-
}, timeoutMs);
|
|
892
|
-
if (typeof timeoutTimer.unref === "function")
|
|
893
|
-
timeoutTimer.unref();
|
|
894
|
-
})
|
|
895
|
-
]);
|
|
896
|
-
} else {
|
|
897
|
-
artifact = await dispatched;
|
|
898
|
-
}
|
|
899
|
-
} catch (err) {
|
|
900
|
-
errorMessage = err instanceof Error ? err.message : String(err);
|
|
901
|
-
} finally {
|
|
902
|
-
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
903
|
-
args.signal.removeEventListener("abort", onCampaignAbort);
|
|
904
|
-
}
|
|
905
|
-
const agentReceipts = args.costLedger.list({ channel: "agent", tags: costTags });
|
|
906
|
-
const agentCost = args.costLedger.summary({ channel: "agent", tags: costTags });
|
|
907
|
-
const tokenUsage = {
|
|
908
|
-
input: agentCost.inputTokens,
|
|
909
|
-
output: agentCost.outputTokens,
|
|
910
|
-
...agentCost.cachedTokens > 0 ? { cached: agentCost.cachedTokens } : {}
|
|
911
|
-
};
|
|
912
|
-
const resolvedModel = agentReceipts.at(-1)?.model;
|
|
913
|
-
const dispatchResult = {
|
|
914
|
-
cellId: args.slot.cellId,
|
|
915
|
-
artifact,
|
|
916
|
-
error: errorMessage,
|
|
917
|
-
costUsd: agentCost.totalCostUsd,
|
|
918
|
-
tokenUsage
|
|
919
|
-
};
|
|
920
|
-
try {
|
|
921
|
-
enforceDispatchUsage(dispatchResult, args.opts.expectUsage ?? "warn");
|
|
922
|
-
} catch (error) {
|
|
923
|
-
await trace.flush();
|
|
924
|
-
throw error;
|
|
925
|
-
}
|
|
926
|
-
const judgeScores = {};
|
|
927
|
-
if (artifact !== void 0) {
|
|
928
|
-
for (const judge of args.opts.judges ?? []) {
|
|
929
|
-
if (judge.appliesTo && !judge.appliesTo(args.slot.scenario)) continue;
|
|
930
|
-
try {
|
|
931
|
-
const score = await runJudgeCell(judge, {
|
|
932
|
-
artifact,
|
|
933
|
-
scenario: args.slot.scenario,
|
|
934
|
-
signal: args.signal,
|
|
935
|
-
costLedger: args.costLedger,
|
|
936
|
-
costPhase: args.costPhase,
|
|
937
|
-
costTags
|
|
938
|
-
});
|
|
939
|
-
judgeScores[judge.name] = score;
|
|
940
|
-
} catch (err) {
|
|
941
|
-
if (err instanceof CostAccountingIncompleteError) {
|
|
942
|
-
await trace.flush();
|
|
943
|
-
throw err;
|
|
944
|
-
}
|
|
945
|
-
errorMessage = `judge '${judge.name}' failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
946
|
-
break;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
await trace.flush();
|
|
951
|
-
const costCallIds = args.costLedger.list({ tags: costTags }).map((receipt) => receipt.callId).sort();
|
|
952
|
-
const cell = {
|
|
953
|
-
manifestHash: args.manifestHash,
|
|
954
|
-
cellId: args.slot.cellId,
|
|
955
|
-
scenarioId: args.slot.scenario.id,
|
|
956
|
-
rep: args.slot.rep,
|
|
957
|
-
artifact: artifact ?? null,
|
|
958
|
-
judgeScores,
|
|
959
|
-
costUsd: agentCost.totalCostUsd,
|
|
960
|
-
costEstimated: agentReceipts.some(
|
|
961
|
-
(receipt) => receipt.actualCostUsd === void 0 && !receipt.costUnknown
|
|
962
|
-
),
|
|
963
|
-
costCallIds,
|
|
964
|
-
tokenUsage,
|
|
965
|
-
...resolvedModel ? { resolvedModel } : {},
|
|
966
|
-
durationMs: Date.now() - startMs,
|
|
967
|
-
seed: args.slot.cellSeed,
|
|
968
|
-
cached: false,
|
|
969
|
-
error: errorMessage
|
|
970
|
-
};
|
|
971
|
-
if (!errorMessage && args.resumable) {
|
|
972
|
-
storage.write(cachePath, JSON.stringify(cell));
|
|
973
|
-
}
|
|
974
|
-
return { cell, artifactsByPath };
|
|
975
|
-
}
|
|
976
|
-
function planCampaignRun(opts) {
|
|
977
|
-
const seed = opts.seed ?? 42;
|
|
978
|
-
const reps = opts.reps ?? 1;
|
|
979
|
-
const resumable = opts.resumable ?? true;
|
|
980
|
-
const storage = opts.storage ?? fsCampaignStorage();
|
|
981
|
-
assertCampaignDesign(opts.scenarios, reps);
|
|
982
|
-
if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
|
|
983
|
-
throw new Error("planCampaignRun: runDir is required and must be a non-empty string");
|
|
984
|
-
}
|
|
985
|
-
opts.runDir = resolveRunDir(opts.runDir, opts.repo);
|
|
986
|
-
const manifestHash = computeManifestHash({
|
|
987
|
-
scenarios: opts.scenarios,
|
|
988
|
-
judges: opts.judges ?? [],
|
|
989
|
-
dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
|
|
990
|
-
seed,
|
|
991
|
-
reps
|
|
992
|
-
});
|
|
993
|
-
const splitDigest = campaignSplitDigest(opts.scenarios, reps);
|
|
994
|
-
const cells = buildCellSchedule(opts.scenarios, seed, reps).map((slot) => {
|
|
995
|
-
const cachePath = join3(
|
|
996
|
-
opts.runDir,
|
|
997
|
-
slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
998
|
-
"cached-result.json"
|
|
999
|
-
);
|
|
1000
|
-
if (!resumable) {
|
|
1001
|
-
return {
|
|
1002
|
-
cellId: slot.cellId,
|
|
1003
|
-
scenarioId: slot.scenario.id,
|
|
1004
|
-
rep: slot.rep,
|
|
1005
|
-
seed: slot.cellSeed,
|
|
1006
|
-
cachePath,
|
|
1007
|
-
status: "run",
|
|
1008
|
-
reason: "resumable-off"
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
const cached = readCachedCell({
|
|
1012
|
-
storage,
|
|
1013
|
-
cachePath,
|
|
1014
|
-
cellId: slot.cellId,
|
|
1015
|
-
manifestHash
|
|
1016
|
-
});
|
|
1017
|
-
if (cached.status === "hit") {
|
|
1018
|
-
return {
|
|
1019
|
-
cellId: slot.cellId,
|
|
1020
|
-
scenarioId: slot.scenario.id,
|
|
1021
|
-
rep: slot.rep,
|
|
1022
|
-
seed: slot.cellSeed,
|
|
1023
|
-
cachePath,
|
|
1024
|
-
status: "cached"
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
return {
|
|
1028
|
-
cellId: slot.cellId,
|
|
1029
|
-
scenarioId: slot.scenario.id,
|
|
1030
|
-
rep: slot.rep,
|
|
1031
|
-
seed: slot.cellSeed,
|
|
1032
|
-
cachePath,
|
|
1033
|
-
status: "run",
|
|
1034
|
-
reason: cached.reason
|
|
1035
|
-
};
|
|
1036
|
-
});
|
|
1037
|
-
const cellsCached = cells.filter((cell) => cell.status === "cached").length;
|
|
1038
|
-
return {
|
|
1039
|
-
manifestHash,
|
|
1040
|
-
splitDigest,
|
|
1041
|
-
totalCells: cells.length,
|
|
1042
|
-
cellsCached,
|
|
1043
|
-
cellsToRun: cells.length - cellsCached,
|
|
1044
|
-
cells
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
function enforceDispatchUsage(cell, mode) {
|
|
1048
|
-
if (mode === "off") return;
|
|
1049
|
-
if (cell.artifact === null || cell.artifact === void 0) return;
|
|
1050
|
-
const zeroTokens = cell.tokenUsage.input === 0 && cell.tokenUsage.output === 0;
|
|
1051
|
-
if (cell.costUsd !== 0 || !zeroTokens) return;
|
|
1052
|
-
const msg = `cell '${cell.cellId}' produced an artifact but reported zero cost and zero tokens \u2014 the dispatch made no paid call through ctx.cost.runPaidCall (a stub cell)`;
|
|
1053
|
-
if (mode === "assert") {
|
|
1054
|
-
const report = {
|
|
1055
|
-
totalRecords: 1,
|
|
1056
|
-
stubRecords: 1,
|
|
1057
|
-
realRecords: 0,
|
|
1058
|
-
uncostedRecords: 0,
|
|
1059
|
-
totalInputTokens: 0,
|
|
1060
|
-
totalOutputTokens: 0,
|
|
1061
|
-
totalCostUsd: 0,
|
|
1062
|
-
verdict: "stub",
|
|
1063
|
-
diagnosis: msg
|
|
1064
|
-
};
|
|
1065
|
-
throw new BackendIntegrityError(`expectUsage: ${msg}`, report);
|
|
1066
|
-
}
|
|
1067
|
-
console.warn(`[runCampaign] expectUsage: ${msg}`);
|
|
1068
|
-
}
|
|
1069
|
-
async function runJudgeCell(judge, input) {
|
|
1070
|
-
const previousJudgeCalls = new Set(
|
|
1071
|
-
input.costLedger?.list({ channel: "judge", tags: input.costTags }).map((receipt) => receipt.callId) ?? []
|
|
1072
|
-
);
|
|
1073
|
-
try {
|
|
1074
|
-
const score = await judge.score(input);
|
|
1075
|
-
assertReportedJudgeCallRecorded(judge.name, score, input, previousJudgeCalls);
|
|
1076
|
-
return score;
|
|
1077
|
-
} catch (error) {
|
|
1078
|
-
assertReportedJudgeCallRecorded(judge.name, error, input, previousJudgeCalls, error);
|
|
1079
|
-
throw error;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
function assertReportedJudgeCallRecorded(judgeName, value, input, previousCallIds, cause) {
|
|
1083
|
-
if (!hasLlmCall(value)) return;
|
|
1084
|
-
const recorded = input.costLedger?.list({ channel: "judge", tags: input.costTags }).some((receipt) => !previousCallIds.has(receipt.callId));
|
|
1085
|
-
if (recorded) return;
|
|
1086
|
-
throw new CostAccountingIncompleteError(
|
|
1087
|
-
`runCampaign: judge '${judgeName}' reported a paid LLM call without a CostLedger receipt`,
|
|
1088
|
-
cause === void 0 ? void 0 : { cause }
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
function hasLlmCall(value) {
|
|
1092
|
-
return typeof value === "object" && value !== null && "llmCall" in value && value.llmCall !== void 0;
|
|
1093
|
-
}
|
|
1094
|
-
function defaultBuildTraceWriter(storage) {
|
|
1095
|
-
return (cellId, dir) => {
|
|
1096
|
-
const spans = [];
|
|
1097
|
-
return {
|
|
1098
|
-
span(name, attributes) {
|
|
1099
|
-
const startMs = Date.now();
|
|
1100
|
-
const record = { name, cellId, startMs, ...attributes ?? {} };
|
|
1101
|
-
const finish = {
|
|
1102
|
-
end(endAttrs) {
|
|
1103
|
-
record.durationMs = Date.now() - startMs;
|
|
1104
|
-
if (endAttrs) Object.assign(record, endAttrs);
|
|
1105
|
-
spans.push(record);
|
|
1106
|
-
},
|
|
1107
|
-
setAttribute(key, value) {
|
|
1108
|
-
record[key] = value;
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
1111
|
-
return finish;
|
|
1112
|
-
},
|
|
1113
|
-
async flush() {
|
|
1114
|
-
storage.write(join3(dir, "spans.jsonl"), spans.map((s) => JSON.stringify(s)).join("\n"));
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
function buildCellSchedule(scenarios, seed, reps) {
|
|
1120
|
-
const schedule = [];
|
|
1121
|
-
let cellIndex = 0;
|
|
1122
|
-
for (const scenario of scenarios) {
|
|
1123
|
-
for (let rep = 0; rep < reps; rep++) {
|
|
1124
|
-
const cellId = `${scenario.id}:${rep}`;
|
|
1125
|
-
const cellSeed = seed + cellIndex;
|
|
1126
|
-
schedule.push({ scenario, rep, cellId, cellSeed });
|
|
1127
|
-
cellIndex += 1;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
return schedule;
|
|
1131
|
-
}
|
|
1132
|
-
function dispatchRefFor(dispatch, override) {
|
|
1133
|
-
const ref = override ?? dispatch?.name ?? "anonymous";
|
|
1134
|
-
if (typeof ref !== "string" || ref.trim().length === 0) {
|
|
1135
|
-
throw new Error("runCampaign: dispatchRef must be a non-empty string when provided");
|
|
1136
|
-
}
|
|
1137
|
-
return ref;
|
|
1138
|
-
}
|
|
1139
|
-
function readCachedCell(args) {
|
|
1140
|
-
const raw = args.storage.read(args.cachePath);
|
|
1141
|
-
if (raw === void 0) return { status: "miss", reason: "missing" };
|
|
1142
|
-
try {
|
|
1143
|
-
const cached = JSON.parse(raw);
|
|
1144
|
-
if (cached.cellId !== args.cellId) return { status: "miss", reason: "cell-mismatch" };
|
|
1145
|
-
if (cached.manifestHash !== args.manifestHash) {
|
|
1146
|
-
return { status: "miss", reason: "manifest-mismatch" };
|
|
1147
|
-
}
|
|
1148
|
-
return { status: "hit", cell: cached };
|
|
1149
|
-
} catch {
|
|
1150
|
-
return { status: "miss", reason: "corrupt" };
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
async function captureToStore(args) {
|
|
1154
|
-
await args.store.observe({
|
|
1155
|
-
scenario: args.scenario,
|
|
1156
|
-
artifact: args.cell.artifact,
|
|
1157
|
-
judgeScores: args.cell.judgeScores,
|
|
1158
|
-
source: args.opts.captureSource ?? "eval-run",
|
|
1159
|
-
sourceVersionHash: args.opts.captureSourceVersionHash ?? "unknown",
|
|
1160
|
-
capturedAt: args.now().toISOString(),
|
|
1161
|
-
redactionStatus: "raw"
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
function computeManifestHash(input) {
|
|
1165
|
-
return contentHash({
|
|
1166
|
-
scenarios: input.scenarios,
|
|
1167
|
-
judges: input.judges.map((judge) => ({
|
|
1168
|
-
name: judge.name,
|
|
1169
|
-
dims: judge.dimensions,
|
|
1170
|
-
version: judgeVersionFor(judge)
|
|
1171
|
-
})),
|
|
1172
|
-
dispatch: input.dispatchRef,
|
|
1173
|
-
seed: input.seed,
|
|
1174
|
-
reps: input.reps
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1177
|
-
function judgeVersionFor(judge) {
|
|
1178
|
-
if (judge.judgeVersion !== void 0) {
|
|
1179
|
-
const version = judge.judgeVersion.trim();
|
|
1180
|
-
if (version.length === 0) {
|
|
1181
|
-
throw new Error(`runCampaign: judge '${judge.name}' has an empty judgeVersion`);
|
|
1182
|
-
}
|
|
1183
|
-
return version;
|
|
1184
|
-
}
|
|
1185
|
-
return contentHash({
|
|
1186
|
-
score: judge.score.toString(),
|
|
1187
|
-
appliesTo: judge.appliesTo?.toString() ?? null
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
function computeAggregates(cells, judges, seed, cost) {
|
|
1191
|
-
const byJudge = {};
|
|
1192
|
-
for (const judge of judges) {
|
|
1193
|
-
const scores = [];
|
|
1194
|
-
for (const cell of cells) {
|
|
1195
|
-
const s = cell.judgeScores[judge.name];
|
|
1196
|
-
if (s !== void 0) scores.push(s.composite);
|
|
1197
|
-
}
|
|
1198
|
-
byJudge[judge.name] = aggregate(scores, seed);
|
|
1199
|
-
}
|
|
1200
|
-
const byScenario = {};
|
|
1201
|
-
const scenarioGroups = /* @__PURE__ */ new Map();
|
|
1202
|
-
for (const cell of cells) {
|
|
1203
|
-
const composites = Object.values(cell.judgeScores).map((s) => s.composite);
|
|
1204
|
-
if (composites.length === 0) continue;
|
|
1205
|
-
const mean = composites.reduce((a, b) => a + b, 0) / composites.length;
|
|
1206
|
-
const arr = scenarioGroups.get(cell.scenarioId) ?? [];
|
|
1207
|
-
arr.push(mean);
|
|
1208
|
-
scenarioGroups.set(cell.scenarioId, arr);
|
|
1209
|
-
}
|
|
1210
|
-
for (const [scenarioId, samples] of scenarioGroups) {
|
|
1211
|
-
const ag = aggregate(samples, seed);
|
|
1212
|
-
byScenario[scenarioId] = { meanComposite: ag.mean, ci95: ag.ci95, n: ag.n };
|
|
1213
|
-
}
|
|
1214
|
-
return {
|
|
1215
|
-
byJudge,
|
|
1216
|
-
byScenario,
|
|
1217
|
-
cost,
|
|
1218
|
-
totalCostUsd: cost.totalCostUsd,
|
|
1219
|
-
cellsExecuted: cells.filter((c) => !c.error).length,
|
|
1220
|
-
cellsSkipped: cells.filter((c) => c.error?.startsWith("skipped:")).length,
|
|
1221
|
-
cellsCached: cells.filter((c) => c.cached).length,
|
|
1222
|
-
cellsFailed: cells.filter((c) => c.error && !c.error.startsWith("skipped:")).length
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
function aggregate(samples, seed) {
|
|
1226
|
-
const n = samples.length;
|
|
1227
|
-
if (n === 0) return { mean: 0, stdev: 0, ci95: [0, 0], n: 0 };
|
|
1228
|
-
const mean = samples.reduce((a, b) => a + b, 0) / n;
|
|
1229
|
-
const variance = samples.reduce((a, b) => a + (b - mean) ** 2, 0) / Math.max(1, n - 1);
|
|
1230
|
-
const stdev = Math.sqrt(variance);
|
|
1231
|
-
const ci = confidenceInterval(samples, 0.95, { seed, resamples: 1e3 });
|
|
1232
|
-
return { mean, stdev, ci95: [ci.lower, ci.upper], n };
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
export {
|
|
1236
|
-
BackendIntegrityError,
|
|
1237
|
-
summarizeBackendIntegrity,
|
|
1238
|
-
summarizeAgentReceiptIntegrity,
|
|
1239
|
-
assertRealBackend,
|
|
1240
|
-
assertRealAgentReceipts,
|
|
1241
|
-
canonicalJson,
|
|
1242
|
-
contentHash,
|
|
1243
|
-
inMemoryVerdictCache,
|
|
1244
|
-
fileVerdictCache,
|
|
1245
|
-
cachedJudge,
|
|
1246
|
-
assertCampaignDesign,
|
|
1247
|
-
campaignScenarioIdentity,
|
|
1248
|
-
campaignSplitDigestFromIdentities,
|
|
1249
|
-
campaignSplitDigest,
|
|
1250
|
-
assertCampaignSplitIdentity,
|
|
1251
|
-
campaignCoverage,
|
|
1252
|
-
formatCoverageFailures,
|
|
1253
|
-
tangleTracesRoot,
|
|
1254
|
-
resolveRunDir,
|
|
1255
|
-
SearchLedgerError,
|
|
1256
|
-
SearchLedgerIntegrityError,
|
|
1257
|
-
SearchLedgerConflictError,
|
|
1258
|
-
appendSearchLedgerLine,
|
|
1259
|
-
withSearchLedgerFileLock,
|
|
1260
|
-
fsCampaignStorage,
|
|
1261
|
-
inMemoryCampaignStorage,
|
|
1262
|
-
createRunCostLedger,
|
|
1263
|
-
runCampaign,
|
|
1264
|
-
planCampaignRun
|
|
1265
|
-
};
|
|
1266
|
-
//# sourceMappingURL=chunk-XDIRG3TO.js.map
|