Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ type ActivationPreviewResponse = {
type ActivationResponse = {
repoFullName: string;
reviewCheckMode: string;
checkRunMode: string;
linkedIssueGateMode: string;
duplicatePrGateMode: string;
qualityGateMode: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ import { GateRampControl } from "@/components/site/app-panels/gate-ramp-control"
const REVIEWABILITY = [{ pr: "acme/widgets#1" }];

const ADVISORY_SETTINGS = {
commentMode: "detected_contributors_only" as const,
publicAudienceMode: "oss_maintainer" as const,
publicSignalLevel: "standard" as const,
publicSurface: "comment_and_label" as const,
checkRunMode: "enabled" as const,
checkRunDetailLevel: "standard" as const,
reviewCheckMode: "required" as const,
gatePack: "gittensor" as const,
linkedIssueGateMode: "advisory" as const,
Expand All @@ -42,7 +36,6 @@ const ADVISORY_SETTINGS = {
autoLabelEnabled: true,
gittensorLabel: "gittensor",
createMissingLabel: true,
includeMaintainerAuthors: false,
requireLinkedIssue: false,
badgeEnabled: false,
publicQualityMetrics: false,
Expand Down Expand Up @@ -108,7 +101,7 @@ describe("GateRampControl (#2218)", () => {
expect(body.linkedIssueGateMode).toBe("block");
expect(body.duplicatePrGateMode).toBe("block");
expect(body.qualityGateMode).toBe("block");
expect(body.commentMode).toBe("detected_contributors_only");
expect(body.gittensorLabel).toBe("gittensor");
});

it("closes confirm without saving when cancel is clicked", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,65 +142,10 @@ const SLOP_FIELDS: FieldDef[] = [
},
];

// commentMode/publicSurface/publicSignalLevel/publicAudienceMode/checkRunMode/checkRunDetailLevel/
// includeMaintainerAuthors moved off the dashboard entirely (Batch A, loopover#6442) -- configure them via
// this repo's own .loopover.yml settings.* block instead.
const SURFACE_FIELDS: FieldDef[] = [
{
key: "commentMode",
label: "Comment mode",
kind: "select",
options: [
["off", "off"],
["detected_contributors_only", "detected contributors only"],
["all_prs", "all PRs"],
],
},
{
key: "publicSurface",
label: "Public surface",
kind: "select",
options: [
["off", "off"],
["comment_and_label", "comment + label"],
["comment_only", "comment only"],
["label_only", "label only"],
],
},
{
key: "publicSignalLevel",
label: "Public signal level",
kind: "select",
options: [
["minimal", "minimal"],
["standard", "standard"],
],
},
{
key: "publicAudienceMode",
label: "Audience",
kind: "select",
options: [
["oss_maintainer", "OSS maintainer"],
["gittensor_only", "gittensor only"],
],
},
{
key: "checkRunMode",
label: "Context check run",
kind: "select",
options: [
["off", "off"],
["enabled", "enabled"],
],
},
{
key: "checkRunDetailLevel",
label: "Check detail",
kind: "select",
options: [
["minimal", "minimal"],
["standard", "standard"],
],
},
{ key: "includeMaintainerAuthors", label: "Include maintainer-authored PRs", kind: "toggle" },
{ key: "requireLinkedIssue", label: "Require a linked issue", kind: "toggle" },
{ key: "badgeEnabled", label: "Repo badge", kind: "toggle" },
{ key: "publicQualityMetrics", label: "Public quality page", kind: "toggle" },
Expand Down
11 changes: 2 additions & 9 deletions apps/loopover-ui/src/lib/maintainer-settings-editable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import {
} from "@/lib/maintainer-settings-editable";

const SETTINGS: MaintainerSettingsEditable = {
commentMode: "detected_contributors_only",
publicAudienceMode: "oss_maintainer",
publicSignalLevel: "standard",
publicSurface: "comment_and_label",
checkRunMode: "enabled",
checkRunDetailLevel: "standard",
reviewCheckMode: "required",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
Expand All @@ -28,7 +22,6 @@ const SETTINGS: MaintainerSettingsEditable = {
autoLabelEnabled: true,
gittensorLabel: "gittensor",
createMissingLabel: true,
includeMaintainerAuthors: false,
requireLinkedIssue: false,
badgeEnabled: false,
publicQualityMetrics: false,
Expand All @@ -44,7 +37,7 @@ describe("maintainer-settings-editable (#2218)", () => {
const payload = buildMaintainerSettingsSavePayload(SETTINGS);
expect(Object.keys(payload).sort()).toEqual([...MAINTAINER_SETTINGS_EDITABLE_KEYS].sort());
expect(payload.linkedIssueGateMode).toBe("advisory");
expect(payload.commentMode).toBe("detected_contributors_only");
expect(payload.gittensorLabel).toBe("gittensor");
});

it("buildMaintainerSettingsSavePayload merges a partial patch over the base settings", () => {
Expand All @@ -56,7 +49,7 @@ describe("maintainer-settings-editable (#2218)", () => {
expect(payload.duplicatePrGateMode).toBe("block");
// Untouched fields pass through unchanged.
expect(payload.qualityGateMode).toBe("advisory");
expect(payload.commentMode).toBe("detected_contributors_only");
expect(payload.gittensorLabel).toBe("gittensor");
});

it("an empty patch object is a no-op (same as omitting it)", () => {
Expand Down
14 changes: 0 additions & 14 deletions apps/loopover-ui/src/lib/maintainer-settings-editable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ export type AgentActionClass =
export type AutoMergeMethod = "merge" | "squash" | "rebase";

export type MaintainerSettingsEditable = {
commentMode: "off" | "detected_contributors_only" | "all_prs";
publicAudienceMode: "oss_maintainer" | "gittensor_only";
publicSignalLevel: "minimal" | "standard";
publicSurface: "off" | "comment_and_label" | "comment_only" | "label_only";
checkRunMode: "off" | "enabled";
checkRunDetailLevel: "minimal" | "standard";
// #4618/#5373: a prior gateCheckMode field was a deprecated computed read-back, since removed entirely --
// reviewCheckMode is the real, writable authority for whether the review-agent check-run publishes.
reviewCheckMode: "required" | "visible" | "disabled";
Expand All @@ -41,7 +35,6 @@ export type MaintainerSettingsEditable = {
autoLabelEnabled: boolean;
gittensorLabel: string;
createMissingLabel: boolean;
includeMaintainerAuthors: boolean;
requireLinkedIssue: boolean;
badgeEnabled: boolean;
publicQualityMetrics: boolean;
Expand All @@ -54,12 +47,6 @@ export type MaintainerSettingsEditable = {

// The maintainer-editable subset, sent verbatim to PUT /settings (which merges onto current settings).
export const MAINTAINER_SETTINGS_EDITABLE_KEYS: Array<keyof MaintainerSettingsEditable> = [
"commentMode",
"publicAudienceMode",
"publicSignalLevel",
"publicSurface",
"checkRunMode",
"checkRunDetailLevel",
"reviewCheckMode",
"gatePack",
"linkedIssueGateMode",
Expand All @@ -75,7 +62,6 @@ export const MAINTAINER_SETTINGS_EDITABLE_KEYS: Array<keyof MaintainerSettingsEd
"autoLabelEnabled",
"gittensorLabel",
"createMissingLabel",
"includeMaintainerAuthors",
"requireLinkedIssue",
"badgeEnabled",
"publicQualityMetrics",
Expand Down
20 changes: 20 additions & 0 deletions migrations/0158_drop_batch_a_config_as_code_columns.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Config-as-code migration (Batch A, loopover#6442/epic #6440): these 9 fields already parse correctly
-- from .loopover.yml's settings: block (confirmed via audit, zero silent-discard bugs) and resolveEffectiveSettings
-- already overlays manifest settings over the DB value unconditionally -- so once the DB value itself
-- stops carrying a real per-repo override (getRepositorySettings now returns the same built-in default for
-- every repo instead of a live column), the effective behavior collapses to "manifest override, else
-- built-in default": genuine config-as-code, no more DB-vs-yml dual-source ambiguity for these fields.
-- badgeEnabled/publicQualityMetrics are DELIBERATELY excluded and stay DB-only forever: src/api/routes.ts's
-- loadPublicRepoBadge/loadPublicRepoQualityMetrics read them via a direct getRepositorySettings call that
-- bypasses the manifest overlay entirely, a documented perf tradeoff for two unauthenticated, high-frequency
-- public routes (no manifest-cache lookup, no possible cold-cache GitHub fetch, on every image/API load).
-- SQLite 3.35+ / D1 supports DROP COLUMN directly (same precedent as 0122/0146/0150).
ALTER TABLE repository_settings DROP COLUMN comment_mode;
ALTER TABLE repository_settings DROP COLUMN public_audience_mode;
ALTER TABLE repository_settings DROP COLUMN public_signal_level;
ALTER TABLE repository_settings DROP COLUMN check_run_mode;
ALTER TABLE repository_settings DROP COLUMN check_run_detail_level;
ALTER TABLE repository_settings DROP COLUMN regate_sweep_order_mode;
ALTER TABLE repository_settings DROP COLUMN public_surface;
ALTER TABLE repository_settings DROP COLUMN include_maintainer_authors;
ALTER TABLE repository_settings DROP COLUMN backfill_enabled;
78 changes: 43 additions & 35 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ import { buildMaintainerQualityDashboard, isMaintainerQualityDataStale } from ".
import { buildMaintainerSlopDuplicateTrend, SLOP_DUPLICATE_TREND_SNAPSHOT_LIMIT } from "../services/maintainer-slop-duplicate-trend";
import { buildGateOutcomeBreakdown, GATE_OUTCOME_BREAKDOWN_WINDOW_DAYS } from "../services/gate-outcome-breakdown";
import { MAX_LOCAL_SCORER_WARNING_CHARS, MAX_LOCAL_SCORER_WARNING_COUNT } from "../signals/local-scorer-diagnostics";
import { compileFocusManifestPolicy, MAX_FOCUS_MANIFEST_BYTES, normalizeReadinessGateMode } from "../signals/focus-manifest";
import { compileFocusManifestPolicy, MAX_FOCUS_MANIFEST_BYTES, normalizeReadinessGateMode, resolveEffectiveSettings } from "../signals/focus-manifest";
import { resolveRepositorySettings } from "../settings/repository-settings";
import { loadPublicRepoFocusManifest, loadRepoFocusManifest, upsertRepoFocusManifest } from "../signals/focus-manifest-loader";
import { buildRepoOnboardingPackPreviewForRepo } from "../services/repo-onboarding-pack";
Expand Down Expand Up @@ -663,15 +663,6 @@ const agentPlanSchema = z
const agentExplainBlockersSchema = z.union([localBranchAnalysisSchema, agentPlanSchema]);

const repositorySettingsSchema = z.object({
commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]).default("detected_contributors_only"),
publicAudienceMode: z.enum(["oss_maintainer", "gittensor_only"]).default("oss_maintainer"),
publicSignalLevel: z.enum(["minimal", "standard"]).default("standard"),
checkRunMode: z.enum(["off", "enabled"]).default("off"),
// Matches repository_settings.check_run_detail_level's own column default (#2907) -- the Context check's
// public output is intentionally minimal by design (see formatCheckRunOutput's doc comment), so a caller of
// this full-replace route that omits this field must land on the same safe default as a never-configured row.
checkRunDetailLevel: z.enum(["minimal", "standard"]).default("minimal"),
regateSweepOrderMode: z.enum(["staleness", "oldest-first"]).default("staleness"),
// #4618/#5373: this write schema never accepted a gateCheckMode field -- it was a deprecated computed
// read-back of reviewCheckMode, removed from RepositorySettings entirely in #5373. Set reviewCheckMode
// directly.
Expand All @@ -692,10 +683,7 @@ const repositorySettingsSchema = z.object({
gittensorLabel: z.string().trim().min(1).max(50).default("gittensor"),
blacklistLabel: z.string().trim().min(1).max(50).default("slop"),
createMissingLabel: z.boolean().default(true),
publicSurface: z.enum(["off", "comment_and_label", "comment_only", "label_only"]).default("comment_and_label"),
includeMaintainerAuthors: z.boolean().default(false),
requireLinkedIssue: z.boolean().default(false),
backfillEnabled: z.boolean().default(true),
badgeEnabled: z.boolean().default(false),
publicQualityMetrics: z.boolean().default(false),
commandAuthorization: z
Expand All @@ -719,13 +707,6 @@ const repositorySettingsSchema = z.object({
// rather than preserving it.
const maintainerSettingsSchema = z
.object({
commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]),
publicAudienceMode: z.enum(["oss_maintainer", "gittensor_only"]),
publicSignalLevel: z.enum(["minimal", "standard"]),
publicSurface: z.enum(["off", "comment_and_label", "comment_only", "label_only"]),
checkRunMode: z.enum(["off", "enabled"]),
checkRunDetailLevel: z.enum(["minimal", "standard"]),
regateSweepOrderMode: z.enum(["staleness", "oldest-first"]),
reviewCheckMode: z.enum(["required", "visible", "disabled"]),
gatePack: z.enum(["gittensor", "oss-anti-slop"]),
linkedIssueGateMode: z.enum(["off", "advisory", "block"]),
Expand All @@ -747,7 +728,6 @@ const maintainerSettingsSchema = z
gittensorLabel: z.string().trim().min(1).max(50),
blacklistLabel: z.string().trim().min(1).max(50),
createMissingLabel: z.boolean(),
includeMaintainerAuthors: z.boolean(),
closeOwnerAuthors: z.boolean(),
requireLinkedIssue: z.boolean(),
badgeEnabled: z.boolean(),
Expand Down Expand Up @@ -2534,7 +2514,10 @@ export function createApp() {
const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`;
const gate = await requireRepoMaintainer(c, fullName);
if (gate instanceof Response) return gate;
return c.json(await getRepositorySettings(c.env, fullName));
// resolveRepositorySettings (not the raw getRepositorySettings row), so this reflects the true EFFECTIVE
// value -- a config-as-code-only field (Batch A, loopover#6442) would otherwise always show its hardcoded
// default here regardless of what the repo's .loopover.yml actually configures.
return c.json(await resolveRepositorySettings(c.env, fullName));
});

// #130 maintainer settings editor: PATCH-style save of the gate / slop / label / surface / command-auth
Expand Down Expand Up @@ -2770,10 +2753,12 @@ export function createApp() {
if (gate instanceof Response) return gate;
const current = await getRepositorySettings(c.env, fullName);
const updated = await upsertRepositorySettings(c.env, { ...current, ...recommendedAdvisoryActivationSettings() });
// checkRunMode dropped (Batch A, loopover#6442): recommendedAdvisoryActivationSettings() no longer sets
// it (writing it is now a no-op), so echoing updated.checkRunMode here would just always report the
// hardcoded default regardless of what this activation actually did.
return c.json({
repoFullName: fullName,
reviewCheckMode: updated.reviewCheckMode,
checkRunMode: updated.checkRunMode,
linkedIssueGateMode: updated.linkedIssueGateMode,
duplicatePrGateMode: updated.duplicatePrGateMode,
qualityGateMode: updated.qualityGateMode,
Expand Down Expand Up @@ -4208,12 +4193,6 @@ export function createApp() {
return c.json(
await upsertRepositorySettings(c.env, {
repoFullName: fullName,
commentMode: parsed.data.commentMode,
publicAudienceMode: parsed.data.publicAudienceMode,
publicSignalLevel: parsed.data.publicSignalLevel,
checkRunMode: parsed.data.checkRunMode,
checkRunDetailLevel: parsed.data.checkRunDetailLevel,
regateSweepOrderMode: parsed.data.regateSweepOrderMode,
reviewCheckMode: parsed.data.reviewCheckMode,
gatePack: parsed.data.gatePack,
linkedIssueGateMode: parsed.data.linkedIssueGateMode,
Expand All @@ -4231,10 +4210,7 @@ export function createApp() {
gittensorLabel: parsed.data.gittensorLabel,
blacklistLabel: parsed.data.blacklistLabel,
createMissingLabel: parsed.data.createMissingLabel,
publicSurface: parsed.data.publicSurface,
includeMaintainerAuthors: parsed.data.includeMaintainerAuthors,
requireLinkedIssue: parsed.data.requireLinkedIssue,
backfillEnabled: parsed.data.backfillEnabled,
badgeEnabled: parsed.data.badgeEnabled,
publicQualityMetrics: parsed.data.publicQualityMetrics,
commandAuthorization: normalizeCommandAuthorizationPolicy(parsed.data.commandAuthorization).policy,
Expand Down Expand Up @@ -5064,19 +5040,46 @@ async function buildRepoOutcomePatternsResponse(env: Env, fullName: string) {
return attachDataQuality(response as unknown as Record<string, unknown>, dataQuality);
}

// Batch A (loopover#6442): these 9 fields moved off the DB entirely -- rawSettings (getRepositorySettings)
// always returns the same hardcoded default for them now, so a "DB vs yml" comparison built on rawSettings
// alone would be comparing a constant against yml, never reflecting a repo's real .loopover.yml-driven
// behavior. Overlays the true EFFECTIVE value for just these 9 fields onto an otherwise-raw-DB settings
// object, preserving the #2912 DB-vs-yml comparison intent for every other (still DB-backed) field.
const CONFIG_AS_CODE_ONLY_FIELDS = [
"commentMode",
"publicAudienceMode",
"publicSignalLevel",
"checkRunMode",
"checkRunDetailLevel",
"regateSweepOrderMode",
"publicSurface",
"includeMaintainerAuthors",
"backfillEnabled",
] as const satisfies ReadonlyArray<keyof RepositorySettings>;
function applyConfigAsCodeOnlyFields(rawSettings: RepositorySettings, resolvedSettings: RepositorySettings): RepositorySettings {
const settings = { ...rawSettings };
for (const field of CONFIG_AS_CODE_ONLY_FIELDS) (settings[field] as unknown) = resolvedSettings[field];
return settings;
}

export async function buildRegistrationReadinessResponse(env: Env, fullName: string) {
/* v8 ignore start -- Registration readiness route-level shaping over covered signal helpers. */
// Intentionally the raw DB `settings` alongside the raw (cache-only, never live-fetched) `focusManifest`,
// not resolveRepositorySettings's merged view: this endpoint's whole purpose is to advise on the
// relationship between the two config layers (e.g. "your yml sets X but the currently active settings say
// Y"), which requires seeing them unmerged (#2912). See buildRegistrationReadiness's use of `focusManifest`
// for the yml-compiled policy section, separate from `settings` for the currently-active-behavior section.
const [intelligence, settings, upstreamReports, focusManifest] = await Promise.all([
const [intelligence, rawSettings, upstreamReports, focusManifest] = await Promise.all([
buildRepoIntelligenceResponse(env, fullName),
getRepositorySettings(env, fullName),
listUpstreamDriftReports(env, 20),
loadRepoFocusManifest(env, fullName, { fetcher: async () => null }),
]);
// Batch A (loopover#6442): the 9 config-as-code-only fields no longer have an independent DB value to
// compare against yml (#2912's rationale doesn't apply to them anymore -- `rawSettings` would always show
// the same hardcoded default), so overlay the real EFFECTIVE value for those specific fields onto the raw
// DB settings used for everything else.
const settings = applyConfigAsCodeOnlyFields(rawSettings, resolveEffectiveSettings(rawSettings, focusManifest));
const repo = intelligence.repo;
const installation = await loadInstallationHealthSummary(env, repo);
const report = buildRegistrationReadiness({
Expand Down Expand Up @@ -5128,8 +5131,13 @@ export async function buildGittensorConfigRecommendationResponse(env: Env, fullN
// to ADD to .loopover.yml based on the repo's currently-active (dashboard/API-configured) behavior — using
// the yml-merged view here would be comparing the recommendation against itself once a yml override exists
// (#2912).
const intelligence = await buildRepoIntelligenceResponse(env, fullName);
const settings = await getRepositorySettings(env, fullName);
const [intelligence, rawSettings, resolvedSettings] = await Promise.all([
buildRepoIntelligenceResponse(env, fullName),
getRepositorySettings(env, fullName),
resolveRepositorySettings(env, fullName),
]);
// Batch A (loopover#6442): see buildRegistrationReadinessResponse's identical comment above.
const settings = applyConfigAsCodeOnlyFields(rawSettings, resolvedSettings);
const repo = intelligence.repo;
const recommendation = buildGittensorConfigRecommendation({
repoFullName: fullName,
Expand Down
Loading
Loading