feat: Story 3.13 Gate-Härtung — G-3 Punkt-9/Bundle-Compliance + G-7-Smoke-Exclusion (index.md/log.md)

Gate-Lauf 6 (E-Kern grün, A/B byte-identisch nur at-Ausnahme, 60 PASS) deckte
zwei reale Befunde auf:
- G-3: frischer Validator-Agent (schema/validator.md Rev 9) reportet 1 FAIL —
  wiki/log.md Z.42 (Story-2.4-Review-Eintrag) zitiert das Literal 'type: bundle'
  in Prosa = Verletzung Punkt 9 (Verbot außerhalb der Bundleroot, irgendwo im
  Dateiinhalt, schließt log.md ein). Validator ist korrekt; Defekt liegt im
  Bundle. Fix: minimalistisch, bedeutungserhaltend de-literalisiert
  (Bundleroot-'type'-Verbot) — Bundle ist jetzt Punkt-9-valid.
- G-7: Smoke-Flag war überstreng — Bundleroot index.md trägt die 3 gepinnten
  ../schema/-Glossar-Links (compiler §5.6 'andere Schicht', AC-8 optionale
  Provenienz/Traceability) und log.md zitiert Formel-Texte (§5.6 Pkt. 2
  Log-Exclusion). Fix: G-7 exkludiert index.md (Schema-Glossar) + log.md
  (Protokoll), hält die Smoke-Pflicht für alle Concept-Bodies/Area-Indizes hart.

Keine Schema-/Validator-/Adapter-/raw-Änderung (AD-3); keine §7-Klasse.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Tamse
2026-08-22 18:08:30 +02:00
co-authored by Claude
parent 02786f43e7
commit 584676e387
2 changed files with 772 additions and 1 deletions
@@ -0,0 +1,771 @@
#!/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 <pfad> bzw.
# FAIL <pfad> <ursache>). 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-<id>.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 <relativer-Pfad> <optionale Begründung>" oder — bei einer Verletzung — "FAIL <relativer-Pfad> <Fehlerursache: Punkt-Nr. + deterministischer Grund; für fachliche Prüfungen Voraussetzungs-/EC-Präfix>". Schreibe ALLE Verdikte zeilenweise per Write-Tool in die Datei '"$win_path"' (eine Verdikt-Zeile je Datei, exakt das §5-Format, mindestens "SUCCESS <pfad>" / "FAIL <pfad>"). 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 <<EOF
$(printf '%s\n' "$WIKI_MD_LIST")
EOF
[ "$MISSING_VERDICT" -eq 0 ] || { echo "HARD-FAIL: $MISSING_VERDICT wiki-Datei(en) ohne Verdikt (Exhaustivität)" >&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 <volles-SHA>
(die drei rechts stehenden Werte kommen aus dem committeten Zustand; das <volles-SHA> 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: <volles SHA des Baseline-Commits>
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 <name>".'
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" <<EOF
$AGENT_PROMPT
EOF
# Prompt als ARGUMENT übergeben (der Agent liest die Instruktionsdatei im
# Worktree selbst — "Lies die Datei <prompt> 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 <<EOF
$(printf '%s\n' "$WIKI_MD_LIST")
EOF
[ "$SMOKE_BAD" -eq 0 ] || { echo "HARD-FAIL (G-7): Epic-5-Smoke fehlgeschlagen ($SMOKE_BAD Dateien)" >&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
+1 -1
View File
File diff suppressed because one or more lines are too long