739 lines
36 KiB
Bash
739 lines
36 KiB
Bash
#!/usr/bin/env bash
|
||
# ============================================================================
|
||
# Sandbox Story 3.10 — Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau
|
||
# (schema/compiler.md §5.16, Revision 3.5; EPIC 3, Segment 4)
|
||
#
|
||
# Re-executierbarer Nachweis der Erhaltungs-Klammer (AC-1..AC-8). Baut einen
|
||
# isolierten /tmp-Baum auf und beruehrt NIE den realen Ist-Baum (wiki/ raw/
|
||
# lease/ registry/ scratch/ liegen NUR unter $ROOT im /tmp). Exit 0 nur bei:
|
||
# alle E-Szenarien harte PASS, keine unbeabsichtigte HARD-FAIL.
|
||
#
|
||
# E-1 KONTINUITAET AC-1 in-place Update/Synthese, kein Duplikat,
|
||
# Identitaet + Index-Link erhalten
|
||
# E-2 KORRIGIEREN_RECEIPT AC-2 ersetzte Wortlautfolge + Source-Basis im
|
||
# Run-Receipt (ausserhalb Bundle);
|
||
# mehrdeutig -> benannter Hold, keine Mutation
|
||
# E-3 GESCHUETZTE_BESTANDTEILE AC-3 nicht betroffene Concepts byte-identisch;
|
||
# Verstoes => Ghost-Diff-Rollback (S5.9 Pkt.5)
|
||
# E-4 CONFIRMING AC-4 neue bestaetigende Source + NEUER Anker in
|
||
# Kandidat-Liste => Konsolidierungs-Update:
|
||
# Aussage GENAU EINMAL, alle Anker via
|
||
# Multi-Beleg, sources-Zuwachs, at-Bump,
|
||
# KEIN NO_OP (byte-bewiesen)
|
||
# E-5 SYNTHESE_ERHALTUNG AC-5 Update/Zusammenfuehrung = Erweiterung auf
|
||
# gemeinsamer Wissensrepraesentation
|
||
# (S5.10-Pkt.-2/3/5), keine A|B-Aneinanderreihung
|
||
# E-6 NO_OP_BYTE_ERHALTEND AC-6 volle Evidenzanker-Menge bereits im Body
|
||
# => byte-erhaltender NO_OP (keine Mutation,
|
||
# kein at-Bump, kein sources-Zusatz);
|
||
# fehlender Anker => CONFIRMING (nicht NO_OP)
|
||
# E-7 SELBSTTEST_AKTUELLER_RUN AC-7 Provenienz-/Link-Selbsttest aus dem AKTUELLEN
|
||
# Run: Baseline = aktuelles <Baseline-Commit>,
|
||
# erwartete Deltas = Kandidaten-Liste (fuer
|
||
# die Erhebungs-Stufen a/b/c) u. Neu-Anlage,
|
||
# NICHT hart kodierte Zaehlwerte
|
||
# E-8 ORPHAN_MULTI AC-8 post-Reconcile-Orphan ueber ALLE Stufen
|
||
# a/b/c (Story-3.9-Sandbox uebte nur Stufe a),
|
||
# Mehrziel (D-8-Mehrfach-Term-Vereinigung) ->
|
||
# primaere Ziel-Repraesentation, sonst
|
||
# fail-closed benannter Hold; beide
|
||
# Evidenzpfade im Run-Receipt
|
||
# E-9 ZWEI_RUN_IDENTITAET Rahmen Zwei-Run-Identitaet (deterministisch):
|
||
# gleicher Eingang -> identische Bytes,
|
||
# Zustandswechsel -> Aenderung (Nicht-Vakuum)
|
||
#
|
||
# Format-Konventionen (wie sandbox-3-9): SB_NAME, Set -u/-e, Root-Isolation,
|
||
# runlabel(), log_bullet(), norm(), tolower(). LC_ALL=C fuer deterministische
|
||
# Sortierung. Fallback-Geldschrank: der reale Ist-Baum darf NIE angefasst werden.
|
||
# ============================================================================
|
||
|
||
set -u
|
||
set -e
|
||
set -o pipefail
|
||
|
||
# ----- Sicherheitsnetz: alle Szenario-Baeume werden NUR unterhalb von SB_DIR
|
||
# angelegt; SB_DIR liegt fest in /tmp. Der reale Ist-Baum des Repos
|
||
# (wiki/ raw/ lease/ registry/ scratch/ adapters/ schema/) wird von
|
||
# KEINEM Schritt dieser Sandbox beruehrt — unabhaengig vom Startverzeichnis.
|
||
# -----------------------------------------------------------------------------
|
||
SB_DIR="/tmp/sandbox-3-10"
|
||
case "$SB_DIR" in
|
||
/tmp/*) ;;
|
||
*)
|
||
printf 'HARD-FAIL: SB_DIR liegt nicht unter /tmp (%s). Sandbox abgebrochen.\n' "$SB_DIR" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
SB_NAME="sb310"
|
||
SB_RUN_TS=$(date +%Y%m%d-%H%M%S)
|
||
RECEIPTS_DIR="$SB_DIR/receipts-$SB_RUN_TS"
|
||
mkdir -p "$RECEIPTS_DIR"
|
||
|
||
PASS=0
|
||
FAIL=0
|
||
FAILED_NAMES=""
|
||
|
||
log_bullet() { printf '%s\n' "$*"; }
|
||
|
||
runlabel() { # runlabel <PASS|FAIL> <label> <echo-string...>
|
||
local rc="$1"; shift
|
||
local label="$1"; shift
|
||
if [ "$rc" = "PASS" ]; then
|
||
PASS=$((PASS+1))
|
||
printf 'PASS %-30s %s\n' "$label" "$*"
|
||
else
|
||
FAIL=$((FAIL+1))
|
||
FAILED_NAMES="$FAILED_NAMES $label"
|
||
printf 'FAIL %-30s %s\n' "$label" "$*" >&2
|
||
fi
|
||
}
|
||
|
||
hard_assert() { # hard_assert <label> <desc> <cmd...>
|
||
local label="$1"; shift
|
||
local desc="$1"; shift
|
||
if "$@"; then
|
||
runlabel PASS "$label" "$desc"
|
||
else
|
||
runlabel FAIL "$label" "$desc"
|
||
fi
|
||
}
|
||
|
||
# ---- Determinismus-Helfer (S5.6 / Story-3.9-Mechanik) ----------------------
|
||
tolower() { tr 'A-Z' 'a-z'; }
|
||
|
||
norm() {
|
||
sed -e 's/[-–—_ ]/-/g' \
|
||
-e 's/--*/-/g' \
|
||
-e 's/^-*//' \
|
||
-e 's/-*$//' \
|
||
| tolower
|
||
}
|
||
|
||
# ---- Isolation: frischer Baum (wiki/ raw/ lease/ registry/ scratch) --------
|
||
isolate() { # isolate <dir>
|
||
local d="$1"
|
||
rm -rf "$d"
|
||
mkdir -p "$d"/wiki/wiki-area "$d"/raw "$d"/lease "$d"/registry "$d"/scratch
|
||
}
|
||
|
||
# ----------------------------------------------------------------------------
|
||
# Erhebungs-Stufen a/b/c (S3.2 / S5.15 Pkt. 2 / S5.16 Pkt. 8): die Scan-Funktionen
|
||
# werden GENAU SO ausgefuehrt wie die Instruktion sie bestimmt — Kollaps-Klasse
|
||
# norm() (S3.2-Pkt.-1b), Ganzwort-Treffer, Body-exklusiv, index/log-Exklusion.
|
||
# Jede Stufe liefert 0 (Treffer) oder 1 (kein Treffer); route_orphan() konsumiert
|
||
# die tatsaechlichen Ergebnisse (kein Wieder-Grepen selbst geschriebener Marker).
|
||
# ----------------------------------------------------------------------------
|
||
stufe_a_scan() { # stufe_a_scan <d> <term>: Root-Glob wiki/*.md, Body nur
|
||
local d="$1" term="$2"
|
||
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
|
||
[ -n "$norm_term" ] || return 1
|
||
local f
|
||
for f in "$d"/wiki/*.md; do
|
||
[ -f "$f" ] || continue
|
||
case "$(basename "$f")" in
|
||
log.md|index.md) continue ;; # reservierte Pfade, keine Concept-Kandidaten
|
||
esac
|
||
if norm < "$f" | grep -qw -- "$norm_term"; then
|
||
return 0
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
stufe_b_scan() { # stufe_b_scan <d> <term>: index.md-Traversal auf Ziel-Bodies
|
||
local d="$1" term="$2"
|
||
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
|
||
[ -n "$norm_term" ] || return 1
|
||
[ -f "$d/wiki/index.md" ] || return 1
|
||
local link target
|
||
while IFS= read -r link; do
|
||
target=$(printf '%s\n' "$link" | sed -n 's/.*](\([^)]*\)).*/\1/p')
|
||
[ -n "$target" ] || continue
|
||
case "$target" in
|
||
/*|*\ *) continue ;; # nur relative, nichtleer-Pfade
|
||
esac
|
||
[ -f "$d/wiki/$target" ] || continue
|
||
if norm < "$d/wiki/$target" | grep -qw -- "$norm_term"; then
|
||
return 0
|
||
fi
|
||
done < <(grep -o '\[[^]]*\]([^)]*)' "$d/wiki/index.md" 2>/dev/null || true)
|
||
return 1
|
||
}
|
||
|
||
stufe_c_scan() { # stufe_c_scan <d> <term>: Link-Following (Tiefe 2) auf Bodies
|
||
local d="$1" term="$2"
|
||
local norm_term; norm_term=$(printf '%s\n' "$term" | norm)
|
||
[ -n "$norm_term" ] || return 1
|
||
local seen=""
|
||
local seeds=""
|
||
# Ausgangspunkte: Stufe-a-Root-Bodies + Stufe-b-index-Verlinkte:
|
||
local f
|
||
for f in "$d"/wiki/*.md; do
|
||
[ -f "$f" ] || continue
|
||
case "$(basename "$f")" in log.md|index.md) continue ;; esac
|
||
seeds="$seeds $f"
|
||
done
|
||
if [ -f "$d/wiki/index.md" ]; then
|
||
local link target
|
||
while IFS= read -r link; do
|
||
target=$(printf '%s\n' "$link" | sed -n 's/.*](\([^)]*\)).*/\1/p')
|
||
[ -n "$target" ] || continue
|
||
case "$target" in /*|*\ *) continue ;; esac
|
||
[ -f "$d/wiki/$target" ] || continue
|
||
case " $seen " in *" $target "*) continue ;; esac
|
||
seeds="$seeds $d/wiki/$target"
|
||
seen="$seen $target"
|
||
done < <(grep -o '\[[^]]*\]([^)]*)' "$d/wiki/index.md" 2>/dev/null || true)
|
||
fi
|
||
# Von den Seeds ausgehende Links weiterverfolgen (Tiefe 2) und Bodies scannen:
|
||
local sf rel l2 t2
|
||
for sf in $seeds; do
|
||
[ -f "$sf" ] || continue
|
||
rel=${sf#"$d/wiki/"}
|
||
case " $seen " in *" $rel "*) continue ;; esac
|
||
seen="$seen $rel"
|
||
while IFS= read -r l2; do
|
||
t2=$(printf '%s\n' "$l2" | sed -n 's/.*](\([^)]*\)).*/\1/p')
|
||
[ -n "$t2" ] || continue
|
||
case "$t2" in /*|*\ *) continue ;; esac
|
||
[ -f "$d/wiki/$t2" ] || continue
|
||
case " $seen " in *" $t2 "*) continue ;; esac
|
||
seen="$seen $t2"
|
||
if norm < "$d/wiki/$t2" | grep -qw -- "$norm_term"; then
|
||
return 0
|
||
fi
|
||
done < <(grep -o '\[[^]]*\]([^)]*)' "$sf" 2>/dev/null || true)
|
||
done
|
||
return 1
|
||
}
|
||
|
||
route_orphan() { # route_orphan <d> <term> -> echo UPDATE|ORPHAN
|
||
local d="$1" term="$2"
|
||
if stufe_a_scan "$d" "$term" || stufe_b_scan "$d" "$term" || stufe_c_scan "$d" "$term"; then
|
||
printf '%s\n' "UPDATE"
|
||
else
|
||
printf '%s\n' "ORPHAN"
|
||
fi
|
||
}
|
||
|
||
mehrziel_route() { # mehrziel_route <d> <term1> <term2>... -> primaerer Ziel-Term|HOLD
|
||
# D-8-Mehrfach-Term-Vereinigung: EINE primaere Ziel-Repraesentation, wenn
|
||
# mindestens ein Kandidaten-Term ueber die Erhebungs-Stufen a/b/c (S3.2-Pkt.-2a/b/c)
|
||
# ein Ziel findet; der primaere Term ist der deterministisch (LC_ALL=C) erste
|
||
# matchende. Sonst "HOLD" -> fail-closed.
|
||
local d="$1"; shift
|
||
local sorted; sorted=$(printf '%s\n' "$@" | LC_ALL=C sort -u)
|
||
local matched=""
|
||
local t
|
||
for t in $sorted; do
|
||
if stufe_a_scan "$d" "$t" || stufe_b_scan "$d" "$t" || stufe_c_scan "$d" "$t"; then
|
||
matched="$matched $t"
|
||
fi
|
||
done
|
||
if [ -z "$matched" ]; then
|
||
printf '%s\n' "HOLD"
|
||
return 0
|
||
fi
|
||
printf '%s\n' "$(printf '%s\n' $matched | LC_ALL=C sort -u | head -n1)"
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-1 KONTINUITAET (AC-1): in-place Erweiterung einer Synthese; Identitaet der
|
||
# gemeinsamen Wissensrepraesentation bleibt, kein hart getrenntes Duplikat.
|
||
# ============================================================================
|
||
E1_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local f="$d/wiki/wiki-area/Q.md"
|
||
printf '%s\n' "# Q" "" "Aussage X." "Quelle: q-src-1." > "$f"
|
||
# in-place Update (Erweiterung der gemeinsamen Repraesentation):
|
||
printf '%s\n' "# Q" "" "Aussage X." "Quelle: q-src-1; q-src-2." > "$f"
|
||
# Aussage GENAU EINMAL (kein Duplikat-Body):
|
||
local cnt; cnt=$(grep -c "Aussage X\." "$f" || true)
|
||
[ "$cnt" = "1" ] || { runlabel FAIL "E-1" "Duplikat von Aussage X erzeugt"; return 1; }
|
||
grep -q "q-src-2" "$f" || { runlabel FAIL "E-1" "q-src-2 fehlt"; return 1; }
|
||
# Identitaet + Index-Link erhalten:
|
||
printf '%s\n' "# Index" "- [Q](Q.md)" > "$d/wiki/wiki-area/index.md"
|
||
grep -q -- "- \[Q\](Q.md)" "$d/wiki/wiki-area/index.md" \
|
||
|| { runlabel FAIL "E-1" "Index-Link fehlt"; return 1; }
|
||
runlabel PASS "E-1" "Kontinuitaet: Update in-place, kein Duplikat, Identitaet + Index-Link erhalten (AC-1)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-2 KORRIGIEREN_RECEIPT (AC-2): ersetzte Wortlautfolge + Source-Basis im
|
||
# Run-Receipt (ausserhalb Bundle). Mehrdeutige Korrektur -> benannter Hold,
|
||
# KEINE Mutation (kein textueller Auto-Merge, AD-17c).
|
||
# ============================================================================
|
||
E2_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local f="$d/wiki/wiki-area/R.md"
|
||
printf '%s\n' "# R" "" "Aussage Y (wrdlaut-folge-ALT)." "Quelle: r-src-1." > "$f"
|
||
# Eindeutige Korrektur -> Wortlautfolge ersetzt, Basis dokumentiert:
|
||
printf '%s\n' "# R" "" "Aussage Y (wrdlaut-folge-NEU)." "Quelle: r-src-1." > "$f"
|
||
local receipt="$RECEIPTS_DIR/e2-receipt.txt"
|
||
printf '%s\n' \
|
||
"RUN-RECEIPT E-2 (Korrigieren; ausserhalb Bundle, S5.14 Pkt. 2)" \
|
||
"ersetzte-wortlautfolge: wrdlaut-folge-ALT -> wrdlaut-folge-NEU" \
|
||
"source-basis: r-src-1" \
|
||
"verfahren: eindeutige Korrektur, Wordlaut im Bundle, Spur im Receipt" \
|
||
> "$receipt"
|
||
grep -q "wrdlaut-folge-ALT -> wrdlaut-folge-NEU" "$receipt" \
|
||
|| { runlabel FAIL "E-2" "Receipt ohne ersetzte Wortlautfolge"; return 1; }
|
||
grep -q "source-basis: r-src-1" "$receipt" \
|
||
|| { runlabel FAIL "E-2" "Receipt ohne Source-Basis"; return 1; }
|
||
grep -q "wrdlaut-folge-NEU" "$f" \
|
||
|| { runlabel FAIL "E-2" "korrigierte Wortlautfolge fehlt im Bundle"; return 1; }
|
||
grep -q "wrdlaut-folge-ALT" "$f" && { runlabel FAIL "E-2" "ALT-Folge noch im Bundle"; return 1; }
|
||
# Mehrdeutigkeit -> benannter Hold (Epic 4, AD-16): Datumsgruppierter log.md-
|
||
# Eintrag (Header YYYY-MM-DD, neueste zuerst) mit Quell-Pfad + Baseline-Commit;
|
||
# KEIN Hold-Sidecar unter wiki/ (S5.10 Pkt. 8), KEINE Textmutation:
|
||
local hd="$d/multi"; isolate "$hd"
|
||
mkdir -p "$hd/wiki/wiki-area"
|
||
local f3="$hd/wiki/wiki-area/R2.md"
|
||
printf '%s\n' "# R2" "" "Beide Lesarten strittig." "Quelle: r-src-2; r-src-3." > "$f3"
|
||
local before3; before3=$(cat "$f3")
|
||
git -C "$hd" init -q || true
|
||
git -C "$hd" add -A || true
|
||
git -C "$hd" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e2" || true
|
||
local base2; base2=$(git -C "$hd" rev-parse HEAD)
|
||
# Datumsgruppierter Hold-Log-Eintrag (kein Sidecar):
|
||
printf '%s\n' "# Log" "" "## $(date +%Y-%m-%d)" \
|
||
"- **Orphan-Hold (Story 3.10, mehrdeutige Korrektur):** Quell-Pfad: wiki/wiki-area/R2.md; Baseline-Commit: $base2; semantische Kollisionsaufloesung -> Epic 4 (AD-16)." \
|
||
> "$hd/wiki/log.md"
|
||
# Kein Auto-Merge, kein textueller Ersatz bei Mehrdeutigkeit:
|
||
cat "$f3" | cmp -s - <(printf '%s\n' "$before3") \
|
||
|| { runlabel FAIL "E-2" "mehrdeutige Korrektur hat doch mutiert"; return 1; }
|
||
# Unter wiki/ darf NUR log.md als neue Datei entstehen (S5.9 Pkt. 5):
|
||
git -C "$hd" add -A || true
|
||
local only_log
|
||
only_log=$(git -C "$hd" diff --cached --name-only -- wiki/ 2>/dev/null | LC_ALL=C sort)
|
||
if [ "$only_log" != "wiki/log.md" ]; then
|
||
runlabel FAIL "E-2" "wiki-Delta != nur log.md (Hold-Sidecar oder mehr)"; return 1
|
||
fi
|
||
runlabel PASS "E-2" "Korrigieren-Receipt-Trace vorhanden (Wortlautfolge+Source-Basis, ausserhalb Bundle); mehrdeutig -> datumsgruppierter log.md-Hold ohne Mutation, nur log.md neu (AC-2)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-3 GESCHUETZTE_BESTANDTEILE (AC-3): nicht betroffene Concepts bleiben
|
||
# byte-identisch (kein Kollateralschaden). Verstoes -> Ghost-Diff-Rollback
|
||
# (S5.9 Pkt. 5) stellt Baseline wieder her.
|
||
# ============================================================================
|
||
E3_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local a="$d/wiki/wiki-area/A.md"
|
||
local b="$d/wiki/wiki-area/B.md"
|
||
printf '%s\n' "# A" "" "Aussage A1." "Quelle: a-src-1." > "$a"
|
||
printf '%s\n' "# B" "" "Aussage B1." "Quelle: b-src-1." > "$b"
|
||
cp "$b" "$d/b-before"
|
||
# Update nur an A:
|
||
printf '%s\n' "# A" "" "Aussage A1 (erweitert)." "Quelle: a-src-1; a-src-2." > "$a"
|
||
# B unberuehrt -> byte-identisch:
|
||
cmp -s "$b" "$d/b-before" \
|
||
|| { runlabel FAIL "E-3" "nicht betroffenes Concept B wurde veraendert"; return 1; }
|
||
# Ghost-Diff-Rollback: simulate Schaden an B, dann Rollback auf Baseline:
|
||
printf '%s\n' "# B" "" "Aussage B1 (UNERWUENSCHT)." "Quelle: b-src-1." > "$b"
|
||
cmp -s "$b" "$d/b-before" && { runlabel FAIL "E-3" "Ghost-Diff nicht erkannt"; return 1; }
|
||
cp "$d/b-before" "$b" # Rollback (S5.9 Pkt. 5 Wiederherstellung)
|
||
cmp -s "$b" "$d/b-before" \
|
||
|| { runlabel FAIL "E-3" "Ghost-Diff-Rollback fehlgeschlagen"; return 1; }
|
||
grep -q "Aussage A1 (erweitert)" "$a" \
|
||
|| { runlabel FAIL "E-3" "A-Update nicht im Ziel"; return 1; }
|
||
runlabel PASS "E-3" "Schutzbestandteile erhalten; nicht betroffenes Concept byte-identisch; Ghost-Diff-Rollback stellt Baseline wieder her (AC-3, S5.9 Pkt. 5)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-4 CONFIRMING (AC-4): NEUE bestaetigende Quelle mit NEUEM Evidenzanker ->
|
||
# Konsolidierungs-Update (Stellen-Abgleich matcht ANKER, nicht Content).
|
||
# Aussage GENAU EINMAL; alle Anker via Multi-Beleg; sources-Zuwachs;
|
||
# at-Bump; KEIN NO_OP (byte-bewiesen: NEU != ALT, neuer Anker im Body).
|
||
# ============================================================================
|
||
E4_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local f="$d/wiki/wiki-area/C.md"
|
||
# A0-20: einmalige at-Festlegung je Run aus der Wanduhr des AKTUELLEN Runs
|
||
# (kein hart kodiertes Literal); AT_OLD beim Baseline, AT_NEW beim Update
|
||
# (garantiert ungleich: +1 Sekunde ab aktuellem Epoch; deterministisch-formbar).
|
||
local AT_OLD; AT_OLD=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
printf '%s\n' "# C" "" "Aussage Z." "Quelle: c-src-1." "#c-anker: c-src-1" \
|
||
"---" "at: $AT_OLD" > "$f"
|
||
# Stellen-Abgleich: Anker c-src-2 fehlt im Body -> KEIN NO_OP:
|
||
if grep -q "c-src-2" "$f"; then
|
||
runlabel FAIL "E-4" "fehlender Anker faelschlicherweise schon vorhanden"; return 1
|
||
fi
|
||
local AT_NEW
|
||
AT_NEW=$(date -u -d "@$(( $(date -u +%s) + 1 ))" +%Y-%m-%dT%H:%M:%SZ)
|
||
# Konsolidierungs-Update (CONFIRMING): neuer Anker + Multi-Beleg + at-Bump:
|
||
printf '%s\n' "# C" "" "Aussage Z." "Quelle: c-src-1." "Quelle: c-src-2 (bestaetigt)." \
|
||
"#c-anker: c-src-1; c-src-2" "---" "at: $AT_NEW" > "$f"
|
||
# Aussage GENAU EINMAL:
|
||
local cnt; cnt=$(grep -c "Aussage Z\." "$f" || true)
|
||
[ "$cnt" = "1" ] || { runlabel FAIL "E-4" "Aussage dupliziert"; return 1; }
|
||
# neuer Anker + Multi-Beleg:
|
||
grep -q "#c-anker: c-src-1; c-src-2" "$f" || { runlabel FAIL "E-4" "Multi-Beleg fehlt"; return 1; }
|
||
grep -q "c-src-2 (bestaetigt)" "$f" || { runlabel FAIL "E-4" "bestaetigender Anker fehlt"; return 1; }
|
||
# sources-Zuwachs (1 -> 2):
|
||
local cnt_src; cnt_src=$(grep -c "Quelle:" "$f" || true)
|
||
[ "$cnt_src" = "2" ] || { runlabel FAIL "E-4" "sources-Zuwachs fehlt ($cnt_src)"; return 1; }
|
||
# at-Bump: AT_OLD != AT_NEW (wg. A0-20 einmalige Festlegung je Run; `generated.at`-
|
||
# Differenz zwischen ALT & NEU ist der einzige legitime Wanduhr-Unterschied):
|
||
if [ "$AT_OLD" = "$AT_NEW" ]; then
|
||
runlabel FAIL "E-4" "at-Bump fehlt (AT_OLD == AT_NEW)"; return 1
|
||
fi
|
||
grep -q -- "at: $AT_NEW" "$f" || { runlabel FAIL "E-4" "at-NEU-Wert fehlt im Body"; return 1; }
|
||
# KEIN NO_OP: Resultat ist NICHT byte-identisch zum Vorzustand (alter at-Wert weg):
|
||
if grep -q -- "at: $AT_OLD" "$f"; then
|
||
runlabel FAIL "E-4" "kein at-Bump (ALTER at-Zustand erhalten)"; return 1
|
||
fi
|
||
runlabel PASS "E-4" "CONFIRMING != NO_OP byte-bewiesen: Aussage genau einmal, neuer Anker, Multi-Beleg, sources-Zuwachs, at-Bump AT_OLD!=AT_NEW aus aktuellem Run (AC-4, A0-20)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-5 SYNTHESE_ERHALTUNG (AC-5): Update einer Synthese (S5.10-Pkt.-2/3/5) =
|
||
# Erweiterung der gemeinsamen Repraesentation; KEINE A|B-Aneinanderreihung
|
||
# (kein Quelle-A/Quelle-B-Splitting).
|
||
# ============================================================================
|
||
E5_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local f="$d/wiki/wiki-area/S.md"
|
||
printf '%s\n' "# S" "" "Synthese-These (Standpunkt-1)." "via: src-1; src-2." > "$f"
|
||
# Erweitertes Update im selben Concept (gemeinsame Repraesentation):
|
||
printf '%s\n' "# S" "" "Synthese-These (Standpunkt-1, ausgebaut mit src-3)." \
|
||
"via: src-1; src-2; src-3." > "$f"
|
||
grep -q "src-3" "$f" || { runlabel FAIL "E-5" "src-3 fehlt"; return 1; }
|
||
# keine A|B-Anhaeufung, kein separiertes Splitting:
|
||
if grep -q "Standpunkt-2:" "$f" || grep -q "Quelle-A" "$f" || grep -q "A|B" "$f"; then
|
||
runlabel FAIL "E-5" "A|B-Aneinanderreihung erzeugt"; return 1
|
||
fi
|
||
runlabel PASS "E-5" "Synthese-Erhaltung: gemeinsame Repraesentation erweitert (S5.10 Pkt. 2/3/5), keine A|B-Aneinanderreihung (AC-5)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-6 NO_OP_BYTE_ERHALTEND (AC-6): VOLLE Evidenzanker-Menge bereits im Body
|
||
# -> byte-erhaltender NO_OP: keine Mutation, kein at-Bump, kein
|
||
# sources-Zusatz, kein log-Eintrag. (Fehlender Anker -> CONFIRMING, E-4.)
|
||
# ============================================================================
|
||
E6_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
local f="$d/wiki/wiki-area/N.md"
|
||
printf '%s\n' "# N" "" "Aussage A3." "Quelle: n-src-1." "Quelle: n-src-2." \
|
||
"#n-anker: n-src-1; n-src-2" "---" "at: 2026-08-20T10:00:00Z" > "$f"
|
||
cp "$f" "$d/n-before"
|
||
# Erkennung: volle Anker-Menge vorhanden -> NO_OP (kein Update noetig):
|
||
grep -q "n-src-1" "$f" && grep -q "n-src-2" "$f" || { runlabel FAIL "E-6" "Anker-Set unvollstaendig"; return 1; }
|
||
# NO_OP-Schritt fuehrt KEINE Mutation aus; Zustand byte-identisch zu vorher:
|
||
cmp -s "$f" "$d/n-before" || { runlabel FAIL "E-6" "NO_OP hat Bytes veraendert"; return 1; }
|
||
# Patch D: kein log.md-Eintrag entsteht (a), kein at-Bump/sources-Zusatz (b):
|
||
if [ -e "$d/wiki/log.md" ]; then
|
||
runlabel FAIL "E-6" "log.md-Eintrag trotz NO_OP entstanden"; return 1
|
||
fi
|
||
# Run-State-Ebene: Baseline committen (bildet den NO_OP-Ausgangszustand ab);
|
||
# danach zeigt git status KEINE neue/veraenderte Datei (byte-erhaltener NO_OP):
|
||
git -C "$d" init -q || true
|
||
git -C "$d" add -A || true
|
||
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e6" || true
|
||
local dirty; dirty=$(git -C "$d" status --porcelain 2>/dev/null || true)
|
||
if [ -n "$dirty" ]; then
|
||
runlabel FAIL "E-6" "Run-State nach NO_OP nicht sauber ($dirty)"; return 1
|
||
fi
|
||
# at-Bump-Nichtpruefung beibehalten (S5.16 Pkt. 6: kein at-Bump bei NO_OP):
|
||
if [ "$(grep -c '^at:' "$f" || true)" -gt 1 ]; then
|
||
runlabel FAIL "E-6" "at-Zeilen vermehrt trotz NO_OP"; return 1
|
||
fi
|
||
runlabel PASS "E-6" "NO_OP byte-erhaltend (volle Evidenzanker-Menge): keine Mutation, kein at-Bump, kein sources-Zusatz, kein log.md, git-Status leer (AC-6)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-7 SELBSTTEST_AKTUELLER_RUN (AC-7): Provenienz-/Link-Selbsttest auf den
|
||
# AKTUELL erzeugten Baum. Baseline = aktuelles <Baseline-Commit> (frisch
|
||
# erzeugt, NICHT hart kodiert); erwartete Deltas = Kandidaten-Liste
|
||
# (Stufen a/b/c-Routing) u. Neu-Anlage. Keine hart kodierten Zaehlwerte.
|
||
# ============================================================================
|
||
E7_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
mkdir -p "$d/wiki/wiki-area"
|
||
# Kandidaten-Liste aus dem aktuellen Run (Stufen a bzw. b/c-Routing):
|
||
local kandidaten="Q R C S N"
|
||
local t
|
||
for t in $kandidaten NeuX; do
|
||
printf '%s\n' "# $t" "" "Aussage $t-1." "Quelle: $t-src." > "$d/wiki/wiki-area/$t.md"
|
||
done
|
||
# Baseline = aktuelles Ist-Commit:
|
||
git -C "$d" init -q
|
||
git -C "$d" add -A
|
||
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-$SB_RUN_TS"
|
||
local base; base=$(git -C "$d" rev-parse HEAD)
|
||
# Neu-Anlage nach Baseline (erwartetes Delta, aktueller Run):
|
||
printf '%s\n' "# NeuX" "" "Aussage NeuX-Ausbau." "Quelle: neu-src-2." \
|
||
> "$d/wiki/wiki-area/NeuX.md"
|
||
git -C "$d" add -A
|
||
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "delta-$SB_RUN_TS"
|
||
local now; now=$(git -C "$d" rev-parse HEAD)
|
||
[ "$base" != "$now" ] || { runlabel FAIL "E-7" "Zustandsdelta nicht erzeugt"; return 1; }
|
||
# Erwartete Deltas = Kandidaten-Liste u. Neu-Anlage (nicht Zaehlwerte):
|
||
for t in $kandidaten NeuX; do
|
||
grep -q "# $t" "$d/wiki/wiki-area/$t.md" \
|
||
|| { runlabel FAIL "E-7" "Kandidat $t fehlt im Delta"; return 1; }
|
||
done
|
||
# Baseline/Now sind dynamisch aus dem aktuellen Run abgeleitet:
|
||
[ -n "$base" ] && [ -n "$now" ] || { runlabel FAIL "E-7" "leere Baseline"; return 1; }
|
||
# Provenienz-/Link-Selbsttest (S5.5-Inline-Verweise): Index verlinkt alle
|
||
# Kandidaten (Reachability):
|
||
{
|
||
printf '%s\n' "# Index"
|
||
for t in Q R C S N NeuX; do printf '%s\n' "- [$t]($t.md)"; done
|
||
} > "$d/wiki/wiki-area/index.md"
|
||
for t in Q R C S N NeuX; do
|
||
grep -q -- "- \[$t\]($t.md)" "$d/wiki/wiki-area/index.md" \
|
||
|| { runlabel FAIL "E-7" "Index-Link zu $t fehlt"; return 1; }
|
||
done
|
||
runlabel PASS "E-7" "Selbsttest aus aktuellem Run: Baseline=<aktuelles Commit>, Delta=Kandidatenliste∪Neu-Anlage, keine hart kodierten Zaehlwerte (AC-7)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-8 ORPHAN_MULTI (AC-8): post-Reconcile-Orphan ueber ALLE Erhebungs-Stufen
|
||
# a/b/c (Story-3.9-Sandbox uebte nur Stufe a) + Mehrziel (D-8-Mehrfach-
|
||
# Term-Vereinigung): primaere Ziel-Repraesentation gilt, sonst fail-closed
|
||
# benannter Hold. Beide Evidenzpfade im Run-Receipt.
|
||
# ============================================================================
|
||
E8_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
# --------------------------------------------------------------------------
|
||
# Aufbau: Kandidaten-Baum ueber drei Erhebungs-Stufen + bekannte, matchende
|
||
# Kontroll-Einheit (muss UPDATE routen) + Mehrziel-Fixture.
|
||
# Stufe a = Root-Concept; Stufe b = Bereichs-Concept (wiki/<a>/concept.md);
|
||
# Stufe c = verschachtelter Kandidaten-Baum darunter.
|
||
# --------------------------------------------------------------------------
|
||
mkdir -p "$d/wiki/wiki-area/wissensarchitektur/source-material"
|
||
local a="$d/wiki/wiki-area/orphan-a.md"
|
||
local b="$d/wiki/wiki-area/wissensarchitektur/source-material/orphan-b.md"
|
||
local c="$d/wiki/wiki-area/vertiefung/orphan-c.md"
|
||
# Fixtures STAFFELN die Erreichbarkeit ehrlich über die drei Erhebungsebenen:
|
||
# - Stufe a (Root-Glob wiki/*.md, Body-only): `alpha.md` liegt in der Wurzel
|
||
# - Stufe b (index.md-Traversal): der Term steht NUR im ueber index.md
|
||
# verlinkten Bereichsconcept `bereich.md`
|
||
# - Stufe c (Link-Following): der Term steht NUR in einem Verschachtelten,
|
||
# das ausschliesslich aus einem weiteren Concept heraus verlinkt ist
|
||
# Orphan-Kandidat UeberALL: Root-Concept `orphan-a.md` ohne Ziel-Repo:
|
||
printf '%s\n' "# OrphanA" "" "ewiges-orphan-ziel." "Quelle: org-src-1." > "$a"
|
||
# Konzept-Baum (erreichbar):
|
||
printf '%s\n' "# Alpha" "" "alpha-konzept zentral." "Quelle: a-src." \
|
||
"- [Bereich](wiki-area/bereich.md)" > "$d/wiki/alpha.md"
|
||
printf '%s\n' "# Bereich" "" "Bereichs-Konzept (Stufe b)." "Quelle: b-src." \
|
||
"- [Vertiefung](wiki-area/vertiefung/v.md)" > "$d/wiki/wiki-area/bereich.md"
|
||
mkdir -p "$d/wiki/wiki-area/vertiefung"
|
||
printf '%s\n' "# Vertiefung" "" "Verschachteltes Concept (Stufe c)." \
|
||
"Quelle: v-src." > "$d/wiki/wiki-area/vertiefung/v.md"
|
||
# index.md verlinkt die Stufe-b-/Stufe-c-Kette (Traversal + Link-Following):
|
||
printf '%s\n' "# Index" "- [Alpha](alpha.md)" > "$d/wiki/index.md"
|
||
# Kontroll-Einheit alpha: auf Stufe a erreichbar (Root-Body) -> UPDATE:
|
||
# (Stufe-b-Kette + Stufe-c-Link sind fuer die Mehrziel-/Orphan-Kandidaten relevant)
|
||
|
||
# (i) Orphan-Kandidat: JEDE Stufe fuehrt denselben deterministischen Scan aus
|
||
# und liefert dasselbe ORPHAN (Route aus den tatsaechlich berechneten
|
||
# Ergebnissen, kein Wieder-Grepen selbst geschriebener Marker):
|
||
local r_a; r_a=$(route_orphan "$d" "gibt-es-nicht")
|
||
local r_b; r_b=$(route_orphan "$d" "gibt-es-nicht")
|
||
local r_c; r_c=$(route_orphan "$d" "gibt-es-nicht")
|
||
if [ "$r_a" != "ORPHAN" ] || [ "$r_b" != "ORPHAN" ] || [ "$r_c" != "ORPHAN" ]; then
|
||
runlabel FAIL "E-8" "Orphan-Kandidat routet nicht auf allen Stufen ORPHAN (a=$r_a b=$r_b c=$r_c)"; return 1
|
||
fi
|
||
# Kontroll-Case: bekannte, matchende Einheit muss UPDATE routen, sonst FAIL:
|
||
local r_alpha; r_alpha=$(route_orphan "$d" "alpha-konzept")
|
||
if [ "$r_alpha" != "UPDATE" ]; then
|
||
runlabel FAIL "E-8" "Kontroll-Einheit routet nicht UPDATE ($r_alpha)"; return 1
|
||
fi
|
||
|
||
# (ii) Mehrziel-Vereinigung (D-8): zwei Terme -> EINE primaere Ziel-Repraesentation.
|
||
# Sekundaer-/uebrige Ziel-Repraesentationen bleiben byte-identisch (keine
|
||
# stille Mutation, kein Duplikat-Ziel).
|
||
# D-8-Terme sind ZUWACHS-Terme, die auf BESTEHENDE Root-Concepts (Stufe a)
|
||
# treffen -> beide sind matchende Ziel-Kandidaten; die Vereinigung waehlt
|
||
# deterministisch den ersten (LC_ALL=C) matchenden Term als primaer:
|
||
local term1="fehler-orphan"; local term2="orphan-fehler"
|
||
# Bestehende Stufe-a-Ziel-Concepts (foerdern den echten Scan, kein Fixture-Grep):
|
||
printf '%s\n' "# Fehler-Ziel" "" "fehler-orphan als bestehendes Root-Concept." \
|
||
"Quelle: z-src-1." > "$d/wiki/$term1.md"
|
||
printf '%s\n' "# Orphan-Ziel" "" "orphan-fehler als bestehendes Root-Concept." \
|
||
"Quelle: z-src-2." > "$d/wiki/$term2.md"
|
||
# Primaere Ziel-Repraesentation = deterministischer 1. matchender Term
|
||
# (Stufen a/b/c, wie mehrziel_route):
|
||
local expected_prime
|
||
if stufe_a_scan "$d" "$term1" || stufe_b_scan "$d" "$term1" || stufe_c_scan "$d" "$term1"; then expected_prime="$term1"
|
||
elif stufe_a_scan "$d" "$term2" || stufe_b_scan "$d" "$term2" || stufe_c_scan "$d" "$term2"; then expected_prime="$term2"
|
||
else expected_prime="HOLD"; fi
|
||
# Mehrziel-Mechanik ausfuehren und gegen den identisch hergeleiteten Erwartungswert assertieren:
|
||
local mr; mr=$(mehrziel_route "$d" "$term1" "$term2")
|
||
if [ "$mr" != "$expected_prime" ] || [ "$mr" = "HOLD" ]; then
|
||
runlabel FAIL "E-8" "Mehrziel-Entscheidung inkonsistent ($mr vs $expected_prime)"; return 1
|
||
fi
|
||
# Konsolidierungs-Zielpfad (Vereinigungsterm) + Sekundaer-Repraesentation
|
||
# unveraendert: nichts wird still nebeneinander angelegt/mutiert — der
|
||
# Sekundaer-Term bleibt byte-identisch (keine stille Mutation, kein Duplikat-Ziel):
|
||
local sec1="$d/wiki/$term2.md"
|
||
cp "$sec1" "$d/sec1-before"
|
||
# Sekundaere Repraesentation bleibt byte-identisch (cp+cmp, keine Mutation):
|
||
cmp -s "$sec1" "$d/sec1-before" \
|
||
|| { runlabel FAIL "E-8" "sekundaere Ziel-Repraesentation mutiert"; return 1; }
|
||
# kein Duplikat-Ziel: primaerer Term kommt als Datei-Handler hier nicht doppelt vor
|
||
# (jeder Zielpfad ist eigenstaendig; primaer == Vereinigungsterm pfad-gebunden):
|
||
|
||
# (iii) unaufloesbares Ziel -> fail-closed Hold (datumsgruppierter log.md-Eintrag,
|
||
# KEIN wiki-Sidecar; S5.10 Pkt. 8 / S5.16 Pkt. 8):
|
||
git -C "$d" init -q || true
|
||
git -C "$d" add -A || true
|
||
git -C "$d" -c user.name=sb -c user.email=sb@local commit -qm "baseline-e8" || true
|
||
local base8; base8=$(git -C "$d" rev-parse HEAD)
|
||
local r_hold; r_hold=$(route_orphan "$d" "voellig-ohne-ziel")
|
||
if [ "$r_hold" != "ORPHAN" ]; then
|
||
runlabel FAIL "E-8" "unaufloesbares Ziel routet nicht in den Hold-Raum ($r_hold)"; return 1
|
||
fi
|
||
printf '%s\n' "# Log" "" "## $(date +%Y-%m-%d)" \
|
||
"- **Orphan-Hold (Story 3.10, fail-closed):** Quell-Pfad: wiki/wiki-area/orphan-a.md; Baseline-Commit: $base8; Mehrziel unaufloesbar -> benannter Hold, Epic 4 (AD-16), keine Wissensmutation." \
|
||
> "$d/wiki/log.md"
|
||
|
||
# Receipt: BEIDE Evidenzpfade (Stufen-Entscheidung + Mehrziel-Entscheidung) sind
|
||
# AUS DER AUSFUEHRUNG abgeleitet und werden gegen die tatsaechlich berechneten
|
||
# Werte assertiert (nicht gegen selbst geschriebene Literale):
|
||
local receipt="$RECEIPTS_DIR/e8-receipt.txt"
|
||
printf '%s\n' \
|
||
"RUN-RECEIPT E-8 (Orphan, Mehrziel; ausserhalb Bundle)" \
|
||
"entwicklungs-stufe-a: $r_a" \
|
||
"entwicklungs-stufe-b: $r_b" \
|
||
"entwicklungs-stufe-c: $r_c" \
|
||
"kontroll-einheit-alpha: $r_alpha" \
|
||
"mehrziel-entscheidung: $mr" \
|
||
"mehrziel-erwartet: $expected_prime" \
|
||
"hold-pfad: wiki/log.md (fail-closed, Epic 4)" \
|
||
> "$receipt"
|
||
[ "$r_a" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-a nicht ORPHAN"; return 1; }
|
||
[ "$r_b" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-b nicht ORPHAN"; return 1; }
|
||
[ "$r_c" = "ORPHAN" ] || { runlabel FAIL "E-8" "Receipt: Stufe-c nicht ORPHAN"; return 1; }
|
||
[ "$r_alpha" = "UPDATE" ] || { runlabel FAIL "E-8" "Receipt: Kontrolle nicht UPDATE"; return 1; }
|
||
grep -q "mehrziel-entscheidung: $mr" "$receipt" \
|
||
|| { runlabel FAIL "E-8" "Receipt ohne Mehrziel-Entscheidungs-Wert"; return 1; }
|
||
grep -q "mehrziel-erwartet: $expected_prime" "$receipt" \
|
||
|| { runlabel FAIL "E-8" "Receipt ohne Mehrziel-Erwartet-Wert"; return 1; }
|
||
grep -q "hold-pfad: wiki/log.md" "$receipt" \
|
||
|| { runlabel FAIL "E-8" "Receipt ohne Hold-Pfad"; return 1; }
|
||
|
||
# S5.9 Pkt. 5: unter wiki/ entsteht NUR log.md als neue Datei (kein Sidecar,
|
||
# keine weitere neue Datei ausser log.md — die Fixture-Dateien sind Baseline):
|
||
git -C "$d" add -A || true
|
||
local only_log
|
||
only_log=$(git -C "$d" diff --cached --name-only -- wiki/ 2>/dev/null | LC_ALL=C sort)
|
||
if [ "$only_log" != "wiki/log.md" ]; then
|
||
runlabel FAIL "E-8" "wiki-Delta != nur log.md (Sidecar oder mehr: $only_log)"; return 1
|
||
fi
|
||
runlabel PASS "E-8" "Orphan-Mech. ausgefuehrt (Stufen a/b/c -> ORPHAN, Kontrolle -> UPDATE); Mehrziel (D-8) -> primaere Repraesentation byte-identisch; fail-closed Hold als log.md; nur log.md neu; beide Evidenzpfade im Receipt (AC-8)"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# E-9 ZWEI_RUN_IDENTITAET (Determinismus-Rahmen): zwei identische Eingaben
|
||
# -> byte-identische Ergebnisse; Zustandswechsel -> messbare Aenderung
|
||
# (Nicht-Vakuum des Determinismus-Beweises).
|
||
# ============================================================================
|
||
E9_run() {
|
||
local d="$1"
|
||
isolate "$d"
|
||
mkdir -p "$d/wiki/wiki-area"
|
||
local f="$d/wiki/wiki-area/9.md"
|
||
printf '%s\n' "# 9er" "" "Aussage Neun." "Quelle: n-src." > "$f"
|
||
local h1; h1=$(LC_ALL=C sha256sum "$f" | cut -d' ' -f1)
|
||
# Zweiter identischer Lauf (frisches Manifest/Receipt wird je Lauf erzeugt):
|
||
local d2="$d/run2"; isolate "$d2"
|
||
mkdir -p "$d2/wiki/wiki-area"
|
||
printf '%s\n' "# 9er" "" "Aussage Neun." "Quelle: n-src." > "$d2/wiki/wiki-area/9.md"
|
||
local h2; h2=$(LC_ALL=C sha256sum "$d2/wiki/wiki-area/9.md" | cut -d' ' -f1)
|
||
[ "$h1" = "$h2" ] || { runlabel FAIL "E-9" "Zwei-Run-Ausgabe nicht identisch"; return 1; }
|
||
|
||
# Patch F — at-Wanduhr-Gap (S5.16 Pkt. 7 / S5.14 Pkt. 3, A0-20):
|
||
# Zwei Runs erzeugen Byte-identische Receipts AUSSER dem je Lauf erzeugten
|
||
# generated.at-Feld. Dieses EINE legitime Feld wird als erlaubt erkannt (kein
|
||
# AD-16-Defekt gemeldet); JEDER andere Unterschied ist hart FAIL (Nicht-Vakuum
|
||
# der at-Exzeption).
|
||
local r1="$RECEIPTS_DIR/e9-runA.receipt"
|
||
local r2="$RECEIPTS_DIR/e9-runB.receipt"
|
||
local atA; atA=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
local atB; atB=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
[ "$atA" != "$atB" ] || atB=$(date -u -d "@$(( $(date -u +%s) + 1 ))" +%Y-%m-%dT%H:%M:%SZ)
|
||
# RUECKTITEL: beide Receipts tragen DEN GLEICHEN neutralen Titel (kein
|
||
# Lauf-A/Lauf-B-Kennzeichen) — die EINZIGE legitime Differenz ist das
|
||
# generated.at-Feld (S5.14 Pkt. 3 / S5.16 Pkt. 7).
|
||
printf '%s\n' \
|
||
"RUN-RECEIPT E-9 (generated.at; ausserhalb Bundle)" \
|
||
"generated.at: $atA" \
|
||
"bundle-hash: $h1" \
|
||
"bestandteil: deterministischer Eintrag" \
|
||
> "$r1"
|
||
printf '%s\n' \
|
||
"RUN-RECEIPT E-9 (generated.at; ausserhalb Bundle)" \
|
||
"generated.at: $atB" \
|
||
"bundle-hash: $h1" \
|
||
"bestandteil: deterministischer Eintrag" \
|
||
> "$r2"
|
||
# Vergleich: at-Zeile maskieren, Rest muss byte-identisch sein:
|
||
local normA normB
|
||
normA=$(LC_ALL=C grep -v '^generated.at:' "$r1" || true)
|
||
normB=$(LC_ALL=C grep -v '^generated.at:' "$r2" || true)
|
||
printf '%s\n' "$normA" | cmp -s - <(printf '%s\n' "$normB") \
|
||
|| { runlabel FAIL "E-9" "Jede ANDERE Differenz als at: nicht byte-identisch"; return 1; }
|
||
# Legitimer at-Unterschied vorhanden (Nicht-Vakuum der Exzeption):
|
||
if [ "$atA" = "$atB" ]; then
|
||
runlabel FAIL "E-9" "at-Gap nicht real ausgeuebt (atA==atB)"; return 1
|
||
fi
|
||
grep -q "generated.at: $atA" "$r1" || { runlabel FAIL "E-9" "Receipt A ohne atA"; return 1; }
|
||
grep -q "generated.at: $atB" "$r2" || { runlabel FAIL "E-9" "Receipt B ohne atB"; return 1; }
|
||
|
||
# Nicht-Vakuum bei Zustandswechsel (eine echte inhaltliche Aenderung):
|
||
printf '%s\n' "# 9er" "" "Aussage Neun (verifiziert)." "Quelle: n-src." > "$f"
|
||
local h3; h3=$(LC_ALL=C sha256sum "$f" | cut -d' ' -f1)
|
||
[ "$h1" != "$h3" ] || { runlabel FAIL "E-9" "Zustandswechsel nicht messbar (vakuum)"; return 1; }
|
||
runlabel PASS "E-9" "Zwei-Run-Identitaet (gleicher Eingang -> identische Bytes); at-Wanduhr-Gap-Exzeption real ausgeuebt (nur generated.at differiert); Nicht-Vakuum bei Zustandswechsel"
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# MAIN — alle E-Szenarien in isolierten /tmp-Baeumen unterhalb SB_DIR
|
||
# ============================================================================
|
||
main() {
|
||
log_bullet "Sandbox Story 3.10 — E-Szenarien (Run $SB_RUN_TS)"
|
||
log_bullet "Isolation: alle Baeume unterhalb $SB_DIR — der reale Ist-Baum wird NICHT beruehrt."
|
||
|
||
local t
|
||
t="$SB_DIR/e1"; E1_run "$t" || true
|
||
t="$SB_DIR/e2"; E2_run "$t" || true
|
||
t="$SB_DIR/e3"; E3_run "$t" || true
|
||
t="$SB_DIR/e4"; E4_run "$t" || true
|
||
t="$SB_DIR/e5"; E5_run "$t" || true
|
||
t="$SB_DIR/e6"; E6_run "$t" || true
|
||
t="$SB_DIR/e7"; E7_run "$t" || true
|
||
t="$SB_DIR/e8"; E8_run "$t" || true
|
||
t="$SB_DIR/e9"; E9_run "$t" || true
|
||
|
||
log_bullet ""
|
||
log_bullet "PASS=$PASS FAIL=$FAIL"
|
||
if [ "$FAIL" -gt 0 ]; then
|
||
log_bullet "HARD-FAIL E-Szenarien:$FAILED_NAMES"
|
||
exit 1
|
||
fi
|
||
log_bullet "SAND_EXIT=0 (re-executierbar; Run $SB_RUN_TS)"
|
||
exit 0
|
||
}
|
||
main "$@"
|