@skill-harness/core 0.16.0 → 0.18.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 (93) hide show
  1. package/README.md +1 -2
  2. package/dist/adapters/types.d.ts +0 -12
  3. package/dist/capture-trace-types.d.ts +0 -96
  4. package/dist/capture-trace-types.js +0 -4
  5. package/dist/command-cost.d.ts +1 -1
  6. package/dist/command-cost.js +0 -5
  7. package/dist/execution-trace.js +1 -1
  8. package/dist/index.d.ts +3 -23
  9. package/dist/index.js +3 -23
  10. package/dist/instruction-coverage.d.ts +0 -7
  11. package/dist/instruction-coverage.js +5 -7
  12. package/dist/journal.d.ts +0 -8
  13. package/dist/metrics.d.ts +18 -0
  14. package/dist/metrics.js +28 -0
  15. package/dist/redaction.d.ts +6 -0
  16. package/dist/redaction.js +55 -0
  17. package/dist/regate.js +6 -71
  18. package/dist/regrade.d.ts +1 -3
  19. package/dist/regrade.js +1 -21
  20. package/dist/report.d.ts +1 -15
  21. package/dist/report.js +1 -13
  22. package/dist/reps.d.ts +1 -2
  23. package/dist/reps.js +0 -2
  24. package/dist/results.d.ts +0 -9
  25. package/dist/results.js +2 -19
  26. package/dist/run.js +11 -72
  27. package/dist/seeded.d.ts +1 -7
  28. package/dist/seeded.js +5 -11
  29. package/dist/sources.js +4 -9
  30. package/dist/spec.d.ts +0 -13
  31. package/dist/spec.js +6 -48
  32. package/dist/trajectory-events.d.ts +58 -0
  33. package/dist/trajectory-events.js +119 -0
  34. package/package.json +1 -1
  35. package/schemas/results-v2.schema.json +376 -75
  36. package/schemas/specification-v1.schema.json +128 -174
  37. package/dist/adjudication.d.ts +0 -219
  38. package/dist/adjudication.js +0 -422
  39. package/dist/adoption.d.ts +0 -128
  40. package/dist/adoption.js +0 -102
  41. package/dist/affected.d.ts +0 -88
  42. package/dist/affected.js +0 -222
  43. package/dist/capture.d.ts +0 -193
  44. package/dist/capture.js +0 -344
  45. package/dist/comparison.d.ts +0 -97
  46. package/dist/comparison.js +0 -189
  47. package/dist/factory-calibration.d.ts +0 -63
  48. package/dist/factory-calibration.js +0 -115
  49. package/dist/intervention-results.d.ts +0 -6
  50. package/dist/intervention-results.js +0 -52
  51. package/dist/intervention.d.ts +0 -123
  52. package/dist/intervention.js +0 -218
  53. package/dist/investigation.d.ts +0 -94
  54. package/dist/investigation.js +0 -148
  55. package/dist/judge-agreement.d.ts +0 -19
  56. package/dist/judge-agreement.js +0 -31
  57. package/dist/qualification-capture.d.ts +0 -44
  58. package/dist/qualification-capture.js +0 -163
  59. package/dist/qualification-config.d.ts +0 -233
  60. package/dist/qualification-config.js +0 -694
  61. package/dist/qualification-lock.d.ts +0 -15
  62. package/dist/qualification-lock.js +0 -87
  63. package/dist/qualification-oauth-directory.d.ts +0 -79
  64. package/dist/qualification-oauth-directory.js +0 -444
  65. package/dist/qualification-panel-store.d.ts +0 -14
  66. package/dist/qualification-panel-store.js +0 -188
  67. package/dist/qualification-panels.d.ts +0 -99
  68. package/dist/qualification-panels.js +0 -91
  69. package/dist/qualification-process.d.ts +0 -13
  70. package/dist/qualification-process.js +0 -190
  71. package/dist/qualification-runner.d.ts +0 -203
  72. package/dist/qualification-runner.js +0 -2037
  73. package/dist/qualification-store.d.ts +0 -187
  74. package/dist/qualification-store.js +0 -833
  75. package/dist/qualification-validation.d.ts +0 -12
  76. package/dist/qualification-validation.js +0 -8
  77. package/dist/screen.d.ts +0 -31
  78. package/dist/screen.js +0 -105
  79. package/dist/trajectory-gates.d.ts +0 -137
  80. package/dist/trajectory-gates.js +0 -576
  81. package/dist/trusted-host-supervision.d.ts +0 -25
  82. package/dist/trusted-host-supervision.js +0 -94
  83. package/dist/work-capture-schema.d.ts +0 -38
  84. package/dist/work-capture-schema.js +0 -33
  85. package/dist/work-capture.d.ts +0 -43
  86. package/dist/work-capture.js +0 -81
  87. package/dist/work-signals.d.ts +0 -55
  88. package/dist/work-signals.js +0 -71
  89. package/schemas/qualification-cell-result-v1.schema.json +0 -32
  90. package/schemas/qualification-config-v1.schema.json +0 -244
  91. package/schemas/qualification-invocation-request-v1.schema.json +0 -59
  92. package/schemas/qualification-judge-panel-result-v1.schema.json +0 -42
  93. package/schemas/trajectory-event-v1.schema.json +0 -80
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. `grade --auto-rejudge` recomputes it.
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 "./comparison.js";
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 "./comparison.js";
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, SubjectInvocationObservation } from "./results.js";
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
  }
