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

A test pins which finished runs the feed folds as quiet: nothing pushed, opened or said in the run's window, and never a run the record cannot find #6710

Merged opened this

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

Merged

scout/forge:scout/6701-a-test-pins-which-finished-runs-the-feed → main 2 commits fork 4 threads · 4 unresolved

  • Merged. scout/6701-a-test-pins-which-finished-runs-the-feed was merged into main as ffd89c2.

Review

Sign in to review this change.
1 file changed +117 −1
test/feed-quiet.test.ts +117 −1
@@ -2,7 +2,7 @@
22 * Quiet runs: an agent run that ended without failing and left no push,
33 * pull, comment or issue is out of the timeline and counted in one line.
44 */
5 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
5 import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
66 import { mkdtemp, rm } from "node:fs/promises";
77 import { tmpdir } from "node:os";
88 import { join } from "node:path";
@@ -15,6 +15,8 @@ import { forkRepo, type Repo } from "../src/core/repos.ts";
1515 import { createUser, type User } from "../src/core/users.ts";
1616 import { serve, type Servers } from "../src/http/server.ts";
1717 import { readFeed, quietRunCount } from "../src/feed/feed.ts";
18 import { quietRunEventIds } from "../src/feed/quiet.ts";
19 import type { ForgeEvent } from "../src/core/events.ts";
1820 import { candidatesFor } from "../src/feed/items.ts";
1921 import { noChips } from "../src/core/feed_chips.ts";
2022 import { mcpTool } from "./helpers/mcp.ts";
@@ -181,3 +183,117 @@ describe("quiet runs", () => {
181183 expect(plan).toContain("event_repo_actor_at");
182184 });
183185 });
186
187 describe("quietRunEventIds", () => {
188 const START = "2026-01-01T10:00:00.000Z";
189 const END = "2026-01-01T10:10:00.000Z";
190 const INSIDE = "2026-01-01T10:05:00.000Z";
191 let dataDir: string;
192 let app: App;
193 let agent: User;
194 let repo: Repo;
195 let fork: Repo;
196
197 const mkUser = async (username: string, first = false) => {
198 const r = await createUser(app.db, {
199 origin: "seed",
200 trusted: true,
201 username,
202 allowReserved: true,
203 email: `${username}@forge.local`,
204 password: "a-good-passphrase",
205 adminIfFirst: first,
206 });
207 if (!r.ok) throw new Error(r.error);
208 return r.value;
209 };
210
211 /** A finished agent run with its window pinned; returns its `run.finished` event. */
212 const run = (state: "ok" | "failed" = "ok", gone = false): ForgeEvent => {

Every run shares START..END, and events from earlier tests stay in the log, so only the first 'not quiet' case can tell a broken classifier from a working one. Clear the non-run.finished events before each test (beforeEach), or give each run its own window.

213 const r = createRun(app.db, { repoId: repo.id, kind: "agent", actorId: agent.id });
214 claimRun(app.db, r.id);
215 finishRun(app.db, r.id, { state, summary: "x", costUsd: 0 });
216 app.db
217 .query("UPDATE run SET started_at = ?1, finished_at = ?2 WHERE id = ?3")
218 .run(START, END, r.id);
219 if (gone) app.db.query("DELETE FROM run WHERE id = ?1").run(r.id);
220 return {
221 id: `fin-${r.id}`,
222 kind: "run.finished",
223 repoId: repo.id,
224 actorId: agent.id,
225 payload: { runId: r.id, kind: "agent", state, summary: "x", costUsd: 0 },
226 createdAt: END,
227 };
228 };
229
230 const wrote = (kind: string, at: string, repoId = repo.id) => {
231 app.db
232 .query(
233 "INSERT INTO event (id, kind, repo_id, actor_id, payload, created_at) VALUES (?1, ?2, ?3, ?4, '{}', ?5)",
234 )
235 .run(`w-${Math.random().toString(36).slice(2)}`, kind, repoId, agent.id, at);
236 };
237
238 const isQuiet = (e: ForgeEvent) => quietRunEventIds(app.db, [e]).has(e.id);
239
240 beforeAll(async () => {
241 dataDir = await mkdtemp(join(tmpdir(), "forge-quiet-unit-"));
242 app = createApp({ dataDir, port: 0 });
243 const owner = await mkUser("owner", true);
244 agent = await mkUser("agent");
245 const r = await createRepo(app, owner, { name: "unit", visibility: "public" });
246 if (!r.ok) throw new Error("repo");
247 repo = r.value;
248 const f = await forkRepo(app.db, app.reposRoot, repo, agent.id);
249 if (!f.ok) throw new Error("fork");
250 fork = f.value;
251 });
252
253 afterAll(async () => {
254 app.close();
255 await rm(dataDir, { recursive: true, force: true });
256 });
257
258 // Every case starts from an empty event log, so a `false` can only come from its own cause.
259 beforeEach(() => {
260 app.db.query("DELETE FROM event WHERE kind != 'run.finished'").run();
261 });
262
263 test("a finished ok run whose agent wrote nothing in its window is quiet", () => {
264 expect(isQuiet(run())).toBe(true);
265 });
266
267 const cases: [string, string, () => string][] = [
268 ["a push to the repository", "push", () => repo.id],
269 ["a push to the agent's fork", "push", () => fork.id],
outdated

After the first iteration, the repository push it inserted at INSIDE already makes every later run not quiet. The fork, pull, comment and issue cases passed against a quiet.ts with those kinds removed and the fork lookup gone.

270 ["a pull", "pull.opened", () => repo.id],
271 ["a comment", "issue.commented", () => repo.id],
272 ["an issue", "issue.opened", () => repo.id],
273 ];
274 for (const [name, kind, where] of cases)
275 test(`${name} in the window makes the run not quiet`, () => {
276 const e = run();
outdated

This passes even with the state === "failed" check deleted, because earlier cases left events in the window. Start from an empty event log so false can only come from the state.

277 expect(isQuiet(e)).toBe(true);
278 wrote(kind, INSIDE, where());
279 expect(isQuiet(e)).toBe(false);
280 });
outdated

Same problem: leftover events make this false whether or not the missing row is handled. With the window clear, it pins the run === undefined guard.

281
282 test("a failed run is never quiet", () => {
283 expect(isQuiet(run("ok"))).toBe(true);
284 expect(isQuiet(run("failed"))).toBe(false);
285 });
286
287 test("a run.finished whose run row is gone is never quiet", () => {
288 expect(isQuiet(run("ok"))).toBe(true);
289 expect(isQuiet(run("ok", true))).toBe(false);
290 });
291
292 test("something written just outside the window does not stop a run being quiet", () => {
293 const e = run();
294 // The window is closed at both ends; neighbours of a run are not its work.
295 wrote("push", "2026-01-01T09:59:59.999Z");
296 wrote("issue.opened", "2026-01-01T10:10:00.001Z");
297 expect(isQuiet(e)).toBe(true);
298 });
299 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs