@skill-harness/core 0.10.0 → 0.12.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/README.md +2 -1
- package/dist/adapters/types.d.ts +30 -0
- package/dist/adjudication.d.ts +9 -1
- package/dist/adjudication.js +41 -33
- package/dist/arms.d.ts +51 -0
- package/dist/arms.js +255 -0
- package/dist/capture-trace-types.d.ts +2 -0
- package/dist/command-cost.d.ts +9 -0
- package/dist/command-cost.js +23 -0
- package/dist/comparison.d.ts +1 -0
- package/dist/comparison.js +9 -3
- package/dist/execution-trace.js +9 -1
- package/dist/grade.d.ts +2 -0
- package/dist/grade.js +2 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/journal.d.ts +2 -1
- package/dist/judge-agreement.d.ts +19 -0
- package/dist/judge-agreement.js +31 -0
- package/dist/judge-policy.js +5 -3
- package/dist/lift.js +1 -1
- package/dist/lint.js +1 -1
- package/dist/prompt-normalization.d.ts +6 -0
- package/dist/prompt-normalization.js +14 -0
- package/dist/provider-failure.d.ts +56 -0
- package/dist/provider-failure.js +98 -0
- package/dist/qualification-capture.d.ts +44 -0
- package/dist/qualification-capture.js +163 -0
- package/dist/qualification-config.d.ts +233 -0
- package/dist/qualification-config.js +694 -0
- package/dist/qualification-lock.d.ts +15 -0
- package/dist/qualification-lock.js +87 -0
- package/dist/qualification-oauth-directory.d.ts +79 -0
- package/dist/qualification-oauth-directory.js +444 -0
- package/dist/qualification-panel-store.d.ts +14 -0
- package/dist/qualification-panel-store.js +188 -0
- package/dist/qualification-panels.d.ts +99 -0
- package/dist/qualification-panels.js +91 -0
- package/dist/qualification-process.d.ts +13 -0
- package/dist/qualification-process.js +188 -0
- package/dist/qualification-runner.d.ts +203 -0
- package/dist/qualification-runner.js +2037 -0
- package/dist/qualification-store.d.ts +187 -0
- package/dist/qualification-store.js +833 -0
- package/dist/qualification-validation.d.ts +12 -0
- package/dist/qualification-validation.js +8 -0
- package/dist/regate.js +49 -10
- package/dist/regrade.d.ts +3 -1
- package/dist/regrade.js +62 -6
- package/dist/reps.d.ts +4 -2
- package/dist/reps.js +13 -4
- package/dist/rescore.js +9 -1
- package/dist/results.d.ts +99 -7
- package/dist/results.js +228 -10
- package/dist/run.d.ts +14 -0
- package/dist/run.js +260 -57
- package/dist/score.d.ts +4 -3
- package/dist/score.js +13 -4
- package/dist/screen.d.ts +31 -0
- package/dist/screen.js +105 -0
- package/dist/seeded.d.ts +12 -1
- package/dist/seeded.js +9 -2
- package/dist/sources.d.ts +7 -1
- package/dist/sources.js +13 -5
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +1 -1
- package/dist/stability.js +1 -1
- package/dist/trajectory-gates.d.ts +14 -2
- package/dist/trajectory-gates.js +26 -6
- package/dist/vote-panel.d.ts +33 -0
- package/dist/vote-panel.js +41 -0
- package/dist/workspace.js +34 -3
- package/package.json +3 -3
- package/schemas/qualification-cell-result-v1.schema.json +32 -0
- package/schemas/qualification-config-v1.schema.json +244 -0
- package/schemas/qualification-invocation-request-v1.schema.json +59 -0
- package/schemas/qualification-judge-panel-result-v1.schema.json +42 -0
- package/schemas/results-v2.schema.json +19 -21
- package/schemas/results-v3.schema.json +654 -0
- package/schemas/specification-v1.schema.json +1 -1
- package/schemas/trajectory-event-v1.schema.json +20 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Canonical final validator: invocation evidence plus panel-board completeness. */
|
|
2
|
+
export declare function validateQualificationRunnerSpool(spoolDir: string): {
|
|
3
|
+
derived: {
|
|
4
|
+
panels: number;
|
|
5
|
+
cells: number;
|
|
6
|
+
};
|
|
7
|
+
ok: true;
|
|
8
|
+
configuration_sha256: string;
|
|
9
|
+
invocations: number;
|
|
10
|
+
accounting: import("./qualification-store.js").QualificationAccountingReport;
|
|
11
|
+
terminal: number;
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { validateQualificationPanelOutputs } from "./qualification-panel-store.js";
|
|
2
|
+
import { validateQualificationBaseSpool } from "./qualification-runner.js";
|
|
3
|
+
/** Canonical final validator: invocation evidence plus panel-board completeness. */
|
|
4
|
+
export function validateQualificationRunnerSpool(spoolDir) {
|
|
5
|
+
const base = validateQualificationBaseSpool(spoolDir);
|
|
6
|
+
return { ...base, derived: validateQualificationPanelOutputs(spoolDir) };
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=qualification-validation.js.map
|
package/dist/regate.js
CHANGED
|
@@ -6,7 +6,7 @@ import { evaluateTraceGates } from "./trace-gates.js";
|
|
|
6
6
|
import { mergeTraces, deserializeTrace } from "./execution-trace.js";
|
|
7
7
|
import { deserializeTrajectoryEvents, evaluateTrajectoryGates } from "./trajectory-gates.js";
|
|
8
8
|
import { judgeOneRep } from "./regrade.js";
|
|
9
|
-
import { readResults, writeResults, diffPath, transcriptPath, judgeRawPath, repIndexOf, findDiffFiles, findTraceFiles, findTrajectoryFiles, tracePath, trajectoryPath, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, } from "./results.js";
|
|
9
|
+
import { readResults, writeResults, diffPath, transcriptPath, judgeRawPath, repIndexOf, findDiffFiles, findTraceFiles, findTrajectoryFiles, tracePath, trajectoryPath, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, deliveryStatusForObservations, } from "./results.js";
|
|
10
10
|
import { outcomesToResult } from "./reps.js";
|
|
11
11
|
import { appendJournal } from "./journal.js";
|
|
12
12
|
import { gatesDigest, GATES_PREFIX } from "./sources.js";
|
|
@@ -222,9 +222,11 @@ export async function regateRun(opts) {
|
|
|
222
222
|
const priorStatus = objective?.status ?? "PASS";
|
|
223
223
|
const status = priorStatus === "ERROR" || g.status === "ERROR" || priorEvidenceErrors.length || digestMismatch
|
|
224
224
|
? "ERROR"
|
|
225
|
-
: priorStatus === "
|
|
226
|
-
? "
|
|
227
|
-
: "
|
|
225
|
+
: priorStatus === "NOT-MEASURED"
|
|
226
|
+
? "NOT-MEASURED"
|
|
227
|
+
: priorStatus === "FAIL" || g.status === "FAIL"
|
|
228
|
+
? "FAIL"
|
|
229
|
+
: "PASS";
|
|
228
230
|
objective = {
|
|
229
231
|
...(objective ?? {}),
|
|
230
232
|
status,
|
|
@@ -242,6 +244,21 @@ export async function regateRun(opts) {
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
}
|
|
247
|
+
// Prompt delivery was observed during the subject invocation and cannot be
|
|
248
|
+
// replayed by regate. Carry its objective finding while refreshing only
|
|
249
|
+
// trace/trajectory/needle assertions.
|
|
250
|
+
const retainedDelivery = rec.objective?.assertions.find(assertion => assertion.kind === "skill_delivered");
|
|
251
|
+
const repObservations = prev.subject_invocations?.filter(observation => observation.scenario_id === scenario.id && observation.repetition === (rep ?? 0)) ?? [];
|
|
252
|
+
const delivery = prev.schema === 3
|
|
253
|
+
? { kind: "skill_delivered", status: deliveryStatusForObservations(repObservations), detail: `${repObservations.length} retained provider request observation(s)` }
|
|
254
|
+
: retainedDelivery;
|
|
255
|
+
if (delivery) {
|
|
256
|
+
const prior = objective?.status ?? "PASS";
|
|
257
|
+
const status = delivery.status === "ERROR" || prior === "ERROR" ? "ERROR" : delivery.status === "NOT-MEASURED" ? "NOT-MEASURED" : prior === "FAIL" ? "FAIL" : "PASS";
|
|
258
|
+
objective = { ...(objective ?? {}), status, assertions: [delivery, ...(objective?.assertions ?? [])] };
|
|
259
|
+
if (delivery.status !== "PASS")
|
|
260
|
+
traceFailure = `objective: ${delivery.detail}`;
|
|
261
|
+
}
|
|
245
262
|
const gate = { lines: needleGate.lines, failure: needleGate.failure ?? traceFailure ?? trajectoryFailure };
|
|
246
263
|
const tPath = transcriptPath(opts.runDir, scenario.id, mode, rep);
|
|
247
264
|
const before = existsSync(tPath) ? readFileSync(tPath, "utf8") : "";
|
|
@@ -250,7 +267,7 @@ export async function regateRun(opts) {
|
|
|
250
267
|
// trace gate leaves an `objective` block on the result. Reading only the
|
|
251
268
|
// trailer meant a trace gate flipping to PASS never triggered the re-judge
|
|
252
269
|
// it needs, leaving a stale FAIL verdict beside a PASS objective.
|
|
253
|
-
const oldObjectiveFailed = rec.objective?.status === "FAIL" || rec.objective?.status === "ERROR";
|
|
270
|
+
const oldObjectiveFailed = rec.objective?.status === "FAIL" || rec.objective?.status === "ERROR" || rec.objective?.status === "NOT-MEASURED";
|
|
254
271
|
const oldGateFailed = GATE_FAILED_RE.test(before.slice(before.indexOf(TRAILER))) || oldObjectiveFailed;
|
|
255
272
|
// The trailer is regenerated whatever the outcome: leaving a stale
|
|
256
273
|
// `MISSING` note beside a corrected verdict would misinform the next reader
|
|
@@ -258,11 +275,11 @@ export async function regateRun(opts) {
|
|
|
258
275
|
if (existsSync(tPath))
|
|
259
276
|
rewriteTranscript(tPath, gate.lines);
|
|
260
277
|
if (gate.failure) {
|
|
261
|
-
if (objective?.status === "ERROR")
|
|
278
|
+
if (objective?.status === "ERROR" || objective?.status === "NOT-MEASURED")
|
|
262
279
|
gateErroredHere = true;
|
|
263
280
|
else
|
|
264
281
|
gateFailedHere = true;
|
|
265
|
-
outcomes.push({ verdict: objective?.status === "ERROR" ? "ERROR" : "FAIL", reason: gate.failure, suspect: false, objective });
|
|
282
|
+
outcomes.push({ verdict: objective?.status === "ERROR" ? "ERROR" : objective?.status === "NOT-MEASURED" ? "NOT-MEASURED" : "FAIL", reason: gate.failure, suspect: false, objective });
|
|
266
283
|
continue;
|
|
267
284
|
}
|
|
268
285
|
if (!oldGateFailed) {
|
|
@@ -284,12 +301,26 @@ export async function regateRun(opts) {
|
|
|
284
301
|
}
|
|
285
302
|
const threshold = effectiveThreshold(rec, scenario);
|
|
286
303
|
const next = outcomesToResult(scenario.id, outcomes, outcomes.length, threshold);
|
|
304
|
+
if (prev.schema === 3) {
|
|
305
|
+
next.criterion_count = scenario.checklist.length;
|
|
306
|
+
next.rep_judgments = outcomes.map((outcome, repetition) => {
|
|
307
|
+
const fresh = next.rep_judgments?.find(panel => panel.repetition === repetition);
|
|
308
|
+
const prior = rec.rep_judgments?.find(panel => panel.repetition === repetition);
|
|
309
|
+
const judgments = fresh?.judgments.length ? fresh.judgments : prior?.judgments ?? [];
|
|
310
|
+
return {
|
|
311
|
+
repetition,
|
|
312
|
+
judgments,
|
|
313
|
+
recorded_verdict: outcome.verdict,
|
|
314
|
+
...(outcome.objective ? { objective: outcome.objective } : {}),
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
287
318
|
next.metrics = mergeScenarioMetrics(rec.metrics, next.metrics);
|
|
288
319
|
// Overrides and their notes survive: a regate re-decides the gate, and an author
|
|
289
320
|
// override is a statement about the judge, not about the needle.
|
|
290
|
-
// `regate` re-evaluates gates from saved artifacts
|
|
291
|
-
//
|
|
292
|
-
//
|
|
321
|
+
// `regate` re-evaluates gates from saved artifacts. It may judge a previously
|
|
322
|
+
// blocked fail→pass rep above; carrying the recorded panel still preserves any
|
|
323
|
+
// earlier multi-judge decision for judgments that were not replaced.
|
|
293
324
|
scenarios.push(rebuildScenarioResult(next, rec, { objective: "fresh", adjudication: "carry" }));
|
|
294
325
|
const to = next.judge_verdict;
|
|
295
326
|
if (to !== rec.judge_verdict) {
|
|
@@ -302,9 +333,17 @@ export async function regateRun(opts) {
|
|
|
302
333
|
}
|
|
303
334
|
const ctx = scoreContextFor(prev, opts.spec);
|
|
304
335
|
const results = writeResults(opts.runDir, {
|
|
336
|
+
schema: prev.schema, subject_invocations: prev.subject_invocations,
|
|
305
337
|
skill: prev.skill, harness: prev.harness, model: prev.model,
|
|
306
338
|
// Carried verbatim: a regate re-reads saved diffs, it never re-runs the harness.
|
|
307
339
|
harness_cli_version: prev.harness_cli_version, delivery_canary: prev.delivery_canary,
|
|
340
|
+
// The arm is provenance of the MEASUREMENT, not of this rewrite, and it is the
|
|
341
|
+
// only record that a `+<arm>` run actually delegated: rebuilding the draft
|
|
342
|
+
// field-by-field without it silently deleted `definitions`/`ledger_events`
|
|
343
|
+
// from any arm run that was ever re-graded, leaving a record
|
|
344
|
+
// indistinguishable from a vacuous arm. Same reason `harness_cli_version`,
|
|
345
|
+
// `delivery_canary` and `source_hashes` are carried here.
|
|
346
|
+
arm: prev.arm,
|
|
308
347
|
judge: { provider: opts.judge.provider, model: opts.judge.model },
|
|
309
348
|
timestamp: prev.timestamp, label: prev.label, mode: prev.mode, partial: prev.partial,
|
|
310
349
|
// Only the `gates:` keys of the scenarios actually re-evaluated. Stimulus, rubric
|
package/dist/regrade.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Spec, Scenario } from "./spec.js";
|
|
2
2
|
import type { HarnessAdapter, ModelRef } from "./adapters/types.js";
|
|
3
|
-
import { type ScenarioResult, type ResultsFile } from "./results.js";
|
|
3
|
+
import { type Judgment, type ScenarioResult, type ResultsFile } from "./results.js";
|
|
4
4
|
import { type RepOutcome } from "./reps.js";
|
|
5
5
|
/**
|
|
6
6
|
* Carry a run's recorded hashes forward, refreshing only the `rubric:` keys this
|
|
@@ -84,4 +84,6 @@ export interface RegradeRunOptions {
|
|
|
84
84
|
* results.yaml, emits the `score` journal event, and returns the new
|
|
85
85
|
* ResultsFile. Shared by `cmdGrade` and the pi-extension's `judge` command.
|
|
86
86
|
*/
|
|
87
|
+
/** Preserve prior panel votes and append a full-cell grade without writing invalid one-vote history. */
|
|
88
|
+
export declare function appendJudgeHistory(prior: ScenarioResult | undefined, priorJudge: ModelRef | undefined, fresh: Omit<Judgment, "ordinal">): Judgment[] | undefined;
|
|
87
89
|
export declare function regradeRun(opts: RegradeRunOptions): Promise<ResultsFile>;
|
package/dist/regrade.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { buildJudgePrompt, judgeInWorkspace } from "./grade.js";
|
|
4
|
-
import { findTranscriptFiles, judgeRawPath, repIndexOf, readResults, writeResults, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, } from "./results.js";
|
|
4
|
+
import { findTranscriptFiles, judgeRawPath, repIndexOf, readResults, writeResults, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, carryRepObjectives, completeCriterionVotes, } from "./results.js";
|
|
5
5
|
import { outcomesToResult } from "./reps.js";
|
|
6
6
|
import { appendJournal } from "./journal.js";
|
|
7
7
|
import { rubricDigest, personaDigest, RUBRIC_PREFIX, PERSONA_KEY } from "./sources.js";
|
|
8
|
+
import { providerFailureFromTranscript } from "./provider-failure.js";
|
|
8
9
|
/**
|
|
9
10
|
* Carry a run's recorded hashes forward, refreshing only the `rubric:` keys this
|
|
10
11
|
* re-grade actually judged under (plus the persona, which applies to all of them).
|
|
@@ -37,15 +38,32 @@ export function refreshRubricHashes(recorded, spec, judgedIds) {
|
|
|
37
38
|
export async function judgeOneRep(opts) {
|
|
38
39
|
const { runDir, spec, scenario, transcript, adapter, judge, specDir, mode, rep, now } = opts;
|
|
39
40
|
const startedAt = performance.now();
|
|
41
|
+
const repField = rep === undefined ? {} : { rep };
|
|
42
|
+
// A provider outage recorded in the transcript (either by `run()`'s marker or,
|
|
43
|
+
// since the fix for I2, by `runStructured` too) must stay ERROR through a
|
|
44
|
+
// re-judge. Without this check, `grade`/`regrade` reads the saved transcript
|
|
45
|
+
// with no infrastructure-failure guard at all — the one `run.ts` applies BEFORE
|
|
46
|
+
// ever calling this function — and re-judges "provider never ran the request"
|
|
47
|
+
// into a model verdict, spending a judge call to turn infrastructure noise into
|
|
48
|
+
// a false FAIL.
|
|
49
|
+
const providerFailure = providerFailureFromTranscript(transcript);
|
|
50
|
+
if (providerFailure) {
|
|
51
|
+
const reason = `provider failure — ${providerFailure}`;
|
|
52
|
+
appendJournal(runDir, { event: "judge-verdict", ts: now(), id: scenario.id, verdict: "ERROR", reason, suspect: false, ...repField });
|
|
53
|
+
return {
|
|
54
|
+
verdict: "ERROR", reason, suspect: false,
|
|
55
|
+
metrics: { wall_time_ms: Math.max(0, Math.round(performance.now() - startedAt)), judge_calls: 0, judge_rejudge_calls: 0 },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
40
58
|
const prompt = buildJudgePrompt({ skill: spec.skill, persona: spec.judge_persona, scenario, transcript });
|
|
41
59
|
const g = await judgeInWorkspace(adapter, judge, prompt, specDir);
|
|
42
60
|
writeFileSync(judgeRawPath(runDir, scenario.id, mode, rep), g.raw, "utf8");
|
|
43
|
-
const repField = rep === undefined ? {} : { rep };
|
|
44
61
|
appendJournal(runDir, { event: "judge-verdict", ts: now(), id: scenario.id, verdict: g.verdict, reason: g.reason, suspect: g.suspect, ...repField });
|
|
45
62
|
if (g.suspect)
|
|
46
63
|
appendJournal(runDir, { event: "misfire-flag", ts: now(), id: scenario.id, reason: g.reason, ...repField });
|
|
47
64
|
return {
|
|
48
65
|
verdict: g.verdict, reason: g.reason, suspect: g.suspect,
|
|
66
|
+
judgment: { ordinal: 1, judge: { ...judge }, verdict: g.verdict, reason: g.reason, suspect: g.suspect, criteria: completeCriterionVotes(g.criteria, scenario.checklist.length) },
|
|
49
67
|
metrics: {
|
|
50
68
|
wall_time_ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
51
69
|
judge_calls: 1,
|
|
@@ -95,6 +113,22 @@ export async function regradeScenario(opts) {
|
|
|
95
113
|
* results.yaml, emits the `score` journal event, and returns the new
|
|
96
114
|
* ResultsFile. Shared by `cmdGrade` and the pi-extension's `judge` command.
|
|
97
115
|
*/
|
|
116
|
+
/** Preserve prior panel votes and append a full-cell grade without writing invalid one-vote history. */
|
|
117
|
+
export function appendJudgeHistory(prior, priorJudge, fresh) {
|
|
118
|
+
const history = prior?.judge_history ?? prior?.adjudication?.judgments ?? (prior && priorJudge ? [{
|
|
119
|
+
ordinal: 1,
|
|
120
|
+
judge: priorJudge,
|
|
121
|
+
verdict: prior.judge_verdict ?? "JUDGE-AMBIGUOUS",
|
|
122
|
+
reason: prior.judge_reason ?? "prior grade",
|
|
123
|
+
suspect: prior.suspect ?? true,
|
|
124
|
+
criteria: prior.rep_judgments?.find(panel => panel.repetition === 0)?.judgments[0]?.criteria ?? [],
|
|
125
|
+
}] : []);
|
|
126
|
+
const next = [
|
|
127
|
+
...history,
|
|
128
|
+
{ ...fresh, criteria: fresh.criteria ?? [], ordinal: history.length + 1 },
|
|
129
|
+
].slice(-3).map((judgment, index) => ({ ...judgment, ordinal: index + 1 }));
|
|
130
|
+
return next.length >= 2 ? next : undefined;
|
|
131
|
+
}
|
|
98
132
|
export async function regradeRun(opts) {
|
|
99
133
|
const { runDir, spec, adapter, judge, specDir } = opts;
|
|
100
134
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
@@ -129,10 +163,16 @@ export async function regradeRun(opts) {
|
|
|
129
163
|
targets = prev.scenarios
|
|
130
164
|
.filter((s) => s.suspect || s.judge_verdict === "JUDGE-AMBIGUOUS")
|
|
131
165
|
.map((s) => s.id);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
}
|
|
167
|
+
// Schema-3 delivery is a prerequisite for behavioral judging. A later `grade`
|
|
168
|
+
// cannot turn an undelivered or unauthenticated transcript into product evidence.
|
|
169
|
+
if (prev?.schema === 3) {
|
|
170
|
+
const blocked = new Set(prev.scenarios.filter(s => s.objective?.assertions.some(a => a.kind === "skill_delivered" && a.status !== "PASS")).map(s => s.id));
|
|
171
|
+
targets = targets.filter(id => !blocked.has(id));
|
|
172
|
+
}
|
|
173
|
+
if (targets.length === 0 && prev) {
|
|
174
|
+
// Nothing judgeable — a no-op, preserving both evidence and judge identity.
|
|
175
|
+
return prev;
|
|
136
176
|
}
|
|
137
177
|
const completeTranscripts = (record) => {
|
|
138
178
|
const expected = record.reps ?? 1;
|
|
@@ -165,7 +205,14 @@ export async function regradeRun(opts) {
|
|
|
165
205
|
expectedReps: prevScenario?.reps ?? 1,
|
|
166
206
|
});
|
|
167
207
|
const carry = overrides.get(id);
|
|
208
|
+
if (prev?.schema === 3)
|
|
209
|
+
rr.criterion_count = scenario.checklist.length;
|
|
168
210
|
rr.metrics = mergeScenarioMetrics(carry?.metrics, rr.metrics);
|
|
211
|
+
rr.rep_judgments = carryRepObjectives(rr.rep_judgments, carry?.rep_judgments);
|
|
212
|
+
rr.judge_history = appendJudgeHistory(carry, prev?.judge, {
|
|
213
|
+
judge, verdict: rr.judge_verdict, reason: rr.judge_reason, suspect: rr.suspect,
|
|
214
|
+
criteria: rr.rep_judgments?.find(panel => panel.repetition === 0)?.judgments[0]?.criteria,
|
|
215
|
+
});
|
|
169
216
|
// `grade` re-judges the saved transcript. It does not re-evaluate trace gates
|
|
170
217
|
// (that is `regate`), so `objective` still describes this run; and it replaced
|
|
171
218
|
// the judgments a prior adjudication described, so that panel must go.
|
|
@@ -173,6 +220,8 @@ export async function regradeRun(opts) {
|
|
|
173
220
|
}
|
|
174
221
|
const ctx = scoreContextFor({ mode, partial: prev?.partial }, spec);
|
|
175
222
|
const results = writeResults(runDir, {
|
|
223
|
+
schema: prev?.schema,
|
|
224
|
+
subject_invocations: prev?.subject_invocations,
|
|
176
225
|
skill: spec.skill,
|
|
177
226
|
harness: prev?.harness ?? "pi",
|
|
178
227
|
// The harness CLI that produced these transcripts, carried verbatim: a re-grade
|
|
@@ -180,6 +229,13 @@ export async function regradeRun(opts) {
|
|
|
180
229
|
// here would credit the old transcripts to a version that never ran them.
|
|
181
230
|
harness_cli_version: prev?.harness_cli_version,
|
|
182
231
|
delivery_canary: prev?.delivery_canary,
|
|
232
|
+
// The arm is provenance of the MEASUREMENT, not of this rewrite, and it is the
|
|
233
|
+
// only record that a `+<arm>` run actually delegated: rebuilding the draft
|
|
234
|
+
// field-by-field without it silently deleted `definitions`/`ledger_events`
|
|
235
|
+
// from any arm run that was ever re-graded, leaving a record
|
|
236
|
+
// indistinguishable from a vacuous arm. Same reason `harness_cli_version`,
|
|
237
|
+
// `delivery_canary` and `source_hashes` are carried here.
|
|
238
|
+
arm: prev?.arm,
|
|
183
239
|
model: prev?.model ?? "unknown",
|
|
184
240
|
judge: { provider: judge.provider, model: judge.model },
|
|
185
241
|
timestamp: prev?.timestamp ?? now(),
|
package/dist/reps.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Verdict } from "./score.js";
|
|
2
|
-
import type { ScenarioResult, ObjectiveResult } from "./results.js";
|
|
2
|
+
import type { ScenarioResult, ObjectiveResult, Judgment, SubjectInvocationObservation } from "./results.js";
|
|
3
3
|
import type { TraceMetrics } from "./capture-trace-types.js";
|
|
4
4
|
/** One rep's outcome (subject run + judge). */
|
|
5
5
|
export interface RepOutcome {
|
|
@@ -8,6 +8,8 @@ export interface RepOutcome {
|
|
|
8
8
|
suspect: boolean;
|
|
9
9
|
/** Present only when the scenario declared structured objective assertions. */
|
|
10
10
|
objective?: ObjectiveResult;
|
|
11
|
+
judgment?: Judgment;
|
|
12
|
+
subject_invocations?: SubjectInvocationObservation[];
|
|
11
13
|
metrics?: {
|
|
12
14
|
wall_time_ms: number;
|
|
13
15
|
judge_calls: number;
|
|
@@ -22,7 +24,7 @@ export interface RepOutcome {
|
|
|
22
24
|
* pass-threshold aggregation: an objective assertion is a statement about what
|
|
23
25
|
* the model DID, so one rep that called a forbidden tool is a real finding, not
|
|
24
26
|
* a minority draw to be voted away. ERROR dominates (missing evidence is never a
|
|
25
|
-
* pass), then FAIL, then PASS. The retained assertion detail comes from the
|
|
27
|
+
* pass), then NOT-MEASURED (delivery absent), then FAIL, then PASS. The retained assertion detail comes from the
|
|
26
28
|
* first non-passing rep, since that is the one worth reading.
|
|
27
29
|
*/
|
|
28
30
|
export declare function aggregateObjective(outcomes: RepOutcome[]): ObjectiveResult | undefined;
|
package/dist/reps.js
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* pass-threshold aggregation: an objective assertion is a statement about what
|
|
6
6
|
* the model DID, so one rep that called a forbidden tool is a real finding, not
|
|
7
7
|
* a minority draw to be voted away. ERROR dominates (missing evidence is never a
|
|
8
|
-
* pass), then FAIL, then PASS. The retained assertion detail comes from the
|
|
8
|
+
* pass), then NOT-MEASURED (delivery absent), then FAIL, then PASS. The retained assertion detail comes from the
|
|
9
9
|
* first non-passing rep, since that is the one worth reading.
|
|
10
10
|
*/
|
|
11
11
|
export function aggregateObjective(outcomes) {
|
|
12
12
|
const present = outcomes.map((o) => o.objective).filter((o) => o !== undefined);
|
|
13
13
|
if (present.length === 0)
|
|
14
14
|
return undefined;
|
|
15
|
-
const picked = present.find((o) => o.status === "ERROR") ?? present.find((o) => o.status === "FAIL") ?? present[0];
|
|
15
|
+
const picked = present.find((o) => o.status === "ERROR") ?? present.find((o) => o.status === "NOT-MEASURED") ?? present.find((o) => o.status === "FAIL") ?? present[0];
|
|
16
16
|
if (present.length === 1)
|
|
17
17
|
return picked;
|
|
18
18
|
const eventHashes = present.map((objective) => objective.events_sha256);
|
|
@@ -41,6 +41,10 @@ export function aggregateReps(outcomes, threshold) {
|
|
|
41
41
|
if (errored > 0) {
|
|
42
42
|
return { verdict: "ERROR", reason: `${errored}/${reps} reps errored — infrastructure, not behavior`, passes, reps, clean: clean.length, flakiness: 0, suspect: false };
|
|
43
43
|
}
|
|
44
|
+
const notMeasured = outcomes.filter((o) => o.verdict === "NOT-MEASURED").length;
|
|
45
|
+
if (notMeasured > 0) {
|
|
46
|
+
return { verdict: "NOT-MEASURED", reason: `${notMeasured}/${reps} reps not measured — skill delivery was not established`, passes, reps, clean: clean.length - notMeasured, flakiness: 0, suspect: false };
|
|
47
|
+
}
|
|
44
48
|
if (clean.length * 2 < reps) {
|
|
45
49
|
// majority of reps misfired → untrustworthy
|
|
46
50
|
return { verdict: "FAIL", reason: `${reps - clean.length}/${reps} reps misfired — re-judge`, passes, reps, clean: clean.length, flakiness: 0, suspect: true };
|
|
@@ -65,15 +69,17 @@ export function outcomesToResult(id, outcomes, repCount, threshold) {
|
|
|
65
69
|
const objectiveField = objective ? { objective } : {};
|
|
66
70
|
const metrics = aggregateMetrics(outcomes);
|
|
67
71
|
const metricsField = metrics ? { metrics } : {};
|
|
72
|
+
const repJudgments = outcomes.map((outcome, repetition) => ({ repetition, judgments: outcome.judgment ? [outcome.judgment] : [], recorded_verdict: outcome.verdict, ...(outcome.objective ? { objective: outcome.objective } : {}) }));
|
|
73
|
+
const repJudgmentField = outcomes.some(outcome => outcome.judgment) ? { rep_judgments: repJudgments } : {};
|
|
68
74
|
if (repCount === 1) {
|
|
69
75
|
const o = outcomes[0];
|
|
70
|
-
return { id, judge_verdict: o.verdict, judge_reason: o.reason, suspect: o.suspect, ...metricsField, override: null, note: "", ...objectiveField };
|
|
76
|
+
return { id, judge_verdict: o.verdict, judge_reason: o.reason, suspect: o.suspect, ...metricsField, override: null, note: "", ...objectiveField, ...repJudgmentField };
|
|
71
77
|
}
|
|
72
78
|
const agg = aggregateReps(outcomes, threshold);
|
|
73
79
|
return {
|
|
74
80
|
id, judge_verdict: agg.verdict, judge_reason: agg.reason, suspect: agg.suspect,
|
|
75
81
|
reps: agg.reps, passes: agg.passes, clean: agg.clean, flakiness: agg.flakiness,
|
|
76
|
-
pass_threshold: threshold, ...metricsField, override: null, note: "", ...objectiveField,
|
|
82
|
+
pass_threshold: threshold, ...metricsField, override: null, note: "", ...objectiveField, ...repJudgmentField,
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
85
|
function aggregateMetrics(outcomes) {
|
|
@@ -97,6 +103,9 @@ function aggregateMetrics(outcomes) {
|
|
|
97
103
|
cache_read_tokens: subjects.reduce((sum, metrics) => sum + metrics.cache_read_tokens, 0),
|
|
98
104
|
cache_write_tokens: subjects.reduce((sum, metrics) => sum + metrics.cache_write_tokens, 0),
|
|
99
105
|
subject_cost_usd: subjects.reduce((sum, metrics) => sum + metrics.cost_usd, 0),
|
|
106
|
+
cost_source: subjects.every((metrics) => metrics.cost_source === subjects[0].cost_source)
|
|
107
|
+
? subjects[0].cost_source
|
|
108
|
+
: "unreported",
|
|
100
109
|
tool_calls: subjects.reduce((sum, metrics) => sum + metrics.tool_calls, 0),
|
|
101
110
|
delegated_children: subjects.reduce((sum, metrics) => sum + metrics.delegated_children, 0),
|
|
102
111
|
max_concurrency: Math.max(...subjects.map((metrics) => metrics.max_concurrency)),
|
package/dist/rescore.js
CHANGED
|
@@ -52,7 +52,7 @@ export function rescoreRun(opts) {
|
|
|
52
52
|
// no rate to re-apply, or an untrustworthy verdict → carry verbatim
|
|
53
53
|
if (!scenario || s.reps === undefined || s.clean === undefined || s.passes === undefined)
|
|
54
54
|
return s;
|
|
55
|
-
if (s.judge_verdict === "ERROR" || s.judge_verdict === "JUDGE-AMBIGUOUS")
|
|
55
|
+
if (s.judge_verdict === "ERROR" || s.judge_verdict === "NOT-MEASURED" || s.judge_verdict === "JUDGE-AMBIGUOUS")
|
|
56
56
|
return s;
|
|
57
57
|
if (s.clean === 0)
|
|
58
58
|
return s;
|
|
@@ -78,6 +78,7 @@ export function rescoreRun(opts) {
|
|
|
78
78
|
});
|
|
79
79
|
const ctx = scoreContextFor(prev, opts.spec);
|
|
80
80
|
const results = writeResults(opts.runDir, {
|
|
81
|
+
schema: prev.schema, subject_invocations: prev.subject_invocations,
|
|
81
82
|
skill: prev.skill, harness: prev.harness, model: prev.model, judge: prev.judge,
|
|
82
83
|
timestamp: prev.timestamp, label: prev.label, mode: prev.mode,
|
|
83
84
|
partial: prev.partial,
|
|
@@ -85,6 +86,13 @@ export function rescoreRun(opts) {
|
|
|
85
86
|
// threshold to reps the recorded harness already produced.
|
|
86
87
|
harness_cli_version: prev.harness_cli_version,
|
|
87
88
|
delivery_canary: prev.delivery_canary,
|
|
89
|
+
// The arm is provenance of the MEASUREMENT, not of this rewrite, and it is the
|
|
90
|
+
// only record that a `+<arm>` run actually delegated: rebuilding the draft
|
|
91
|
+
// field-by-field without it silently deleted `definitions`/`ledger_events`
|
|
92
|
+
// from any arm run that was ever re-graded, leaving a record
|
|
93
|
+
// indistinguishable from a vacuous arm. Same reason `harness_cli_version`,
|
|
94
|
+
// `delivery_canary` and `source_hashes` are carried here.
|
|
95
|
+
arm: prev.arm,
|
|
88
96
|
// A rescore re-applies the CURRENT policy (thresholds, critical set) to the
|
|
89
97
|
// recorded reps, so `policy:` drift is genuinely resolved by having run this —
|
|
90
98
|
// that is what makes `rescore` the honest remedy lint names for it. Stimulus,
|
package/dist/results.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ModelRef } from "./adapters/types.js";
|
|
|
2
2
|
import { type ScenarioVerdict } from "./score.js";
|
|
3
3
|
import type { Verdict } from "./score.js";
|
|
4
4
|
import type { ShipBar, Scenario } from "./spec.js";
|
|
5
|
+
import type { CostSource } from "./capture-trace-types.js";
|
|
5
6
|
export interface ScenarioMetrics {
|
|
6
7
|
wall_time_ms: number;
|
|
7
8
|
judge_calls: number;
|
|
@@ -13,14 +14,36 @@ export interface ScenarioMetrics {
|
|
|
13
14
|
cache_read_tokens?: number;
|
|
14
15
|
cache_write_tokens?: number;
|
|
15
16
|
subject_cost_usd?: number;
|
|
17
|
+
cost_source?: CostSource;
|
|
16
18
|
tool_calls?: number;
|
|
17
19
|
delegated_children?: number;
|
|
18
20
|
max_concurrency?: number;
|
|
19
21
|
}
|
|
20
22
|
/** Merge bookkeeping from a later judge/gate pass without duplicating subject usage. */
|
|
21
23
|
export declare function mergeScenarioMetrics(prior: ScenarioMetrics | undefined, fresh: ScenarioMetrics | undefined): ScenarioMetrics | undefined;
|
|
24
|
+
export interface CriterionVote {
|
|
25
|
+
index: number;
|
|
26
|
+
verdict: "PASS" | "FAIL" | "ERROR";
|
|
27
|
+
reason: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RepJudgmentPanel {
|
|
30
|
+
repetition: number;
|
|
31
|
+
judgments: Judgment[];
|
|
32
|
+
recorded_verdict: Verdict;
|
|
33
|
+
/** Per-repetition objective result; needed because scenario.objective is a strict aggregate. */
|
|
34
|
+
objective?: ObjectiveResult;
|
|
35
|
+
}
|
|
36
|
+
export declare function carryRepObjectives(fresh: RepJudgmentPanel[] | undefined, prior: RepJudgmentPanel[] | undefined): RepJudgmentPanel[] | undefined;
|
|
37
|
+
export interface SubjectInvocationObservation {
|
|
38
|
+
scenario_id: string;
|
|
39
|
+
repetition: number;
|
|
40
|
+
attempt?: number;
|
|
41
|
+
prompt: import("./adapters/types.js").PromptProvenance;
|
|
42
|
+
}
|
|
22
43
|
export interface ScenarioResult {
|
|
23
44
|
id: string;
|
|
45
|
+
/** Numbered rubric items expected on every schema-3 judgment. */
|
|
46
|
+
criterion_count?: number;
|
|
24
47
|
judge_verdict: Verdict;
|
|
25
48
|
judge_reason: string;
|
|
26
49
|
suspect: boolean;
|
|
@@ -49,6 +72,10 @@ export interface ScenarioResult {
|
|
|
49
72
|
* actually blocks SHIP; this field is the audit trail behind that flag.
|
|
50
73
|
*/
|
|
51
74
|
adjudication?: AdjudicationResult;
|
|
75
|
+
/** Latest three full-cell grades; enables offline agreement reports with distinct judges. */
|
|
76
|
+
judge_history?: Judgment[];
|
|
77
|
+
/** Initial and panel judge members, by repetition, sufficient to recompute the recorded verdict. */
|
|
78
|
+
rep_judgments?: RepJudgmentPanel[];
|
|
52
79
|
}
|
|
53
80
|
/** One judge's answer for a cell, kept verbatim however the collapse turned out. */
|
|
54
81
|
export interface Judgment {
|
|
@@ -62,8 +89,12 @@ export interface Judgment {
|
|
|
62
89
|
reason: string;
|
|
63
90
|
/** The judge misfired — recorded, never counted as a clean vote. */
|
|
64
91
|
suspect: boolean;
|
|
92
|
+
/** Every numbered rubric-item vote parsed from this exact judge response. Required in schema v3. */
|
|
93
|
+
criteria?: CriterionVote[];
|
|
65
94
|
}
|
|
66
95
|
export interface AdjudicationResult {
|
|
96
|
+
/** The saved transcript repetition judged by every panel member (first rep by policy). */
|
|
97
|
+
repetition?: number;
|
|
67
98
|
state: "confirmed" | "tie_broken" | "unresolved";
|
|
68
99
|
/** Why the cell was re-judged. */
|
|
69
100
|
trigger: "ambiguous" | "contradictory" | "non_unanimous" | "ship_deciding";
|
|
@@ -74,8 +105,8 @@ export interface AdjudicationResult {
|
|
|
74
105
|
}
|
|
75
106
|
/** Objective gate outcome for one scenario cell. */
|
|
76
107
|
export interface ObjectiveResult {
|
|
77
|
-
/** ERROR
|
|
78
|
-
status: "PASS" | "FAIL" | "ERROR";
|
|
108
|
+
/** ERROR is malformed evidence; NOT-MEASURED is a known-undelivered experiment. */
|
|
109
|
+
status: "PASS" | "FAIL" | "ERROR" | "NOT-MEASURED";
|
|
79
110
|
trace_version?: number;
|
|
80
111
|
trace_sha256?: string;
|
|
81
112
|
trajectory_version?: string;
|
|
@@ -85,7 +116,7 @@ export interface ObjectiveResult {
|
|
|
85
116
|
rep_trace_sha256?: string[];
|
|
86
117
|
assertions: {
|
|
87
118
|
kind: string;
|
|
88
|
-
status: "PASS" | "FAIL" | "ERROR";
|
|
119
|
+
status: "PASS" | "FAIL" | "ERROR" | "NOT-MEASURED";
|
|
89
120
|
detail: string;
|
|
90
121
|
}[];
|
|
91
122
|
}
|
|
@@ -98,7 +129,7 @@ export interface GradeSummary {
|
|
|
98
129
|
note: string;
|
|
99
130
|
}
|
|
100
131
|
export interface ResultsFile {
|
|
101
|
-
schema: 2;
|
|
132
|
+
schema: 2 | 3;
|
|
102
133
|
/**
|
|
103
134
|
* The harness version that wrote this file — provenance for the numbers in it.
|
|
104
135
|
*
|
|
@@ -157,6 +188,39 @@ export interface ResultsFile {
|
|
|
157
188
|
timestamp: string;
|
|
158
189
|
label: string | null;
|
|
159
190
|
mode: string;
|
|
191
|
+
/**
|
|
192
|
+
* The arm this run was measured under, absent for the control.
|
|
193
|
+
*
|
|
194
|
+
* Recorded rather than inferred from the tag: the tag says which arm, this says
|
|
195
|
+
* what the arm actually loaded — a run whose extension list or definition count
|
|
196
|
+
* differed from today's `arms.yaml` is not comparable, and only the record can
|
|
197
|
+
* show that.
|
|
198
|
+
*
|
|
199
|
+
* `ledger_events` is the arm's delivery proof, `--canary`'s equivalent for this
|
|
200
|
+
* axis: the pi-daddy ledger itself is gitignored (`*.jsonl`), so without a
|
|
201
|
+
* count here a vacuous arm run (extension loaded, nothing ever delegated)
|
|
202
|
+
* commits a record indistinguishable from one that actually exercised
|
|
203
|
+
* delegation. 0 is a reportable outcome, not an absent field.
|
|
204
|
+
*
|
|
205
|
+
* `env` is the arm's declared env for the subject process (`PI_GRANTS_GRANT`,
|
|
206
|
+
* `PI_GRANTS_MAX_DEPTH`, …) — recorded because it IS the experimental
|
|
207
|
+
* condition, not decoration around it. Without it, changing `PI_GRANTS_MAX_DEPTH`
|
|
208
|
+
* from 1 to 3 and re-running lands both runs in the same `+<arm>` tag with
|
|
209
|
+
* byte-identical `arm` records, and `stability` reads a verdict flip between two
|
|
210
|
+
* different conditions as one lineage being bimodal — the exact misreading the
|
|
211
|
+
* tag placement exists to prevent. Recorded as DECLARED, with `<run-dir>` and
|
|
212
|
+
* `<workspace>` unsubstituted: the substituted form differs every run for the same condition,
|
|
213
|
+
* which would make two identical conditions look like two.
|
|
214
|
+
*
|
|
215
|
+
* Optional only for records written before it was added; every writer emits it.
|
|
216
|
+
*/
|
|
217
|
+
arm?: {
|
|
218
|
+
name: string;
|
|
219
|
+
extensions: string[];
|
|
220
|
+
definitions: number;
|
|
221
|
+
ledger_events: number;
|
|
222
|
+
env?: Record<string, string>;
|
|
223
|
+
};
|
|
160
224
|
/** True for an `--only`-filtered run: a scenario subset, never ship-graded, never a release run. */
|
|
161
225
|
partial?: boolean;
|
|
162
226
|
/**
|
|
@@ -171,6 +235,8 @@ export interface ResultsFile {
|
|
|
171
235
|
source_hashes?: Record<string, string>;
|
|
172
236
|
effective_grade: GradeSummary;
|
|
173
237
|
scenarios: ScenarioResult[];
|
|
238
|
+
/** Adapter-computed model-visible prompt observations. Required for schema v3. */
|
|
239
|
+
subject_invocations?: SubjectInvocationObservation[];
|
|
174
240
|
}
|
|
175
241
|
/**
|
|
176
242
|
* The run modes whose results carry a real grade: the ones where the skill under
|
|
@@ -222,13 +288,28 @@ export declare function scoreContextFor(run: {
|
|
|
222
288
|
/** The pass-threshold a re-grade uses: the run's persisted value, else the spec's per-scenario value, else 0.5. */
|
|
223
289
|
export declare function effectiveThreshold(prevScenario: ScenarioResult | undefined, scenario: Scenario): number;
|
|
224
290
|
/** Everything a caller may set. The grade is computed, never supplied. */
|
|
225
|
-
export type ResultsDraft = Omit<ResultsFile, "schema" | "effective_grade"
|
|
291
|
+
export type ResultsDraft = Omit<ResultsFile, "schema" | "effective_grade"> & {
|
|
292
|
+
schema?: 2 | 3;
|
|
293
|
+
};
|
|
226
294
|
export interface ScoreContext {
|
|
227
295
|
shipBar: ShipBar;
|
|
228
296
|
critical: string[];
|
|
229
297
|
}
|
|
230
|
-
/**
|
|
231
|
-
|
|
298
|
+
/**
|
|
299
|
+
* `<skillDir>/tests/results/<harness>-<model-slug>[+<arm>]/<timestamp-slug>/`
|
|
300
|
+
*
|
|
301
|
+
* The arm is part of the run's IDENTITY, not of the spec's digest. `lint` and
|
|
302
|
+
* `stability` both key on this tag, so two arms become separate lineages that can
|
|
303
|
+
* never be misread as one lineage flipping run-over-run — and no
|
|
304
|
+
* `specification.yaml` byte moves, so every committed run stays valid.
|
|
305
|
+
*
|
|
306
|
+
* `+` is deliberate and is appended OUTSIDE `modelSlug`, whose character class
|
|
307
|
+
* (`[^A-Za-z0-9._-]`) cannot emit one. So the separator can never occur inside a
|
|
308
|
+
* slug and the tag stays unambiguously splittable back into model and arm. The
|
|
309
|
+
* control arm appends nothing, so its paths are byte-identical to before arms
|
|
310
|
+
* existed.
|
|
311
|
+
*/
|
|
312
|
+
export declare function runDirFor(skillDir: string, harness: string, model: ModelRef, timestamp: string, armName?: string): string;
|
|
232
313
|
/** Path of a transcript file within a run dir. A rep index (for --reps N>1) is suffixed. */
|
|
233
314
|
export declare function transcriptPath(runDir: string, scenarioId: string, mode: string, rep?: number): string;
|
|
234
315
|
export declare function reportPath(runDir: string): string;
|
|
@@ -270,6 +351,17 @@ export declare function writeResults(runDir: string, draft: ResultsDraft, ctx: S
|
|
|
270
351
|
export declare function migrateResults(raw: unknown): ResultsFile;
|
|
271
352
|
/** Read results.yaml from a run dir, migrating schema-1 files in memory. */
|
|
272
353
|
export declare function readResults(runDir: string): ResultsFile;
|
|
354
|
+
/** Parse rubric-item votes for retention only; verdict semantics remain in grade.ts. */
|
|
355
|
+
export declare function parseCriterionVotes(raw: string): CriterionVote[];
|
|
356
|
+
export declare function completeCriterionVotes(votes: CriterionVote[], expected: number): CriterionVote[];
|
|
357
|
+
export declare function deliveryStatusForObservations(observations: SubjectInvocationObservation[]): "PASS" | "NOT-MEASURED" | "ERROR";
|
|
358
|
+
export declare function recomputeRecordedPanels(results: ResultsFile): Array<{
|
|
359
|
+
scenario_id: string;
|
|
360
|
+
repetition: number;
|
|
361
|
+
verdict: Verdict;
|
|
362
|
+
}>;
|
|
363
|
+
/** Runtime v2/v3 validator. v2 remains permissive; v3 fails closed on observation gaps. */
|
|
364
|
+
export declare function validateResults(raw: unknown): ResultsFile;
|
|
273
365
|
/** Pure: return a copy with override + note applied to one scenario. */
|
|
274
366
|
export declare function applyOverride(results: ResultsFile, scenarioId: string, override: Verdict | null, note: string): ResultsFile;
|
|
275
367
|
/**
|