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
8 changes: 0 additions & 8 deletions .loopover.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,6 @@ gate:
# conclusion; it does NOT stop the agent action layer from real writes.
dryRun: false

# First-time-contributor grace. RESERVED / currently INERT: this value is
# parsed and stored, but the gate does not read it — a first-time
# contributor with a real blocker is one-shot closed the same as a
# repeat contributor (blocker findings must remain closure outcomes).
# Setting this to true has no effect today; kept for potential future
# use. Bool. Default: false.
firstTimeContributorGrace: false

# Live premerge migrations/** collision recheck (#2550, anti-abuse-adjacent safety net). When true, a PR
# touching migrations/** gets a fresh GitHub read of the base branch's CURRENT migration filenames
# immediately before an agent-driven merge (not just at CI time against this PR's own branch snapshot) —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ For a repo that has never been configured, the default is `disabled`; an already
- `gate.premergeContentRecheck` — when `true`, a PR touching `migrations/**` gets a fresh GitHub read of the base branch's current migration filenames immediately before an agent-driven merge, catching a different PR that merged a same-numbered migration in the meantime. A live collision holds the PR instead of merging blind. Default `false` — costs one extra GitHub API call per migrations-touching PR.
- `gate.requireFreshRebaseWindow` — when the base branch has advanced within this many minutes of the actual merge decision, forces an `update_branch` + fresh CI recheck before merging, instead of trusting a possibly-stale `mergeable_state: clean` read. A bounded retry cap prevents a fast-moving base from live-locking the PR. Default `null` (never force).
- `gate.dryRun` — when `true`, the posted check conclusion remains the real non-enforcing verdict while comments/check text may also show the would-be stricter verdict for AI-review blocker mode. It does not disable downstream merge/close planning for failures from already-enforced gates. Default `false`.
- `gate.firstTimeContributorGrace` — reserved and currently inert: parsed and stored, but the gate does not read it. A first-time contributor with a real blocker is one-shot closed the same as a repeat contributor. Kept for potential future use.

### settings.closeOwnerAuthors and blockedPaths

