354 lines
15 KiB
Bash
354 lines
15 KiB
Bash
#!/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"
|