@skill-harness/core 0.29.0 → 0.31.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.
Files changed (59) hide show
  1. package/README.md +16 -2
  2. package/dist/adapters/types.d.ts +2 -0
  3. package/dist/authoring-preflight.d.ts +22 -0
  4. package/dist/authoring-preflight.js +111 -0
  5. package/dist/authoring.d.ts +96 -0
  6. package/dist/authoring.js +451 -0
  7. package/dist/calibration.d.ts +138 -0
  8. package/dist/calibration.js +352 -0
  9. package/dist/canary.d.ts +28 -26
  10. package/dist/canary.js +109 -92
  11. package/dist/command-cost.d.ts +1 -1
  12. package/dist/command-cost.js +2 -0
  13. package/dist/diagnose.d.ts +17 -0
  14. package/dist/diagnose.js +70 -0
  15. package/dist/execution-trace.d.ts +2 -1
  16. package/dist/execution-trace.js +4 -3
  17. package/dist/grade.d.ts +8 -0
  18. package/dist/grade.js +37 -8
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.js +8 -0
  21. package/dist/journal.d.ts +3 -0
  22. package/dist/judge-evidence.d.ts +40 -0
  23. package/dist/judge-evidence.js +120 -0
  24. package/dist/judge-references.d.ts +8 -0
  25. package/dist/judge-references.js +34 -0
  26. package/dist/lint.d.ts +1 -1
  27. package/dist/lint.js +22 -0
  28. package/dist/regate.js +21 -3
  29. package/dist/regrade.d.ts +10 -1
  30. package/dist/regrade.js +146 -75
  31. package/dist/rescore.js +1 -0
  32. package/dist/resource-monitor.d.ts +3 -0
  33. package/dist/resource-monitor.js +72 -0
  34. package/dist/results.d.ts +16 -4
  35. package/dist/results.js +10 -0
  36. package/dist/run.d.ts +2 -2
  37. package/dist/run.js +147 -116
  38. package/dist/saved-run.d.ts +15 -0
  39. package/dist/saved-run.js +114 -0
  40. package/dist/scheduler.d.ts +3 -5
  41. package/dist/scheduler.js +18 -6
  42. package/dist/seeded.js +1 -0
  43. package/dist/sources.d.ts +4 -1
  44. package/dist/sources.js +44 -40
  45. package/dist/spec.d.ts +8 -0
  46. package/dist/spec.js +27 -0
  47. package/dist/trace-gates.d.ts +2 -0
  48. package/dist/trace-gates.js +13 -3
  49. package/dist/util/exec.d.ts +5 -0
  50. package/dist/util/exec.js +16 -32
  51. package/dist/util/fixture-inventory.d.ts +19 -0
  52. package/dist/util/fixture-inventory.js +68 -0
  53. package/dist/util/process.d.ts +78 -0
  54. package/dist/util/process.js +394 -0
  55. package/dist/workspace.js +13 -29
  56. package/package.json +1 -1
  57. package/schemas/results-v2.schema.json +10 -0
  58. package/schemas/results-v3.schema.json +10 -0
  59. package/schemas/specification-v1.schema.json +21 -0
