Files
stack/scripts/test-land-work.sh
T
jason.woltjeandClaude Opus 5.5 1f2ac5b848 scripts: land-work turns a resolved merge decision into a gated local commit (row 116, #1610)
Candidate manifest 8b4ab31014a124ec0e3abba44238976d6f4c9af48cbdb36c2c77d18f0137f888
from Darkwing's round 2 packet (0a4076e8e), on BASE 1223a0f37. No
manifest path changed between BASE and the landing target. Filbert
approved round 2 in 28468, closing C1 and D1 from round 1 (28464).
Sage reran the full gate at the landing target with the candidate
applied. The replay acceptance follows ruling sage-0204.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 14:24:57 -05:00

405 lines
21 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Sandboxed selftests for scripts/land-work.sh (row 116, #1610).
#
# Builds a fixture project repository, a working repository holding the
# coder's work refs, and per case a bus database written through the real
# Store (scripts/land-work/fixture.mjs). Every refusal is exercised, then a
# landing, a dry run, a gate failure and a base that moves during the gate.
# Docker is a fake on PATH that logs its calls. Nothing here opens the real
# config, data root or bus: MOSAIC_CONFIG always names a sandbox file.
set -uo pipefail
cd "$(dirname "$0")/.."
REPO="$(pwd)"
SANDBOX="$(cd "$(mktemp -d)" && pwd -P)"
trap 'rm -rf "$SANDBOX"' EXIT
PASS=0
FAIL=0
# Status colors: terminal-only, NO_COLOR-respecting; plain when piped.
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m'
else
C_OK=""; C_FAIL=""; C_RESET=""
fi
check() {
if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi
}
# Git sees no user or system config: no hooks path, no signing, a fixed identity.
: > "$SANDBOX/gitconfig"
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL="$SANDBOX/gitconfig"
export GIT_AUTHOR_NAME=suite GIT_AUTHOR_EMAIL=suite@local GIT_COMMITTER_NAME=suite GIT_COMMITTER_EMAIL=suite@local
REAL_GIT="$(command -v git)"
# ---- fakes: docker logs its calls; a git that tampers with the applied diff ----
mkdir -p "$SANDBOX/bin" "$SANDBOX/tamper"
cat > "$SANDBOX/bin/docker" <<EOF
#!/usr/bin/env bash
echo "docker \$*" >> "$SANDBOX/docker.log"
[ "\$1 \$2" != "network inspect" ] || [ "\${FAKE_NET_EXISTS:-}" = 1 ]
EOF
cat > "$SANDBOX/tamper/git" <<EOF
#!/usr/bin/env bash
"$REAL_GIT" "\$@"; rc=\$?
if [ "\${1:-}" = -C ] && [ "\${3:-}" = apply ] && [ "\$rc" -eq 0 ]; then
printf 'tampered\n' >> "\$2/README.md" && "$REAL_GIT" -C "\$2" add README.md
fi
exit "\$rc"
EOF
chmod +x "$SANDBOX/bin/docker" "$SANDBOX/tamper/git"
# ---- the project repository at the review's merge base ----
P="$SANDBOX/proto"
T="$P/target"
git init -q -b refactor "$T"
printf 'line1\nline2\nline3\n' > "$T/README.md"
mkdir -p "$T/packages/demo/tests" "$T/packages/queue/tests" "$T/scripts" "$T/src"
cat > "$T/packages/demo/tests/demo.test.mjs" <<'EOF'
import test from 'node:test';
import assert from 'node:assert/strict';
test('demo', () => assert.notEqual(process.env.LW_FAIL, 'pkg'));
EOF
cat > "$T/packages/queue/tests/never.test.mjs" <<'EOF'
import test from 'node:test';
test('the gate runs no pkg-queue step', () => { throw new Error('pkg-queue ran'); });
EOF
cat > "$T/scripts/test-demo.sh" <<'EOF'
#!/usr/bin/env bash
[ "${LW_FAIL:-}" != script ] || exit 1
[ -z "${LW_DIRTY:-}" ] || echo "during the gate" >> "$LW_DIRTY"
if [ -n "${LW_MOVE_BASE:-}" ]; then
moved="$(git -C "$LW_MOVE_BASE" commit-tree "refactor^{tree}" -p refactor -m moved)" \
&& git -C "$LW_MOVE_BASE" update-ref refs/heads/refactor "$moved"
fi
echo "ℹ pass 1"
EOF
echo 'echo legacy' > "$T/src/legacy.sh"
echo node_modules > "$T/.gitignore"
git -C "$T" add -A && git -C "$T" commit -q -m base
MB="$(git -C "$T" rev-parse HEAD)"
# ---- the coder's commits, pushed to the working repository ----
git clone -q "$T" "$P/coder"
CO="$P/coder"
commit_on() { # message: commit what's changed in the coder clone, print the id
git -C "$CO" add -A && git -C "$CO" commit -q -m "$1" && git -C "$CO" rev-parse HEAD
}
git -C "$CO" checkout -q -b w42 "$MB"
printf 'line1\nline2 changed\nline3\n' > "$CO/README.md"
echo feature > "$CO/packages/demo/feature.txt"
W_OK="$(commit_on w42)"
git -C "$CO" checkout -q -b w43 "$MB"; echo notes > "$CO/notes.txt"
W_ROOT="$(commit_on w43)"
git -C "$CO" checkout -q -b w44 "$MB"; mkdir "$CO/tools2"; echo x > "$CO/tools2/x.txt"
W_NEWDIR="$(commit_on w44)"
git -C "$CO" checkout -q -b w45 "$MB"; echo 'export {};' > "$CO/src/parse-requirement-id.mjs"
W_SRC="$(commit_on w45)"
git -C "$CO" checkout -q --orphan w46; git -C "$CO" rm -rfq .; echo o > "$CO/o.txt"
W_ORPHAN="$(commit_on w46)"
git init -q --bare "$P/working"
for pair in "w42:42" "w43:43" "w44:44" "w45:45" "w46:46"; do
git -C "$CO" push -q "$P/working" "${pair%%:*}:refs/heads/work/vikunja-7-${pair#*:}"
done
git -C "$CO" push -q "$P/working" "$MB:refs/heads/work/vikunja-7-47"
# The base moves on after the review, on a path the work doesn't touch.
echo docs > "$T/docs.txt"
git -C "$T" add docs.txt && git -C "$T" commit -q -m "base moves"
OLD="$(git -C "$T" rev-parse HEAD)"
mkdir "$T/node_modules"
echo canary > "$T/node_modules/canary" # cleanup must not empty the linked directory
# ---- bus rows: one decision and its verdicts, in the live rows' shape ----
D="d0000000-0000-4000-8000-000000000001"
V="e0000000-0000-4000-8000-000000000002"
cat > "$SANDBOX/gen.mjs" <<'EOF'
const [variant, p, t, commit] = process.argv.slice(2);
const D = 'd0000000-0000-4000-8000-000000000001';
const L = 'd0000000-0000-4000-8000-000000000009';
const MERGE = 'git.merge.protected';
const biz = variant === 'other-business' ? 'other' : 'fx';
const ref = `work/vikunja-${p}-${t}`;
const task = `vikunja:${p}/${t}`;
const action = variant === 'not-a-merge' ? 'git.push.protected' : MERGE;
const question =
{
'two-refs': `Squash-merge ${ref} and work/vikunja-7-99 into refactor?`,
'bad-ref': 'Squash-merge work/feature-x into refactor?',
}[variant] ?? `Squash-merge ${ref} into refactor? It carries task ${p}/${t}, approved by the reviewer.`;
const options = [
{ key: 'yes', text: `Squash-merge ${ref} into refactor` },
{ key: 'no', text: "Don't merge yet" },
];
const rows = [
{
table: 'decisions', id: D, business: biz, project: null, action, question, options,
...(variant === 'task-ref-disagrees' ? { task_ref: 'vikunja:7/99' } : {}),
},
];
// An earlier merge decision on the same ref, withdrawn before D was raised:
// only a later one supersedes D.
const E = 'd0000000-0000-4000-8000-000000000008';
if (variant === 'earlier-merge') {
rows.unshift({ table: 'decisions', id: E, business: biz, project: null, action: MERGE, task_ref: task, question, options });
rows.push({ table: 'decision_events', decision: E, op: 'withdrawn', by: 'pm', choice: null, via: null });
}
if (variant !== 'no-raise')
rows.push({
table: 'events', business: biz, kind: 'action.allowed', actor_role: 'pm', actor_run: 'rfixture', subject: D,
body: { decision: D, action, operation: 'decision.raise',
task_ref: variant === 'raise-task-disagrees' ? 'vikunja:7/99' : null,
target: variant === 'target-main' ? 'main' : 'refactor', approvalChoice: 'yes', holderRun: null },
});
const choice = variant === 'chose-no' ? 'no' : 'yes';
if (variant === 'withdrawn') rows.push({ table: 'decision_events', decision: D, op: 'withdrawn', by: 'pm', choice: null, via: null });
else if (variant !== 'open') {
rows.push({ table: 'decision_events', decision: D, op: 'resolved', by: variant === 'by-agent' ? 'pm' : 'jason', choice,
via: variant === 'via-webui' ? 'webui' : 'cli' });
const answer = {
table: 'events', business: biz, kind: 'human.input', subject: D,
actor_role: variant === 'answer-role' ? 'pm' : null, actor_run: variant === 'answer-run' ? 'rfixture' : null,
body: { kind: 'answer', decision: D, op: 'resolved', choice: variant === 'answer-choice' ? 'no' : choice },
};
if (variant !== 'no-answer') rows.push(answer);
if (variant === 'two-answers') rows.push({ ...answer });
}
const later = {
'superseded-question': { action: MERGE, question: `Hold ${ref} back after all?` },
'superseded-task': { action: MERGE, task_ref: task, question: `Hold task ${p}/${t} back after all?` },
'superseded-by': { action: 'scope.change', supersedes: D, question: 'Replace the earlier merge decision' },
}[variant];
if (later) rows.push({ table: 'decisions', id: L, business: biz, project: null, options, ...later });
const verdict = (id, v, c, body = {}) => ({
table: 'events', id, business: biz, kind: 'review.verdict', actor_role: 'reviewer', actor_run: 'rrev', subject: task,
body: { task_ref: task, request: 'f0000000-0000-4000-8000-000000000003', commit: c, verdict: v, message: 'fixture', ...body },
});
// An earlier changes verdict, as 19/265 had, so "latest" is what's read.
if (variant !== 'no-verdict') {
rows.push(verdict('e0000000-0000-4000-8000-000000000001', 'changes', '1'.repeat(40)));
rows.push(verdict('e0000000-0000-4000-8000-000000000002', 'approve', variant === 'short-commit' ? commit.slice(0, 12) : commit,
variant === 'wrong-task-body' ? { task_ref: 'vikunja:7/99' } : {}));
}
if (variant === 'changes-later') rows.push(verdict('e0000000-0000-4000-8000-000000000004', 'changes', commit));
process.stdout.write(JSON.stringify(rows));
EOF
# ---- per case: a copy of the target and the working repository, and a bus ----
setup() { # name variant task commit
local c="$SANDBOX/c/$1"
mkdir -p "$c/businesses" "$c/tmp" "$c/data/working/fx"
chmod 700 "$c/data"
cp -a "$T" "$c/target"
cp -a "$P/working" "$c/data/working/fx/stack.git"
printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"mock"}}\n' \
"$c/data" > "$c/config.json"
printf '{"id":"fx","human":"jason","projects":{"stack":{"root":"%s"}}}\n' "$c/target" > "$c/businesses/fx.json"
node "$SANDBOX/gen.mjs" "$2" 7 "$3" "$4" > "$c/rows.json" \
&& node scripts/land-work/fixture.mjs "$c/data" "$c/rows.json" \
|| echo "setup failed for $1" >&2
}
lw() { # name args...
local c="$SANDBOX/c/$1"
shift
MOSAIC_CONFIG="$c/config.json" TMPDIR="$c/tmp" PATH="${LW_PATH:+$LW_PATH:}$SANDBOX/bin:$PATH" \
bash scripts/land-work.sh "$@" > "$c/out" 2> "$c/err"
}
expect() { # name rc reason args...: run land-work, check the exit and the refusal
local name="$1" want="$2" reason="$3" rc=0
shift 3
lw "$name" "$@" || rc=$?
if [ "$rc" -eq "$want" ] && { [ -z "$reason" ] || grep -q "refused: $reason\b" "$SANDBOX/c/$name/err"; }; then
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $name: exit $rc${reason:+, $reason}"
else
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $name: exit $rc, wanted $want${reason:+ $reason}: $(tail -n2 "$SANDBOX/c/$name/err" | tr '\n' ' ')"
fi
}
cleaned() { # name: one worktree left in the target, no scratch left in TMPDIR
local c="$SANDBOX/c/$1"
[ "$(git -C "$c/target" worktree list --porcelain | grep -c '^worktree ')" -eq 1 ] \
&& [ -z "$(find "$c/tmp" -maxdepth 1 -name 'land-work.*')" ]
}
# ---- arguments and configuration ----
setup args ok 42 "$W_OK"
expect args 2 target-branch "$D" --base next
expect args 2 target-branch "$D" --base main
expect args 4 "" "bad/../id"
expect args 4 "" "$D" --unknown
expect args 4 "" "$D" --subject $'two\nlines'
rc=0; MOSAIC_CONFIG="$SANDBOX/missing.json" TMPDIR="$SANDBOX/c/args/tmp" bash scripts/land-work.sh "$D" --base next >/dev/null 2>&1 || rc=$?
check "--base next refuses before the config is read (got $rc)" "$([ "$rc" -eq 2 ]; echo $?)"
rc=0; MOSAIC_CONFIG="$SANDBOX/missing.json" TMPDIR="$SANDBOX/c/args/tmp" bash scripts/land-work.sh "$D" >/dev/null 2>&1 || rc=$?
check "a missing system config exits 3 (got $rc)" "$([ "$rc" -eq 3 ]; echo $?)"
mkdir -p "$SANDBOX/c/nobus/data/working/fx" "$SANDBOX/c/nobus/businesses" "$SANDBOX/c/nobus/tmp"
chmod 700 "$SANDBOX/c/nobus/data"
cp "$SANDBOX/c/args/businesses/fx.json" "$SANDBOX/c/nobus/businesses/"
sed "s#$SANDBOX/c/args/data#$SANDBOX/c/nobus/data#" "$SANDBOX/c/args/config.json" > "$SANDBOX/c/nobus/config.json"
expect nobus 3 "" "$D"
# ---- the bus database ----
setup schema-table ok 42 "$W_OK"
node -e 'const {DatabaseSync}=require("node:sqlite"); const db=new DatabaseSync(process.argv[1]); db.exec("CREATE TABLE extra(x)"); db.close()' \
"$SANDBOX/c/schema-table/data/bus/bus.sqlite"
expect schema-table 2 bus-schema "$D"
setup schema-meta ok 42 "$W_OK"
# The same schema, so the digest matches, with meta saying another version.
node -e '
const {DatabaseSync}=require("node:sqlite"); const db=new DatabaseSync(process.argv[1]);
const sql=db.prepare("SELECT sql FROM sqlite_master WHERE name=?").get("meta_no_update").sql;
db.exec("DROP TRIGGER meta_no_update"); db.exec("UPDATE meta SET value=\x273a\x27 WHERE key=\x27schema_version\x27"); db.exec(sql); db.close()' \
"$SANDBOX/c/schema-meta/data/bus/bus.sqlite"
expect schema-meta 2 bus-schema "$D"
rc=0
node --input-type=module -e '
import { openBus } from "./scripts/land-work/read.mjs";
const db = openBus(process.argv[1]);
try { db.exec("CREATE TABLE x(y)"); process.exit(1); } catch (e) { process.exit(/readonly/.test(e.message) ? 0 : 1); }' \
"$SANDBOX/c/args/data" || rc=$?
check "openBus opens the database read-only" "$rc"
# ---- the decision ----
for v in "decision-not-found:other-business" "decision-not-found:no-raise" "not-a-merge:not-a-merge" \
"not-resolved:open" "not-resolved:withdrawn" "not-human:by-agent" "not-human:via-webui" \
"not-human:no-answer" "not-human:answer-role" "not-human:answer-run" "not-human:answer-choice" \
"not-human:two-answers" "not-merge-option:chose-no" "work-ref:two-refs" "work-ref:bad-ref" \
"work-ref:task-ref-disagrees" "work-ref:raise-task-disagrees" "target-branch:target-main" "superseded:superseded-question" \
"superseded:superseded-task" "superseded:superseded-by" "no-verdict:no-verdict" \
"not-approved:changes-later" "not-approved:short-commit" "not-approved:wrong-task-body"; do
setup "${v#*:}" "${v#*:}" 42 "$W_OK"
expect "${v#*:}" 2 "${v%%:*}" "$D"
done
expect args 2 decision-not-found "d0000000-0000-4000-8000-00000000000f"
# A withdrawn earlier merge decision on the same ref doesn't supersede D.
setup earlier-merge earlier-merge 42 "$W_OK"
expect earlier-merge 0 "" "$D" --dry-run
# ---- the work ref and the reviewed diff ----
setup ref-moved ok 42 "$W_OK"
git -C "$SANDBOX/c/ref-moved/data/working/fx/stack.git" update-ref refs/heads/work/vikunja-7-42 "$W_ROOT"
expect ref-moved 2 ref-moved "$D"
setup root-top ok 43 "$W_ROOT"
expect root-top 2 root-file "$D"
setup root-newdir ok 44 "$W_NEWDIR"
expect root-newdir 2 root-file "$D"
setup root-src ok 45 "$W_SRC"
expect root-src 2 root-file "$D"
setup unrelated ok 46 "$W_ORPHAN"
expect unrelated 2 base-conflict "$D"
setup empty ok 47 "$MB"
expect empty 2 base-conflict "$D"
grep -q "is empty" "$SANDBOX/c/empty/err"
check "an empty reviewed diff says so" "$?"
# The base is checked out in the target: a reviewed path there must be clean.
setup dirty-changed ok 42 "$W_OK"
echo local >> "$SANDBOX/c/dirty-changed/target/README.md"
expect dirty-changed 2 checkout-dirty "$D"
setup dirty-staged ok 42 "$W_OK"
echo local >> "$SANDBOX/c/dirty-staged/target/README.md"
git -C "$SANDBOX/c/dirty-staged/target" add README.md
printf 'line1\nline2\nline3\n' > "$SANDBOX/c/dirty-staged/target/README.md"
expect dirty-staged 2 checkout-dirty "$D"
setup dirty-untracked ok 42 "$W_OK"
echo local > "$SANDBOX/c/dirty-untracked/target/packages/demo/feature.txt"
expect dirty-untracked 2 checkout-dirty "$D"
setup conflict ok 42 "$W_OK"
printf 'line1\nline2 on the base\nline3\n' > "$SANDBOX/c/conflict/target/README.md"
git -C "$SANDBOX/c/conflict/target" commit -q -am "base edits the reviewed line"
expect conflict 2 base-conflict "$D"
check "a refusal after the worktree is added leaves no worktree or scratch" "$(cleaned conflict; echo $?)"
grep -q "network inspect lw-" "$SANDBOX/docker.log" 2>/dev/null
check "cleanup looks for the worktree's Docker network" "$?"
setup dirty-during ok 42 "$W_OK"
LW_DIRTY="$SANDBOX/c/dirty-during/target/README.md" expect dirty-during 2 checkout-dirty "$D"
check "a reviewed path dirtied during the gate moves no ref" \
"$([ "$(git -C "$SANDBOX/c/dirty-during/target" rev-parse refactor)" = "$OLD" ]; echo $?)"
setup drift ok 42 "$W_OK"
LW_PATH="$SANDBOX/tamper" expect drift 2 patch-drift "$D"
check "patch-drift leaves no worktree or scratch" "$(cleaned drift; echo $?)"
# ---- the gate ----
setup gate-pkg ok 42 "$W_OK"
LW_FAIL=pkg expect gate-pkg 2 gate-failed "$D"
log="$(sed -n 's/.*gate-failed: pkg-demo, see \(.*\)$/\1/p' "$SANDBOX/c/gate-pkg/err")"
check "gate-failed names the failing step's log, and it exists" "$([ -n "$log" ] && [ -s "$log" ]; echo $?)"
check "gate-failed leaves no worktree or scratch" "$(cleaned gate-pkg; echo $?)"
setup gate-script ok 42 "$W_OK"
LW_FAIL=script expect gate-script 2 gate-failed "$D"
grep -q "gate-failed: script-demo" "$SANDBOX/c/gate-script/err"
check "a failing scripts/test-*.sh fails the gate" "$?"
check "a failed gate moves no ref" "$([ "$(git -C "$SANDBOX/c/gate-script/target" rev-parse refactor)" = "$OLD" ]; echo $?)"
setup nomods ok 42 "$W_OK"
rm -rf "$SANDBOX/c/nomods/target/node_modules"
expect nomods 4 "" "$D"
check "a target without node_modules leaves no worktree or scratch" "$(cleaned nomods; echo $?)"
setup moved ok 42 "$W_OK"
git -C "$SANDBOX/c/moved/target" checkout -q -b side
LW_MOVE_BASE="$SANDBOX/c/moved/target" expect moved 2 base-moved "$D"
check "base-moved keeps the base where it moved to" \
"$([ "$(git -C "$SANDBOX/c/moved/target" log -1 --format=%s refactor)" = moved ]; echo $?)"
# ---- a dry run ----
setup dry ok 42 "$W_OK"
before="$(git -C "$SANDBOX/c/dry/target" for-each-ref; git -C "$SANDBOX/c/dry/target" reflog refactor; git -C "$SANDBOX/c/dry/target" status --porcelain)"
expect dry 0 "" "$D" --dry-run --subject "Custom subject" --attribution "Applied-under: suite (conductor)"
after="$(git -C "$SANDBOX/c/dry/target" for-each-ref; git -C "$SANDBOX/c/dry/target" reflog refactor; git -C "$SANDBOX/c/dry/target" status --porcelain)"
check "--dry-run writes no ref" "$([ "$before" = "$after" ]; echo $?)"
grep -q "no ref was written" "$SANDBOX/c/dry/out" && grep -qx " Custom subject" "$SANDBOX/c/dry/out" \
&& grep -qx " Applied-under: suite (conductor)" "$SANDBOX/c/dry/out"
check "--dry-run prints the commit with --subject and --attribution" "$?"
grep -q "^GATE_PASSED" "$SANDBOX/c/dry/err"
check "--dry-run runs the gate" "$?"
check "--dry-run leaves no worktree or scratch" "$(cleaned dry; echo $?)"
DRY_TREE="$(sed -n 's/.*would commit tree \([0-9a-f]*\) .*/\1/p' "$SANDBOX/c/dry/out")"
# ---- a landing ----
setup land ok 42 "$W_OK"
L="$SANDBOX/c/land"
echo "local edit" >> "$L/target/docs.txt"
BUS_BEFORE="$(sha256sum "$L/data/bus/bus.sqlite")"
: > "$SANDBOX/docker.log"
FAKE_NET_EXISTS=1 expect land 0 "" "$D"
NEW="$(git -C "$L/target" rev-parse refactor)"
check "the landing is one commit on the old base" "$([ "$(git -C "$L/target" rev-parse "$NEW^")" = "$OLD" ]; echo $?)"
pid() { git -C "$L/target" diff "$1" "$2" | git patch-id --stable | cut -d' ' -f1; }
check "its diff is the reviewed diff by patch-id" "$([ "$(pid "$OLD" "$NEW")" = "$(pid "$MB" "$W_OK")" ]; echo $?)"
check "its tree is the dry run's" "$([ -n "$DRY_TREE" ] && [ "$(git -C "$L/target" rev-parse "$NEW^{tree}")" = "$DRY_TREE" ]; echo $?)"
msg="$(git -C "$L/target" log -1 --format=%B "$NEW")"
PATCH_ID="$(pid "$MB" "$W_OK")"
ok=0
[ "$(head -n1 <<<"$msg")" = "Squash-merge work/vikunja-7-42 into refactor" ] || ok=1
for line in "Decision: $D" "Verdict: $V" "Work-ref: work/vikunja-7-42" "Reviewed-commit: $W_OK" \
"Base: refactor $OLD" "Patch-id: $PATCH_ID"; do
grep -qxF "$line" <<<"$msg" || ok=1
done
[ "$(sed '/^$/d' <<<"$msg" | tail -n1)" = "Applied-under: scripts/land-work.sh (conductor)" ] || ok=1
check "the message has the option's subject, the citations and the attribution last" "$ok"
check "it prints the line for the PM" \
"$([ "$(tail -n1 "$L/out")" = "work/vikunja-7-42 (vikunja:7/42) is merged to refactor as ${NEW:0:9}; push pending." ]; echo $?)"
check "the checked-out base has the reviewed paths and keeps the unrelated edit" \
"$([ "$(git -C "$L/target" status --porcelain)" = " M docs.txt" ] && [ -f "$L/target/packages/demo/feature.txt" ]; echo $?)"
check "only refs/heads/refactor exists in the target" \
"$([ "$(git -C "$L/target" for-each-ref --format='%(refname)')" = refs/heads/refactor ]; echo $?)"
check "the work ref is unchanged" \
"$([ "$(git -C "$L/data/working/fx/stack.git" rev-parse refs/heads/work/vikunja-7-42)" = "$W_OK" ]; echo $?)"
check "the bus database is unchanged" "$([ "$(sha256sum "$L/data/bus/bus.sqlite")" = "$BUS_BEFORE" ]; echo $?)"
logs="$(sed -n 's/^land-work: gate logs in //p' "$L/err")"
grep -q "^pkg-demo rc=0" "$logs/gate.log" && grep -q "^script-demo rc=0" "$logs/gate.log" \
&& grep -q "^GATE_PASSED" "$logs/gate.log" && ! grep -q "pkg-queue" "$logs/gate.log"
check "the gate ran each package suite but queue, and each scripts/test-*.sh" "$?"
check "the landing leaves no worktree or scratch" "$(cleaned land; echo $?)"
check "the target's node_modules is still there, with its contents" \
"$([ -d "$L/target/node_modules" ] && [ ! -L "$L/target/node_modules" ] && [ -f "$L/target/node_modules/canary" ]; echo $?)"
grep -q "^docker network rm lw-[0-9]*-[0-9]*_default$" "$SANDBOX/docker.log"
check "the worktree's Docker network is removed" "$?"
echo "---"
echo "land-work selftests: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]