Expand Down
10 changes: 0 additions & 10 deletions apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8928,10 +8928,6 @@
"block"
]
},
"firstTimeContributorGrace": {
"type": "boolean",
"description": "Reserved (#2266) -- the gate evaluator never reads this field. Currently has no effect on gate decisions."
},
"slopGateMinScore": {
"type": "number",
"nullable": true
Expand Down Expand Up @@ -9717,7 +9713,6 @@
"manifestPolicyGateMode",
"selfAuthoredLinkedIssueGateMode",
"linkedIssueSatisfactionGateMode",
"firstTimeContributorGrace",
"slopAiAdvisory",
"aiReviewMode",
"aiReviewByok",
Expand Down Expand Up @@ -10316,10 +10311,6 @@
"block"
]
},
"firstTimeContributorGrace": {
"type": "boolean",
"description": "Reserved (#2266) -- the gate evaluator never reads this field. Currently has no effect on gate decisions."
},
"slopGateMinScore": {
"type": "number",
"nullable": true
Expand Down Expand Up @@ -10444,7 +10435,6 @@
"manifestPolicyGateMode",
"selfAuthoredLinkedIssueGateMode",
"linkedIssueSatisfactionGateMode",
"firstTimeContributorGrace",
"autoLabelEnabled",
"typeLabelsEnabled",
"gittensorLabel",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const ADVISORY_SETTINGS = {
qualityGateMinScore: null,
mergeReadinessGateMode: "off" as const,
manifestPolicyGateMode: "off" as const,
firstTimeContributorGrace: false,
slopGateMode: "off" as const,
slopGateMinScore: null,
slopAiAdvisory: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ type ToggleFieldDef = {
kind: "toggle";
hint?: string;
// Renders greyed-out and non-interactive with the hint as the disclosure -- for a field that is real
// and DB-backed but currently wired to nothing (e.g. firstTimeContributorGrace, #2266/#2411), so a
// maintainer can't be misled into thinking the toggle has an effect.
// and DB-backed but currently wired to nothing, so a maintainer can't be misled into thinking the
// toggle has an effect.
disabled?: boolean;
};
type NumberFieldDef = {
Expand Down Expand Up @@ -116,13 +116,6 @@ const GATE_FIELDS: FieldDef[] = [
kind: "select",
options: GATE_MODE_OPTIONS,
},
{
key: "firstTimeContributorGrace",
label: "First-time-contributor grace",
kind: "toggle",
hint: "Reserved — currently has no effect on gate decisions (#2266)",
disabled: true,
},
];

const SLOP_FIELDS: FieldDef[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const SETTINGS: MaintainerSettingsEditable = {
qualityGateMinScore: null,
mergeReadinessGateMode: "off",
manifestPolicyGateMode: "off",
firstTimeContributorGrace: false,
slopGateMode: "off",
slopGateMinScore: null,
slopAiAdvisory: false,
Expand Down
2 changes: 0 additions & 2 deletions apps/loopover-ui/src/lib/maintainer-settings-editable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export type MaintainerSettingsEditable = {
qualityGateMinScore: number | null;
mergeReadinessGateMode: GateMode;
manifestPolicyGateMode: GateMode;
firstTimeContributorGrace: boolean;
slopGateMode: GateMode;
slopGateMinScore: number | null;
slopAiAdvisory: boolean;
Expand All @@ -53,7 +52,6 @@ export const MAINTAINER_SETTINGS_EDITABLE_KEYS: Array<keyof MaintainerSettingsEd
"qualityGateMinScore",
"mergeReadinessGateMode",
"manifestPolicyGateMode",
"firstTimeContributorGrace",
"slopGateMode",
"slopGateMinScore",
"slopAiAdvisory",
Expand Down
8 changes: 0 additions & 8 deletions config/examples/loopover.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,6 @@ gate:
# conclusion; it does NOT stop the agent action layer from real writes.
dryRun: false

# First-time-contributor grace. RESERVED / currently INERT: this value is
# parsed and stored, but the gate does not read it — a first-time
# contributor with a real blocker is one-shot closed the same as a
# repeat contributor (blocker findings must remain closure outcomes).
# Setting this to true has no effect today; kept for potential future
# use. Bool. Default: false.
firstTimeContributorGrace: false

# Live premerge migrations/** collision recheck (#2550, anti-abuse-adjacent safety net). When true, a PR
# touching migrations/** gets a fresh GitHub read of the base branch's CURRENT migration filenames
# immediately before an agent-driven merge (not just at CI time against this PR's own branch snapshot) —
Expand Down
7 changes: 7 additions & 0 deletions migrations/0161_drop_first_time_contributor_grace.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- loopover#6446/epic #6440: firstTimeContributorGrace (added by migration 0039) is a dead surface -- parsed,
-- clamped, and threaded end-to-end since PR #552, but evaluateGateCheckCore never read it (a deliberate
-- removal per #2411: blocker findings must remain closure/rejection outcomes, not softened for a genuine
-- newcomer). #5321 already spent a PR just disclosing the inertness in the dashboard rather than fixing it.
-- Decision (#6446): delete the dead parsing/manifest/DB/dashboard surface entirely instead of wiring it in,
-- since re-wiring would reintroduce a softened-block exception against the gate's settled one-shot design.
ALTER TABLE repository_settings DROP COLUMN first_time_contributor_grace;
12 changes: 4 additions & 8 deletions packages/loopover-engine/src/advisory/gate-advisory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,11 @@ export type GateCheckPolicy = {
* conclusion) confirms consent — becomes a hard blocker. `off` (default) = no finding at all; `advisory` =
* the finding surfaces but never blocks. Independent of every other gate dimension, like manifestPolicy. */
claGateMode?: GateRuleMode | undefined;
/** First-time-contributor grace (#552). RESERVED / currently INERT (#2266): threaded through from config,
* but evaluateGateCheckCore never reads it (see the removal note below) — a would-be blocker gates a
* genuine newcomer exactly like a repeat contributor. Kept for potential future use. */
firstTimeContributorGrace?: boolean | undefined;
/** The PR author's merged PR count in THIS repo. RESERVED / currently INERT (#2266) alongside
* firstTimeContributorGrace above — populated but never read by the gate evaluator today. */
/** The PR author's merged PR count in THIS repo. RESERVED / currently INERT (#2266) — populated but never
* read by the gate evaluator today. */
authorMergedPrCount?: number | undefined;
/** The PR author's closed-unmerged PR count in THIS repo. RESERVED / currently INERT (#2266) alongside
* firstTimeContributorGrace above — populated but never read by the gate evaluator today. */
/** The PR author's closed-unmerged PR count in THIS repo. RESERVED / currently INERT (#2266) — populated
* but never read by the gate evaluator today. */
authorClosedUnmergedPrCount?: number | undefined;
/** The PR author's confirmed-Gittensor status. Carried for context/telemetry only — it no longer
* changes the gate verdict (every author is gated identically; a configured blocker fails the gate
Expand Down
12 changes: 0 additions & 12 deletions packages/loopover-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ export type FocusManifestGateConfig = {
* cross-reference. */
linkedIssueSatisfaction: GateRuleMode | null;
dryRun: boolean | null;
firstTimeContributorGrace: boolean | null;
/** `gate.premergeContentRecheck` (#2550): for a PR touching `migrations/**`, re-verify against a live,
* freshly-fetched tip of the base branch — unioned with this PR's own new migration filenames — for a
* migration-number collision immediately before an agent-driven merge, not just at CI time against the
Expand Down Expand Up @@ -1166,7 +1165,6 @@ const EMPTY_GATE_CONFIG: FocusManifestGateConfig = {
selfAuthoredLinkedIssue: null,
linkedIssueSatisfaction: null,
dryRun: null,
firstTimeContributorGrace: null,
premergeContentRecheck: null,
requireFreshRebaseWindowMinutes: null,
claMode: null,
Expand Down Expand Up @@ -1629,7 +1627,6 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
selfAuthoredLinkedIssue: normalizeOptionalGateMode(record.selfAuthoredLinkedIssue, "gate.selfAuthoredLinkedIssue", warnings),
linkedIssueSatisfaction: normalizeOptionalGateMode(record.linkedIssueSatisfaction, "gate.linkedIssueSatisfaction", warnings),
dryRun: normalizeOptionalBoolean(record.dryRun, "gate.dryRun", warnings),
firstTimeContributorGrace: normalizeOptionalBoolean(record.firstTimeContributorGrace, "gate.firstTimeContributorGrace", warnings),
premergeContentRecheck: normalizeOptionalBoolean(record.premergeContentRecheck, "gate.premergeContentRecheck", warnings),
requireFreshRebaseWindowMinutes: normalizeOptionalPositiveInteger(record.requireFreshRebaseWindow, "gate.requireFreshRebaseWindow", warnings),
claMode: normalizeOptionalGateMode(record.claMode, "gate.claMode", warnings),
Expand All @@ -1642,13 +1639,6 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
copycatMode: normalizeOptionalEnum(copycatRecord?.mode, "gate.copycat.mode", ["off", "warn", "label", "block"] as const, warnings),
copycatMinScore: normalizeOptionalScore(copycatRecord?.minScore, "gate.copycat.minScore", warnings),
};
// #2266: the flag is parsed, clamped, and threaded end-to-end, but the gate evaluator never reads it — a
// maintainer who sets it to true believing it softens a blocker for newcomers gets no such effect. Surface
// that inertness at parse time rather than leaving it silently no-op; `false`/unset matches the (also inert)
// default, so only an explicit `true` is worth flagging.
if (gate.firstTimeContributorGrace === true) {
warnings.push(`Manifest field "gate.firstTimeContributorGrace" is currently reserved/inert — it does not soften a blocker outcome for first-time contributors.`);
}
// gate.enabled only controls whether the "LoopOver Orb Review Agent" check-run publishes (the legacy
// enabled -> reviewCheckMode alias in applyGateConfigOverrides) -- it does NOT gate spend, merge, comment,
// label, or close behavior, and it cannot express checkMode's "visible" state. checkMode always wins when
Expand Down Expand Up @@ -1692,7 +1682,6 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
gate.selfAuthoredLinkedIssue !== null ||
gate.linkedIssueSatisfaction !== null ||
gate.dryRun !== null ||
gate.firstTimeContributorGrace !== null ||
gate.premergeContentRecheck !== null ||
gate.requireFreshRebaseWindowMinutes !== null ||
gate.claMode !== null ||
Expand Down Expand Up @@ -1774,7 +1763,6 @@ export function gateConfigToJson(gate: FocusManifestGateConfig): JsonValue {
if (gate.selfAuthoredLinkedIssue !== null) out.selfAuthoredLinkedIssue = gate.selfAuthoredLinkedIssue;
if (gate.linkedIssueSatisfaction !== null) out.linkedIssueSatisfaction = gate.linkedIssueSatisfaction;
if (gate.dryRun !== null) out.dryRun = gate.dryRun;
if (gate.firstTimeContributorGrace !== null) out.firstTimeContributorGrace = gate.firstTimeContributorGrace;
if (gate.premergeContentRecheck !== null) out.premergeContentRecheck = gate.premergeContentRecheck;
if (gate.requireFreshRebaseWindowMinutes !== null) out.requireFreshRebaseWindow = gate.requireFreshRebaseWindowMinutes;
if (gate.claMode !== null) out.claMode = gate.claMode;
Expand Down
1 change: 0 additions & 1 deletion packages/loopover-engine/src/predicted-gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ export function buildPredictedGateVerdict(args: {
claGateMode: gate.claMode ?? undefined,
readinessScore: readiness.total,
confirmedContributor: effectiveConfirmedContributor,
firstTimeContributorGrace: gate.firstTimeContributorGrace ?? undefined,
authorMergedPrCount: authorHistory.filter((pr) => pr.state === "merged" || pr.mergedAt).length,
authorClosedUnmergedPrCount: authorHistory.filter((pr) => pr.state === "closed" && !pr.mergedAt).length,
// Size-hold + guardrail-hold parity (#2458): only meaningful when changed paths were supplied — changedPaths
Expand Down
5 changes: 0 additions & 5 deletions packages/loopover-engine/src/types/manifest-deps-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,6 @@ export type RepositorySettings = {
* opened the linked issue (`pr.authorLogin === issue.authorLogin`). Defaults to `advisory` — the finding
* is surfaced in the review panel but never blocks unless the maintainer opts in. */
selfAuthoredLinkedIssueGateMode: GateRuleMode;
/** First-time-contributor grace (#552). RESERVED / currently INERT (#2266): parsed, clamped, and threaded
* end-to-end, but the gate evaluator never reads it — a genuine newcomer with a real blocker is still
* one-shot closed exactly like a repeat contributor (blocker findings must remain closure outcomes).
* Setting this true has no runtime effect today; kept for potential future use. Default false. */
firstTimeContributorGrace: boolean;
/** Slop-risk threshold (0-100) at/above which `slopGateMode: block` blocks. Default 60 (the `high` band). */
slopGateMinScore?: number | null | undefined;
/** AI-assisted slop advisory (the `slopAiAdvisory` capability). When true AND `slopGateMode != off`, a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ export type FocusManifestGateConfig = {
manifestPolicy: GateRuleMode | null;
selfAuthoredLinkedIssue: GateRuleMode | null;
dryRun: boolean | null;
firstTimeContributorGrace: boolean | null;
premergeContentRecheck: boolean | null;
requireFreshRebaseWindowMinutes: number | null;
claMode: GateRuleMode | null;
Expand Down
6 changes: 3 additions & 3 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,9 @@ const maintainerSettingsSchema = z
linkedIssueSatisfactionGateMode: z.enum(["off", "advisory", "block"]),
// #6443: mergeTrainMode/gittensorLabel/blacklistLabel/createMissingLabel removed -- no longer DB-backed,
// config-as-code only via .loopover.yml's settings: block now.
firstTimeContributorGrace: z
.boolean()
.describe("Reserved (#2266) -- the gate evaluator never reads this field. Currently has no effect on gate decisions."),
// #6446: firstTimeContributorGrace removed -- a dead, never-wired RESERVED/INERT field (#2266); deleted
// rather than wired in, since the gate's one-shot design deliberately never softens a blocker for a
// newcomer.
slopGateMode: z.enum(["off", "advisory", "block"]),
slopGateMinScore: z.number().int().min(0).max(100).nullable(),
slopAiAdvisory: z.boolean(),
Expand Down
5 changes: 0 additions & 5 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,6 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
manifestPolicyGateMode: "off",
selfAuthoredLinkedIssueGateMode: "advisory",
linkedIssueSatisfactionGateMode: "off",
firstTimeContributorGrace: false,
slopGateMinScore: null,
slopAiAdvisory: false,
aiReviewMode: "off",
Expand Down Expand Up @@ -676,7 +675,6 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
manifestPolicyGateMode: parseGateRuleMode(row.manifestPolicyGateMode),
selfAuthoredLinkedIssueGateMode: parseGateRuleMode(row.selfAuthoredLinkedIssueGateMode),
linkedIssueSatisfactionGateMode: parseGateRuleMode(row.linkedIssueSatisfactionGateMode),
firstTimeContributorGrace: row.firstTimeContributorGrace,
slopGateMinScore: normalizeQualityGateMinScore(row.slopGateMinScore),
slopAiAdvisory: row.slopAiAdvisory,
aiReviewMode: parseGateRuleMode(row.aiReviewMode),
Expand Down Expand Up @@ -809,7 +807,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
manifestPolicyGateMode: settings.manifestPolicyGateMode ?? "off",
selfAuthoredLinkedIssueGateMode: settings.selfAuthoredLinkedIssueGateMode ?? "advisory",
linkedIssueSatisfactionGateMode: settings.linkedIssueSatisfactionGateMode ?? "off",
firstTimeContributorGrace: settings.firstTimeContributorGrace ?? false,
slopGateMinScore: normalizeQualityGateMinScore(settings.slopGateMinScore),
slopAiAdvisory: settings.slopAiAdvisory ?? false,
aiReviewMode: settings.aiReviewMode ?? "off",
Expand Down Expand Up @@ -893,7 +890,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
manifestPolicyGateMode: resolved.manifestPolicyGateMode,
selfAuthoredLinkedIssueGateMode: resolved.selfAuthoredLinkedIssueGateMode,
linkedIssueSatisfactionGateMode: resolved.linkedIssueSatisfactionGateMode,
firstTimeContributorGrace: resolved.firstTimeContributorGrace,
slopGateMinScore: resolved.slopGateMinScore,
slopAiAdvisory: resolved.slopAiAdvisory,
aiReviewMode: resolved.aiReviewMode,
Expand Down Expand Up @@ -938,7 +934,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
manifestPolicyGateMode: resolved.manifestPolicyGateMode,
selfAuthoredLinkedIssueGateMode: resolved.selfAuthoredLinkedIssueGateMode,
linkedIssueSatisfactionGateMode: resolved.linkedIssueSatisfactionGateMode,
firstTimeContributorGrace: resolved.firstTimeContributorGrace,
slopGateMinScore: resolved.slopGateMinScore,
slopAiAdvisory: resolved.slopAiAdvisory,
aiReviewMode: resolved.aiReviewMode,
Expand Down
1 change: 0 additions & 1 deletion src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export const repositorySettings = sqliteTable("repository_settings", {
// confidence-floor "unaddressed" verdict additionally becomes a hard blocker. See src/rules/advisory.ts's
// isConfiguredGateBlocker (linked_issue_scope_mismatch) and loopover-gate-setting-wiring for the pattern.
linkedIssueSatisfactionGateMode: text("linked_issue_satisfaction_gate_mode").notNull().default("off"),
firstTimeContributorGrace: integer("first_time_contributor_grace", { mode: "boolean" }).notNull().default(false),
slopGateMinScore: integer("slop_gate_min_score"),
slopAiAdvisory: integer("slop_ai_advisory", { mode: "boolean" }).notNull().default(false),
aiReviewMode: text("ai_review_mode").notNull().default("off"),
Expand Down
Loading
Loading