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:
@@ -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
|
||||
Reference in New Issue
Block a user