#!/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 '' -g '!log.md' wiki/ # grep-Form:grep -rl '' --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"