Skip to content
Merged
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
52 changes: 52 additions & 0 deletions apps/gittensory-extension/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async function load(container, target) {
`,
)
.join("");
renderActions(body, response.payload?.actions);
}

function escapeHtml(value) {
Expand All @@ -67,3 +68,54 @@ function escapeHtml(value) {
}
});
}

function renderActions(body, actions) {
const list = Array.isArray(actions) ? actions : [];
if (list.length === 0) return;
const container = document.createElement("section");
container.className = "gittensory-overlay__panel";
container.innerHTML = `
<div class="gittensory-overlay__panel-head">
<strong>Actions</strong>
<span>extension</span>
</div>
<div class="gittensory-overlay__actions"></div>
`;
const actionsNode = container.querySelector(".gittensory-overlay__actions");
if (!actionsNode) return;
for (const action of list) {
if (action?.id === "copy_public_safe_packet" && typeof action?.markdown === "string") {
const button = document.createElement("button");
button.type = "button";
button.textContent = "Copy public-safe packet";
button.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(action.markdown);
button.textContent = "Copied";
window.setTimeout(() => {
button.textContent = "Copy public-safe packet";
}, 1400);
} catch {
button.textContent = "Copy failed";
}
});
actionsNode.appendChild(button);
continue;
}
if (action?.id === "view_private_blockers" && Array.isArray(action?.blockers)) {
const details = document.createElement("details");
const summary = document.createElement("summary");
summary.textContent = "Private blockers";
details.appendChild(summary);
const listNode = document.createElement("ul");
for (const blocker of action.blockers.slice(0, 8)) {
const item = document.createElement("li");
item.textContent = String(blocker?.detail ?? "");
listNode.appendChild(item);
}
details.appendChild(listNode);
actionsNode.appendChild(details);
}
}
body.appendChild(container);
}
80 changes: 80 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot }
import { fetchPublicContributorProfile } from "../github/public";
import { GITTENSORY_MENTION_COMMAND_CATALOG } from "../github/commands";
import { handleGitHubWebhook } from "../github/webhook";
import { sanitizePublicComment } from "../github/commands";
import { handleMcpRequest } from "../mcp/server";
import { buildOpenApiSpec } from "../openapi/spec";
import { generateSignalSnapshots } from "../queue/processors";
Expand Down Expand Up @@ -1107,6 +1108,24 @@ export function createApp() {
profile: contributorContext?.profile,
outcomeHistory: contributorContext?.outcomeHistory,
Comment thread
JSONbored marked this conversation as resolved.
});
const publicSafePacketMarkdown = buildExtensionPublicSafePacket({
repoFullName: fullName,
pullNumber,
reviewability,
contributor: contributor ?? "unknown",
});
const privateBlockers = buildExtensionPrivateBlockers(reviewability);
await recordAuditEvent(c.env, {
eventType: "extension.pull_context_view",
actor: contributor ?? "unknown",
route: c.req.path,
outcome: "success",
metadata: {
redacted: true,
hasPublicPacket: publicSafePacketMarkdown.length > 0,
blockerCount: privateBlockers.length,
},
});
await recordRouteProductUsage(c, {
surface: "browser_extension",
eventName: "pull_context_viewed",
Expand All @@ -1122,6 +1141,21 @@ export function createApp() {
repoFullName: fullName,
pullNumber,
reviewability,
actions: [
{
id: "copy_public_safe_packet",
label: "Copy public-safe packet",
visibility: "public_safe",
markdown: publicSafePacketMarkdown,
},
{
id: "view_private_blockers",
label: "View private blockers",
visibility: "private",
requiresAuth: true,
blockers: privateBlockers,
},
],
panels: [
{ label: "Reviewability", badge: reviewability.action, rows: [{ k: "action", v: reviewability.action }, { k: "score", v: String(reviewability.score) }] },
{ label: "Contributor", badge: contributor ?? "unknown", rows: [{ k: "author", v: contributor ?? "unknown" }, { k: "prs", v: String(contributorContext?.contributorPullRequests.length ?? 0) }] },
Expand Down Expand Up @@ -2687,3 +2721,49 @@ function normalizeOrigin(value: string | undefined): string | null {
return null;
}
}

function buildExtensionPublicSafePacket(args: { repoFullName: string; pullNumber: number; contributor: string; reviewability: { action: string; noiseSources: string[]; maintainerNextSteps: string[] } }): string {
const lines = [
"# Public-safe PR packet",
"",
"## Linked context",
`- Repository: ${args.repoFullName}`,
`- Pull request: #${args.pullNumber}`,
`- Contributor: ${args.contributor}`,
"",
"## Review readiness",
`- Current action: ${args.reviewability.action.replace(/_/g, " ")}`,
...args.reviewability.maintainerNextSteps.slice(0, 4).map((step) => `- ${step}`),
"",
"## Queue caution",
...(args.reviewability.noiseSources.length > 0
? args.reviewability.noiseSources.slice(0, 4).map((source) => `- ${source}`)
: ["- No high-noise warning is visible from cached metadata."]),
"",
"## Safety",
"- Keep public comments limited to linked context, validation status, and maintainer-ready next steps.",
];
const markdown = sanitizePublicComment(lines.join("\n"));
return ensureExtensionPublicSafeText(markdown);
}

function buildExtensionPrivateBlockers(reviewability: { noiseSources: string[]; maintainerNextSteps: string[]; privateSummary: string }) {
const items = [...reviewability.noiseSources.slice(0, 5), ...reviewability.maintainerNextSteps.slice(0, 3)];
if (items.length === 0) items.push("No private blocker detail is currently cached.");
return items.map((detail, index) => ({ id: `blocker-${index + 1}`, detail: sanitizePublicComment(detail) }));
}

function ensureExtensionPublicSafeText(text: string): string {
const compact = text.replace(/\s+/g, " ").trim();
if (/\b(wallet|hotkey|coldkey|raw trust score|trust score|estimated score|score estimate|reward estimate|payout|farming|private reviewability|reviewability\s*\d|\/100)\b/i.test(compact)) {
return "# Public-safe PR packet\n\n- Public-safe packet unavailable. Regenerate after private context is sanitized.";
}
return text;
}

export const __routesInternals = {
buildExtensionPublicSafePacket,
buildExtensionPrivateBlockers,
ensureExtensionPublicSafeText,
authenticateRequestIdentity,
};
4 changes: 4 additions & 0 deletions src/auth/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ function serializeCookie(name: string, value: string, options: CookieOptions): s
return parts.join("; ");
}

export const __securityInternals = {
serializeCookie,
};

function shouldUseSecureCookie(requestUrl: string): boolean {
try {
const hostname = new URL(requestUrl).hostname;
Expand Down
55 changes: 54 additions & 1 deletion test/integration/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,41 @@ describe("api routes", () => {
expect(minerWithEmptyFit.status).toBe(200);
await expect(minerWithEmptyFit.json()).resolves.toMatchObject({ status: "ready", repoFit: [] });

await persistSignalSnapshot(env, {
id: "lane-pack",
signalType: "contributor-decision-pack",
targetKey: "lane-user",
payload: {
status: "ready",
source: "computed",
login: "lane-user",
generatedAt: new Date().toISOString(),
stale: false,
freshness: "fresh",
rebuildEnqueued: false,
scoringModelSnapshotId: "scoring-1",
repoDecisions: [],
topActions: [],
pursueRepos: [{ repoFullName: "owner/pursue", recommendation: "watch" }],
cleanupFirst: [{ repoFullName: "owner/cleanup", recommendation: "cleanup_first" }],
maintainerLaneRepos: [{ repoFullName: "owner/maintainer", recommendation: "maintainer_lane" }],
avoidRepos: [{ repoFullName: "owner/avoid", recommendation: "avoid_for_now" }],
scoreBlockers: [],
dataQuality: { signalFidelity: { status: "ok" } },
} as never,
generatedAt: new Date().toISOString(),
});
const minerWithLaneBuckets = await app.request("/v1/app/miner-dashboard?login=lane-user", { headers: apiHeaders(env) }, env);
expect(minerWithLaneBuckets.status).toBe(200);
await expect(minerWithLaneBuckets.json()).resolves.toMatchObject({
repoFit: expect.arrayContaining([
expect.objectContaining({ repoFullName: "owner/pursue", lane: "pursue" }),
expect.objectContaining({ repoFullName: "owner/cleanup", lane: "cleanup-first" }),
expect.objectContaining({ repoFullName: "owner/maintainer", lane: "maintainer-lane" }),
expect.objectContaining({ repoFullName: "owner/avoid", lane: "avoid" }),
]),
});

await recordGitHubRateLimitObservation(env, {
id: "rate-limit-healthy",
repoFullName: "entrius/allways-ui",
Expand Down Expand Up @@ -1914,12 +1949,29 @@ describe("api routes", () => {
env,
);
expect(extensionContext.status).toBe(200);
await expect(extensionContext.json()).resolves.toMatchObject({
const extensionPayload = (await extensionContext.json()) as {
repoFullName: string;
pullNumber: number;
reviewability: { repoFullName: string; pullNumber: number };
actions: Array<{ id: string; markdown?: string; blockers?: Array<{ detail: string }> }>;
panels: Array<{ label: string }>;
};
expect(extensionPayload).toMatchObject({
repoFullName: "entrius/allways-ui",
pullNumber: 12,
reviewability: { repoFullName: "entrius/allways-ui", pullNumber: 12 },
actions: expect.arrayContaining([
expect.objectContaining({ id: "copy_public_safe_packet", visibility: "public_safe" }),
expect.objectContaining({ id: "view_private_blockers", visibility: "private", requiresAuth: true }),
]),
panels: expect.arrayContaining([expect.objectContaining({ label: "Reviewability" }), expect.objectContaining({ label: "Boundary" })]),
});
const packet = extensionPayload.actions.find((action) => action.id === "copy_public_safe_packet")?.markdown ?? "";
expect(packet).toContain("# Public-safe PR packet");
expect(packet).not.toMatch(/wallet|hotkey|coldkey|reward estimate|payout|farming|raw trust score|estimated score|score estimate|private reviewability/i);
const blockers = extensionPayload.actions.find((action) => action.id === "view_private_blockers")?.blockers ?? [];
expect(blockers.length).toBeGreaterThan(0);
expect(JSON.stringify(blockers)).not.toMatch(/wallet|hotkey|coldkey|payout|farming|guaranteed payout/i);

const missingPullContext = await app.request(
"/v1/extension/pull-context?owner=entrius&repo=allways-ui&pullNumber=99",
Expand All @@ -1930,6 +1982,7 @@ describe("api routes", () => {
await expect(missingPullContext.json()).resolves.toMatchObject({
repoFullName: "entrius/allways-ui",
pullNumber: 99,
actions: expect.arrayContaining([expect.objectContaining({ id: "copy_public_safe_packet" }), expect.objectContaining({ id: "view_private_blockers" })]),
panels: expect.arrayContaining([expect.objectContaining({ label: "Contributor", badge: "unknown" })]),
});

Expand Down
56 changes: 56 additions & 0 deletions test/unit/routes-extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import { __routesInternals } from "../../src/api/routes";
import { createSessionForGitHubUser } from "../../src/auth/security";
import { createTestEnv } from "../helpers/d1";

describe("extension packet helper internals", () => {
it("falls back when extension packet text contains forbidden public terms", () => {
const result = __routesInternals.ensureExtensionPublicSafeText("# Public-safe PR packet\n\n- reviewability 91/100");
expect(result).toContain("Public-safe packet unavailable");
});

it("keeps safe extension packet text unchanged", () => {
const text = "# Public-safe PR packet\n\n- Repository: owner/repo\n- Keep public comments focused on linked context.";
expect(__routesInternals.ensureExtensionPublicSafeText(text)).toBe(text);
});

it("builds private blocker fallback when no blocker signals are present", () => {
const blockers = __routesInternals.buildExtensionPrivateBlockers({
noiseSources: [],
maintainerNextSteps: [],
privateSummary: "",
});
expect(blockers).toEqual([{ id: "blocker-1", detail: "No private blocker detail is currently cached." }]);
});

it("sanitizes extension packet markdown before returning it", () => {
const markdown = __routesInternals.buildExtensionPublicSafePacket({
repoFullName: "owner/repo",
pullNumber: 12,
contributor: "alice",
reviewability: {
action: "review_now",
noiseSources: ["avoid payout language in public"],
maintainerNextSteps: ["remove wallet references"],
},
});
expect(markdown).toContain("# Public-safe PR packet");
expect(markdown).not.toMatch(/wallet|payout|hotkey|reward estimate|estimated score|raw trust score/i);
});

it("authenticates request identity from browser session cookie fallback", async () => {
const env = createTestEnv();
const { token } = await createSessionForGitHubUser(env, { login: "jsonbored", id: 7 });
const identity = await __routesInternals.authenticateRequestIdentity({
env,
req: {
header(name: string) {
if (name.toLowerCase() === "cookie") return `gittensory_session=${token}`;
return undefined;
},
},
json: (_payload: { error: string }, status?: number) => Response.json({}, status === undefined ? undefined : { status }),
});
expect(identity).toMatchObject({ kind: "session", actor: "jsonbored" });
});
});
28 changes: 28 additions & 0 deletions test/unit/security-internals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { __securityInternals } from "../../src/auth/security";

describe("security internals", () => {
it("serializes cookies with optional HttpOnly and Secure flags", () => {
const minimal = __securityInternals.serializeCookie("a", "b", {
maxAge: 10,
path: "/",
httpOnly: false,
sameSite: "Lax",
secure: false,
});
expect(minimal).toContain("a=b");
expect(minimal).not.toContain("HttpOnly");
expect(minimal).not.toContain("Secure");

const strict = __securityInternals.serializeCookie("a", "b", {
maxAge: 10,
path: "/",
httpOnly: true,
sameSite: "Strict",
secure: true,
});
expect(strict).toContain("HttpOnly");
expect(strict).toContain("Secure");
expect(strict).toContain("SameSite=Strict");
});
});