@skill-harness/core 0.11.0 → 0.13.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 +19 -0
- package/dist/adjudication.d.ts +9 -1
- package/dist/adjudication.js +41 -33
- package/dist/adoption.d.ts +128 -0
- package/dist/adoption.js +102 -0
- package/dist/arms.d.ts +1 -1
- 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/factory-calibration.d.ts +63 -0
- package/dist/factory-calibration.js +115 -0
- package/dist/grade.d.ts +2 -0
- package/dist/grade.js +2 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/intervention-results.d.ts +6 -0
- package/dist/intervention-results.js +52 -0
- package/dist/intervention.d.ts +123 -0
- package/dist/intervention.js +218 -0
- package/dist/investigation.d.ts +94 -0
- package/dist/investigation.js +148 -0
- package/dist/journal.d.ts +1 -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/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 +190 -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 +42 -10
- package/dist/regrade.d.ts +3 -1
- package/dist/regrade.js +37 -5
- package/dist/reps.d.ts +4 -2
- package/dist/reps.js +13 -4
- package/dist/rescore.js +2 -1
- package/dist/results.d.ts +53 -7
- package/dist/results.js +210 -7
- package/dist/run.js +66 -17
- 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 +2 -1
- package/dist/seeded.js +1 -0
- 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 -14
- package/dist/trajectory-gates.js +18 -34
- package/dist/trusted-host-supervision.d.ts +25 -0
- package/dist/trusted-host-supervision.js +94 -0
- package/dist/vote-panel.d.ts +33 -0
- package/dist/vote-panel.js +41 -0
- package/dist/work-capture-schema.d.ts +38 -0
- package/dist/work-capture-schema.js +33 -0
- package/dist/work-capture.d.ts +43 -0
- package/dist/work-capture.js +81 -0
- package/dist/work-signals.d.ts +55 -0
- package/dist/work-signals.js +71 -0
- package/dist/workspace.js +4 -1
- 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,6 +333,7 @@ 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,
|
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,7 +1,7 @@
|
|
|
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";
|
|
@@ -63,6 +63,7 @@ export async function judgeOneRep(opts) {
|
|
|
63
63
|
appendJournal(runDir, { event: "misfire-flag", ts: now(), id: scenario.id, reason: g.reason, ...repField });
|
|
64
64
|
return {
|
|
65
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) },
|
|
66
67
|
metrics: {
|
|
67
68
|
wall_time_ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
68
69
|
judge_calls: 1,
|
|
@@ -112,6 +113,22 @@ export async function regradeScenario(opts) {
|
|
|
112
113
|
* results.yaml, emits the `score` journal event, and returns the new
|
|
113
114
|
* ResultsFile. Shared by `cmdGrade` and the pi-extension's `judge` command.
|
|
114
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
|
+
}
|
|
115
132
|
export async function regradeRun(opts) {
|
|
116
133
|
const { runDir, spec, adapter, judge, specDir } = opts;
|
|
117
134
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
@@ -146,10 +163,16 @@ export async function regradeRun(opts) {
|
|
|
146
163
|
targets = prev.scenarios
|
|
147
164
|
.filter((s) => s.suspect || s.judge_verdict === "JUDGE-AMBIGUOUS")
|
|
148
165
|
.map((s) => s.id);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
153
176
|
}
|
|
154
177
|
const completeTranscripts = (record) => {
|
|
155
178
|
const expected = record.reps ?? 1;
|
|
@@ -182,7 +205,14 @@ export async function regradeRun(opts) {
|
|
|
182
205
|
expectedReps: prevScenario?.reps ?? 1,
|
|
183
206
|
});
|
|
184
207
|
const carry = overrides.get(id);
|
|
208
|
+
if (prev?.schema === 3)
|
|
209
|
+
rr.criterion_count = scenario.checklist.length;
|
|
185
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
|
+
});
|
|
186
216
|
// `grade` re-judges the saved transcript. It does not re-evaluate trace gates
|
|
187
217
|
// (that is `regate`), so `objective` still describes this run; and it replaced
|
|
188
218
|
// the judgments a prior adjudication described, so that panel must go.
|
|
@@ -190,6 +220,8 @@ export async function regradeRun(opts) {
|
|
|
190
220
|
}
|
|
191
221
|
const ctx = scoreContextFor({ mode, partial: prev?.partial }, spec);
|
|
192
222
|
const results = writeResults(runDir, {
|
|
223
|
+
schema: prev?.schema,
|
|
224
|
+
subject_invocations: prev?.subject_invocations,
|
|
193
225
|
skill: spec.skill,
|
|
194
226
|
harness: prev?.harness ?? "pi",
|
|
195
227
|
// The harness CLI that produced these transcripts, carried verbatim: a re-grade
|
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,
|
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
|
*
|
|
@@ -177,8 +208,8 @@ export interface ResultsFile {
|
|
|
177
208
|
* from 1 to 3 and re-running lands both runs in the same `+<arm>` tag with
|
|
178
209
|
* byte-identical `arm` records, and `stability` reads a verdict flip between two
|
|
179
210
|
* different conditions as one lineage being bimodal — the exact misreading the
|
|
180
|
-
* tag placement exists to prevent. Recorded as DECLARED, with `<run-dir>`
|
|
181
|
-
* unsubstituted: the substituted form differs every run for the same condition,
|
|
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,
|
|
182
213
|
* which would make two identical conditions look like two.
|
|
183
214
|
*
|
|
184
215
|
* Optional only for records written before it was added; every writer emits it.
|
|
@@ -204,6 +235,8 @@ export interface ResultsFile {
|
|
|
204
235
|
source_hashes?: Record<string, string>;
|
|
205
236
|
effective_grade: GradeSummary;
|
|
206
237
|
scenarios: ScenarioResult[];
|
|
238
|
+
/** Adapter-computed model-visible prompt observations. Required for schema v3. */
|
|
239
|
+
subject_invocations?: SubjectInvocationObservation[];
|
|
207
240
|
}
|
|
208
241
|
/**
|
|
209
242
|
* The run modes whose results carry a real grade: the ones where the skill under
|
|
@@ -255,7 +288,9 @@ export declare function scoreContextFor(run: {
|
|
|
255
288
|
/** The pass-threshold a re-grade uses: the run's persisted value, else the spec's per-scenario value, else 0.5. */
|
|
256
289
|
export declare function effectiveThreshold(prevScenario: ScenarioResult | undefined, scenario: Scenario): number;
|
|
257
290
|
/** Everything a caller may set. The grade is computed, never supplied. */
|
|
258
|
-
export type ResultsDraft = Omit<ResultsFile, "schema" | "effective_grade"
|
|
291
|
+
export type ResultsDraft = Omit<ResultsFile, "schema" | "effective_grade"> & {
|
|
292
|
+
schema?: 2 | 3;
|
|
293
|
+
};
|
|
259
294
|
export interface ScoreContext {
|
|
260
295
|
shipBar: ShipBar;
|
|
261
296
|
critical: string[];
|
|
@@ -316,6 +351,17 @@ export declare function writeResults(runDir: string, draft: ResultsDraft, ctx: S
|
|
|
316
351
|
export declare function migrateResults(raw: unknown): ResultsFile;
|
|
317
352
|
/** Read results.yaml from a run dir, migrating schema-1 files in memory. */
|
|
318
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;
|
|
319
365
|
/** Pure: return a copy with override + note applied to one scenario. */
|
|
320
366
|
export declare function applyOverride(results: ResultsFile, scenarioId: string, override: Verdict | null, note: string): ResultsFile;
|
|
321
367
|
/**
|