From f85de863c6b5d45dc0371f98b5b818cf9852aff8 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:39:50 -0700 Subject: [PATCH] feat(review): migrate grounding onto the per-repo feature-activation resolver Adds "grounding" to CONVERGED_FEATURE_KEYS and FEATURE_GLOBAL_FLAG, and migrates both real call sites in processors.ts from the raw isGroundingEnabled(env) && isConvergenceRepoAllowed(env, repo) check to resolveConvergedFeature, so an operator can now turn grounding on/off per-repo through .gittensory.yml alone. The original "coupled to the merge/close disposition path" blocker was the removed AI CI-refutation path (grounding-wire.ts's aiCiRefutationActive is now a vestigial compatibility helper with zero real callers) -- grounding today only shapes reviewer prompt content, same shape as rag/reputation. Closes #4100. --- .gittensory.yml.example | 10 ++++-- config/examples/gittensory.full.yml | 10 ++++-- .../gittensory-engine/src/focus-manifest.ts | 16 ++++++---- src/queue/processors.ts | 31 ++++++++++++------- src/review/feature-activation.ts | 2 ++ test/unit/feature-activation.test.ts | 3 +- test/unit/focus-manifest.test.ts | 3 +- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/.gittensory.yml.example b/.gittensory.yml.example index b4e1d11e8c..a190d1c30d 100644 --- a/.gittensory.yml.example +++ b/.gittensory.yml.example @@ -1005,14 +1005,18 @@ settings: # auto_pause_after_reviewed_commits: 3 # Per-repo activation overrides for the converged review features that ship behind a deployment-wide -# GITTENSORY_REVIEW_* env kill-switch (rag/reputation/unifiedComment/safety). Each key is `true` (force on -# for this repo, subject to the env flag still being enabled), `false` (force off), or omitted (falls back -# to the GITTENSORY_REVIEW_REPOS allowlist default -- an operator who sets nothing keeps today's behavior). +# GITTENSORY_REVIEW_* env kill-switch (rag/reputation/unifiedComment/safety/grounding). Each key is `true` +# (force on for this repo, subject to the env flag still being enabled), `false` (force off), or omitted +# (falls back to the GITTENSORY_REVIEW_REPOS allowlist default -- an operator who sets nothing keeps today's +# behavior). Exception: `safety` is force-on-only -- an untrusted repo-controlled `false` is treated as "no +# opinion" rather than an active force-off (#2269), since a lower-trust actor must never be able to silently +# defeat the operator's own security-hardening enablement. # features: # rag: true # reputation: false # unifiedComment: true # safety: true +# grounding: true # Registry-review lane (#2435): lets a self-hosted maintainer point gittensory at their OWN structured # registry (e.g. a subnet/plugin/package catalog) without a gittensory code change -- reviewing additions diff --git a/config/examples/gittensory.full.yml b/config/examples/gittensory.full.yml index 6513dceaee..60e1ac1abe 100644 --- a/config/examples/gittensory.full.yml +++ b/config/examples/gittensory.full.yml @@ -1018,14 +1018,18 @@ settings: # auto_pause_after_reviewed_commits: 3 # Per-repo activation overrides for the converged review features that ship behind a deployment-wide -# GITTENSORY_REVIEW_* env kill-switch (rag/reputation/unifiedComment/safety). Each key is `true` (force on -# for this repo, subject to the env flag still being enabled), `false` (force off), or omitted (falls back -# to the GITTENSORY_REVIEW_REPOS allowlist default -- an operator who sets nothing keeps today's behavior). +# GITTENSORY_REVIEW_* env kill-switch (rag/reputation/unifiedComment/safety/grounding). Each key is `true` +# (force on for this repo, subject to the env flag still being enabled), `false` (force off), or omitted +# (falls back to the GITTENSORY_REVIEW_REPOS allowlist default -- an operator who sets nothing keeps today's +# behavior). Exception: `safety` is force-on-only -- an untrusted repo-controlled `false` is treated as "no +# opinion" rather than an active force-off (#2269), since a lower-trust actor must never be able to silently +# defeat the operator's own security-hardening enablement. # features: # rag: true # reputation: false # unifiedComment: true # safety: true +# grounding: true # Registry-review lane (#2435): lets a self-hosted maintainer point gittensory at their OWN structured # registry (e.g. a subnet/plugin/package catalog) without a gittensory code change -- reviewing additions diff --git a/packages/gittensory-engine/src/focus-manifest.ts b/packages/gittensory-engine/src/focus-manifest.ts index f08b6367ba..8623c09b2d 100644 --- a/packages/gittensory-engine/src/focus-manifest.ts +++ b/packages/gittensory-engine/src/focus-manifest.ts @@ -168,12 +168,15 @@ export type FocusManifestGateConfig = { // `.gittensory.yml`. Each feature ALSO has a GLOBAL env flag (GITTENSORY_REVIEW_*) that stays a master // kill-switch (the feature never runs when its env flag is off, regardless of this block). See // review/feature-activation.ts for the resolver (env kill-switch → per-repo override → env-allowlist default). -// NOTE: only the per-PR REVIEW features whose every activation site is migrated are listed here. grounding and -// screenshots stay on the GITTENSORY_REVIEW_REPOS allowlist for now (grounding is coupled to the merge/close -// DISPOSITION path; screenshots' capture path needs dedicated coverage) — a follow-up. contentLane got its own -// richer `contentLane:` block below (#2435) instead of a boolean here, since it resolves to a whole -// RegistryLaneSpec, not an on/off toggle — see resolveRegistryLaneSpec in review/content-lane/spec-resolver.ts. -export const CONVERGED_FEATURE_KEYS = ["rag", "reputation", "unifiedComment", "safety"] as const; +// NOTE: only the per-PR REVIEW features whose every activation site is migrated are listed here. grounding +// (#4100) is now migrated too — its original "coupled to the merge/close DISPOSITION path" blocker was the +// removed AI CI-refutation path (grounding-wire.ts's aiCiRefutationActive is now a vestigial historical- +// compatibility helper with zero real callers); grounding today only shapes reviewer PROMPT content, same +// shape as rag/reputation. `screenshots` has its own richer `visual:` block instead (review.visual.enabled, +// #4083) since it carries more than a single boolean. contentLane got its own richer `contentLane:` block below +// (#2435) instead of a boolean here, since it resolves to a whole RegistryLaneSpec, not an on/off toggle — see +// resolveRegistryLaneSpec in review/content-lane/spec-resolver.ts. +export const CONVERGED_FEATURE_KEYS = ["rag", "reputation", "unifiedComment", "safety", "grounding"] as const; export type ConvergedFeatureKey = (typeof CONVERGED_FEATURE_KEYS)[number]; /** Per-repo activation overrides for the converged review features (`features:` block). `true`/`false` force the @@ -826,6 +829,7 @@ const EMPTY_FEATURES_CONFIG: FocusManifestFeaturesConfig = { reputation: null, unifiedComment: null, safety: null, + grounding: null, }; const EMPTY_CONTENT_LANE_CONFIG: FocusManifestContentLaneConfig = { diff --git a/src/queue/processors.ts b/src/queue/processors.ts index 75c769f19f..cf7ef2bb2b 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -6999,13 +6999,13 @@ export async function runAiReviewForAdvisory( // feature's global flag below. Empty/unset allowlist → false → every converged branch here is unreachable // (byte-identical to today) regardless of the global flags. const convergedRepoAllowed = isConvergenceRepoAllowed(env, args.repoFullName); - // Per-repo feature overrides (phase 2): reputation + RAG honor the container-private `.gittensory.yml` `features:` - // block, falling back to the `convergedRepoAllowed` allowlist when unset (byte-identical default). The (cached) - // manifest is loaded once and shared, and ONLY when at least one of the two features is globally enabled — so a - // deploy with both flags off does no extra read (preserves the no-op default). Grounding deliberately stays on - // `convergedRepoAllowed` here so prompt grounding remains tied to the converged review allowlist. + // Per-repo feature overrides (phase 2): reputation + RAG + grounding (#4100) honor the container-private + // `.gittensory.yml` `features:` block, falling back to the `convergedRepoAllowed` allowlist when unset + // (byte-identical default). The (cached) manifest is loaded once and shared, and ONLY when at least one of the + // three features is globally enabled — so a deploy with all three flags off does no extra read (preserves the + // no-op default). const featureManifest = - isReputationEnabled(env) || isRagEnabled(env) + isReputationEnabled(env) || isRagEnabled(env) || isGroundingEnabled(env) ? await loadRepoFocusManifest(env, args.repoFullName).catch(() => null) : null; const reputationActive = resolveConvergedFeature( @@ -7020,6 +7020,12 @@ export async function runAiReviewForAdvisory( "rag", args.repoFullName, ); + const groundingActive = resolveConvergedFeature( + env, + featureManifest, + "grounding", + args.repoFullName, + ); // Reputation anti-abuse (convergence, flag-gated by GITTENSORY_REVIEW_REPUTATION). Extends the AI-spend gate above: // an INTERNAL low-reputation / burst / new submitter is downgraded to a DETERMINISTIC-ONLY review — the // (paid) AI neurons are skipped here exactly as they are for an unconfirmed contributor, so a serial abuser @@ -7088,12 +7094,13 @@ export async function runAiReviewForAdvisory( args.settings.aiReviewMode === "block" ? allFiles : filterReviewFilesForAi(allFiles, args.reviewExcludePaths ?? [], args.reviewPathFilters ?? []); - // Grounding (convergence, flag-gated by GITTENSORY_REVIEW_GROUNDING). Build the FINISHED CI status + the full - // content of the changed files so the reviewer verifies its claims against reality instead of guessing. - // Flag-OFF (default) → we take no new branch at all: NO check/repo load, NO file fetch, and `grounding` - // is left undefined so the prompt handed to the model is byte-identical to today. Fully fail-safe. + // Grounding (convergence, flag-gated by GITTENSORY_REVIEW_GROUNDING; per-repo `features.grounding` override, + // #4100). Build the FINISHED CI status + the full content of the changed files so the reviewer verifies its + // claims against reality instead of guessing. Flag-OFF (default) → we take no new branch at all: NO + // check/repo load, NO file fetch, and `grounding` is left undefined so the prompt handed to the model is + // byte-identical to today. Fully fail-safe. const grounding = - isGroundingEnabled(env) && convergedRepoAllowed + groundingActive ? await buildReviewGroundingText(env, { repoFullName: args.repoFullName, headSha: args.advisory.headSha, @@ -9273,7 +9280,7 @@ async function maybePublishPrPublicSurface( // without fetching the content itself (which would defeat caching), so a repo with ANY of these active // bypasses the cache entirely rather than fingerprinting a value that can't prove freshness. const dynamicReviewFeatures = { - grounding: isGroundingEnabled(env) && convergedRepoAllowed, + grounding: resolveConvergedFeature(env, reviewManifest, "grounding", repoFullName), rag: resolveConvergedFeature(env, reviewManifest, "rag", repoFullName), enrichment: isEnrichmentEnabled(env) && convergedRepoAllowed, reputation: resolveConvergedFeature( diff --git a/src/review/feature-activation.ts b/src/review/feature-activation.ts index 25a646d0b4..34bec05c52 100644 --- a/src/review/feature-activation.ts +++ b/src/review/feature-activation.ts @@ -17,6 +17,7 @@ // `resolveConvergedFeature` is the pure core (takes the already-loaded manifest). `convergedFeatureActive` is the // async convenience that loads the cached focus manifest itself — used at call sites that don't already hold one. import { isConvergenceRepoAllowed } from "./cutover-gate"; +import { isGroundingEnabled } from "./grounding-wire"; import { isRagEnabled } from "./rag-wire"; import { isReputationEnabled } from "./reputation-wire"; import { isSafetyEnabled } from "./safety"; @@ -30,6 +31,7 @@ const FEATURE_GLOBAL_FLAG: Record boolean> = reputation: isReputationEnabled, unifiedComment: isUnifiedReviewCommentEnabled, safety: isSafetyEnabled, + grounding: isGroundingEnabled, }; /** diff --git a/test/unit/feature-activation.test.ts b/test/unit/feature-activation.test.ts index 07383c4603..65acca57d4 100644 --- a/test/unit/feature-activation.test.ts +++ b/test/unit/feature-activation.test.ts @@ -12,13 +12,14 @@ const FLAG: Record = { reputation: "GITTENSORY_REVIEW_REPUTATION", unifiedComment: "GITTENSORY_REVIEW_UNIFIED_COMMENT", safety: "GITTENSORY_REVIEW_SAFETY", + grounding: "GITTENSORY_REVIEW_GROUNDING", }; function env(overrides: Record): Env { return overrides as unknown as Env; } function manifestWith(features: Partial>): Pick { - const base = { present: false, rag: null, reputation: null, unifiedComment: null, safety: null } as FocusManifest["features"]; + const base = { present: false, rag: null, reputation: null, unifiedComment: null, safety: null, grounding: null } as FocusManifest["features"]; return { features: { ...base, ...features, present: Object.keys(features).length > 0 } }; } diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 536fe9f4f7..5456d69160 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -397,6 +397,7 @@ describe(".gittensory.yml.example field-exhaustiveness (#1670)", () => { reputation: "reputation:", unifiedComment: "unifiedComment:", safety: "safety:", + grounding: "grounding:", } satisfies Record, string>; it.each(Object.entries(FEATURES_FIELD_TOKENS))("documents features.%s", (_field, token) => { @@ -801,7 +802,7 @@ describe("compileFocusManifestPolicy", () => { gate: { present: false, enabled: null, checkMode: null, pack: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, slopMode: null, slopMinScore: null, slopAiAdvisory: null, sizeMode: null, lockfileIntegrityMode: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, aiReviewAllAuthors: null, aiReviewCloseConfidence: null, aiReviewCombine: null, aiReviewOnMerge: null, aiReviewReviewers: null, mergeReadiness: null, selfAuthoredLinkedIssue: null, linkedIssueSatisfaction: null, manifestPolicy: null, dryRun: null, firstTimeContributorGrace: null, premergeContentRecheck: null, requireFreshRebaseWindowMinutes: null, claMode: null, claConsentPhrase: null, claCheckRunName: null, claCheckRunAppSlug: null, expectedCiContexts: null }, settings: {}, review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, testGeneration: null, impactMap: null, cultureProfile: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { blockers: null, nits: null }, commentVerbosity: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, labelingRules: [], aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null }, - features: { present: false, rag: null, reputation: null, unifiedComment: null, safety: null }, + features: { present: false, rag: null, reputation: null, unifiedComment: null, safety: null, grounding: null }, contentLane: { present: false, entryFileGlob: null, providerFileGlob: null, artifactGlob: null, collectionField: null, maxAppendedEntries: null, duplicateKeyFields: [], validatorId: null }, repoDocGeneration: { present: false, enabled: false, scope: ["agents"], allowOverwriteExisting: false, refreshIntervalDays: 7 }, reviewRecap: { present: false, enabled: false, cadenceDays: 7 },