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
14 changes: 14 additions & 0 deletions .loopover.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,20 @@ gate:
# spelling has the same real effect; this one wins if both are set.
linkedIssueSatisfaction: off

# Content-lane linked-issue deliverable gate — only meaningful for a repo with
# a registry content-lane spec resolved (see review/content-lane/spec-resolver.ts);
# a no-op otherwise. When the PR's primary linked issue's own text names a path
# matching that spec's entry/provider file pattern, the PR's changed files must
# touch at least one matching file -- a PR that never touches the content lane
# at all (e.g. adds only a regression test) cannot silently close a content-
# delivery issue via a bare "Closes #N" reference. off = never checked;
# advisory = a miss renders as a warning but never blocks; block = a miss
# ALSO becomes a hard blocker (content_lane_deliverable_missing). Fully
# deterministic (a text/path match, no AI call). off | advisory | block.
# Default: off. DB-backed (dashboard-settable too); this overrides the stored
# value.
contentLaneDeliverable: off

# Gate-check dry-run. 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
Expand Down
5 changes: 5 additions & 0 deletions apps/loopover-ui/content/docs/github-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ concern from the gate's own merge/close decision.
its primary linked issue's intent, distinct from `linkedIssueGateMode` (which only checks a link
exists). Default `off`; `advisory` renders in the review comment without blocking, `block`
additionally lets a confidence-floor-passing "unaddressed" verdict become a blocker.
- `contentLaneDeliverableGateMode` — only meaningful for a repo with a registry content-lane spec
configured; a no-op otherwise. Fully deterministic (a text/path match, no AI call): when the PR's
primary linked issue's own text names a path matching the content lane's entry/provider file
pattern, the PR's changed files must touch at least one matching file. Default `off`; `advisory`
renders a miss as a warning, `block` additionally makes a miss a blocker.
- `moderationGateMode` — whether the moderation-rules engine (contributor cap, blacklist,
review-nag feeding a shared cross-repo violation tally) runs on this repo. Default `inherit`
(defers to the instance-wide default); `off`/`enabled` force it per repo.
Expand Down
1 change: 1 addition & 0 deletions apps/loopover-ui/content/docs/how-reviews-work.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ These are the deterministic rules the gate runs, with their default modes:
- **CLA / license gate** (`claGateMode`, default `off`) — CLA / license-compatibility check.
- **Self-authored-linked-issue gate** (`selfAuthoredLinkedIssueGateMode`, default `advisory`) — flags or blocks a PR whose author also opened the linked issue.
- **Linked-issue satisfaction gate** (`linkedIssueSatisfactionGateMode`, default `off`) — an AI assessment of whether the PR's diff actually satisfies its primary linked issue's intent (distinct from the linked-issue gate above, which only checks that a link exists). `advisory` renders the assessment in the review comment without ever blocking; `block` additionally lets a confidence-floor-passing "unaddressed" verdict become a hard blocker.
- **Content-lane deliverable gate** (`contentLaneDeliverableGateMode`, default `off`) — only meaningful for a repo with a registry content-lane spec configured (see `contentLane` under Advanced config); a no-op otherwise. Fully deterministic (a text/path match, no AI call): when the PR's primary linked issue's own text names a path matching the content lane's entry/provider file pattern, the PR's changed files must touch at least one matching file, so a PR that never touches the content lane at all (e.g. adds only a regression test) cannot close a content-delivery issue via a bare "Closes #N" reference. `advisory` renders a miss as a warning; `block` additionally makes a miss a hard blocker.
- **Moderation-rules engine** (`moderationGateMode`, default `inherit`) — whether the contributor-cap / blacklist / review-nag mechanisms feed a shared, cross-repo violation tally on this repo; `inherit` defers to the instance-wide default, `off`/`enabled` force it per repo.

