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

855 lines
42 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.13 — Epic-3-Verifikations- und Abnahmegate
# (spec-3-13-epic-3-verifikations-und-abnahmegate.md, 2026-08-22)
# Re-executierbar: bash run-sandbox.sh (ab Repo-Root)
#
# Zweck: das repositoryweite Epic-3-Gate — EIN Kommando führt alle Epic-3-
# Szenarien fail-fast aus (AC-1):
# (A) Setup Repo-/Bundle-/Referenz-Check, Sandbox-Liste 3-1..3-12,
# Porcelain-Pre-Check (vollständig, inkl. Untracked)
# (B) Käfig-Bau /tmp-Kopie des realen Clone-Baums (schema/adapters/
# raw/wiki), git init + Baseline
# (C) 12 Sandbox-Sub-Runs bash …/sandbox-3-N/run-sandbox.sh, hart, fail-fast
# (D) Validator-Agent frischer Sub-Agent liest schema/validator.md und
# erzeugt SUCCESS/FAIL-Verdikte über alle wiki/Dateien;
# das Gate prüft nur Grammatik/Exhaustivität/Exaktheit
# (§5/§5.1), urteilt nie selbst (D-3)
# (D-NEG) Negativ-Fixtures (AC-3-Hälfte): separater /tmp-Käfig mit 3 §7-/§6-
# Negativ-Fixtures + 1 Positiv-Kontrolle; frischer
# Validator-Agent MUSS 3×FAIL + 1×SUCCESS liefern
# (E) Zwei-frische-Agenten zwei /tmp-Worktrees auf denselben $BASE-Commit
# (isolierte Mini-Bundle-Fixture: UPDATE alpha + CREATE
# beta), beide Läufe führen schema/compiler.md aus;
# A/B-anglichen: Frontmatter/Index/Log/Byte-Kern nur
# at-Ausnahme, Run-Receipts A==B (außer at/Tree-Cellen)
# (F) G-1..G-8 harte Assertions (inkl. G-6 echter perturbed-Drittrun-Tree
# C durch denselben E.4-Classifier + Positiv-/Negativ-
# Kontrolle, G-7 Epic-5-Smoke auf Ist-Baum, G-8 Porcelain)
# (G) Nachweise/Porcelain E.9-Nachweis + finaler Porcelain-Check
#
# Exit: 0 = ALLE harten Checks grün (Szenarien + Gate + Nachweise);
# ≠0 = irgendein harter Check rot (fail-fast, FAILED>0 → exit 1).
#
# Review-Loop-2-Härtung (Story 3.13, D-3.13-1..6 + P-2..P-15):
# - D-1: AGENT_PROMPT = nur compiler.md-Anweisung + Fixture-Fakten;
# KEINE erwarteten Plan-/Entscheidungs-/Receipt-/Log-Literale.
# - D-2: E.6 = Run-Receipt-A-vs-B-Abgleich (ohne erwartete Literale;
# Modell: sandbox-3-8 DET-2, at/tree-Cellen gefiltert).
# - D-3: G-6 = echter perturbed committed Tree (wt-c) durch denselben
# E.4-Classifier: MUSS NON_AT≠0 flaggen; at-only-Kontrolle wt-c2
# MUSS NON_AT=0 liefern (deterministisch, kein dritter Agent).
# - D-4: G-7 = generischer Link-Auflösungs-Check + geschlossene, am realen
# Bundle validierte Planungstoken-Menge (P-13).
# - D-5: D-NEG = 3 §7-/§6-Negativ-Fixtures + frischer Validator-Agent,
# der zwingend 3×FAIL + 1×SUCCESS liefert (AC-3-Hälfte).
# - D-6: E.1-Fixture mit CREATE-Fall (beta: neue raw-Quellen → neues
# Concept + Index-Link); AC-6 sechs Demonstrationsfälle.
# - P-2..P-14: siehe spec §5.18 (Patches).
# ============================================================================
set -u
PASS_COUNT=0
FAILED=0
pass() { PASS_COUNT=$((PASS_COUNT+1)); echo "PASS: $1"; }
fail() { FAILED=$((FAILED+1)); echo "FAIL: $1" >&2; }
hardfail() { echo "HARD-FAIL: $1" >&2; exit 1; }
runlabel() { echo; echo "########## $1 ##########"; }
# expect_eq: $1=Beschreibung, $2=erwartet, $3=ist (jeweils mehrzeilig)
expect_eq() {
if [ "$2" = "$3" ]; then pass "$1"; else
fail "$1 — erwartet: [$2], ist: [$3]"
fi
}
# expect_gte: $1=Beschreibung, $2=Schwelle, $3=Zahl
expect_gte() {
case "$3" in (''|*[!0-9]*) fail "$1 — nicht numerisch: [$3]"; return;; esac
if [ "$3" -ge "$2" ]; then pass "$1 ($3$2)"; else
fail "$1$3 < $2"
fi
}
# P-14: /tmp-Aufräum-Trap (kein Orphan-Käfig; kein rm -rf außerhalb $ROOT)
ROOT=$(mktemp -d /tmp/sb313-XXXXXX)
trap 'rm -rf "$ROOT"' EXIT
CAGE="$ROOT/cage"
mkdir -p "$CAGE"
runlabel "A: Setup (Repo-/Bundle-/Referenz-Check, Sandbox-Liste, Porcelain)"
# A.1: Repo-Root auflösen (dieses Skript liegt unter _bmad-output/…)
SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_PATH/../../.." && pwd)"
cd "$REPO_ROOT" || hardfail "Repo-Root nicht erreichbar: $REPO_ROOT"
echo "REPO_ROOT: $REPO_ROOT"
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || hardfail "Repo-Root ist kein Git-Repo"
BASE_REAL=$(git rev-parse HEAD) || hardfail "Repo-Root HEAD nicht auflösbar"
echo "REPO-HEAD (real): $BASE_REAL"
# P-3: Agent-Binary aufloesbar sein MUSS (PATH-Pruefung, kein verhaerteter Pfad)
CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude 2>/dev/null || true)}"
[ -n "$CLAUDE_BIN" ] && [ -x "$CLAUDE_BIN" ] || hardfail "CLAUDE_BIN nicht auflösbar (PATH: 'claude' nicht gefunden; CLAUDE_BIN-Env setzen)"
echo "CLAUDE_BIN: $CLAUDE_BIN"
command -v timeout >/dev/null || hardfail "timeout fehlt (git-for-win/coreutils erwartet)"
command -v sha256sum >/dev/null || hardfail "sha256sum fehlt (git-for-win/coreutils erwartet)"
command -v tar >/dev/null || hardfail "tar fehlt"
command -v cygpath >/dev/null || hardfail "cygpath fehlt (Git-Bash-Umgebung erwartet)"
command -v bash >/dev/null || hardfail "bash fehlt"
# A.2: Referenz-Schemata vorhanden (P-12: Vorhandensein-Hartcheck)
for ref in schema/compiler.md schema/validator.md schema/wiki-compiler.md schema/canonical-terms.md adapters/README.md; do
[ -f "$ref" ] || hardfail "Referenz-Schema fehlt: $ref"
done
pass "A.2 Referenz-Schemata vorhanden (compiler/validator/adapters)"
# A.3: Bundle-Vorbedingungen im Ist-Repo — nur vertraglich definierte Prämissen:
# V-1 Bundleroot (Vertrag §2/Validator §3.2), log.md (Vertrag §5, reserviert),
# raw/ = Evidenzverzeichnis. KEINE fixe Area-Menge im Vertrag (V-5/V-6 existieren
# nicht) — die Ist-Areas (z. B. wissensarchitektur/) trägt Punkt 11 (Area mit
# Inhalt MUSS index.md haben), geprüft vom Validator (Abschnitt D) und G-7 (F).
[ -f wiki/index.md ] || hardfail "V-1: wiki/index.md fehlt (Bundleroot, Vertrag §2)"
[ -d raw ] || hardfail "raw/ fehlt (Evidenzverzeichnis)"
[ -f raw/README.md ] || hardfail "raw/README.md fehlt"
[ -f wiki/log.md ] || hardfail "wiki/log.md fehlt (Vertrag §5, reservierter Name)"
pass "A.3 Bundle-Vorbedingungen erfüllt (Bundleroot/raw/-Evidenz/log.md; Vertrag §2/§5)"
# A.4: Sandbox-Suite 3.1..3.12 vollständig
SUITE_OK=1
for N in 1 2 3 4 5 6 7 8 9 10 11 12; do
sdir="_bmad-output/implementation-artifacts/sandbox-3-$N"
[ -f "$sdir/run-sandbox.sh" ] || { fail "Sandbox-3-$N fehlt: $sdir/run-sandbox.sh"; SUITE_OK=0; }
done
[ "$SUITE_OK" -eq 1 ] || hardfail "Epic-3-Sandbox-Suite unvollständig"
pass "A.4 Sandbox-Suite 3.1..3.12 vollständig (12/12)"
# A.5: Porcelain-Pre-Check: vollständiger Status (P-11: kein --untracked-files=no;
# Untracked = repo-relevanter Zuwaschs, der vor dem Gate committed sein muss)
PORE_PRE=$(git status --porcelain)
echo "--- git status --porcelain (Pre-Check; leer = erwartet) ---"
echo "$PORE_PRE"
[ -z "$PORE_PRE" ] && pass "A.5 Pre-Check: Repo sauber (committed, kein Untracked)" \
|| fail "A.5 Pre-Check: Repo NICHT sauber — Zustand muss vor dem Gate committed sein (AD-17a): $PORE_PRE"
runlabel "B: Käfig-Bau (/tmp-Kopie: schema/adapters/raw/wiki + git init + Baseline)"
mkdir -p "$CAGE"
cp -r schema "$CAGE/schema"
cp -r raw "$CAGE/raw"
cp -r wiki "$CAGE/wiki"
# P-4: Isolation — realen Bundle-Inhalt (raw/wiki) ersetzen durch die
# 3-13-Mini-Fixture; Käfig trägt damit KEINE realen Concept-/Raw-Dateien
rm -rf "$CAGE/raw" "$CAGE/wiki"
# Layout: Root-Concepts, KEINE Areas — Punkt-11-sauber (Area mit Inhalt ohne
# index.md = FAIL) und konsistent mit E.1; V-5-Area-Direktoren sind eine
# Repo-Voraussetzung (A.3), keine Validator-/Käfig-Pflicht.
mkdir -p "$CAGE/raw" "$CAGE/wiki"
cp raw/README.md "$CAGE/raw/README.md" 2>/dev/null || echo "# Raw" > "$CAGE/raw/README.md"
cat > "$CAGE/wiki/log.md" <<'EOF'
# Log
EOF
cat > "$CAGE/wiki/index.md" <<'EOF'
---
type: bundle
okf_version: "0.2"
---
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.md)
EOF
cat > "$CAGE/wiki/alpha.md" <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-21T08:00:00Z
---
Alpha beschreibt die Baseline.
EOF
cat > "$CAGE/wiki/gamma.md" <<'EOF'
---
type: concept
sources:
- resource: raw/gamma-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-21T08:00:00Z
---
Gamma ist eine isolierte Referenz ohne Überschneidung.
EOF
cat > "$CAGE/raw/alpha-v1.md" <<'EOF'
Evidenz v1: quanten-protocol-schlüssel (Stelle S-1).
EOF
cat > "$CAGE/raw/gamma-v1.md" <<'EOF'
Evidenz v1: gamma-observatorium-thema (Stelle S-1).
EOF
cd "$CAGE" || hardfail "Käfig nicht betretbar"
git init -q
git config core.autocrlf false
git config core.filemode false
git config user.email "sandbox@test"
git config user.name "Sandbox 3.13"
git add -A
git commit -qm "Käfig-Baseline" || hardfail "Käfig-Baseline-Commit fehlgeschlagen"
echo "Käfig-Baseline-Commit: $(git rev-parse HEAD)"
runlabel "C: 12 Sandbox-Sub-Runs (hart, fail-fast; §15.2/AC-2)"
for N in 1 2 3 4 5 6 7 8 9 10 11 12; do
sdir="$REPO_ROOT/_bmad-output/implementation-artifacts/sandbox-3-$N"
echo "--- Sub-Run 3.$N: bash $sdir/run-sandbox.sh ---"
if bash "$sdir/run-sandbox.sh"; then
pass "Sub-Run 3.$N: OK (harte Exit-Pfade grün)"
else
fail "Sub-Run 3.$N: EXIT≠0 — fail-fast, Gate bricht ab"
echo "SANDBOX-3-13-FAILED" >&2
exit 1
fi
done
pass "C.12 ALLE 12 Sub-Runs grün"
runlabel "D: Validator-Agent (frischer Sub-Agent; Gate prüft nur §5/§5.1, urteilt nie selbst)"
run_validator_agent() { # $1=Käfigpfad, $2=out-pfad
local cdir="$1" out="$2"
local wdir; wdir=$(cygpath -w "$cdir")
(
cd "$cdir" || exit 1
timeout 2700 "$CLAUDE_BIN" -p --bare \
--add-dir "$wdir" \
--allowedTools "Read Grep Glob" \
--permission-mode dontAsk \
--output-format text \
--max-turns 30 \
"Du bist der Wow-Validator. LIES zuerst $cdir/schema/validator.md und folge ihr EXAKT.
Validiere dann jede Markdown-Datei unter wiki/ (alle .md, inkl. index.md und log.md)
gegen die dort definierten Regeln und das dortige Verletzungs-Katalog-Register.
WICHTIG: Dein OUTPUT ist KEINE Prosa, sondern NUR eine Liste von Zeilen in der
Validator-Grammatik (validator.md):
- für JEDE validierte wiki/-Datei EXAKT EINE Verdict-Zeile:
SUCCESS <pfad> <begründung> ODER FAIL <pfad> <Punkt-Nr.+Grund>
- <pfad> ist der relativ zum Käfig-Root gepunktete Pfad (z.B. 'wiki/index.md').
- Keine weiteren Ausgabezeilen, keine Erklärungen, keine Markdown-Listen-Sterne." \
> "$out" 2>&1
echo "VALIDATOR_DONE $cdir" >> "$out"
)
}
VAL_A="$ROOT/validator-a.out"
run_validator_agent "$CAGE" "$VAL_A"
grep -q '^VALIDATOR_DONE ' "$VAL_A" || hardfail "Validator-Run: VALIDATOR_DONE-Marker fehlt (Agent-Timeout/Fehler?)"
pass "D.1 Validator-Run abgeschlossen (VALIDATOR_DONE-Marker)"
WIKI_MD_LIST=$(find wiki -type f -name '*.md' | LC_ALL=C sort)
WIKI_MD_COUNT=$(printf '%s\n' "$WIKI_MD_LIST" | grep -c '^')
echo "--- wiki/-Dateiliste ($WIKI_MD_COUNT Dateien) ---"
echo "$WIKI_MD_LIST"
VERDICTS=$(grep -E '^(SUCCESS|FAIL) [^ ]+' "$VAL_A" | head -50)
# P-6: exakt EIN Verdict pro Datei (Pfad-Vorkommen zählen; Toleranz wiki/-Präfix)
DUP_FAIL=0
while IFS= read -r wmd; do
[ -z "$wmd" ] && continue
rel="${wmd#wiki/}"
n=$(printf '%s\n' "$VERDICTS" | grep -cE "^(SUCCESS|FAIL) (wiki/)?$rel " || true)
if [ "$n" -eq 0 ]; then
fail "D.4 Exhaustivität: KEIN Verdict für $wmd (Defizit-Route, P-7)"
DUP_FAIL=1
elif [ "$n" -gt 1 ]; then
fail "D.4 Eindeutigkeit: $n Verdicts für $wmd (erwartet exakt 1)"
DUP_FAIL=1
fi
done <<EOF_LIST
$WIKI_MD_LIST
EOF_LIST
[ "$DUP_FAIL" -eq 0 ] && pass "D.4 exakt ein Verdict pro wiki/-Datei (P-6)"
V_COUNT=$(printf '%s\n' "$VERDICTS" | grep -c '^' || true)
expect_gte "D.5 Verdict-Zeilen vorhanden" "$WIKI_MD_COUNT" "$V_COUNT"
# P-7: Defizit-Route — FAIL-Verdikt = Bedarf einer separat autorisierten
# Epic-1-Remediation, NIE automatischer Gate-Pass; Boundary/AC-4 = Non-Zero.
DEFICIT=$(printf '%s\n' "$VERDICTS" | grep -c '^FAIL ' || true)
if [ "$DEFICIT" -eq 0 ]; then
pass "D.6 Validator: 0 FAIL-Verdikte über Käfig-Bundle (alle $WIKI_MD_COUNT SUCCESS)"
else
echo "DEFIZIT: $DEFICIT FAIL-Verdikt(e) — Bedarf einer separat autorisierten Epic-1-Remediation:"
printf '%s\n' "$VERDICTS" | grep '^FAIL ' | sed 's/^/ /'
fail "D.6 Validator-Defizit: $DEFICIT FAIL-Verdikt(e) im Käfig-Bundle (Gate rot, Remediierung separat)"
fi
runlabel "D-NEG: Negativ-Fixtures (AC-3-Hälfte; frischer Validator-Agent MUSS 3×FAIL + 1×SUCCESS liefern)"
NEGDIR="$ROOT/negcage"
mkdir -p "$NEGDIR/wiki/concepts" "$NEGDIR/raw"
cp -r "$CAGE/schema" "$NEGDIR/schema"
# Gültiges Bundleroot-Frontmatter (sonst Punkt-8-FAIL auf wiki/index.md selbst) +
# Area-Index (Punkt 11: Area mit Inhalt MUSS index.md haben; Concepts im Parent-Index).
cat > "$NEGDIR/wiki/index.md" <<'EOF'
---
type: bundle
okf_version: "0.2"
---
# Index
- [Concepts](concepts/index.md)
EOF
cat > "$NEGDIR/wiki/concepts/index.md" <<'EOF'
# Concepts
- [Pos](pos.md)
- [Neg1](neg-1.md)
- [Neg2](neg-2.md)
- [Neg3](neg-3.md)
EOF
cat > "$NEGDIR/wiki/log.md" <<'EOF'
# Log
EOF
# EC-1 (Fachprüfung §6): jede referenzierte raw/-Ressource MUSS als Datei existieren —
# ohne diese Dateien träge AUCH die Positiv-Kontrolle pos.md einen EC-1-FAIL (falsch-rot).
for r in pos neg-1 neg-2 neg-3; do
printf 'Evidenz: Negativ-Fixtur %s (Stelle S-1).\n' "$r" > "$NEGDIR/raw/$r.md"
done
# neg-1: required Feld 'type' fehlt (Punkt 1)
cat > "$NEGDIR/wiki/concepts/neg-1.md" <<'EOF'
---
sources:
- resource: raw/neg-1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-21T08:00:00Z
---
Negativ-Fixtur: required Feld 'type' fehlt.
EOF
# neg-2: okf_version in einer Concept-Datei (Punkt 9: ausschließlich in der Bundleroot)
cat > "$NEGDIR/wiki/concepts/neg-2.md" <<'EOF'
---
okf_version: "0.2"
---
# Neg-2 (okf_version außerhalb der Bundleroot — Punkt 9)
EOF
# neg-3: ungültiges 'at'-Kalenderdatum (Punkt 14/EC-3: 2026-02-31 existiert nicht)
cat > "$NEGDIR/wiki/concepts/neg-3.md" <<'EOF'
---
type: concept
sources:
- resource: raw/neg-3.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-02-31T10:00:00Z
---
Negativ-Fixtur: ungültiges Kalenderdatum im 'at'-Feld.
EOF
# pos-1: valid
cat > "$NEGDIR/wiki/concepts/pos.md" <<'EOF'
---
type: concept
sources:
- resource: raw/pos.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-21T08:00:00Z
---
Positiv-Kontrolle: vollständiges, valides Konzept.
EOF
NEGVAL_OUT="$ROOT/validator-neg.out"
run_validator_agent "$NEGDIR" "$NEGVAL_OUT"
grep -q '^VALIDATOR_DONE ' "$NEGVAL_OUT" || hardfail "D-NEG: Validator-Run fehlgeschlagen (kein VALIDATOR_DONE)"
pass "D-NEG.1 Negativ-Validator-Run abgeschlossen"
NEG_VERDICTS=$(grep -E '^(SUCCESS|FAIL) [^ ]+' "$NEGVAL_OUT" | head -50)
neg_count() { printf '%s\n' "$NEG_VERDICTS" | grep -cE "^FAIL (wiki/)?concepts/$1 " || true; }
pos_count() { printf '%s\n' "$NEG_VERDICTS" | grep -cE "^SUCCESS (wiki/)?concepts/$1 " || true; }
neg1=$(neg_count neg-1.md); neg2=$(neg_count neg-2.md); neg3=$(neg_count neg-3.md)
pos1=$(pos_count pos.md)
[ "$neg1" -ge 1 ] && pass "D-NEG.2 neg-1 (ohne type): FAIL-Verdikt erhalten" \
|| fail "D-NEG.2 neg-1: kein FAIL-Verdikt (AC-3-Hälfte verletzt)"
[ "$neg2" -ge 1 ] && pass "D-NEG.3 neg-2 (ohne okf_version): FAIL-Verdikt erhalten" \
|| fail "D-NEG.3 neg-2: kein FAIL-Verdikt (AC-3-Hälfte verletzt)"
[ "$neg3" -ge 1 ] && pass "D-NEG.4 neg-3 (at: 2026-02-31): FAIL-Verdikt erhalten" \
|| fail "D-NEG.4 neg-3: kein FAIL-Verdikt (AC-3-Hälfte verletzt)"
[ "$pos1" -ge 1 ] && pass "D-NEG.5 pos (valid): SUCCESS-Verdikt erhalten (Positiv-Kontrolle)" \
|| fail "D-NEG.5 pos: kein SUCCESS-Verdikt (Positiv-Kontrolle gescheitert)"
# ==============================================================================
# E. ZWEI FRISCHE AGENT-KONTEXTE A/B — deterministischer Zwei-Run-Kern (AC-7)
# ==============================================================================
runlabel "E. ZWEI FRISCHE AGENT-KONTEXTE A/B (AC-7)"
# --- E.1 Fixture im Käfig (D-6: UPDATE alpha + CREATE beta; Bundle-Frontmatter P-4) ---
# alpha: bestätigender Zuwachs raw/alpha-v2.md (S-3, gleiche Aussage wie alpha-v1#S-2)
# beta: EINDEUTIGER Term quanten-observatorium-kanal — kein bestehendes Concept
# deckt ihn ab ⇒ Routing CREATE (neues Concept + Index-Link, Punkt 11/Anlage).
# Der Ziel-Pfad (Slug) wird vom Producer aus dem Term abgeleitet (§5.15/§5.7) —
# das Gate HARDCODIERT ihn NICHT (D-1: keine erwarteten Plan-Literale); die
# Witness detektiert die neue Datei über Git und prüft deren SEMANTIK.
# gamma: unabhängig, bleibt byte-identisch (AC-6)
rm -rf "$CAGE/wiki" "$CAGE/raw" # E.1 baut die Fixture sauber neu (keine §B-Reste in $BASE)
mkdir -p "$CAGE/wiki" "$CAGE/raw"
cat > "$CAGE/wiki/index.md" <<'FIXTURE'
---
type: bundle
okf_version: "0.2"
---
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.md)
FIXTURE
cat > "$CAGE/wiki/log.md" <<'FIXTURE'
# Log
FIXTURE
cat > "$CAGE/wiki/alpha.md" <<'FIXTURE'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Das Alpha-Protokoll definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
Alpha verwendet ausschließlich lokale Netze (raw/alpha-v1.md#S-2).
FIXTURE
cat > "$CAGE/wiki/gamma.md" <<'FIXTURE'
---
type: concept
sources:
- resource: raw/gamma-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Gamma beschreibt ein anderes, hier nicht betroffenes Thema.
FIXTURE
cat > "$CAGE/raw/alpha-v1.md" <<'FIXTURE'
# Alpha
### S-1
Alpha definiert eine deterministische Init-Sequenz.
### S-2
Alpha verwendet ausschließlich lokale Netze.
FIXTURE
cat > "$CAGE/raw/alpha-v2.md" <<'FIXTURE'
### S-3
Alpha verwendet ausschließlich lokale Netze.
FIXTURE
cat > "$CAGE/raw/gamma-v1.md" <<'FIXTURE'
### S-1
Evidenz v1: Gamma-Thema.
FIXTURE
cat > "$CAGE/raw/beta-v1.md" <<'FIXTURE'
### S-1
Beta: quanten-observatorium-kanal etabliert die Baseline.
FIXTURE
cat > "$CAGE/raw/beta-v2.md" <<'FIXTURE'
### S-2
Beta: quanten-observatorium-kanal bestätigt die Baseline.
FIXTURE
# Term-Isolations-Garantie (Fixture-Setup, kein Run-Zustand): der beta-Term darf
# in KEINER Baseline-Datei vorkommen — sonst wäre CREATE unscharf (Kandidat alpha).
if grep -rq 'quanten-observatorium-kanal' "$CAGE/wiki" "$CAGE/raw/alpha-v1.md" "$CAGE/raw/alpha-v2.md" "$CAGE/raw/gamma-v1.md"; then
echo "HARD-FAIL (E.1): beta-Term taucht in Baseline-Dateien auf (CREATE-Isolation verletzt)" >&2
exit 1
fi
pass "E.1 Fixture: Mini-Bundle (index+alpha+gamma+log) + Zuwachs alpha-v2/beta-v1/beta-v2, term-isoliert"
( cd "$CAGE" && git add -A && git commit -qm "Käfig-Fixture: Baseline inkl. Zuwachs" ) \
|| { echo "HARD-FAIL (E.1): Fixture-Commit fehlgeschlagen" >&2; exit 1; }
BASE=$(git -C "$CAGE" rev-parse HEAD)
[ -n "$BASE" ] && git -C "$CAGE" cat-file -e "$BASE^{commit}" 2>/dev/null \
|| { echo "HARD-FAIL (E.1): Fixture-Baseline nicht verifiziert" >&2; exit 1; }
pass "E.2 Fixture als \$BASE committet (\$BASE=$BASE)"
# --- E.3 Zwei getrennte Worktrees auf $BASE + frische Agent-Kontexte ---
git -C "$CAGE" worktree prune
git -C "$CAGE" worktree add -q "$ROOT/wt-a" "$BASE" \
|| { echo "HARD-FAIL (E.3): Worktree wt-a nicht aufgebaut" >&2; exit 1; }
git -C "$CAGE" worktree add -q "$ROOT/wt-b" "$BASE" \
|| { echo "HARD-FAIL (E.3): Worktree wt-b nicht aufgebaut" >&2; exit 1; }
pass "E.3 Worktrees wt-a/wt-b auf \$BASE angelegt"
# D-1: AGENT_PROMPT = NUR Instruktion + Fixture-Fakten. Keine erwarteten
# Plan-/Entscheidungs-/Log-Literale, keine vorgegebenen Receipt-Werte
# (Gate-Checks sind A-vs-B + Nicht-Vakuum-Witness gegen Baseline).
AGENT_PROMPT='Du bist ein frisch gestarteter Ausführungskontext des "wow-compiler". Arbeite ausschließlich in deinem aktuellen Arbeitsverzeichnis (ein git-Worktree des Bundles). LIES schema/compiler.md im Worktree — sie ist die EINZIGE normative Instruktionsquelle. Führe GENAU EINEN vollständigen Compilation Run nach ihr aus (alle Phasen, inkl. Validierungsphase gegen schema/validator.md). Keine anderen Quellen, keine Annahmen über erwartete Ausgaben.
Fakten zur committeten Fixture in diesem Worktree (ausschließlich Fakten, keine Wertevorgaben):
- wiki/index.md (Bundleroot), wiki/log.md, wiki/alpha.md und wiki/gamma.md sind die bisherigen Concepts.
- raw/alpha-v1.md und raw/gamma-v1.md sind Baseline-Evidenz.
- raw/alpha-v2.md ist ein neu committeter Zuwachs (Stelle S-3); er enthält dieselbe belegte Aussage wie raw/alpha-v1.md#S-2.
- raw/beta-v1.md und raw/beta-v2.md sind neu committete Zuwächse (Stellen S-1/S-2) mit einem eindeutigen Term, für den kein bestehendes Concept existiert.
Mutationen (wiki/-Concepts, index.md, log.md) UND die Receipt-Datei run-receipt.txt (Worktree-Root, AUSSERHALB von wiki/ und raw/) sind EXAKT nach den Konventionen von schema/compiler.md auszuführen — keine vorgegebenen Werte. Die run-receipt.txt trägt diese Felder (Werte aus deinem Lauf):
baseline: <volles SHA des Baseline-Commits>
candidates: <Kandidatenliste oder leer>
decision: <Entscheidung je Mutation>
sources_added: <neue raw/-Ressourcen oder leer>
Commite NICHT selbst (der Harness committiert deine Mutationen mechanisch; run-receipt.txt bleibt uncommittet). Schließe den Lauf mit der Validierungsphase ab und melde am Ende EXAKT "RUN_DONE <name>", wobei <name> der dir mitgegebene Run-Name ist.'
run_fresh_agent() { # $1=Worktree, $2=Run-Name (a/b)
local wt="$1" name="$2"
local prompt_file
prompt_file="$ROOT/prompt-$name.txt"
printf '%s\n' "$AGENT_PROMPT" > "$prompt_file"
local win_prompt
win_prompt=$(cygpath -w "$prompt_file" 2>/dev/null || echo "$prompt_file")
( cd "$wt" && timeout 2700 "$CLAUDE_BIN" -p --bare "Run-Name: $name — Lies die Datei $win_prompt (nicht modifizieren) und führe sie vollständig und exakt aus. Dein Arbeitsverzeichnis ist dieses Worktree." --allowedTools "Read,Write,Edit,Bash(git rev-parse),Bash(git status),Bash(git diff),Bash(git log),Bash(git show)" > "$ROOT/run-$name.out" 2>&1 )
local rc=$?
RUN_AGENT_RC=$rc
return $rc
}
echo "--- Lauf A (frischer Agent-Kontext, Worktree wt-a) ---"
if ! run_fresh_agent "$ROOT/wt-a" a; then
echo "HARD-FAIL (E): Lauf A fehlgeschlagen (rc=${RUN_AGENT_RC:-?})" >&2
tail -20 "$ROOT/run-a.out" >&2 2>/dev/null || true
exit 1
fi
grep -q '^RUN_DONE a$' "$ROOT/run-a.out" || { echo "HARD-FAIL (E.3): Lauf A ohne RUN_DONE-Marker" >&2; tail -10 "$ROOT/run-a.out" >&2; exit 1; }
pass "Lauf A abgeschlossen (RUN_DONE-Marker vorhanden)"
echo "--- Lauf B (frischer Agent-Kontext, Worktree wt-b) ---"
if ! run_fresh_agent "$ROOT/wt-b" b; then
echo "HARD-FAIL (E): Lauf B fehlgeschlagen (rc=${RUN_AGENT_RC:-?})" >&2
tail -20 "$ROOT/run-b.out" >&2 2>/dev/null || true
exit 1
fi
grep -q '^RUN_DONE b$' "$ROOT/run-b.out" || { echo "HARD-FAIL (E.3): Lauf B ohne RUN_DONE-Marker" >&2; tail -10 "$ROOT/run-b.out" >&2; exit 1; }
pass "Lauf B abgeschlossen (RUN_DONE-Marker vorhanden)"
# --- Mechanischer Mutations-Commit (Gate ist kein Content-Autor; AC-5) ---
# P-10: danach MUSS raw/ unverändert sein (Immutabilität des read-only Vorbilds)
commit_worktree() { # $1=Worktree, $2=Name
local wt="$1" name="$2"
( cd "$wt" && git add wiki raw \
&& if git diff --cached --quiet; then
echo " ($name) keine Mutationen staged — kein Commit nötig"
else
git commit -qm "Run: Compilation ($name)"
fi ) \
|| { echo "HARD-FAIL (E): Commit $name fehlgeschlagen" >&2; return 1; }
local rawstat
rawstat=$(git -C "$wt" diff "$BASE" HEAD --stat -- raw/ 2>/dev/null || true)
if [ -n "$rawstat" ]; then
echo "HARD-FAIL (E, P-10): raw/ in $name mutiert (Immutabilität verletzt):" >&2
printf '%s\n' "$rawstat" >&2
return 1
fi
return 0
}
commit_worktree "$ROOT/wt-a" a || exit 1
commit_worktree "$ROOT/wt-b" b || exit 1
pass "Bundle-State beider Worktrees committet (raw/ unverändert, P-10; run-receipt.txt uncommittet)"
# --- E.4 A/B-Vergleich: nur-at-Ausnahme (§5.14 Pkt. 2) — P-2: geankerter Classifier ---
# classify_diff $1=Diff-Datei: echo "NON_AT=<n> AT=<m>"
# at-Zeilen = Frontmatter-geankert (genau zwei führende Leerzeichen nach +/-).
# Der alte un-geankerte *"at: "*-Treffer (Body-Zeilen mit 'at: ') ist behoben.
classify_diff() {
local f="$1" non_at=0 atn=0 line
if [ ! -s "$f" ]; then echo "NON_AT=0 AT=0"; return; fi
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
+*|-*) : ;;
*) continue ;;
esac
case "$line" in
+++*|---*) continue ;;
"+ at: "*|"- at: "*) atn=$((atn+1)) ;;
*) non_at=$((non_at+1)) ;;
esac
done < "$f"
echo "NON_AT=$non_at AT=$atn"
}
HEAD_A=$(git -C "$ROOT/wt-a" rev-parse HEAD)
HEAD_B=$(git -C "$ROOT/wt-b" rev-parse HEAD)
[ -n "$HEAD_A" ] && [ -n "$HEAD_B" ] \
|| { echo "HARD-FAIL (E.4): Run-Commits nicht auflösbar (A=$HEAD_A, B=$HEAD_B)" >&2; exit 1; }
echo " HEAD-A=$HEAD_A"
echo " HEAD-B=$HEAD_B"
AB_DIFF_FILE="$ROOT/ab-wiki.diff"
git -C "$CAGE" diff "$HEAD_A" "$HEAD_B" -- wiki/ > "$AB_DIFF_FILE" 2>&1
AB_DIFF_STAT=$(git -C "$CAGE" diff --stat "$HEAD_A" "$HEAD_B" -- wiki/ 2>&1 || true)
echo " --- A/B diff --stat (wiki/) ---"
printf '%s\n' "$AB_DIFF_STAT"
AB_CLASS=$(classify_diff "$AB_DIFF_FILE")
echo " A/B-Classifier: $AB_CLASS"
AB_NON_AT=$(printf '%s' "$AB_CLASS" | sed -n 's/.*NON_AT=\([0-9]*\).*/\1/p')
if [ "$AB_NON_AT" != "0" ]; then
echo "HARD-FAIL (E.4): Bundle-State A vs B divergiert außerhalb der at-Ausnahme (§5.14 Pkt. 2):" >&2
cat "$AB_DIFF_FILE" >&2
exit 1
fi
[ -z "$AB_DIFF_STAT" ] && echo " BEFUND: A/B-Diff leer — Runs vollständig konvergiert (zulässig)"
pass "E.4 A/B-Vergleich: Bundle-State identisch bis auf at-Ausnahme (§5.14 Pkt. 2 / G-1)"
# --- E.5 Witness: Nicht-Vakuum gegen Baseline (UPDATE alpha + CREATE beta) ---
# alpha (UPDATE): Multi-Beleg-Anker raw/alpha-v2.md#S-3 + sources-Konsolidierung.
ABS_A=$(git -C "$CAGE" show "$BASE:wiki/alpha.md" | sha256sum | cut -d' ' -f1)
for wt in a b; do
SHA_W=$(git -C "$ROOT/wt-$wt" show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1)
[ "$ABS_A" != "$SHA_W" ] \
|| { echo "HARD-FAIL (E.5): Witness $wt alpha == Baseline (Vakuum — alpha-Zuwachs nicht verarbeitet)" >&2; exit 1; }
git -C "$ROOT/wt-$wt" show 'HEAD:wiki/alpha.md' | grep -q 'raw/alpha-v2.md#S-3' \
|| { echo "HARD-FAIL (E.5): Witness $wt alpha ohne Multi-Beleg-Anker raw/alpha-v2.md#S-3" >&2; exit 1; }
done
# beta (CREATE): die neue Concept-Datei wird GIBASES-DIFF-gedetectet (nie
# Slug-hartcodiert — §5.15 leitet den Pfad aus dem Term ab; D-1: kein Plan-Literal).
# Erwartet: exakt EINE neue wiki/-Datei (außer log.md/index.md, die M sind nicht A).
NEW_CONCEPT_A=$(git -C "$CAGE" diff --name-only --diff-filter=A "$BASE" "$HEAD_A" -- wiki/ | grep -vE '(^|/)log\.md$' | grep -vE '(^|/)index\.md$' | LC_ALL=C sort)
NEW_CONCEPT_B=$(git -C "$CAGE" diff --name-only --diff-filter=A "$BASE" "$HEAD_B" -- wiki/ | grep -vE '(^|/)log\.md$' | grep -vE '(^|/)index\.md$' | LC_ALL=C sort)
NEW_A_COUNT=$(printf '%s\n' "$NEW_CONCEPT_A" | grep -c '^' || true)
[ "$NEW_A_COUNT" -eq 1 ] \
|| { echo "HARD-FAIL (E.5): CREATE A — erwartet exakt 1 neue Concept-Datei, sind: $NEW_A_COUNT" >&2; printf '%s\n' "$NEW_CONCEPT_A" >&2; exit 1; }
# A und B müssen dieselbe neue Datei erzeugen (Determinismus der CREATE-Zuordnung)
[ "$NEW_CONCEPT_A" = "$NEW_CONCEPT_B" ] \
|| { echo "HARD-FAIL (E.5): CREATE-Pfad divergiert A($NEW_CONCEPT_A) vs B($NEW_CONCEPT_B) — Determinismus-Verstoß" >&2; exit 1; }
NEW_CONCEPT="$NEW_CONCEPT_A"
NEW_LEAF=$(basename "$NEW_CONCEPT")
echo " CREATE-Zieldatei (Producer-geleitet, §5.15): $NEW_CONCEPT"
for wt in a b; do
git -C "$ROOT/wt-$wt" show "HEAD:$NEW_CONCEPT" | grep -q 'resource: raw/beta-v1.md' \
|| { echo "HARD-FAIL (E.5): CREATE $wt$NEW_CONCEPT sources ohne raw/beta-v1.md" >&2; exit 1; }
git -C "$ROOT/wt-$wt" show "HEAD:$NEW_CONCEPT" | grep -q 'resource: raw/beta-v2.md' \
|| { echo "HARD-FAIL (E.5): CREATE $wt$NEW_CONCEPT sources ohne raw/beta-v2.md" >&2; exit 1; }
git -C "$ROOT/wt-$wt" show "HEAD:$NEW_CONCEPT" | grep -q 'quanten-observatorium-kanal' \
|| { echo "HARD-FAIL (E.5): CREATE $wt$NEW_CONCEPT trägt nicht den beta-Term (falsches Concept)" >&2; exit 1; }
# Index-Verlinkung (Punkt 11/Anlage): die Bundleroot verlinkt die neue Datei.
git -C "$ROOT/wt-$wt" show 'HEAD:wiki/index.md' | grep -qF "$NEW_LEAF" \
|| { echo "HARD-FAIL (E.5): CREATE $wt — index.md verlinkt die neue Concept-Datei $NEW_LEAF nicht (Punkt 11)" >&2; exit 1; }
done
pass "E.5 Witness: alpha UPDATE (Multi-Beleg) + beta CREATE (neue Datei, sources, Term, Index-Link) in A und B"
# --- E.6 Run-Receipt A-vs-B (D-2: Modell sandbox-3-8 DET-2, keine erwarteten Literale) ---
RA="$ROOT/wt-a/run-receipt.txt"
RB="$ROOT/wt-b/run-receipt.txt"
[ -s "$RA" ] || { echo "HARD-FAIL (E.6): run-receipt.txt A fehlt/leer" >&2; exit 1; }
[ -s "$RB" ] || { echo "HARD-FAIL (E.6): run-receipt.txt B fehlt/leer" >&2; exit 1; }
# Normalisierung: at-Cellen = Zeilen mit ISO-8601-Timestamp (Filter), Worktree-
# spezifische Pfade auf WT normalisiert. Danach MUSS der Rest byte-identisch sein.
receipt_norm() {
sed -E "/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|\+00:00)/d" "$1" \
| sed "s|$ROOT/wt-a|WT|g; s|$ROOT/wt-b|WT|g"
}
receipt_norm "$RA" > "$ROOT/receipt-a.norm"
receipt_norm "$RB" > "$ROOT/receipt-b.norm"
echo " --- run-receipt.txt A (normalisiert) ---"
cat "$ROOT/receipt-a.norm"
if cmp -s "$ROOT/receipt-a.norm" "$ROOT/receipt-b.norm"; then
pass "E.6 Run-Receipt A==B byte-identisch (nach at-/Pfad-Filter, D-2)"
else
echo "HARD-FAIL (E.6): Run-Receipts A/B divergieren (Determinismus-Vertrag §5.14):" >&2
diff "$ROOT/receipt-a.norm" "$ROOT/receipt-b.norm" >&2 || true
exit 1
fi
AT_A=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p' | head -1)
AT_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p' | head -1)
[ -n "$AT_A" ] && [ -n "$AT_B" ] \
|| { echo "FAIL: generated.at fehlt in mindestens einem Run-Output" >&2; exit 1; }
echo " BEFUND: at-Werte A=$AT_A B=$AT_B (Ausnahme §5.14 Pkt. 3; Identität zulässig)"
pass "E.6b generated.at in A und B anwesend (nicht-leer)"
# --- E.7 sources-Konsolidierung (alpha: beide Belege) ---
for wt in a b; do
for res in raw/alpha-v1.md raw/alpha-v2.md; do
git -C "$ROOT/wt-$wt" show 'HEAD:wiki/alpha.md' | grep -q "resource: $res" \
|| { echo "HARD-FAIL (E.7): alpha.md in $wt ohne source $res (Konsolidierung fehlt)" >&2; exit 1; }
done
done
pass "E.7 sources-Liste alpha.md (A und B) konsolidiert (raw/alpha-v1.md + raw/alpha-v2.md)"
# --- E.8 Unabhängiges Wissen erhalten (AC-6): gamma byte-pinned; index = BASE + exakt 1 neue Link-Zeile ---
HA=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/gamma.md' | sha256sum | cut -d' ' -f1)
HB=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/gamma.md' | sha256sum | cut -d' ' -f1)
BASEHG=$(git -C "$CAGE" show "$BASE:wiki/gamma.md" | sha256sum | cut -d' ' -f1)
[ "$HA" = "$HB" ] || { echo "HARD-FAIL (E.8): gamma.md weicht zwischen A und B ab" >&2; exit 1; }
[ "$HA" = "$BASEHG" ] || { echo "HARD-FAIL (E.8): gamma.md != Baseline (unabhängiges Wissen mutiert)" >&2; exit 1; }
IDX_A=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/index.md')
IDX_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/index.md')
[ "$IDX_A" = "$IDX_B" ] || { echo "HARD-FAIL (E.8): index.md weicht zwischen A und B ab" >&2; exit 1; }
IDX_DIFF=$(git -C "$CAGE" diff "$BASE" "$HEAD_A" -- wiki/index.md)
IDX_ADDED=$(printf '%s\n' "$IDX_DIFF" | grep -c '^+[^+]' || true)
IDX_DELETED=$(printf '%s\n' "$IDX_DIFF" | grep -c '^-[^-]' || true)
[ "$IDX_DELETED" -eq 0 ] || { echo "HARD-FAIL (E.8): index.md trägt Deletions (nur Appending erlaubt):" >&2; printf '%s\n' "$IDX_DIFF" >&2; exit 1; }
[ "$IDX_ADDED" -eq 1 ] || { echo "HARD-FAIL (E.8): index.md Diff != exakt 1 Zeile (erwartet: CREATE-Link), ist: $IDX_ADDED" >&2; printf '%s\n' "$IDX_DIFF" >&2; exit 1; }
printf '%s\n' "$IDX_DIFF" | grep '^+[^+]' | grep -qF "$NEW_LEAF" \
|| { echo "HARD-FAIL (E.8): die einzige neue index-Zeile ist kein Link auf die CREATE-Datei $NEW_LEAF:" >&2; printf '%s\n' "$IDX_DIFF" >&2; exit 1; }
pass "E.8 Unabhängiges Wissen erhalten: gamma byte-identisch; index = BASE + exakt 1 CREATE-Link (A==B)"
# --- E.9 log.md: Eintrag mit vollem Baseline-SHA (R-1; P-9; kein vorgeschriebener Wortlaut) ---
for wt in a b; do
LOG_BASE=$(git -C "$CAGE" show "$BASE:wiki/log.md" | sha256sum | cut -d' ' -f1)
LOG_HEAD=$(git -C "$ROOT/wt-$wt" show 'HEAD:wiki/log.md' | sha256sum | cut -d' ' -f1)
[ "$LOG_BASE" != "$LOG_HEAD" ] \
|| { echo "HARD-FAIL (E.9): log.md in $wt == Baseline (kein Lauf-Eintrag)" >&2; exit 1; }
git -C "$ROOT/wt-$wt" show 'HEAD:wiki/log.md' | grep -q "$BASE" \
|| { echo "HARD-FAIL (E.9): log.md-Eintrag in $wt ohne volles Baseline-SHA (R-1)" >&2; exit 1; }
done
pass "E.9 log.md (A und B): Lauf-Eintrag vorhanden und trägt volles Baseline-SHA (R-1)"
echo
echo "ZWEI-FRESCHE-AGENTEN A/B abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# F. G-SZENARIEN (G-1..G-8) — harte Assertions
# ==============================================================================
runlabel "F. G-SZENARIEN G-1..G-8"
# G-1 (A/B nur-at) ist in E.4 hart geprüft (geankerter Classifier, P-2) — Referenz.
pass "G-1: Zwei-frische-Agenten A/B deterministisch (nur at-Ausnahme, §5.14 Pkt. 2; E.4)"
# G-2: Validator-Integration — frischer Kontext hat schema/validator.md über alle
# wiki/-Dateien ausgeführt (Abschnitt D); Marker + Grammatik geprüft.
[ -s "$VAL_A" ] || { echo "HARD-FAIL (G-2): Validator-Report fehlt" >&2; exit 1; }
pass "G-2: Validator-Integration (frischer Kontext, schema/validator.md als Instruktion; D.1)"
# G-3: SUCCESS über alle Käfig-Bundle-Dateien (D.6: 0 FAIL-Verdikte — sonst rot)
if [ "${DEFICIT:-1}" -ne 0 ]; then
echo "HARD-FAIL (G-3): ${DEFICIT} FAIL-Verdikte im Validator-Report (alle wiki/ müssen SUCCESS sein)" >&2
exit 1
fi
pass "G-3: Validator-SUCCESS über alle wiki/-Dateien (0 FAIL-Verdikte)"
# G-4: Fail-fast der 12 Sandbox-Sub-Runs (Abschnitt C) — Referenz.
pass "G-4: 12 Sandbox-Sub-Runs alle Exit 0 (Abschnitt C fail-fast)"
# G-5: Defizit-Route (P-7) — im grünen Zustand: kein Defizit, kein Gate-Flip; die
# Route selbst ist in D.6 real implementiert (FAIL-Verdikt → Non-Zero +
# Remediation-Text). Renegotiation-Kandidat: frozen I/O-Matrix-Zeile
# VALIDATOR_DEFIZIT „G-5 PASS" vs. Boundary/AC-4 (Non-Zero) — Boundary/AC-4 gilt.
pass "G-5: Validator-Defizit-Route implementiert (D.6; kein Defizit in diesem Lauf — kein Gate-Flip)"
# G-6 (D-3): echter perturbed committed Tree (wt-c) durch denselben E.4-Classifier.
# wt-c: Body-Eingriff (kein at) auf A-Commit, committet in 3. Worktree ⇒ MUSS NON_AT≠0
# wt-c2: at-only-Eingriff (erlaubte Ausnahme) ⇒ MUSS NON_AT=0 (Positiv-/Kontroll-Kontrolle)
git -C "$CAGE" worktree prune
git -C "$CAGE" worktree add -q "$ROOT/wt-c" "$HEAD_A" \
|| { echo "HARD-FAIL (G-6): Worktree wt-c (perturbed) nicht aufgebaut" >&2; exit 1; }
git -C "$CAGE" worktree add -q "$ROOT/wt-c2" "$HEAD_A" \
|| { echo "HARD-FAIL (G-6): Worktree wt-c2 (at-only-Kontrolle) nicht aufgebaut" >&2; exit 1; }
# Perturbation 1: deterministischer Body-Eingriff (kein at) — Zelle wird umgestellt.
( cd "$ROOT/wt-c" \
&& sed 's/Alpha verwendet ausschließlich lokale Netze/Alpha setzt ausschließlich lokale Netze ein/' wiki/alpha.md > wiki/alpha.perturb.tmp \
&& mv wiki/alpha.perturb.tmp wiki/alpha.md \
&& git add wiki \
&& git commit -qm "G-6: perturbed Body (kein at)" ) \
|| { echo "HARD-FAIL (G-6): Perturbed-Commit wt-c fehlgeschlagen" >&2; exit 1; }
# Perturbation 2: at-only (erlaubte Ausnahme §5.14 Pkt. 3)
( cd "$ROOT/wt-c2" \
&& sed 's/^ at: .*/ at: 2026-08-22T00:00:00Z/' wiki/alpha.md > wiki/alpha.perturb.tmp \
&& mv wiki/alpha.perturb.tmp wiki/alpha.md \
&& git add wiki \
&& git commit -qm "G-6: at-only Perturbation" ) \
|| { echo "HARD-FAIL (G-6): at-only-Commit wt-c2 fehlgeschlagen" >&2; exit 1; }
HEAD_C=$(git -C "$ROOT/wt-c" rev-parse HEAD)
HEAD_C2=$(git -C "$ROOT/wt-c2" rev-parse HEAD)
G6_DIFF="$ROOT/g6-wiki.diff"
git -C "$CAGE" diff "$HEAD_C" "$HEAD_B" -- wiki/ > "$G6_DIFF" 2>&1
G6_CLASS=$(classify_diff "$G6_DIFF")
echo " G-6-Classifier (C vs B): $G6_CLASS"
G6_NON_AT=$(printf '%s' "$G6_CLASS" | sed -n 's/.*NON_AT=\([0-9]*\).*/\1/p')
[ "$G6_NON_AT" != "0" ] \
&& pass "G-6 Negativ-Kontrolle: perturbed committed Tree C erkannt (NON_AT=$G6_NON_AT ≠ 0 — kein false-PASS)" \
|| { echo "HARD-FAIL (G-6): Perturbation NICHT erkannt (false-PASS)" >&2; exit 1; }
G6C2_DIFF="$ROOT/g6c2-wiki.diff"
git -C "$CAGE" diff "$HEAD_C2" "$HEAD_B" -- wiki/ > "$G6C2_DIFF" 2>&1
G6C2_CLASS=$(classify_diff "$G6C2_DIFF")
echo " G-6-Classifier (C2 vs B, at-only-Kontrolle): $G6C2_CLASS"
G6C2_NON_AT=$(printf '%s' "$G6C2_CLASS" | sed -n 's/.*NON_AT=\([0-9]*\).*/\1/p')
[ "$G6C2_NON_AT" = "0" ] \
&& pass "G-6 Positiv-Kontrolle: at-only Perturbation C2 korrekt toleriert (NON_AT=0)" \
|| { echo "HARD-FAIL (G-6): at-only-Kontrolle C2 falsch-positiv (NON_AT=$G6C2_NON_AT ≠ 0)" >&2; exit 1; }
# G-7 (D-4/P-13): Epic-5-Smoke auf dem IST-Baum ($REPO_ROOT, nie der /tmp-Spiegel):
# (a) Link-Auflösung: jeder Markdown-Link in allen wiki/-Dateien muss entweder
# bundle-intern (relative .md, inkl. ../-Auflösung, Anchor gestrippt) oder
# eine raw/-Provenienz-Referenz sein — KEIN Ziel außerhalb wiki//raw/.
# (b) Geschlossene Planungstoken-Menge (am realen Bundle validiert: 0 bare
# Treffer nach raw/-Provenienz-Strich; Exkl. wiki/index.md + wiki/log.md):
# blosse Planungs-Referenzen (Prosa) auf PRD/Spec/Spine/Brief/Story-History
# sind Lese-Zwänge (AC-8) → Non-Zero.
G7_BAD=0
G7_FILES=$(cd "$REPO_ROOT" && find wiki -type f -name '*.md' | LC_ALL=C sort)
while IFS= read -r f; do
[ -z "$f" ] && continue
if [ "$f" = "wiki/index.md" ] || [ "$f" = "wiki/log.md" ]; then
continue
fi
fpath="$REPO_ROOT/$f"
fdir=$(dirname "$fpath")
# (a) Link-Auflösung
LINKS=$(grep -oE '\]\([^)]+\)' "$fpath" | sed -E 's/^\]\(//; s/\)$//' || true)
while IFS= read -r tgt; do
[ -z "$tgt" ] && continue
tgt_clean=${tgt%%#*}
case "$tgt_clean" in
''|http://*|https://*|mailto:*|raw/*) continue ;; # Provenanz/Evidenz/extern-ok
esac
# relative Auflösung gegen die Datei (root- oder area-/relativ)
if [ -f "$fdir/$tgt_clean" ]; then continue; fi
if [ -f "$REPO_ROOT/$tgt_clean" ]; then continue; fi
echo "FAIL (G-7a): $f — Link-Ziel nicht bundle-intern auflösbar: $tgt_clean" >&2
G7_BAD=$((G7_BAD+1))
done <<EOF_LINKS
$LINKS
EOF_LINKS
# (b) Planungstoken (geschlossene Menge, raw/-Provenienz gestrichen)
STRIPPED=$(sed -E 's|raw/[A-Za-z0-9_./#-]+||g' "$fpath")
for tok in \
'prd-wow20' 'spec-[0-9]' 'sprint-status' 'sprint-change-proposal' \
'planning-artifacts' 'implementation-artifacts' 'architecture[- ]spine' \
'product[- ]brief' 'epic-context' 'epic-[0-9]' 'code-review' 'deferred-work' \
'_bmad-output'; do
if printf '%s\n' "$STRIPPED" | grep -qiE "$tok"; then
echo "FAIL (G-7b): $f — Planungs-Referenz (Token $tok) = Lese-Zwang (AC-8)" >&2
G7_BAD=$((G7_BAD+1))
fi
done
done <<EOF_G7
$G7_FILES
EOF_G7
[ "$G7_BAD" -eq 0 ] \
|| { echo "HARD-FAIL (G-7): Epic-5-Abhängigkeits-Smoke fehlgeschlagen ($G7_BAD Verletzung(en))" >&2; exit 1; }
pass "G-7: Epic-5-Smoke auf Ist-Baum (AC-8): Link-Auflösung + geschlossene Planungstoken-Menge, 0 Verletzungen"
# G-8: Porcelain-Endzustands-Invariante — in Abschnitt G hart am Ist-Baum.
pass "G-8 wird in Abschnitt G als Endzustands-Invariante am Ist-Baum geprüft"
echo
echo "G-SZENARIEN abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# G. NACHWEISE / PORCELAIN — Endzustands-Invariante des Ist-Baums
# ==============================================================================
runlabel "G. NACHWEISE / PORCELAIN — Ist-Baum unverändert"
# G-8 (P-11 hart, inkl. Untracked): Das Gate schreibt ausschließlich /tmp; der
# reale Ist-Baum muss VOLLSTÄNDIG unverändert sein (kein --untracked-files=no).
cd "$REPO_ROOT" || hardfail "Repo-Root nicht mehr erreichbar (Abschnitt G)"
G8_PORCELAIN=$(git status --porcelain)
echo " Ist-Baum-Porcelain (vollständig, inkl. Untracked) nach dem Gate-Lauf:"
printf '%s\n' "$G8_PORCELAIN"
if [ -z "$G8_PORCELAIN" ]; then
pass "G-8: Porcelain-Clean (Ist-Baum nach Gate-Lauf unverändert; leer inkl. Untracked)"
else
echo "HARD-FAIL (G-8): Ist-Baum trägt uncommittete Änderungen/Untracked nach dem Gate-Lauf:" >&2
printf '%s\n' "$G8_PORCELAIN" >&2
exit 1
fi
# Negativ-Kontrolle der Porcelain-Detection (IM KÄFIG, nie im realen Repo):
# eine in den Käfig gelegte Scratch-Datei MUSS vom vollständigen Porcelain erfasst
# werden — Beweis, dass die A.5/G-8-Guard nicht blind für Untracked sind.
NC_FILE="$CAGE/wiki/.g8-negative-control.md"
echo "negativ-kontrolle" > "$NC_FILE"
NC_PORC=$(git -C "$CAGE" status --porcelain -- wiki/)
git -C "$CAGE" -c core.hooksPath=/dev/null checkout -q -- . 2>/dev/null || true
rm -f "$NC_FILE"
case "$NC_PORC" in
*"?? wiki/.g8-negative-control.md"*) pass "G-8 Negativ-Kontrolle: Untracked-Datei wird vom Porcelain erfasst (Detection wach)";;
*) echo "HARD-FAIL (G-8): Negativ-Kontrolle — Untracked-Datei NICHT im Porcelain (Guard blind)" >&2; exit 1;;
esac
# Read-only-Artefakte (AD-3): kein Diff am Ist-Baum
for ro in schema/compiler.md schema/validator.md schema/wiki-compiler.md schema/canonical-terms.md adapters raw; do
if ! git diff --quiet -- "$ro" 2>/dev/null; then
echo "HARD-FAIL (G-8): Read-only-Artefakt verändert: $ro (AD-3)" >&2
exit 1
fi
done
pass "G-8: Read-only-Artefakte (schema/*, adapters/, raw/) ohne Diff (AD-3)"
echo
echo "########################################"
echo "# GATE-ENDE — FAILED=$FAILED PASS_COUNT=$PASS_COUNT"
echo "########################################"
if [ "$FAILED" -eq 0 ]; then
echo "SANDBOX-3-13-OK"
echo "RUN_OK"
exit 0
else
echo "SANDBOX-3-13-FAILED" >&2
echo "RUN_FAILED" >&2
exit 1
fi