Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
129 changes: 129 additions & 0 deletions .github/scripts/semantic_review.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const {readFileSync} = require('node:fs');
const {join} = require('node:path');

const NAME = 'Semantic conflict with target branch';
const notice = 'Advisory, non-required AI analysis of the recorded revisions. ' +
'CodeRabbit can miss problems or report false positives. Review the evidence.';
const supported = ref => ref === 'main' || /^release\/[^\s]+$/.test(ref);
const eligible = pr => pr.state === 'open' && !pr.draft && supported(pr.base.ref) &&
(pr.auto_merge || pr.labels.some(label => label.name === 'ci: full pre-merge approved'));
const isCommandUser = user => user?.login === 'trtllm-agent' &&
user.id === 296075020 && user.type === 'User';
const isReviewer = user => user?.login === 'coderabbitai[bot]' &&
user.id === 136622811 && user.type === 'Bot';
const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
const sha = /^[a-f0-9]{40}$/;
const identity = (number, request) => `semantic-review:${number}:${request.id}`;

function requests(comments) {
return comments.filter(comment => isCommandUser(comment.user)).flatMap(comment => {
const marker = comment.body?.match(/<!-- semantic-review-request:(.+) -->/);
if (!marker) return [];
let request;
try { request = JSON.parse(marker[1]); } catch (error) {
if (error instanceof SyntaxError) return [];
throw error;
}
if (!request || !uuid.test(request.id) || !supported(request.branch) ||
![request.head, request.target, request.mergeBase].every(value => sha.test(value)) ||
!Number.isSafeInteger(request.checkId) || request.checkId <= 0) return [];
return [{...request, commentId: comment.id, created_at: comment.created_at}];
}).sort((a, b) => b.commentId - a.commentId);
}

function command(request) {
const prompt = readFileSync(join(__dirname, '../semantic-review-prompt.md'), 'utf8')
.replace(/<!--[^]*?-->/g, '').trim();
return `@coderabbitai\n\n${prompt}\n\n` +
'Analyze only these fixed revisions in NVIDIA/TensorRT-LLM, not the hosting PR:\n' +
`request_id=${request.id}\nhead=${request.head}\ntarget=${request.target}\n` +
`merge_base=${request.mergeBase}\nbranch=${request.branch}`;
}

function awaiting(request) {
return {title: 'Awaiting CodeRabbit analysis (no verdict)',
summary: `Request ${request.id}. Head ${request.head}, target ${request.target}, ` +
`merge base ${request.mergeBase}.\n\n${notice}`};
}

