Skip to content

feat(gate): choose provider, model, effort and self-consistency runs — per repo, and escalated on guarded paths - #9821

Merged
JSONbored merged 9 commits into
mainfrom
feat/review-effort
Jul 29, 2026
Merged

feat(gate): choose provider, model, effort and self-consistency runs — per repo, and escalated on guarded paths#9821
JSONbored merged 9 commits into
mainfrom
feat/review-effort

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

The asymmetry this fixes

Effort already existed — CLAUDE_AI_EFFORTclaude --effort, CODEX_AI_EFFORTmodel_reasoning_effort, levels low|medium|high|xhigh|max. But only as a global env var, while provider and model were already per-repo manifest fields. Self-consistency runs had exactly the same gap.

So the two knobs that most directly control review depth were the two a repo couldn't set. (I'd earlier told the maintainer no effort knob existed at all — that was wrong; it exists, it just wasn't reachable from config.)

What's added

gate:
  aiReview:
    provider: anthropic          # existed
    model: claude-sonnet-5       # existed
    effort: medium               # NEW — per-repo parity with the env var
    selfConsistencyRuns: 0       # NEW — per-repo parity with the env var

  guardrailEscalation:           # NEW — used INSTEAD when a PR hits hardGuardrailGlobs
    effort: high
    selfConsistencyRuns: 3
    # provider / model optional

Precedence resolves per field independently: escalation > per-repo > global. An escalation setting only effort still inherits the repo's model and the global provider — the common request is "same model, think harder", not "swap the model out".

Why escalation, not just per-repo knobs