Which deterministic rules even apply is set by the **policy pack** (`gatePack`): `gittensor` (registry-aware, tracks confirmed-Gittensor- contributor status for scoring) or `oss-anti-slop` (runs the rules against any author on any repo, with no confirmed-contributor tracking at all).
Expand Down
7 changes: 7 additions & 0 deletions apps/loopover-ui/content/docs/tuning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ forward and wins whenever both are set for the same field.
`advisory` renders the assessment in the review comment without blocking;
`block` additionally lets a confidence-floor-passing "unaddressed" verdict
become a blocker.
- `gate.contentLaneDeliverable` — only meaningful for a repo with a registry
content-lane spec configured (`contentLane`); a no-op otherwise. Fully
deterministic (a text/path match, no AI call): when the PR's primary linked
issue's own text names a path matching the content lane's entry/provider
file pattern, the PR's changed files must touch at least one matching file.
Default `off`. `advisory` renders a miss as a warning; `block` additionally
makes a miss a blocker.
- `settings.moderationGateMode` — whether the moderation-rules engine
(contributor cap, blacklist, review-nag feeding a shared cross-repo violation tally) runs
on this repo at all. `inherit` (default) defers to the instance-wide
Expand Down
18 changes: 18 additions & 0 deletions apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9745,6 +9745,14 @@
"nullable": true,
"minimum": 0,
"exclusiveMinimum": true
},
"contentLaneDeliverableGateMode": {
"type": "string",
"enum": [
"off",
"advisory",
"block"
]
}
},
"required": [
Expand All @@ -9765,6 +9773,7 @@
"manifestPolicyGateMode",
"selfAuthoredLinkedIssueGateMode",
"linkedIssueSatisfactionGateMode",
"contentLaneDeliverableGateMode",
"slopAiAdvisory",
"aiReviewMode",
"aiReviewByok",
Expand Down Expand Up @@ -10467,6 +10476,14 @@
"defaultAllowed",
"commandOverrides"
]
},
"contentLaneDeliverableGateMode": {
"type": "string",
"enum": [
"off",
"advisory",
"block"
]
}
},
"required": [
Expand All @@ -10487,6 +10504,7 @@
"manifestPolicyGateMode",
"selfAuthoredLinkedIssueGateMode",
"linkedIssueSatisfactionGateMode",
"contentLaneDeliverableGateMode",
"autoLabelEnabled",
"typeLabelsEnabled",
"gittensorLabel",
Expand Down
14 changes: 14 additions & 0 deletions config/examples/loopover.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,20 @@ gate:
# spelling has the same real effect; this one wins if both are set.
linkedIssueSatisfaction: off

# Content-lane linked-issue deliverable gate — only meaningful for a repo with
# a registry content-lane spec resolved (see review/content-lane/spec-resolver.ts);
# a no-op otherwise. When the PR's primary linked issue's own text names a path
# matching that spec's entry/provider file pattern, the PR's changed files must
# touch at least one matching file -- a PR that never touches the content lane
# at all (e.g. adds only a regression test) cannot silently close a content-
# delivery issue via a bare "Closes #N" reference. off = never checked;
# advisory = a miss renders as a warning but never blocks; block = a miss
# ALSO becomes a hard blocker (content_lane_deliverable_missing). Fully
# deterministic (a text/path match, no AI call). off | advisory | block.
# Default: off. DB-backed (dashboard-settable too); this overrides the stored
# value.
contentLaneDeliverable: off

# Gate-check dry-run. 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
Expand Down
6 changes: 6 additions & 0 deletions migrations/0170_gate_content_lane_deliverable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Content-lane linked-issue deliverable gate (#content-lane-deliverable): off by default -- byte-identical
-- behavior for every existing row. Only meaningful for a repo with a registry content-lane spec resolved
-- (see review/content-lane/spec-resolver.ts). When set to advisory/block, a PR whose primary linked issue's
-- own text names a path matching the resolved spec's entry/provider file pattern must touch at least one
-- matching file; block additionally lets a miss become a gate blocker.
ALTER TABLE repository_settings ADD COLUMN content_lane_deliverable_gate_mode TEXT NOT NULL DEFAULT 'off';
23 changes: 23 additions & 0 deletions packages/loopover-engine/src/advisory/gate-advisory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ export type GateCheckPolicy = {
* the PR author also filed the linked issue — becomes a hard blocker. Defaults to `advisory` — the
* finding is surfaced but never blocks unless the maintainer opts in. */
selfAuthoredLinkedIssueGateMode?: GateRuleMode | undefined;
/** Linked-issue satisfaction gate (#1961/#3906). When `block`, a `linked_issue_scope_mismatch` finding —
* raised when the AI assessment judged (above its confidence floor) that the PR's diff does NOT satisfy
* its primary linked issue's intent — becomes a hard blocker. Defaults to `advisory` — the finding is
* never even produced under `advisory`/`off` (the caller gates the assessment itself on this mode; see
* runLinkedIssueSatisfactionForAdvisory, src/queue/processors.ts on the host side), so this branch only
* matters once a repo has explicitly opted into `block`. */
linkedIssueSatisfactionGateMode?: GateRuleMode | undefined;
/** Content-lane linked-issue deliverable gate (#content-lane-deliverable). When `block`, a
* `content_lane_deliverable_missing` finding — raised when the PR's linked issue's own text names a
* content-lane path the PR's changed files never touch — becomes a hard blocker. Fully deterministic (a
* text/path match, no AI call), so this finding is exempt from the AI-judgment close-precision breaker.
* Defaults to `off` — the finding is never even produced under `off` (the caller gates the check itself
* on this mode; see runContentLaneDeliverableCheckForAdvisory, src/queue/processors.ts on the host side),
* so this branch only matters once a repo has explicitly opted into `advisory`/`block`. */
contentLaneDeliverableGateMode?: GateRuleMode | undefined;
/** CLA / license-compatibility gate (#2564). When `block`, a `cla_consent_missing` finding — raised when
* neither configured detection method (a consent phrase in the PR body, or a named CLA-bot check-run
* conclusion) confirms consent — becomes a hard blocker. `off` (default) = no finding at all; `advisory` =
Expand Down Expand Up @@ -613,6 +628,14 @@ function isConfiguredGateBlocker(finding: AdvisoryFinding, policy: GateCheckPoli
// Self-authored linked-issue gate: blocks only when the maintainer opts in with `block`. Defaults to
// advisory — the finding surfaces in the panel without ever closing the PR unless explicitly configured.
if (code === "self_authored_linked_issue") return gatePolicyBlocks(policy.selfAuthoredLinkedIssueGateMode, "advisory");
// Linked-issue satisfaction gate (#1961/#3906): blocks only when the maintainer opts in with `block`. The
// finding itself is only ever produced when the caller already resolved `block` mode (host side), so this
// is a defense-in-depth mirror of that gate, not the primary enforcement point.
if (code === "linked_issue_scope_mismatch") return gatePolicyBlocks(policy.linkedIssueSatisfactionGateMode, "advisory");
// Content-lane linked-issue deliverable gate (#content-lane-deliverable): blocks only when the maintainer
// opts in with `block`. Fully deterministic (no AI judgment involved), so it is exempt from the
// close-precision circuit breaker on the host side.
if (code === "content_lane_deliverable_missing") return gatePolicyBlocks(policy.contentLaneDeliverableGateMode, "off");
// Lockfile-tamper-risk gate (#2563): blocks only when the maintainer opts in with `block`. Defaults to `off`
// (the finding is never even produced — see maybeAddLockfileTamperFinding's mode gate in queue/processors.ts),
// so this branch only matters once a repo has explicitly turned the scan on.
Expand Down
15 changes: 15 additions & 0 deletions packages/loopover-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ export type FocusManifestGateConfig = {
* merge/close decision. See `src/types.ts`'s `linkedIssueSatisfactionGateMode` doc for the authoritative
* cross-reference. */
linkedIssueSatisfaction: GateRuleMode | null;
/** `gate.contentLaneDeliverable` (#content-lane-deliverable): off|advisory|block, off by default. Only
* meaningful for a repo with a registry content-lane spec resolved (see content-lane/spec-resolver.ts) —
* no-op otherwise. When not off, and the PR's primary linked issue's own text names a path matching that
* spec's entry/provider file pattern, the PR's changed files must touch AT LEAST ONE matching file — a PR
* that never touches the content lane at all (e.g. adds only a regression test) cannot silently close a
* content-delivery issue via a bare "Closes #N" reference. Fully deterministic (a text/path match, no AI
* call) — `block` treats a miss as a hard, zero-hallucination blocker, exempt from the AI-judgment close-
* precision breaker. DB-backed (dashboard-settable too); this overrides the stored value — mirrors
* `linkedIssueSatisfaction` immediately above in shape, but is a purely structural check, not an AI
* opinion, so it carries none of that feature's AI-budget/confidence-floor machinery. */
contentLaneDeliverable: GateRuleMode | null;
dryRun: 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
Expand Down Expand Up @@ -1253,6 +1264,7 @@ const EMPTY_GATE_CONFIG: FocusManifestGateConfig = {
manifestPolicy: null,
selfAuthoredLinkedIssue: null,
linkedIssueSatisfaction: null,
contentLaneDeliverable: null,
dryRun: null,
premergeContentRecheck: null,
requireFreshRebaseWindowMinutes: null,
Expand Down Expand Up @@ -1723,6 +1735,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
manifestPolicy: normalizeOptionalGateMode(record.manifestPolicy, "gate.manifestPolicy", warnings),
selfAuthoredLinkedIssue: normalizeOptionalGateMode(record.selfAuthoredLinkedIssue, "gate.selfAuthoredLinkedIssue", warnings),
linkedIssueSatisfaction: normalizeOptionalGateMode(record.linkedIssueSatisfaction, "gate.linkedIssueSatisfaction", warnings),
contentLaneDeliverable: normalizeOptionalGateMode(record.contentLaneDeliverable, "gate.contentLaneDeliverable", warnings),
dryRun: normalizeOptionalBoolean(record.dryRun, "gate.dryRun", warnings),
premergeContentRecheck: normalizeOptionalBoolean(record.premergeContentRecheck, "gate.premergeContentRecheck", warnings),
requireFreshRebaseWindowMinutes: normalizeOptionalPositiveInteger(record.requireFreshRebaseWindow, "gate.requireFreshRebaseWindow", warnings),
Expand Down Expand Up @@ -1779,6 +1792,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
gate.manifestPolicy !== null ||
gate.selfAuthoredLinkedIssue !== null ||
gate.linkedIssueSatisfaction !== null ||
gate.contentLaneDeliverable !== null ||
gate.dryRun !== null ||
gate.premergeContentRecheck !== null ||
gate.requireFreshRebaseWindowMinutes !== null ||
Expand Down Expand Up @@ -1861,6 +1875,7 @@ export function gateConfigToJson(gate: FocusManifestGateConfig): JsonValue {
if (gate.manifestPolicy !== null) out.manifestPolicy = gate.manifestPolicy;
if (gate.selfAuthoredLinkedIssue !== null) out.selfAuthoredLinkedIssue = gate.selfAuthoredLinkedIssue;
if (gate.linkedIssueSatisfaction !== null) out.linkedIssueSatisfaction = gate.linkedIssueSatisfaction;
if (gate.contentLaneDeliverable !== null) out.contentLaneDeliverable = gate.contentLaneDeliverable;
if (gate.dryRun !== null) out.dryRun = gate.dryRun;
if (gate.premergeContentRecheck !== null) out.premergeContentRecheck = gate.premergeContentRecheck;
if (gate.requireFreshRebaseWindowMinutes !== null) out.requireFreshRebaseWindow = gate.requireFreshRebaseWindowMinutes;
Expand Down
1 change: 1 addition & 0 deletions scripts/check-docs-drift.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export const GATE_MODE_MANIFEST = [
{ field: "manifestPolicyGateMode", aliases: ["manifestPolicyGateMode", "gate.manifestPolicy"], pages: ["how-reviews-work.mdx", "tuning.mdx"] },
{ field: "selfAuthoredLinkedIssueGateMode", aliases: ["selfAuthoredLinkedIssueGateMode", "gate.selfAuthoredLinkedIssue"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
{ field: "linkedIssueSatisfactionGateMode", aliases: ["linkedIssueSatisfactionGateMode", "gate.linkedIssueSatisfaction"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
{ field: "contentLaneDeliverableGateMode", aliases: ["contentLaneDeliverableGateMode", "gate.contentLaneDeliverable"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
{ field: "moderationGateMode", aliases: ["moderationGateMode", "settings.moderationGateMode"], pages: ["how-reviews-work.mdx", "tuning.mdx", "github-app.mdx"] },
];

Expand Down
1 change: 1 addition & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ const maintainerSettingsSchema = z
mergeReadinessGateMode: z.enum(["off", "advisory", "block"]),
manifestPolicyGateMode: z.enum(["off", "advisory", "block"]),
linkedIssueSatisfactionGateMode: z.enum(["off", "advisory", "block"]),
contentLaneDeliverableGateMode: 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.
// #6446: firstTimeContributorGrace removed -- a dead, never-wired RESERVED/INERT field (#2266); deleted
Expand Down
Loading
Loading