Public: anyone can read this; the crew can change it.

A trusted source on this same forge folds in like one on another forge #9552

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/9450-a-trusted-source-on-this-same-forge-fold → main 1 commit fork

  • Merged. mason/9450-a-trusted-source-on-this-same-forge-fold was merged into main as a50392f.

Review

Sign in to review this change.
3 files changed +156 −4
src/app.ts +2
@@ -4,6 +4,7 @@
44 * side-effect-contained, so tests boot real forges by the dozen.
55 */
66
7 import { ownHost, setOwnHost } from "./core/trusted_sources.ts";
78 import { commitIdentity } from "./core/commit_identity.ts";
89 import { getUserById } from "./core/users.ts";
910 import type { MailConfig, Mailer } from "./kernel/mail.ts";
@@ -239,6 +240,7 @@ export function createApp(config: ForgeConfig): App {
239240 const dbPath = join(dataDir, "forge.db");
240241 const fresh = !existsSync(dbPath);
241242 const db = openDb(dbPath, migrationFiles);
243 setOwnHost(db, ownHost(config));
242244 if (config.seedSignup === true) seedSignup(db, fresh);
243245 // Hand-installed new-account guards become the `guard_new_accounts`
244246 // setting (default off): drop the legacy triggers once, here, on boot.
src/core/trusted_sources.ts +34 −1
@@ -275,8 +275,41 @@ export function pullIdsFrom(db: Database, host: string, account: string): string
275275 * person's review and nothing else.
276276 */
277277 export function foldsIn(db: Database, pullId: string, now: Date = new Date()): boolean {
278 if (foldInStopped(db) !== undefined) return false;
278279 const from = pullSourceOf(db, pullId);
279 return from !== undefined && foldInStopped(db) === undefined && isTrusted(db, from.host, from.account, now);
280 if (from !== undefined) return isTrusted(db, from.host, from.account, now);
281 return localSourceTrusted(db, pullId, now);
282 }
283
284 // --- trust for work opened on this same forge --------------------------------
285
286 const selfHosts = new WeakMap<Database, string>();
287
288 /** Tell the trust layer which name this forge goes by in trust lists (called once, where the app is made). */
289 export function setOwnHost(db: Database, host: string): void {
290 selfHosts.set(db, host.toLowerCase());
291 }
292
293 /**
294 * A pull opened here, from the opener's own fork on this forge (not a
295 * mirror's), by an account trusted for this forge's own host. Trust is read
296 * now, so a lapse or a removal makes the next question answer no.
297 */
298 function localSourceTrusted(db: Database, pullId: string, now: Date): boolean {
299 const self = selfHosts.get(db);
300 if (self === undefined) return false;
301 const row = db
302 .query<{ account: string }, [string]>(
303 `SELECT u.username AS account FROM issue i
304 JOIN pull p ON p.issue_id = i.id
305 JOIN user u ON u.id = i.author_id
306 JOIN repo h ON h.id = p.head_repo_id
307 WHERE i.id = ?1 AND i.kind = 'pull'
308 AND p.head_repo_id <> i.repo_id AND p.origin_head_repo IS NULL
309 AND h.owner_id = i.author_id AND h.fork_parent_id = i.repo_id`,
310 )
311 .get(pullId);
312 return row !== null && isTrusted(db, self, row.account, now);
280313 }
281314
282315 /**
test/trusted-fold.test.ts +120 −3
@@ -9,16 +9,17 @@ import { mkdtemp, rm } from "node:fs/promises";
99 import { tmpdir } from "node:os";
1010 import { join } from "node:path";
1111 import { openPull } from "../src/actions/pulls.ts";
12 import { commitToBranch, createRepo } from "../src/actions/repos.ts";
12 import { commitToBranch, createRepo, forkRepo } from "../src/actions/repos.ts";
1313 import { addTrust, setFoldInStopped } from "../src/actions/trusted_sources.ts";
1414 import { createApp, type App } from "../src/app.ts";
1515 import { provideChecksForTest, type ChecksHandle } from "../src/checks/service.ts";
1616 import { createSession } from "../src/core/sessions.ts";
17 import { pullMayRun } from "../src/core/pull_trust.ts";
1718 import { getPull, type Pull } from "../src/core/pulls.ts";
1819 import { getRepoById, type Repo } from "../src/core/repos.ts";
1920 import { claimRun, createRun, finishRun } from "../src/core/runs.ts";
20 import { ownHost, recordPullSource, takeable, viaOf } from "../src/core/trusted_sources.ts";
21 import { createUser, type User } from "../src/core/users.ts";
21 import { foldsIn, ownHost, recordPullSource, takeable, viaOf } from "../src/core/trusted_sources.ts";
22 import { createUser, getUserById, type User } from "../src/core/users.ts";
2223 import { landingStrategy, sweepGreen } from "../src/crew/autonomy.ts";
2324 import { runGitCapture } from "../src/git/env.ts";
2425 import { repoDir } from "../src/git/repos.ts";
@@ -289,3 +290,119 @@ describe("the landing reads the clock it is given", () => {
289290 expect(await landingStrategy(f.app, f.repo, pull, base, new Date("2099-12-03T00:00:00Z"))).toBe("squash");
290291 });
291292 });
293
294 describe("a trusted account working on this same forge (#9450)", () => {
295 let f: Forge;
296 let open: Repo;
297 let bot: User;
298 let carol: User;
299 let n = 0;
300 const HOME = "forge.test";
301
302 beforeAll(async () => {
303 f = await forge(HOME);
304 const made = await createRepo(f.app, f.alice, { name: "open", visibility: "public" });
305 if (!made.ok) throw new Error(String(made.error));
306 const seeded = await commitToBranch(f.app, f.alice, made.value, {
307 message: "Start",
308 files: [
309 { path: "README.md", content: "# open\n" },
310 { path: "forge.json", content: JSON.stringify({ check: { run: "true", fast: "true" } }) },
311 ],
312 });
313 if (!seeded.ok) throw new Error(JSON.stringify(seeded.error));
314 f.app.db.query("UPDATE repo SET autonomy = 'autonomous', autonomy_by = ?2 WHERE id = ?1").run(made.value.id, f.alice.id);
315 open = getRepoById(f.app.db, made.value.id)!;
316 const people: User[] = [];
317 for (const username of ["grokbot", "carol"]) {
318 const u = await createUser(f.app.db, {
319 origin: "seed",
320 trusted: true,
321 username,
322 email: `${username}@forge.local`,
323 password: "a-good-passphrase",
324 });
325 if (!u.ok) throw new Error(u.error);
326 f.app.db.query("UPDATE user SET created_at = ?2 WHERE id = ?1").run(u.value.id, "2020-01-01T00:00:00.000Z");
327 people.push(getUserById(f.app.db, u.value.id)!);
328 }
329 [bot, carol] = people as [User, User];
330 const t = addTrust(f.app, f.alice, { host: HOME, account: "grokbot", until: "2099-12-02" });
331 if (!t.ok) throw new Error(t.error);
332 });
333
334 /** `who`'s pull from their own fork of `open`, with a green check. */
335 async function forkPull(who: User, file = `src/x${++n}.ts`): Promise<Pull> {
336 const forks = f.app.db
337 .query<{ id: string }, [string, string]>("SELECT id FROM repo WHERE owner_id = ?1 AND fork_parent_id = ?2")
338 .get(who.id, open.id);
339 let fork = forks === null ? undefined : getRepoById(f.app.db, forks.id);
340 if (fork === undefined) {
341 const forked = await forkRepo(f.app, who, open, `${who.username}-open`);
342 if (!forked.ok) throw new Error(String(forked.error));
343 fork = forked.value;
344 }
345 const branch = `w${++n}`;
346 const pushed = await commitToBranch(f.app, who, fork, {
347 branch,
348 message: `Add ${file}`,
349 files: [{ path: file, content: "export const n = 1;\n" }],
350 });
351 if (!pushed.ok) throw new Error(JSON.stringify(pushed.error));
352 const opened = await openPull(f.app, who, open, { title: branch, baseBranch: "main", headBranch: branch, headRepo: fork });
353 if (!opened.ok) throw new Error(String(opened.error));
354 const pull = getPull(f.app.db, open.id, opened.value.number)!;
355 const run = createRun(f.app.db, {
356 repoId: open.id,
357 kind: "check",
358 issueId: pull.id,
359 env: `pull-${pull.number}`,
360 commitOid: pull.headOid,
361 reason: "pull",
362 });
363 claimRun(f.app.db, run.id);
364 finishRun(f.app.db, run.id, { state: "ok", summary: "ok" });
365 return pull;
366 }
367
368 const landed = (pull: Pull): boolean => getPull(f.app.db, open.id, pull.number)!.mergedAt !== null;
369
370 test("its pull from its own fork runs its checks and folds in by true merge", async () => {
371 const pull = await forkPull(bot);
372 expect(pullMayRun(f.app.db, open, pull, pull.headOid)).toBe(true);
373 expect(await sweepGreen(f.app, open)).toBe(1);
374 expect(landed(pull)).toBe(true);
375 const dir = repoDir(f.app.reposRoot, open.ownerName, open.name);
376 const parents = (await runGitCapture(["log", "-1", "--format=%P", "refs/heads/main"], { cwd: dir })).stdout.trim().split(" ");
377 expect(parents.length).toBe(2);
378 });
379
380 test("the walls still hold it for a person", async () => {
381 const pull = await forkPull(bot, "src/crew/fold_walls.ts");
382 expect(await sweepGreen(f.app, open)).toBe(0);
383 expect(landed(pull)).toBe(false);
384 });
385
386 test("a guest's pull still waits for approval", async () => {
387 const pull = await forkPull(carol);
388 expect(pullMayRun(f.app.db, open, pull, pull.headOid)).toBe(false);
389 expect(await sweepGreen(f.app, open)).toBe(0);
390 expect(landed(pull)).toBe(false);
391 });
392
393 test("stopping fold-in makes the next pull wait again", async () => {
394 const pull = await forkPull(bot);
395 setFoldInStopped(f.app, f.alice, true);
396 expect(pullMayRun(f.app.db, open, pull, pull.headOid)).toBe(false);
397 expect(await sweepGreen(f.app, open)).toBe(0);
398 setFoldInStopped(f.app, f.alice, false);
399 expect(await sweepGreen(f.app, open)).toBe(1);
400 });
401
402 test("a lapsed trust makes the next pull wait again", async () => {
403 const pull = await forkPull(bot);
404 expect(await sweepGreen(f.app, open, new Date("2100-01-01T00:00:00Z"))).toBe(0);
405 expect(landed(pull)).toBe(false);
406 expect(foldsIn(f.app.db, pull.id, new Date("2100-01-01T00:00:00Z"))).toBe(false);
407 });
408 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs