AtlatestRepositoryfolio
1
#!/bin/sh2
# sabotage-f3b.sh -- proves gate-f3b.sh's arms can go RED. Product-layer3
# plants with the F3a discipline: prediction printed first (red AND green4
# sets), plant landed by hash + marker, planted tree must build, restores by5
# git checkout verified by hash, full gate re-run green at the end.6
#7
# Legs (see the population-split comment before T1 -- the first run's8
# mispredictions are what taught it):9
# T1 raw-vs-semantic ACCOUNTING tolerated -> b6 red: the prose/markdown10
# carriers are UNSTAGED, so only the accounting can refuse for them11
# T2 semantic pass returns NO sites + accounting tolerated -> b5 red on12
# its CONTENT asserts (referrers never touched; closure cannot see13
# unstaged files either)14
# T2c semantic pass keeps only the FIRST site per file + accounting15
# tolerated -> b5 red WITH the closure signature (staged residue is16
# closure's own population)17
# T3 delete ignores referrers -> b7-delete red18
# T4 recovery goes silent (recover-root! returns '() doing nothing) ->19
# b3-recovery-completes red (completion never happens, manifest stays)20
# T5 recovery TOLERATES an unprovable state (the first-pass proof clause21
# always passes) -> gate arm 5 red (the corrupt-temp driver observes22
# recovery proceeding over garbage instead of refusing to a human)23
# T2c semantic pass drops sites after the first per file (accounting24
# tolerated) -> b5 red WITH the closure signature (staged residue)25
# TA assertion layer: flip the bystander hash expectation -> b12 red27
set -u28
repo_dir=$(cd "$(dirname "$0")/.." && pwd)29
cd "$repo_dir" || exit 131
work=$(mktemp -d /tmp/sabotage-f3b.XXXXXX) || { echo "SAB-F3B SETUP-FAILED: no temp dir"; exit 3; }32
trap 'chmod -R u+rwX "$work" 2>/dev/null; rm -rf "$work"' EXIT INT TERM34
sigil="${SIGIL:-sigil}"35
export SIGIL_REDIRECTS="${SIGIL_F3B_REDIRECTS:-$repo_dir/dev-redirects.sgl}"36
F3B_FIX="$repo_dir/test/fixtures/f3b"38
ST=src/folio/doc/structural.sgl39
MAN=src/folio/doc/manifest.sgl40
PROBE=test/f3b-probe.sgl42
legs=0; reds=0; setup_fails=043
fail_leg () { echo "SAB-F3B LEG $1: DID NOT GO RED AS PREDICTED -- $2" >&2; }44
setup_failed_leg () { echo "SAB-F3B LEG $1: SETUP-FAILED -- $2" >&2; setup_fails=$((setup_fails + 1)); }45
hash_of () { sha256sum "$1" | cut -d' ' -f1; }47
h_st=$(hash_of "$ST"); h_man=$(hash_of "$MAN"); h_probe=$(hash_of "$PROBE")48
restore_all () {49
git checkout -- "$ST" "$MAN" "$PROBE" || return 150
[ "$(hash_of "$ST")" = "$h_st" ] || { echo "SAB-F3B RESTORE FAILED: $ST" >&2; return 1; }51
[ "$(hash_of "$MAN")" = "$h_man" ] || { echo "SAB-F3B RESTORE FAILED: $MAN" >&2; return 1; }52
[ "$(hash_of "$PROBE")" = "$h_probe" ] || { echo "SAB-F3B RESTORE FAILED: $PROBE" >&2; return 1; }53
return 054
}55
git diff --quiet -- "$ST" "$MAN" "$PROBE" \56
|| { echo "SAB-F3B SETUP-FAILED: dirty tree under the plant files"; exit 3; }58
plant_landed () {59
leg=$1; file=$2; old=$3; marker=$4; want=$560
[ "$(hash_of "$file")" != "$old" ] || { setup_failed_leg "$leg" "no diff in $file"; return 1; }61
got=$(grep -c "$marker" "$file" || true)62
[ "$got" -eq "$want" ] || { setup_failed_leg "$leg" "marker '$marker' count $got, wanted $want"; return 1; }63
return 064
}65
build_planted () {66
if ! "$sigil" build >"$work/build-$1.log" 2>&1; then67
setup_failed_leg "$1" "planted tree does not build"; return 168
fi69
return 070
}71
run_battery () {72
rb="$work/root-$1"; mkdir -p "$rb"; cp "$F3B_FIX"/* "$rb"/73
F3B_ROOT="$rb" "$sigil" -L build/dev/lib -L src "$PROBE" >"$work/bat-$1.log" 2>&174
return $?75
}76
expect () {77
leg=$1; arm=$2; want=$378
grep -q "F3B_ARM_${arm}_${want}" "$work/bat-$leg.log" && return 079
fail_leg "$leg" "predicted $arm=$want; got: $(grep "F3B_ARM_${arm}_" "$work/bat-$leg.log" | head -1 || echo 'no marker')"80
return 181
}82
leg_result () {83
legs=$((legs + 1))84
[ "$2" -eq 0 ] && { reds=$((reds + 1)); echo "SAB-F3B LEG $1: RED AS PREDICTED"; }85
}87
# ---------------------------------------------------------------------------88
# THE POPULATION SPLIT THESE THREE LEGS DEMONSTRATE (learned from this89
# suite's own first run, whose predictions had it backwards): the ACCOUNTING90
# protects UNSTAGED carriers -- a prose or markdown file is never staged, so91
# graph closure structurally cannot see it; only the raw-vs-semantic92
# arithmetic can refuse for it. GRAPH CLOSURE protects STAGED files -- a93
# rewrite site dropped inside a touched file leaves residue closure sees.94
# The two layers partition the population rather than backing each other up,95
# and each leg below fells exactly one.96
# ---------------------------------------------------------------------------97
echo "SAB-F3B LEG T1: raw-vs-semantic accounting tolerated"98
echo " PREDICT RED: b6-rename-refusals (the prose and markdown occurrences"99
echo " are silently dropped, the renames PROCEED, and b6's"100
echo " expected refusals never come -- closure cannot help:"101
echo " those files are never staged)"102
echo " PREDICT GREEN: b5 (every occurrence in ITS web is rewritable), b1, b7"103
sed -i 's/(if (= accounted (length hits))/(if #t ;; SAB-T1/' src/folio/doc/structural.sgl104
if plant_landed T1 "$ST" "$h_st" "SAB-T1" 1 && build_planted T1; then105
run_battery T1 || true106
ok=0107
expect T1 b6-rename-refusals RED || ok=1108
expect T1 b5-rename GREEN || ok=1109
expect T1 b1-stand-down GREEN || ok=1110
expect T1 b7-delete GREEN || ok=1111
leg_result T1 $ok112
fi113
restore_all || exit 3115
# ---------------------------------------------------------------------------116
echo "SAB-F3B LEG T2: the semantic pass returns NO sites (total silent loss)"117
echo " with the accounting tolerated -- referrer files are never touched at"118
echo " all, so closure (staged-only) cannot see it either; b5's own CONTENT"119
echo " asserts on the referrer files are what catches it"120
echo " PREDICT RED: b5-rename (residue in ref files)"121
echo " PREDICT GREEN: b1"122
sed -i 's/(if (= accounted (length hits))/(if #t ;; SAB-T1/' src/folio/doc/structural.sgl123
sed -i 's/(walk rest (if f (cons (list n f) out) out))/(walk rest (begin (quote sab-t2) out))/' src/folio/doc/structural.sgl124
if plant_landed T2 "$ST" "$h_st" "sab-t2" 1 && build_planted T2; then125
run_battery T2 || true126
ok=0127
expect T2 b5-rename RED || ok=1128
expect T2 b1-stand-down GREEN || ok=1129
leg_result T2 $ok130
fi131
restore_all || exit 3133
# ---------------------------------------------------------------------------134
echo "SAB-F3B LEG T2c: the semantic pass drops every site AFTER the first per"135
echo " file (accounting tolerated) -- ref-two stays TOUCHED with one of its"136
echo " two sites stale, which is exactly the residue GRAPH CLOSURE exists"137
echo " to catch"138
echo " PREDICT RED: b5-rename WITH the closure signature"139
sed -i 's/(if (= accounted (length hits))/(if #t ;; SAB-T1/' src/folio/doc/structural.sgl140
sed -i 's/(walk rest (if f (cons (list n f) out) out))/(walk rest (if (and f (null? out)) (cons (list n f) out) (begin (quote sab-t2c) out)))/' src/folio/doc/structural.sgl141
if plant_landed T2c "$ST" "$h_st" "sab-t2c" 1 && build_planted T2c; then142
run_battery T2c || true143
ok=0144
expect T2c b5-rename RED || ok=1145
grep -q "closure" "$work/bat-T2c.log" \146
|| { fail_leg T2c "red, but not via the closure check"; ok=1; }147
leg_result T2c $ok148
fi149
restore_all || exit 3151
# ---------------------------------------------------------------------------152
echo "SAB-F3B LEG T3: delete ignores referrers"153
echo " PREDICT RED: b7-delete (a referenced delete publishes; the arm"154
echo " expects the refusal)"155
echo " PREDICT GREEN: b1, b5"156
sed -i 's/((or (pair? semantic) (pair? raw-only))/(#f ;; SAB-T3/' src/folio/doc/structural.sgl157
if plant_landed T3 "$ST" "$h_st" "SAB-T3" 1 && build_planted T3; then158
run_battery T3 || true159
ok=0160
expect T3 b7-delete RED || ok=1161
expect T3 b1-stand-down GREEN || ok=1162
expect T3 b5-rename GREEN || ok=1163
leg_result T3 $ok164
fi165
restore_all || exit 3167
# ---------------------------------------------------------------------------168
echo "SAB-F3B LEG T4: recovery goes silent (does nothing, reports nothing)"169
echo " PREDICT RED: b3-recovery-completes (no completion, no report, the"170
echo " manifest survives into the arm's asserts)"171
echo " PREDICT GREEN: b1 (no crash state on its path)"172
sed -i 's/(if (file-exists? mpath)/(if #f ;; SAB-T4/' src/folio/doc/manifest.sgl173
if plant_landed T4 "$MAN" "$h_man" "SAB-T4" 1 && build_planted T4; then174
run_battery T4 || true175
ok=0176
expect T4 b3-recovery-completes RED || ok=1177
expect T4 b1-stand-down GREEN || ok=1178
leg_result T4 $ok179
fi180
restore_all || exit 3182
# ---------------------------------------------------------------------------183
echo "SAB-F3B LEG T5: recovery TOLERATES the unprovable (the first-pass proof"184
echo " always passes) -- gate arm 5's driver must observe recovery proceeding"185
echo " over a corrupt temp instead of refusing to a human"186
# Anchor on the proof loop's own first cond test (unique in the file): making187
# it always true means every entry is 'proven' finishable, so recovery188
# proceeds over the corrupt temp. A bare `(else` anchor is NOT unique -- the189
# containment fix and the sweep added more of them.190
sed -i 's/((equal? cur (mentry-post-rev e)) #t)/((begin (quote sab-t5) #t) #t)/' src/folio/doc/manifest.sgl191
if ! grep -q "sab-t5" src/folio/doc/manifest.sgl; then192
setup_failed_leg T5 "plant did not land (anchor moved)"193
else194
count=$(grep -c "sab-t5" src/folio/doc/manifest.sgl)195
if [ "$count" -ne 1 ]; then196
setup_failed_leg T5 "anchor matched $count sites; the plant is not the plant"197
restore_all || exit 3198
else199
if build_planted T5; then200
r5="$work/root-T5"; mkdir -p "$r5"; cp "$F3B_FIX"/* "$r5"/201
F3B_ROOT="$r5" "$sigil" -L build/dev/lib -L src test/f3b-corrupt-temp-driver.sgl >"$work/t5.log" 2>&1202
ok=0203
if grep -q "CORRUPT_TEMP_REFUSED_TO_HUMAN" "$work/t5.log"; then204
fail_leg T5 "the driver still saw a refusal with the proof clause disabled"; ok=1205
else206
grep -qE "PROCEEDED_OVER_UNPROVABLE_STATE|RAISED_BUT_WRONG_SHAPE" "$work/t5.log" \207
|| { fail_leg T5 "no recognisable outcome: $(tail -2 "$work/t5.log")"; ok=1; }208
fi209
leg_result T5 $ok210
fi211
fi212
fi213
restore_all || exit 3215
# ---------------------------------------------------------------------------216
echo "SAB-F3B LEG TA: assertion layer -- flip the bystander hash expectation"217
echo " PREDICT RED: b12-bystander"218
# The marker lives INSIDE a string literal: the first spelling appended a219
# `;;` comment mid-expression, which swallowed the closing parens and killed220
# the whole probe at parse (observed: an EMPTY arm set, the worst red shape).221
sed -i 's/(equal? (revision-of-file (rooted "bystander.md")) bystander-hash)/(equal? "SAB-TA-impossible" (revision-of-file (rooted "bystander.md")))/' test/f3b-probe.sgl222
if plant_landed TA "$PROBE" "$h_probe" "SAB-TA-impossible" 1; then223
run_battery TA || true224
ok=0225
expect TA b12-bystander RED || ok=1226
leg_result TA $ok227
fi228
restore_all || exit 3230
# ---------------------------------------------------------------------------231
echo "SAB-F3B: rebuilding restored tree and re-running the FULL gate"232
"$sigil" build >"$work/build-final.log" 2>&1 || { echo "SAB-F3B SETUP-FAILED: restored tree does not build"; exit 3; }233
if sh test/gate-f3b.sh >"$work/gate-final.log" 2>&1; then234
echo "SAB-F3B: restored gate GREEN"235
else236
echo "SAB-F3B FAILED: gate RED on the RESTORED tree"; tail -5 "$work/gate-final.log"; exit 1237
fi239
echo "---"240
echo "F3B_SABOTAGE legs=$legs red=$reds setup_failed=$setup_fails"241
[ "$legs" -eq 7 ] && [ "$reds" -eq 7 ] && [ "$setup_fails" -eq 0 ] && exit 0242
exit 1