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
16 changes: 16 additions & 0 deletions apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -7956,6 +7956,13 @@
"qualityGateMinScore": {
"type": "number",
"nullable": true
},
"gatePack": {
"type": "string",
"enum": [
"gittensor",
"oss-anti-slop"
]
}
},
"required": [
Expand All @@ -7966,6 +7973,7 @@
"checkRunMode",
"checkRunDetailLevel",
"gateCheckMode",
"gatePack",
"linkedIssueGateMode",
"duplicatePrGateMode",
"qualityGateMode",
Expand Down Expand Up @@ -8528,6 +8536,13 @@
"qualityGateMinScore": {
"type": "number",
"nullable": true
},
"gatePack": {
"type": "string",
"enum": [
"gittensor",
"oss-anti-slop"
]
}
},
"required": [
Expand All @@ -8538,6 +8553,7 @@
"checkRunMode",
"checkRunDetailLevel",
"gateCheckMode",
"gatePack",
"linkedIssueGateMode",
"duplicatePrGateMode",
"qualityGateMode",
Expand Down
4 changes: 4 additions & 0 deletions migrations/0032_repository_gate_pack.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Policy-pack-pluggable gate (#692). `gittensor` (default) = the full Gittensor policy (confirmed-
-- contributor-gated, registry-aware). `oss-anti-slop` = the deterministic rules against any author on any
-- repo, with no Gittensor coupling. Default preserves existing behavior for every current repo.
ALTER TABLE repository_settings ADD COLUMN gate_pack TEXT NOT NULL DEFAULT 'gittensor';
8 changes: 8 additions & 0 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
checkRunMode: "off",
checkRunDetailLevel: "minimal",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "block",
qualityGateMode: "advisory",
Expand Down Expand Up @@ -420,6 +421,7 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise
checkRunMode: parseCheckRunMode(row.checkRunMode),
checkRunDetailLevel: parseCheckRunDetailLevel(row.checkRunDetailLevel),
gateCheckMode: parseGateCheckMode(row.gateCheckMode),
gatePack: parseGatePack(row.gatePack),
linkedIssueGateMode: parseGateRuleMode(row.linkedIssueGateMode),
duplicatePrGateMode: parseGateRuleMode(row.duplicatePrGateMode),
qualityGateMode: parseGateRuleMode(row.qualityGateMode),
Expand Down Expand Up @@ -451,6 +453,7 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
checkRunMode: settings.checkRunMode ?? "off",
checkRunDetailLevel: settings.checkRunDetailLevel ?? "minimal",
gateCheckMode: settings.gateCheckMode ?? "off",
gatePack: parseGatePack(settings.gatePack),
linkedIssueGateMode: settings.linkedIssueGateMode ?? "advisory",
duplicatePrGateMode: settings.duplicatePrGateMode ?? "block",
qualityGateMode: settings.qualityGateMode ?? "advisory",
Expand Down Expand Up @@ -480,6 +483,7 @@ export async function upsertRepositorySettings(env: Env, settings: Partial<Repos
checkRunMode: resolved.checkRunMode,
checkRunDetailLevel: resolved.checkRunDetailLevel,
gateCheckMode: resolved.gateCheckMode,
gatePack: resolved.gatePack,
linkedIssueGateMode: resolved.linkedIssueGateMode,
duplicatePrGateMode: resolved.duplicatePrGateMode,
qualityGateMode: resolved.qualityGateMode,
Expand Down Expand Up @@ -4710,6 +4714,10 @@ function parseGateCheckMode(value: string): RepositorySettings["gateCheckMode"]
return value === "enabled" ? "enabled" : "off";
}

function parseGatePack(value: string | null | undefined): RepositorySettings["gatePack"] {
return value === "oss-anti-slop" ? "oss-anti-slop" : "gittensor";
}

function parseGateRuleMode(value: string): RepositorySettings["linkedIssueGateMode"] {
if (value === "off" || value === "block") return value;
return "advisory";
Expand Down
1 change: 1 addition & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const repositorySettings = sqliteTable("repository_settings", {
checkRunMode: text("check_run_mode").notNull().default("off"),
checkRunDetailLevel: text("check_run_detail_level").notNull().default("minimal"),
gateCheckMode: text("gate_check_mode").notNull().default("off"),
gatePack: text("gate_pack").notNull().default("gittensor"),
linkedIssueGateMode: text("linked_issue_gate_mode").notNull().default("block"),
duplicatePrGateMode: text("duplicate_pr_gate_mode").notNull().default("block"),
qualityGateMode: text("quality_gate_mode").notNull().default("advisory"),
Expand Down
2 changes: 2 additions & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ export const RepositorySettingsSchema = z
checkRunMode: z.enum(["off", "enabled"]),
checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]),
gateCheckMode: z.enum(["off", "enabled"]),
gatePack: z.enum(["gittensor", "oss-anti-slop"]),
linkedIssueGateMode: z.enum(["off", "advisory", "block"]),
duplicatePrGateMode: z.enum(["off", "advisory", "block"]),
qualityGateMode: z.enum(["off", "advisory", "block"]),
Expand Down Expand Up @@ -591,6 +592,7 @@ export const RepoSettingsPreviewSchema = z
checkRunMode: z.enum(["off", "enabled"]),
checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]),
gateCheckMode: z.enum(["off", "enabled"]),
gatePack: z.enum(["gittensor", "oss-anti-slop"]),
linkedIssueGateMode: z.enum(["off", "advisory", "block"]),
duplicatePrGateMode: z.enum(["off", "advisory", "block"]),
qualityGateMode: z.enum(["off", "advisory", "block"]),
Expand Down
7 changes: 5 additions & 2 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,15 +817,18 @@ function shouldProcessPullRequestPublicSurface(action: string | undefined): bool
export function gateCheckPolicy(settings: RepositorySettings, readinessScore?: number | null, confirmedContributor?: boolean) {
// `settings` is already the EFFECTIVE config (`.gittensory.yml` > DB > defaults), resolved upstream by
// resolveRepositorySettings, so the blocker modes here reflect the repo's config file directly.
// confirmedContributor governs WHO can be blocked, downstream in evaluateGateCheck.
// The `oss-anti-slop` pack (#692) is repo-agnostic: it blocks ANY author whose PR trips an opted-in
// deterministic rule, so it drops the confirmed-contributor gate entirely (no Gittensor coupling). The
// `gittensor` pack keeps the contributor gate — only confirmed contributors are hard-blocked.
const confirmedContributorForPack = settings.gatePack === "oss-anti-slop" ? undefined : confirmedContributor;
return {
linkedIssueGateMode: settings.linkedIssueGateMode,
duplicatePrGateMode: settings.duplicatePrGateMode,
qualityGateMode: settings.qualityGateMode,
qualityGateMinScore: settings.qualityGateMinScore ?? null,
aiReviewGateMode: settings.aiReviewMode,
readinessScore: readinessScore ?? null,
confirmedContributor,
confirmedContributor: confirmedContributorForPack,
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/signals/focus-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse as parseYaml } from "yaml";
import type { GateRuleMode, JsonValue, RepositorySettings } from "../types";
import type { GatePolicyPack, GateRuleMode, JsonValue, RepositorySettings } from "../types";

export type FocusManifestSource = "repo_file" | "api_record" | "none";
export type FocusManifestLinkedIssuePolicy = "required" | "preferred" | "optional";
Expand All @@ -17,6 +17,7 @@ export type FocusManifestIssueDiscoveryPolicy = "encouraged" | "neutral" | "disc
export type FocusManifestGateConfig = {
present: boolean;
enabled: boolean | null;
pack: GatePolicyPack | null;
linkedIssue: GateRuleMode | null;
duplicates: GateRuleMode | null;
readinessMode: GateRuleMode | null;
Expand Down Expand Up @@ -141,6 +142,7 @@ export const MAX_FOCUS_MANIFEST_BYTES = 64 * 1024;
const EMPTY_GATE_CONFIG: FocusManifestGateConfig = {
present: false,
enabled: null,
pack: null,
linkedIssue: null,
duplicates: null,
readinessMode: null,
Expand Down Expand Up @@ -272,6 +274,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
const gate: FocusManifestGateConfig = {
present: false,
enabled: normalizeOptionalBoolean(record.enabled, "gate.enabled", warnings),
pack: normalizeOptionalEnum(record.pack, "gate.pack", ["gittensor", "oss-anti-slop"] as const, warnings),
linkedIssue: normalizeOptionalGateMode(record.linkedIssue, "gate.linkedIssue", warnings),
duplicates: normalizeOptionalGateMode(record.duplicates, "gate.duplicates", warnings),
readinessMode: normalizeOptionalGateMode(readinessRecord?.mode, "gate.readiness.mode", warnings),
Expand All @@ -283,6 +286,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu
};
gate.present =
gate.enabled !== null ||
gate.pack !== null ||
gate.linkedIssue !== null ||
gate.duplicates !== null ||
gate.readinessMode !== null ||
Expand All @@ -302,6 +306,7 @@ export function gateConfigToJson(gate: FocusManifestGateConfig): JsonValue {
if (!gate.present) return null;
const out: Record<string, JsonValue> = {};
if (gate.enabled !== null) out.enabled = gate.enabled;
if (gate.pack !== null) out.pack = gate.pack;
if (gate.linkedIssue !== null) out.linkedIssue = gate.linkedIssue;
if (gate.duplicates !== null) out.duplicates = gate.duplicates;
if (gate.readinessMode !== null || gate.readinessMinScore !== null) {
Expand Down Expand Up @@ -451,6 +456,7 @@ export function resolveEffectiveSettings(dbSettings: RepositorySettings, manifes
const effective: RepositorySettings = { ...dbSettings, ...manifest.settings };
const gate = manifest.gate;
if (gate.enabled !== null) effective.gateCheckMode = gate.enabled ? "enabled" : "off";
if (gate.pack !== null) effective.gatePack = gate.pack;
if (gate.linkedIssue !== null) effective.linkedIssueGateMode = gate.linkedIssue;
if (gate.duplicates !== null) effective.duplicatePrGateMode = gate.duplicates;
if (gate.readinessMode !== null) effective.qualityGateMode = gate.readinessMode;
Expand Down
2 changes: 2 additions & 0 deletions src/signals/settings-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export type RepoSettingsPreview = {
checkRunMode: RepositorySettings["checkRunMode"];
checkRunDetailLevel: RepositorySettings["checkRunDetailLevel"];
gateCheckMode: RepositorySettings["gateCheckMode"];
gatePack: RepositorySettings["gatePack"];
linkedIssueGateMode: RepositorySettings["linkedIssueGateMode"];
duplicatePrGateMode: RepositorySettings["duplicatePrGateMode"];
qualityGateMode: RepositorySettings["qualityGateMode"];
Expand Down Expand Up @@ -293,6 +294,7 @@ export function buildRepoSettingsPreview(args: {
checkRunMode: settings.checkRunMode,
checkRunDetailLevel: settings.checkRunDetailLevel,
gateCheckMode: settings.gateCheckMode,
gatePack: settings.gatePack,
linkedIssueGateMode: settings.linkedIssueGateMode,
duplicatePrGateMode: settings.duplicatePrGateMode,
qualityGateMode: settings.qualityGateMode,
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ export type BountyRecord = {

export type GateRuleMode = "off" | "advisory" | "block";

/** Which policy pack the gate runs under (#692). `gittensor` = the full Gittensor policy: only confirmed
* Gittensor contributors are hard-blocked (registry/emissions-aware). `oss-anti-slop` = a general, repo-
* agnostic pack: the same deterministic rules (slop/duplicate/linked-issue/readiness/AI-consensus) block
* ANY author, with no emissions/registry/confirmed-contributor coupling — so the gate runs on any repo. */
export type GatePolicyPack = "gittensor" | "oss-anti-slop";

export type RepositorySettings = {
repoFullName: string;
commentMode: "off" | "detected_contributors_only" | "all_prs";
Expand All @@ -378,6 +384,9 @@ export type RepositorySettings = {
checkRunMode: "off" | "enabled";
checkRunDetailLevel: "minimal" | "standard" | "deep";
gateCheckMode: "off" | "enabled";
/** Policy pack the gate evaluates under (#692). Default `gittensor` (confirmed-contributor-gated,
* registry-aware). `oss-anti-slop` runs the deterministic rules against any author on any repo. */
gatePack: GatePolicyPack;
linkedIssueGateMode: GateRuleMode;
duplicatePrGateMode: GateRuleMode;
qualityGateMode: GateRuleMode;
Expand Down
7 changes: 7 additions & 0 deletions test/unit/data-spine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
upsertRepoLabel,
upsertRepoSyncState,
upsertRepositoryFromGitHub,
upsertRepositorySettings,
} from "../../src/db/repositories";
import { createTestEnv } from "../helpers/d1";

Expand Down Expand Up @@ -234,7 +235,13 @@ describe("data spine repositories", () => {
checkRunMode: "off",
checkRunDetailLevel: "minimal",
publicSurface: "comment_and_label",
gatePack: "gittensor",
});
// gatePack (#692) round-trips and defaults to gittensor.
await upsertRepositorySettings(env, { repoFullName: "owner/repo", gatePack: "oss-anti-slop" });
expect((await getRepositorySettings(env, "owner/repo")).gatePack).toBe("oss-anti-slop");
await upsertRepositorySettings(env, { repoFullName: "owner/defaultpack" });
expect((await getRepositorySettings(env, "owner/defaultpack")).gatePack).toBe("gittensor");
expect(await getRepoSyncState(env, "missing/repo")).toBeNull();
expect(await getPullRequest(env, "owner/repo", 404)).toBeNull();
expect(await getIssue(env, "owner/repo", 404)).toBeNull();
Expand Down
13 changes: 11 additions & 2 deletions test/unit/focus-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ describe("compileFocusManifestPolicy", () => {
issueDiscoveryPolicy: "neutral",
maintainerNotes: [],
publicNotes: ["Keep PRs focused.", "Maximize your reward payout"],
gate: { present: false, enabled: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null },
gate: { present: false, enabled: null, pack: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null },
settings: {},
review: { present: false, footerText: null, note: null, fields: {} },
warnings: [],
Expand Down Expand Up @@ -688,7 +688,16 @@ describe("parseFocusManifest gate config", () => {
it("parses a full gate section including the readiness block", () => {
const m = parseFocusManifest({ gate: { linkedIssue: "block", duplicates: "advisory", readiness: { mode: "block", minScore: 70 } } });
expect(m.present).toBe(true);
expect(m.gate).toEqual({ present: true, enabled: null, linkedIssue: "block", duplicates: "advisory", readinessMode: "block", readinessMinScore: 70, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null });
expect(m.gate).toEqual({ present: true, enabled: null, pack: null, linkedIssue: "block", duplicates: "advisory", readinessMode: "block", readinessMinScore: 70, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null });
});

it("parses gate.pack and ignores an unknown pack with a warning (#692)", () => {
expect(parseFocusManifest({ gate: { pack: "oss-anti-slop" } }).gate.pack).toBe("oss-anti-slop");
expect(parseFocusManifest({ gate: { pack: "gittensor" } }).gate.pack).toBe("gittensor");
expect(parseFocusManifest({ gate: { pack: "oss-anti-slop" } }).gate.present).toBe(true);
const bad = parseFocusManifest({ gate: { pack: "nonsense" } });
expect(bad.gate.pack).toBeNull();
expect(bad.warnings.some((w) => /gate\.pack/.test(w))).toBe(true);
});

it("parses gate.enabled (on/off) and ignores non-boolean values with a warning", () => {
Expand Down
30 changes: 30 additions & 0 deletions test/unit/gate-check-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ describe(".gittensory.yml settings override (resolveEffectiveSettings)", () => {
});
});

describe("policy pack (#692)", () => {
it("gittensor pack hard-blocks only confirmed contributors", () => {
const gittensor = settings({ gatePack: "gittensor", linkedIssueGateMode: "block" });
expect(evaluateGateCheck(missingIssueAdvisory(), gateCheckPolicy(gittensor, null, false)).conclusion).toBe("neutral");
expect(evaluateGateCheck(missingIssueAdvisory(), gateCheckPolicy(gittensor, null, true)).conclusion).toBe("failure");
});

it("oss-anti-slop pack blocks ANY author whose PR trips an opted-in rule (no confirmed-contributor gate)", () => {
const oss = settings({ gatePack: "oss-anti-slop", linkedIssueGateMode: "block" });
const blocked = evaluateGateCheck(missingIssueAdvisory(), gateCheckPolicy(oss, null, false));
expect(blocked.conclusion).toBe("failure");
expect(blocked.blockers.map((finding) => finding.code)).toEqual(["missing_linked_issue"]);
// Still passes a clean PR (no opted-in blocker) regardless of author.
expect(evaluateGateCheck(missingIssueAdvisory(), gateCheckPolicy(settings({ gatePack: "oss-anti-slop", linkedIssueGateMode: "advisory" }), null, false)).conclusion).toBe("success");
});

it("gateCheckPolicy drops confirmedContributor under oss-anti-slop and keeps it (incl. default) under gittensor", () => {
expect(gateCheckPolicy(settings({ gatePack: "oss-anti-slop" }), null, false).confirmedContributor).toBeUndefined();
expect(gateCheckPolicy(settings({ gatePack: "oss-anti-slop" }), null, true).confirmedContributor).toBeUndefined();
expect(gateCheckPolicy(settings({ gatePack: "gittensor" }), null, false).confirmedContributor).toBe(false);
expect(gateCheckPolicy(settings({ gatePack: "gittensor" }), null, true).confirmedContributor).toBe(true);
});

it(".gittensory.yml gate.pack overlays the pack and flips the gate to block any author end-to-end", () => {
const eff = resolveEffectiveSettings(settings({ gatePack: "gittensor", linkedIssueGateMode: "block" }), parseFocusManifest({ gate: { pack: "oss-anti-slop" } }));
expect(eff.gatePack).toBe("oss-anti-slop");
expect(evaluateGateCheck(missingIssueAdvisory(), gateCheckPolicy(eff, null, false)).conclusion).toBe("failure");
});
});

describe("AI consensus defect gate blocker", () => {
function aiDefectAdvisory(): Advisory {
return { ...missingIssueAdvisory(), findings: [{ code: "ai_consensus_defect", title: "AI reviewers agree on a likely critical defect", severity: "critical", detail: "Both models flagged a null deref.", action: "Resolve it." }] };
Expand Down
1 change: 1 addition & 0 deletions test/unit/maintainer-activation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function settings(overrides: Partial<RepositorySettings> = {}): RepositorySettin
checkRunMode: "off",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/policy-sanitizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function settingsFor(repoFullName: string, overrides: Partial<RepositorySettings
checkRunMode: "off",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/registration-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function settingsFor(repoFullName: string, overrides: Partial<RepositorySettings
checkRunMode: "enabled",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/repo-policy-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function settings(overrides: Partial<RepositorySettings> = {}): RepositorySettin
checkRunMode: "enabled",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/self-dogfood-registration-pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function settingsFor(repoFullName: string, overrides: Partial<RepositorySettings
checkRunMode: "enabled",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/settings-preview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function settings(overrides: Partial<RepositorySettings> = {}): RepositorySettin
checkRunMode: "off",
checkRunDetailLevel: "standard",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
1 change: 1 addition & 0 deletions test/unit/signals-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,7 @@ function repoSettings(repoFullName: string): RepositorySettings {
checkRunMode: "off",
checkRunDetailLevel: "minimal",
gateCheckMode: "off",
gatePack: "gittensor",
linkedIssueGateMode: "advisory",
duplicatePrGateMode: "advisory",
qualityGateMode: "advisory",
Expand Down
Loading
Loading