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

A pure read says the stranded spend: dollars of crew runs on pulls that are open and not landed #6434

Merged opened this

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

Merged

scout/forge:scout/6421-a-pure-read-says-the-stranded-spend-doll → main 4 commits fork 3 threads · 3 unresolved

  • Merged. scout/6421-a-pure-read-says-the-stranded-spend-doll was merged into main as a6a3cd3.

Review

Sign in to review this change.
2 files changed +140 −0
src/flow/stranded.ts added +52
@@ -0,0 +1,52 @@
1 /**
2 * The stranded spend (#5930): what the crew's runs on pulls that are open and
3 * not landed have cost so far. One read; the same `publicOnly` rule as
4 * `readVitals`, so a reader who may not see private repositories learns
5 * nothing about them. Numbers and dollars only, no repository named.
6 */
7
8 import type { Database } from "bun:sqlite";
9
10 const COSTLIEST = 5;
11
12 export interface StrandedPull {
13 readonly number: number;
14 readonly usd: number;
15 }
16
17 export interface Stranded {
18 /** Dollars of runs on open, unmerged pulls. */
19 readonly usd: number;
20 /** Open, unmerged pulls, whether or not they have cost anything yet. */
21 readonly pulls: number;
22 /** The five that cost most, costliest first. */
23 readonly costliest: readonly StrandedPull[];
24 }
25
26 interface Row {
27 readonly number: number;
28 readonly usd: number;
29 readonly total_usd: number;
30 readonly total_pulls: number;
31 }
32
33 export function readStranded(db: Database, publicOnly = false): Stranded {
34 const rows = db
35 .query<Row, [number, number]>(
36 `WITH open_pull AS (
37 SELECT i.number AS number,
38 COALESCE((SELECT SUM(u.cost_usd) FROM run u WHERE u.issue_id = p.issue_id), 0) AS usd
39 FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id
40 WHERE (?1 = 0 OR r.visibility = 'public') AND i.state = 'open' AND p.merged_at IS NULL
41 )
42 SELECT number, usd, SUM(usd) OVER () AS total_usd, COUNT(*) OVER () AS total_pulls
43 FROM open_pull ORDER BY usd DESC, number ASC LIMIT ?2`,
44 )
45 .all(publicOnly ? 1 : 0, COSTLIEST);
46 const first = rows[0];
47 return {
48 usd: first?.total_usd ?? 0,
49 pulls: first?.total_pulls ?? 0,
50 costliest: rows.map((r) => ({ number: r.number, usd: r.usd })),
51 };
52 }
test/flow-stranded.test.ts added +88
@@ -0,0 +1,88 @@
1 /** The stranded spend (#5930): runs on open, unmerged pulls, and nothing else. */
outdated

The header cites #6421, but the module comment cites #5930. Use one issue number.

2 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp } from "../src/app.ts";
7 import { createRepo } from "../src/core/repos.ts";
8 import { createUser } from "../src/core/users.ts";
9 import { id } from "../src/kernel/id.ts";
10 import { readStranded } from "../src/flow/stranded.ts";
11
12 describe("readStranded", () => {
13 let root: string;
14 let app: ReturnType<typeof createApp>;
15 let owner: string;
16 let pub: string;
17 let priv: string;
18 const counter = new Map<string, number>();
19
20 beforeAll(async () => {
21 root = mkdtempSync(join(tmpdir(), "forge-stranded-"));
22 app = createApp({ dataDir: root, port: 0 });
23 const made = await createUser(app.db, { username: "alice", email: "[email protected]", password: "a-good-passphrase", origin: "seed", trusted: true, adminIfFirst: true });
24 if (!made.ok) throw new Error(made.error);
25 owner = made.value.id;
26 for (const [name, visibility] of [["pub", "public"], ["priv", "private"]] as const) {
27 const r = await createRepo(app.db, app.reposRoot, { ownerId: owner, name, visibility, description: name });
28 if (!r.ok) throw new Error(r.error);
29 if (name === "pub") pub = r.value.id;
30 else priv = r.value.id;
31 }
32 });
33 afterAll(() => {
34 app.db.close();
35 rmSync(root, { recursive: true, force: true });
36 });
37
38 const pull = (repo: string, state: string, merged: string | null, costs: number[]) => {
39 const issueId = id();
40 const n = (counter.get(repo) ?? 0) + 1;
41 counter.set(repo, n);
42 app.db
43 .query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state) VALUES (?1, ?2, ?3, 'pull', 'P', '', ?4, ?5)`)
44 .run(issueId, repo, n, owner, state);
45 app.db
46 .query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id, merged_at) VALUES (?1, 'main', ?2, '', '', ?3, ?4)`)
47 .run(issueId, `b/${n}`, repo, merged);
48 for (const c of costs) {
49 app.db
50 .query(`INSERT INTO run (id, repo_id, kind, issue_id, state, cost_usd) VALUES (?1, ?2, 'agent', ?3, 'ok', ?4)`)
51 .run(id(), repo, issueId, c);
52 }
53 return n;
54 };
55
56 test("nothing open reads as zero", () => {
57 expect(readStranded(app.db)).toEqual({ usd: 0, pulls: 0, costliest: [] });
58 });
59
60 test("counts open pulls, leaves out landed and closed ones, ranks the costliest", () => {
61 const a = pull(pub, "open", null, [1, 0.5]);
62 pull(pub, "closed", "2026-10-26T00:00:00.000Z", [100]); // landed
63 pull(pub, "closed", null, [50]); // closed unmerged

This assertion proves nothing about readStranded; remove it.

64 const b = pull(pub, "open", null, [4]);
65 const c = pull(pub, "open", null, []);
66 const s = readStranded(app.db);
67 expect(s.usd).toBeCloseTo(5.5);
68 expect(s.pulls).toBe(3);
69 expect(s.costliest.map((p) => p.number)).toEqual([b, a, c]);
70 expect(s.costliest[0]!.usd).toBeCloseTo(4);
71 });
72
73 test("publicOnly leaves out private repositories", () => {
74 pull(priv, "open", null, [10]);
75 expect(readStranded(app.db).usd).toBeCloseTo(15.5);
76 const s = readStranded(app.db, true);
77 expect(s.usd).toBeCloseTo(5.5);
78 expect(s.pulls).toBe(3);
79 });
80
81 test("keeps only the five costliest, with 200 open pulls and 2,000 runs", () => {
82 for (let i = 0; i < 200; i++) pull(pub, "open", null, Array.from({ length: 10 }, () => 0.1));
83 const s = readStranded(app.db, true);
84 expect(s.costliest).toHaveLength(5);
85 expect(s.pulls).toBe(203);
86 expect(s.usd).toBeCloseTo(5.5 + 200);
87 });
88 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs