From 1d9f2d753a3dca63e433ef257c8191e51791550a Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 28 Jul 2026 15:15:39 +0000 Subject: [PATCH] fix(ci): require a line range for context evidence The tri-review's adversarial lane executed `context({name: 'AGENTS.md'})` and had the result accepted: the gate checked only that the resolved filePath was in the changed set, so a bare File node passed for a review of that file's contents. The trusted prescan already defines an indexable symbol as one with startLine and endLine, so require the same here. Pre-existing rather than introduced by this branch, but it is the same "what counts as proof" surface the rest of this PR tightens. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/gitnexus-review-agent.yml | 7 ++++++- .../test/unit/review-agent-workflow.test.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gitnexus-review-agent.yml b/.github/workflows/gitnexus-review-agent.yml index 039e772fb..080b45ada 100644 --- a/.github/workflows/gitnexus-review-agent.yml +++ b/.github/workflows/gitnexus-review-agent.yml @@ -1748,11 +1748,16 @@ jobs: throw new Error('context tool result is not strict JSON'); } validateBoundedJson(decoded, { nodes: 0 }); + // A line range is what the trusted prescan calls an indexable + // symbol, so a bare File node — `context({name: 'AGENTS.md'})` — + // must not pass for a review of that file's contents. if ( !isRecord(decoded) || Object.hasOwn(decoded, 'error') || decoded.status !== 'found' || - !isRecord(decoded.symbol) + !isRecord(decoded.symbol) || + !Number.isFinite(decoded.symbol.startLine) || + !Number.isFinite(decoded.symbol.endLine) ) { rejected.unresolved += 1; return false; diff --git a/gitnexus/test/unit/review-agent-workflow.test.ts b/gitnexus/test/unit/review-agent-workflow.test.ts index 59cbb1c89..6ed37b5ad 100644 --- a/gitnexus/test/unit/review-agent-workflow.test.ts +++ b/gitnexus/test/unit/review-agent-workflow.test.ts @@ -1776,6 +1776,27 @@ describe('gitnexus review-agent workflow security contract', () => { expect(structural.artifact.failure_code).toBe('invalid_execution_transcript'); }); + it('refuses a bare File node as evidence, matching the prescan definition of indexable', () => { + const fileNode = runArtifactScenario({ + rawTranscript: JSON.stringify( + reviewTranscript({ + toolInput: { name: 'status.ts' }, + toolResultContent: JSON.stringify({ + status: 'found', + symbol: { + uid: `File:${CHANGED_PATH}`, + name: 'status.ts', + kind: 'File', + filePath: CHANGED_PATH, + }, + }), + }), + ), + }); + expect(fileNode.artifact.failure_code).toBe('missing_graph_evidence'); + expect(fileNode.stderr).toContain('results that resolved nothing: 1'); + }); + it('scopes a deletion-only PR to the merge-base set instead of an empty head set', () => { const deletedPath = 'gitnexus/src/cli/deleted-command.ts'; const headScopedCall = runArtifactScenario({