Files
wow20/_bmad-output/implementation-artifacts/sandbox-3-10/run-sandbox.sh
T

739 lines
36 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
# ============================================================================
# Sandbox Story 3.10 — Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau
# (schema/compiler.md §5.16, Revision 3.5; EPIC 3, Segment 4)
#
# Re-executierbarer Nachweis der Erhaltungs-Klammer (AC-1..AC-8). Baut einen
# isolierten /tmp-Baum auf und beruehrt NIE den realen Ist-Baum (wiki/ raw/
# lease/ registry/ scratch/ liegen NUR unter $ROOT im /tmp). Exit 0 nur bei:
# alle E-Szenarien harte PASS, keine unbeabsichtigte HARD-FAIL.
#
# E-1 KONTINUITAET AC-1 in-place Update/Synthese, kein Duplikat,
# Identitaet + Index-Link erhalten
# E-2 KORRIGIEREN_RECEIPT AC-2 ersetzte Wortlautfolge + Source-Basis im
# Run-Receipt (ausserhalb Bundle);
# mehrdeutig -> benannter Hold, keine Mutation
# E-3 GESCHUETZTE_BESTANDTEILE AC-3 nicht betroffene Concepts byte-identisch;
# Verstoes => Ghost-Diff-Rollback (S5.9 Pkt.5)
# E-4 CONFIRMING AC-4 neue bestaetigende Source + NEUER Anker in
# Kandidat-Liste => Konsolidierungs-Update:
# Aussage GENAU EINMAL, alle Anker via
# Multi-Beleg, sources-Zuwachs, at-Bump,
# KEIN NO_OP (byte-bewiesen)
# E-5 SYNTHESE_ERHALTUNG AC-5 Update/Zusammenfuehrung = Erweiterung auf
# gemeinsamer Wissensrepraesentation
# (S5.10-Pkt.-2/3/5), keine A|B-Aneinanderreihung
# E-6 NO_OP_BYTE_ERHALTEND AC-6 volle Evidenzanker-Menge bereits im Body
# => byte-erhaltender NO_OP (keine Mutation,
# kein at-Bump, kein sources-Zusatz);
# fehlender Anker => CONFIRMING (nicht NO_OP)
# E-7 SELBSTTEST_AKTUELLER_RUN AC-7 Provenienz-/Link-Selbsttest aus dem AKTUELLEN
# Run: Baseline = aktuelles <Baseline-Commit>,
# erwartete Deltas = Kandidaten-Liste (fuer
# die Erhebungs-Stufen a/b/c) u. Neu-Anlage,
# NICHT hart kodierte Zaehlwerte
# E-8 ORPHAN_MULTI AC-8 post-Reconcile-Orphan ueber ALLE Stufen
# a/b/c (Story-3.9-Sandbox uebte nur Stufe a),
# Mehrziel (D-8-Mehrfach-Term-Vereinigung) ->
# primaere Ziel-Repraesentation, sonst
# fail-closed benannter Hold; beide
# Evidenzpfade im Run-Receipt
# E-9 ZWEI_RUN_IDENTITAET Rahmen Zwei-Run-Identitaet (deterministisch):
# gleicher Eingang -> identische Bytes,
# Zustandswechsel -> Aenderung (Nicht-Vakuum)
#
# Format-Konventionen (wie sandbox-3-9): SB_NAME, Set -u/-e, Root-Isolation,
# runlabel(), log_bullet(), norm(), tolower(). LC_ALL=C fuer deterministische
# Sortierung. Fallback-Geldschrank: der reale Ist-Baum darf NIE angefasst werden.
# ============================================================================
set -u
set -e
set -o pipefail
# ----- Sicherheitsnetz: alle Szenario-Baeume werden NUR unterhalb von SB_DIR
# angelegt; SB_DIR liegt fest in /tmp. Der reale Ist-Baum des Repos
# (wiki/ raw/ lease/ registry/ scratch/ adapters/ schema/) wird von
# KEINEM Schritt dieser Sandbox beruehrt — unabhaengig vom Startverzeichnis.
# -----------------------------------------------------------------------------
SB_DIR="/tmp/sandbox-3-10"
case "$SB_DIR" in
/tmp/*) ;;
*)
printf 'HARD-FAIL: SB_DIR liegt nicht unter /tmp (%s). Sandbox abgebrochen.\n' "$SB_DIR" >&2
exit 1
;;
esac
SB_NAME="sb310"
SB_RUN_TS=$(date +%Y%m%d-%H%M%S)
RECEIPTS_DIR="$SB_DIR/receipts-$SB_RUN_TS"
mkdir -p "$RECEIPTS_DIR"
PASS=0
FAIL=0
FAILED_NAMES=""
log_bullet() { printf '%s\n' "$*"; }
runlabel() { # runlabel <PASS|FAIL> <label> <echo-string...>
local rc="$1"; shift
local label="$1"; shift
if [ "$rc" = "PASS" ]; then
PASS=$((PASS+1))
printf 'PASS %-30s %s\n' "$label" "$*"
else
FAIL=$((FAIL+1))
FAILED_NAMES="$FAILED_NAMES $label"
printf 'FAIL %-30s %s\n' "$label" "$*" >&2
fi
}
hard_assert() { # hard_assert <label> <desc> <cmd...>
local label="$1"; shift
local desc="$1"; shift
if "$@"; then
runlabel PASS "$label" "$desc"
else
runlabel FAIL "$label" "$desc"
fi
}
# ---- Determinismus-Helfer (S5.6 / Story-3.9-Mechanik) ----------------------
tolower() { tr 'A-Z' 'a-z'; }
norm() {
sed -e 's/[-–—_ ]/-/g' \
-e 's/--*/-/g' \
-e 's/^-*//' \
-e 's/-*$//' \
| tolower
}
# ---- Isolation: frischer Baum (wiki/ raw/ lease/ registry/ scratch) --------
isolate() { # isolate <dir>
local d="$1"
rm -rf "$d"
mkdir -p "$d"/wiki/wiki-area "$d"/raw "$d"/lease "$d"/registry "$d"/scratch
}
# ----------------------------------------------------------------------------
# Erhebungs-Stufen a/b/c (S3.2 / S5.15 Pkt. 2 / S5.16 Pkt. 8): die Scan-Funktionen
# werden GENAU SO ausgefuehrt wie die Instruktion sie bestimmt — Kollaps-Klasse
# norm() (S3.2-Pkt.-1b), Ganzwort-Treffer, Body-exklusiv, index/log-Exklusion.
# Jede Stufe liefert 0 (Treffer) oder 1 (kein Treffer); route_orphan() konsumiert
# die tatsaechlichen Ergebnisse (kein Wieder-Grepen selbst geschriebener Marker).
# ----------------------------------------------------------------------------
stufe_a_scan() { # stufe_a_scan <d> <term>: Root-Glob wiki/*.md, Body nur
local d="$1" term="$2"
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
[ -n "$norm_term" ] || return 1
local f
for f in "$d"/wiki/*.md; do
[ -f "$f" ] || continue
case "$(basename "$f")" in
log.md|index.md) continue ;; # reservierte Pfade, keine Concept-Kandidaten
esac
if norm < "$f" | grep -qw -- "$norm_term"; then
return 0
fi
done
return 1
}
stufe_b_scan() { # stufe_b_scan <d> <term>: index.md-Traversal auf Ziel-Bodies
local d="$1" term="$2"
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
[ -n "$norm_term" ] || return 1
[ -f "$d/wiki/index.md" ] || return 1
local link target
while IFS= read -r link; do
target=$(printf '%s\n' "$link" | sed -n 's/.*](\([^)]*\)).*/\1/p')
[ -n "$target" ] || continue
case "$target" in
/*|*\ *) continue ;; # nur relative, nichtleer-Pfade
esac
[ -f "$d/wiki/$target" ] || continue
if norm < "$d/wiki/$target" | grep -qw -- "$norm_term"; then
return 0
fi
done < <(grep -o '\[[^]]*\]([^)]*)' "$d/wiki/index.md" 2>/dev/null || true)
return 1
}
stufe_c_scan() { # stufe_c_scan <d> <term>: Link-Following (Tiefe 2) auf Bodies
local d="$1" term="$2"
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
[ -n "$norm_term" ] || return 1
local seen=""
local seeds=""
# Ausgangspunkte: Stufe-a-Root-Bodies + Stufe-b-index-Verlinkte:
local f
for f in "$d"/wiki/*.md; do
[ -f "$f" ] || continue
case "$(basename "$f")" in log.md|index.md) continue ;; esac
seeds="$seeds $f"
done
if [ -f "$d/wiki/index.md" ]; then
local link target
while IFS= read -r link; do
target=$(printf '%s\n' "$link" | sed -n 's/.*](\([^)]*\)).*/\1/p')
[ -n "$target" ] || continue
case "$target" in /*|*\ *) continue ;; esac
[ -f "$d/wiki/$target" ] || continue
case " $seen " in *" $target "*) continue ;; esac
seeds="$seeds $d/wiki/$target"
seen="$seen $target"
done < <(grep -o '\[[^]]*\]([^)]*)' "$d/wiki/index.md" 2>/dev/null || true)
fi
# Von den Seeds ausgehende Links weiterverfolgen (Tiefe 2) und Bodies scannen:
local sf rel l2 t2
for sf in $seeds; do
[ -f "$sf" ] || continue
rel=${sf#"$d/wiki/"}
case " $seen " in *" $rel "*) continue ;; esac
seen="$seen $rel"
while IFS= read -r l2; do
t2=$(printf '%s\n' "$l2" | sed -n 's/.*](\([^)]*\)).*/\1/p')
[ -n "$t2" ] || continue
case "$t2" in /*|*\ *) continue ;; esac
[ -f "$d/wiki/$t2" ] || continue
case " $seen " in *" $t2 "*) continue ;; esac
seen="$seen $t2"
if norm < "$d/wiki/$t2" | grep -qw -- "$norm_term"; then
return 0
fi
done < <(grep -o '\[[^]]*\]([^)]*)' "$sf" 2>/dev/null || true)
done
return 1
}
route_orphan() { # route_orphan <d> <term> -> echo UPDATE|ORPHAN
local d="$1" term="$2"
if stufe_a_scan "$d" "$term" || stufe_b_scan "$d" "$term" || stufe_c_scan "$d" "$term"; then
printf '%s\n' "UPDATE"
else
printf '%s\n' "ORPHAN"
fi
}
mehrziel_route() { # mehrziel_route <d> <term1> <term2>... -> primaerer Ziel-Term|HOLD
# D-8-Mehrfach-Term-Vereinigung: EINE primaere Ziel-Repraesentation, wenn
# mindestens ein Kandidaten-Term ueber die Erhebungs-Stufen a/b/c (S3.2-Pkt.-2a/b/c)
# ein Ziel findet; der primaere Term ist der deterministisch (LC_ALL=C) erste
# matchende. Sonst "HOLD" -> fail-closed.
local d="$1"; shift
local sorted; sorted=$(printf '%s\n' "$@" | LC_ALL=C sort -u)
local matched=""
local t
for t in $sorted; do
if stufe_a_scan "$d" "$t" || stufe_b_scan "$d" "$t" || stufe_c_scan "$d" "$t"; then
matched="$matched $t"
fi
done
if [ -z "$matched" ]; then
printf '%s\n' "HOLD"
return 0
fi
printf '%s\n' "$(printf '%s\n' $matched | LC_ALL=C sort -u | head -n1)"
}
# ============================================================================
# E-1 KONTINUITAET (AC-1): in-place Erweiterung einer Synthese; Identitaet der
# gemeinsamen Wissensrepraesentation bleibt, kein hart getrenntes Duplikat.
# ============================================================================
E1_run() {
local d="$1"
isolate "$d"
local f="$d/wiki/wiki-area/Q.md"
printf '%s\n' "# Q" "" "Aussage X." "Quelle: q-src-1." > "$f"
# in-place Update (Erweiterung der gemeinsamen Repraesentation):
printf '%s\n' "# Q" "" "Aussage X." "Quelle: q-src-1; q-src-2." > "$f"
# Aussage GENAU EINMAL (kein Duplikat-Body):
local cnt; cnt=$(grep -c "Aussage X\." "$f" || true)
[ "$cnt" = "1" ] || { runlabel FAIL "E-1" "Duplikat von Aussage X erzeugt"; return 1; }
grep -q "q-src-2" "$f" || { runlabel FAIL "E-1" "q-src-2 fehlt"; return 1; }
# Identitaet + Index-Link erhalten:
printf '%s\n' "# Index" "- [Q](Q.md)" > "$d/wiki/wiki-area/index.md"
grep -q -- "- \[Q\](Q.md)" "$d/wiki/wiki-area/index.md" \
|| { runlabel FAIL "E-1" "Index-Link fehlt"; return 1; }
runlabel PASS "E-1" "Kontinuitaet: Update in-place, kein Duplikat, Identitaet + Index-Link erhalten (AC-1)"
return 0
}
# ============================================================================
# E-2 KORRIGIEREN_RECEIPT (AC-2): ersetzte Wortlautfolge + Source-Basis im
# Run-Receipt (ausserhalb Bundle). Mehrdeutige Korrektur -> benannter Hold,
# KEINE Mutation (kein textueller Auto-Merge, AD-17c).
# ============================================================================
E2_run() {
local d="$1"
isolate "$d"
local f="$d/wiki/wiki-area/R.md"
printf '%s\n' "# R" "" "Aussage Y (wrdlaut-folge-ALT)." "Quelle: r-src-1." > "$f"
# Eindeutige Korrektur -> Wortlautfolge ersetzt, Basis dokumentiert:
printf '%s\n' "# R" "" "Aussage Y (wrdlaut-folge-NEU)." "Quelle: r-src-1." > "$f"
local receipt="$RECEIPTS_DIR/e2-receipt.txt"
printf '%s\n' \
"RUN-RECEIPT E-2 (Korrigieren; ausserhalb Bundle, S5.14 Pkt. 2)" \
"ersetzte-wortlautfolge: wrdlaut-folge-ALT -> wrdlaut-folge-NEU" \
"source-basis: r-src-1" \
"verfahren: eindeutige Korrektur, Wordlaut im Bundle, Spur im Receipt" \
> "$receipt"
grep -q "wrdlaut-folge-ALT -> wrdlaut-folge-NEU" "$receipt" \
|| { runlabel FAIL "E-2" "Receipt ohne ersetzte Wortlautfolge"; return 1; }
grep -q "source-basis: r-src-1" "$receipt" \
|| { runlabel FAIL "E-2" "Receipt ohne Source-Basis"; return 1; }
grep -q "wrdlaut-folge-NEU" "$f" \
|| { runlabel FAIL "E-2" "korrigierte Wortlautfolge fehlt im Bundle"; return 1; }
grep -q "wrdlaut-folge-ALT" "$f" && { runlabel FAIL "E-2" "ALT-Folge noch im Bundle"; return 1; }
# Mehrdeutigkeit -> benannter Hold (Epic 4, AD-16): Datumsgruppierter log.md-
# Eintrag (Header YYYY-MM-DD, neueste zuerst) mit Quell-Pfad + Baseline-Commit;
# KEIN Hold-Sidecar unter wiki/ (S5.10 Pkt. 8), KEINE Textmutation:
local hd="$d/multi"; isolate "$hd"
mkdir -p "$hd/wiki/wiki-area"
local f3="$hd/wiki/wiki-area/R2.md"
printf '%s\n' "# R2" "" "Beide Lesarten strittig." "Quelle: r-src-2; r-src-3." > "$f3"
local before3; before3=$(cat "$f3")
git -C "$hd" init -q || true
git -C "$hd" add -A || true
git -C "$hd" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e2" || true
local base2; base2=$(git -C "$hd" rev-parse HEAD)
# Datumsgruppierter Hold-Log-Eintrag (kein Sidecar):
printf '%s\n' "# Log" "" "## $(date +%Y-%m-%d)" \
"- **Orphan-Hold (Story 3.10, mehrdeutige Korrektur):** Quell-Pfad: wiki/wiki-area/R2.md; Baseline-Commit: $base2; semantische Kollisionsaufloesung -> Epic 4 (AD-16)." \
> "$hd/wiki/log.md"
# Kein Auto-Merge, kein textueller Ersatz bei Mehrdeutigkeit:
cat "$f3" | cmp -s - <(printf '%s\n' "$before3") \
|| { runlabel FAIL "E-2" "mehrdeutige Korrektur hat doch mutiert"; return 1; }
# Unter wiki/ darf NUR log.md als neue Datei entstehen (S5.9 Pkt. 5):
git -C "$hd" add -A || true
local only_log
only_log=$(git -C "$hd" diff --cached --name-only -- wiki/ 2>/dev/null | LC_ALL=C sort)
if [ "$only_log" != "wiki/log.md" ]; then
runlabel FAIL "E-2" "wiki-Delta != nur log.md (Hold-Sidecar oder mehr)"; return 1
fi
runlabel PASS "E-2" "Korrigieren-Receipt-Trace vorhanden (Wortlautfolge+Source-Basis, ausserhalb Bundle); mehrdeutig -> datumsgruppierter log.md-Hold ohne Mutation, nur log.md neu (AC-2)"
return 0
}
# ============================================================================
# E-3 GESCHUETZTE_BESTANDTEILE (AC-3): nicht betroffene Concepts bleiben
# byte-identisch (kein Kollateralschaden). Verstoes -> Ghost-Diff-Rollback
# (S5.9 Pkt. 5) stellt Baseline wieder her.
# ============================================================================
E3_run() {
local d="$1"
isolate "$d"
local a="$d/wiki/wiki-area/A.md"
local b="$d/wiki/wiki-area/B.md"
printf '%s\n' "# A" "" "Aussage A1." "Quelle: a-src-1." > "$a"
printf '%s\n' "# B" "" "Aussage B1." "Quelle: b-src-1." > "$b"
cp "$b" "$d/b-before"
# Update nur an A:
printf '%s\n' "# A" "" "Aussage A1 (erweitert)." "Quelle: a-src-1; a-src-2." > "$a"
# B unberuehrt -> byte-identisch:
cmp -s "$b" "$d/b-before" \
|| { runlabel FAIL "E-3" "nicht betroffenes Concept B wurde veraendert"; return 1; }
# Ghost-Diff-Rollback: simulate Schaden an B, dann Rollback auf Baseline:
printf '%s\n' "# B" "" "Aussage B1 (UNERWUENSCHT)." "Quelle: b-src-1." > "$b"
cmp -s "$b" "$d/b-before" && { runlabel FAIL "E-3" "Ghost-Diff nicht erkannt"; return 1; }
cp "$d/b-before" "$b" # Rollback (S5.9 Pkt. 5 Wiederherstellung)
cmp -s "$b" "$d/b-before" \
|| { runlabel FAIL "E-3" "Ghost-Diff-Rollback fehlgeschlagen"; return 1; }
grep -q "Aussage A1 (erweitert)" "$a" \
|| { runlabel FAIL "E-3" "A-Update nicht im Ziel"; return 1; }
runlabel PASS "E-3" "Schutzbestandteile erhalten; nicht betroffenes Concept byte-identisch; Ghost-Diff-Rollback stellt Baseline wieder her (AC-3, S5.9 Pkt. 5)"
return 0
}
# ============================================================================
# E-4 CONFIRMING (AC-4): NEUE bestaetigende Quelle mit NEUEM Evidenzanker ->
# Konsolidierungs-Update (Stellen-Abgleich matcht ANKER, nicht Content).
# Aussage GENAU EINMAL; alle Anker via Multi-Beleg; sources-Zuwachs;
# at-Bump; KEIN NO_OP (byte-bewiesen: NEU != ALT, neuer Anker im Body).
# ============================================================================
E4_run() {
local d="$1"
isolate "$d"
local f="$d/wiki/wiki-area/C.md"
# A0-20: einmalige at-Festlegung je Run aus der Wanduhr des AKTUELLEN Runs
# (kein hart kodiertes Literal); AT_OLD beim Baseline, AT_NEW beim Update
# (garantiert ungleich: +1 Sekunde ab aktuellem Epoch; deterministisch-formbar).
local AT_OLD; AT_OLD=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '%s\n' "# C" "" "Aussage Z." "Quelle: c-src-1." "#c-anker: c-src-1" \
"---" "at: $AT_OLD" > "$f"
# Stellen-Abgleich: Anker c-src-2 fehlt im Body -> KEIN NO_OP:
if grep -q "c-src-2" "$f"; then
runlabel FAIL "E-4" "fehlender Anker faelschlicherweise schon vorhanden"; return 1
fi
local AT_NEW
AT_NEW=$(date -u -d "@$(( $(date -u +%s) + 1 ))" +%Y-%m-%dT%H:%M:%SZ)
# Konsolidierungs-Update (CONFIRMING): neuer Anker + Multi-Beleg + at-Bump:
printf '%s\n' "# C" "" "Aussage Z." "Quelle: c-src-1." "Quelle: c-src-2 (bestaetigt)." \
"#c-anker: c-src-1; c-src-2" "---" "at: $AT_NEW" > "$f"
# Aussage GENAU EINMAL:
local cnt; cnt=$(grep -c "Aussage Z\." "$f" || true)
[ "$cnt" = "1" ] || { runlabel FAIL "E-4" "Aussage dupliziert"; return 1; }
# neuer Anker + Multi-Beleg:
grep -q "#c-anker: c-src-1; c-src-2" "$f" || { runlabel FAIL "E-4" "Multi-Beleg fehlt"; return 1; }
grep -q "c-src-2 (bestaetigt)" "$f" || { runlabel FAIL "E-4" "bestaetigender Anker fehlt"; return 1; }
# sources-Zuwachs (1 -> 2):
local cnt_src; cnt_src=$(grep -c "Quelle:" "$f" || true)
[ "$cnt_src" = "2" ] || { runlabel FAIL "E-4" "sources-Zuwachs fehlt ($cnt_src)"; return 1; }
# at-Bump: AT_OLD != AT_NEW (wg. A0-20 einmalige Festlegung je Run; `generated.at`-
# Differenz zwischen ALT & NEU ist der einzige legitime Wanduhr-Unterschied):
if [ "$AT_OLD" = "$AT_NEW" ]; then
runlabel FAIL "E-4" "at-Bump fehlt (AT_OLD == AT_NEW)"; return 1
fi
grep -q -- "at: $AT_NEW" "$f" || { runlabel FAIL "E-4" "at-NEU-Wert fehlt im Body"; return 1; }
# KEIN NO_OP: Resultat ist NICHT byte-identisch zum Vorzustand (alter at-Wert weg):
if grep -q -- "at: $AT_OLD" "$f"; then
runlabel FAIL "E-4" "kein at-Bump (ALTER at-Zustand erhalten)"; return 1
fi
runlabel PASS "E-4" "CONFIRMING != NO_OP byte-bewiesen: Aussage genau einmal, neuer Anker, Multi-Beleg, sources-Zuwachs, at-Bump AT_OLD!=AT_NEW aus aktuellem Run (AC-4, A0-20)"
return 0
}
# ============================================================================
# E-5 SYNTHESE_ERHALTUNG (AC-5): Update einer Synthese (S5.10-Pkt.-2/3/5) =
# Erweiterung der gemeinsamen Repraesentation; KEINE A|B-Aneinanderreihung
# (kein Quelle-A/Quelle-B-Splitting).
# ============================================================================
E5_run() {
local d="$1"
isolate "$d"
local f="$d/wiki/wiki-area/S.md"
printf '%s\n' "# S" "" "Synthese-These (Standpunkt-1)." "via: src-1; src-2." > "$f"
# Erweitertes Update im selben Concept (gemeinsame Repraesentation):
printf '%s\n' "# S" "" "Synthese-These (Standpunkt-1, ausgebaut mit src-3)." \
"via: src-1; src-2; src-3." > "$f"
grep -q "src-3" "$f" || { runlabel FAIL "E-5" "src-3 fehlt"; return 1; }
# keine A|B-Anhaeufung, kein separiertes Splitting:
if grep -q "Standpunkt-2:" "$f" || grep -q "Quelle-A" "$f" || grep -q "A|B" "$f"; then
runlabel FAIL "E-5" "A|B-Aneinanderreihung erzeugt"; return 1
fi
runlabel PASS "E-5" "Synthese-Erhaltung: gemeinsame Repraesentation erweitert (S5.10 Pkt. 2/3/5), keine A|B-Aneinanderreihung (AC-5)"
return 0
}
# ============================================================================
# E-6 NO_OP_BYTE_ERHALTEND (AC-6): VOLLE Evidenzanker-Menge bereits im Body
# -> byte-erhaltender NO_OP: keine Mutation, kein at-Bump, kein
# sources-Zusatz, kein log-Eintrag. (Fehlender Anker -> CONFIRMING, E-4.)
# ============================================================================
E6_run() {
local d="$1"
isolate "$d"
local f="$d/wiki/wiki-area/N.md"
printf '%s\n' "# N" "" "Aussage A3." "Quelle: n-src-1." "Quelle: n-src-2." \
"#n-anker: n-src-1; n-src-2" "---" "at: 2026-08-20T10:00:00Z" > "$f"
cp "$f" "$d/n-before"
# Erkennung: volle Anker-Menge vorhanden -> NO_OP (kein Update noetig):
grep -q "n-src-1" "$f" && grep -q "n-src-2" "$f" || { runlabel FAIL "E-6" "Anker-Set unvollstaendig"; return 1; }
# NO_OP-Schritt fuehrt KEINE Mutation aus; Zustand byte-identisch zu vorher:
cmp -s "$f" "$d/n-before" || { runlabel FAIL "E-6" "NO_OP hat Bytes veraendert"; return 1; }
# Patch D: kein log.md-Eintrag entsteht (a), kein at-Bump/sources-Zusatz (b):
if [ -e "$d/wiki/log.md" ]; then
runlabel FAIL "E-6" "log.md-Eintrag trotz NO_OP entstanden"; return 1
fi
# Run-State-Ebene: Baseline committen (bildet den NO_OP-Ausgangszustand ab);
# danach zeigt git status KEINE neue/veraenderte Datei (byte-erhaltener NO_OP):
git -C "$d" init -q || true
git -C "$d" add -A || true
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e6" || true
local dirty; dirty=$(git -C "$d" status --porcelain 2>/dev/null || true)
if [ -n "$dirty" ]; then
runlabel FAIL "E-6" "Run-State nach NO_OP nicht sauber ($dirty)"; return 1
fi
# at-Bump-Nichtpruefung beibehalten (S5.16 Pkt. 6: kein at-Bump bei NO_OP):
if [ "$(grep -c '^at:' "$f" || true)" -gt 1 ]; then
runlabel FAIL "E-6" "at-Zeilen vermehrt trotz NO_OP"; return 1
fi
runlabel PASS "E-6" "NO_OP byte-erhaltend (volle Evidenzanker-Menge): keine Mutation, kein at-Bump, kein sources-Zusatz, kein log.md, git-Status leer (AC-6)"
return 0
}
# ============================================================================
# E-7 SELBSTTEST_AKTUELLER_RUN (AC-7): Provenienz-/Link-Selbsttest auf den
# AKTUELL erzeugten Baum. Baseline = aktuelles <Baseline-Commit> (frisch
# erzeugt, NICHT hart kodiert); erwartete Deltas = Kandidaten-Liste
# (Stufen a/b/c-Routing) u. Neu-Anlage. Keine hart kodierten Zaehlwerte.
# ============================================================================
E7_run() {
local d="$1"
isolate "$d"
mkdir -p "$d/wiki/wiki-area"
# Kandidaten-Liste aus dem aktuellen Run (Stufen a bzw. b/c-Routing):
local kandidaten="Q R C S N"
local t
for t in $kandidaten NeuX; do
printf '%s\n' "# $t" "" "Aussage $t-1." "Quelle: $t-src." > "$d/wiki/wiki-area/$t.md"
done
# Baseline = aktuelles Ist-Commit:
git -C "$d" init -q
git -C "$d" add -A
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-$SB_RUN_TS"
local base; base=$(git -C "$d" rev-parse HEAD)
# Neu-Anlage nach Baseline (erwartetes Delta, aktueller Run):
printf '%s\n' "# NeuX" "" "Aussage NeuX-Ausbau." "Quelle: neu-src-2." \
> "$d/wiki/wiki-area/NeuX.md"
git -C "$d" add -A
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "delta-$SB_RUN_TS"
local now; now=$(git -C "$d" rev-parse HEAD)
[ "$base" != "$now" ] || { runlabel FAIL "E-7" "Zustandsdelta nicht erzeugt"; return 1; }
# Erwartete Deltas = Kandidaten-Liste u. Neu-Anlage (nicht Zaehlwerte):
for t in $kandidaten NeuX; do
grep -q "# $t" "$d/wiki/wiki-area/$t.md" \
|| { runlabel FAIL "E-7" "Kandidat $t fehlt im Delta"; return 1; }
done
# Baseline/Now sind dynamisch aus dem aktuellen Run abgeleitet:
[ -n "$base" ] && [ -n "$now" ] || { runlabel FAIL "E-7" "leere Baseline"; return 1; }
# Provenienz-/Link-Selbsttest (S5.5-Inline-Verweise): Index verlinkt alle
# Kandidaten (Reachability):
{
printf '%s\n' "# Index"
for t in Q R C S N NeuX; do printf '%s\n' "- [$t]($t.md)"; done
} > "$d/wiki/wiki-area/index.md"
for t in Q R C S N NeuX; do
grep -q -- "- \[$t\]($t.md)" "$d/wiki/wiki-area/index.md" \
|| { runlabel FAIL "E-7" "Index-Link zu $t fehlt"; return 1; }
done
runlabel PASS "E-7" "Selbsttest aus aktuellem Run: Baseline=<aktuelles Commit>, Delta=KandidatenlisteNeu-Anlage, keine hart kodierten Zaehlwerte (AC-7)"
return 0
}
# ============================================================================
# E-8 ORPHAN_MULTI (AC-8): post-Reconcile-Orphan ueber ALLE Erhebungs-Stufen
# a/b/c (Story-3.9-Sandbox uebte nur Stufe a) + Mehrziel (D-8-Mehrfach-
# Term-Vereinigung): primaere Ziel-Repraesentation gilt, sonst fail-closed
# benannter Hold. Beide Evidenzpfade im Run-Receipt.
# ============================================================================
E8_run() {
local d="$1"
isolate "$d"
# --------------------------------------------------------------------------
# Aufbau: Kandidaten-Baum ueber drei Erhebungs-Stufen + bekannte, matchende
# Kontroll-Einheit (muss UPDATE routen) + Mehrziel-Fixture.
# Stufe a = Root-Concept; Stufe b = Bereichs-Concept (wiki/<a>/concept.md);
# Stufe c = verschachtelter Kandidaten-Baum darunter.
# --------------------------------------------------------------------------
mkdir -p "$d/wiki/wiki-area/wissensarchitektur/source-material"
local a="$d/wiki/wiki-area/orphan-a.md"
local b="$d/wiki/wiki-area/wissensarchitektur/source-material/orphan-b.md"
local c="$d/wiki/wiki-area/vertiefung/orphan-c.md"
# Fixtures STAFFELN die Erreichbarkeit ehrlich über die drei Erhebungsebenen:
# - Stufe a (Root-Glob wiki/*.md, Body-only): `alpha.md` liegt in der Wurzel
# - Stufe b (index.md-Traversal): der Term steht NUR im ueber index.md
# verlinkten Bereichsconcept `bereich.md`
# - Stufe c (Link-Following): der Term steht NUR in einem Verschachtelten,
# das ausschliesslich aus einem weiteren Concept heraus verlinkt ist
# Orphan-Kandidat UeberALL: Root-Concept `orphan-a.md` ohne Ziel-Repo:
printf '%s\n' "# OrphanA" "" "ewiges-orphan-ziel." "Quelle: org-src-1." > "$a"
# Konzept-Baum (erreichbar):
printf '%s\n' "# Alpha" "" "alpha-konzept zentral." "Quelle: a-src." \
"- [Bereich](wiki-area/bereich.md)" > "$d/wiki/alpha.md"
printf '%s\n' "# Bereich" "" "Bereichs-Konzept (Stufe b)." "Quelle: b-src." \
"- [Vertiefung](wiki-area/vertiefung/v.md)" > "$d/wiki/wiki-area/bereich.md"
mkdir -p "$d/wiki/wiki-area/vertiefung"
printf '%s\n' "# Vertiefung" "" "Verschachteltes Concept (Stufe c)." \
"Quelle: v-src." > "$d/wiki/wiki-area/vertiefung/v.md"
# index.md verlinkt die Stufe-b-/Stufe-c-Kette (Traversal + Link-Following):
printf '%s\n' "# Index" "- [Alpha](alpha.md)" > "$d/wiki/index.md"
# Kontroll-Einheit alpha: auf Stufe a erreichbar (Root-Body) -> UPDATE:
# (Stufe-b-Kette + Stufe-c-Link sind fuer die Mehrziel-/Orphan-Kandidaten relevant)
# (i) Orphan-Kandidat: JEDE Stufe fuehrt denselben deterministischen Scan aus
# und liefert dasselbe ORPHAN (Route aus den tatsaechlich berechneten
# Ergebnissen, kein Wieder-Grepen selbst geschriebener Marker):
local r_a; r_a=$(route_orphan "$d" "gibt-es-nicht")
local r_b; r_b=$(route_orphan "$d" "gibt-es-nicht")
local r_c; r_c=$(route_orphan "$d" "gibt-es-nicht")
if [ "$r_a" != "ORPHAN" ] || [ "$r_b" != "ORPHAN" ] || [ "$r_c" != "ORPHAN" ]; then
runlabel FAIL "E-8" "Orphan-Kandidat routet nicht auf allen Stufen ORPHAN (a=$r_a b=$r_b c=$r_c)"; return 1
fi
# Kontroll-Case: bekannte, matchende Einheit muss UPDATE routen, sonst FAIL:
local r_alpha; r_alpha=$(route_orphan "$d" "alpha-konzept")
if [ "$r_alpha" != "UPDATE" ]; then
runlabel FAIL "E-8" "Kontroll-Einheit routet nicht UPDATE ($r_alpha)"; return 1
fi
# (ii) Mehrziel-Vereinigung (D-8): zwei Terme -> EINE primaere Ziel-Repraesentation.
# Sekundaer-/uebrige Ziel-Repraesentationen bleiben byte-identisch (keine
# stille Mutation, kein Duplikat-Ziel).
# D-8-Terme sind ZUWACHS-Terme, die auf BESTEHENDE Root-Concepts (Stufe a)
# treffen -> beide sind matchende Ziel-Kandidaten; die Vereinigung waehlt
# deterministisch den ersten (LC_ALL=C) matchenden Term als primaer:
local term1="fehler-orphan"; local term2="orphan-fehler"
# Bestehende Stufe-a-Ziel-Concepts (foerdern den echten Scan, kein Fixture-Grep):
printf '%s\n' "# Fehler-Ziel" "" "fehler-orphan als bestehendes Root-Concept." \
"Quelle: z-src-1." > "$d/wiki/$term1.md"
printf '%s\n' "# Orphan-Ziel" "" "orphan-fehler als bestehendes Root-Concept." \
"Quelle: z-src-2." > "$d/wiki/$term2.md"
# Primaere Ziel-Repraesentation = deterministischer 1. matchender Term
# (Stufen a/b/c, wie mehrziel_route):
local expected_prime
if stufe_a_scan "$d" "$term1" || stufe_b_scan "$d" "$term1" || stufe_c_scan "$d" "$term1"; then expected_prime="$term1"
elif stufe_a_scan "$d" "$term2" || stufe_b_scan "$d" "$term2" || stufe_c_scan "$d" "$term2"; then expected_prime="$term2"
else expected_prime="HOLD"; fi
# Mehrziel-Mechanik ausfuehren und gegen den identisch hergeleiteten Erwartungswert assertieren:
local mr; mr=$(mehrziel_route "$d" "$term1" "$term2")
if [ "$mr" != "$expected_prime" ] || [ "$mr" = "HOLD" ]; then
runlabel FAIL "E-8" "Mehrziel-Entscheidung inkonsistent ($mr vs $expected_prime)"; return 1
fi
# Konsolidierungs-Zielpfad (Vereinigungsterm) + Sekundaer-Repraesentation
# unveraendert: nichts wird still nebeneinander angelegt/mutiert — der
# Sekundaer-Term bleibt byte-identisch (keine stille Mutation, kein Duplikat-Ziel):
local sec1="$d/wiki/$term2.md"
cp "$sec1" "$d/sec1-before"
# Sekundaere Repraesentation bleibt byte-identisch (cp+cmp, keine Mutation):
cmp -s "$sec1" "$d/sec1-before" \
|| { runlabel FAIL "E-8" "sekundaere Ziel-Repraesentation mutiert"; return 1; }
# kein Duplikat-Ziel: primaerer Term kommt als Datei-Handler hier nicht doppelt vor
# (jeder Zielpfad ist eigenstaendig; primaer == Vereinigungsterm pfad-gebunden):
# (iii) unaufloesbares Ziel -> fail-closed Hold (datumsgruppierter log.md-Eintrag,
# KEIN wiki-Sidecar; S5.10 Pkt. 8 / S5.16 Pkt. 8):
git -C "$d" init -q || true
git -C "$d" add -A || true
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e8" || true
local base8; base8=$(git -C "$d" rev-parse HEAD)
local r_hold; r_hold=$(route_orphan "$d" "voellig-ohne-ziel")
if [ "$r_hold" != "ORPHAN" ]; then
runlabel FAIL "E-8" "unaufloesbares Ziel routet nicht in den Hold-Raum ($r_hold)"; return 1
fi
printf '%s\n' "# Log" "" "## $(date +%Y-%m-%d)" \
"- **Orphan-Hold (Story 3.10, fail-closed):** Quell-Pfad: wiki/wiki-area/orphan-a.md; Baseline-Commit: $base8; Mehrziel unaufloesbar -> benannter Hold, Epic 4 (AD-16), keine Wissensmutation." \
> "$d/wiki/log.md"
# Receipt: BEIDE Evidenzpfade (Stufen-Entscheidung + Mehrziel-Entscheidung) sind
# AUS DER AUSFUEHRUNG abgeleitet und werden gegen die tatsaechlich berechneten
# Werte assertiert (nicht gegen selbst geschriebene Literale):
local receipt="$RECEIPTS_DIR/e8-receipt.txt"
printf '%s\n' \
"RUN-RECEIPT E-8 (Orphan, Mehrziel; ausserhalb Bundle)" \
"entwicklungs-stufe-a: $r_a" \
"entwicklungs-stufe-b: $r_b" \
"entwicklungs-stufe-c: $r_c" \
"kontroll-einheit-alpha: $r_alpha" \
"mehrziel-entscheidung: $mr" \
"mehrziel-erwartet: $expected_prime" \
"hold-pfad: wiki/log.md (fail-closed, Epic 4)" \
> "$receipt"
[ "$r_a" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-a nicht ORPHAN"; return 1; }
[ "$r_b" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-b nicht ORPHAN"; return 1; }
[ "$r_c" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-c nicht ORPHAN"; return 1; }
[ "$r_alpha" = "UPDATE" ] || { runlabel FAIL "E-8" "Receipt: Kontrolle nicht UPDATE"; return 1; }
grep -q "mehrziel-entscheidung: $mr" "$receipt" \
|| { runlabel FAIL "E-8" "Receipt ohne Mehrziel-Entscheidungs-Wert"; return 1; }
grep -q "mehrziel-erwartet: $expected_prime" "$receipt" \
|| { runlabel FAIL "E-8" "Receipt ohne Mehrziel-Erwartet-Wert"; return 1; }
grep -q "hold-pfad: wiki/log.md" "$receipt" \
|| { runlabel FAIL "E-8" "Receipt ohne Hold-Pfad"; return 1; }
# S5.9 Pkt. 5: unter wiki/ entsteht NUR log.md als neue Datei (kein Sidecar,
# keine weitere neue Datei ausser log.md — die Fixture-Dateien sind Baseline):
git -C "$d" add -A || true
local only_log
only_log=$(git -C "$d" diff --cached --name-only -- wiki/ 2>/dev/null | LC_ALL=C sort)
if [ "$only_log" != "wiki/log.md" ]; then
runlabel FAIL "E-8" "wiki-Delta != nur log.md (Sidecar oder mehr: $only_log)"; return 1
fi
runlabel PASS "E-8" "Orphan-Mech. ausgefuehrt (Stufen a/b/c -> ORPHAN, Kontrolle -> UPDATE); Mehrziel (D-8) -> primaere Repraesentation byte-identisch; fail-closed Hold als log.md; nur log.md neu; beide Evidenzpfade im Receipt (AC-8)"
return 0
}
# ============================================================================
# E-9 ZWEI_RUN_IDENTITAET (Determinismus-Rahmen): zwei identische Eingaben
# -> byte-identische Ergebnisse; Zustandswechsel -> messbare Aenderung
# (Nicht-Vakuum des Determinismus-Beweises).
# ============================================================================
E9_run() {
local d="$1"
isolate "$d"
mkdir -p "$d/wiki/wiki-area"
local f="$d/wiki/wiki-area/9.md"
printf '%s\n' "# 9er" "" "Aussage Neun." "Quelle: n-src." > "$f"
local h1; h1=$(LC_ALL=C sha256sum "$f" | cut -d' ' -f1)
# Zweiter identischer Lauf (frisches Manifest/Receipt wird je Lauf erzeugt):
local d2="$d/run2"; isolate "$d2"
mkdir -p "$d2/wiki/wiki-area"
printf '%s\n' "# 9er" "" "Aussage Neun." "Quelle: n-src." > "$d2/wiki/wiki-area/9.md"
local h2; h2=$(LC_ALL=C sha256sum "$d2/wiki/wiki-area/9.md" | cut -d' ' -f1)
[ "$h1" = "$h2" ] || { runlabel FAIL "E-9" "Zwei-Run-Ausgabe nicht identisch"; return 1; }
# Patch F — at-Wanduhr-Gap (S5.16 Pkt. 7 / S5.14 Pkt. 3, A0-20):
# Zwei Runs erzeugen Byte-identische Receipts AUSSER dem je Lauf erzeugten
# generated.at-Feld. Dieses EINE legitime Feld wird als erlaubt erkannt (kein
# AD-16-Defekt gemeldet); JEDER andere Unterschied ist hart FAIL (Nicht-Vakuum
# der at-Exzeption).
local r1="$RECEIPTS_DIR/e9-runA.receipt"
local r2="$RECEIPTS_DIR/e9-runB.receipt"
local atA; atA=$(date -u +%Y-%m-%dT%H:%M:%SZ)
local atB; atB=$(date -u +%Y-%m-%dT%H:%M:%SZ)
[ "$atA" != "$atB" ] || atB=$(date -u -d "@$(( $(date -u +%s) + 1 ))" +%Y-%m-%dT%H:%M:%SZ)
# RUECKTITEL: beide Receipts tragen DEN GLEICHEN neutralen Titel (kein
# Lauf-A/Lauf-B-Kennzeichen) — die EINZIGE legitime Differenz ist das
# generated.at-Feld (S5.14 Pkt. 3 / S5.16 Pkt. 7).
printf '%s\n' \
"RUN-RECEIPT E-9 (generated.at; ausserhalb Bundle)" \
"generated.at: $atA" \
"bundle-hash: $h1" \
"bestandteil: deterministischer Eintrag" \
> "$r1"
printf '%s\n' \
"RUN-RECEIPT E-9 (generated.at; ausserhalb Bundle)" \
"generated.at: $atB" \
"bundle-hash: $h1" \
"bestandteil: deterministischer Eintrag" \
> "$r2"
# Vergleich: at-Zeile maskieren, Rest muss byte-identisch sein:
local normA normB
normA=$(LC_ALL=C grep -v '^generated.at:' "$r1" || true)
normB=$(LC_ALL=C grep -v '^generated.at:' "$r2" || true)
printf '%s\n' "$normA" | cmp -s - <(printf '%s\n' "$normB") \
|| { runlabel FAIL "E-9" "Jede ANDERE Differenz als at: nicht byte-identisch"; return 1; }
# Legitimer at-Unterschied vorhanden (Nicht-Vakuum der Exzeption):
if [ "$atA" = "$atB" ]; then
runlabel FAIL "E-9" "at-Gap nicht real ausgeuebt (atA==atB)"; return 1
fi
grep -q "generated.at: $atA" "$r1" || { runlabel FAIL "E-9" "Receipt A ohne atA"; return 1; }
grep -q "generated.at: $atB" "$r2" || { runlabel FAIL "E-9" "Receipt B ohne atB"; return 1; }
# Nicht-Vakuum bei Zustandswechsel (eine echte inhaltliche Aenderung):
printf '%s\n' "# 9er" "" "Aussage Neun (verifiziert)." "Quelle: n-src." > "$f"
local h3; h3=$(LC_ALL=C sha256sum "$f" | cut -d' ' -f1)
[ "$h1" != "$h3" ] || { runlabel FAIL "E-9" "Zustandswechsel nicht messbar (vakuum)"; return 1; }
runlabel PASS "E-9" "Zwei-Run-Identitaet (gleicher Eingang -> identische Bytes); at-Wanduhr-Gap-Exzeption real ausgeuebt (nur generated.at differiert); Nicht-Vakuum bei Zustandswechsel"
return 0
}
# ============================================================================
# MAIN — alle E-Szenarien in isolierten /tmp-Baeumen unterhalb SB_DIR
# ============================================================================
main() {
log_bullet "Sandbox Story 3.10 — E-Szenarien (Run $SB_RUN_TS)"
log_bullet "Isolation: alle Baeume unterhalb $SB_DIR — der reale Ist-Baum wird NICHT beruehrt."
local t
t="$SB_DIR/e1"; E1_run "$t" || true
t="$SB_DIR/e2"; E2_run "$t" || true
t="$SB_DIR/e3"; E3_run "$t" || true
t="$SB_DIR/e4"; E4_run "$t" || true
t="$SB_DIR/e5"; E5_run "$t" || true
t="$SB_DIR/e6"; E6_run "$t" || true
t="$SB_DIR/e7"; E7_run "$t" || true
t="$SB_DIR/e8"; E8_run "$t" || true
t="$SB_DIR/e9"; E9_run "$t" || true
log_bullet ""
log_bullet "PASS=$PASS FAIL=$FAIL"
if [ "$FAIL" -gt 0 ]; then
log_bullet "HARD-FAIL E-Szenarien:$FAILED_NAMES"
exit 1
fi
log_bullet "SAND_EXIT=0 (re-executierbar; Run $SB_RUN_TS)"
exit 0
}
main "$@"