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

A branch already on the base opens no pull: 97 empty pulls in 5 minutes must not happen #8631

Merged opened this in answer to

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

Merged

mason/forge:mason/8628-a-branch-already-on-the-base-opens-no-pu → main 6 commits fork 2 threads · 2 unresolved

  • Merged. mason/8628-a-branch-already-on-the-base-opens-no-pu was merged into main as 4e48172.

Review

Sign in to review this change.
14 files changed +209 −22
src/core/migration-pins/20261102180000-pull-carried.pin added +1
@@ -0,0 +1,1 @@
1 72334d5a3c674352e7ffd37575f6f58ffc2453eee7e3b756f2d3fea0375051c2
src/core/migrations/20261102180000-pull-carried.ts added +6
@@ -0,0 +1,6 @@
1 // A pull closed as already on the base landed nothing (#8628): the vitals and
2 // the home KPIs count a landing only where this is 0. Every pull before is 0.
3 // Additive.
4 export default `
5 ALTER TABLE pull ADD COLUMN carried INTEGER NOT NULL DEFAULT 0;
6 `;
src/core/pulls.ts +12 −6
@@ -16,12 +16,12 @@ import { refTip } from "../git/read.ts";
1616 import { readTips } from "../git/tips.ts";
1717 import {
1818 aheadCount,
19 contentOnBase,
1920 fetchInto,
2021 merge,
2122 mergeBase,
2223 mergeable,
2324 resolveOid,
24 treeOf,
2525 writeRef,
2626 type MergeError,
2727 type MergeStrategy,
@@ -1129,6 +1129,7 @@ export async function mergePull(
11291129 actorId: input.actorId,
11301130 baseOid: status.baseOid,
11311131 headOid: status.headOid,
1132 carried: true,
11321133 });
11331134 return ok({ mergeOid: carrier, carried: true });
11341135 }
@@ -1189,9 +1190,7 @@ async function carriedBy(
11891190 ): Promise<string | undefined> {
11901191 if (strategy !== "squash" || status.ahead === 0 || !status.mergeable)
11911192 return undefined;
1192 const merged = await mergeable(dir, status.baseOid, status.headOid);
1193 if (!merged.ok) return undefined;
1194 if (merged.value.treeOid !== (await treeOf(dir, status.baseOid)))
1193 if (!(await contentOnBase(dir, status.baseOid, status.headOid)))
11951194 return undefined;
11961195 const landing = await resolveOid(dir, `refs/pull/${pull.number}/merged`);
11971196 if (
@@ -1207,12 +1206,18 @@ async function carriedBy(
12071206 function closeAsMerged(
12081207 db: Database,
12091208 pullId: string,
1210 landed: { mergeOid: string; actorId: string; baseOid: string; headOid: string },
1209 landed: {
1210 mergeOid: string;
1211 actorId: string;
1212 baseOid: string;
1213 headOid: string;
1214 carried?: boolean;
1215 },
12111216 ): void {
12121217 const now = new Date().toISOString();
12131218 transact(db, () => {
12141219 db.query(
1215 "UPDATE pull SET merged_at = ?1, merged_oid = ?2, merged_by = ?3, base_oid = ?4, head_oid = ?5 WHERE issue_id = ?6 AND merged_at IS NULL",
1220 "UPDATE pull SET merged_at = ?1, merged_oid = ?2, merged_by = ?3, base_oid = ?4, head_oid = ?5, carried = ?7 WHERE issue_id = ?6 AND merged_at IS NULL",
12161221 ).run(
12171222 now,
12181223 landed.mergeOid,
@@ -1220,6 +1225,7 @@ function closeAsMerged(
12201225 landed.baseOid,
12211226 landed.headOid,
12221227 pullId,
1228 landed.carried === true ? 1 : 0,
12231229 );
12241230 setIssueState(db, pullId, "closed");
12251231 });
src/crew/early.ts +10 −1
@@ -21,7 +21,7 @@ import { listReviews } from "../core/reviews.ts";
2121 import type { Repo } from "../core/repos.ts";
2222 import { getUserById } from "../core/users.ts";
2323 import type { Run } from "../core/runs.ts";
24 import { aheadCount, resolveOid } from "../git/merge.ts";
24 import { aheadCount, contentOnBase, resolveOid } from "../git/merge.ts";
2525
2626 /** How often the branch is looked at at first. */
2727 export const WATCH_MS = 1000;
@@ -112,6 +112,8 @@ export function watchForPull(input: EarlyInput): EarlyPull {
112112 const log = `${input.forgeUrl}/${input.repo.ownerName}/${input.repo.name}/runs/${input.run.id}`;
113113 let done = input.continuing;
114114 let busy: Promise<void> = Promise.resolve();
115 // The (base, head) pair last found level in content: judged once, not every tick.
116 let level = "";
115117 const tick = async (): Promise<void> => {
116118 if (done) return;
117119 const head = await resolveOid(input.forkBare, `refs/heads/${input.branch}`);
@@ -119,6 +121,13 @@ export function watchForPull(input: EarlyInput): EarlyPull {
119121 const base = await resolveOid(input.bare, `refs/heads/${input.repo.defaultBranch}`);
120122 if (base === undefined || done) return;
outdated

This runs a merge-tree every WATCH_MS for a content-level branch, since done stays false. Remember the last (base, head) judged and skip when unchanged.

121123 if ((await aheadCount(input.forkBare, base, head)) === 0 || done) return;
124 // Ahead by commits, level in content: a squash already carried it.
125 if (level === `${base} ${head}`) return;
126 if (await contentOnBase(input.forkBare, base, head)) {
127 level = `${base} ${head}`;
128 return;
129 }
130 if (done) return;
122131 // Set before the open, so a failed open is not retried every second:
123132 // `landWork` opens the pull at the run's end when this did not.
124133 done = true;
src/crew/runner.ts +4 −2
@@ -80,7 +80,7 @@ import { cloneWithMirror } from "./mirror.ts";
8080 import { isSafeRef } from "../git/guard.ts";
8181 import { runGitCapture } from "../git/env.ts";
8282 import { looksForEnv } from "../core/looks.ts";
83 import { aheadCount, resolveOid } from "../git/merge.ts";
83 import { aheadCount, contentOnBase, resolveOid } from "../git/merge.ts";
8484 import { repoDir } from "../git/repos.ts";
8585 import { bar } from "../cloud/gate.ts";
8686 import { declarationsOf } from "../cloud/declared.ts";
@@ -2109,7 +2109,9 @@ async function landWork(
21092109 // alternates and its own on top.
21102110 const proposed =
21112111 remote !== undefined && base !== undefined
2112 ? await aheadCount(forkBare, base, remote)
2112 ? (await contentOnBase(forkBare, base, remote))

No test covers this branch. Add a landWork test (content-level fork branch proposes 0, opens no pull) that fails if this hunk is reverted.

2113 ? 0
2114 : await aheadCount(forkBare, base, remote)
21132115 : 0;
21142116 const report = [message.trim(), note]
21152117 .filter((part): part is string => part !== undefined && part !== "")
src/flow/clog_tick.ts +1 −1
@@ -63,7 +63,7 @@ function twoHours(db: Database, now: Date): { opened: number; landed: number } {
6363 (SELECT COUNT(*) FROM pull p JOIN issue i ON i.id = p.issue_id
6464 WHERE i.created_at >= ?1 AND i.created_at <= ?2) AS opened,
6565 (SELECT COUNT(*) FROM pull p JOIN repo r ON r.id = (SELECT repo_id FROM issue WHERE id = p.issue_id)
66 WHERE p.base_branch = r.default_branch AND p.merged_at >= ?1 AND p.merged_at <= ?2) AS landed`,
66 WHERE p.base_branch = r.default_branch AND p.carried = 0 AND p.merged_at >= ?1 AND p.merged_at <= ?2) AS landed`,
6767 )
6868 .get(from, to);
6969 return { opened: row?.opened ?? 0, landed: row?.landed ?? 0 };
src/flow/keeper_rate.ts +2 −2
@@ -19,7 +19,7 @@ export function landedBetween(db: Database, fromMs: number, toMs: number): numbe
1919 const row = db
2020 .query<{ n: number }, [string, string]>(
2121 `SELECT COUNT(*) AS n FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id
22 WHERE p.base_branch = r.default_branch AND p.merged_at >= ?1 AND p.merged_at <= ?2`,
22 WHERE p.base_branch = r.default_branch AND p.carried = 0 AND p.merged_at >= ?1 AND p.merged_at <= ?2`,
2323 )
2424 .get(new Date(fromMs).toISOString(), new Date(toMs).toISOString());
2525 return row?.n ?? 0;
@@ -35,7 +35,7 @@ export function hourly(db: Database, now: number, hours: number): { landed: numb
3535 const merged = db
3636 .query<{ at: string }, [string, string]>(
3737 `SELECT p.merged_at AS at FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id
38 WHERE p.base_branch = r.default_branch AND p.merged_at >= ?1 AND p.merged_at <= ?2`,
38 WHERE p.base_branch = r.default_branch AND p.carried = 0 AND p.merged_at >= ?1 AND p.merged_at <= ?2`,
3939 )
4040 .all(since, until);
4141 for (const m of merged) {
src/flow/kpis.ts +1 −1
@@ -89,7 +89,7 @@ function firstLandings(db: Database, since: string, to: string): string[] {
8989 .query<{ repo: string; oid: string; at: string; issue: string; landing: number }, [string, string]>(
9090 `SELECT i.repo_id AS repo, q.merged_oid AS oid, q.merged_at AS at, q.issue_id AS issue,
9191 (q.base_branch = r.default_branch AND q.merged_at >= ?1
92 AND q.merged_oid != q.base_oid) AS landing
92 AND q.carried = 0 AND q.merged_oid != q.base_oid) AS landing
9393 FROM pull q JOIN issue i ON i.id = q.issue_id JOIN repo r ON r.id = i.repo_id
9494 WHERE q.merged_at IS NOT NULL AND q.merged_at <= ?2
9595 AND q.merged_oid IN (
src/flow/repo_flow.ts +2 −2
@@ -44,9 +44,9 @@ export function readRepoFlow(db: Database, repoId: string, now = new Date()): Re
4444 (SELECT COUNT(*) FROM pull p JOIN issue i ON i.id = p.issue_id
4545 WHERE i.repo_id = ?1 AND i.created_at >= ?3 AND i.created_at <= ?4) AS opened6h,
4646 (SELECT COUNT(*) FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id
47 WHERE i.repo_id = ?1 AND p.base_branch = r.default_branch AND p.merged_at >= ?2 AND p.merged_at <= ?4) AS landed1h,
47 WHERE i.repo_id = ?1 AND p.base_branch = r.default_branch AND p.carried = 0 AND p.merged_at >= ?2 AND p.merged_at <= ?4) AS landed1h,
4848 (SELECT COUNT(*) FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id
49 WHERE i.repo_id = ?1 AND p.base_branch = r.default_branch AND p.merged_at >= ?3 AND p.merged_at <= ?4) AS landed6h`,
49 WHERE i.repo_id = ?1 AND p.base_branch = r.default_branch AND p.carried = 0 AND p.merged_at >= ?3 AND p.merged_at <= ?4) AS landed6h`,
5050 )
5151 .get(repoId, h1, h6, to, fresh);
5252 return {
src/git/merge.ts +15
@@ -376,6 +376,21 @@ export async function aheadCount(
376376 return result.ok ? Number(result.value.trim()) || 0 : 0;
377377 }
378378
379 /**
380 * The head is ahead of the base by commit count while merging it onto the
381 * base would write the base's own tree: its content is already there (a
382 * squash landed it), so there is nothing to propose. The one test `watchForPull`,
383 * `landWork` and the autonomy's carried check share.
384 */
385 export async function contentOnBase(
386 dir: string,
387 baseOid: string,
388 headOid: string,
389 ): Promise<boolean> {
390 const merged = await mergeable(dir, baseOid, headOid);
391 return merged.ok && merged.value.treeOid === (await treeOf(dir, baseOid));
392 }
393
379394 /**
380395 * Fetch a ref from another local repo (a fork) into this one, bringing its
381396 * objects across, and return the fetched tip. Used to pull a fork's head into
test/crew-early.test.ts +95 −3
@@ -20,11 +20,13 @@ import { answeredBeforeStop } from "../src/crew/already.ts";
2020 import { alreadyDone } from "../src/core/done_on_main.ts";
2121 import { updateRepoSettings } from "../src/actions/repos.ts";
2222 import { pullOfBranch, watchForPull } from "../src/crew/early.ts";
23 import { resolveOid } from "../src/git/merge.ts";
23 import { contentOnBase, resolveOid } from "../src/git/merge.ts";
24 import { openPull } from "../src/actions/pulls.ts";
25 import { readRepoFlow } from "../src/flow/repo_flow.ts";
2426 import { abortAgentRun } from "../src/crew/running.ts";
2527 import { getAgentByName } from "../src/core/agents.ts";
2628 import { createIssue, getIssue, listComments } from "../src/core/issues.ts";
27 import { listPulls } from "../src/core/pulls.ts";
29 import { listPulls, mergePull } from "../src/core/pulls.ts";
2830 import { repoDir } from "../src/git/repos.ts";
2931 import { getForkOf, type Repo } from "../src/core/repos.ts";
3032 import { getRun, listRuns, type Run } from "../src/core/runs.ts";
@@ -94,6 +96,25 @@ describe("the crew, with a stand-in Claude Code: the early pull", () => {
9496 expect(log).toContain(`opened pull #${early.number} at the first push`);
9597 }, 60_000);
9698
99 test("a run whose branch is ahead by commits but level in content proposes nothing and opens no pull (#8628)", async () => {
100 rmSync(join(dataDir, "release"), { force: true });
101 const issue = openIssue(app, alice, repo, { title: "Plant lichen", body: "LEVEL-ME on the wall." });
102 if (!issue.ok) throw new Error("issue");
103 expect(assignIssue(app, alice, repo, issue.value, bot).ok).toBe(true);
104 const branch = `bot/${issue.value.number}-plant-lichen`;
105 const run = await settled((r) => r.branch === branch);
106 expect(run.summary).not.toMatch(/opened pull|updated pull/);
107 const fork = getForkOf(app.db, bot.id, repo.id)!;
108 const forkBare = repoDir(app.reposRoot, fork.ownerName, fork.name);
109 const bare = repoDir(app.reposRoot, repo.ownerName, repo.name);
110 // The branch is ahead by a commit, yet holds nothing the base lacks.
111 const head = (await resolveOid(forkBare, `refs/heads/${branch}`))!;
112 const base = (await resolveOid(bare, `refs/heads/${repo.defaultBranch}`))!;
113 expect(await contentOnBase(forkBare, base, head)).toBe(true);
114 expect(pullOfBranch(app, repo, fork, branch)).toBeUndefined();
115 expect(readRunLog(dataDir, run.id).lines.join("\n")).toContain("nothing to propose");
116 }, 60_000);
117
97118 test("a body a person edited while the run was going is left alone at the run's end", async () => {
98119 rmSync(join(dataDir, "release"), { force: true });
99120 const issue = openIssue(app, alice, repo, {
@@ -266,7 +287,9 @@ describe("the crew, with a stand-in Claude Code: the early pull", () => {
266287 return out.trim();
267288 };
268289 const baseTip = (await resolveOid(bare, `refs/heads/${repo.defaultBranch}`))!;
269 const tree = await git("rev-parse", `${baseTip}^{tree}`);
290 // The empty tree: content the base does not hold (#8628 stops a branch
291 // whose content it does).
292 const tree = await git("hash-object", "-t", "tree", "/dev/null");
270293 const commit = await git("commit-tree", tree, "-p", baseTip, "-m", "ferns");
271294 await git("update-ref", `refs/heads/${branch}`, commit);
272295
@@ -293,6 +316,75 @@ describe("the crew, with a stand-in Claude Code: the early pull", () => {
293316 expect(pullOfBranch(app, repo, fork, branch)).toBeUndefined();
294317 }, 30_000);
295318
319 test("a branch ahead by commits but level in content opens no pull, run after run, and a carried close is no landing (#8628)", async () => {
320 const agent = getAgentByName(app.db, "bot")!;
321 const fork = getForkOf(app.db, bot.id, repo.id)!;
322 const bare = repoDir(app.reposRoot, repo.ownerName, repo.name);
323 const forkBare = repoDir(app.reposRoot, fork.ownerName, fork.name);
324 const issue = openIssue(app, alice, repo, { title: "Plant moss", body: "Damp." });
325 if (!issue.ok) throw new Error("issue");
326 const git = async (...args: string[]) => {
327 const p = Bun.spawn(["git", ...args], {
328 cwd: forkBare,
329 stdout: "pipe",
330 stderr: "pipe",
331 env: {
332 ...process.env,
333 GIT_AUTHOR_NAME: "t",
334 GIT_AUTHOR_EMAIL: "[email protected]",
335 GIT_COMMITTER_NAME: "t",
336 GIT_COMMITTER_EMAIL: "[email protected]",
337 },
338 });
339 const out = await new Response(p.stdout).text();
340 expect(await p.exited).toBe(0);
341 return out.trim();
342 };
343 // One commit beyond the base whose tree is the base's: its content is
344 // already there, as after a squash landed it.
345 const branch = "bot/already-carried";
346 const baseTip = (await resolveOid(bare, `refs/heads/${repo.defaultBranch}`))!;
347 const tree = await git("rev-parse", `${baseTip}^{tree}`);
348 const commit = await git("commit-tree", tree, "-p", baseTip, "-m", "moss");
349 await git("update-ref", `refs/heads/${branch}`, commit);
350 expect(await contentOnBase(forkBare, baseTip, commit)).toBe(true);
351
352 const run = listRuns(app.db, repo.id, { kind: "agent" })[0]!;
353 for (let again = 0; again < 3; again++) {
354 const lines: string[] = [];
355 const early = watchForPull({
356 app, repo, agent, issue: issue.value, run, fork, branch, forkBare, bare,
357 forgeUrl: "http://forge.test",
358 write: (l) => lines.push(l),
359 variantGroup: null,
360 continuing: false,
361 intervalMs: 10,
362 });
363 await Bun.sleep(100);
364 await early.stop();
365 expect(lines).toEqual([]);
366 }
367 expect(pullOfBranch(app, repo, fork, branch)).toBeUndefined();
368
369 // Opened anyway, a pull like it closes as merged, and landed counts none.
370 const opened = await openPull(app, bot, repo, {
371 title: "Plant moss",
372 body: `Closes #${issue.value.number}`,
373 baseBranch: repo.defaultBranch,
374 headBranch: branch,
375 headRepo: fork,
376 });
377 if (!opened.ok) throw new Error("pull");
378 const landed = await mergePull(app.db, bare, opened.value, {
379 strategy: "squash",
380 message: "moss",
381 actorId: alice.id,
382 author: { name: "t", email: "[email protected]" },
383 });
384 expect(landed.ok && landed.value.carried).toBe(true);
385 expect(readRepoFlow(app.db, repo.id).landed1h).toBe(0);
386 }, 30_000);
387
296388 test("a run whose pull is closed under it, after it answered already done on main, notes and holds its issue", async () => {
297389 rmSync(join(dataDir, "answered"), { force: true });
298390 // The note and the hold are the autonomous crew's (`settleAgentRun`).
test/flow-carried.test.ts added +51
@@ -0,0 +1,51 @@
1 /** A pull closed as carried (#8628) lands nothing: the clog tick, the keeper's rate and the home figures leave it out. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts";
4 import { loadClog, tickClog } from "../src/flow/clog_tick.ts";
5 import { hourly, landedBetween } from "../src/flow/keeper_rate.ts";
6 import { readKpis } from "../src/flow/kpis.ts";
7
8 const MIN = 60_000;
9 let f: SnappyFixture;
10 let n = 0;
11
12 /** A merged pull on the default branch; `carried` says it found its change already there. */
13 function merged(at: number, carried: boolean): void {
14 const repo = f.repos[0]!;
15 const id = `carried-${n}`;
16 f.app.db
17 .query(`INSERT INTO issue (id, repo_id, number, kind, title, author_id, state) VALUES (?1, ?2, ?3, 'pull', 'p', ?4, 'closed')`)
18 .run(id, repo.id, 5000 + n, f.owner.id);
19 // Its own merged_oid, never the base's: only the flag can keep it out.
20 f.app.db
21 .query(
22 `INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, merged_at, merged_oid, carried)
23 VALUES (?1, ?2, ?3, ?4, '', ?5, ?6, ?7)`,
24 )
25 .run(id, repo.defaultBranch, `b/${id}`, `base-${n}`, new Date(at).toISOString(), `oid-${n}`, carried ? 1 : 0);
26 n++;
27 }
28
29 beforeAll(async () => {
30 f = await snappyFixture({ repos: 1, issuesPerRepo: 0 });
31 }, 60_000);
32 afterAll(async () => {
33 await f.close();
34 });
35
36 test("a carried pull is no landing in the keeper's rate, the hourly bars or the home figures", () => {
37 const now = Date.now();
38 merged(now - 10 * MIN, false);
39 for (let i = 0; i < 3; i++) merged(now - (20 + i) * MIN, true);
40 expect(landedBetween(f.app.db, now - 60 * MIN, now)).toBe(1);
41 expect(hourly(f.app.db, now, 3).landed.reduce((a, b) => a + b, 0)).toBe(1);
42 expect(readKpis(f.app.db, new Date(now)).landed).toBe(1);
43 });
44
45 test("ten carried closes in two hours are not ten landings: the clog tick still sees the clog", () => {
46 const now = Date.now();
47 for (let i = 0; i < 10; i++) merged(now - (30 + i) * MIN, true);
48 tickClog(f.app.db, f.app.bus, new Date(now + MIN));
49 expect(loadClog(f.app.db)?.firstReasons.join(" ")).toContain("landed of");
50 expect(loadClog(f.app.db)?.firstReasons.join(" ")).not.toContain("11 landed");
51 });
test/helpers/crew-standin.ts +5
@@ -135,6 +135,11 @@ touch "${dataDir}/answered"
135135 sleep 30
136136 exit 0
137137 fi
138 if grep -q LEVEL-ME "${promptFile}"; then
139 git commit -q --allow-empty -m "level with main" && git push -q -u origin HEAD
140 echo '{"type":"result","subtype":"success","is_error":false,"result":"Nothing to change.","total_cost_usd":0.005,"num_turns":1,"session_id":"fake-8","terminal_reason":"completed"}'
141 exit 0
142 fi
138143 if grep -q PUSH-THEN-FAIL "${promptFile}"; then
139144 echo "early work $(date +%s%N)" >> CREW.md
140145 git add -A && git commit -qm "crew work" && git push -q -u origin HEAD
test/home-kpi-row.test.ts +4 −4
@@ -124,7 +124,7 @@ describe("the builders figure from the database", () => {
124124 const db = new Database(":memory:");
125125 db.run("CREATE TABLE repo (autonomy TEXT, autonomy_slots INTEGER, default_branch TEXT, id TEXT)");
126126 db.run("CREATE TABLE run (id TEXT, kind TEXT, state TEXT, started_at TEXT, finished_at TEXT, serving_at TEXT, env TEXT, commit_oid TEXT, cost_usd REAL, created_at TEXT)");
127 db.run("CREATE TABLE pull (merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
127 db.run("CREATE TABLE pull (carried INTEGER NOT NULL DEFAULT 0, merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
128128 db.run("CREATE TABLE issue (id TEXT, repo_id TEXT)");
129129 reachTables(db);
130130 db.run("INSERT INTO repo VALUES ('autonomous', 3, 'main', 'a'), ('autonomous', 1, 'main', 'b'), ('off', 8, 'main', 'c')");
@@ -188,7 +188,7 @@ describe("the spend figure from the database", () => {
188188 const db = new Database(":memory:");
189189 db.run("CREATE TABLE repo (autonomy TEXT, autonomy_slots INTEGER, default_branch TEXT, id TEXT)");
190190 db.run("CREATE TABLE run (id TEXT, kind TEXT, state TEXT, started_at TEXT, finished_at TEXT, serving_at TEXT, env TEXT, commit_oid TEXT, cost_usd REAL, created_at TEXT)");
191 db.run("CREATE TABLE pull (merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
191 db.run("CREATE TABLE pull (carried INTEGER NOT NULL DEFAULT 0, merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
192192 db.run("CREATE TABLE issue (id TEXT, repo_id TEXT)");
193193 reachTables(db);
194194 db.run(
@@ -240,7 +240,7 @@ describe("home kpi row: the last off-box backup", () => {
240240 const db = new Database(":memory:");
241241 db.run("CREATE TABLE repo (autonomy TEXT, autonomy_slots INTEGER, default_branch TEXT, id TEXT)");
242242 db.run("CREATE TABLE run (id TEXT, kind TEXT, state TEXT, started_at TEXT, finished_at TEXT, serving_at TEXT, env TEXT, commit_oid TEXT, cost_usd REAL, created_at TEXT)");
243 db.run("CREATE TABLE pull (merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
243 db.run("CREATE TABLE pull (carried INTEGER NOT NULL DEFAULT 0, merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
244244 db.run("CREATE TABLE issue (id TEXT, repo_id TEXT)");
245245 reachTables(db);
246246 expect(readKpis(db, NOW, { ceilingUsd: null, dataDir: dir }).backupAt).toBeNull();
@@ -430,7 +430,7 @@ describe("the forge ceiling hit (#5798)", () => {
430430 const db = new Database(":memory:");
431431 db.run("CREATE TABLE repo (autonomy TEXT, autonomy_slots INTEGER, default_branch TEXT, id TEXT)");
432432 db.run("CREATE TABLE run (id TEXT, kind TEXT, state TEXT, started_at TEXT, finished_at TEXT, serving_at TEXT, env TEXT, commit_oid TEXT, cost_usd REAL, created_at TEXT)");
433 db.run("CREATE TABLE pull (merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
433 db.run("CREATE TABLE pull (carried INTEGER NOT NULL DEFAULT 0, merged_at TEXT, merged_oid TEXT, base_oid TEXT, base_branch TEXT, issue_id TEXT)");
434434 db.run("CREATE TABLE issue (id TEXT, repo_id TEXT)");
435435 reachTables(db);
436436 db.run(

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs