Skip to content

fix(trust): unauthenticate ledger verify, catch tail truncation, publish records, record every close (#9120 #9122 #9123 #9134) - #9172

Merged
JSONbored merged 1 commit into
mainfrom
fix/decision-ledger-9120-9122-9123-9134
Jul 27, 2026
Merged

fix(trust): unauthenticate ledger verify, catch tail truncation, publish records, record every close (#9120 #9122 #9123 #9134)#9172
JSONbored merged 1 commit into
mainfrom
fix/decision-ledger-9120-9122-9123-9134

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Four linked issues on the decision-ledger/decision-record trust surface, fixed together since they touch the same small set of files (src/review/decision-record.ts, src/api/routes.ts, src/queue/processors.ts, src/services/agent-action-executor.ts, src/services/agent-approval-queue.ts).

Closes #9120
Closes #9122
Closes #9123
Closes #9134

#9120 — public decision-ledger verify endpoint required an API token (401 in prod)

/v1/public/decision-ledger/verify's own doc comment always claimed "safe unauthenticated," but the route was missing from requiresApiToken's exemption list in src/api/routes.ts, so it 401'd in prod while its siblings (/v1/public/subnet-interface, /v1/public/stats, .../quality) answered 200.

  • Added the missing exemption.
  • Added a table-driven test (test/integration/public-decision-ledger-routes.test.ts) over every currently-exempt /v1/public/* route, asserting each answers without credentials, plus a negative control proving the table isn't vacuously passing (a genuinely protected route still 401s).
  • Audited every other /v1/public/* route against its own doc comment vs. the exemption list: all others were already correctly exempted (subnet-interface, stats, github/repos/.../stats, repos/.../badge.{svg,json}, repos/.../quality) — this route was the only mismatch.
  • Deferred, not fixed here: the self-hosted-instance tunnel-exposure half of orb(trust): the 'public' decision-ledger verify endpoint requires an API token — 401 in prod, so the chain is verifiable by nobody #9120 is an infra/ops decision, out of scope for a code PR.

#9122 — no external anchor, verify returns no hash, tail truncation passes clean

verifyDecisionLedger proved only self-consistency and returned nothing an observer could persist/re-compare, and a DELETE FROM decision_ledger WHERE seq > N (dropping the newest rows) verified perfectly clean since nothing in the remaining window was disturbed.

  • verifyDecisionLedger now returns tipSeq, tipHash, and totalCount on every call, so a third party can checkpoint independent of pagination position.
  • Added a genuine, code-only tail-truncation catch: decision_ledger and decision_records are written together in the same call (persistDecisionRecord appends its ledger row immediately after inserting the record), so deleting ledger rows can never touch the records they describe. On the last page of a verify pass, a decision_records row created strictly after the verified tip with no chain entry covering it is exactly the signature of a truncated tail — now reported as a new short_tail break, not ok: true. Regression test included (decision-record.test.ts), using fake system time to keep the timestamp ordering deterministic rather than relying on real wall-clock speed.
  • buildLedgerAnchorPayload({seq, rowHash}, at?) — the exact, documented shape a future periodic external-anchoring job (git commit / transparency log / on-chain commitment) would publish. Not wired to a scheduler; this repo doesn't call it from anywhere yet.
  • Propagated the honest tamper-evident-not-tamper-proof limit (already stated in migrations/0180_decision_ledger.sql's header) into decision-record.ts's own module header, and corrected the overclaiming route comment in routes.ts (it previously implied the endpoint could rule out a wholesale chain rewrite, which it cannot without an external anchor).
  • Deferred, explicitly: real external anchoring (a live git-commit publish job, a transparency log, an on-chain commitment) is a genuinely open infra/protocol decision, intentionally not built here — only the cheap, code-side shape (buildLedgerAnchorPayload) is included.

#9123 — decision record never published; 12-char digest prefixes; re-gate at the same head destroyed the prior record

The record was persisted (decision_records) but never exposed anywhere beyond a bounded, truncated review-comment summary, and persistDecisionRecord used ON CONFLICT(id) DO UPDATE, so a re-gate at the same head SHA silently overwrote the earlier record body while the ledger still held a chain row pointing at the now-destroyed digest.

  • Added GET /v1/public/decision-records/:owner/:repo/:pull, returning the latest record_json verbatim + its record_digest. DecisionRecord is public-safe by construction (its own type doc: counts/digests/enums only, no diffs, no private config contents, no wallet/hotkey/trust-score/reward fields) — no field-level redaction was needed before exposing it. Added to the same requiresApiToken exemption list as orb(trust): the 'public' decision-ledger verify endpoint requires an API token — 401 in prod, so the chain is verifiable by nobody #9120's fix, in this same PR.
  • renderDecisionRecordSection now prints full 64-hex digests instead of 12-char prefixes (the head SHA keeps its conventional 7-char git abbreviation — a display convention, not a digest commitment).
  • persistDecisionRecord no longer overwrites: the first record for a (repo, pull, head) keeps the plain record:<repo>#<pr>@<head> id every existing consumer expects; a supersession at the exact same head gets its own row at <baseId>:rev<N> instead, so a digest the ledger already chained always keeps a live preimage. Returns the id it actually wrote so persistDecisionReplayInputForGate (the private replay-input sibling) targets the correct row, including on a supersession.
  • Documented, not backfilled: the 51 already-superseded live rows referenced in the issue cannot be recovered — their bodies were already destroyed by the old UPDATE before this fix landed. This is a documentation note, not a data migration.

#9134 — six of seven actuation sites wrote no decision record

buildDecisionRecord/persistDecisionRecord ran at exactly one of executeAgentMaintenanceActions's seven call sites. The other six — a contributor-cap close on PR open, two review-nag closes (ping-count and monitored-mention), an approval-queue accept (merge/close), and two forced-rebase update_branch calls — performed real GitHub mutations with no record and no ledger row, biasing loadCalibrationPairs's label population away from exactly the closes a contributor is most likely to dispute.

  • Hoisted record emission into executeAgentMaintenanceActions itself: every completed merge/close action now emits a decision record + chained ledger row by construction, regardless of which of the seven call sites produced it.
  • Added a decisionRecord: DecisionRecordContext field to AgentActionExecutionContext, made required (not optional) so a new call site cannot compile without deciding what its record should carry. The one call site that already built its own record unconditionally at plan time (covering merge/close/hold dispositions, since a hold never even reaches this executor) opts out via an explicit { managedByCaller: true } — a deliberate, self-documenting override, not a silent omission, to avoid double-recording the same decision.
  • The single most valuable test in this PR: a structural invariant test (agent-action-executor.test.ts) that exercises the executor directly across every action class and asserts the decision-record/ledger-row invariant holds for all of them at once — a future call site that adds a new action class without accounting for this would fail here, not silently ship unrecorded.
  • One regression test per previously-uncovered close path, asserting a record + ledger row now exist: contributor-cap-on-open (queue-3.test.ts), both review-nag variants (queue-5.test.ts), and the approval-queue's staged merge and staged close (agent-approval-queue.test.ts).
  • I did not assume a parallel PR (orb(breaker): #9086's review_nag breaker coverage is inert — both nag close sites skip the breaker entirely, and each close is scored as a merge misprediction #9132, extracting processors.ts:3322-3410 for the two review-nag sites) had landed — git log on rebase showed no such merge, so this fix composes at the shared executor level regardless of landing order.
  • Deferred, operational: recomputing the risk-control label population and republishing the certified-guarantee page with the now-complete record set is an operational follow-up, not part of this code PR.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused (all four issues touch the same decision-ledger/decision-record surface and the same small file set) and does not mix in unrelated backend, UI, MCP, docs, dependency, or deploy changes.
  • This follows CONTRIBUTING.md and does not touch site/, CNAME, or **/lovable/**.
  • Linked issues: Closes #9120, Closes #9122, Closes #9123, Closes #9134 (all currently open).

Validation

  • git diff --check
  • npm run typecheck (clean, full repo)
  • npm run actionlint — not run (no workflow files touched); skipped per explicit instruction to skip the full local gate for this PR and rely on CI
  • npm run test:coverage — not run as the full/unsharded suite; instead ran targeted vitest run --coverage scoped to every touched file (decision-record.ts 100% stmts/branches/functions/lines; agent-action-executor.ts, agent-approval-queue.ts, processors.ts, routes.ts diffs all 100% line+branch covered when run against their existing + new test files) — see "If any required check was skipped" below
  • npm run test:workers — not run, per the same instruction
  • npm run build:mcp / npm run test:mcp-pack — not run; this PR does not touch packages/loopover-mcp
  • npm run ui:openapi:check (regenerated apps/loopover-ui/public/openapi.json via npm run ui:openapi, then verified clean)
  • npm run ui:lint / npm run ui:typecheck / npm run ui:build — not run; this PR touches no apps/loopover-ui/** files
  • npm audit --audit-level=moderate — not run, per the same instruction
  • New/changed behavior has unit/integration tests for new branches, fallback paths, and the auth exemption boundary (see per-issue notes above)

If any required check was skipped, explain why:

  • Per explicit direction for this task, I did not run the full local gate (npm run test:ci, npm audit, etc.) before pushing — CI runs the gates. I instead ran npm run typecheck (clean) and targeted vitest run / vitest run --coverage against every touched test file and every file I changed, confirming 100% line+branch coverage on my actual diff in each case. I also discovered, and separately flagged (not fixed here, out of scope), one pre-existing test failure on origin/main (test/unit/queue-4.test.ts's "debounces noisy PR events" test) introduced by an unrelated recent commit (fix(webhook): catch a base retarget with no new commit, tighten event health, and stop label/issue churn from going stale #9169) — reproduced identically with this PR's changes fully reverted, so it predates and is unrelated to this PR.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed. DecisionRecord (now published verbatim via the new route) was verified public-safe against its own type doc before exposing it.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth changes (the requiresApiToken exemption list) include negative-path tests (the genuinely-protected-route 401 control, plus per-route exemption assertions).
  • API/OpenAPI behavior is updated and tested: new route registered in src/openapi/spec.ts, spec regenerated and checked.
  • UI changes use live API data — N/A, no UI changes in this PR.
  • UI Evidence — N/A, backend/API-only change, no visible UI/frontend/docs changes.
  • Public docs/changelogs — N/A; CHANGELOG.md not touched.

UI Evidence

Not applicable — this PR is backend/API-only (no apps/loopover-ui/** changes).

Notes

  • Maintainer PR (JSONbored is the repo owner) — the one-shot contributor auto-close gate does not apply, but every other CI/coverage bar was targeted in full per the touched-file coverage checks above.

…tion, publish decision records, and record every completed merge/close (#9120, #9122, #9123, #9134)

Four linked issues on the decision-ledger/decision-record trust surface:

- #9120: /v1/public/decision-ledger/verify was missing from the requiresApiToken
  exemption list and 401'd in prod despite its own doc comment always claiming
  otherwise. Added the exemption, table-driven tests over every /v1/public/*
  exemption, and audited every other public route (all already exempted).

- #9122: verifyDecisionLedger now returns tipSeq/tipHash/totalCount so a third
  party can checkpoint, and catches tail truncation (DELETE ... WHERE seq > N)
  by cross-checking decision_ledger against decision_records — deleting ledger
  rows never touches the records they describe, so an orphaned record past the
  verified tip is the tell. Propagated the tamper-evident-not-tamper-proof
  honest limit into decision-record.ts's module header and corrected the
  overclaiming route comment. Added buildLedgerAnchorPayload as the documented,
  not-yet-wired shape a future external-anchoring job would publish.

- #9123: added GET /v1/public/decision-records/:owner/:repo/:pull, publishing
  the full record + digest (DecisionRecord is public-safe by construction).
  Full 64-hex digests now render in the review panel instead of 12-char
  prefixes. persistDecisionRecord no longer UPDATEs a record body in place —
  a re-persist at the same (repo, pull, head) gets its own revisioned row, so
  a chained digest always keeps a live preimage.

- #9134: hoisted decision-record + ledger-row emission into
  executeAgentMaintenanceActions itself, so every completed merge/close gets
  one by construction instead of depending on the call site remembering to.
  Six of seven call sites previously wrote nothing at all.

Deferred (noted in the PR): self-hosted tunnel exposure (#9120, ops decision);
real external anchoring beyond the documented payload shape (#9122); backfilling
the 51 already-superseded live records (#9123, undoable); recomputing the risk-
control label population (#9134, operational follow-up).
@superagent-security

Copy link
Copy Markdown
Contributor

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

@cloudflare-workers-and-pages

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 fddfc0a Commit Preview URL

Branch Preview URL
Jul 27 2026, 05:32 AM

@JSONbored
JSONbored merged commit c0ea1ac into main Jul 27, 2026
6 of 7 checks passed
@JSONbored
JSONbored deleted the fix/decision-ledger-9120-9122-9123-9134 branch July 27, 2026 05:34
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
loopover-ui 7.43MB 2.24kB (0.03%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-D9g1Fa59.js (New) 2.17MB 2.17MB 100.0% 🚀
assets/tanstack-vendor-DnkLsFdE.js (New) 803.86kB 803.86kB 100.0% 🚀
openapi.json 1.42kB 531.66kB 0.27%
assets/docs.fumadocs-spike-api-reference-0ztk97XB.js (New) 442.88kB 442.88kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-BHZ0IJit.js (New) 201.71kB 201.71kB 100.0% 🚀
assets/modal-8H71opET.js (New) 184.39kB 184.39kB 100.0% 🚀
assets/client-Cm8D67sn.js (New) 146.06kB 146.06kB 100.0% 🚀
assets/maintainer-panel-B9SzvtGe.js (New) 79.0kB 79.0kB 100.0% 🚀
assets/routes-B-Vrl-QJ.js (New) 35.77kB 35.77kB 100.0% 🚀
assets/owner-panel-DxWng-26.js (New) 27.52kB 27.52kB 100.0% 🚀
assets/app-BpgBSXQr.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-BXVVHffv.js (New) 22.28kB 22.28kB 100.0% 🚀
assets/miner-panel-Sy8R21qR.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-BG-0Zh8P.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-P4aqu0cS.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-C12gdBz7.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-DlszAMG3.js (New) 15.37kB 15.37kB 100.0% 🚀
assets/playground-panel-BCBXtf3k.js (New) 14.43kB 14.43kB 100.0% 🚀
assets/fairness-BWnZEdTN.js (New) 10.6kB 10.6kB 100.0% 🚀
assets/app.audit-CM6lw47g.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-CyhZzWz5.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-DG_BL-hH.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-qEW09Rev.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-BjwDd_mz.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-CbdHzyhO.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-Dh44JmDV.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-BpAPfiwv.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-1nF_oONZ.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-Cc2R9cDa.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/docs.index-Dscb7lt8.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-u0bGGwRV.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-qvuObKXy.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-DGwUwjVD.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-CNOTn-Bd.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-DtAhNeU6.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-C-Fg1LB5.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-D6uNOL2O.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-BK6ZsL9F.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-DSfrlVnr.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-7oXekdl3.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-DMUxL_5f.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-CdoT8vGh.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-74zCT5cr.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-D3lEg0YX.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-B3IPBxd5.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-Dw4nX0hz.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-DmwNXg-3.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-DcA9YYCl.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-6t9LTLIr.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-BWrz5SQb.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-D81GerdE.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-BJxiYM4U.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-BxtXOyhL.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BVN-8CO6.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-Dke7Npn7.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-pQdMfT1o.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-D22CB_-s.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-RhisjeSL.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DeXnNgDx.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-B_mgMJHM.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-PzqNs3V3.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-BMI3cqcW.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-DeJbEOwJ.js (Deleted) -2.17MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-B4ZO9PHT.js (Deleted) -803.04kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-DcCvObhL.js (Deleted) -442.88kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-uHDPW65F.js (Deleted) -201.71kB 0 bytes -100.0% 🗑️
assets/modal-5-NrVVLW.js (Deleted) -184.39kB 0 bytes -100.0% 🗑️
assets/client-_bZEbloi.js (Deleted) -146.06kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-CJVQjxPs.js (Deleted) -79.0kB 0 bytes -100.0% 🗑️
assets/routes-C47JiqCj.js (Deleted) -35.77kB 0 bytes -100.0% 🗑️
assets/owner-panel-6Ftbmyvn.js (Deleted) -27.52kB 0 bytes -100.0% 🗑️
assets/app-BUdDr_Hf.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-D2xF876d.js (Deleted) -22.28kB 0 bytes -100.0% 🗑️
assets/miner-panel-GXnGYtZN.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-Jo0oAY0O.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-DeNOpRQh.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-BtnlW0V6.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-qutJjiaX.js (Deleted) -15.37kB 0 bytes -100.0% 🗑️
assets/playground-panel-aIz7NKUH.js (Deleted) -14.43kB 0 bytes -100.0% 🗑️
assets/fairness-C3bjhAXb.js (Deleted) -10.6kB 0 bytes -100.0% 🗑️
assets/app.audit-BDjATJWH.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-BQ1-n0o7.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-Blc3td7Z.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-DWxdTEly.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-44qqnHdb.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-Beinj28u.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-nTasPKP5.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-GqOLdnLa.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-Bi4dDp2v.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-CMsnTZOB.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/docs.index-rOsiynp3.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-CCQCicHH.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-SRrv-kYR.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-D0Q-tQYM.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-9WCvcPVS.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-BUU_BHB4.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-CYrOzsXd.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-sVTAzfrq.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-DZNg7vap.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-UksDPVEw.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-DcRoEUJp.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BJkB6YI6.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-D83LLNqq.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BpbLZ_0a.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-CS405Gta.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-u1KNH_4l.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-BNZ9SuT-.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-D1rX1Mew.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-hS5z6pDy.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-DLJaETYE.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-D2o2bCF9.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-BqEBEFUV.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-CvAigVqp.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-BA8WxGPd.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BHh5fLpu.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DCUAwm_m.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-L7cu4_Du.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-DGVekwJN.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-CNUHKdhV.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-DFXF2jw1.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-R3t2eVq4.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CnLXYsCn.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-i6D7Wmiw.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
21920 6 21914 21
View the top 3 failed test(s) by shortest run time
test/unit/setup-wizard-docs-parity.test.ts > self-host GitHub App manifest <-> docs parity (#2542) > the docs page's events sentence names every buildManifest default_event, and only those
Stack Traces | 0.0153s run time
AssertionError: expected [ 'check run', 'check suite', …(5) ] to deeply equal [ 'check run', 'check suite', …(10) ]

- Expected
+ Received

  [
    "check run",
    "check suite",
-   "deployment status",
-   "issue comment",
    "issues",
    "pull request",
    "pull request review",
-   "pull request review thread",
    "push",
-   "repository",
    "status",
-   "workflow run",
  ]

 ❯ test/unit/setup-wizard-docs-parity.test.ts:65:31
test/unit/backfill.test.ts > GitHub backfill > refreshes installation health from live GitHub App metadata
Stack Traces | 0.0537s run time
AssertionError: expected [ { installationId: 123, …(19) } ] to deeply equal ArrayContaining{…}

- Expected
+ Received

- ArrayContaining [
-   ObjectContaining {
+ [
+   {
      "accountLogin": "JSONbored",
+     "authMode": "local",
+     "checkedAt": "2026-07-27T05:34:13.097Z",
+     "errorSummary": undefined,
+     "eventRemediation": [
+       {
+         "action": "No change needed.",
+         "event": "issues",
+         "ok": true,
+       },
+       {
+         "action": "No change needed.",
+         "event": "issue_comment",
+         "ok": true,
+       },
+       {
+         "action": "No change needed.",
+         "event": "pull_request",
+         "ok": true,
+       },
+       {
+         "action": "Subscribe to the pull_request_review webhook event.",
+         "event": "pull_request_review",
+         "ok": false,
+       },
+       {
+         "action": "No change needed.",
+         "event": "repository",
+         "ok": true,
+       },
+       {
+         "action": "Subscribe to the check_run webhook event.",
+         "event": "check_run",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_suite webhook event.",
+         "event": "check_suite",
+         "ok": false,
+       },
+     ],
+     "events": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "repository",
+       "installation_repositories",
+     ],
      "installationId": 123,
-     "missingEvents": [],
+     "installedReposCount": 0,
+     "missingEvents": [
+       "pull_request_review",
+       "check_run",
+       "check_suite",
+     ],
      "missingPermissions": [],
-     "status": "healthy",
+     "optionalPermissions": {
+       "checks": "write",
+     },
+     "optionalVisibleEvents": [
+       "installation_target",
+       "installation_repositories",
+     ],
+     "permissionRemediation": [
+       {
+         "action": "No change needed.",
+         "currentAccess": "read",
+         "ok": true,
+         "permission": "metadata",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "No change needed.",
+         "currentAccess": "write",
+         "ok": true,
+         "permission": "pull_requests",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "No change needed.",
+         "currentAccess": "write",
+         "ok": true,
+         "permission": "issues",
+         "requiredAccess": "write",
+       },
+     ],
+     "permissions": {
+       "checks": "write",
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "write",
+     },
+     "registeredInstalledCount": 0,
+     "repairSteps": [
+       "Update the GitHub App permissions and subscribed events.",
+       "Approve the changed permissions or reinstall the app on the target account.",
+       "Run refresh-installation-health after GitHub sends the updated installation payload.",
+       "Recheck /v1/readiness and this installation health endpoint.",
+     ],
+     "repositorySelection": "selected",
+     "requiredEvents": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "pull_request_review",
+       "repository",
+       "check_run",
+       "check_suite",
+     ],
+     "requiredPermissions": {
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "read",
+     },
+     "status": "needs_attention",
    },
  ]

 ❯ test/unit/backfill.test.ts:1369:37
test/unit/queue-4.test.ts > queue processors > debounces noisy PR events without publishing public surfaces
Stack Traces | 0.108s run time
AssertionError: expected 3 to be +0 // Object.is equality

- Expected
+ Received

- 0
+ 3

 ❯ test/unit/queue-4.test.ts:2745:25
test/unit/backfill.test.ts > GitHub backfill > uses installation source for queued segment jobs and sparse live installation fallback metadata
Stack Traces | 0.172s run time
AssertionError: expected [ { installationId: 123, …(19) } ] to deeply equal ArrayContaining{…}

- Expected
+ Received

- ArrayContaining [
-   ObjectContaining {
+ [
+   {
      "accountLogin": "JSONbored",
+     "authMode": "local",
+     "checkedAt": "2026-07-27T05:34:19.232Z",
+     "errorSummary": undefined,
+     "eventRemediation": [
+       {
+         "action": "Subscribe to the issues webhook event.",
+         "event": "issues",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the issue_comment webhook event.",
+         "event": "issue_comment",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the pull_request webhook event.",
+         "event": "pull_request",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the pull_request_review webhook event.",
+         "event": "pull_request_review",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the repository webhook event.",
+         "event": "repository",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_run webhook event.",
+         "event": "check_run",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_suite webhook event.",
+         "event": "check_suite",
+         "ok": false,
+       },
+     ],
      "events": [],
      "installationId": 123,
+     "installedReposCount": 1,
      "missingEvents": [
        "issues",
        "issue_comment",
        "pull_request",
+       "pull_request_review",
        "repository",
+       "check_run",
+       "check_suite",
      ],
      "missingPermissions": [
        "metadata",
        "pull_requests",
        "issues",
      ],
+     "optionalPermissions": {
+       "checks": "write",
+     },
+     "optionalVisibleEvents": [
+       "installation_target",
+       "installation_repositories",
+     ],
+     "permissionRemediation": [
+       {
+         "action": "Set repository permission metadata to read.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "metadata",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "Set repository permission pull_requests to read.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "pull_requests",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "Set repository permission issues to write.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "issues",
+         "requiredAccess": "write",
+       },
+     ],
      "permissions": {},
+     "registeredInstalledCount": 1,
+     "repairSteps": [
+       "Update the GitHub App permissions and subscribed events.",
+       "Approve the changed permissions or reinstall the app on the target account.",
+       "Run refresh-installation-health after GitHub sends the updated installation payload.",
+       "Recheck /v1/readiness and this installation health endpoint.",
+     ],
      "repositorySelection": "selected",
+     "requiredEvents": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "pull_request_review",
+       "repository",
+       "check_run",
+       "check_suite",
+     ],
+     "requiredPermissions": {
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "read",
+     },
      "status": "needs_attention",
    },
  ]

 ❯ test/unit/backfill.test.ts:4724:34
test/unit/backfill.test.ts > GitHub backfill > reports installation health from stored permissions and events
Stack Traces | 0.179s run time
AssertionError: expected { installationId: 123, …(19) } to match object { status: 'needs_attention', …(3) }
(22 matching properties omitted from actual)

- Expected
+ Received

@@ -1,10 +1,13 @@
  {
    "missingEvents": [
      "issues",
      "issue_comment",
+     "pull_request_review",
      "repository",
+     "check_run",
+     "check_suite",
    ],
    "missingPermissions": [
      "pull_requests",
      "issues",
    ],

 ❯ test/unit/backfill.test.ts:686:37
test/integration/api.test.ts > api routes > serves installation repair diagnostics and refreshes installation health
Stack Traces | 0.248s run time
AssertionError: expected { …(12) } to match object { refreshed: true, …(2) }
(37 matching properties omitted from actual)

- Expected
+ Received

@@ -1,10 +1,14 @@
  {
    "installation": {
-     "missingEvents": [],
+     "missingEvents": [
+       "pull_request_review",
+       "check_run",
+       "check_suite",
+     ],
      "missingPermissions": [],
-     "status": "healthy",
+     "status": "needs_attention",
    },
    "refreshed": true,
    "requiredPermissions": {
      "checks": "write",
      "issues": "write",

 ❯ test/integration/api.test.ts:2364:35

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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