From 3cb196aa0f3195e6daff2ea29f0bc5539c1d9b73 Mon Sep 17 00:00:00 2001 From: Tom Smith <142233216+tomsmith8@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:19:32 +0100 Subject: [PATCH 1/3] Rail: join concept attempts to runs via graph-stamped unique_source_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jarvis-backend stamps unique_source_id (the Stakwork project id) on nodes at dispatch — the proposed-fixes route already reads it with project_id as legacy fallback. That is the graph->run key concept attempts were missing: they carry no evalTriggerRef anywhere, so the rail could only show em-dashes for them. - The walker now carries edge properties through (some writers stamp unique_source_id on edges rather than nodes) - Charted rows resolve a graph project id (output node -> owning trigger node -> touching edges, unique_source_id before project_id) and use it as a second join key against all three run lists, giving concept attempts real status, run type, and report state when a run row exists - When no run row exists at all, the graph-stamped project id still powers the super-admin Stakwork link, so every attempt is openable --- .../unit/hooks/useEvalRunHistory.test.ts | 111 ++++++++++++++++++ src/hooks/useEvalRunHistory.ts | 59 +++++++++- src/lib/harvey-lab/fix-chain-walker.ts | 2 + src/lib/harvey-lab/hill-climb-series.ts | 2 + 4 files changed, 172 insertions(+), 2 deletions(-) diff --git a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts index 7ad6dc41ce..8eae28f226 100644 --- a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts +++ b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts @@ -759,3 +759,114 @@ describe("useEvalRunHistory — attemptRows", () => { expect(keys).toEqual(["output-base", "output-r1", "eval-live"]); }); }); + +// ─── Graph-stamped Stakwork project id (unique_source_id) ──────────────────── + +describe("useEvalRunHistory — unique_source_id join", () => { + const EVAL_SET_REF = "ref-usid-001"; + const TASK_SLUG = "antitrust/task-1"; + + afterEach(() => { + vi.restoreAllMocks(); + mockBuildHillClimbSeries.mockReset(); + }); + + /** Concept-pipeline shape: no identity fields, unique_source_id on the output node. */ + function conceptGraph(uniqueSourceId?: string | number) { + return { + nodes: [ + makeTriggerNode("trigger-c1", false), + { + ref_id: "output-c1", + node_type: "EvalTriggerOutput", + date_added_to_graph: "1720000000", + properties: { + result: "partial", + score: 70 / 71, + n_passed: 70, + n_total: 71, + ...(uniqueSourceId != null ? { unique_source_id: uniqueSourceId } : {}), + }, + }, + ], + edges: [ + { source: EVAL_SET_REF, target: "trigger-c1", edge_type: "HAS_TRIGGER" }, + { source: "trigger-c1", target: "output-c1", edge_type: "HAS_OUTPUT" }, + ], + }; + } + + function renderUsid(routes: Record) { + mockBuildHillClimbSeries.mockReturnValue([]); + mockFetch(routes); + return renderHook(() => useEvalRunHistory({ refId: EVAL_SET_REF, slug: TASK_SLUG })); + } + + it("joins a concept attempt to its run row by graph-stamped project id", async () => { + const graph = conceptGraph("152583201"); + const { result } = renderUsid({ + "fix-chain": makeFixChainResponse(graph.nodes, graph.edges), + "type=LEGAL_BENCHMARK_RUNNER": { runs: [] }, + "type=LEGAL_BENCHMARK_EVAL": { runs: [] }, + "type=LEGAL_BENCHMARK_RECURSION": { + runs: [ + { + id: "rec-run-1", + projectId: 152583201, + status: "COMPLETED", + createdAt: "2026-08-19T10:00:00.000Z", + hasReport: true, + result: JSON.stringify({ taskSlug: TASK_SLUG, recursionId: "r-1" }), + }, + ], + }, + }); + await waitFor(() => expect(result.current.isLoading).toBe(false), { timeout: 5000 }); + + const row = result.current.attemptRows.find((r) => r.key === "output-c1")!; + // No evalTriggerRef anywhere — joined purely on projectId + expect(row.status).toBe("COMPLETED"); + expect(row.runType).toBe("recursion"); + expect(row.runId).toBe("rec-run-1"); + expect(row.hasReport).toBe(true); + expect(row.projectId).toBe(152583201); + // Claimed by the join — must not duplicate as a run-only row + expect(result.current.attemptRows.filter((r) => r.runId === "rec-run-1")).toHaveLength(1); + }); + + it("keeps the Stakwork link from the graph when no run row exists at all", async () => { + const graph = conceptGraph(152583201); + const { result } = renderUsid({ + "fix-chain": makeFixChainResponse(graph.nodes, graph.edges), + "type=LEGAL_BENCHMARK_RUNNER": { runs: [] }, + "type=LEGAL_BENCHMARK_EVAL": { runs: [] }, + "type=LEGAL_BENCHMARK_RECURSION": { runs: [] }, + }); + await waitFor(() => expect(result.current.isLoading).toBe(false), { timeout: 5000 }); + + const row = result.current.attemptRows.find((r) => r.key === "output-c1")!; + expect(row.status).toBeNull(); + expect(row.runId).toBeNull(); + expect(row.hasReport).toBe(false); + // The graph-stamped project id still powers the super-admin link + expect(row.projectId).toBe(152583201); + }); + + it("reads unique_source_id from edge properties when nodes lack it", async () => { + const graph = conceptGraph(undefined); + graph.edges[1] = { + ...graph.edges[1], + properties: { unique_source_id: "99887766" }, + } as (typeof graph.edges)[number] & { properties: Record }; + const { result } = renderUsid({ + "fix-chain": makeFixChainResponse(graph.nodes, graph.edges), + "type=LEGAL_BENCHMARK_RUNNER": { runs: [] }, + "type=LEGAL_BENCHMARK_EVAL": { runs: [] }, + "type=LEGAL_BENCHMARK_RECURSION": { runs: [] }, + }); + await waitFor(() => expect(result.current.isLoading).toBe(false), { timeout: 5000 }); + + const row = result.current.attemptRows.find((r) => r.key === "output-c1")!; + expect(row.projectId).toBe(99887766); + }); +}); diff --git a/src/hooks/useEvalRunHistory.ts b/src/hooks/useEvalRunHistory.ts index 5ca6c22c4b..966c45c714 100644 --- a/src/hooks/useEvalRunHistory.ts +++ b/src/hooks/useEvalRunHistory.ts @@ -83,6 +83,22 @@ export interface AttemptRailRow { const NON_TERMINAL_STATUSES = new Set(["PENDING", "IN_PROGRESS"]); +/** + * Stakwork project id from graph properties — same precedence as + * proposed-fixes/route.ts: `unique_source_id` (written by jarvis-backend at + * Stakwork dispatch) wins over legacy `project_id`. + */ +function projectIdFromProps(props: Record | undefined): number | null { + if (!props) return null; + for (const key of ["unique_source_id", "project_id"]) { + const raw = props[key]; + if (raw == null) continue; + const n = Number(raw); + if (Number.isFinite(n) && n > 0) return n; + } + return null; +} + /** Convert a Jarvis epoch-seconds string to ISO; null when unparseable. */ function graphEpochToIso(raw: string | undefined): string | null { if (!raw) return null; @@ -481,6 +497,28 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist for (const o of t.outputs ?? []) triggerByOutputRef.set(o.ref_id, t); } + // Graph-side Stakwork project id per attempt. jarvis-backend stamps + // `unique_source_id` on nodes at dispatch (and it appears on edges in + // some writers), so a concept attempt that never created a StakworkRun + // row still knows which Stakwork project produced it. Precedence: + // output node → owning trigger node → any edge touching either. + const rawNodeByRef = new Map(fixChain.nodes.map((n) => [n.ref_id, n])); + const edgeProjectIdByRef = new Map(); + for (const e of fixChain.edges) { + const pid = projectIdFromProps(e.properties); + if (pid == null) continue; + if (!edgeProjectIdByRef.has(e.target)) edgeProjectIdByRef.set(e.target, pid); + if (!edgeProjectIdByRef.has(e.source)) edgeProjectIdByRef.set(e.source, pid); + } + const graphProjectIdFor = (outputRefId: string, triggerRefId: string | null): number | null => + projectIdFromProps(rawNodeByRef.get(outputRefId)?.properties) ?? + (triggerRefId ? projectIdFromProps(rawNodeByRef.get(triggerRefId)?.properties) : null) ?? + edgeProjectIdByRef.get(outputRefId) ?? + (triggerRefId ? edgeProjectIdByRef.get(triggerRefId) : null) ?? + null; + + const allRunRows = [...runRows, ...evalRunRows, ...recursionRunRows]; + const triggerJoinableRuns = [...runRows, ...evalRunRows]; const claimedRunIds = new Set(); @@ -523,15 +561,32 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist const chartedRows: AttemptRailRow[] = finalAttempts.map((attempt, index) => { const trigger = triggerByOutputRef.get(attempt.ref_id) ?? null; if (trigger) chartedTriggerRefs.add(trigger.ref_id); - const statusRun = trigger ? pickStatusRun(runsForTrigger(trigger.ref_id)) : null; + const graphProjectId = graphProjectIdFor(attempt.ref_id, trigger?.ref_id ?? null); + + // Join key 1: result.evalTriggerRef (hive-dispatched runs). + // Join key 2: the graph-stamped Stakwork project id — this is how a + // concept attempt (no evalTriggerRef anywhere) finds its run row. + let statusRun = trigger ? pickStatusRun(runsForTrigger(trigger.ref_id)) : null; + if (!statusRun && graphProjectId != null) { + const byProject = allRunRows.filter((r) => r.projectId === graphProjectId); + for (const c of byProject) claimedRunIds.add(c.id); + statusRun = pickStatusRun(byProject); + } + const passed = attempt.actualPassed ?? attempt.n_passed; const total = attempt.n_total; - return rowFromRun(attempt.ref_id, statusRun, { + const row = rowFromRun(attempt.ref_id, statusRun, { label: attempt.label ?? null, attemptIndex: index, score: passed != null && total != null ? { passed, total } : null, graphTime: graphEpochToIso(attempt.date_added_to_graph), }); + // No run row anywhere, but the graph knows the Stakwork project — + // keep the link (super-admin) even for row-less concept attempts. + if (row.projectId == null && graphProjectId != null) { + row.projectId = graphProjectId; + } + return row; }); // Identity triggers with no charted output yet — a dispatched run whose diff --git a/src/lib/harvey-lab/fix-chain-walker.ts b/src/lib/harvey-lab/fix-chain-walker.ts index a9bb562ab1..71df811dad 100644 --- a/src/lib/harvey-lab/fix-chain-walker.ts +++ b/src/lib/harvey-lab/fix-chain-walker.ts @@ -138,6 +138,7 @@ interface JarvisExpandResponse { target: string; ref_id?: string; edge_type: string; + properties?: Record; }>; } @@ -229,6 +230,7 @@ async function fetchNodeForEdgeTypes( source: e.source, target: e.target, edge_type: e.edge_type, + ...(e.properties ? { properties: e.properties } : {}), }); } } diff --git a/src/lib/harvey-lab/hill-climb-series.ts b/src/lib/harvey-lab/hill-climb-series.ts index e998c63161..6ff7b65185 100644 --- a/src/lib/harvey-lab/hill-climb-series.ts +++ b/src/lib/harvey-lab/hill-climb-series.ts @@ -46,6 +46,8 @@ export interface SubgraphEdge { source: string; target: string; edge_type: string; + /** Edge-level properties (e.g. unique_source_id written at Stakwork dispatch) */ + properties?: Record; } export interface SubgraphNode { From b3778d0e8c7eb102c842c821a0eb94911c4e5d0d Mon Sep 17 00:00:00 2001 From: Tom Smith <142233216+tomsmith8@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:31:45 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Demote=20unique=5Fsource=5Fid=20to=20lineag?= =?UTF-8?q?e=20link=20=E2=80=94=20never=20a=20status/report=20join?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Field data shows the stamp can be the ROOT PARENT's Stakwork project (the orchestrating dispatch), not the re-runner that produced the specific attempt. Joining status/report on it would let a rerun row wear the parent run's outcome — including its report link, which would be factually wrong on that row. The graph-stamped project id now powers ONLY the super-admin Stakwork link (lineage: the project that orchestrated the attempt). Status, run type, and report state join exclusively on result.evalTriggerRef, the one key that is per-attempt by construction. --- .../unit/hooks/useEvalRunHistory.test.ts | 16 +++++----- src/hooks/useEvalRunHistory.ts | 32 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts index 8eae28f226..b7e04a1650 100644 --- a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts +++ b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts @@ -802,7 +802,10 @@ describe("useEvalRunHistory — unique_source_id join", () => { return renderHook(() => useEvalRunHistory({ refId: EVAL_SET_REF, slug: TASK_SLUG })); } - it("joins a concept attempt to its run row by graph-stamped project id", async () => { + it("does NOT wear a run's status via the graph-stamped project id — lineage, not identity", async () => { + // The stamp can be the ROOT PARENT's project, not this attempt's + // re-runner: joining status/report on it would let a rerun row wear the + // parent run's outcome. Only the Stakwork link may use it. const graph = conceptGraph("152583201"); const { result } = renderUsid({ "fix-chain": makeFixChainResponse(graph.nodes, graph.edges), @@ -824,14 +827,11 @@ describe("useEvalRunHistory — unique_source_id join", () => { await waitFor(() => expect(result.current.isLoading).toBe(false), { timeout: 5000 }); const row = result.current.attemptRows.find((r) => r.key === "output-c1")!; - // No evalTriggerRef anywhere — joined purely on projectId - expect(row.status).toBe("COMPLETED"); - expect(row.runType).toBe("recursion"); - expect(row.runId).toBe("rec-run-1"); - expect(row.hasReport).toBe(true); + expect(row.status).toBeNull(); + expect(row.runId).toBeNull(); + expect(row.hasReport).toBe(false); + // …but the lineage link survives expect(row.projectId).toBe(152583201); - // Claimed by the join — must not duplicate as a run-only row - expect(result.current.attemptRows.filter((r) => r.runId === "rec-run-1")).toHaveLength(1); }); it("keeps the Stakwork link from the graph when no run row exists at all", async () => { diff --git a/src/hooks/useEvalRunHistory.ts b/src/hooks/useEvalRunHistory.ts index 966c45c714..b97dc3f20f 100644 --- a/src/hooks/useEvalRunHistory.ts +++ b/src/hooks/useEvalRunHistory.ts @@ -497,11 +497,14 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist for (const o of t.outputs ?? []) triggerByOutputRef.set(o.ref_id, t); } - // Graph-side Stakwork project id per attempt. jarvis-backend stamps - // `unique_source_id` on nodes at dispatch (and it appears on edges in - // some writers), so a concept attempt that never created a StakworkRun - // row still knows which Stakwork project produced it. Precedence: - // output node → owning trigger node → any edge touching either. + // Graph-side Stakwork project id per attempt — LINEAGE, not identity. + // jarvis-backend stamps `unique_source_id` on nodes at dispatch (and it + // appears on edges in some writers), but field data shows the stamp can + // be the ROOT PARENT's project (the orchestrating dispatch), not the + // re-runner that produced this specific attempt. It is therefore used + // ONLY to power the Stakwork link — never as a status/report join, + // which would let a rerun row wear the parent run's outcome. + // Precedence: output node → owning trigger node → any edge touching either. const rawNodeByRef = new Map(fixChain.nodes.map((n) => [n.ref_id, n])); const edgeProjectIdByRef = new Map(); for (const e of fixChain.edges) { @@ -517,7 +520,6 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist (triggerRefId ? edgeProjectIdByRef.get(triggerRefId) : null) ?? null; - const allRunRows = [...runRows, ...evalRunRows, ...recursionRunRows]; const triggerJoinableRuns = [...runRows, ...evalRunRows]; const claimedRunIds = new Set(); @@ -563,15 +565,10 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist if (trigger) chartedTriggerRefs.add(trigger.ref_id); const graphProjectId = graphProjectIdFor(attempt.ref_id, trigger?.ref_id ?? null); - // Join key 1: result.evalTriggerRef (hive-dispatched runs). - // Join key 2: the graph-stamped Stakwork project id — this is how a - // concept attempt (no evalTriggerRef anywhere) finds its run row. - let statusRun = trigger ? pickStatusRun(runsForTrigger(trigger.ref_id)) : null; - if (!statusRun && graphProjectId != null) { - const byProject = allRunRows.filter((r) => r.projectId === graphProjectId); - for (const c of byProject) claimedRunIds.add(c.id); - statusRun = pickStatusRun(byProject); - } + // Status joins on result.evalTriggerRef only — the one key that is + // per-attempt by construction. The graph-stamped project id is + // deliberately NOT a status join key (see lineage note above). + const statusRun = trigger ? pickStatusRun(runsForTrigger(trigger.ref_id)) : null; const passed = attempt.actualPassed ?? attempt.n_passed; const total = attempt.n_total; @@ -581,8 +578,9 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist score: passed != null && total != null ? { passed, total } : null, graphTime: graphEpochToIso(attempt.date_added_to_graph), }); - // No run row anywhere, but the graph knows the Stakwork project — - // keep the link (super-admin) even for row-less concept attempts. + // No per-attempt run join — but the graph knows the Stakwork + // lineage, so the super-admin link still opens the project that + // orchestrated this attempt (parent, when the stamp is lineage-level). if (row.projectId == null && graphProjectId != null) { row.projectId = graphProjectId; } From d4cb724f6225d2b8bfe4fc5ddad343542419b9de Mon Sep 17 00:00:00 2001 From: Tom Smith <142233216+tomsmith8@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:32:44 +0100 Subject: [PATCH 3/3] Correct unique_source_id rationale: stamp is the child re-runner's project id --- .../unit/hooks/useEvalRunHistory.test.ts | 8 ++--- src/hooks/useEvalRunHistory.ts | 29 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts index b7e04a1650..07dc5d35b0 100644 --- a/src/__tests__/unit/hooks/useEvalRunHistory.test.ts +++ b/src/__tests__/unit/hooks/useEvalRunHistory.test.ts @@ -802,10 +802,10 @@ describe("useEvalRunHistory — unique_source_id join", () => { return renderHook(() => useEvalRunHistory({ refId: EVAL_SET_REF, slug: TASK_SLUG })); } - it("does NOT wear a run's status via the graph-stamped project id — lineage, not identity", async () => { - // The stamp can be the ROOT PARENT's project, not this attempt's - // re-runner: joining status/report on it would let a rerun row wear the - // parent run's outcome. Only the Stakwork link may use it. + it("does NOT wear a run's status via the graph-stamped project id", async () => { + // The stamp is the CHILD re-runner's project id; hive run rows carry the + // PARENT orchestrator's. A projectId match is coincidence, never identity — + // so only the Stakwork link may use the stamp. const graph = conceptGraph("152583201"); const { result } = renderUsid({ "fix-chain": makeFixChainResponse(graph.nodes, graph.edges), diff --git a/src/hooks/useEvalRunHistory.ts b/src/hooks/useEvalRunHistory.ts index b97dc3f20f..71884c6011 100644 --- a/src/hooks/useEvalRunHistory.ts +++ b/src/hooks/useEvalRunHistory.ts @@ -497,13 +497,15 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist for (const o of t.outputs ?? []) triggerByOutputRef.set(o.ref_id, t); } - // Graph-side Stakwork project id per attempt — LINEAGE, not identity. - // jarvis-backend stamps `unique_source_id` on nodes at dispatch (and it - // appears on edges in some writers), but field data shows the stamp can - // be the ROOT PARENT's project (the orchestrating dispatch), not the - // re-runner that produced this specific attempt. It is therefore used - // ONLY to power the Stakwork link — never as a status/report join, - // which would let a rerun row wear the parent run's outcome. + // Graph-side Stakwork project id per attempt — the CHILD re-runner. + // Topology per recursion iteration: the cron's StakworkRun row stores + // the PARENT orchestrator's project id; that parent spawns a child + // workflow (new project id) which actually re-runs the eval and stamps + // `unique_source_id` = its own (child) id onto the graph it writes. + // So the stamp is true per-attempt identity — perfect for the Stakwork + // link — but it can never join hive's run rows, which only ever carry + // the parent's id. Status/report therefore join exclusively on + // result.evalTriggerRef. // Precedence: output node → owning trigger node → any edge touching either. const rawNodeByRef = new Map(fixChain.nodes.map((n) => [n.ref_id, n])); const edgeProjectIdByRef = new Map(); @@ -565,9 +567,10 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist if (trigger) chartedTriggerRefs.add(trigger.ref_id); const graphProjectId = graphProjectIdFor(attempt.ref_id, trigger?.ref_id ?? null); - // Status joins on result.evalTriggerRef only — the one key that is - // per-attempt by construction. The graph-stamped project id is - // deliberately NOT a status join key (see lineage note above). + // Status joins on result.evalTriggerRef only — hive rows carry the + // parent orchestrator's project id, the graph stamp carries the + // child re-runner's; a projectId join can never legitimately match + // (see topology note above). const statusRun = trigger ? pickStatusRun(runsForTrigger(trigger.ref_id)) : null; const passed = attempt.actualPassed ?? attempt.n_passed; @@ -578,9 +581,9 @@ export function useEvalRunHistory(input: UseEvalRunHistoryInput): UseEvalRunHist score: passed != null && total != null ? { passed, total } : null, graphTime: graphEpochToIso(attempt.date_added_to_graph), }); - // No per-attempt run join — but the graph knows the Stakwork - // lineage, so the super-admin link still opens the project that - // orchestrated this attempt (parent, when the stamp is lineage-level). + // No hive run row exists for the child re-runner — but the graph + // stamp IS its project id, so the super-admin link opens the exact + // Stakwork execution that produced this attempt. if (row.projectId == null && graphProjectId != null) { row.projectId = graphProjectId; }