dewey: row 91 round 3 review packet, approve (comment 28150, #1585)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 91 (#1585) round 3 mutants: round 1's list, rerun on the round 3
|
||||
# candidate, then the submodule guards. One or more per guard in
|
||||
# checkout.mjs, then the launcher's refusal and record, then the prompt line. Run from the
|
||||
# candidate worktree's root:
|
||||
# LOG=/some/dir bash mutants.sh /path/to/mut.mjs > mutants.txt
|
||||
# Each mutant edits one or two sites with mut.mjs (each exactly one match,
|
||||
# else exit 3),
|
||||
# runs the named tests and restores the file. "killed" means the run
|
||||
# failed; "survived" means it passed.
|
||||
set -u
|
||||
MUT=$1
|
||||
run() {
|
||||
local name=$1 file=$2 from=$3 to=$4; shift 4
|
||||
edits "$name" "$file" "$@" -- "$from" "$to"
|
||||
}
|
||||
# run2 name file from1 to1 from2 to2 tests...: two edits in one mutant.
|
||||
run2() {
|
||||
local name=$1 file=$2 a=$3 b=$4 c=$5 d=$6; shift 6
|
||||
edits "$name" "$file" "$@" -- "$a" "$b" "$c" "$d"
|
||||
}
|
||||
# edits name file tests... -- from to [from to]
|
||||
edits() {
|
||||
local name=$1 file=$2; shift 2
|
||||
local tests=()
|
||||
while [ "$1" != "--" ]; do tests+=("$1"); shift; done; shift
|
||||
local pairs=("$@")
|
||||
cp -- "$file" "$file.orig"
|
||||
if ! node "$MUT" "$file" "${pairs[@]}"; then
|
||||
echo "$name: NO MATCH"; mv -- "$file.orig" "$file"; return
|
||||
fi
|
||||
if timeout -s KILL 400 node --test "${tests[@]}" > "${LOG:-.}/mut-$name.log" 2>&1; then
|
||||
echo "$name: survived"
|
||||
else
|
||||
echo "$name: killed ($(grep -m1 -o 'ℹ fail [0-9]*' "${LOG:-.}/mut-$name.log"); $(grep '^✖ ' "${LOG:-.}/mut-$name.log" | grep -v 'failing tests' | sort -u | sed 's/^✖ //; s/ ([0-9.]*ms)$//' | paste -sd '|'))"
|
||||
fi
|
||||
mv -- "$file.orig" "$file"
|
||||
}
|
||||
C=packages/seat/src/checkout.mjs
|
||||
L=packages/cli/src/launcher.mjs
|
||||
B=packages/harness/src/bundle.mjs
|
||||
CT=(packages/seat/tests/checkout.test.mjs)
|
||||
LT=(packages/cli/tests/launcher.test.mjs)
|
||||
BT=(packages/harness/tests/bundle.test.mjs)
|
||||
|
||||
# The walk of .git.
|
||||
run walk-off $C 'if (walk(join(dest, ".git"))) return "not-a-clone";' 'if (false) return "not-a-clone";' "${CT[@]}"
|
||||
run walk-no-nlink $C '} else if (!st.isFile() || st.nlink !== 1) return true;' '} else if (!st.isFile()) return true;' "${CT[@]}"
|
||||
run walk-symlinks-pass $C '} else if (!st.isFile() || st.nlink' '} else if ((!st.isFile() && !st.isSymbolicLink()) || st.nlink' "${CT[@]}"
|
||||
run clone-dir-symlink-ok $C 'return lstatSync(p).isDirectory();' 'return lstatSync(p).isDirectory() || lstatSync(p).isSymbolicLink();' "${CT[@]}"
|
||||
run no-commondir $C 'for (const f of ["commondir", join("objects", "info", "alternates")])' 'for (const f of [join("objects", "info", "alternates")])' "${CT[@]}"
|
||||
run no-alternates $C 'for (const f of ["commondir", join("objects", "info", "alternates")])' 'for (const f of ["commondir"])' "${CT[@]}"
|
||||
# Hooks and the config file.
|
||||
run hooks-ignored $C 'if (readdirSync(join(dest, ".git", "hooks")).length > 0) return "hooks-present";' 'if (false) return "hooks-present";' "${CT[@]}"
|
||||
run config-cap-off $C 'st.size > CONFIG_MAX_BYTES' 'st.size > Infinity' "${CT[@]}"
|
||||
# The allowlist.
|
||||
run allow-any-key $C 'default: return false;' 'default: return true;' "${CT[@]}"
|
||||
run origin-any-url $C 'case "remote.origin.url": return value === root;' 'case "remote.origin.url": return true;' "${CT[@]}"
|
||||
run fetch-any $C 'case "remote.origin.fetch": return value === FETCH;' 'case "remote.origin.fetch": return true;' "${CT[@]}"
|
||||
run branch-remote-any $C 'return branch[1] === "remote" ? value === "origin" :' 'return branch[1] === "remote" ? true :' "${CT[@]}"
|
||||
run branch-merge-any $C ': /^refs\/heads\/./.test(value ?? "");' ': true;' "${CT[@]}"
|
||||
run duplicate-key-ok $C 'if (seen.has(key) || !allowed(key, value, root))' 'if (!allowed(key, value, root))' "${CT[@]}"
|
||||
run no-origin-ok $C 'if (!seen.has("remote.origin.url")) return "config-changed";' '' "${CT[@]}"
|
||||
# The root, the update and the clone.
|
||||
run ceiling-off $C 'const ceiling = { GIT_CEILING_DIRECTORIES: dirname(root) };' 'const ceiling = {};' "${CT[@]}"
|
||||
run detached-root-clones $C 'if (!at.branch) throw new CheckoutError(project, `the root ${root} has no branch checked out`);' '' "${CT[@]}"
|
||||
run other-branch-updates $C 'if (!head.ok || head.out.trim() !== at.branch) return' 'if (!head.ok) return' "${CT[@]}"
|
||||
run dirty-updates $C 'if (status.out !== "") return { outcome: "kept", reason: "dirty" };' '' "${CT[@]}"
|
||||
run untracked-ignored $C '"--untracked-files=all", ' '"--untracked-files=no", ' "${CT[@]}"
|
||||
run merge-not-ff-only $C '"merge", "-q", "--ff-only", "--no-edit"' '"merge", "-q", "--no-edit"' "${CT[@]}"
|
||||
run temp-left $C 'rmSync(tmp, { recursive: true, force: true });' 'void tmp;' "${CT[@]}"
|
||||
run clone-in-place $C 'const tmp = join(workspace, `.${project}.clone-${randomBytes(4).toString("hex")}`);' 'const tmp = dest;' "${CT[@]}"
|
||||
run clone-hardlinks $C '"clone", "-q", "--no-hardlinks", ' '"clone", "-q", ' "${CT[@]}"
|
||||
# Second-line guards: the checks above stop each case first, so these are
|
||||
# expected to survive.
|
||||
run safe-no-hookspath $C '"-c", "core.hooksPath=/dev/null",' '' "${CT[@]}"
|
||||
run safe-no-fsmonitor $C '"-c", "core.fsmonitor=false",' '' "${CT[@]}"
|
||||
# The launcher.
|
||||
run refusal-unmapped $L 'if (e instanceof CheckoutError) throw new Refusal(' 'if (false) throw new Refusal(' "${LT[@]}"
|
||||
run kept-unrecorded $L 'for (const c of checkouts) if (c.outcome === "kept") record(' 'for (const c of checkouts) if (false) record(' "${LT[@]}"
|
||||
run checkouts-not-in-session $L 'session: { run, launchedBy, workspace, toolSocket, turnMarker, checkouts },' 'session: { run, launchedBy, workspace, toolSocket, turnMarker },' "${LT[@]}"
|
||||
# The prompt line.
|
||||
run prompt-no-clones $B '...checkoutLines(session.checkouts ?? []),' '' "${BT[@]}"
|
||||
run prompt-kept-as-current $B '(c.outcome === "kept" ?' '(c.outcome === "never" ?' "${BT[@]}"
|
||||
# Round 3: the submodule guards. The first two must die; the next three are
|
||||
# second-line guards the first two stop first, so they are expected to
|
||||
# survive; the last two take the first away and must die on the marker.
|
||||
run gitlink-check-off $C 'if (staged.out.split("\0").some((e) => e.startsWith("160000 "))) return { outcome: "kept", reason: "submodule" };' '' "${CT[@]}"
|
||||
run modules-check-off $C 'return "submodule";' '' "${CT[@]}"
|
||||
run status-recurses $C '"--ignore-submodules=all"' '"--ignore-submodules=none"' "${CT[@]}"
|
||||
run safe-no-submodule-recurse $C '"-c", "submodule.recurse=false",' '' "${CT[@]}"
|
||||
run lsfiles-failure-passes $C 'if (!staged.ok) return { outcome: "kept", reason: "status-failed" };' '' "${CT[@]}"
|
||||
run2 recursion-unguarded $C 'if (staged.out.split("\0").some((e) => e.startsWith("160000 "))) return { outcome: "kept", reason: "submodule" };' '' '"--ignore-submodules=all"' '"--ignore-submodules=none"' "${CT[@]}"
|
||||
run2 gitlink-check-after-status $C 'if (staged.out.split("\0").some((e) => e.startsWith("160000 "))) return { outcome: "kept", reason: "submodule" };' '' '"--ignore-submodules=all"], env);
|
||||
if (!status.ok) return { outcome: "kept", reason: "status-failed" };' '"--ignore-submodules=none"], env);
|
||||
if (!status.ok) return { outcome: "kept", reason: "status-failed" };
|
||||
if (staged.out.split("\0").some((e) => e.startsWith("160000 "))) return { outcome: "kept", reason: "submodule" };' "${CT[@]}"
|
||||
# Dewey's additions (row 91 round 3 review): the clone's empty template, the
|
||||
# clones before the run, and the fetch's own no-recursion flag (second line,
|
||||
# expected to survive).
|
||||
run clone-template-default $C '"clone", "-q", "--no-hardlinks", "--template=", ' '"clone", "-q", "--no-hardlinks", ' "${CT[@]}"
|
||||
run2 checkouts-after-run $L ' const { run, runDir } = newRun(dataRoot, b.id);
|
||||
' '' ' const workspace = workspaceDir(dataRoot, b.id, instance);' ' const { run, runDir } = newRun(dataRoot, b.id);
|
||||
const workspace = workspaceDir(dataRoot, b.id, instance);' "${LT[@]}"
|
||||
run fetch-recurses $C '"--no-recurse-submodules", ' '' "${CT[@]}"
|
||||
@@ -0,0 +1,41 @@
|
||||
walk-off: killed (ℹ fail 7; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a reflog hard-linked to a file outside|config a symlink to /dev/zero|.git/logs a symlink|.git/objects a symlink|.git/refs/heads a symlink|HEAD a fifo)
|
||||
walk-no-nlink: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a reflog hard-linked to a file outside)
|
||||
walk-symlinks-pass: killed (ℹ fail 5; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|config a symlink to /dev/zero|.git/logs a symlink|.git/objects a symlink|.git/refs/heads a symlink)
|
||||
clone-dir-symlink-ok: killed (ℹ fail 3; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|.git a symlink|the clone a symlink to the bait)
|
||||
no-commondir: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a commondir file)
|
||||
no-alternates: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|an alternates file)
|
||||
hooks-ignored: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a hook file)
|
||||
config-cap-off: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a config file over the size cap)
|
||||
allow-any-key: killed (ℹ fail 7; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a filter driver|a gitlink and submodule.active in config|core.fsmonitor|core.hooksPath|includeIf|include.path)
|
||||
origin-any-url: killed (ℹ fail 3; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|an ext:: origin|origin moved)
|
||||
fetch-any: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a fetch line onto local branches)
|
||||
branch-remote-any: killed (ℹ fail 2; a branch tracking another remote|a clone the session rigged is kept, and git runs nothing and writes nothing outside it)
|
||||
branch-merge-any: killed (ℹ fail 2; a branch merging a tag|a clone the session rigged is kept, and git runs nothing and writes nothing outside it)
|
||||
duplicate-key-ok: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|the origin url twice, both the root)
|
||||
no-origin-ok: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|no origin)
|
||||
ceiling-off: killed (ℹ fail 2; a plain directory inside another repository|a root that can't be cloned refuses with the project id and leaves nothing behind)
|
||||
detached-root-clones: killed (ℹ fail 2; a root that can't be cloned refuses with the project id and leaves nothing behind|on no branch)
|
||||
other-branch-updates: killed (ℹ fail 2; a dirty clone, one on another branch and one diverged are kept as they are|another branch)
|
||||
dirty-updates: killed (ℹ fail 4; a dirty clone, one on another branch and one diverged are kept as they are|a modified file|an untracked file|a staged file)
|
||||
untracked-ignored: killed (ℹ fail 2; a dirty clone, one on another branch and one diverged are kept as they are|an untracked file)
|
||||
merge-not-ff-only: survived
|
||||
temp-left: killed (ℹ fail 2; a root that can't be cloned refuses with the project id and leaves nothing behind|missing an object)
|
||||
clone-in-place: killed (ℹ fail 1; the clone is not at its place until git clone has finished)
|
||||
clone-hardlinks: killed (ℹ fail 47; a branch merging a tag|a branch tracking another remote|a clone ahead of the root keeps its commits and counts as current|a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a clone whose root is on no branch, or gone, is kept and named|a commondir file|a config file over the size cap|a detached HEAD|a dirty clone, one on another branch and one diverged are kept as they are|a diverged branch|a fetch line onto local branches|a filter driver|a first launch clones the root at its branch, with the root's path as origin|a gitlink and .gitmodules committed|a gitlink and .gitmodules staged|a gitlink and submodule.active in config|a gitlink staged, no .gitmodules|a gitlink to a repository with its own gitlink|a .git/modules with no gitlink|a hook file|a later launch fast-forwards a clean clone after a commit to the root, then finds it current|a modified file|an alternates file|an ext:: origin|another branch|an untracked file|a reflog hard-linked to a file outside|a second origin url|a staged file|config a symlink to /dev/zero|core.fsmonitor|core.hooksPath|.git a gitdir: file|.git a symlink|.git/logs a symlink|.git/objects a symlink|.git/refs/heads a symlink|HEAD a fifo|includeIf|include.path|no credential or forge remote of the root reaches a clone|no origin|origin moved|the clone a file|the clone a plain directory|the clone a symlink to the bait|the origin url twice, both the root)
|
||||
safe-no-hookspath: survived
|
||||
safe-no-fsmonitor: survived
|
||||
refusal-unmapped: killed (ℹ fail 1; a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left)
|
||||
kept-unrecorded: killed (ℹ fail 1; each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged)
|
||||
checkouts-not-in-session: killed (ℹ fail 1; each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged)
|
||||
prompt-no-clones: killed (ℹ fail 1; the prompt names the project clones in the workspace, and none when there are none)
|
||||
prompt-kept-as-current: killed (ℹ fail 1; the prompt names the project clones in the workspace, and none when there are none)
|
||||
gitlink-check-off: killed (ℹ fail 5; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a gitlink and .gitmodules committed|a gitlink and .gitmodules staged|a gitlink staged, no .gitmodules|a gitlink to a repository with its own gitlink)
|
||||
modules-check-off: killed (ℹ fail 2; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a .git/modules with no gitlink)
|
||||
status-recurses: survived
|
||||
safe-no-submodule-recurse: survived
|
||||
lsfiles-failure-passes: survived
|
||||
recursion-unguarded: killed (ℹ fail 5; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a gitlink and .gitmodules committed|a gitlink and .gitmodules staged|a gitlink staged, no .gitmodules|a gitlink to a repository with its own gitlink)
|
||||
gitlink-check-after-status: killed (ℹ fail 5; a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a gitlink and .gitmodules committed|a gitlink and .gitmodules staged|a gitlink staged, no .gitmodules|a gitlink to a repository with its own gitlink)
|
||||
clone-template-default: killed (ℹ fail 33; a branch merging a tag|a branch tracking another remote|a clone ahead of the root keeps its commits and counts as current|a clone the session rigged is kept, and git runs nothing and writes nothing outside it|a clone whose root is on no branch, or gone, is kept and named|a config file over the size cap|a detached HEAD|a dirty clone, one on another branch and one diverged are kept as they are|a diverged branch|a fetch line onto local branches|a filter driver|a first launch clones the root at its branch, with the root's path as origin|a gitlink and .gitmodules committed|a gitlink and .gitmodules staged|a gitlink and submodule.active in config|a gitlink staged, no .gitmodules|a gitlink to a repository with its own gitlink|a hook file|a later launch fast-forwards a clean clone after a commit to the root, then finds it current|a modified file|an ext:: origin|another branch|an untracked file|a second origin url|a staged file|core.fsmonitor|core.hooksPath|includeIf|include.path|no credential or forge remote of the root reaches a clone|no origin|origin moved|the origin url twice, both the root)
|
||||
checkouts-after-run: killed (ℹ fail 1; a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left)
|
||||
fetch-recurses: survived
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Does git status run a submodule's clean filter with --ignore-submodules=all vs =none,
|
||||
# under checkout.mjs's SAFE flags and environment? Builds the bait fresh per mode.
|
||||
set -u
|
||||
B=$HOME/dewey-scratch/r91probe/w
|
||||
SAFE=(-c core.hooksPath=/dev/null -c core.fsmonitor=false -c protocol.allow=never -c protocol.file.allow=always -c submodule.recurse=false)
|
||||
E=(env -i PATH="$PATH" HOME=/nonexistent GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null GIT_TERMINAL_PROMPT=0 LC_ALL=C)
|
||||
tg(){ "${E[@]}" git -c user.name=t -c user.email=[email protected] "$@"; }
|
||||
for nested in 0 1; do for mode in all none; do
|
||||
rm -rf "$B"; mkdir -p "$B"; M=$B/RAN
|
||||
printf '#!/bin/sh\ntouch %s\ncat\n' "$M" > $B/run.sh; chmod +x $B/run.sh
|
||||
tg init -q -b main $B/c; echo one > $B/c/README; tg -C $B/c add README; tg -C $B/c commit -q -m one
|
||||
mk(){ tg init -q -b main $1; echo one > $1/data; echo 'data filter=x' > $1/.gitattributes; tg -C $1 add data .gitattributes; tg -C $1 commit -q -m s; tg -C $1 config filter.x.clean $B/run.sh; echo ONE > $1/data; }
|
||||
if [ $nested = 0 ]; then mk $B/c/sub; tg -C $B/c update-index --add --cacheinfo 160000,$(tg -C $B/c/sub rev-parse HEAD),sub
|
||||
else tg init -q -b main $B/c/sub; echo x > $B/c/sub/f; tg -C $B/c/sub add f; mk $B/c/sub/sub
|
||||
tg -C $B/c/sub update-index --add --cacheinfo 160000,$(tg -C $B/c/sub/sub rev-parse HEAD),sub; tg -C $B/c/sub commit -q -m o
|
||||
tg -C $B/c update-index --add --cacheinfo 160000,$(tg -C $B/c/sub rev-parse HEAD),sub; fi
|
||||
rm -f $M
|
||||
out=$("${E[@]}" git "${SAFE[@]}" --no-optional-locks --git-dir=$B/c/.git --work-tree=$B/c status --porcelain=v1 -z --untracked-files=all --ignore-submodules=$mode | tr '\0' '|'); rc=$?
|
||||
echo "nested=$nested ignore=$mode rc=$rc marker=$([ -e $M ] && echo PRESENT || echo absent) status='$out'"
|
||||
done; done
|
||||
rm -rf "$B"
|
||||
@@ -0,0 +1,5 @@
|
||||
nested=0 ignore=all rc=0 marker=absent status=''
|
||||
nested=0 ignore=none rc=0 marker=PRESENT status='AM sub|'
|
||||
nested=1 ignore=all rc=0 marker=absent status=''
|
||||
nested=1 ignore=none rc=0 marker=PRESENT status='AM sub|'
|
||||
2026-10-13T13:18:10Z git version 2.55.0
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dewey's row 91 round 3 gate and mutants. Gate on ~/dewey-scratch/r91 (2b2a9e14 + build.patch staged),
|
||||
# mutants on ~/dewey-scratch/r91mut. Only test-task gets the dead Docker socket.
|
||||
set -u
|
||||
O=~/dewey-scratch/r91-out; W=~/dewey-scratch/r91; M=~/dewey-scratch/r91mut
|
||||
export TMPDIR=~/dewey-scratch/tmp
|
||||
busy(){ for p in $(pgrep -f -- --test) $(pgrep -f "scripts/test-"); do [ "$p" = "$$" ] && continue; c=$(readlink /proc/$p/cwd 2>/dev/null) || continue; case "$(cat /proc/$p/comm 2>/dev/null)" in node*|bash) ;; *) continue;; esac; case "$c" in $HOME/dewey-scratch/*) ;; *) return 0;; esac; done; return 1; }
|
||||
if busy; then echo "BUSY, abort" >> $O/summary.txt; exit 9; fi
|
||||
cd $W || exit 2
|
||||
echo "start $(date -u +%Y-%m-%dT%H:%M:%SZ) base $(git rev-parse HEAD) staged=$(git diff --cached --name-only | wc -l)" >> $O/summary.txt
|
||||
sha256sum -c --quiet $O/candidate-manifest.sha256 && echo "manifest 8/8 OK" >> $O/summary.txt || { echo "manifest mismatch" >> $O/summary.txt; exit 3; }
|
||||
for p in tasks bus harness webui cli business seat control-board; do
|
||||
case $p in seat|webui) g="packages/$p/tests/";; *) g="packages/$p/tests/*.test.mjs";; esac
|
||||
node --test "$g" > $O/gate/node-$p.txt 2>&1; rc=$?
|
||||
echo "node $p rc=$rc $(grep -E '^ℹ (tests|pass|fail|skipped)' $O/gate/node-$p.txt | tr '\n' ' ') $(grep -c '✖' $O/gate/node-$p.txt)x" >> $O/summary.txt
|
||||
done
|
||||
for f in scripts/test-*.sh; do n=$(basename $f .sh)
|
||||
if [ $n = test-task ]; then DOCKER_HOST=unix:///nonexistent.sock bash $f > $O/gate/$n.txt 2>&1; else bash $f > $O/gate/$n.txt 2>&1; fi; rc=$?
|
||||
echo "$n rc=$rc $(tail -1 $O/gate/$n.txt | cut -c1-100)" >> $O/summary.txt
|
||||
done
|
||||
echo "suites end $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $O/summary.txt
|
||||
cd $M || exit 2
|
||||
sha256sum -c --quiet $O/candidate-manifest.sha256 || { echo "mut manifest mismatch" >> $O/summary.txt; exit 3; }
|
||||
LOG=$O/mut bash $O/mutants.sh $O/mut.mjs > $O/mutants.txt 2>&1; echo "mutants rc=$? end $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $O/summary.txt
|
||||
sha256sum -c --quiet $O/candidate-manifest.sha256 && git status --porcelain | grep -v '^M ' | grep -v '^A ' | wc -l | sed 's/^/mut copy manifest OK, stray entries: /' >> $O/summary.txt || echo "mut copy NOT restored" >> $O/summary.txt
|
||||
echo "done $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $O/summary.txt
|
||||
@@ -0,0 +1,23 @@
|
||||
start 2026-10-13T14:08:39Z base 2b2a9e14eec4ff81cdada5dc42bbf1b8063b29b4 staged=8
|
||||
manifest 8/8 OK
|
||||
node tasks rc=0 ℹ tests 89 ℹ pass 89 ℹ fail 0 ℹ skipped 0 0x
|
||||
node bus rc=0 ℹ tests 115 ℹ pass 115 ℹ fail 0 ℹ skipped 0 0x
|
||||
node harness rc=0 ℹ tests 67 ℹ pass 67 ℹ fail 0 ℹ skipped 0 0x
|
||||
node webui rc=0 ℹ tests 104 ℹ pass 104 ℹ fail 0 ℹ skipped 0 0x
|
||||
node cli rc=0 ℹ tests 131 ℹ pass 131 ℹ fail 0 ℹ skipped 0 0x
|
||||
node business rc=0 ℹ tests 60 ℹ pass 60 ℹ fail 0 ℹ skipped 0 0x
|
||||
node seat rc=0 ℹ tests 89 ℹ pass 89 ℹ fail 0 ℹ skipped 0 0x
|
||||
node control-board rc=0 ℹ tests 124 ℹ pass 124 ℹ fail 0 ℹ skipped 0 0x
|
||||
test-auth rc=0 selftest: 15 passed, 0 failed
|
||||
test-conductor rc=0 selftest: 17 passed, 0 failed
|
||||
test-config rc=0 selftest: 24 passed, 0 failed
|
||||
test-discord rc=0 discord suite: 66 passed, 0 failed
|
||||
test-extension-package rc=0 extension package selftest: 18 passed, 0 failed
|
||||
test-foundation rc=0 selftest: 44 passed, 0 failed
|
||||
test-queue rc=0 queue suite: 27 passed, 0 failed
|
||||
test-release rc=0 selftest: 14 passed, 0 failed
|
||||
test-task rc=0 selftest: 26 passed, 0 failed
|
||||
suites end 2026-10-13T14:11:56Z
|
||||
mutants rc=0 end 2026-10-13T14:15:22Z
|
||||
mut copy manifest OK, stray entries: 1
|
||||
done 2026-10-13T14:15:22Z
|
||||
@@ -0,0 +1,64 @@
|
||||
**Dewey: approve, round 3, candidate manifest `ca57060709379b89b5e4e81b2e13ad68ead2be14f7106ea38e6c416680b4f7df`**
|
||||
|
||||
I'm the reviewer for this row under decision 130 (sandbox row, Claude review, no Codex check). This verdict covers the whole row on round 3's manifest, not only the round 3 delta.
|
||||
|
||||
**What I checked**
|
||||
|
||||
- `build.patch` from `agents/darkwing/work/queue-91/r3/`, applied with `git apply --index` to a detached worktree of `2b2a9e14`, checks 8/8 against `candidate-manifest.sha256`. No commit from `2b2a9e14` to `refactor` HEAD touches the eight paths.
|
||||
- Static review of `packages/seat/src/checkout.mjs` in full, its tests, the launcher's `start()` change, the prompt line, and both READMEs.
|
||||
- The submodule fix closes Filbert's escape from both sides. `unsafe()` keeps a clone with anything at `.git/modules` as `submodule`. `update()` reads the index with `ls-files --stage -z` before any other git runs in the clone, and keeps any gitlink (`160000`) as `submodule`. `status` uses `--ignore-submodules=all`, and `SAFE` adds `-c submodule.recurse=false`. `ls-files` runs only after the config allowlist passes, and it reads the index only.
|
||||
- The six new cases build a real bait: a repository at `<clone>/sub` whose own config names a clean filter, with a same-size edit, so any status inside it must run the filter. Each case asserts the outcome, that the marker is absent, and that the bait repository is unchanged.
|
||||
- The launcher makes the clones after `credentialStatus` and before `newRun`, so a `checkout-failed` leaves no run. A session launch refuses through `refuse()` and the launch log; `--pm` refuses through the log and exit 3, as the other steps do. A kept clone is a `checkout-kept` record and the launch goes on, as the brief says.
|
||||
|
||||
**The assert-order point in your build.md**
|
||||
|
||||
Your build.md notes that under `gitlink-check-off` the outcome assertion fails before the marker assertion, so the tests don't show whether `--ignore-submodules=all` alone stops the filter. I checked that directly: `probe.sh` here builds your bait (a staged gitlink to a repository whose own config names a clean filter, same-size edit; and the two-level form) and runs `git status` with `SAFE`'s flags and `gitEnv()`'s environment, git 2.55.0, 13:18:10Z:
|
||||
|
||||
| bait | `--ignore-submodules=all` | `--ignore-submodules=none` |
|
||||
|---|---|---|
|
||||
| one level | marker absent, status empty | marker present, `AM sub` |
|
||||
| two levels, filter in the inner repository only | marker absent, status empty | marker present, `AM sub` |
|
||||
|
||||
So `=none` reproduces Filbert's escape on this git, and `=all` alone stops it at both depths. `status-recurses` surviving is a real second-line guard, not a dead one. No test change needed.
|
||||
|
||||
**Gate**
|
||||
|
||||
Run 14:08:39Z to 14:11:56Z on `2b2a9e14` plus the candidate (manifest 8/8 before and after). Every suite green. This is a full rerun: my first run died in the 13:40Z dragon-lin reboot, and I discarded its partial output.
|
||||
|
||||
| suite | pass | suite | pass |
|
||||
|---|---|---|---|
|
||||
| tasks | 89/89 | auth | 15 |
|
||||
| bus | 115/115 | conductor | 17 |
|
||||
| harness | 67/67 | config | 24 |
|
||||
| webui | 104/104 | discord | 66 |
|
||||
| cli | 131/131 | extension-package | 18 |
|
||||
| business | 60/60 | foundation | 44 |
|
||||
| seat | 89/89 | queue | 27 |
|
||||
| control-board | 124/124 | release | **14** |
|
||||
| | | task (dead socket) | 26 |
|
||||
|
||||
The counts you reported match mine, except test-release (see below).
|
||||
|
||||
**Mutants**
|
||||
|
||||
Your 38 under a 400 s KILL timeout, then 3 of mine (14:11:56Z to 14:15:22Z). The mutant copy checked 8/8 against the manifest afterwards.
|
||||
|
||||
- **Your 38:** 32 killed, 6 survived, the same six you report: merge-not-ff-only, safe-no-hookspath, safe-no-fsmonitor, status-recurses, safe-no-submodule-recurse, lsfiles-failure-passes. I agree each one is equivalent or a second-line guard:
|
||||
- **hooksPath and fsmonitor:** `unsafe()` already keeps a clone with a hook file or either key, and the global config is `/dev/null`.
|
||||
- **submodule.recurse and status-recurses:** a gitlink or `.git/modules` is kept before `status` runs. The probe above shows `=all` alone holds even when that check is off.
|
||||
- **lsfiles-failure-passes:** an `ls-files` that fails after the allowlist passes has no route to a recursing call that I can find.
|
||||
- **Mine:**
|
||||
- `clone-template-default` (drop `--template=`) is killed by 33 tests.
|
||||
- `checkouts-after-run` (move `ensureCheckouts` after `newRun`) is killed by the "no run and no clone left" test.
|
||||
- `fetch-recurses` (drop `--no-recurse-submodules`) survives, as I expected: a clone that gets as far as `fetch` has no gitlink in its index and no `.git/modules`, so nothing is populated for fetch to recurse into. It's the same kind of second guard as `submodule.recurse=false`.
|
||||
|
||||
**My round 2 points**
|
||||
|
||||
- test-release: your gate ran every script with `DOCKER_HOST` at a dead socket, so `test-release.sh` ran 4 of its 14 cases, the same mistake I made in my row 94 round 2 run. Mine gives the dead socket to `test-task.sh` only, and test-release ran all 14 cases, green. Sage's gate rerun is the one that counts; it should run test-release with Docker.
|
||||
- merge-not-ff-only: equivalent on this host, as you say. Under `gitEnv()`, `git var GIT_COMMITTER_IDENT` exits 128 with "unable to auto-detect email address (got 'jwoltje@dragon-lin.(none)')", so a merge without `--ff-only` can't commit and the diverged clone is kept as `not-fast-forward` either way. On a host whose name has a domain, the "a diverged branch" case would kill it. Non-blocking.
|
||||
|
||||
**Non-blocking notes**
|
||||
|
||||
1. A project whose root has submodules is never updated after its first launch. `git clone` without `--recurse-submodules` leaves the gitlinks in the clone's index, so every later `update()` keeps the clone as `submodule`. That's the safe choice, and no project has one today: the only business file names `stack`, `/mnt/storage/src/mosaic-stack`, which has 0 gitlinks. The seat README's reasons table says what `submodule` means but not that a root with submodules always gets it. Worth one sentence there when someone next touches the README; it doesn't need another round.
|
||||
|
||||
**Landing:** the launcher runs in the bus host, so the change is live at the next bus restart (decision 109). Mos hears before the landing. Nothing here is under `webui/src/public`, so decision 123 doesn't apply.
|
||||
Reference in New Issue
Block a user