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

The no_release alarm fires on any repository's oldest merge, so it always claims nothing has gone live #8687

Merged opened this in answer to

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

Merged

mason/forge:mason/7638-the-no-release-alarm-fires-on-any-reposi → main 2 commits fork

  • Merged. mason/7638-the-no-release-alarm-fires-on-any-reposi was merged into main as 154b825.

Review

Sign in to review this change.
4 files changed +40 −8
src/release/landings.ts +2 −1
@@ -99,7 +99,8 @@ export function forgetLive(): void {
9999
100100 function isLive(row: MergedRow, state: LiveState | undefined): boolean {
101101 if (state === undefined || row.repo.toLowerCase() !== state.own.toLowerCase()) return true;
102 return row.oid === "" ? Date.parse(row.at) <= state.at : state.oids.has(row.oid);
102 // A merge made before the running commit is behind it, though git was not asked about it (or answered no for a rewritten history).
103 return Date.parse(row.at) <= state.at || (row.oid !== "" && state.oids.has(row.oid));
103104 }
104105
105106 /**
test/changelog.test.ts +3 −2
@@ -65,7 +65,8 @@ describe("changelog page", () => {
6565 const day = new Date().toISOString().slice(0, 10);
6666 merge("forge", "First <b>thing</b>", `${day}T01:00:00.000Z`, oids[0]!);
6767 merge("forge", "Second thing", `${day}T02:00:00.000Z`, oids[1]!);
68 merge("forge", "Third thing", `${day}T03:00:00.000Z`, oids[2]!);
68 // Not before the running commit was made, or it would be behind it.
69 merge("forge", "Third thing", `${day}T23:59:59.000Z`, oids[2]!);
6970 merge("notes", "A note app change", `${day}T02:30:00.000Z`, "");
7071 token = createToken(app.db, alice.id, "rw", "rw").token;
7172 }, 60_000);
@@ -174,7 +175,7 @@ describe("changelog page", () => {
174175 const fork = must(await createRepo(app, alice, { name: "fork", visibility: "public" }));
175176 const c = must(await commitToBranch(app, alice, fork, { message: "fork work", files: [{ path: "x.txt", content: "x\n" }] }));
176177 const day = new Date().toISOString().slice(0, 10);
177 merge("fork", "Fork change", `${day}T23:00:00.000Z`, c.commitOid);
178 merge("fork", "Fork change", `${day}T23:59:59.000Z`, c.commitOid);
178179 delete app.config.forgeRepo;
179180 forgetLive();
180181 process.env["FORGE_COMMIT"] = c.commitOid;
test/flow-alarm-no-release.test.ts +33 −4
@@ -7,7 +7,7 @@ import { createApp } from "../src/app.ts";
77 import { createRepo } from "../src/core/repos.ts";
88 import { createUser } from "../src/core/users.ts";
99 import { id } from "../src/kernel/id.ts";
10 import { forgetLive, keepLive } from "../src/release/landings.ts";
10 import { forgetLive, keepLive, split, waitingSince } from "../src/release/landings.ts";
1111 import { noReleaseAlarm } from "../src/flow/alarm_no_release.ts";
1212
1313 const NOW = new Date("2026-10-26T12:00:00.000Z");
@@ -19,16 +19,19 @@ async function seed() {
1919 if (!u.ok) throw new Error(u.error);
2020 const repo = await createRepo(app.db, app.reposRoot, { ownerId: u.value.id, name: "r", visibility: "public", description: "r" });
2121 if (!repo.ok) throw new Error(repo.error);
22 const other = await createRepo(app.db, app.reposRoot, { ownerId: u.value.id, name: "other", visibility: "public", description: "o" });
23 if (!other.ok) throw new Error(other.error);
2224 let n = 0;
2325 return {
2426 app,
2527 /** The running commit was made at `at`: it contains every merge before it. */
2628 release: (at: string) => keepLive("alice/r", [], Date.parse(at)),
27 merge: (at: string) => {
29 merge: (at: string, oid?: string, into: "r" | "other" = "r") => {
30 const target = into === "r" ? repo.value : other.value;
2831 const issueId = id();
2932 n++;
30 app.db.query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state, created_at) VALUES (?1, ?2, ?3, 'pull', 'P', '', ?4, 'closed', ?5)`).run(issueId, repo.value.id, n, u.value.id, at);
31 app.db.query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id, merged_at) VALUES (?1, ?2, ?3, '', '', ?4, ?5)`).run(issueId, repo.value.defaultBranch, `b/${n}`, repo.value.id, at);
33 app.db.query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state, created_at) VALUES (?1, ?2, ?3, 'pull', 'P', '', ?4, 'closed', ?5)`).run(issueId, target.id, n, u.value.id, at);
34 app.db.query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id, merged_at, merged_oid) VALUES (?1, ?2, ?3, '', '', ?4, ?5, ?6)`).run(issueId, target.defaultBranch, `b/${n}`, target.id, at, oid ?? null);
3235 },
3336 };
3437 }
@@ -62,4 +65,30 @@ describe("no release alarm", () => {
6265 t.release(minAgo(100));
6366 expect(noReleaseAlarm(t.app.db, NOW).fired).toBe(false);
6467 });
68 test("stays quiet with an empty release record: a merge older than the running commit is behind it", async () => {
69 const s = await seed();
70 s.merge(minAgo(4000), "cafe");
71 s.release(minAgo(10));
72 expect(noReleaseAlarm(s.app.db, NOW).fired).toBe(false);
73 expect(waitingSince(s.app.db)).toBeUndefined();
74 });
75 test("a merge in another repository never fires it", async () => {
76 const s = await seed();
77 s.merge(minAgo(4000), "beef", "other");
78 s.release(minAgo(10));
79 expect(noReleaseAlarm(s.app.db, NOW).fired).toBe(false);
80 });
81 test("a git miss merged after the running commit still fires it", async () => {
82 const s = await seed();
83 s.merge(minAgo(91), "cafe");
84 s.release(minAgo(200));
85 expect(noReleaseAlarm(s.app.db, NOW).fired).toBe(true);
86 });
87 test("split counts a miss older than the running commit live, so /changelog agrees", () => {
88 const row = (n: number, at: string) => ({ repo: "alice/r", number: n, title: "P", at, oid: `o${n}` });
89 const state = { own: "alice/r", oids: new Set<string>(), misses: new Set(["o1", "o2"]), at: Date.parse(minAgo(100)), commit: "c" };
90 const { live, onItsWay } = split([row(2, minAgo(50)), row(1, minAgo(500))], state);
91 expect(live.map((l) => l.number)).toEqual([1]);
92 expect(onItsWay.map((l) => l.number)).toEqual([2]);
93 });
6594 });
test/landings-asked-once.test.ts +2 −1
@@ -45,7 +45,8 @@ describe("landings ask git once per merge commit", () => {
4545 let n = 0;
4646 const merge = (oid: string) => {
4747 n++;
48 const at = new Date(Date.now() - (10 - n) * 60_000).toISOString();
48 // After the running commit was made, or it would already contain the merge.
49 const at = new Date(Date.now() + n * 60_000).toISOString();
4950 const issueId = id();
5051 app.db.query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state, created_at) VALUES (?1, ?2, ?3, 'pull', 'P', '', ?4, 'closed', ?5)`).run(issueId, repo.value.id, n, u.value.id, at);
5152 app.db.query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id, merged_at, merged_oid) VALUES (?1, ?2, ?3, '', '', ?4, ?5, ?6)`).run(issueId, repo.value.defaultBranch, `b/${n}`, repo.value.id, at, oid);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs