Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ import { buildContributorOpenPrMonitor } from "../signals/contributor-open-pr-mo
import { buildPullRequestReviewability, type PullRequestReviewability } from "../signals/reward-risk";
import { buildLocalBranchAnalysis, findCurrentBranchPullRequest } from "../signals/local-branch";
import { MAX_LOCAL_SCORER_WARNING_CHARS, MAX_LOCAL_SCORER_WARNING_COUNT } from "../signals/local-scorer-diagnostics";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { compileFocusManifestPolicy } from "../signals/focus-manifest";
import { loadRepoFocusManifest, upsertRepoFocusManifest } from "../signals/focus-manifest-loader";
import { buildRepoOnboardingPackPreviewForRepo } from "../services/repo-onboarding-pack";
import { buildRepoSettingsPreview, type PublicSurfaceSkipReason } from "../signals/settings-preview";
import {
Expand Down Expand Up @@ -2223,6 +2224,32 @@ export function createApp() {
);
});

app.get("/v1/internal/repos/:owner/:repo/contribution-policy", async (c) => {
const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`;
const focusManifest = await loadRepoFocusManifest(c.env, fullName, { fetcher: async () => null });
const generatedAt = nowIso();
return c.json({
repoFullName: fullName,
generatedAt,
focusManifest,
policy: compileFocusManifestPolicy(fullName, focusManifest, { generatedAt }),
});
});

app.post("/v1/internal/repos/:owner/:repo/contribution-policy", async (c) => {
const body = await c.req.json().catch(() => null);
if (body === null) return c.json({ error: "invalid_contribution_policy_json" }, 400);
const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`;
const focusManifest = await upsertRepoFocusManifest(c.env, fullName, body, "api_record");
const generatedAt = nowIso();
return c.json({
repoFullName: fullName,
generatedAt,
focusManifest,
policy: compileFocusManifestPolicy(fullName, focusManifest, { generatedAt }),
});
});

return app;
}

Expand Down
8 changes: 7 additions & 1 deletion src/signals/focus-manifest-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,14 @@ export async function upsertRepoFocusManifest(env: Env, repoFullName: string, ra
async function readCachedManifest(env: Env, repoFullName: string, maxAgeMs: number): Promise<FocusManifest | null> {
const [latest] = await listSignalSnapshots(env, REPO_FOCUS_MANIFEST_SIGNAL, repoFullName);
if (!latest) return null;
const manifest = parseFocusManifest(latest.payload);
const explicitSource =
latest.payload !== null && typeof latest.payload === "object" && !Array.isArray(latest.payload)
? (latest.payload as Record<string, JsonValue>).source
: undefined;
if (explicitSource === "api_record") return manifest;
if (snapshotAgeMs(latest.generatedAt) > maxAgeMs) return null;
return parseFocusManifest(latest.payload);
return manifest;
}

async function persistRepoFocusManifest(env: Env, repoFullName: string, manifest: FocusManifest): Promise<void> {
Expand Down
146 changes: 146 additions & 0 deletions test/integration/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5122,6 +5122,152 @@ describe("api routes", () => {
commandAuthorizationPreview: { commandName: "preflight", decision: { authorized: true, reason: "allowed_pr_author", matchedRole: "pr_author" } },
});
});

