feat(review): gate surface entries on live grounding and functional-surface checks (#8908, #8909) - #9255
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Logic backtestReplayed 0 historical case(s) for Backtest comparison:
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
loopover-ui | bf34c0e | Commit Preview URL Branch Preview URL |
Jul 27 2026, 10:46 AM |
❌ 17 Tests Failed:
View the top 3 failed test(s) by shortest run time
View the full list of 5 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
Bundle ReportChanges will increase total bundle size by 2.02kB (0.03%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
|
Caution 🛑 LoopOver review result - fixes requiredReview updated: 2026-07-27 10:40:34 UTC
Review summary Nits — 6 non-blocking
CI checks failing
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
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.
|
…urface checks (#8908, #8909) `computeGrounding` and `probeFunctionalSurface` have been fully implemented and unit-tested in registry-logic.ts since the metagraphed port, but neither had a single caller outside its own test file. The live surface-entry gate (assessSurfaceEntry -> runSurfaceReview) merged a submission having never once looked at what its URLs actually serve: it validated that `url`/`source_url` were well-formed public HTTPS/WSS URLs and stopped there. Add the missing half — the fetch plumbing and the gating decision — without touching either primitive: - surface-verification.ts: SSRF-guarded, redirect-following, truncation-tolerant probes of the entry's `source_url` and `url`; evidence extraction (title + tag-stripped text, script/style dropped so bundled code cannot forge a signal); and the close-vs-hold policy. - orchestrator: one optional injected `verifyEntry` hook, applied per appended entry, only to entries that already passed static validation. The orchestrator stays pure and domain-agnostic — any registry can supply its own verifier. - content-lane-wire: builds the verifier when the new flag is on. Policy. A CONFIRMED functional failure (a 2xx whose body demonstrably is not the declared surface — an `openapi` url serving HTML) CLOSES: an objective, reproducible, trivially-fixable fact, in the same class as the shape violations the entry validator already closes on. Unconfirmed grounding HOLDS rather than closes — it is a heuristic over page text, and a legitimate surface can fail it, so closing on it would one-shot-close good contributions. It no longer merges, which is the actual #8908 gap. Three-state, never fail-open. Each check resolves to pass / fail / inconclusive and these stay strictly distinct: an unreachable probe, a 2xx with an unreadable or empty body, a missing source, or a throwing verifier all HOLD with their own reason code, never a pass. A check that silently passes when it could not run launders "we didn't look" into "we verified". Flag-gated and OFF by default, on its own flag rather than the content lane's: this is the lane's first outbound request to submitter-controlled URLs and it can hold or close submissions that merge today, so both need independent rollback. Flag-off, no probe is made and the verdict is byte-identical.
… node:test suite The host vitest test for isSurfaceVerificationEnabled/isContentLaneEnabled (test/unit/content-lane-flag.test.ts) doesn't count toward the engine package's own Codecov flag -- that's measured from packages/loopover-engine's node:test suite via c8 (#9064), which had no equivalent test for this file.
770f27b to
bf34c0e
Compare


Summary
computeGroundingandprobeFunctionalSurface(src/review/content-lane/registry-logic.ts) have been fully implemented and unit-tested since the metagraphed port, but neither had a single caller outside its own test file. Confirmed still true onorigin/mainbefore starting: the only non-test references were theindex.tsre-exports.So the live surface-entry gate (
assessSurfaceEntry→runSurfaceReview→runRegistrySurfaceGate) merged a submission having never once looked at what its URLs actually serve — it validated thaturl/source_urlwere well-formed public HTTPS/WSS URLs and stopped there. Nothing confirmed the URL's content corroborated the claimed netuid/owner/host, and nothing confirmed anopenapi/subnet-api/sseentry served the interface itskinddeclared.This adds the missing half — the fetch plumbing and the gating decision — without modifying either primitive:
src/review/content-lane/surface-verification.ts(new): SSRF-guarded, redirect-following, truncation-tolerant probes of the entry'ssource_urlandurl; evidence extraction (title + tag-stripped text, withscript/stylecontents dropped so a bundled script mentioning an unrelated "subnet 14" cannot forge a grounding signal); and the close-vs-hold policy.orchestrator.ts: one optional injectedverifyEntryhook onSurfaceReviewInput— same injected-I/O shape as the existingloadFile. Applied per appended entry, only to entries that already passed static validation. The orchestrator stays pure and domain-agnostic; any registry can supply its own verifier.content-lane-wire.ts: builds the production verifier when the new flag is on.Closes #8908
Closes #8909
Design decision 1 — block, hold, or warn?
Split, and deliberately asymmetric:
served:falseserved: false". A 2xx whose body demonstrably is not the declared surface (anopenapiurl serving an HTML marketing page) is an objective, reproducible fact about the submission — the same class as the shape/kind violationsassessSurfaceEntryalready closes on (unsupported-shape), and trivially fixable by resubmitting with the rightkindor url.strongbelow threshold)source-evidence.tsalready sets this precedent — a dead source only hard-closes when all authoritative sources failed and there is more than one; otherwise it routes to manual. Closing on a heuristic would one-shot-close good contributions. Crucially it no longer merges, which is the actual #8908 gap.Precedence is fail-closed-first: a confirmed functional failure outranks everything, then any inconclusive probe, then unconfirmed grounding.
The grounding threshold is
SURFACE_GROUNDING_MIN_STRONG = 1— one independent signal (netuid named, owner named, or host independently backed), net ofcomputeGrounding's own cross-origin-redirect penalty. Not 2: metagraphed is a public registry whose own code comments state this is "ACCURACY corroboration, NOT ownership gating", and requiring two signals would hold a large share of legitimate submissions. One still forecloses the case the issue exists to catch — a surface whose evidence corroborates nothing about the subnet it claims.Design decision 2 — flag gating and shipped default
New
LOOPOVER_REVIEW_SURFACE_VERIFICATION, default"false",isSurfaceVerificationEnabledin the engine flag module alongsideisContentLaneEnabled.Deliberately its own flag rather than riding on
LOOPOVER_REVIEW_CONTENT_LANE: this is the first thing in the lane that makes outbound requests to submitter-controlled URLs, and it can hold or close submissions that merge today. Both properties need to be rollable back without taking the (already-cutover) content lane down with them.Per-repo scoping comes for free — the caller ANDs this with the lane's existing activation, so verification only ever runs where a
RegistryLaneSpecalready resolved. No new per-repo config surface, so no.loopover.ymlschema / settings-resolver / migration parity work.Flag-off (the shipped default),
verifyEntryisundefined, the orchestrator runs no verification, no outbound probe is made, and the verdict is byte-identical. There is a regression test asserting exactly this withfetchstubbed to reject.Design decision 3 — inconclusive must never look like a pass
Each check resolves to a strict three-state
pass | fail | inconclusive, and inconclusive is never collapsed into either neighbour. It HOLDS, with its own reason code and its own public text, so "we could not check" never renders as "we checked and disliked it":grounding-inconclusive— thesource_urlcould not be fetched (throw, non-2xx, or no fetchable source declared). There is nothing to corroborate against.grounding-unconfirmed— evidence was fetched and corroborates nothing.functional-probe-inconclusive— the url could not be probed, returned non-2xx, returned a 2xx with an unreadable body (broken stream), or returned a 2xx with an empty body. That last one matters:probeFunctionalSurfacewould read a bareapplication/jsoncontent-type as a served API, so a blank 2xx is explicitly degraded rather than a pass.verification-error— the verifier itself threw.makeSurfaceEntryVerifieris written not to throw; the orchestrator guards anyway so a future/third-party verifier cannot fail open.fetchSurfaceProbenever throws — every failure mode collapses into anok:falseprobe carrying an outcome token, so a thrown fetch can't escape into the review pipeline or be caught upstream and read as success.Safety notes:
isSafeHttpUrlis re-applied on every redirect hop (redirects are followed manually for exactly this reason — an https origin that 302s to127.0.0.1is refused), hops are capped at 4, bodies are capped at 64 KB, and a redirect landing on a different registrable domain setscross_origin_redirectsocomputeGroundingapplies its existing bait-and-switch penalty. A base-layerwss://entry is not http-fetchable, so grounding rests on source evidence alone (still conclusive) and no functional kind is a wss kind.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #8908,Closes #8909).Validation
git diff --checknpm run actionlint— not run; no workflow files touched.npm run typecheck— clean (tsc --noEmit -p tsconfig.json --incremental false).surface-verification.ts,orchestrator.ts, and the engineflag.ts. Full unshardedtest:coveragenot run — see note below.npm run test:workers— not run; no Workers-runtime surface touched.npm run build:mcp/test:mcp-pack— not run; no MCP surface touched.npm run ui:openapi:check— not run; no API routes or OpenAPI schemas touched.npm run ui:lint/ui:typecheck/ui:build— not run; no UI files touched.npm audit --audit-level=moderate— not run; no dependency changes.Regenerated artifacts (committed):
npm run cf-typegen→worker-configuration.d.ts;src/env.d.tsdeclaration added to match the repo's optional-flag convention.npm run selfhost:env-referenceandnpm run db:migrations:checkboth re-run immediately before pushing — no diff, and no migration added (next free remains 0195).If any required check was skipped, explain why:
content-lane-surface-verification.test.ts, plus additions to the orchestrator, wire, and flag suites).test/unitsuite was run for regression safety: 22,477 tests, of which 17 fail in 12 files. All 17 were verified pre-existing on a cleanorigin/mainby stashing this branch's changes and re-running the same 12 files — identical failures, identical count. None are content-lane related.validate-testsis currently red onmainitself, independently of this PR — the last four completed CI runs onmain(3a3c1c29b,32c7d5ad8,5cece2759,3e1654572) all fail that job, and32c7d5ad8is this branch's exact base. The failures here (config-templates,engine-parity-fixtures,predicted-gate-engine,worker-entry-boundary,linked-issue-label-propagation-fetch,reputation-wiring,selfhost-pg-retention,selfhost-metrics,salvageability,check-ui-kit-package,loopover-engine-scaffold,queue-5) are that same pre-existing set. Everything this PR is responsible for —validate-code,codecov/patch,codecov/project, and all 447 content-lane tests plus the engine's 746node:testtests — is green.Safety
isSafeHttpUrlSSRF guard and re-checks it per redirect hop, with negative-path tests for loopback, private IPs, non-https, malformed URLs, redirect-into-loopback, missing/unparseableLocation, and redirect-loop exhaustion.Notes
Engine-package coverage:
packages/loopover-engine/src/review/content-lane/flag.tsis measured by the engine's OWN Codecov flag, sourced from itsnode:testsuite via c8 — the host vitest test does not count toward it. A matchingpackages/loopover-engine/test/content-lane-flag.test.tsis included so the newisSurfaceVerificationEnabledbranch is covered on both sides. Engine suite: 746 tests pass.Both issues are labelled
maintainer-onlyand explicitly call for a maintainer decision on the fetch/gating design before implementation — this PR is that decision, made and documented above, plus the wiring. TheBoundariessection of each issue asked that contributors not be unlocked without it; that stays as-is.Follow-ups deliberately out of scope: the
netuid-verification.tstaostats/public-registry identity signals (fetchSubnetRecord,deriveRegistryIdentityTokens,surfaceMatchesRegistryIdentity) are a separate corroboration axis that could feed the same hold decision, but wiring them needs theTAOSTATS_API_KEYsecret story resolved and belongs in its own change.