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>
933 lines
45 KiB
Bash
933 lines
45 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 —
|
||
# 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
|