#!/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" # P-16 (Re-Run-#3/#4-Befund): CLI-Per-Response-Output-Limit reicht für # E.3-Compiler-Läufe nicht — "API Error: Claude's response exceeded the # 16000 output token maximum". ACHTUNG: 16000 ist hier KEIN bloßes Default — # die Session-Umgebung EXPORTIERT es bereits (CLI tragt das Modell-Built-in # in Child-Env), daher greift ${VAR:-32000} nie. Das Gate definiert eine # BODEN-GRANZE (Floor): geerbte Werte unter 32000 werden angehoben, # höhere bleiben erhalten (P-3-Override-Semantik). A/B-geprüft: 1698 # Zeilen abgebrochen bei 16000 vs 5002 Zeilen vollständig bei 32000. TOKEN_FLOOR=32000 _cur_tokens="${CLAUDE_CODE_MAX_OUTPUT_TOKENS:-}" case "$_cur_tokens" in ''|*[!0-9]*) export CLAUDE_CODE_MAX_OUTPUT_TOKENS="$TOKEN_FLOOR" ;; *) if [ "$_cur_tokens" -lt "$TOKEN_FLOOR" ]; then export CLAUDE_CODE_MAX_OUTPUT_TOKENS="$TOKEN_FLOOR"; fi ;; esac echo "CLAUDE_CODE_MAX_OUTPUT_TOKENS: $CLAUDE_CODE_MAX_OUTPUT_TOKENS (Floor $TOKEN_FLOOR, P-16)" 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. 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 ODER FAIL - ist der RELATIVE PFAD zum Käfig-Root mit Schrägstrichen (z.B. 'wiki/index.md' — NICHT 'wiki.index.md', NICHT absolut). - 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) echo "--- Verdict-Zeilen (Validator-Ausgabe, Diagnose) ---" printf '%s\n' "$VERDICTS" # P-17 (Re-Run-#3-Befund): Pfad-Token-Normalisierung — der Agent schrieb # 'wiki.alpha.md' (alte Prompt-Wortung „gepunktete Pfad") und kann absolute # oder backslash-Pfade liefern. Normalisiert auf Käfig-relativ ohne wiki/: # 'wiki/alpha.md', 'alpha.md', '/…/cage/wiki/alpha.md', 'wiki\.alpha.md' # -> 'alpha.md'. D.5 (Zählung) bleibt unverändert; D.4 matcht normalisiert. norm_verdict_path() { local p p="${1//\\//}" # Backslash -> Slash (bash-nativ, kein tr/Transport-Problem) p="${p#\"}"; p="${p%\"}" p="${p#\'}"; p="${p%\'}" p="${p#./}" # Absolute/relative Vorpfad bis 'wiki/' abschneiden (auch führende 'wiki/'). case "$p" in */wiki/*) p="${p##*/wiki/}" ;; esac case "$p" in wiki/*) p="${p#wiki/}" ;; esac case "$p" in wiki.*) p="${p#wiki.}" ;; esac # Dotted-Form der alten Prompt-Wortung ('gepunktete Pfad'): 'concepts.neg-1.md' # / 'alpha.md' -> alle Punkte zu '/', dann das LETZTE '/' (die .md-Extension) # wiederherstellen. Nur ohne bereits vorhandenes '/' (sonst wäre es schon Slash-Form). if [[ "$p" == *.* && "$p" != */* ]]; then p="${p//./\/}" p="${p%/*}.${p##*/}" fi printf '%s' "$p" } # P-6: exakt EIN Verdict pro Datei (normalisierte Pfad-Vorkommen zählen) DUP_FAIL=0 while IFS= read -r wmd; do [ -z "$wmd" ] && continue rel="${wmd#wiki/}" n=$(printf '%s\n' "$VERDICTS" | while IFS= read -r v; do [ -z "$v" ] && continue vp=$(printf '%s\n' "$v" | awk '{print $2}') [ "$(norm_verdict_path "$vp")" = "$rel" ] && echo X done | grep -c '^X$' || 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 < "$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) # P-17: Pfad-Normalisierung auch hier (gleicher Mechanismus wie D.4) — # normalisiertes Käfig-relativ 'concepts/' wird matcht. neg_count() { printf '%s\n' "$NEG_VERDICTS" | while IFS= read -r v; do [ -z "$v" ] && continue case "$v" in FAIL\ *) : ;; *) continue ;; esac vp=$(printf '%s\n' "$v" | awk '{print $2}') [ "$(norm_verdict_path "$vp")" = "concepts/$1" ] && echo X done | grep -c '^X$' || true } pos_count() { printf '%s\n' "$NEG_VERDICTS" | while IFS= read -r v; do [ -z "$v" ] && continue case "$v" in SUCCESS\ *) : ;; *) continue ;; esac vp=$(printf '%s\n' "$v" | awk '{print $2}') [ "$(norm_verdict_path "$vp")" = "concepts/$1" ] && echo X done | grep -c '^X$' || 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: candidates: decision: sources_added: 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 ", wobei 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= AT=" # 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 <&2 G7_BAD=$((G7_BAD+1)) fi done done <&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