Files

123 lines
9.4 KiB
Python

#!/usr/bin/env python3
# Row 270 mutants. M1-M12 are round 1's (anchors at 7d199f033); M13-M21 are round 2's
# (duckworth-0223 B1-B3, sage-3030, sage-3031, mos-1352); M22-M24 are round 3's (duckworth-0226
# B4, sage-3038). Round 3's kill lines are at round 3's tests. `apply <id>` edits the worktree and
# prints the touched path; each edit asserts its anchor appears exactly once. `check` prints each
# anchor's count and kill. K names the test and the first assertion each mutant should fail
# (checked by reading, not run). The base blob of every touched file is in BUILD.md; a restore is
# `git checkout -- <path>`, then sha256 against it (sage-3030 item 3).
import sys
WT = "/mnt/storage/scratch/gizmo-270/"
C = "packages/seat/src/compactions.mjs"
B = "packages/bus/src/broker.mjs"
L = "packages/cli/src/launcher.mjs"
R = "packages/bus/src/runtime.mjs"
ST = "packages/seat/tests/compactions.test.mjs"
SB = "packages/seat/tests/compactions-budget.test.mjs"
BT = "packages/bus/tests/session-compactions.test.mjs"
CT = "packages/cli/tests/compactions-launch.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 })"),
# Round 3 moved M10's anchor to the Set (duckworth-0226 B4); the mutant is the same.
"M10": (C, ' for (const stream of Object.keys(cursors)) if (!present.has(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 };"),
# B2: the launcher's two collections and the runtime's reader.
"M13": (L, " if (store) compactions(store, model.harness, { instance, thread, session, run });\n const toolSocket",
" const toolSocket"),
"M14": (L, " if (store) compactions(store, model.harness, { instance, thread, session, run });\n try { lock?.release(); }",
" try { lock?.release(); }"),
"M15": (R, " return dir ? compactionCount(dir) : { count: null, known: false, reason: 'store-unreadable', at: null };",
" return null;"),
# B1: the budget, its setting and the carry across reads.
"M16": (C, " const stop = Math.min(size, from + left);", " const stop = size;"),
"M17": (C, " return Number.isSafeInteger(value) && value >= READ_MIN && value <= READ_MAX ? value : READ_BYTES;",
" return value ?? READ_BYTES;"),
"M18": (L, " const budget = readBudget(system?.compactionReadBytes);", " const budget = READ_BYTES;"),
"M19": (C, " take(carry.length ? Buffer.concat([...carry, view.subarray(i, nl)]) : view.subarray(i, nl));",
" take(view.subarray(i, nl));"),
# B3: Claude history from before the ledger, and its stickiness.
"M20": (C, ' if (harness === "pi" && !prior && claudeHistory(store)) {', ' if (harness === "pi" && !prior && false) {'),
"M21": (C, 'if (r.harness !== "pi" || r.reason === "harness-unsupported") unsupported = true;',
'if (r.harness !== "pi") unsupported = true;'),
# B4: the stream cap and its setting.
"M22": (C, ' if (visit.length > streams) problems.add("not-collected");\n for (const name of visit.slice(0, streams)) {',
' for (const name of visit) {'),
"M23": (C, " return Number.isSafeInteger(value) && value >= STREAMS_MIN && value <= STREAMS_MAX ? value : STREAMS;",
" return value ?? STREAMS;"),
"M24": (L, " const streams = readStreams(system?.compactionStreams);", " const streams = STREAMS;"),
}
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"),
"M13": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 188,
"no start collection: run 1 logs only [D,2,true,null]; settle returns after 5 s with one event"),
"M14": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 188,
"no end collection: run 1 logs only [D,1,true,null]"),
"M15": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 203,
"session.list's compactions is null: {at: 'undefined'} and no count; the clear test's :328 and the legacy :348 fail the same way"),
"M16": (SB, "a stream past the default budget is not-collected with a lower bound, and a later collection catches up", 50,
"the whole 66 MiB is read: {count 1, known true, reason null}"),
"M17": (SB, "the budget is the setting's whole number of bytes from 1 MiB to 1 GiB, and the default for anything else", 39,
"readBudget(null) is the default, but readBudget('8388608') is the string"),
"M18": (CT, "the read budget is the system config's compactionReadBytes, and a bad value takes the default", 254,
"the 1 MiB setting is ignored, the 2 MiB hole is read whole: [D,1,true,null] twice"),
"M19": (SB, "a line split across reads counts once", 81,
"c1's two halves parse alone and fail: {count null, known false, reason store-incomplete}"),
"M20": (SB, "Claude history from before the ledger makes the first Pi collection harness-unsupported, for good", 90,
"the first collection counts c1: {count 1, known true}"),
"M21": (SB, "Claude history from before the ledger makes the first Pi collection harness-unsupported, for good", 91,
"a Pi collected record isn't sticky: the second reads {count 1, known true}"),
"M22": (SB, "more streams than the cap is not-collected, with cursors only for the streams opened, and a larger cap catches up", 129,
"all four streams are opened: {count 4, known true, reason null}; the default-cap test's :143 fails the same way (1025 cursors)"),
"M23": (SB, "the stream cap is the setting's whole number from 32 to 65536, and the default for anything else", 120,
"readStreams(null) is the default, but readStreams('2048') is the string"),
"M24": (CT, "the stream cap is the system config's compactionStreams, and a bad value takes the default", 290,
"the cap of 32 is ignored, all 34 streams are opened: [D,1,true,null] twice"),
}
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)