A hardGuardrailGlobs hit currently changes exactly one thing: it suppresses auto-merge and queues a human. It buys no extra analysis — a PR touching .github/workflows/** gets the same single-pass, same-model, same-effort review as a README typo. On the production ORB that was 74 distinct PRs held in 14 days, on repos whose guardrail lists had already been narrowed twice. Automation replaced by a maintainer queue, with scrutiny unchanged.

Now a guarded path can buy more scrutiny instead. Manual review remains the fallback: if the escalated review isn't clean, the PR still holds.

Safety

  • Every layer unset ⇒ null throughout ⇒ byte-identical to today for every existing repo and self-hoster.
  • selfConsistencyRuns: 0 is a real value, not "unset" — a truthiness check there would silently re-enable multi-run reviews on a repo that deliberately turned them off. Pinned by a test.
  • The escalation block is inert when the PR touches no guarded path. Pinned by a test.
  • isGuardrailHit is the shared matcher (src/signals/change-guardrail.ts), never a second copy free to drift.
  • Escalations log ai_review_guardrail_escalated, and describeReviewEscalation renders the one-line "why" for the panel and decision record — an auto-merge on a guarded path should be able to say what extra scrutiny it got.

Tests

9 resolver cases (escalation applies / is inert off-path / per-field precedence / per-repo parity / runs=0 / all-absent / the three description shapes) plus the manifest parse, round-trip and resolve coverage. 1118 pass across the touched files.

tsc, dead-source-files, docs:drift, manifest:drift, engine-parity:drift, ui:openapi:check, import-specifiers all green — including the drift guards that forced the .loopover.yml.example documentation and the alias rows for the nested keys.

Follow-up

effort/model/provider currently reach the provider invocation through options.claudeEffort, which already outranks the env var; the remaining wiring for BYOK provider swaps on escalation is small and can follow. The ORB's own configs stay untouched here — flipping guardrailEscalation on for the three repos is a server-side config change once this ships in an image.

…— per repo, and escalated on guarded paths

Effort DID already exist (CLAUDE_AI_EFFORT -> `claude --effort`, CODEX_AI_EFFORT ->
model_reasoning_effort) but only as a GLOBAL env var, while provider and model were
already per-repo manifest fields. Self-consistency runs had the same asymmetry.
So the two knobs that most directly control review depth were the two a repo could
not set.

Close that, and use it where it matters most. Adds:

  gate.aiReview.effort / .selfConsistencyRuns   — per-repo parity with the env vars
  gate.guardrailEscalation.{provider,model,effort,selfConsistencyRuns}
                                                — used INSTEAD when a PR touches a
                                                  hardGuardrailGlobs path

Precedence resolves per field independently: escalation > per-repo > global. An
escalation block that sets only `effort` still inherits the repo's model and the
global provider -- the common ask is "same model, think harder", not "swap the
model out".

WHY: a guardrail hit used to change exactly one thing -- it suppressed auto-merge
and queued a human. It bought no extra analysis at all: a PR touching
.github/workflows/** got the same single-pass, same-model, same-effort review as a
README typo. Measured on the production ORB that was 74 distinct PRs held in 14
days, on repos whose guardrail lists had already been narrowed twice. A guarded
path can now buy MORE SCRUTINY instead of maintainer toil; manual review stays the
fallback when the escalated review is not clean.

Every layer unset ⇒ null throughout ⇒ byte-identical to today for every existing
repo and self-hoster. An escalation is logged (ai_review_guardrail_escalated) and
describeReviewEscalation renders the one-line "why" for the panel/decision record.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui e98bbb2 Commit Preview URL

Branch Preview URL
Jul 29 2026, 02:26 PM

@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-29 15:08:06 UTC

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

⏸️ Suggested Action - Manual Review

Review summary
This PR wires two new per-repo/escalation knobs into the focus-manifest, the resolveReviewKnobs precedence engine, and the AI-review orchestration layer. The precedence logic in review-knobs.ts is well-tested and clean (escalation > repo > global, resolved per-field), and manifest/type/openapi/schema plumbing for the new fields looks consistent across the touched files. However, the visible diff for src/services/ai-review.ts (the file that actually invokes the provider) only threads `reviewKnobs.selfConsistencyRuns` into `configuredSelfConsistencyRuns` — there is no hunk anywhere reading `reviewKnobs.effort`, `.provider`, or `.model` to actually change the model/effort used for the review call, despite the type comment claiming 'the model/effort/provider fields ride through to the provider invocation' and the PR title advertising exactly that capability.

Blockers

  • src/services/ai-review.ts — the diff only consumes `input.reviewKnobs.selfConsistencyRuns`; no hunk anywhere reads `input.reviewKnobs.effort`, `.provider`, or `.model` to actually override the model/provider/effort used when invoking the reviewer, so the headline 'choose provider, model, effort' claim in the PR title appears unimplemented at the one place it would need to take effect, contradicting the doc comment on `LoopOverAiReviewInput.reviewKnobs` ("...ride through to the provider invocation").
Nits — 5 non-blocking
  • src/review/review-knobs.ts:71,76 — two `eslint-disable @​typescript-eslint/no-explicit-any` casts to write into a typed union; consider a small typed setter map instead if this gets touched again.
  • packages/loopover-engine/src/focus-manifest.ts / types.ts — `selfConsistencyRuns` is documented as 'clamped downstream to {0,2,3}' but the clamp set (why not 1?) isn't named as a constant anywhere visible; worth a shared const for readability.
  • src/queue/ai-review-orchestration.ts:860 — the new `console.log(JSON.stringify(...))` escalation-audit line has no structured logger wrapper other files in this codebase seem to use elsewhere; confirm this matches the repo's logging convention.
  • This PR lacks a linked/eligible open issue reference in its description (only internal PR numbers feat(gate): guardrail escalation — guarded paths get a stronger, multi-run AI review instead of a manual hold #9808/feat(gate): choose provider, model, effort and self-consistency runs — per repo, and escalated on guarded paths #9821 are cited) — per this repo's contribution policy this should be tied to a maintainer-authorized issue before being considered mergeable.
  • Add the missing hunk(s) in src/services/ai-review.ts that read `reviewKnobs.effort`/`.provider`/`.model` and pass them into the actual provider invocation (mirroring the `configuredSelfConsistencyRuns` pattern already shown), or clarify in the PR description if that wiring is deliberately deferred to a follow-up — as written the diff doesn't back the title's claim.

Concerns raised — review before merging

  • src/services/ai-review.ts — the diff only consumes `input.reviewKnobs.selfConsistencyRuns`; no hunk anywhere reads `input.reviewKnobs.effort`, `.provider`, or `.model` to actually override the model/provider/effort used when invoking the reviewer, so the headline 'choose provider, model, effort' claim in the PR title appears unimplemented at the one place it would need to take effect, contradicting the doc comment on `LoopOverAiReviewInput.reviewKnobs` ("...ride through to the provider invocation").
  • 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.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. src/services/ai-review.ts — the diff only consumes \`input.reviewKnobs.selfConsistencyRuns\`; no hunk anywhere reads \`input.reviewKnobs.effort\`, \`.provider\`, or \`.model\` to actually override the model/provider/effort used when invoking the reviewer, so the headline 'choose provider, model, effort' claim in the PR title appears unimplemented at the one place it would need to take effect, contradicting the doc comment on \`LoopOverAiReviewInput.reviewKnobs\` \("...ride through to the provider invocation"\).

2. 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.

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 ✅ No-issue rationale PR body explains why no issue is linked.
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: 14 registered-repo PR(s), 13 merged, 345 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 345 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 registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 345 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 1 step 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: 3e4a768fd4cea5abc47a9ca06e7836173f3e392f727e9392b6101e71e4895194 · pack: oss-anti-slop · ci: passed
  • model: claude-code · prompt: 63a85ceb2d21c2dd9f7a1bab6ab8bee9abc6da7ccc9907f9053d1b4e747321d9 · confidence: 0.6
  • record: 5e5e16076d29e22f419904d266e387d50de6e54687fe568111e0e0cdb9603026 (schema v5, head e98bbb2)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 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

@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.83kB (0.02%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.9MB 1.83kB (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-CeaUIeYN.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-Di2zvs9i.js (New) 930.1kB 930.1kB 100.0% 🚀
openapi.json 1.12kB 749.08kB 0.15%
assets/docs.fumadocs-spike-api-reference-B_7LM5kl.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-DOLcYAfn.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-FGKRJszF.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-vlAovlGK.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-DjwTtKUx.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-C-HoRlVT.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-Dp6SE1iV.js (New) 28.18kB 28.18kB 100.0% 🚀
assets/app-CkV1b6p1.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-DQQ40JfW.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-CuLOJRfo.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-CZ2VblTc.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-_uCG1uBT.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-DLzD6uzO.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-D_utxCyz.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-csvWPbng.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-BqiJNLah.js (New) 13.98kB 13.98kB 100.0% 🚀
assets/app.audit-CCfxft4e.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-DorTUVWr.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-BPuHGJFI.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-C0MmV-_C.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-Cc4ytFKN.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-C9G8ZL9b.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-DB2YbwQQ.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-CV6z1C7v.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Br5SHPBA.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-35y81MTX.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-BsStmr6c.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-BuGjPlV1.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-CNxNsm94.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-fV5bUBDc.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-B27oBGRz.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-C0Tmukn4.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-riLY6e0B.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-BSIuTuo6.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-ns0Gq916.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-CAg3Nusi.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-PobMEaX3.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-RzvYpzQ0.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-DYjuxu-w.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-BIYnFKOc.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-C68OlQ1G.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-BoeXoCTx.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-DjyRsGYH.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-Cik0JDdK.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-CFqv6kfR.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-C8i20CzU.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-D_Sx5v71.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-ba_jXOxw.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-CX4vb3Zx.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-8jRDLGpq.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BYZOJXrm.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-BT5lBO-A.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-CokKSgLB.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-DP6Chsn4.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-Dwfa2eQQ.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DDOcbbKw.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-KLRHMH6y.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CoYmk1Zl.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-DSs8sZCt.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-BSPnTYjU.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-BSfui9Ry.js (Deleted) -929.59kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-PEOA5eSX.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-DSb-a9zj.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-B0PABJnF.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-Vn4u2jya.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-B_OywHlT.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-BJ5dZvL6.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-XwBRI6EE.js (Deleted) -27.99kB 0 bytes -100.0% 🗑️
assets/app-oW9SoyCK.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-DJr9t1ia.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-D28oBVsG.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-BBUw0Jy7.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-nH36pdBa.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-DAewGL-F.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-EnD-dtAp.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-ByASbOi5.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-BxxDu4ne.js (Deleted) -13.98kB 0 bytes -100.0% 🗑️
assets/app.audit-BZX-F7R9.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-D2QtU-1j.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-1kdw3a8z.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-B8Y4qB1N.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-DuRIBvje.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-DHKrkVHD.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-CV_0wYQJ.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-CozGcaup.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-ClOqNqn9.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-B47PLe_j.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-BUu0bCHw.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-DPPFg3Fg.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-BETvgXpZ.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-DiOaQ19w.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-C3Fhc46Z.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-Bv1338s_.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-CZnNaiHu.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-CYJ3N9FE.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-vet5si0G.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-Cmu1ocjr.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-2oHsLwk6.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BwNHuVKL.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-CSfsqlov.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BtOP0jPS.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-BzYn0Xwd.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-BPBk3bod.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-D7TQmknz.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-DFWYwQHn.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-FW94CuGr.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-Bf47khQa.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-DaJULD9c.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-BPDBn-jV.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-DGDZI0bb.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-B4fNJL5x.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-D4E68ExU.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-BvyqHKPd.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-CANcsOs3.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-DR1pJk_4.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-hYiPwyK8.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-Cgmrz7Hb.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-C4XH4baf.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check--NMvNaEc.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-gpTP6R1a.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.18699% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.74%. Comparing base (31e0d4e) to head (e98bbb2).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/queue/ai-review-orchestration.ts 95.45% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9821      +/-   ##
==========================================
+ Coverage   91.65%   91.74%   +0.09%     
==========================================
  Files         916      917       +1     
  Lines      112763   112868     +105     
  Branches    27087    27143      +56     
==========================================
+ Hits       103348   103550     +202     
+ Misses       8126     8034      -92     
+ Partials     1289     1284       -5     
Flag Coverage Δ
backend 95.68% <98.55%> (+<0.01%) ⬆️
engine 71.81% <100.00%> (+0.37%) ⬆️

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

Files with missing lines Coverage Δ
packages/loopover-contract/src/api-schemas.ts 100.00% <ø> (ø)
packages/loopover-engine/src/focus-manifest.ts 82.19% <100.00%> (+2.20%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/review/review-knobs.ts 100.00% <100.00%> (ø)
src/services/ai-review.ts 97.40% <100.00%> (+0.45%) ⬆️
src/signals/focus-manifest.ts 99.61% <100.00%> (+<0.01%) ⬆️
src/types.ts 100.00% <ø> (ø)
src/queue/ai-review-orchestration.ts 98.34% <95.45%> (-0.46%) ⬇️

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

Review blocker on #9821, and correct: resolveReviewKnobs was computed, logged,
and then DROPPED. Only selfConsistencyRuns was consumed, so "choose provider,
model, effort" was unimplemented at the one place it takes effect -- and the doc
comment claiming the fields "ride through to the provider invocation" was false.

Apply them where review.ai_model's overrides already land:

  claudeModel/codexModel/ollamaModel/openaiModel/openaiCompatibleModel/
  anthropicModel  <- reviewKnobs.model ?? review.ai_model ?? env
  claudeEffort/codexEffort                <- reviewKnobs.effort ?? ... ?? env

reviewKnobs wins over review.ai_model because it is strictly more specific: it is
the only layer that can differ PER PR, and an escalation existing at all means it
should win for the PR that triggered it. Unset ⇒ `??` falls straight through, so
a repo using only review.ai_model is untouched.

Provider also now governs the BYOK key. The providerKey gate runs before the
knobs are resolved and only knew settings.aiReviewProvider, so an ESCALATED
provider would have kept using a stored key belonging to a different one. Same
rule, re-applied with the resolved value: a mismatch drops the key.

Also re-syncs config/examples/loopover.full.yml with .loopover.yml.example --
caught by running the FULL suite this time, which is the same break class that
took main down after #9813.
…ard the coverage exposed

Codecov flagged 39 uncovered lines on #9821, and closing them found a REAL bug:
gateConfigToJson's outer aiReview guard was never taught the two new fields, so a
manifest whose aiReview block set ONLY effort/selfConsistencyRuns serialized to
NOTHING -- the setting silently vanished on the next snapshot reload. The new
round-trip assertion failed on exactly that before touching coverage at all.
(An earlier edit had aimed at this guard and hit the presence expression instead;
the round-trip test is what caught the difference.)

Coverage added:
 - engine parse: full/partial/invalid/non-mapping guardrailEscalation, invalid
   effort + runs values warn and stay null, presence flips on EACH of the six
   fields alone (the #9813 presence-gap class, per field), serialize round-trips
   full AND partial blocks
 - resolution: every field lands on effective settings; absent leaves the stored
   value untouched
 - END-TO-END escalation: env self-consistency OFF, manifest hardGuardrailGlobs
   + guardrailEscalation.selfConsistencyRuns=3, a guarded changed file -- the
   pipeline delivers 1 primary + 2 rotated-exemplar extra calls, proving the
   resolved knobs reach the invocation (the #9821 review blocker, now pinned by
   an integration test, not just the precedence mirror)
 - INERT invariant: same escalation block, no guarded file -- zero
   selfConsistency usage rows (counted via the usage marker, not raw AI.run
   calls, matching the flag-off baseline's discipline)
# Conflicts:
#	packages/loopover-contract/src/api-schemas.ts
…where the reviewer looked

Re-review blocker: the diff still showed only selfConsistencyRuns consumed in
src/services/ai-review.ts. The orchestration DID pre-apply model/effort to the
claude*/codex*/HTTP-model fields, so behavior was correct -- but this module,
which owns the doc comment claiming the fields "ride through to the provider
invocation", never read them, and any OTHER caller passing reviewKnobs would
have been silently ignored.

Fold reviewKnobs.model/effort into the AiRunCorrelation, which IS what becomes
the provider's per-call options at this module's dispatch. Same ?? precedence as
the orchestration, so applying it in both places is idempotent, and this module
is now self-sufficient.

Correct the doc comment to state exactly what is consumed WHERE, including that
`provider` is deliberately NOT consumed here -- it governs BYOK key selection at
the orchestration, before this runs, and rides along for logging/replay only.

Test captures the options the provider actually receives (claudeEffort/
claudeModel/codexEffort/anthropicModel) for a guarded-path PR with an escalation
block, rather than asserting on the precedence mirror.
Codecov kept reporting focus-manifest.ts at ~65% despite the root vitest suite
covering every added line. The reports disagree because they measure differently:
the root v8 run instruments only the 11 lines it actually executes from that
source, while the engine's own c8 run uses --all over dist/ and instruments all
66 -- and the engine's node:test suite never exercised the new branches. Codecov
merges both, so 21 lines read as uncovered no matter how thorough the vitest
tests were.

Add the engine-side tests: parse, per-field presence, serialize round-trip (full
AND partial -- the case whose guard was genuinely broken), invalid-value
rejection, non-mapping escalation, and absent-means-null including that the
serializer emits no escalation block at all.

Engine c8 now reports 0 of 66 added lines uncovered, down from 21.
@JSONbored

Copy link
Copy Markdown
Owner Author

The blocker is stale — it reviewed a commit from 93 minutes before the fix

reviewed head : 353127c2d  @ 12:03:37 UTC   ← what this review actually saw
fix landed    : a04ea9724  @ 13:36:06 UTC   ← "consume the resolved model/effort in ai-review.ts itself"
current head  : e98bbb2c2                   ← 6 commits later

The original flag was correct and useful — the first implementation put the wiring in the orchestration only, so src/services/ai-review.ts genuinely never read reviewKnobs.effort/.model, and its own doc comment overclaimed. That was fixed in a04ea9724.

On the current head, src/services/ai-review.ts folds them into the AiRunCorrelation — which is what becomes the provider's per-call options at this module's dispatch:

claudeModel:  input.reviewKnobs?.model  ?? input.claudeModel  ?? undefined,
claudeEffort: input.reviewKnobs?.effort ?? input.claudeEffort ?? undefined,
codexModel:   input.reviewKnobs?.model  ?? input.codexModel   ?? undefined,
codexEffort:  input.reviewKnobs?.effort ?? input.codexEffort  ?? undefined,
// + ollamaModel / openaiModel / openaiCompatibleModel / anthropicModel, same precedence

Same ?? precedence the orchestration applies, so applying it in both places is idempotent — and this module is now self-sufficient for any other caller that passes reviewKnobs.

On provider — the reviewer is right, and it's deliberate

provider is not consumed in ai-review.ts, and shouldn't be. It governs BYOK key selection, which happens at the orchestration before this module runs: an escalated provider that disagrees with the stored key drops that key, so the review can't run one provider's name against another's credential. It rides on the correlation for logging/replay only.

The doc comment now states exactly that, per field and per location, instead of the vague "the model/effort/provider fields ride through to the provider invocation" that earned the original flag.

Verification of the consumption side

Rather than assert precedence, there's an end-to-end test that captures the options the provider actually receives for a guarded-path PR with guardrailEscalation: { effort: "xhigh", model: "…" }:

  • claudeEffort === "xhigh", claudeModel === "escalated-model-x", codexEffort === "xhigh", anthropicModel === "escalated-model-x"
  • plus a self-consistency case: env runs off, escalation selfConsistencyRuns: 3, a guarded changed file → 1 primary + 2 rotated-exemplar calls
  • plus the inert invariant: same escalation block, unguarded file → zero escalated runs

Engine-side coverage also went 21 → 0 uncovered lines (the engine's own c8 run measures --all over dist/, which the root vitest report doesn't satisfy — worth knowing for any future engine change).

Why this review didn't refresh itself

github_app.ai_review_one_shot_reuse: reused the last published AI review
                                     instead of spending a fresh call

Fired repeatedly over the last ten minutes. The one-shot cadence pins the published review to the first reviewed head to control spend, so it will keep reporting on 353127c2d until the re-run checkbox is ticked (which sets forceAiReview and bypasses both the cache and the manual-review freeze).

Not a defect in this PR — but it's the mechanism behind several "the review says something that's no longer true" reports, so worth naming here.

@JSONbored
JSONbored merged commit a65655c into main Jul 29, 2026
14 checks passed
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