Files
wow20/_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh
T
Michael TamseandClaude 77fd460ec3 fix: Story 3.13 Re-Run-#6-Auflösung (E.6-Over-Pin) — Gate-Härtung: Receipt-Abgleich
auf die drei deterministischen Felder (baseline/candidates/sources_added byte-identisch
A==B + decision-Vorhandensein); decision-Prosa = Befund-Äquivalenz (Nicht-Wort-Identität)

Befund Re-Run #6 (grün bis E.6): E.4 GRÜN (NON_AT=0 AT=2 — Rev-3.8-Remediation
wirkt: Bundle-State byte-identisch bis at), D/D-NEG/C/E.5/E.7/E.8/E.9 grün;
HARD-FAIL an E.6: Run-Receipt decision:-Zeile (freie LLM-Prosa) wurde byte-
vergleicht und divergierte A/B (identischer Befund, freie Wortung), während
baseline/candidates/sources_added byte-identisch A==B waren.

Klassifikation: Gate-Over-Pin (kein compiler.md-Defizit, kein AC-4-Fall) —
schema/compiler.md definiert den Receipt nicht (0 Treffer "receipt"), der
Gate-Prompt (D-1) deklariert decision selbst als frei; der alte E.6-Byte-
Vergleich widersprach damit dem eigenen Prompt (selbe Over-Pin-Klasse wie
Re-Run #5 log.md, auf der Gate-Assertion-Seite). Semantische Entscheidung
bereits am Bundle-State bewiesen (E.4 + E.5/E.7/E.8).

Änderung (Gate-only, bash -n clean; kein compiler.md-/frozen-Block-Change;
D-2/D-1-konsistent, D-3.13-2-Option-1-Prinzip):
- E.6: receipt_field-Helfer (Präfix-Strip, WT-Pfad-Normalisierung, Trailing-
  Whitespace/CRLF-robust); nur deterministische Felder byte-identisch A==B,
  decision-Zeile auf Vorhandensein (nicht leer); Prosa = Befund-Äquivalenz.
- Header-D-2-Bullet: Härtung + Defer-Verweis dokumentiert.

Dokumentation:
- Spec Change Log: Re-Run-#6-Befund + Härtung + Defer (frozen Block L12-51
  und GENERATED_AT_AUSNAHME unangetastet, diff-geprüft).
- deferred-work.md: neuer Abschnitt "Deferred from: Re-Run #6" (D-3.13-R6-1):
  byte-deterministischer Receipt = offener Defer/Ask-First-Kandidat (spätere
  compiler.md-Revision Rev 3.9 oder Epic-4), kein stiller Semantik-Change.

Entscheidungshinweis: Gate-Härtung + Defer-Notiz ausgeführt (kleinste, ehrliche,
vertragskonforme Variante, konsistent mit dem autorisierten Muster Rev 3.8 /
D-3.13-6 Option 2); Nutzer-Away — zur Bestätigung im Review-Loop-2.

Nächster Schritt: Re-Run #7 (Erwartung: E.6 grün, F G-1..G-8 + G Porcelain
erstmals erreichbar).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 07:19:14 +02:00

933 lines
45 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 —
# Update-/Erhaltungs-Kern; CREATE-/Synthese-Kern über
# Sub-Runs 3-3/3-4, D-3.13-6 Option 2), 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).
# Re-Run-#6-Härtung: NUR die drei deterministischen Felder
# baseline/candidates/sources_added werden A==B byte-identisch
# verglichen + decision-Zeile auf Vorhandensein; die decision-PROSA
# gilt als Befund-Äquivalenz (Nicht-Wort-Identität) — der Receipt ist
# ein Gate-Artefakt, das compiler.md nicht definiert und dessen
# decision-Feld der Prompt selbst als frei deklariert (Over-Pin-Befund
# Re-Run #6; Defer D-3.13-R6-1 in deferred-work.md).
# - 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 — Update-/Erhaltungs-Kern (alpha UPDATE + gamma Erhaltung);
# CREATE-/Synthese-Kern bewusst NICHT in der A/B-Fixture (D-3.13-6 Option 2,
# Re-Run #5-Befund: CREATE-Fall 24/26 der NON_AT-Abweichungen treibend —
# Slug-Ableitung + CREATE-Prosa — und hebt den A/B-Byte-Kern aus der
# at-Ausnahme; CREATE/Synthese gelten über die Sub-Runs 3-3/3-4 als
# demostriert); AC-6 Demonstrationsfälle über Sub-Runs + A/B-Kern abgedeckt.
# - 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 <pfad> <begründung> ODER FAIL <pfad> <Punkt-Nr.+Grund>
- <pfad> 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 <<EOF_LIST
$WIKI_MD_LIST
EOF_LIST
[ "$DUP_FAIL" -eq 0 ] && pass "D.4 exakt ein Verdict pro wiki/-Datei (P-6/P-17)"
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)
# P-17: Pfad-Normalisierung auch hier (gleicher Mechanismus wie D.4) —
# normalisiertes Käfig-relativ 'concepts/<datei>' 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 (AC-6-Update-/Erhaltungs-Kern; Bundle-Frontmatter P-4) ---
# alpha: bestätigender Zuwachs raw/alpha-v2.md (S-3, gleiche Aussage wie alpha-v1#S-2)
# ⇒ Routing UPDATE (CONFIRMING-Konsolidierung §5.16 Pkt. 4, Multi-Beleg §5.5).
# gamma: unabhängig, bleibt byte-identisch (AC-6)
# CREATE-/Synthese-Kern: NICHT Teil der A/B-Fixture — die CREATE-/Synthese-Mechanik
# (neues Concept + Index-Link Punkt 11 + Slug-Ableitung §5.15/§5.7 + log.md)
# ist über die Sub-Runs 3-3/3-4 (mechanische, gepinnte Fixture) als demostriert
# gelten (Spec-Notiz, D-3.13-6 Option 2; Re-Run #5-Befund: der CREATE-Fall war
# 24/26 der NON_AT-Abweichungen treibend — Slug-Ableitung + CREATE-Prosa — und
# würde den A/B-Byte-Kern aus der at-Ausnahme herausheben. Die A/B-Prüfung
# deckt damit den Update-+Erhaltungs-Kern; CREATE/Synthese bleiben über die
# Sub-Runs + D-Validator abgedeckt.)
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
# CREATE-/Synthese-Kern (beta-Quellen) bewusst NICHT in der A/B-Fixture — die
# CREATE-Mechanik ist über die Sub-Runs 3-3/3-4 als demostriert gelten
# (D-3.13-6 Option 2; Re-Run #5-Befund: 24/26 NON_AT-Abweichungen CREATE-treibend).
pass "E.1 Fixture: Mini-Bundle (index+alpha+gamma+log) + Zuwachs alpha-v2 (UPDATE-Kern)"
( 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.
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-/Synthese-Kern
# über Sub-Runs 3-3/3-4, nicht Teil der A/B-Fixture — D-3.13-6 Option 2) ---
# 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
pass "E.5 Witness: alpha UPDATE (Multi-Beleg raw/alpha-v2.md#S-3 + sources-Konsolidierung) in A und B"
# --- E.6 Run-Receipt A-vs-B (D-2: Modell sandbox-3-8 DET-2, keine erwarteten Literale) ---
# Re-Run-#6-Befund (E.6-Over-Pin): der Receipt ist ein GATE-Artefakt, das
# schema/compiler.md NICHT definiert (0 Treffer); der Gate-Prompt deklariert die
# decision:-Zeile als frei ("Werte aus deinem Lauf", "keine vorgegebenen Werte").
# Der alte E.6-Byte-Vergleich des KOMPLETTEN Receipt überpinsste damit die freie
# decision-PROSA (Re-Run #6: A "alpha: UPDATE (...)" vs B "wiki/alpha.md = UPDATE
# (...)" — identischer Befund, freie Wortung) = dieselbe Over-Pin-Klasse wie das
# log.md-Problem in Re-Run #5, nur dass diesmal die Gate-Assertion den eigenen
# Prompt widerspricht. HÄRTUNG (Gate-Defekt, kein compiler.md-Defizit, kein
# AC-4-Fall; konsistent mit D-3.13-2-Option-1 + D-1): die drei DETERMINISTISCHEN
# strukturierten Felder (baseline/candidates/sources_added) werden A==B
# byte-identisch geprüft; die decision-PROSA wird als Befund-Äquivalenz behandelt
# (Nicht-Wort-Identität, §5.10-Pkt-3-Prinzip) — die SEMANTISCHE Entscheidung ist
# bereits am Bundle-State bewiesen (E.4 byte-identisch bis at + E.5/E.7/E.8
# Witness). Vertrags-Vollständigkeit (byte-deterministischer Receipt) = benannter
# Defer (Spec Change Log + deferred-work), Ask-First-Kandidat.
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; }
# receipt_field: $1=Receipt-Datei, $2=Feldname -> Wert (erster Treffer,
# "Feld: "-Präfix gestrippt, Worktree-absolute Pfade auf WT normalisiert,
# Trailing-Whitespace/CRLF gestrippt — beiderseits, damit einseitiges
# CRLF/LF oder Trailing-Spaces kein false-fail im A==B-Byte-Vergleich ist).
receipt_field() {
sed -n "s/^$2:[[:space:]]*//p" "$1" | head -1 \
| sed "s|$ROOT/wt-a|WT|g; s|$ROOT/wt-b|WT|g" \
| sed -E 's/[[:space:]]+$//; s/\r$//'
}
# Deterministische strukturierte Felder: A==B byte-identisch (Muss-Kern).
E6_FAIL=0
for fld in baseline candidates sources_added; do
va=$(receipt_field "$RA" "$fld")
vb=$(receipt_field "$RB" "$fld")
if [ "$va" != "$vb" ]; then
echo "HARD-FAIL (E.6): Receipt-Feld '$fld' divergiert A/B (deterministisches Feld, §5.14):" >&2
echo " A: $va" >&2
echo " B: $vb" >&2
E6_FAIL=1
fi
done
# decision: Struktur-Vorhandensein (nicht leer) — PROSA = Befund-Äquivalenz (kein Byte-Vergleich).
da=$(receipt_field "$RA" "decision")
db=$(receipt_field "$RB" "decision")
[ -n "$da" ] || { echo "HARD-FAIL (E.6): Receipt-Feld 'decision' A fehlt/leer" >&2; E6_FAIL=1; }
[ -n "$db" ] || { echo "HARD-FAIL (E.6): Receipt-Feld 'decision' B fehlt/leer" >&2; E6_FAIL=1; }
echo " --- run-receipt.txt A (deterministische Felder + decision-Befund) ---"
cat "$RA"
echo " --- run-receipt.txt B (deterministische Felder + decision-Befund) ---"
cat "$RB"
if [ "$E6_FAIL" -eq 0 ]; then
pass "E.6 Run-Receipt A==B: deterministische Felder (baseline/candidates/sources_added) byte-identisch; decision-Befund anwesend (Prosa = Befund-Äquivalenz, D-2/D-1)"
else
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.md UNVERÄNDERT
# (kein CREATE in der A/B-Fixture — D-3.13-6 Option 2: kein neuer/entfernter Link) ---
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_ADDED" -eq 0 ] || { echo "HARD-FAIL (E.8): index.md trägt neue Zeilen (erwartet: unverändert — kein CREATE in A/B-Fixture):" >&2; printf '%s\n' "$IDX_DIFF" >&2; exit 1; }
[ "$IDX_DELETED" -eq 0 ] || { echo "HARD-FAIL (E.8): index.md trägt Deletions (erwartet: unverändert):" >&2; printf '%s\n' "$IDX_DIFF" >&2; exit 1; }
pass "E.8 Unabhängiges Wissen erhalten: gamma byte-identisch; index.md unverändert (kein CREATE in A/B-Fixture, A==B)"
# --- E.9 log.md: Eintrag mit vollem Baseline-SHA (R-1; P-9) ---
# Wort-Form: der A/B-Byte-Kern von log.md wird über die kanonische Log-Eintragsform
# (compiler.md Rev 3.8, §5.9 Pkt. 4) garantiert — das Gate selbst HARDCODIERT den
# Eintragswortlaut NICHT (D-1: keine erwarteten Literale), es prüft nur Struktur
# (Lauf-Eintrag vorhanden + voller Baseline-SHA, R-1).
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