@skill-harness/core 0.17.0 → 0.19.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 +1 -2
- package/dist/adapters/types.d.ts +2 -12
- package/dist/capture-trace-types.d.ts +8 -102
- package/dist/capture-trace-types.js +0 -4
- package/dist/command-cost.d.ts +1 -1
- package/dist/command-cost.js +0 -5
- package/dist/execution-trace.d.ts +0 -16
- package/dist/execution-trace.js +31 -42
- package/dist/index.d.ts +3 -23
- package/dist/index.js +3 -23
- package/dist/instruction-coverage.d.ts +0 -7
- package/dist/instruction-coverage.js +5 -7
- package/dist/journal.d.ts +0 -8
- package/dist/metrics.d.ts +19 -0
- package/dist/metrics.js +28 -0
- package/dist/redaction.d.ts +6 -0
- package/dist/redaction.js +55 -0
- package/dist/regate.js +6 -71
- package/dist/regrade.d.ts +1 -3
- package/dist/regrade.js +1 -21
- package/dist/report.d.ts +1 -15
- package/dist/report.js +1 -13
- package/dist/reps.d.ts +1 -2
- package/dist/reps.js +33 -10
- package/dist/results.d.ts +10 -9
- package/dist/results.js +3 -19
- package/dist/run.js +15 -74
- package/dist/seeded.d.ts +1 -7
- package/dist/seeded.js +5 -11
- package/dist/sources.js +4 -9
- package/dist/spec.d.ts +0 -13
- package/dist/spec.js +6 -48
- package/dist/trajectory-events.d.ts +58 -0
- package/dist/trajectory-events.js +119 -0
- package/package.json +1 -1
- package/schemas/results-v2.schema.json +394 -75
- package/schemas/results-v3.schema.json +18 -0
- package/schemas/specification-v1.schema.json +128 -174
- package/dist/adjudication.d.ts +0 -219
- package/dist/adjudication.js +0 -422
- package/dist/adoption.d.ts +0 -128
- package/dist/adoption.js +0 -102
- package/dist/affected.d.ts +0 -88
- package/dist/affected.js +0 -222
- package/dist/capture.d.ts +0 -193
- package/dist/capture.js +0 -344
- package/dist/comparison.d.ts +0 -97
- package/dist/comparison.js +0 -189
- package/dist/factory-calibration.d.ts +0 -63
- package/dist/factory-calibration.js +0 -115
- package/dist/intervention-results.d.ts +0 -6
- package/dist/intervention-results.js +0 -52
- package/dist/intervention.d.ts +0 -123
- package/dist/intervention.js +0 -218
- package/dist/investigation.d.ts +0 -94
- package/dist/investigation.js +0 -148
- package/dist/judge-agreement.d.ts +0 -19
- package/dist/judge-agreement.js +0 -31
- package/dist/qualification-capture.d.ts +0 -44
- package/dist/qualification-capture.js +0 -163
- package/dist/qualification-config.d.ts +0 -233
- package/dist/qualification-config.js +0 -694
- package/dist/qualification-lock.d.ts +0 -15
- package/dist/qualification-lock.js +0 -87
- package/dist/qualification-oauth-directory.d.ts +0 -79
- package/dist/qualification-oauth-directory.js +0 -444
- package/dist/qualification-panel-store.d.ts +0 -14
- package/dist/qualification-panel-store.js +0 -188
- package/dist/qualification-panels.d.ts +0 -99
- package/dist/qualification-panels.js +0 -91
- package/dist/qualification-process.d.ts +0 -13
- package/dist/qualification-process.js +0 -190
- package/dist/qualification-runner.d.ts +0 -203
- package/dist/qualification-runner.js +0 -2037
- package/dist/qualification-store.d.ts +0 -187
- package/dist/qualification-store.js +0 -833
- package/dist/qualification-validation.d.ts +0 -12
- package/dist/qualification-validation.js +0 -8
- package/dist/screen.d.ts +0 -31
- package/dist/screen.js +0 -105
- package/dist/trajectory-gates.d.ts +0 -137
- package/dist/trajectory-gates.js +0 -576
- package/dist/trusted-host-supervision.d.ts +0 -25
- package/dist/trusted-host-supervision.js +0 -94
- package/dist/work-capture-schema.d.ts +0 -38
- package/dist/work-capture-schema.js +0 -33
- package/dist/work-capture.d.ts +0 -43
- package/dist/work-capture.js +0 -81
- package/dist/work-signals.d.ts +0 -55
- package/dist/work-signals.js +0 -71
- package/schemas/qualification-cell-result-v1.schema.json +0 -32
- package/schemas/qualification-config-v1.schema.json +0 -244
- package/schemas/qualification-invocation-request-v1.schema.json +0 -59
- package/schemas/qualification-judge-panel-result-v1.schema.json +0 -42
- package/schemas/trajectory-event-v1.schema.json +0 -80
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Values longer than this are truncated rather than persisted whole. */
|
|
2
|
+
export const MAX_VALUE_CHARS = 2000;
|
|
3
|
+
const REDACTED = "[redacted]";
|
|
4
|
+
const SECRET_KEY = /^(.*[-_])?(password|passwd|secret|token|api[-_]?key|apikey|auth|authorization|credential|private[-_]?key|access[-_]?key|session[-_]?key)([-_].*)?$/i;
|
|
5
|
+
const SECRET_VALUE = [
|
|
6
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
7
|
+
/\bBearer\s+[A-Za-z0-9._~+/-]{16,}=*/g,
|
|
8
|
+
/\bsk-[A-Za-z0-9]{16,}\b/g,
|
|
9
|
+
/\bgh[pousr]_[A-Za-z0-9]{16,}\b/g,
|
|
10
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}\b/g,
|
|
11
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
12
|
+
/\bey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
13
|
+
/\b([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+:[^/\s@]+@/gi,
|
|
14
|
+
];
|
|
15
|
+
export function redactText(input, homeDir) {
|
|
16
|
+
let out = input;
|
|
17
|
+
out = out.replace(SECRET_VALUE[SECRET_VALUE.length - 1], `$1${REDACTED}@`);
|
|
18
|
+
for (const re of SECRET_VALUE.slice(0, -1))
|
|
19
|
+
out = out.replace(re, REDACTED);
|
|
20
|
+
if (homeDir && homeDir.length > 1)
|
|
21
|
+
out = out.split(homeDir).join("~");
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
export function truncate(input, max = MAX_VALUE_CHARS) {
|
|
25
|
+
if (input.length <= max)
|
|
26
|
+
return input;
|
|
27
|
+
return `${input.slice(0, max)}… [truncated ${input.length - max} chars]`;
|
|
28
|
+
}
|
|
29
|
+
export function redactArgs(args, homeDir, depth = 0) {
|
|
30
|
+
if (args === null || typeof args !== "object" || Array.isArray(args))
|
|
31
|
+
return {};
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [key, value] of Object.entries(args)) {
|
|
34
|
+
if (SECRET_KEY.test(key)) {
|
|
35
|
+
out[key] = REDACTED;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out[key] = redactValue(value, homeDir, depth);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function redactValue(value, homeDir, depth) {
|
|
43
|
+
if (typeof value === "string")
|
|
44
|
+
return truncate(redactText(value, homeDir));
|
|
45
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null)
|
|
46
|
+
return value;
|
|
47
|
+
if (depth >= 3)
|
|
48
|
+
return "[nested]";
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.slice(0, 20).map((v) => redactValue(v, homeDir, depth + 1));
|
|
51
|
+
if (typeof value === "object")
|
|
52
|
+
return redactArgs(value, homeDir, depth + 1);
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=redaction.js.map
|
package/dist/regate.js
CHANGED
|
@@ -4,9 +4,8 @@ import { parseVerdict, detectMisfire } from "./grade.js";
|
|
|
4
4
|
import { evaluateNeedleGates, hasNeedleGates } from "./seeded.js";
|
|
5
5
|
import { evaluateTraceGates } from "./trace-gates.js";
|
|
6
6
|
import { mergeTraces, deserializeTrace } from "./execution-trace.js";
|
|
7
|
-
import { deserializeTrajectoryEvents, evaluateTrajectoryGates } from "./trajectory-gates.js";
|
|
8
7
|
import { judgeOneRep } from "./regrade.js";
|
|
9
|
-
import { readResults, writeResults, diffPath, transcriptPath, judgeRawPath, repIndexOf, findDiffFiles, findTraceFiles,
|
|
8
|
+
import { readResults, writeResults, diffPath, transcriptPath, judgeRawPath, repIndexOf, findDiffFiles, findTraceFiles, tracePath, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, deliveryStatusForObservations, } from "./results.js";
|
|
10
9
|
import { outcomesToResult } from "./reps.js";
|
|
11
10
|
import { appendJournal } from "./journal.js";
|
|
12
11
|
import { gatesDigest, GATES_PREFIX } from "./sources.js";
|
|
@@ -89,8 +88,7 @@ export async function regateRun(opts) {
|
|
|
89
88
|
const s = specById.get(rec.id);
|
|
90
89
|
const needles = hasNeedleGates(s ?? {});
|
|
91
90
|
const traceGated = Boolean(s?.traceAssert);
|
|
92
|
-
|
|
93
|
-
if (!s || (!needles && !traceGated && !trajectoryGated))
|
|
91
|
+
if (!s || (!needles && !traceGated))
|
|
94
92
|
continue; // nothing for regate to re-decide
|
|
95
93
|
if (s.assert?.vitest || s.assert?.post_test) {
|
|
96
94
|
blocked.push(`${s.id}: declares ${s.assert.vitest ? "assert.vitest" : "assert.post_test"}, which needs the workspace — ` +
|
|
@@ -112,16 +110,11 @@ export async function regateRun(opts) {
|
|
|
112
110
|
`(traces are gitignored, or capture failed) — it needs a re-run`);
|
|
113
111
|
continue;
|
|
114
112
|
}
|
|
115
|
-
if (trajectoryGated && !complete(findTrajectoryFiles(opts.runDir, s.id, mode), expectedSuffixes.map((rep) => trajectoryPath(opts.runDir, s.id, mode, rep)))) {
|
|
116
|
-
blocked.push(`${s.id}: assert.trajectory artifacts are incomplete for ${expectedReps} recorded rep(s) ` +
|
|
117
|
-
`(events are gitignored, or required native evidence was missing) — it needs a re-run`);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
113
|
targets.push(s);
|
|
121
114
|
}
|
|
122
115
|
if (targets.length === 0) {
|
|
123
116
|
throw new Error(`nothing to regate in ${opts.runDir}` +
|
|
124
|
-
(blocked.length > 0 ? `:\n ${blocked.join("\n ")}` : " — no scenario declares diff_contains/diff_excludes
|
|
117
|
+
(blocked.length > 0 ? `:\n ${blocked.join("\n ")}` : " — no scenario declares diff_contains/diff_excludes or assert.trace"));
|
|
125
118
|
}
|
|
126
119
|
const changes = [];
|
|
127
120
|
let judgeCalls = 0;
|
|
@@ -134,15 +127,9 @@ export async function regateRun(opts) {
|
|
|
134
127
|
}
|
|
135
128
|
const diffFiles = findDiffFiles(opts.runDir, scenario.id, mode);
|
|
136
129
|
const traceFiles = findTraceFiles(opts.runDir, scenario.id, mode);
|
|
137
|
-
const eventFiles = findTrajectoryFiles(opts.runDir, scenario.id, mode);
|
|
138
|
-
// Reps come from whichever artifact this scenario actually has. A trajectory-only
|
|
139
|
-
// scenario has neither diff nor trace, so falling back only once would regate
|
|
140
|
-
// nothing and report success.
|
|
141
130
|
const repKeys = diffFiles.length > 0
|
|
142
131
|
? diffFiles.map((f) => ({ rep: repIndexOf(f) ?? undefined, diffFile: f }))
|
|
143
|
-
: traceFiles.
|
|
144
|
-
? traceFiles.map((f) => ({ rep: repIndexOf(f) ?? undefined, diffFile: undefined }))
|
|
145
|
-
: eventFiles.map((f) => ({ rep: repIndexOf(f) ?? undefined, diffFile: undefined }));
|
|
132
|
+
: traceFiles.map((f) => ({ rep: repIndexOf(f) ?? undefined, diffFile: undefined }));
|
|
146
133
|
const outcomes = [];
|
|
147
134
|
// Per scenario, not run-wide: with several regated scenarios, a global counter
|
|
148
135
|
// would report every change as "re-judged" because some other scenario was.
|
|
@@ -192,61 +179,9 @@ export async function regateRun(opts) {
|
|
|
192
179
|
}
|
|
193
180
|
}
|
|
194
181
|
}
|
|
195
|
-
// Adapter-neutral trajectory gate, replayed from the saved normalized
|
|
196
|
-
// event artifact. Native ledgers are deliberately not re-read here: the
|
|
197
|
-
// artifact is the immutable evidence this run actually captured.
|
|
198
|
-
let trajectoryFailure = null;
|
|
199
|
-
if (scenario.trajectoryAssert) {
|
|
200
|
-
const ep = trajectoryPath(opts.runDir, scenario.id, mode, rep);
|
|
201
|
-
const events = existsSync(ep) ? deserializeTrajectoryEvents(readFileSync(ep, "utf8")) : null;
|
|
202
|
-
// The normalized artifact contains only events that were successfully captured.
|
|
203
|
-
// Source-read/normalization failures are persisted on the original objective;
|
|
204
|
-
// dropping them here would let a partial ledger become PASS on replay.
|
|
205
|
-
const priorEvidenceErrors = (rec.objective?.assertions ?? [])
|
|
206
|
-
.filter((result) => result.kind === "trajectory_evidence" && result.status === "ERROR");
|
|
207
|
-
if (!events) {
|
|
208
|
-
trajectoryFailure = "objective: saved normalized events are missing, malformed, or from an unsupported version";
|
|
209
|
-
objective = {
|
|
210
|
-
...(objective ?? { assertions: [] }),
|
|
211
|
-
status: "ERROR",
|
|
212
|
-
assertions: [...(objective?.assertions ?? []), { kind: "trajectory_evidence", status: "ERROR", detail: trajectoryFailure }],
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
const g = evaluateTrajectoryGates(scenario.trajectoryAssert, events);
|
|
217
|
-
const expectedHash = rec.objective?.rep_events_sha256?.[rep ?? 0] ?? (rec.reps === undefined ? rec.objective?.events_sha256 : undefined);
|
|
218
|
-
const digestMismatch = expectedHash !== undefined && expectedHash !== g.events_sha256;
|
|
219
|
-
const integrityAssertion = digestMismatch
|
|
220
|
-
? [{ kind: "trajectory_evidence", status: "ERROR", detail: "saved normalized events no longer match the hash recorded by the run" }]
|
|
221
|
-
: [];
|
|
222
|
-
const priorStatus = objective?.status ?? "PASS";
|
|
223
|
-
const status = priorStatus === "ERROR" || g.status === "ERROR" || priorEvidenceErrors.length || digestMismatch
|
|
224
|
-
? "ERROR"
|
|
225
|
-
: priorStatus === "NOT-MEASURED"
|
|
226
|
-
? "NOT-MEASURED"
|
|
227
|
-
: priorStatus === "FAIL" || g.status === "FAIL"
|
|
228
|
-
? "FAIL"
|
|
229
|
-
: "PASS";
|
|
230
|
-
objective = {
|
|
231
|
-
...(objective ?? {}),
|
|
232
|
-
status,
|
|
233
|
-
trajectory_version: g.event_version,
|
|
234
|
-
events_sha256: g.events_sha256,
|
|
235
|
-
assertions: [...(objective?.assertions ?? []), ...priorEvidenceErrors, ...integrityAssertion, ...g.assertions],
|
|
236
|
-
};
|
|
237
|
-
if (g.status !== "PASS" || priorEvidenceErrors.length || digestMismatch) {
|
|
238
|
-
const details = [
|
|
239
|
-
...priorEvidenceErrors.map((result) => result.detail),
|
|
240
|
-
...integrityAssertion.map((result) => result.detail),
|
|
241
|
-
...g.assertions.filter((result) => result.status === g.status && g.status !== "PASS").map((result) => result.detail),
|
|
242
|
-
];
|
|
243
|
-
trajectoryFailure = `objective: ${details.join("; ")}`;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
182
|
// Prompt delivery was observed during the subject invocation and cannot be
|
|
248
183
|
// replayed by regate. Carry its objective finding while refreshing only
|
|
249
|
-
// trace/
|
|
184
|
+
// trace/needle assertions.
|
|
250
185
|
const retainedDelivery = rec.objective?.assertions.find(assertion => assertion.kind === "skill_delivered");
|
|
251
186
|
const repObservations = prev.subject_invocations?.filter(observation => observation.scenario_id === scenario.id && observation.repetition === (rep ?? 0)) ?? [];
|
|
252
187
|
const delivery = prev.schema === 3
|
|
@@ -259,7 +194,7 @@ export async function regateRun(opts) {
|
|
|
259
194
|
if (delivery.status !== "PASS")
|
|
260
195
|
traceFailure = `objective: ${delivery.detail}`;
|
|
261
196
|
}
|
|
262
|
-
const gate = { lines: needleGate.lines, failure: needleGate.failure ?? traceFailure
|
|
197
|
+
const gate = { lines: needleGate.lines, failure: needleGate.failure ?? traceFailure };
|
|
263
198
|
const tPath = transcriptPath(opts.runDir, scenario.id, mode, rep);
|
|
264
199
|
const before = existsSync(tPath) ? readFileSync(tPath, "utf8") : "";
|
|
265
200
|
// Two sources, because the two gate kinds record their prior state
|
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
|
|
3
|
+
import { 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,6 +84,4 @@ 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;
|
|
89
87
|
export declare function regradeRun(opts: RegradeRunOptions): Promise<ResultsFile>;
|
package/dist/regrade.js
CHANGED
|
@@ -113,22 +113,6 @@ export async function regradeScenario(opts) {
|
|
|
113
113
|
* results.yaml, emits the `score` journal event, and returns the new
|
|
114
114
|
* ResultsFile. Shared by `cmdGrade` and the pi-extension's `judge` command.
|
|
115
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
|
-
}
|
|
132
116
|
export async function regradeRun(opts) {
|
|
133
117
|
const { runDir, spec, adapter, judge, specDir } = opts;
|
|
134
118
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
@@ -140,7 +124,7 @@ export async function regradeRun(opts) {
|
|
|
140
124
|
// silently downgraded a gated scenario to "no assertions declared".
|
|
141
125
|
// `adjudication` is deliberately NOT carried: it describes the judgments this
|
|
142
126
|
// re-grade just replaced, and a stale panel beside a fresh verdict is worse
|
|
143
|
-
// than none.
|
|
127
|
+
// than none.
|
|
144
128
|
// The whole prior result per id — `rebuildScenarioResult` decides, field by
|
|
145
129
|
// field, what survives. Passing a hand-picked subset here is how fields got
|
|
146
130
|
// dropped before.
|
|
@@ -209,10 +193,6 @@ export async function regradeRun(opts) {
|
|
|
209
193
|
rr.criterion_count = scenario.checklist.length;
|
|
210
194
|
rr.metrics = mergeScenarioMetrics(carry?.metrics, rr.metrics);
|
|
211
195
|
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
|
-
});
|
|
216
196
|
// `grade` re-judges the saved transcript. It does not re-evaluate trace gates
|
|
217
197
|
// (that is `regate`), so `objective` still describes this run; and it replaced
|
|
218
198
|
// the judgments a prior adjudication described, so that panel must go.
|
package/dist/report.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ShipBar } from "./spec.js";
|
|
2
2
|
import { type ResultsFile } from "./results.js";
|
|
3
3
|
import { type Lift } from "./lift.js";
|
|
4
|
-
import { type AggregateMetrics } from "./
|
|
4
|
+
import { type AggregateMetrics } from "./metrics.js";
|
|
5
5
|
export interface RunColumn {
|
|
6
6
|
index: number;
|
|
7
7
|
label: string;
|
|
@@ -22,13 +22,6 @@ export interface RunColumn {
|
|
|
22
22
|
status: string;
|
|
23
23
|
detail: string;
|
|
24
24
|
};
|
|
25
|
-
/** Adjudication outcome, when the cell was re-judged. */
|
|
26
|
-
adjudication?: {
|
|
27
|
-
state: string;
|
|
28
|
-
trigger: string;
|
|
29
|
-
count: number;
|
|
30
|
-
detail: string;
|
|
31
|
-
};
|
|
32
25
|
reps?: number;
|
|
33
26
|
passes?: number;
|
|
34
27
|
clean?: number;
|
|
@@ -112,13 +105,6 @@ export declare function publicView(data: ReportData): {
|
|
|
112
105
|
status: string;
|
|
113
106
|
detail: string;
|
|
114
107
|
};
|
|
115
|
-
/** Adjudication outcome, when the cell was re-judged. */
|
|
116
|
-
adjudication?: {
|
|
117
|
-
state: string;
|
|
118
|
-
trigger: string;
|
|
119
|
-
count: number;
|
|
120
|
-
detail: string;
|
|
121
|
-
};
|
|
122
108
|
reps?: number;
|
|
123
109
|
passes?: number;
|
|
124
110
|
clean?: number;
|
package/dist/report.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadSpec } from "./spec.js";
|
|
|
4
4
|
import { readResults } from "./results.js";
|
|
5
5
|
import { collectLift, liftHeadline } from "./lift.js";
|
|
6
6
|
import { boundaryCells, collectStability, stabilityNote } from "./stability.js";
|
|
7
|
-
import { aggregateMetrics } from "./
|
|
7
|
+
import { aggregateMetrics } from "./metrics.js";
|
|
8
8
|
/** Most-recent run dir (by name, which is an ISO-ish slug) under a model-tag dir. */
|
|
9
9
|
function latestRunDir(tagDir) {
|
|
10
10
|
if (!statSync(tagDir).isDirectory())
|
|
@@ -65,18 +65,6 @@ export function collectReport(skillDir) {
|
|
|
65
65
|
},
|
|
66
66
|
}
|
|
67
67
|
: {}),
|
|
68
|
-
...(s.adjudication
|
|
69
|
-
? {
|
|
70
|
-
adjudication: {
|
|
71
|
-
state: s.adjudication.state,
|
|
72
|
-
trigger: s.adjudication.trigger,
|
|
73
|
-
count: s.adjudication.judgments.length,
|
|
74
|
-
detail: s.adjudication.judgments
|
|
75
|
-
.map((j) => `#${j.ordinal} ${j.judge.provider}:${j.judge.model} ${j.verdict}${j.suspect ? " (misfired, not counted)" : ""}`)
|
|
76
|
-
.join(" · "),
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
: {}),
|
|
80
68
|
judge_verdict: s.judge_verdict,
|
|
81
69
|
judge_reason: s.judge_reason,
|
|
82
70
|
suspect: s.suspect ?? false, // suspect defaults false for older results that predate the field
|
package/dist/reps.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Verdict } from "./score.js";
|
|
2
|
-
import type { ScenarioResult, ObjectiveResult, Judgment
|
|
2
|
+
import type { ScenarioResult, ObjectiveResult, Judgment } 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 {
|
|
@@ -9,7 +9,6 @@ export interface RepOutcome {
|
|
|
9
9
|
/** Present only when the scenario declared structured objective assertions. */
|
|
10
10
|
objective?: ObjectiveResult;
|
|
11
11
|
judgment?: Judgment;
|
|
12
|
-
subject_invocations?: SubjectInvocationObservation[];
|
|
13
12
|
metrics?: {
|
|
14
13
|
wall_time_ms: number;
|
|
15
14
|
judge_calls: number;
|
package/dist/reps.js
CHANGED
|
@@ -15,11 +15,9 @@ export function aggregateObjective(outcomes) {
|
|
|
15
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
|
-
const eventHashes = present.map((objective) => objective.events_sha256);
|
|
19
18
|
const traceHashes = present.map((objective) => objective.trace_sha256);
|
|
20
19
|
return {
|
|
21
20
|
...picked,
|
|
22
|
-
...(eventHashes.every((hash) => typeof hash === "string") ? { rep_events_sha256: eventHashes } : {}),
|
|
23
21
|
...(traceHashes.every((hash) => typeof hash === "string") ? { rep_trace_sha256: traceHashes } : {}),
|
|
24
22
|
};
|
|
25
23
|
}
|
|
@@ -69,17 +67,32 @@ export function outcomesToResult(id, outcomes, repCount, threshold) {
|
|
|
69
67
|
const objectiveField = objective ? { objective } : {};
|
|
70
68
|
const metrics = aggregateMetrics(outcomes);
|
|
71
69
|
const metricsField = metrics ? { metrics } : {};
|
|
70
|
+
const usage = outcomes.flatMap((outcome, repetition) => {
|
|
71
|
+
const subject = outcome.metrics?.subject;
|
|
72
|
+
if (!subject)
|
|
73
|
+
return [];
|
|
74
|
+
const reported = (value) => value !== null && value > 0 ? value : null;
|
|
75
|
+
return [{
|
|
76
|
+
repetition,
|
|
77
|
+
inputTokens: reported(subject.input_tokens),
|
|
78
|
+
outputTokens: reported(subject.output_tokens),
|
|
79
|
+
cacheReadTokens: reported(subject.cache_read_tokens),
|
|
80
|
+
costUsd: reported(subject.cost_usd),
|
|
81
|
+
priceAsOf: subject.price_as_of,
|
|
82
|
+
}];
|
|
83
|
+
});
|
|
84
|
+
const usageField = usage.length ? { usage } : {};
|
|
72
85
|
const repJudgments = outcomes.map((outcome, repetition) => ({ repetition, judgments: outcome.judgment ? [outcome.judgment] : [], recorded_verdict: outcome.verdict, ...(outcome.objective ? { objective: outcome.objective } : {}) }));
|
|
73
86
|
const repJudgmentField = outcomes.some(outcome => outcome.judgment) ? { rep_judgments: repJudgments } : {};
|
|
74
87
|
if (repCount === 1) {
|
|
75
88
|
const o = outcomes[0];
|
|
76
|
-
return { id, judge_verdict: o.verdict, judge_reason: o.reason, suspect: o.suspect, ...metricsField, override: null, note: "", ...objectiveField, ...repJudgmentField };
|
|
89
|
+
return { id, judge_verdict: o.verdict, judge_reason: o.reason, suspect: o.suspect, ...metricsField, ...usageField, override: null, note: "", ...objectiveField, ...repJudgmentField };
|
|
77
90
|
}
|
|
78
91
|
const agg = aggregateReps(outcomes, threshold);
|
|
79
92
|
return {
|
|
80
93
|
id, judge_verdict: agg.verdict, judge_reason: agg.reason, suspect: agg.suspect,
|
|
81
94
|
reps: agg.reps, passes: agg.passes, clean: agg.clean, flakiness: agg.flakiness,
|
|
82
|
-
pass_threshold: threshold, ...metricsField, override: null, note: "", ...objectiveField, ...repJudgmentField,
|
|
95
|
+
pass_threshold: threshold, ...metricsField, ...usageField, override: null, note: "", ...objectiveField, ...repJudgmentField,
|
|
83
96
|
};
|
|
84
97
|
}
|
|
85
98
|
function aggregateMetrics(outcomes) {
|
|
@@ -87,22 +100,32 @@ function aggregateMetrics(outcomes) {
|
|
|
87
100
|
if (present.length === 0)
|
|
88
101
|
return undefined;
|
|
89
102
|
const subjects = present.map((metrics) => metrics.subject).filter((metrics) => metrics !== undefined);
|
|
103
|
+
const reportedSubjects = subjects.filter(metrics => metrics.input_tokens !== null || metrics.output_tokens !== null || metrics.cache_read_tokens !== null);
|
|
90
104
|
const base = {
|
|
91
105
|
wall_time_ms: present.reduce((sum, metrics) => sum + metrics.wall_time_ms, 0),
|
|
92
106
|
judge_calls: present.reduce((sum, metrics) => sum + metrics.judge_calls, 0),
|
|
93
107
|
judge_rejudge_calls: present.reduce((sum, metrics) => sum + metrics.judge_rejudge_calls, 0),
|
|
94
|
-
subject_metrics_reps:
|
|
108
|
+
subject_metrics_reps: reportedSubjects.length,
|
|
95
109
|
total_reps: outcomes.length,
|
|
96
110
|
};
|
|
97
111
|
if (subjects.length === 0)
|
|
98
112
|
return base;
|
|
113
|
+
const sumReported = (field) => {
|
|
114
|
+
const values = subjects.map(metrics => metrics[field]).filter((value) => value !== null && value > 0);
|
|
115
|
+
return values.length ? values.reduce((sum, value) => sum + value, 0) : undefined;
|
|
116
|
+
};
|
|
117
|
+
const inputTokens = sumReported("input_tokens");
|
|
118
|
+
const outputTokens = sumReported("output_tokens");
|
|
119
|
+
const cacheReadTokens = sumReported("cache_read_tokens");
|
|
120
|
+
const cacheWriteTokens = sumReported("cache_write_tokens");
|
|
121
|
+
const subjectCost = sumReported("cost_usd");
|
|
99
122
|
return {
|
|
100
123
|
...base,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
...(inputTokens === undefined ? {} : { input_tokens: inputTokens }),
|
|
125
|
+
...(outputTokens === undefined ? {} : { output_tokens: outputTokens }),
|
|
126
|
+
...(cacheReadTokens === undefined ? {} : { cache_read_tokens: cacheReadTokens }),
|
|
127
|
+
...(cacheWriteTokens === undefined ? {} : { cache_write_tokens: cacheWriteTokens }),
|
|
128
|
+
...(subjectCost === undefined ? {} : { subject_cost_usd: subjectCost }),
|
|
106
129
|
cost_source: subjects.every((metrics) => metrics.cost_source === subjects[0].cost_source)
|
|
107
130
|
? subjects[0].cost_source
|
|
108
131
|
: "unreported",
|
package/dist/results.d.ts
CHANGED
|
@@ -3,6 +3,14 @@ import { type ScenarioVerdict } from "./score.js";
|
|
|
3
3
|
import type { Verdict } from "./score.js";
|
|
4
4
|
import type { ShipBar, Scenario } from "./spec.js";
|
|
5
5
|
import type { CostSource } from "./capture-trace-types.js";
|
|
6
|
+
export interface SubjectUsage {
|
|
7
|
+
repetition: number;
|
|
8
|
+
inputTokens: number | null;
|
|
9
|
+
outputTokens: number | null;
|
|
10
|
+
cacheReadTokens: number | null;
|
|
11
|
+
costUsd: number | null;
|
|
12
|
+
priceAsOf: string | null;
|
|
13
|
+
}
|
|
6
14
|
export interface ScenarioMetrics {
|
|
7
15
|
wall_time_ms: number;
|
|
8
16
|
judge_calls: number;
|
|
@@ -56,6 +64,8 @@ export interface ScenarioResult {
|
|
|
56
64
|
pass_threshold?: number;
|
|
57
65
|
/** Cost/latency counters. Separate from verdicts: cheaper failure is still failure. */
|
|
58
66
|
metrics?: ScenarioMetrics;
|
|
67
|
+
/** Per-repetition subject usage; null values mean Pi did not report that counter. */
|
|
68
|
+
usage?: SubjectUsage[];
|
|
59
69
|
/**
|
|
60
70
|
* Objective trace-gate evidence. ADDITIVE and optional.
|
|
61
71
|
*
|
|
@@ -72,8 +82,6 @@ export interface ScenarioResult {
|
|
|
72
82
|
* actually blocks SHIP; this field is the audit trail behind that flag.
|
|
73
83
|
*/
|
|
74
84
|
adjudication?: AdjudicationResult;
|
|
75
|
-
/** Latest three full-cell grades; enables offline agreement reports with distinct judges. */
|
|
76
|
-
judge_history?: Judgment[];
|
|
77
85
|
/** Initial and panel judge members, by repetition, sufficient to recompute the recorded verdict. */
|
|
78
86
|
rep_judgments?: RepJudgmentPanel[];
|
|
79
87
|
}
|
|
@@ -109,10 +117,7 @@ export interface ObjectiveResult {
|
|
|
109
117
|
status: "PASS" | "FAIL" | "ERROR" | "NOT-MEASURED";
|
|
110
118
|
trace_version?: number;
|
|
111
119
|
trace_sha256?: string;
|
|
112
|
-
trajectory_version?: string;
|
|
113
|
-
events_sha256?: string;
|
|
114
120
|
/** Per-repetition hashes retained when an aggregate has more than one rep. */
|
|
115
|
-
rep_events_sha256?: string[];
|
|
116
121
|
rep_trace_sha256?: string[];
|
|
117
122
|
assertions: {
|
|
118
123
|
kind: string;
|
|
@@ -443,10 +448,6 @@ export declare function rebuildScenarioResult(fresh: ScenarioResult, prior: Scen
|
|
|
443
448
|
* traces append without a wrapper.
|
|
444
449
|
*/
|
|
445
450
|
export declare function tracePath(runDir: string, scenarioId: string, mode: string, rep?: number): string;
|
|
446
|
-
/** Saved adapter-neutral workflow events for one scenario rep. */
|
|
447
|
-
export declare function trajectoryPath(runDir: string, scenarioId: string, mode: string, rep?: number): string;
|
|
448
|
-
/** A scenario's normalized-event files, sorted (plain first, then numeric rep). */
|
|
449
|
-
export declare function findTrajectoryFiles(runDir: string, scenarioId: string, mode?: string): string[];
|
|
450
451
|
/** A scenario's staged-diff files, sorted (plain first, then numeric rep). Mode-scoped when given. */
|
|
451
452
|
export declare function findDiffFiles(runDir: string, scenarioId: string, mode?: string): string[];
|
|
452
453
|
/** A scenario's execution-trace files, sorted (plain first, then numeric rep). Mode-scoped when given. */
|
package/dist/results.js
CHANGED
|
@@ -409,7 +409,7 @@ export function validateResults(raw) {
|
|
|
409
409
|
const aggregateObjectiveStatus = objectiveStatuses.includes("ERROR") ? "ERROR" : objectiveStatuses.includes("NOT-MEASURED") ? "NOT-MEASURED" : objectiveStatuses.includes("FAIL") ? "FAIL" : "PASS";
|
|
410
410
|
if (!scenario.objective || scenario.objective.status !== aggregateObjectiveStatus)
|
|
411
411
|
throw new Error(`scenario objective diverges from per-repetition objectives for ${scenario.id}`);
|
|
412
|
-
for (const judgment of
|
|
412
|
+
for (const judgment of scenario.adjudication?.judgments ?? [])
|
|
413
413
|
assertCriteria(judgment);
|
|
414
414
|
let adjudicatedVerdict;
|
|
415
415
|
if (scenario.adjudication) {
|
|
@@ -599,7 +599,7 @@ export function diffPath(runDir, scenarioId, mode, rep) {
|
|
|
599
599
|
export function rebuildScenarioResult(fresh, prior, policy) {
|
|
600
600
|
// Exhaustive destructure. Do not replace with a spread: the spread is what
|
|
601
601
|
// allowed a new field to pass through unconsidered in the first place.
|
|
602
|
-
const { id, criterion_count: freshCriterionCount, judge_verdict, judge_reason, suspect, override: _freshOverride, note: _freshNote, reps, passes, clean, flakiness, pass_threshold, metrics: freshMetrics, objective: freshObjective, adjudication: freshAdjudication,
|
|
602
|
+
const { id, criterion_count: freshCriterionCount, judge_verdict, judge_reason, suspect, override: _freshOverride, note: _freshNote, reps, passes, clean, flakiness, pass_threshold, metrics: freshMetrics, usage: freshUsage, objective: freshObjective, adjudication: freshAdjudication, rep_judgments: freshRepJudgments, ...rest } = fresh;
|
|
603
603
|
const _exhaustive = rest;
|
|
604
604
|
void _exhaustive;
|
|
605
605
|
void _freshOverride;
|
|
@@ -652,6 +652,7 @@ export function rebuildScenarioResult(fresh, prior, policy) {
|
|
|
652
652
|
...(flakiness === undefined ? {} : { flakiness }),
|
|
653
653
|
...(pass_threshold === undefined ? {} : { pass_threshold }),
|
|
654
654
|
...((freshMetrics ?? prior?.metrics) ? { metrics: freshMetrics ?? prior.metrics } : {}),
|
|
655
|
+
...((freshUsage ?? prior?.usage) ? { usage: freshUsage ?? prior.usage } : {}),
|
|
655
656
|
// The author owns the verdict; a re-measurement never discards their call.
|
|
656
657
|
override: prior?.override ?? null,
|
|
657
658
|
note: prior?.note ?? "",
|
|
@@ -660,7 +661,6 @@ export function rebuildScenarioResult(fresh, prior, policy) {
|
|
|
660
661
|
// existed.
|
|
661
662
|
...(objective ? { objective } : {}),
|
|
662
663
|
...(adjudication ? { adjudication } : {}),
|
|
663
|
-
...((freshJudgeHistory ?? prior?.judge_history) ? { judge_history: freshJudgeHistory ?? prior.judge_history } : {}),
|
|
664
664
|
...((freshRepJudgments ?? prior?.rep_judgments) ? { rep_judgments: freshRepJudgments ?? prior.rep_judgments } : {}),
|
|
665
665
|
};
|
|
666
666
|
}
|
|
@@ -675,21 +675,6 @@ export function tracePath(runDir, scenarioId, mode, rep) {
|
|
|
675
675
|
const base = rep === undefined ? `${scenarioId}.${mode}` : `${scenarioId}.${mode}.rep${rep}`;
|
|
676
676
|
return join(runDir, `${base}.trace.jsonl`);
|
|
677
677
|
}
|
|
678
|
-
/** Saved adapter-neutral workflow events for one scenario rep. */
|
|
679
|
-
export function trajectoryPath(runDir, scenarioId, mode, rep) {
|
|
680
|
-
const base = rep === undefined ? `${scenarioId}.${mode}` : `${scenarioId}.${mode}.rep${rep}`;
|
|
681
|
-
return join(runDir, `${base}.events.jsonl`);
|
|
682
|
-
}
|
|
683
|
-
/** A scenario's normalized-event files, sorted (plain first, then numeric rep). */
|
|
684
|
-
export function findTrajectoryFiles(runDir, scenarioId, mode) {
|
|
685
|
-
if (!existsSync(runDir))
|
|
686
|
-
return [];
|
|
687
|
-
const esc = scenarioId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
688
|
-
const re = mode === undefined
|
|
689
|
-
? new RegExp(`^${esc}\\..*\\.events\\.jsonl$`)
|
|
690
|
-
: new RegExp(`^${esc}\\.${mode}(\\.rep\\d+)?\\.events\\.jsonl$`);
|
|
691
|
-
return sortByRep(readdirSync(runDir).filter((f) => re.test(f)));
|
|
692
|
-
}
|
|
693
678
|
/** A scenario's staged-diff files, sorted (plain first, then numeric rep). Mode-scoped when given. */
|
|
694
679
|
export function findDiffFiles(runDir, scenarioId, mode) {
|
|
695
680
|
if (!existsSync(runDir))
|
|
@@ -738,7 +723,6 @@ export function preserveTranscript(resultsRoot, runDir, scenarioId) {
|
|
|
738
723
|
// override whose justification was gitignored, and left `regate` with nothing
|
|
739
724
|
// to re-evaluate on the one cell a human had disputed.
|
|
740
725
|
...findTraceFiles(runDir, scenarioId),
|
|
741
|
-
...findTrajectoryFiles(runDir, scenarioId),
|
|
742
726
|
];
|
|
743
727
|
if (files.length === 0)
|
|
744
728
|
return;
|