Skip to content

feat(gate): guardrailEscalation.onCleanReview — release the guardrail hold when the escalated review is clean - #9869

Open
JSONbored wants to merge 3 commits into
mainfrom
feat/guardrail-mode
Open

feat(gate): guardrailEscalation.onCleanReview — release the guardrail hold when the escalated review is clean#9869
JSONbored wants to merge 3 commits into
mainfrom
feat/guardrail-mode

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 29, 2026

Copy link
Copy Markdown
Owner

The second half of #9808, as a mode

#9821 shipped the escalation knobs — a guarded PR now gets a high-effort review — but the disposition still held it unconditionally: guardrailHit sat in heldForManualReview regardless of what the escalated review found. The 74 held PRs / 14 days kept landing on the maintainer, just with better reviews attached.

gate:
  guardrailEscalation:
    effort: high
    onCleanReview: proceed   # NEW — full-autonomy mode
    # hold (default) = today's behavior exactly, human-in-the-loop

proceed: a clean escalated review (gate success + green CI) releases the guardrail hold and the normal approve/merge path continues — the guarded path is protected by the escalated review instead of a human queue. Because it's a manifest field it layers global → per-repo like everything else: repos flip one at a time, and the mode is reversible with a config edit, no deploy.

Fail-closed on every axis

  • proceed is inert unless at least one escalation knob is actually set. The release is justified by extra scrutiny; absent scrutiny, nothing vouches for the guarded path. A config that sets the mode but forgets the escalation changes nothing.
  • The disposition re-checks reviewGood independently of the caller's flag — a red gate or CI still blocks even if cleared were mis-set upstream. Pinned by a test.
  • Only the guardrail term is released. Migration collisions, unlinked-issue holds, advisory-check holds, unstable merge states all still hold — each pinned individually.
  • The manual-hold reason no longer claims "guarded path → manual review" on a cleared PR, so the audit trail matches the action.

Note reviewGood already folds in the AI verdict: a consensus blocker fails the gate itself, so "clean escalated review" is not a separate, bypassable signal — it's the same gate every other merge decision trusts.

Tests

4 disposition cases (release, default-hold byte-identical, only-the-guardrail-term, red-gate-cannot-merge-despite-flag) + engine parse/round-trip/presence/junk-rejection for the new field. Full suite 25,455 passed; docs-drift (both example configs), openapi, contract schemas, dead-source-files green.

Activation

On merge + image: set onCleanReview: proceed in the three repos' private configs (server-side, no code). Every hold family is then either eliminated, one-shot-closed, or maintainer-only — the full-autonomy target, with hold mode one config edit away if precision degrades.

Closes #9808.

@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 5ae2ba9 Commit Preview URL

Branch Preview URL
Jul 29 2026, 10:13 PM

@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

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

@JSONbored JSONbored self-assigned this Jul 29, 2026
@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 361 bytes (0.0%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.82MB 361 bytes (0.0%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-Bp8cJ6LF.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-B5Ij-9Ml.js (New) 930.74kB 930.74kB 100.0% 🚀
openapi.json 223 bytes 750.34kB 0.03%
assets/docs.fumadocs-spike-api-reference-BBY6WcBC.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-BgTY9c1D.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-DbBNyhzT.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-C3xSI6Ou.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-CMzdeK-9.js (New) 78.96kB 78.96kB 100.0% 🚀
assets/routes-Dlv1edB7.js (New) 36.41kB 36.41kB 100.0% 🚀
assets/owner-panel-CF0lF9AM.js (New) 28.26kB 28.26kB 100.0% 🚀
assets/app-C40KhfB-.js (New) 25.82kB 25.82kB 100.0% 🚀
assets/ui-vendor-VmR4oGg6.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-Ck2kB5X6.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-C5YVFQrS.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DWOfM9ZD.js (New) 17.54kB 17.54kB 100.0% 🚀
assets/self-hosting-docs-audit-DfEwEM4Z.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-BiOeTZ6z.js (New) 15.63kB 15.63kB 100.0% 🚀
assets/playground-panel-BTdBB0dO.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-BlXU8Not.js (New) 13.98kB 13.98kB 100.0% 🚀
assets/app.audit-BUwuGXLb.js (New) 10.22kB 10.22kB 100.0% 🚀
assets/app.config-generator-ImrXbWQl.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-Cn5rvWF6.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-t1WvPAPq.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-C-UmLzOn.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-DNLSXKpS.js (New) 6.74kB 6.74kB 100.0% 🚀
assets/maintainer-workflow-W8Mycl2j.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-BproAl5Q.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-CRLV4FUJ.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-CiSil6Iq.js (New) 6.06kB 6.06kB 100.0% 🚀
assets/docs.index-F7re2WL5.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-a8i5O_5I.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-DZJhY5aO.js (New) 2.93kB 2.93kB 100.0% 🚀
assets/api-cWmm5Ff2.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-DwSvcOic.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-CbRSq6m_.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-Bvrf0zXB.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-C187V6h1.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-BP67iuus.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-BcywM_Xp.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-jnkmO1r9.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-1HNLLjcl.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-Bx1zG4iq.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-DYwNL7KO.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-C7q1gYNy.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-Cubdwx8u.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-ocR45zfU.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-DB2Qx5k-.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-CnlMhorE.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-CiOImw0f.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-BvjmdBt-.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-BfK97oZP.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-D1siH_ot.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-BhZTnEUF.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-CIrKXrx_.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-BeEt3DxV.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-BbCXqGYu.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-Cu6LhHVv.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CQu-NVKz.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-CKHGeqsL.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-DayjAleI.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-E1K4T4Z3.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-GZPsUhBt.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-CepxXuDy.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-AwjN6nKq.js (Deleted) -930.64kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-yrtF2yG2.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-DpxZdr4p.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-BZsvE1y5.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-COX_GLVr.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-DOJ5zlPL.js (Deleted) -78.96kB 0 bytes -100.0% 🗑️
assets/routes-Cy1A0Rh0.js (Deleted) -36.41kB 0 bytes -100.0% 🗑️
assets/owner-panel-DR-WkbD2.js (Deleted) -28.22kB 0 bytes -100.0% 🗑️
assets/app-ChJbfLCb.js (Deleted) -25.82kB 0 bytes -100.0% 🗑️
assets/ui-vendor-DMhklvhg.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-BUjifE2k.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-oro2kiGh.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-FAsUFfkA.js (Deleted) -17.54kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-1VxZHCSg.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-DTstgoEh.js (Deleted) -15.63kB 0 bytes -100.0% 🗑️
assets/playground-panel-DoR5208S.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-DDsxDFvD.js (Deleted) -13.98kB 0 bytes -100.0% 🗑️
assets/app.audit-PPe_-k2I.js (Deleted) -10.22kB 0 bytes -100.0% 🗑️
assets/app.config-generator-CD8EbFqg.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-BPsG3NI-.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-_yugMxF4.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-DZInFXYk.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-C4XgA2SB.js (Deleted) -6.74kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-TYeSYWhi.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-pa2sM7Aw.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DW47d8zs.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-CaHfOrgH.js (Deleted) -6.06kB 0 bytes -100.0% 🗑️
assets/docs.index-CkfaheGH.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-Ckg1gC3E.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-7ima7Aq2.js (Deleted) -2.93kB 0 bytes -100.0% 🗑️
assets/api-CoeevdYE.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-lt63N7xs.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-oIj3aNct.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-sPiCHKS8.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-ibA9-M6P.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-BE89HZTy.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-ZiI6IT0X.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-z9n0uxio.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BoQwmeTx.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-DuBluKDb.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-DhptOC8r.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-CeO1KpiV.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-DIWCSuER.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-_lTLVrqf.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-Gw5ZDj3v.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-WD9haHN-.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-BbcWnC5k.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-DZBlDl7F.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-PdGAXEaH.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-FZ5viRKr.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-CwFAEqHL.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BxSljzj4.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DEaQMQ7G.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-xItiYNz_.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-Dp6zYKsI.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-B_yC31cr.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-BHvWflKw.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-5ne5Q6nV.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-BfjsYT4U.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-tGyBe_6N.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.76%. Comparing base (af98111) to head (59b7643).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9869   +/-   ##
=======================================
  Coverage   91.76%   91.76%           
=======================================
  Files         921      921           
  Lines      113173   113195   +22     
  Branches    27244    27256   +12     
=======================================
+ Hits       103854   103876   +22     
  Misses       8034     8034           
  Partials     1285     1285           
Flag Coverage Δ
backend 95.68% <100.00%> (+<0.01%) ⬆️
engine 71.88% <100.00%> (+0.01%) ⬆️

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.31% <100.00%> (+0.04%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/queue/processors.ts 94.81% <100.00%> (+<0.01%) ⬆️
src/settings/agent-actions.ts 98.26% <100.00%> (+0.01%) ⬆️
src/settings/pr-disposition.ts 100.00% <100.00%> (ø)
src/signals/focus-manifest.ts 99.61% <100.00%> (+<0.01%) ⬆️
src/types.ts 100.00% <ø> (ø)

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 29, 2026
@JSONbored
JSONbored force-pushed the feat/guardrail-mode branch 2 times, most recently from a1367ff to f970cea Compare July 29, 2026 19:29
… hold when the escalated review is clean

The second half of #9808, as an explicit MODE rather than a policy flip.

review -- but the disposition still held it unconditionally: guardrailHit sat
in heldForManualReview regardless of what the escalated review found. The 74
held PRs / 14 days kept landing on the maintainer, just with better reviews
attached.

gate.guardrailEscalation.onCleanReview:
  hold     (default) today's behavior exactly -- human-in-the-loop
  proceed  a clean escalated review releases the guardrail hold and the normal
           approve/merge path continues -- full-autonomy mode

Because it is a manifest field it layers global -> per-repo like everything
else, so repos can be flipped one at a time and the mode is reversible without
a deploy.

Fail-closed on every axis:
 - `proceed` is inert unless at least one escalation knob is actually SET: the
   release is justified by extra scrutiny, so absent scrutiny nothing vouches.
 - the release requires reviewGood -- gate success (which folds in the AI
   verdict's blockers) AND green CI -- and the disposition re-checks that
   independently of the caller's flag.
 - only the guardrail term is released: migration collisions, unlinked-issue
   holds, advisory-check holds, and unstable merge states all still hold.
 - the manual-hold reason no longer claims "guarded path -> manual review" for
   a cleared PR.
…t merges it

The manual-review label announces a HOLD. Its emit condition checked
`guardrailHit` alone, so once a clean escalated review released that hold
(guardrailEscalationCleared), the planner emitted both in one pass: the merge
AND the label telling a human to come look at it. Self-contradictory, and it
leaves a manual-review label sitting on merged PRs in precisely the
full-autonomy mode the feature exists to enable.

Also closes the two patch-coverage gaps this PR still carried, both of them
the "test both sides" class:

- agent-actions: no test made guardrailHit, reviewGood and escalationConfigured
  true at once, so the fourth conjunct -- `onCleanReview === "proceed"`, the
  term the whole feature turns on -- was never evaluated at all. Now asserted
  in both directions against identical input, plus the case where "proceed" is
  set with NO escalation configured, which must NOT release (that promise is
  about an escalated review; honouring it without one would leave guarded paths
  weaker than before the feature).

- focus-manifest: onCleanReview was covered through parse but not through
  resolveEffectiveSettings. Parsing it and dropping it during resolution would
  silently restore always-hold while the manifest still read as configured for
  full autonomy.
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.

feat(gate): guardrail escalation — guarded paths get a stronger, multi-run AI review instead of a manual hold

1 participant