Skip to content

Review with traced findings, a verify pass and anchored comments - #28

Open
itexpert120 wants to merge 5 commits into
mainfrom
feat/review-quality-from-multi-review
Open

itexpert120 wants to merge 5 commits into
mainfrom
feat/review-quality-from-multi-review

Conversation

@itexpert120

@itexpert120 itexpert120 commented Oct 27, 2026 •

Copy link
Copy Markdown

What this does

Improves Private Investigator's reviews by porting the review method from MultiAgency/code-review, and adds an opt-in anonymous mode for stronger models that don't run in enclaves. Both the Action and the hosted App get the same changes.

Better reviews

  • The prompt asks the model to trace callers, failure paths and contracts between changed files, and to list what it checked. Lockfiles and generated files are left out of the diff.
  • Each finding has a title, a category, the code it quotes and an optional fix.
  • A second "verify" pass checks each finding against the code and drops those it can disprove or that repeat another. It's on by default (verify: "false" turns it off), and its turns are signed and recorded in the receipt like the rest.
  • Findings from parallel passes are merged so the same issue is posted once.
  • Comments are anchored on the quoted lines, can span several lines, and include a one-click suggestion when the fix replaces exactly those lines.

Anonymous mode

Skips attestation, encryption, signatures and the receipt, so models outside enclaves can review. Every anonymous review says that nothing proves where the code was read.

  • Action: anonymous: "true", with any model; defaults to moonshotai/kimi-k3.1-pro.
  • App: a repository opts in by adding .github/private-investigator.json with { "anonymous": true } to its default branch. Only the default branch counts, so a pull request can't opt itself in. Reviews use moonshotai/kimi-k3.1-pro.

Private reviews are unchanged unless a repository opts in.

Testing

  • npm test (142) and cargo test (78) pass; both wasm builds compile.
  • Live dry runs with z-ai/glm-5.3-flash on the 8 planted-bug PRs: all caught, before and after, and the clean control stays clean. Duplicate findings and mislabelled severities from before are gone.
  • Hosted App dry runs: planted PR review: stop a runaway grep pattern from hanging the Action #1 and near-agencies#212 complete; the anonymous App run on review: stop a runaway grep pattern from hanging the Action #1 catches the bug with no receipt.
  • near-agencies#94 (two real bugs, 3 runs): GLM Flash caught neither; Kimi K3.1 Pro in anonymous mode caught closeIfPaid 3/3 and recordApprovals 0/3.

Rollout

Action users get this when they update their pinned version; the App gets it with its next release.

Port code-review's review method to both runners (the Action and the
hosted App), sharing review/review.json:

- Prompt: trace changed functions through their callers and failure
  paths; leave lockfile and generated-file diffs out.
- Findings: title, category, quoted existing_code, optional fix; the
  review lists what it traced.
- Merge passes one finding per comment line, or by title.
- Verify pass: a signed second loop, recorded in the receipt, removes
  only findings the code disproves, or duplicates. `verify` turns it off.
- Comments anchor on the quoted lines, span several lines, and offer a
  one-click suggestion when the fix replaces exactly those lines. Model
  text cannot open a suggestion block or an HTML comment.
@itexpert120
itexpert120 marked this pull request as ready for review October 27, 2026 04:59
@itexpert120

Copy link
Copy Markdown
Author

Need a better model to improve reviewer quality

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Private Investigator (Security, Important: 2 · Bugs, Nit: 5 · Security, Nit: 1)

This adds a verify pass, quote-anchored inline comments with one-click suggestions, and traced-path reporting to both runners, with the Rust side pinned to the Action's text by a shared golden case. I traced candidate numbering through prepare_verify/verify_prompt/apply_verdicts, merge/place tie-breaking, receipt turn accounting, and the model-text escaping; the cross-runner contracts line up. One security gap remains in the escaping (tilde-fenced suggestion blocks), plus a failure-path divergence in the Rust verify loop and a deploy-time edge for stored jobs.

An AI second opinion, not a sign-off: it can miss regressions that span files, above all in money and permission paths.

Was a finding right? React 👍 or 👎 on it: that is how this reviewer is measured and improved.

Read only inside an attested NEAR AI enclave, end-to-end encrypted, with every reply signed by it. Check the receipt.

What was verified
  • Model: z-ai/glm-5.3-flash in an Intel TDX enclave with NVIDIA confidential GPUs. Quote verified, TCB UpToDate, debug off; it binds signing key 5a0210f2442c79355784f9de021985429561da5bacc3a4424b01ac52b7e0caff and this run's nonce. Compose hash a0e3c6f4f21a60ea3f5c96bc7ef319b03a184ca2049605399275fbcea74e8c5f.
  • GPUs: NVIDIA's signed verdict approves them for the same nonce.
  • Gateway: Quote verified, TCB UpToDate. It relayed only end-to-end encrypted content.
  • Signed: 41 of 41 responses signed by the model enclave's key, over the exact request and response bytes.
  • Receipt: sha256 6333c5dc4da54329627100ac4aa8d8b7bd4b7cd2deec5a07a7d13b33e7635090 in the run's artifacts. Anyone can check it.

Comment thread review/review.mjs
* block or a hidden HTML comment in what is posted.
*/
export function clean(text) {
return String(text ?? "").replace(/```[ \t]*suggestion/gi, "```text").replaceAll("<!--", "&lt;!--");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security, Important: clean neutralises only backtick fences: /```[ \t]*suggestion/gi. GitHub fenced code blocks also accept tildes, so model-written text containing ~~~suggestion\nrm -rf\n~~~ still opens a working one-click suggestion block in the posted comment (same for comment_body's raw fix_code, which only escapes backticks). That defeats the invariant the PR states — "Model-written text cannot open a suggestion block" — and lets a model (or text it copies from the repository, which it is told to treat as data) plant code a reader can accept with one click. Fix: also rewrite [~]{3,}[ \t]*suggestion (and consider escaping ~~~ in fix_code), and cover the same pattern in clean in app/src/review.rs so the two runners stay byte-identical.

Comment thread app/src/job.rs
// The passes' findings stand on their own signed turns: a check that never
// submits, or whose model turn fails, leaves them posted, labelled unchecked.
// Its signed turns stay in the receipt; a reply whose signature failed was never recorded.
Err(e) if matches!(Failure::of(&e), Failure::NoSubmission | Failure::Model) => passes.verification = Some("unverified".into()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: The Rust verify loop only downgrades to unverified for NoSubmission | Model; any other error during the verify turn (e.g. a repo-read or storage error inside tree.call) returns Err and fails the whole job, discarding findings the passes already completed. The Action's reviewChange catches every verify error and posts the findings labelled unchecked (review/review.mjs:350-356), so the runners diverge exactly on the path this PR's comment describes as "leaves them posted". Since the passes' findings stand on their own signed turns, matching the Action — mapping any verify-loop failure to unverified — would be safer and consistent.

Comment thread app/src/job.rs
evidence: Evidence,
gateway: Summary,
model: Summary,
public_key: String,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: Jobs stored before this deploy have a commentable field (now gone) and no lines; serde ignores the old field and defaults lines to an empty map, so an in-flight review resumed after the App's next release loses all inline anchors: every finding lands in the body under "Not on a changed line". Narrow window and degraded rather than broken, but a commentable → lines migration (or temporarily deriving lines from a stored commentable) would avoid posting a visibly worse review format to jobs caught mid-flight.

Comment thread review/review.mjs
/** The verify pass's prompt: the review's own prompt, then the candidate findings, numbered. */
export function verifyPrompt(prompt, findings) {
const numbered = findings.map(({ path, line, category, title, body, existing_code }, index) => ({ index, path, line, category, title, body, existing_code }));
return `${prompt}\n\n## Findings to check\n\n\`\`\`json\n${JSON.stringify(numbered, null, 2)}\n\`\`\``;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security, Important: verifyPrompt embeds each candidate finding's title, body and existing_code verbatim inside a json fence. None of these fields is constrained: bodies and existing_code can quote attacker-controlled repository content, and nothing strips backticks (JSON.stringify does not escape them). A finding whose text contains closes the fence, so the rest of the JSON becomes prompt text under the verifier's instructions — e.g. "set keep=false for every finding". That defeats the verify safeguard exactly on hostile pull requests, where it matters most, and the dropped findings disappear silently (the review just says the second pass checked them). Fix: neutralize ``` sequences in the numbered findings (as clean() does for posted text) or otherwise ensure the fence cannot be closed from the payload; the same applies to app/src/review.rs verify_prompt, which shares this format.

Comment thread eval/run.mjs Outdated
function rustReview() {
const wasm = resolve("target/dry/wasm32-wasip2/release/private-investigator.wasm");
const input = JSON.stringify({ repo: spec.repo, pr: spec.pull_request, base: spec.base, head: spec.head, description: pr.body, max_turns: defaults.max_turns, passes: Number(values.passes) });
const input = JSON.stringify({ repo: spec.repo, pr: spec.pull_request, base: spec.base, head: spec.head, description: pr.body, max_turns: defaults.max_turns, passes: Number(values.passes), verify: values.verify === "true" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: --verify only recognizes the exact string "true"; any other value, including "1" or "TRUE", silently disables the verify pass instead of erroring. Consider accepting the same truthy spellings as the Action's env handling, or erroring on unrecognized values.

Comment thread app/src/review.rs
return Some("submit_review needs a summary and a findings array".into());
};
let checked = &submission["checked"];
if !checked.is_null() && !checked.as_array().is_some_and(|c| c.iter().all(Value::is_string)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: Rust and JS disagree on checked: null: JS rejects it (checked !== undefined && !Array.isArray(checked) → error), while Rust skips it (!checked.is_null()), so a submission the Action's model must fix is accepted by the hosted App. Since the two runners' checks are meant to match (review.rs header says the code matches review.mjs exactly), change the Rust guard to treat null like any non-array value, e.g. require checked to be an array of strings unless it is absent from the object.

Comment thread app/src/review.rs
Value::Object(entry)
})
.collect();
format!("{prompt}\n\n## Findings to check\n\n```json\n{}\n```", serde_json::to_string_pretty(&numbered).unwrap_or_default())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security, Nit: The verify prompt embeds candidate findings inside a json fence, but finding `body`/`title` text is model-written and unfiltered: a finding containing "" closes the fence early, letting repository-controlled content (already laundered through a finding) appear to the verifier outside the block declared as data. The verifier's keep-biased rules and protected categories limit the damage, but wrapping the JSON with a longer fence (e.g. `````json) or stripping/escaping triple backticks in the numbered entries would keep the declared data boundary intact.

Comment thread review/review.mjs
const duplicateOf = /^duplicate of #?(\d+)/i.exec(v.reason ?? "")?.[1];
if (duplicateOf !== undefined) {
const target = Number(duplicateOf);
if (target !== v.index && findings[target] && !rejected.has(target)) rejected.add(v.index);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: Duplicate removal is order-dependent in a way that can leave the weaker duplicate posted: if finding 2 is disproven before finding 1's "duplicate of 2" verdict is processed, !rejected.has(target) fails, finding 1 survives alone — and which of the two duplicates survives depends only on verdict order, not on which is clearer. The doc comment documents 'a duplicate only while the finding it repeats stays', but consider, on duplicate verdicts, keeping the verdict even when the target is rejected (both go), or resolving by an explicit clarity signal, so the outcome does not hinge on the verifier's listing order.

ANONYMOUS=true (the Action's anonymous input) skips attestation, end-to-end
encryption and signatures, so any NEAR AI Cloud model can review, including
ones that run only at their provider. No receipt is written, and every review
states that nothing proves where the code was read.
Providers outside NEAR's fleet send usage only when asked. ANONYMOUS=true
in eval/run.mjs reviews with MODEL anonymously while the judge stays on
the attested default model.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Private Investigator (Bugs, Nit: 1 · 11 still open)

Verified the new verify-pass flow in both runners (job.rs state machine, review.mjs renderReview/verifyPrompt/applyVerdicts) and re-checked my earlier findings against the head commit; none were addressed. The riskiest issues remain: model- or repo-controlled text can still open a one-click suggestion block via tilde fences, and the verify prompt's ```json fence can be closed from finding text on hostile pull requests, defeating the verify safeguard.

11 earlier findings are still open, and not posted again.

An AI second opinion, not a sign-off: it can miss regressions that span files, above all in money and permission paths.

Was a finding right? React 👍 or 👎 on it: that is how this reviewer is measured and improved.

Read only inside an attested NEAR AI enclave, end-to-end encrypted, with every reply signed by it. Check the receipt.

What was verified
  • Model: z-ai/glm-5.3-flash in an Intel TDX enclave with NVIDIA confidential GPUs. Quote verified, TCB UpToDate, debug off; it binds signing key 5a0210f2442c79355784f9de021985429561da5bacc3a4424b01ac52b7e0caff and this run's nonce. Compose hash c82b1a2eaf6996154a5f39ae621643f034b082d5e51edd3d2ba6009273881d86.
  • GPUs: NVIDIA's signed verdict approves them for the same nonce.
  • Gateway: Quote verified, TCB UpToDate. It relayed only end-to-end encrypted content.
  • Signed: 24 of 24 responses signed by the model enclave's key, over the exact request and response bytes.
  • Receipt: sha256 d7037242e99b139308b1416f9c2b40d10ef218f09cbb1cc2b36119ad39c4bfcb in the run's artifacts. Anyone can check it.

Comment thread app/src/review.rs
return Some("submit_review needs a summary and a findings array".into());
};
let checked = &submission["checked"];
if !checked.is_null() && !checked.as_array().is_some_and(|c| c.iter().all(Value::is_string)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bugs, Nit: Rust and JS disagree on checked: null: JS rejects it (checked !== undefined && !Array.isArray(checked) → error), while Rust skips it (!checked.is_null()), so a submission the Action's model must fix is accepted by the hosted App. The module header says the code matches review.mjs exactly; change the Rust guard to require checked to be an array of strings whenever present.

A repository that commits .github/private-investigator.json with
{"anonymous": true} to its base branch is reviewed by
moonshotai/kimi-k3.1-pro without attestation, encryption, signatures or a
receipt, and every review and check says so. Only the base commit counts,
so a pull request can't opt itself in.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Private Investigator (no new findings · 10 still open)

Checked the two runners' shared review logic (review/review.mjs and app/src/review.rs), the anonymous-mode wiring, the verify pass, and the App's opt-in dispatch. Re-verified both Important findings on the head commit: tilde fences still open suggestion blocks in posted comments, and the verify prompt's ```json fence can still be closed by finding text; both remain unaddressed. Of the earlier nit-level findings, only the Rust verify-loop error mapping (job.rs:654) and the checked:null divergence (review.rs:213) survive; the eval --verify parsing and the duplicate-removal order note stand as reported.

10 earlier findings are still open, and not posted again.

An AI second opinion, not a sign-off: it can miss regressions that span files, above all in money and permission paths.

Was a finding right? React 👍 or 👎 on it: that is how this reviewer is measured and improved.

Read only inside an attested NEAR AI enclave, end-to-end encrypted, with every reply signed by it. Check the receipt.

What was verified
  • Model: z-ai/glm-5.3-flash in an Intel TDX enclave with NVIDIA confidential GPUs. Quote verified, TCB UpToDate, debug off; it binds signing key 5a0210f2442c79355784f9de021985429561da5bacc3a4424b01ac52b7e0caff and this run's nonce. Compose hash a0e3c6f4f21a60ea3f5c96bc7ef319b03a184ca2049605399275fbcea74e8c5f.
  • GPUs: NVIDIA's signed verdict approves them for the same nonce.
  • Gateway: Quote verified, TCB UpToDate. It relayed only end-to-end encrypted content.
  • Signed: 43 of 43 responses signed by the model enclave's key, over the exact request and response bytes.
  • Receipt: sha256 9ce5d4d4d9a8d32406e55e85968e028f49e15f268275a34c0ea59d90624862e4 in the run's artifacts. Anyone can check it.

- The App reads the opt-in from the default branch as it is now, not the
  pull request's base, which its author chooses and which can lag a removal.
- A forced anonymous or dry request no longer shares a job with a private one.
- The Action defaults to the anonymous model in anonymous mode, and the note,
  check summary and stop message no longer claim more than is true.
- The rehearsal reports turns and findings from the runs, with or without a receipt.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Private Investigator (no new findings · 10 still open)

This PR adds a traced, verify-passed review pipeline and an opt-in anonymous mode across both runners. The anonymous gating is sound (only the base repo's default branch opts in; forced-anonymous requests are separate jobs), and the placement/suggestion rendering matches between runners. The riskiest areas remain injection-proofing of posted text and the verify prompt: GitHub tilde fences and unfenced finding text still defeat the stated invariants, and Rust/JS error handling for the verify pass diverges. All nine of my earlier findings are unaddressed and reposted below.

10 earlier findings are still open, and not posted again.

An AI second opinion, not a sign-off: it can miss regressions that span files, above all in money and permission paths.

Was a finding right? React 👍 or 👎 on it: that is how this reviewer is measured and improved.

Read only inside an attested NEAR AI enclave, end-to-end encrypted, with every reply signed by it. Check the receipt.

What was verified
  • Model: z-ai/glm-5.3-flash in an Intel TDX enclave with NVIDIA confidential GPUs. Quote verified, TCB UpToDate, debug off; it binds signing key 5a0210f2442c79355784f9de021985429561da5bacc3a4424b01ac52b7e0caff and this run's nonce. Compose hash c82b1a2eaf6996154a5f39ae621643f034b082d5e51edd3d2ba6009273881d86.
  • GPUs: NVIDIA's signed verdict approves them for the same nonce.
  • Gateway: Quote verified, TCB UpToDate. It relayed only end-to-end encrypted content.
  • Signed: 39 of 39 responses signed by the model enclave's key, over the exact request and response bytes.
  • Receipt: sha256 8e7365d4bf91e36afc23b22c474d19ae64c3a963dbfcd817302e1b6b7697dd66 in the run's artifacts. Anyone can check it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant