Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
115 lines
8.7 KiB
Bash
115 lines
8.7 KiB
Bash
#!/usr/bin/env bash
|
||
# Row 148 (#1642): a Console editor for the project's spec and docs.
|
||
# Each check must fail on a mutant. For each mutant this runs one named test file against the
|
||
# real source (control) and against the source with one exact-once text edit, in the given worktree,
|
||
# one test file at a time, and restores every source file on exit.
|
||
# Usage: mutants.sh <worktree> <outdir> (MUTANT_DRY=1 only proves every edit matches exactly once)
|
||
set -u
|
||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||
cd "$WT" || exit 2
|
||
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
|
||
declare -A SAVED
|
||
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
|
||
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
|
||
trap restore EXIT
|
||
edit() { # file old new: exact-once replace
|
||
save "$1"
|
||
node -e '
|
||
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
|
||
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
|
||
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
|
||
fs.writeFileSync(f, s.replace(a, () => b));
|
||
' "$1" "$2" "$3"
|
||
}
|
||
run() { # name testfile
|
||
if [ -n "${MUTANT_DRY:-}" ]; then echo "$1: edit ok (dry)"; return; fi
|
||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||
}
|
||
mutant() { # name testfile file old new
|
||
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
|
||
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
|
||
run "$1" "$2"
|
||
cp "$BAK/$3" "$3"
|
||
}
|
||
PI=packages/project/src/index.mjs; PP=packages/project/src/paths.mjs; PT=packages/project/tests/human-file.test.mjs
|
||
BB=packages/bus/src/broker.mjs; BT=packages/bus/tests/project-files.test.mjs
|
||
WS=packages/webui/src/serve.mjs; WT_=packages/webui/tests/project-file.test.mjs
|
||
MD=packages/webui/src/public/markdown-preview.js; MT=packages/webui/tests/markdown-preview.test.mjs
|
||
BJ=packages/webui/src/public/bus.js; DT=packages/webui/tests/docs-browser.test.mjs
|
||
if [ -z "${MUTANT_DRY:-}" ]; then for t in "$PT" "$BT" "$WT_" "$MT" "$DT"; do run "control-$(basename "$t" .test.mjs)" "$t"; done; fi
|
||
|
||
# project: the human writer
|
||
mutant p1-no-stale-guard "$PT" "$PI" "'HEAD', '--', path]) !== base)" "'HEAD', '--', path]) !== base && false)"
|
||
mutant p2-any-path "$PT" "$PP" "(path === 'spec/north-star.md' || /^docs" "(true || path === 'spec/north-star.md' || /^docs"
|
||
mutant p3-author-not-the-human "$PT" "$PI" "const name = checkedId(context.business.human);" "const name = 'someone';"
|
||
mutant p4-creates-missing-file "$PT" "$PI" "if (before === null) throw new ProjectError('project-read-missing'" "if (false) throw new ProjectError('project-read-missing'"
|
||
mutant p5-no-content-check "$PT" "$PI" "if (typeof content !== 'string' || !content.length ||" "if (typeof content !== 'string' ||"
|
||
|
||
# bus: the human-only verbs
|
||
mutant b1-agent-may-save "$BT" "$BB" " this.#human(s);
|
||
if (!this.#project?.listFiles) fail('project-not-configured');" " if (!this.#project?.listFiles) fail('project-not-configured');"
|
||
mutant b2-save-keys-not-strict "$BT" "$BB" "keys(args, ['project', 'path', 'base', 'content'], ['project', 'path', 'base', 'content']);" "keys(args, ['project', 'path', 'base', 'content'], ['project', 'path']);"
|
||
|
||
# webui server: the save route
|
||
mutant w1-no-origin-check "$WT_" "$WS" " if (one(req, 'origin') !== \`https://\${preview.host}\`) return refuse(403, 'cross-origin request refused', 'a save needs the Console\\'s own Origin');
|
||
" ""
|
||
mutant w2-no-fetch-metadata "$WT_" "$WS" " if (one(req, 'sec-fetch-site') !== 'same-origin') return refuse(403, 'cross-site request refused', 'a save needs same-origin Fetch Metadata');
|
||
" ""
|
||
mutant w3-extra-keys-allowed "$WT_" "$WS" "if (Object.keys(value).length !== FILE_SAVE_KEYS.length || !FILE_SAVE_KEYS.every" "if (!FILE_SAVE_KEYS.every"
|
||
mutant w4-stale-looks-unknown "$WT_" "$WS" "return sent === false
|
||
? refuse(busStatus(code), code, code === 'stale-base'" "return false
|
||
? refuse(busStatus(code), code, code === 'stale-base'"
|
||
mutant w5-answer-path-unchecked "$WT_" "$WS" "|| result.path !== value.path) return refuse(503, 'outcome-unknown'" ") return refuse(503, 'outcome-unknown'"
|
||
|
||
# preview: no script, no sink, no unsafe link
|
||
mutant v1-sink-in-preview "$MT" "$MD" "window.MosaicMarkdown = Object.freeze({ render, linkTarget });" "window.MosaicMarkdown = Object.freeze({ render, linkTarget }); void 0 && document.body.innerHTML;"
|
||
mutant v2-any-scheme-links "$MT" "$MD" "return /^https?:\\/\\/[^/?#]/i.test(target) ? target : null;" "return target;"
|
||
mutant v3-protocol-relative-links "$MT" "$MD" "return target.startsWith('//') ? null : target;" "return target;"
|
||
mutant v4-no-rel "$MT" "$MD" "a.setAttribute('rel', 'noopener noreferrer');" ""
|
||
mutant v5-evasion-class-skipped "$MT" "$MD" "if (!target || target.length > SPAN ||" "if (!target || target.length > SPAN || false &&"
|
||
mutant v6-scheme-links-in-browser "$DT" "$MD" "return /^https?:\\/\\/[^/?#]/i.test(target) ? target : null;" "return target;"
|
||
|
||
mutant v7-unbounded-scan "$MT" "$MD" "const found = text.slice(from, last + 1).indexOf(ch);" "const found = text.slice(from).indexOf(ch);"
|
||
mutant v8-no-budget-check "$MT" "$MD" " if (work >= budget) return null;
|
||
const miss" " const miss"
|
||
mutant v9-budget-never-spent "$MT" "$MD" "const miss = reached => { work += reached - at + 1; return null; };" "const miss = reached => { return null; };"
|
||
mutant v10-budget-multiplier "$MT" "$MD" "BUDGET_PER_CHAR = 32, BUDGET_FLOOR = 1 << 20;" "BUDGET_PER_CHAR = 32000, BUDGET_FLOOR = 1 << 20;"
|
||
mutant v11-code-span-scan-uncharged "$MT" "$MD" " work += j - from;
|
||
return -1;" " return -1;"
|
||
mutant v12-closer-scan-uncharged "$MT" "$MD" " work += j - from;
|
||
return null;" " return null;"
|
||
mutant v13-closer-unbudgeted "$MT" "$MD" " function closerOf(text, from, ch, use) {
|
||
if (work >= budget) return null;" " function closerOf(text, from, ch, use) {"
|
||
|
||
mutant v14-paragraph-trim-by-regex "$MT" "$MD" "para.map(trimBlanksEnd).join" "para.map(l => l.replace(/[ \\t]+\$/, '')).join"
|
||
mutant v15-closing-hashes-by-regex "$MT" "$MD" "inline(stripClosingHashes((atx[2] ?? '').trim()).trim()," "inline((atx[2] ?? '').trim().replace(/(^|[ \\t]+)#+\$/, '').trim(),"
|
||
|
||
# round 3 (B1): the `s` flag on the three block regexes that end in (.*)$; each is removed in turn
|
||
mutant v16-fence-without-s-flag "$MT" "$MD" '(`{3,}|~{3,})(.*)$/s;' '(`{3,}|~{3,})(.*)$/;'
|
||
mutant v17-atx-without-s-flag "$MT" "$MD" '(?:[ \t]+(.*))?$/s;' '(?:[ \t]+(.*))?$/;'
|
||
mutant v18-item-without-s-flag "$MT" "$MD" '([ \t]+|$)(.*)$/s;' '([ \t]+|$)(.*)$/;'
|
||
|
||
# round 3 (sage-0621): the thematic-break scan that replaced the HR regex; one rule of it is broken in turn
|
||
mutant v19-rule-needs-one-mark "$MT" "$MD" 'return marks >= 3;' 'return marks >= 1;'
|
||
mutant v20-rule-needs-two-marks "$MT" "$MD" 'return marks >= 3;' 'return marks >= 2;'
|
||
mutant v21-rule-allows-four-spaces "$MT" "$MD" "while (i < 3 && line[i] === ' ') i++;" "while (i < 4 && line[i] === ' ') i++;"
|
||
mutant v22-rule-ignores-other-chars "$MT" "$MD" 'else if (!blank(line[i])) return false;' 'else if (false) return false;'
|
||
mutant v23-rule-mixes-marks "$MT" "$MD" "if (line[i] === mark) marks++;" "if (line[i] === '-' || line[i] === '*' || line[i] === '_') marks++;"
|
||
mutant v24-rule-refuses-tabs "$MT" "$MD" 'else if (!blank(line[i])) return false;' "else if (line[i] !== ' ') return false;"
|
||
mutant v25-rule-back-to-regex "$MT" "$MD" 'const isRule = line => {' 'const isRule = line => /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/.test(line);
|
||
const isRuleScan = line => {'
|
||
|
||
# editor view
|
||
mutant e1-preview-by-markup "$DT" "$BJ" "box.replaceChildren(window.MosaicMarkdown.render(s.text));" "box.innerHTML = s.text;"
|
||
mutant e2-wrong-base "$DT" "$BJ" "base: s.commit, content: s.text }" "base: '0'.repeat(40), content: s.text }"
|
||
mutant e3-base-does-not-move "$DT" "$BJ" "s.commit = body.commit; s.original = sent.content;" "s.original = sent.content;"
|
||
mutant e4-unchanged-is-savable "$DT" "$BJ" "&& s.text !== s.original && !s.raw.includes" "&& !s.raw.includes"
|
||
mutant e5-refusal-drops-draft "$DT" "$BJ" "} else if (body?.sent === false) s.status = \`\${typeof body.message === 'string' && body.message ? body.message : 'Not saved.'} Draft kept.\`;" "} else if (body?.sent === false) { s.status = 'Not saved.'; s.text = s.original; }"
|
||
|
||
# round 4 (sage-0624): a throw in the preview or a paint must not wedge the editor
|
||
mutant e6-preview-throw-uncaught "$DT" "$BJ" "} catch { box.textContent = 'The preview failed. The source is unchanged.'; }" "} finally { }"
|
||
mutant e7-saving-not-reset-on-throw "$DT" "$BJ" "} finally { s.saving = false; } // a throw above must not leave Save off and Reload refused" "} finally { } s.saving = false;"
|
||
echo "mutants finished"
|