diff --git a/agents/gizmoduck/work/queue-270/BUILD.md b/agents/gizmoduck/work/queue-270/BUILD.md index e40f775c7..c0662ba9c 100644 --- a/agents/gizmoduck/work/queue-270/BUILD.md +++ b/agents/gizmoduck/work/queue-270/BUILD.md @@ -154,3 +154,43 @@ Each must turn a new test red: - M10 removed stream ignored: drop the missing-cursor loop. Kill: remove subtest. - M11 broker legacy flag: `op === 'legacy-3l'` → `false`. Kill: bus legacy test. - M12 broker default 0: no reader → `{count: 0, known: true, ...}`. Kill: bus no-reader test. + +## Round 1 for review (sage-3024) + +270's code reached the 271 branch in 09b20784f and 48c34539c and has no verdict and no mutant run. +This round submits it on its own: the manifest is the paths 270's commits change, hashed at +7d199f033 (the 271 r3 tip). queue-271/BUILD.md, which 8365e4e5b also touched, stays with 271. +broker.mjs, runtime.mjs and launcher.mjs carry 271's changes too, so their blobs match the 271 round. + +Mutants: `mutants.py` beside this file, anchors at 7d199f033, each matching once (`check`, python +only). Each kill below is read from the test, not run yet. + +| Mutant | Edit | Kill: first failing assertion | +| --- | --- | --- | +| M1 | collect key `keyOf(f.stream + f.entry, ...)` | seat compactions.test.mjs:64, ledger lines gain `[c1,b.jsonl]` (counts hold, readLedger dedupes) | +| M2 | `keyOf` drops the timestamp | :81, count 1 not 2 | +| M3 | count `compactionSummary` messages | :40, m2 counts 1 | +| M4 | tail digest check `true` | :170 in `rewrite`, known true | +| M5 | `bad = torn = true` to `torn = true` | :131, the torn line is popped unparsed, count reads known 0 | +| M6 | no newline after a torn line | :133, c1 joins the torn line, count null not 1 | +| M7 | drop `if (ledger.incomplete)` | :174 in `shrink` (`remove` stays incomplete by its missing cursor) | +| M8 | drop `if (ledger.unsupported)` | :201, Pi run reads known | +| M9 | `count \|\| null` to `count` | :131, count 0 not null | +| M10 | drop the missing-cursor loop | :170 in `remove` | +| M11 | broker `op === 'legacy-3l'` to `false` | bus session-compactions.test.mjs:76 | +| M12 | broker no reader gives `{count: 0, known: true}` | bus session-compactions.test.mjs:81 | + +Run plan, on a CLEAR only: each mutant runs its test file whole under `timeout -k 10 120` (M1-M10 +seat compactions.test.mjs, M11-M12 bus session-compactions.test.mjs), TMPDIR from +`mktemp -d /mnt/storage/scratch/tmp/gXXX`, restore with `git checkout` and a clean-tree check after +each. A kill is an AssertionError at the named line. M11 and M12 edit broker.mjs, which 271 also +changed, so after their restore bus session-clear.test.mjs and sessions.test.mjs run whole, each +under `timeout -k 10 300`. compactions.mjs has no 271 change. + +Not covered: launcher.mjs's two `compactions(store, ...)` calls (run start, run end) and runtime.mjs's +reader closure have no test in either file, and no test elsewhere names compactions. A mutant +dropping either launcher call or making the closure answer a constant would survive. This is the +T3 Pi half as built; the Tests section above never claimed the wiring. Adding a cli and a runtime +test is a scope call for Sage. + +Prior coverage: none. 271's M1-M14 edit no 270 line, and no past verdict names 270. diff --git a/agents/gizmoduck/work/queue-270/mutants.py b/agents/gizmoduck/work/queue-270/mutants.py new file mode 100644 index 000000000..08d8ef47a --- /dev/null +++ b/agents/gizmoduck/work/queue-270/mutants.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Row 270 mutants M1-M12 (BUILD.md "Mutants", anchors at 7d199f033). `apply ` edits the +# worktree and prints the touched path; each edit asserts its anchor appears exactly once. +# K names the test and the first assertion each mutant should fail (checked by reading, not run). +import sys +WT = "/mnt/storage/scratch/gizmo-270/" +C = "packages/seat/src/compactions.mjs" +B = "packages/bus/src/broker.mjs" +ST = "packages/seat/tests/compactions.test.mjs" +BT = "packages/bus/tests/session-compactions.test.mjs" +M = { + "M1": (C, " const key = keyOf(f.entry, f.completedAt);\n if (ledger.events.has(key)) continue;", + " const key = keyOf(f.stream + f.entry, f.completedAt);\n if (ledger.events.has(key)) continue;"), + "M2": (C, "const keyOf = (entry, completedAt) => JSON.stringify([entry, completedAt]);", + "const keyOf = (entry, completedAt) => JSON.stringify([entry, null]);"), + "M3": (C, 'if (e.type === "compaction" && typeof e.id === "string" && e.id) {', + 'if ((e.type === "compaction" || e.message?.role === "compactionSummary") && typeof e.id === "string" && e.id) {'), + "M4": (C, "digest(tail) === c.tail) from = c.bytes;", "true) from = c.bytes;"), + "M5": (C, 'if (lines.pop() !== "") bad = torn = true;', 'if (lines.pop() !== "") torn = true;'), + "M6": (C, '(ledger.torn ? "\\n" : "") + out.map', 'out.map'), + "M7": (C, ' if (ledger.incomplete) return unknown("store-incomplete", count, at);\n', ""), + "M8": (C, ' if (ledger.unsupported) return unknown("harness-unsupported", count, at);\n', ""), + "M9": (C, "({ count: count || null, known: false, reason, at })", "({ count, known: false, reason, at })"), + "M10": (C, ' for (const stream of Object.keys(cursors)) if (!names.includes(stream)) problems.add("store-incomplete");\n', ""), + "M11": (B, "op === 'legacy-3l')", "false)"), + "M12": (B, "if (!this.#compactions) return unknown('not-collected');", + "if (!this.#compactions) return { count: 0, known: true, reason: null, at: null };"), +} +K = { + "M1": (ST, "a compaction copied into a branched stream counts once, under the stream it was first seen in", 64, + "c1 is written again under b.jsonl: [[c1,a],[c1,b],[c2,b]]; the count holds at 1 and 2 because readLedger dedupes"), + "M2": (ST, "the same entry id at another time in another stream is another compaction", 81, "count 1, not 2"), + "M3": (ST, "an empty store, a store with no compaction and a failed start count 0, known", 40, + "the compactionSummary message m2 counts: count 1, not 0"), + "M4": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > rewrite", 170, + "same length, other bytes, read from the old cursor: known true"), + "M5": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 131, + "the torn line is popped unparsed, so nothing sets bad: {count 0, known true}"), + "M6": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 133, + "the c1 record joins the torn line and is lost: count null, not 1"), + "M7": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > shrink", 174, + "the later clean collection reads known; remove would pass, its cursor stays missing"), + "M8": (ST, "a Claude run is harness-unsupported, and stays so after a Pi run", 201, "the Pi run reads known true"), + "M9": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 131, + "count 0, not null; the symlinked-stream test's :184 fails the same way"), + "M10": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > remove", 170, + "a removed stream raises no problem: known true"), + "M11": (BT, "a legacy session is asked for with legacy true", 76, "asked with legacy false"), + "M12": (BT, "without a reader the count is unknown, never 0; a reader that throws is store-unreadable", 81, + "{count 0, known true} for no reader"), +} +cmd = sys.argv[1] +if cmd == "list": + print(" ".join(M)); sys.exit(0) +if cmd == "check": + for k, (f, a, b) in M.items(): + n = open(WT + f).read().count(a) + tf, name, line, why = K[k] + print(k, f, n, "kill", f"{tf}:{line}", name) + sys.exit(0) +f, a, b = M[sys.argv[2]] +s = open(WT + f).read() +assert s.count(a) == 1, (sys.argv[2], s.count(a)) +open(WT + f, "w").write(s.replace(a, b)) +print(f)