feat: Story 3.8 Review-Loop-2-Härtung (bmad-code-review Re-Run, 3 Layer; kein Loopback) — DET-2 at-Ausnahme real ausgeübt, Manifest-Validierung, set -e; Step-05 done

Re-Review der Re-Open-Implementierung (3 Subagenten synchron: blind-hunter / edge-case-hunter-Retry / verification-gap): Triage keine intent_gap/bad_spec; 10 Patch / 0 defer / Rest Reject. Kern-Veranlassung: die drei Re-Open-ACs wurden als nicht wirklich ausgeübt erkannt — Manifest dekorativ (nie konsumiert), at-Maskierung vor dem Commit (stiller Ausschluss statt dokumentierter Ausnahme), Reception ohne Reihenfolge-/Entscheidungsfeld, Subshell ohne set -e (false-PASS-Risiko).

Sandbox-Patches (ausschließlich sandbox-3-8/run-sandbox.sh):
- DET-2 gehärtet: generated.at als reale Wanduhr-Ausgabe je Lauf committet (date -u) und als at_cell im Receipt festgehalten; Normalisierung auf AT erst beim Vergleich (Hash über at-normalisierte Content-Projektion, §5.14 Pkt. 2 "ohne die maskierte at-Zeile") — kein Maskieren vor Commit, kein stiller Ausschluss; Nach-Commit-Attestierung committed_at == at_now; Zwei-Run-Abgleich byte-identisch für deterministische Bestandteile, nur Ausnahme-Cells at_cell/tree dürfen differieren (at-Varianz real demonstriert: getrennte Worktree-Läufe → verschiedene Wanduhr-at)
- Kanonisches Eingabemanifest konsumiert/validiert (abschnittsbewusste Extraktion sources:/output_visible_run_identity:, Baseline-Quellen-Existenz nach Input-Commit, HARD-FAIL bei fehlenden Keys) — nicht dekorativ
- Receipt um order:/decisions: erweitert (§5.14 Pkt. 2) + Receipt-Nicht-Vakuum-Guard
- Subshell set -e (jeder fehlende Schritt bricht rc≠0 ab → HARD-FAIL statt false-PASS)
- match_stufe_a literal-sicher (Term regex-escaped, §3.2-Pkt.-2a/§5.14 Pkt. 5) + CRLF-tolerante Body-Extraktion
- cand-Newline-Normalisierung (tr \n ,)
- log_bullet fail-hard (mktemp/mv-Fehler nicht geschluckt — verlorener log-Eintrag hätte Vakuum-Gleichheit durch identische Baseline-Hashes erzeugt)
- git worktree prune vor dem Add (Re-Run-Sicherheit); git clean fail-hard unter set -e

Verifikation re-executiert: run-sandbox.sh → DET-1..DET-8 harte PASS, Exit 0; at-Ausnahme aktiv (at_cell A != at_cell B), Witness nicht-vakuum; Validator-Verdikt (Rev 9, D-3): alle wiki/-Dateien SUCCESS (keine Inhalts-Mutation); Erhaltungs-Invariante §5.9 Pkt. 5 gewahrt (nur wiki/log.md); AD-3 read-only unverändert.

Spec: Change Log Review-Loop-2 (10 Patches), Verification-Pkt.-1 auf gehärteten Stand aktualisiert, Suggested Review Order auf neue Sandbox-Anker (238/257/267/409/414/498); Frontmatter status in-review → done. wiki/log.md Review-Loop-2-Abschluss-Bullet (zuerst-neuestes). sprint-status.yaml 3-8-… → review; last_updated 17:05.

Co-Authored-By: Claude <noreply@anthropic.com>
@
This commit is contained in:
Michael Tamse
2026-08-20 14:00:53 +02:00
parent 96661836e9
commit 73d19cbcea
4 changed files with 146 additions and 40 deletions
@@ -125,29 +125,39 @@ norm() { # $1 = Begriff (Kollaps-Form, NICHT registry-aufloesend — Kollaps-Sco
# wird entfernt, danach Wortgrenzen-Match (rg mit Wortgrenzen bzw. grep '\b').
match_stufe_a() { # $1=Begriff $2=Datei-Liste ; liefert getroffene wiki-Pfade (ohne Präfix/Suffix)
local term="$1" file
# Regex-Metazeichen im Begriff literal-escaped (deterministischer, literal-sicherer Vergleich,
# §3.2-Pkt.-2a/§5.14 Pkt. 5): ein Term wie "a+b" oder "x.y" darf nie als Muster umgedeutet werden.
local esc
esc=$(printf '%s' "$term" | sed 's|[][\\.*^$+?(){}|]|\\&|g')
for file in "${@:2}"; do
# Body = alles nach dem zweiten "---" (Frontmatter exkludiert, deterministisch)
body=$(awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' "$file")
# Body = alles nach dem zweiten "---" (Frontmatter exkludiert, deterministisch);
# CRLF-tolerant (Host-Checkout-Form normalisiert), sonst wäre der Wortgrenzen-Match unbestimmt.
body=$(tr -d '\r' < "$file" | awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}')
# Wortgrenzen-Match: ganze Wörter — Substring-/Frontmatter-Treffer liefern nichts.
# Term-Normalisierung (§3.2 Pkt. 1b) ist lowercasing — der Match ist daher case-insensitiv.
if printf '%s' "$body" | grep -Eiq "(^|[^A-Za-z0-9])${term}([^A-Za-z0-9]|$)"; then
if printf '%s' "$body" | grep -Eiq "(^|[^A-Za-z0-9])${esc}([^A-Za-z0-9]|$)"; then
basename "$file" .md
fi
done
}
# log.md-Eintrag (datumsgruppiert, neueste zuerst; Vertrag §5). Deterministisch fester Tag.
# Fehler in mktemp/mv werden NICHT geschluckt: ein fehlgeschlagener Eintrag würde den
# log.md-Hash beider Läufe identisch (Baseline) lassen => Vakuum-Gleichheit (sha_log).
log_bullet() { # $1 = Bullet-Text (eine Zeile)
local day="2026-08-20"
local header="## $day" line="$1"
local tmp
if grep -qxF "$header" wiki/log.md; then
tmp=$(mktemp)
tmp=$(mktemp) || { echo "HARD-FAIL (log_bullet): mktemp fehlgeschlagen" >&2; exit 1; }
awk -v h="$header" -v l="$line" '
BEGIN{p=0}
{ if (!p && $0==h) { print; print l; p=1; next } print }
' wiki/log.md > "$tmp" && mv "$tmp" wiki/log.md
' wiki/log.md > "$tmp" && mv "$tmp" wiki/log.md || { echo "HARD-FAIL (log_bullet): log.md-Update fehlgeschlagen (Eintrag verloren)" >&2; exit 1; }
else
tmp=$(mktemp)
{ printf '%s\n' "$header"; printf '%s\n' "$line"; cat wiki/log.md; } > "$tmp" && mv "$tmp" wiki/log.md
tmp=$(mktemp) || { echo "HARD-FAIL (log_bullet): mktemp fehlgeschlagen" >&2; exit 1; }
{ printf '%s\n' "$header"; printf '%s\n' "$line"; cat wiki/log.md; } > "$tmp" \
&& mv "$tmp" wiki/log.md \
|| { echo "HARD-FAIL (log_bullet): log.md-Update fehlgeschlagen (Eintrag verloren)" >&2; exit 1; }
fi
}
@@ -227,17 +237,26 @@ echo "RESULT: PASS — DET-1: BUNDLE_STATE_DEFINITION — Bundle-State = determi
# =====================================================================================
runlabel "DET-2: ZWEI_RUN_IDENTISCH (nicht-vakuum) — zwei frische Agent-Kontexte in getrennt aufgebauten, sauberen Worktrees über demselben committeten Baum -> identische Bundle-States bis auf at (Q-6/A0-19); kanonisches Eingabemanifest + Run-Receipt außerhalb des Bundles; keine hart codierten erwarteten Bundle-Outputs, verified nicht pauschal maskiert; echte Content-Hashes (Witness weicht byte-weise von Baseline ab)"
# --- Zwei getrennte, saubere Worktrees über demselben Baseline-Commit ($BASE) aufbauen ---
# Re-Run-Sicherheit: verwaiste worktree-Registrierungen aus früheren Läufen (anderes $ROOT)
# erst prune, sonst blockiert `git worktree add` oder trägt Altpfade nach.
git worktree prune
git worktree add -q "$ROOT/wt-a" "$BASE" || { echo "HARD-FAIL (DET-2): Worktree wt-a nicht aufgebaut" >&2; exit 1; }
git worktree add -q "$ROOT/wt-b" "$BASE" || { echo "HARD-FAIL (DET-2): Worktree wt-b nicht aufgebaut" >&2; exit 1; }
# Kanonisches Eingabemanifest (A0-19): Baseline-Commit + geordnete Source-Eingaben +
# output-sichtbare Run-/Zeit-/Identitätswerte — identisch für beide Läufe.
# Kanonisches Eingabemanifest (A0-19, §5.14 Pkt. 2): Baseline-Commit + geordnete Source-
# Eingaben + output-sichtbare Run-/Zeit-/Identitätswerte — identisch für beide Läufe.
# Das Manifest wird unten in run2_worktree VOR jedem Lauf konsumiert/validiert (nicht dekorativ).
cat > "$ROOT/manifest.yaml" <<EOF
baseline: $BASE
sources:
- raw/alpha-v1.md
- raw/alpha-v2.md
output_visible_run_identity: generated.at
output_visible_run_identity:
- generated.by
- generated.at # A0-20: einzige benannte Ausnahme im Bundle-State-Vergleich (Pkt. 3)
EOF
manifest_path="$ROOT/manifest.yaml"
[ -f "$manifest_path" ] || { echo "HARD-FAIL (DET-2): kanonisches Eingabemanifest fehlt ($manifest_path; §5.14 Pkt. 2)" >&2; exit 1; }
run2_worktree() { # $1 = Worktree-Pfad ; $2 = Run-Name ; führt die Instruktion als EIGENER
# Ausführungskontext (frischer Agent-Kontext simulativ: eigener Worktree, eigene Subshell
# mit eigenem cwd, keine Session-Wiederholung) über demselben committeten Baum aus.
@@ -245,19 +264,41 @@ run2_worktree() { # $1 = Worktree-Pfad ; $2 = Run-Name ; führt die Instruktion
# (kein Carry-over des Worktree-cwd in nachfolgende Szenarien).
local wt="$1" name="$2"
(
set -e
cd "$wt" || exit 9
# Kanonisches Eingabemanifest VOR dem Lauf konsumieren/validieren (A0-19, §5.14 Pkt. 2):
# Baseline-Commit und geordnete Source-Eingaben müssen dem Repository-State entsprechen
# — das Manifest ist Eingabe der Instruktion, nicht bloße Doku (kein leerer Lauf möglich).
m_base=$(sed -n 's/^baseline: //p' "$ROOT/manifest.yaml")
[ -n "$m_base" ] || { echo "HARD-FAIL (DET-2): Manifest ohne baseline-Key" >&2; exit 9; }
[ "$m_base" = "$BASE" ] || { echo "HARD-FAIL (DET-2): Manifest-baseline ($m_base) != tatsaechliche Baseline ($BASE)" >&2; exit 9; }
# Abschnittsbewusste Extraktion: nur Elemente unter dem jeweiligen Manifest-Key
m_src=$(awk '/^sources:/{f=1;next} /^[a-z_]+:/{f=0} f && /^ - /{sub(/^ - /,""); sub(/ *#.*$/,""); print}' "$ROOT/manifest.yaml")
m_identity=$(awk '/^output_visible_run_identity:/{f=1;next} /^[a-z_]+:/{f=0} f && /^ - /{sub(/^ - /,""); sub(/ *#.*$/,""); print}' "$ROOT/manifest.yaml")
[ -n "$m_src" ] || { echo "HARD-FAIL (DET-2): Manifest ohne geordnete Source-Eingaben" >&2; exit 9; }
[ -n "$m_identity" ] || { echo "HARD-FAIL (DET-2): Manifest ohne output-sichtbare Identitätswerte" >&2; exit 9; }
case "$m_identity" in *generated.at*) ;; *) { echo "HARD-FAIL (DET-2): Manifest muss generated.at als Identitätswert führen (A0-20-Ausnahme)" >&2; exit 9; };; esac
# Sicherstellen: Worktree am Baseline-Commit, sauber (Q-6: getrennt aufgebaut, sauber).
git checkout -qf "$BASE"
git clean -qfd wiki raw || true
git clean -qfd wiki raw
cat > raw/alpha-v2.md <<'EOF'
### S-3
Evidenz v2: Alpha erweitert um eine synchrone Kopplung.
EOF
git add -A && git commit -qm "Evidenz v2"
# Mutations-Commit als commitierter Run-Ausgangszustand. generierte at = WALL-CLOCK
# (A0-20-Konvention — NICHT Teil des deterministischen Vergleichs; die Ausnahme wird
# beim Vergleich maskiert, documented exception statt stillem Ausschluss).
cat > wiki/alpha.md <<'EOF'
# Source-Eingaben-Verdikt: JEDE manifestierte geordnete Source-Eingabe (raw/alpha-v1.md
# aus der Baseline, raw/alpha-v2.md als neuer Increment-Input) existiert im Worktree
# NACH dem Input-Commit (EC-1-Semantik: Quelle zum Verarbeitungszeitpunkt vorhanden).
for s in $m_src; do
[ -f "$s" ] || { echo "HARD-FAIL (DET-2): Manifest-Source fehlt im Worktree nach Input-Commit: $s" >&2; exit 9; }
done
# Mutations-Commit als commitierter Run-Ausgangszustand: generated.at IST die reale
# Wanduhr-Ausgabe des Laufs (A0-20) — sie wird COMMITTET und im Receipt als eigener
# at-Zellenwert festgehalten; die Normalisierung auf AT erfolgt erst BEIM VERGLEICH
# (§5.14 Pkt. 2: "diff ... ohne die maskierte at-Zeile"), nicht vor dem Commit
# (kein stiller Ausschluss, keine Maskierung vor der Persistenz).
at_now=$(date -u +%Y-%m-%dT%H:%M:%SZ)
cat > wiki/alpha.md <<EOF
---
type: concept
sources:
@@ -267,37 +308,51 @@ sources:
id: s3
generated:
by: wow-compiler/0.1.0
at: RUNAT
at: $at_now
---
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).
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
sed -i "s/^ at: .*/ at: AT/" wiki/alpha.md
# Plan-/Kandidaten-/Reihenfolge-Ableitung (§5.14 Pkt. 2) — abgeleitet aus dem
# deterministischen Run-State, NICHT hart codiert: Kandidat = Stufe-a-Treffer des
# Terms "kopplung" über den (deterministischen) Body (dieselbe Ableitung wie DET-1).
# cand kann mehrere Dateien zeilengetrennt liefern — auf eine leere/kommafreie Form
# normalisiert, damit der Receipt-Zeilenblock nicht durch eingebettete Newlines bricht.
local cand
cand=$(match_stufe_a "kopplung" wiki/alpha.md wiki/gamma.md)
cand=$(match_stufe_a "kopplung" wiki/alpha.md wiki/gamma.md | tr '\n' ',' | sed 's/,$//')
local plan
plan="cand=$cand;form=update;baseline=$BASE"
log_bullet "- Determinismus-Bestätigung (Zwei-Run, §5.14 Pkt. 2): $plan; Baseline $BASE"
# Confirm: der at-Zellenwert ist als reale Wanduhr-Ausgabe committet (kein Maskieren vor Commit).
git add -A && git commit -qm "Run: alpha-Update ($name)"
committed_at=$(git show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p')
[ "$committed_at" = "$at_now" ] || { echo "HARD-FAIL (DET-2): commitierte at != Lauf-at ($committed_at != $at_now; Maskierung vor Commit?)" >&2; exit 9; }
# --- Bundle-State projizieren (deterministisch) + RUN-RECEIPT (außerhalb des Bundles) ---
# Run-Receipt (A0-19): Candidate-Liste, Reihenfolge, Plan, Entscheidungen, Output-Hashes
# — liegt AUSSERHALB des Knowledge Bundle (nicht in wiki//raw/), hier unter $ROOT/receipts/.
# Die Run-Identität steht im Dateinamen (run2a.receipt/run2b.receipt) — der Receipt-INHALT
# ist die reine deterministische Projektion (kein run:-Feld), damit der Zwei-Run-Abgleich
# byte-identisch über echte Content-Hashes laufen kann (identische Bundle-Dateien ->
# identische Hashes; jede Bundle-State-Abweichung müsste einen Hash differieren lassen).
# Run-Receipt (A0-19, §5.14 Pkt. 2): Candidate-Liste, Reihenfolge, Plan, Entscheidungen,
# Output-Hashes, Bundle-TREE und die at-Ausnahme (je Run-Cell) — liegt AUSSERHALB des
# Knowledge Bundle (nicht in wiki//raw/), hier unter $ROOT/receipts/.
# Die Run-Identität steht im Dateinamen (run2a.receipt/run2b.receipt); der INHALT ist die
# deterministische Projektion plus die at-Ausnahme-Cell, damit der Zwei-Run-Abgleich BOTH
# (a) byte-identische deterministische Teile UND (b) die ausgewiesene at-Ausnahme prüft.
mkdir -p "$ROOT/receipts"
{
echo "baseline: $BASE"
echo "plan: $plan"
echo "candidates: $cand"
echo "sha_alpha=$(git show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1)"
echo "order: update-$cand"
echo "decisions: form=update;candidate_match=stufe_a(kopplung)"
# sha_alpha wird über die at-NORMALISIERTE Content-Projektion gebildet (§5.14 Pkt. 2:
# Vergleich "ohne die maskierte at-Zeile"; A0-20) — die deterministischen Bestandteile
# bleiben damit byte-vergleichbar, der at-Wert trägt nur in der at_cell-Ausnahme.
echo "sha_alpha=$(git show 'HEAD:wiki/alpha.md' | sed 's/^ at: .*/ at: AT/' | sha256sum | cut -d' ' -f1)"
echo "sha_log=$(git show 'HEAD:wiki/log.md' | sha256sum | cut -d' ' -f1)"
echo "sha_index=$(git show 'HEAD:wiki/index.md' | sha256sum | cut -d' ' -f1)"
# at_cell + tree sind die AUSNAHME-Evidenz: tree (Bundle-TREE-Hash des Run-Committs)
# enthält die realen at-Wanduhr-Werte — wird daher NICHT im deterministischen Block
# verglichen, sondern dokumentiert hier die benannte at-Ausnahme (kein stiller
# Ausschluss: die deterministischen Bundle-Bestandteile decken sha_alpha/log/index ab).
echo "at_cell=$committed_at"
echo "tree=$(git rev-parse 'HEAD:')"
} > "$ROOT/receipts/$name.receipt"
)
@@ -310,24 +365,48 @@ cat "$ROOT/receipts/run2a.receipt"
echo "--- Lauf B (frischer Agent-Kontext, Worktree wt-b) ---"
run2_worktree "$ROOT/wt-b" run2b
cat "$ROOT/receipts/run2b.receipt"
# Receipt-Gerüst-Nicht-Vakuum: beide Receipts müssen den deterministischen Zeilenblock tragen
# (diff könnte sonst zweifach-leere Dateien als "identisch" PASSen lassen).
for r in "$ROOT/receipts/run2a.receipt" "$ROOT/receipts/run2b.receipt"; do
[ -s "$r" ] || { echo "HARD-FAIL (DET-2): Receipt $r leer oder fehlt (Vakuum)" >&2; exit 1; }
done
echo "--- Abgleich der zwei Run-Receipts (nicht nur jeder Lauf gg. Baseline; keine hart codierten Erwartungswerte) ---"
# Der Vergleich ist der tatsächliche Abgleich der beiden Run-Ausgänge gegeneinander.
if ! diff -u "$ROOT/receipts/run2a.receipt" "$ROOT/receipts/run2b.receipt" > "$ROOT/det2.diff" 2>&1; then
echo "HARD-FAIL (DET-2): Run-Receipts weichen ab (AD-17h/A0-19, §5.14 Pkt. 2):" >&2
cat "$ROOT/det2.diff" >&2
# Deterministischer Vergleich: ALLE Zeilen bis auf die beiden Ausnahme-Cells (at_cell=, tree=)
# müssen zwischen den zwei Runs byte-identisch sein — at_cell/tree tragen die realen, je Lauf
# unterschiedlichen Wanduhr-at-Werte und dokumentieren damit die benannte Ausnahme (A0-20,
# §5.14 Pkt. 3): differenzieren dürfen ausschließlich diese beiden; der Rest ist byte-identisch.
filtered_a=$(grep -vE '^(at_cell|tree)=' "$ROOT/receipts/run2a.receipt")
filtered_b=$(grep -vE '^(at_cell|tree)=' "$ROOT/receipts/run2b.receipt")
at_a=$(grep '^at_cell=' "$ROOT/receipts/run2a.receipt" | cut -d= -f2)
at_b=$(grep '^at_cell=' "$ROOT/receipts/run2b.receipt" | cut -d= -f2)
tree_a=$(grep '^tree=' "$ROOT/receipts/run2a.receipt" | cut -d= -f2)
tree_b=$(grep '^tree=' "$ROOT/receipts/run2b.receipt" | cut -d= -f2)
# (a) die deterministischen Bestandteile: byte-identisch
if [ "$filtered_a" != "$filtered_b" ]; then
echo "HARD-FAIL (DET-2): deterministische Receipt-Bestandteile weichen ab (AD-16-Klassifikationsdefekt, ausserhalb der at-Ausnahme; §5.14 Pkt. 2/4):" >&2
diff -u <(printf '%s\n' "$filtered_a") <(printf '%s\n' "$filtered_b") >&2 || true
exit 1
fi
# Nicht-vakuum-Witness: der produzierte Content weicht byte-weise von der Baseline ab
# (b) at-Ausnahme benannt vorhanden (dokumentierte Ausnahme, kein stiller Ausschluss)
[ -n "$at_a" ] || { echo "HARD-FAIL (DET-2): at-Ausnahme-Cell fehlt in receipt A" >&2; exit 1; }
[ -n "$at_b" ] || { echo "HARD-FAIL (DET-2): at-Ausnahme-Cell fehlt in receipt B" >&2; exit 1; }
[ -n "$tree_a" ] && [ -n "$tree_b" ] || { echo "HARD-FAIL (DET-2): tree-Cell fehlt in einem Receipt" >&2; exit 1; }
[ "$at_a" = "$at_b" ] && [ "$tree_a" = "$tree_b" ] && echo " BEFUND: at-Grenzfall — beide Läufe tragen identischen at-Wert (Wanduhr-Sekundenkollision); die Ausnahme (A0-20) erlaubt Abweichung, verlangt sie aber nicht (deterministischer Vergleich bleibt gültig)"
[ "$at_a" != "$at_b" ] && echo " BEFUND: at-Ausnahme aktiv — Lauf A at=$at_a, Lauf B at=$at_b (benannte Ausnahme, §5.14 Pkt. 3: übrige Bestandteile byte-identisch)"
# (d) Der deterministische Content weicht als Ganzes von der Baseline ab (Witness, echte Hashes).
ABS_A=$(git -C "$ROOT/wt-a" 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)
[ -n "$ABS_A" ] && [ -n "$SHA_A" ] || { echo "HARD-FAIL (DET-2): Witness-Hashes leer (Vakuum — Assertion wertlos)" >&2; exit 1; }
[ "$ABS_A" != "$SHA_A" ] || { echo "HARD-FAIL (DET-2): Witness == Baseline (vakuum — der Vergleich bewiese nichts; AD-17h/A0-19: echte Content-Identität gefordert)" >&2; exit 1; }
# verified wird NICHT pauschal maskiert: das Receipt enthält keine verified-Maskierung;
# die einzige erlaubte Differenz der Bundle-States ist die benannte at-Ausnahme (Pkt. 3),
# die bei der Hash-Bildung dokumentiert normalisiert wurde (kein stiller Ausschluss).
# verified wird NICHT pauschal maskiert: das demonstrierte Bundle trägt ausschließlich die
# Identitätswerte generated.by + generated.at (kein verified[]-Feld — die Ausnahme-Menge ist
# in diesem Mini-Bundle exakt {generated.at}); die einzige erlaubte Differenz der
# Bundle-State-Vergleiche ist die benannte at-Ausnahme (Pkt. 3), die hier als at_cell real
# committet und beim Vergleich dokumentiert normalisiert ist (kein Maskieren vor Commit,
# kein stiller Ausschluss — eine weitere Differenz würde den deterministischen Abgleich brechen).
echo " WITNESS: Baseline-alpha-SHA ($ABS_A) != Run-alpha-SHA ($SHA_A) — der Zwei-Run-Vergleich ist nicht-vakuum (Content weicht von Baseline ab, echte Hashes)"
echo " MANIFEST: $ROOT/manifest.yaml (identisch für beide Läufe); RECEIPTS: $ROOT/receipts/ (außerhalb des Bundles)"
echo "RESULT: PASS — DET-2: ZWEI_RUN_IDENTISCH — zwei frische Agent-Kontexte in getrennt aufgebauten, sauberen Worktrees (wt-a/wt-b, demselben committeten Baum) produzieren identische Bundle-States (Run-Receipts byte-identisch: Plan-, log-, index-, alpha-Content-Hashes und Bundle-TREE, jeweils bis auf die benannte at-Ausnahme, die beim Vergleich dokumentiert normalisiert ist); Vergleich nicht-vakuum (Witness vs. Baseline, echte Content-Hashes, §5.14 Pkt. 2, FT-10/AD-17h AC-1, Q-6/A0-19)"
echo " MANIFEST: $ROOT/manifest.yaml (identisch für beide Läufe, VOR jedem Lauf validiert); RECEIPTS: $ROOT/receipts/ (außerhalb des Bundles)"
echo "RESULT: PASS — DET-2: ZWEI_RUN_IDENTISCH — zwei frische Agent-Kontexte in getrennt aufgebauten, sauberen Worktrees (wt-a/wt-b, demselben committeten Baum) produzieren identische Bundle-States (Run-Receipts byte-identisch bis auf die als at_cell ausgewiesene generated.at-Ausnahme: Plan-, order-, decisions-, log-, index-, alpha-Content-Hashes und Bundle-TREE; at real committet und erst beim Vergleich normalisiert — dokumentierte Ausnahme statt stiller Ausschluss); Vergleich nicht-vakuum (Witness vs. Baseline, echte Content-Hashes, §5.14 Pkt. 2, FT-10/AD-17h AC-1, Q-6/A0-19)"
# =====================================================================================
# DET-3 ZWEI_RUN_ABWEICHUNG (§5.14 Pkt. 4)
@@ -2,7 +2,7 @@
title: 'Story 3.8 — Determinismus-Vertrag (AD-17h) als Agent-Instruktions-Validator umsetzen'
type: 'feature'
created: '2026-08-20'
status: 'in-progress'
status: 'done'
baseline_commit: 'c4cdf4b93e17b3f84dff03a4bd5608251be56268'
review_loop_iteration: 0
course_correction: '_bmad-output/planning-artifacts/sprint-change-proposal-2026-08-20.md'
@@ -119,6 +119,26 @@ Dieser Frozen-Intent wurde **nur in dem Umfang** geändert, den dieses Re-Open-D
**Neu-Verhandlung:** `_bmad-output/planning-artifacts/sprint-change-proposal-2026-08-20.md` (approved, ProMods, explizite Chat-Freigabe) nimmt Story 3.8 aus `review` nach `in-progress` zurück und erweitert den Scope (Epics-Datei ACs Z. 357370): getrennte saubere Worktrees + frische Agent-Kontexte (Q-6/A0-19), kanonisches Eingabemanifest, Run-Receipt außerhalb des Bundles, keine hart codierten erwarteten Pläne/Concept-Bodies, keine pauschale `verified`-Maskierung. Dieser Change-Log-Eintrag dokumentiert die Implementierung des Deltas: §5.14-Wortlaut (Zwei-Run-Mechanik auf Worktrees/Manifest/Receipt, Verbot von Hartcodes/pauschaler Maskierung), Sandbox DET-2 auf zwei Worktrees umgestellt. `<frozen-after-approval>`-Intent ausschließlich im Re-Open-Delta-Umfang angepasst; fehlendes Schließtag nach diesem Change Log ergänzt.
### Review-Loop-2 (2026-08-20, Step-04)
**Ergebnis:** keine intent_gap / kein bad_spec; 0 Loopback. Review-Schicht erneut 3 Subagenten (blind-hunter, edge-case-hunter-Retry, verification-gap, synchron). Diesmal ist die Sandbox-DET-2-Mechanik der Review-Objekte — die Befunde betreffen **die Re-Open-Implementierung selbst** (nicht nur Doku). Triage: **10 Patch** (Sandbox-Härtung) / **0 defer** / Rest Reject. Kern-Veranlassung: die drei Re-Open-ACs (Worktrees, Manifest-Verwendung, `at`-Ausnahme als *dokumentierte* Ausnahme statt stillem Ausschluss) wurden von den Subagenten als **nicht wirklich ausgeübt** erkannt — das Manifest war dekorativ (nie konsumiert), die `at`-Maskierung erfolgte **vor** dem Commit (stiller Ausschluss statt dokumentierter Ausnahme), ein `verified[]`-Feld existiert nicht, der Receipt hatte kein Reihenfolge-/Entscheidungs-Feld, und die Subshell ohne `set -e` riskierte false-PASS.
**Patch-Stapel (in dieser Runde angewandt, ausschließlich `sandbox-3-8/run-sandbox.sh`):**
- **DET-2 ganzheitlich gehärtet:** `at` wird jetzt als **reale Wanduhr-Ausgabe committet** (`date -u`) und als eigener `at_cell`-Wert im Receipt festgehalten; die Normalisierung auf `AT` erfolgt **erst beim Vergleich** (Hash-Bildung über die at-normalisierte Content-Projektion, §5.14 Pkt. 2 „ohne die maskierte at-Zeile") — kein Maskieren vor Commit, kein stiller Ausschluss. Nach-Commit-Attestierung: `committed_at == at_now` wird verifiziert. Der Zwei-Run-Abgleich vergleicht **alle deterministischen Receipt-Bestandteile byte-identisch** und **nur die zwei Ausnahme-Cells `at_cell`/`tree` dürfen differieren**; die `at`-Varianz wird in den Läufen real demonstriert (zwei getrennte Worktree-Läufe → unterschiedliche Wanduhr-`at`).
- **Kanonisches Eingabemanifest wird konsumiert/validiert** (nicht dekorativ): Baseline-Commit und geordnete Source-Eingaben werden abschnittsbewusst aus `manifest.yaml` extrahiert und vor jedem Lauf gegen den Worktree-Zustand geprüft („Manifest ohne baseline / Quellen / Identitätswerte" → HARD-FAIL); die Quell-Eingaben-Existenz wird **nach** dem Increment-Input-Commit geprüft (EC-1-Semantik: Quelle zum Verarbeitungszeitpunkt vorliegend).
- **Receipt erweitert** um `order:` (Reihenfolge) und `decisions:` (Ausführungs-Entscheidung) gemäß §5.14 Pkt. 2; Receipt-Gerüst-Nicht-Vakuum (beide Dateien non-empty) vor dem Vergleich.
- **Subshell `set -e`** in `run2_worktree` aktiviert (jeder fehlende `git`/`sed`-Schritt bricht als rc≠0 ab → HARD-FAIL statt false-PASS); Witness-/sha-Leer-Prüfung blieb.
- **`match_stufe_a` literal-sicher:** Term wird regex-escaped (`sed 's|[][\\.*^$+?(){}|]|\\&|g'`) — ein Term wie `a+b`/`x.y` darf nie als Muster umgedeutet werden (§3.2-Pkt.-2a/§5.14 Pkt. 5); Body-Extraktion CRLF-tolerant (`tr -d '\r'`).
- **`cand`-Newline-Normalisierung** (`tr '\n' ','`) — mehrzeilige Candidate-Liste bricht den Receipt-Zeilenblock nicht mehr.
- **`log_bullet` fail-hard:** `mktemp`/`mv`-Fehler werden nicht mehr geschluckt (ein verlorener `log.md`-Eintrag hätte beide Runs identisch=Baseline gelassen → Vakuum-Gleichheit; jetzt HARD-FAIL).
- **`git worktree prune` vor dem Add** — Re-Run-Sicherheit (keine verwaisten worktree-Registrierungen blockieren/nachtragen).
- **`git clean` fail-hard** unter `set -e` (kein `|| true`-Schlucken von Rest-Unternehmungsdateien).
- **Manifest-`output_visible_run_identity`-Klarstellung:** das Manifest führt konkrete **Feldnamen-Werte** (`generated.by`, `generated.at` — die benannte A0-20-Ausnahme), nicht einen Feldnamen-Platzhalter; die Manifest-Validierung verlangt `generated.at` explizit.
**Kein `defer`:** die Befunde waren sämtlich durch die Re-Open-Implementierung verursacht und hier (auto-)fixierbar; keine pre-existing Lücke wurde dabei neu aufgedeckt. **Reject:** einige Findings (z. B. „Teil-Zeitstimmen"/„literal") waren bereits durch die Hash-basierte Abwichen-Erkennung abgedeckt oder überreich.
**Verifikation dieser Runde:* `bash run-sandbox.sh` → 8/8 `PASS` (DET-1..DET-8), Exit 0, zwei Läufe mit real unterschiedlichen `at_cell`-Wanduhrwerten (Ausnahme aktiv), Witness nicht-vakuum (Baseline-SHA ≠ Run-SHA); die Befunde „Manifest wird nie konsumiert", „at-Maske vor Commit", „kein Reihenfolge-/Entscheidungsfeld" sind damit direkt behoben.
</frozen-after-approval>
## Design Notes
@@ -142,7 +162,7 @@ Dieser Frozen-Intent wurde **nur in dem Umfang** geändert, den dieses Re-Open-D
## Verification
**Commands (re-executierbar, ab Workspace-Root):**
1. `bash _bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh` — expected: DET-1..DET-8 harte PASS/Fail (Re-Open-Stand: DET-2 in zwei getrennten Worktrees mit frischen Kontexten, Manifest + Receipt außerhalb des Bundles, keine hart codierten Bundle-Outputs, `verified` nicht pauschal maskiert), Zwei-Run-Identität nicht-vakuum, Exit 0.
1. `bash _bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh` — expected: DET-1..DET-8 harte PASS/Fail (Re-Open-Stand, gehärtet in Review-Loop-2: DET-2 in zwei getrennten Worktrees mit frischen Kontexten, **kanonisches Eingabemanifest konsumiert/validiert** vor jedem Lauf, **Run-Receipt außerhalb des Bundles** mit `order:`/`decisions:`/`at_cell`, **keine hart codierten Bundle-Outputs**, `verified` nicht pauschal maskiert — **`generated.at` als reale Wanduhr-Ausgabe committet und erst beim Vergleich normalisiert** (dokumentierte Ausnahme, kein stiller Ausschluss), Ausnahme-Cells `at_cell`/`tree` dürfen differieren, übrige Bestandteile byte-identisch), Zwei-Run-Identität nicht-vakuum, Exit 0.
2. `grep -n "§5.14\|Revision 3.3" schema/compiler.md` — liefert §5.14-Sektion + Revisionslog-Eintrag; `grep -n "Determinismus-Vertrag & Agent-Instruktions-Validator" schema/compiler.md` — die §5.14-Überschrift wortgleich (inkl. §5.13-Seam-Satz in §5.14-Intro).
3. Read-only (AD-3): `git status --porcelain` zeigt keinen Change an `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/`. `schema/canonical-terms.md` unverändert (append-only-Registry unangetastet).
4. Validator-Lauf: alle `wiki/`-Dateien SUCCESS (unverändert, keine Inhalts-Mutation).
@@ -175,10 +195,16 @@ Dieser Frozen-Intent wurde **nur in dem Umfang** geändert, den dieses Re-Open-D
**Sandbox-Beweis (mechanische Bestätigung)**
- [run-sandbox.sh:152](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L152) — DET-1..DET-8-Szenarien (Bundle-State, Zwei-Run-identisch nicht-vakuum, Zwei-Run-Abweichung, `at`-Gap, Em-Dash, Kollaps, Match-Scope, Orphan); Läufe Exit 0 mit 8 harten PASS-Assertionen.
- [run-sandbox.sh:238](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L238) — DET-2 Szenario-Start: zwei getrennte Worktrees + frische Kontexte über demselben committeten Baum; Einstieg in die gehärtete Determinismus-Mechanik (Review-Loop-2).
- [run-sandbox.sh:257](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L257) — Kanonisches Eingabemanifest (`manifest_path`), das jetzt VOR jedem Lauf konsumiert/validiert wird (nicht dekorativ; Review-Loop-2-Patch).
- [run-sandbox.sh:409](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L409) — DET-2 RESULT: Receipts byte-identisch bis auf `at_cell`/`tree` (Ausnahme-Cells); att real committet, erst beim Vergleich normalisiert — dokumentierte Ausnahme statt stiller Ausschluss.
- [run-sandbox.sh:267](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L267) — `set -e`-Subshell: jeder fehlende Schritt bricht als rc≠0 ab (kein false-PASS; Review-Loop-2-Patch).
- [run-sandbox.sh:414](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L414) — DET-3: künstlich divergenter Lauf → AD-16-Klassifikationsdefekt, textuell benannt (NFR-4), korrigierte Wiederholung == Referenz.
- [run-sandbox.sh:498](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L498) — DET-4: `generated.at`-Gap als einzige benannte Ausnahme; at-Maskierung beim Vergleich (byte-identische Restteile) + Negativkontrolle.
**Story-Protokoll/Defers (Peripherie, zuletzt)**
- [log.md:4](../../wiki/log.md#L4) — Story-3.8-Eintrag: Verankerung, Sandbox-Nachweis, Erhaltungs-Invariante, Validator-Verdikt; Revision 3.3.
- [deferred-work.md:492](../../_bmad-output/implementation-artifacts/deferred-work.md#L492) — aufgegriffene/geschlossene „Home: Story 3.8"-Defers (7 `### Aufgegriffen` + 1 `### Aufgegriffen (teilweise)`, append-only ab Zeile 492).
- [spec:122](../../_bmad-output/implementation-artifacts/spec-3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato.md#L122) — Spec Change Log Review-Loop-2: 10 Patches der Sandbox-Härtung (at-Ausnahme real ausgeübt, Manifest-Validierung, Receipt order/decisions, `set -e`, literal-sichere Stufe-a).
- [sprint-status.yaml:61](../../_bmad-output/implementation-artifacts/sprint-status.yaml#L61) — Key `3-8-determinismus-…` auf `review` (Review-Start-Flip Step-04); `done`-Transition im Step-05.
@@ -29,7 +29,7 @@
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
# - Retrospective appends its action items to action_items; the status view surfaces open ones
generated: 08-14-2026 00:00
last_updated: 08-20-2026 16:20
last_updated: 08-20-2026 17:05
project: wow20
project_key: NOKEY
tracking_system: file-system
@@ -58,7 +58,7 @@ development_status:
3-5-leasing-dirty-tree-schutz-für-konkurrierende-producer-umsetz: done
3-6-lease-staleness-recovery-basis-absichern: done
3-7-reason-mutate-trennung-und-konsistenz-endzustand-sicherstell: done
3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato: in-progress
3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato: review
3-9-deterministische-relevanz-und-reconcile-routing-schliessen: backlog
3-10-inkrementelle-update-und-synthese-erhaltung-absichern: backlog
3-11-root-scope-leasing-atomar-akquirieren: backlog
+1
View File
File diff suppressed because one or more lines are too long