function parseResult(comment, request, repo) {
if (!isReviewer(comment.user)) return;
const parts = (comment.body || '').split(/^(?:#{1,6}[ \t]+)?SEMANTIC_REVIEW[ \t]*\r?$/m);
if (parts.length !== 2) return;
const records = [...parts[1].matchAll(/^SEMANTIC_RESULT request_id=([^\s]+) head=([^\s]+) target=([^\s]+) merge_base=([^\s]+) verdict=(PASS|FAIL|INCONCLUSIVE)[ \t]*\r?$/gmi)];
if (records.length !== 1) return;
const [, id, head, target, mergeBase, rawVerdict] = records[0];
if (id !== request.id || head !== request.head || target !== request.target ||
mergeBase !== request.mergeBase) return;
let verdict = rawVerdict.toUpperCase();
const citations = [...parts[1].matchAll(/https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/blob\/([a-f0-9]{40})\/[^\s<>)]+#L[1-9]\d*/g)]
.filter(match => match[1].toLowerCase() === `${repo.owner}/${repo.repo}`.toLowerCase())
.map(match => match[2]);
const missingEvidence = verdict !== 'INCONCLUSIVE' &&
![head, target].every(revision => citations.includes(revision));
if (missingEvidence) verdict = 'INCONCLUSIVE';
return {verdict, missingEvidence, comment};
}

async function publish({github, context, core}) {
if (!context.payload.issue?.pull_request || !isReviewer(context.payload.comment?.user)) return;
const repo = context.repo;
const number = context.payload.issue.number;
const comments = await github.paginate(github.rest.issues.listComments,
{...repo, issue_number: number, per_page: 100});
const request = requests(comments)[0];
if (!request) return;
// The workflow serializes this read/update with switches to a newer request.
const {data: check} = await github.rest.checks.get({...repo, check_run_id: request.checkId});
if (check.app?.slug !== 'github-actions' || check.name !== NAME ||
check.head_sha !== request.head || check.external_id !== identity(number, request)) return;
const publishedId = Number(check.details_url?.match(/#issuecomment-(\d+)$/)?.[1]);
const sourceId = publishedId > request.commentId ? publishedId : 0;
const replies = comments.filter(comment => isReviewer(comment.user) &&
comment.id > request.commentId && (!sourceId || comment.id >= sourceId) &&
Date.parse(comment.created_at) >= Date.parse(request.created_at))
.sort((a, b) => b.id - a.id);
let result;
let invalidSource;
for (const comment of replies) {
const parsed = parseResult(comment, request, repo);
if (parsed) { result = parsed; break; }
// An invalid current-request reply must not resurrect an earlier PASS.
if (comment.id === sourceId || comment.body?.includes(request.id)) {
invalidSource = comment;
break;
}
}
if (!result && !sourceId && !invalidSource) return;
const verdict = result?.verdict || 'INCONCLUSIVE';
const title = {PASS: 'No semantic conflict found (best effort)',
FAIL: 'Possible semantic conflict', INCONCLUSIVE: 'Semantic analysis inconclusive'}[verdict];
const url = result?.comment.html_url || invalidSource?.html_url || check.details_url;
const summary = `Request ${request.id}. Head ${request.head}, target ${request.target}, ` +
`merge base ${request.mergeBase}.\n\n` +
(result ? `Result received ${result.comment.created_at}. [CodeRabbit analysis](${url}).\n\n` :
'The published reply no longer provides a valid result for this request.\n\n') +
(result?.missingEvidence ? 'Missing fixed-revision source citations; no verified verdict.\n\n' : '') + notice;
await github.rest.checks.update({...repo, check_run_id: check.id, status: 'completed',
conclusion: {PASS: 'success', FAIL: 'failure', INCONCLUSIVE: 'neutral'}[verdict],
...(url ? {details_url: url} : {}), output: {title, summary}});
await core.summary.addRaw(`${title}\n\n${summary}\n`).write();
}

module.exports = {NAME, notice, supported, eligible, isCommandUser, isReviewer,
identity, requests, command, awaiting, parseResult, publish};
239 changes: 239 additions & 0 deletions .github/scripts/semantic_review.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const test = require('node:test');
const assert = require('node:assert/strict');
const {readFileSync} = require('node:fs');
const {join} = require('node:path');
const {NAME, identity, requests, parseResult, command, publish} = require('./semantic_review');
const cases = require('./semantic_review_cases');

const repo = {owner: 'NVIDIA', repo: 'TensorRT-LLM'};
const service = {login: 'trtllm-agent', id: 296075020, type: 'User'};
const bot = {login: 'coderabbitai[bot]', id: 136622811, type: 'Bot'};
const id = n => `00000000-0000-4000-8000-${String(n).padStart(12, '0')}`;
const request = (n = 1, extra = {}) => ({id: id(n), head: 'a'.repeat(40),
target: 'b'.repeat(40), mergeBase: 'c'.repeat(40), branch: 'main', checkId: 100, ...extra});
const comment = (n, body, user = bot) => ({id: n, body, user,
created_at: new Date(1700000000000 + n * 1000).toISOString(),
html_url: `https://github.com/NVIDIA/TensorRT-LLM/pull/1#issuecomment-${n}`});
const record = (n, r) => comment(n, `<!-- semantic-review-request:${JSON.stringify(r)} -->`, service);
function reply(n, r, verdict = 'PASS', evidence = true) {
const body = `Analysis before the result.\nSEMANTIC_REVIEW\n` +
(evidence ? [r.head, r.target].map(sha =>
`https://github.com/NVIDIA/TensorRT-LLM/blob/${sha}/path.py#L12`).join('\n') + '\n' : '') +
`SEMANTIC_RESULT request_id=${r.id} head=${r.head} target=${r.target} merge_base=${r.mergeBase} verdict=${verdict}\n`;
return comment(n, body);
}
function harness(r = request()) {
const state = {comments: [record(10, r)], updates: [], summaries: [],
check: {id: r.checkId, name: NAME, head_sha: r.head, external_id: identity(1, r),
app: {slug: 'github-actions'}, status: 'completed', conclusion: 'neutral'}};
const github = {
paginate: async () => structuredClone(state.comments),
rest: {issues: {listComments() {}}, checks: {
get: async () => ({data: structuredClone(state.check)}),
update: async update => { state.updates.push(update); Object.assign(state.check, update); },
}},
};
const core = {summary: {addRaw(text) {state.summaries.push(text); return this;}, async write() {}},
setFailed() {throw new Error('AI verdict must not fail the orchestration job');}};
const deliver = async event => publish({github, core, context: {
repo, eventName: 'issue_comment', payload: {issue: {number: 1, pull_request: {}}, comment: event},
}});
return {state, deliver};
}

test('request records require the pinned account and valid immutable metadata', () => {
const r = request();
assert.equal(requests([record(10, r)]).length, 1);
for (const user of [{...service, id: 1}, {...service, type: 'Bot'}, bot]) {
assert.deepEqual(requests([{...record(10, r), user}]), []);
}
for (const extra of [{id: 'unknown'}, {head: 'main'}, {branch: 'feature/x'}, {checkId: 0}]) {
assert.deepEqual(requests([record(10, {...r, ...extra})]), []);
}
assert.deepEqual(requests([comment(10, '<!-- semantic-review-request:{invalid} -->', service)]), []);
});

test('protocol requires matching request ID, all revisions and the real reviewer', () => {
const r = request();
assert.equal(parseResult(reply(20, r), r, repo).verdict, 'PASS');
for (const extra of [{id: id(2)}, {head: 'd'.repeat(40)}, {target: 'd'.repeat(40)},
{mergeBase: 'd'.repeat(40)}]) {
assert.equal(parseResult(reply(20, {...r, ...extra}), r, repo), undefined);
}
for (const user of [{...bot, id: 1}, {...bot, type: 'User'}, service]) {
assert.equal(parseResult({...reply(20, r), user}, r, repo), undefined);
}
});

test('missing or wrong-repository evidence is inconclusive, never PASS', () => {
const r = request();
assert.equal(parseResult(reply(20, r, 'PASS', false), r, repo).verdict, 'INCONCLUSIVE');
assert.equal(parseResult(reply(20, r, 'FAIL', false), r, repo).verdict, 'INCONCLUSIVE');
const wrong = reply(20, r);
wrong.body = wrong.body.replaceAll('NVIDIA/TensorRT-LLM/blob', 'elsewhere/project/blob');
assert.equal(parseResult(wrong, r, repo).verdict, 'INCONCLUSIVE');
assert.equal(parseResult(reply(20, r, 'INCONCLUSIVE', false), r, repo).verdict, 'INCONCLUSIVE');
});

test('Markdown result headings publish verdicts without accepting quoted or duplicate sections', async () => {
const r = request();
for (const heading of ['# SEMANTIC_REVIEW', '## SEMANTIC_REVIEW', '###### SEMANTIC_REVIEW']) {
const message = reply(20, r, 'FAIL');
message.body = message.body.replace('SEMANTIC_REVIEW', heading);
const {state, deliver} = harness(r);
state.comments.push(message);
await deliver(message);
assert.equal(state.check.conclusion, 'failure');
assert.equal(parseResult({...message, body: message.body.replace(heading, `> ${heading}`)}, r, repo), undefined);
assert.equal(parseResult({...message, body: `${message.body}\nSEMANTIC_REVIEW\n`}, r, repo), undefined);
}
});

test('conflicting records and missing protocol markers are rejected', () => {
const r = request();
const message = reply(20, r);
message.body += reply(21, r, 'FAIL').body.split('SEMANTIC_REVIEW\n')[1];
assert.equal(parseResult(message, r, repo), undefined);
assert.equal(parseResult(comment(20, 'PASS'), r, repo), undefined);
});

test('publishes an exact-version result without requiring the live main SHA', async () => {
const r = request();
const {state, deliver} = harness(r);
state.comments.push(reply(20, r, 'FAIL'));
await deliver(state.comments.at(-1));
assert.equal(state.check.conclusion, 'failure');
assert.match(state.check.output.summary, new RegExp(r.target));
assert.match(state.check.details_url, /issuecomment-20$/);
});

test('late old PASS cannot overwrite newer FAIL when only target changed', async () => {
const old = request();
const current = request(2, {target: 'd'.repeat(40)});
const {state, deliver} = harness(current);
state.comments = [record(10, old), record(30, current), reply(40, current, 'FAIL'), reply(50, old)];
await deliver(state.comments[2]);
await deliver(state.comments[3]);
assert.equal(state.check.conclusion, 'failure');
assert.match(state.check.output.summary, new RegExp(current.id));
assert.match(state.check.details_url, /issuecomment-40$/);
});

test('old reply cannot temporarily approve an awaiting newer request', async () => {
const old = request();
const current = request(2);
const {state, deliver} = harness(current);
state.comments = [record(10, old), record(30, current), reply(40, old)];
await deliver(state.comments.at(-1));
assert.equal(state.check.conclusion, 'neutral');
assert.equal(state.updates.length, 0);
});

test('same-version explicit retry requires its own request ID', async () => {
const old = request();
const current = request(2);
const {state, deliver} = harness(current);
state.comments = [record(10, old), record(30, current), reply(40, old), reply(50, current, 'FAIL')];
await deliver(state.comments.at(-1));
assert.equal(state.check.conclusion, 'failure');
assert.match(state.check.details_url, /issuecomment-50$/);
});

test('new head and current check identity are enforced', async () => {
for (const extra of [{head_sha: 'd'.repeat(40)}, {external_id: identity(1, request(2))},
{app: {slug: 'untrusted'}}, {name: 'Different check'}]) {
const r = request();
const {state, deliver} = harness(r);
Object.assign(state.check, extra);
state.comments.push(reply(20, r));
await deliver(state.comments.at(-1));
assert.equal(state.updates.length, 0);
}
});

test('editing a published PASS into invalid text revokes the green check', async () => {
for (const body of ['Cannot verify the revisions.', 'SEMANTIC_REVIEW\nINCONCLUSIVE']) {
const r = request();
const {state, deliver} = harness(r);
const result = reply(20, r);
state.comments.push(result);
await deliver(result);
assert.equal(state.check.conclusion, 'success');
result.body = body;
await deliver(result);
assert.equal(state.check.conclusion, 'neutral');
}
});

test('deleting the published result does not fall back to an earlier PASS', async () => {
const r = request();
const {state, deliver} = harness(r);
const removed = reply(30, r, 'FAIL');
state.comments.push(reply(20, r), removed);
await deliver(removed);
state.comments = state.comments.filter(c => c.id !== removed.id);
await deliver(removed);
assert.equal(state.check.conclusion, 'neutral');
});

test('a newer malformed reply for the current request invalidates an older PASS', async () => {
const r = request();
const {state, deliver} = harness(r);
state.comments.push(reply(20, r));
await deliver(state.comments.at(-1));
const invalid = reply(30, r);
invalid.body += reply(31, r, 'FAIL').body.split('SEMANTIC_REVIEW\n')[1];
state.comments.push(invalid);
await deliver(invalid);
assert.equal(state.check.conclusion, 'neutral');
assert.match(state.check.details_url, /issuecomment-30$/);
});

test('new valid reply can supersede an invalidated source', async () => {
const r = request();
const {state, deliver} = harness(r);
const first = reply(20, r);
state.comments.push(first);
await deliver(first);
first.body = 'Cannot verify.';
state.comments.push(reply(30, r, 'FAIL'));
await deliver(first);
assert.equal(state.check.conclusion, 'failure');
});

test('a bot-looking user cannot publish or revoke results', async () => {
const r = request();
const {state, deliver} = harness(r);
const forged = {...reply(20, r), user: {...bot, id: 123}};
state.comments.push(forged);
await deliver(forged);
assert.equal(state.updates.length, 0);
});

test('real divergent, integrated and repaired histories use the same result protocol', () => {
assert.equal(cases.length, 9);
for (const fixture of cases) {
const r = request(1, fixture);
const body = command(r);
for (const value of [r.id, r.head, r.target, r.mergeBase]) assert.ok(body.includes(value));
assert.ok(body.startsWith('@coderabbitai\n'));
assert.doesNotMatch(body, /llm_build_stats|routed_output_is_global|get_steady_clock_now_in_seconds/);
assert.equal(parseResult(reply(20, r, 'FAIL'), r, repo).verdict, 'FAIL');
}
});

test('privileged jobs run trusted code and serialize request switches with publication', () => {
const workflow = readFileSync(join(__dirname, '../workflows/semantic-review.yml'), 'utf8');
assert.doesNotMatch(workflow, /pull_request_target:|pull_request:/);
assert.match(workflow, /cron: '23 \*\/2 \* \* \*'/);
assert.match(workflow, /group: semantic-review-pr-\$\{\{ matrix.number \}\}/);
assert.match(workflow, /group: semantic-review-pr-\$\{\{ github.event.issue.number \}\}/);
assert.equal(workflow.match(/ cancel-in-progress: false\n queue: max/g).length, 2);
assert.equal(workflow.match(/ref: \$\{\{ github.event.repository.default_branch \}\}/g).length, 3);
assert.match(workflow, /types: \[created, edited, deleted\]/);
assert.equal(workflow.match(/secrets\./g).length, 1);
assert.doesNotMatch(workflow.split(' publish:')[1], /SEMANTIC_COMMAND_TOKEN|issues: write/);
});
Loading
Loading