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