it("persists repo-owner contribution policy snapshots through protected internal API", async () => {
const app = createApp();
const env = createTestEnv();

const rejected = await app.request(
"/v1/internal/repos/entrius/allways-ui/contribution-policy",
{
method: "POST",
body: JSON.stringify({ wantedPaths: ["src/"] }),
},
env,
);
expect(rejected.status).toBe(401);

const invalidJson = await app.request(
"/v1/internal/repos/entrius/allways-ui/contribution-policy",
{
method: "POST",
headers: internalHeaders(env),
body: "{",
},
env,
);
expect(invalidJson.status).toBe(400);
await expect(invalidJson.json()).resolves.toEqual({ error: "invalid_contribution_policy_json" });

const privateNote = "Internal: wallet and hotkey evidence stays private.";
const updated = await app.request(
"/v1/internal/repos/entrius/allways-ui/contribution-policy",
{
method: "POST",
headers: internalHeaders(env),
body: JSON.stringify({
wantedPaths: ["src/"],
blockedPaths: ["dist/"],
preferredLabels: ["bug"],
linkedIssuePolicy: "required",
issueDiscoveryPolicy: "discouraged",
testExpectations: ["Run npm run test:ci."],
maintainerNotes: [privateNote],
publicNotes: ["Prefer small, focused PRs."],
}),
},
env,
);
expect(updated.status).toBe(200);
const updatedPayload = (await updated.json()) as {
generatedAt: string;
policy: { generatedAt: string; publicSafe: { contributionLanes: unknown[] } };
};
expect(updatedPayload.policy.generatedAt).toBe(updatedPayload.generatedAt);
expect(updatedPayload).toMatchObject({
repoFullName: "entrius/allways-ui",
focusManifest: {
present: true,
source: "api_record",
wantedPaths: ["src/"],
blockedPaths: ["dist/"],
maintainerNotes: [privateNote],
},
policy: {
repoFullName: "entrius/allways-ui",
present: true,
source: "api_record",
publicSafe: {
contributionLanes: [
expect.objectContaining({
id: "direct-pr",
preference: "preferred",
preferredPaths: ["src/"],
discouragedPaths: ["dist/"],
validationExpectations: ["Run npm run test:ci."],
publicNotes: ["Prefer small, focused PRs."],
}),
expect.objectContaining({
id: "issue-discovery",
preference: "discouraged",
preferredPaths: [],
discouragedPaths: ["dist/"],
}),
],
labelPolicy: { preferredLabels: ["bug"], required: true },
validation: { expectations: ["Run npm run test:ci."], linkedIssuePolicy: "required" },
issueDiscoveryPolicy: "discouraged",
publicNotes: ["Prefer small, focused PRs."],
},
authenticated: { maintainerContext: [privateNote] },
},
});

const readback = await app.request("/v1/internal/repos/entrius/allways-ui/contribution-policy", { headers: internalHeaders(env) }, env);
expect(readback.status).toBe(200);
await expect(readback.json()).resolves.toMatchObject({
policy: {
publicSafe: { summary: expect.stringMatching(/direct PRs/i) },
authenticated: { maintainerContext: [privateNote] },
},
});

const readiness = await app.request("/v1/repos/entrius/allways-ui/registration-readiness", { headers: apiHeaders(env) }, env);
expect(readiness.status).toBe(200);
const readinessPayload = (await readiness.json()) as { policyReadiness: Record<string, unknown> };
expect(readinessPayload.policyReadiness).toMatchObject({
source: "focus_manifest_policy",
present: true,
});
expect(readinessPayload.policyReadiness).not.toHaveProperty("ownerContext");
expect(JSON.stringify(readinessPayload)).not.toContain(privateNote);
expect(JSON.stringify(readinessPayload)).not.toMatch(/privateNoteCount|blockedPathCount|validationExpectationCount/i);
expect(JSON.stringify(readinessPayload)).not.toMatch(FORBIDDEN_PUBLIC_REPORT_TERMS);

const malformed = await app.request(
"/v1/internal/repos/entrius/allways-ui/contribution-policy",
{
method: "POST",
headers: internalHeaders(env),
body: JSON.stringify({
wantedPaths: "src/",
preferredLabels: [123, "bug"],
linkedIssuePolicy: "sometimes",
publicNotes: ["reward estimate", "Keep scope focused."],
}),
},
env,
);
expect(malformed.status).toBe(200);
const malformedPayload = (await malformed.json()) as {
focusManifest: { warnings: string[] };
policy: { publicSafe: Record<string, unknown> };
};
expect(malformedPayload.focusManifest).toMatchObject({
present: true,
wantedPaths: [],
preferredLabels: ["bug"],
linkedIssuePolicy: "optional",
});
expect(malformedPayload.focusManifest.warnings).toEqual(
expect.arrayContaining([
expect.stringContaining("wantedPaths"),
expect.stringContaining("preferredLabels"),
expect.stringContaining("linkedIssuePolicy"),
]),
);
expect(JSON.stringify(malformedPayload.policy.publicSafe)).not.toMatch(FORBIDDEN_PUBLIC_REPORT_TERMS);
});
});

async function signWebhook(body: string, secret: string): Promise<string> {
Expand Down
3 changes: 2 additions & 1 deletion test/unit/focus-manifest-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ describe("focus-manifest loader", () => {
const saved = await upsertRepoFocusManifest(env, "owner/api", { wantedPaths: ["lib/"] });
expect(saved.present).toBe(true);
expect(saved.source).toBe("api_record");
// A subsequent load (without forcing refresh) returns the persisted manifest without calling the fetcher.
// API-backed settings snapshots are durable and do not age out like repo-file fetch caches.
const reloaded = await loadRepoFocusManifest(env, "owner/api", {
maxAgeMs: -1,
fetcher: async () => {
throw new Error("should not be called");
},
Expand Down