package/dist/results.d.ts CHANGED
@@ -72,8 +72,6 @@ export interface ScenarioResult {
72
72
  * actually blocks SHIP; this field is the audit trail behind that flag.
73
73
  */
74
74
  adjudication?: AdjudicationResult;
75
- /** Latest three full-cell grades; enables offline agreement reports with distinct judges. */
76
- judge_history?: Judgment[];
77
75
  /** Initial and panel judge members, by repetition, sufficient to recompute the recorded verdict. */
78
76
  rep_judgments?: RepJudgmentPanel[];
79
77
  }
@@ -109,10 +107,7 @@ export interface ObjectiveResult {
109
107
  status: "PASS" | "FAIL" | "ERROR" | "NOT-MEASURED";
110
108
  trace_version?: number;
111
109
  trace_sha256?: string;
112
- trajectory_version?: string;
113
- events_sha256?: string;
114
110
  /** Per-repetition hashes retained when an aggregate has more than one rep. */
115
- rep_events_sha256?: string[];
116
111
  rep_trace_sha256?: string[];
117
112
  assertions: {
118
113
  kind: string;
@@ -443,10 +438,6 @@ export declare function rebuildScenarioResult(fresh: ScenarioResult, prior: Scen
443
438
  * traces append without a wrapper.
444
439
  */
445
440
  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
441
  /** A scenario's staged-diff files, sorted (plain first, then numeric rep). Mode-scoped when given. */
451
442
  export declare function findDiffFiles(runDir: string, scenarioId: string, mode?: string): string[];
452
443
  /** 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 [...(scenario.judge_history ?? []), ...(scenario.adjudication?.judgments ?? [])])
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, judge_history: freshJudgeHistory, rep_judgments: freshRepJudgments, ...rest } = fresh;
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, rep_judgments: freshRepJudgments, ...rest } = fresh;
603
603
  const _exhaustive = rest;
604
604
  void _exhaustive;
605
605
  void _freshOverride;
@@ -660,7 +660,6 @@ export function rebuildScenarioResult(fresh, prior, policy) {
660
660
  // existed.
661
661
  ...(objective ? { objective } : {}),
662
662
  ...(adjudication ? { adjudication } : {}),
663
- ...((freshJudgeHistory ?? prior?.judge_history) ? { judge_history: freshJudgeHistory ?? prior.judge_history } : {}),
664
663
  ...((freshRepJudgments ?? prior?.rep_judgments) ? { rep_judgments: freshRepJudgments ?? prior.rep_judgments } : {}),
665
664
  };
666
665
  }
@@ -675,21 +674,6 @@ export function tracePath(runDir, scenarioId, mode, rep) {
675
674
  const base = rep === undefined ? `${scenarioId}.${mode}` : `${scenarioId}.${mode}.rep${rep}`;
676
675
  return join(runDir, `${base}.trace.jsonl`);
677
676
  }
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
677
  /** A scenario's staged-diff files, sorted (plain first, then numeric rep). Mode-scoped when given. */
694
678
  export function findDiffFiles(runDir, scenarioId, mode) {
695
679
  if (!existsSync(runDir))
@@ -738,7 +722,6 @@ export function preserveTranscript(resultsRoot, runDir, scenarioId) {
738
722
  // override whose justification was gitignored, and left `regate` with nothing
739
723
  // to re-evaluate on the one cell a human had disputed.
740
724
  ...findTraceFiles(runDir, scenarioId),
741
- ...findTrajectoryFiles(runDir, scenarioId),
742
725
  ];
743
726
  if (files.length === 0)
744
727
  return;
package/dist/run.js CHANGED
@@ -1,22 +1,21 @@
1
1
  import { mkdirSync, writeFileSync, readFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { sourceHashes, PROMPT_NORMALIZATION_SOURCE_KEY, PROMPT_NORMALIZATION_SOURCE_DIGEST } from "./sources.js";
3
+ import { sourceHashes } from "./sources.js";
4
4
  import { judgeResemblesSubject } from "./grade.js";
5
5
  import { NONE_ARM, seedArmDefinitions } from "./arms.js";
6
- import { runDirFor, transcriptPath, diffPath, tracePath, trajectoryPath, writeResults, ensureResultsGitignore, scoreContextFor, effectiveThreshold, isScoredMode, deliveryStatusForObservations, } from "./results.js";
6
+ import { runDirFor, transcriptPath, diffPath, tracePath, writeResults, ensureResultsGitignore, scoreContextFor, effectiveThreshold, isScoredMode, } from "./results.js";
7
7
  import { appendJournal } from "./journal.js";
8
8
  import { liftHeadline } from "./lift.js";
9
9
  import { runSeeded } from "./seeded.js";
10
10
  import { serializeTrace, mergeTraces, traceSha256 } from "./execution-trace.js";
11
11
  import { evaluateTraceGates } from "./trace-gates.js";
12
- import { evaluateTrajectoryGates, serializeTrajectoryEvents, } from "./trajectory-gates.js";
13
12
  import { snapshotPaths, diffSnapshots, createWorkspace } from "./workspace.js";
14
13
  import { runPool } from "./scheduler.js";
15
14
  import { outcomesToResult } from "./reps.js";
16
15
  import { judgeOneRep } from "./regrade.js";
17
16
  import { runDeliveryCanary, canaryFailure } from "./canary.js";
18
17
  import { boundaryCells, stabilityNote } from "./stability.js";
19
- import { aggregateMetrics } from "./comparison.js";
18
+ import { aggregateMetrics } from "./metrics.js";
20
19
  import { providerFailureFromTranscript } from "./provider-failure.js";
21
20
  /**
22
21
  * The pi-daddy arm's delivery proof, by convention: `arms.yaml` points
@@ -139,20 +138,13 @@ export async function runSkillModel(opts) {
139
138
  }
140
139
  });
141
140
  const flat = await runPool(tasks, opts.concurrency ?? 1);
142
- const subjectInvocations = flat.flatMap(outcome => outcome.subject_invocations ?? []);
143
141
  const grouped = scenarios.map(() => []);
144
142
  flat.forEach((outcome, i) => grouped[owners[i]].push(outcome));
145
143
  const scenarioResults = scenarios.map((scenario, si) => {
146
144
  const threshold = scenario.critical
147
145
  ? effectiveThreshold(undefined, scenario)
148
146
  : scenario.passThreshold ?? opts.passThreshold ?? 0.5;
149
- const result = outcomesToResult(scenario.id, grouped[si], repCounts[si], threshold);
150
- if (adapter.observesPrompts)
151
- result.criterion_count = scenario.checklist.length;
152
- if (adapter.observesPrompts && !result.rep_judgments) {
153
- result.rep_judgments = grouped[si].map((outcome, repetition) => ({ repetition, judgments: [], recorded_verdict: outcome.verdict, ...(outcome.objective ? { objective: outcome.objective } : {}) }));
154
- }
155
- return result;
147
+ return outcomesToResult(scenario.id, grouped[si], repCounts[si], threshold);
156
148
  });
157
149
  const ctx = scoreContextFor({ mode, partial }, spec);
158
150
  const results = writeResults(runDir, {
@@ -168,12 +160,8 @@ export async function runSkillModel(opts) {
168
160
  ...(partial ? { partial: true } : {}),
169
161
  // Only the scenarios this run actually measured: a --only run must not claim
170
162
  // coverage of scenarios it skipped.
171
- source_hashes: {
172
- ...sourceHashes({ skillDir, specDir: dirname(opts.specPath), scenarios, judgePersona: spec.judge_persona }),
173
- ...(adapter.observesPrompts ? { [PROMPT_NORMALIZATION_SOURCE_KEY]: PROMPT_NORMALIZATION_SOURCE_DIGEST } : {}),
174
- },
163
+ source_hashes: sourceHashes({ skillDir, specDir: dirname(opts.specPath), scenarios, judgePersona: spec.judge_persona }),
175
164
  scenarios: scenarioResults,
176
- ...(adapter.observesPrompts ? { schema: 3, subject_invocations: subjectInvocations } : {}),
177
165
  ...(arm.name === NONE_ARM.name ? {} : {
178
166
  arm: {
179
167
  name: arm.name,
@@ -240,7 +228,6 @@ async function runRep(scenario, rep, repCount, ctx) {
240
228
  // produces no diff, and writing an empty artifact there would misreport "the
241
229
  // model changed nothing" for a rep that never ran.
242
230
  let stagedDiff = null;
243
- const subjectInvocations = [];
244
231
  try {
245
232
  try {
246
233
  ws = createWorkspace(scenario.workspace, { specDir: dirname(ctx.specPath), remote: scenario.remote });
@@ -254,8 +241,6 @@ async function runRep(scenario, rep, repCount, ctx) {
254
241
  }
255
242
  let noResponse = false;
256
243
  let traces = [];
257
- let events = [];
258
- let eventErrors = [];
259
244
  let unobservablePaths = false;
260
245
  // The pre-run state, captured AFTER `createWorkspace` has applied the
261
246
  // fixture's `_staged/` and `_uncommitted/` trees. Those land after the
@@ -286,10 +271,10 @@ async function runRep(scenario, rep, repCount, ctx) {
286
271
  // used to pass `trace: {...}` unconditionally and let `runSeeded` throw
287
272
  // ``scenario `X` declares `assert.trace`…`` at a scenario that declares no
288
273
  // such thing.
289
- const needsStructuredEvidence = Boolean(scenario.traceAssert || scenario.trajectoryAssert);
274
+ const needsStructuredEvidence = Boolean(scenario.traceAssert);
290
275
  if (needsStructuredEvidence && !ctx.adapter.runStructured) {
291
276
  throw new Error(`scenario \`${scenario.id}\` declares structured objective assertions, but the \`${ctx.adapter.name}\` adapter` +
292
- ` cannot produce execution traces/events — the gate would have no evidence to read.`);
277
+ ` cannot produce execution traces — the gate would have no evidence to read.`);
293
278
  }
294
279
  const useStructured = (Boolean(ctx.structured) || needsStructuredEvidence) && Boolean(ctx.adapter.runStructured);
295
280
  // A blank assistant turn is a harness timeout, not model behavior: retry ONCE in a
@@ -297,7 +282,6 @@ async function runRep(scenario, rep, repCount, ctx) {
297
282
  // it happens again the verdict is ERROR — never a judged FAIL on an empty reply.
298
283
  // An adapter that THREW is the same class of event and takes the same retry.
299
284
  for (let attempt = 0; attempt < 2; attempt++) {
300
- const observe = (prompt) => subjectInvocations.push({ scenario_id: scenario.id, repetition: rep, attempt, prompt });
301
285
  if (attempt > 0) {
302
286
  const why = adapterFailure ? `adapter failed (${adapterFailure})` : "empty response";
303
287
  appendJournal(runDir, { event: "empty-response-retry", ts: now(), id: scenario.id, attempt, reason: why, ...repField });
@@ -337,15 +321,12 @@ async function runRep(scenario, rep, repCount, ctx) {
337
321
  // already substituted) both must reach pi.
338
322
  armExtensions: arm.extensions,
339
323
  ...(armEnvFor(ws.cwd) ? { armEnv: armEnvFor(ws.cwd) } : {}),
340
- ...(ctx.adapter.observesPrompts ? { onPromptObservation: observe } : {}),
341
324
  });
342
325
  transcript = r.transcript;
343
326
  gatePrefix = r.gateFailure;
344
327
  infrastructureFailure = r.gateError;
345
328
  stagedDiff = r.diff; // a retry replaces the aborted attempt's diff, as it should
346
329
  traces = r.traces;
347
- events = r.events;
348
- eventErrors = r.eventErrors;
349
330
  }
350
331
  else {
351
332
  const req = {
@@ -361,16 +342,12 @@ async function runRep(scenario, rep, repCount, ctx) {
361
342
  ...(scenario.extensions?.map((e) => resolve(dirname(ctx.specPath), e)) ?? []),
362
343
  ...arm.extensions,
363
344
  ],
364
- eventSources: scenario.eventSources,
365
345
  ...(armEnvFor(ws.cwd) ? { armEnv: armEnvFor(ws.cwd) } : {}),
366
- ...(ctx.adapter.observesPrompts ? { onPromptObservation: observe } : {}),
367
346
  };
368
347
  if (useStructured) {
369
348
  const structured = await ctx.adapter.runStructured({ ...req, scenarioId: scenario.id, rep });
370
349
  transcript = structured.transcript;
371
350
  traces = structured.traces;
372
- events = structured.events ?? [];
373
- eventErrors = structured.eventErrors ?? [];
374
351
  if (structured.providerFailure)
375
352
  infrastructureFailure = `provider failure — ${structured.providerFailure}`;
376
353
  }
@@ -399,8 +376,6 @@ async function runRep(scenario, rep, repCount, ctx) {
399
376
  gatePrefix = null;
400
377
  stagedDiff = null;
401
378
  traces = [];
402
- events = [];
403
- eventErrors = [];
404
379
  }
405
380
  // A provider outage is infrastructure, never a model verdict. Checked on
406
381
  // every path: the text path carries the marker in the transcript, the
@@ -457,16 +432,6 @@ async function runRep(scenario, rep, repCount, ctx) {
457
432
  });
458
433
  }
459
434
  }
460
- // Delivery is an objective gate on every Pi subject invocation. It is derived
461
- // from captured provider payload bytes, never from mode/argv supplied by a caller.
462
- let deliveryObjective;
463
- if (ctx.adapter.observesPrompts) {
464
- const statuses = subjectInvocations.map(observation => observation.prompt.status);
465
- const status = deliveryStatusForObservations(subjectInvocations);
466
- deliveryObjective = { status, assertions: [{ kind: "skill_delivered", status, detail: statuses.length === 0 ? "no model-visible prompt observation was retained" : `${statuses.length} provider request(s): ${statuses.join(", ")}` }] };
467
- if (status !== "PASS")
468
- gatePrefix = `objective: skill_delivered ${status.toLowerCase()} — ${deliveryObjective.assertions[0].detail}`;
469
- }
470
435
  // Objective evidence is persisted for every rep, pass or fail — a failing gate
471
436
  // is exactly when someone wants to read what the model actually did.
472
437
  // `!adapterFailure`: a rep whose adapter threw never ran, so there is nothing
@@ -474,12 +439,11 @@ async function runRep(scenario, rep, repCount, ctx) {
474
439
  // which is the symptom — it names the missing artifact instead of the crash or
475
440
  // timeout that caused it, and it is that text, not the real reason, that would
476
441
  // reach the results record. The verdict is ERROR either way.
477
- let objective = deliveryObjective;
478
- if ((scenario.traceAssert || scenario.trajectoryAssert) && !adapterFailure) {
442
+ let objective;
443
+ if (scenario.traceAssert && !adapterFailure) {
479
444
  const assertionResults = [];
480
445
  let status = "PASS";
481
446
  let traceMeta = {};
482
- let trajectoryMeta = {};
483
447
  if (scenario.traceAssert) {
484
448
  if (traces.length > 0) {
485
449
  writeFileSync(tracePath(runDir, scenario.id, mode, repSuffix), traces.map(serializeTrace).join(""), "utf8");
@@ -500,32 +464,7 @@ async function runRep(scenario, rep, repCount, ctx) {
500
464
  traceMeta = { trace_version: merged.trace_version, trace_sha256: merged.trace_sha256 };
501
465
  }
502
466
  }
503
- if (scenario.trajectoryAssert) {
504
- if (events.length > 0) {
505
- writeFileSync(trajectoryPath(runDir, scenario.id, mode, repSuffix), serializeTrajectoryEvents(events), "utf8");
506
- }
507
- if (eventErrors.length > 0) {
508
- status = "ERROR";
509
- assertionResults.push(...eventErrors.map((detail) => ({ kind: "trajectory_evidence", status: "ERROR", detail })));
510
- }
511
- else if (events.length === 0) {
512
- status = "ERROR";
513
- assertionResults.push({ kind: "trajectory_evidence", status: "ERROR", detail: "no normalized workflow events were produced" });
514
- }
515
- else {
516
- const gate = evaluateTrajectoryGates(scenario.trajectoryAssert, events);
517
- if (gate.status === "ERROR" || (gate.status === "FAIL" && status === "PASS"))
518
- status = gate.status;
519
- assertionResults.push(...gate.assertions);
520
- trajectoryMeta = { trajectory_version: gate.event_version, events_sha256: gate.events_sha256 };
521
- }
522
- }
523
- if (deliveryObjective) {
524
- if (deliveryObjective.status === "ERROR" || (deliveryObjective.status === "NOT-MEASURED" && status !== "ERROR") || (deliveryObjective.status === "FAIL" && status === "PASS"))
525
- status = deliveryObjective.status;
526
- assertionResults.unshift(...deliveryObjective.assertions);
527
- }
528
- objective = { status, ...traceMeta, ...trajectoryMeta, assertions: assertionResults };
467
+ objective = { status, ...traceMeta, assertions: assertionResults };
529
468
  if (status !== "PASS") {
530
469
  const details = assertionResults.filter((result) => result.status === status).map((result) => result.detail);
531
470
  gatePrefix = `objective: ${details.join("; ") || "structured evidence could not be evaluated"}`;
@@ -580,7 +519,7 @@ async function runRep(scenario, rep, repCount, ctx) {
580
519
  log(` → ${scenario.id}${repCount > 1 ? `#${rep}` : ""} ${verdict}${reason ? `: ${reason}` : ""}${suspect ? " ⚠ suspect" : ""}`);
581
520
  const subject = mergeTraces(traces)?.metrics;
582
521
  return {
583
- verdict, reason, suspect, objective, judgment, subject_invocations: subjectInvocations,
522
+ verdict, reason, suspect, objective, judgment,
584
523
  metrics: {
585
524
  wall_time_ms: Math.max(0, Math.round(performance.now() - startedAt)),
586
525
  judge_calls: judgeCalls,
package/dist/seeded.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { Scenario } from "./spec.js";
2
2
  import type { ExecutionTraceV1 } from "./capture-trace-types.js";
3
- import type { TrajectoryEventV1 } from "./trajectory-gates.js";
4
- import type { HarnessAdapter, ModelRef, RunMode, PromptProvenance } from "./adapters/types.js";
3
+ import type { HarnessAdapter, ModelRef, RunMode } from "./adapters/types.js";
5
4
  import { type ExecResult } from "./util/exec.js";
6
5
  interface SeededOpts {
7
6
  skillDir: string;
@@ -37,7 +36,6 @@ interface SeededOpts {
37
36
  armExtensions?: string[];
38
37
  /** The arm's env for the subject process; `<run-dir>` already substituted by the caller. */
39
38
  armEnv?: Record<string, string>;
40
- onPromptObservation?: (observation: PromptProvenance) => void;
41
39
  }
42
40
  /**
43
41
  * Result of one vitest invocation — deliberately `ExecResult`, not a narrower
@@ -58,10 +56,6 @@ export interface SeededOutcome {
58
56
  diff: string;
59
57
  /** One per turn; empty unless structured execution was requested. */
60
58
  traces: ExecutionTraceV1[];
61
- /** Adapter-neutral workflow/tool events for assert.trajectory. */
62
- events: TrajectoryEventV1[];
63
- /** Required native sources that could not be read or normalized. */
64
- eventErrors: string[];
65
59
  }
66
60
  /**
67
61
  * The added/removed lines of a unified diff — what the model actually *changed*,
package/dist/seeded.js CHANGED
@@ -173,17 +173,13 @@ export async function runSeeded(scenario, opts) {
173
173
  ...(scenario.extensions?.map((e) => resolve(opts.specDir, e)) ?? []),
174
174
  ...(opts.armExtensions ?? []),
175
175
  ],
176
- eventSources: scenario.eventSources,
177
176
  ...(opts.armEnv ? { armEnv: opts.armEnv } : {}),
178
- ...(opts.onPromptObservation ? { onPromptObservation: opts.onPromptObservation } : {}),
179
177
  };
180
178
  // A trace-gated seeded scenario runs through the structured path so the tool
181
179
  // calls are recorded; everything downstream (gates, diff, transcript) is
182
180
  // identical, because the rebuilt transcript is what print mode would have
183
181
  // emitted anyway.
184
182
  let traces = [];
185
- let events = [];
186
- let eventErrors = [];
187
183
  let harnessOut;
188
184
  if (opts.trace) {
189
185
  if (!opts.adapter.runStructured) {
@@ -197,8 +193,6 @@ export async function runSeeded(scenario, opts) {
197
193
  });
198
194
  harnessOut = structured.transcript;
199
195
  traces = structured.traces;
200
- events = structured.events ?? [];
201
- eventErrors = structured.eventErrors ?? [];
202
196
  }
203
197
  else {
204
198
  harnessOut = await opts.adapter.run(req);
@@ -227,7 +221,7 @@ export async function runSeeded(scenario, opts) {
227
221
  parts.push(` staged diff: ERROR (${msg})`);
228
222
  gateFailure = msg;
229
223
  gateError = msg;
230
- return finish(parts, gateFailure, gateError, diff, traces, events, eventErrors);
224
+ return finish(parts, gateFailure, gateError, diff, traces);
231
225
  }
232
226
  // BOTH needle gates read the changed lines only, never context. A unified diff
233
227
  // carries three lines of context per hunk, so an untouched symbol near the edit
@@ -301,7 +295,7 @@ export async function runSeeded(scenario, opts) {
301
295
  if (!gateFailure)
302
296
  gateFailure = msg;
303
297
  gateError = msg;
304
- return finish(parts, gateFailure, gateError, diff, traces, events, eventErrors);
298
+ return finish(parts, gateFailure, gateError, diff, traces);
305
299
  }
306
300
  const v = await runVitest([POST_TEST_BASE], repo);
307
301
  const out = `${v.stdout}\n${v.stderr}`;
@@ -358,7 +352,7 @@ export async function runSeeded(scenario, opts) {
358
352
  gateError = problem;
359
353
  }
360
354
  }
361
- return finish(parts, gateFailure, gateError, diff, traces, events, eventErrors);
355
+ return finish(parts, gateFailure, gateError, diff, traces);
362
356
  }
363
357
  function git(cwd, args) {
364
358
  return exec("git", args, { cwd, timeoutMs: 30_000 });
@@ -392,13 +386,13 @@ function bothStreams(v) {
392
386
  return o || e;
393
387
  }
394
388
  /** Append the staged diff and return the outcome. Every exit path goes through here, so the judge always sees the same sections in the same order. */
395
- function finish(parts, gateFailure, gateError, diff, traces = [], events = [], eventErrors = []) {
389
+ function finish(parts, gateFailure, gateError, diff, traces = []) {
396
390
  // The code itself, last — the gates above only prove that keywords appeared.
397
391
  // Without this section a seeded checklist item about what the code *does* is
398
392
  // graded from the model's own description of its work.
399
393
  parts.push("", "=== STAGED DIFF ===");
400
394
  parts.push(diff.trim() === "" ? " (empty — the model left no staged changes)" : capDiff(diff));
401
- return { transcript: parts.join("\n"), gateFailure, gateError, diff, traces, events, eventErrors };
395
+ return { transcript: parts.join("\n"), gateFailure, gateError, diff, traces };
402
396
  }
403
397
  /**
404
398
  * Parse vitest's `Tests N passed | M skipped (T)` summary line.
package/dist/sources.js CHANGED
@@ -170,13 +170,13 @@ function walk(dir, prefix = "") {
170
170
  * transcripts are actually invalid.
171
171
  */
172
172
  function facets(s) {
173
- const { id, title, critical, mode, turns, checklist, fixture, assert, traceAssert, trajectoryAssert, workspace, remote, systemPromptFile, extensions, eventSources, reps, passThreshold, covers: _coversIsMetadata, ...restScenario } = s;
173
+ const { id, title, critical, mode, turns, checklist, fixture, assert, traceAssert, workspace, remote, systemPromptFile, extensions, reps, passThreshold, covers: _coversIsMetadata, ...restScenario } = s;
174
174
  const _scenarioExhaustive = restScenario;
175
175
  void _scenarioExhaustive;
176
176
  // `covers` is destructured into a discard on purpose, and this comment is the
177
177
  // decision the guard demanded: it belongs to NO digest. It records which
178
- // instruction sections a scenario is declared to exercise, which changes what
179
- // `--affected` selects next time — not what any past run measured. Bucketing it
178
+ // instruction sections a scenario is declared to exercise; it does not change
179
+ // what any past run measured. Bucketing it
180
180
  // anywhere would charge a re-run (or at best a re-score) for editing a label,
181
181
  // which is the exact trap the facet split was built to remove.
182
182
  void _coversIsMetadata;
@@ -187,7 +187,7 @@ function facets(s) {
187
187
  // already saved, so `regate` can re-answer it without re-running the subject model.
188
188
  // Note the asymmetry with `env.extensions` in Phase 3, which IS stimulus — one
189
189
  // changes what gets executed, the other only what we conclude from it.
190
- const hasGates = diff_contains !== undefined || diff_excludes !== undefined || traceAssert !== undefined || trajectoryAssert !== undefined;
190
+ const hasGates = diff_contains !== undefined || diff_excludes !== undefined || traceAssert !== undefined;
191
191
  return {
192
192
  // `vitest` and the `post_test` PATH are stimulus, not gates: both change what the
193
193
  // run executes in the workspace, and neither can be re-evaluated from a saved
@@ -206,10 +206,6 @@ function facets(s) {
206
206
  id, mode, turns, workspace, remote, systemPromptFile ?? null,
207
207
  fixture ?? null, vitest ?? null, post_test ?? null,
208
208
  ...(extensions ? [extensions] : []),
209
- // Event-source paths choose which native ledgers are captured. A saved
210
- // normalized artifact cannot answer for a source that was never collected,
211
- // so changing this is stimulus and needs a re-run.
212
- ...(eventSources ? [eventSources] : []),
213
209
  ]),
214
210
  rubric: JSON.stringify([id, title, checklist]),
215
211
  policy: JSON.stringify([id, critical, reps ?? null, passThreshold ?? null]),
@@ -219,7 +215,6 @@ function facets(s) {
219
215
  ? JSON.stringify([
220
216
  id, diff_contains ?? null, diff_excludes ?? null,
221
217
  ...(traceAssert ? [traceAssert] : []),
222
- ...(trajectoryAssert ? [trajectoryAssert] : []),
223
218
  ])
224
219
  : null,
225
220
  };
package/dist/spec.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { WorkspaceKind } from "./workspace.js";
2
2
  import { type TraceAssert } from "./trace-gates.js";
3
- import { type TrajectoryAssert } from "./trajectory-gates.js";
4
3
  export type ScenarioMode = "inline" | "seeded";
5
4
  export interface SeededAssert {
6
5
  vitest?: boolean;
@@ -21,14 +20,6 @@ export interface SeededAssert {
21
20
  */
22
21
  post_test?: string;
23
22
  }
24
- export interface TrajectoryEventSource {
25
- /** Native format adapter registered by the harness adapter. */
26
- adapter: "normalized-v1" | "principal-assurance-v1" | "pi-daddy-v1" | "pi-daddy-ledger-v3";
27
- /** Workspace-relative file/glob. Never an absolute path or traversal. */
28
- path: string;
29
- /** Missing evidence is an error by default; false is for optional supplemental ledgers. */
30
- required: boolean;
31
- }
32
23
  export interface Scenario {
33
24
  id: string;
34
25
  title: string;
@@ -46,10 +37,6 @@ export interface Scenario {
46
37
  * Declaring it opts the scenario into structured (`--mode json`) execution.
47
38
  */
48
39
  traceAssert?: TraceAssert;
49
- /** Objective assertions over adapter-neutral, versioned workflow events. */
50
- trajectoryAssert?: TrajectoryAssert;
51
- /** Native event/ledger files the harness normalizes after execution. */
52
- eventSources?: TrajectoryEventSource[];
53
40
  workspace: WorkspaceKind;
54
41
  remote: boolean;
55
42
  systemPromptFile?: string;