AtlatestRepositoryfolio
1
#!/bin/sh2
# sabotage-f3a.sh -- proves gate-f3a.sh's arms can go RED, one product-layer3
# plant per leg, plus one assertion-layer leg.4
#5
# DISCIPLINE (procedures/worker-instructions, predict-which-arms-go-red):6
# - every leg PRINTS ITS PREDICTION (red set AND green set) before running;7
# - every plant is verified LANDED (source hash moved + marker count) and8
# the planted tree must still BUILD, so a red is about behaviour, not a9
# syntax error;10
# - every restore is verified BY HASH against git's blob, and the final11
# step re-runs the full gate green on the restored tree;12
# - a mismatch between predicted and observed arm sets is SETUP-FAILED for13
# that leg, not a verdict.14
#15
# Legs:16
# S1 remove the revision comparison -> sab-revcheck red17
# S2 remove rebase precondition 2 -> sab-precond2 red18
# S3 first-matching-text instead of span -> sab-span, sab-uncontended,19
# sab-rebase-report red20
# S4a serializer defect (due drops quote) -> postcheck DEFENDS: sab-span,21
# sab-rebase-report red;22
# sab-postcheck-defends GREEN23
# (refusal + no publish)24
# S4b S4a + verify verdict ignored -> sab-postcheck-defends red25
# (corrupt note PUBLISHED)26
# S5 coordinator bypasses the lock -> gate arm 3 (real contention)27
# red: two OKs in a round28
# S6 status lands without its @update -> sab-journal red29
# S7 acquisition degrades to a SHARED lock -> sab-coord-init red (the30
# second-acquisition direction31
# of the exclusion probe)32
# S8 a rebase stops reporting itself -> sab-rebase-report red33
# SA assertion layer: expected reason flip -> sab-revcheck red35
set -u36
repo_dir=$(cd "$(dirname "$0")/.." && pwd)37
cd "$repo_dir" || exit 139
work=$(mktemp -d /tmp/sabotage-f3a.XXXXXX) || { echo "SAB-F3A SETUP-FAILED: no temp dir"; exit 3; }40
trap 'chmod -R u+rwX "$work" 2>/dev/null; rm -rf "$work"' EXIT INT TERM42
sigil="${SIGIL:-sigil}"43
export SIGIL_REDIRECTS="${SIGIL_F3A_REDIRECTS:-$repo_dir/dev-redirects.sgl}"44
FIXTURES="$repo_dir/test/fixtures/f3a"46
TXN=src/folio/doc/transaction.sgl47
LOCK=src/folio/doc/lock.sgl48
COORD=src/folio/doc/coordinator.sgl49
DRIVER=test/f3a-sab-driver.sgl51
legs=0; reds=0; setup_fails=052
fail_leg () { echo "SAB-F3A LEG $1: DID NOT GO RED AS PREDICTED -- $2" >&2; }53
setup_failed_leg () { echo "SAB-F3A LEG $1: SETUP-FAILED -- $2" >&2; setup_fails=$((setup_fails + 1)); }55
hash_of () { sha256sum "$1" | cut -d' ' -f1; }57
# Baseline hashes for restore verification.58
h_txn=$(hash_of "$TXN"); h_lock=$(hash_of "$LOCK"); h_coord=$(hash_of "$COORD"); h_driver=$(hash_of "$DRIVER")60
restore_all () {61
git checkout -- "$TXN" "$LOCK" "$COORD" "$DRIVER" || return 162
[ "$(hash_of "$TXN")" = "$h_txn" ] || { echo "SAB-F3A: RESTORE FAILED for $TXN" >&2; return 1; }63
[ "$(hash_of "$LOCK")" = "$h_lock" ] || { echo "SAB-F3A: RESTORE FAILED for $LOCK" >&2; return 1; }64
[ "$(hash_of "$COORD")" = "$h_coord" ] || { echo "SAB-F3A: RESTORE FAILED for $COORD" >&2; return 1; }65
[ "$(hash_of "$DRIVER")" = "$h_driver" ] || { echo "SAB-F3A: RESTORE FAILED for $DRIVER" >&2; return 1; }66
return 067
}69
# Refuse to run on a dirty tree: plants and restores are only meaningful70
# against a known baseline.71
if ! git diff --quiet -- "$TXN" "$LOCK" "$COORD" "$DRIVER"; then72
echo "SAB-F3A SETUP-FAILED: tree is dirty under the files this script plants into"; exit 373
fi75
# plant_landed <leg> <file> <old-hash> <marker> <count>76
plant_landed () {77
leg=$1; file=$2; old=$3; marker=$4; want=$578
[ "$(hash_of "$file")" != "$old" ] || { setup_failed_leg "$leg" "plant produced no diff in $file"; return 1; }79
got=$(grep -c "$marker" "$file" || true)80
[ "$got" -eq "$want" ] || { setup_failed_leg "$leg" "marker '$marker' count $got, wanted $want"; return 1; }81
return 082
}84
build_planted () {85
leg=$186
if ! "$sigil" build >"$work/build-$leg.log" 2>&1; then87
setup_failed_leg "$leg" "the planted tree does not build; the leg would redden on a build failure, not behaviour"88
return 189
fi90
return 091
}93
# run_driver <leg> <arms-csv> -> leaves output in $work/driver-$leg.log94
run_driver () {95
leg=$1; arms=$296
F3A_FIXTURES="$FIXTURES" F3A_SAB_ARMS="$arms" \97
"$sigil" -L build/dev/lib -L src "$DRIVER" >"$work/driver-$leg.log" 2>&198
return $?99
}101
# expect <leg> <log> <arm> <RED|GREEN>102
expect () {103
leg=$1; log=$2; arm=$3; want=$4104
if grep -q "SAB_ARM_${arm}_${want}" "$log"; then return 0; fi105
fail_leg "$leg" "predicted $arm=$want; log says: $(grep "SAB_ARM_${arm}_" "$log" | head -1 || echo 'no marker at all')"106
return 1107
}109
leg_result () {110
# $1 leg name, $2 = 0 if all expectations held111
legs=$((legs + 1))112
if [ "$2" -eq 0 ]; then113
reds=$((reds + 1))114
echo "SAB-F3A LEG $1: RED AS PREDICTED"115
fi116
}118
# ---------------------------------------------------------------------------119
echo "SAB-F3A LEG S1: remove the revision comparison"120
echo " PREDICT RED: sab-revcheck (a stale write publishes)"121
echo " PREDICT GREEN: sab-uncontended"122
sed -i 's/((equal? rev expected-revision)$/(#t ;; SAB-S1/' src/folio/doc/transaction.sgl123
if plant_landed S1 "$TXN" "$h_txn" "SAB-S1" 1 && build_planted S1; then124
run_driver S1 "sab-revcheck,sab-uncontended" || true125
ok=0126
expect S1 "$work/driver-S1.log" sab-revcheck RED || ok=1127
expect S1 "$work/driver-S1.log" sab-uncontended GREEN || ok=1128
leg_result S1 $ok129
fi130
restore_all || exit 3132
# ---------------------------------------------------------------------------133
echo "SAB-F3A LEG S2: remove rebase precondition 2 (old value unchanged)"134
echo " PREDICT RED: sab-precond2 (a conflicting rebase publishes)"135
echo " PREDICT GREEN: sab-uncontended, sab-rebase-report"136
sed -i 's/((not (equal? (field-value f) expected-old-value))$/(#f/' src/folio/doc/transaction.sgl137
if plant_landed S2 "$TXN" "$h_txn" '(#f' 1 && build_planted S2; then138
run_driver S2 "sab-precond2,sab-uncontended,sab-rebase-report" || true139
ok=0140
expect S2 "$work/driver-S2.log" sab-precond2 RED || ok=1141
expect S2 "$work/driver-S2.log" sab-uncontended GREEN || ok=1142
expect S2 "$work/driver-S2.log" sab-rebase-report GREEN || ok=1143
leg_result S2 $ok144
fi145
restore_all || exit 3147
# ---------------------------------------------------------------------------148
echo "SAB-F3A LEG S3: land the edit at the FIRST MATCHING TEXT, not the span"149
echo " PREDICT RED: sab-span, sab-uncontended, sab-rebase-report (the"150
echo " fixture's prose duplicates catch all three via the"151
echo " postcheck: the value never lands in the task)"152
echo " PREDICT GREEN: sab-journal, sab-revcheck, sab-precond2, sab-postcheck-defends"153
sed -i 's/^ (define (splice source start end replacement)$/ (define (sab-first s needle) (let ((sl (string-length s)) (nl (string-length needle))) (let loop ((i 0)) (cond ((> (+ i nl) sl) 0) ((equal? (substring s i (+ i nl)) needle) i) (else (loop (+ i 1)))))))\n (define (splice source start end replacement)/' src/folio/doc/transaction.sgl154
sed -i 's/(staged (splice source (span-scalar-start sp)/(staged (splice source (sab-first source (serialize-value field (field-value f)))/' src/folio/doc/transaction.sgl155
sed -i 's/(span-scalar-end sp) replacement)))/(+ (sab-first source (serialize-value field (field-value f))) (string-length (serialize-value field (field-value f)))) replacement)))/' src/folio/doc/transaction.sgl156
if plant_landed S3 "$TXN" "$h_txn" 'sab-first' 3 && build_planted S3; then157
run_driver S3 "sab-span,sab-uncontended,sab-rebase-report,sab-journal,sab-revcheck,sab-precond2,sab-postcheck-defends" || true158
ok=0159
expect S3 "$work/driver-S3.log" sab-span RED || ok=1160
expect S3 "$work/driver-S3.log" sab-uncontended RED || ok=1161
expect S3 "$work/driver-S3.log" sab-rebase-report RED || ok=1162
expect S3 "$work/driver-S3.log" sab-journal GREEN || ok=1163
expect S3 "$work/driver-S3.log" sab-revcheck GREEN || ok=1164
expect S3 "$work/driver-S3.log" sab-precond2 GREEN || ok=1165
expect S3 "$work/driver-S3.log" sab-postcheck-defends GREEN || ok=1166
leg_result S3 $ok167
fi168
restore_all || exit 3170
# ---------------------------------------------------------------------------171
echo "SAB-F3A LEG S4a: serializer drops due's closing quote -- the POSTCHECK"172
echo " must refuse and nothing may publish (this leg proves the reparse"173
echo " DEFENDS; S4b then proves the defense is load-bearing)"174
echo " PREDICT RED: sab-span, sab-rebase-report (their due edits refuse)"175
echo " PREDICT GREEN: sab-postcheck-defends (refusal + unchanged bytes is"176
echo " that arm's plant-mode acceptance), sab-uncontended"177
sed -i 's/((due) (escape-string-literal value))/((due) (string-append "\\"" value))/' src/folio/doc/transaction.sgl178
if plant_landed S4a "$TXN" "$h_txn" '((due) (string-append' 1 && build_planted S4a; then179
run_driver S4a "sab-span,sab-rebase-report,sab-postcheck-defends,sab-uncontended" || true180
ok=0181
expect S4a "$work/driver-S4a.log" sab-span RED || ok=1182
expect S4a "$work/driver-S4a.log" sab-rebase-report RED || ok=1183
expect S4a "$work/driver-S4a.log" sab-postcheck-defends GREEN || ok=1184
expect S4a "$work/driver-S4a.log" sab-uncontended GREEN || ok=1185
leg_result S4a $ok186
fi187
restore_all || exit 3189
# ---------------------------------------------------------------------------190
echo "SAB-F3A LEG S4b: S4a's serializer defect PLUS the verify verdict ignored"191
echo " -- skip post-write reparse: the corrupt note PUBLISHES"192
echo " PREDICT RED: sab-postcheck-defends (published corruption), sab-span"193
echo " PREDICT GREEN: sab-uncontended (nothing corrupt to catch on priority)"194
sed -i 's/((due) (escape-string-literal value))/((due) (string-append "\\"" value))/' src/folio/doc/transaction.sgl195
sed -i 's/(if bad$/(if #f/' src/folio/doc/transaction.sgl196
if plant_landed S4b "$TXN" "$h_txn" '(if #f' 1 && build_planted S4b; then197
run_driver S4b "sab-postcheck-defends,sab-span,sab-uncontended" || true198
ok=0199
expect S4b "$work/driver-S4b.log" sab-postcheck-defends RED || ok=1200
expect S4b "$work/driver-S4b.log" sab-span RED || ok=1201
expect S4b "$work/driver-S4b.log" sab-uncontended GREEN || ok=1202
leg_result S4b $ok203
fi204
restore_all || exit 3206
# ---------------------------------------------------------------------------207
echo "SAB-F3A LEG S5: the coordinator publishes WITHOUT taking the lock"208
echo " PREDICT RED: gate-f3a arm 3 -- some round yields two OKs (a lost"209
echo " update), which is what the lock exists to prevent"210
sed -i 's/(with-root-lock (coordinator-record-root c)/((lambda (sab-r sab-w sab-t) (sab-t)) (coordinator-record-root c)/' src/folio/doc/coordinator.sgl211
if plant_landed S5 "$COORD" "$h_coord" 'sab-t' 1 && build_planted S5; then212
if sh test/gate-f3a.sh 3 >"$work/gate-S5.log" 2>&1; then213
fail_leg S5 "gate arm 3 stayed GREEN with the lock bypassed"214
legs=$((legs + 1))215
else216
if grep -q "expected exactly 1 OK, got 2" "$work/gate-S5.log"; then217
legs=$((legs + 1)); reds=$((reds + 1))218
echo "SAB-F3A LEG S5: RED AS PREDICTED ($(grep -c 'expected exactly 1 OK, got 2' "$work/gate-S5.log") round(s) with a lost update)"219
else220
fail_leg S5 "gate went red but not with the predicted two-OK signature: $(grep 'GATE-F3A FAIL' "$work/gate-S5.log" | head -2)"221
legs=$((legs + 1))222
fi223
fi224
fi225
restore_all || exit 3227
# ---------------------------------------------------------------------------228
echo "SAB-F3A LEG S6: the status transition no longer writes its @update"229
echo " PREDICT RED: sab-journal (the count-increase postcheck REFUSES the"230
echo " unjournalled write, so the transition cannot publish)"231
echo " PREDICT GREEN: sab-uncontended"232
# The matched text contains string-append's OPENING paren, so the233
# replacement carries one fewer close than the match: "")) not ""))).234
# (The first spelling failed the planted-tree build -- caught by235
# build_planted as SETUP-FAILED, which is that guard doing its job.)236
sed -i '0,/(string-append "\\n" update-src)))/s//""))/' src/folio/doc/transaction.sgl237
if plant_landed S6 "$TXN" "$h_txn" 'string-append "\\n" update-src' 1 && build_planted S6; then238
run_driver S6 "sab-journal,sab-uncontended" || true239
ok=0240
expect S6 "$work/driver-S6.log" sab-journal RED || ok=1241
expect S6 "$work/driver-S6.log" sab-uncontended GREEN || ok=1242
leg_result S6 $ok243
fi244
restore_all || exit 3246
# ---------------------------------------------------------------------------247
echo "SAB-F3A LEG S7: acquisition degrades to a SHARED lock (excludes nothing"248
echo " it cooperates with, still conflicts with an exclusive prober)"249
echo " PREDICT RED: sab-coord-init via the SECOND-ACQUISITION direction of"250
echo " the exclusion probe -- the raw-flock direction alone"251
echo " cannot see this plant, which is why 1b exists"252
sed -i 's/(process-spawn-pipe "flock" "-n" path/(process-spawn-pipe "flock" "-s" "-n" path/' src/folio/doc/lock.sgl253
sed -i 's/(process-spawn-pipe "flock" "-w"/(process-spawn-pipe "flock" "-s" "-w"/' src/folio/doc/lock.sgl254
if plant_landed S7 "$LOCK" "$h_lock" '"-s"' 2 && build_planted S7; then255
run_driver S7 "sab-coord-init" || true256
ok=0257
expect S7 "$work/driver-S7.log" sab-coord-init RED || ok=1258
grep -q "SECOND ACQUISITION" "$work/driver-S7.log" \259
|| { fail_leg S7 "red, but not via the second-acquisition direction"; ok=1; }260
leg_result S7 $ok261
fi262
restore_all || exit 3264
# ---------------------------------------------------------------------------265
echo "SAB-F3A LEG S8: a successful rebase stops reporting itself"266
echo " PREDICT RED: sab-rebase-report (precondition 5: a silent rebase is a"267
echo " defect even when byte-correct)"268
echo " PREDICT GREEN: sab-uncontended"269
sed -i 's/rebased: rebased?$/rebased: #f/' src/folio/doc/transaction.sgl270
if plant_landed S8 "$TXN" "$h_txn" 'rebased: #f' 1 && build_planted S8; then271
run_driver S8 "sab-rebase-report,sab-uncontended" || true272
ok=0273
expect S8 "$work/driver-S8.log" sab-rebase-report RED || ok=1274
expect S8 "$work/driver-S8.log" sab-uncontended GREEN || ok=1275
leg_result S8 $ok276
fi277
restore_all || exit 3279
# ---------------------------------------------------------------------------280
echo "SAB-F3A LEG SA: assertion layer -- flip sab-revcheck's expected reason"281
echo " PREDICT RED: sab-revcheck (the driver's own assertions are live)"282
sed -i "s/(assert-refusal \"stale title edit refused\" r 'revision-mismatch)/(assert-refusal \"stale title edit refused\" r 'wrong-kind)/" test/f3a-sab-driver.sgl283
if plant_landed SA "$DRIVER" "$h_driver" "'wrong-kind" 1; then284
# No rebuild needed: the driver is interpreted from source.285
run_driver SA "sab-revcheck" || true286
ok=0287
expect SA "$work/driver-SA.log" sab-revcheck RED || ok=1288
leg_result SA $ok289
fi290
restore_all || exit 3292
# ---------------------------------------------------------------------------293
echo "SAB-F3A: restoring and re-running the FULL gate as the restore proof"294
if ! "$sigil" build >"$work/build-final.log" 2>&1; then295
echo "SAB-F3A SETUP-FAILED: restored tree does not build"; exit 3296
fi297
if sh test/gate-f3a.sh >"$work/gate-final.log" 2>&1; then298
echo "SAB-F3A: restored gate GREEN"299
else300
echo "SAB-F3A FAILED: the gate is RED on the RESTORED tree -- a plant leaked"; tail -5 "$work/gate-final.log"; exit 1301
fi303
echo "---"304
echo "F3A_SABOTAGE legs=$legs red=$reds setup_failed=$setup_fails"305
if [ "$legs" -eq 10 ] && [ "$reds" -eq 10 ] && [ "$setup_fails" -eq 0 ]; then306
exit 0307
else308
exit 1309
fi