Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2d746ac
[jwbron/live-eval-corpus] review: live-enabled corpus format and ten …
jwbron Jul 9, 2026
6727eb6
[jwbron/live-eval-producer-staging] review: live-producer prompt extr…
jwbron Jul 9, 2026
5c00cf3
[jwbron/live-eval-producer-staging] review: the live producer and SDK…
jwbron Jul 9, 2026
52073ef
[jwbron/live-eval-ab-runner] review: the live A/B runner (phase 3)
jwbron Jul 9, 2026
93770e6
[jwbron/live-eval-ab-ci] review: per-PR live A/B workflow (phase 4)
jwbron Jul 9, 2026
30e7527
[jwbron/live-eval-corpus] review: exclude eval-corpus trees from lint…
jwbron Jul 9, 2026
19f06cb
[jwbron/live-eval-producer-staging] Merge branch 'jwbron/live-eval-co…
jwbron Jul 9, 2026
d1b5288
[jwbron/live-eval-ab-runner] Merge branch 'jwbron/live-eval-producer-…
jwbron Jul 9, 2026
254dfc4
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 9, 2026
917cc11
[jwbron/live-eval-producer-staging] review: namespace live finding id…
jwbron Jul 9, 2026
0ee295a
[jwbron/live-eval-ab-runner] Merge branch 'jwbron/live-eval-producer-…
jwbron Jul 9, 2026
58cd4ed
[jwbron/live-eval-ab-runner] review: judge failures degrade the A/B r…
jwbron Jul 9, 2026
b19b08d
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 9, 2026
bced291
[jwbron/review-trial-skill] review: add the review-trial skill (live …
jwbron Jul 9, 2026
da1115c
[tmp-refresh] Merge remote-tracking branch 'origin/main' into tmp-ref…
jwbron Jul 9, 2026
a6883be
[tmp-refresh] Merge remote-tracking branch 'origin/jwbron/live-eval-c…
jwbron Jul 9, 2026
0d02672
[tmp-refresh] Merge remote-tracking branch 'origin/jwbron/live-eval-p…
jwbron Jul 9, 2026
93d8dec
[tmp-refresh] Merge remote-tracking branch 'origin/jwbron/live-eval-a…
jwbron Jul 9, 2026
e3b34eb
[tmp-refresh] Merge remote-tracking branch 'origin/jwbron/live-eval-a…
jwbron Jul 9, 2026
491a983
[jwbron/review-rereview-accountability] review: re-review accountabil…
jwbron Jul 9, 2026
7b5318c
[jwbron/review-out-of-lane] review: hand off out-of-lane observations…
jwbron Jul 9, 2026
5dd182b
[jwbron/review-out-artifact-upload] review: fix the out/ artifact upl…
jwbron Jul 9, 2026
92bffa2
[jwbron/review-rereview-accountability] review: prettier-format the r…
jwbron Jul 9, 2026
2be8ede
[jwbron/review-out-of-lane] Merge branch 'jwbron/review-rereview-acco…
jwbron Jul 9, 2026
3a8fc5c
[jwbron/review-out-of-lane] review: prettier-format finding-schema
jwbron Jul 9, 2026
2812679
[jwbron/live-eval-corpus] review: route the specialist lens on each l…
jwbron Jul 9, 2026
2ce35a0
[jwbron/live-eval-producer-staging] Merge branch 'jwbron/live-eval-co…
jwbron Jul 9, 2026
c0fece2
[jwbron/live-eval-ab-runner] Merge branch 'jwbron/live-eval-producer-…
jwbron Jul 9, 2026
e02ac40
[jwbron/live-eval-ab-runner] review: carry agent-failure reasons into…
jwbron Jul 9, 2026
d225bd4
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 9, 2026
cbc838d
[jwbron/live-eval-ab-runner] review: close every A/B report with a pe…
jwbron Jul 9, 2026
63097f1
[jwbron/live-eval-corpus] Merge remote-tracking branch 'origin/jwbron…
jwbron Jul 9, 2026
9012508
[jwbron/live-eval-producer-staging] Merge remote-tracking branch 'ori…
jwbron Jul 9, 2026
25133b4
[jwbron/live-eval-producer-staging] Merge branch 'jwbron/live-eval-co…
jwbron Jul 9, 2026
0a3d212
[jwbron/live-eval-ab-runner] Merge remote-tracking branch 'origin/jwb…
jwbron Jul 9, 2026
a547972
[jwbron/live-eval-ab-runner] Merge branch 'jwbron/live-eval-producer-…
jwbron Jul 9, 2026
391151b
[jwbron/live-eval-ab-ci] Merge remote-tracking branch 'origin/jwbron/…
jwbron Jul 9, 2026
d2c4c70
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 9, 2026
996766f
[jwbron/live-eval-ab-runner] review: identity short-circuit, gate-fli…
jwbron Jul 10, 2026
fb81be8
[jwbron/live-eval-ab-runner] review: judge economics (Haiku pin, retr…
jwbron Jul 10, 2026
a659be8
[jwbron/live-eval-ab-ci] review: document why the baseline is the bas…
jwbron Jul 10, 2026
b7c3786
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 10, 2026
541e413
[jwbron/review-trial-skill] Merge branch 'jwbron/live-eval-ab-ci' int…
jwbron Jul 10, 2026
fd42efd
[jwbron/review-out-artifact-upload] Merge branch 'jwbron/review-trial…
jwbron Jul 10, 2026
6d3459a
[jwbron/review-rereview-accountability] Merge branch 'jwbron/review-o…
jwbron Jul 10, 2026
1438a67
[jwbron/review-out-of-lane] Merge branch 'jwbron/review-rereview-acco…
jwbron Jul 10, 2026
193ae69
[jwbron/live-eval-ab-runner] review: type the judge response via the …
jwbron Jul 10, 2026
7a2065c
[jwbron/live-eval-ab-ci] Merge branch 'jwbron/live-eval-ab-runner' in…
jwbron Jul 10, 2026
00ce9d4
[jwbron/review-trial-skill] Merge branch 'jwbron/live-eval-ab-ci' int…
jwbron Jul 10, 2026
4bd445c
[jwbron/review-out-artifact-upload] Merge branch 'jwbron/review-trial…
jwbron Jul 10, 2026
da1e1db
[jwbron/review-rereview-accountability] Merge branch 'jwbron/review-o…
jwbron Jul 10, 2026
1bc9e08
[jwbron/review-out-of-lane] Merge branch 'jwbron/review-rereview-acco…
jwbron Jul 10, 2026
80da666
Merge branch 'main' into jwbron/live-eval-corpus
jwbron Jul 10, 2026
82bc5ed
Merge branch 'jwbron/live-eval-corpus' into jwbron/live-eval-producer…
jwbron Jul 10, 2026
7f2fc3a
Merge branch 'jwbron/live-eval-producer-staging' into jwbron/live-eva…
jwbron Jul 10, 2026
9cc1a89
Merge branch 'jwbron/live-eval-ab-runner' into jwbron/live-eval-ab-ci
jwbron Jul 10, 2026
da93344
Merge branch 'jwbron/live-eval-ab-ci' into jwbron/review-trial-skill
jwbron Jul 10, 2026
aa5a246
Merge branch 'jwbron/review-trial-skill' into jwbron/review-out-artif…
jwbron Jul 10, 2026
248cdf8
Merge branch 'jwbron/review-out-artifact-upload' into jwbron/review-r…
jwbron Jul 10, 2026
51691de
Merge branch 'jwbron/review-rereview-accountability' into jwbron/revi…
jwbron Jul 10, 2026
7de0635
[landtmp] Merge remote-tracking branch 'origin/main' into landtmp
jwbron Jul 10, 2026
6a1723c
[jwbron/review-out-of-lane] Merge remote-tracking branch 'origin/main…
jwbron Jul 13, 2026
cbb2999
[jwbron/review-out-of-lane] ci: re-fire checks against the retargeted…
jwbron Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/review-out-of-lane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"review": minor
---

Out-of-lane observations are handed off, never dropped. Observed on the review-v1.4.0 re-run lifecycle: the skill-auditor found a real eventual-consistency bug (dedup reads via a Query immediately after PutMulti) and discarded it, verbatim "that's a correctness concern, not a quotable skill-rule violation, so I'll leave it"; correct under quote-the-rule, but the observation died and the defect shipped unflagged. The skill-auditor and every specialist lens may now return `out_of_lane_observations[]` (path, optional line, the concern stated concretely, a required concrete `failure_scenario`, optional `suggested_lane`) for real concerns their own mandate does not let them report. The orchestrator converts each one into a candidate comment with the code-assigned label `question (non-blocking)` (a handoff is not a vetted finding and can never block on its own; the claim-validator never upgrades severity) and routes it through the identical provenance gate, scope filter, claims.json, and validation path as every other candidate. The shape is validated by the new `validateOutOfLaneObservation` in `lib/finding-schema.ts` (a sibling type, so `FINDING_SCHEMA_VERSION` stays 2: no serialized finding is invalidated).
5 changes: 5 additions & 0 deletions workflows/review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ read-only **sub-agents** (it makes every GitHub and comment call itself):
blocking first, with the resolved count, and an approval that resolved the last
open threads states that every prior thread is resolved — resolving some threads
never leaves the rest silently open.
A reviewer that surfaces a real concern its own mandate does not let it report — a
correctness problem the skill-auditor cannot quote a rule for, or something outside
a specialist lens's domain — hands it off as an `out_of_lane_observations[]` entry
instead of dropping it; the orchestrator routes each one into claim validation as a
non-blocking candidate (label code-assigned, so a handoff can never block on its own).
3. If those reviewers proposed any comments, **`claim-validator`** re-checks each one
against the actual code (attacking the finding's stated failure scenario) and,
for best-practice claims, against the relevant skill's
Expand Down
88 changes: 88 additions & 0 deletions workflows/review/lib/finding-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
validateFinding,
isValidFinding,
assertFinding,
validateOutOfLaneObservation,
isValidOutOfLaneObservation,
} from "./finding-schema.ts";

/**
Expand Down Expand Up @@ -384,3 +386,89 @@ describe("assertFinding", () => {
}
});
});

describe("validateOutOfLaneObservation", () => {
const makeValidObservation = (
overrides: Record<string, unknown> = {},
): Record<string, unknown> => ({
path: "services/foo/dedup.go",
line: 42,
observation:
"Dedup reads via an eventually-consistent Query immediately after PutMulti.",
failure_scenario:
"Two rapid submissions race the index; the second Query misses the first PutMulti and both rows persist.",
suggested_lane: "correctness",
...overrides,
});

it("accepts a well-formed observation", () => {
const result = validateOutOfLaneObservation(makeValidObservation());
expect(result.ok).toBe(true);
});

it("accepts the minimal shape (no line, no suggested_lane)", () => {
const minimal = makeValidObservation();
delete minimal["line"];
delete minimal["suggested_lane"];
expect(validateOutOfLaneObservation(minimal).ok).toBe(true);
});

it("rejects a non-object", () => {
const result = validateOutOfLaneObservation("nope");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors).toEqual(["observation: must be an object"]);
}
});

it("requires path, observation, and failure_scenario", () => {
const result = validateOutOfLaneObservation({});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors).toContain("path: required non-empty string");
expect(result.errors).toContain(
"observation: required non-empty string",
);
expect(result.errors).toContain(
"failure_scenario: required non-empty string",
);
}
});

it("collects every violation rather than failing on the first", () => {
const result = validateOutOfLaneObservation(
makeValidObservation({
path: "",
line: 0,
observation: "",
suggested_lane: "",
}),
);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.errors.length).toBe(4);
}
});

it("rejects a non-integer or non-positive line when present", () => {
expect(
validateOutOfLaneObservation(makeValidObservation({line: 1.5})).ok,
).toBe(false);
expect(
validateOutOfLaneObservation(makeValidObservation({line: -3})).ok,
).toBe(false);
});
});

describe("isValidOutOfLaneObservation", () => {
it("narrows well-formed input and rejects malformed input", () => {
expect(
isValidOutOfLaneObservation({
path: "a.go",
observation: "A real concern.",
failure_scenario: "Input X produces wrong output Y.",
}),
).toBe(true);
expect(isValidOutOfLaneObservation({path: "a.go"})).toBe(false);
});
});
94 changes: 94 additions & 0 deletions workflows/review/lib/finding-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,46 @@ export type Finding = {
model_authored_prose: string;
};

/**
* An out-of-lane observation: a real concern a reviewer surfaced that its own
* mandate does not let it report as a finding — e.g. the skill-auditor notices
* a correctness problem while checking a rule, but quote-the-rule rightly
* forbids reporting it as a skill violation. Production motivation: on the
* review-v1.4.0 re-run lifecycle the skill-auditor found a real
* eventual-consistency bug (dedup reads via a Query immediately after
* PutMulti) and dropped it — "that's a correctness concern, not a quotable
* skill-rule violation, so I'll leave it". Correct per quote-the-rule, but the
* observation died.
*
* The skill-auditor and every specialist lens may return these alongside
* `findings[]`; the orchestrator routes each one into claim validation as a
* non-blocking candidate (label code-assigned, never model-chosen), so a
* declined-as-out-of-lane observation is validated and surfaced rather than
* discarded. An observation is a handoff, not a vetted finding: it can never
* block on its own.
*/
export type OutOfLaneObservation = {
/** Repo-relative path the observation anchors on. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (non-blocking): The production orchestrator now routes out-of-lane observations, but the live-eval producer (eval/live-producer.ts parseAgentFindings) reads only findings[]. So in a review-trial a handed-off observation is dropped and the arm scores as if it never happened — the same drop this PR closes on the production path. Worth teaching the eval harness to read out_of_lane_observations[] in a follow-up so the trial can measure this feature.

path: string;
/** RIGHT-side diff line, when the concern is line-specific. */
line?: number;
/** One sentence: the concern, stated concretely. Model-authored. */
observation: string;
/**
* One sentence: the concrete inputs/state and the wrong outcome they
* produce — the claim the claim-validator attacks, exactly as on a
* finding. An observation whose scenario cannot be stated concretely is
* not worth handing off.
*/
failure_scenario: string;
/** The lane the producer thinks owns this, e.g. `correctness`. Optional. */
suggested_lane?: string;
};

export type ObservationValidationResult =
| {ok: true; observation: OutOfLaneObservation}
| {ok: false; errors: string[]};

export type ValidationResult =
| {ok: true; finding: Finding}
| {ok: false; errors: string[]};
Expand Down Expand Up @@ -328,6 +368,60 @@ export const validateFinding = (input: unknown): ValidationResult => {
return {ok: true, finding: input as Finding};
};

/**
* Validate an untrusted value against the out-of-lane observation shape.
* Same error-collecting contract as {@link validateFinding}: every problem is
* returned, so a producer's malformed handoff is diagnosable from the run
* artifact rather than silently dropped.
*/
export const validateOutOfLaneObservation = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): validateOutOfLaneObservation is referenced only by the tests — the orchestrator prose in review.md never invokes it (unlike the provenance gate, which runs a real CLI). Is the intent that shape enforcement stays eval/test-only, as validateFinding already is? If so, the PR description's "shape is code-validated" slightly overstates the live behavior; a one-line note that the live gate is the orchestrator prompt would settle it.

input: unknown,
): ObservationValidationResult => {
const errors: string[] = [];

if (!isRecord(input)) {
return {ok: false, errors: ["observation: must be an object"]};
}

if (!isNonEmptyString(input["path"])) {
errors.push("path: required non-empty string");
}

const line = input["line"];
if (
line !== undefined &&
(!Number.isInteger(line) || (line as number) < 1)
) {
errors.push("line: must be a positive integer when present");
}

if (!isNonEmptyString(input["observation"])) {
errors.push("observation: required non-empty string");
}

if (!isNonEmptyString(input["failure_scenario"])) {
errors.push("failure_scenario: required non-empty string");
}

if (
input["suggested_lane"] !== undefined &&
!isNonEmptyString(input["suggested_lane"])
) {
errors.push("suggested_lane: must be a non-empty string when present");
}

if (errors.length > 0) {
return {ok: false, errors};
}

return {ok: true, observation: input as OutOfLaneObservation};
};

/** Narrowing boolean wrapper around {@link validateOutOfLaneObservation}. */
export const isValidOutOfLaneObservation = (
input: unknown,
): input is OutOfLaneObservation => validateOutOfLaneObservation(input).ok;

/** Narrowing boolean wrapper around {@link validateFinding}. */
export const isValidFinding = (input: unknown): input is Finding =>
validateFinding(input).ok;
Expand Down
Loading
Loading