Skip to content

fix(review): close four testing/coverage-gate blind spots — TTL-aware fixtures, shutdown parity, invariant sweeps, engine coverage#9245

Merged
JSONbored merged 7 commits into
mainfrom
fix/9063-9064-9065-test-gate-integrity
Jul 27, 2026
Merged

fix(review): close four testing/coverage-gate blind spots — TTL-aware fixtures, shutdown parity, invariant sweeps, engine coverage#9245
JSONbored merged 7 commits into
mainfrom
fix/9063-9064-9065-test-gate-integrity

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Test plan

Closes #9063
Closes #9064
Closes #9065

@JSONbored JSONbored self-assigned this Jul 27, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 27, 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 4d88ff8 Commit Preview URL

Branch Preview URL
Jul 27 2026, 09:27 AM

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

❌ 17 Tests Failed:

Tests completed Failed Passed Skipped
22924 17 22907 21
View the top 3 failed test(s) by shortest run time
test/unit/selfhost-pg-retention.test.ts > runRetentionPrune + processJob on the Postgres backend (#977) > processJob prune-retention deletes eligible rows and records a success audit event on Postgres
Stack Traces | 0.0054s run time
AssertionError: expected 3 to be +0 // Object.is equality

- Expected
+ Received

- 0
+ 3

 ❯ test/unit/selfhost-pg-retention.test.ts:119:44
test/unit/loopover-engine-scaffold.test.ts > loopover-engine package scaffold > publishes only the build output and changelog, not source
Stack Traces | 0.012s run time
AssertionError: expected [ 'dist/**/*.js', …(2) ] to deeply equal [ 'dist', 'CHANGELOG.md' ]

- Expected
+ Received

  [
-   "dist",
+   "dist/**/*.js",
+   "dist/**/*.d.ts",
    "CHANGELOG.md",
  ]

 ❯ test/unit/loopover-engine-scaffold.test.ts:30:29
test/unit/selfhost-pg-retention.test.ts > pruneExpiredRecords on the Postgres backend (#977) > deletes across multiple bounded batches and stops at the per-table cap, same as the SQLite path
Stack Traces | 0.0131s run time
AssertionError: expected +0 to be 4 // Object.is equality

- Expected
+ Received

- 4
+ 0

 ❯ test/unit/selfhost-pg-retention.test.ts:76:33
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > never downgrades the locally-computed signal when the enabled bridge does not vouch
Stack Traces | 0.018s run time
AssertionError: expected "vi.fn()" to be called once, but got 2 times
 ❯ test/unit/reputation-wiring.test.ts:441:25
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > keeps the pre-bridge output unchanged when any activation gate is off
Stack Traces | 0.0196s run time
AssertionError: expected "vi.fn()" to not be called at all, but actually been called 1 times

Received:

  1st vi.fn() call:

    Array [
      "https://api.gittensor.io/miners",
      Object {
        "headers": Object {
          "accept": "application/json",
          "user-agent": "loopover/0.1",
        },
        "signal": AbortSignal {
          Symbol(kEvents): Map {},
          Symbol(events.maxEventTargetListeners): 0,
          Symbol(events.maxEventTargetListenersWarned): false,
          Symbol(kHandlers): Map {},
          Symbol(kAborted): false,
          Symbol(kReason): undefined,
          Symbol(kComposite): false,
          Symbol(kTimeout): true,
        },
      },
    ]


Number of calls: 1

 ❯ test/unit/reputation-wiring.test.ts:419:31
test/unit/reputation-wiring.test.ts > getEffectiveSubmitterReputation (#4513, install-wide for a confirmed miner) > skips the miner-identity lookup entirely when the per-repo signal already justifies downgrading
Stack Traces | 0.0203s run time
AssertionError: expected 'neutral' to be 'low' // Object.is equality

Expected: "low"
Received: "neutral"

 ❯ test/unit/reputation-wiring.test.ts:520:26
test/unit/reputation-wiring.test.ts > getEffectiveSubmitterReputation (#4513, install-wide for a confirmed miner) > widens to the install-wide signal for a CONFIRMED miner when the per-repo signal alone stays neutral
Stack Traces | 0.0322s run time
AssertionError: expected 'neutral' to be 'low' // Object.is equality

Expected: "low"
Received: "neutral"

 ❯ test/unit/reputation-wiring.test.ts:482:26
test/unit/reputation-wiring.test.ts > ORB/AMS reputation bridge wiring (#6801) > REGRESSION (#6801): feature on upgrades a low local signal and changes the end-to-end gate decision
Stack Traces | 0.0405s run time
AssertionError: expected "vi.fn()" to be called 2 times, but got 3 times
 ❯ test/unit/reputation-wiring.test.ts:393:25
test/unit/selfhost-metrics.test.ts > DEFAULT_METRIC_META completeness (drift guard, 2026-07 fix) > every literal metric name emitted anywhere in src/ has a registered DEFAULT_METRIC_META entry
Stack Traces | 0.171s run time
AssertionError: expected [ Array(1) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   "loopover_private_manifest_warnings_total",
+ ]

 ❯ test/unit/selfhost-metrics.test.ts:498:21
test/unit/config-templates.test.ts > config/examples review templates (#1682) > lints loopover.full.yml with zero warnings, including no retired top-level fields
Stack Traces | 0.206s run time
AssertionError: expected [ Array(1) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   "gate.mergeReadiness (\"off\") is set alongside an explicitly-authored mode for gate.linkedIssue, gate.duplicates, gate.slop.mode. The composite only fills in a sub-gate mode left unset -- it never overrides an explicitly-configured one, so those fields stay exactly as authored regardless of gate.mergeReadiness.",
+ ]

 ❯ test/unit/config-templates.test.ts:64:29
test/unit/queue-5.test.ts > queue processors > type label decoupling (#label-decoupling) > REGRESSION (#4528, PR #4494 shape): keeps the propagated labels on the PR's own merge-closed webhook, instead of falling back to the title guess
Stack Traces | 0.227s run time
AssertionError: expected [ 'gittensor:feature' ] to deeply equal [ 'gittensor:feature', …(1) ]

- Expected
+ Received

  [
    "gittensor:feature",
-   "gittensor:priority",
  ]

 ❯ test/unit/queue-5.test.ts:6660:34
test/unit/worker-entry-boundary.test.ts > worker entry boundary > does not reference pixelmatch, pngjs, visual-diff, gifenc, or sharp in worker-reachable source
Stack Traces | 0.5s run time
AssertionError: worker-reachable files must not mention Node-only visual diff/GIF/image deps: .../review/visual/visual-findings.ts: expected [ Array(1) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   ".../review/visual/visual-findings.ts",
+ ]

 ❯ test/unit/worker-entry-boundary.test.ts:82:118
View the full list of 5 ❄️ flaky test(s)
test/contract/engine-parity.test.ts > predicted-gate engine parity (#2286) > 'merge-readiness-composite-preserves-e…' matches the committed golden output

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0188s run time
AssertionError: expected { predicted: true, …(11) } to deeply equal { predicted: true, …(10) }

- Expected
+ Received

  {
    "basis": "public_config",
-   "blockers": [
+   "blockers": [],
+   "conclusion": "success",
+   "confirmedContributor": undefined,
+   "funnel": null,
+   "note": "Predicted from the repo's public .loopover.yml gate config + safe defaults. The maintainer may have private dashboard overrides not reflected here, and the dual-model AI-consensus blocker is only evaluated on a real PR. The slop score is NOT evaluated pre-submission (it needs the diff content) and may still fail the real gate. Provide the PR's changed paths to also predict the focus-manifest path policy, the size/guardrail hold, and any pre-merge check scoped to changed paths; without them only path-independent title/description/label pre-merge checks are predicted. Every author is gated the same: a configured hard blocker fails the gate regardless of confirmed-contributor status (which affects only on-chain scoring).",
+   "pack": "gittensor",
+   "predicted": true,
+   "readinessScore": 80,
+   "summary": "No configured hard blocker was found. Advisory findings, if any, stay advisory.",
+   "title": "LoopOver Orb Review Agent passed",
+   "warnings": [
      {
        "action": "If this PR is intended to solve an issue, link it explicitly in the PR body.",
        "code": "missing_linked_issue",
        "detail": "No closing reference or linked issue number was found in the PR metadata/body.",
        "title": "No linked issue detected",
      },
    ],
-   "conclusion": "failure",
-   "funnel": null,
-   "note": "Predicted from the repo's public .loopover.yml gate config + safe defaults. The maintainer may have private dashboard overrides not reflected here, and the dual-model AI-consensus blocker is only evaluated on a real PR. The slop score is NOT evaluated pre-submission (it needs the diff content) and may still fail the real gate. Provide the PR's changed paths to also predict the focus-manifest path policy, the size/guardrail hold, and any pre-merge check scoped to changed paths; without them only path-independent title/description/label pre-merge checks are predicted. Every author is gated the same: a configured hard blocker fails the gate regardless of confirmed-contributor status (which affects only on-chain scoring).",
-   "pack": "gittensor",
-   "predicted": true,
-   "readinessScore": 80,
-   "summary": "No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.",
-   "title": "LoopOver Orb Review Agent: No linked issue detected",
-   "warnings": [],
  }

 ❯ test/contract/engine-parity.test.ts:46:21
test/unit/engine-parity-fixtures.test.ts > predicted-gate engine parity fixtures > keeps the 'merge-readiness-composite-preserves-e…' fixture parseable and aligned with its documented surface

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0113s run time
AssertionError: expected 'success' to be 'failure' // Object.is equality

Expected: "failure"
Received: "success"

 ❯ test/unit/engine-parity-fixtures.test.ts:29:31
test/unit/linked-issue-label-propagation-fetch.test.ts > fetchLinkedIssueLabelsForPropagation (#priority-linked-issue-gate) > reward-label gate on direct author/assignee match (#9161) > propagates the reward label to its own issue's author when that author genuinely IS a repo maintainer (opt-in + maintainer check both satisfied)

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0367s run time
AssertionError: expected { labels: [ 'gittensor:bug' ], …(1) } to deeply equal { …(2) }

- Expected
+ Received

  {
    "inconclusive": false,
    "labels": [
      "gittensor:bug",
-     "gittensor:priority",
    ],
  }

 ❯ expectPropagation test/unit/linked-issue-label-propagation-fetch.test.ts:40:18
 ❯ test/unit/linked-issue-label-propagation-fetch.test.ts:1087:7
test/unit/predicted-gate-engine.test.ts > predicted-gate engine branch coverage (#2283) > exercises gate-advisory gateMode and blocker policy branches

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0328s run time
AssertionError: expected 'block' to be 'advisory' // Object.is equality

Expected: "advisory"
Received: "block"

 ❯ test/unit/predicted-gate-engine.test.ts:1558:55
test/unit/salvageability.test.ts > resolveAiReviewSalvageableHold > defaults: no configured floor uses the gate default, and a confidence-less blocker counts as certainty (at/above floor)

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0072s run time
AssertionError: expected undefined to be defined
 ❯ test/unit/salvageability.test.ts:88:18

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

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
loopover-ui 7.45MB 401 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-B0pqPK2R.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-I32wbYMe.js (New) 816.15kB 816.15kB 100.0% 🚀
assets/docs.fumadocs-spike-api-reference-_G9JesYj.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-BzzLAXxP.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-CgtSvJ_e.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-DLnbUH18.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-Bv9uHHch.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-B-At4R0D.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-BiuQJXU0.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-C2n226si.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-Ss7ThOc5.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-D3CQlqB3.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-D3czFr4u.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DJgnfvRs.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-DB0cjVL9.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-Bz9We4vJ.js (New) 15.37kB 15.37kB 100.0% 🚀
assets/playground-panel--KwM4HNn.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-CLxvf12E.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-BYC-EWZD.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-CO_j-xw0.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-lf95JEDG.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-B0BzZNhq.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-DuyV-PXQ.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-BC1mEtEZ.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-CPhmPboo.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-53jZCO1g.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-DLvoZF8a.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-DFk1NHVT.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/docs.index-CY0680f6.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-ClCQ1erQ.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-40nFznx4.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-c-sHtGZy.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-Cq5R7DXp.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-DtD_aDEv.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-CMs_lO2a.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-Dvi5g1V8.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-BsC0mgvm.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-Bu9nYQHP.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-CBUM-yHH.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-BsSfSxWe.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-k_0B-Q20.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-eY_wZWCw.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-3GgsRd8v.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-Cllxa-kn.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-IZ6IqHKQ.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-B89gmLr2.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-Bbhldg4C.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-BeN1lYCf.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-B3H0hxu0.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-DBRGCkp2.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-BvalNNDG.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-D6kiYud6.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-CNeBiXvg.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-CaJdLgXg.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-BHbdk9wa.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-LlOJCa0s.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CWG3j3e-.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DL-YQFjz.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-HGieaJ1P.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-2wI_hF7g.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-C8kIZVSn.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-CZbdVWRi.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-QZy6JG-s.js (Deleted) -816.15kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-C7yUBZ0l.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-BbA-8TxY.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-DTfUC8zN.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-o1BfpR6e.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-BkZKWXRf.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-Bs2Z5888.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-DjD1ulC7.js (Deleted) -27.52kB 0 bytes -100.0% 🗑️
assets/app-DXX8fabJ.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-N3yylJvL.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-BFiPrZJp.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-DYGzL-E0.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-DQsUDJRA.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-CQMxjgkm.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-CuIXrpmc.js (Deleted) -15.37kB 0 bytes -100.0% 🗑️
assets/playground-panel-BqQum6-0.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-ZCbeOHsf.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-BIrk4QXU.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-BlOsev43.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-DKXOa11K.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-B5-Dqv9f.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-Dsdxq-VL.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-DZEvK5JX.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-CDQfYH29.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-O1kWUhBZ.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-wVTDYgmp.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-BBfbpEyf.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/docs.index-Cuesh7YW.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-Dd-TYm8R.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs--2XVy6qp.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-0tlm_RgD.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-Ds-HoibM.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-CuKEyTT5.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-KYEl1Xi0.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-wNaiH7Gb.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-sOkxdteQ.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-Du7oeyau.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-CZkDioEy.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-CKd0WuZY.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-CaN99JEN.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BEwedl0B.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-B72rMpal.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-C7Eq8Tme.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-DXHITv9M.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-ngCtf9wN.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-qPygE-7v.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-C4qfo8N0.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-BdrH5fBE.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-BD89ZavB.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-CsBrKsqi.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-DjQ8ly2E.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BOLMkDol.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DegvVdBY.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-DLLxaTrN.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-DgsRUFoM.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-BnSYsJjI.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-CTCdU_6q.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-R5YKPg7N.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CVVzdpzz.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-g7xzvZCC.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@JSONbored
JSONbored force-pushed the fix/9063-9064-9065-test-gate-integrity branch from 040acc4 to 9afeb92 Compare July 27, 2026 09:16
@superagent-security

Copy link
Copy Markdown
Contributor

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

…ts coverage (#9064)

@loopover/engine's own node:test suite (packages/loopover-engine/test/**, 65 files, ~734 tests)
had never run in this workflow before -- only locally via `npm run test:ci`'s own `npm run test
--workspace @loopover/engine`, and at release time via publish-engine.yml's workflow_dispatch-only
validate job. So engine source was graded only by whatever vitest's root test/** suite happened
to exercise transitively, even though packages/loopover-engine/src/** is already counted in
vitest.config.ts's coverage.include -- a PR touching an engine module with a real node:test suite
could satisfy codecov/patch with a thin, duplicate vitest test instead of ever running the real one.

Adds an "engine" Codecov flag mirroring the existing rees/control-plane pattern: enable sourceMap
in packages/loopover-engine/tsconfig.json, add c8 as a devDependency, and harvest lcov via
scripts/engine-coverage.mjs (c8 remaps dist/ hits back to packages/loopover-engine/src/**.ts
through the source maps). ci.yml's validate-code job now runs the real test suite (authoritative
pass/fail) and then the c8-instrumented re-run (coverage only), uploaded under the new flag.

Running the suite in CI for the first time surfaced two real, currently-stale tests -- exactly the
failure mode #9064 warns about (a test in a package the gate never runs can silently rot). #9129's
duplicate-overlap host-parity split changed a bare linked-issue citation from a blocking finding to
the always-non-blocking duplicate_pr_risk_unconfirmed code, and made an all-duplicate blocker set
hold ("neutral") rather than fail. iterate-loop.test.ts's and self-review-adapter.test.ts's shared
openPr() fixture had no changedFiles, so their "genuine blocking duplicate" scenarios silently
stopped blocking; self-review-adapter.test.ts also still asserted the pre-#9129 "failure" conclusion.
Fixed the fixture to carry corroborating changedFiles (restoring the intended blocking scenario) and
updated the conclusion assertion to the current, correct "neutral"/held behavior.
… catch new ones (#9064)

Two concrete, malformed ignore directives silently widened their exempted range far past what was
intended (empirically confirmed by measuring coverage before/after each fix):

- src/scenarios/input-model.ts used `/* v8 ignore end */`, not a valid v8 terminator (must be
  `stop`). The unterminated `v8 ignore start` above it stayed open through EOF, silently excluding
  ~60 lines (validateBucketKinds, sortEntries, compactRepoConfig, trimScenarioText,
  optionalScenarioText, ...) instead of the ~3-line defensive branch it was meant to cover. Fixed
  the terminator and added the one real test the wider surface needed (a whitespace-only
  branchName/baseRef falling back to "absent", not a blank string).
- src/review/loop-escalation-wire.ts carried a whole-file ignore whose justification (codecov patch
  reporting one branch as uncovered "across shards") stopped applying when codecov.yml unsharded
  the backend suite to one whole-suite upload on 2026-07-24. Removed the ignore and added the one
  real test it needed: continuing (not throwing) when recording the missing-webhook "denied" audit
  event itself fails.

Building the linter for these (test/unit/codecov-policy.test.ts) surfaced the same defect class a
third time, already live: packages/loopover-engine/src/opportunity-metadata.ts's `computeMetadataPotential`
ignore-start (line 110) was missing its `stop` before the next function's own start/stop pair, so it
silently absorbed all of computeMetadataFeasibility's real logic too; a separate stray `v8 ignore
stop` at EOF had no matching start at all. Fixed both.

The new check scans src/**, packages/loopover-engine/src/**, packages/loopover-{miner,mcp}/{lib,bin}/**,
and packages/discovery-index/src/** (mirroring vitest.config.ts's own coverage.include roots) for any
v8-ignore keyword outside the {next, start, stop, file, else} set, an unterminated start, a stray stop,
or a whole-file ignore with no issue-number reference -- so a new malformed directive fails CI going
forward instead of silently widening. Deliberately does NOT require the ~105 pre-existing ignores
without a justification comment to gain one (a much larger, separate cleanup); it only enforces
structural validity.

Matches on the comment opener directly (`/\*\s*v8 ignore ...`) rather than pairing every `/*` with the
next `*/` in the file: src/api/routes.ts contains a literal `/*` substring in ordinary prose (a quoted
route-path glob, `` `/v1/internal/*` ``, inside a `//` comment) that the naive approach merged with an
unrelated real comment 56KB later, swallowing a legitimate directive in between.
…t double

The shared SELFHOST_TRANSIENT_CACHE mock in createTestEnv() previously
discarded ttlSeconds entirely, so lock expiry, orphaned-lock recovery, and
starvation scenarios built on AI_REVIEW_LOCK_TTL_SECONDS were unrepresentable
in every test that used it. Store a real expiresAtMs (Date.now() + ttl*1000)
so vitest's fake timers can advance past it, mirroring the real Redis-backed
cache's SET ... EX semantics (#9063).
…parity (#9063)

Three invariant tests modeled on test/unit/pr-disposition-invariants.test.ts, each targeting
one of the defect classes ordinary coverage cannot see:

- AI-review lock claim outcomes are exhaustive and traceable (#9008): sweeps
  {contested, steal} so a forced retrigger never lands on the silent-contended branch, and
  confirms an expired (not stolen) lock recovers on its own now that the shared transient-cache
  test double honors ttlSeconds.
- manual-review lock-contention auto-clear is reachable exactly once (#9009): sweeps four
  independent live-hold causes (guardrail, unverified CI, action-required, generic not-review-good)
  proving none of them is ever overridden by the lock-contention-resolved marker, plus the
  control case where removal is reachable.
- reputation-triggered and contributor-controlled AI-review skips hold in lockstep (#9015):
  sweeps every aiReviewMode proving both sibling functions agree on whether to hold, never one
  without the other.
…rser (#9065)

unknownTopLevelWarnings previously ran ONLY in the offline validator (loopover_validate_config,
the validate route, the admin dry-run write, the CLI lint script) -- the runtime load path
(loadRepoFocusManifest -> parseFocusManifestContent -> parseFocusManifest) never called it, so an
operator editing a mounted manifest file directly (the normal self-host workflow) got zero
feedback on a typo'd top-level key.

- Moved the top-level-field knowledge (previously a hand-duplicated TOP_LEVEL_FIELDS list living
  only in config-lint.ts) into focus-manifest.ts as unknownTopLevelManifestWarnings(), the single
  source of truth both the offline linter and the runtime parser now call, so the two can never
  disagree. Also fixed parseFocusManifestContent to retry YAML on a strict-JSON parse failure
  (matching config-lint's own long-standing, more lenient fallback for YAML flow mappings that
  start with "{"/"[") -- a genuine parity gap this wiring exposed.
- Added per-block known-key sets for `settings`/`gate`/`features`/`review`: a typo'd nested key
  (e.g. `settings.agentPause`, `gate.checkModee`) previously read as `undefined` via direct field
  access and silently behaved as absent, with no warning anywhere. `features`'s fix also makes its
  existing docstring -- which already claimed unknown keys "are dropped with a warning" -- true
  instead of aspirational.
- private-config.ts's combineConfigLayersWithMeta previously warned ONLY when the shared-base
  layer failed to parse; a malformed global-default or per-repo layer was dropped silently. All
  three layers now warn, naming the candidate path when known.
… structured logs (#9065)

loopover_admin_get_config --scope effective previously extracted only the loaded manifest's
content and silently discarded its warnings -- an operator could ask "what's effectively loaded
for this repo" and see a clean-looking config even when a layer had been dropped as malformed or
the merged content carried an unknown-key parse warning. Now returns warnings alongside content.

makeLocalManifestReader's own dropped-layer warnings previously had no consumer outside that one
return value; they're now also logged as a structured event (selfhost_private_manifest_warnings)
and counted (loopover_private_manifest_warnings_total) so a sustained run of dropped layers is
visible on a dashboard, not just discoverable after the fact.

Also fixes a duplicate-warning bug the #9065 runtime wiring exposed: buildFocusManifestValidation
independently re-computed unknownTopLevelWarnings(content) on top of manifest.warnings, which now
already includes it via parseFocusManifest's own call -- doubling the same warning in
loopover_validate_config, the validate route, and the admin dry-run write.
#9064 enabled tsconfig.json's "sourceMap": true so scripts/engine-coverage.mjs's c8 remapping
could map coverage hits back to src/**.ts paths, but package.json's files: ["dist", ...] bundles
the whole directory verbatim, so `npm pack` now also ships every dist/**/*.js.map -- which
scripts/check-engine-package.ts's ALLOWED list (dist/*.{js,d.ts} only) correctly flags as
unexpected. A bare directory entry in npm's "files" array ignores .npmignore entirely, so the fix
is to name the two extensions the published package actually needs instead of the whole directory;
the local dist/ build (and its .map files) used by coverage tooling is untouched.
@JSONbored
JSONbored force-pushed the fix/9063-9064-9065-test-gate-integrity branch from 9afeb92 to 4d88ff8 Compare July 27, 2026 09:24
@JSONbored
JSONbored merged commit d9bc1ef into main Jul 27, 2026
2 of 3 checks passed
@JSONbored
JSONbored deleted the fix/9063-9064-9065-test-gate-integrity branch July 27, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment