diff --git a/.gittensory.yml.example b/.gittensory.yml.example index 08c4ffa618..f5a955b7f9 100644 --- a/.gittensory.yml.example +++ b/.gittensory.yml.example @@ -607,9 +607,6 @@ settings: # Bool. Default: true. backfillEnabled: true - # Use private trust signals in scoring. Bool. Default: true. - privateTrustEnabled: true - # Render a README status badge for the repo. Bool. Default: false. badgeEnabled: false diff --git a/apps/gittensory-ui/public/openapi.json b/apps/gittensory-ui/public/openapi.json index 34d2a92fc7..7eeb1e007a 100644 --- a/apps/gittensory-ui/public/openapi.json +++ b/apps/gittensory-ui/public/openapi.json @@ -8895,9 +8895,6 @@ "backfillEnabled": { "type": "boolean" }, - "privateTrustEnabled": { - "type": "boolean" - }, "badgeEnabled": { "type": "boolean" }, @@ -9336,7 +9333,6 @@ "includeMaintainerAuthors", "requireLinkedIssue", "backfillEnabled", - "privateTrustEnabled", "commandAuthorization" ] }, diff --git a/apps/gittensory-ui/src/routes/docs.tuning.tsx b/apps/gittensory-ui/src/routes/docs.tuning.tsx index 46bac21749..13e6f61052 100644 --- a/apps/gittensory-ui/src/routes/docs.tuning.tsx +++ b/apps/gittensory-ui/src/routes/docs.tuning.tsx @@ -464,10 +464,9 @@ function Tuning() {
  • includeMaintainerAuthors (default false),{" "} requireLinkedIssue (default false), backfillEnabled{" "} - (default true), privateTrustEnabled (default true), - and badgeEnabled (README status badge, default false), and{" "} - publicQualityMetrics (public review-quality page, default false - ). + (default true), and badgeEnabled (README status badge, default{" "} + false), and publicQualityMetrics (public review-quality page, + default false).
  • agentPaused (per-repo kill-switch, default false) and{" "} diff --git a/config/examples/gittensory.full.yml b/config/examples/gittensory.full.yml index a5b2fd8e9a..124d96d809 100644 --- a/config/examples/gittensory.full.yml +++ b/config/examples/gittensory.full.yml @@ -620,9 +620,6 @@ settings: # Bool. Default: true. backfillEnabled: true - # Use private trust signals in scoring. Bool. Default: true. - privateTrustEnabled: true - # Render a README status badge for the repo. Bool. Default: false. badgeEnabled: false diff --git a/migrations/0122_drop_private_trust_enabled.sql b/migrations/0122_drop_private_trust_enabled.sql new file mode 100644 index 0000000000..b65d90d3b2 --- /dev/null +++ b/migrations/0122_drop_private_trust_enabled.sql @@ -0,0 +1,6 @@ +-- Dead-code cleanup (#4012). private_trust_enabled was persisted and exposed via the maintainer settings +-- API but read by zero conditional logic anywhere in src/ -- only ever assigned and passed through. Its doc +-- comment described gating "private trust signals in scoring", which this repo's house rules explicitly +-- forbid wiring in (no trust scores / reward values anywhere), so the correct disposition is removal, not +-- implementation. SQLite 3.35+ / D1 supports DROP COLUMN directly. +ALTER TABLE repository_settings DROP COLUMN private_trust_enabled; diff --git a/packages/gittensory-engine/src/focus-manifest.ts b/packages/gittensory-engine/src/focus-manifest.ts index 5e58199b49..9afc2bf03f 100644 --- a/packages/gittensory-engine/src/focus-manifest.ts +++ b/packages/gittensory-engine/src/focus-manifest.ts @@ -278,7 +278,6 @@ export type FocusManifestSettings = Partial< | "includeMaintainerAuthors" | "requireLinkedIssue" | "backfillEnabled" - | "privateTrustEnabled" | "autonomy" | "autoMaintain" | "agentPaused" @@ -1535,7 +1534,7 @@ function parseSettingsOverride(value: JsonValue | undefined, warnings: string[]) } const publicSurface = normalizeOptionalEnum(r.publicSurface, "settings.publicSurface", ["off", "comment_and_label", "comment_only", "label_only"] as const, warnings); if (publicSurface !== null) out.publicSurface = publicSurface; - for (const key of ["aiReviewByok", "aiReviewAllAuthors", "closeOwnerAuthors", "autoLabelEnabled", "typeLabelsEnabled", "badgeEnabled", "publicQualityMetrics", "createMissingLabel", "includeMaintainerAuthors", "requireLinkedIssue", "backfillEnabled", "privateTrustEnabled", "agentPaused", "agentDryRun"] as const) { + for (const key of ["aiReviewByok", "aiReviewAllAuthors", "closeOwnerAuthors", "autoLabelEnabled", "typeLabelsEnabled", "badgeEnabled", "publicQualityMetrics", "createMissingLabel", "includeMaintainerAuthors", "requireLinkedIssue", "backfillEnabled", "agentPaused", "agentDryRun"] as const) { const flag = normalizeOptionalBoolean(r[key], `settings.${key}`, warnings); if (flag !== null) out[key] = flag; } diff --git a/packages/gittensory-engine/src/types/manifest-deps-types.ts b/packages/gittensory-engine/src/types/manifest-deps-types.ts index c6736de040..ba64bddd55 100644 --- a/packages/gittensory-engine/src/types/manifest-deps-types.ts +++ b/packages/gittensory-engine/src/types/manifest-deps-types.ts @@ -309,7 +309,6 @@ export type RepositorySettings = { includeMaintainerAuthors: boolean; requireLinkedIssue: boolean; backfillEnabled: boolean; - privateTrustEnabled: boolean; /** Opt-in for the public, unauthenticated README status badge (#541). Always populated by the DB layer * (default false); optional so existing settings fixtures/callers need not be touched. */ badgeEnabled?: boolean | undefined; diff --git a/src/api/routes.ts b/src/api/routes.ts index dbd3c3ca6e..2949e9e9aa 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -681,7 +681,6 @@ const repositorySettingsSchema = z.object({ includeMaintainerAuthors: z.boolean().default(false), requireLinkedIssue: z.boolean().default(false), backfillEnabled: z.boolean().default(true), - privateTrustEnabled: z.boolean().default(true), badgeEnabled: z.boolean().default(false), publicQualityMetrics: z.boolean().default(false), commandAuthorization: z @@ -700,9 +699,9 @@ const repositorySettingsSchema = z.object({ // #130 maintainer self-serve settings editor. A PATCH-style subset: every field optional so the maintainer // dashboard can save just the group it changed. Excludes the secret-bearing aiReview* group (set via the -// dedicated /ai-review + /ai-key routes) and the operator-only scoring internals (backfillEnabled, -// privateTrustEnabled). The handler loads current settings and merges, since upsertRepositorySettings -// defaults any absent field rather than preserving it. +// dedicated /ai-review + /ai-key routes) and the operator-only scoring internal (backfillEnabled). The +// handler loads current settings and merges, since upsertRepositorySettings defaults any absent field +// rather than preserving it. const maintainerSettingsSchema = z .object({ commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]), @@ -3823,7 +3822,6 @@ export function createApp() { includeMaintainerAuthors: parsed.data.includeMaintainerAuthors, requireLinkedIssue: parsed.data.requireLinkedIssue, backfillEnabled: parsed.data.backfillEnabled, - privateTrustEnabled: parsed.data.privateTrustEnabled, badgeEnabled: parsed.data.badgeEnabled, publicQualityMetrics: parsed.data.publicQualityMetrics, commandAuthorization: normalizeCommandAuthorizationPolicy(parsed.data.commandAuthorization).policy, diff --git a/src/api/workboard.ts b/src/api/workboard.ts deleted file mode 100644 index 542d55bed9..0000000000 --- a/src/api/workboard.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { IssueRecord, RepositoryRecord } from "../types"; - -export type WorkboardItem = { - repoFullName: string; - issueNumber: number; - title: string; - state: string; - htmlUrl?: string | null | undefined; - fit: "good" | "caution" | "hold"; - reasons: string[]; -}; - -export function buildWorkboard(repo: RepositoryRecord | null, issues: IssueRecord[]): WorkboardItem[] { - if (!repo) return []; - return issues.map((issue) => { - const reasons: string[] = []; - let fit: WorkboardItem["fit"] = "good"; - if (!repo.isRegistered) { - fit = "hold"; - reasons.push("Repository is not present in the latest registry snapshot."); - } - if (issue.linkedPrs.length > 0) { - fit = fit === "hold" ? "hold" : "caution"; - reasons.push("Issue already has linked pull requests."); - } - if (issue.authorAssociation && ["OWNER", "MEMBER", "COLLABORATOR"].includes(issue.authorAssociation)) { - reasons.push("Issue was opened by a maintainer-associated account."); - } - if (reasons.length === 0) reasons.push("Open issue with no linked pull request detected by Gittensory."); - return { - repoFullName: repo.fullName, - issueNumber: issue.number, - title: issue.title, - state: issue.state, - htmlUrl: issue.htmlUrl, - fit, - reasons, - }; - }); -} diff --git a/src/db/repositories.ts b/src/db/repositories.ts index fcd0e2dfea..0e3a6e096a 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -534,7 +534,6 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, badgeEnabled: false, publicQualityMetrics: false, agentPaused: false, @@ -612,7 +611,6 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise includeMaintainerAuthors: row.includeMaintainerAuthors, requireLinkedIssue: row.requireLinkedIssue, backfillEnabled: row.backfillEnabled, - privateTrustEnabled: row.privateTrustEnabled, badgeEnabled: row.badgeEnabled, publicQualityMetrics: row.publicQualityMetrics, agentPaused: row.agentPaused, @@ -733,7 +731,6 @@ export async function upsertRepositorySettings(env: Env, settings: Partial 0 && !facts.changedPaths.some((path) => rule.whenPaths.some((glob) => matchesManifestPath(path, glob)))) { - return false; - } - if (rule.titleContains !== null && !facts.title.toLowerCase().includes(rule.titleContains.toLowerCase())) return false; - if (rule.descriptionContains !== null && !facts.description.toLowerCase().includes(rule.descriptionContains.toLowerCase())) return false; - return true; -} - -/** Resolve the labels suggested (and, when auto-labeling is on, to apply) for a PR. Deterministic: preserves rule - * order, dedupes, and never mutates its inputs. Pure. */ -export function resolveLabelingRules(input: { - rules: readonly LabelingRule[]; - facts: LabelingRuleFacts; - autoLabelEnabled: boolean; -}): LabelingDecision { - const suggest: string[] = []; - for (const rule of input.rules) { - if (ruleMatches(rule, input.facts) && !suggest.includes(rule.label)) suggest.push(rule.label); - } - return { suggest, apply: input.autoLabelEnabled ? [...suggest] : [] }; -} diff --git a/src/services/queue-burden-breakdown.ts b/src/services/queue-burden-breakdown.ts deleted file mode 100644 index e0c6111425..0000000000 --- a/src/services/queue-burden-breakdown.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { sanitizePublicComment } from "../github/commands"; -import type { QueueHealth } from "../signals/engine"; - -// ─── Queue burden breakdown (explanation family) ───────────────────────────────────────────────── -// A pure projection over a computed {@link QueueHealth} that decomposes the otherwise-opaque -// `burdenScore` into its weighted, observable contributors and names the single highest-leverage lever -// a maintainer can pull to bring queue pressure down fastest. Sibling of `score-breakdown.ts`, -// `miner-dashboard-recommendations.ts`, and `agent-action-explanation-card.ts`: deterministic, no I/O, -// no GitHub fetch. Public-safe by construction — it reports observable counts, relative shares, and -// bands only, and routes every rendered string through `sanitizePublicComment`. - -export type QueueBurdenBand = "credit" | "none" | "low" | "moderate" | "high"; - -export type QueueBurdenComponent = { - /** The QueueHealth signal this contribution is derived from. */ - component: string; - /** Observable signal count (open PRs, unlinked PRs, collision clusters, …). */ - count: number; - /** Signed per-unit weight this signal carries in the burden formula (the reviewable credit is negative). */ - weightPerUnit: number; - /** `count * weightPerUnit` — positive for a penalty, negative for the reviewable credit. */ - contribution: number; - /** Share of total positive penalty (0–100). For the credit it is the percentage of penalty it offsets. */ - sharePercent: number; - band: QueueBurdenBand; - summary: string; - lever: string; - /** 0–100 ranking weight used to pick the single highest-leverage improvement lever. */ - leverageScore: number; -}; - -export type QueueBurdenBreakdown = { - repoFullName: string; - generatedAt: string; - /** The authoritative (already clamped) burden score carried on the QueueHealth. */ - burdenScore: number; - level: QueueHealth["level"]; - /** Pre-clamp signed sum of every contribution (penalties minus the reviewable credit). */ - rawBurden: number; - /** True when the raw sum fell outside the 0–100 band and the engine clamped it. */ - clamped: boolean; - /** Sum of the positive penalty contributions. */ - totalPenalty: number; - /** Absolute size of the reviewable credit that offsets the penalties. */ - totalCredit: number; - components: QueueBurdenComponent[]; - highestLeverageLever: { component: string; lever: string; reason: string }; - summary: string; -}; - -// These per-unit weights MIRROR buildQueueHealth() in src/signals/engine.ts. A drift-guard test rebuilds a -// QueueHealth through buildQueueHealth and asserts this module recomposes the same burdenScore, so any change -// to the engine weights fails the suite instead of silently producing a wrong breakdown. -const PENALTY_DESCRIPTORS: ReadonlyArray<{ - component: string; - weightPerUnit: number; - count: (health: QueueHealth) => number; - describe: (count: number) => { summary: string; lever: string }; -}> = [ - { - component: "unlinkedPullRequests", - weightPerUnit: 8, - count: (health) => health.signals.unlinkedPullRequests, - describe: (count) => - count > 0 - ? { - summary: `${count} open pull request(s) lack a linked issue, the heaviest per-PR burden factor.`, - lever: "Ask contributors to link a closing issue or state explicit no-issue intent so unlinked PRs stop driving burden.", - } - : { - summary: "Every open pull request carries linked-issue context, so this factor adds no burden.", - lever: "Keep requiring a linked issue or a clear no-issue rationale so this factor stays at zero.", - }, - }, - { - component: "collisionClusters", - weightPerUnit: 10, - count: (health) => health.signals.collisionClusters, - describe: (count) => - count > 0 - ? { - summary: `${count} duplicate or overlapping work cluster(s) carry the highest per-unit burden weight.`, - lever: "Resolve overlapping submissions before spending detailed review time to cut collision burden fastest.", - } - : { - summary: "No duplicate or overlapping work clusters were detected, so this factor adds no burden.", - lever: "Keep deduplicating incoming work early so collision burden stays at zero.", - }, - }, - { - component: "openPullRequests", - weightPerUnit: 6, - count: (health) => health.signals.openPullRequests, - describe: (count) => - count > 0 - ? { - summary: `${count} open pull request(s) contribute baseline review load.`, - lever: "Land or close open pull requests to reduce the baseline queue load.", - } - : { - summary: "There are no open pull requests adding baseline load.", - lever: "No action needed; baseline pull-request load is already at zero.", - }, - }, - { - component: "stalePullRequests", - weightPerUnit: 6, - count: (health) => health.signals.stalePullRequests, - describe: (count) => - count > 0 - ? { - summary: `${count} open pull request(s) have stalled without an update for at least 14 days.`, - lever: "Review, nudge, or close stale pull requests so they stop accruing burden.", - } - : { - summary: "No open pull requests have stalled past the 14-day staleness threshold.", - lever: "Keep pull requests moving so none cross the staleness threshold.", - }, - }, - { - component: "over30DayPullRequests", - weightPerUnit: 4, - count: (health) => health.signals.ageBuckets.over30Days, - describe: (count) => - count > 0 - ? { - summary: `${count} open pull request(s) have aged past 30 days.`, - lever: "Resolve the long-aged pull requests to clear the oldest backlog in the queue.", - } - : { - summary: "No open pull requests have aged past 30 days.", - lever: "Keep clearing aged work so none crosses the 30-day mark.", - }, - }, - { - component: "openIssues", - weightPerUnit: 1, - count: (health) => health.signals.openIssues, - describe: (count) => - count > 0 - ? { - summary: `${count} open issue(s) add minor triage load.`, - lever: "Triage or close resolved open issues to trim residual queue load.", - } - : { - summary: "There are no open issues adding triage load.", - lever: "No action needed; open-issue triage load is already at zero.", - }, - }, -]; - -const REVIEWABLE_CREDIT_PER_UNIT = -2; - -function penaltyBand(count: number, sharePercent: number): QueueBurdenBand { - if (count <= 0) return "none"; - if (sharePercent >= 40) return "high"; - if (sharePercent >= 15) return "moderate"; - return "low"; -} - -function shareOf(contribution: number, totalPenalty: number): number { - if (totalPenalty <= 0) return 0; - return Math.round((Math.abs(contribution) / totalPenalty) * 100); -} - -function creditComponent(health: QueueHealth, totalPenalty: number): QueueBurdenComponent { - const count = health.signals.likelyReviewablePullRequests; - const contribution = count * REVIEWABLE_CREDIT_PER_UNIT; - const sharePercent = shareOf(contribution, totalPenalty); - return { - component: "likelyReviewablePullRequests", - count, - weightPerUnit: REVIEWABLE_CREDIT_PER_UNIT, - contribution, - sharePercent, - band: "credit", - summary: - count > 0 - ? `${count} open pull request(s) look readily reviewable and reduce net queue burden.` - : "No open pull requests are currently counted as readily reviewable, so nothing is offsetting burden.", - lever: - count > 0 - ? "Keep pull requests linked and fresh so they stay readily reviewable and keep offsetting burden." - : "Help open pull requests become linked and fresh so they start offsetting queue burden.", - // The credit is already helping; it is never the lever a maintainer pulls to REDUCE burden, so it stays - // out of the highest-leverage ranking. - leverageScore: 0, - }; -} - -function pickHighestLeverage(components: QueueBurdenComponent[]): QueueBurdenBreakdown["highestLeverageLever"] { - // Rank by share of burden, then break ties toward the heavier per-unit weight (reducing one high-weight item - // removes more burden per action, so it is the better lever), and finally by name purely for determinism. - const ranked = [...components].sort( - (left, right) => - right.leverageScore - left.leverageScore || - right.weightPerUnit - left.weightPerUnit || - left.component.localeCompare(right.component), - ); - const top = ranked[0]!; - // When no penalty is active (every leverageScore is 0), the sort tie-break would otherwise surface an - // arbitrary alphabetically-first component as "the lever" — which is misleading because there is nothing to - // reduce. Return an explicit no-op lever instead so the breakdown stays honest for a healthy queue. - if (top.leverageScore <= 0) { - return { - component: "none", - lever: sanitizePublicComment("No queue-burden lever needs attention; there are no active contributors to reduce."), - reason: sanitizePublicComment("Queue burden has no active penalty contributors right now, so there is no pressing lever to pull."), - }; - } - const reason = - top.band === "high" - ? `${top.component} is the dominant queue-burden contributor right now.` - : `${top.component} is the largest remaining queue-burden contributor.`; - return { - component: top.component, - lever: top.lever, - reason: sanitizePublicComment(reason), - }; -} - -/** - * Pure projection over a {@link QueueHealth} that explains how the queue `burdenScore` breaks down into its - * weighted, observable contributors and names the single highest-leverage lever to reduce queue pressure. - */ -export function explainQueueBurden(health: QueueHealth): QueueBurdenBreakdown { - const penalties = PENALTY_DESCRIPTORS.map((descriptor) => { - const count = descriptor.count(health); - const contribution = count * descriptor.weightPerUnit; - return { descriptor, count, contribution }; - }); - const totalPenalty = penalties.reduce((sum, entry) => sum + entry.contribution, 0); - - const penaltyComponents: QueueBurdenComponent[] = penalties.map(({ descriptor, count, contribution }) => { - const sharePercent = shareOf(contribution, totalPenalty); - const copy = descriptor.describe(count); - return { - component: descriptor.component, - count, - weightPerUnit: descriptor.weightPerUnit, - contribution, - sharePercent, - band: penaltyBand(count, sharePercent), - summary: copy.summary, - lever: copy.lever, - // A penalty's leverage is exactly its share of the burden — the biggest contributor is the best lever. - leverageScore: sharePercent, - }; - }); - - const credit = creditComponent(health, totalPenalty); - const totalCredit = Math.abs(credit.contribution); - const rawBurden = totalPenalty + credit.contribution; - // The engine clamps burden to 0–100. The lower bound is unreachable in practice: every open PR adds 6 to the - // penalty while its reviewable credit only subtracts 2 (and reviewable PRs never exceed open PRs), so the - // penalties always dominate the credit. Only the upper clamp is observable here. - const clamped = rawBurden > 100; - - const components = [...penaltyComponents, credit].map((entry) => ({ - ...entry, - summary: sanitizePublicComment(entry.summary), - lever: sanitizePublicComment(entry.lever), - })); - - const highestLeverageLever = pickHighestLeverage(components); - const summary = - highestLeverageLever.component === "none" - ? `Queue burden is ${health.level} with no active contributors to address.` - : `Queue burden is ${health.level}; ${highestLeverageLever.component} is the leading factor to address.`; - - return { - repoFullName: health.repoFullName, - generatedAt: health.generatedAt, - burdenScore: health.burdenScore, - level: health.level, - rawBurden, - clamped, - totalPenalty, - totalCredit, - components, - highestLeverageLever, - summary: sanitizePublicComment(summary), - }; -} diff --git a/src/settings/settings-drift.ts b/src/settings/settings-drift.ts deleted file mode 100644 index 482d3e9635..0000000000 --- a/src/settings/settings-drift.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getGlobalContributorBlacklist, getRepositorySettings } from "../db/repositories"; -import { parseFocusManifest, resolveEffectiveSettings, type FocusManifest } from "../signals/focus-manifest"; -import { loadRepoFocusManifest } from "../signals/focus-manifest-loader"; -import type { RepositorySettings } from "../types"; - -// The "no manifest at all" baseline (parseFocusManifest(null) is this codebase's existing idiom for it, e.g. -// test/unit/focus-manifest.test.ts). Comparing against THIS instead of the raw dbSettings row isolates drift -// caused specifically by the manifest -- resolveEffectiveSettings also applies manifest-INDEPENDENT -// normalization (the shared contributor-blacklist merge below the gate block, and the requireLinkedIssue-implies- -// block downgrade), which would otherwise misreport as "shadowed by private config" for every repo with a -// global blacklist entry, even one with no manifest at all. -const NO_MANIFEST = parseFocusManifest(null); - -export type SettingsDriftEntry = { - field: keyof RepositorySettings; - dbValue: unknown; - effectiveValue: unknown; -}; - -// Local copy of the stableStringify helper already duplicated in review/ai-review-cache-input.ts and -// upstream/ruleset.ts for the same order-independent-equality purpose -- matches this codebase's existing -// pattern of a small per-module copy rather than a shared utility for a ~6-line function. -function stableStringify(value: unknown): string { - if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`; - if (value !== null && typeof value === "object") { - return `{${Object.entries(value as Record) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([key, nested]) => `${JSON.stringify(key)}:${stableStringify(nested)}`) - .join(",")}}`; - } - return JSON.stringify(value); -} - -/** - * Diagnostic-only, read-only diff between a repo's DB-stored `repository_settings` row and the LIVE effective - * settings `resolveEffectiveSettings` would actually apply for it (config-as-code `.gittensory.yml` merged over - * the DB row). Every entry here is a DB-stored field whose value is silently shadowed by a manifest override -- - * useful for a self-host operator who changed something via the dashboard and can't tell why it isn't taking - * effect. PURE and never called from the live review/gate path itself (see resolveEffectiveSettings, - * settings/repository-settings.ts's resolveRepositorySettings) -- this can never affect what settings a PR - * review actually resolves to, only report on it after the fact. - */ -export function computeSettingsDrift( - dbSettings: RepositorySettings, - manifest: FocusManifest, - sharedContributorBlacklist: RepositorySettings["contributorBlacklist"] = [], -): SettingsDriftEntry[] { - const baseline = resolveEffectiveSettings(dbSettings, NO_MANIFEST, sharedContributorBlacklist); - const effective = resolveEffectiveSettings(dbSettings, manifest, sharedContributorBlacklist); - return (Object.keys(dbSettings) as (keyof RepositorySettings)[]) - .filter((field) => stableStringify(baseline[field]) !== stableStringify(effective[field])) - .map((field) => ({ field, dbValue: dbSettings[field], effectiveValue: effective[field] })); -} - -/** Same diff, but fetching the DB row, manifest, and shared contributor blacklist live for one repo -- the - * same three reads settings/repository-settings.ts's resolveRepositorySettings already makes, so this never - * introduces a new data-fetch path, only a read-only diagnostic view over the existing one. */ -export async function computeSettingsDriftForRepo(env: Env, repoFullName: string): Promise { - const [dbSettings, manifest, sharedContributorBlacklist] = await Promise.all([ - getRepositorySettings(env, repoFullName), - loadRepoFocusManifest(env, repoFullName), - getGlobalContributorBlacklist(env).catch(() => []), - ]); - return computeSettingsDrift(dbSettings, manifest, sharedContributorBlacklist); -} diff --git a/src/types.ts b/src/types.ts index 24b6e0fa6e..3af634f15a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -844,7 +844,6 @@ export type RepositorySettings = { includeMaintainerAuthors: boolean; requireLinkedIssue: boolean; backfillEnabled: boolean; - privateTrustEnabled: boolean; /** Opt-in for the public, unauthenticated README status badge (#541). Always populated by the DB layer * (default false); optional so existing settings fixtures/callers need not be touched. */ badgeEnabled?: boolean | undefined; diff --git a/test/integration/routes-errors.test.ts b/test/integration/routes-errors.test.ts index 9f0e08e1b7..3cfe12e8ea 100644 --- a/test/integration/routes-errors.test.ts +++ b/test/integration/routes-errors.test.ts @@ -1172,13 +1172,12 @@ describe("api route guards and error branches", () => { publicSignalLevel: "minimal", checkRunDetailLevel: "deep", backfillEnabled: false, - privateTrustEnabled: false, }), }, env, ); expect(updated.status).toBe(200); - await expect(updated.json()).resolves.toMatchObject({ commentMode: "all_prs", checkRunDetailLevel: "deep", backfillEnabled: false, privateTrustEnabled: false }); + await expect(updated.json()).resolves.toMatchObject({ commentMode: "all_prs", checkRunDetailLevel: "deep", backfillEnabled: false }); }); }); diff --git a/test/unit/adapters.test.ts b/test/unit/adapters.test.ts index 83229e0793..e246f96b5e 100644 --- a/test/unit/adapters.test.ts +++ b/test/unit/adapters.test.ts @@ -1,9 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { buildWorkboard } from "../../src/api/workboard"; import { normalizeGittBountySnapshot } from "../../src/bounties/ingest"; import { fetchPublicContributorProfile } from "../../src/github/public"; import { jsonString, normalizeRepoFullName, parseJson, repoParts } from "../../src/utils/json"; -import type { IssueRecord, RepositoryRecord } from "../../src/types"; describe("small adapters and normalizers", () => { afterEach(() => { @@ -61,46 +59,6 @@ describe("small adapters and normalizers", () => { expect(records[2]?.payload).not.toHaveProperty("active"); }); - it("builds workboard holds and maintainer-authored context", () => { - const repo: RepositoryRecord = { - fullName: "JSONbored/gittensory", - owner: "JSONbored", - name: "gittensory", - isInstalled: true, - isRegistered: false, - isPrivate: true, - }; - const issues: IssueRecord[] = [ - { - repoFullName: repo.fullName, - number: 1, - title: "Add queue health endpoint", - state: "open", - authorLogin: "maintainer", - authorAssociation: "OWNER", - labels: [], - linkedPrs: [7], - }, - ]; - - expect(buildWorkboard(null, issues)).toEqual([]); - const item = buildWorkboard(repo, issues)[0]; - expect(item).toMatchObject({ fit: "hold", issueNumber: 1 }); - expect(item?.reasons).toEqual(expect.arrayContaining(["Repository is not present in the latest registry snapshot.", "Issue already has linked pull requests.", "Issue was opened by a maintainer-associated account."])); - - const registeredRepo = { ...repo, isRegistered: true, isPrivate: false }; - const baseIssue = issues[0]!; - expect( - buildWorkboard(registeredRepo, [ - { ...baseIssue, number: 2, linkedPrs: [], authorAssociation: "CONTRIBUTOR" }, - { ...baseIssue, number: 3, linkedPrs: [9], authorAssociation: "CONTRIBUTOR" }, - ]), - ).toEqual([ - expect.objectContaining({ fit: "good", reasons: ["Open issue with no linked pull request detected by Gittensory."] }), - expect.objectContaining({ fit: "caution", reasons: ["Issue already has linked pull requests."] }), - ]); - }); - it("fetches public contributor profile languages and handles unavailable GitHub responses", async () => { vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { const url = input.toString(); diff --git a/test/unit/backfill.test.ts b/test/unit/backfill.test.ts index fb25cf3711..3666963751 100644 --- a/test/unit/backfill.test.ts +++ b/test/unit/backfill.test.ts @@ -1155,7 +1155,6 @@ describe("GitHub backfill", () => { checkRunMode: "enabled", checkRunDetailLevel: "standard", backfillEnabled: false, - privateTrustEnabled: true, }); const result = await backfillRegisteredRepositories(env); @@ -3017,7 +3016,6 @@ describe("GitHub backfill", () => { checkRunMode: "enabled", checkRunDetailLevel: "standard", backfillEnabled: false, - privateTrustEnabled: true, }); await expect(enqueueRepositoryOpenDataBackfill(env, { repoFullName: "missing/repo", requestedBy: "api" })).resolves.toMatchObject({ status: "skipped" }); @@ -3030,7 +3028,6 @@ describe("GitHub backfill", () => { checkRunMode: "enabled", checkRunDetailLevel: "standard", backfillEnabled: true, - privateTrustEnabled: true, }); await upsertRepoSyncSegment(env, { repoFullName: "JSONbored/gittensory", diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 59646e6e41..cbcb7d27f8 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -305,7 +305,6 @@ describe(".gittensory.yml.example field-exhaustiveness (#1670)", () => { includeMaintainerAuthors: "includeMaintainerAuthors:", requireLinkedIssue: "requireLinkedIssue:", backfillEnabled: "backfillEnabled:", - privateTrustEnabled: "privateTrustEnabled:", autonomy: "autonomy:", autoMaintain: "autoMaintain:", agentPaused: "agentPaused:", @@ -1755,7 +1754,6 @@ describe("parseFocusManifest settings override + resolveEffectiveSettings", () = includeMaintainerAuthors: true, requireLinkedIssue: true, backfillEnabled: false, - privateTrustEnabled: true, }, }); expect(m.present).toBe(true); @@ -1777,7 +1775,6 @@ describe("parseFocusManifest settings override + resolveEffectiveSettings", () = includeMaintainerAuthors: true, requireLinkedIssue: true, backfillEnabled: false, - privateTrustEnabled: true, }); }); diff --git a/test/unit/maintainer-activation.test.ts b/test/unit/maintainer-activation.test.ts index 18da722617..997001b235 100644 --- a/test/unit/maintainer-activation.test.ts +++ b/test/unit/maintainer-activation.test.ts @@ -48,7 +48,6 @@ function settings(overrides: Partial = {}): RepositorySettin includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/policy-sanitizer.test.ts b/test/unit/policy-sanitizer.test.ts index 2812d92623..72f3678a81 100644 --- a/test/unit/policy-sanitizer.test.ts +++ b/test/unit/policy-sanitizer.test.ts @@ -82,7 +82,6 @@ function settingsFor(repoFullName: string, overrides: Partial Math.max(0, Math.min(100, value)); - -function makeHealth(input: { - openPullRequests?: number; - openIssues?: number; - unlinkedPullRequests?: number; - stalePullRequests?: number; - over30Days?: number; - collisionClusters?: number; - likelyReviewablePullRequests?: number; - burdenScore?: number; - level?: QueueHealth["level"]; - repoFullName?: string; - generatedAt?: string; -}): QueueHealth { - return { - repoFullName: input.repoFullName ?? "owner/repo", - generatedAt: input.generatedAt ?? "2026-06-30T00:00:00.000Z", - burdenScore: input.burdenScore ?? 0, - level: input.level ?? "low", - summary: "fixture", - signals: { - openIssues: input.openIssues ?? 0, - openPullRequests: input.openPullRequests ?? 0, - unlinkedPullRequests: input.unlinkedPullRequests ?? 0, - stalePullRequests: input.stalePullRequests ?? 0, - draftPullRequests: 0, - maintainerAuthoredPullRequests: 0, - collisionClusters: input.collisionClusters ?? 0, - ageBuckets: { under7Days: 0, days7To30: 0, over30Days: input.over30Days ?? 0 }, - likelyReviewablePullRequests: input.likelyReviewablePullRequests ?? 0, - }, - findings: [], - }; -} - -const componentByName = (breakdown: ReturnType, name: string) => - breakdown.components.find((entry) => entry.component === name)!; - -describe("queue burden breakdown", () => { - it("reports all-zero burden with no active levers and a complete component set", () => { - const breakdown = explainQueueBurden(makeHealth({})); - expect(breakdown.totalPenalty).toBe(0); - expect(breakdown.totalCredit).toBe(0); - expect(breakdown.rawBurden).toBe(0); - expect(breakdown.clamped).toBe(false); - // Six penalty signals plus the reviewable credit. - expect(breakdown.components).toHaveLength(7); - for (const entry of breakdown.components.filter((c) => c.component !== "likelyReviewablePullRequests")) { - expect(entry.band).toBe("none"); - expect(entry.sharePercent).toBe(0); - expect(entry.leverageScore).toBe(0); - } - const credit = componentByName(breakdown, "likelyReviewablePullRequests"); - expect(credit.band).toBe("credit"); - expect(credit.leverageScore).toBe(0); - // No active penalty → an honest no-op lever, never an arbitrary alphabetically-first component. - expect(breakdown.highestLeverageLever.component).toBe("none"); - expect(breakdown.highestLeverageLever.reason).toMatch(/no active penalty/i); - expect(breakdown.summary).toMatch(/no active contributors/i); - }); - - it("breaks an equal-share tie toward the heavier-weighted contributor, not alphabetical order", () => { - // unlinked (weight 8) and stale (weight 6) both reach a 24-point contribution → equal share; the heavier - // per-unit weight (unlinked) is the better lever even though "stalePullRequests" sorts later alphabetically. - const breakdown = explainQueueBurden(makeHealth({ unlinkedPullRequests: 3, stalePullRequests: 4, burdenScore: 48 })); - expect(componentByName(breakdown, "unlinkedPullRequests").sharePercent).toBe(50); - expect(componentByName(breakdown, "stalePullRequests").sharePercent).toBe(50); - expect(breakdown.highestLeverageLever.component).toBe("unlinkedPullRequests"); - }); - - it("names a real penalty lever even when the reviewable credit is offsetting burden", () => { - // Realistic: 3 open PRs (18 penalty), 3 reviewable (-6 credit) → still an open-PR lever to pull. - const breakdown = explainQueueBurden(makeHealth({ openPullRequests: 3, likelyReviewablePullRequests: 3 })); - expect(breakdown.highestLeverageLever.component).toBe("openPullRequests"); - expect(breakdown.highestLeverageLever.component).not.toBe("none"); - }); - - it("flags the dominant contributor as high band and the top lever", () => { - // collisionClusters carries weight 10 → 50 of a 64 total penalty (≈78% share, high band). - const breakdown = explainQueueBurden( - makeHealth({ collisionClusters: 5, unlinkedPullRequests: 1, openPullRequests: 1, burdenScore: 64, level: "high" }), - ); - const collisions = componentByName(breakdown, "collisionClusters"); - expect(collisions.contribution).toBe(50); - expect(collisions.sharePercent).toBe(78); - expect(collisions.band).toBe("high"); - expect(breakdown.totalPenalty).toBe(64); - expect(breakdown.rawBurden).toBe(64); - expect(breakdown.clamped).toBe(false); - expect(breakdown.highestLeverageLever.component).toBe("collisionClusters"); - expect(breakdown.highestLeverageLever.reason).toMatch(/dominant/i); - // unlinked at 8/64 ≈ 13% is below the moderate threshold → low band. - expect(componentByName(breakdown, "unlinkedPullRequests").band).toBe("low"); - }); - - it("classifies a moderate top contributor and names the largest-remaining lever", () => { - // Three equal 12-point contributors → 36 total, 33% each (moderate); name tie-break picks openPullRequests. - const breakdown = explainQueueBurden( - makeHealth({ openPullRequests: 2, stalePullRequests: 2, over30Days: 3, burdenScore: 36, level: "medium" }), - ); - const openPr = componentByName(breakdown, "openPullRequests"); - expect(openPr.sharePercent).toBe(33); - expect(openPr.band).toBe("moderate"); - expect(breakdown.highestLeverageLever.component).toBe("openPullRequests"); - expect(breakdown.highestLeverageLever.reason).toMatch(/largest remaining/i); - }); - - it("marks the breakdown clamped when raw penalties exceed 100", () => { - const breakdown = explainQueueBurden(makeHealth({ collisionClusters: 11, burdenScore: 100, level: "critical" })); - expect(breakdown.rawBurden).toBe(110); - expect(breakdown.clamped).toBe(true); - expect(breakdown.burdenScore).toBe(100); - }); - - it("applies the reviewable credit as an offset without ever driving burden negative", () => { - // Realistic: reviewable PRs cannot exceed open PRs. 4 open (24 penalty) with 4 reviewable (-8 credit). - const breakdown = explainQueueBurden(makeHealth({ openPullRequests: 4, likelyReviewablePullRequests: 4 })); - expect(breakdown.totalPenalty).toBe(24); - expect(breakdown.totalCredit).toBe(8); - expect(breakdown.rawBurden).toBe(16); - expect(breakdown.clamped).toBe(false); - const credit = componentByName(breakdown, "likelyReviewablePullRequests"); - expect(credit.contribution).toBe(-8); - expect(credit.band).toBe("credit"); - expect(credit.leverageScore).toBe(0); - expect(credit.summary).toMatch(/readily reviewable/i); - }); - - it("passes through repo identity, level, and generatedAt", () => { - const breakdown = explainQueueBurden( - makeHealth({ repoFullName: "acme/widgets", generatedAt: "2026-01-02T03:04:05.000Z", level: "high", burdenScore: 60, collisionClusters: 6 }), - ); - expect(breakdown.repoFullName).toBe("acme/widgets"); - expect(breakdown.generatedAt).toBe("2026-01-02T03:04:05.000Z"); - expect(breakdown.level).toBe("high"); - expect(breakdown.summary).toMatch(/queue burden is high/i); - }); - - it("never leaks private or reward terminology in any rendered string", () => { - const breakdown = explainQueueBurden( - makeHealth({ openPullRequests: 4, openIssues: 3, unlinkedPullRequests: 2, stalePullRequests: 2, over30Days: 1, collisionClusters: 2, likelyReviewablePullRequests: 1 }), - ); - for (const entry of breakdown.components) { - expect(entry.summary).not.toMatch(FORBIDDEN); - expect(entry.lever).not.toMatch(FORBIDDEN); - } - expect(breakdown.highestLeverageLever.reason).not.toMatch(FORBIDDEN); - expect(breakdown.summary).not.toMatch(FORBIDDEN); - }); - - it("recomposes the exact burdenScore the engine computes (weight drift guard)", () => { - const repo = { fullName: "owner/repo", isRegistered: true } as unknown as RepositoryRecord; - const fresh = new Date().toISOString(); - const pullRequests: PullRequestRecord[] = [ - // Linked + fresh → readily reviewable credit, not unlinked, not stale. - { repoFullName: "owner/repo", number: 1, title: "linked fresh", state: "open", labels: [], linkedIssues: [10], updatedAt: fresh }, - // Unlinked + aged → unlinked + stale + over-30. - { repoFullName: "owner/repo", number: 2, title: "aged unlinked", state: "open", labels: [], linkedIssues: [], updatedAt: "2020-01-01T00:00:00.000Z" }, - // Unlinked + aged draft → unlinked + stale + over-30. - { repoFullName: "owner/repo", number: 3, title: "aged draft", state: "open", labels: [], linkedIssues: [], updatedAt: "2020-01-01T00:00:00.000Z", isDraft: true }, - ]; - const issues = [{ repoFullName: "owner/repo", number: 10, title: "open issue", state: "open", labels: [], linkedPrs: [], body: null }]; - const collisions = { repoFullName: "owner/repo", summary: { clusterCount: 2, highRiskCount: 0 } } as unknown as CollisionReport; - - const health = buildQueueHealth(repo, issues, pullRequests, collisions); - const breakdown = explainQueueBurden(health); - - // openPRs 3×6 + openIssues 1×1 + unlinked 2×8 + stale 2×6 + over30 2×4 + clusters 2×10 − reviewable 1×2 = 73. - expect(health.burdenScore).toBe(73); - expect(breakdown.rawBurden).toBe(73); - expect(clamp(breakdown.rawBurden)).toBe(health.burdenScore); - expect(componentByName(breakdown, "unlinkedPullRequests").count).toBe(2); - expect(componentByName(breakdown, "likelyReviewablePullRequests").count).toBe(1); - }); -}); diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts index 0df1406fe7..ba7e9176d6 100644 --- a/test/unit/queue.test.ts +++ b/test/unit/queue.test.ts @@ -16885,7 +16885,6 @@ describe("queue processors", () => { checkRunMode: "off", checkRunDetailLevel: "minimal", backfillEnabled: true, - privateTrustEnabled: true, }); await processJob(env, { type: "github-webhook", @@ -16916,7 +16915,6 @@ describe("queue processors", () => { checkRunMode: "off", checkRunDetailLevel: "minimal", backfillEnabled: true, - privateTrustEnabled: true, }); await processJob(env, { type: "github-webhook", @@ -16964,7 +16962,6 @@ describe("queue processors", () => { checkRunMode: "off", checkRunDetailLevel: "minimal", backfillEnabled: true, - privateTrustEnabled: true, }); await processJob(env, { type: "github-webhook", @@ -17041,7 +17038,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, }); let postedBody = ""; @@ -17226,7 +17222,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, }); let postedBody = ""; @@ -17370,7 +17365,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, }); let postedBody = ""; @@ -17543,7 +17537,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, }); let postedBody = ""; @@ -17723,7 +17716,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, linkedIssueGateMode: "block", }); @@ -17888,7 +17880,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, autonomy: { update_branch: "auto" }, qualityGateMode: "advisory", qualityGateMinScore: 100, @@ -18290,7 +18281,6 @@ describe("queue processors", () => { checkRunDetailLevel: "minimal", gateCheckMode: "enabled", backfillEnabled: true, - privateTrustEnabled: true, }); // Seed a FAILED check summary with a per-check WHY (codecov-style) so listCheckSummaries returns it and the // unified site populates failingDetails. (The PR row + headSha must match for the check to associate.) diff --git a/test/unit/registration-readiness.test.ts b/test/unit/registration-readiness.test.ts index c19bf18d79..e30ed3eac5 100644 --- a/test/unit/registration-readiness.test.ts +++ b/test/unit/registration-readiness.test.ts @@ -62,7 +62,6 @@ function settingsFor(repoFullName: string, overrides: Partial = {}): RepositorySettin includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/repository-settings-enforcement.test.ts b/test/unit/repository-settings-enforcement.test.ts index 936c0ef895..b5508eea16 100644 --- a/test/unit/repository-settings-enforcement.test.ts +++ b/test/unit/repository-settings-enforcement.test.ts @@ -36,7 +36,6 @@ function settings(over: Partial = {}): RepositorySettings { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/review-labeling-rules.test.ts b/test/unit/review-labeling-rules.test.ts deleted file mode 100644 index 622ce2673e..0000000000 --- a/test/unit/review-labeling-rules.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { parseFocusManifest, reviewConfigToJson } from "../../src/signals/focus-manifest"; -import { resolveLabelingRules } from "../../src/review/labeling-rules"; - -const rulesOf = (labeling_rules: unknown) => parseFocusManifest({ review: { labeling_rules } }); -const facts = (over: Partial<{ changedPaths: string[]; title: string; description: string }> = {}) => ({ - changedPaths: [], - title: "", - description: "", - ...over, -}); - -describe("review.labeling_rules parse + round-trip (#2045)", () => { - it("absent ⇒ empty and OMITTED on serialize (byte-identical)", () => { - const review = parseFocusManifest({ review: { note: "x" } }).review; - expect(review.labelingRules).toEqual([]); - expect("labeling_rules" in (reviewConfigToJson(review) as Record)).toBe(false); - }); - - it("a full rule round-trips parse → serialize → parse identically", () => { - const review = rulesOf([ - { label: "area:docs", when_paths: ["docs/**"], title_contains: "doc", description_contains: "readme" }, - ]).review; - expect(review.labelingRules).toEqual([ - { label: "area:docs", whenPaths: ["docs/**"], titleContains: "doc", descriptionContains: "readme" }, - ]); - const json = reviewConfigToJson(review) as Record; - expect(json.labeling_rules).toEqual([ - { label: "area:docs", when_paths: ["docs/**"], title_contains: "doc", description_contains: "readme" }, - ]); - expect(parseFocusManifest({ review: json }).review.labelingRules).toEqual(review.labelingRules); - }); - - it("serializes only the criteria that are set (a path-only rule omits title/description keys)", () => { - const review = rulesOf([{ label: "area:ci", when_paths: [".github/**"] }]).review; - expect((reviewConfigToJson(review) as Record).labeling_rules).toEqual([ - { label: "area:ci", when_paths: [".github/**"] }, - ]); - }); - - it("refuses a reserved gittensor: label and warns", () => { - const m = rulesOf([{ label: "gittensor:feature", when_paths: ["src/**"] }]); - expect(m.review.labelingRules).toEqual([]); - expect(m.warnings.some((w) => /reserved "gittensor:" namespace/.test(w))).toBe(true); - }); - - it("drops a rule with no when-criterion, a rule with no label, and a non-mapping entry (each warns)", () => { - const m = rulesOf([{ label: "area:x" }, { when_paths: ["a/**"] }, "nope"]); - expect(m.review.labelingRules).toEqual([]); - expect(m.warnings.some((w) => /needs at least one of when_paths/.test(w))).toBe(true); - expect(m.warnings.some((w) => /\.label" is required/.test(w))).toBe(true); - expect(m.warnings.some((w) => /\[2\]" must be a mapping/.test(w))).toBe(true); - }); - - it("a present-but-invalid (non-string) label is dropped and warned by the text validator (not 'required')", () => { - const m = rulesOf([{ label: 123, when_paths: ["src/**"] }]); - expect(m.review.labelingRules).toEqual([]); - expect(m.warnings.some((w) => /labeling_rules\[0\]\.label/.test(w))).toBe(true); - }); - - it("a title-only rule round-trips with when_paths omitted", () => { - const review = rulesOf([{ label: "type:wip", title_contains: "WIP" }]).review; - expect(review.labelingRules).toEqual([ - { label: "type:wip", whenPaths: [], titleContains: "WIP", descriptionContains: null }, - ]); - expect((reviewConfigToJson(review) as Record).labeling_rules).toEqual([ - { label: "type:wip", title_contains: "WIP" }, - ]); - }); - - it("a non-list labeling_rules warns and is ignored", () => { - const m = rulesOf("nope"); - expect(m.review.labelingRules).toEqual([]); - expect(m.warnings.some((w) => /"review\.labeling_rules" must be a list/.test(w))).toBe(true); - }); - - it("caps at 50 rules", () => { - const many = Array.from({ length: 60 }, (_, i) => ({ label: `area:${i}`, when_paths: ["src/**"] })); - const m = rulesOf(many); - expect(m.review.labelingRules.length).toBe(50); - expect(m.warnings.some((w) => /capped at 50/.test(w))).toBe(true); - }); -}); - -describe("resolveLabelingRules deterministic evaluation (#2045)", () => { - const rules = [ - { label: "area:docs", whenPaths: ["docs/**"], titleContains: null, descriptionContains: null }, - { label: "type:wip", whenPaths: [], titleContains: "WIP", descriptionContains: null }, - { label: "needs:migration", whenPaths: ["migrations/**"], titleContains: null, descriptionContains: "schema" }, - ]; - - it("fires a path rule only when a changed path matches", () => { - expect(resolveLabelingRules({ rules, facts: facts({ changedPaths: ["docs/readme.md"] }), autoLabelEnabled: false }).suggest).toEqual(["area:docs"]); - expect(resolveLabelingRules({ rules, facts: facts({ changedPaths: ["src/a.ts"] }), autoLabelEnabled: false }).suggest).toEqual([]); - }); - - it("title match is case-insensitive; a multi-criterion rule needs ALL criteria", () => { - expect(resolveLabelingRules({ rules, facts: facts({ title: "wip: draft" }), autoLabelEnabled: false }).suggest).toEqual(["type:wip"]); - // needs:migration requires BOTH a migrations/** path AND "schema" in the description - expect(resolveLabelingRules({ rules, facts: facts({ changedPaths: ["migrations/001.sql"] }), autoLabelEnabled: false }).suggest).toEqual([]); - expect(resolveLabelingRules({ rules, facts: facts({ changedPaths: ["migrations/001.sql"], description: "adds a schema column" }), autoLabelEnabled: false }).suggest).toEqual(["needs:migration"]); - }); - - it("apply is empty unless autoLabelEnabled, then mirrors suggest", () => { - const f = facts({ changedPaths: ["docs/x.md"], title: "WIP" }); - expect(resolveLabelingRules({ rules, facts: f, autoLabelEnabled: false })).toEqual({ suggest: ["area:docs", "type:wip"], apply: [] }); - expect(resolveLabelingRules({ rules, facts: f, autoLabelEnabled: true })).toEqual({ suggest: ["area:docs", "type:wip"], apply: ["area:docs", "type:wip"] }); - }); - - it("dedupes a label shared by two firing rules, preserving first-seen order", () => { - const dup = [ - { label: "area:x", whenPaths: ["a/**"], titleContains: null, descriptionContains: null }, - { label: "area:x", whenPaths: ["b/**"], titleContains: null, descriptionContains: null }, - ]; - expect(resolveLabelingRules({ rules: dup, facts: facts({ changedPaths: ["a/1", "b/2"] }), autoLabelEnabled: true }).suggest).toEqual(["area:x"]); - }); -}); diff --git a/test/unit/self-dogfood-registration-pack.test.ts b/test/unit/self-dogfood-registration-pack.test.ts index f41c998963..f666afc1bb 100644 --- a/test/unit/self-dogfood-registration-pack.test.ts +++ b/test/unit/self-dogfood-registration-pack.test.ts @@ -75,7 +75,6 @@ function settingsFor(repoFullName: string, overrides: Partial): string[] { - return entries.map((entry) => entry.field).sort(); -} - -describe("computeSettingsDrift (#config-drift-audit)", () => { - it("reports no drift for an empty manifest", () => { - const dbSettings = { gittensorLabel: "gittensor", qualityGateMinScore: 50 } as RepositorySettings; - expect(computeSettingsDrift(dbSettings, parseFocusManifest(null))).toEqual([]); - }); - - it("reports a settings: override that differs from the DB value", () => { - const dbSettings = { gittensorLabel: "gittensor" } as RepositorySettings; - const manifest = parseFocusManifest({ settings: { gittensorLabel: "custom-label" } }); - const drift = computeSettingsDrift(dbSettings, manifest); - expect(drift).toEqual([{ field: "gittensorLabel", dbValue: "gittensor", effectiveValue: "custom-label" }]); - }); - - it("does NOT report drift when the manifest sets the SAME value already in the DB", () => { - const dbSettings = { gittensorLabel: "gittensor" } as RepositorySettings; - const manifest = parseFocusManifest({ settings: { gittensorLabel: "gittensor" } }); - expect(computeSettingsDrift(dbSettings, manifest)).toEqual([]); - }); - - it("reports a gate: override the same way as an equivalent settings: override", () => { - const dbSettings = { gateCheckMode: "off" } as RepositorySettings; - const manifest = parseFocusManifest({ gate: { enabled: true } }); - const drift = computeSettingsDrift(dbSettings, manifest); - expect(drift).toEqual([{ field: "gateCheckMode", dbValue: "off", effectiveValue: "enabled" }]); - }); - - it("detects array-valued drift (hardGuardrailGlobs) by content, not by reference", () => { - const dbSettings = { hardGuardrailGlobs: ["src/scoring/**"] } as RepositorySettings; - const sameContent = parseFocusManifest({ settings: { hardGuardrailGlobs: ["src/scoring/**"] } }); - expect(computeSettingsDrift(dbSettings, sameContent)).toEqual([]); - - const different = parseFocusManifest({ settings: { hardGuardrailGlobs: ["src/settings/**"] } }); - const drift = computeSettingsDrift(dbSettings, different); - expect(drift).toEqual([{ field: "hardGuardrailGlobs", dbValue: ["src/scoring/**"], effectiveValue: ["src/settings/**"] }]); - }); - - it("detects object-valued drift (typeLabels) from a sparse per-category manifest override", () => { - const dbSettings = { typeLabels: { bug: "bug", feature: "enhancement" } } as unknown as RepositorySettings; - const manifest = parseFocusManifest({ settings: { typeLabels: { bug: "defect" } } }); - const drift = computeSettingsDrift(dbSettings, manifest); - expect(drift).toEqual([ - { field: "typeLabels", dbValue: { bug: "bug", feature: "enhancement" }, effectiveValue: { bug: "defect", feature: "enhancement" } }, - ]); - }); - - it("does not report contributorBlacklist drift from the shared/global blacklist merge alone (manifest-independent normalization, not manifest shadowing)", () => { - const dbSettings = { contributorBlacklist: [] } as unknown as RepositorySettings; - // No manifest override at all, but a non-empty shared/global blacklist -- resolveEffectiveSettings ALWAYS - // merges this in regardless of manifest presence, so it must not be misreported as manifest-driven drift. - const drift = computeSettingsDrift(dbSettings, parseFocusManifest(null), [{ login: "GlobalBad", reason: "global" }]); - expect(drift.some((entry) => entry.field === "contributorBlacklist")).toBe(false); - }); - - it("still reports a MANIFEST-driven contributorBlacklist entry on top of an unrelated shared blacklist", () => { - const dbSettings = { contributorBlacklist: [] } as unknown as RepositorySettings; - const manifest = parseFocusManifest({ settings: { contributorBlacklist: [{ login: "ManifestBad" }] } }); - const drift = computeSettingsDrift(dbSettings, manifest, [{ login: "GlobalBad", reason: "global" }]); - const entry = drift.find((e) => e.field === "contributorBlacklist"); - expect(entry?.effectiveValue).toEqual(expect.arrayContaining([expect.objectContaining({ login: "ManifestBad" }), expect.objectContaining({ login: "GlobalBad" })])); - }); - - it("does not report drift for a DB field the manifest never touches, even when other fields drift", () => { - const dbSettings = { gittensorLabel: "gittensor", checkRunMode: "enabled" } as RepositorySettings; - const manifest = parseFocusManifest({ settings: { gittensorLabel: "custom-label" } }); - expect(fields(computeSettingsDrift(dbSettings, manifest))).toEqual(["gittensorLabel"]); - }); -}); - -describe("computeSettingsDriftForRepo — live DB + manifest fetch (#config-drift-audit)", () => { - it("reports no drift for a repo with DB settings only, no manifest", async () => { - const env = createTestEnv(); - const repo = "acme/no-manifest"; - await env.DB.prepare("INSERT INTO repositories (full_name, owner, name, is_installed, is_registered) VALUES (?, 'acme', 'no-manifest', 1, 1)").bind(repo).run(); - await repositories.upsertRepositorySettings(env, { repoFullName: repo, gittensorLabel: "gittensor" }); - expect(await computeSettingsDriftForRepo(env, repo)).toEqual([]); - }); - - it("reports drift for a repo whose manifest shadows a DB-stored field", async () => { - const env = createTestEnv(); - const repo = "acme/shadowed"; - await env.DB.prepare("INSERT INTO repositories (full_name, owner, name, is_installed, is_registered) VALUES (?, 'acme', 'shadowed', 1, 1)").bind(repo).run(); - await Promise.all([ - repositories.upsertRepositorySettings(env, { repoFullName: repo, gittensorLabel: "gittensor" }), - upsertRepoFocusManifest(env, repo, { settings: { gittensorLabel: "manifest-label" } }, "api_record"), - ]); - const drift = await computeSettingsDriftForRepo(env, repo); - expect(drift).toEqual([{ field: "gittensorLabel", dbValue: "gittensor", effectiveValue: "manifest-label" }]); - }); - - it("falls back to an empty shared blacklist (never throws) when the global blacklist read rejects", async () => { - const env = createTestEnv(); - const repo = "acme/blacklist-read-fails"; - await env.DB.prepare("INSERT INTO repositories (full_name, owner, name, is_installed, is_registered) VALUES (?, 'acme', 'blacklist-read-fails', 1, 1)").bind(repo).run(); - await repositories.upsertRepositorySettings(env, { repoFullName: repo, gittensorLabel: "gittensor" }); - const getGlobalSpy = vi.spyOn(repositories, "getGlobalContributorBlacklist").mockRejectedValue(new Error("transient DB issue")); - try { - await expect(computeSettingsDriftForRepo(env, repo)).resolves.toEqual([]); - } finally { - getGlobalSpy.mockRestore(); - } - }); -}); diff --git a/test/unit/settings-preview.test.ts b/test/unit/settings-preview.test.ts index 03cccc58fd..bbb0997970 100644 --- a/test/unit/settings-preview.test.ts +++ b/test/unit/settings-preview.test.ts @@ -60,7 +60,6 @@ function settings(overrides: Partial = {}): RepositorySettin includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts index 961557653d..b1b70e7bc1 100644 --- a/test/unit/signals-coverage.test.ts +++ b/test/unit/signals-coverage.test.ts @@ -2216,7 +2216,6 @@ function repoSettings(repoFullName: string): RepositorySettings { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/signals-v2.test.ts b/test/unit/signals-v2.test.ts index b01ed8b1b3..a5d685d871 100644 --- a/test/unit/signals-v2.test.ts +++ b/test/unit/signals-v2.test.ts @@ -2105,7 +2105,6 @@ describe("v2 signal builders", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/signals.test.ts b/test/unit/signals.test.ts index 1fbe1af33d..da94eb2702 100644 --- a/test/unit/signals.test.ts +++ b/test/unit/signals.test.ts @@ -523,7 +523,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off" as const, aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, @@ -578,7 +577,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off" as const, aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, @@ -653,7 +651,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off" as const, aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, @@ -782,7 +779,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off" as const, aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, @@ -853,7 +849,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off" as const, aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, @@ -968,7 +963,6 @@ describe("world-class backend signals", () => { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false, diff --git a/test/unit/unified-comment-parity.test.ts b/test/unit/unified-comment-parity.test.ts index f0df9e59da..b9040f7a86 100644 --- a/test/unit/unified-comment-parity.test.ts +++ b/test/unit/unified-comment-parity.test.ts @@ -72,7 +72,6 @@ const settings: RepositorySettings = { includeMaintainerAuthors: false, requireLinkedIssue: false, backfillEnabled: true, - privateTrustEnabled: true, aiReviewMode: "off", aiReviewByok: false, aiReviewAllAuthors: false, closeOwnerAuthors: false,