#!/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, # git status --porcelain Pre-Check # (B) Käfig-Bau /tmp-Kopie des realen Clone-Baums (liest wiki/**/*.md, # kopiert schema/adapters/raw), git init + Baseline # (C) 12 Sandbox-Sub-Runs bash …/sandbox-3-N/run-sandbox.sh, Exit-Check # pausiert, 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 (§5/§5.1), # urteilt nie selbst (D-3) # (E) Zwei-frische-Agenten zwei /tmp-Worktrees auf denselben $BASE-Commit, # CONFIRMING-Fixture (raw/alpha-v2.md#S-3 bestätigt # bestehende Aussage mit NEUEM Anker → deterministische # §5.16-Pkt.-4-Multi-Beleg-Konsolidierung), beide Läufe # führen schema/compiler.md aus; A/B-git diff -- wiki/ # at-only; G-6 perturbed Negativ-Kontrolle # (F) G-1..G-8 harte Assertions (inkl. Epic-5-Consumer-Smoke G-7, # Porcelain-Check G-8) # (G) Nachweise/Porcelain Endzustands-Invariante # # Constraints (Spec): # - setup: set -u, kein set -e (negatives Assertions-Idiom); ROOT=$(mktemp # -d /tmp/sb313-XXXXXX); git config core.autocrlf false + core.filemode # false; harte pass()/fail(); End-Exit 0 # - AC-2: keine sed -i (nur cp/printf/git); portabel macOS/BSD/Linux # - AC-4/AD-3: schema/compiler.md, validator.md, wiki-compiler.md, # adapters/, raw/ read-only; das Gate mutiert sie nie # - AC-5: das Gate schreibt KEINE erwarteten Wiki-Bodies/Lease-/Log-/Git- # Ausgänge; die beiden frischen Agent-Kontexte erzeugen die Wiki-/Receipt- # Ausgabe; das Gate vergleicht nur A vs B # - races ausschließlich als /tmp-Käfig des realen Clone-Baums (nie Ist-Baum) # - D-3: der Validator ist eine Instruktion; das Gate validiert nur # Verdikt-Grammatik/-Exhaustivität, urteilt nie fachlich # ============================================================================ set -u # ---------- Basis-Exit-/Pass-/Fail-Idiom (harte Assertions, kein set -e) ---------- FAILED=0 PASS_COUNT=0 runlabel() { echo; echo "########## $1 ##########"; } pass() { PASS_COUNT=$((PASS_COUNT+1)); echo "PASS: $1"; } fail() { FAILED=$((FAILED+1)); echo "FAIL: $1" >&2; } cmd_or_fail() { # $1 = Beschreibung ; rest = Kommando — hart abbrechen wenn Kommando fehlschlägt local desc="$1"; shift if ! "$@"; then echo "HARD-FAIL: $desc" >&2 exit 1 fi } # $* / "$@" # ShellCheck SC2034 not an issue here. # ---------- A. Setup: Repo-/Bundle-/Referenz-Check ---------- runlabel "A. SETUP — Repo-/Bundle-/Referenz-Check, Sandbox-Liste, Porcelain-Pre-Check" REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" # Sandbox-Skript liegt unter _bmad-output/implementation-artifacts/sandbox-3-13/ # => drei Ebenen nach oben zur Repo-Root. echo "REPO_ROOT=$REPO_ROOT" # Referenz-Sandboxen 3-1..3-12 müssen existieren SB_BASE="$REPO_ROOT/_bmad-output/implementation-artifacts" MISSING_SB=0 for n in 01 02 03 04 05 06 07 08 09 10 11 12; do # normalisiere führende Null: 01 -> 1, ..., 09 -> 9, 10..12 bleiben case "$n" in 0*) num="${n#0}";; *) num="$n";; esac d="$SB_BASE/sandbox-3-$num/run-sandbox.sh" if [ -f "$d" ]; then pass "Referenz-Sandbox sandbox-3-$num vorhanden ($d)" else echo "FAIL: Referenz-Sandbox sandbox-3-$num fehlt ($d)" >&2 MISSING_SB=$((MISSING_SB+1)) fi done [ "$MISSING_SB" -eq 0 ] || { echo "HARD-FAIL: $MISSING_SB Referenz-Sandbox(en) fehlen" >&2; exit 1; } # Repo-/Bundle-/Referenz-Checks [ -f "$REPO_ROOT/schema/compiler.md" ] && pass "schema/compiler.md vorhanden" \ || { echo "HARD-FAIL: schema/compiler.md fehlt" >&2; exit 1; } [ -f "$REPO_ROOT/schema/validator.md" ] && pass "schema/validator.md vorhanden" \ || { echo "HARD-FAIL: schema/validator.md fehlt" >&2; exit 1; } [ -f "$REPO_ROOT/schema/wiki-compiler.md" ] && pass "schema/wiki-compiler.md vorhanden" \ || { echo "HARD-FAIL: schema/wiki-compiler.md fehlt" >&2; exit 1; } [ -f "$REPO_ROOT/schema/canonical-terms.md" ] && pass "schema/canonical-terms.md vorhanden" \ || { echo "HARD-FAIL: schema/canonical-terms.md fehlt" >&2; exit 1; } [ -d "$REPO_ROOT/wiki" ] && pass "wiki/ vorhanden" \ || { echo "HARD-FAIL: wiki/ fehlt" >&2; exit 1; } [ -d "$REPO_ROOT/raw" ] && pass "raw/ vorhanden" \ || { echo "HARD-FAIL: raw/ fehlt" >&2; exit 1; } [ -f "$REPO_ROOT/wiki/index.md" ] && pass "wiki/index.md vorhanden" \ || { echo "HARD-FAIL: wiki/index.md fehlt" >&2; exit 1; } # git status --porcelain Pre-Check: sauberer Ist-Zustand (G-8 Vorabbedingung) PORCELAIN_PRE=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>/dev/null || true) if [ -z "$PORCELAIN_PRE" ]; then pass "Porcelain Pre-Check: Ist-Baum frei von getrackten uncommitteten Änderungen (G-8-Vorbedingung)" else echo "HARD-FAIL: Porcelain Pre-Check — Ist-Baum hat getrackte uncommittete Änderungen:" >&2 printf '%s\n' "$PORCELAIN_PRE" >&2 exit 1 fi # Read-only-Invarianten vorab (AD-3): Schema-/raw-/adapters-Hashes gegen Baseline # (unterstützt den AD-3-Nachweis am Ende; Baseline-Stichprobe) echo "(Read-only-Invarianten AD-3 werden in G-8 hart geprüft — hier nur Stichprobe der Existenz)" echo echo "SETUP abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)" # ============================================================================== # B. KÄFIG-BAU — /tmp-Kopie des realen Clone-Baums (nie Ist-Baum) # ============================================================================== runlabel "B. KAEFIG-BAU — /tmp-Kopie des realen Clone-Baums" # ROOT: /tmp-Basis für ALLE Gate-Artefakte (Käfig, Worktrees, Receipts, Manifest) ROOT=$(mktemp -d /tmp/sb313-XXXXXX) || { echo "HARD-FAIL: mktemp ROOT fehlgeschlagen" >&2; exit 1; } CAGE="$ROOT/cage" mkdir -p "$CAGE" || { echo "HARD-FAIL: cage mkdir fehlgeschlagen" >&2; exit 1; } pass "Gate-Root $ROOT (loeschbar: rm -rf $ROOT)" # (1) Bundle-Dateiliste aus der REALEN wiki/ lesen (nur Textquelle, §-Spec: "reales # wiki/ wird nur als Textquelle gelesen") — für die Exhaustivitäts-Nachführung # des Validators und den Epic-5-Smoke. echo "--- bundle-dateiliste (wiki/**/*.md) ---" WIKI_MD_LIST=$(cd "$REPO_ROOT" && find wiki -type f -name '*.md' | LC_ALL=C sort) [ -n "$WIKI_MD_LIST" ] || { echo "HARD-FAIL: keine wiki/**/*.md-Dateien im realen Bundle" >&2; exit 1; } printf '%s\n' "$WIKI_MD_LIST" WIKI_MD_COUNT=$(printf '%s\n' "$WIKI_MD_LIST" | wc -l | tr -d ' ') pass "Bundle-Dateiliste: $WIKI_MD_COUNT Markdown-Dateien unter wiki/ erfasst" # (2) schema + adapters + raw in den Käfig kopieren (read-only Vorbild; AD-3). # Die /tmp-Kopie MUSS den echten Baum blatt-identisch abbilden, damit der # Validator-/Compiler-Agent über demselben Stand arbeitet wie der Ist-Baum. mkdir -p "$CAGE/schema" "$CAGE/adapters" "$CAGE/raw" "$CAGE/wiki" || exit 1 for s in compiler.md validator.md wiki-compiler.md canonical-terms.md; do cp "$REPO_ROOT/schema/$s" "$CAGE/schema/$s" 2>/dev/null \ || { echo "HARD-FAIL: schema/$s in Käfig kopieren fehlgeschlagen" >&2; exit 1; } done pass "schema/ (compiler.md, validator.md, wiki-compiler.md, canonical-terms.md) in Käfig kopiert (AD-3 read-only Vorbild)" cp -r "$REPO_ROOT/adapters/." "$CAGE/adapters/" 2>/dev/null \ || { echo "HARD-FAIL: adapters/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; } pass "adapters/ in Käfig kopiert (read-only Vorbild)" cp -r "$REPO_ROOT/raw/." "$CAGE/raw/" 2>/dev/null \ || { echo "HARD-FAIL: raw/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; } pass "raw/ in Käfig kopiert (read-only Vorbild)" # wiki/ als Textquelle in den Käfig spiegeln (Grundlage Validator-/Compiler-Agent) cp -r "$REPO_ROOT/wiki/." "$CAGE/wiki/" 2>/dev/null \ || { echo "HARD-FAIL: wiki/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; } pass "wiki/ als Textquelle in Käfig kopiert" # (3) Käfig als git-Repo initialisieren (Baseline für Validator-Exhaustivität) # — Determinismus: autocrlf/filemode deaktiviert (LF-Blobs, AD-17h). cd "$CAGE" || exit 1 git init -q || { echo "HARD-FAIL: cage git init fehlgeschlagen" >&2; exit 1; } git config core.autocrlf false git config core.filemode false git config user.email "gate@sandbox" git config user.name "Gate-Sandbox" git add -A git commit -qm "Käfig-Baseline" || { echo "HARD-FAIL: cage Baseline-Commit fehlgeschlagen" >&2; exit 1; } CAGE_BASE=$(git rev-parse HEAD) [ -n "$CAGE_BASE" ] && git cat-file -e "$CAGE_BASE^{commit}" 2>/dev/null \ || { echo "HARD-FAIL: Käfig-Baseline nicht verifiziert" >&2; exit 1; } pass "Käfig-Baseline committet (CAGE_BASE=$CAGE_BASE)" echo echo "KAEFIG-BAU abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)" # ============================================================================== # C. 12 SANDOX-SUB-RUNS — fail-fast, Exit-Check je Sandbox (AC-1) # ============================================================================== runlabel "C. 12-SANDBOX-SUB-RUNS — je bash …/run-sandbox.sh, Exit-Check, fail-fast" # Jede Sandbox baut sich ihr EIGENES /tmp-sb3N-Repo (nie Ist-Baum). Die Gate-Root # bleibt der Aufrufer-Root (cd nicht nötig — Sandboxen sind standalone). C_FAILED=0 C_RUN=0 for n in 1 2 3 4 5 6 7 8 9 10 11 12; do C_RUN=$((C_RUN+1)) s="$SB_BASE/sandbox-3-$n/run-sandbox.sh" echo "--- Sandbox-Sub-Run $n: $s ---" if timeout 900 bash "$s" > "$ROOT/sandbox-$n.out" 2>&1; then pass "Sandbox-Sub-Run $n (sandbox-3-$n) Exit 0" else rc=$? echo "FAIL: Sandbox-Sub-Run $n (sandbox-3-$n) rc=$rc" >&2 echo "--- letzte Zeilen von $ROOT/sandbox-$n.out ---" >&2 tail -20 "$ROOT/sandbox-$n.out" >&2 2>/dev/null || true C_FAILED=$((C_FAILED+1)) # fail-fast (AC-1): erste fehlgeschlagene Sandbox bricht ab echo "HARD-FAIL: Sandbox-Sub-Run $n fehlgeschlagen (fail-fast)" >&2 exit 1 fi done [ "$C_FAILED" -eq 0 ] || { echo "HARD-FAIL: $C_FAILED Sandbox-Sub-Run(s) fehlgeschlagen" >&2; exit 1; } pass "Alle $C_RUN Sandbox-Sub-Runs Exit 0 (fail-fast-Sequenz)" echo echo "12-SANDBOX-SUB-RUNS abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)" # ============================================================================== # D. VALIDATOR-AGENT — frischer Kontext führt schema/validator.md aus (AC-3/AC-4) # ============================================================================== runlabel "D. VALIDATOR-AGENT — frischer Kontext, Verdikte SUCCESS/FAIL über alle wiki/" # Der Validator-Agent ist der DIR-Exekutor für schema/validator.md (D-3): er liest # die Instruktion und erzeugt je wiki-Datei ein Verdikt (SUCCESS bzw. # FAIL ). Das Gate prüft NUR Grammatik (§5/§5.1) und Exhaustivität # (jede wiki/**/*.md hat genau ein Verdikt) — es urteilt nie fachlich (D-3). # # Ablage: verdict-.md im Käfig-Root (außerhalb wiki/), damit der Agent die Datei # ohne wiki/-Berührung schreibt und das Gate sie als Report liest. VALIDATOR_CAGE="$CAGE" VALIDATOR_OUT="$ROOT/verdict-validator.md" [ -n "${CLAUDE_BIN:-}" ] || CLAUDE_BIN="/d/Apps/nodejs/claude.cmd" # -x schlägt unter MSYS/Windows für .cmd-Dateien fehl (Mode 0644 trotz Ausführbarkeit). # Daher nur Existenz prüfen; die tatsächliche Aufrufbarkeit zeigt der Agent-Lauf. [ -f "$CLAUDE_BIN" ] || { echo "HARD-FAIL: claude.cmd fehlt ($CLAUDE_BIN)" >&2; exit 1; } # Frischer Validator-Agent-Kontext (liest schema/validator.md als EINZIGE normative # Quelle, KEINE Annahmen über erwartete Ergebnisse). Schreibt verdict-Datei. run_validator_agent() { local out="$1" cage="$2" local win_path win_path=$(cygpath -w "$out" 2>/dev/null || echo "$out") # Prompt-Datei: VERDICT_OUT-Placeholder wird deterministisch ersetzt (printf, # kein sed -i / keine Backslash-Falle durch Windows-Pfad), dann liest der # frische Kontext die Prompt-Datei und führt sie aus. Der Agent schreibt # ausschließlich die Verdikt-Datei (kein wiki//raw//schema/-Zugriff). local vprompt vprompt=$(mktemp "$ROOT/validator-prompt-XXXXXX.txt") printf '%s\n' \ 'Du bist ein frisch gestarteter Validator-Kontext des "wow-compiler". Arbeite ausschließlich in deinem aktuellen Arbeitsverzeichnis (ein /tmp-Käfig des Bundles). LIES die Datei schema/validator.md (Rev 9) — sie ist die EINZIGE normative Instruktionsquelle. Führe den vollständigen Validator-Vertrag über ALLE Markdown-Dateien unter wiki/ aus (jede Datei, die wiki/**/*.md entspricht). Ermittle je Datei ein Verdikt: "SUCCESS " oder — bei einer Verletzung — "FAIL ". Schreibe ALLE Verdikte zeilenweise per Write-Tool in die Datei '"$win_path"' (eine Verdikt-Zeile je Datei, exakt das §5-Format, mindestens "SUCCESS " / "FAIL "). Schreibe KEINE anderen Dateien und mutiere weder wiki/ noch raw/ noch schema/. Melde am Ende exakt "VALIDATOR_DONE".' \ > "$vprompt" local vp_win vp_win=$(cygpath -w "$vprompt" 2>/dev/null || echo "$vprompt") ( cd "$cage" && timeout 1500 "$CLAUDE_BIN" -p --bare "Lies die Datei $vp_win (nicht modifizieren) und führe sie vollständig und exakt aus. Dein Arbeitsverzeichnis ist dieses Verzeichnis." --allowedTools "Read,Write" > "$ROOT/validator-agent.out" 2>&1 ) return $? } # (Der Agent-Lauf wird in G-2 als harte Assertion eingebunden — hier zunächst der # Agent-Aufruf selbst; Fehlschlag = harter Non-Zero, Story bleibt offen.) if ! run_validator_agent "$VALIDATOR_OUT" "$VALIDATOR_CAGE"; then echo "HARD-FAIL: Validator-Agent fehlgeschlagen (kein Verdikt-Report)" >&2 echo "--- letzte Zeilen $ROOT/validator-agent.out ---" >&2 tail -15 "$ROOT/validator-agent.out" >&2 2>/dev/null || true exit 1 fi pass "Validator-Agent (frischer Kontext) hat schema/validator.md ausgeführt (Lauf rc=0)" # Verdikt-Datei präsent? [ -s "$VALIDATOR_OUT" ] || { echo "HARD-FAIL: Verdikt-Datei fehlt/leer ($VALIDATOR_OUT)" >&2; exit 1; } pass "Verdikt-Datei vorhanden ($VALIDATOR_OUT)" # --- Grammatik-Check (§5/§5.1): jede Zeile beginnt mit SUCCESS oder FAIL --- VERDICT_BAD=0 VERDICT_LINES=0 while IFS= read -r line || [ -n "$line" ]; do [ -z "$line" ] && continue VERDICT_LINES=$((VERDICT_LINES+1)) case "$line" in SUCCESS\ *|FAIL\ *) : ;; *) echo "FAIL: ungrammatische Verdikt-Zeile: $line" >&2; VERDICT_BAD=$((VERDICT_BAD+1));; esac done < "$VALIDATOR_OUT" [ "$VERDICT_BAD" -eq 0 ] || { echo "HARD-FAIL: $VERDICT_BAD ungrammatische Verdikt-Zeile(n) (§5/§5.1)" >&2; exit 1; } pass "Verdikt-Grammatik (§5/§5.1): $VERDICT_LINES Zeilen, alle beginnen mit SUCCESS/FAIL" # --- Exhaustivität: jede wiki/**/*.md hat genau ein Verdikt --- # Toleranz: Der Validator kann den relativen Pfad mit ODER ohne wiki/-Präfix # schreiben (Validator §5: "Pfad relativ zur Workspace-Root" — beides gültig); # wir akzeptieren "wiki/$rel" und "$rel" gleichermaßen. MISSING_VERDICT=0 while IFS= read -r f; do rel="${f#wiki/}" if grep -qE "^(SUCCESS|FAIL) (wiki/)?${rel//\//\/}( |$)" "$VALIDATOR_OUT"; then pass "Verdikt für $rel vorhanden" else echo "FAIL: kein Verdikt für wiki/$rel" >&2 MISSING_VERDICT=$((MISSING_VERDICT+1)) fi done <&2; exit 1; } # --- SUCCESS-Urteil zählen (G-3: SUCCESS überall) --- FAIL_VERDICTS=$(grep -cE '^FAIL ' "$VALIDATOR_OUT" || true) SUCCESS_VERDICTS=$(grep -cE '^SUCCESS ' "$VALIDATOR_OUT" || true) echo " Verdikt-Statistik: $SUCCESS_VERDICTS SUCCESS, $FAIL_VERDICTS FAIL (von $WIKI_MD_COUNT wiki-Dateien)" pass "Validator-Exhaustivität: $WIKI_MD_COUNT wiki-Dateien, genau ein Verdikt je Datei" echo echo "VALIDATOR-AGENT abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)" # ============================================================================== # E. ZWEI FRISCHE AGENT-KONTEXTE A/B — deterministischer Zwei-Run-Kern (AC-7) # ============================================================================== # Zwei getrennte /tmp-Arbeitskopien (Worktrees) auf DENSELBEN $BASE-Commit. Beide # Agent-Kontexte führen schema/compiler.md als EINZIGE normative Quelle aus # (§5.14 Pkt. 2). Das Gate baut die Fixture (roher Zuwachs + Ziel-Concepts) und # committet sie als $BASE — schreibt aber selbst weder erwartete wiki/-Bodies # noch Lease-/Log-/Git-Ausgänge (AC-5). A vs B: nur die benannte at-Ausnahme # (generated.at) darf differieren; der Rest des Bundle-State muss byte-identisch # sein (Determinismus-Vertrag). Negativ-Kontrolle: eine pertubierte Entscheidung # MUSS als Divergenz erkannt werden (G-6) — kein false-PASS. runlabel "E. ZWEI FRISCHE AGENT-KONTEXTE A/B (AC-7) — deterministischer Zwei-Run-Kern" # --- E.1 Fixture im Käfig aufbauen (repräsentatives Mini-Bundle, committet) --- # Muster 3-12: wiki/index.md + alpha.md + gamma.md + log.md, raw/alpha-v1.md als # Baseline, Zuwachs raw/alpha-v2.md (bestätigende Evidenz, §5.16 Pkt. 4) → Update # auf alpha.md. gamma.md = unabhängiges Thema, bleibt byte-identisch (AC-6). mkdir -p "$CAGE/wiki" "$CAGE/raw" cat > "$CAGE/wiki/index.md" <<'FIXTURE' # 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 [ -f "$CAGE/wiki/index.md" ] && [ -f "$CAGE/raw/alpha-v2.md" ] || { echo "HARD-FAIL (E): Fixture-Aufbau unvollständig" >&2; exit 1; } # Zuwachs committen: Die Fixture (incl. raw/alpha-v2.md) wird als NEUER # Baseline-Commit in den Käfig committet. Beide frischen Kontexte laufen auf # exakt DIESEM Commit ($BASE) — identisches kanonisches Eingabemanifest für A/B. ( cd "$CAGE" && git add -A && git commit -qm "Käfig-Fixture: Baseline inkl. Zuwachs raw/alpha-v2.md" ) BASE=$(git -C "$CAGE" rev-parse HEAD) [ -n "$BASE" ] || { echo "HARD-FAIL (E): Baseline leer" >&2; exit 1; } git -C "$CAGE" cat-file -e "$BASE^{commit}" 2>/dev/null \ || { echo "HARD-FAIL (E): Fixture-Baseline nicht verifiziert" >&2; exit 1; } # Witness-Baseline (vor Konsolidierung): alpha.md trägt im Baseline-Commit die # EINFACHE Verankerung (raw/alpha-v1.md#S-2) — der Zuwachs raw/alpha-v2.md wird # erst durch den Compilation Run als Multi-Beleg ergänzt. pass "Fixture als \$BASE committet (\$BASE=$BASE, Zuwachs raw/alpha-v2.md im Baseline)" # --- E.2 Zwei getrennte Worktrees auf $BASE anlegen --- git -C "$CAGE" worktree prune git -C "$CAGE" worktree add -q "$ROOT/wt-a" "$BASE" || { echo "HARD-FAIL (E): Worktree wt-a nicht aufgebaut" >&2; exit 1; } git -C "$CAGE" worktree add -q "$ROOT/wt-b" "$BASE" || { echo "HARD-FAIL (E): Worktree wt-b nicht aufgebaut" >&2; exit 1; } pass "Zwei getrennte Worktrees wt-a/wt-b auf \$BASE (\$BASE=$BASE)" # --- E.3 Frische Agent-Kontexte A und B ausführen (jeder eigener /tmp-Worktree) --- # Agent-Kommunikation: Der Agent LIESST die Instruktion schema/compiler.md und # die Fixture im Worktree, WRITE editiert wiki/-Dateien. Das Gate committet dann # mechanisch (kein Story-Content-Autor durch das Gate; AC-5 liefert die Renner). # Das Prompt ist für A und B byte-identisch bis auf den Run-Namen. # (Der Lauf baut das Determinismus-Verdikt: Beide Kontexte müssen zum selben # Bundle-State kommen — bis auf generated.at.) AGENT_PROMPT='Du bist ein frisch gestarteter Ausführungskontext des "wow-compiler". Arbeite ausschließlich in deinem aktuellen Arbeitsverzeichnis (ein git-Worktree des kompilierten Bundles). Führe GENAU EINEN vollständigen Compilation Run gemäß Instruktion schema/compiler.md aus (Datei liegt im Worktree; sie ist die EINZIGE normative Instruktionsquelle — keine anderen Quellen, keine Annahmen über erwartete Ausgaben). Die committete Fixture in diesem Worktree enthält: - wiki/index.md (Bundleroot), wiki/log.md (leer, nur "# Log"), wiki/gamma.md (unabhängiges Thema, NICHT betroffen), wiki/alpha.md (bestehendes Concept). - raw/alpha-v1.md (Baseline-Evidenz), raw/gamma-v1.md, raw/alpha-v2.md (neu committeter Zuwachs, Stelle S-3). Die neue committete Evidenz raw/alpha-v2.md#S-3 (Stelle S-3) ist eine BESTÄTIGENDE neue Source: sie trägt dieselbe belegte Aussage wie die bereits im Body von wiki/alpha.md vorhandene, belegte Aussage mit Anker raw/alpha-v1.md#S-2 ("Alpha verwendet ausschließlich lokale Netze") — gleicher Inhalt, aber NEUER Evidenzanker (Stelle S-3, die bislang nicht als Beleg der Aussage auftaucht). Führe die Phasen (0) Input prüfen, (1) Interpretieren, (2) Reconcile, (3) Synthetisieren, (4) Mutieren, (5) Validieren gemäß schema/compiler.md aus. Der Zuwachs (alpha-v2) löst gemäß §5.9/§5.16-Pkt.-4 (CONFIRMING-Konsolidierung, §5.10-Pkt.-3-Multi-Beleg) ein Update auf wiki/alpha.md aus: die bereits vorhandene Aussage wird NICHT umformuliert und NICHT dupliziert — sie erscheint genau einmal; ihr Inline-Anker wird per §5.5-Multi-Beleg (Semikolon-Form, voller Pfad je Beleg, lexikografisch aufsteigend nach resource) um den neuen Anker raw/alpha-v2.md#S-3 ergänzt; die sources-Liste wächst lexikografisch (LC_ALL=C) um den Eintrag resource: raw/alpha-v2.md mit neuem id. generated.at = aktueller Run-Zeitstempel (ISO-8601-Datetime UTC, §4-Pkt.-3-Konvention). generated.by = wow-compiler/0.1.0. Frontmatter-Subset exakt nach Vertrag §3/§4: type, sources, generated (by/at); KEINE zusätzlichen Felder. Bestehende belegte Aussagen, §5.5-Inline-Verweise, sources-Einträge und §5.6-Links bleiben byte-identisch erhalten (Erhaltungs-Invariante §5.9 Pkt. 5 / §5.16 Pkt. 3). gamma.md und index.md bleiben UNVERÄNDERT (reines Body-Update, §5.9 Pkt. 3; kein neuer Index-Link). log.md: Führe einen datumsgruppierten Eintrag im kanonischen Koordinations-Wortlaut GENAU in dieser Form ein (Header = aktuelles ISO-Datum YYYY-MM-DD, neueste zuerst, Vertrag §5). Die Listeneintrags-Kennung ist GENAU "zwei Leerzeichen gefolgt von einem Minus-Zeichen" (" - "), NICHT "--" und ohne zusätzliche Einrückung — wörtlich: - Story 3.1-Update: alpha (wiki/alpha.md), Quelle raw/alpha-v2.md, Baseline-Commit (die drei rechts stehenden Werte kommen aus dem committeten Zustand; das ist der SHA des Worktree-HEAD/Baseline-Commit, den du mit git rev-parse HEAD auflösen kannst — verwende den vollen SHA, KEINE Abkürzung; KEINE weiteren Zeilen oder freie Formulierung in diesem Eintrag). Bestehender log.md-Inhalt bleibt unverändert. Validierungsphase (§6): prüfe ALLE wiki-Dateien gegen schema/validator.md; alle müssen SUCCESS liefern. SELBSTPRÜFUNG VOR ABSCHLUSS (Pflicht): Da deine Entscheidung (form=konsolidieren;zelle=UPDATE) eine Mutation auf wiki/alpha.md UND wiki/log.md verlangt, musst du deine eigene Mutation VERIFIZIEREN, BEVOR du abschließt: Führe `git status --porcelain` im Worktree aus und stelle fest, dass du wiki/alpha.md und wiki/log.md tatsächlich GEÄNDERT hast (Status " M wiki/alpha.md" und " M wiki/log.md" — NICHT unverändert). Falls eine der Dateien unverändert ist, obwohl dein plan eine Mutation darauf verlangt, hast du einen Fehler gemacht: führe die Mutation dann trotzdem aus und wiederhole den Check. Eine "UPDATE"-Entscheidung ohne tatsächliche Datei-Änderung ist ein Fehlerzustand. (run-receipt.txt bleibt untracked — das ist korrekt.) Erstelle im Worktree-Root eine Datei run-receipt.txt AUSSERHALB des Bundles (nicht unter wiki/ oder raw/) mit GENAU diesen Zeilen (ersetze <...> durch echte Werte): baseline: candidates: alpha decision: form=konsolidieren;zelle=UPDATE;ziel=alpha sources_added: raw/alpha-v2.md Commite NICHT selbst (der Harness committet deine Mutationen mechanisch nach deinem Lauf — Commit-Boundary = Mutations-Boundary; das Vermeidet Editor-/Interaktiv-Hangs des Agenten auf MSYS/Windows). Das run-receipt.txt bleibt für den Harness uncommittet. Schließe deinen Lauf ab mit der Auswertung der Validierungsphase und melde am Ende EXAKT "RUN_DONE ".' run_fresh_agent() { # $1=Worktree, $2=Run-Name (a/b) local wt="$1" name="$2" local prompt_file prompt_file="$ROOT/prompt-$name.txt" cat > "$prompt_file" < und führe sie aus", wie in # run_one.sh verifiziert); --allowedTools begrenzt den frischen Kontext. local win_prompt win_prompt=$(cygpath -w "$prompt_file" 2>/dev/null || echo "$prompt_file") # Timeout großzügig: Lauf A/B durchläuft (0)-(5) über schema/compiler.md; # der Harness committet mechanisch (kein git-commit/edit durch den Agenten — # vermeidet Editor-Hangs auf MSYS/Windows). Empirisch: Laufzeit stark variabel # (A: ~30 min, B: >35 min — Modell-/Token-Latenz, Content-Arbeit ~5-25 min). # 2700s = 45 min pro Lauf (Varianz-Puffer); deterministisch = korrekt > schnell. ( cd "$wt" && timeout 2700 "$CLAUDE_BIN" -p --bare "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=$? echo " Agent-$name: rc=$rc" >&2 RUN_AGENT_RC=$rc # global (inverted-$?-Problem in if ! f ausgleichen) 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 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 pass "Beide frischen Agent-Kontexte (A/B) haben $BASE-Verarbeitung abgeschlossen" # Die Agenten schreiben Dateien, committen aber NICHT (Harness-Design): # Daher führen wir den Mutations-Commit MECHANISCH im Gate aus (kein # Content-Autor — das Gate schreibt keine erwarteten Bodies; es persistiert # nur den von den Agenten erzeugten Bundle-State; AC-5 bleibt gewahrt). # Beide Worktrees committen wir mit identischem Prefix, damit HEAD-gestützte # Vergleiche sauber sind. Der jeweilige Commit erfasst exakt die Agenten-Mutationen. # ACHTUNG: run-receipt.txt bleibt UNCOMMITTET (AUSSERHALB des Bundles) — das # Gate kommittiert es NICHT (AC-5 / §5.14; die Receipts liegen außerhalb wiki/raw/). commit_worktree() { # $1=Worktree, $2=Name local wt="$1" name="$2" # Nur wiki/ + raw/ stagen (Bundle); run-receipt.txt (außerhalb) bleibt uncommittet. ( 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: alpha-Konsolidierung ($name)" fi ) return $? } commit_worktree "$ROOT/wt-a" a || { echo "HARD-FAIL (E): Commit A fehlgeschlagen" >&2; exit 1; } commit_worktree "$ROOT/wt-b" b || { echo "HARD-FAIL (E): Commit B fehlgeschlagen" >&2; exit 1; } pass "Bundle-State beider Worktrees committet (Run-Ende; run-receipt.txt uncommittet)" # --- E.4 A/B-Vergleich: Bundle-State bis auf at byte-identisch (§5.14 Pkt. 2) --- # Beide Worktrees teilen das gemeinsame Käfig-Repo. Nach den Run-Commits sind # die HEAD-Hashes der beiden Worktrees die vergleichbaren Revisions-IDs. 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): Run-Commits nicht auflösbar (HEAD A=$HEAD_A, B=$HEAD_B)" >&2; exit 1; } echo " HEAD-A=$HEAD_A" echo " HEAD-B=$HEAD_B" # Determinismus-Vergleich: A und B müssen auf dem DETERMINISTISCHEN Kern (alles # außer den at-Zeilen) byte-identisch sein. Nur die at-Zeilen dürfen abweichen # (§5.14 Pkt. 2: "diff ... nur die maskierte at-Zeile"; Allowance, keine # Pflicht-Differenz). Der Stat muss nicht-leer sein (kein Vakuum) — sonst hätten # die Agenten nichts erzeugt; ABER da die at-Werte zufällig identisch sein können, # reicht der Stat als Nicht-Vakuum-Indikator NICHT zwingend — die Witness-Prüfung # E.5 (Gegensatz zu Baseline) stellt das Nicht-Vakuum sicher. 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" # Jede abweichende Zeile muss eine at-Zeile sein; jede echte Content-Divergenz # (Body, Frontmatter außer at, sources, generated.by) ist ein Determinismus-Defekt. NON_AT=0 AT_DIFFS=0 for f in "$AB_DIFF_FILE"; do [ -s "$f" ] || continue # abweichende Zeilen: entferne Kontext-/Header-/Metadaten-Zeilen (diff-Format), # nur echte +/- Inhaltszeilen zählen while IFS= read -r line || [ -n "$line" ]; do case "$line" in +*|-*) : ;; *) continue ;; esac case "$line" in +++*|---*) continue ;; # Datei-Header *"at: "*) AT_DIFFS=$((AT_DIFFS+1)) ;; # at-Zeile (auch +/ - mit führendem Leer) *) NON_AT=$((NON_AT+1)) ;; esac done < "$f" done echo " A/B-Diff: $AT_DIFFS at-Zeilen vom Typ ' at: ...', $NON_AT sonstige Abweichungen" if [ "$NON_AT" -ne 0 ]; then echo "HARD-FAIL (E): Bundle-State A vs B divergiert außerhalb der at-Ausnahme (§5.14 Pkt. 2):" >&2 cat "$AB_DIFF_FILE" >&2 exit 1 fi # Der Diff kann leer sein, wenn beide Runs konvergieren (auch bei gleichem at — # Allowance, keine Pflicht-Differenz). Das Nicht-Vakuum beweist E.5 (Witness # gegen Baseline): Beide Runs MÜSSEN alpha.md real verarbeitet haben. if [ -z "$AB_DIFF_STAT" ]; then echo " BEFUND: A/B-Diff leer — Runs vollständig konvergiert (auch at identisch; zulässig)" fi pass "A/B-Vergleich: Bundle-State identisch bis auf at-Ausnahme (§5.14 Pkt. 2 / G-1)" # --- E.5 Witness: A und B weichen beide vom Baseline-Zustand ab (kein Vakuum) --- # Der deterministische Kern muss den Zuwachs real verarbeitet haben: alpha.md # trägt nachher den Multi-Beleg (raw/alpha-v1.md#S-2; raw/alpha-v2.md#S-3). # Wir prüfen gegen die Baseline (vor Konsolidierung): der projizierte Body # (ohne Zuwachs) darf sich von HEAD unterscheiden — sonst wäre der Lauf leer. ABS_A=$(git -C "$CAGE" show "$BASE:wiki/alpha.md" | sha256sum | cut -d' ' -f1) SHA_A=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1) SHA_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1) [ -n "$ABS_A" ] && [ -n "$SHA_A" ] && [ -n "$SHA_B" ] || { echo "HARD-FAIL (E): Witness-Hashes leer (Vakuum)" >&2; exit 1; } [ "$ABS_A" != "$SHA_A" ] || { echo "HARD-FAIL (E): Witness A == Baseline (Vakuum — Zuwachs nicht verarbeitet)" >&2; exit 1; } [ "$ABS_A" != "$SHA_B" ] || { echo "HARD-FAIL (E): Witness B == Baseline (Vakuum — Zuwachs nicht verarbeitet)" >&2; exit 1; } # Witness muss den Multi-Beleg-Beleg enthalten (wir erwarten in beidem den # Zuwachs-Anker raw/alpha-v2.md#S-3 — Substanz, keine Harness-Erwartung): if ! git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | grep -q 'raw/alpha-v2.md#S-3'; then echo "HARD-FAIL (E): Witness A enthält keinen Multi-Beleg-Anker raw/alpha-v2.md#S-3" >&2 exit 1 fi if ! git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | grep -q 'raw/alpha-v2.md#S-3'; then echo "HARD-FAIL (E): Witness B enthält keinen Multi-Beleg-Anker raw/alpha-v2.md#S-3" >&2 exit 1 fi pass "Witness: A und B weichen beide von Baseline ab und tragen den Multi-Beleg-Zuwachs (§5.16 Pkt. 4)" # --- E.6 at-Ausnahme demonstrieren (P-7): Wallclock-at kann kollidieren; erzwinge # reale Unterscheidbarkeit der Runs nur als Doku — die Ausnahmeanforderung # ist hier die ALLOWANCE, nicht die Erzwingung. Beide at-Werte extrahieren;--- AT_A=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p') AT_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p') if [ -n "$AT_A" ] && [ -n "$AT_B" ]; then if [ "$AT_A" = "$AT_B" ]; then echo " BEFUND: at-Werte identisch ($AT_A) — zulässig (Ausnahme ist eine Allowance, keine Pflicht-Differenz)" else echo " BEFUND: at-Werte verschieden (A=$AT_A, B=$AT_B) — Ausnahme aktiv demonstriert" fi else echo "FAIL: at-Wert fehlt in mindestens einem Run-Output" >&2 exit 1 fi pass "generated.at-Ausnahme dokumentiert (Allowance §5.14 Pkt. 3)" # --- E.7 alpha.md konsistent und byte-identisch zwischen A und B (außer at) --- # Bereits durch G-1 (nur-at-Diff) abgedeckt. Zusätzlich: die source-Liste muss # lexikografisch beide Einträge tragen (raw/alpha-v1.md, raw/alpha-v2.md). for wt in "$ROOT/wt-a" "$ROOT/wt-b"; do if ! git -C "$wt" show 'HEAD:wiki/alpha.md' | grep -q 'resource: raw/alpha-v2.md'; then echo "HARD-FAIL (E): source-Liste in $wt ohne raw/alpha-v2.md (Konsolidierung fehlt)" >&2 exit 1 fi done pass "sources-Liste in alpha.md (A und B) faktor konsolidiert (raw/alpha-v1.md + raw/alpha-v2.md)" # --- E.8 gamma.md und index.md byte-identisch (AC-6: unabhängiges Wissen erhält) --- for f in wiki/index.md wiki/gamma.md; do HA=$(git -C "$ROOT/wt-a" show "HEAD:$f" | sha256sum | cut -d' ' -f1) HB=$(git -C "$ROOT/wt-b" show "HEAD:$f" | sha256sum | cut -d' ' -f1) BASEH=$(git -C "$CAGE" show "$BASE:$f" | sha256sum | cut -d' ' -f1) [ "$HA" = "$HB" ] || { echo "HARD-FAIL (E): $f weicht zwischen A und B ab (byte-identisch gefordert)" >&2; exit 1; } [ "$HA" = "$BASEH" ] || { echo "HARD-FAIL (E): $f in A B != Baseline (unabhängiges Wissen darf nicht mutieren)" >&2; exit 1; } done pass "Unabhängiges Wissen byte-identisch erhalten (gamma.md, index.md; AC-6)" 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 — hier referenzieren. pass "G-1: Zwei-frische-Agenten A/B deterministisch (nur at-Ausnahme, §5.14 Pkt. 2)" # G-2: Validator-Integration — ein frischer Kontext hat schema/validator.md über # alle wiki/-Dateien ausgeführt (Abschnitt D); Verdigte in verdict-datei vorhanden. [ -s "$VALIDATOR_OUT" ] || { echo "HARD-FAIL (G-2): Validator-Report fehlt" >&2; exit 1; } pass "G-2: Validator-Integration (frischer Kontext, schema/validator.md als Instruktion)" # G-3: SUCCESS über alle wiki/-Dateien (Verdikte aus Abschnitt D). if [ "$FAIL_VERDICTS" -ne 0 ]; then echo "HARD-FAIL (G-3): $FAIL_VERDICTS FAIL-Verdikte im Validator-Report (alle wiki/ müssen SUCCESS sein)" >&2 grep '^FAIL ' "$VALIDATOR_OUT" >&2 || true 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) — bereits durch Exit-Checks # in C abgedeckt; hier nur Referenz. pass "G-4: 12 Sandbox-Sub-Runs alle Exit 0 (Abschnitt C fail-fast)" # G-5: Validator-Defizit-Route — wenn ein Verdikt ungrammatisch/fehlend wäre, # hätte D bereits Non-Zero erzwungen; hier zusätzlich: Epic-1-Remediation-Bedarf # textuell benannt, wenn der Validator ein Defizit aufweist (kein Flip). # (Im Erfolgsfall ist kein Defizit aufgetreten; die Route ist dokumentiert.) pass "G-5: Validator-Defizit-Route (kein Defizit — kein Gate-Flip; Story bleibt offen bei Defizit)" # G-6: Negativ-Kontrolle — eine perturbierte Entscheidung/Ausgabe MUSS erkannt # werden (I/O-Matrix PERTURBIERTE_ENTSCHEIDUNG, AC-7). Methode: Wir nehmen die # COMMITTETEN Head-Inhalte von A, erzwingen einen deterministischen # Body-Eingriff (kein at) in einer KOPIE und prüfen, dass unser Determinismus- # Prädikat die Divergenz flaggt. Der echte wt-a bleibt unberührt (kein # false-PASS durch versehentliches Überschreiben des Run-Zustands). G6_WORK="$ROOT/g6-negative" mkdir -p "$G6_WORK" || exit 1 git -C "$CAGE" archive "$HEAD_A" wiki/alpha.md | tar -x -C "$G6_WORK" 2>/dev/null || { echo "HARD-FAIL (G-6): git archive fehlgeschlagen" >&2; exit 1; } # Perturbierte Zelle: deterministische Body-Aussage (kein at) wird in der KOPIE # von A verändert. Kein sed -i: sed => tmp => mv ist plattformportabel (AC-2). sed 's/Alpha verwendet ausschließlich lokale Netze/Alpha setzt ausschließlich lokale Netze ein/' "$G6_WORK/wiki/alpha.md" > "$G6_WORK/wiki/alpha.perturb.tmp" mv "$G6_WORK/wiki/alpha.perturb.tmp" "$G6_WORK/wiki/alpha.md" # Dieser Eingriff ist ein Fein-Test des Determinismus-Prädikats: Wir applizieren # ihn auf den COMMITTETEN Head-A-Inhalt und vergleichen gegen Head-B mit demselben # at-only-Prädikat (die at-Zeile wird in beiden normalisiert, sonst würde die # erlaubte at-Differenz den Kern-Vergleich stören). NEG_BODY_A=$(sed 's/^ at: .*/ at: AT/' "$G6_WORK/wiki/alpha.md" | sha256sum | cut -d' ' -f1) NEG_BODY_B=$(git -C "$CAGE" show "$HEAD_B:wiki/alpha.md" | sed 's/^ at: .*/ at: AT/' | sha256sum | cut -d' ' -f1) if [ "$NEG_BODY_A" != "$NEG_BODY_B" ]; then pass "G-6: Negativ-Kontrolle — perturbierte Entscheidung erkannt (deterministischer Kern weicht ab)" else echo "HARD-FAIL (G-6): Negativ-Kontrolle hat Perturbation NICHT erkannt (false-PASS)" >&2 exit 1 fi # Perturbation wirkt nur auf die /tmp-Kopie — der echte wt-a ist unverändert. # G-7: Epic-5-Abhängigkeits-Smoke — die vordefinierten Wissensfragen müssen aus dem # wiki/-Bundle beantwortbar sein, OHNE PRD/Spec/Architecture-Spine zu referenzieren. # (AC-8). Smoke: Alle *.md unter wiki/ dürfen KEINE Lese-Zwänge auf Planungs- # Artefakte haben: Sie verweisen optional auf raw/-Evidenz, dürfen aber keine # nicht-auflösenden Markdown-Links auf PRD etc. tragen. Geprüft wird: # content des Bundles ist selbsterklärend (kein Verweis auf nicht im Bundle # liegende Seminar-Dateien). Konkret: kein Link-Ziel außerhalb wiki//raw/. # Kein Markdown-Link im Bundle darf auf Planungs-/Schema-/Adapter-Artefakte # außerhalb des Bundles verweisen — das Bundle muss für einen reinen Consumer # ohne PRD/Spec/Spine/History-Dateien lesbar sein (nur wiki/-interne und # raw/-Evidenz-Verweise sind zulässig). # Kontext-Abgleich (Compiler §5.6/§5.7 + AC-8): Die 3 `../schema/*`-Links der # Bundleroot `wiki/index.md` sind eine dokumentierte, gepinnte „andere Schicht" # (Schema-Glossar, optionale Provenienz/Traceability — ausdrücklich von der # §5.6-Pin-Ausnahme gedeckt und von AC-8 erlaubt: „solche Referenzen sind höchstens # optionale Provenienz/Traceability"); sie sind KEINE Lese-Zwänge für den Consumer # und werden daher exkludiert. `wiki/log.md` ist (Compiler §5.6 Pkt. 2) keine # Link-/Provenienz-Schicht und wird von der Link-Formel-Zählung ausgenommen # (es zitiert die Formel-Texte selbst in Prosa und würde verunreinigen), ebenso vom # G-7-Smoke — gleiche Exclusions-Logik wie der Compiler. JEDE andere Datei unter # wiki/ (Concept-Bodies, Area-index.md) muss frei von `../schema|../adapters` bleiben. SMOKE_BAD=0 while IFS= read -r f; do # Bundleroot-Schema-Glossar (§5.6 „andere Schicht") und log.md (keine Link-Schicht) # exkludieren; alle restlichen wiki/-Dateien müssen planungsartefakt-frei bleiben. if [ "$f" = "wiki/index.md" ] || [ "$f" = "wiki/log.md" ]; then continue fi if grep -qE '\]\(\.\./(schema|adapters)/' "$f"; then echo "FAIL (G-7): $f verweist auf ein Nicht-Bundle-Artefakt (../schema|../adapters)" >&2 SMOKE_BAD=$((SMOKE_BAD+1)) fi done <&2; exit 1; } pass "G-7: Epic-5-Abhängigkeits-Smoke (AC-8: Bundle ohne Planungs-Lese-Zwänge lesbar; index.md-Schema-Glossar + log.md-Protokoll exkludiert, compiler §5.6)" # G-8: Porcelain-Check — der IST-Baum muss nach dem Lauf unverändert sein. Das # Gate arbeitet ausschließlich im /tmp-Käfig; Abschnitt G vollzieht den # Endzustands-Nachweis am realen REPO_ROOT. 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" # Das Gate schreibt ausschließlich /tmp; der reale Ist-Baum muss unverändert sein. # Vorab-Zustand (Record im Gate selbst, §G-7/Abschnitt A) wird gegen den # Ist-Zustand verglichen: kein Diff, keine Untracked-Dateien außerhalb der # Gate-eigenen Artefakte (die live in $ROOT liegen, nicht im Repo). G8_PORCELAIN=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>&1) [ -n "$G8_PORCELAIN" ] && echo " Ist-Baum-Porcelain vor Abnahme:" printf '%s\n' "$G8_PORCELAIN" # Der Ist-Baum darf durch den Gate-Lauf keine Spur hinterlassen (außer den # durch die Story-3.13-Synchronisation erzeugten Artefakten, die Abschnitt # SyncWrite nach dem Gate erzeugt — PORCELAIN_CLEAN ist nur die Gate-Invariante). # Reale Assertion: KEINE non-Story-Touched-Datei darf verändert sein; alle # Änderungen sind genau die vom Story-Sync vorgesehenen. # Wir erfassen die Referenz-Dateien read-only (AD-3) — dürfen nie diffen. for ro in schema/compiler.md schema/validator.md schema/wiki-compiler.md schema/canonical-terms.md adapters; do if ! git -C "$REPO_ROOT" diff --quiet -- "$ro"; then echo "HARD-FAIL (G-8): Read-only-Artefakt verändert: $ro (AD-3)" >&2 exit 1 fi done for ro in raw; do if ! git -C "$REPO_ROOT" diff --quiet -- "$ro"; then echo "HARD-FAIL (G-8): raw/ verändert (AD-3)" >&2 exit 1 fi done pass "G-8: Read-only-Artefakte (schema/validator.md, compiler.md, wiki-compiler.md, canonical-terms.md, adapters/, raw/) ohne Diff" # Porcelain-Gesamt: überszogene Gate-Artefakte = harter Non-Zero. Wenn Änderungen # auftreten, müssen sie EXAKT die Story-3.13-Artefakte sein (run-sandbox.sh selbst # + Dokumentations-Sync). Wir prüfen hier die Gate-Invariante: solange der Sync # nicht gelaufen ist, MUSS der Ist-Baum völlig unverändert sein — der Sync (Step-05) # ist eine separates, explizit autorisiertes Artefakt-Set. G8_TRAIL=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>&1) echo " Ist-Baum-Porcelain nach dem Gate-Lauf: '$(printf '%s' "$G8_TRAIL" | head -c 200)'" # G-8 hart (spec: "überszogene Gate-Artefakte = Non-Zero", "git status --porcelain # --untracked-files=no leer"). Das Gate arbeitet ausschließlich /tmp und darf am # Ist-Baum KEINE getrackte Änderung hinterlassen. Wenn der Story-3.13-Sync noch # nicht committet ist, wäre das eine VORAB zu behebende Bedingung (der Sync ist # Bestandteil der Story — er wird VOR dem finalen Gate-Lauf committet). if [ -z "$G8_TRAIL" ]; then pass "G-8: Porcelain-Clean (Ist-Baum nach Gate-Lauf unverändert, git status --porcelain leer)" else echo "HARD-FAIL (G-8): Ist-Baum trägt getrackte uncommittete Änderungen nach dem Gate-Lauf:" >&2 printf '%s\n' "$G8_TRAIL" >&2 echo "HINWEIS: Der Story-3.13-Artefakt-Sync (sprint-status/epic-3-context/log) muss VOR dem finalen Lauf committet sein." >&2 exit 1 fi echo echo "########################################" echo "# GATE-ENDE — FAILED=$FAILED PASS_COUNT=$PASS_COUNT" echo "########################################" if [ "$FAILED" -eq 0 ]; then echo "RUN_OK" exit 0 else echo "RUN_FAILED" >&2 exit 1 fi