Skip to content

fix(review): filter the AI narrative assessment per sentence, not all-or-nothing - #9445

Merged
JSONbored merged 2 commits into
mainfrom
fix/assessment-sentence-level-public-safe
Jul 27, 2026
Merged

fix(review): filter the AI narrative assessment per sentence, not all-or-nothing#9445
JSONbored merged 2 commits into
mainfrom
fix/assessment-sentence-level-public-safe

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Fixes the root cause behind #9432.

FORBIDDEN_PUBLIC_COMMENT_WORDS is matched with a plain case-insensitive .includes() over a list that contains ordinary review vocabulary — reward, rewards, ranking, rankings, cohort, farming, reviewability — and toPublicSafe drops its whole input when sanitizePublicComment throws. So a perfectly safe review of this codebase's own gate/scoring code had its entire narrative discarded and replaced by the generic "did not include a separate narrative summary" placeholder.

Verified against the real filter — 3 of 4 realistic review sentences are rejected wholesale:

REJECTED: "...updates the ranking comparator so ties resolve..."  -> forbidden word: "ranking"
REJECTED: "The reward-label propagation path now handles..."      -> forbidden word: "reward"
REJECTED: "Adds a cohort filter to the contributor query..."      -> forbidden word: "cohort"
OK:       "Refactors the readiness scoring helper..."

Evidence this is the right cause

  • The assessment was present. No ai_review_missing_assessment diagnostic was emitted for any affected PR (docker logs over the incident window: zero matches), so parseModelReview returned a non-empty assessment and it was discarded downstream.
  • The reviews succeeded. Affected ai_review_cache rows carry inconclusive: false and reviewer_count: 1 — this is not a provider failure.
  • ~40% of reviews in the sample window landed on the placeholder while others produced real summaries, which matches "did this particular narrative happen to use the word ranking/reward/cohort".

Fix

Filter the assessment per sentence and keep the survivors.

Safety is not weakened. A leaked private VALUE ("trust score 0.82", "reward estimate 12 TAO") necessarily sits in the same sentence as the term naming it, so dropping that sentence removes the risky content — whereas discarding the whole assessment protected nothing additional and cost the reader every other sentence.

It also makes the narrative consistent with findings, which already filter per item (safeBlockers / safeNits keep survivors). The assessment was the lone all-or-nothing holdout.

Behaviour is unchanged in both edge cases: a fully-safe assessment takes a whole-text fast path (preserving exact original spacing), and an assessment whose every sentence is unsafe still returns null and falls through to the existing placeholder.

Test plan

  • Regression test: an assessment mixing an unsafe sentence ("ranking comparator") with a safe one keeps the safe sentence and does not emit the placeholder
  • Safety test: a sentence carrying a private value is removed (0.82, 12 TAO, trust score all absent) while the safe lead sentence survives
  • Safety test: an assessment whose every sentence is unsafe still falls back to the placeholder, with nits preserved
  • Existing all-or-nothing tests (composeAdvisoryNotes returns null when no assessment or finding is public-safe, preserves blockers and nits when the model omits a narrative assessment) still pass unchanged
  • npm run typecheck clean
  • Full local suite: 1242 files / 23,362 tests, 0 failures

Not covered by this PR

The separate inconclusive: true failure mode (e.g. awesome-claude#5599, which returns "AI review is unavailable for this PR head") is a different branch and remains open under #9432.

…-or-nothing

FORBIDDEN_PUBLIC_COMMENT_WORDS is matched with a plain case-insensitive
`.includes()` over a list containing ordinary review vocabulary --
"reward", "rewards", "ranking", "rankings", "cohort", "farming",
"reviewability" -- and toPublicSafe drops its WHOLE input when
sanitizePublicComment throws. A safe review of this codebase's own
gate/scoring code ("updates the ranking comparator so ties resolve
deterministically") therefore had its entire narrative discarded and
replaced by the generic "did not include a separate narrative summary"
placeholder.

Observed live across ~40% of reviews. The model's assessment was
confirmed PRESENT in those cases: no `ai_review_missing_assessment`
diagnostic was emitted for the affected PRs, so the text was produced and
then thrown away downstream, and the cached rows carry
`inconclusive: false` (the review succeeded).

Drop only the offending SENTENCE instead. A leaked private VALUE
("trust score 0.82", "reward estimate 12 TAO") necessarily sits in the
same sentence as the term naming it, so removing that sentence removes
the risky content -- discarding the whole assessment protected nothing
additional and cost the reader every other sentence. It also matches how
this file already treats findings: safeBlockers/safeNits filter per item
and keep the survivors, so the assessment was the lone all-or-nothing
holdout. An assessment whose every sentence is unsafe still returns null
and takes the existing placeholder path unchanged.
@loopover-orb

loopover-orb Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-27 19:32:42 UTC

2 files · 1 AI reviewer · 2 blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
The AI review returned non-blocking notes for this change but did not include a separate narrative summary. Review the nits below before deciding this PR.

Nits — 3 non-blocking
  • Neither added test exercises the `kept.length > 1` branch of `toPublicSafeBySentence` (src/services/ai-review.ts new fn) where two or more surviving sentences are actually `.join(" ")`ed together — both tests leave exactly one sentence surviving, which plausibly explains the reported codecov/patch shortfall (90% vs 99% target).
  • The external brief's magic-number flags (0.82, 12, 40) are false positives — those are literal test-data strings simulating a leaked value/percentage, not unexplained production constants; safe to disregard.
  • Consider a short doc-comment example on `SENTENCE_BOUNDARY` showing the decimal non-split case explicitly, since it's the subtlety that makes the safety property hold.

Concerns raised — review before merging

  • No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example Closes #123) before opening the PR.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.

2. Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example `Closes #123`) before opening the PR.

Decision drivers

  • ❌ Code review — 2 blockers (1 reviewer)
  • ❌ Gate result — Blocking (Repo-configured hard blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 13 registered-repo PR(s), 13 merged, 313 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 13 PR(s), 313 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 13 PR(s), 313 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 2 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: missing_linked_issue
  • config: ef69a8088eccb69c7609db3efa04949e30b5a0201575c8f16ccdfebf650fe4c7 · pack: oss-anti-slop · ci: passed
  • record: a3371e0b5153fd65c50e85ba2d8b0283de65334a4d23b1f3ba79b3d8c9d34bb4 (schema v5, head 68af7df)

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (fb8e2eb) and head (68af7df) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 27, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.64%. Comparing base (fb8e2eb) to head (68af7df).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9445      +/-   ##
==========================================
- Coverage   89.54%   88.64%   -0.90%     
==========================================
  Files         843      843              
  Lines      110011   110020       +9     
  Branches    26184    26188       +4     
==========================================
- Hits        98511    97529     -982     
- Misses      10238    11520    +1282     
+ Partials     1262      971     -291     
Flag Coverage Δ
backend 93.62% <100.00%> (-1.65%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/services/ai-review.ts 97.00% <100.00%> (+0.03%) ⬆️

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 27, 2026
… coalesce

codecov/patch flagged line 707 branch 1: `text ?? ""` was only ever exercised with a
present string. A model that returns no assessment field at all reaches this as undefined,
and composeAdvisoryNotes' own `assessments[0] ?? ""` reaches it as "" -- neither may be
split into sentences, since an empty assessment must fall through to the existing
placeholder path rather than becoming an empty joined string that reads as a real (blank)
narrative.

Exports the helper through __aiReviewInternals so the test can reach it directly.
@JSONbored
JSONbored merged commit 6269d27 into main Jul 27, 2026
9 checks passed
@JSONbored
JSONbored deleted the fix/assessment-sentence-level-public-safe branch July 27, 2026 19:36
JSONbored added a commit that referenced this pull request Jul 28, 2026
…d attribute votes to the producing model (#9477, #9478) (#9513)

* fix(ai): make prompt drift a cache miss, bound the Ollama context, and attribute votes to the producing model (#9477, #9478)

getCachedAiReview reuses a cacheable row with NO maxAgeMs and replays its findings verbatim,
including a severity:critical ai_consensus_defect. AI_REVIEW_CACHE_INPUT_VERSION was hand-bumped
and last moved for #8364, but #8789, #8791, #8833, #8845, #8961, #9035, #9074, #9087, #9114,
#9145 and #9445 all changed prompt text or verdict logic without one -- so a PR closed or held
under the pre-#9074 false-consensus rule re-gated at the same head to the SAME stale finding and
the corrected logic never ran. The fingerprint now folds in REVIEW_PROMPT_VERSION and a digest of
the canonical judge prompt, both pure and available before the call, so drift is an automatic
miss rather than something a human must remember. Bumped to v7 to kill rows written under the
incomplete fingerprint.

Ollama silently left-truncates past num_ctx (typically 4k-8k by default) and then answers
confidently over whatever survived -- the TAIL of the prompt, i.e. the context sections rather
than the diff. The review path sends up to 120k chars of diff plus a 240k-char context budget,
so on the fallback provider a review could come from a fraction of the change while carrying the
same blocker authority and confidence semantics as the primary. An explicit num_ctx is now sent
for the ollama provider only, since other OpenAI-compatible servers may reject an unknown
options key, and an explicit caller providerOptions still wins.

runWorkersOpinion iterates [primary, fallback] internally and its outcome carried no model
identity, so a fallback-produced review was recorded as a PRIMARY vote -- poisoning the
reviewer_vote audit events, #8229's routing track records, and scoreJudgmentAgreement's
contribution to decision-record confidence. The doc's claim that slot-to-model is unambiguous by
construction holds for the tie-break swap, not for in-slot fallback.

* chore(selfhost): regenerate the env reference for the new env reads
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant