From e234c8f009e9c4051280dae28c430a0ee5c2cffb Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 17 Jun 2026 03:59:47 -0700 Subject: [PATCH] feat(gate): focus-manifest policy as enforceable gate input (#555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an opt-in per-repo manifestPolicyGateMode (off|advisory|block, default off). When block, the focus manifest's declared policy becomes hard gate blockers via the existing Gittensory Gate check: the three enforceable findings the guidance builder already emits — manifest_blocked_path, manifest_linked_issue_required, manifest_missing_tests — gate a confirmed contributor's PR. Gate-path integration mirrors the slop block: a single shared changed-file load (slop + manifest-policy no longer double-fetch; lazy so disabled repos pay nothing), the cached manifest is reloaded via loadRepoFocusManifest, buildFocusManifestGuidance recomputes over the changed files, and ONLY the three enforceable codes are pushed into the advisory. isConfiguredGateBlocker gates them on manifestPolicy: block. An INDEPENDENT dimension — deliberately not folded into the merge-readiness composite (a test asserts mergeReadiness:block does not promote a manifest-policy finding). Setting wired through types / schema / migration 0040 / repositories / openapi / settings-preview, plus .gittensory.yml parity in the same change (gate.manifestPolicy) so it ships without a config-as-code gap. isTestPath is exported from rules/advisory for the testFileCount. --- apps/gittensory-ui/public/openapi.json | 18 +++++++ migrations/0040_manifest_policy_gate.sql | 6 +++ src/db/repositories.ts | 5 ++ src/db/schema.ts | 1 + src/openapi/schemas.ts | 2 + src/queue/processors.ts | 40 ++++++++++++-- src/rules/advisory.ts | 12 ++++- src/signals/focus-manifest.ts | 6 +++ src/signals/settings-preview.ts | 2 + src/types.ts | 4 ++ test/unit/focus-manifest.test.ts | 15 +++++- test/unit/gate-check-policy.test.ts | 54 +++++++++++++++++++ test/unit/maintainer-activation.test.ts | 1 + test/unit/policy-sanitizer.test.ts | 1 + test/unit/registration-readiness.test.ts | 1 + test/unit/repo-policy-readiness.test.ts | 1 + .../self-dogfood-registration-pack.test.ts | 1 + test/unit/settings-preview.test.ts | 1 + test/unit/signals-coverage.test.ts | 1 + test/unit/signals-v2.test.ts | 1 + test/unit/signals.test.ts | 5 ++ 21 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 migrations/0040_manifest_policy_gate.sql diff --git a/apps/gittensory-ui/public/openapi.json b/apps/gittensory-ui/public/openapi.json index fe05bb57f8..becbedf7eb 100644 --- a/apps/gittensory-ui/public/openapi.json +++ b/apps/gittensory-ui/public/openapi.json @@ -8017,6 +8017,14 @@ }, "firstTimeContributorGrace": { "type": "boolean" + }, + "manifestPolicyGateMode": { + "type": "string", + "enum": [ + "off", + "advisory", + "block" + ] } }, "required": [ @@ -8033,6 +8041,7 @@ "qualityGateMode", "slopGateMode", "mergeReadinessGateMode", + "manifestPolicyGateMode", "firstTimeContributorGrace", "slopAiAdvisory", "autoLabelEnabled", @@ -8624,6 +8633,14 @@ }, "firstTimeContributorGrace": { "type": "boolean" + }, + "manifestPolicyGateMode": { + "type": "string", + "enum": [ + "off", + "advisory", + "block" + ] } }, "required": [ @@ -8640,6 +8657,7 @@ "qualityGateMode", "slopGateMode", "mergeReadinessGateMode", + "manifestPolicyGateMode", "firstTimeContributorGrace", "autoLabelEnabled", "gittensorLabel", diff --git a/migrations/0040_manifest_policy_gate.sql b/migrations/0040_manifest_policy_gate.sql new file mode 100644 index 0000000000..9101b09528 --- /dev/null +++ b/migrations/0040_manifest_policy_gate.sql @@ -0,0 +1,6 @@ +-- Focus-manifest policy gate (#555). One tunable `manifest_policy_gate_mode`: off (default) | advisory | +-- block. When set to block, the focus manifest's declared policy (blocked paths, required-linked-issue, test +-- expectations) becomes an enforceable `Gittensory Gate` blocker — surfaced through the single required check. +-- An INDEPENDENT dimension, deliberately not folded into the merge-readiness composite. Default 'off' +-- preserves existing behavior for every current repo. +ALTER TABLE repository_settings ADD COLUMN manifest_policy_gate_mode TEXT NOT NULL DEFAULT 'off'; diff --git a/src/db/repositories.ts b/src/db/repositories.ts index aef947574e..7460b1717e 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -402,6 +402,7 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise qualityGateMinScore: null, slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopGateMinScore: null, slopAiAdvisory: false, @@ -436,6 +437,7 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise qualityGateMinScore: normalizeQualityGateMinScore(row.qualityGateMinScore), slopGateMode: parseGateRuleMode(row.slopGateMode), mergeReadinessGateMode: parseGateRuleMode(row.mergeReadinessGateMode), + manifestPolicyGateMode: parseGateRuleMode(row.manifestPolicyGateMode), firstTimeContributorGrace: row.firstTimeContributorGrace, slopGateMinScore: normalizeQualityGateMinScore(row.slopGateMinScore), slopAiAdvisory: row.slopAiAdvisory, @@ -474,6 +476,7 @@ export async function upsertRepositorySettings(env: Env, settings: Partial> | null = null; + if (settings.slopGateMode !== "off" || settings.manifestPolicyGateMode !== "off") { + gateFiles = await listPullRequestFiles(env, repoFullName, pr.number); + } if (settings.slopGateMode !== "off") { - const slopFiles = await listPullRequestFiles(env, repoFullName, pr.number); + const slopFiles = gateFiles ?? []; const slop = buildSlopAssessment({ changedFiles: slopFiles.map((file) => ({ path: file.path, additions: file.additions, deletions: file.deletions })), description: pr.body, @@ -1240,6 +1247,33 @@ async function maybePublishPrPublicSurface( await runAiSlopForAdvisory(env, { settings, advisory, repoFullName, pr, author, files: slopFiles, deterministicBand: slop.band, confirmedContributor }); } } + // Focus-manifest policy (#555, opt-in via manifestPolicyGateMode). Reload the CACHED manifest (the + // settings resolver discards the raw manifest, but loadRepoFocusManifest is cached so this is cheap), + // recompute the guidance over the PR's changed files, and push ONLY the three enforceable policy + // findings into the advisory so isConfiguredGateBlocker can block under manifestPolicy: block. + if (settings.manifestPolicyGateMode !== "off") { + const manifestFiles = gateFiles ?? []; + const manifest = await loadRepoFocusManifest(env, repoFullName); + const guidance = buildFocusManifestGuidance({ + manifest, + changedPaths: manifestFiles.map((file) => file.path), + labels: pr.labels, + linkedIssueCount: pr.linkedIssues.length, + testFileCount: manifestFiles.filter((file) => isTestPath(file.path)).length, + passedValidationCount: 0, + }); + const policyCodes = new Set(["manifest_blocked_path", "manifest_linked_issue_required", "manifest_missing_tests"]); + for (const finding of guidance.findings) { + if (!policyCodes.has(finding.code)) continue; + advisory.findings.push({ + code: finding.code, + severity: finding.severity, + title: finding.title, + detail: finding.detail, + ...(finding.action !== undefined ? { action: finding.action } : {}), + }); + } + } // AI maintainer review (opt-in via aiReviewMode). Mutates `advisory` with a consensus defect (if any) // BEFORE the gate evaluates, and returns advisory notes for the panel. Inside the try so any AI diff --git a/src/rules/advisory.ts b/src/rules/advisory.ts index 88ec3c3794..984371fdea 100644 --- a/src/rules/advisory.ts +++ b/src/rules/advisory.ts @@ -32,6 +32,11 @@ export type GateCheckPolicy = { * linked-issue, duplicate, quality/readiness, slop — to its mode, so a maintainer flips ONE switch instead * of four and `Gittensory Gate` stays the single required check. `off` = sub-gates use their own modes. */ mergeReadinessGateMode?: GateRuleMode | undefined; + /** Focus-manifest policy gate (#555). When `block`, the focus manifest's declared policy findings — + * `manifest_blocked_path`, `manifest_linked_issue_required`, `manifest_missing_tests` — become hard + * blockers. An INDEPENDENT dimension, deliberately NOT folded into the merge-readiness composite so #555 + * stays focused. `off`/`advisory` = the findings stay advisory (never block). Default off. */ + manifestPolicyGateMode?: GateRuleMode | undefined; /** First-time-contributor grace (#552). When true AND the author is a genuine newcomer (0 merged PRs in * this repo) who is NOT a repeat offender (< 3 closed-unmerged PRs), a would-be BLOCK is softened to a * neutral/advisory gate. `undefined`/false = the grace rule does not apply and blockers gate normally. */ @@ -177,7 +182,7 @@ function isCodePath(path: string): boolean { return /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|cs|cpp|c|h|swift|kt|m|sql|yaml|yml|json|toml|md)$/i.test(path); } -function isTestPath(path: string): boolean { +export function isTestPath(path: string): boolean { return ( /(^|\/)(test|tests|spec|__tests__)\//i.test(path) || /\.(test|spec)\.(ts|tsx|js|jsx|py|go|rs)$/i.test(path) || @@ -604,6 +609,11 @@ function isConfiguredGateBlocker(code: string, policy: GateCheckPolicy): boolean // most conservative AI signal (two independent models, high confidence) but still confirmed-contributor // gated by evaluateGateCheck, and advisory by default. if (code === "ai_consensus_defect") return gateMode(policy.aiReviewGateMode ?? "advisory") === "block"; + // Focus-manifest policy (#555): the three enforceable manifest findings block ONLY when the maintainer + // opts into manifestPolicy: block. Default off/advisory keeps them advisory-only. + if (code === "manifest_blocked_path" || code === "manifest_linked_issue_required" || code === "manifest_missing_tests") { + return gateMode(policy.manifestPolicyGateMode ?? "off") === "block"; + } return false; } diff --git a/src/signals/focus-manifest.ts b/src/signals/focus-manifest.ts index f739456ee1..7c4006815a 100644 --- a/src/signals/focus-manifest.ts +++ b/src/signals/focus-manifest.ts @@ -30,6 +30,7 @@ export type FocusManifestGateConfig = { aiReviewProvider: "anthropic" | "openai" | null; aiReviewModel: string | null; mergeReadiness: GateRuleMode | null; + manifestPolicy: GateRuleMode | null; firstTimeContributorGrace: boolean | null; }; @@ -160,6 +161,7 @@ const EMPTY_GATE_CONFIG: FocusManifestGateConfig = { aiReviewProvider: null, aiReviewModel: null, mergeReadiness: null, + manifestPolicy: null, firstTimeContributorGrace: null, }; @@ -302,6 +304,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu aiReviewProvider: normalizeOptionalEnum(aiReviewRecord?.provider, "gate.aiReview.provider", ["anthropic", "openai"] as const, warnings), aiReviewModel: normalizeOptionalString(aiReviewRecord?.model, "gate.aiReview.model", warnings), mergeReadiness: normalizeOptionalGateMode(record.mergeReadiness, "gate.mergeReadiness", warnings), + manifestPolicy: normalizeOptionalGateMode(record.manifestPolicy, "gate.manifestPolicy", warnings), firstTimeContributorGrace: normalizeOptionalBoolean(record.firstTimeContributorGrace, "gate.firstTimeContributorGrace", warnings), }; gate.present = @@ -319,6 +322,7 @@ function parseGateConfig(value: JsonValue | undefined, warnings: string[]): Focu gate.aiReviewProvider !== null || gate.aiReviewModel !== null || gate.mergeReadiness !== null || + gate.manifestPolicy !== null || gate.firstTimeContributorGrace !== null; return gate; } @@ -356,6 +360,7 @@ export function gateConfigToJson(gate: FocusManifestGateConfig): JsonValue { out.aiReview = aiReview; } if (gate.mergeReadiness !== null) out.mergeReadiness = gate.mergeReadiness; + if (gate.manifestPolicy !== null) out.manifestPolicy = gate.manifestPolicy; if (gate.firstTimeContributorGrace !== null) out.firstTimeContributorGrace = gate.firstTimeContributorGrace; return out; } @@ -503,6 +508,7 @@ export function resolveEffectiveSettings(dbSettings: RepositorySettings, manifes if (gate.aiReviewProvider !== null) effective.aiReviewProvider = gate.aiReviewProvider; if (gate.aiReviewModel !== null) effective.aiReviewModel = gate.aiReviewModel; if (gate.mergeReadiness !== null) effective.mergeReadinessGateMode = gate.mergeReadiness; + if (gate.manifestPolicy !== null) effective.manifestPolicyGateMode = gate.manifestPolicy; if (gate.firstTimeContributorGrace !== null) effective.firstTimeContributorGrace = gate.firstTimeContributorGrace; return effective; } diff --git a/src/signals/settings-preview.ts b/src/signals/settings-preview.ts index f0fb9a7d90..91ab43a1b3 100644 --- a/src/signals/settings-preview.ts +++ b/src/signals/settings-preview.ts @@ -189,6 +189,7 @@ export type RepoSettingsPreview = { qualityGateMinScore?: number | null | undefined; slopGateMode: RepositorySettings["slopGateMode"]; mergeReadinessGateMode: RepositorySettings["mergeReadinessGateMode"]; + manifestPolicyGateMode: RepositorySettings["manifestPolicyGateMode"]; firstTimeContributorGrace: boolean; slopGateMinScore?: number | null | undefined; autoLabelEnabled: boolean; @@ -306,6 +307,7 @@ export function buildRepoSettingsPreview(args: { qualityGateMinScore: settings.qualityGateMinScore ?? null, slopGateMode: settings.slopGateMode, mergeReadinessGateMode: settings.mergeReadinessGateMode, + manifestPolicyGateMode: settings.manifestPolicyGateMode, firstTimeContributorGrace: settings.firstTimeContributorGrace, slopGateMinScore: settings.slopGateMinScore ?? null, autoLabelEnabled: settings.autoLabelEnabled, diff --git a/src/types.ts b/src/types.ts index de60f9b4f2..ae9fe67abd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -415,6 +415,10 @@ export type RepositorySettings = { slopGateMode: GateRuleMode; /** Merge-readiness gate (#merge-readiness). `off`/`advisory`/`block`. No min-score. Default `off`. */ mergeReadinessGateMode: GateRuleMode; + /** Focus-manifest policy gate (#555). When `block`, the focus manifest's declared policy (blocked paths, + * required-linked-issue, test expectations) becomes an enforceable `Gittensory Gate` blocker. An + * INDEPENDENT dimension, deliberately not folded into the merge-readiness composite. Default `off` — opt-in. */ + manifestPolicyGateMode: GateRuleMode; /** First-time-contributor grace (#552). When true, a would-be BLOCK is softened to a neutral/advisory gate * for a genuine newcomer (0 merged PRs in this repo) who is NOT a repeat offender (< 3 closed-unmerged PRs). * Repeat offenders and authors with merge history are gated normally. Default false — opt-in. */ diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 34f704c143..331c102647 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -400,7 +400,7 @@ describe("compileFocusManifestPolicy", () => { issueDiscoveryPolicy: "neutral", maintainerNotes: [], publicNotes: ["Keep PRs focused.", "Maximize your reward payout"], - gate: { present: false, enabled: null, pack: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, slopMode: null, slopMinScore: null, slopAiAdvisory: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, mergeReadiness: null, firstTimeContributorGrace: null }, + gate: { present: false, enabled: null, pack: null, linkedIssue: null, duplicates: null, readinessMode: null, readinessMinScore: null, slopMode: null, slopMinScore: null, slopAiAdvisory: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, mergeReadiness: null, manifestPolicy: null, firstTimeContributorGrace: null }, settings: {}, review: { present: false, footerText: null, note: null, fields: {} }, warnings: [], @@ -688,7 +688,7 @@ 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, pack: null, linkedIssue: "block", duplicates: "advisory", readinessMode: "block", readinessMinScore: 70, slopMode: null, slopMinScore: null, slopAiAdvisory: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, mergeReadiness: null, firstTimeContributorGrace: null }); + expect(m.gate).toEqual({ present: true, enabled: null, pack: null, linkedIssue: "block", duplicates: "advisory", readinessMode: "block", readinessMinScore: 70, slopMode: null, slopMinScore: null, slopAiAdvisory: null, aiReviewMode: null, aiReviewByok: null, aiReviewProvider: null, aiReviewModel: null, mergeReadiness: null, manifestPolicy: null, firstTimeContributorGrace: null }); }); it("parses gate.mergeReadiness + gate.firstTimeContributorGrace, round-trips them, and warns on bad values (#822)", () => { @@ -703,6 +703,17 @@ describe("parseFocusManifest gate config", () => { expect(bad.gate.present).toBe(false); }); + it("parses gate.manifestPolicy, round-trips it through gateConfigToJson, and warns + nulls on a bad value (#555)", () => { + const m = parseFocusManifest({ gate: { manifestPolicy: "block" } }); + expect(m.gate.present).toBe(true); + expect(m.gate.manifestPolicy).toBe("block"); + expect(gateConfigToJson(m.gate)).toMatchObject({ manifestPolicy: "block" }); + const bad = parseFocusManifest({ gate: { manifestPolicy: "sometimes" } }); + expect(bad.gate.manifestPolicy).toBeNull(); + expect(bad.gate.present).toBe(false); + expect(bad.warnings.some((w) => w.includes("gate.manifestPolicy"))).toBe(true); + }); + it("parses the gate.slop block, round-trips it, and warns on a non-mapping (#530/#532)", () => { const m = parseFocusManifest({ gate: { slop: { mode: "block", minScore: 55 } } }); expect(m.gate.present).toBe(true); diff --git a/test/unit/gate-check-policy.test.ts b/test/unit/gate-check-policy.test.ts index a7dc097484..38f97addfa 100644 --- a/test/unit/gate-check-policy.test.ts +++ b/test/unit/gate-check-policy.test.ts @@ -277,3 +277,57 @@ describe("first-time-contributor grace (#552)", () => { expect(evaluateGateCheck(missingIssueAdvisory(), { ...policy, linkedIssueGateMode: "block" }).conclusion).toBe("neutral"); }); }); + +describe("focus-manifest policy gate (#555)", () => { + // The three enforceable manifest-policy findings buildFocusManifestGuidance emits. + const POLICY_FINDINGS = { + manifest_blocked_path: { code: "manifest_blocked_path", title: "Change touches a maintainer-blocked area", severity: "critical" as const, detail: "Changed paths match maintainer-blocked patterns.", action: "Move out of the blocked area." }, + manifest_linked_issue_required: { code: "manifest_linked_issue_required", title: "Maintainer requires a linked issue", severity: "warning" as const, detail: "Manifest requires a linked issue.", action: "Link the issue." }, + manifest_missing_tests: { code: "manifest_missing_tests", title: "Maintainer test expectations unmet", severity: "warning" as const, detail: "Manifest expects test evidence.", action: "Add tests." }, + }; + + function manifestAdvisory(code: keyof typeof POLICY_FINDINGS): Advisory { + return { ...missingIssueAdvisory(), findings: [POLICY_FINDINGS[code]] }; + } + + for (const code of Object.keys(POLICY_FINDINGS) as (keyof typeof POLICY_FINDINGS)[]) { + describe(code, () => { + it("blocks a confirmed contributor when manifestPolicy: block", () => { + const result = evaluateGateCheck(manifestAdvisory(code), { manifestPolicyGateMode: "block", confirmedContributor: true }); + expect(result.conclusion).toBe("failure"); + expect(result.blockers.map((finding) => finding.code)).toContain(code); + }); + + it("does not block when manifestPolicy: off (advisory-only)", () => { + expect(evaluateGateCheck(manifestAdvisory(code), { manifestPolicyGateMode: "off", confirmedContributor: true }).conclusion).toBe("success"); + }); + + it("does not block when manifestPolicy: advisory (advisory != block)", () => { + expect(evaluateGateCheck(manifestAdvisory(code), { manifestPolicyGateMode: "advisory", confirmedContributor: true }).conclusion).toBe("success"); + }); + + it("never blocks a non-confirmed contributor even with manifestPolicy: block", () => { + const result = evaluateGateCheck(manifestAdvisory(code), { manifestPolicyGateMode: "block", confirmedContributor: false }); + expect(result.conclusion).toBe("neutral"); + expect(result.blockers).toEqual([]); + }); + }); + } + + it("is an INDEPENDENT dimension: mergeReadiness: block does NOT promote a manifest-policy finding (kept out of the composite)", () => { + const eff = resolveEffectiveSettings(settings({ manifestPolicyGateMode: "off", mergeReadinessGateMode: "block" }), parseFocusManifest(null)); + expect(evaluateGateCheck(manifestAdvisory("manifest_blocked_path"), gateCheckPolicy(eff, null, true)).conclusion).toBe("success"); + }); + + it("gateCheckPolicy threads manifestPolicyGateMode into the policy", () => { + expect(gateCheckPolicy(settings({ manifestPolicyGateMode: "block" }), null, true).manifestPolicyGateMode).toBe("block"); + }); + + it("end-to-end: a manifest gate.manifestPolicy: block sets effective.manifestPolicyGateMode and blocks a blockedPath PR", () => { + const eff = resolveEffectiveSettings(settings({ manifestPolicyGateMode: "off" }), parseFocusManifest({ gate: { manifestPolicy: "block" } })); + expect(eff.manifestPolicyGateMode).toBe("block"); + const result = evaluateGateCheck(manifestAdvisory("manifest_blocked_path"), gateCheckPolicy(eff, null, true)); + expect(result.conclusion).toBe("failure"); + expect(result.blockers.map((finding) => finding.code)).toContain("manifest_blocked_path"); + }); +}); diff --git a/test/unit/maintainer-activation.test.ts b/test/unit/maintainer-activation.test.ts index ffcead4a83..6ccbab7e03 100644 --- a/test/unit/maintainer-activation.test.ts +++ b/test/unit/maintainer-activation.test.ts @@ -34,6 +34,7 @@ function settings(overrides: Partial = {}): RepositorySettin qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, diff --git a/test/unit/policy-sanitizer.test.ts b/test/unit/policy-sanitizer.test.ts index 88a3912600..e7e8298d35 100644 --- a/test/unit/policy-sanitizer.test.ts +++ b/test/unit/policy-sanitizer.test.ts @@ -68,6 +68,7 @@ function settingsFor(repoFullName: string, overrides: Partial = {}): RepositorySettin qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, diff --git a/test/unit/self-dogfood-registration-pack.test.ts b/test/unit/self-dogfood-registration-pack.test.ts index b8f13151a9..44bab4b884 100644 --- a/test/unit/self-dogfood-registration-pack.test.ts +++ b/test/unit/self-dogfood-registration-pack.test.ts @@ -61,6 +61,7 @@ function settingsFor(repoFullName: string, overrides: Partial = {}): RepositorySettin qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts index 7bbd6e3cf4..1dff213cac 100644 --- a/test/unit/signals-coverage.test.ts +++ b/test/unit/signals-coverage.test.ts @@ -1531,6 +1531,7 @@ function repoSettings(repoFullName: string): RepositorySettings { qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, diff --git a/test/unit/signals-v2.test.ts b/test/unit/signals-v2.test.ts index 7637faceb9..a19ab07ad0 100644 --- a/test/unit/signals-v2.test.ts +++ b/test/unit/signals-v2.test.ts @@ -1625,6 +1625,7 @@ describe("v2 signal builders", () => { qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, diff --git a/test/unit/signals.test.ts b/test/unit/signals.test.ts index d482ce4126..4ea84c2728 100644 --- a/test/unit/signals.test.ts +++ b/test/unit/signals.test.ts @@ -391,6 +391,7 @@ describe("world-class backend signals", () => { qualityGateMode: "advisory" as const, slopGateMode: "off" as const, mergeReadinessGateMode: "off" as const, + manifestPolicyGateMode: "off" as const, firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, @@ -441,6 +442,7 @@ describe("world-class backend signals", () => { qualityGateMode: "advisory" as const, slopGateMode: "off" as const, mergeReadinessGateMode: "off" as const, + manifestPolicyGateMode: "off" as const, firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, @@ -511,6 +513,7 @@ describe("world-class backend signals", () => { qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, @@ -602,6 +605,7 @@ describe("world-class backend signals", () => { qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null, @@ -668,6 +672,7 @@ describe("world-class backend signals", () => { qualityGateMode: "advisory", slopGateMode: "off", mergeReadinessGateMode: "off", + manifestPolicyGateMode: "off", firstTimeContributorGrace: false, slopAiAdvisory: false, qualityGateMinScore: null,