Public: anyone can read this; the crew can change it.
A trusted source's pull lands by true merge when its checks pass, with no review, credited in the changelog and the live stream #12066
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9015-a-trusted-source-s-pull-lands-by-true-me → main 2 commits fork 1 thread · 1 unresolved
2 files
changed +25
−2
src/feed/items.ts +21 −2
| @@ -599,6 +599,20 @@ function reviewedPost( | ||
| 599 | 599 | }; |
| 600 | 600 | } |
| 601 | 601 | |
| 602 | /** A landing's stamped source (`account@host`, `child:` prefix allowed). */ | |
| 603 | const SOURCE = /^(child:)?[a-z0-9][a-z0-9._-]{0,63}@[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?(:\d{1,5})?$/; | |
| 604 | ||
| 605 | /** | |
| 606 | * "folded in from [email protected], by jay": the source a trusted | |
| 607 | * landing came from and the one who wrote it there. Empty for an ordinary | |
| 608 | * landing, or a payload that is not a source's shape. | |
| 609 | */ | |
| 610 | function sourceWords(source: unknown, pull: Pull | undefined): string { | |
| 611 | if (typeof source !== "string" || !SOURCE.test(source)) return ""; | |
| 612 | const author = pull?.originAuthor ?? (pull?.authorIsAgent ? null : pull?.authorName) ?? null; | |
| 613 | return `folded in from ${source}${author === null ? "" : `, by ${clip(author)}`}`; | |
| 614 | } | |
| 615 | ||
| 602 | 616 | /** Up to the first sentence's end, at most 100 characters. */ |
| 603 | 617 | function firstSentence(body: string): string { |
| 604 | 618 | const text = body.trim().replace(/\s+/g, " "); |
| @@ -679,8 +693,13 @@ function fromEvent( | ||
| 679 | 693 | } |
| 680 | 694 | case "pull.opened": |
| 681 | 695 | return { ...common, kind: "pull", link: change, what: `opened a pull request${title ? `: ${clip(title)}` : ""}${toldWords}` }; |
| 682 | case "pull.merged": | |
| 683 | return { ...common, kind: "pull", link: change, what: `merged a pull request${title ? `: ${clip(title)}` : ""}${toldWords}` }; | |
| 696 | case "pull.merged": { | |
| 697 | // A trusted source's landing is credited as it happens (#9015): | |
| 698 | // where it came from, and whose work it is. | |
| 699 | const from = sourceWords(p["source"], pull); | |
| 700 | const words = from === "" ? toldWords : ` (${[toldParts, from].filter((s) => s !== "").join(", ")})`; | |
| 701 | return { ...common, kind: "pull", link: change, what: `merged a pull request${title ? `: ${clip(title)}` : ""}${words}` }; | |
| 702 | } | |
| 684 | 703 | case "pull.reviewed": |
| 685 | 704 | return reviewedPost(db, p, { ...common, kind: "pull", link: change }, title); |
| 686 | 705 | case "look.taken": { |
test/trusted-fold.test.ts +4
| @@ -10,6 +10,7 @@ import { tmpdir } from "node:os"; | ||
| 10 | 10 | import { join } from "node:path"; |
| 11 | 11 | import { openPull } from "../src/actions/pulls.ts"; |
| 12 | 12 | import { commitToBranch, createRepo, forkRepo } from "../src/actions/repos.ts"; |
| 13 | import { readFeed } from "../src/feed/feed.ts"; | |
| 13 | 14 | import { addTrust, setFoldInStopped } from "../src/actions/trusted_sources.ts"; |
| 14 | 15 | import { createApp, type App } from "../src/app.ts"; |
| 15 | 16 | import { provideChecksForTest, type ChecksHandle } from "../src/checks/service.ts"; |
| @@ -155,6 +156,9 @@ describe("the tick's sweep admits a trusted source's pull", () => { | ||
| 155 | 156 | const dir = repoDir(f.app.reposRoot, f.repo.ownerName, f.repo.name); |
| 156 | 157 | const parents = (await runGitCapture(["log", "-1", "--format=%P", "refs/heads/main"], { cwd: dir })).stdout.trim().split(" "); |
| 157 | 158 | expect(parents.length).toBe(2); |
| 159 | // The home stream credits the landing as it happens: its source and its author (#9015). | |
| 160 | const post = readFeed(f.app.db, f.alice, { limit: 50 }).items.find((i) => i.link.endsWith(`/pulls/${pull.number}`)); | |
| 161 | expect(post?.what).toContain(`folded in from jmoran@${FROM}, by outsider`); | |
| 158 | 162 | }); |
| 159 | 163 | |
| 160 | 164 | test("a pull with no check yet, or a red one, goes back", async () => { |
new above
offline
Nit: the 64-char account part and 253-char host are fine, but consider reusing any existing source-shape validator rather than a second regex, so the two cannot drift.