Files
wow20/_bmad-output/implementation-artifacts/sandbox-3-2/run-sandbox.sh
T

354 lines
15 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Story 3.2 — Sandbox-Tests der textuell-deterministischen Relevanzbestimmung (§3.2)
# Re-executierbar: bash run-sandbox.sh (ab Workspace-Root; Sandbox unter /tmp/sb32)
# Zweck: Membership-Pin (T1) + Zwei-Run-Identität (T2) + NO_MATCH (T3) +
# log.md-Kontaminations-Pin (T1: der Term liegt AUCH in log.md — die
# Candidate-Liste bleibt trotzdem auf Concept-Pfade definiert, §3.2-Pkt.-2a)
# + LINK_FOLLOWING_ZYKLUS (T5) + TRAVERSAL_REACH_ONLY (T6) +
# TERM_ABLEITUNG_SYNONYM (T7); T1/T4 mit harten Assertions (Review-Patch P-1).
set -u
ROOT=$(mktemp -d /tmp/sb32-XXXXXX)
SB="$ROOT/sb"
mkdir -p "$SB/wiki" "$SB/raw"
cd "$SB"
git init -q
git config user.email "sandbox@test"
git config user.name "Sandbox"
# ---------- Mini-Baum: Concept-Bodies + log.md, die den EXAKTEN Test-Term tragen ----------
cat > wiki/index.md <<'EOF'
# Index
- [Alpha](alpha.md)
EOF
cat > wiki/alpha.md <<'EOF'
---
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 verwendet deterministische-relevanz-bestimmung für den Abgleich.
EOF
cat > wiki/beta.md <<'EOF'
---
type: concept
sources:
- resource: raw/beta-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Beta beschreibt ein anderes, hier nicht betroffenes Thema.
EOF
# log.md trägt denselben Term — der Kontaminations-Fall, den die Exklusion entschärft:
cat > wiki/log.md <<'EOF'
# Log
## 2026-08-19
- Story-3.2-Update: alpha (raw/alpha-v2.md; deterministische-relevanz-bestimmung).
EOF
cat > raw/alpha-v1.md <<'EOF'
Evidenz v1: deterministische-relevanz-bestimmung (Stelle S-1).
EOF
cat > raw/beta-v1.md <<'EOF'
Evidenz v1: Beta-Thema (Stelle S-1).
EOF
git add -A
git commit -qm "Baseline"
BASE=$(git rev-parse HEAD)
echo "BASELINE-COMMIT: $BASE (Sandbox-Root: $ROOT; loeschbar: rm -rf $ROOT)"
echo
# ---------- Helfer: candidate-Erhebung mit tool-korrekter Exklusion (§3.2-Pkt.-2a) ----------
# rg-Form: rg -l '<term>' -g '!log.md' wiki/
# grep-Form:grep -rl '<term>' --exclude=log.md wiki/
terms() { # $1=Term; nutzt rg wenn vorhanden (native -g-Exklusion), sonst grep --exclude
if command -v rg >/dev/null 2>&1; then
rg -l "$1" -g '!log.md' wiki/
else
grep -rl "$1" --exclude=log.md wiki/
fi
}
# Normalisierung der Ausgabe: wiki/-Präfix + .md-Suffix strippen (§3.2-Pkt.-3a)
normalize() { sed -e 's|^wiki/||' -e 's|\.md$||'; }
# ---------- Helfer: Stufe b/c (Erhebung) + Terminormalisierung (Review-Loop-2, D-2) ----------
# Stufe b — index.md-Traversal (TRAVERSAL_REACH_ONLY, §3.2-Pkt.-2b):
# $1 = normalisierter Treffer-Name einer index.md (z. B. 'trafo/index' oder 'index');
# gibt die darunter gewurzelten Concept-Pfade (normalisiert) aus — die Concept-Links
# der getroffenen index.md, file-relativ aufgelöst (§5.7-Pkt.-4).
traverse() {
local idx="wiki/$1.md" d tgt
d=$(dirname "$idx")
grep -oE '\]\([^)]+\.md\)' "$SB/$idx" 2>/dev/null | sed -e 's|^\](||' -e 's|)$||' | while read -r tgt; do
case "$tgt" in ./*) tgt=${tgt#./} ;; esac
printf '%s/%s\n' "$d" "$tgt" | sed -e 's|^wiki/||' -e 's|\.md$||'
done
}
# Stufe c — Link-Following mit besuchter Menge (LINK_FOLLOWING_ZYKLUS, §3.2-Pkt.-2c):
# $@ = in Stufe a getroffene Concept-Pfade (normalisiert, z. B. 'sigma');
# Concept-Links werden file-relativ verfolgt (§5.7-Pkt.-4); ein bereits besuchter
# Concept-Pfad wird nicht erneut besucht — Zyklen (A -> B -> A) enden, die
# Candidate-Liste bleibt endlich. Ausgabe: getroffene + erreichte Pfade,
# in Besuchsreihenfolge (Stufe a zuerst, dann Stufe c; §3.2-Pkt.-3b).
follow() {
local visited="$ROOT/visited.$$" seed f base tgt res
: > "$visited"
local queue=()
for seed in "$@"; do
printf '%s\n' "$seed" >> "$visited"
queue+=("$seed")
done
while [ "${#queue[@]}" -gt 0 ]; do
f="wiki/${queue[0]}.md"
queue=("${queue[@]:1}")
base=$(dirname "$f")
for tgt in $(grep -oE '\]\([^)]+\.md\)' "$SB/$f" 2>/dev/null | sed -e 's|^\](||' -e 's|)$||'); do
case "$tgt" in ./*) tgt=${tgt#./} ;; esac
res=$(printf '%s/%s\n' "$base" "$tgt" | sed -e 's|^wiki/||' -e 's|\.md$||')
grep -qx "$res" "$visited" && continue
printf '%s\n' "$res" >> "$visited"
queue+=("$res")
done
done
cat "$visited"
}
# Terminormalisierung (§3.2-Pkt.-1b): (i) lowercasing; (ii) Binde-Varianten-Kollaps
# [-_ ] -> - (jedes Vorkommen wird in einen einzelnen Bindestrich kollabiert).
# Normalisierte Form = die Form, die in der Registry gespeichert/verglichen wird.
norm_term() { printf '%s' "$1" | LC_ALL=C tr 'A-Z' 'a-z' | sed -e 's/[-_ ]/-/g'; }
# Registry-Lookup (§3.2-Pkt.-1b, canonical-terms.md-Format):
# $1 = normalisierter Term, $2 = Registry-Datei; gibt die canonische Form aus,
# oder — nicht auflösbar — den Term selbst (§3.2-Pkt.-1b: kein stiller Ausschluss).
lookup() {
awk -F'|' -v t="$1" '
NR > 2 {
canon = $2; gsub(/^[ \t]+|[ \t]+$/, "", canon)
if (canon ~ /^—/) next
if (canon == t) { print canon; found = 1; exit }
vars = $3; gsub(/`/, " ", vars)
n = split(vars, a, ",")
for (i = 1; i <= n; i++) {
gsub(/^[ \t]+|[ \t]+$/, "", a[i])
if (a[i] != "" && a[i] == t) { print canon; found = 1; exit }
}
}
END { if (!found) print t }
' "$2"
}
# =====================================================================
runlabel() { echo; echo "########## $1 ##########"; }
runlabel "T1: Membership-Pin — Term trifft Concept-Body UND log.md; Candidate-Liste nur Concept-Pfade"
echo "--- Kandidaten-Erhebung: terms 'deterministische-relevanz-bestimmung' (Exklusions-Form) ---"
terms 'deterministische-relevanz-bestimmung' | normalize
echo "--- Erwartet: AUSSCHLIESSLICH 'alpha' (Concept-Pfad, ohne wiki/ + ohne .md) ---"
T1_OUT=$(terms 'deterministische-relevanz-bestimmung' | normalize)
if [ "$T1_OUT" = "alpha" ]; then
echo "RESULT: PASS (Candidate-Liste = [alpha]; log.md strukturell exkludiert, §3.2-Pkt.-2a)"
else
echo "FAIL: T1 — erwartet 'alpha', erhalten: '${T1_OUT}'" >&2
exit 1
fi
echo "--- Kontaminations-Kontrolle: was OHNE Exklusion getroffen würde (rg-OHNE -g / grep-OHNE --exclude) ---"
if command -v rg >/dev/null 2>&1; then
rg -l 'deterministische-relevanz-bestimmung' wiki/ | normalize
else
grep -rl 'deterministische-relevanz-bestimmung' wiki/ | normalize
fi
echo "--- OHNE Exklusion trifft es 'alpha' UND 'log' — die Exklusion (§3.2-Pkt.-2a) ist damit reell getestet ---"
# =====================================================================
runlabel "T2: Zwei-Run-Identität (AD-17h/A0-19) — zwei unabhängige Läufe, byte-identische Candidate-Liste"
FIRST1=$(terms 'deterministische-relevanz-bestimmung' | normalize)
SECOND1=$(terms 'deterministische-relevanz-bestimmung' | normalize)
FIRST2=$(terms 'alpha' | normalize)
SECOND2=$(terms 'alpha' | normalize)
echo "Lauf 1 (Term deterministische-relevanz-bestimmung): $FIRST1"
echo "Lauf 2 (Term deterministische-relevanz-bestimmung): $SECOND1"
echo "Lauf 1 (Term alpha): $FIRST2 # 'index' ist ein Stufe-a-Treffer auf wiki/index.md — Stufe b (TRAVERSAL_REACH_ONLY, §3.2-Pkt.-2b) löst die darunter gewurzelten Concept-Pfade als Candidate aus (index.md selbst ist KEIN Concept-Kandidat); kein Kontaminations-Fehler"
echo "Lauf 2 (Term alpha): $SECOND2 # identische Ausgabe (Zwei-Run-Identität, AD-17h)"
if [ "$FIRST1" = "$SECOND1" ] && [ "$FIRST2" = "$SECOND2" ]; then
echo "RESULT: IDENTISCH (gleicher Git-State + gleiches Eingabeset -> identische Candidate-Liste, AD-17h)"
else
echo "RESULT: ABWEICHUNG (Determinismus-Vertrag verletzt)" >&2
exit 1
fi
# =====================================================================
runlabel "T3: NO_MATCH — Term trifft kein bestehendes Concept -> leere Candidate-Liste -> UNTOUCHED_CONCEPT"
echo "--- Kandidaten-Erhebung: terms 'vollkommen-neuer-gamma-termin' ---"
RES=$(terms 'vollkommen-neuer-gamma-termin' | normalize)
echo "Candidate-Liste: '${RES}' (leer = gewuenscht)"
if [ -z "$RES" ]; then
echo "RESULT: LEER (NO_MATCH -> UNTOUCHED_CONCEPT: keine Mutation, kein log.md-Zusatz; leere Menge ⊆ erlaubte Menge)"
else
echo "RESULT: FEHLER (erwartet leer)" >&2
exit 1
fi
# =====================================================================
runlabel "T4: Determinismus-Vertrag praktisch — Term in Mehrfach-Konzepten, Konsolidierung zur Vereinigung"
cat > wiki/gamma.md <<'EOF'
---
type: concept
sources:
- resource: raw/gamma-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Gamma teilt den Term deterministische-relevanz-bestimmung (Stelle S-1).
EOF
git add -A; git commit -qm "Zuwachs gamma (teilt den Term)"
echo "--- Kandidaten-Erhebung (Term in alpha + gamma; §3.2-Pkt.-3b: innerhalb jeder Stufe rein lexikografisch, LC_ALL=C): ---"
terms 'deterministische-relevanz-bestimmung' | normalize | LC_ALL=C sort | paste -sd, -
echo "--- Erwartet: alpha,gamma (beide Concept-Pfade, lexikografisch; log ABGESCHNITTEN) ---"
T4_OUT=$(terms 'deterministische-relevanz-bestimmung' | normalize | LC_ALL=C sort | paste -sd, -)
if [ "$T4_OUT" = "alpha,gamma" ]; then
echo "RESULT: PASS (Vereinigung beider Concept-Pfade, lexikografisch; log.md exkludiert)"
else
echo "FAIL: T4 — erwartet 'alpha,gamma', erhalten: '${T4_OUT}'" >&2
exit 1
fi
# =====================================================================
# T5-T7 (Review-Loop-2, D-2): Zyklen-Endlichkeit, Traversal-Only, Synonym-Auflösung
# =====================================================================
# ---------- T5: LINK_FOLLOWING_ZYKLUS — A -> B -> A endet endlich (besuchte Menge) ----------
runlabel "T5: LINK_FOLLOWING_ZYKLUS — Zyklus alpha <-> omega endet endlich; Candidate-Liste endlich (§3.2-Pkt.-2c/3b)"
cat > wiki/omega.md <<'EOF'
---
type: concept
sources:
- resource: raw/omega-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Omega verweist zirkular auf Alpha: [Alpha](alpha.md).
EOF
# Backlink: alpha -> omega (erzeugt den echten A->B->A-Zyklus, der die besuchte Menge übt):
printf '\nOmega-Verweis: [Omega](omega.md).\n' >> wiki/alpha.md
git add -A; git commit -qm "Zuwachs omega (Link-Zyklus alpha <-> omega)"
echo "--- Stufe a (Term zirkular): ---"
STAGE_A=$(terms 'zirkular' | normalize | LC_ALL=C sort | paste -sd, -)
echo "$STAGE_A"
echo "--- Stufe c (Follow aus Stufe a; ZYKLUS omega -> alpha -> omega ... MUSS enden): ---"
STAGE_C=$(follow omega)
echo "$STAGE_C"
# Deduplication über die besuchte Menge (sort -u: endliche, doppelungsfreie Menge —
# die Stufe-Ordnung a→c ist bereits in STAGE_A/STAGE_C sichtbar; hier wird die Endlichkeit geprüft):
T5_EXPECT="alpha,omega"
T5_LIST=$(printf '%s\n%s\n' "$STAGE_A" "$STAGE_C" | grep -v '^$' | LC_ALL=C sort -u | paste -sd, -)
if [ "$T5_LIST" = "$T5_EXPECT" ]; then
echo "RESULT: PASS (endlich, dedupliziert: omega, alpha — Zyklus endete; jeder Pfad genau einmal)"
else
echo "FAIL: T5 — erwartet '$T5_EXPECT', erhalten: '$T5_LIST'" >&2
exit 1
fi
# ---------- T6: TRAVERSAL_REACH_ONLY — Term nur in index.md -> gewurzelte Concept-Pfade ----------
runlabel "T6: TRAVERSAL_REACH_ONLY — Term 'trafo-gebiet' trifft NUR wiki/trafo/index.md -> gewurzelte Concept-Pfade sind die Treffer (index.md selbst kein Concept-Kandidat, §3.2-Pkt.-2b)"
mkdir -p wiki/trafo
cat > wiki/trafo/index.md <<'EOF'
# Trafo-Gebiet-Index
Dieses trafo-gebiet wird hier indexiert.
- [Delta](delta.md)
- [Epsilon](epsilon.md)
EOF
cat > wiki/trafo/delta.md <<'EOF'
---
type: concept
sources:
- resource: raw/delta-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Delta-Body (trägt den Index-Term NICHT — nur der Index trägt ihn).
EOF
cat > wiki/trafo/epsilon.md <<'EOF'
---
type: concept
sources:
- resource: raw/epsilon-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Epsilon-Body (trägt den Index-Term NICHT — nur der Index trägt ihn).
EOF
git add -A; git commit -qm "Zuwachs trafo/Area (Index trägt den Term, Bodies nicht)"
echo "--- Stufe a (Term trafo-gebiet; nur index.md trifft — der Traversal-Trigger): ---"
STAGE_A=$(terms 'trafo-gebiet' | normalize)
echo "$STAGE_A"
echo "--- Stufe b (Traversal: gewurzelte Concept-Pfade der getroffenen index.md): ---"
STAGE_B=$(traverse 'trafo/index')
echo "$STAGE_B"
# Kandidaten = gewurzelte Concept-Pfade (lexikografisch; index.md selbst ist KEIN Concept-Kandidat):
T6_OUT=$(printf '%s\n' "$STAGE_B" | grep -v '^$' | LC_ALL=C sort | paste -sd, -)
if [ "$T6_OUT" = "trafo/delta,trafo/epsilon" ]; then
echo "RESULT: PASS (Candidate-Liste = gewurzelte Concept-Pfade; Index-Body-Trigger, kein Kontaminations-Fehler)"
else
echo "FAIL: T6 — erwartet 'trafo/delta,trafo/epsilon', erhalten: '$T6_OUT'" >&2
exit 1
fi
# ---------- T7: TERM_ABLEITUNG_SYNONYM — Schreibvariante -> canonische Form -> Kandidaten ----------
runlabel "T7: TERM_ABLEITUNG_SYNONYM — gezogene Schreibvariante wird über canonical-terms.md auf die canonische Form aufgelöst (Lookup auf normalisierter Form, §3.2-Pkt.-1b)"
mkdir -p "$ROOT/registry"
cat > "$ROOT/registry/canonical-terms.md" <<'EOF'
# Sandbox-Registry (Test-Doppel des canonical-terms-Formats; append-only, ein Eintrag = Canon + Varianten;
# D-5-Invariante: Canon- UND Varianten-Spalte tragen ausschliesslich NORMALISIERTE Formen)
| canonische Form | erlaubte Schreibvarianten | Semantik |
|---|---|---|
| quanten-protocol | `quantenprotocol` | Sandbox-Semantik: Test-Synonymgruppe |
EOF
# Invariante-Check (D-5): keine normalisierte Form in beiden Spalten oder in zwei Einträgen:
# hier: 'quanten-protocol' nur Canon-Spalte, 'quantenprotocol' nur Varianten-Spalte — eindeutig.
# (Registry liegt ausserhalb des Sandbox-Git-Roots — sie modelliert den Git-State-Anteil des Resolvers;
# der Sandbox-Repo-Commit trägt sie deshalb nicht, hier nur Notiz statt Commit-Noise:)
echo "Registry (ausserhalb des Sandbox-Roots, modelliert den Resolver-Git-State): $ROOT/registry/canonical-terms.md"
echo "--- Gezogene Schreibvarianten (Roh-Notat): 'Quanten Protocol' + 'QuantenProtocol' ---"
RAW_A='Quanten Protocol'
RAW_B='QuantenProtocol'
NORM_A=$(norm_term "$RAW_A")
NORM_B=$(norm_term "$RAW_B")
echo "Normalisierung (§3.2-Pkt.-1b): '$RAW_A' -> '$NORM_A'; '$RAW_B' -> '$NORM_B'"
CANON_A=$(lookup "$NORM_A" "$ROOT/registry/canonical-terms.md")
CANON_B=$(lookup "$NORM_B" "$ROOT/registry/canonical-terms.md")
echo "Lookup: '$NORM_A' -> '$CANON_A'; '$NORM_B' -> '$CANON_B'"
if [ "$CANON_A" = "quanten-protocol" ] && [ "$CANON_B" = "quanten-protocol" ]; then
echo "RESULT: PASS (beide Varianten -> canonische Form 'quanten-protocol'; Auflösung auf normalisierter Form)"
else
echo "FAIL: T7 — erwartet 'quanten-protocol' für beide, erhalten: '$CANON_A' / '$CANON_B'" >&2
exit 1
fi
# Nicht aufgelöster Term wird wie notiert (Kollaps-normalisiert) verwendet — kein stiller Ausschluss:
CANON_MISS=$(lookup 'nicht-in-registry' "$ROOT/registry/canonical-terms.md")
if [ "$CANON_MISS" = 'nicht-in-registry' ]; then
echo "PASS (Negativ-Fall: 'nicht-in-registry' -> wie notiert, kein stiller Ausschluss)"
else
echo "FAIL: T7-Negativ — erwartet 'nicht-in-registry', erhalten: '$CANON_MISS'" >&2
exit 1
fi
echo
echo "===== Sandbox abgeschlossen (alle 7 Tests) ====="
echo "Sandbox-Root: $ROOT"