diff --git a/apps/gittensory-ui/public/openapi.json b/apps/gittensory-ui/public/openapi.json index 5b2fc50a..f7a7e78d 100644 --- a/apps/gittensory-ui/public/openapi.json +++ b/apps/gittensory-ui/public/openapi.json @@ -3385,6 +3385,13 @@ "all_prs" ] }, + "publicAudienceMode": { + "type": "string", + "enum": [ + "oss_maintainer", + "gittensor_only" + ] + }, "checkRunMode": { "type": "string", "enum": [ @@ -3392,6 +3399,13 @@ "enabled" ] }, + "gateCheckMode": { + "type": "string", + "enum": [ + "off", + "enabled" + ] + }, "quietByDefault": { "type": "boolean" }, @@ -3403,20 +3417,6 @@ "items": { "type": "string" } - }, - "publicAudienceMode": { - "type": "string", - "enum": [ - "oss_maintainer", - "gittensor_only" - ] - }, - "gateCheckMode": { - "type": "string", - "enum": [ - "off", - "enabled" - ] } }, "required": [ @@ -7384,40 +7384,6 @@ "high" ] }, - "linkedPrs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "number": { - "type": "number" - }, - "state": { - "type": "string", - "enum": [ - "open", - "closed", - "merged", - "unknown" - ] - }, - "isActive": { - "type": "boolean" - } - }, - "required": [ - "number", - "state", - "isActive" - ] - } - }, - "findings": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Finding" - } - }, "source": { "type": "object", "properties": { @@ -7454,6 +7420,40 @@ "ageDays", "freshness" ] + }, + "linkedPrs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "number": { + "type": "number" + }, + "state": { + "type": "string", + "enum": [ + "open", + "closed", + "merged", + "unknown" + ] + }, + "isActive": { + "type": "boolean" + } + }, + "required": [ + "number", + "state", + "isActive" + ] + } + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Finding" + } } }, "required": [ @@ -7537,6 +7537,13 @@ "all_prs" ] }, + "publicAudienceMode": { + "type": "string", + "enum": [ + "oss_maintainer", + "gittensor_only" + ] + }, "publicSignalLevel": { "type": "string", "enum": [ @@ -7559,6 +7566,13 @@ "deep" ] }, + "gateCheckMode": { + "type": "string", + "enum": [ + "off", + "enabled" + ] + }, "autoLabelEnabled": { "type": "boolean" }, @@ -7633,20 +7647,6 @@ "type": "string", "nullable": true }, - "publicAudienceMode": { - "type": "string", - "enum": [ - "oss_maintainer", - "gittensor_only" - ] - }, - "gateCheckMode": { - "type": "string", - "enum": [ - "off", - "enabled" - ] - }, "linkedIssueGateMode": { "type": "string", "enum": [ @@ -7738,21 +7738,18 @@ "all_prs" ] }, - "checkRunMode": { + "publicAudienceMode": { "type": "string", "enum": [ - "off", - "enabled" + "oss_maintainer", + "gittensor_only" ] }, - "autoLabelEnabled": { - "type": "boolean" - }, - "publicAudienceMode": { + "checkRunMode": { "type": "string", "enum": [ - "oss_maintainer", - "gittensor_only" + "off", + "enabled" ] }, "gateCheckMode": { @@ -7761,6 +7758,9 @@ "off", "enabled" ] + }, + "autoLabelEnabled": { + "type": "boolean" } }, "required": [ @@ -8120,6 +8120,13 @@ "all_prs" ] }, + "publicAudienceMode": { + "type": "string", + "enum": [ + "oss_maintainer", + "gittensor_only" + ] + }, "publicSignalLevel": { "type": "string", "enum": [ @@ -8142,6 +8149,13 @@ "deep" ] }, + "gateCheckMode": { + "type": "string", + "enum": [ + "off", + "enabled" + ] + }, "autoLabelEnabled": { "type": "boolean" }, @@ -8205,20 +8219,6 @@ "commandOverrides" ] }, - "publicAudienceMode": { - "type": "string", - "enum": [ - "oss_maintainer", - "gittensor_only" - ] - }, - "gateCheckMode": { - "type": "string", - "enum": [ - "off", - "enabled" - ] - }, "linkedIssueGateMode": { "type": "string", "enum": [ @@ -11140,30 +11140,6 @@ "warnings" ] }, - "status": { - "type": "string", - "enum": [ - "ready", - "needs_proof", - "hold", - "do_not_use" - ] - }, - "score": { - "type": "number" - }, - "reasons": { - "type": "array", - "items": { - "type": "string" - } - }, - "warnings": { - "type": "array", - "items": { - "type": "string" - } - }, "bounty": { "type": "object", "properties": { @@ -11276,6 +11252,30 @@ "source", "linkedPrs" ] + }, + "status": { + "type": "string", + "enum": [ + "ready", + "needs_proof", + "hold", + "do_not_use" + ] + }, + "score": { + "type": "number" + }, + "reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -12394,6 +12394,99 @@ ] } }, + "/v1/app/self-dogfood/registration-pack": { + "get": { + "responses": { + "200": { + "description": "Private self-dogfood registration pack for the Gittensory repo", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "403": { + "description": "Insufficient role for maintainer-only self-dogfood report" + } + }, + "security": [ + { + "GittensoryBearer": [] + }, + { + "GittensorySessionCookie": [] + } + ] + } + }, + "/v1/repos/{owner}/{repo}/self-dogfood-registration-pack": { + "get": { + "responses": { + "200": { + "description": "Private self-dogfood registration pack when repo matches configured Gittensory target", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "403": { + "description": "Insufficient role or repo is not the configured self-dogfood target" + } + }, + "security": [ + { + "GittensoryBearer": [] + }, + { + "GittensorySessionCookie": [] + } + ] + } + }, + "/v1/repos/{owner}/{repo}/onboarding-pack/preview": { + "get": { + "responses": { + "200": { + "description": "Preview-only repo onboarding pack for accepted repositories", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + } + } + }, + "403": { + "description": "Insufficient role" + }, + "404": { + "description": "Repository is not accepted or preview unavailable" + } + }, + "security": [ + { + "GittensoryBearer": [] + }, + { + "GittensorySessionCookie": [] + } + ] + } + }, "/v1/repos/{owner}/{repo}/settings": { "get": { "responses": { @@ -14154,39 +14247,6 @@ } ] } - }, - "/v1/repos/{owner}/{repo}/onboarding-pack/preview": { - "get": { - "responses": { - "200": { - "description": "Preview-only repo onboarding pack for accepted repositories", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "nullable": true - } - } - } - } - }, - "403": { - "description": "Insufficient role" - }, - "404": { - "description": "Repository is not accepted or preview unavailable" - } - }, - "security": [ - { - "GittensoryBearer": [] - }, - { - "GittensorySessionCookie": [] - } - ] - } } }, "servers": [ diff --git a/src/api/routes.ts b/src/api/routes.ts index 0d2458d3..5eef23d5 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -142,6 +142,7 @@ import { MINIMUM_SUPPORTED_MCP_VERSION, } from "../services/mcp-compatibility"; import { buildOperatorDashboardPayload } from "../services/operator-dashboard"; +import { buildSelfDogfoodRegistrationPack, resolveSelfDogfoodRepoFullName } from "../services/self-dogfood-registration-pack"; import { buildWeeklyValueReport, formatWeeklyValueReportMarkdown, @@ -184,7 +185,12 @@ import { MAX_LOCAL_SCORER_WARNING_CHARS, MAX_LOCAL_SCORER_WARNING_COUNT } from " import { loadRepoFocusManifest } from "../signals/focus-manifest-loader"; import { buildRepoOnboardingPackPreviewForRepo } from "../services/repo-onboarding-pack"; import { buildRepoSettingsPreview, type PublicSurfaceSkipReason } from "../signals/settings-preview"; -import { buildGittensorConfigRecommendation, buildRegistrationReadiness, type InstallationHealthSummary } from "../signals/registration-readiness"; +import { + buildGittensorConfigRecommendation, + buildRegistrationReadiness, + type InstallationHealthSummary, + type RegistrationReadinessReport, +} from "../signals/registration-readiness"; import { fileUpstreamDriftIssues, loadUpstreamStatus, refreshUpstreamDrift, registryHyperparameterDriftWarningsForRepo } from "../upstream/ruleset"; import type { BountyLifecycleEventRecord, @@ -1491,6 +1497,22 @@ export function createApp() { return c.json(await buildGittensorConfigRecommendationResponse(c.env, fullName)); }); + app.get("/v1/app/self-dogfood/registration-pack", async (c) => { + const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]); + if (forbidden) return forbidden; + return c.json(await buildSelfDogfoodRegistrationPackResponse(c.env)); + }); + + app.get("/v1/repos/:owner/:repo/self-dogfood-registration-pack", async (c) => { + const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]); + if (forbidden) return forbidden; + const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`; + if (fullName.toLowerCase() !== resolveSelfDogfoodRepoFullName(c.env).toLowerCase()) { + return c.json({ error: "self_dogfood_repo_only", repoFullName: resolveSelfDogfoodRepoFullName(c.env) }, 403); + } + return c.json(await buildSelfDogfoodRegistrationPackResponse(c.env)); + }); + app.get("/v1/repos/:owner/:repo/onboarding-pack/preview", async (c) => { const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`; const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]); @@ -2898,6 +2920,24 @@ function stripOwnerPolicyContext(policyRead return publicPolicyReadiness; } +async function buildSelfDogfoodRegistrationPackResponse(env: Env) { + const fullName = resolveSelfDogfoodRepoFullName(env); + const [readinessPayload, recommendationPayload] = await Promise.all([ + buildRegistrationReadinessResponse(env, fullName), + buildGittensorConfigRecommendationResponse(env, fullName), + ]); + const { dataQuality: _readinessQuality, ...registrationReadiness } = readinessPayload; + const { dataQuality: _recommendationQuality, ...gittensorConfigRecommendation } = recommendationPayload; + return { + ...buildSelfDogfoodRegistrationPack({ + repoFullName: fullName, + registrationReadiness: registrationReadiness as RegistrationReadinessReport, + gittensorConfigRecommendation, + }), + dataQuality: _readinessQuality, + }; +} + async function buildGittensorConfigRecommendationResponse(env: Env, fullName: string) { /* v8 ignore start -- Config recommendation route-level shaping over covered signal helpers. */ const intelligence = await buildRepoIntelligenceResponse(env, fullName); diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts index b173894d..45039e13 100644 --- a/src/openapi/spec.ts +++ b/src/openapi/spec.ts @@ -373,6 +373,22 @@ export function buildOpenApiSpec() { 200: { description: "Private Gittensor config recommendation for repo owners", content: { "application/json": { schema: GittensorConfigRecommendationSchema } } }, }, }); + registry.registerPath({ + method: "get", + path: "/v1/app/self-dogfood/registration-pack", + responses: { + 200: { description: "Private self-dogfood registration pack for the Gittensory repo", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, + 403: { description: "Insufficient role for maintainer-only self-dogfood report" }, + }, + }); + registry.registerPath({ + method: "get", + path: "/v1/repos/{owner}/{repo}/self-dogfood-registration-pack", + responses: { + 200: { description: "Private self-dogfood registration pack when repo matches configured Gittensory target", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } }, + 403: { description: "Insufficient role or repo is not the configured self-dogfood target" }, + }, + }); registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/onboarding-pack/preview", diff --git a/src/services/self-dogfood-registration-pack.ts b/src/services/self-dogfood-registration-pack.ts new file mode 100644 index 00000000..903c3bdd --- /dev/null +++ b/src/services/self-dogfood-registration-pack.ts @@ -0,0 +1,161 @@ +import { + buildGittensorConfigRecommendation, + buildRegistrationReadiness, + type GittensorConfigRecommendation, + type GittensorConfigRecommendationInput, + type RegistrationReadinessInput, + type RegistrationReadinessReport, +} from "../signals/registration-readiness"; +import { nowIso } from "../utils/json"; + +export const DEFAULT_SELF_DOGFOOD_REPO = "JSONbored/gittensory"; + +export type SelfDogfoodActionArea = { + area: string; + status: "ready" | "needs_attention" | "blocked"; + actions: string[]; +}; + +export type SelfDogfoodRegistrationPack = { + kind: "gittensory_self_dogfood_registration_pack"; + repoFullName: string; + generatedAt: string; + privateOnly: true; + advisoryOnly: true; + directPrFirst: boolean; + contributorLaneStrategy: string; + maintainerEconomicsNote: string; + minerScoreabilityNote: string; + registrationReadiness: RegistrationReadinessReport; + gittensorConfigRecommendation: GittensorConfigRecommendation; + actionableAreas: SelfDogfoodActionArea[]; + rerunHint: string; +}; + +export function resolveSelfDogfoodRepoFullName(env: { GITTENSORY_DRIFT_ISSUE_REPO?: string }): string { + const configured = env.GITTENSORY_DRIFT_ISSUE_REPO?.trim(); + if (!configured || !configured.includes("/")) return DEFAULT_SELF_DOGFOOD_REPO; + return configured; +} + +export function buildSelfDogfoodRegistrationPack(args: { + repoFullName: string; + registrationReadiness: RegistrationReadinessReport; + gittensorConfigRecommendation: GittensorConfigRecommendation; +}): SelfDogfoodRegistrationPack { + const { registrationReadiness: readiness, gittensorConfigRecommendation: recommendation } = args; + const issueDiscoveryReady = + readiness.issueDiscoveryReadiness.ready && readiness.issueDiscoveryReadiness.recommendation === "enabled"; + const directPrFirst = !issueDiscoveryReady && readiness.recommendedRegistrationMode !== "issue_discovery"; + + return { + kind: "gittensory_self_dogfood_registration_pack", + repoFullName: args.repoFullName, + generatedAt: nowIso(), + privateOnly: true, + advisoryOnly: true, + directPrFirst, + contributorLaneStrategy: directPrFirst + ? "Keep contributor intake direct-PR-first until issue-discovery signals, label policy, and queue health are excellent." + : "Issue-discovery intake is strong enough to keep a bounded issue-discovery lane alongside direct PRs.", + maintainerEconomicsNote: + "Maintainer cut and registry emission splits are maintainer-economics controls only; they do not change private miner scoreability or public compensation claims.", + minerScoreabilityNote: + "Miner-facing scoreability stays in private API/MCP surfaces with hashed actors; sensitive identity and ranking fields stay out of this report.", + registrationReadiness: readiness, + gittensorConfigRecommendation: recommendation, + actionableAreas: buildActionableAreas(readiness, recommendation), + rerunHint: "Rerun this pack after registry, .gittensor.yml, label policy, GitHub App, or queue changes to refresh readiness and config tradeoffs.", + }; +} + +export function buildSelfDogfoodRegistrationPackFromSignals( + input: RegistrationReadinessInput & GittensorConfigRecommendationInput, +): SelfDogfoodRegistrationPack { + const registrationReadiness = buildRegistrationReadiness(input); + const gittensorConfigRecommendation = buildGittensorConfigRecommendation(input); + return buildSelfDogfoodRegistrationPack({ + repoFullName: input.repoFullName, + registrationReadiness, + gittensorConfigRecommendation, + }); +} + +function buildActionableAreas( + readiness: RegistrationReadinessReport, + recommendation: GittensorConfigRecommendation, +): SelfDogfoodActionArea[] { + const areas: SelfDogfoodActionArea[] = [ + { + area: "direct_pr", + status: readiness.directPrReadiness.ready ? "ready" : readiness.blockers.length > 0 ? "blocked" : "needs_attention", + actions: readiness.directPrReadiness.ready + ? ["Keep direct PRs as the default contributor lane."] + : [...readiness.directPrReadiness.reasons, ...readiness.blockers], + }, + { + area: "issue_discovery", + status: + readiness.issueDiscoveryReadiness.recommendation === "not_recommended" + ? "blocked" + : readiness.issueDiscoveryReadiness.ready + ? "ready" + : "needs_attention", + actions: + readiness.issueDiscoveryReadiness.reasons.length > 0 + ? readiness.issueDiscoveryReadiness.reasons + : ["Issue discovery is intentionally deprioritized until intake is staffed and config is excellent."], + }, + { + area: "label_policy", + status: readiness.labelPolicy.trustedPipelineReady ? "ready" : "needs_attention", + actions: [ + ...(readiness.labelPolicy.missingOrUnusedRegistryLabels.length > 0 + ? readiness.labelPolicy.missingOrUnusedRegistryLabels.map((label) => `Add or retire registry label "${label}".`) + : ["Label policy matches cached repo activity."]), + recommendation.recommended.labelMultipliers === "start_without_trusted_label_multipliers" + ? "Start without trusted label multipliers until labels are observed in live activity." + : "Prune unused configured labels before expanding trusted multipliers.", + ], + }, + { + area: "maintainer_cut", + status: readiness.maintainerCutReadiness.ready ? "ready" : "needs_attention", + actions: readiness.maintainerCutReadiness.ready + ? [`Consider maintainer cut near ${recommendation.recommended.maintainerCut}; keep it separate from miner scoreability.`] + : readiness.maintainerCutReadiness.reasons, + }, + { + area: "tests_and_docs", + status: readiness.testCoverageHealth.status === "gate_ready" ? "ready" : "needs_attention", + actions: [ + ...readiness.testCoverageHealth.requiredGate.map((gate) => `Preserve CI gate: ${gate}.`), + ...readiness.docsCompleteness.requiredDocs.map((doc) => `Keep ${doc} current for contributor intake.`), + ], + }, + { + area: "queue_and_github_app", + status: + readiness.queueHealth.level === "critical" || readiness.queueHealth.level === "high" + ? "blocked" + : readiness.githubApp.installed + ? "ready" + : "needs_attention", + actions: [ + readiness.queueHealth.summary, + readiness.githubApp.behavior, + ...readiness.githubApp.warnings, + ].filter(Boolean), + }, + ]; + + if (readiness.blockers.length > 0) { + areas.unshift({ + area: "registration_blockers", + status: "blocked", + actions: readiness.blockers, + }); + } + + return areas; +} diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index 01dd4700..1066e1bc 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -120,6 +120,14 @@ describe("parseFocusManifestContent", () => { expect(manifest.present).toBe(false); expect(manifest.warnings.join(" ")).toMatch(/not valid JSON/i); }); + + it("warns when JSON content is not a mapping", () => { + for (const content of ['["a","b"]', '"string"']) { + const manifest = parseFocusManifestContent(content); + expect(manifest.present).toBe(false); + expect(manifest.warnings.join(" ")).toMatch(/must be a mapping/i); + } + }); }); describe("matchesManifestPath", () => { @@ -366,6 +374,24 @@ describe("compileFocusManifestPolicy", () => { expect(publicText).toContain("npm run test:ci"); }); + it("skips unsafe publicNotes when entry guidance is compiled from a raw manifest", () => { + const policy = compileFocusManifestPolicy({ + present: true, + source: "api_record", + wantedPaths: ["src/"], + blockedPaths: [], + preferredLabels: [], + linkedIssuePolicy: "optional", + testExpectations: [], + issueDiscoveryPolicy: "neutral", + maintainerNotes: [], + publicNotes: ["Keep PRs focused.", "Maximize your reward payout"], + warnings: [], + }); + expect(policy.publicSafe.entryGuidance).toContain("Keep PRs focused."); + expect(policy.publicSafe.entryGuidance.join(" ")).not.toMatch(/reward payout/i); + }); + it("publicSafe.summary never contains forbidden language", () => { const dangerous = parseFocusManifest({ wantedPaths: ["src/"], publicNotes: ["Boost your raw trust score here"] }); const policy = compileFocusManifestPolicy(dangerous); diff --git a/test/unit/registration-readiness.test.ts b/test/unit/registration-readiness.test.ts index 7ef1c68c..f4026d3c 100644 --- a/test/unit/registration-readiness.test.ts +++ b/test/unit/registration-readiness.test.ts @@ -161,6 +161,21 @@ describe("buildRegistrationReadiness", () => { expect(report.githubApp.behavior).toContain("for all PRs"); }); + it("describes a quiet public surface while keeping the opt-in gate check enabled", () => { + const repo = repoFor("octo/quiet-gate", configFor({ repo: "octo/quiet-gate" })); + const settings = settingsFor(repo.fullName, { publicSurface: "off", gateCheckMode: "enabled" }); + const report = buildRegistrationReadiness({ + repoFullName: repo.fullName, + repo, + settings, + installation: healthyInstall, + ...signalsFor(repo, [], [], [label("bug")]), + }); + + expect(report.githubApp.behavior).toContain("stays quiet"); + expect(report.githubApp.behavior).toContain("opt-in gate check still enabled"); + }); + it("notes when the GitHub App is not installed", () => { const repo = repoFor("octo/uninstalled", configFor({ repo: "octo/uninstalled" }), { isInstalled: false, installationId: null }); const report = buildRegistrationReadiness({ repoFullName: repo.fullName, repo, settings: settingsFor(repo.fullName), installation: null, ...signalsFor(repo, [], [], [label("bug")]) }); diff --git a/test/unit/routes-self-dogfood-registration-pack.test.ts b/test/unit/routes-self-dogfood-registration-pack.test.ts new file mode 100644 index 00000000..77316da1 --- /dev/null +++ b/test/unit/routes-self-dogfood-registration-pack.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { createApp } from "../../src/api/routes"; +import { createSessionForGitHubUser } from "../../src/auth/security"; +import { createTestEnv } from "../helpers/d1"; + +const SELF_DOGFOOD_PATH = "/v1/repos/JSONbored/gittensory/self-dogfood-registration-pack"; + +function apiHeaders(env: Env): Record { + return { + authorization: `Bearer ${env.GITTENSORY_API_TOKEN}`, + "content-type": "application/json", + }; +} + +describe("self-dogfood registration-pack route auth", () => { + it("rejects unauthenticated access to the repo-scoped route", async () => { + const app = createApp(); + const env = createTestEnv(); + const response = await app.request(SELF_DOGFOOD_PATH, {}, env); + expect(response.status).toBe(401); + await expect(response.json()).resolves.toMatchObject({ error: "unauthorized" }); + }); + + it("rejects unauthorized session access to the repo-scoped route", async () => { + const app = createApp(); + const env = createTestEnv({ ADMIN_GITHUB_LOGINS: "jsonbored" }); + const { token } = await createSessionForGitHubUser(env, { login: "new-user", id: 2468 }); + const response = await app.request(SELF_DOGFOOD_PATH, { headers: { cookie: `gittensory_session=${token}` } }, env); + expect(response.status).toBe(403); + await expect(response.json()).resolves.toMatchObject({ error: "insufficient_role" }); + }); + + it("rejects wrong-repo access after role check", async () => { + const app = createApp(); + const env = createTestEnv({ ADMIN_GITHUB_LOGINS: "jsonbored" }); + const { token } = await createSessionForGitHubUser(env, { login: "jsonbored", id: 1 }); + const response = await app.request( + "/v1/repos/other/repo/self-dogfood-registration-pack", + { headers: { cookie: `gittensory_session=${token}` } }, + env, + ); + expect(response.status).toBe(403); + await expect(response.json()).resolves.toMatchObject({ error: "self_dogfood_repo_only", repoFullName: "JSONbored/gittensory" }); + }); + + it("allows static-token access to the configured self-dogfood repo", async () => { + const app = createApp(); + const env = createTestEnv(); + const response = await app.request(SELF_DOGFOOD_PATH, { headers: apiHeaders(env) }, env); + expect(response.status).toBe(200); + await expect(response.json()).resolves.toMatchObject({ + kind: "gittensory_self_dogfood_registration_pack", + repoFullName: "JSONbored/gittensory", + privateOnly: true, + advisoryOnly: true, + }); + }); +}); diff --git a/test/unit/self-dogfood-registration-pack.test.ts b/test/unit/self-dogfood-registration-pack.test.ts new file mode 100644 index 00000000..21022cfe --- /dev/null +++ b/test/unit/self-dogfood-registration-pack.test.ts @@ -0,0 +1,384 @@ +import { describe, expect, it } from "vitest"; +import { + buildCollisionReport, + buildConfigQuality, + buildContributorIntakeHealth, + buildLabelAudit, + buildLaneAdvice, + buildMaintainerCutReadiness, + buildQueueHealth, +} from "../../src/signals/engine"; +import { + buildSelfDogfoodRegistrationPack, + buildSelfDogfoodRegistrationPackFromSignals, + DEFAULT_SELF_DOGFOOD_REPO, + resolveSelfDogfoodRepoFullName, + type SelfDogfoodRegistrationPack, +} from "../../src/services/self-dogfood-registration-pack"; +import { + buildGittensorConfigRecommendation, + buildRegistrationReadiness, + type GittensorConfigRecommendation, + type InstallationHealthSummary, + type RegistrationReadinessReport, +} from "../../src/signals/registration-readiness"; +import type { IssueRecord, PullRequestRecord, RepoLabelRecord, RegistryRepoConfig, RepositoryRecord, RepositorySettings } from "../../src/types"; + +const FORBIDDEN_PUBLIC_LANGUAGE = + /wallet|hotkey|payout|reward estimate|raw trust score|public score estimate|private reviewability|farming/i; + +function repoFor(fullName: string, registryConfig: RegistryRepoConfig | null, overrides: Partial = {}): RepositoryRecord { + const [owner, name] = fullName.split("/"); + return { + fullName, + owner: owner ?? fullName, + name: name ?? fullName, + installationId: 1, + isInstalled: true, + isRegistered: registryConfig !== null, + isPrivate: false, + registryConfig, + ...overrides, + }; +} + +function configFor(overrides: Partial = {}): RegistryRepoConfig { + return { repo: "x/y", emissionShare: 0.02, issueDiscoveryShare: 0, labelMultipliers: { bug: 1.1 }, trustedLabelPipeline: true, maintainerCut: 0, raw: {}, ...overrides }; +} + +function settingsFor(repoFullName: string, overrides: Partial = {}): RepositorySettings { + return { + repoFullName, + commentMode: "detected_contributors_only", + publicAudienceMode: "oss_maintainer", + publicSignalLevel: "standard", + checkRunMode: "enabled", + checkRunDetailLevel: "standard", + gateCheckMode: "off", + linkedIssueGateMode: "advisory", + duplicatePrGateMode: "advisory", + qualityGateMode: "advisory", + qualityGateMinScore: null, + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, + backfillEnabled: true, + privateTrustEnabled: true, + ...overrides, + }; +} + +const healthyInstall: InstallationHealthSummary = { status: "healthy", missingPermissions: [], missingEvents: [] }; + +function signalsFor(repo: RepositoryRecord, issues: IssueRecord[], pullRequests: PullRequestRecord[], labels: RepoLabelRecord[]) { + const fullName = repo.fullName; + const collisions = buildCollisionReport(fullName, issues, pullRequests); + return { + lane: buildLaneAdvice(repo, fullName), + configQuality: buildConfigQuality(repo, issues, pullRequests, fullName), + labelAudit: buildLabelAudit(repo, labels, issues, pullRequests, fullName), + queueHealth: buildQueueHealth(repo, issues, pullRequests, collisions), + maintainerCutReadiness: buildMaintainerCutReadiness(repo, issues, pullRequests, fullName, {}, collisions), + contributorIntakeHealth: buildContributorIntakeHealth(repo, issues, pullRequests, fullName, collisions), + }; +} + +function label(name: string): RepoLabelRecord { + return { repoFullName: "x/y", name, isConfigured: true, observedCount: 3, payload: {} }; +} + +function readinessFixture(overrides: Partial = {}): RegistrationReadinessReport { + return { + repoFullName: "octo/test", + generatedAt: "2026-05-28T00:00:00.000Z", + ready: true, + recommendedRegistrationMode: "split", + issuePolicy: "split_pr_and_issue_discovery_enabled", + directPrReadiness: { ready: false, reasons: ["Direct PR lane still needs maintainer review staffing."] }, + issueDiscoveryReadiness: { ready: true, recommendation: "enabled", reasons: ["Issue discovery lane is staffed."] }, + labelPolicy: { + autoLabelEnabled: true, + label: "gittensor", + createMissingLabel: true, + configuredRegistryLabels: ["bug"], + missingOrUnusedRegistryLabels: ["stale-label"], + trustedPipelineReady: false, + }, + maintainerCutReadiness: { + repoFullName: "octo/test", + generatedAt: "2026-05-28T00:00:00.000Z", + ready: false, + maintainerCut: 0, + recommendedAction: "leave_disabled", + reasons: ["Maintainer cut stays off until config is excellent."], + warnings: [], + }, + testCoverageHealth: { + status: "gate_unknown", + trustedLabelPipelineReady: false, + checkRunMode: "off", + requiredGate: ["npm run test:ci"], + note: "Coverage gate note.", + warnings: [], + }, + queueHealth: { level: "high", burdenScore: 88, reviewablePullRequests: 0, summary: "Queue burden is high." }, + contributorIntakeHealth: { + repoFullName: "octo/test", + generatedAt: "2026-05-28T00:00:00.000Z", + level: "healthy", + score: 90, + queueHealth: { + burdenScore: 88, + level: "high", + signals: { + openIssues: 4, + openPullRequests: 6, + unlinkedPullRequests: 1, + stalePullRequests: 2, + maintainerAuthoredPullRequests: 0, + collisionClusters: 0, + ageBuckets: { under7Days: 2, days7To30: 3, over30Days: 1 }, + likelyReviewablePullRequests: 0, + }, + }, + configLevel: "excellent", + duplicateClusters: 0, + reviewablePullRequests: 0, + summary: "Healthy intake.", + findings: [], + }, + docsCompleteness: { status: "repo_docs_not_crawled", requiredDocs: ["README"], note: "Docs not crawled." }, + githubApp: { + installed: false, + publicSurface: "comment_and_label", + commentMode: "all_prs", + publicAudienceMode: "oss_maintainer", + checkRunMode: "off", + gateCheckMode: "off", + quietByDefault: false, + behavior: "Gittensory would stay silent because the GitHub App is not installed.", + warnings: ["GitHub App is not installed on this repo; maintainers will not get any automated assistance."], + }, + policyReadiness: null, + blockers: [], + warnings: [], + ...overrides, + }; +} + +function recommendationFixture(overrides: Partial = {}): GittensorConfigRecommendation { + return { + repoFullName: "octo/test", + generatedAt: "2026-05-28T00:00:00.000Z", + privateOnly: true, + current: null, + recommended: { + participationMode: "split", + issueDiscoveryShare: 0.1, + directPrShare: 0.9, + maintainerCut: 0, + requireLinkedIssue: false, + labelMultipliers: "start_without_trusted_label_multipliers", + publicSurface: "comment_and_label", + confirmedMinerLabel: "gittensor", + }, + tradeoffs: [], + reasons: [], + warnings: [], + ...overrides, + }; +} + +function packFromRepo( + repo: RepositoryRecord, + issues: IssueRecord[] = [], + pullRequests: PullRequestRecord[] = [], + labels: RepoLabelRecord[] = [label("bug")], + settingsOverrides: Partial = {}, +): SelfDogfoodRegistrationPack { + return buildSelfDogfoodRegistrationPackFromSignals({ + repoFullName: repo.fullName, + repo, + settings: settingsFor(repo.fullName, settingsOverrides), + installation: healthyInstall, + ...signalsFor(repo, issues, pullRequests, labels), + }); +} + +describe("resolveSelfDogfoodRepoFullName", () => { + it("defaults to the Gittensory repo when drift issue repo is unset", () => { + expect(resolveSelfDogfoodRepoFullName({})).toBe(DEFAULT_SELF_DOGFOOD_REPO); + expect(resolveSelfDogfoodRepoFullName({ GITTENSORY_DRIFT_ISSUE_REPO: "" })).toBe(DEFAULT_SELF_DOGFOOD_REPO); + }); + + it("uses the configured drift issue repo when valid", () => { + expect(resolveSelfDogfoodRepoFullName({ GITTENSORY_DRIFT_ISSUE_REPO: "acme/widget" })).toBe("acme/widget"); + }); + + it("falls back when drift issue repo is missing a slash", () => { + expect(resolveSelfDogfoodRepoFullName({ GITTENSORY_DRIFT_ISSUE_REPO: "invalid" })).toBe(DEFAULT_SELF_DOGFOOD_REPO); + }); +}); + +describe("buildSelfDogfoodRegistrationPack", () => { + it("ready fixture recommends direct-PR-first with actionable areas", () => { + const repo = repoFor("octo/ready", configFor({ repo: "octo/ready" })); + const issues: IssueRecord[] = [{ repoFullName: repo.fullName, number: 4, title: "Fix flaky cache test", state: "open", labels: ["bug"], linkedPrs: [] }]; + const pack = packFromRepo(repo, issues); + + expect(pack).toMatchObject({ + kind: "gittensory_self_dogfood_registration_pack", + privateOnly: true, + advisoryOnly: true, + directPrFirst: true, + registrationReadiness: { ready: true, recommendedRegistrationMode: "direct_pr" }, + }); + expect(pack.actionableAreas.some((area) => area.area === "direct_pr" && area.status === "ready")).toBe(true); + expect(pack.maintainerEconomicsNote).toMatch(/maintainer-economics/i); + expect(pack.minerScoreabilityNote).toMatch(/scoreability/i); + expect(pack.rerunHint).toMatch(/Rerun this pack/i); + }); + + it("not-ready fixture surfaces registration blockers", () => { + const repo = repoFor("octo/unregistered", null); + const pack = packFromRepo(repo, [], [], []); + + expect(pack.registrationReadiness.ready).toBe(false); + expect(pack.directPrFirst).toBe(true); + expect(pack.actionableAreas[0]).toMatchObject({ area: "registration_blockers", status: "blocked" }); + expect(pack.gittensorConfigRecommendation.recommended.issueDiscoveryShare).toBe(0); + }); + + it("issue-discovery disabled fixture keeps direct PR lane primary", () => { + const repo = repoFor("octo/direct", configFor({ repo: "octo/direct", issueDiscoveryShare: 0 })); + const base = signalsFor(repo, [], [], [label("bug")]); + const pack = buildSelfDogfoodRegistrationPackFromSignals({ + repoFullName: repo.fullName, + repo, + settings: settingsFor(repo.fullName), + installation: healthyInstall, + ...base, + contributorIntakeHealth: { ...base.contributorIntakeHealth, level: "strained" }, + }); + + expect(pack.registrationReadiness.issueDiscoveryReadiness.recommendation).toBe("not_recommended"); + expect(pack.directPrFirst).toBe(true); + expect(pack.contributorLaneStrategy).toMatch(/direct-PR-first/i); + expect(pack.gittensorConfigRecommendation.recommended.participationMode).toBe("direct_pr"); + expect(pack.gittensorConfigRecommendation.recommended.issueDiscoveryShare).toBe(0); + }); + + it("maintainer-cut fixture separates economics from miner scoreability", () => { + const repo = repoFor("octo/cut", configFor({ repo: "octo/cut", maintainerCut: 0.05 })); + const base = signalsFor(repo, [], [], [label("bug")]); + const pack = buildSelfDogfoodRegistrationPackFromSignals({ + repoFullName: repo.fullName, + repo, + settings: settingsFor(repo.fullName), + installation: healthyInstall, + ...base, + maintainerCutReadiness: { ...base.maintainerCutReadiness, ready: true }, + }); + + expect(pack.actionableAreas.some((area) => area.area === "maintainer_cut")).toBe(true); + expect(pack.maintainerEconomicsNote).not.toMatch(FORBIDDEN_PUBLIC_LANGUAGE); + expect(pack.minerScoreabilityNote).toMatch(/private API\/MCP surfaces/i); + }); + + it("public wording regression stays free of forbidden language", () => { + const repo = repoFor("JSONbored/gittensory", configFor({ repo: "JSONbored/gittensory" })); + const pack = packFromRepo(repo); + expect(JSON.stringify(pack)).not.toMatch(FORBIDDEN_PUBLIC_LANGUAGE); + }); + + it("issue-discovery enabled fixture keeps bounded issue-discovery lane guidance", () => { + const pack = buildSelfDogfoodRegistrationPack({ + repoFullName: "octo/split", + registrationReadiness: readinessFixture(), + gittensorConfigRecommendation: recommendationFixture(), + }); + + expect(pack.directPrFirst).toBe(false); + expect(pack.contributorLaneStrategy).toMatch(/bounded issue-discovery lane/i); + expect(pack.actionableAreas).toEqual( + expect.arrayContaining([ + expect.objectContaining({ area: "direct_pr", status: "needs_attention" }), + expect.objectContaining({ area: "issue_discovery", status: "ready" }), + expect.objectContaining({ area: "queue_and_github_app", status: "blocked" }), + ]), + ); + }); + + it("actionable areas cover deprioritized issue discovery and label-prune guidance", () => { + const pack = buildSelfDogfoodRegistrationPack({ + repoFullName: "octo/labels", + registrationReadiness: readinessFixture({ + directPrReadiness: { ready: true, reasons: [] }, + issueDiscoveryReadiness: { ready: false, recommendation: "recommended", reasons: [] }, + queueHealth: { level: "low", burdenScore: 10, reviewablePullRequests: 2, summary: "Queue is manageable." }, + githubApp: { + installed: true, + publicSurface: "comment_and_label", + commentMode: "all_prs", + publicAudienceMode: "oss_maintainer", + checkRunMode: "off", + gateCheckMode: "off", + quietByDefault: false, + behavior: "Gittensory posts comment and label in oss maintainer mode, for all PRs.", + warnings: [], + }, + }), + gittensorConfigRecommendation: recommendationFixture({ + recommended: { + participationMode: "direct_pr", + issueDiscoveryShare: 0, + directPrShare: 1, + maintainerCut: 0, + requireLinkedIssue: false, + labelMultipliers: "keep_current_and_prune_unused", + publicSurface: "comment_and_label", + confirmedMinerLabel: "gittensor", + }, + }), + }); + + expect(pack.actionableAreas).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + area: "issue_discovery", + status: "needs_attention", + actions: ["Issue discovery is intentionally deprioritized until intake is staffed and config is excellent."], + }), + expect.objectContaining({ + area: "label_policy", + actions: expect.arrayContaining(["Prune unused configured labels before expanding trusted multipliers."]), + }), + expect.objectContaining({ area: "queue_and_github_app", status: "ready" }), + ]), + ); + }); + + it("composes from explicit readiness and recommendation payloads", () => { + const repo = repoFor("octo/ready", configFor({ repo: "octo/ready" })); + const signals = signalsFor(repo, [], [], [label("bug")]); + const registrationReadiness = buildRegistrationReadiness({ + repoFullName: repo.fullName, + repo, + settings: settingsFor(repo.fullName), + installation: healthyInstall, + ...signals, + }); + const gittensorConfigRecommendation = buildGittensorConfigRecommendation({ + repoFullName: repo.fullName, + repo, + settings: settingsFor(repo.fullName), + ...signals, + }); + const pack = buildSelfDogfoodRegistrationPack({ repoFullName: repo.fullName, registrationReadiness, gittensorConfigRecommendation }); + expect(pack.repoFullName).toBe("octo/ready"); + expect(pack.registrationReadiness).toBe(registrationReadiness); + }); +});