Skip to content

feat(ledger): let an operator declare historical failed appends, so /verify can go green again - #9935

Merged
JSONbored merged 1 commit into
mainfrom
fix/ledger-unchained-waiver
Jul 30, 2026
Merged

feat(ledger): let an operator declare historical failed appends, so /verify can go green again#9935
JSONbored merged 1 commit into
mainfrom
fix/ledger-unchained-waiver

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #9933

The problem, measured on the production ORB (orb-v3.7.0-beta.1, 2069 rows)

/v1/public/decision-ledger/verify reports ok:false and cannot be made honest-green, because one of its two findings has no declaration mechanism.

83 content mismatches — already declarable. All in seq 5..257, all in schemaVersion 2 and 3; every one of the 1,569 v5 rows verifies clean. I recomputed the digests offline with the repo’s own canonicalJson/contentDigest to confirm the count and range exactly, then brute-forced every subset of null-valued keys per row (the obvious "stored as null, hashed as undefined" hypothesis) — no subset reproduces the committed digest for any of the 83. Genuinely unrecoverable preimages, which LOOPOVER_LEDGER_CONTENT_WAIVER (#9850) already covers.

231 unchained records — not declarable. decision_records rows no ledger row ever vouched for, between 2026-07-04 and 2026-07-24, none since. Historical residue from a durability bug that has stopped, against a structurally perfect chain (the tip chains correctly; no sequence_gap, predecessor_mismatch or row_hash_mismatch anywhere).

The content waiver deliberately never applies to chain findings — correct, since a waived row that is mis-chained must still fail. But that leaves the endpoint permanently red for a known, bounded, already-fixed cause, and permanently red is indistinguishable from newly-broken, so a real failed append tomorrow would be invisible.

The fix

LOOPOVER_LEDGER_UNCHAINED_WAIVER, format <fromIso>..<toIso>|<maxRecords>|<reason>, mirroring #9850’s four properties. Two deliberate differences, both argued in the parser’s doc comment:

  • Time-bounded, not seq-bounded. An orphan is defined by having no ledger row, so it has no sequence number to name. ledger: /verify reports ok:false — content_mismatch at seq 5 on a 3-day-old chain #9850’s rule against time bounds targets boundaries that move — a relative window swallows new damage as the clock advances. Both bounds here are absolute instants, so the interval is exactly as fixed as a seq range.
  • A declared maxRecords, which closes what a seq range gets for free: a time interval could come to contain more orphans than were counted. Exceeding the declared count makes the whole waiver stop applying, so a new (or backdated) failed append cannot hide behind an old declaration.

Pipe-delimited because ISO-8601 instants contain colons.

A truncated tail stays unwaivable

Writing that invariant test caught a real flaw in my own first implementation: it excluded the waiver window in SQL before distinguishing an interior orphan from a short_tail, which silently waived the truncated-tail signature — precisely the attack the doc promised was never waivable. Every waiver predicate now carries the interior test, so the count and the break search agree on which rows are covered.

Tests

parseLedgerUnchainedWaiver: bounded parse, pipes preserved inside a reason, mandatory reason, both bounds required, descending range rejected, non-positive/fractional/12abc counts rejected, fail-closed on malformed.

Applied behaviour: a declared orphan verifies clean and is disclosed (waivedUnchainedRecords + unchainedWaiver, never folded into silence); one more orphan than declared makes the whole waiver lapse; an orphan outside the window is still reported; a short_tail inside the window is still reported; no waiver is byte-identical to before; a malformed waiver waives nothing. Plus preflight coverage for the set-but-unparseable case.

Changed-line coverage measured at zero uncovered statements and zero uncovered branches.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 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 1007552 Commit Preview URL

Branch Preview URL
Jul 30 2026, 05:59 PM

@loopover-orb

loopover-orb Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-30 18:22:11 UTC

8 files · 1 AI reviewer · no blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR adds a second, orthogonal waiver (`LOOPOVER_LEDGER_UNCHAINED_WAIVER`) for interior unchained decision_records, mirroring the existing content waiver's four properties but substituting an absolute time range + declared max-count guard for a seq range, since orphans have no sequence number. The SQL threading is careful: the 'covered' count query and the orphan-search exclusion clause use matching predicates (window bounds + `<= lastVerifiedCreatedAt` as the interior test + `<= graceCutoffIso`), so a short_tail record (created_at > lastVerifiedCreatedAt) is never excluded by the waiver and stays reported, exactly as the doc comments claim and the tests verify. The change is directly tied to and closes the linked issue #9933, is heavily covered by both unit tests (parser edge cases, interior/tail/outside/over-count invariants) and a preflight config check, and every one of the ~10 `verifyDecisionLedger` return sites was updated in lockstep with the two new fields.

Nits — 5 non-blocking
  • src/review/decision-record.ts: the `covered` query repeats `r.created_at <= ?` three times (toIso, lastVerifiedCreatedAt, graceCutoffIso) — computing the effective min bound once in JS before binding would be a bit more readable, though functionally equivalent.
  • The CI checks 'validate', 'validate-code', 'validate-tests', 'Build UI preview artifact', and 'Workers Builds: loopover-ui' failed with no detail provided in the status given here, so the cause could not be verified from what was shown — worth checking the actual failure output before merging.
  • src/review/decision-record.ts: `maxRecords` guard uses `<=` so exactly `maxRecords` orphans waive cleanly and `maxRecords+1` goes fully red — this cliff behavior is intentional per the docs, but worth double-checking operators understand a single unexpected orphan turns the whole waiver off rather than degrading gracefully.
  • Consider a short table-driven test asserting `waivedUnchainedRecords` stays at exactly `maxRecords` when the covered count equals maxRecords exactly (currently only tested at max-1 and max+1 boundaries).
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9933
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 (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 9 registered-repo PR(s), 8 merged, 303 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 9 PR(s), 303 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The PR adds LOOPOVER_LEDGER_UNCHAINED_WAIVER mirroring the content waiver with absolute time bounds, a mandatory reason, and a maxRecords fail-closed guard, publishes waivedUnchainedRecords/unchainedWaiver separately from the real count, keeps short_tail and other structural findings unwaivable while only excluding interior unchained_record orphans, and adds preflight validation plus tests coverin

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: not available
  • Official Gittensor activity: 9 PR(s), 303 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
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 &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; 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: ai_consensus_defect
  • config: b0183d357b7bce682c2a9c21c854252306891cf6c2af6e424ba53550c5d7a6eb · pack: oss-anti-slop · ci: passed
  • model: claude-code · prompt: 63a85ceb2d21c2dd9f7a1bab6ab8bee9abc6da7ccc9907f9053d1b4e747321d9 · confidence: 0.5
  • record: 424df3ddb78082e5eaf76469230aaefc57aff4eef9a2e9691b0a6ce0858c4c94 (schema v6, head 1007552)
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 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.00%. Comparing base (edf7b37) to head (1007552).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9935      +/-   ##
==========================================
- Coverage   91.87%   91.00%   -0.88%     
==========================================
  Files         923      923              
  Lines      113436   113461      +25     
  Branches    27331    27343      +12     
==========================================
- Hits       104219   103252     -967     
- Misses       7928     9117    +1189     
+ Partials     1289     1092     -197     
Flag Coverage Δ
backend 94.12% <100.00%> (-1.56%) ⬇️

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

Files with missing lines Coverage Δ
src/openapi/spec.ts 99.27% <ø> (ø)
src/review/decision-record.ts 100.00% <100.00%> (ø)
src/selfhost/preflight.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 30, 2026
…verify can go green again

The public verifier on the production ORB reports ok:false and cannot be made
honest-green, because one of its two findings has no declaration mechanism.

Measured there (2069 rows):

- 83 content mismatches, all in seq 5..257, all in schemaVersion 2 and 3 --
  every one of the 1,569 v5 rows verifies clean. Recomputed offline with the
  repo's own canonicalJson/contentDigest to confirm the count and range, then
  brute-forced every subset of null-valued keys per row (the obvious "stored
  as null, hashed as undefined" hypothesis): no subset reproduces the
  committed digest for any of the 83. Those preimages are genuinely
  unrecoverable, which LOOPOVER_LEDGER_CONTENT_WAIVER (#9850) already covers.

- 231 unchained records -- decision_records rows no ledger row ever vouched
  for -- between 2026-07-04 and 2026-07-24, and NONE since. Historical residue
  from a durability bug that has stopped, against a structurally perfect chain.
  The content waiver deliberately cannot cover this (chain findings are never
  waivable), so there was no way to declare it.

A permanently-red integrity endpoint is the worst possible signal: "red" stops
distinguishing old damage from a real failed append tomorrow, which is the
exact thing the check exists to catch.

LOOPOVER_LEDGER_UNCHAINED_WAIVER declares it, mirroring #9850's four
properties. Two differences, both forced and both argued in the parser's doc:

- TIME-bounded, not seq-bounded, because an orphan has no ledger row and
  therefore no sequence number. #9850's rule against time bounds is about
  boundaries that MOVE; both bounds here are absolute instants, so the interval
  is exactly as fixed as a seq range.
- A declared maxRecords closes what a seq range gets for free. A time interval
  could come to contain more orphans than were counted, so exceeding the
  declared count makes the WHOLE waiver stop applying -- a new failed append
  cannot hide behind an old declaration.

A truncated TAIL stays unwaivable. Writing the test for that caught the first
implementation excluding the window in SQL before distinguishing interior from
tail, which silently waived the very attack signature the doc promised was
never waivable; every waiver predicate now carries the interior test.
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 913 bytes (0.01%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.84MB 913 bytes (0.01%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-DCBwHl1n.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-C_JJTCpD.js (New) 954.12kB 954.12kB 100.0% 🚀
openapi.json 422 bytes 758.15kB 0.06%
assets/docs.fumadocs-spike-api-reference-CJXdiJYP.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-ChznU_bT.js (New) 201.69kB 201.69kB 100.0% 🚀
assets/modal-D8MkZNWo.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-BwOa0FpN.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/self-hosting-configuration-Cfy8pMsY.js (New) 116.47kB 116.47kB 100.0% 🚀
assets/maintainer-panel-WE5GIWkZ.js (New) 78.97kB 78.97kB 100.0% 🚀
assets/routes-CFwUcuKA.js (New) 35.88kB 35.88kB 100.0% 🚀
assets/owner-panel-DwERlVyS.js (New) 28.26kB 28.26kB 100.0% 🚀
assets/app-CWjk07nf.js (New) 25.82kB 25.82kB 100.0% 🚀
assets/ui-vendor-CVP2eJng.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-DCVRyzn6.js (New) 20.49kB 20.49kB 100.0% 🚀
assets/fairness-BZwHz3j8.js (New) 19.37kB 19.37kB 100.0% 🚀
assets/api._op-DZqhCvSM.js (New) 17.51kB 17.51kB 100.0% 🚀
assets/self-hosting-docs-audit-BlnKdRAx.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-BCynTLab.js (New) 15.63kB 15.63kB 100.0% 🚀
assets/playground-panel-DVmHzBMX.js (New) 14.35kB 14.35kB 100.0% 🚀
assets/app.audit-CHJ8AkIa.js (New) 10.22kB 10.22kB 100.0% 🚀
assets/app.config-generator-DsvfNWQB.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-C6VnDNnW.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-ljnZUFWw.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-DIfoFbC3.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-B-3mfJHO.js (New) 6.74kB 6.74kB 100.0% 🚀
assets/maintainer-workflow-jrekOIf3.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-BDWonmKD.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Dt3NIm8k.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-DJ5Yoo7Q.js (New) 6.06kB 6.06kB 100.0% 🚀
assets/docs.index-Bxxunsxb.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-BnuMBtUe.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-CnB5Y4uk.js (New) 2.93kB 2.93kB 100.0% 🚀
assets/api-4hr5cqxd.js (New) 2.66kB 2.66kB 100.0% 🚀
assets/docs-page-BsYBngsG.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-B9ILo6Bc.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-aHy-hsiX.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-m9NB7AQb.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-D1VzcTS5.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-DlU1kCwA.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-B48XTzb8.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-DgfEt9Ck.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-Bduzcape.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-YwoZszPU.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-DJtxowiy.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-DvDC4zeo.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-Cgl3bEJl.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-DkswwFZZ.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-DxdDTEN3.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-Div9vOJJ.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-ClgeNio_.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/git-pull-request-arrow-BsaM4GEu.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BGOTcpg0.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-CKUiCoGv.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-gS41X2ZR.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-kFhXNqAq.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CpQ7khwr.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/play-j5OmoLCm.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-Cr51v0v9.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/add-scalar-classes-CrnBCxCL.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-D7fsyBSA.js (Deleted) -953.69kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-DmHkklDD.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-BsmReu79.js (Deleted) -201.69kB 0 bytes -100.0% 🗑️
assets/modal-Bz6LZpR4.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-KQauf23M.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/self-hosting-configuration-DwCFaekz.js (Deleted) -116.4kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-DoMMdhmN.js (Deleted) -78.97kB 0 bytes -100.0% 🗑️
assets/routes-BNAoA6Dw.js (Deleted) -35.88kB 0 bytes -100.0% 🗑️
assets/owner-panel-_N9sxAE7.js (Deleted) -28.26kB 0 bytes -100.0% 🗑️
assets/app-B2_J5-dK.js (Deleted) -25.82kB 0 bytes -100.0% 🗑️
assets/ui-vendor-BILpQtaj.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-DCFuyYaj.js (Deleted) -20.49kB 0 bytes -100.0% 🗑️
assets/fairness-DTSShFPs.js (Deleted) -19.37kB 0 bytes -100.0% 🗑️
assets/api._op-CEIS-ove.js (Deleted) -17.51kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-DjzxbOcC.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-DBc8EQku.js (Deleted) -15.63kB 0 bytes -100.0% 🗑️
assets/playground-panel-CSzjWglB.js (Deleted) -14.35kB 0 bytes -100.0% 🗑️
assets/app.audit-DrzNqLgM.js (Deleted) -10.22kB 0 bytes -100.0% 🗑️
assets/app.config-generator-wRjl3vQC.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-B_gsljqC.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-BnanHzHx.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-CpYZdhgk.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-XztetXDd.js (Deleted) -6.74kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-CXNgr2za.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-CcUs8S-F.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-GszCvJV_.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-Lhew49gS.js (Deleted) -6.06kB 0 bytes -100.0% 🗑️
assets/docs.index-BBRcPaqy.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-ZP3Ep2wW.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-D0Xf2uwB.js (Deleted) -2.93kB 0 bytes -100.0% 🗑️
assets/api-CxYbEv4_.js (Deleted) -2.66kB 0 bytes -100.0% 🗑️
assets/docs-page-DQOt8yJX.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-C8Ne4qwj.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-BDWN2ee_.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-QigYkRIo.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-A1bmwbyt.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-lP3QZYrs.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-5Mk5992K.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-v3g1kERL.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-CzCbCNep.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-DmeH3qMx.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-xm_WO6pW.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-C6fasRh7.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-D9_CGGKw.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-CLuSTWea.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-DJPwKwt9.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round--HXUT99k.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-B8av5H6a.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-DTbixhYU.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BcXT25G1.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-M5YjrKDr.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-B793qYPB.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-B3sKB7cf.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-DKypXJhU.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/play-CdFqq3Up.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CEK7ta-G.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️

@JSONbored

Copy link
Copy Markdown
Owner Author

@Loopover review

@JSONbored
JSONbored merged commit 7e0897c into main Jul 30, 2026
12 checks passed
@JSONbored
JSONbored deleted the fix/ledger-unchained-waiver branch July 30, 2026 18:30
loopover-orb Bot pushed a commit that referenced this pull request Jul 30, 2026
…ging the merge train (#9942)

Two independent reasons an autonomous queue stalls with no human in it, both
found on live PRs.

1) The manual-review label was a ONE-WAY LATCH.

The sibling-label cleanup deliberately refuses to touch it, because the same
label string is also a maintainer's manual freeze and the planner had no way to
tell them apart -- and silently undoing a human's freeze is far worse than
leaving a stale hold. That caution was right; the missing piece was provenance.

Seen on #9935: the label went on when a finding fired against a stale head, a
rebase removed the cause, a fresh escalated review returned ZERO findings, and
the PR was mergeable, green and clean -- and still would not merge:

  agent.action.merge | denied | manual-review label "manual-review" is present
                               on the live PR -- merge not executed

It took a human removing the label by hand, in the mode that is supposed to
have no human in it.

The planner now records the head SHA and reason when IT applies the label, and
may lift it once manualHoldReason is null (no reason wants a hold this pass, so
a label applied for reason A can never be lifted by reason B clearing). A label
with NO provenance -- a human's, or one predating this -- is never touched.
That asymmetry is the whole safety property.

2) A draft sibling wedged the merge train.

The train already evicts conflicted and manual-review-held siblings on the
principle that waiting for a PR which is not trying to reach merge buys nothing
the 24h cap does not already bound far worse. A DRAFT is the strongest form of
that signal: GitHub itself refuses to merge one, so it is not one merge away
from landing, it is one author action away from being eligible at all.

The production shape: a maintainer PR with red CI cannot be auto-closed
(maintainers are exempt on purpose, so they can iterate), so it stays open and
overlapping for as long as the fix takes -- holding every newer overlapping PR
behind it, green and approved, for up to the full 24 hours. Marking it draft is
the author saying "skip me", and the train now understands that.

Eviction removes ONE PR from the queue; it does not disable the gate. A real
older sibling behind the draft still holds the line.
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.

A historical failed-append leaves /verify permanently red with no way to declare it

1 participant