# Row 81 mutants: each swaps one exact string in the candidate copy, runs the named tests, # restores the file, and reports killed (tests fail) or SURVIVED. import subprocess, sys, os M = os.path.expanduser('~/dewey-scratch/r81/mut') env = dict(os.environ, TMPDIR=os.path.expanduser('~/dewey-scratch/tmp')) B, W, C = 'packages/bus/tests/replies.test.mjs', 'packages/webui/tests/bus.test.mjs', 'packages/cli/tests/verbs.test.mjs' SB = 'packages/webui/tests/send-browser.test.mjs' muts = [ ('broker: no human-required', 'packages/bus/src/broker.mjs', " case 'replies': {\n if (!s.human && !s.reader) fail('human-required');\n", " case 'replies': {\n", [B]), ('broker: no message-not-found', 'packages/bus/src/broker.mjs', " fail('message-not-found');\n return this.#store.all(\n `SELECT * FROM messages WHERE business=? AND in_reply_to=?", " (void 0);\n return this.#store.all(\n `SELECT * FROM messages WHERE business=? AND in_reply_to=?", [B]), ('broker: newest first', 'packages/bus/src/broker.mjs', 'in_reply_to=? ORDER BY seq LIMIT', 'in_reply_to=? ORDER BY seq DESC LIMIT', [B]), ('broker: cap 51', 'packages/bus/src/broker.mjs', 'LIMIT ${REPLIES}`', 'LIMIT ${REPLIES + 1}`', [B]), ('broker: reader not allowed', 'packages/bus/src/broker.mjs', "'credentials', 'replies'", "'credentials'", [B]), ('broker: no strict keys', 'packages/bus/src/broker.mjs', " keys(a, ['id'], ['id']);\n identifier(a.id);\n if (!this.#store.get('SELECT id FROM messages", " if (!this.#store.get('SELECT id FROM messages", [B]), ('views: subject arg', 'packages/bus/src/views.mjs', "client.call('replies', { id })", "client.call('replies', { subject: id })", [B, W]), ('serve: no id check', 'packages/webui/src/serve.mjs', " if (!subjectOk(arg)) return json(res, 400, { error: 'invalid-request', message: 'id must be a message id' });\n", "", [W]), ('serve: replies not a verb', 'packages/webui/src/serve.mjs', ", 'replies'", "", [W]), ('page: query as subject', 'packages/webui/src/public/bus.js', "verb === 'replies' ? 'id' : 'subject'", "'subject'", [SB]), ('page: no replies read', 'packages/webui/src/public/bus.js', "try { rows = await get('replies', r.id); } catch {}", "rows = [];", [SB]), ('page: no reset on send', 'packages/webui/src/public/bus.js', "receipt = { kind: 'sent', to, id: body.id, at: body.at }; replies = [];", "receipt = { kind: 'sent', to, id: body.id, at: body.at };", [SB]), ('page: no stale guard', 'packages/webui/src/public/bus.js', 'if (receipt === r) replies', 'if (true) replies', [SB]), ('page: cap note off by one', 'packages/webui/src/public/bus.js', 'replies.length >= REPLIES ?', 'replies.length > REPLIES ?', [SB]), ('page: body unescaped', 'packages/webui/src/public/bus.js', '

${txt(m.body)}

', '

${m.body}

', [SB]), ('page: failure shown as none', 'packages/webui/src/public/bus.js', "if (receipt === r) replies = Array.isArray(rows) ? rows : null;", "if (receipt === r) replies = Array.isArray(rows) ? rows : [];", [SB]), ('cli: old message', 'packages/cli/src/cli.mjs', 'and the next mosaic talk shows it`', 'and the next mosaic talk or mosaic inbox shows it`', [C]), ] only = sys.argv[1:] for name, f, old, new, tests in muts: if only and not any(o in name for o in only): continue p = os.path.join(M, f); src = open(p).read() n = src.count(old) if n != 1: print(f'BAD-ANCHOR ({n}) {name}', flush=True); continue open(p, 'w').write(src.replace(old, new)) try: r = subprocess.run(['node', '--test', *tests], cwd=M, env=env, capture_output=True, text=True, timeout=600) finally: open(p, 'w').write(src) print(('killed ' if r.returncode else 'SURVIVED') + f' {name}', flush=True)