@@ -0,0 +1,120 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { EXECUTION_TRACE_VERSION } from "./capture-trace-types.js";
3
+ import { deserializeTrace, mergeTraces, traceSha256 } from "./execution-trace.js";
4
+ import { redactText } from "./redaction.js";
5
+ import { finalAssistantMessage } from "./seeded.js";
6
+ const HASH = /^[a-f0-9]{64}$/;
7
+ const MAX_CALLS = 32;
8
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9
+ const index = (value) => Number.isSafeInteger(value) && value >= 0;
10
+ /** Validate the fields consumed by merging and the evidence projection before using them. */
11
+ function validTrace(trace) {
12
+ if (!object(trace) || trace.trace_version !== EXECUTION_TRACE_VERSION || !object(trace.subject) || typeof trace.subject.provider !== "string" || typeof trace.subject.model !== "string" ||
13
+ !(trace.pi_version === null || typeof trace.pi_version === "string") || typeof trace.final_text !== "string" ||
14
+ !index(trace.turn) || !index(trace.rep) || !Array.isArray(trace.tool_calls) ||
15
+ !(trace.changed_paths === null || Array.isArray(trace.changed_paths) && trace.changed_paths.every(p => typeof p === "string")) ||
16
+ !(trace.cost_usd === null || typeof trace.cost_usd === "number" && Number.isFinite(trace.cost_usd)) ||
17
+ !(trace.capture_errors === undefined || Array.isArray(trace.capture_errors) && trace.capture_errors.every(e => typeof e === "string")))
18
+ return false;
19
+ if (trace.metrics !== undefined && (!object(trace.metrics) ||
20
+ ["input_tokens", "output_tokens", "cache_read_tokens", "cache_write_tokens", "cost_usd"].some(key => {
21
+ const value = trace.metrics[key];
22
+ return value !== null && !(typeof value === "number" && Number.isFinite(value) && value >= 0);
23
+ }) || [trace.metrics.tool_calls, trace.metrics.delegated_children, trace.metrics.max_concurrency].some(value => !index(value))))
24
+ return false;
25
+ const ids = new Set();
26
+ const completions = new Set();
27
+ for (const [position, call] of trace.tool_calls.entries()) {
28
+ if (!object(call) || typeof call.id !== "string" || !call.id || ids.has(call.id) ||
29
+ typeof call.name !== "string" || !call.name || !object(call.args) || call.issueIndex !== position ||
30
+ !(call.completionIndex === -1 || index(call.completionIndex)) || typeof call.isError !== "boolean" ||
31
+ !object(call.result) || !index(call.result.bytes) || typeof call.result.sha256 !== "string" || !HASH.test(call.result.sha256))
32
+ return false;
33
+ ids.add(call.id);
34
+ if (call.completionIndex >= 0) {
35
+ if (completions.has(call.completionIndex))
36
+ return false;
37
+ completions.add(call.completionIndex);
38
+ }
39
+ }
40
+ return [...completions].sort((a, b) => a - b).every((value, position) => value === position);
41
+ }
42
+ export function unavailableExecution(reason = "No bound execution trace was supplied.", status = "unknown") {
43
+ return { schema: 1, status, coverage: "unknown", reason, observedCalls: null, omittedCalls: 0, calls: [] };
44
+ }
45
+ /** Tool inputs are untrusted labels. Keep only bounded local read paths, never commands, URLs, tasks or results. */
46
+ function readPath(call) {
47
+ if (call.name !== "read")
48
+ return undefined;
49
+ const path = call.args.path ?? call.args.file_path;
50
+ if (typeof path !== "string")
51
+ return undefined;
52
+ if (path.length > 256 || /[\x00-\x1f\x7f?#]/.test(path) || /^[a-z][a-z0-9+.-]*:\/\//i.test(path))
53
+ return "[path withheld]";
54
+ return redactText(path);
55
+ }
56
+ /** Digest binding establishes artifact continuity, not a signature or proof of file contents. */
57
+ export function executionFacts(traces, identity, binding) {
58
+ if (binding.source === "saved" && !binding.expectedSha256)
59
+ return unavailableExecution("Legacy trace has no recorded digest binding; execution is unverified.");
60
+ const invalid = (reason) => unavailableExecution(reason, "invalid");
61
+ if (!traces.length)
62
+ return binding.source === "live" ? unavailableExecution() : invalid("Recorded execution trace is missing or unreadable.");
63
+ if (traces.some(trace => !validTrace(trace)))
64
+ return invalid("Execution trace has malformed structure.");
65
+ if (traces.some((trace, turn) => trace.scenario_id !== identity.scenarioId || trace.mode !== identity.mode || trace.rep !== identity.repetition || trace.turn !== turn ||
66
+ identity.subject && (trace.subject.provider !== identity.subject.provider || trace.subject.model !== identity.subject.model) ||
67
+ identity.piVersion !== undefined && trace.pi_version !== identity.piVersion ||
68
+ trace.subject.provider !== traces[0].subject.provider || trace.subject.model !== traces[0].subject.model || trace.pi_version !== traces[0].pi_version)) {
69
+ return invalid("Execution trace identity or turn sequence does not match this repetition.");
70
+ }
71
+ if (identity.responses !== undefined && (identity.responses === null || identity.responses.length !== traces.length || traces.some((trace, turn) => trace.final_text.trim() !== identity.responses[turn]))) {
72
+ return invalid("Execution trace final answers do not match the judged transcript.");
73
+ }
74
+ if (traces.some(trace => trace.trace_sha256 !== undefined && trace.trace_sha256 !== traceSha256(trace)))
75
+ return invalid("Execution trace has a mismatching turn digest.");
76
+ // Use the existing canonical merge/digest; only the projection below reaches the judge.
77
+ const merged = mergeTraces(traces);
78
+ const digest = merged?.trace_sha256;
79
+ if (!merged || !digest)
80
+ return invalid("Execution trace could not be merged.");
81
+ if (binding.source === "saved" && binding.expectedSha256 !== digest)
82
+ return invalid("Saved trace no longer matches the digest recorded by the run.");
83
+ const complete = traces.length === identity.turns && traces.every(trace => trace.final_status === "complete" && !trace.capture_errors?.length && trace.tool_calls.every(call => call.completionIndex >= 0));
84
+ const calls = merged.tool_calls.slice(0, MAX_CALLS).map(call => ({
85
+ tool: /^[A-Za-z0-9_.:/-]{1,80}$/.test(call.name) ? call.name : "[tool name withheld]",
86
+ state: call.completionIndex < 0 ? "unfinished" : call.isError ? "error" : "completed",
87
+ ...(readPath(call) === undefined ? {} : { path: readPath(call) }),
88
+ ...(call.completionIndex < 0 ? {} : { resultBytes: call.result.bytes, resultSha256: call.result.sha256 }),
89
+ }));
90
+ const omittedCalls = merged.tool_calls.length - calls.length;
91
+ return {
92
+ schema: 1, status: "verified", coverage: complete && !omittedCalls ? "complete" : "partial",
93
+ reason: complete ? omittedCalls ? "Call list is bounded; omitted calls may include relevant actions." : "All expected turns settled; all recorded calls are listed." : "Turn settlement or capture coverage is incomplete; do not infer that unlisted actions did not occur.",
94
+ traceSha256: digest, observedCalls: merged.tool_calls.length, omittedCalls, calls,
95
+ };
96
+ }
97
+ /** Reuse the transcript ambiguity check, then compare every assistant turn, excluding harness-owned trailers. */
98
+ export function transcriptResponses(transcript, scenario) {
99
+ if (finalAssistantMessage(transcript, scenario) === null)
100
+ return null;
101
+ return transcript.split(/^<<< ASSISTANT:[ \t]*$/m).slice(1).map(section => section.split(/^(?:>>> USER(?: \(turn \d+\/\d+\))?:[ \t]*|=== SEEDED GATES ===[ \t]*)$/m)[0].trim());
102
+ }
103
+ /** Read every JSONL turn or decline the whole artifact; never silently drop malformed lines. */
104
+ export function loadExecutionFacts(path, identity, expectedSha256) {
105
+ if (!expectedSha256)
106
+ return unavailableExecution("Legacy run has no recorded trace digest; execution is unverified.");
107
+ if (!existsSync(path))
108
+ return unavailableExecution("Recorded execution trace is missing.", "invalid");
109
+ try {
110
+ const lines = readFileSync(path, "utf8").split("\n").filter(line => line.trim());
111
+ const traces = lines.map(deserializeTrace);
112
+ if (traces.some(trace => trace === null))
113
+ return unavailableExecution("Execution trace contains unreadable turns.", "invalid");
114
+ return executionFacts(traces, identity, { source: "saved", expectedSha256 });
115
+ }
116
+ catch {
117
+ return unavailableExecution("Execution trace could not be read or validated.", "invalid");
118
+ }
119
+ }
120
+ //# sourceMappingURL=judge-evidence.js.map
@@ -0,0 +1,8 @@
1
+ import type { Scenario } from "./spec.js";
2
+ export interface JudgeReference {
3
+ file: string;
4
+ sha256: string;
5
+ text: string;
6
+ }
7
+ /** Only explicitly selected local UTF-8 files are read. Never discover sibling files. */
8
+ export declare function resolveJudgeReferences(scenario: Scenario, specDir: string): JudgeReference[];
@@ -0,0 +1,34 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync, realpathSync, statSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve } from "node:path";
4
+ const LIMIT = 65_536;
5
+ /** Only explicitly selected local UTF-8 files are read. Never discover sibling files. */
6
+ export function resolveJudgeReferences(scenario, specDir) {
7
+ const selected = scenario.judgeReferences ?? [];
8
+ if (!selected.length)
9
+ return [];
10
+ if (selected.length > 16 || new Set(selected).size !== selected.length)
11
+ throw new Error("Invalid judge reference selection");
12
+ const root = realpathSync(specDir);
13
+ let total = 0;
14
+ return selected.map(file => {
15
+ if (!file || /[\\:\x00-\x1f]/.test(file) || file.startsWith("/") || file.split("/").some(part => !part || part === "." || part === ".."))
16
+ throw new Error(`Unsafe judge reference: ${file}`);
17
+ const path = realpathSync(resolve(root, file));
18
+ const rel = relative(root, path);
19
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\"))
20
+ throw new Error(`Judge reference escapes specification directory: ${file}`);
21
+ const info = statSync(path);
22
+ if (!info.isFile() || info.size > LIMIT)
23
+ throw new Error(`Judge reference must be a regular file of at most ${LIMIT} bytes: ${file}`);
24
+ const bytes = readFileSync(path);
25
+ total += bytes.length;
26
+ if (bytes.length > LIMIT || total > LIMIT)
27
+ throw new Error(`Selected judge references exceed ${LIMIT} bytes`);
28
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
29
+ if (text.includes("\0"))
30
+ throw new Error(`Judge reference contains binary content: ${file}`);
31
+ return { file, sha256: createHash("sha256").update(bytes).digest("hex"), text };
32
+ });
33
+ }
34
+ //# sourceMappingURL=judge-references.js.map
package/dist/lint.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type LintCode = "spec" | "ship_bar" | "critical" | "fixture" | "fixture-marker" | "consistency" | "stale" | "stability" | "covers" | "lint-error";
1
+ export type LintCode = "spec" | "ship_bar" | "critical" | "fixture" | "fixture-marker" | "consistency" | "stale" | "stability" | "covers" | "lint-error" | "authoring";
2
2
  /**
3
3
  * How much a finding means.
4
4
  *
package/dist/lint.js CHANGED
@@ -9,6 +9,8 @@ import { downgradeWarning } from "./downgrade.js";
9
9
  import { collectScoredRuns } from "./trends.js";
10
10
  import { boundaryCells, stabilityFrom, stabilityNote } from "./stability.js";
11
11
  import { MARKERS, unknownMarkerDirs, suggestMarker } from "./workspace.js";
12
+ import { fixtureInventory, inspectEvalAuthoring, authoringEvidenceProfile } from "./authoring-preflight.js";
13
+ import { resolveJudgeReferences } from "./judge-references.js";
12
14
  /** True when a finding fails the gate. The single place the exit-code rule lives. */
13
15
  export function failsGate(f) {
14
16
  return (f.severity ?? "error") === "error";
@@ -49,6 +51,20 @@ export function lintSkill(skillDir, testsDir = join(skillDir, "tests")) {
49
51
  return [{ skill: basename(skillDir), code: "spec", message }];
50
52
  }
51
53
  const skill = spec.skill;
54
+ for (const hint of inspectEvalAuthoring(spec, { ...authoringEvidenceProfile(testsDir), fixtureFiles: scenario => {
55
+ const fixture = effectiveFixture(scenario);
56
+ if (!fixture)
57
+ return [];
58
+ // Unreadable/missing fixtures are reported by the fixture checks below.
59
+ try {
60
+ return fixtureInventory(resolve(testsDir, fixture));
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ } })) {
66
+ findings.push({ skill, scenario: hint.scenario, code: "authoring", severity: hint.severity ?? "info", message: `${hint.code}: ${hint.message}` });
67
+ }
52
68
  // A lint from an older tool than the records it is checking cannot be trusted to
53
69
  // check them: a 0.1.0 binary reported 38 spurious `consistency` findings against
54
70
  // partial runs that 0.3.x handles correctly, and it cannot read source-hash key
@@ -91,6 +107,12 @@ export function lintSkill(skillDir, testsDir = join(skillDir, "tests")) {
91
107
  // relative to the spec's dir, matching workspace.ts resolve(specDir, fixture) where specDir = <skillDir>/tests.
92
108
  const specDir = dirname(specPath);
93
109
  for (const s of spec.scenarios) {
110
+ try {
111
+ resolveJudgeReferences(s, specDir);
112
+ }
113
+ catch (error) {
114
+ findings.push({ skill, scenario: s.id, code: "fixture", message: String(error) });
115
+ }
94
116
  const fx = effectiveFixture(s); // shared with sources.ts so hashing and linting can't drift
95
117
  if (fx) {
96
118
  const abs = isAbsolute(fx) ? fx : resolve(specDir, fx);
package/dist/regate.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { basename, join } from "node:path";
3
- import { parseVerdict, detectMisfire } from "./grade.js";
3
+ import { parseModelRef } from "./adapters/types.js";
4
+ import { parseVerdict, detectMisfire, resolveJudgeReferences } from "./grade.js";
4
5
  import { evaluateNeedleGates, evaluateOutputGates, hasNeedleGates, hasOutputGates } from "./seeded.js";
5
6
  import { evaluateTraceGates } from "./trace-gates.js";
6
7
  import { mergeTraces, deserializeTrace } from "./execution-trace.js";
7
8
  import { judgeOneRep } from "./regrade.js";
9
+ import { loadExecutionFacts, transcriptResponses, unavailableExecution } from "./judge-evidence.js";
8
10
  import { readResults, writeResults, diffPath, transcriptPath, judgeRawPath, findDiffFiles, findTranscriptFiles, findTraceFiles, tracePath, effectiveThreshold, scoreContextFor, rebuildScenarioResult, mergeScenarioMetrics, deliveryStatusForObservations, } from "./results.js";
9
11
  import { outcomesToResult } from "./reps.js";
10
12
  import { appendJournal } from "./journal.js";
@@ -150,6 +152,7 @@ export async function regateRun(opts) {
150
152
  throw new Error(`nothing to regate in ${opts.runDir}` +
151
153
  (blocked.length > 0 ? `:\n ${blocked.join("\n ")}` : " — no scenario declares diff/output assertions or assert.trace"));
152
154
  }
155
+ const references = Object.fromEntries(targets.map(s => [s.id, resolveJudgeReferences(s, opts.specDir)]));
153
156
  const changes = [];
154
157
  let judgeCalls = 0;
155
158
  const scenarios = [];
@@ -176,6 +179,16 @@ export async function regateRun(opts) {
176
179
  const tPath = transcriptPath(opts.runDir, scenario.id, mode, rep);
177
180
  const before = existsSync(tPath) ? readFileSync(tPath, "utf8") : "";
178
181
  const outputGate = evaluateOutputGates(scenario, before);
182
+ const retainedPanel = rec.rep_judgments?.find(panel => panel.repetition === (rep ?? 0));
183
+ const recordedTraceHashes = [retainedPanel?.judgments[0]?.input?.trace_sha256, retainedPanel?.objective?.trace_sha256,
184
+ rec.objective?.rep_trace_sha256?.[rep ?? 0], rec.reps === undefined ? rec.objective?.trace_sha256 : undefined].filter((hash) => hash !== undefined);
185
+ const execution = new Set(recordedTraceHashes).size > 1
186
+ ? unavailableExecution("Conflicting recorded trace digests.", "invalid")
187
+ : loadExecutionFacts(tracePath(opts.runDir, scenario.id, mode, rep), {
188
+ scenarioId: scenario.id, mode, repetition: rep ?? 0, turns: scenario.turns.length,
189
+ ...(prev.model.includes(":") ? { subject: parseModelRef(prev.model) } : {}), piVersion: prev.harness_cli_version,
190
+ responses: transcriptResponses(before, scenario),
191
+ }, recordedTraceHashes[0]);
179
192
  // Output and trace gates are re-decided from saved artifacts. Free: no model, no judge.
180
193
  let traceFailure = null;
181
194
  const expectedOutputHash = rec.objective?.rep_output_sha256?.[rep ?? 0]
@@ -191,7 +204,11 @@ export async function regateRun(opts) {
191
204
  assertions: [...outputIntegrityAssertion, ...outputGate.assertions],
192
205
  }
193
206
  : undefined;
194
- if (scenario.traceAssert) {
207
+ if (execution.status === "invalid") {
208
+ traceFailure = `objective: ${execution.reason}`;
209
+ objective = { ...(objective ?? {}), status: "ERROR", assertions: [...(objective?.assertions ?? []), { kind: "trace_evidence", status: "ERROR", detail: execution.reason }] };
210
+ }
211
+ else if (scenario.traceAssert) {
195
212
  const tp = tracePath(opts.runDir, scenario.id, mode, rep);
196
213
  // A PARTIAL read is refused, not graded. `deserializeTrace` returns null
197
214
  // for a malformed line and for a version it declines, and dropping those
@@ -303,7 +320,7 @@ export async function regateRun(opts) {
303
320
  const judged = await judgeOneRep({
304
321
  runDir: opts.runDir, spec: opts.spec, scenario, transcript,
305
322
  adapter: opts.adapter, judge: opts.judge, specDir: opts.specDir,
306
- mode, rep, now, rejudge: true,
323
+ mode, rep, now, rejudge: true, execution, objective, references: references[scenario.id],
307
324
  });
308
325
  outcomes.push({ ...judged, objective });
309
326
  judgeCalls += judged.metrics?.judge_calls ?? 0;
@@ -347,6 +364,7 @@ export async function regateRun(opts) {
347
364
  skill: prev.skill, harness: prev.harness, model: prev.model,
348
365
  // Carried verbatim: a regate re-reads saved diffs, it never re-runs the harness.
349
366
  harness_cli_version: prev.harness_cli_version, delivery_canary: prev.delivery_canary,
367
+ delivery_observations: prev.delivery_observations,
350
368
  // The arm is provenance of the MEASUREMENT, not of this rewrite, and it is the
351
369
  // only record that a `+<arm>` run actually delegated: rebuilding the draft
352
370
  // field-by-field without it silently deleted `definitions`/`ledger_events`
package/dist/regrade.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { Spec, Scenario } from "./spec.js";
2
2
  import type { HarnessAdapter, ModelRef } from "./adapters/types.js";
3
+ import { type JudgeReference } from "./grade.js";
3
4
  import { type ScenarioResult, type ResultsFile } from "./results.js";
4
5
  import { type RepOutcome } from "./reps.js";
6
+ import { type JudgeExecutionFacts } from "./judge-evidence.js";
5
7
  /**
6
8
  * Carry a run's recorded hashes forward, refreshing only the `rubric:` keys this
7
9
  * re-grade actually judged under (plus the persona, which applies to all of them).
@@ -14,8 +16,9 @@ import { type RepOutcome } from "./reps.js";
14
16
  * No hashes recorded (a pre-`source_hashes` run) stays that way: inventing hashes for
15
17
  * a run that never recorded any would claim a coverage it does not have.
16
18
  */
17
- export declare function refreshRubricHashes(recorded: Record<string, string> | undefined, spec: Spec, judgedIds: string[]): Record<string, string> | undefined;
19
+ export declare function refreshRubricHashes(recorded: Record<string, string> | undefined, spec: Spec, judgedIds: string[], specDir?: string, references?: Record<string, JudgeReference[]>, refreshPersona?: boolean): Record<string, string> | undefined;
18
20
  export interface RegradeOptions {
21
+ references?: JudgeReference[];
19
22
  runDir: string;
20
23
  spec: Spec;
21
24
  scenario: Scenario;
@@ -37,6 +40,8 @@ export interface RegradeOptions {
37
40
  /** Select only repetitions with retained ERROR criterion votes. */
38
41
  onlyUnparsed?: boolean;
39
42
  prior?: ScenarioResult;
43
+ subject?: ModelRef;
44
+ piVersion?: string;
40
45
  now?: () => string;
41
46
  }
42
47
  /** Judge one saved transcript: writes the judge-raw artifact, emits a `judge-verdict` journal event (plus `misfire-flag` when the verdict is suspect), and returns the outcome. */
@@ -55,6 +60,8 @@ export declare function judgeOneRep(opts: {
55
60
  rejudge?: boolean;
56
61
  /** Retained objective evidence must precede missing-vote normalization. */
57
62
  objective?: RepOutcome["objective"];
63
+ execution?: JudgeExecutionFacts;
64
+ references?: JudgeReference[];
58
65
  }): Promise<RepOutcome>;
59
66
  /**
60
67
  * Re-judge a scenario's saved transcript(s) for the run's mode with `judge` — no
@@ -77,6 +84,8 @@ export interface RegradeRunOptions {
77
84
  * cleanly, so spending judge calls on them buys nothing.
78
85
  */
79
86
  onlySuspect?: boolean;
87
+ /** Rejudge only these recorded scenarios; untouched verdicts remain historical. */
88
+ only?: string[];
80
89
  /** Rejudge only retained repetitions containing ERROR criterion votes, with the recorded judge. */
81
90
  onlyUnparsed?: boolean;
82
91
  }