= {
+ ready: "ready",
+ needs_attention: "attention",
+ blocked: "blocked",
+};
+
+function TrustChecklist({ installPreview }: { installPreview: InstallPreview }) {
+ const attentionItems = installPreview.checklist.filter((item) => item.status !== "ready");
+ const detailGroups = [
+ {
+ title: "Scope",
+ items: [
+ ...installPreview.readScope,
+ ...installPreview.computedContext,
+ ...installPreview.previewBehavior,
+ ],
+ },
+ {
+ title: "Public boundary",
+ items: [
+ ...installPreview.publicOutputs,
+ ...installPreview.privateOnlyContext,
+ ...installPreview.sanitizerBoundaries,
+ ],
+ },
+ {
+ title: "Controls",
+ items: [
+ ...installPreview.commandAuthorization,
+ ...installPreview.auditBehavior,
+ ...installPreview.manualControls,
+ ],
+ },
+ ];
+
+ return (
+
+
+
+
+
+ Maintainer trust checklist
+
+
{installPreview.summary}
+
+
+ {TRUST_STATUS_LABEL[installPreview.status]}
+
+
+
+
+ {installPreview.checklist.map((item) => (
+
+
+
+ {item.label}
+
+
+
+ {TRUST_STATUS_LABEL[item.status]}
+
+
+ ))}
+
+
+ {attentionItems.length > 0 ? (
+
+
Review before enabling
+
+ {attentionItems.map((item) => (
+ -
+ {item.label}:{" "}
+ {item.action}
+
+ ))}
+
+
+ ) : null}
+
+
+
+ Preview scope and controls
+ details
+
+
+ {detailGroups.map((group) => (
+
+ ))}
+
+
+
+ );
+}
+
+function TrustDetailList({ title, items }: { title: string; items: string[] }) {
+ return (
+
+
+ {title}
+
+
+ {items.map((item) => (
+ -
+
+ {item}
+
+ ))}
+
+
+ );
+}
+
+function TrustStatusIcon({ status }: { status: TrustChecklistStatus }) {
+ if (status === "blocked") return ;
+ if (status === "needs_attention")
+ return ;
+ return ;
+}
+
function ActionState({ active, label }: { active: boolean; label: string }) {
return (
;
};
+export type RepoInstallPreviewStatus = "ready" | "needs_attention" | "blocked";
+
+export type RepoInstallPreviewChecklistItem = {
+ id: string;
+ category: "permissions" | "public_outputs" | "private_context" | "command_authorization" | "audit" | "sanitizer" | "manual_control";
+ status: RepoInstallPreviewStatus;
+ label: string;
+ summary: string;
+ action: string;
+};
+
+export type RepoInstallPreview = {
+ status: RepoInstallPreviewStatus;
+ summary: string;
+ readScope: string[];
+ computedContext: string[];
+ previewBehavior: string[];
+ permissions: {
+ status: RepoInstallPreviewStatus;
+ required: string[];
+ missing: string[];
+ missingEvents: string[];
+ summary: string;
+ };
+ publicOutputs: string[];
+ privateOnlyContext: string[];
+ commandAuthorization: string[];
+ auditBehavior: string[];
+ sanitizerBoundaries: string[];
+ manualControls: string[];
+ checklist: RepoInstallPreviewChecklistItem[];
+};
+
export type RepoSettingsPreview = {
repoFullName: string;
generatedAt: string;
@@ -151,6 +184,7 @@ export type RepoSettingsPreview = {
previewComment: string | null;
appliedLabel: string | null;
checkRun: { willCreate: boolean; title: string; detailLevel: RepositorySettings["checkRunDetailLevel"] } | null;
+ installPreview: RepoInstallPreview;
warnings: string[];
summary: string;
};
@@ -192,6 +226,13 @@ export function buildRepoSettingsPreview(args: {
: null;
const warnings = buildWarnings(settings, decision, args.installation);
+ const installPreview = buildRepoInstallPreview({
+ repo,
+ settings,
+ installation: args.installation,
+ decision,
+ appliedLabel: decision.willLabel ? settings.gittensorLabel : null,
+ });
return {
repoFullName,
@@ -214,6 +255,7 @@ export function buildRepoSettingsPreview(args: {
previewComment,
appliedLabel: decision.willLabel ? settings.gittensorLabel : null,
checkRun: decision.willCheckRun ? { willCreate: true, title: "Gittensory context posted", detailLevel: settings.checkRunDetailLevel } : null,
+ installPreview,
warnings,
summary: decision.skipped
? `Sample PR would be skipped: ${decision.summary}`
@@ -243,6 +285,175 @@ function buildWarnings(settings: RepositorySettings, decision: PublicSurfaceDeci
return warnings;
}
+function buildRepoInstallPreview(args: {
+ repo: RepositoryRecord | null;
+ settings: RepositorySettings;
+ installation: InstallationHealthSummary | null;
+ decision: PublicSurfaceDecision;
+ appliedLabel: string | null;
+}): RepoInstallPreview {
+ const required = requiredInstallPermissions(args.settings, args.decision);
+ const missing = activeMissingPermissions(args.settings, args.decision, args.installation);
+ const missingEvents = args.installation?.missingEvents ?? [];
+ const permissionStatus: RepoInstallPreviewStatus = !args.installation || args.installation.status === "broken" ? "blocked" : missing.length > 0 || missingEvents.length > 0 || args.installation.status === "needs_attention" ? "needs_attention" : "ready";
+ const publicOutputStatus: RepoInstallPreviewStatus = args.settings.commentMode === "all_prs" ? "needs_attention" : "ready";
+ const commandAuthorizationStatus: RepoInstallPreviewStatus = !args.installation ? "blocked" : new Set(args.installation.missingPermissions).has("issues") ? "needs_attention" : "ready";
+ const manualControlStatus: RepoInstallPreviewStatus = args.settings.commentMode === "all_prs" ? "needs_attention" : "ready";
+ const checklist: RepoInstallPreviewChecklistItem[] = [
+ {
+ id: "permissions",
+ category: "permissions",
+ status: permissionStatus,
+ label: "Permissions and events",
+ summary: permissionSummary(args.installation, missing, missingEvents),
+ action: permissionStatus === "ready" ? "No permission change is needed for this previewed behavior." : "Refresh installation health, then approve the missing permission or webhook event before enabling public output.",
+ },
+ {
+ id: "public-outputs",
+ category: "public_outputs",
+ status: publicOutputStatus,
+ label: "Public outputs",
+ summary: publicOutputSummary(args.decision),
+ action: publicOutputStatus === "ready" ? "Review the rendered public preview before enabling this repo." : "Review all-PR mode carefully; confirmed-miner-only output is quieter for first enablement.",
+ },
+ {
+ id: "private-context",
+ category: "private_context",
+ status: "ready",
+ label: "Private-only context",
+ summary: "Decision packs, blocker detail, maintainer packet evidence, and scoring evidence stay on authenticated API or MCP surfaces.",
+ action: "Keep private evidence out of public issue bodies, PR bodies, comments, and copied snippets.",
+ },
+ {
+ id: "command-authorization",
+ category: "command_authorization",
+ status: commandAuthorizationStatus,
+ label: "Command authorization",
+ summary: "Public command responses require a maintainer or confirmed PR author; maintainer queue commands require owner, member, or collaborator context.",
+ action: commandAuthorizationStatus === "ready" ? "Use command previews to confirm actor and permission behavior before relying on repo commands." : "Restore Issues: write before enabling public command responses.",
+ },
+ {
+ id: "audit-behavior",
+ category: "audit",
+ status: "ready",
+ label: "Audit behavior",
+ summary: "This preview is read-only; live webhook skips, command handling, auth, and usage paths are recorded through audit or product-usage logs.",
+ action: "Use preview output for review; use live audit records for production behavior after enablement.",
+ },
+ {
+ id: "sanitizer-boundaries",
+ category: "sanitizer",
+ status: "ready",
+ label: "Sanitizer boundaries",
+ summary: "Public comments and copied snippets are sanitized before they leave the Worker.",
+ action: "Private evidence remains authenticated-only and should not be copied into public surfaces.",
+ },
+ {
+ id: "manual-controls",
+ category: "manual_control",
+ status: manualControlStatus,
+ label: "Manual controls",
+ summary: "Public surface mode, comments, labels, check runs, maintainer-author inclusion, and linked-issue requirements remain repo-controlled settings.",
+ action: manualControlStatus === "ready" ? "Enable only the specific public surface you want after previewing it." : "Switch away from all-PR mode unless broad public output is intentional.",
+ },
+ ];
+ const status = checklist.some((item) => item.status === "blocked") ? "blocked" : checklist.some((item) => item.status === "needs_attention") ? "needs_attention" : "ready";
+
+ return {
+ status,
+ summary:
+ status === "ready"
+ ? "Install preview is ready for maintainer review before enabling repo commands."
+ : status === "blocked"
+ ? "Install preview has a blocking setup gap before repo commands should be enabled."
+ : "Install preview is usable, but one or more setup details need maintainer attention.",
+ readScope: [
+ "Cached repository metadata, issues, pull requests, labels, linked issues, repo settings, and installation health.",
+ args.repo?.isInstalled ? "GitHub App installation metadata for the selected repository." : "No GitHub App installation metadata is linked to this repository yet.",
+ ],
+ computedContext: [
+ "Public surface decision for comment, label, check-run, or skip behavior.",
+ "Queue, collision, contributor profile, and preflight context for the sample public preview.",
+ ],
+ previewBehavior: [
+ "The dry-run preview does not create GitHub comments, labels, check runs, or installation changes.",
+ "A public comment body is rendered only when current settings would comment for the sample PR.",
+ ],
+ permissions: {
+ status: permissionStatus,
+ required,
+ missing,
+ missingEvents,
+ summary: permissionSummary(args.installation, missing, missingEvents),
+ },
+ publicOutputs: publicOutputsFor(args.decision, args.appliedLabel),
+ privateOnlyContext: [
+ "Decision packs, blocker details, maintainer packet evidence, and scoring evidence stay authenticated-only.",
+ "This preview uses cached metadata and the supplied sample PR fields; it does not upload repository source.",
+ ],
+ commandAuthorization: [
+ "Maintainer-only commands require owner, member, or collaborator context.",
+ "Contributor-invoked public commands require the commenter to be the confirmed PR author.",
+ "Private API commands require authenticated control-panel access and do not post public GitHub output.",
+ ],
+ auditBehavior: [
+ "This preview is read-only and does not mutate GitHub.",
+ "Live webhook skips, command handling, miner-detection fallbacks, auth, and usage paths are audit or product-usage logged.",
+ ],
+ sanitizerBoundaries: [
+ "Public GitHub comments and copied snippets are sanitized before posting.",
+ "Credential/key material, compensation estimates, trust metrics, score-prediction claims, private review evidence, private scoring evidence, and gaming language stay out of public output.",
+ "Private evidence is not copied into public comments, issue bodies, PR bodies, or extension public panels.",
+ ],
+ manualControls: [
+ "Public surface mode, comment mode, label name, check-run mode, maintainer-author inclusion, and linked-issue requirements remain repo settings.",
+ "Maintainers preview first, then enable the specific public output they want.",
+ ],
+ checklist,
+ };
+}
+
+function requiredInstallPermissions(settings: RepositorySettings, decision: PublicSurfaceDecision): string[] {
+ const permissions = new Set(["metadata: read", "pull_requests: read"]);
+ if (decision.willComment || decision.willLabel || shouldPublishPrComment(settings) || shouldApplyPrLabel(settings)) permissions.add("issues: write");
+ if (decision.willCheckRun || settings.checkRunMode === "enabled") permissions.add("checks: write");
+ return [...permissions];
+}
+
+function activeMissingPermissions(settings: RepositorySettings, decision: PublicSurfaceDecision, installation: InstallationHealthSummary | null): string[] {
+ if (!installation) return [];
+ const missing = new Set(installation.missingPermissions);
+ const active: string[] = [];
+ if ((decision.willComment || decision.willLabel || shouldPublishPrComment(settings) || shouldApplyPrLabel(settings)) && missing.has("issues")) active.push("issues");
+ if ((decision.willCheckRun || settings.checkRunMode === "enabled") && missing.has("checks")) active.push("checks");
+ return active;
+}
+
+function permissionSummary(installation: InstallationHealthSummary | null, missing: string[], missingEvents: string[]): string {
+ if (!installation) return "No installation health is cached for this repository.";
+ if (installation.status === "broken") return "Installation health is broken and needs recovery before enablement.";
+ if (missing.length > 0 || missingEvents.length > 0) {
+ return `Installation needs attention: ${[missing.length > 0 ? `missing permission(s) ${missing.join(", ")}` : "", missingEvents.length > 0 ? `missing webhook event(s) ${missingEvents.join(", ")}` : ""].filter(Boolean).join("; ")}.`;
+ }
+ if (installation.status === "needs_attention") return "Installation health needs attention; review remediation before enabling public output.";
+ return "Required permissions and webhook events are ready for the previewed behavior.";
+}
+
+function publicOutputsFor(decision: PublicSurfaceDecision, appliedLabel: string | null): string[] {
+ if (decision.skipped) return [`No public output for this sample: ${decision.summary}`];
+ const outputs = [
+ ...(decision.willComment ? ["One sanitized sticky PR comment."] : []),
+ ...(decision.willLabel ? [`Configured label "${appliedLabel ?? "gittensor"}".`] : []),
+ ...(decision.willCheckRun ? ["Minimal GitHub check run."] : []),
+ ];
+ return outputs.length > 0 ? outputs : ["No public comment, label, or check run for this sample."];
+}
+
+function publicOutputSummary(decision: PublicSurfaceDecision): string {
+ if (decision.skipped) return `Current sample is skipped: ${decision.summary}`;
+ return decision.actions.includes("none") ? "The sample qualifies, but no public output action is enabled." : `Current sample would create: ${decision.actions.join(", ")}.`;
+}
+
function buildSamplePreviewComment(args: {
repoFullName: string;
repo: RepositoryRecord | null;
diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts
index aaad3ca6ba..4a1b8c20cd 100644
--- a/test/integration/api.test.ts
+++ b/test/integration/api.test.ts
@@ -481,11 +481,29 @@ describe("api routes", () => {
env,
);
expect(minerPreview.status).toBe(200);
- const minerPreviewBody = (await minerPreview.json()) as { decision: { willComment: boolean; skipped: boolean }; previewComment: string | null; settings: { publicSurface: string } };
+ const minerPreviewBody = (await minerPreview.json()) as {
+ decision: { willComment: boolean; skipped: boolean };
+ previewComment: string | null;
+ settings: { publicSurface: string };
+ installPreview: {
+ status: string;
+ permissions: { required: string[]; status: string };
+ checklist: Array<{ id: string; status: string; summary: string; action: string }>;
+ };
+ };
expect(minerPreviewBody.decision.skipped).toBe(false);
expect(minerPreviewBody.decision.willComment).toBe(true);
expect(minerPreviewBody.previewComment).toContain("Gittensory contribution context");
expect(minerPreviewBody.previewComment).not.toMatch(/wallet|hotkey|trust score|scoreability|payout/i);
+ expect(minerPreviewBody.installPreview).toMatchObject({
+ status: "ready",
+ permissions: { required: expect.arrayContaining(["issues: write"]) },
+ checklist: expect.arrayContaining([
+ expect.objectContaining({ id: "permissions", status: "ready" }),
+ expect.objectContaining({ id: "sanitizer-boundaries", status: "ready" }),
+ expect.objectContaining({ id: "manual-controls", status: "ready" }),
+ ]),
+ });
const invalidPreview = await app.request(
"/v1/repos/entrius/allways-ui/settings-preview",
@@ -498,6 +516,7 @@ describe("api routes", () => {
expect(unknownRepoPreview.status).toBe(200);
await expect(unknownRepoPreview.json()).resolves.toMatchObject({
installation: null,
+ installPreview: { status: "blocked", permissions: { status: "blocked" } },
sample: { authorLogin: "sample-contributor", minerStatus: "confirmed" },
});
diff --git a/test/unit/repo-policy-readiness.test.ts b/test/unit/repo-policy-readiness.test.ts
index 87b0b24b0c..8bbddf0fc7 100644
--- a/test/unit/repo-policy-readiness.test.ts
+++ b/test/unit/repo-policy-readiness.test.ts
@@ -134,6 +134,14 @@ function input(overrides: Partial = {}): RepoPolicyRea
}
describe("buildRepoPolicyReadiness", () => {
+ it("summarizes a clean policy when no warnings are present", () => {
+ const report = buildRepoPolicyReadiness(input());
+
+ expect(report.publicWarnings).toEqual([]);
+ expect(report.droppedPublicWarnings).toEqual([]);
+ expect(report.summary).toBe("Policy readiness has no public-safe warnings for owner review.");
+ });
+
it("warns when direct-PR policy is loose", () => {
const report = buildRepoPolicyReadiness(
input({
@@ -158,6 +166,32 @@ describe("buildRepoPolicyReadiness", () => {
expect(report.publicWarnings.flatMap((warning) => [warning.title, warning.detail, warning.action]).every(isFocusManifestPublicSafe)).toBe(true);
});
+ it("warns when contribution scope only defines blocked work", () => {
+ const report = buildRepoPolicyReadiness(
+ input({
+ settings: settings({ requireLinkedIssue: true }),
+ focusManifest: parseFocusManifest({
+ blockedPaths: ["docs/"],
+ linkedIssuePolicy: "optional",
+ testExpectations: ["Run npm run test:ci."],
+ }),
+ }),
+ );
+
+ expect(report.ownerContext).toMatchObject({
+ wantedPathCount: 0,
+ blockedPathCount: 1,
+ issuePolicy: "direct_pr_requires_linked_issue",
+ });
+ expect(report.publicWarnings.map((warning) => warning.code)).toEqual(
+ expect.arrayContaining([
+ "contribution_scope_unclear",
+ "blocked_work_without_wanted_scope",
+ "linked_issue_policy_mismatch",
+ ]),
+ );
+ });
+
it("warns about issue-discovery policy conflicts and intake gaps", () => {
const report = buildRepoPolicyReadiness(
input({
@@ -181,6 +215,31 @@ describe("buildRepoPolicyReadiness", () => {
);
});
+ it("marks blocked issue-discovery intake as critical", () => {
+ const report = buildRepoPolicyReadiness(
+ input({
+ lane: lane({ lane: "issue_discovery", issueDiscoveryShare: 1, directPrShare: 0 }),
+ focusManifest: parseFocusManifest({
+ wantedPaths: ["src/"],
+ linkedIssuePolicy: "required",
+ issueDiscoveryPolicy: "neutral",
+ testExpectations: ["Run npm run test:ci."],
+ }),
+ contributorIntakeHealth: intake({ level: "blocked" }),
+ }),
+ );
+
+ expect(report.ownerContext.issuePolicy).toBe("issue_discovery_enabled");
+ expect(report.publicWarnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ code: "issue_discovery_intake_not_ready",
+ severity: "critical",
+ }),
+ ]),
+ );
+ });
+
it("warns when a direct-PR repo has issue-discovery encouraged in focus policy", () => {
const report = buildRepoPolicyReadiness(
input({
@@ -325,9 +384,10 @@ describe("buildRepoPolicyReadiness", () => {
);
});
- it("handles absent optional policy data and clean policy summaries", () => {
- const missing = buildRepoPolicyReadiness(input({ focusManifest: undefined }));
- expect(missing).toMatchObject({
+ it("uses empty owner-context defaults when the focus manifest is omitted", () => {
+ const report = buildRepoPolicyReadiness(input({ focusManifest: undefined }));
+
+ expect(report).toMatchObject({
present: false,
ownerContext: {
manifestPresent: false,
@@ -340,10 +400,10 @@ describe("buildRepoPolicyReadiness", () => {
issueDiscoveryPolicy: "neutral",
},
});
-
- const clean = buildRepoPolicyReadiness(input());
- expect(clean.publicWarnings).toEqual([]);
- expect(clean.droppedPublicWarnings).toEqual([]);
- expect(clean.summary).toBe("Policy readiness has no public-safe warnings for owner review.");
+ expect(report.publicWarnings).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ code: "focus_policy_missing" }),
+ ]),
+ );
});
});
diff --git a/test/unit/settings-preview.test.ts b/test/unit/settings-preview.test.ts
index e39899906d..c61332be3d 100644
--- a/test/unit/settings-preview.test.ts
+++ b/test/unit/settings-preview.test.ts
@@ -2,6 +2,9 @@ import { describe, expect, it } from "vitest";
import { buildRepoSettingsPreview, decidePublicSurface, type InstallationHealthSummary } from "../../src/signals/settings-preview";
import type { IssueRecord, PullRequestRecord, RepositoryRecord, RepositorySettings } from "../../src/types";
+const FORBIDDEN_INSTALL_PREVIEW_PUBLIC_LANGUAGE =
+ /\b(wallet|hotkey|coldkey|raw[-\s]?trust|trust[-\s]?score|reward[-\s]?estimate|payout|farming(?:[-\s]?language)?|private[-\s]?reviewability|private[-\s]?scoreability|scoreability|public[-\s]?score[-\s]?(?:estimate|prediction)|estimated[-\s]?score|score[-\s]?estimate)\b/i;
+
const repo: RepositoryRecord = {
fullName: "entrius/allways-ui",
owner: "entrius",
@@ -101,6 +104,34 @@ describe("buildRepoSettingsPreview", () => {
expect(preview.appliedLabel).toBe("gittensor");
expect(preview.previewComment).toContain("Gittensory contribution context");
expect(preview.warnings).toHaveLength(0);
+ expect(preview.installPreview).toMatchObject({
+ status: "ready",
+ permissions: { status: "ready", required: expect.arrayContaining(["metadata: read", "pull_requests: read", "issues: write"]) },
+ publicOutputs: expect.arrayContaining(["One sanitized sticky PR comment.", 'Configured label "gittensor".']),
+ checklist: expect.arrayContaining([
+ expect.objectContaining({ id: "permissions", status: "ready" }),
+ expect.objectContaining({ id: "public-outputs", status: "ready" }),
+ expect.objectContaining({ id: "private-context", status: "ready" }),
+ expect.objectContaining({ id: "command-authorization", status: "ready" }),
+ expect.objectContaining({ id: "audit-behavior", status: "ready" }),
+ expect.objectContaining({ id: "sanitizer-boundaries", status: "ready" }),
+ expect.objectContaining({ id: "manual-controls", status: "ready" }),
+ ]),
+ });
+ expect(preview.installPreview.readScope.join(" ")).toMatch(/repository metadata/i);
+ expect(preview.installPreview.computedContext.join(" ")).toMatch(/Public surface decision/i);
+ expect(preview.installPreview.privateOnlyContext.join(" ")).toMatch(/authenticated-only/i);
+ expect(preview.installPreview.commandAuthorization.join(" ")).toMatch(/Maintainer-only commands/i);
+ expect(preview.installPreview.auditBehavior.join(" ")).toMatch(/read-only/i);
+ expect(preview.installPreview.manualControls.join(" ")).toMatch(/repo settings/i);
+ expect(
+ JSON.stringify([
+ preview.installPreview.publicOutputs,
+ preview.installPreview.sanitizerBoundaries,
+ preview.installPreview.manualControls,
+ preview.installPreview.checklist.map((item) => [item.summary, item.action]),
+ ]),
+ ).not.toMatch(FORBIDDEN_INSTALL_PREVIEW_PUBLIC_LANGUAGE);
});
it("uses safe defaults for an empty sample preview", () => {
@@ -117,6 +148,12 @@ describe("buildRepoSettingsPreview", () => {
sample: { authorLogin: "miner", minerStatus: "confirmed" },
});
expect(preview.warnings.some((warning) => /Issues: write/.test(warning))).toBe(true);
+ expect(preview.installPreview.status).toBe("needs_attention");
+ expect(preview.installPreview.permissions).toMatchObject({ status: "needs_attention", missing: ["issues"] });
+ expect(preview.installPreview.checklist.find((item) => item.id === "permissions")).toMatchObject({
+ status: "needs_attention",
+ action: expect.stringContaining("approve the missing permission"),
+ });
});
it("explains a missing optional Checks: write permission only when check runs are enabled", () => {
@@ -168,6 +205,11 @@ describe("buildRepoSettingsPreview", () => {
it("warns when installation health is unknown", () => {
const preview = buildRepoSettingsPreview({ ...base, settings: settings(), installation: null, sample: { authorLogin: "miner", minerStatus: "confirmed" } });
expect(preview.warnings.some((warning) => /Installation health is unknown/.test(warning))).toBe(true);
+ expect(preview.installPreview).toMatchObject({
+ status: "blocked",
+ permissions: { status: "blocked", required: expect.arrayContaining(["issues: write"]), missing: [], missingEvents: [] },
+ });
+ expect(preview.installPreview.checklist.find((item) => item.id === "permissions")).toMatchObject({ status: "blocked" });
});
it("explains missing webhook event subscriptions", () => {
@@ -178,6 +220,7 @@ describe("buildRepoSettingsPreview", () => {
sample: { authorLogin: "miner", minerStatus: "confirmed" },
});
expect(preview.warnings).toEqual(expect.arrayContaining([expect.stringMatching(/pull_request webhook event/)]));
+ expect(preview.installPreview.permissions).toMatchObject({ status: "needs_attention", missingEvents: ["pull_request"] });
});
it("falls back to the installation status warning when no specific remediation is available", () => {
@@ -188,6 +231,26 @@ describe("buildRepoSettingsPreview", () => {
sample: { authorLogin: "miner", minerStatus: "confirmed" },
});
expect(preview.warnings).toEqual(["Installation status is broken; review the installation health endpoint for remediation steps."]);
+ expect(preview.installPreview.status).toBe("blocked");
+ expect(preview.installPreview.permissions.summary).toMatch(/broken/i);
+ });
+
+ it("marks broad all-PR output as needing maintainer attention before enablement", () => {
+ const preview = buildRepoSettingsPreview({
+ ...base,
+ settings: settings({ commentMode: "all_prs" }),
+ installation: healthyInstall,
+ sample: { authorLogin: "miner", minerStatus: "confirmed" },
+ });
+ expect(preview.installPreview.status).toBe("needs_attention");
+ expect(preview.installPreview.checklist.find((item) => item.id === "public-outputs")).toMatchObject({
+ status: "needs_attention",
+ action: expect.stringContaining("confirmed-miner-only"),
+ });
+ expect(preview.installPreview.checklist.find((item) => item.id === "manual-controls")).toMatchObject({
+ status: "needs_attention",
+ action: expect.stringContaining("all-PR mode"),
+ });
});
it("never leaks private scoring/trust terms into the preview comment (sanitizer regression)", () => {