From 8b95af4ceca8596245fec9e2ab47cc80a3e66c45 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Thu, 25 Jun 2026 01:27:38 -0700 Subject: [PATCH] feat(review): wire the deterministic surface lane into the gate (flag-gated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The content/registry SURFACE LANE (runSurfaceReview) — a pure, AI-FREE, structured-data adjudicator for metagraphed registry-submission PRs — is now wired into the gate behind GITTENSORY_REVIEW_CONTENT_LANE (default OFF) + the per-repo GITTENSORY_REVIEW_REPOS allowlist. When off, the processor takes no new branch, resolves no files, and the gate disposition is byte-identical. Because the verdict never depends on an AI model, this is independent of the AI-reviewer accuracy work (the lane emits none of the AI_JUDGMENT_BLOCKER_CODES). New src/review/content-lane-wire.ts maps the deterministic verdict → gate disposition (merge→success, close→failure with a critical surface_lane_reject blocker, manual→action_required) and overrides the generic gate for a registry submission, with two safety guards: - applySurfaceGate PRESERVES the generic gate's hard blockers, so a surface "merge" can never clear a real critical (e.g. a committed secret) the generic gate already raised. - A fetch blip defers instead of auto-closing: an unreadable head, or a null base on a file GitHub marks "modified" (whose base must exist, so null is transient — distinguished from an ADDED file's expected absent base via the PR file status), routes to the generic gate rather than one-shot-closing a valid append. The close code is deliberately NOT an AI-judgment code, so green-CI refutation can never flip a deterministic surface close. The processor seam is a single call (evaluateWithSurfaceLane); the nullable-ref + override logic lives in the unit-tested helper. Advances #1255. --- src/env.d.ts | 6 + src/queue/processors.ts | 12 ++ src/review/content-lane-wire.ts | 169 +++++++++++++++++++++++++++ test/unit/content-lane-wire.test.ts | 171 ++++++++++++++++++++++++++++ worker-configuration.d.ts | 5 +- wrangler.jsonc | 5 + 6 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 src/review/content-lane-wire.ts create mode 100644 test/unit/content-lane-wire.test.ts diff --git a/src/env.d.ts b/src/env.d.ts index 27bff25116..15bfefb677 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -136,6 +136,12 @@ declare global { * unreachable when off). Even when ON, retrieval is INERT until a vector index exists for the repo (a * cold/missing index degrades to no context) — the index-population job is a deploy-time follow-up. */ GITTENSORY_REVIEW_RAG?: string; + /** Convergence flag: the deterministic content/registry SURFACE LANE drives the gate for registry-submission + * PRs (metagraphed surfaces[]/providers/candidates). Truthy ON *AND* the repo in GITTENSORY_REVIEW_REPOS — + * see review/content-lane-wire. Default OFF: unset/false takes no new branch, runs no fetch, and leaves the + * gate disposition byte-identical. AI-FREE (pure structured-data adjudication), so independent of the AI + * reviewer; a generic hard blocker (e.g. a committed secret) is always preserved over a surface "merge". */ + GITTENSORY_REVIEW_CONTENT_LANE?: string; /** Convergence (self-improve / auto-tune): when truthy, the ported self-improvement loop * (src/review/auto-tune.ts + auto-apply.ts) runs on the cron tick over gittensory's OWN review-outcome * data — it computes tuning recommendations, SHADOW-SOAKS any STRICTLY-TIGHTENING recommendation in the diff --git a/src/queue/processors.ts b/src/queue/processors.ts index 3c351d2c8e..e23601783b 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -181,6 +181,7 @@ import { runGittensoryAiReview } from "../services/ai-review"; import { secretLeakFinding } from "../review/safety"; import { aiCiRefutationActive, buildReviewGroundingText, checkSummaryText as checkFailureSummaryText, isGroundingEnabled } from "../review/grounding-wire"; import { buildReviewRagContext, isRagEnabled } from "../review/rag-wire"; +import { evaluateWithSurfaceLane } from "../review/content-lane-wire"; import { indexRepo, reindexChangedPaths } from "../review/rag-index"; import { isReputationEnabled, recordReputationOutcome, shouldSkipAiForReputation } from "../review/reputation-wire"; import { isConvergenceRepoAllowed } from "../review/cutover-gate"; @@ -2542,6 +2543,17 @@ async function maybePublishPrPublicSurface( const gatePolicy = gateCheckPolicy(settings, readiness.total, confirmedContributor, slopRisk, authorHistory); gateEvaluation = gateEnabled ? evaluateGateCheck(advisory, gatePolicy) : undefined; + // Deterministic content/registry surface lane (#1255) — flag-gated + per-repo allowlist, byte-identical when + // off (evaluateWithSurfaceLane returns the generic evaluation unchanged and resolves no files). A metagraphed + // registry-submission PR's surface verdict OVERRIDES the generic gate; the helper preserves a generic HARD + // blocker (e.g. a committed secret) and an unreadable head defers. AI-free → independent of the AI reviewer. + gateEvaluation = await evaluateWithSurfaceLane(env, repoFullName, gateEnabled, gateEvaluation, { + installationId, + pr, + repo, + advisory, + getChangedFiles: getReviewFiles, + }); // #554 gate false-positive telemetry: when the gate BLOCKS, record the block (one latest row per PR) so a // maintainer can later compute a per-gate-type false-positive rate (blocked-then-merged / blocked). // MEASUREMENT only — never adjusts the gate. Best-effort: a write failure must NOT abort finalization diff --git a/src/review/content-lane-wire.ts b/src/review/content-lane-wire.ts new file mode 100644 index 0000000000..a92221e81d --- /dev/null +++ b/src/review/content-lane-wire.ts @@ -0,0 +1,169 @@ +// Content/registry surface-lane HOST ADAPTER (#1255 convergence). `runSurfaceReview` is a pure, AI-FREE, +// structured-data adjudicator for registry-submission PRs (metagraphed's surfaces[]/providers/candidates). This +// file is the thin host wiring that lets its deterministic verdict drive the SAME gate disposition (check-run + +// auto-action + public comment) the generic gate produces: the flag + per-repo allowlist guard, the GitHub-backed +// loadFile, and the verdict → GateCheckEvaluation conversion. +// +// FLAG-GATED + DEFAULT-OFF: GITTENSORY_REVIEW_CONTENT_LANE must be truthy AND the repo must be in the per-repo +// GITTENSORY_REVIEW_REPOS cutover allowlist. When off (the default) the caller takes no new branch, runs no +// fetch, and `gateEvaluation` is byte-identical to today. The verdict NEVER depends on an AI model, so this is +// independent of the AI-reviewer accuracy work (the surface lane emits none of the AI_JUDGMENT_BLOCKER_CODES). +// +// SAFETY (two deliberate guards): +// 1. A generic HARD blocker (e.g. a committed secret detected before this runs) is PRESERVED — a surface +// "merge" can never clear a real critical the generic gate already raised (applySurfaceGate unions them). +// 2. An unreadable head — or a null base on a file GitHub marks "modified" (whose base MUST exist, so a null +// read is a transient blip, not an absent base) — defers to the generic gate rather than auto-closing a good +// PR on a spurious "the submission looks empty/invalid" read. (A null base on an ADDED file is the expected +// brand-new-entry case and is not deferred.) +import type { GateCheckEvaluation } from "../rules/advisory"; +import type { AdvisoryFinding, AdvisorySeverity } from "../types"; +import { type ContentLaneEnv, isContentLaneEnabled } from "./content-lane/flag"; +import { runSurfaceReview, type SurfaceReviewInput, type SurfaceReviewResult } from "./content-lane/orchestrator"; +import { METAGRAPHED_LANE_SPEC } from "./content-lane/registry-logic"; +import { isConvergenceRepoAllowed } from "./cutover-gate"; +import { makeGithubFileFetcher } from "./grounding-wire"; + +// Deterministic surface-lane finding codes. DELIBERATELY NOT in AI_JUDGMENT_BLOCKER_CODES — a deterministic +// surface close is a FACT, so the green-CI refutation (reconcileGateEvaluationForGreenCi) must never flip it to +// a merge. Regression-asserted in the test suite. +const SURFACE_REJECT_CODE = "surface_lane_reject"; +const SURFACE_MANUAL_CODE = "surface_lane_manual"; +const SURFACE_TITLE = "Registry surface review"; + +/** True when the deterministic surface lane should drive the gate for `repoFullName`: the flag is on AND the + * repo is in the per-repo cutover allowlist. Flag-OFF (default) ⇒ the caller takes no new branch. */ +export function isContentLaneWired( + env: ContentLaneEnv & { GITTENSORY_REVIEW_REPOS?: string | undefined }, + repoFullName: string, +): boolean { + return isContentLaneEnabled(env) && isConvergenceRepoAllowed(env, repoFullName); +} + +function surfaceFinding(code: string, severity: AdvisorySeverity, summary: string): AdvisoryFinding { + return { code, title: SURFACE_TITLE, severity, detail: summary, publicText: summary }; +} + +/** Convert the deterministic surface verdict into a gate evaluation. merge→success, manual→action_required + * (a warning, not auto-closed), and any decisive non-merge/non-manual verdict (close) → failure with a single + * critical blocker. Returns the finding to splice into the advisory so the public comment renders the reason. */ +export function surfaceVerdictToGate(result: SurfaceReviewResult): { + evaluation: GateCheckEvaluation; + finding: AdvisoryFinding | null; +} { + const summary = result.summary ?? "Registry surface review."; + if (result.verdict === "merge") { + return { evaluation: { enabled: true, conclusion: "success", title: SURFACE_TITLE, summary, blockers: [], warnings: [] }, finding: null }; + } + if (result.verdict === "manual") { + const finding = surfaceFinding(SURFACE_MANUAL_CODE, "warning", summary); + return { evaluation: { enabled: true, conclusion: "action_required", title: SURFACE_TITLE, summary, blockers: [], warnings: [finding] }, finding }; + } + const finding = surfaceFinding(SURFACE_REJECT_CODE, "critical", summary); + return { evaluation: { enabled: true, conclusion: "failure", title: SURFACE_TITLE, summary, blockers: [finding], warnings: [] }, finding }; +} + +/** Merge the surface override onto the generic gate while PRESERVING the generic gate's hard blockers. A surface + * "merge" must NOT clear a real critical (e.g. a committed secret) the generic gate already raised — so when the + * generic gate carries blockers, they survive and the conclusion stays a failure. `null` surface ⇒ defer (the + * generic gate is returned unchanged). PURE. */ +export function applySurfaceGate( + generic: GateCheckEvaluation | undefined, + surface: GateCheckEvaluation | null, +): GateCheckEvaluation | undefined { + if (surface === null) return generic; + if (!generic || generic.blockers.length === 0) return surface; // gate off, or generic was clean → surface stands + return { + enabled: true, + conclusion: "failure", + title: surface.title, + summary: surface.summary, + blockers: [...generic.blockers, ...surface.blockers], + warnings: [...generic.warnings, ...surface.warnings], + }; +} + +/** Run the deterministic surface review for a registry-submission PR and return its gate evaluation, or `null` + * to defer to the generic gate (not a submission, or an unreadable file — see below). Mutates `advisory.findings` + * so the reason renders in the unified public comment. NEVER throws on a fetch blip — the file fetcher is + * fail-safe. `loadFileOverride` is injected by unit tests; production builds a lazy GitHub-Contents-backed loader + * so a non-submission PR (the common case) pays for no fetch at all. `files` carries each changed file's GitHub + * status so a null BASE read can be told apart from an absent base (see the defer guard). */ +export async function runMetagraphedSurfaceGate( + env: Env, + args: { + installationId: number | null | undefined; + repoFullName: string; + pr: { headSha: string; baseRef: string }; + advisory: { findings: AdvisoryFinding[] }; + files: { path: string; status?: string | null | undefined }[]; + }, + loadFileOverride?: SurfaceReviewInput["loadFile"], +): Promise { + let fetcherPromise: ReturnType | null = null; + const githubLoad = async (path: string, ref: "head" | "base"): Promise => { + fetcherPromise ??= makeGithubFileFetcher(env, args.repoFullName, args.installationId); + const fetcher = await fetcherPromise; + return fetcher.getFileContent(path, ref === "head" ? args.pr.headSha : args.pr.baseRef); + }; + const baseLoad = loadFileOverride ?? githubLoad; + const statusByPath = new Map(args.files.map((file) => [file.path, file.status ?? null])); + let deferUnreadable = false; + const loadFile = async (path: string, ref: "head" | "base"): Promise => { + const content = await baseLoad(path, ref); + // An unreadable HEAD — or a null BASE for a file GitHub reports as "modified" (whose base MUST exist, so a + // null read is a transient fetch blip, NOT an absent base) — would make a valid submission read as empty/ + // invalid → a spurious one-shot close. Defer to the generic gate instead. A null base for an ADDED file is + // the expected brand-new-entry case and is left to the orchestrator (one new entry merges; many close). + if (ref === "head" && content === null) deferUnreadable = true; + if (ref === "base" && content === null && statusByPath.get(path) === "modified") deferUnreadable = true; + return content; + }; + const result = await runSurfaceReview(METAGRAPHED_LANE_SPEC, { + changedFiles: args.files.map((file) => file.path), + loadFile, + opts: { secretsScan: true, sourceUrlValidation: true }, + }); + if (result === null) return null; // not a registry submission → the generic gate applies + if (deferUnreadable) return null; // a fetch blip on a file that must be readable → defer, never auto-close + const { evaluation, finding } = surfaceVerdictToGate(result); + if (finding) args.advisory.findings.push(finding); + return evaluation; +} + +/** Resolve the head/base refs the surface loader needs from a (nullable) PR record: head SHA, and base ref + * falling back to the repo default branch then empty. PURE — keeps the nullable-field branches out of the hot + * processor seam so they're unit-tested here. */ +export function resolveSurfaceRefs( + pr: { headSha?: string | null | undefined; baseRef?: string | null | undefined }, + repo: { defaultBranch?: string | null | undefined } | null | undefined, +): { headSha: string; baseRef: string } { + return { headSha: pr.headSha ?? "", baseRef: pr.baseRef ?? repo?.defaultBranch ?? "" }; +} + +/** The processor SEAM in one testable call: when the surface lane is wired for this repo, run it and merge its + * verdict onto the generic gate (preserving generic hard blockers); otherwise return the generic evaluation + * unchanged. `getChangedFiles` is a thunk so an unwired repo resolves no files (no extra diff load). */ +export async function evaluateWithSurfaceLane( + env: Env, + repoFullName: string, + gateEnabled: boolean, + gateEvaluation: GateCheckEvaluation | undefined, + args: { + installationId: number | null | undefined; + pr: { headSha?: string | null | undefined; baseRef?: string | null | undefined }; + repo: { defaultBranch?: string | null | undefined } | null | undefined; + advisory: { findings: AdvisoryFinding[] }; + getChangedFiles: () => Promise<{ path: string; status?: string | null | undefined }[]>; + }, +): Promise { + if (!gateEnabled || !isContentLaneWired(env, repoFullName)) return gateEvaluation; + const surfaceGate = await runMetagraphedSurfaceGate(env, { + installationId: args.installationId, + repoFullName, + pr: resolveSurfaceRefs(args.pr, args.repo), + advisory: args.advisory, + files: await args.getChangedFiles(), + }); + return applySurfaceGate(gateEvaluation, surfaceGate); +} diff --git a/test/unit/content-lane-wire.test.ts b/test/unit/content-lane-wire.test.ts new file mode 100644 index 0000000000..861aea4974 --- /dev/null +++ b/test/unit/content-lane-wire.test.ts @@ -0,0 +1,171 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { AI_JUDGMENT_BLOCKER_CODES, type GateCheckEvaluation } from "../../src/rules/advisory"; +import { applySurfaceGate, evaluateWithSurfaceLane, isContentLaneWired, resolveSurfaceRefs, runMetagraphedSurfaceGate, surfaceVerdictToGate } from "../../src/review/content-lane-wire"; +import type { SurfaceReviewInput } from "../../src/review/content-lane/orchestrator"; +import type { AdvisoryFinding } from "../../src/types"; + +const env = {} as unknown as Env; +const REPO = "JSONbored/metagraphed"; +const SUBNET = "registry/subnets/foo.json"; +const PROVIDER = "registry/providers/acme.json"; +const existing = { kind: "website", url: "https://old.example.ai", source_url: "https://github.com/a/b", public_safe: true }; +const newEntry = { kind: "subnet-api", url: "https://api.example.ai", source_url: "https://github.com/x/y", public_safe: true }; +const doc = (surfaces: unknown[]) => JSON.stringify({ netuid: 14, surfaces }); +const validProvider = JSON.stringify({ provider: { id: "acme", name: "Acme", website_url: "https://acme.example" } }); + +// A loadFile stub keyed by `${ref}:${path}` (mirrors the orchestrator test) so the adapter never hits the network. +const loader = (files: Record): SurfaceReviewInput["loadFile"] => (path, ref) => Promise.resolve(files[`${ref}:${path}`] ?? null); +const gate = (over: Partial): GateCheckEvaluation => ({ enabled: true, conclusion: "success", title: "Gate", summary: "", blockers: [], warnings: [], ...over }); + +afterEach(() => vi.unstubAllGlobals()); + +describe("isContentLaneWired", () => { + it("requires BOTH the flag and the per-repo allowlist", () => { + expect(isContentLaneWired({ GITTENSORY_REVIEW_REPOS: REPO }, REPO)).toBe(false); // flag off + expect(isContentLaneWired({ GITTENSORY_REVIEW_CONTENT_LANE: "true", GITTENSORY_REVIEW_REPOS: "OtherOrg/other" }, REPO)).toBe(false); // not allowlisted + expect(isContentLaneWired({ GITTENSORY_REVIEW_CONTENT_LANE: "true", GITTENSORY_REVIEW_REPOS: REPO }, REPO)).toBe(true); + }); +}); + +describe("surfaceVerdictToGate", () => { + it("merge → success with no finding", () => { + const { evaluation, finding } = surfaceVerdictToGate({ verdict: "merge", summary: "ok" }); + expect(evaluation.conclusion).toBe("success"); + expect(evaluation.blockers).toEqual([]); + expect(finding).toBeNull(); + }); + + it("close → failure with a single critical blocker that is NOT an AI-judgment code", () => { + const { evaluation, finding } = surfaceVerdictToGate({ verdict: "close", summary: "bad entry" }); + expect(evaluation.conclusion).toBe("failure"); + expect(evaluation.blockers).toHaveLength(1); + expect(evaluation.blockers[0]?.severity).toBe("critical"); + expect(finding?.code).toBe("surface_lane_reject"); + // Regression guard: a deterministic surface close must never be refutable by green CI. + expect(AI_JUDGMENT_BLOCKER_CODES.has(evaluation.blockers[0]!.code)).toBe(false); + }); + + it("manual → action_required with a warning (not auto-closed)", () => { + const { evaluation, finding } = surfaceVerdictToGate({ verdict: "manual", summary: "auth declared" }); + expect(evaluation.conclusion).toBe("action_required"); + expect(evaluation.blockers).toEqual([]); + expect(evaluation.warnings).toHaveLength(1); + expect(finding?.code).toBe("surface_lane_manual"); + }); + + it("falls back to a default summary when the verdict carries none", () => { + expect(surfaceVerdictToGate({ verdict: "merge" }).evaluation.summary).toBe("Registry surface review."); + }); +}); + +describe("applySurfaceGate", () => { + const surfaceClose = gate({ conclusion: "failure", blockers: [{ code: "surface_lane_reject", title: "S", severity: "critical", detail: "" }] }); + it("null surface defers to the generic gate", () => { + const generic = gate({ conclusion: "success" }); + expect(applySurfaceGate(generic, null)).toBe(generic); + }); + it("a missing generic gate yields the surface gate", () => { + expect(applySurfaceGate(undefined, surfaceClose)).toBe(surfaceClose); + }); + it("a clean generic gate (no blockers) lets the surface verdict stand", () => { + expect(applySurfaceGate(gate({ conclusion: "success", blockers: [] }), surfaceClose)).toBe(surfaceClose); + }); + it("PRESERVES a generic hard blocker over a surface merge (a committed secret can never merge)", () => { + const secret: AdvisoryFinding = { code: "secret_leak", title: "Secret", severity: "critical", detail: "leaked key" }; + const generic = gate({ conclusion: "failure", blockers: [secret], warnings: [] }); + const surfaceMerge = gate({ conclusion: "success", title: "Surface", summary: "valid entry" }); + const out = applySurfaceGate(generic, surfaceMerge); + expect(out?.conclusion).toBe("failure"); // the secret still blocks the merge + expect(out?.blockers).toEqual([secret]); // the generic blocker survives the override + }); +}); + +describe("runMetagraphedSurfaceGate (injected loader — adapter logic)", () => { + const run = (files: { path: string; status?: string | null }[], stub: Record, advisory = { findings: [] as AdvisoryFinding[] }) => + runMetagraphedSurfaceGate(env, { installationId: 0, repoFullName: REPO, pr: { headSha: "HEAD", baseRef: "BASE" }, advisory, files }, loader(stub)); + + it("defers (null) for a non-submission PR", async () => { + expect(await run([{ path: "README.md", status: "added" }], {})).toBeNull(); + }); + + it("a valid provider submission → success, advisory left untouched (no finding)", async () => { + const advisory = { findings: [] as AdvisoryFinding[] }; + const out = await run([{ path: PROVIDER, status: "added" }], { [`head:${PROVIDER}`]: validProvider }, advisory); + expect(out?.conclusion).toBe("success"); + expect(advisory.findings).toEqual([]); + }); + + it("an invalid entry → failure, and pushes the reason into the advisory for the comment", async () => { + const advisory = { findings: [] as AdvisoryFinding[] }; + const out = await run([{ path: SUBNET, status: "modified" }], { [`head:${SUBNET}`]: doc([existing, { ...newEntry, public_safe: false }]), [`base:${SUBNET}`]: doc([existing]) }, advisory); + expect(out?.conclusion).toBe("failure"); + expect(advisory.findings.map((f) => f.code)).toEqual(["surface_lane_reject"]); + }); + + it("an unreadable head (transient fetch blip) DEFERS instead of auto-closing", async () => { + expect(await run([{ path: SUBNET, status: "modified" }], { [`base:${SUBNET}`]: doc([existing]) })).toBeNull(); + }); + + it("a null BASE on a MODIFIED file (transient blip) DEFERS — never a spurious close on a valid append", async () => { + // Valid single-entry append, but the base fetch fails (null). Without the status-aware guard the orchestrator + // would read base as [] → both head entries "new" → close. The "modified" status proves the base must exist. + expect(await run([{ path: SUBNET, status: "modified" }], { [`head:${SUBNET}`]: doc([existing, newEntry]) })).toBeNull(); + }); + + it("a null BASE on an ADDED file is the expected new-file case — NOT over-deferred (one entry merges)", async () => { + const out = await run([{ path: SUBNET, status: "added" }], { [`head:${SUBNET}`]: doc([newEntry]) }); + expect(out?.conclusion).toBe("success"); + }); +}); + +describe("resolveSurfaceRefs", () => { + it("resolves head + base, falling base back to the repo default branch then empty", () => { + expect(resolveSurfaceRefs({ headSha: "H", baseRef: "B" }, { defaultBranch: "main" })).toEqual({ headSha: "H", baseRef: "B" }); + expect(resolveSurfaceRefs({ headSha: null, baseRef: null }, { defaultBranch: "main" })).toEqual({ headSha: "", baseRef: "main" }); + expect(resolveSurfaceRefs({}, null)).toEqual({ headSha: "", baseRef: "" }); + }); +}); + +describe("evaluateWithSurfaceLane (the processor seam helper)", () => { + const generic = gate({ conclusion: "success", summary: "generic" }); + const baseArgs = { + installationId: null, + pr: { headSha: "HEAD", baseRef: "BASE" }, + repo: { defaultBranch: "main" }, + advisory: { findings: [] as AdvisoryFinding[] }, + getChangedFiles: async () => { + throw new Error("getChangedFiles must NOT be called when the lane is unwired"); + }, + }; + + it("returns the generic gate unchanged when the gate is disabled (no file resolve)", async () => { + expect(await evaluateWithSurfaceLane({ GITTENSORY_REVIEW_CONTENT_LANE: "true", GITTENSORY_REVIEW_REPOS: REPO } as unknown as Env, REPO, false, generic, baseArgs)).toBe(generic); + }); + + it("returns the generic gate unchanged when the lane is not wired (no file resolve)", async () => { + expect(await evaluateWithSurfaceLane({} as unknown as Env, REPO, true, generic, baseArgs)).toBe(generic); + }); + + it("when wired, runs the surface lane via the real GitHub loader and overrides the gate", async () => { + const bodies: Record = { + "HEAD:registry/subnets/foo.json": doc([existing, newEntry]), + "BASE:registry/subnets/foo.json": doc([existing]), + }; + vi.stubGlobal("fetch", async (url: string | URL) => { + const m = /\/contents\/(.+)\?ref=(.+)$/.exec(String(url)); + if (!m) return new Response("nope", { status: 404 }); + const path = m[1]!.split("/").map(decodeURIComponent).join("/"); + const body = bodies[`${decodeURIComponent(m[2]!)}:${path}`]; + return body === undefined ? new Response("missing", { status: 404 }) : new Response(body); + }); + const wiredEnv = { GITTENSORY_REVIEW_CONTENT_LANE: "true", GITTENSORY_REVIEW_REPOS: REPO } as unknown as Env; + const out = await evaluateWithSurfaceLane(wiredEnv, REPO, true, generic, { + installationId: null, // → unauthenticated fetcher; only the stub is hit + pr: { headSha: "HEAD", baseRef: "BASE" }, + repo: { defaultBranch: "main" }, + advisory: { findings: [] }, + getChangedFiles: async () => [{ path: SUBNET, status: "modified" }], + }); + expect(out?.conclusion).toBe("success"); // a clean append merges, overriding the generic gate + }); +}); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index ea02ad582b..412a1741cf 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 75bd6451096b895d6eceb7b7762abc79) +// Generated by Wrangler by running `wrangler types` (hash: 3ebf2c74f0fe5d19fd93a60b6f1b7380) // Runtime types generated with workerd@1.20260617.1 2026-05-28 nodejs_compat interface __BaseEnv_Env { REVIEW_CONFIG: KVNamespace; @@ -37,6 +37,7 @@ interface __BaseEnv_Env { GITTENSORY_REVIEW_REPUTATION: "true"; GITTENSORY_REVIEW_OPS: "false"; GITTENSORY_REVIEW_RAG: "true"; + GITTENSORY_REVIEW_CONTENT_LANE: "false"; GITTENSORY_REVIEW_SELFTUNE: "false"; GITTENSORY_REVIEW_DRAFT: "false"; GITTENSORY_REVIEW_PARITY_AUDIT: "false"; @@ -57,7 +58,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/wrangler.jsonc b/wrangler.jsonc index 810c84f0fd..f419feb3d5 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -86,6 +86,11 @@ // reviewer prompt byte-identical. Even when ON it is inert until a repo's vector index is populated (the // index-population job + cron is a deploy-time follow-up; a cold/missing index degrades to no context). "GITTENSORY_REVIEW_RAG": "true", + // Convergence (content/registry SURFACE LANE): when truthy AND the repo is in GITTENSORY_REVIEW_REPOS, the + // deterministic, AI-FREE surface review drives the gate for registry-submission PRs (metagraphed). Default + // OFF (false): the processor takes no new branch + resolves no files, so the gate disposition is byte- + // identical until deliberately enabled per-repo. See review/content-lane-wire. + "GITTENSORY_REVIEW_CONTENT_LANE": "false", // Convergence (self-improve / auto-tune): run the ported self-improvement loop on the cron tick over // gittensory's own review-outcome data — compute tuning recommendations, SHADOW-SOAK any strictly- // tightening one, and AUTO-PROMOTE it to live ONLY after the soak window passes the gate; every action is