@feigi/fleet-ctl 3.19.8 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/fleet-tick.test.mjs +1 -1
- package/scripts/ledger-dispatch.test.mjs +34 -9
- package/scripts/ledger-grammar.mjs +10 -4
- package/scripts/ledger.mjs +6 -1
- package/scripts/router-table.json +24 -0
- package/scripts/ticket-router.mjs +543 -0
- package/scripts/ticket-router.test.mjs +981 -0
- package/scripts/within-run-pair-prose.test.mjs +46 -10
- package/skills/run-team/SKILL.md +59 -32
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import {
|
|
9
|
+
COLUMNS, DEFAULT_TABLE, issueMetrics, ruleStratum, route, fitTable, readGuard, validateTable, parseFeatures, formatFeatureRow, mergedSince,
|
|
10
|
+
} from "./ticket-router.mjs";
|
|
11
|
+
import { drawCell, CELL } from "./ledger-grammar.mjs";
|
|
12
|
+
import { COLUMNS as MEMBER_COLUMNS } from "./member-outcomes.mjs";
|
|
13
|
+
import { COLUMNS as VERDICT_COLUMNS } from "./tier-outcomes.mjs";
|
|
14
|
+
|
|
15
|
+
const SCRIPT = fileURLToPath(new URL("./ticket-router.mjs", import.meta.url));
|
|
16
|
+
const SESSION = "2026-10-01T10-00-00-000Z_01a0b344-33f6-7640-96a8-05f45bf847a9";
|
|
17
|
+
const STAGE1 = ["slow-high", "task-high", "smol-high"];
|
|
18
|
+
|
|
19
|
+
const baseTable = (over = {}) => ({
|
|
20
|
+
window_start: null, fitted_through: null, n_rows: 0, stage: 1, cells: [...STAGE1], burn_in: false,
|
|
21
|
+
rows: { "*": "slow-high" }, classifier: { b: null, tau: null }, estimates: {}, guard: { tripped: [], n: {} }, ...over,
|
|
22
|
+
});
|
|
23
|
+
const guardFile = (over = {}) => ({
|
|
24
|
+
computed_at: "2026-10-01T00:00:00Z", window_start: "2026-09-01",
|
|
25
|
+
baseline: { cell: "slow-high", n: 5, mean_usd: 10, fail_rate: 0.5 },
|
|
26
|
+
cells: STAGE1.map((cell) => ({ cell, n: 5, mean_usd: 5, fail_rate: 0.5 })), tripped: [], verdict: "none", ...over,
|
|
27
|
+
});
|
|
28
|
+
const issue = (over = {}) => ({
|
|
29
|
+
title: "t", body: "short brief\n- [ ] one\n- [x] two", comments: [], labels: [{ name: "bug" }],
|
|
30
|
+
createdAt: new Date(Date.now() - 3.5 * 86_400_000).toISOString(), ...over,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function world(t, { table = baseTable(), guard = guardFile(), issueJson = issue(), sizing } = {}) {
|
|
34
|
+
const dir = mkdtempSync(join(tmpdir(), "ticket-router-"));
|
|
35
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
36
|
+
const paths = {
|
|
37
|
+
table: join(dir, "router-table.json"), guard: join(dir, "cost-guard.json"), issue: join(dir, "issue.json"),
|
|
38
|
+
sizing: join(dir, "sizing.json"), pending: join(dir, ".fleet", "ticket-features.pending.tsv"), dir,
|
|
39
|
+
};
|
|
40
|
+
writeFileSync(paths.table, typeof table === "string" ? table : JSON.stringify(table));
|
|
41
|
+
if (guard !== null) writeFileSync(paths.guard, typeof guard === "string" ? guard : JSON.stringify(guard));
|
|
42
|
+
if (issueJson !== null) writeFileSync(paths.issue, typeof issueJson === "string" ? issueJson : JSON.stringify(issueJson));
|
|
43
|
+
if (sizing !== undefined) writeFileSync(paths.sizing, JSON.stringify(sizing));
|
|
44
|
+
return paths;
|
|
45
|
+
}
|
|
46
|
+
const cli = (args) => spawnSync(process.execPath, [SCRIPT, ...args], { encoding: "utf8" });
|
|
47
|
+
function routeCli(p, { ticket = 42, arm = "A", implRow = 3, extra = [] } = {}) {
|
|
48
|
+
return cli(["route", "--session", SESSION, "--ticket", String(ticket), "--arm", arm, "--impl-row", String(implRow),
|
|
49
|
+
"--issue", p.issue, "--guard", p.guard, "--table", p.table, "--pending", p.pending, ...extra]);
|
|
50
|
+
}
|
|
51
|
+
const parseLine = (stdout) => Object.fromEntries(stdout.trim().split(" ").map((kv) => kv.split("=")));
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// route: one line, every REASON, exit 0 on every degradation
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
test("route: a clean non-exploration Pull prints the full line, REASON=ok, and records a features row", (t) => {
|
|
58
|
+
const p = world(t);
|
|
59
|
+
const r = routeCli(p);
|
|
60
|
+
assert.equal(r.status, 0, r.stderr);
|
|
61
|
+
assert.equal(r.stdout, "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=light REASON=ok\n");
|
|
62
|
+
const lines = readFileSync(p.pending, "utf8").trim().split("\n");
|
|
63
|
+
assert.equal(lines[0], COLUMNS.join("\t"), "a new pending file starts with the header");
|
|
64
|
+
const [row] = parseFeatures(lines.join("\n"));
|
|
65
|
+
assert.equal(row.run_date, "2026-10-01");
|
|
66
|
+
assert.equal(row.session, SESSION);
|
|
67
|
+
assert.equal(row.agent, "impl-42");
|
|
68
|
+
assert.equal(row.policy_cell, "slow-high");
|
|
69
|
+
assert.equal(row.chosen_cell, "slow-high");
|
|
70
|
+
assert.equal(row.exploration_draw, "", "no draw, so the draw column stays blank");
|
|
71
|
+
assert.equal(row.sizing_src, "rule");
|
|
72
|
+
assert.equal(row.criteria, "2");
|
|
73
|
+
assert.equal(row.age_days, "3");
|
|
74
|
+
assert.equal(row.kind, "bug");
|
|
75
|
+
// A second Pull appends, never a second header.
|
|
76
|
+
routeCli(p, { ticket: 43 });
|
|
77
|
+
const again = readFileSync(p.pending, "utf8").trim().split("\n");
|
|
78
|
+
assert.equal(again.length, 3);
|
|
79
|
+
assert.equal(again.filter((l) => l === COLUMNS.join("\t")).length, 1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("route: with no --pending the features row lands beside the guard, in the run's .fleet/", (t) => {
|
|
83
|
+
const p = world(t);
|
|
84
|
+
const r = cli(["route", "--session", SESSION, "--ticket", "42", "--arm", "A", "--impl-row", "3",
|
|
85
|
+
"--issue", p.issue, "--guard", p.guard, "--table", p.table]);
|
|
86
|
+
assert.equal(r.status, 0, r.stderr);
|
|
87
|
+
assert.equal(parseFeatures(readFileSync(join(p.dir, "ticket-features.pending.tsv"), "utf8")).length, 1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// A scripted Pull's step 7, run against the real ledger.mjs: route, write the
|
|
91
|
+
// row with `tier=<CELL>` exactly when the router drew, then dispatch — whose
|
|
92
|
+
// printed `agent` is the definition the `task` call names.
|
|
93
|
+
test("a scripted Pull dispatches the router's cell: tier=<cell> on an Exploration Pull, no tier= otherwise", (t) => {
|
|
94
|
+
const LEDGER = fileURLToPath(new URL("./ledger.mjs", import.meta.url));
|
|
95
|
+
const pull = (p, ticket, implRow) => {
|
|
96
|
+
const out = parseLine(routeCli(p, { ticket, implRow }).stdout);
|
|
97
|
+
const ledgerFile = join(p.dir, "ledger.md");
|
|
98
|
+
const env = { ...process.env, PATH: p.dir };
|
|
99
|
+
delete env.GIT_DIR;
|
|
100
|
+
delete env.GIT_WORK_TREE;
|
|
101
|
+
const led = (...args) => {
|
|
102
|
+
const r = spawnSync(process.execPath, [LEDGER, "--file", ledgerFile, ...args], { encoding: "utf8", env, cwd: p.dir });
|
|
103
|
+
assert.equal(r.status, 0, `${args.join(" ")}: ${r.stderr}`);
|
|
104
|
+
return JSON.parse(r.stdout);
|
|
105
|
+
};
|
|
106
|
+
const row = led("row", String(ticket), `impl-${ticket}${out.DRAW === "-" ? "" : ` · tier=${out.CELL}`}`);
|
|
107
|
+
return { out, row: row.line, agent: led("dispatch", String(ticket), `impl-${ticket}`).agent };
|
|
108
|
+
};
|
|
109
|
+
const burnIn = world(t, { table: baseTable({ burn_in: true }) });
|
|
110
|
+
for (const ticket of [101, 102, 103, 104]) {
|
|
111
|
+
const { out, row, agent } = pull(burnIn, ticket, 1);
|
|
112
|
+
assert.notEqual(out.DRAW, "-");
|
|
113
|
+
assert.equal(row, `#${ticket} impl-${ticket} · tier=${out.CELL}`);
|
|
114
|
+
assert.equal(agent, `fleet-implementer-${out.CELL}`);
|
|
115
|
+
}
|
|
116
|
+
const after = world(t);
|
|
117
|
+
const plain = pull(after, 201, 4);
|
|
118
|
+
assert.equal(plain.row, "#201 impl-201", "a Pull the router did not draw for carries no tier=");
|
|
119
|
+
assert.equal(plain.agent, "fleet-implementer-slow-high");
|
|
120
|
+
const explore = pull(after, 202, 5);
|
|
121
|
+
assert.equal(explore.row, `#202 impl-202 · tier=${explore.out.CELL}`);
|
|
122
|
+
assert.notEqual(explore.out.CELL, "slow-high");
|
|
123
|
+
assert.equal(explore.agent, `fleet-implementer-${explore.out.CELL}`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("route: a session given as its directory path records the session id, not the path", (t) => {
|
|
127
|
+
const p = world(t);
|
|
128
|
+
const r = cli(["route", "--session", `/x/sessions/proj/${SESSION}/`, "--ticket", "42", "--arm", "A", "--impl-row", "3",
|
|
129
|
+
"--issue", p.issue, "--guard", p.guard, "--table", p.table, "--pending", p.pending]);
|
|
130
|
+
assert.equal(r.status, 0, r.stderr);
|
|
131
|
+
assert.equal(parseFeatures(readFileSync(p.pending, "utf8"))[0].session, SESSION);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("route: no-issue-json — a missing or unparseable issue file blanks the metrics, routes `unknown`, exits 0", (t) => {
|
|
135
|
+
for (const issueJson of [null, "{not json"]) {
|
|
136
|
+
const p = world(t, { issueJson });
|
|
137
|
+
const r = routeCli(p);
|
|
138
|
+
assert.equal(r.status, 0, r.stderr);
|
|
139
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "slow-high", DRAW: "-", STRATUM: "unknown", REASON: "no-issue-json" });
|
|
140
|
+
const [row] = parseFeatures(readFileSync(p.pending, "utf8"));
|
|
141
|
+
for (const c of ["brief_chars", "criteria", "comments", "age_days", "paths", "test_paths", "xrefs", "kind"]) assert.equal(row[c], "", c);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("route: no-sizing — a live B Pull without a usable sizing file routes `unknown`, exits 0", (t) => {
|
|
146
|
+
const table = baseTable({ classifier: { b: "haiku", tau: null }, rows: { "*": "slow-high", heavy: "task-high" } });
|
|
147
|
+
const p = world(t, { table });
|
|
148
|
+
const r = routeCli(p, { arm: "B" });
|
|
149
|
+
assert.equal(r.status, 0, r.stderr);
|
|
150
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "slow-high", DRAW: "-", STRATUM: "unknown", REASON: "no-sizing" });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("route: a live B Pull WITH a sizing verdict routes by it — the must-accept half of no-sizing", (t) => {
|
|
154
|
+
const table = baseTable({ classifier: { b: "haiku", tau: null }, rows: { "*": "slow-high", heavy: "task-high" } });
|
|
155
|
+
const p = world(t, { table, sizing: { source: "haiku", model: "m", label: "heavy", confidence: 1, usd: 0.0002 } });
|
|
156
|
+
const r = routeCli(p, { arm: "B", extra: ["--sizing", p.sizing] });
|
|
157
|
+
assert.equal(r.status, 0, r.stderr);
|
|
158
|
+
// The table row for the classifier's stratum is dispatched; POLICY stays the free rule's.
|
|
159
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "task-high", DRAW: "-", STRATUM: "heavy", REASON: "ok" });
|
|
160
|
+
const [row] = parseFeatures(readFileSync(p.pending, "utf8"));
|
|
161
|
+
assert.equal(row.sizing_src, "haiku");
|
|
162
|
+
assert.equal(row.sizing_pre, "heavy");
|
|
163
|
+
assert.equal(row.router_usd, "0.0002");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("route: arm B on a table whose B gate is closed routes exactly as arm A and never reads a sizing file", (t) => {
|
|
167
|
+
const p = world(t);
|
|
168
|
+
const r = routeCli(p, { arm: "B" });
|
|
169
|
+
assert.equal(r.status, 0, r.stderr);
|
|
170
|
+
assert.equal(r.stdout, "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=light REASON=ok\n");
|
|
171
|
+
assert.equal(parseFeatures(readFileSync(p.pending, "utf8"))[0].sizing_src, "rule");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("route: guard-missing — no guard (or an unparseable one) dispatches the default cell only, even in burn-in", (t) => {
|
|
175
|
+
for (const guard of [null, "{nope", JSON.stringify({ tripped: "x" })]) {
|
|
176
|
+
const p = world(t, { table: baseTable({ burn_in: true, rows: { "*": "task-high" } }), guard });
|
|
177
|
+
const r = routeCli(p, { implRow: 5 });
|
|
178
|
+
assert.equal(r.status, 0, r.stderr);
|
|
179
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "slow-high", DRAW: "-", STRATUM: "light", REASON: "guard-missing" });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("route: guard-tripped — a tripped cell leaves the draw's pool, and the line says so", (t) => {
|
|
184
|
+
const p = world(t, { table: baseTable({ burn_in: true }), guard: guardFile({ tripped: ["smol-high"], verdict: "tripped" }) });
|
|
185
|
+
const r = routeCli(p);
|
|
186
|
+
assert.equal(r.status, 0, r.stderr);
|
|
187
|
+
const out = parseLine(r.stdout);
|
|
188
|
+
assert.equal(out.REASON, "guard-tripped");
|
|
189
|
+
assert.notEqual(out.CELL, "smol-high");
|
|
190
|
+
assert.match(out.DRAW, /^[12]\/2$/);
|
|
191
|
+
assert.equal(out.CELL, drawCell({ session: SESSION, ticket: 42, policyCell: null, cells: ["slow-high", "task-high"] }).cell);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("route: table-cell-tripped — a table row naming a tripped cell is replaced by the default without waiting for a re-fit", (t) => {
|
|
195
|
+
const p = world(t, { table: baseTable({ rows: { "*": "task-high" } }), guard: guardFile({ tripped: ["task-high"] }) });
|
|
196
|
+
const r = routeCli(p);
|
|
197
|
+
assert.equal(r.status, 0, r.stderr);
|
|
198
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "slow-high", DRAW: "-", STRATUM: "light", REASON: "table-cell-tripped" });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("route: a non-default table row is dispatched when its cell is not tripped", (t) => {
|
|
202
|
+
const p = world(t, { table: baseTable({ rows: { "*": "slow-high", light: "task-high" } }) });
|
|
203
|
+
const r = routeCli(p);
|
|
204
|
+
assert.equal(r.stdout, "POLICY=task-high CELL=task-high DRAW=- STRATUM=light REASON=ok\n");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("route: burn-in draws on EVERY Pull, uniformly over all cells including the default", (t) => {
|
|
208
|
+
const p = world(t, { table: baseTable({ burn_in: true }) });
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (let ticket = 1; ticket <= 30; ticket++) {
|
|
211
|
+
const r = routeCli(p, { ticket, implRow: ticket });
|
|
212
|
+
assert.equal(r.status, 0, r.stderr);
|
|
213
|
+
const out = parseLine(r.stdout);
|
|
214
|
+
const d = drawCell({ session: SESSION, ticket, policyCell: null, cells: STAGE1 });
|
|
215
|
+
assert.equal(out.CELL, d.cell);
|
|
216
|
+
assert.equal(out.DRAW, `${d.k}/3`);
|
|
217
|
+
seen.add(out.CELL);
|
|
218
|
+
}
|
|
219
|
+
assert.deepEqual([...seen].sort(), [...STAGE1].sort(), "thirty burn-in draws never reached every cell");
|
|
220
|
+
// Reproducible from the row: the same session and ticket draw the same cell.
|
|
221
|
+
assert.equal(routeCli(p, { ticket: 7 }).stdout, routeCli(p, { ticket: 7, implRow: 99 }).stdout);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("route: after burn-in only the Pull creating impl- row 5k draws, over the cells minus the policy cell", (t) => {
|
|
225
|
+
const p = world(t);
|
|
226
|
+
for (const implRow of [1, 4, 6, 9, 11]) assert.equal(parseLine(routeCli(p, { implRow }).stdout).DRAW, "-", `row ${implRow}`);
|
|
227
|
+
for (const implRow of [5, 10, 15]) {
|
|
228
|
+
const out = parseLine(routeCli(p, { implRow }).stdout);
|
|
229
|
+
const d = drawCell({ session: SESSION, ticket: 42, policyCell: "slow-high", cells: STAGE1 });
|
|
230
|
+
assert.equal(out.CELL, d.cell);
|
|
231
|
+
assert.equal(out.DRAW, `${d.k}/2`);
|
|
232
|
+
assert.notEqual(out.CELL, "slow-high");
|
|
233
|
+
}
|
|
234
|
+
const [, , explore] = parseFeatures(readFileSync(p.pending, "utf8")).filter((r) => r.exploration_draw !== "");
|
|
235
|
+
assert.ok(explore, "an exploration Pull records its draw");
|
|
236
|
+
assert.notEqual(explore.chosen_cell, explore.policy_cell);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("route: a chain head on row 5k does not draw — the assignment rolls to the next Pull", (t) => {
|
|
240
|
+
const p = world(t);
|
|
241
|
+
assert.equal(parseLine(routeCli(p, { implRow: 5, extra: ["--chain-head"] }).stdout).DRAW, "-");
|
|
242
|
+
// In burn-in every Pull draws, chain head or not.
|
|
243
|
+
const b = world(t, { table: baseTable({ burn_in: true }) });
|
|
244
|
+
assert.notEqual(parseLine(routeCli(b, { implRow: 5, extra: ["--chain-head"] }).stdout).DRAW, "-");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("route: with every non-default cell withdrawn (K=0) an exploration Pull runs the policy cell and records no draw", (t) => {
|
|
248
|
+
const p = world(t, { table: baseTable({ cells: ["slow-high"] }) });
|
|
249
|
+
const r = routeCli(p, { implRow: 5 });
|
|
250
|
+
assert.equal(r.stdout, "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=light REASON=ok\n");
|
|
251
|
+
assert.equal(parseFeatures(readFileSync(p.pending, "utf8"))[0].exploration_draw, "");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("route: exit 2 only on usage errors and an unreadable or invalid table — and nothing is recorded", (t) => {
|
|
255
|
+
const p = world(t);
|
|
256
|
+
const base = ["route", "--session", SESSION, "--ticket", "42", "--arm", "A", "--impl-row", "3", "--issue", p.issue, "--guard", p.guard, "--table", p.table, "--pending", p.pending];
|
|
257
|
+
const without = (flag) => { const i = base.indexOf(flag); return [...base.slice(0, i), ...base.slice(i + 2)]; };
|
|
258
|
+
const swap = (flag, value) => base.map((a, i) => (base[i - 1] === flag ? value : a));
|
|
259
|
+
const cases = [
|
|
260
|
+
[without("--session"), /route needs --session/],
|
|
261
|
+
[without("--guard"), /route needs --guard/],
|
|
262
|
+
[swap("--arm", "C"), /--arm C is not A or B/],
|
|
263
|
+
[swap("--ticket", "0"), /--ticket 0 is not an issue number/],
|
|
264
|
+
[swap("--impl-row", "0"), /--impl-row 0 is not a positive row count/],
|
|
265
|
+
[[...base, "--features", "x"], /--features does not apply to route/],
|
|
266
|
+
[[...base, "--features=x"], /--features does not apply to route/],
|
|
267
|
+
[[...without("--table"), `--table=${p.table}`], /--table needs a space-separated value/],
|
|
268
|
+
[base.slice(1), /exactly one mode/],
|
|
269
|
+
[swap("--table", join(p.dir, "absent.json")), /cannot read the router table/],
|
|
270
|
+
];
|
|
271
|
+
for (const [args, why] of cases) {
|
|
272
|
+
const r = cli(args);
|
|
273
|
+
assert.equal(r.status, 2, `${args.join(" ")}: ${r.stderr}`);
|
|
274
|
+
assert.match(r.stderr, why);
|
|
275
|
+
assert.equal(r.stdout, "");
|
|
276
|
+
}
|
|
277
|
+
for (const bad of [{ ...baseTable(), cells: ["slow-high", "alt"] }, { ...baseTable(), rows: {} }, { ...baseTable(), burn_in: "yes" }]) {
|
|
278
|
+
const q = world(t, { table: bad });
|
|
279
|
+
const r = routeCli(q);
|
|
280
|
+
assert.equal(r.status, 2, r.stderr);
|
|
281
|
+
assert.match(r.stderr, /router table:/);
|
|
282
|
+
}
|
|
283
|
+
assert.equal(existsSync(p.pending), false, "a refused route wrote a features row");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("route: the checked-in table is valid and is the day-one table", () => {
|
|
287
|
+
const table = JSON.parse(readFileSync(DEFAULT_TABLE, "utf8"));
|
|
288
|
+
assert.equal(validateTable(table), table);
|
|
289
|
+
assert.equal(table.burn_in, true);
|
|
290
|
+
assert.equal(table.stage, 1);
|
|
291
|
+
assert.deepEqual(table.cells, STAGE1);
|
|
292
|
+
assert.deepEqual(table.rows, { "*": "slow-high" });
|
|
293
|
+
for (const c of table.cells) {
|
|
294
|
+
assert.ok(CELL.test(c));
|
|
295
|
+
assert.ok(existsSync(fileURLToPath(new URL(`../agents/fleet-implementer-${c}.agent.md`, import.meta.url))), `${c} has no definition`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("route: pure — the same inputs give the same line, and the line names every field", () => {
|
|
300
|
+
const args = { session: SESSION, ticket: 9, arm: "A", implRow: 10, issue: issue(), guard: readGuard(guardFile()), table: baseTable() };
|
|
301
|
+
const a = route(args);
|
|
302
|
+
assert.deepEqual(route(args), a);
|
|
303
|
+
assert.match(a.line, /^POLICY=\S+ CELL=\S+ DRAW=\S+ STRATUM=(light|heavy|unknown) REASON=\S+$/);
|
|
304
|
+
assert.equal(formatFeatureRow(a.row).split("\t").length, COLUMNS.length);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// route() called directly, so the gate and sizing matrices need no process per case.
|
|
308
|
+
const routeIn = (over) => route({ session: SESSION, ticket: 42, arm: "B", implRow: 3, issue: issue(), guard: readGuard(guardFile()), table: baseTable(), ...over });
|
|
309
|
+
const liveB = (over = {}) => baseTable({ classifier: { b: "haiku", tau: null }, rows: { "*": "slow-high", heavy: "task-high" }, ...over });
|
|
310
|
+
const haikuSizing = { source: "haiku", model: "m", label: "heavy", confidence: 1, usd: 0.5 };
|
|
311
|
+
|
|
312
|
+
test("route: arm B is live only on arm B, with a classifier set and some row not the default", () => {
|
|
313
|
+
const ruleLine = "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=light REASON=ok";
|
|
314
|
+
const cases = [
|
|
315
|
+
["a classifier but every row the default", liveB({ rows: { "*": "slow-high", heavy: "slow-high" } }), "B"],
|
|
316
|
+
["a non-default row but no classifier", liveB({ classifier: { b: null, tau: null } }), "B"],
|
|
317
|
+
["an open gate on arm A", liveB(), "A"],
|
|
318
|
+
];
|
|
319
|
+
for (const [what, table, arm] of cases) {
|
|
320
|
+
const { line, row } = routeIn({ table, arm, sizing: haikuSizing });
|
|
321
|
+
assert.equal(line, ruleLine, what);
|
|
322
|
+
assert.equal(row.sizing_src, "rule", what);
|
|
323
|
+
assert.equal(row.sizing_pre, "", what);
|
|
324
|
+
assert.equal(row.router_usd, "", what);
|
|
325
|
+
}
|
|
326
|
+
assert.equal(routeIn({ table: liveB(), arm: "B", sizing: haikuSizing }).line, "POLICY=slow-high CELL=task-high DRAW=- STRATUM=heavy REASON=ok", "the same inputs with the gate open and arm B do route by the sizing");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("route: a live B sizing is used only when its source is the table's classifier and its label is light or heavy", () => {
|
|
330
|
+
const refused = (table, sizing, what) => {
|
|
331
|
+
const { line, row } = routeIn({ table, sizing });
|
|
332
|
+
assert.equal(line, "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=unknown REASON=no-sizing", what);
|
|
333
|
+
assert.equal(row.sizing_src, "", what);
|
|
334
|
+
assert.equal(row.sizing_pre, "", what);
|
|
335
|
+
};
|
|
336
|
+
refused(liveB(), { ...haikuSizing, source: "jev" }, "a jev verdict on a haiku table");
|
|
337
|
+
refused(liveB({ classifier: { b: "jev", tau: null } }), haikuSizing, "a haiku verdict on a jev table");
|
|
338
|
+
refused(liveB(), { ...haikuSizing, source: undefined }, "no source");
|
|
339
|
+
refused(liveB(), null, "no verdict at all");
|
|
340
|
+
for (const label of ["medium", "Heavy", "", null, undefined, 1, ["heavy"]]) refused(liveB(), { ...haikuSizing, label }, `label ${JSON.stringify(label)}`);
|
|
341
|
+
// A light verdict on a heavy-by-rule brief: the table row of the classifier's stratum, not the rule's.
|
|
342
|
+
const light = routeIn({ table: liveB({ rows: { "*": "slow-high", light: "smol-high" } }), issue: issue({ body: "x".repeat(4000) }), sizing: { ...haikuSizing, label: "light" } });
|
|
343
|
+
assert.equal(light.line, "POLICY=slow-high CELL=smol-high DRAW=- STRATUM=light REASON=ok");
|
|
344
|
+
assert.equal(light.row.sizing_pre, "light");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("route: a jev sizing below tau abstains to `unknown` but records its label, and sizing_src names the model", () => {
|
|
348
|
+
const table = liveB({ classifier: { b: "jev", tau: 0.8 } });
|
|
349
|
+
const jev = (over) => ({ source: "jev", model: "jev-1", label: "heavy", confidence: 0.9, usd: 0.25, ...over });
|
|
350
|
+
const confident = routeIn({ table, sizing: jev() });
|
|
351
|
+
assert.equal(confident.line, "POLICY=slow-high CELL=task-high DRAW=- STRATUM=heavy REASON=ok");
|
|
352
|
+
assert.equal(confident.row.sizing_src, "jev:jev-1");
|
|
353
|
+
assert.equal(confident.row.sizing_pre, "heavy");
|
|
354
|
+
assert.equal(confident.row.router_usd, "0.25");
|
|
355
|
+
assert.equal(routeIn({ table, sizing: jev({ confidence: 0.8 }) }).line, "POLICY=slow-high CELL=task-high DRAW=- STRATUM=heavy REASON=ok", "confidence exactly at tau does not abstain");
|
|
356
|
+
for (const confidence of [0.79, 0, undefined, "high", null]) {
|
|
357
|
+
const r = routeIn({ table, sizing: jev({ confidence }) });
|
|
358
|
+
assert.equal(r.line, "POLICY=slow-high CELL=slow-high DRAW=- STRATUM=unknown REASON=ok", `confidence ${JSON.stringify(confidence)}`);
|
|
359
|
+
assert.equal(r.row.sizing_src, "jev:jev-1");
|
|
360
|
+
assert.equal(r.row.sizing_pre, "heavy", "the abstained label is still recorded");
|
|
361
|
+
assert.equal(r.row.router_usd, "0.25", "the abstained verdict's spend is still recorded");
|
|
362
|
+
}
|
|
363
|
+
assert.equal(routeIn({ table, sizing: jev({ model: undefined }) }).row.sizing_src, "jev:", "no model: the prefix alone");
|
|
364
|
+
assert.equal(routeIn({ table, sizing: jev({ usd: undefined }) }).row.router_usd, "", "no finite usd: blank");
|
|
365
|
+
assert.equal(routeIn({ table, sizing: jev({ usd: "0.25" }) }).row.router_usd, "", "a usd that is not a number: blank");
|
|
366
|
+
// Without a tau the confidence is not consulted; and tau binds only a jev verdict.
|
|
367
|
+
const noTau = routeIn({ table: liveB({ classifier: { b: "jev", tau: null } }), sizing: jev({ confidence: undefined }) });
|
|
368
|
+
assert.equal(noTau.line, "POLICY=slow-high CELL=task-high DRAW=- STRATUM=heavy REASON=ok");
|
|
369
|
+
const haiku = routeIn({ table: liveB({ classifier: { b: "haiku", tau: 0.8 } }), sizing: { ...haikuSizing, confidence: 0.1 } });
|
|
370
|
+
assert.equal(haiku.line, "POLICY=slow-high CELL=task-high DRAW=- STRATUM=heavy REASON=ok");
|
|
371
|
+
assert.equal(haiku.row.sizing_src, "haiku");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("route: a tripped token that is not a cell makes the guard unreadable — default cell only — while a real trip still reads", (t) => {
|
|
375
|
+
const table = baseTable({ burn_in: true });
|
|
376
|
+
for (const tripped of [["task-hgih"], [{ cell: "task-hgih" }], ["smol-high", "task-hgih"]]) {
|
|
377
|
+
const p = world(t, { table, guard: guardFile({ tripped, verdict: "tripped" }) });
|
|
378
|
+
const r = routeCli(p, { implRow: 5 });
|
|
379
|
+
assert.equal(r.status, 0, r.stderr);
|
|
380
|
+
assert.deepEqual(parseLine(r.stdout), { POLICY: "slow-high", CELL: "slow-high", DRAW: "-", STRATUM: "light", REASON: "guard-missing" }, JSON.stringify(tripped));
|
|
381
|
+
}
|
|
382
|
+
const real = world(t, { table, guard: guardFile({ tripped: [{ cell: "smol-high" }], verdict: "tripped" }) });
|
|
383
|
+
const out = parseLine(routeCli(real, { implRow: 5 }).stdout);
|
|
384
|
+
assert.equal(out.REASON, "guard-tripped");
|
|
385
|
+
assert.match(out.DRAW, /^[12]\/2$/);
|
|
386
|
+
assert.notEqual(out.CELL, "smol-high");
|
|
387
|
+
// The fit refuses the same guard rather than fitting around it.
|
|
388
|
+
const fp = fitWorld(t, { features: [], members: [], verdicts: [], guard: guardFile({ tripped: ["task-hgih"] }) });
|
|
389
|
+
const fit = cli(["fit", ...fp.fitArgs, "--guard", fp.guard]);
|
|
390
|
+
assert.equal(fit.status, 2);
|
|
391
|
+
assert.match(fit.stderr, /cannot read --guard/);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("readGuard: a cost guard missing its tripped list, its cells, or well-formed entries in either reads as null", () => {
|
|
395
|
+
const good = guardFile();
|
|
396
|
+
const malformed = [
|
|
397
|
+
["null", null],
|
|
398
|
+
["a string", "x"],
|
|
399
|
+
["tripped missing", { ...good, tripped: undefined }],
|
|
400
|
+
["tripped a string", { ...good, tripped: "task-high" }],
|
|
401
|
+
["tripped an object", { ...good, tripped: {} }],
|
|
402
|
+
["cells missing", { ...good, cells: undefined }],
|
|
403
|
+
["cells an object", { ...good, cells: {} }],
|
|
404
|
+
["a tripped number", { ...good, tripped: [5] }],
|
|
405
|
+
["a tripped null", { ...good, tripped: [null] }],
|
|
406
|
+
["a tripped object with no cell", { ...good, tripped: [{}] }],
|
|
407
|
+
["a tripped object with a numeric cell", { ...good, tripped: [{ cell: 5 }] }],
|
|
408
|
+
["a tripped object with an array cell", { ...good, tripped: [{ cell: ["slow-high"] }] }],
|
|
409
|
+
["a tripped array", { ...good, tripped: [["slow-high"]] }],
|
|
410
|
+
["a tripped token that is not a cell", { ...good, tripped: ["task-hgih"] }],
|
|
411
|
+
["a tripped object whose cell is not a cell", { ...good, tripped: [{ cell: "nope" }] }],
|
|
412
|
+
["one bad tripped token among good ones", { ...good, tripped: ["slow-high", "slow-higher"] }],
|
|
413
|
+
["an empty tripped token", { ...good, tripped: [""] }],
|
|
414
|
+
["a null cells entry", { ...good, cells: [null] }],
|
|
415
|
+
["a bad entry after a good one", { ...good, cells: [{ cell: "slow-high", n: 5 }, null] }],
|
|
416
|
+
["a cells entry with a numeric cell", { ...good, cells: [{ cell: 5, n: 1 }] }],
|
|
417
|
+
["a cells entry with no cell", { ...good, cells: [{ n: 1 }] }],
|
|
418
|
+
["a cells entry with no n", { ...good, cells: [{ cell: "slow-high" }] }],
|
|
419
|
+
["a cells entry with a string n", { ...good, cells: [{ cell: "slow-high", n: "5" }] }],
|
|
420
|
+
["a cells entry with a null n", { ...good, cells: [{ cell: "slow-high", n: null }] }],
|
|
421
|
+
];
|
|
422
|
+
for (const [what, raw] of malformed) assert.equal(readGuard(raw), null, what);
|
|
423
|
+
assert.deepEqual(
|
|
424
|
+
readGuard(guardFile({ tripped: ["task-high", { cell: "smol-high", fail_rate: 1 }, "task-high"] })),
|
|
425
|
+
{ tripped: ["smol-high", "task-high"], n: { "slow-high": 5, "task-high": 5, "smol-high": 5 }, window_start: "2026-09-01" },
|
|
426
|
+
"tripped cells are de-duplicated and sorted; n is per cell; the window rides along",
|
|
427
|
+
);
|
|
428
|
+
assert.deepEqual(readGuard({ tripped: [], cells: [] }), { tripped: [], n: {}, window_start: null });
|
|
429
|
+
assert.equal(readGuard(guardFile({ window_start: 20260901 })).window_start, null, "a window that is not a string is no window");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("validateTable: each malformed router table is refused for its own reason, and each well-formed variant passes", () => {
|
|
433
|
+
const bad = (over) => ({ ...baseTable(), ...over });
|
|
434
|
+
const cases = [
|
|
435
|
+
[/not a JSON object/, null], [/not a JSON object/, []], [/not a JSON object/, "x"],
|
|
436
|
+
[/`cells` is not a non-empty array/, bad({ cells: undefined })],
|
|
437
|
+
[/`cells` is not a non-empty array/, bad({ cells: "slow-high" })],
|
|
438
|
+
[/`cells` is not a non-empty array/, bad({ cells: [] })],
|
|
439
|
+
[/`cells` holds 5/, bad({ cells: [5] })],
|
|
440
|
+
[/`cells` holds "alt"/, bad({ cells: ["slow-high", "alt"] })],
|
|
441
|
+
[/`cells` holds \["slow-high"\]/, bad({ cells: [["slow-high"]] })],
|
|
442
|
+
[/`cells` repeats a cell/, bad({ cells: ["slow-high", "task-high", "slow-high"] })],
|
|
443
|
+
[/`rows` is not an object/, bad({ rows: undefined })],
|
|
444
|
+
[/`rows` is not an object/, bad({ rows: null })],
|
|
445
|
+
[/`rows` is not an object/, bad({ rows: [] })],
|
|
446
|
+
[/`rows` is not an object/, bad({ rows: "x" })],
|
|
447
|
+
[/`rows` has no `\*` row/, bad({ rows: {} })],
|
|
448
|
+
[/`rows` has no `\*` row/, bad({ rows: { heavy: "task-high" } })],
|
|
449
|
+
[/`rows` key "medium" is not/, bad({ rows: { "*": "slow-high", medium: "task-high" } })],
|
|
450
|
+
[/`rows` key "" is not/, bad({ rows: { "*": "slow-high", "": "task-high" } })],
|
|
451
|
+
[/`rows\.heavy` is "alt"/, bad({ rows: { "*": "slow-high", heavy: "alt" } })],
|
|
452
|
+
[/`rows\.heavy` is 5/, bad({ rows: { "*": "slow-high", heavy: 5 } })],
|
|
453
|
+
[/`rows\.heavy` is null/, bad({ rows: { "*": "slow-high", heavy: null } })],
|
|
454
|
+
[/`rows\.\*` is \["slow-high"\]/, bad({ rows: { "*": ["slow-high"] } })],
|
|
455
|
+
[/`burn_in` is not a boolean/, bad({ burn_in: "yes" })],
|
|
456
|
+
[/`burn_in` is not a boolean/, bad({ burn_in: 1 })],
|
|
457
|
+
[/`burn_in` is not a boolean/, bad({ burn_in: undefined })],
|
|
458
|
+
[/`stage` is not a positive integer/, bad({ stage: 0 })],
|
|
459
|
+
[/`stage` is not a positive integer/, bad({ stage: -1 })],
|
|
460
|
+
[/`stage` is not a positive integer/, bad({ stage: 1.5 })],
|
|
461
|
+
[/`stage` is not a positive integer/, bad({ stage: "1" })],
|
|
462
|
+
[/`stage` is not a positive integer/, bad({ stage: undefined })],
|
|
463
|
+
[/`classifier` is not an object/, bad({ classifier: null })],
|
|
464
|
+
[/`classifier` is not an object/, bad({ classifier: undefined })],
|
|
465
|
+
[/`classifier` is not an object/, bad({ classifier: "haiku" })],
|
|
466
|
+
[/`classifier.b` is not/, bad({ classifier: { b: "sonnet", tau: null } })],
|
|
467
|
+
[/`classifier.b` is not/, bad({ classifier: { b: "Haiku", tau: null } })],
|
|
468
|
+
[/`classifier.b` is not/, bad({ classifier: { tau: null } })],
|
|
469
|
+
[/`classifier.tau` is not/, bad({ classifier: { b: null, tau: "0.5" } })],
|
|
470
|
+
[/`classifier.tau` is not/, bad({ classifier: { b: "jev" } })],
|
|
471
|
+
[/`guard` is not/, bad({ guard: undefined })],
|
|
472
|
+
[/`guard` is not/, bad({ guard: null })],
|
|
473
|
+
[/`guard` is not/, bad({ guard: "x" })],
|
|
474
|
+
[/`guard` is not/, bad({ guard: { tripped: "x", n: {} } })],
|
|
475
|
+
[/`guard` is not/, bad({ guard: { n: {} } })],
|
|
476
|
+
[/`guard` is not/, bad({ guard: { tripped: [] } })],
|
|
477
|
+
[/`guard` is not/, bad({ guard: { tripped: [], n: null } })],
|
|
478
|
+
[/`guard` is not/, bad({ guard: { tripped: [], n: "x" } })],
|
|
479
|
+
];
|
|
480
|
+
for (const [why, table] of cases) {
|
|
481
|
+
const r = validateTable(table);
|
|
482
|
+
assert.ok(r instanceof Error, JSON.stringify(table));
|
|
483
|
+
assert.match(r.message, /^router table: /);
|
|
484
|
+
assert.match(r.message, why, JSON.stringify(table));
|
|
485
|
+
}
|
|
486
|
+
const valid = [
|
|
487
|
+
baseTable(),
|
|
488
|
+
bad({ burn_in: true, stage: 2, cells: [...STAGE1, "slow-medium", "task-max"] }),
|
|
489
|
+
bad({ rows: { "*": "slow-high", light: "smol-high", heavy: "task-high", unknown: "slow-high" } }),
|
|
490
|
+
bad({ classifier: { b: "haiku", tau: null } }),
|
|
491
|
+
bad({ classifier: { b: "jev", tau: 0.8 } }),
|
|
492
|
+
bad({ classifier: { b: null, tau: 0 } }),
|
|
493
|
+
bad({ guard: { tripped: ["task-high"], n: { "slow-high": 3 } } }),
|
|
494
|
+
];
|
|
495
|
+
for (const table of valid) assert.equal(validateTable(table), table, JSON.stringify(table));
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("route and fit: a router table that fails validation exits 2 naming the table and the reason, and records nothing", (t) => {
|
|
499
|
+
const dup = baseTable({ cells: ["slow-high", "slow-high"] });
|
|
500
|
+
const p = world(t, { table: dup });
|
|
501
|
+
const r = routeCli(p);
|
|
502
|
+
assert.equal(r.status, 2, r.stderr);
|
|
503
|
+
assert.equal(r.stdout, "");
|
|
504
|
+
assert.ok(r.stderr.includes(`${p.table}: router table: \`cells\` repeats a cell`), r.stderr);
|
|
505
|
+
assert.equal(existsSync(p.pending), false);
|
|
506
|
+
const fp = fitWorld(t, { features: [], members: [], verdicts: [], table: dup });
|
|
507
|
+
const fit = cli(["fit", ...fp.fitArgs, "--guard", fp.guard]);
|
|
508
|
+
assert.equal(fit.status, 2, fit.stderr);
|
|
509
|
+
assert.match(fit.stderr, /router table: `cells` repeats a cell/);
|
|
510
|
+
assert.equal(cli(["--check", ...fp.fitArgs]).status, 2);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("cli: route or fit combined with --check is refused — exactly one mode", (t) => {
|
|
514
|
+
const p = world(t);
|
|
515
|
+
for (const args of [["route", "--check", "--table", p.table], ["--check", "fit", "--table", p.table], ["--check", "route", "--table", p.table]]) {
|
|
516
|
+
const r = cli(args);
|
|
517
|
+
assert.equal(r.status, 2, `${args.join(" ")}: ${r.stderr}`);
|
|
518
|
+
assert.match(r.stderr, /exactly one mode/, args.join(" "));
|
|
519
|
+
assert.equal(r.stdout, "");
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// metrics
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
test("metrics: the last Agent Brief comment is the brief, else the body", () => {
|
|
528
|
+
const brief = "## Agent Brief\nTouch `plugin/scripts/a.mjs`, `tests/b.test.mjs` and `c.json`, not `two words`.\n* [ ] one\n- [X] two\n - [ ] three\nSee #12, #12 and #13.";
|
|
529
|
+
const m = issueMetrics(issue({
|
|
530
|
+
body: "body only #99",
|
|
531
|
+
comments: [{ body: "## Agent Brief\nold" }, { body: brief }, { body: "chatter" }],
|
|
532
|
+
labels: [{ name: "ready-for-agent" }, { name: "enhancement" }, { name: "bug" }],
|
|
533
|
+
}));
|
|
534
|
+
assert.equal(m.brief_chars, String([...brief.trim()].length));
|
|
535
|
+
assert.equal(m.criteria, "3");
|
|
536
|
+
assert.equal(m.comments, "3");
|
|
537
|
+
assert.equal(m.paths, "3");
|
|
538
|
+
assert.equal(m.test_paths, "1");
|
|
539
|
+
assert.equal(m.xrefs, "2");
|
|
540
|
+
assert.equal(m.kind, "enhancement", "the FIRST kind label in label order");
|
|
541
|
+
assert.equal(issueMetrics(issue({ body: "#5 `x/y`" })).xrefs, "1", "no Agent Brief comment: the body is the brief");
|
|
542
|
+
assert.equal(issueMetrics([]), null);
|
|
543
|
+
assert.equal(issueMetrics({ title: "no body" }), null);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("metrics: the rule classifier is heavy past 3483 brief chars or 5 criteria, unknown on blank metrics", () => {
|
|
547
|
+
assert.equal(ruleStratum({ brief_chars: "3483", criteria: "5" }), "light");
|
|
548
|
+
assert.equal(ruleStratum({ brief_chars: "3484", criteria: "0" }), "heavy");
|
|
549
|
+
assert.equal(ruleStratum({ brief_chars: "10", criteria: "6" }), "heavy");
|
|
550
|
+
assert.equal(ruleStratum({ brief_chars: "", criteria: "" }), "unknown");
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("metrics: a backticked token is a path only without whitespace and with a slash or an extension, and test_paths needs a .test. infix or a tests?/ segment", () => {
|
|
554
|
+
const m = (body) => issueMetrics(issue({ body }));
|
|
555
|
+
assert.equal(m("`my dir/file.ts`").paths, "0", "a token with whitespace is prose");
|
|
556
|
+
assert.equal(m("`plugin/scripts`").paths, "1", "a slash alone makes a path");
|
|
557
|
+
assert.equal(m("`main.c`").paths, "1", "a one-letter extension is an extension");
|
|
558
|
+
assert.equal(m("`a.mjs`").paths, "1", "an extension alone makes a path");
|
|
559
|
+
assert.equal(m("`1.9`").paths, "0", "an extension starts with a letter");
|
|
560
|
+
assert.equal(m("`a.b-c`").paths, "0", "an extension runs to the end of the token");
|
|
561
|
+
assert.equal(m("`Makefile`").paths, "0", "a bare word is not a path");
|
|
562
|
+
assert.equal(m("`a.mjs2`").paths, "1", "an extension may carry digits after its first letter");
|
|
563
|
+
assert.equal(m("`a/b` `a/b`").paths, "1", "a path named twice counts once");
|
|
564
|
+
assert.equal(m("`a/x.test.mjs`").test_paths, "1", "a .test. infix alone");
|
|
565
|
+
assert.equal(m("`a/b.test`").test_paths, "0", ".test needs a trailing dot");
|
|
566
|
+
assert.equal(m("`src/contest.mjs`").test_paths, "0", ".test. needs its leading dot");
|
|
567
|
+
assert.equal(m("`tests/x.mjs`").test_paths, "1", "a leading tests/ alone");
|
|
568
|
+
assert.equal(m("`a/test/x.mjs`").test_paths, "1", "test/ after a slash");
|
|
569
|
+
assert.equal(m("`a/tests/x.mjs`").test_paths, "1", "tests/ after a slash");
|
|
570
|
+
assert.equal(m("`contests/x.mjs`").test_paths, "0", "tests/ must start a path segment");
|
|
571
|
+
assert.equal(m("`lib/testdata/x.json`").test_paths, "0", "test/ and tests/ end in a slash");
|
|
572
|
+
assert.equal(m("`x.mjs`").test_paths, "0");
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// ---------------------------------------------------------------------------
|
|
576
|
+
// fit and --check
|
|
577
|
+
// ---------------------------------------------------------------------------
|
|
578
|
+
|
|
579
|
+
const featureRow = (over) => ({
|
|
580
|
+
run_date: "2026-10-01", session: SESSION, agent: `impl-${over.ticket}`, policy_cell: "slow-high", chosen_cell: "slow-high",
|
|
581
|
+
exploration_draw: "", sizing_src: "rule", sizing_pre: "", router_usd: "", brief_chars: "100", criteria: "1", comments: "0",
|
|
582
|
+
age_days: "1", paths: "0", test_paths: "0", xrefs: "0", kind: "bug", ...over,
|
|
583
|
+
});
|
|
584
|
+
const memberRow = (over) => Object.fromEntries(MEMBER_COLUMNS.map((c) => [c, ""]).concat(Object.entries({ session: SESSION, ...over })));
|
|
585
|
+
const verdictRow = (over) => ({ run_date: "2026-10-01", class: "", tier: "", note: "n", sizing: "", profile: "", loc: "", files: "", closed_own_ticket: "yes", minted_false_claim: "no", ...over });
|
|
586
|
+
const tsv = (cols, rows, header = false) => (header ? `${cols.join("\t")}\n` : "") + rows.map((r) => cols.map((c) => r[c] ?? "").join("\t")).join("\n") + (rows.length ? "\n" : "");
|
|
587
|
+
|
|
588
|
+
function fitWorld(t, { features, members, verdicts, table = baseTable({ burn_in: true }), guard = guardFile() }) {
|
|
589
|
+
const p = world(t, { table, guard });
|
|
590
|
+
p.features = join(p.dir, "ticket-features.tsv");
|
|
591
|
+
p.members = join(p.dir, "member-outcomes.tsv");
|
|
592
|
+
p.verdicts = join(p.dir, "tier-outcomes.tsv");
|
|
593
|
+
writeFileSync(p.features, tsv(COLUMNS, features, true));
|
|
594
|
+
writeFileSync(p.members, tsv(MEMBER_COLUMNS, members.map(memberRow)));
|
|
595
|
+
writeFileSync(p.verdicts, tsv(VERDICT_COLUMNS, verdicts.map(verdictRow)));
|
|
596
|
+
p.fitArgs = ["--table", p.table, "--features", p.features, "--members", p.members, "--verdicts", p.verdicts];
|
|
597
|
+
return p;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
test("fit: a day-one three-TSV fixture writes an estimates-only table, and --check accepts it", (t) => {
|
|
601
|
+
const features = [
|
|
602
|
+
featureRow({ ticket: "1", chosen_cell: "task-high", exploration_draw: "2/3" }),
|
|
603
|
+
featureRow({ ticket: "2", chosen_cell: "slow-high", exploration_draw: "1/3", brief_chars: "5000" }),
|
|
604
|
+
featureRow({ ticket: "3", chosen_cell: "smol-high", exploration_draw: "3/3", run_date: "2026-10-02" }),
|
|
605
|
+
];
|
|
606
|
+
const members = [
|
|
607
|
+
{ member: "impl-1", agent: "impl-1", ticket: "1", pr: "11", role: "implementer" },
|
|
608
|
+
{ member: "fix-pr-11", agent: "fix-pr-11", pr: "11", role: "fix-applier" },
|
|
609
|
+
{ member: "merge-bot-1", agent: "merge-bot-1", pr: "11", role: "merge-bot" },
|
|
610
|
+
];
|
|
611
|
+
const verdicts = [{ pr: "11", ticket: "1" }, { pr: "12", ticket: "2+9", minted_false_claim: "yes" }];
|
|
612
|
+
const p = fitWorld(t, { features, members, verdicts });
|
|
613
|
+
const r = cli(["fit", ...p.fitArgs, "--guard", p.guard]);
|
|
614
|
+
assert.equal(r.status, 0, r.stderr);
|
|
615
|
+
const table = JSON.parse(readFileSync(p.table, "utf8"));
|
|
616
|
+
assert.deepEqual(table.rows, { "*": "slow-high" }, "no cell has n>=20 yet, so no row is adopted");
|
|
617
|
+
assert.equal(table.burn_in, true);
|
|
618
|
+
assert.equal(table.stage, 1);
|
|
619
|
+
assert.equal(table.n_rows, 3);
|
|
620
|
+
assert.equal(table.fitted_through, "2026-10-02");
|
|
621
|
+
assert.equal(table.window_start, "2026-09-01", "the window comes from the guard; the fit never moves it");
|
|
622
|
+
assert.deepEqual(table.estimates["*"]["task-high"], { n: 1, merged: 1, usd_per_merged: null, fail_rate: 0, fix_rounds: 1, review_findings: null });
|
|
623
|
+
assert.deepEqual(table.estimates.heavy["slow-high"], { n: 1, merged: 1, usd_per_merged: null, fail_rate: 1, fix_rounds: 0, review_findings: null });
|
|
624
|
+
assert.equal(table.estimates.light["smol-high"].merged, 0);
|
|
625
|
+
assert.deepEqual(table.guard, { tripped: [], n: { "slow-high": 5, "smol-high": 5, "task-high": 5 } });
|
|
626
|
+
|
|
627
|
+
const check = cli(["--check", ...p.fitArgs]);
|
|
628
|
+
assert.equal(check.status, 0, check.stderr);
|
|
629
|
+
assert.match(check.stdout, /matches a re-fit of 3 tickets through 2026-10-02/);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("--check fails when the checked-in table is not a re-fit of its rows", (t) => {
|
|
633
|
+
const features = [featureRow({ ticket: "1", chosen_cell: "task-high", exploration_draw: "2/3" })];
|
|
634
|
+
const p = fitWorld(t, { features, members: [], verdicts: [{ pr: "11", ticket: "1" }] });
|
|
635
|
+
assert.equal(cli(["fit", ...p.fitArgs, "--guard", p.guard]).status, 0);
|
|
636
|
+
const good = JSON.parse(readFileSync(p.table, "utf8"));
|
|
637
|
+
for (const [what, edit] of [
|
|
638
|
+
["a hand-adopted row", (x) => { x.rows["*"] = "task-high"; }],
|
|
639
|
+
["burn-in switched off by hand", (x) => { x.burn_in = false; }],
|
|
640
|
+
["an edited estimate", (x) => { x.estimates["*"]["task-high"].n = 30; }],
|
|
641
|
+
]) {
|
|
642
|
+
const bad = structuredClone(good);
|
|
643
|
+
edit(bad);
|
|
644
|
+
writeFileSync(p.table, JSON.stringify(bad));
|
|
645
|
+
const r = cli(["--check", ...p.fitArgs]);
|
|
646
|
+
assert.equal(r.status, 1, `${what}: ${r.stderr}`);
|
|
647
|
+
assert.match(r.stderr, /is not a re-fit of its own rows/);
|
|
648
|
+
}
|
|
649
|
+
// Rows dated after fitted_through are the next fit's, not a mismatch.
|
|
650
|
+
writeFileSync(p.table, JSON.stringify(good));
|
|
651
|
+
writeFileSync(p.features, tsv(COLUMNS, [...features, featureRow({ ticket: "2", run_date: "2026-10-09" })], true));
|
|
652
|
+
assert.equal(cli(["--check", ...p.fitArgs]).status, 0);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test("--check stays green when a verdict or member row lands after the fit, for a ticket the fit already read", (t) => {
|
|
656
|
+
const features = [featureRow({ ticket: "1", chosen_cell: "task-high", exploration_draw: "2/3" })];
|
|
657
|
+
const members = [{ agent: "impl-1", member: "impl-1", ticket: "1", pr: "11" }];
|
|
658
|
+
const p = fitWorld(t, { features, members, verdicts: [{ pr: "11", ticket: "1" }] });
|
|
659
|
+
assert.equal(cli(["fit", ...p.fitArgs, "--guard", p.guard]).status, 0);
|
|
660
|
+
const fitted = readFileSync(p.table, "utf8");
|
|
661
|
+
assert.equal(JSON.parse(fitted).fitted_through, "2026-10-01");
|
|
662
|
+
// The ruling lands later and flips the ticket to a failure, and a fix round books against it.
|
|
663
|
+
writeFileSync(p.verdicts, tsv(VERDICT_COLUMNS, [verdictRow({ pr: "11", ticket: "1" }), verdictRow({ run_date: "2026-10-09", pr: "11", ticket: "1", minted_false_claim: "yes" })], true));
|
|
664
|
+
writeFileSync(p.members, tsv(MEMBER_COLUMNS, [...members, { run_date: "2026-10-09", agent: "fix-pr-11", member: "fix-pr-11", ticket: "1", pr: "11" }].map(memberRow), true));
|
|
665
|
+
const r = cli(["--check", ...p.fitArgs]);
|
|
666
|
+
assert.equal(r.status, 0, r.stderr);
|
|
667
|
+
assert.equal(readFileSync(p.table, "utf8"), fitted, "--check never rewrites the table");
|
|
668
|
+
// The next fit, once a later features row exists, reads both.
|
|
669
|
+
writeFileSync(p.features, tsv(COLUMNS, [...features, featureRow({ ticket: "2", run_date: "2026-10-09" })], true));
|
|
670
|
+
assert.equal(cli(["fit", ...p.fitArgs, "--guard", p.guard]).status, 0);
|
|
671
|
+
const next = JSON.parse(readFileSync(p.table, "utf8"));
|
|
672
|
+
assert.equal(next.fitted_through, "2026-10-09");
|
|
673
|
+
assert.equal(next.estimates["*"]["task-high"].fail_rate, 1, "the later ruling is read once the fit reaches its date");
|
|
674
|
+
assert.equal(next.estimates["*"]["task-high"].fix_rounds, 1);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("--check passes on a day-one table over a header-only features file", (t) => {
|
|
678
|
+
const p = fitWorld(t, { features: [], members: [], verdicts: [], table: JSON.parse(readFileSync(DEFAULT_TABLE, "utf8")) });
|
|
679
|
+
const r = cli(["--check", ...p.fitArgs]);
|
|
680
|
+
assert.equal(r.status, 0, r.stderr);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Twenty-plus tickets a side, so the adoption rule can decide.
|
|
684
|
+
function adoptionInput({ cheapFails, cheapUsd = 2, baseUsd = 10, n = 20 }) {
|
|
685
|
+
const features = [];
|
|
686
|
+
const members = [];
|
|
687
|
+
const verdicts = [];
|
|
688
|
+
let ticket = 100;
|
|
689
|
+
for (const [cell, usd, fail] of [["slow-high", baseUsd, false], ["smol-high", cheapUsd, cheapFails]]) {
|
|
690
|
+
for (let i = 0; i < n; i++, ticket++) {
|
|
691
|
+
features.push(featureRow({ ticket: String(ticket), chosen_cell: cell, exploration_draw: "1/3" }));
|
|
692
|
+
members.push({ session: SESSION, agent: `impl-${ticket}`, member: `impl-${ticket}`, ticket: String(ticket), pr: String(ticket + 1000), cost: String(usd) });
|
|
693
|
+
verdicts.push({ ticket: String(ticket), pr: String(ticket + 1000), closed_own_ticket: "yes", minted_false_claim: fail ? "yes" : "no" });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { features, members, verdicts: verdicts.map(verdictRow) };
|
|
697
|
+
}
|
|
698
|
+
const atN = { tripped: [], n: { "slow-high": 20, "task-high": 20, "smol-high": 20 }, window_start: null };
|
|
699
|
+
|
|
700
|
+
test("fit: adopts the cheapest cell at n>=20 under 0.75x the default's $/merged PR, and never re-tests the quality floor", () => {
|
|
701
|
+
// Every smol-high PR fails the floor; the fit adopts it anyway — the guard,
|
|
702
|
+
// not the fit, is the quality gate.
|
|
703
|
+
const next = fitTable({ prior: baseTable({ burn_in: true }), ...adoptionInput({ cheapFails: true }), guard: atN });
|
|
704
|
+
assert.equal(next.estimates["*"]["smol-high"].fail_rate, 1);
|
|
705
|
+
assert.equal(next.rows["*"], "smol-high");
|
|
706
|
+
assert.equal(next.rows.light, "smol-high", "a stratum with both sides at n>=20 gets its own row");
|
|
707
|
+
assert.equal(next.stage, 2, "an adoption with every stage-1 cell at n advances the stage");
|
|
708
|
+
assert.deepEqual(next.cells, [...STAGE1, "slow-medium", "task-max"]);
|
|
709
|
+
// Burn-in ends only when EVERY cell in `cells` has n>=20, and the stage-2
|
|
710
|
+
// cells have none yet.
|
|
711
|
+
assert.equal(next.burn_in, true);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("fit: no adoption above the 0.75x margin, for a tripped cell, or below n=20 — the default stays", () => {
|
|
715
|
+
const above = fitTable({ prior: baseTable({ burn_in: true }), ...adoptionInput({ cheapFails: false, cheapUsd: 8 }), guard: atN });
|
|
716
|
+
assert.deepEqual(above.rows, { "*": "slow-high", light: "slow-high" });
|
|
717
|
+
assert.equal(above.burn_in, false, "every stage-1 cell has pooled n>=20 in the guard");
|
|
718
|
+
assert.equal(above.stage, 1, "no adoption and no eviction: the stage holds");
|
|
719
|
+
const tripped = fitTable({ prior: baseTable(), ...adoptionInput({ cheapFails: false }), guard: { ...atN, tripped: ["smol-high"] } });
|
|
720
|
+
assert.deepEqual(tripped.rows, { "*": "slow-high" });
|
|
721
|
+
assert.equal(tripped.stage, 2, "an eviction with every stage-1 cell at n advances the stage too");
|
|
722
|
+
assert.deepEqual(tripped.cells, [...STAGE1, "slow-medium", "task-max"]);
|
|
723
|
+
const small = fitTable({ prior: baseTable(), ...adoptionInput({ cheapFails: false, n: 19 }), guard: atN });
|
|
724
|
+
assert.deepEqual(small.rows, { "*": "slow-high" });
|
|
725
|
+
// A booked row with no cost on record makes the cell's $ unknown, and unknown never adopts.
|
|
726
|
+
const input = adoptionInput({ cheapFails: false });
|
|
727
|
+
input.members[30].cost = "";
|
|
728
|
+
const unknown = fitTable({ prior: baseTable(), ...input, guard: atN });
|
|
729
|
+
assert.equal(unknown.estimates["*"]["smol-high"].usd_per_merged, null);
|
|
730
|
+
assert.deepEqual(unknown.rows, { "*": "slow-high" });
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test("fit: a non-exploration row a live B classifier routed is the A/B's test set, not fit input", () => {
|
|
734
|
+
const features = [
|
|
735
|
+
featureRow({ ticket: "1", sizing_src: "haiku", chosen_cell: "task-high" }),
|
|
736
|
+
featureRow({ ticket: "2", sizing_src: "haiku", chosen_cell: "task-high", exploration_draw: "1/2" }),
|
|
737
|
+
featureRow({ ticket: "3" }),
|
|
738
|
+
];
|
|
739
|
+
const next = fitTable({ prior: baseTable(), features, members: [], verdicts: [], guard: atN });
|
|
740
|
+
assert.equal(next.n_rows, 2);
|
|
741
|
+
assert.equal(next.estimates["*"]["task-high"].n, 1);
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("fit --due counts merged PRs since fitted_through against the cadence", (t) => {
|
|
745
|
+
const features = Array.from({ length: 50 }, (_, i) => featureRow({ ticket: String(i + 1), run_date: "2026-10-05" }));
|
|
746
|
+
const verdicts = features.map((f) => ({ ticket: f.ticket, pr: String(Number(f.ticket) + 500) }));
|
|
747
|
+
const p = fitWorld(t, { features, members: [], verdicts: verdicts.slice(0, 49), table: baseTable({ fitted_through: "2026-10-01" }) });
|
|
748
|
+
const before = readFileSync(p.table, "utf8");
|
|
749
|
+
const no = cli(["fit", ...p.fitArgs, "--guard", p.guard, "--due"]);
|
|
750
|
+
assert.equal(no.stdout, "DUE=no MERGED=49/50\n");
|
|
751
|
+
assert.equal(readFileSync(p.table, "utf8"), before, "--due writes nothing");
|
|
752
|
+
writeFileSync(p.verdicts, tsv(VERDICT_COLUMNS, verdicts.map(verdictRow)));
|
|
753
|
+
assert.equal(cli(["fit", ...p.fitArgs, "--guard", p.guard, "--due"]).stdout, "DUE=yes MERGED=50/50\n");
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
test("mergedSince: strictly after fitted_through, on or after window_start, each bound only when set, the window bounding what fitted_through admits", () => {
|
|
757
|
+
const features = ["2026-09-30", "2026-10-01", "2026-10-02"].map((d, i) => featureRow({ ticket: String(i + 1), run_date: d }));
|
|
758
|
+
const verdicts = features.map((f) => ({ ticket: f.ticket, pr: String(Number(f.ticket) + 500) }));
|
|
759
|
+
const count = (over) => mergedSince({ table: baseTable(over), features, verdicts });
|
|
760
|
+
assert.equal(count({ fitted_through: null, window_start: null }), 3, "neither bound set: every ruled ticket counts");
|
|
761
|
+
assert.equal(count({ fitted_through: "2026-10-01" }), 1, "the fitted_through day itself is already fitted");
|
|
762
|
+
assert.equal(count({ window_start: "2026-10-01" }), 2, "the window_start day itself is inside the window");
|
|
763
|
+
assert.equal(count({ window_start: "2026-09-30", fitted_through: "2026-09-30" }), 2);
|
|
764
|
+
assert.equal(count({ window_start: "2026-10-02", fitted_through: "2026-09-30" }), 1, "the window still bounds what fitted_through admits");
|
|
765
|
+
assert.equal(mergedSince({ table: baseTable(), features, verdicts: verdicts.slice(1) }), 2, "a ticket with no verdict is not a merged PR");
|
|
766
|
+
const twice = [...features, featureRow({ ticket: "3", run_date: "2026-10-03" })];
|
|
767
|
+
assert.equal(mergedSince({ table: baseTable(), features: twice, verdicts }), 3, "a ticket with two features rows is one merged PR");
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("fit refuses an unreadable guard or input file at exit 2 and leaves the table alone", (t) => {
|
|
771
|
+
const p = fitWorld(t, { features: [], members: [], verdicts: [], guard: null });
|
|
772
|
+
const before = readFileSync(p.table, "utf8");
|
|
773
|
+
const r = cli(["fit", ...p.fitArgs, "--guard", p.guard]);
|
|
774
|
+
assert.equal(r.status, 2);
|
|
775
|
+
assert.match(r.stderr, /cannot read --guard/);
|
|
776
|
+
writeFileSync(p.features, "not\tthe\theader\n");
|
|
777
|
+
const r2 = cli(["--check", ...p.fitArgs]);
|
|
778
|
+
assert.equal(r2.status, 2);
|
|
779
|
+
assert.match(r2.stderr, /cannot read --features/);
|
|
780
|
+
assert.equal(readFileSync(p.table, "utf8"), before);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// A direct fitTable call: the `cost` column is not in member-outcomes COLUMNS, so cost-bearing fixtures skip the TSV round trip.
|
|
784
|
+
const fitDirect = (input, guard = {}) => fitTable({ prior: baseTable(), ...input, guard: { tripped: [], n: {}, window_start: null, ...guard } });
|
|
785
|
+
const exploring = (over) => featureRow({ exploration_draw: "1/3", ...over });
|
|
786
|
+
const ruled = (ticket, pr, over) => verdictRow({ ticket: String(ticket), pr: String(pr), ...over });
|
|
787
|
+
|
|
788
|
+
test("fit: a ticket's cell and stratum are its last features row's", () => {
|
|
789
|
+
const features = [
|
|
790
|
+
exploring({ ticket: "1", chosen_cell: "task-high", brief_chars: "100" }),
|
|
791
|
+
exploring({ ticket: "1", chosen_cell: "smol-high", exploration_draw: "2/3", brief_chars: "5000" }),
|
|
792
|
+
];
|
|
793
|
+
const members = [memberRow({ agent: "impl-1", member: "impl-1", ticket: "1", cost: "3" })];
|
|
794
|
+
const next = fitDirect({ features, members, verdicts: [ruled(1, 11)] });
|
|
795
|
+
assert.equal(next.n_rows, 1);
|
|
796
|
+
assert.deepEqual(Object.keys(next.estimates["*"]), ["smol-high"]);
|
|
797
|
+
assert.deepEqual(Object.keys(next.estimates.heavy), ["smol-high"]);
|
|
798
|
+
assert.equal(next.estimates.light, undefined);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("fit: a ticket's $ adds the router's own spend from every one of its features rows to its members' cost", () => {
|
|
802
|
+
const features = [
|
|
803
|
+
exploring({ ticket: "1", router_usd: "0.5" }),
|
|
804
|
+
exploring({ ticket: "1", router_usd: "" }),
|
|
805
|
+
exploring({ ticket: "1", router_usd: "0.25" }),
|
|
806
|
+
];
|
|
807
|
+
const members = [memberRow({ agent: "impl-1", member: "impl-1", ticket: "1", cost: "3" })];
|
|
808
|
+
const next = fitDirect({ features, members, verdicts: [ruled(1, 11)] });
|
|
809
|
+
assert.equal(next.estimates["*"]["slow-high"].usd_per_merged, 3.75);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("fit: members whose cost does not vary with the cell are not booked against a ticket", () => {
|
|
813
|
+
const features = [exploring({ ticket: "1" })];
|
|
814
|
+
const named = (member, cost) => memberRow({ agent: member, member, ticket: "1", cost });
|
|
815
|
+
const members = [named("impl-1", "2"), named("merge-bot-9", "100"), named("memory", "200"), named("__advisor", "400")];
|
|
816
|
+
const next = fitDirect({ features, members, verdicts: [ruled(1, 11)] });
|
|
817
|
+
assert.equal(next.estimates["*"]["slow-high"].usd_per_merged, 2);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("fit: a ticket whose only members are cell-independent has an unknown $, not a free one", () => {
|
|
821
|
+
const features = [exploring({ ticket: "1" })];
|
|
822
|
+
const named = (member) => memberRow({ agent: member, member, ticket: "1", cost: "1" });
|
|
823
|
+
const members = [named("merge-bot-9"), named("memory"), named("__advisor")];
|
|
824
|
+
const next = fitDirect({ features, members, verdicts: [ruled(1, 11)] });
|
|
825
|
+
assert.equal(next.estimates["*"]["slow-high"].merged, 1);
|
|
826
|
+
assert.equal(next.estimates["*"]["slow-high"].usd_per_merged, null);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("fit: a member row books against a ticket by its ticket, by its session and agent, or by the ticket's ruled PR, and no other way", () => {
|
|
830
|
+
const features = [
|
|
831
|
+
exploring({ ticket: "1", chosen_cell: "slow-high" }),
|
|
832
|
+
exploring({ ticket: "2", chosen_cell: "task-high" }),
|
|
833
|
+
exploring({ ticket: "3", chosen_cell: "smol-high" }),
|
|
834
|
+
];
|
|
835
|
+
const members = [
|
|
836
|
+
memberRow({ session: "elsewhere", agent: "impl-x1", member: "impl-x1", ticket: "1", pr: "", cost: "2" }),
|
|
837
|
+
memberRow({ session: SESSION, agent: "impl-2", member: "impl-2", ticket: "", pr: "", cost: "3" }),
|
|
838
|
+
memberRow({ session: "elsewhere", agent: "impl-x3", member: "impl-x3", ticket: "", pr: "703", cost: "5" }),
|
|
839
|
+
// The right agent in another session, and the right session under another agent, are neither of the three.
|
|
840
|
+
memberRow({ session: "elsewhere", agent: "impl-2", member: "impl-2", ticket: "", pr: "", cost: "1000" }),
|
|
841
|
+
memberRow({ session: SESSION, agent: "impl-9", member: "impl-9", ticket: "", pr: "", cost: "2000" }),
|
|
842
|
+
];
|
|
843
|
+
const verdicts = [ruled(1, 701), ruled(2, 702), ruled(3, 703)];
|
|
844
|
+
const est = fitDirect({ features, members, verdicts }).estimates["*"];
|
|
845
|
+
assert.equal(est["slow-high"].usd_per_merged, 2, "booked by ticket alone");
|
|
846
|
+
assert.equal(est["task-high"].usd_per_merged, 3, "booked by session and agent alone");
|
|
847
|
+
assert.equal(est["smol-high"].usd_per_merged, 5, "booked by the ticket's ruled PR alone");
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test("fit: features rows dated before the window are not the fit's, whole tickets and single rows alike", () => {
|
|
851
|
+
const features = [
|
|
852
|
+
exploring({ ticket: "1", run_date: "2026-08-31" }),
|
|
853
|
+
exploring({ ticket: "2", run_date: "2026-08-31", router_usd: "5" }),
|
|
854
|
+
exploring({ ticket: "2", run_date: "2026-09-01", router_usd: "1" }),
|
|
855
|
+
];
|
|
856
|
+
const members = [
|
|
857
|
+
memberRow({ agent: "impl-1", member: "impl-1", ticket: "1", cost: "2" }),
|
|
858
|
+
memberRow({ agent: "impl-2", member: "impl-2", ticket: "2", cost: "1" }),
|
|
859
|
+
];
|
|
860
|
+
const next = fitDirect({ features, members, verdicts: [ruled(2, 12, { run_date: "2026-09-01" })] }, { window_start: "2026-09-01" });
|
|
861
|
+
assert.equal(next.n_rows, 1, "the window's first day is in; the day before is out");
|
|
862
|
+
const e = next.estimates["*"]["slow-high"];
|
|
863
|
+
assert.equal(e.n, 1);
|
|
864
|
+
assert.equal(e.usd_per_merged, 2, "the out-of-window row's router spend is not the ticket's");
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// Tickets in one cell mixing ruled-pass, ruled-failure and not-yet-ruled, each booked at $2.
|
|
868
|
+
function unmergedMix() {
|
|
869
|
+
const ids = ["1", "2", "3", "4"];
|
|
870
|
+
return {
|
|
871
|
+
features: ids.map((ticket) => exploring({ ticket })),
|
|
872
|
+
members: ids.map((ticket) => memberRow({ agent: `impl-${ticket}`, member: `impl-${ticket}`, ticket, cost: "2" })),
|
|
873
|
+
verdicts: [ruled(1, 11), ruled(2, 12, { minted_false_claim: "yes" })],
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
test("fit: a cell's fail rate is its failed PRs over its merged PRs, not over every ticket in it", () => {
|
|
878
|
+
const e = fitDirect(unmergedMix()).estimates["*"]["slow-high"];
|
|
879
|
+
assert.equal(e.n, 4);
|
|
880
|
+
assert.equal(e.merged, 2);
|
|
881
|
+
assert.equal(e.fail_rate, 0.5);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("fit: a cell's $ per merged PR spreads every ticket's cost over the merged PRs, not over every ticket", () => {
|
|
885
|
+
assert.equal(fitDirect(unmergedMix()).estimates["*"]["slow-high"].usd_per_merged, 4);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// `[cell, usd, n]` entries: n tickets in the cell, each booked at `usd` and ruled a pass, so the cell's $ per merged PR is `usd`.
|
|
889
|
+
function cellInput(spec) {
|
|
890
|
+
const features = [];
|
|
891
|
+
const members = [];
|
|
892
|
+
const verdicts = [];
|
|
893
|
+
let ticket = 100;
|
|
894
|
+
for (const [cell, usd, n = 20] of spec) {
|
|
895
|
+
for (let i = 0; i < n; i++, ticket++) {
|
|
896
|
+
features.push(exploring({ ticket: String(ticket), chosen_cell: cell }));
|
|
897
|
+
members.push(memberRow({ agent: `impl-${ticket}`, member: `impl-${ticket}`, ticket: String(ticket), cost: String(usd) }));
|
|
898
|
+
verdicts.push(ruled(ticket, ticket + 1000));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return { features, members, verdicts };
|
|
902
|
+
}
|
|
903
|
+
const stage1AtN = { "slow-high": 20, "task-high": 20, "smol-high": 20 };
|
|
904
|
+
|
|
905
|
+
test("fit: a cell at exactly 0.75x the default's $ per merged PR is adopted", () => {
|
|
906
|
+
const next = fitDirect(cellInput([["slow-high", 10], ["smol-high", 7.5]]));
|
|
907
|
+
assert.equal(next.estimates["*"]["smol-high"].usd_per_merged, 7.5);
|
|
908
|
+
assert.equal(next.estimates["*"]["slow-high"].usd_per_merged, 10);
|
|
909
|
+
assert.equal(next.rows["*"], "smol-high");
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test("fit: a cell just above 0.75x the default's $ per merged PR is not adopted", () => {
|
|
913
|
+
const next = fitDirect(cellInput([["slow-high", 10], ["smol-high", 7.6]]));
|
|
914
|
+
assert.equal(next.estimates["*"]["smol-high"].usd_per_merged, 7.6);
|
|
915
|
+
assert.equal(next.rows["*"], "slow-high");
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
test("fit: equally cheap candidates break the tie by cell name, whatever their order in `cells`", () => {
|
|
919
|
+
// `cells` lists task-high before smol-high; the name order is the reverse.
|
|
920
|
+
const next = fitDirect(cellInput([["slow-high", 10], ["task-high", 2], ["smol-high", 2]]));
|
|
921
|
+
assert.equal(next.rows["*"], "smol-high");
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
test("fit: the cheapest candidate is the one adopted, wherever it sits in `cells`", () => {
|
|
925
|
+
const lastCheapest = fitDirect(cellInput([["slow-high", 10], ["task-high", 5], ["smol-high", 3]]));
|
|
926
|
+
assert.equal(lastCheapest.rows["*"], "smol-high");
|
|
927
|
+
const firstCheapest = fitDirect(cellInput([["slow-high", 10], ["task-high", 3], ["smol-high", 5]]));
|
|
928
|
+
assert.equal(firstCheapest.rows["*"], "task-high");
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
test("fit: a stratum with no default cell on record adopts nothing", () => {
|
|
932
|
+
const next = fitDirect(cellInput([["smol-high", 2]]));
|
|
933
|
+
assert.deepEqual(next.rows, { "*": "slow-high" });
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test("fit: a stratum whose default cell is below n=20 adopts nothing", () => {
|
|
937
|
+
const next = fitDirect(cellInput([["slow-high", 10, 19], ["smol-high", 2]]));
|
|
938
|
+
assert.deepEqual(next.rows, { "*": "slow-high" });
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test("fit: a stratum whose default cell's $ is unknown adopts nothing, even from a cell that cost nothing", () => {
|
|
942
|
+
const input = cellInput([["slow-high", 10], ["smol-high", 0]]);
|
|
943
|
+
input.members[0].cost = "";
|
|
944
|
+
const next = fitDirect(input);
|
|
945
|
+
assert.equal(next.estimates["*"]["slow-high"].usd_per_merged, null);
|
|
946
|
+
assert.equal(next.estimates["*"]["smol-high"].usd_per_merged, 0);
|
|
947
|
+
assert.deepEqual(next.rows, { "*": "slow-high" });
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test("fit: stage 2 waits until every stage-1 cell is at n>=20, even with an adoption in hand", () => {
|
|
951
|
+
const next = fitDirect(cellInput([["slow-high", 10], ["smol-high", 2]]), { n: { ...stage1AtN, "task-high": 19 } });
|
|
952
|
+
assert.equal(next.rows["*"], "smol-high");
|
|
953
|
+
assert.equal(next.stage, 1);
|
|
954
|
+
assert.deepEqual(next.cells, STAGE1);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test("fit: stage 2 adds no effort cell on a role whose stage-1 cell is tripped", () => {
|
|
958
|
+
const input = cellInput([["slow-high", 10], ["smol-high", 2]]);
|
|
959
|
+
const taskTripped = fitDirect(input, { n: stage1AtN, tripped: ["task-high"] });
|
|
960
|
+
assert.equal(taskTripped.stage, 2);
|
|
961
|
+
assert.deepEqual(taskTripped.cells, [...STAGE1, "slow-medium"]);
|
|
962
|
+
const slowTripped = fitDirect(input, { n: stage1AtN, tripped: ["slow-high"] });
|
|
963
|
+
assert.equal(slowTripped.stage, 2);
|
|
964
|
+
assert.deepEqual(slowTripped.cells, [...STAGE1, "task-max"]);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("fit: stage 2 does not add an effort cell the table already holds", () => {
|
|
968
|
+
const prior = baseTable({ cells: [...STAGE1, "slow-medium"] });
|
|
969
|
+
const n = { ...stage1AtN, "slow-medium": 20 };
|
|
970
|
+
const next = fitTable({ prior, ...cellInput([["slow-high", 10], ["smol-high", 2]]), guard: { tripped: [], n, window_start: null } });
|
|
971
|
+
assert.equal(next.stage, 2);
|
|
972
|
+
assert.deepEqual(next.cells, [...STAGE1, "slow-medium", "task-max"]);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test("fit: stage 2 adds no effort cell on a role the table holds no stage-1 cell for", () => {
|
|
976
|
+
const prior = baseTable({ cells: ["slow-high", "smol-high"] });
|
|
977
|
+
const n = { "slow-high": 20, "smol-high": 20 };
|
|
978
|
+
const next = fitTable({ prior, ...cellInput([["slow-high", 10], ["smol-high", 2]]), guard: { tripped: [], n, window_start: null } });
|
|
979
|
+
assert.equal(next.stage, 2);
|
|
980
|
+
assert.deepEqual(next.cells, ["slow-high", "smol-high", "slow-medium"]);
|
|
981
|
+
});
|