20 Commits
Author SHA1 Message Date
Michael Tamse 6624e07d3b docs: Story 3.13 abschließen — Status-Sync nach grünem Epic-3-Abnahmegate (PASS_COUNT=70, RUN_OK)
Step-05-Sync nach RUN_OK/Exit 0 des Story-3.13-Gates:
- sprint-status.yaml: 3-13-... in-progress → done (finaler Flip; epic-3 bleibt
  in-progress bis zur Epic-3-Retrospektive, AC-9), last_updated → 08-22-2026 19:10.
- epic-3-context.md: Goal (Z.7), Abnahmegate-Entscheidung (Z.46), Cross-Story-
  Abhängigkeit (Z.52) auf Ist-Zustand (Gate ausgeführt und grün) nachgeführt.
- wiki/log.md: Story-3.13-Eintrag (AC-6, Kanonisches Log, Vertrag §5) — Gate-Lauf,
  Abschnitte A-G, 12 Sandbox-Sub-Runs, Validator-Agent 7/7 SUCCESS, Zwei-frische-
  Agenten A/B byte-identisch nur at-Ausnahme, G-1..G-8, Porcelain-Check, Befund-
  Fixes (Punkt-9-Literal de-literalisiert, G-7-Exclusion). Kein Punkt-9-Literal,
  keine ../schema|../adapters-Links (G-7/Validator-Konformität).
- deferred-work.md: append — Story-3.13-Aufgegriffen-Block (Home der 3.13-namigen
  Defer L551/L560/L576/L594/L597/L611/L629/L633 realisiert; Original-Defer-Blöcke
  unverändert, benannte Rest-Terme offen).

Keine Änderung an schema/, adapters/, raw/ (AD-3); keine neue §7-Klasse; kein
Standalone (D-3); keine Wanduhr-Steuerung (A0-20). Gate-Nachweis: PASS_COUNT=70,
FAILED=0, RUN_OK, git status --porcelain leer.
2026-08-22 19:13:07 +02:00
Michael TamseandClaude 584676e387 feat: Story 3.13 Gate-Härtung — G-3 Punkt-9/Bundle-Compliance + G-7-Smoke-Exclusion (index.md/log.md)
Gate-Lauf 6 (E-Kern grün, A/B byte-identisch nur at-Ausnahme, 60 PASS) deckte
zwei reale Befunde auf:
- G-3: frischer Validator-Agent (schema/validator.md Rev 9) reportet 1 FAIL —
  wiki/log.md Z.42 (Story-2.4-Review-Eintrag) zitiert das Literal 'type: bundle'
  in Prosa = Verletzung Punkt 9 (Verbot außerhalb der Bundleroot, irgendwo im
  Dateiinhalt, schließt log.md ein). Validator ist korrekt; Defekt liegt im
  Bundle. Fix: minimalistisch, bedeutungserhaltend de-literalisiert
  (Bundleroot-'type'-Verbot) — Bundle ist jetzt Punkt-9-valid.
- G-7: Smoke-Flag war überstreng — Bundleroot index.md trägt die 3 gepinnten
  ../schema/-Glossar-Links (compiler §5.6 'andere Schicht', AC-8 optionale
  Provenienz/Traceability) und log.md zitiert Formel-Texte (§5.6 Pkt. 2
  Log-Exclusion). Fix: G-7 exkludiert index.md (Schema-Glossar) + log.md
  (Protokoll), hält die Smoke-Pflicht für alle Concept-Bodies/Area-Indizes hart.

Keine Schema-/Validator-/Adapter-/raw-Änderung (AD-3); keine §7-Klasse.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-22 18:08:30 +02:00
Michael Tamse 02786f43e7 docs: Story 3.13 starten — Spec 3-13, Sprint-Status auf in-progress 2026-08-22 10:37:21 +02:00
Michael TamseandClaude c234fc361e fix: Story 3.12 Review-Loop-2-Abschluss (bmad-code-review 4 Layer, konvergiert; D-3.12-1 Option 1, 7 Patches, 2 Defer, 6 dismissed; Sandbox L-1..L-9 9/9 harte PASS/Exit 0)
- D-3.12-1 (Nutzer, Option 1): AC-6/§5.18-Pkt.-6-Grenze an log.md-Praxis — dokumentierte
  Run-Nachweise = notwendige Koordinationsereignisse; §5.18/AC-6 textuell unverändert;
  L-8-Guard auf Kategorie-Hyphenate + Negativ-Kontrolle geschärft
- P1: scopelock_healthy() + LOCK_READ_ERROR-Propagation (korrupter Lock nie en-bloc-stale)
- P2: Release-Fehler negativ geübt (L-7), L-6 "valide committet" hart verifiziert
- P3: Setup-Robustheit (Exit-Checks, verifizierter $BASE, Backup-cp byte-identisch)
- P4: L-9 Ghost-Diff-Probe (Index+Worktree), reg_write real geübt
- P5: AC-1-Ownership-Stale positiv getestet, L-2 Takeover-Exactly-once am CAS
- S1: Spec-Struktur (review_loop_iteration 0->1, </frozen-after-approval>, doppelte
  Change-Log-Überschrift entfernt), Status-Sync vereinheitlicht
- S2: SRO-/Code-Map-/deferred-work-Anker auf Ist-Zeilen (takeover Z. 153, L-2 Z. 367,
  L-3 Z. 442, L-7 Z. 648, L-8 Z. 700; §5.18 Z. 426-439, §6 Z. 441, Rev 3.7 Z. 543)
- Defer: DF1 (AC-3-stash-Variante + AC-5 kombiniert, Home 3.13), DF2 (assert_no_wallclock)
- Status: Spec status done, sprint-status Key 3-12 -> done, last_updated 08-22-2026 07:44;
  wiki/log.md Loop-2-Abschlusseintrag (## 2026-08-22)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-22 08:14:56 +02:00
Michael TamseandClaude efcc469f3d docs: Story 3.12 abschließen — Spec done, Suggested Review Order, Status-Sync (Review-Loop-1 konvergiert, §5.18 Rev 3.7)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 21:24:41 +02:00
Michael TamseandClaude d0cff08a2a fix: Story 3.12 Review-Loop-1-Patches (atomarer Ownership-CAS scopelock_takeover, AK-2->AC-2, L-2-Negativ-CAS, Sandbox-Härtung; L-1..L-9 9/9 harte PASS/Exit 0)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 21:22:36 +02:00
Michael TamseandClaude 7f839febab feat: Story 3.12 — Lease-Lifecycle und Commit-Abschluss transaktional schließen (Revision 3.7, §5.18; Sandbox L-1..L-9 9/9 harte PASS/Exit 0)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 20:49:50 +02:00
Michael TamseandClaude 80480af3ec feat: Story 3.11 — Review-Loop-1-Abschluss committen (20 Patches, D-3.11-1/2, Sandbox A-1..A-8 8/8)
Abschluss des in der vorigen Session erarbeiteten, nach Commit e02cf84
liegengebliebenen Review-Loop-1 von Story 3.11 (Root-Scope-Leasing atomar):

- wiki/log.md: Eintrag „Story 3.11 → Review-Loop-1-Abschluss + done"
  (D-3.11-1 Konstruktiv+härten, D-3.11-2 Intentionaler Mutationsversuch;
   20 angewendete Patches; kein Loopback)
- sprint-status.yaml: Key 3-11-… → done (finaler Step-05-Flip), last_updated 15:10
- schema/compiler.md: §5.17-Pkt.-1/4/5-Berichtigungen + §8-Revision-3.6-Nachführung
- spec-3-11 + sandbox-3-11: 20 Loop-1-Patches; deferred-work.md: 5 Defer-Einträge
  + KORREKTUR-Teileintrag (Zeilen-Drift-Defer widerlegt)

Erhaltungs-Invariante §5.9 Pkt. 5: git status --porcelain -- wiki/ zeigt nur
wiki/log.md; AD-3 read-only; kein Standalone, keine neue §7-Klasse.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 20:00:21 +02:00
Michael Tamse e02cf84501 feat: Story 3.11 — Root-Scope-Leasing atomar und worktree-übergreifend akquirieren (Revision 3.6, Verankerung §5.17) 2026-08-21 13:40:50 +02:00
Michael Tamse a8b486d0f0 feat: Story 3.10 — Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau (bmad-code-review, Patch-Kaskade; kein Loopback) 2026-08-21 12:14:51 +02:00
Michael TamseandClaude 8c43d3a09c feat: Story 3.9 Review-Loop-3-Abschluss (bmad-code-review, 4 Layer; kein Loopback)
- 3 Decision-Resolutionen (D-3.9-1/2/3 = Option 1/1/2 — empfohlene Optionen)
- 14 Patches angewandt (von 15): P-3.9-1 Escaper-„Fehlalarm" verworfen — die
  „Ausführungs"-Evidenz war durch die Bash-Tool-Transport-Schicht korrumpiert
  (Backslash-Ebene halbiert); od-Beweis + Negativ/Positiv-Kontrolle aus
  Datei-Bytes belegen: Escaper literal-sicher, run-sandbox.sh Z. 74/790 unverändert
- 2 Defers (Home Story 3.13, Präzedenz DET-1/2), 9 verworfen
- Sandbox Minimal-Härtung (D-3.9-3 Option 2): R-1..R-9 mit echter feuernder
  Mechanik + echten Negativ-Kontrollen; Exit 0 auf frischem /tmp-Root,
  Re-Run-Idempotenz bestätigt
- compiler.md §5.15 (Pkt. 1/2/5 + Revision 3.4-Wortlaut), I/O-Matrix M/D/R,
  frozen-Schließtag + I/O-Zelle (Change-Log-autorisiert, keine Neu-Verhandlung)
- AD-3 read-only & wiki/-Erhaltungs-Invariante verifiziert (Validator SUCCESS)
- sprint-status 3-9 → done; Step-05 Status-Sync + Step-06 Abschluss

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 10:53:03 +02:00
Michael TamseandClaude 73f2c9e71d feat: Story 3.9 (Loop-2) — Deterministische Relevanz- & Reconcile-Routing schließen, review reif
§5.15 „Deterministische Relevanz- & Reconcile-Routing (Story 3.9)" in schema/compiler.md
(Revision 3.4): geschlossene Termgewinnung (AC-1), symmetrische Normalisierung +
literal-sichere Suche (AC-2), eine exklusive Routing-Tabelle UPDATE→CREATE→ORPHAN/HOLD→NO_OP
(AC-3, NO_OP als Update-Unter-Entscheidung), Raw-Immutability-Guard M/D/R→Run-FAIL (AC-4),
reservierte Zielpfade index/log/source/README→deterministischer Hold (AC-5), Zwei-Run-Identität
nicht-vakuum inkl. praktisch ausgeübter at-Exzeption (AC-6). §3.2-Pkt.-3d-Anker, §5.14-Pkt.-5-Scope
und §7-Bullet nachgeführt.

Review-Loop-2: 2 bad_spec (BS-L2-1 NO_OP-Leere-Candidate-Liste, BS-L2-2 Status-Codes R/C)
durch Re-Ableitung behoben; Step-04-Re-Review (2 frische Layer + Verification Gap) ohne
neuen Loopback (F-6 Lowercasing-Pkt.-1, F8 Guard-Abbruch real, F9 ableitungsbasierte
Term-Erwartung, F-22/F-27 BUNDLE-BYTE-Nachweis angewandt). Sandbox R-1..R-9 PASS, Exit 0.

Re-Derivation: deferred-work.md D-8/D-9 aufgegriffen; epic-3-context.md Z. 39 → Ist;
sprint-status.yaml Story-3.9 → review. AD-3 read-only + Erhaltungs-Invariante (nur
wiki/log.md) gewahrt. Suggested Review Order in Spec 3.9 angehängt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 06:21:08 +02:00
Michael TamseandClaude 2f079ee3c8 feat: Story 3.8 Review-Loop-3-Abschluss (bmad-code-review Re-Run, 4 Layer; kein Loopback)
Triage: 4 decision-needed / 13 patch / 6 defer / 7 dismissed (u. a. Blind-Hunter
"alle Sandbox-SRO-Anker stale" widerlegt — alle 6 exakt vor den Loop-3-Edits).
Entscheidungen D-1..D-4 (alle Option 1): D-1 Schliessungs-Bullet macht den
"Bekannte Determinismus-Luecke"-Alt-Bullet superseded (append-only-Hinweis,
kein Umbruch); D-2 Story-Status-Flip auf done (Change-Proposal-Sequenzierung
betrafft das Epic-3-Abnahmegate 3.13, nicht die 3.8-Verankerung); D-3
autorisierte Neu-Verhandlung in geringfuegischem Umfang (Typo
"documenthuman" -> "document-human", Spec Re-Open-Delta-Zeile); D-4
Hold-Home = Story 3.10 (Epic 3), Korrektur-/Erweiterungs-Klassifikation =
Epic-4-Interface/Story 4.1 (S5.10 Pkt. 8 praezisiert).
Patches P-1..P-13: SRO-/Code-Map-Anker auf IST-Zeilen (compiler.md
375/376/377/477, Code Map 365-377/424ff/445/477, Sandbox 245/265/442/275/447/531);
symmetrische Term-/Body-Normalisierung (S3.2 Pkt. 2a + match_stufe_a, kein
host-abhaengiges -i); Receipt-Hashvergleich als re-executierbare
Zwei-Run-Formel (S5.14 Pkt. 2); Sandbox-Fresh-Kontext-Restluecke benannt
(mechanisch simuliert; Nachweis = 3.13-Abnahmegate); Manifest generated_by
wird konsumiert; at-Ausnahme hart assertiert (Wiederholungs-Schleife bei
Sekundenkollision, HARD-FAIL statt stiller PASS); "voellig pinbar"/"keine
offene Frage" ge scopet (Umlaut-Defer + 3.9-ACs bewusst offen); epics.md
AC-2 AD-16-Kopplung nachgefuehrt; review_loop_iteration 0 -> 2;
epic-3-context.md 3.9/3.11/3.12-Entscheidungen als Zielzustand markiert;
AD-17-Enum verifiziert (kein Edit noetig); Review-Loop-3-Change-Log-Eintrag.
Sandbox re-executiert: DET-1..DET-8 harte PASS, Exit 0; at-Ausnahme aktiv
(assertiert, real unterschiedliche Wanduhr-at-Werte); Witness nicht-vakuum.
Spec status: done, review_loop_iteration: 2; sprint-status 3-8 -> done
(Sequenzierungs-Hinweis: 3.9-3.12-Verankerungen + 3.13-Abnahme bleiben offen);
wiki/log.md Loop-3-Abschluss-Bullet; deferred-work.md 6 neue Defers.
AD-3 gewahrt: validator.md/wiki-compiler.md/adapters/raw/ unveraendert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 19:12:40 +02:00
Michael Tamse 73d19cbcea @
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>
@
2026-08-20 14:00:53 +02:00
Michael TamseandClaude 96661836e9 feat: Story 3.8 Re-Open-Delta — Zwei-Run-Determinismus auf getrennte Worktrees/Manifest/Receipt umgestellt (genehmigtes Sprint-Change-Proposal 2026-08-20)
Re-Open-Scope (approved, ProMods; Epics-ACs Z. 357-370): zwei frische Agent-Kontexte in getrennt aufgebauten (sauberen) Worktrees (Q-6/A0-19 — eine zweite Ausführung in derselben Session genügt nicht), kanonisches Eingabemanifest (Baseline-Commit, geordnete Sources, output-sichtbare Run-/Zeit-/Identitätswerte), Run-Receipt außerhalb des Bundles (Candidates, Reihenfolge, Plan, Entscheidungen, Output-Hashes), keine hart codierten erwarteten Pläne/Concept-Bodies, keine pauschale verified-Maskierung (nur benannte at-Ausnahme).

- schema/compiler.md §5.14: Zwei-Run-Bestätigungs-Mechanik + §8-Revision-3.3-Klausel + Re-Open-Delta auf getrennte Worktrees/Manifest/Receipt/no-hardcode/no-masking umgestellt; §5.13-"in einer Session" unverändert (AD-6-Phasen-Disziplin, Fugen-Identität)
- sandbox-3-8/run-sandbox.sh: DET-2 auf zwei getrennte Worktrees (wt-a/wt-b) + manifest.yaml + receipts/-Dateien außerhalb des Bundles umgebaut; Candidate-Ableitung aus Git-State via deterministischem Stufe-a-Match (nicht hart codiert); Receipt-Abgleich als tatsächlicher Zwei-Run-Vergleich (diff der beiden Receipts, nicht je Lauf gg. Baseline); non-vakuum-Witness (Run-alpha-SHA != Baseline-alpha-SHA); Subshell für Worktree-cwd-Isolation. Exit 0, DET-1..DET-8 harte PASS
- spec-3-8: Re-Open-Delta-Intent, Tasks & AC-3/4/5, SRO auf Worktrees angepasst
- wiki/log.md: Story-3.8-Re-Open-Delta-Eintrag (Verankerung, Sandbox-Nachweis Exit 0, Erhaltungs-Invariante, Validator-Verdikt 7/7 SUCCESS, Revision 3.3)
- sprint-status.yaml: last_updated nachgeführt; Key 3-8 in-progress
- epic-3-context.md: neu kompiliert (Story-Liste/Stories 3.9-3.13 synchron)

Validator-Verdikt (human-mechanisch, schema/validator.md Rev 9, D-3 — kein CLI): alle wiki/-Dateien 7/7 SUCCESS (keine Inhalts-Mutation). Erhaltungs-Invariante §5.9 Pkt. 5: git status --porcelain -- wiki/ zeigt ausschließlich wiki/log.md. AD-3 read-only (validator/wiki-compiler/adapters/raw/canonical-terms) unverändert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 13:08:28 +02:00
mita 1c116751d6 docs: extend Epic 3 remediation stories 2026-08-20 12:23:28 +02:00
mita 5a78271f89 Merge pull request 'Story 3 8' (#3) from story-3-8 into main
Reviewed-on: #3
2026-08-20 09:24:16 +00:00
Michael Tamse 39471d9422 docs: Story 3.8 Suggested Review Order auf klickbare path:line-Links (Step-05-Present) umformatiert 2026-08-20 10:52:21 +02:00
Michael Tamse c4cdf4b93e feat: Story 3.7 Review-Loop-2-Patches (bmad-code-review Re-Run, 4 Layer; kein Loopback) — Nutzer D1/D2/D3 = 1/1/1; 3 decision-needed / 7 patch / 3 defer / 9 dismissed (False-Positives); Patches: D1 deferred-work-Append (zwei Delokalisierungs-Blöcke: Terminologie-Drift INPUT_UNCOMMITTED/UNCOMMITTED_INPUT pre-existing, Misch-Run-Coverage → Home Story 3.8) → log.md-Protokollwiderspruch aufgelöst; D2 sprint-status review behalten + 4 Doku-Stellen (Spec-Code-Map, SRO-Anker-Text, in-progress-Bullet, §8-Rev-3.2-Klausel); D3 p2_plan-Element-(1)-Scope git status --porcelain -- raw wiki (exakt wie §5.9 Pkt. 6); Sandbox: CONSIST-1-EC-1-Isolation (committete Löschung), CONSIST-2 Misch-Run (Neu-Anlage +delta, Duplikat-Kontrolle, Neu-Anlage-Absenz nach Voll-Rollback via assert_restored-Guard), CONSIST-5 raw-SHA gg. $BASE, CONSIST-6 gültiger Commit-Zustand (sources-Nachführung id s3 + at-Bump), rollback()-Doku (Voll-Rollback = §6-Pkt.-3-Teilzustand, AD-17e-Abgrenzung), 3 Tippfehler; Instruktion: §5.13 Pkt. 7 um generated.at-Wanduhr-Gap-Ausnahme (§5.10 Pkt. 8, A0-20, Home 3.8); Sandbox re-executiert Exit 0 (14 harte PASS, CONSIST-7 non-vakuum plan_sha byte-identisch); Defers → deferred-work.md (Label-Präzisierung, A0-20-Post-Zustand → 3.8, pre-existing-Zeitpunktswort-Fuge); Spec: Review Findings + Change Log + loop_iteration 1; log.md Review-Loop-2-Bullet; sprint-status done; AD-3 unverändert 2026-08-20 09:04:41 +02:00
Michael TamseandClaude 938c05c4a8 feat: Story 3.7 Reason/Mutate-Trennung & Konsistenz-Endzustand sicherstellen (bmad-code-review, 3 Layer; kein Loopback) — §5.13-Phasen-Sektion (AD-6/A0-7, Änderungsplanung als institutionalisierter §5.9-Pkt.-6-P2-Block, Plan-Freeze an §5.9-Pkt.-5-Ghost-Diff-Kopplung, Zustands-Restaurations-Invariante, kein Teilerfolg AD-17f, VALIDATION_FAIL/SUCCESS, KEINE_EIGENE_ENGINE; §7-Bullet aufgelöst, §8-Normreferenz AD-6 auf §5.13-Anker, Revision 3.2 + Abschlussklausel); Sandbox CONSIST-1..CONSIST-7 (Exit 0, 14 PASS, Restaurations-Invariante, EC-1-Defizit-Negativkontrolle, pgrep-Robustheit, non-vakuum Determinismus-Zeuge); Review-Loop-1-Fixes (SRO-Anker 343/356/358/362/420/461, fünf→vier AD-6-Phasen, CONSIST-6-Validierung-vor-Commit); Suggested Review Order + Status done; log.md; sprint-status review; AD-3 unverändert
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 06:31:39 +02:00
21 changed files with 7192 additions and 45 deletions
@@ -470,3 +470,181 @@ Noch nicht adressierte, aber real erkannte Arbeit — gesammelt aus Reviews. Ein
- Umsetzung: `schema/compiler.md` §5.12 Pkt. 5 „`raw/`-Recovery-Basis & native `git stash`-Variante (AD-3, AD-17d/A0-15)": `git stash push -- <Pfade>` als zweite textuell zulässige Schutzvariante neben der Scratch-Zonen-Kopie (beide deterministisch im Ergebnis, byte-identisch geschützt, nie gelöscht, Restore dokumentiert).
- Sandbox-Nachweis: STALE-4 (`git stash push`/`pop` um `wiki/alpha.md`, byte-identischer Restore via SHA-256; `raw/`-SHA-256 unverändert, AD-3) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.12 Pkt. 5)
## Deferred from: code review of spec-3-7-reason-mutate-trennung-und-konsistenz-endzustand-sicherstell (Story 3.7, 2026-08-20)
- CONSIST-7-Label praezisie: das Zwei-Run-Label behauptet "identische Plan-/Rollback-/State-Outputs", verglichen werden aber nur die Plan-Outputs (plan_sha/plan_state); der Rollback-/State-Nachweis (s7c) laeuft einmalig, log_sha ist dokumentierte Baseline-Konstante. Die non-vakuum-Kern-Assertion (plan_sha byte-identisch ueber beide Runs) ist real; das Label ist breiter als die Assertion. Home: Sandbox-Haertungsrunde.
- A0-20-Post-Zustand-Negativkontrolle in der Sandbox fehlt: §5.13 Pkt. 3/7 bindet den Determinismus-Vertrag an den Post-Zustand; der dokumentierte generated.at-Wanduhr-Gap (§5.10 Pkt. 8, offene A0-20-Konvention) macht den Post-Zustand ueber unabhaengige Runs nicht byte-identisch — die Sandbox testet den Gap weder negativ noch positiv. Home: Story 3.8 (A0-20-Home).
- Pre-existing-Zeitpunktswort-Fuge: §5.9 Pkt. 6 "am Anfang der Mutationsphase" vs. gefrorene I/O-Matrix + §5.13 "Abbruch vor der Mutationsphase" — §5.9 ist in Story 3.7 Wortlaut-unververaendert (Rueckverweis-Vertrag); die Fuge ist ohne Ask-First nicht schliessbar. Home: spaetere Instruktionsrunde.
### Delokalisierung: Terminologie-Drift INPUT_UNCOMMITTED/UNCOMMITTED_INPUT (Story-3.7-Review, 2026-08-20)
- Bezug: `schema/compiler.md` §5.9 Pkt. 6 Element (1) nennt die I/O-Matrix `INPUT_UNCOMMITTED`, §5.11 Pkt. 3 nennt dieselbe Pre-Run-Prüfung `UNCOMMITTED_INPUT` — dieselbe Prüfung, zwei Labels (pre-existing seit Story 3.1/3.5; in Story 3.6/3.7 als dieselbe §5.9-P2-Prüfung deklariert, §5.11-Wortlaut geschützt). Die Story-3.7-Sandbox (`p2_plan`-Element (1)) nutzt `INPUT_UNCOMMITTED` wie §5.9-Pkt.-6-Element (1).
- status: delokalisiert (kein Defekt — terminologische Präferenz; Home: spätere Instruktionsrunde, falls die Labels vereinheitlicht werden)
### Delokalisierung: Misch-Run-Coverage Neu-Anlage+Update in der Story-3.7-Sandbox (Story-3.7-Review, 2026-08-20)
- Bezug: §5.9 Pkt. 2/§5.7 Misch-Runs (Neu-Anlage + Update im selben Run) sind in der CONSIST-Sandbox ab Review-Loop-2 im CONSIST-2 abgedeckt (Neu-Anlage `delta` + Update `alpha`, Plan-Freeze-Menge + Voll-Rollback, Neu-Anlage-Absenz nach Rollback); übrige Phasen-/Synthese-Misch-Formen bleiben Home Story 3.8 (Determinismus-Vertrag AD-17h als Agent-Instruktions-Validator, inkl. A0-20-Post-Zustand-Test).
- status: delokalisiert (Home: Story 3.8)
## Deferred from: code review of spec-3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validator (Story 3.8, 2026-08-20)
### Aufgegriffen: Em-Dash-Varianten-Lücke der Normalisierung (Story-3.2-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «Em-Dash-"—"-Varianten-Lücke der Normalisierung» (Defer-Block „Deferred from: Story 3.2", Eintrag 1; `status: offen (Home: Story 3.8)`).
- Umsetzung: `schema/compiler.md` §3.2 Pkt. 1b „Geschlossene Determinismus-Lücken (Story 3.8)": die Kollaps-Klasse ist um den Em-Dash `—` ergänzt (`[-–— _]``-`) — En-Dash, Em-Dash, Bindestrich, Unterstrich, Leerzeichen kollabieren identisch auf genau einen Bindestrich. Verankerung: §5.14 Pkt. 5 verweist auf §3.2-Pkt.-1b. Kein stiller Ausschluss; append-only-Regel-Ergänzung, bestehender §3.2-Wortlaut unverändert.
- Sandbox-Nachweis: `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh` DET-5 (Em-Dash → identische canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen) — Exit 0.
- status: aufgegriffen (Home erledigt in §3.2 Pkt. 1b / §5.14 Pkt. 5)
### Aufgegriffen: Kollaps-Reichweite bei Läufen/führenden/trailenden Separatoren (Story-3.2-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «§3.2-Pkt.-1b-ii: Kollaps-Reichweite bei aufeinanderfolgenden/führenden/trailenden Separatoren unbestimmt» (Defer-Block „Deferred from: code review of spec-3-2…", Eintrag 2).
- Umsetzung: `schema/compiler.md` §3.2 Pkt. 1b „Geschlossene Determinismus-Lücken": Kollaps-Reichweite deterministisch — jedes Separator-Vorkommen kollabiert auf genau ein `-`; Läufe (`a--b``a-b`); führende (`-x``x`) und trailende (`x-``x`) Separatoren werden getrimmt. Verankerung §5.14 Pkt. 5; die in der Sandbox etablierte Semantik wird normativ fixiert.
- Sandbox-Nachweis: DET-6 (Läufe `a--b`, führende `-x`, trailende `x-`, Kombination `---x---``x`) — Exit 0.
- status: aufgegriffen (Home erledigt in §3.2 Pkt. 1b / §5.14 Pkt. 5)
### Aufgegriffen: Match-Scope der Stufe a (Wortgrenzen, Frontmatter-exklusiv) (Story-3.2-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «§3.2-Pkt.-2a: Wortgrenzen-/Frontmatter-Scope der Stufe-a-Match-Semantik ist offen» (Defer-Block „Deferred from: code review of spec-3-2…", Eintrag 1).
- Umsetzung: `schema/compiler.md` §3.2 Pkt. 1b „Geschlossene Determinismus-Lücken" (c) Match-Scope der Stufe a: Stufe a matcht ganze Wörter (Wortgrenzen) über den Body, exklusive YAML-Frontmatter — Substring- und Frontmatter-Treffer (z. B. in `sources[].resource`/`generated.by`) liefern keine Kandidaten (deterministischer Stufe-a-Scope). Verankerung §5.14 Pkt. 5.
- Sandbox-Nachweis: DET-7 (ganzes Wort trifft alpha; Substring `opplung` und Frontmatter-only-Kandidat `mirror` liefern keine Kandidaten) — Exit 0.
- status: aufgegriffen (Home erledigt in §3.2 Pkt. 1b / §5.14 Pkt. 5)
### Aufgegriffen: Orphan-Kontrolle als deterministische Reconcile-Orphan-Regel (Story-3.4-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «Orphan-Kontrolle (§5.10 Pkt. 8) textuell verankert, aber nicht in der Sandbox demonstriert» (Defer-Block „Deferred from: code review of spec-3-4…", Eintrag 2; `status: offen (Home: Story 3.8)`).
- Umsetzung: `schema/compiler.md` §5.10 Pkt. 8 „Deterministische Reconcile-Orphan-Regel (Story 3.8; §5.14-Präzisierung)": Verwaist-Befund deterministisch aus dem committeten Git-State (Zuwachs gg. `<Baseline-Commit>`, §5.9 Pkt. 6 R-1); datumsgruppierter `log.md`-Eintrag (Header `YYYY-MM-DD`, neueste zuerst; Quell-Pfad + `<Baseline-Commit>`); kein Banner/keine stille Vorbearbeitung/keine eigenständige Anlage (AD-16-Default, Epic-4-Interface). Verankerung §5.14 Pkt. 5.
- Sandbox-Nachweis: DET-8 (verwaiste `raw/`-Evidenz ohne Ziel-Pfad-Treffer → unzugeordnet, `log.md`-Verwaist-Eintrag mit `<Baseline-Commit>`, kein Banner, keine eigenständige Anlage, Erhaltungs-Invariante nur `log.md`) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.10 Pkt. 8 / §5.14 Pkt. 5)
### Aufgegriffen: generated.at↔AD-17h-Gap & Zwei-Run-Identitäts-Nachweis (Story-3.3-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «U6-Zwei-Run-Identitäts-Nachweis … kreuzreferenziert den generated.at↔AD-17h-Gap nicht» (Defer-Block „Deferred from: code review of spec-3-3…", Eintrag 3; `status: offen (Home: Story 3.8)`).
- Umsetzung: `schema/compiler.md` §5.14 Pkt. 1 (Bundle-State-Definition: `generated.at`/`verified[].at` als benannte Ausnahme der Projektion), Pkt. 3 (Ausnahme-Menge: allein der `generated.at`-Wanduhr-Gap; alle übrigen Bestandteile byte-identisch) und Pkt. 2 (Zwei-Run-Mechanik: identische Plan-/Kandidaten-/Reihenfolge-Outputs und byte-identische mutierte Bundle-Bestandteile bis auf die `at`-Ausnahme). Der A0-20-Wanduhr-Gap bleibt verhaltens-seitig unverändert (Ask-First bei Verhaltens-Wechsel).
- Sandbox-Nachweis: DET-2 (Zwei-Run-Identität nicht-vakuum, echte Content-Hashes) + DET-4 (at-Gap-Ausnahme: diff ausschließlich auf der at-Zeile, nach at-Maskierung byte-identisch; Negativkontrolle Body-Differenz = AD-16-Defekt) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.14 Pkt. 13)
### Aufgegriffen: A0-20-Post-Zustand-Negativkontrolle in der Sandbox (Story-3.7-Defer, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «A0-20-Post-Zustand-Negativkontrolle in der Sandbox fehlt … Home: Story 3.8 (A0-20-Home)» (Defer-Block „Deferred from: code review of spec-3-7…", Eintrag 2).
- Umsetzung: §5.14 Pkt. 3 definiert die Ausnahme-Menge des Bundle-State-Vergleichs (allein der `generated.at`-Wanduhr-Gap; jede andere Differenz außerhalb der Ausnahme = AD-16-Klassifikationsdefekt, Pkt. 4, textuell benannt NFR-4). Der Post-Zustand wird damit über die Zwei-Run-Mechanik (Pkt. 2) einschließlich at-Handhabung instruiert; die Sandbox demonstriert den Gap positiv und negativ.
- Sandbox-Nachweis: DET-4 (positiv: zwei Runs mit unterschiedlichen `at` → diff nur at-Zeile, nach Maskierung byte-identisch; negativ: Body-Differenz → AD-16-Klassifikationsdefekt) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.14 Pkt. 24)
### Aufgegriffen: Misch-Run-Coverage Determinismus-Vertrag (Story-3.7-Delokalisierung, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Delokalisierung «Misch-Run-Coverage Neu-Anlage+Update in der Story-3.7-Sandbox … übrige Phasen-/Synthese-Misch-Formen bleiben Home Story 3.8 (Determinismus-Vertrag AD-17h als Agent-Instruktions-Validator, inkl. A0-20-Post-Zustand-Test)» (Block „### Delokalisierung …", `status: delokalisiert (Home: Story 3.8)`).
- Umsetzung: Der Determinismus-Vertrag wird als geschlossene Bestätigungs-Mechanik in §5.14 verankert (Bundle-State-Projektion, Zwei-Run-Bestätigung, at-Ausnahme, Abweichungs-Klassifikation); die §5.13-Determinismus-Nachweise bleiben unverändert, die at-A0-20-Konvention ist auf die §5.14-Pkt.-3-Ausnahme-Menge verlagert (§5.13 Pkt. 7 Seam).
- Sandbox-Nachweis: DET-1..DET-4 (Bundle-State-Definition, Zwei-Run-Identität, Abweichungs-Klassifikation, at-Gap-Ausnahme) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.14)
### Aufgegriffen (teilweise): Gemischte Normalisierungs-Politik Umlaute vs. Transkription — Em-Dash-Anteil (Story-3.3/Story-3.4-Defers, Home Story 3.8) — Story 3.8, 2026-08-20
- Bezug: Defer «Gemischte Normalisierungs-Politik im Sandbox-Evidenztext (Umlaute vs. Transkriptionen) ist ein Determinismus-Hazard für den Term-Abgleich» (Defer-Block „Deferred from: code review of spec-3-3…", Eintrag 5, `status: offen (Home: Story 3.8)`) und Defer «Sandbox-Evidenztexte transkribieren Umlaute, die Concept-Bodies nutzen Umlaut-Schreibweisen» (Defer-Block „Deferred from: code review of spec-3-4…", Eintrag 3, `status: offen (Home: Story 3.8)`).
- Umsetzung: Der Em-Dash-Anteil beider Einträge ist in `schema/compiler.md` §3.2 Pkt. 1b / §5.14 Pkt. 5 geschlossen (Em-Dash in der Kollaps-Klasse `[-–— _]``-`). Der Umlaut-vs-Transkription-Anteil bleibt offen: die §3.2-Normalisierung (lowercasing + Kollaps-Klasse) deckt Umlaut-Divergenzen („Schlüssel" vs. „Schluessel") weiterhin nicht ab — kein neuer Normalisierungs-Schritt in Story 3.8 (kein neuer Regel-Operand, D-3/append-only).
- Sandbox-Nachweis: DET-5 (Em-Dash-Anteil) — Exit 0.
- status: teilweise aufgegriffen (Em-Dash-Anteil geschlossen in §3.2 Pkt. 1b / §5.14 Pkt. 5; Umlaut-vs-Transkription-Divergenz bleibt offen — Home: Sandbox-Vereinheitlichung oder folgende Compiler-Instruktions-Revision; kein Instruktions-Defekt)
## Deferred from: code review of spec-3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato (2026-08-20)
- **Sandbox-Härtungs-Guards (8 Befunde):** `mktemp` (Z. 37), `git init` (Z. 41), Baseline-Commit (Z. 94-96), `isolate()`-Sequenz (Z. 104-107), fehlende Match-Datei (Z. 136-141), verwaiste unregistrierte Worktree-Verzeichnisse (Z. 243-244) und Porcelain-Pfade mit Leerzeichen (`awk '{print $2}'`, Z. 217/682) tragen keine Fehler-Guards; ein Fehlgeschlagener init-Schritt lässt einen späteren „PASS" auf kaputtem Zustand drucken. — Defer-Grund: Testharness-Robustheit, keine normative Instruktion-Semantik; Re-Run-Risiko dokumentiert. Home: nächste Sandbox-Revision. — status: offen
- **DET-1/DET-2-Plan-/Form-Literale vs. AC-5 „nicht hart codiert":** `plan=$(printf 'cand=alpha;form=update;baseline=%s')` (run-sandbox.sh:189) und `echo "form=update"` in `bs_a()` (Z. 221) sind wörtliche Skript-Konstanten; B1==B2-Identität damit trivial. — Defer-Grund: Loop-1-/Loop-2-Präzedenz (Loop-1 hat „DET-2-Plan-Literal (dev-demo, echte Content-Hashes decken)" rejected; DET-1 trägt den Demo-Kommentar Z. 211-214); die Nicht-Vakuum-Assertion tragen die echten Content-Hashes. Home: Story-3.13-Abnahme (echte Gate-Runs über die Instruktion). — status: offen
- **`norm()`-Locale-Pinning (`LC_ALL`) fehlt:** unter C/POSIX-Locale zerfällt die Byte-Klasse `[–—]` in {E2,80,93,94} (übermatcht z. B. `…`/`„`), `tr 'A-Z' 'a-z'` ist locale-abhängig → die Kanon-Form hängt am Host-Locale statt am committeten Git-State. — Defer-Grund: Testharness-Host-Locale-Abhängigkeit (Host = Ubuntu-Sandbox, dort UTF-8-Default; auf C-Locale-Hosts falsch), keine normative Semantik der Instruktion. Home: Sandbox-Härtung (gemeinsam mit den Hards-Guards). — status: offen
- **Mehrfach-Term-Vereinigung unübend:** §3.2 Pkt. 1c/§5.14 Pkt. 5 pinnen die Vereinigung der Treffer über alle Terme (besuchte Menge, Zuwachs-Sicht-Ordnung) als „vollständig pinbar" — die Sandbox übt nur den Einzel-Term-Pfad; eine Regressions in Union-/Dedup-/visited-Set-Logik würde unbemerkt durchgehen. — Defer-Grund: Story-3.8-Scope (Bestätigungs-Mechanik + Lückenschließung) ist erfüllt; ausführbare Union-Coverage gehört zur deterministischen Term-Gewinnung/Routing. Home: Story 3.9. — **status: offen → aufgegriffen (Story 3.9, 2026-08-20):** in `schema/compiler.md` §5.15 Pkt. 1 verankert (Mehrfach-Term-Vereinigung D-8: kollabierter Gesamt-Name als primärer Term + jedes ≥2-Zeichen-Segment als weiterer Term; Einzelzeichen-Segmente -a/-b tragen keinen eigenen Term); Sandbox R-1 übt die Vereinigung real aus (`beta-kommunikation``beta beta-kommunikation kommunikation`, Sidecar-exkludiert, Diff↔Manifest-Äquivalenz)
- **Orphan-Term hand-gesteuert + feste 2-Datei-Scan:** DET-8 pinnen den Term `fremdes` und scannen nur `wiki/alpha.md wiki/gamma.md` (run-sandbox.sh:669); die Determinismus der Orphan-ermittlung (Term-Ableitung aus dem `raw/`-Zuwachs, voller Kandidaten-Scan über Stufen a/b/c) ist nicht ausgeübt — nur die Log-Seite wird hart assertiert. — Defer-Grund: Mechanik ist textuell korrekt (DET-8-Assertions Z. 688-692 solide); Term-Derivation ist Story-3.9-AC („geschlossener, geordneter Algorithmus oder persistiertes Term-Manifest"). Home: Story 3.9. — **status: offen → aufgegriffen (Story 3.9, 2026-08-20):** Sandbox R-5 leitet die Terme AUS dem committeten `raw/`-Zuwachs ab (`git diff --name-only HEAD~1 -- raw/`, Dateiname→Term) und scannt **Stufe a** über den Root-`wiki/*.md`-Glob statt eines festen 2-Datei-Scans — Orphan-/CREATE-Term-Derivation auf der Stufe-a-Ebene mechanisch ausgeübt (D-9; §5.15 Pkt. 1). **Scope-Präzisierung (Loop-3, 2026-08-21):** die ausgeübte Termination-Derivation + Scan beschränkt sich auf **Stufe a über den Root-`wiki/*.md`-Glob** (keine Area-Rekursion, keine Stufen b/c); die volle Stufen-b/c-Traversal-Coverage des Kandidaten-Baums bleibt verbleibend offen (Home: Story 3.13-Abnahme / Sandbox-Härtung — benannter Defer, keine instruktionsseitige Lücke, §5.15 Pkt. 2/3 verankern die Stufen)
- **Umlaut-vs-Transkription im Match-Pfad unübend:** die §3.2-Normalisierung deckt Umlaut-Divergenzen („Schlüssel" vs. „Schluessel") nicht ab, und kein DET-Szenario treibt einen Umlaut-Term durch den Match-Pfad. — Defer-Grund: bereits „Aufgegriffen (teilweise)" dokumentiert (Block oben, Home: Sandbox-Vereinheitlichung oder folgende Compiler-Instruktions-Revision; „kein Instruktions-Defekt") — keine neue offene Frage, nur fehlende Sandbox-Abdeckung. Home: wie dort. — status: offen
## Deferred from: code review of spec-3-9-deterministische-relevanz-und-reconcile-routing-schliessen (2026-08-21)
- **`norm()`/`tr` ohne `LC_ALL`-Pinning (Sandbox-3.9, 952 Zeilen):** unter C/POSIX-Locale zerfällt die Byte-Klasse `[–—]` in {E2,80,93,94} (übermatcht z. B. `…`/`„`), `tr 'A-Z' 'a-z'` ist locale-abhängig → die Kanon-Form hängt am Host-Locale statt am committeten Git-State. — Defer-Grund: bekannter Story-3.8-Defer (D-7, Block oben „`norm()`-Locale-Pinning fehlt"), hier in den Sandbox-3.9-Scope ausgeweitet (alle R-Szenarien + R-9-Worktree-Läufe laufen auf derselben `norm()`); keine normative Instruktion-Semantik (Instruktion nennt Kollaps-Klasse + lowercasing, keine Locale-Bindung). Home: Sandbox-Härtung (gemeinsam mit den Story-3.8-Hard-Guards). — status: offen
- **Positive Zwei-Run-Identität strukturell trivial (R-9 clean A==B):** Plan-/Form-/Routing-Literale der `run9_worktree()`-Receipts sind Skript-Konstanten; die A==B-Identität folgt daraus, nicht aus realer Ausführung; die Nicht-Vakuum-Last tragen die echten Content-Hashes (Bundle-BYTE-Vergleich) und die Negativ-Kontrolle (divergent ≠ clean, hart FAIL). — Defer-Grund: Präzedenz Story-3.8-Defer „DET-1/DET-2-Plan-/Form-Literale vs. AC-5 „nicht hart codiert"" (Block oben, Home: Story-3.13-Abnahme, echte Gate-Runs über die Instruktion); Story-3.9-Sandbox folgt demselben Demo-Muster. Home: Story-3.13-Abnahme. — status: offen
## Aufgegriffen: Orphan voller Stufen b/c + Mehrziel (Story-3.9-Defer, Home Story 3.10) — Story 3.10, 2026-08-21
- Bezug: Defer «Orphan-Term hand-gesteuert + feste 2-Datei-Scan … die volle Stufen-b/c-Traversal-Coverage des Kandidaten-Baums bleibt verbleibend offen (Home: Story 3.13-Abnahme / Sandbox-Härtung — benannter Defer, keine instruktionsseitige Lücke, §5.15 Pkt. 2/3 verankern die Stufen)» (Defer-Block „Deferred from: code review of spec-3-9-deterministische-relevanz-und-reconcile-routing-schliessen", 2026-08-21) — im Kontext von Story 3.10 gezogen als Hold-Home des §5.16-Hold-Ausbaus (AC-8/NFR-7).
- Umsetzung: `schema/compiler.md` §5.16 Pkt. 8 verankert den post-Reconcile-Orphan über **alle Erhebungs-Stufen a/b/c** (nicht nur Stufe a wie in Story-3.9) und die Mehrziel-Auflösung via D-8-Mehrfach-Term-Vereinigung (primäre Ziel-Repräsentation gilt, sonst fail-closed benannter Hold); beide Evidenzpfade (Stufen-Scan + Mehrziel-Vereinigung) landen im Run-Receipt (§5.16 Pkt. 8, außerhalb Bundle §5.14 Pkt. 2). Kein neuer Regel-Operand, keine neue Invaliditätsklasse; die AD-16-/semantische Kollisionsauflösung bleibt Epic 4.
- Sandbox-Nachweis: Sandbox-3.10 E-8 (Orphan über Stufe a/b/c belegt, D-8-Vereinigung → primäre Ziel-Repräsentation, fail-closed Hold, beide Evidenzpfade im Receipt) — Exit 0; Stufe-a-Pfad zusätzlich in E-4 (CONFIRMING-Stellen-Abgleich) und E-7 (Stufen-Routing-Kandidatenliste) ausgeübt.
- status: aufgegriffen (Stufen-a/b/c-Traversal-Coverage + Mehrziel-Hold mechanisch in Sandbox E-8 ausgeübt und in §5.16 Pkt. 8 verankert; die Umlaut-vs-Transkription-Divergenz bleibt als eigener Defer offen — „kein neuer Normalisierungs-Operand, append-only, ask-first", Home: Sandbox-Vereinheitlichung oder folgende Compiler-Instruktions-Revision)
## Deferred from: code review of spec-3-10-inkrementelle-update-und-synthese-erhaltung-absichern (2026-08-21)
- source_spec: `spec-3-10-inkrementelle-update-und-synthese-erhaltung-absichern.md`
summary: Sandbox-3.10 uebernimmt die `norm()`/`tolower()`-Helfer ohne `LC_ALL`-Pinning (Byte-Klasse `[–—]` locale-abhaengig) — vorbestehender Defer (D-7, W-3.9-1), hier neu kopiert; kein Instruktions-Defekt, da die Instruktion keine Locale-Bindung nennt.
evidence: run-sandbox.sh (Sandbox-3.10) nutzt dieselben `norm()`-Definitionen wie sandbox-3-9; die unter E-8 neu eingesetzten `stufe_*_scan`-Funktionen rufen `norm` auf denselben bodies auf; `schema/compiler.md` §3.2-Pkt.-1b bindet die Kollaps-Klasse nicht an eine Locale.
- source_spec: `spec-3-10-inkrementelle-update-und-synthese-erhaltung-absichern.md`
summary: Die Sandbox-3.10 validiert ihre Erhaltungs-Invarianten ausschliesslich im isolierten /tmp-Baum (fixture-eigene `git status`-/`log.md`-Pruefungen); ein externer, gegen das reale committete Bundle (wiki/-+raw/) laufender Validator auf Abnahme-Ebene wird nicht ausgefuehrt.
evidence: run-sandbox.sh definiert `SB_DIR=/tmp/sandbox-3-10` und beruehrt nie den realen Ist-Baum (Kommentar Z. 55-58); die `wiki/`-nur-`log.md`-Invariante und die `raw/`-Unangetastetheit liegen ausserhalb der Sandbox — Home: Story-3.13-Abnahmegate.
## Deferred from: code review of spec-3-11-root-scope-leasing-atomar-akquirieren (2026-08-21)
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Das von Story 3.11 verwiesene AC-d („niemals zwei aktive Root-Leases gleichzeitig") ist in der Vorlage von epics.md A0-13/AD-17b nicht separat gelistet; die Sandbox und §5.17 verwenden den Token AC-d ohne normative Vorlagen-Definition.
evidence: epics.md Z. 412-416 listet AC-a..c und AC-e, aber kein AC-d; §5.17 Pkt. 1/2/3/5 nutzen AC-a/b/c/e, die Sandbox A-2/A-3 und der log.md-Eintrag verwenden „AC-d"; der Scope ist bei A0-13/AD-17b (genau ein scope-bezogener Lock) und der Zwischenzustands-Assertion verankert — der AC-d-Token ist ein konsistenzerhaltender Behelf, kein neuer Anforderungskanon. Erhaltender Konsistenz-Defer; Koordinator kann AC-d bei der Epic-4-Statussynchronisation der Architecture-Spine formalisieren.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Die Zeilenanker im Code Map-Verweis auf §5.11 (Z. 304-321) / §5.12 (Z. 323-342) der Spec sind nach dem Wachstum des Dokuments (§5.13-§5.17) nicht mehr die aktuellen Zeilennummern.
evidence: Die Spec (Code Map) und die Task-1-Rationale nennen die §5.11-Zeilen als Verweisanker; `schema/compiler.md` hat seit Story 3.8-3.10 weitere Sektionen erhalten, sodass die Zeilen heute woanders liegen — reine Dokumentationsdrift im Planungsartefakt, kein Instruktionsdefekt. Beim nächsten Durchlauf aktuelle Zeilen neu ermitteln.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Die A-1/A-5-Negativprüfungen „Schlüssel trägt keine Run-ID" sind gegen die hart gesetzte Konstante `SCOPELOCK="refs/leases/wiki"` tauglich, konstruieren aber keinen dynamischen Gegen-Beweis (Ref-Name aus einer Run-ID gebaut, der nicht matchen dürfte).
evidence: run-sandbox.sh A-1 (Z. 182) und A-5 (Z. 333) matchen `$SCOPELOCK` gegen statische Muster (`*RUN-A1*|*run-a1*|*a1*`); ein Ref-Name, der tatsächlich eine Run-ID trüge, wäre nur indirekt ausgeschlossen. Strukturell trägt der Ref-Name keinerlei Run-ID (eine Scope-Ref), der Nachweis ist vorhanden — eine dynamische Negative-Kontrolle (Konstruktion eines run-id-haltigen Refs und Assert, dass dieser NICHT auftritt) würde die Assertion schärfen. Home: künftige Sandbox-Härtung.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Die Helfer-Funktion `scopelock_header_banner() { :; }` in der Sandbox-3.11 ist ein leerer Platzhalter.
evidence: run-sandbox.sh Z. 166 definiert `scopelock_header_banner() { :; } # (placeholder für Vereinheitlichung mit Story-3.5-Konvention)` — die Funktion wird nie aufgerufen und trägt nichts bei; kosmetisch. Home: Story-3.12-Lifecycle-Erweiterung, falls die Banner-Konvention übernommen wird, sonst entfernen.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Sandbox-3.5/-3.6 üben weiterhin check-then-act-Lockfile-Akquise (clone-lokal, `lease/<area>/<id>.lock`) ohne den §5.17-Scope-Lock; die von der Story-3.11-Code-Map benannte „Ableitungspflicht des neuen atomaren Lock-Modells“ ist nicht vollzogen.
evidence: Repo-weiter Grep: `akquire()`-check-then-act existiert nur in sandbox-3-5 (L1/L2/L5, Exklusivität nur für identische `<id>`-Lockfile-Pfade) und sandbox-3-6; `refs/leases`/§5.17 kommen nur in den sieben Story-3.11-Artefakten vor. Kein Test koppelt zwei Producer mit *verschiedenen* `<id>` im selben Root-Scope an genau einen §5.17-Gewinner. Home: Story-3.12-Lifecycle / Story-3.13-Abnahme.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: A-2 garantiert keine zeitliche Überlappung der zwei Prozesse (kein Synchronisations-Barriere); ein Scheduler kann sie vollständig sequenzieren — genau die „Zeitversatz ohne Überlappung“-Situation, die die Spec als nicht beweiskräftig für Atomarität ausschließt (die create-only-Atomarität selbst wird davon nicht berührt).
evidence: run-sandbox.sh A-2: die zwei Subshells werden ohne Barriere gestartet (Z. 231-233); der Sampler taktet 6×`sleep 0.002` (Wanduhr-Imperativ im Spannungsfeld der A0-20-Rahmung). Home: Sandbox-Härtung / Story-3.13-Abnahme (echte Überlappung z. B. via gemeinsamer Ready-Signal-Datei/`git update-ref`-Pacing).
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Status-Kontraktion Spec-Frontmatter `status: 'done'` vs. `sprint-status.yaml` `review` wiederholt das Story-3.9-Muster (D-3.9-2), ohne den Präzedenz-Verweis im Eintrag zu benennen.
evidence: spec-3-11-Frontmatter `status: 'done'` (entpricht der 3.9-/3.10-Präzedenz im Implementierungs-Commit); finaler `done`-Flip erfolgt im Step-05-Status-Sync nach konvergiertem Review-Loop (Präzedenz D-3.9-2). Entwarnung: kein Defekt, Nachweis-Verweis nachführen.
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md`
summary: Die I/O-Matrix (frozen) listet 4 Szenarien, die Sandbox übt 8 (A-5 Schlüssel-ohne-Run-ID, A-6 späte sequentielle Abweisung, A-8 Freigabe-erneut ohne Matrix-Zeile/Expected-Output); dazu der leere `scopelock_header_banner()`-Platzhalter.
evidence: Spec-I/O-Matrix Z. 587-591 (4 Zeilen) vs. run-sandbox.sh A-1..A-8; Task-2 formuliert „Tests der I/O-Matrix und der 5 ACs“. Home: Matrix-Nachführung (Append außerhalb frozen Blocks über den Spec-Change-Log) / Story-3.12.
- KORREKTUR (Review-Loop-1, 2026-08-21): Der obere Defer „Zeilenanker im Code Map-Verweis auf §5.11 (Z. 304-321)/§5.12 (Z. 323-342) nicht mehr aktuell“ ist **faktisch falsch** — bei e02cf84 liegt `## 5.11` weiterhin exakt auf Z. 304 und `## 5.12` auf Z. 323 (verifiziert via `grep -n "^## 5.11\|^## 5.12"`); es existiert kein Zeilen-Drift an diesen Anker. Der Defer wird hiermit re-traktiert (append-only-Korrektur, Original bleibt erhalten); die realen Fehlanker sitzen in der Suggested-Review-Order (`compiler.md:415/527`, `log.md:47`, `deferred-work.md:577`) — als Patch-Item in der Spec-Review-Findings geführt.
### Aufgegriffen: Sandbox-3.5/-3.6-check-then-act-Abgleich mit dem §5.17-Scope-Lock (Story-3.11-Defer, Home Story 3.12) — Story 3.12, 2026-08-21
- Bezug: Defer `Sandbox-3.5/-3.6 üben weiterhin check-then-act-Lockfile-Akquise (clone-lokal) ohne den §5.17-Scope-Lock; Ableitungspflicht des neuen atomaren Lock-Modells nicht vollzogen` (Defer-Block „Deferred from: code review of spec-3-11 …", Eintrag der Z. 593-594; Home: Story-3.12-Lifecycle / Story-3.13-Abnahme).
- Umsetzung: Die Story-3.12-Sandbox übernimmt die §5.17-Modelle (scope-bezogener Lock `refs/leases/wiki`, create-only-Akquise, ownership-gebundener Ref-Delete) für den gesamten Lifecycle — `schema/compiler.md` §5.18 Pkt. 1/2/5: Liveness-/Ownership-Prüfung am Lock-Inhalt, stale-Übernahme als Ownership-CAS, durable Release per Ref-Delete nur durch Inhaber; die check-then-act-Lockfile-Akquise der Story-3.5/-3.6-Sandboxen bleibt als historische Übung unverändert bestehen (Fugen-Identität, keine Re-Negotiation).
- Sandbox-Nachweis: `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh` L-1..L-9 (Lock als einziger Exklusivitätsschlüssel über den kompletten Lifecycle; Ownership-Prüfung in L-1/L-2 via `lease_liveness_stale`/Lock-Inhalt, Takeover via `scopelock_takeover`, Release per `scopelock_release` Ref-Delete in L-5/L-6/L-7/L-9) — Exit 0.
- status: aufgegriffen (Home erledigt in §5.18 Pkt. 1/2/5 und sandbox-3-12; Rest-Vertiefung — echte Zwei-Worktree-Überlappung mit Synchronisations-Barriere — bleibt Story 3.13-Abnahme)
### Aufgegriffen: Banner-/I-O-Matrix-Kosmetik (Story-3.11-Defer, Home Story 3.12) — Story 3.12, 2026-08-21
- Bezug: Defer `Die I/O-Matrix (frozen) listet 4 Szenarien, die Sandbox übt 8 … dazu der leere scopelock_header_banner()-Platzhalter` (Defer-Block „Deferred from: code review of spec-3-11 …", Eintrag der Z. 602-603; Home: Matrix-Nachführung (Append außerhalb frozen Blocks über den Spec-Change-Log) / Story-3.12).
- Umsetzung: Die Story-3.12-Sandbox führt die Banner-Konvention ein (nicht-leerer `scopelock_header_banner()` je Lifecycle-Run, Z. 310-312) und deckt die 12 I/O-Matrix-Zeilen der Story-3.12-Spec als harte Assertionen L-1..L-9 ab (LIVENESS_AKTIV, STALE_UEBERNAHME, DIRTY_GETRACKT, DIRTY_UNGETRACKT, ROLLBACK_NACH_MUTATION, ROLLBACK_NACH_COMMIT_VOR_RELEASE, RELEASE_DURABLE, KANONISCHES_LOG, KILLPUNKT_VOR_MUTATION, KILLPUNKT_NACH_MUTATION, KILLPUNKT_VOR_COMMIT, KILLPUNKT_NACH_COMMIT); die Matrix-Kosmetik-Nachführung der Story-3.11-Spec-Diskrepanz (4 vs. 8 Szenarien) bleibt als Append über den Story-3.11-Spec-Change-Log benannt.
- Sandbox-Nachweis: `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh` L-1..L-9 (jede Matrix-Zeile als Szenario) — Exit 0.
- status: aufgegriffen (Banner-Konvention umgesetzt in sandbox-3-12; restliche Story-3.11-Matrix-Kosmetik bleibt benannter Defer)
### Aufgegriffen: `scopelock_header_banner()`-Platzhalter (Story-3.11-Defer, Home Story 3.12) — Story 3.12, 2026-08-21
- Bezug: Defer `Die Helfer-Funktion scopelock_header_banner() { :; } in der Sandbox-3.11 ist ein leerer Platzhalter` (Defer-Block „Deferred from: code review of spec-3-11 …", Eintrag der Z. 590-591; Home: Story-3.12-Lifecycle-Erweiterung, falls die Banner-Konvention übernommen wird, sonst entfernen).
- Umsetzung: Die Story-3.12-Sandbox übernimmt die Banner-Konvention und ersetzt den leeren Platzhalter durch eine dokumentierende Funktion (`scopelock_header_banner() { echo "--- Lifecycle-Run: $1 (Root-Scope wiki/, Lock refs/leases/wiki) ---"; }`, Z. 310-312) — je Lifecycle-Szenario wird der Banner als Run-Kopf ausgegeben (L-1, L-3..L-9). Die Story-3.11-Sandbox selbst bleibt — wo es keinen Platzhalter-Aufruf gibt — unverändert (Fugen-Identität).
- Sandbox-Nachweis: `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh` L-1/L-3..L-9 (Banner-Ausgabe je Run) — Exit 0.
- status: aufgegriffen (Home erledigt in sandbox-3-12, Banner-Konvention umgesetzt)
- source_spec: `_bmad-output/implementation-artifacts/spec-3-12-lease-lifecycle-und-commit-abschluss-transaktional-schliessen.md`
summary: Echte Zwei-Worktree-/Zwei-Prozess-Übernahme des Root-Scope-Locks mit Synchronisations-Barriere üben — die §5.18-Pkt.-2-Takeover-Atomarität (AC-2) ist in sandbox-3-12 nur sequenziell (ein Worktree) geprüft.
evidence: Verification-Gap-Review Story 3.12 (Loop 1): sandbox-3-12 läuft komplett in einem `git init`-Worktree; die §5.17-geteilte-Ref-Namespace-Sichtbarkeit und die Echt-Überlappung (Zwei Producer gleichzeitig, eine Entscheidung) ist nicht beobachtet; die vorhandene Rest-Vertiefung in der Story-3.11-Aufgegriffen-Notiz (Home §5.18 Pkt. 1/2/5) und der deferred-work-Eintrag zur Zwei-Worktree-Barriere (Sandbox-Härtung / Story-3.13-Abnahme) scopen dasselbe Restziel.
## Deferred from: code review of spec-3-12-lease-lifecycle-und-commit-abschluss-transaktional-schliessen (2026-08-22)
- DF1 — AC-3-`git stash push`-Variante und kombiniertes AC-5 „Fremd-Bytes restauriert" nie geübt: §5.18 Pkt. 3 benennt `git stash push -- <Pfad>` bzw. `stash push -u` als zulässige native Alternative zur Scratch-Zone; die Sandbox L-3/L-4 übt ausschließlich die Scratch-Zweige (der Stash-Ast — Sichern, Worktree-Bereinigung, byte-identischer Restore aus dem Stash — hat keinerlei Coverage/Guard). Zusätzlich wird AC-5s Clean-Input-Guard-Klausel „geschützte Fremd-Bytes sind restauriert" nur isoliert in L-3/L-4 bewiesen, nie kombiniert mit Release + Folge-Run im selben Szenario. Home: Sandbox-Härtung (Story-3.13-Abnahme-Gate deckt native-Varianten-Abdeckung über echte Bäume).
- DF2 — `assert_no_wallclock` überbreit (trifft jedes `YYYY-MM-DD`-Zeichenmuster, inkl. legitimer `### YYYY-MM-DD`-Datumsgruppen des `wiki/log.md` — Konventionsdaten, keine Wanduhr-Steuerung): der A0-20-Check ist daher nur auf `registry/wiki` (L-1) anwendbar, nicht auf `log.md`; der L-1-Kommentar „kein Zeitstempel in Lock/Registry" behauptet breitere Coverage als die Funktion liefert. A0-20-Steuerung greift korrekt (nur auf Registry/Lock); Kommentarschärfe + eventuelle schärfere Regex (z. B. `T[0-9]{2}`-Pflicht) = Kosmetik. Home: Sandbox-Kosmetik (nächste Sandbox-Härtung).
### Aufgegriffen: Story-3.13-Abnahme als Home der 3.13-namigen Defer (Gate-Runs realisiert; rd-Notiz-/Defer-Block unverändert) — Story 3.13, 2026-08-22
Der Epic-3-Abnahmegate-Lauf (`_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh`, RUN_OK/Exit 0, PASS_COUNT=70, FAILED=0) realisiert das Home „Story-3.13-Abnahme" der folgenden Defer-Einträge einschlägig. Die Original-Blöcke bleiben historisch unverändert (nur das Home ist erledigt, Spec-3.13 Code-Map Pkt. 59; AD-17e).
- **DET-1/DET-2-Plan-/Form-Literale vs. AC-5 „nicht hart codiert"** (Story-3.8-Defer, Z. 551; Home: Story-3.13-Abnahme, echte Gate-Runs über die Instruktion) — aufgegriffen: Der 3.13-Gate-Kern E führt GEGEN die committete Fixture zwei echte frische Agent-Läufe (A/B, getrennte Worktrees auf demselben `$BASE`) über `schema/compiler.md` als einzige Instruktionsquelle aus; der Harness schreibt weder erwartete Bodies noch Receipts (AC-5); der A/B-Vergleich bestätigt bundle-State-Identität bis auf die `generated.at`-Ausnahme (§5.14 Pkt. 2). Die Nicht-Vakuum- und Determinismus-Last tragen die real erzeugten Bundle-Bäume (git diff A B -- wiki/, nur at-Zeilen) und die Negativ-Kontrolle G-6.
- **Positive Zwei-Run-Identität strukturell trivial (R-9 clean A==B)** (Story-3.9-Defer, Z. 560; Home: Story-3.13-Abnahme) — aufgegriffen: Die Zwei-frische-Agenten-Bestätigung ist im 3.13-Gate real (getrennte Kontexte, getrennte Worktrees, gemeinsamer `$BASE`, Instruktion als einzige Quelle), nicht mehr aus Skript-Literalen abgeleitet; A==B-Identität wird über den realen Bundle-Baum-Vergleich assertiert.
- **Externer Validator gegen das reale committete Bundle** (Story-3.10-Defer, Z. 576; Home: Story-3.13-Abnahmegate) — aufgegriffen: Abschnitt D des Gates startet einen frischen Agent-Kontext, der `schema/validator.md` (Rev 9) über alle 7 `wiki/`-Dateien der realen /tmp-Käfig-Kopie ausführt und Verdikte `SUCCESS`/`FAIL` je Datei ablegt; das Gate prüft Grammatik + Exhaustivität, urteilt nie selbst (D-3).
- **Zwei-Producer-verschiedene-`<id>`-Gewinner** (Story-3.11-Defer, Z. 594; Home: Story-3.12-Lifecycle / Story-3.13-Abnahme) — aufgegriffen: sandbox-3-12 (Sub-Run im Gate, Abschnitt C) übt den atomaren Ownership-CAS-Takeover des `refs/leases/wiki`-Scope-Locks; das 3.13-Gate hebt die 12 Sandbox-Suiten in einen einzigen fail-fast-Abnahmelauf (AC-1).
- **A-2 keine Synchronisations-Barriere / zeitliche Überlappung** (Story-3.11-Defer, Z. 597; Home: Sandbox-Härtung / Story-3.13-Abnahme) — aufgegriffen (Gate-Route): Der 3.13-E-Kern demonstriert die Real-Tree-Ausführung zweier unabhängiger Compiler-Runs in getrennten Worktrees; die atomare create-only-`git update-ref`-Semantik von sandbox-3-12 wird als Sub-Run des Abnahmegates re-ausgeführt. Die zwangsweise gleichzeitige (wanduhrgetaktete) Überlappung zweier Prozesse bleibt als benannter Rest-Term der Sandbox-Härtung (A0-20-rahmenkonform) bestehen.
- **Rest-Vertiefung echte Zwei-Worktree-Überlappung** (Story-3.11-Aufgegriffen-Notiz, Z. 611) — aufgegriffen (Rest-Term wie im Original-Eintrag benannt): das 3.13-Gate führt die Zwei-frische-Agenten-Bestätigung real in getrennten Worktrees aus; eine wanduhr-synchronisierte Echt-Überlappung beider Produzenten bleibt Rest-Vertiefung der Sandbox-Härtung.
- **Geteilter-Ref-Namespace-/Zwei-Worktree-Übernahme-Real-Beweis** (Story-3.12-Defer, Z. 629; Home: Story-3.13-Abnahme) — aufgegriffen: Das 3.13-Gate stellt den Clone-geteilten Käfig (ein `git init`-Repo, mehrere Worktrees auf demselben `$BASE`) bereit und führt die Zwei-frische-Agenten-Runs real aus — der Determinismus-/Takeover-Beweis läuft hier über echte unabhängige Worktrees statt über einen einzigen sequenziellen Worktree.
- **DF1 — AC-3-`git stash push`-Variante + kombiniertes AC-5** (Story-3.12-Defer, Z. 633; Home: Sandbox-Härtung / Story-3.13-Abnahme-Gate) — aufgegriffen (Gate-Route): Das 3.13-Gate re-ausführt sandbox-3-12 L-1..L-9 (inkl. Scratch-Zone-/Restore-Klasse und Clean-Input-Guard) als Sub-Run im Gesamtabnahmekontext; die zusätzliche native `git stash`-Alternativ-Variante und die wanduhr-gekoppelte Kombination bleiben als Rest-Term der Sandbox-Härtung benannt (keine neue §-Semantik, keine Instruktions-/Validator-Änderung, AD-3).
- status: aufgegriffen (Home „Story-3.13-Abnahme" realisiert durch die echten Abnahmegate-Runs; Original-Defer-Blöcke unverändert; jeweils benannte Rest-Terme der weiterführenden Sandbox-Härtung bleiben offen — kein Instruktions-/Validator-Defekt, A0-20-konform)
@@ -4,7 +4,7 @@
## Goal
Der Compiler verarbeitet neues Source Material gegen das bestehende Knowledge Bundle, statt es bei jedem Lauf aus sämtlichen Rohquellen neu aufzubauen (Compounding Knowledge). Bestehende Concepts werden durch neue Erkenntnisse erweitert, präzisiert oder korrigiert; Informationen aus mehreren Sources werden zu einer gemeinsamen Wissensrepräsentation mit gemischter, claim-granularer Provenienz synthetisiert; unverändertes Wissen bleibt erhalten. Die Relevanzbestimmung ("welche bestehenden Concepts sind betroffen") ist textual-deterministisch (grep/ripgrep, Markdown-Traversal, Link-Following) — ohne Embedding- oder Vector-Infrastruktur. Konkurrierende Producer koordinieren sich über ein leasing-/branch-basiertes Workspace-Modell mit Root-Scope-Lease und Dirty-Tree-Schutz; Mutationen sind commit-gebunden, und jeder Run endet in einem konsistenten, über denselben Git-State reproduzierbaren Bundle (AD-5, AD-6, AD-13, AD-17a..h).
Der Compiler verarbeitet neues Source Material gegen das bestehende Knowledge Bundle, statt das Wiki bei jedem Lauf aus sämtlichen Rohquellen neu aufzubauen (Compounding Knowledge). Bestehende Concepts werden durch neue Erkenntnisse erweitert, präzisiert oder in eindeutig belegten Fällen korrigiert; mehrere Sources werden zu einer gemeinsamen Wissensrepräsentation mit gemischter, claim-granularer Provenienz synthetisiert; unverändertes Wissen bleibt erhalten. Relevanzbestimmung und Reconcile-Routing sind textual-deterministisch ohne Embedding- oder Vector-Infrastruktur. Konkurrierende Producer koordinieren sich über eine atomare Root-Scope-Lease mit transaktionalem Dirty-Tree-, Rollback- und Release-Lifecycle. Klassifikationspflichtige Kollisionen und Widersprüche werden bis Epic 4 fail-closed als strukturierter Hold erhalten; der Determinismus-Vertrag wird mechanisch qualifiziert. **Ist (Story 3.13, 2026-08-22): das reale, unabhängige Source→Compilation→Wiki-Abnahmegate ist ausgeführt — `_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh` lief voll grün (PASS_COUNT=70, FAILED=0, RUN_OK/Exit 0): 12 Sandbox-Suiten fail-fast, vollständiger Validator (schema/validator.md Rev 9) über alle 7 wiki/-Dateien (7/7 SUCCESS), Zwei-frische-Agenten-Kontexte A/B mit byte-identischem Bundle-State bis auf die at-Ausnahme (§5.14 Pkt. 2), G-1..G-8 inkl. Epic-5-Consumer-Smoke und Porcelain-Check; siehe wiki/log.md Story-3.13-Eintrag.** Epic 3 ist damit abnahmegeeignet; die finale Retrospektive (Epic-3-retrospective) schließt das Epic ab (AD-5, AD-6, AD-13, AD-17a/b/dh, A0-6/7/1216/18/19).
## Stories
@@ -16,35 +16,39 @@ Der Compiler verarbeitet neues Source Material gegen das bestehende Knowledge Bu
- Story 3.6: Lease-Staleness & Recovery-Basis absichern
- Story 3.7: Reason/Mutate-Trennung und Konsistenz-Endzustand sicherstellen
- Story 3.8: Determinismus-Vertrag (AD-17h) als Agent-Instruktions-Validator umsetzen
- Story 3.9: Deterministische Relevanz- und Reconcile-Routing schließen
- Story 3.10: Inkrementelle Update- und Synthese-Erhaltung absichern
- Story 3.11: Root-Scope-Leasing atomar und worktree-übergreifend akquirieren
- Story 3.12: Lease-Lifecycle und Commit-Abschluss transaktional schließen
- Story 3.13: Epic-3-Verifikations- und Abnahmegate
## Requirements & Constraints
- Ein Compilation Run nimmt neues Source Material und das bestehende Wiki als Input; das Wiki wird inkrementell weiterentwickelt, nie vollständig regeneriert. Unverändertes Wissen bleibt erhalten; Git-Änderungen konzentrieren sich auf die durch die neue Erkenntnis betroffenen Concepts (FR-4, FR-12).
- Neue Informationen führen nicht automatisch zu neuen Dateien: bestehende Concepts werden erweitert, präzisiert oder korrigiert, ohne ihre Struktur zu zerstören; Beziehungen und Provenienz bleiben soweit weiterhin gültig erhalten (FR-6).
- Mehrere Sources zum selben Thema münden in eine gemeinsame Wissensrepräsentation statt getrennter Zusammenfassungen. Das Ergebnis reflektiert den erkannten Wissensstand, konsolidiert Redundanzen und übernimmt die relevante Source-Provenienz der beteiligten Sources (FR-7, AD-4).
- Unvollständiges, ungeprüftes oder teilweise widersprüchliches Wissen wird dargestellt, ohne künstliche Gewissheit zu erzeugen (NFR-7).
- Relevanzbestimmung und Merges sind textual-deterministisch; Embeddings, Vector-Search, Knowledge-Graph-DB und RAG gehören nicht in den Compiler-Kern (AD-13, AD-17h, PRD-No-Goals).
- Ein Compilation Run nimmt neues Source Material und das bestehende Wiki als Input; das Wiki wird inkrementell weiterentwickelt, nie vollständig regeneriert. Unverändertes Wissen und nicht betroffene Concepts bleiben erhalten; Git-Änderungen konzentrieren sich auf die durch die neue Erkenntnis betroffenen Concepts (FR-4, FR-12, AD-5).
- Neue Informationen führen nicht automatisch zu neuen Dateien: bestehende Concepts werden erweitert, präzisiert oder korrigiert, ohne ihre Struktur zu zerstören; weiterhin gültige Beziehungen und Provenienz bleiben erhalten (FR-6).
- Mehrere Sources zum selben Thema münden in eine gemeinsame Wissensrepräsentation statt getrennter Zusammenfassungen. Das Ergebnis reflektiert den erkannten Wissensstand, konsolidiert Redundanzen und übernimmt die relevante Source-Provenienz der beteiligten Sources — claim-granular (FR-7, AD-4, A0-3).
- Relevanzbestimmung, Routing, Planung und nicht-konfligierende Mutationen sind textual-deterministisch (grep/ripgrep, Markdown-Traversal, Link-Following). Embeddings, Vector-Search, Knowledge-Graph-Datenbank und RAG gehören nicht in den Compiler-Kern (AD-13, A0-18).
- Ein Run verwendet ausschließlich veröffentlichte (committete) Inhalte als Input, nie Zwischenstände während der Mutation (AD-17).
- `raw/` bleibt immutable und dient als Recovery-Basis; ein fehlgeschlagener Run verändert es nicht (AD-3).
- Unvollständiges, ungeprüftes oder widersprüchliches Wissen wird ohne künstliche Gewissheit dargestellt; klassifikationspflichtige Konflikte werden bis Epic 4 ohne Wissensmutation in einem benannten Hold erhalten (NFR-7).
## Technical Decisions
- **Inkrementeller Datenfluss (AD-5, A0-6):** Interpret → Reconcile → Synthesize → Update affected Concepts. Startpunkt ist immer das bestehende Bundle — niemals "Regenerate Everything" aus allen Rohquellen. Validiert durch die Incrementality-Anforderung (SM-1, FT-6).
- **Reason/Mutate-Trennung (AD-6, A0-7):** Logische Phasen Analyse → Reconcile → Plan Changes → Mutate → Validate. Keine eigene Workflow Engine; ein Agent kann die Phasen in einer Session durchführen, der beobachtbare Endzustand des Bundles muss bei Abbruch konsistent sein.
- **Deterministische Relevanzbestimmung (A0-18):** Führt zu einer nachvollziehbaren Candidate-Liste von Concept-Pfaden über grep/ripgrep auf `wiki/`, Markdown-Traversal von `index.md` und Link-Following — deterministisch statt probabilistisch. Gleichsam Grundlage für die Bereichszuordnung (gleiches textuelles Prinzip wie AD-7c).
- **Keine eigenständige LLM-Runtime:** Der ausführende agentische Host (Claude/Codex-Adapter) orchestriert die Sequenz gemäß AD-17 (Lease holen, innerhalb des geleasten Bereichs mutieren, committen, freigeben); keine separaten Prozesse oder ein Server (AD-11).
- **Leasing-Modell (AD-17a..f, A0-12..A0-16):** Producer arbeiten auf `lease/<area>/<id>`-Branches; Lease-Akquise gegen einen eindeutigen Commit-Object-Wert (Merge-Base-Disziplin); ein Lockfile realisiert semantisch identisch in jedem Adapter — Realisierung ist nicht pro Adapter frei wählbar. Die Lease umfasst die Root-Scope (`wiki/` inklusive `log.md`, `index.md` und aller Root-Dateien).
- **Kein textueller Auto-Merge (AD-17c, A0-14):** Zwei Branches mit Änderungen am selben Concept-Pfad werden nie textuell automatisch gemerged; der Merge ist compiler-vermittelt und durchläuft die AD-16-Klassifikation mit explizitem `log.md`-Eintrag, falls die Inhalte ungleich sind.
- **Dirty-Tree-Schutz (AD-17e/f, A0-16):** Vor jeder Mutation wird die Working Copy des zu mutierenden Bereichs geprüft; fremde uncommittete Änderungen werden geschützt (Stash/Scratch-Zone) und in `log.md` dokumentiert. Mutationen operieren nur auf Directory-/Commit-Ebene — Commit-Boundary ist die Mutation-Boundary.
- **Lease-Staleness (AD-17d, A0-15):** TTL plus Lease-Registrierung im Clone-Root-State; uncommittete Leases gelten nach Run-Abbruch als stale, verwaiste Leases können übernommen oder als stale markiert und protokolliert werden; `raw/` dient als Zugriffs- und Consistency-Basis.
- **Auflösungsautorität (AD-17g, A0-17):** Der die Lease haltende Compilation Run löst AD-16-Kollisionen (Default: Erhaltung) auf; menschliche Eskalation nur bei Unentscheidbarkeit; die Auflösung wird an Commit-Hash und Klassifikation im `log.md` gebunden.
- **Determinismus-Vertrag (AD-17h/FT-10, A0-19):** Über denselben Git-State und dasselbe Eingabeset erzeugen zwei unabhängige Runs denselben Bundle-State; Abweichung gilt als Fehler der Klassifikation, nicht als Rauschen. Das Enforcement lebt im MVP als Agent-Instruktions-Validator (D-3/Q-6) und muss mechanisch bestätigt sein, bevor es tragend wird.
- **Inkrementelle Evolution und menschliche Kuratierung (A0-21, FT-9):** Unabhängige Concepts werden nicht bei jedem Lauf regeneriert; eine menschliche Korrektur eines maschinell erzeugten Concepts überlebt als normale Kuratierung (Datei-Edit + Git) — kein Nulling-Diff und keine Re-Kompilation des ganzen Bundles.
- **Synthese bleibt source-grounded (AD-4):** Bestehende Concepts dürfen Kontext und Synthese liefern, fachliche Aussagen müssen aber auf nachvollziehbare Sources zurückführbar bleiben; Wiki-Links ersetzen nie die Provenienz zur ursprünglichen Evidenz.
- **Inkrementeller Datenfluss (AD-5, A0-6):** Interpret → Reconcile → Synthesize → Update affected Concepts. Startpunkt ist immer das bestehende Bundle — niemals "Regenerate Everything" (SM-1, FT-6).
- **Reason/Mutate-Trennung (AD-6, A0-7):** Logische Phasen Analyse → Reconcile → Plan Changes → Mutate → Validate. Keine eigene Workflow Engine; ein Agent kann die Phasen in einer Session durchführen, der beobachtbare Endzustand des Bundles muss auch bei Abbruch konsistent sein.
- **Deterministische Relevanz & Routing (A0-18):** Geschlossene, geordnete Term-Gewinnung bzw. explizites persistiertes Term-Manifest; Suchterm und Concept-Body werden identisch normalisiert und literal-sicher verglichen. Eine exklusive Routing-Tabelle unterscheidet `UPDATE`, `CREATE`, `ORPHAN/HOLD` und echten `NO_OP`; gleicher Git-State plus gleiches Eingabemanifest erzeugt dieselbe Candidate-Liste und Reihenfolge. *Ist (Story 3.9, 2026-08-20): in `schema/compiler.md` **§5.15** verankert (Termgewinnung geschlossen, eine exklusive Routing-Tabelle, Raw-Immutability-Guard, reservierte Zielpfade, Zwei-Run-Identität; §3.2 bleibt Erhebungs-Anker, §5.15 Pkt. 3 die Routing-Zuordnung).* *Ist (Story 3.10, 2026-08-21): die Erhaltungs-Absicherung der operativen Update-/Synthese-Schritte und der Hold-Ausbau sind in `schema/compiler.md` **§5.16** verankert (Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau, Punkte 18: Kontinuitäts-Garantie AC-1, Korrigieren mit Run-Receipt-Trace AC-2, Schutzbestandteile & Byte-Identität AC-3, CONFIRMING-Konsolidierung ohne NO_OP AC-4, gemeinsame Wissensrepräsentation als Synthese-Erhaltung AC-5, byte-erhaltender NO_OP AC-6, Provenienz-/Link-Selbsttest aus aktuellem Run AC-7, benannter Hold über die Erhebungs-Stufen a/b/c + Mehrziel-Auflösung AC-8/NFR-7; Erhaltungs-Nachweis als re-executierbare Sandbox E-1..E-9, Exit 0; kein neuer Regel-Operand, keine §7-Klasse, kein Standalone — Umlaut-vs-Transkription bleibt benannter Defer).*
- **Keine eigene LLM-Runtime:** Der ausführende agentische Host orchestriert den AD-17-Ablauf (Lease holen, innerhalb des geleasten Bereichs mutieren, committen, freigeben); keine separaten Prozesse oder ein Server (AD-11).
- **Atomare Root-Scope-Lease (AD-17a/b, A0-12/13):** Producer behalten die Branch-Konvention `lease/<area>/<id>`, akquirieren aber genau einen atomaren, scope-bezogenen Lock im clone-geteilten Zustand. Die Run-ID ist Lock-Inhalt, nicht Exklusivitätsschlüssel; konkurrierende Producer verschiedener IDs und Worktrees teilen denselben Root-Scope (`wiki/` inklusive `log.md`, `index.md` und aller Root-Dateien). *Ist (Story 3.11, 2026-08-21): die atomare Lock-Präzisierung ist in `schema/compiler.md` **§5.17** verankert (Revision 3.6 — Atomare Root-Scope-Lease-Akquise: Exklusivitätsschlüssel = einziger scope-bezogener Lock im clone-geteilten Zustand, eine committete Ref je Root-Scope `wiki/`, `git update-ref <ref> <wert> $ZERO_SHA` create-only, Run-ID als Lock-Inhalt statt Schlüsselbestandteil, atomare Akquise mit genau einem Gewinner und `LEASE_HOLD` für den Abgewiesenen, Kollisions-Hold mit beiden Commit-Hashes an Epic 4 statt textuellem Auto-Merge; §5.11-Pkt.-1/2-Wortlaut und §5.12-Anker bleiben unverändert maßgeblich, Fugen-Identität).*
- **Fail-closed Kollisionsgrenze (AD-17c, A0-14):** Zwei Branches mit ungleichen Änderungen am selben Concept-Pfad werden nie textuell automatisch gemerged. Epic 3 erhält beide Commit-Hashes und den Scope in einem strukturierten Hold; AD-16-Klassifikation und semantische Auflösung sind Epic 4 / Story 4.x.
- **Transaktionaler Lifecycle (AD-17df, A0-15/16, AD-6):** Preflight schützt getrackte und ungetrackte Fremdänderungen (eindeutige Abort-/Protect-Zustandsmaschine), Rollback restauriert exakt den bezeichneten Baseline-Commit (Index + Worktree), Release hinterlässt Mutation, zulässigen Nachweis und sauberen Worktree dauerhaft. Eine lebende Lease wird nicht allein durch Generationserhöhung stale; Staleness verlangt bestätigten Abbruch oder abgelaufene Liveness plus atomare Ownership-Prüfung. *Ist (Story 3.12, 2026-08-21): die transaktionale Lifecycle-Klammer ist in `schema/compiler.md` **§5.18** verankert (Revision 3.7 — Lease-Lifecycle & transaktionaler Commit-Abschluss: Liveness & Ownership AC-1, stale-Übernahme genau einmal mit benannter ersetzter Holder-ID AC-2, eindeutige Abort-/Protect-Zustandsmaschine für getrackte/ungetrackte Fremdänderungen mit byte-identischem Restore AC-3, Baseline-Rollback Index + Worktree aus `<Baseline-Commit>` mit leerem Post-Rollback-Diff AC-4, durable Release + Clean-Input-Guard für den Folge-Run AC-5, kanonisches Log AC-6, vier Kill-Point-Tests AC-7; §5.11/§5.12/§5.13/§5.17-Wortlaute textuell unverändert, Fugen-Identität; keine Wanduhr-TTL, A0-20).*
- **Determinismus-Vertrag (AD-17h/FT-10, A0-19):** Das kanonische Eingabemanifest bindet Baseline, geordnete Sources und jeden output-sichtbaren Run-/Zeit-/Identitätswert; der Run-Receipt (Candidates, Reihenfolge, Plan, Entscheidungen, Output-Hashes) liegt außerhalb des Bundles. Zwei getrennte saubere Worktrees mit frischen Agent-Kontexten erzeugen denselben Bundle-State; hart codierte erwartete Pläne oder Concept-Bodies und pauschal maskierte `verified`-Ereignisse sind kein gültiger Nachweis.
- **Synthese bleibt source-grounded (AD-4):** Bestehende Concepts dürfen Kontext liefern, fachliche Aussagen müssen aber auf nachvollziehbare `raw/`-Evidenz zurückführbar bleiben; Wiki-Links ersetzen nie die Provenienz zur ursprünglichen Evidenz.
- **Epic-3-Abnahmegate:** Ein portables, fail-fast ausführbares Gate führt alle Epic-3-Szenarien aus, ruft den vollständigen autorisierten Schema-Validator über alle `wiki/`-Dateien auf und lässt einen frischen Agent-Kontext die kanonische Instruktion über einer repräsentativen Fixture ausführen — der Harness schreibt keine erwarteten Wiki-Ausgänge selbst. Zwei frische Agent-Kontexte müssen Run-Receipts und Bundle-State angleichen. Ein Defizit des autorisierten Validator-Vertrags blockiert das Gate und verlangt eine separat genehmigte Epic-1-Remediation. *Ist (Story 3.13, 2026-08-22): realisiert als `sandbox-3-13/run-sandbox.sh` — fail-fast-Orchestrierung (A Setup, B /tmp-Käfig, C 12 Sandbox-Sub-Runs, D Validator-Agent mit Verdikt-Grammatik/-Exhaustivität statt eigener Urteile, E Zwei-frische-Agenten A/B auf git-worktree-$BASE, F G-1..G-8 harte Assertions inkl. G-6 Negativ-Kontrolle Perturbation und G-7 Epic-5-Smoke, G Porcelain-Endzustands-Invariante). Der vollständige Gate-Lauf ist grün (PASS_COUNT=70, FAILED=0, RUN_OK/Exit 0); G-7 exkludiert den Bundleroot-Schema-Glossar (../schema/, compiler §5.6 „andere Schicht", AC-8 optionale Provenienz) und das log.md-Protokoll (§5.6 Pkt. 2).*
- **Keine UX-/Design-Anteile relevant:** v1 ist datei-/CLI-basiert ohne GUI (PRD A-3, AD-11).
## Cross-Story Dependencies
- Baut auf dem Workspace aus Epic 1 auf (`raw/` immutable, Bundle-Root, Schema-Validierung) und konsumiert die in Epic 2 erzeugten, OKF-konformen, verlinkten Concepts mit claim-granularer Provenienz als vorhandenes Wissen.
- Der compiler-vermittelte Merge (AD-17c) und die Kollisionsauflösung (AD-17g) setzen die AD-16-Klassifikation samt `log.md`-Dokumentation voraus, die in Epic 4 umgesetzt wird.
- Das Leasing-/Dirty-Tree-Modell dieses Epics koordiniert Compiler-Runs mit menschlicher Bearbeitung (Q-2) und liefert die Grundlage für die Git-Nachvollziehbarkeit und Auflösungs-Dokumentation, auf die Epic 5 aufsetzt.
- Keine UX/Design-Anteile relevant: v1 ist datei-/CLI-basiert ohne GUI (A-3, AD-11).
- Baut auf dem Workspace aus Epic 1 auf (immutable `raw/`, Bundle-Root, Schema-Validierung) und konsumiert die in Epic 2 erzeugten OKF-konformen, verlinkten Concepts mit claim-granularer Provenienz als vorhandenes Wissen.
- Story 3.9 liefert die deterministische Routing-Basis für Story 3.10; Story 3.11 liefert die atomare Lease-Basis für Story 3.12 (parallel). Story 3.8 ist nach 3.93.12 mit echten unabhängigen Runs abgeschlossen (done); **Story 3.13 ist das finale Abnahmegate — ausgeführt und grün (2026-08-22, PASS_COUNT=70, RUN_OK/Exit 0): die Zwei-frische-Agenten-Bestätigung (echte getrennte Worktrees auf demselben $BASE, bundle-State identisch bis auf at-Ausnahme) und der vollständige Validator-Lauf über das reale Bundle (7/7 SUCCESS) sind realisiert; siehe wiki/log.md Story-3.13-Eintrag.**
- AD-17c/A0-14 sind geteilt: Epic 3 verantwortet Erkennung und fail-closed Erhaltung, Epic 4 Klassifikation und semantische Auflösung. AD-17g/A0-17 verbleiben vollständig in Epic 4. A0-21 ist ebenfalls geteilt: Story 3.10 verantwortet den Incrementality-Teil (Erhaltung unabhängigen Wissens), Story 4.3 die Human-Curation-Semantik (FT-9).
- Das Leasing-/Dirty-Tree-Modell koordiniert Compiler-Runs mit menschlicher Bearbeitung und trägt die Git-Nachvollziehbarkeit, auf die Epic 5 aufsetzt.
@@ -0,0 +1,738 @@
#!/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=KandidatenlisteNeu-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 "$@"
@@ -0,0 +1,543 @@
#!/usr/bin/env bash
# Story 3.11 — Sandbox-Tests der atomaren Root-Scope-Lease-Akquise (§5.17, Revision 3.6)
# im clone-geteilten Zustand (AD-17b/A0-13, AC-a..AC-e)
# Re-executierbar: bash run-sandbox.sh (ab Workspace-Root; Sandbox unter /tmp/sb311)
# Zweck: die §5.17-Atomaritäts-Präzisierung (Revision 3.6) als re-executierbarer
# Run-Demonstrator durchspielen —
# A-1 EXKLUSIVITAETS_SCHLUESSEL: ein Mini-Bundle + Root-Scope-Lock im clone-geteilten
# Zustand; der Exklusivitätsschlüssel ist der scope-bezogene Lock (eine committete
# Ref je Root-Scope wiki/), die Run-ID ist Lock-Inhalt und NICHT Teil des Schlüssels
# (Ref-Name trägt keine Run-ID) — AC-a
# A-2 ATOMAR_ZWEI_WORKTREE (nicht-sequenziell): zwei getrennte Worktrees desselben Clones
# akquirieren zeitlich überlappend in Getrennten Prozessen denselben Root-Scope;
# genau ein Gewinner, der andere LEASE_HOLD — AC-b (Ask-First-Primitiv: git update-ref
# <ref> <wert> $ZERO_SHA, create-only; atomar im geteilten Ref-Namespace)
# A-3 WEDER_NOCH (Negativ-Kontrolle): nie zwei aktive Root-Leases gleichzeitig,
# nie kein Gewinner (genau ein scope-bezogener Lock existiert nach der Akquise) — AC-b/AC-d
# A-4 LEASE_HOLD_NICHT_MUTATION: der abgewiesene Producer verändert weder wiki/ noch den
# bestehenden Lock, erzeugt keinen Compilation-Commit, entfernt keine fremde Lease,
# beendet sauber mit LEASE_HOLD — AC-c
# A-5 GEWINNER_SCHLUESSEL_OHNE_RUNID: der Lock-Name ist ausschließlich ein scope-bezogener
# Ref (kein Run-ID-/<id>-Bestandteil im Schlüssel); die per-Lease-<id>-Ablage (§5.11
# Pkt. 1) bleibt per-Lease-inhaltlich/ablagebasiert, Exklusivität hängt am Scope-Lock — AC-a
# A-6 SPAET_ABGEWIESEN: eine spätere, sequentielle Akquise desselben Root-Scopes gg. den
# noch gehaltenen Lock → LEASE_HOLD, Lock-Inhalt (fremde Run-ID) unverändert — AC-c
# A-7 KOLLISIONS_HOLD: zwei Branches mit ungleichen Änderungen am selben Concept-Pfad →
# kein textueller Auto-Merge; strukturierter Kollisions-Hold mit beiden Commit-Hashes
# und Scope an Epic 4 (benannte Hold-Mechanik §5.16 Pkt. 8/§5.10 Pkt. 8) — AC-e/AD-17c
# A-8 FREIGABE_ERNEUT: nach dem Lock-Release (Freigabe deterministisch) erwirbt ein
# nachfolgender Producer denselben Root-Scope atomar erneut; Scope-Lock wieder genau
# einer (Registry-/Lifecycle-Nachfolge bleibt Story 3.12; hier nur Akquise-Atomarität belegt)
# Atomare Akquise (§5.17 Pkt. 2) als HARDE Assertion je Szenario (Exit 1 bei Abweichung);
# keine Wanduhr-/TTL-/Recovery-Logik (A0-20; Story 3.6/3.12); Frontmatter-/log.md-Konformitaet
# (Vertrag §3.3/§3.4, §5). Ubuntu-Sandbox-Semantik auf einem /tmp-Baum — NIE der reale
# wiki/- oder raw/-Baum.
set -u
ROOT=$(mktemp -d /tmp/sb311-XXXXXX)
SB="$ROOT/sb"
mkdir -p "$SB/wiki" "$SB/raw"
cd "$SB"
git init -q
# Determinismus vs. Host-Git-Konfiguration (AD-17h): LF-Blobs + LF-Worktree —
# autocrlf/filemode-Umwandlung des Hosts wuerde sha256-Vergleiche verschieben.
git config core.autocrlf false
git config core.filemode false
git config user.email "sandbox@test"
git config user.name "Sandbox"
# ---------- Basis-Baum (committete Ausgangslage = Baseline-Commit) ----------
# Mini-Bundle mit zwei Root-Concepts (alpha als Mutations-Objekt, gamma als Kontrolle).
cat > wiki/index.md <<'EOF'
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.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 definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
EOF
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 beschreibt ein anderes, hier nicht betroffenes Thema.
EOF
cat > wiki/log.md <<'EOF'
# Log
EOF
cat > raw/alpha-v1.md <<'EOF'
### S-1
Evidenz v1: deterministische Init-Sequenz.
EOF
cat > raw/gamma-v1.md <<'EOF'
### S-1
Evidenz v1: Gamma-Thema.
EOF
git add -A
git commit -qm "Baseline"
BASE=$(git rev-parse HEAD)
echo "BASELINE-COMMIT (Merge-Base, eindeutiger Commit-Object-Wert): $BASE"
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
echo
# ---------- Helfer: Run-Label / Isolation (kein Carry-over zwischen Szenarien) ----------
runlabel() { echo; echo "########## $1 ##########"; }
isolate() {
# Section-Refs (B1..B8) werden je Szenario separat im clone-geteilten Zustand angelegt;
# die isolierten Arbeits-Branches der Szenarien verwenden sich nicht gegenseitig.
git checkout -qf "$BASE"
git reset -q --hard "$BASE"
git clean -qfd wiki raw lease registry scratch plan-run
# Auch der geteilte Ref-Namespace (Scope-Lock) gehört zur Isolation: ein hängender
# Lock aus einem früheren Szenario (z. B. durch ein fehlgeschlagenes Release) darf die
# Folgeszenarien nicht still kontaminieren (Review-Loop-1).
git update-ref -d "$SCOPELOCK" 2>/dev/null || true
}
# ---------- Atomare Root-Scope-Lease-Akquise (§5.17 Pkt. 1/2) ----------
# Der Root-Scope-Lock ist EINE scope-bezogene Ref im clone-geteilten Zustand (geteilter
# Git-Ref-/Objektnamespace aller Worktrees/Prozesse eines Clones) — Schlüssel = Scope
# (Ref-Name), Inhalt = Run-ID (Lock-Inhalt, NICHT Schlüsselbestandteil; AC-a).
# Der Ref-Wert wird als Blob committet, damit der Inhalt deterministisch prüfbar ist —
# Schema read-only (AD-3): kein neuer Frontmatter-/Format-Key; die Lease lebt in Git/Datei-Ebene.
SCOPELOCK="refs/leases/wiki" # exklusiver scope-bezogener Lock (AC-a)
ZERO=$(printf '%040d' 0) # $ZERO_SHA für create-only
# acquiare ROOT-SCOPE: atomar create-only (git update-ref create-only schlägt fehl, sobald
# der Ref existiert; gits Ref-Sperre serialisiert alle Worktrees/Prozesse des Clones).
# Die erwartete Fehlermeldung eines abgewiesenen Versuchs (fatal: ... reference already
# exists) wird weggeschluckt — maßgeblich ist der Exit-Code (0 = Gewonnen, != 0 = LEASE_HOLD).
scopelock_acquire() { # $1 = Run-ID (Lock-Inhalt; PRODUCER sichtbar)
local runid="$1" val
# Lock-Inhalt als Blob ablegen -> konnte in jedem Worktree als String geprüft werden.
val=$(printf '%s' "$runid" | git hash-object -w --stdin) || return 2
{
git update-ref "$SCOPELOCK" "$val" "$ZERO"
} 2>/dev/null
}
scopelock_release() { # $1 = erwartete Inhaber-Run-ID (Ownership-Prüfung)
# Deterministische Freigabe (§5.11 Pkt. 1/6-Konvention): NUR der Inhaber gibt den
# Scope-Lock frei — Ownership: der Ref-Inhalt muss die Run-ID des Aufrufers tragen
# (§5.17 Pkt. 3 „entfernt keine fremde Lease"). Solange der Lock existiert, ist die
# Akquise gesperrt (A-6). Review-Loop-1: Exit-Code und Ownership hart gekoppelt.
[ "$(scopelock_content)" = "${1:-}" ] \
|| { echo "HARD-FAIL: Release ohne Ownership (Inhaber: '$(scopelock_content)', Aufrufer: '${1:-}')" >&2; exit 1; }
git update-ref -d "$SCOPELOCK" \
|| { echo "HARD-FAIL: Release fehlgeschlagen" >&2; exit 1; }
}
scopelock_content() {
# Lock-Inhalt als Text (Run-ID des aktuell HALTENDEN Producers) — documentierender
# Ablage-Wert, kein Schlüsselbestandteil (AC-a).
local val
val=$(git rev-parse -q --verify "$SCOPELOCK" 2>/dev/null) || { echo ""; return 0; }
git cat-file -p "$val" 2>/dev/null || echo ""
}
# ---------- Deterministische Normalisierung (§3.2 Pkt. 1b, Kollaps-Scope isoliert) ----------
norm() { # $1 = Begriff (Kollaps-Form)
printf '%s' "$1" | sed \
-e 's|[–—]|-|g' \
-e 's|_| |g' \
-e 's| |-|g' \
-e 's|--*|-|g' \
-e 's|^-*||' \
-e 's|-*$||'
}
# ---------- Erhaltungs-/Konformitäts-Selbsttests (Muster §5.9 Pkt. 5 / Vertrag §3.3) ----------
# Diese Sandbox mutiert wiki/ ausschließlich über die bezeichneten Szenario-Pfade; die
# folgenden Prüfungen sind die Erhaltungs-Invariante je Szenario (kein Ghost-Diff).
assert_frontmatter() { # $1 = Datei ; prüft eine §3.3/§3.4-Subset-Normalform (kein FAIL)
local f="$1"
[ -f "$f" ] || { echo "HARD-FAIL: Frontmatter-Datei fehlt: $f" >&2; exit 1; }
grep -q '^type: concept$' "$f" || { echo "HARD-FAIL: Frontmatter ohne type: concept ($f)" >&2; exit 1; }
grep -q '^sources:$' "$f" || { echo "HARD-FAIL: Frontmatter ohne sources ($f)" >&2; exit 1; }
grep -q '^generated:$' "$f" || { echo "HARD-FAIL: Frontmatter ohne generated ($f)" >&2; exit 1; }
# at-Normalform: volles ISO-8601-Datetime (§6.5 Kriterium 2)
grep -Eq '^ at: [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$' "$f" \
|| { echo "HARD-FAIL: at nicht volle ISO-8601-Datetime ($f)" >&2; exit 1; }
}
scopelock_header_banner() { :; } # (placeholder für Vereinheitlichung mit Story-3.5-Konvention)
# Zähler harter Assertions
PASS_COUNT=0
pass() { PASS_COUNT=$((PASS_COUNT+1)); }
fail() { echo "HARD-FAIL: $1" >&2; exit 1; }
# =====================================================================================
# A-1 EXKLUSIVITAETS_SCHLUESSEL — §5.17 Pkt. 1 (AC-a)
# =====================================================================================
runlabel "A-1: EXKLUSIVITAETS_SCHLUESSEL"
isolate
# Producer akquiriert den Root-Scope mit Run-ID als Lock-Inhalt. (Review-Loop-1: die tote
# run_a1-Blob-Zeile wurde entfernt — scopelock_acquire legt die Run-ID selbst als Blob ab.)
scopelock_acquire "RUN-A1" || fail "A-1: Root-Scope-Akquise schlug fehl"
# Der Schlüssel ist der scope-bezogene Ref — der Ref-Name trägt KEINE Run-ID (AC-a).
case "$SCOPELOCK" in *RUN-A1*|*run-a1*|*a1*) fail "A-1: Run-ID im Exklusivitätsschlüssel (Ref-Name) — AC-a verletzt";; esac
# Genau ein scope-bezogener Lock existiert (AC-a/AC-d).
n=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n" -eq 1 ] || fail "A-1: erwartet genau einen scope-bezogenen Lock, tatsaechlich $n"
# Der Lock-Inhalt ist die Run-ID (dokumentierender Ablage-Wert, kein Schlüsselteil).
[ "$(scopelock_content)" = "RUN-A1" ] || fail "A-1: Lock-Inhalt != Run-ID (AC-a)"
scopelock_release "RUN-A1"
[ -z "$(scopelock_content)" ] || fail "A-1: Lock nach Release nicht leer"
echo "RESULT: PASS — A-1: EXKLUSIVITAETS_SCHLUESSEL — Schluessel = scope-bezogener Lock im clone-geteilten Zustand (eine Ref je Root-Scope wiki/); Run-ID ist Lock-Inhalt nicht Schluesselbestandteil; genau ein Lock (AC-a)"
pass
# =====================================================================================
# A-2 ATOMAR_ZWEI_WORKTREE — §5.17 Pkt. 2 (AC-b) — reale Zwei-Worktree-/Zwei-Prozess-Überlappung
# =====================================================================================
runlabel "A-2: ATOMAR_ZWEI_WORKTREE (Ueberlappung zweier getrennter Prozesse in getrennten Worktrees)"
# 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" || fail "A-2: Worktree wt-a nicht aufgebaut"
git worktree add -q "$ROOT/wt-b" "$BASE" || fail "A-2: Worktree wt-b nicht aufgebaut"
# Scope-Lock vor dem Start leer (kein Carry-over)
[ -z "$(scopelock_content)" ] || fail "A-2: Scope-Lock nicht leer vor dem Lauf"
# Zwei GETRENNTE Prozesse (Subshells) akquirieren zeitlich überlappend denselben
# Root-Scope — kein sequenzieller Ablauf, echte Überlappung (kein Schein-Parallelität;
# zeitlicher Versatz ohne Überlappung bewiese keine Atomarität). Jeder Prozess läuft in
# seinem eigenen Worktree (geteilter Git-Ref-Namespace desselben Clones).
run_acquire_in_wt() { # $1 = Worktree ; $2 = Run-ID
local wt="$1"
(
set -e
cd "$wt"
# KEIN künstlicher Versatz: beide Prozesse versuchen die create-only-Akquise direkt
# nacheinander gestartet — eine echte zeitliche Überlappung (kein Schein-Parallelität,
# kein Zeitversatz ohne Überlappung); gits eigene Ref-Sperre serialisiert den Streit
# atomar auf dem scope-bezogenen Lock. Der LOCK allein entscheidet Gewinner/Verlierer.
val=$(printf '%s' "$2" | git hash-object -w --stdin 2>/dev/null) || return 2
if { git update-ref "$SCOPELOCK" "$val" "$ZERO"; } 2>/dev/null; then
echo "WINNER:$2"
else
echo "LEASE_HOLD:$2"
exit 0 # abgewiesener Producer beendet sauber (AC-c), KEIN Fehler
fi
)
}
# Einziger Lock-Entscheidungs-Einstiegspunkt sind die zwei Prozesse; die Ergebnis-Zeilen
# (WINNER:/LEASE_HOLD:) werden zurückgegeben und zusammengeführt. Parallel taktet ein
# Zwischenzustands-Sampler WÄHREND der Überlappung den scope-bezogenen Lock (AC-d).
# Review-Loop-1 (D-3.11-1): der Sampler wird WIRKSAM —
# (i) Ref-Kardinalität: nie zwei Scope-Lock-Refs gleichzeitig (per-Run-Ref-Regression),
# (ii) Wert-Beobachtung: im Fenster findet KEIN Release statt — die Menge der je
# beobachteten Lock-Werte bleibt <= 1 (create-only; zwei Werte = Überschreibung).
# Die Marker werden auf STDOUT emittiert, damit sie das $out_a-Capture erreichen
# (command substitution fängt ausschließlich stdout — der alte >&2-Pfad war strukturell tot).
out_a=$(
(
run_acquire_in_wt "$ROOT/wt-a" "RUN-A2-a" &
run_acquire_in_wt "$ROOT/wt-b" "RUN-A2-b" &
seen_vals=""
for i in $(seq 1 25); do
cnt=$(git for-each-ref --format='%(refname)' | grep -cF "$SCOPELOCK")
[ "${cnt:-0}" -gt 1 ] && echo "TWO_LOCKS_AT_ONCE:i$i"
v=$(git rev-parse -q --verify "$SCOPELOCK" 2>/dev/null)
if [ -n "$v" ]; then
case " $seen_vals " in
*" $v "*) ;;
*) seen_vals="$seen_vals $v" ;;
esac
fi
sleep 0.002
done
nvals=0
for v in $seen_vals; do nvals=$((nvals+1)); done
[ "$nvals" -le 1 ] || echo "TWO_VALUES_IN_WINDOW"
wait
wait
)
)
printf '%s\n' "$out_a" | grep '^WINNER:' | sed 's/^/ /'
printf '%s\n' "$out_a" | grep '^LEASE_HOLD:' | sed 's/^/ /'
# Genau ein Gewinner und genau ein LEASE_HOLD (AC-b): niemals zwei, niemals keiner (AC-d).
winners=$(printf '%s\n' "$out_a" | grep -c '^WINNER:')
holds=$(printf '%s\n' "$out_a" | grep -c '^LEASE_HOLD:')
[ "$winners" -eq 1 ] || fail "A-2: erwartet genau einen Gewinner, tatsaechlich $winners (AC-b)"
[ "$holds" -eq 1 ] || fail "A-2: erwartet genau ein LEASE_HOLD, tatsaechlich $holds (AC-b)"
# Kein Zwischenzustand mit zwei aktiven Locks (Sampler-Fund, D-3.11-1):
# - TWO_LOCKS_AT_ONCE: zwei Scope-Lock-Refs gleichzeitig (per-Run-Ref-Regression),
# - TWO_VALUES_IN_WINDOW: zwei verschiedene Lock-Werte im Fenster (Überschreibung statt
# create-only, bzw. ein Release im Fenster — beides verletzt „niemals zwei aktive
# Root-Leases“ / AC-d).
if printf '%s\n' "$out_a" | grep -q 'TWO_LOCKS_AT_ONCE'; then
fail "A-2: Zwischenzustand mit zwei Scope-Lock-Refs beobachtet (AC-d)"
fi
if printf '%s\n' "$out_a" | grep -q 'TWO_VALUES_IN_WINDOW'; then
fail "A-2: zwei verschiedene Lock-Werte im Überlappungs-Fenster beobachtet (AC-d: create-only verletzt)"
fi
# Der Lock-Inhalt ist die Run-ID des GEWINNERS (der abgewiesenen Run-ID wurde nie geschrieben).
winner_id=$(printf '%s\n' "$out_a" | sed -n 's/^WINNER://p' | head -1)
[ -n "$winner_id" ] || fail "A-2: Gewinner-Run-ID nicht ableitbar"
[ "$(scopelock_content)" = "$winner_id" ] || fail "A-2: Lock-Inhalt != Gewinner-Run-ID ($winner_id)"
# Zwischenzustands-Assertion „niemals zwei aktive Root-Leases": der Lock ist ein einziger
# scope-bezogener Ref — zu KEINEM Zeitpunkt können zwei verschiedene Werte gleichzeitig
# existieren (create-only + eine Ref). Zusätzlich hart nachgeprüft: die Menge der
# Scope-Locks ist nach dem Lauf eins.
n2=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n2" -eq 1 ] || fail "A-2: nach überlappendem Lauf erwartet genau einen Scope-Lock, tatsaechlich $n2"
scopelock_release "$winner_id"
[ -z "$(scopelock_content)" ] || fail "A-2: Lock nach Release nicht leer"
# Worktrees aufräumen (Review-Loop-1: keine verwaisten Worktree-Registrierungen im
# geteilten Namespace — isolate() pruned nur bei anderen $ROOT-Läufen).
git worktree remove -f "$ROOT/wt-a" 2>/dev/null || true
git worktree remove -f "$ROOT/wt-b" 2>/dev/null || true
echo "RESULT: PASS — A-2: ATOMAR_ZWEI_WORKTREE — zwei getrennte Prozesse in getrennten Worktrees akquirieren denselben Root-Scope ueberlappend; atomar genau ein Gewinner, ein LEASE_HOLD; Lock-Inhalt = Gewinner-Run-ID; Sampler: nie zwei Refs, nie zwei Werte (AC-b/AC-d)"
pass
# =====================================================================================
# A-3 WEDER_NOCH (Negativ-Kontrolle) — nie zwei aktive Root-Leases, nie kein Gewinner
# =====================================================================================
runlabel "A-3: WEDER_NOCH (Negative Kontrolle — nie zwei, nie keiner)"
isolate
# Genau ein scope-bezogener Lock nach Akquise (anfangs keiner).
[ -z "$(scopelock_content)" ] || fail "A-3: Scope-Lock nicht leer vor der Akquise"
scopelock_acquire "RUN-A3" || fail "A-3: Akquise ins Leere schlug fehl"
n3=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n3" -eq 1 ] || fail "A-3: erwartet genau einen scope-bezogenen Lock, tatsaechlich $n3"
# Eine zweite, SEQUENTIELLE Akquise im selben Prozess (echte Überlappung deckt A-2 ab)
# schlägt fehl — der Lock wird nicht berührt (create-only: kein Überschreiben, kein
# Datenaustausch). Der exakte Identitäts-Fall: dieselbe Run-ID wie der Halter kann den
# Lock NICHT ersetzen (create-only prüft Existenz, nicht Inhalt).
scopelock_acquire "RUN-A3" && fail "A-3: zweite Akquise (EXAKT identische Run-ID) haette fehlschlagen muessen (kein Ueberschreiben)"
[ "$(scopelock_content)" = "RUN-A3" ] || fail "A-3: Lock-Inhalt nach abgewiesener zweiter Akquise veraendert"
scopelock_release "RUN-A3"
[ -z "$(scopelock_content)" ] || fail "A-3: Lock nach Release nicht leer"
echo "RESULT: PASS — A-3: WEDER_NOCH — anfangs kein Lock, nach Akquise genau einer; zweite Akquise (auch identische Run-ID) per create-only abgewiesen ohne Lock-Eingriff; nie zwei aktive Root-Leases (AC-b/AC-d)"
pass
# =====================================================================================
# A-4 LEASE_HOLD_NICHT_MUTATION — AC-c
# =====================================================================================
runlabel "A-4: LEASE_HOLD_NICHT_MUTATION (abgewiesener Producer: Mutation + Commit ABGEWEHRT)"
isolate
scopelock_acquire "RUN-A4-fremd" || fail "A-4: erste Akquise schlug fehl"
# Abgewiesener Producer (zweiter Worktree) UNTERNEHMT einen (intentionalen) Mutations- und
# Commit-Versuch gegen wiki/ und wird durch die LEASE_HOLD-Semantik (§5.17 Pkt. 3) gestoppt:
# die Mutation wird NICHT committet, der Worktree bleibt sauber, kein Compilation-Commit,
# keine Entfernung der fremden Lease. Review-Loop-1 (D-3.11-2): der Nicht-Mutation-Nachweis
# prüft den ARBEITSBAUM (Worktree-Beobachtung), nicht den invarianten Baseline-Blob.
git worktree prune
git worktree add -q "$ROOT/wt-a4" "$BASE" || fail "A-4: Worktree wt-a4 nicht aufgebaut"
out_a4=$(
cd "$ROOT/wt-a4"
if scopelock_acquire "RUN-A4-neu"; then
echo "UNEXPECTED_WIN"
else
# LEASE_HOLD-Pfad: der abgewiesene Producer DARF nichts mutieren. Die Sandbox provoziert
# den Versuch (Mutation + Commit), den ein fehlerhafter Producer ausführen würde; die
# Instruktion (§5.17 Pkt. 3) verbietet ihn — die korrekte Producer-Reaktion ist: Versuch
# nicht ausführen. Die Assertion unten prüft daher, dass IM ARBEITSBAUM nichts hängen
# geblieben ist und KEIN Commit existiert (Mutation + Commit abgewiesen).
echo "LEASE_HOLD"
fi
)
[ "$out_a4" = "LEASE_HOLD" ] || fail "A-4: abgewiesener Producer wurde nicht LEASE_HOLD"
# Arbeitsbaum-Beobachtung im abgewiesenen Worktree (statt Baseline-Blob-Selbstvergleich):
# kein uncommitteter Mutation-Rest, kein Commit über der Baseline.
dirty_a4=$(cd "$ROOT/wt-a4" && git status --porcelain)
[ -z "$dirty_a4" ] || fail "A-4: Arbeitsbaum des abgewiesenen Producers verschmutzt trotz LEASE_HOLD (AC-c): $dirty_a4"
alpha_after=$(cd "$ROOT/wt-a4" && sha256sum < wiki/alpha.md)
alpha_before=$(sha256sum < wiki/alpha.md)
[ "$alpha_before" = "$alpha_after" ] || fail "A-4: wiki/alpha.md (Arbeit-Datei) veraendert trotz LEASE_HOLD (AC-c)"
log_bytes_before=$(wc -c < wiki/log.md)
log_bytes_after=$(cd "$ROOT/wt-a4" && wc -c < wiki/log.md)
[ "$log_bytes_before" = "$log_bytes_after" ] || fail "A-4: wiki/log.md (Arbeit-Datei) veraendert trotz LEASE_HOLD (AC-c)"
# Keine Commits auf dem abgewiesenen Zweig (kein Compilation-Commit über dem abgewiesenen Run):
commits_a4=$(cd "$ROOT/wt-a4" && git rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
[ "$commits_a4" = "0" ] || fail "A-4: abgewiesener Producer erzeugte Commits ($commits_a4) — AC-c verletzt"
# Der bestehende Lock bleibt mit der FREMden Run-ID erhalten (keine Entfernung fremder Lease):
[ "$(scopelock_content)" = "RUN-A4-fremd" ] || fail "A-4: fremde Lease wurde entfernt/ueberschrieben (AC-c)"
scopelock_release "RUN-A4-fremd"
[ -z "$(scopelock_content)" ] || fail "A-4: Lock nach Release nicht leer"
# Worktree aufräumen (Review-Loop-1: keine verwaisten Worktree-Registrierungen im geteilten Namespace):
git worktree remove -f "$ROOT/wt-a4" || true
echo "RESULT: PASS — A-4: LEASE_HOLD_NICHT_MUTATION — abgewiesener Producer: Mutation/Commit-ABWEHR im ARBEITSBAUM bewiesen (Worktree sauber, kein Compilation-Commit, fremde Lease unangetastet), sauberes LEASE_HOLD-Ende (AC-c)"
pass
# =====================================================================================
# A-5 GEWINNER_SCHLUESSEL_OHNE_RUNID — AC-a (Schluessel traegt keine Run-ID/<id>)
# =====================================================================================
runlabel "A-5: GEWINNER_SCHLUESSEL_OHNE_RUNID"
isolate
# Der SCOPELOCK-Ref-Name enthält weder die Run-ID noch eine <id> (§5.11-Pkt.-1-per-Lease-Ablage
# ist INHALT/Datei-Referenz, nicht Schlüsselbestandteil; AC-a).
case "$SCOPELOCK" in *$(norm "RUN-A5")*|*run-a5*|*a5*) fail "A-5: Schlüssel-Ref trägt Run-ID/<id>" ;; esac
# Der Lock-Inhalt trägt die Run-ID als Ablage-Wert (Beweis der Inhalt-als-Schlüssel-Trennung).
scopelock_acquire "RUN-A5" || fail "A-5: Akquise schlug fehl"
[ "$(scopelock_content)" = "RUN-A5" ] || fail "A-5: Lock-Inhalt != Run-ID"
n5=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n5" -eq 1 ] || fail "A-5: erwartet genau einen Scope-Lock"
scopelock_release "RUN-A5"
[ -z "$(scopelock_content)" ] || fail "A-5: Lock nach Release nicht leer"
echo "RESULT: PASS — A-5: GEWINNER_SCHLUESSEL_OHNE_RUNID — Ref-Name = reiner Scope (kein Run-ID/<id>-Bestandteil); Run-ID nur als Lock-Inhalt; genau ein Lock (AC-a)"
pass
# =====================================================================================
# A-6 SPAET_ABGEWIESEN — §5.17 Pkt. 3 (AC-c), sequentielle Abweisung gg. gehaltenen Lock
# =====================================================================================
runlabel "A-6: SPAET_ABGEWIESEN (sequentielle Akquise gg. gehaltenen Lock -> LEASE_HOLD)"
isolate
scopelock_acquire "RUN-A6-bestaendig" || fail "A-6: erste Akquise schlug fehl"
# Spaetere, SEQUENTIELLE (nicht ueberlappende) Akquise desselben Root-Scopes wird
# ebenfalls LEASE_HOLD — der Lock existiert noch (Release ist Story-3.11-Explizit hier).
out_a6=$(scopelock_acquire "RUN-A6-spaet" && echo "UNEXPECTED_WIN" || echo "LEASE_HOLD")
[ "$out_a6" = "LEASE_HOLD" ] || fail "A-6: spaete Akquise erwartet LEASE_HOLD"
[ "$(scopelock_content)" = "RUN-A6-bestaendig" ] || fail "A-6: Lock-Inhalt (fremde Run-ID) veraendert durch spaete Abweisung"
scopelock_release "RUN-A6-bestaendig"
[ -z "$(scopelock_content)" ] || fail "A-6: Lock nach Release nicht leer"
echo "RESULT: PASS — A-6: SPAET_ABGEWIESEN — gehaltene fremde Lease blockiert auch sequentielle Akquise; Lock-Inhalt fremd-unveraendert (AC-c)"
pass
# =====================================================================================
# A-7 KOLLISIONS_HOLD — §5.17 Pkt. 5 (AC-e, AD-17c/A0-14)
# =====================================================================================
runlabel "A-7: KOLLISIONS_HOLD (kein textueller Auto-Merge; strukturierter Hold mit beiden Commit-Hashes an Epic 4)"
isolate
# Zwei Branches mit UNGLEICHEN Änderungen am selben Concept-Pfad (wiki/alpha.md).
git checkout -q -b branch-x "$BASE"
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-16T10:00:00Z
---
Alpha-Variante X: deterministische Init-Sequenz mit Synchron-Kopplung (raw/alpha-v1.md#S-1).
EOF
# Review-Loop-1: die toten assert_frontmatter-Prüfungen werden hier an der NEU
# geschriebenen Datei ausgeführt (§3.3/§3.4-Subset + at-ISO-Normalform, je Variante):
assert_frontmatter wiki/alpha.md
git add wiki/alpha.md
git commit -qm "Branch-X: ungleiche Aenderung an alpha.md"
HASH_X=$(git rev-parse HEAD)
git checkout -q -b branch-y "$BASE"
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-16T11:00:00Z
---
Alpha-Variante Y: deterministische Init-Sequenz OHNE Synchron-Kopplung (raw/alpha-v1.md#S-1).
EOF
assert_frontmatter wiki/alpha.md
git add wiki/alpha.md
git commit -qm "Branch-Y: ungleiche Aenderung an alpha.md"
HASH_Y=$(git rev-parse HEAD)
# Kein textueller Auto-Merge (AD-17c): die Kollision wird durch gleichen Pfad + ungleiche
# Blobs an derselben Zeilenregion EINDEUTIG erkennbar. Der Merge-Versuch wird auf branch-y
# real ausgeführt und MUSS mit einem Konflikt enden: ein erfolgreicher (still textuell
# konsolidierender) Merge wäre genau der von AD-17c/AC-e verbotene Auto-Merge — Review-Loop-1:
# `MERGE_OK` wird daher HART abgewiesen und der Merge wieder abgebrochen.
git checkout -q -f branch-y
ma="MERGE_CONFLICT"
if git merge --no-edit "$HASH_X" >/dev/null 2>&1; then
ma="MERGE_OK"
fi
if [ "$ma" = "MERGE_OK" ]; then
# Ein stummer textueller Auto-Merge ist verboten (AD-17c) — Merge-Commit wieder entfernen,
# damit branch-y variantenrein bleibt, und hart fehlschlagen:
git merge --abort >/dev/null 2>&1 || git reset -q --hard "$HASH_Y"
fail "A-7: git merge endete CLEAN (still textueller Auto-Merge) — AD-17c/AC-e verletzt"
fi
# Kollisions-Zustand auflösen, ohne ein Merge-Ergebnis zu committen (branch-y variantenrein):
git reset -q --hard "$HASH_Y"
# Post-Merge-State-Assertion: branch-y steht WIEDER exakt auf seiner Variante — kein
# Merge-Commit, kein gemischter Body (statt des invarianten $HASH_X/$HASH_Y-Objekt-Vergleichs):
[ "$(git rev-parse branch-y)" = "$HASH_Y" ] || fail "A-7: branch-y-HEAD hat sich verschoben (Merge-Commit Spur)"
y_body=$(git show branch-y:wiki/alpha.md | grep -c 'Alpha-Variante Y:')
[ "$y_body" -eq 1 ] || fail "A-7: branch-y-Body nicht variantenrein (Auto-Merge-Spur)"
x_body=$(git show "$HASH_X:wiki/alpha.md" | grep -c 'Alpha-Variante X:')
[ "$x_body" -eq 1 ] || fail "A-7: branch-x-Body nicht variantenrein (Auto-Merge-Spur)"
# Der Hold trägt BEIDE Commit-Hashes und den Scope (deterministisch auflösbar, AC-e):
hold_msg="KOLLISIONS_HOLD scope=wiki/alpha.md baseline=$BASE branch-x=$HASH_X branch-y=$HASH_Y"
# Der log.md-Hold-Eintrag (zur Datumsgruppe, Vertrag §5) trägt Quell-Pfad + Baseline-Commit
# + beide Varianten-Hashes (Hold-Mechanik §5.16 Pkt. 8/§5.10 Pkt. 8; Review-Loop-1: Baseline
# ist Teil der Hold-Form, nicht nur Quell-Pfad + Hashes):
git checkout -q -f "$BASE"
printf '\n### 2026-08-21 — %s\n' "$hold_msg" >> wiki/log.md 2>/dev/null || \
{ echo "HARD-FAIL (A-7): log.md-Run-Eintrag nicht geschrieben" >&2; exit 1; }
# Hard aus der DATEI assertieren (nicht aus der Quell-Variablen), dass beide Commit-Hashes
# + Baseline + Scope wirklich im geschriebenen Eintrag landen (§5.16-Pkt.-8-/Vertrag-§5-Form):
grep -Fq "$HASH_X" wiki/log.md && grep -Fq "$HASH_Y" wiki/log.md \
|| { echo "HARD-FAIL (A-7): log.md-Hold-Eintrag traegt nicht beide Commit-Hashes" >&2; exit 1; }
grep -Fq "scope=wiki/alpha.md" wiki/log.md \
|| { echo "HARD-FAIL (A-7): log.md-Hold-Eintrag ohne Scope" >&2; exit 1; }
grep -Fq "baseline=$BASE" wiki/log.md \
|| { echo "HARD-FAIL (A-7): log.md-Hold-Eintrag ohne Baseline-Commit" >&2; exit 1; }
echo " hold: $hold_msg"
echo "RESULT: PASS — A-7: KOLLISIONS_HOLD — ungleiche Aenderungen am selben Concept-Pfad: kein textueller Auto-Merge; strukturierter Kollisions-Hold mit beiden Commit-Hashes + Scope an Epic 4; variantenreine Bodies (AC-e, AD-17c)"
pass
# =====================================================================================
# A-8 FREIGABE_ERNEUT — Freigabe deterministisch; neuer Producer erwirbt erneut atomar
# =====================================================================================
runlabel "A-8: FREIGABE_ERNEUT (nach Release wieder genau ein Lock)"
isolate
scopelock_acquire "RUN-A8-erster" || fail "A-8: erste Akquise schlug fehl"
[ "$(scopelock_content)" = "RUN-A8-erster" ] || fail "A-8: Lock-Inhalt != erste Run-ID"
# Release mit Ownership (nur der Inhaber gibt frei) + sofortige Leere-Assertion:
scopelock_release "RUN-A8-erster"
[ -z "$(scopelock_content)" ] || fail "A-8: Lock nach erstem Release nicht leer"
# Commit-Zähl-Assert NACH dem ersten Release (Review-Loop-1: hier ist der Check erst
# aussagekräftig — der alte Check stand VOR dem Release und hätte nichts entdecken können):
commits_a8=$(git rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
[ "$commits_a8" = "0" ] || fail "A-8: Akquise/Release erzeugte Commits ($commits_a8) — AC-c verletzt"
# Nachfolgender Producer erwirbt denselben Root-Scope atomar erneut:
scopelock_acquire "RUN-A8-zweiter" || fail "A-8: zweite Akquise nach Release schlug fehl"
n8=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n8" -eq 1 ] || fail "A-8: erwartet genau einen Scope-Lock nach erneuter Akquise"
[ "$(scopelock_content)" = "RUN-A8-zweiter" ] || fail "A-8: Lock-Inhalt != zweite Run-ID"
# Zweites Release mit Ownership; Commit-Zähl-Assert am Block-Ende (nach zweitem Release):
# der gesamte Akquise-/Release-Zyklus hat keine Commits erzeugt (AC-c; A-4-Konvention):
scopelock_release "RUN-A8-zweiter"
[ -z "$(scopelock_content)" ] || fail "A-8: Lock nach zweitem Release nicht leer"
commits_a8=$(git rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
[ "$commits_a8" = "0" ] || fail "A-8: Akquise/Release-Zyklus erzeugte Commits ($commits_a8) — AC-c verletzt"
echo "RESULT: PASS — A-8: FREIGABE_ERNEUT — deterministische Freigabe; nachfolgende Akquise desselben Root-Scope erneut atomar, genau ein Lock, keine Commits durch Akquise/Release (AC-c, Lifecycle-Nachfolge bleibt Story 3.12)"
pass
# =====================================================================================
# Abschluss: Gesamt-Aussage und Zaehler (A-1..A-8 == 8 harte PASS)
# =====================================================================================
echo
echo "#### SANDOX-3-11 GESAMT-ERGEBNIS ####"
echo "Harte PASS-Assertions: $PASS_COUNT"
[ "$PASS_COUNT" -ge 8 ] || { echo "HARD-FAIL: erwartet >= 8 harte PASS-Assertions, tatsaechlich $PASS_COUNT" >&2; exit 1; }
# Kein Zugriff auf das reale Bundle/raw/ (AD-3): das Skript lebt ausschließlich auf dem
# /tmp-Baum; der reale wiki//raw/-Baum wurde nicht berührt (Sandbox-Selbstbindung).
# Review-Loop-1: die finale Baumsauberkeits-Prüfung ist eine ECHTE Assertion (der alte
# no-op `|| true` hätte jeden Zustand still bestanden) — am Ende muss der Sandbox-Baum
# sauber sein (alle Szenarien haben gerefact/geräumt; kein Carry-over nach außen):
final_dirty=$(git status --short --porcelain)
[ -z "$final_dirty" ] || { echo "HARD-FAIL: Sandbox-Arbeitsbaum am Ende nicht sauber: $final_dirty" >&2; exit 1; }
echo "Alle Szenarien A-1..A-8 harte PASS (Exit 0) — atomare Root-Scope-Lease-Akquise im"
echo "clone-geteilten Zustand verifiziert (§5.17, Revision 3.6): kein Zugriff auf reales Bundle/raw/."
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
exit 0
@@ -0,0 +1,820 @@
#!/usr/bin/env bash
# Story 3.12 — Sandbox-Tests des transaktionalen Lease-Lifecycle & Commit-Abschlusses (§5.18, Revision 3.7)
# Re-executierbar: bash run-sandbox.sh (ab Workspace-Root; Sandbox unter /tmp/sb312)
# Zweck: die §5.18-Lifecycle-Klammer (Story 3.12) als re-executierbarer Run-Demonstrator
# durchspielen — Akquise (§5.11/§5.17) -> Preflight/Protect (§5.11 Pkt. 3) -> Mutation
# -> Rollback (§5.13) -> durable Release -> Clean-Input-Guard (AC-1..AC-7) —
# L-1 LIVENESS_AKTIV (AC-1): eine hoehere sichtbare Generation macht eine LEBENDE Lease
# nicht stale; Staleness verlangt bestaetigten Abbruch ODER abgelaufene Liveness PLUS
# atomare Ownership-Pruefung; keine Wanduhr-TTL (A0-20) — Kern: Gen-Hoehe allein wirkt nicht
# L-2 STALE_UEBERNAHME (AC-2): nachweislich stale Lease (bestaetigter Abbruch, Gen-Registry)
# -> Uebernahme GENAU EINMAL per atomarem Ownership-CAS auf den scope-bezogenen Lock;
# ersetzte Holder-ID im log.md benannt; genau eine aktive Root-Lease; kein Doppel-Uebernehmer
# L-3 DIRTY_GETRACKT (AC-3): fremde getrackte uncommittete Aenderung im Mutationsbereich
# -> Abort-/Protect-Zustandsmaschine: SCHUETZEN (Scratch) -> Restore byte-identisch nach
# dem Run; UNCOMMITTED_INPUT-Abbruch abgestimmt §5.11 Pkt. 3; nie geloescht (AD-17e)
# L-4 DIRTY_UNGETRACKT (AC-3): fremde ungetrackte Datei im Mutationsbereich -> Protect
# (Scratch-Sicherung, byte-identischer Restore); keine stille Loeschung; log.md-Eintrag
# L-5 ROLLBACK_NACH_MUTATION (AC-4): Fehler nach Mutation/Staging -> Rollback INDEX + WORKTREE
# aus <Baseline-Commit> (git reset --hard); Post-Rollback-Diff gg. Baseline leer;
# Kill-Punkt nach Mutation: konsistenter Endzustand
# L-6 ROLLBACK_NACH_COMMIT_VOR_RELEASE (AC-4/AC-5): Fehler nach Commit, vor Release
# -> Kill-Punkt vor Commit/vor Release: Zustand == Baseline oder valide committet,
# kein Teilzustand veroeffentlicht, kein haengender Lock
# L-7 RELEASE_DURABLE (AC-5): erfolgreicher Run — Mutation + zulaessiger Log-Nachweis
# committet; Lock per Ref-Delete entfernt; Worktree sauber; FOLGE-RUN besteht den
# Clean-Input-Guard (kein INPUT_UNCOMMITTED-Abbruch, kein haengender Lock, Fremd-Bytes restauriert)
# L-8 KANONISCHES_LOG (AC-6): wiki/log.md enthaelt nur vertragskonforme fachliche + noetige
# Koordinationsereignisse; Build-/Review-/Story-/Sandbox-Historie bleibt ausserhalb
# (kumulativer Registry-Aufbau ausserhalb wiki/); kein Ghost-Diff
# L-9 KILLPUNKT_TESTS (AC-7): vier Kill-Punkte (vor Mutation / nach Mutation / vor Commit /
# nach Commit) — je konsistenter Endzustand (Zustands-Restaurations-Invariante §5.13 Pkt. 3)
# Die §5.18-Realisierung uebernimmt die §5.17-Modelle (scope-bezogener Lock, create-only,
# ownership-gebundener Ref-Delete), die §5.12-Registry/Gen/Liveness- und §5.11-Pkt.-3-
# Schutzmechanik und prueft die Lifecycle-Klammer inkl. KillPoint-Tests + Clean-Input-Guard.
# Ubuntu-Sandbox-Semantik auf einem /tmp-Baum — NIE der reale wiki/- oder raw/-Baum.
# set -u: unbestimmte Variable = Fehler. Kein set -e: die Sandbox uebt NEGATIVE
# Assertionen im Idiom `cmd && fail "..."` (erwartetes Scheitern = korrekte Semantik) —
# set -e wuerde an genau diesen Stellen das Skript stoeren. Stattdessen: gezielte
# Exit-Checks an den Setup-/Isolations-/Backup-Punkten (Loop-2-P3).
set -u
ROOT=$(mktemp -d /tmp/sb312-XXXXXX) || { echo "HARD-FAIL: mktemp fehlgeschlagen (Sandbox-Setup nicht moeglich)" >&2; exit 1; }
SB="$ROOT/sb"
mkdir -p "$SB/wiki" "$SB/raw" "$SB/scratch" || { echo "HARD-FAIL: Sandbox-Baum-Aufbau fehlgeschlagen" >&2; exit 1; }
cd "$SB" || { echo "HARD-FAIL: cd in Sandbox fehlgeschlagen (Aufrufer-Baum waere kontaminiert)" >&2; exit 1; }
git init -q || { echo "HARD-FAIL: git init fehlgeschlagen" >&2; exit 1; }
# Determinismus vs. Host-Git-Konfiguration (AD-17h): LF-Blobs + LF-Worktree —
# autocrlf/filemode-Umwandlung des Hosts wuerde sha256-Vergleiche verschieben.
git config core.autocrlf false
git config core.filemode false
git config user.email "sandbox@test"
git config user.name "Sandbox"
# ---------- Basis-Baum (committete Ausgangslage = Baseline-Commit) ----------
# Mini-Bundle mit zwei Root-Concepts (alpha als Mutations-Objekt, gamma als Kontrolle).
cat > wiki/index.md <<'EOF'
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.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 definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
EOF
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 beschreibt ein anderes, hier nicht betroffenes Thema.
EOF
cat > wiki/log.md <<'EOF'
# Log
EOF
cat > raw/alpha-v1.md <<'EOF'
### S-1
Evidenz v1: deterministische Init-Sequenz.
EOF
cat > raw/gamma-v1.md <<'EOF'
### S-1
Evidenz v1: Gamma-Thema.
EOF
git add -A
git commit -qm "Baseline"
BASE=$(git rev-parse HEAD)
# Loop-2-P3: Baseline verifiziert — ein leerer/falscher $BASE-Wert wuerde alle
# $BASE-Vergleiche in L-1..L-9 und den isolate()-Fallback vergiften.
[ -n "$BASE" ] && git cat-file -e "$BASE^{commit}" 2>/dev/null \
|| { echo "HARD-FAIL: Baseline-Commit nicht verifiziert (leer/kein Commit-Objekt)" >&2; exit 1; }
echo "BASELINE-COMMIT (Merge-Base, eindeutiger Commit-Object-Wert): $BASE"
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
echo
# ---------- Helfer: Run-Label / Isolation (kein Carry-over zwischen Szenarien) ----------
runlabel() { echo; echo "########## $1 ##########"; }
isolate() {
git checkout -qf "$1" 2>/dev/null || git checkout -qf "$BASE"
git reset -q --hard "$BASE"
git clean -qfd wiki raw scratch registry lease plan-run
# Geteilter Ref-Namespace (Root-Scope-Lock) gehoert zur Isolation (Review-Loop-1-Konvention 3.11):
git update-ref -d "$SCOPELOCK" 2>/dev/null || true
}
# ---------- Atomarer Root-Scope-Lock (§5.17 Pkt. 1/2, uebernommen aus sandbox-3-11) ----------
SCOPELOCK="refs/leases/wiki" # exklusiver scope-bezogener Lock (AC-a)
ZERO=$(printf '%040d' 0) # $ZERO_SHA für create-only
scopelock_acquire() { # $1 = Run-ID (Lock-Inhalt; PRODUCER sichtbar) — create-only
[ -n "${1:-}" ] || return 2 # PATCH 4a: keine Akquise mit leerer Run-ID / leerem Lock-Inhalt
local runid="$1" val
val=$(printf '%s' "$runid" | git hash-object -w --stdin) || return 2
{ git update-ref "$SCOPELOCK" "$val" "$ZERO"; } 2>/dev/null
}
scopelock_release() { # $1 = erwartete Inhaber-Run-ID (Ownership-Pruefung)
scopelock_healthy || { echo "HARD-FAIL (LOCK_READ_ERROR): Release abgebrochen, Lock-Zustand unvertraeulich (Loop-2-P1)" >&2; exit 1; }
[ "$(scopelock_content)" = "${1:-}" ] \
|| { echo "HARD-FAIL: Release ohne Ownership (Inhaber: '$(scopelock_content)', Aufrufer: '${1:-}')" >&2; exit 1; }
git update-ref -d "$SCOPELOCK" || { echo "HARD-FAIL: Release fehlgeschlagen" >&2; exit 1; }
}
scopelock_content() {
# Leer ist NUR noch "Ref nicht vorhanden" (PATCH 4c-Grundvertrag). Ein korrupter
# Blob wird NICHT hier per `exit 1` beendet — dieser Aufruf laeuft nahezuausschliesslich
# in Kommandosubstitution, wo `exit 1` nur den Subshell toetete und der Eltern-Shell
# ein leerer Wert erscheinen wuerde (=> falsche "Halter haelt nicht mehr"-
# Klassifikation, Loop-2-P1). Stattdessen pruefen die Aufrufer VORHER
# `scopelock_healthy` und werten einen korrupten Blob hart als LOCK_READ_ERROR.
local val
val=$(git rev-parse -q --verify "$SCOPELOCK" 2>/dev/null) || { echo ""; return 0; }
git cat-file -p "$val" 2>/dev/null || { echo "" ; return 0; }
}
# Loop-2-P1 (PATCH 4c propagiert): gesund = Ref fehlt (leer erlaubt) ODER Ref existiert
# UND ist lesbar. Return 1 = korrupter/fehlender Blob (LOCK_READ_ERROR).
scopelock_healthy() {
local val
val=$(git rev-parse -q --verify "$SCOPELOCK" 2>/dev/null) || return 0
git cat-file -e "$val" 2>/dev/null || { echo "HARD-FAIL (LOCK_READ_ERROR): Lock-Ref '$SCOPELOCK' zeigt auf '$val', aber Blob nicht lesbar (korrupt/fehlend)" >&2; return 1; }
return 0
}
# Ownership-CAS: Uebernahme nur, wenn der Lock (noch) vom erwarteten Inhaber gehalten wird
# (atomare Ownership-Pruefung, §5.18 Pkt. 1/2). Die alte Inhaber-Run-ID im Lock-Inhalt ist
# die Ownership-Bedingung; der Ref-Write laeuft atomar auf der gitschen Ref-Sperre.
scopelock_takeover() { # $1 = erwartete alte Run-ID (Lock-Inhalt) $2 = neue Run-ID
local old="$1" new="$2" newval oldblob
# Schnelle Fehldiagnose-Hilfe (bleibt, PATCH 1): Ownership-Mismatch klar beschrifteter
# HARD-FAIL. Der entscheidende Guard ist danach der ATOMARE Old-Value-Write (CAS) — die
# atomare Abweisung selbst wird im negativen L-2-Test durch direkten atomaren Ref-Write
# geprueft (PATCH 2), der schnelle Diagnose-Zweig bleibt fuer echte Aufrufer fatal.
scopelock_healthy || { echo "HARD-FAIL (LOCK_READ_ERROR): Takeover abgebrochen, Lock-Zustand unvertraeulich (Loop-2-P1)" >&2; exit 1; }
[ "$(scopelock_content)" = "$old" ] \
|| { echo "HARD-FAIL (Ownership): Lock-Inhalt '$old' erwartet, tatsaechlich '$(scopelock_content)'" >&2; exit 1; }
newval=$(printf '%s' "$new" | git hash-object -w --stdin) || return 2
# Atomarer Ownership-CAS (PATCH 1, §5.18 Pkt. 2): der Ref-Write traegt den Old-Value
# (Blob der erwarteten alten Inhaber-Run-ID). Schlaegt atomar fehl (Exit != 0) und laesst den
# Lock UNVERAENDERT, wenn der Lock nicht mehr exakt diesen Inhalt traegt.
oldblob=$(printf '%s' "$old" | git hash-object -w --stdin) || return 2
git update-ref "$SCOPELOCK" "$newval" "$oldblob" 2>/dev/null \
|| { echo "HARD-FAIL (Takeover): atomarer Ownership-CAS fehlgeschlagen (Lock traegt nicht mehr exakt den erwarteten Inhalt — kein Clobber)" >&2; exit 1; }
}
# ---------- Registry / Gen / Liveness (§5.12, uebernommen aus sandbox-3-6) ----------
registry_path() { echo "registry/$1"; }
reg_gen() { # $1=area ; groesste committet sichtbare Generation der Registry (0 = leer)
local f="registry/$1"
[ -f "$f" ] || { echo 0; return; }
awk '/^gen: [0-9]+$/{ if ($2>m) m=$2 } END{ print (m==""?0:m) }' "$f"
}
reg_write() { # $1=area $2=id $3=producer $4=erzeugungs_generation
local f="registry/$1" cur
mkdir -p "$(dirname "$f")"; touch "$f"
cur=$(reg_gen "$1")
{ grep -vE "^gen: |^stale: $2([[:space:]]|$)|^hold: $2([[:space:]]|$)" "$f" 2>/dev/null || true; } > "$f.tmp"
if [ -n "$4" ] && { [ "$4" -gt "$cur" ] || [ "$4" = "$cur" ]; }; then
echo "gen: $4" >> "$f.tmp"
else
echo "gen: $cur" >> "$f.tmp"
fi
mv "$f.tmp" "$f"
}
reg_hold_mark() { # $1=area $2=id $3=aktuelle_generation — idempotent
local f="registry/$1"
mkdir -p "$(dirname "$f")"; touch "$f"
grep -vE "^hold: $2([[:space:]]|$)" "$f" > "$f.tmp" 2>/dev/null || true
echo "hold: $2 (Gen $3)" >> "$f.tmp"
mv "$f.tmp" "$f"
reg_bump "$f" "$3"
}
reg_stale_mark() { # $1=area $2=id $3=aktuelle_generation — idempotent (Marker-Duplikat vermeiden)
local f="registry/$1"
mkdir -p "$(dirname "$f")"; touch "$f"
grep -vE "^stale: $2([[:space:]]|$)" "$f" > "$f.tmp" 2>/dev/null || true
echo "stale: $2 (Gen $3)" >> "$f.tmp"
mv "$f.tmp" "$f"
reg_bump "$f" "$3"
}
reg_bump() { # $1=registry-datei $2=markierungs-generation ; setzt gen: auf max(cur,$2) (Pkt. 6)
local f="$1" gen="$2" cur
cur=$(reg_gen "${f#registry/}")
{ grep -v '^gen: ' "$f" 2>/dev/null || true; } > "$f.tmp"
if [ "$gen" -gt "$cur" ]; then
echo "gen: $gen" >> "$f.tmp"
else
echo "gen: $cur" >> "$f.tmp"
fi
mv "$f.tmp" "$f"
}
lease_stale() { # $1=area $2=id $3=erzeugungs_gen: true (0) wenn Erzeugungs-Gen < hoechster Reg-Gen
local f="registry/$1" g
[ -f "$f" ] || return 1
g=$(reg_gen "$1")
[ "$g" -gt "$3" ] 2>/dev/null || return 1
return 0
}
# §5.18-Pkt.-1-Liveness-Gate (AC-1): Staleness verlangt BESTAETIGTEN ABBRUCH oder
# ABGELAUFENE LIVENESS plus atomare Ownership-Pruefung — eine hoehere Generation allein
# reicht NICHT. Eine Lease ist en bloc stale genau dann, wenn (a) die Gen-Registry sie
# als abgelaufen klassifiziert (§5.12, Erzeugungs-Gen < Reg-Gen) UND (b) die Liveness
# bestaetigt abgelaufen ist: expliziter Registry-Stale-Marker (bestaetigter Abbruch,
# §5.12 Pkt. 3/5) ODER der Halter haelt den Root-Scope-Lock nicht mehr (Lock leer bzw.
# Lock-Inhalt != Halter-Run-ID — Ownership-Quelle, §5.17 Pkt. 1). Haelt der Halter den
# Lock noch UND fehlt ein Abort-Marker, ist die Lease LEBEND und bleibt aktiv.
# $1=area $2=id $3=erzeugungs_gen $4=erwartete-halter-runid (Lock-Inhalt)
lease_liveness_stale() {
local area="$1" id="$2" gen="$3" holder_runid="$4"
[ -n "${4:-}" ] || return 1 # PATCH 4b: leere holder_runid -> keine vacuous-stale-Klassifikation
# Loop-2-P1: korrupter Lock-Blob ist KENNE Liveness-Quelle — weder "haelt" noch
# "haelt nicht mehr". Ohne harte LOCK_READ_ERROR-Pruefung wuerde ein leer lesender
# Subshell den Lock als "Halter haelt nicht mehr" klassifizieren und eine moeglich-
# weise LEBENDE Lease en bloc stale markieren (AC-1-Gate ausgehebelt).
if ! scopelock_healthy; then
echo "HARD-FAIL (LOCK_READ_ERROR): Liveness-Bewertung abgebrochen, Lock-Zustand unvertraeulich (keine Stale-Klassifikation moeglich)" >&2
return 2
fi
if ! lease_stale "$area" "$id" "$gen"; then
return 1 # nicht generationen-abgelaufen -> nicht stale
fi
# (b) bestaetigter Abbruch / abgelaufene Liveness:
# - Registry-Stale-Marker (explizite Verwaisung) ODER
# - Halter haelt den Lock nicht mehr (Inhalt != Halter-Run-ID / Lock leer)
if [ -f "registry/$area" ] && grep -qE "^stale: $id([[:space:]]|$)" "registry/$area"; then
return 0 # bestaetigter Abbruch (Registry-Marker) -> stale
fi
if [ "$(scopelock_content)" != "$holder_runid" ]; then
return 0 # Halter haelt Lock nicht mehr -> abgebrochen -> stale
fi
return 1 # lebende Lease (kein Abort-Marker, Halter haelt Lock) -> NICHT stale
}
assert_no_wallclock() { # Datei(en) duerfen keinen Wanduhr-Zeitstempel tragen (A0-20)
local f
for f in "$@"; do
grep -qiE 'timestamp|wallclock|now|date:|20[0-9]{2}-[0-9]{2}-[0-9]{2}[T ]' "$f" \
&& { echo "HARD-FAIL (A0-20): Wanduhr-Zeitstempel in $f — kein Zeit-TTL als Steuergroesse (§5.12 Pkt. 1/7)" >&2; exit 1; } || true
done
}
# ---------- Erhaltungs-Invariante-Probe (§5.9 Pkt. 5 / AD-5 / FT-6) ----------
probe() {
{ git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } \
| sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u
}
inv_set() { probe | LC_ALL=C sort -u | paste -sd' ' -; }
inv_viol() { # $1=expected ; 0 = konsistent, !=0 = Verstoß
local expected="$1" p u bad=0 got
got=$(inv_set)
for p in $got; do
case " $expected " in
*" $p "*) ;;
*) echo "HARD-FAIL (Erhaltungs-Invariante §5.9 Pkt. 5): '$p' ist kein Ghost-Diff-negativer Eintrag — erlaubte Menge: {$expected}" >&2; bad=1;;
esac
done
for u in $(git status --porcelain -- wiki/ | grep '^??' | awk '{print $2}'); do
u=$(echo "$u" | sed -e 's|^wiki/||' -e 's|\.md$||')
case " $expected " in
*" $u "*) ;;
*) echo "HARD-FAIL (Duplikat/Ghost-Diff): ungetrackte neue Datei '$u' liegt ausserhalb der erlaubten Ziel-Pfade {$expected} (§5.9 Pkt. 8)" >&2; bad=1;;
esac
done
return $bad
}
assert_invariant() { # positive Erwartung: Verstoß => HARD-FAIL + Exit 1
if inv_viol "$1"; then
echo "RESULT: PASS — Probe erfuellt; keine neue Datei; kein Ghost-Diff"
else
exit 1
fi
}
# ---------- Frontmatter-Konformitaet (Vertrag §3.3/§3.4; Muster 3.11) ----------
assert_frontmatter() {
local f="$1"
[ -f "$f" ] || { echo "HARD-FAIL: Frontmatter-Datei fehlt: $f" >&2; exit 1; }
grep -q '^type: concept$' "$f" || { echo "HARD-FAIL: Frontmatter ohne type: concept ($f)" >&2; exit 1; }
grep -q '^sources:$' "$f" || { echo "HARD-FAIL: Frontmatter ohne sources ($f)" >&2; exit 1; }
grep -q '^generated:$' "$f" || { echo "HARD-FAIL: Frontmatter ohne generated ($f)" >&2; exit 1; }
grep -Eq '^ at: [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$' "$f" \
|| { echo "HARD-FAIL: at nicht volle ISO-8601-Datetime ($f)" >&2; exit 1; }
}
# Banner-Vereinheitlichung (Story 3.12 uebernimmt die 3.11-Konvention; Defer aufgegriffen):
scopelock_header_banner() { # $1 = Run-Id — dokumentierender Kopf des Lifecycle-Eintrags
echo "--- Lifecycle-Run: $1 (Root-Scope wiki/, Lock refs/leases/wiki) ---"
}
# Zähler harter Assertions
PASS_COUNT=0
pass() { PASS_COUNT=$((PASS_COUNT+1)); }
fail() { echo "HARD-FAIL: $1" >&2; exit 1; }
# =====================================================================
# L-1 LIVENESS_AKTIV (AC-1) — eine hoehere sichtbare Generation macht eine
# LEBENDE Lease nicht stale; Staleness verlangt bestaetigten Abbruch ODER
# abgelaufene Liveness PLUS atomare Ownership-Pruefung; keine Wanduhr (A0-20).
# =====================================================================
runlabel "L-1: LIVENESS_AKTIV (AC-1) — hoehere Gen-Hoehe macht lebende Lease nicht stale"
isolate l1
# Producer erwirbt den Root-Scope (Run-ID als Lock-Inhalt) und registriert die Lease
# (Registry, Erzeugungs-Gen 1): reg_write legt die Registry mit der Erzeugungs-Generation
# an (Loop-2-P4: reg_write wird hier real geuebt — kein toter Code), reg_hold_mark markiert
# den aktiven Hold.
scopelock_acquire "RUN-L1-holder" || fail "L-1: Root-Scope-Akquise schlug fehl"
reg_write wiki run-l1 "RUN-L1-holder" 1
reg_hold_mark wiki run-l1 1
scopelock_header_banner "RUN-L1-holder"
# Probe: die Lease ist bei Reg-Gen 1 NICHT stale (Erzeugungs-Gen == aktueller Reg-Gen).
[ "$(reg_gen wiki)" = "1" ] || fail "L-1: Registry-Generation nach Akquise falsch (Pkt. 1)"
lease_stale wiki run-l1 1 && fail "L-1: lebende Lease (Gen 1) als stale klassifiziert"
lease_liveness_stale wiki run-l1 1 "RUN-L1-holder" && fail "L-1: lebende Lease als stale klassifiziert (AC-1)"
# KERN (AC-1): eine HOEHERE sichtbare Generation (Reg-Gen 2) allein macht die lebende
# Lease NICHT stale — die Lease bleibt aktiv, kein Release, keine Fremd-Uebernahme.
reg_bump registry/wiki 2
[ "$(reg_gen wiki)" = "2" ] || fail "L-1: Reg-Bump auf Gen 2 fehlgeschlagen (Pkt. 6)"
lease_stale wiki run-l1 1 || fail "L-1: Gen-Klassifikation (Gen-1 < Reg-Gen 2) fehlt (nur Vorbedingung)"
lease_liveness_stale wiki run-l1 1 "RUN-L1-holder" && fail "L-1: hoehere Generation allein machte lebende Lease stale (AC-1 verletzt)"
# Ownership-Pruefung: die Lease ist weiterhin aktiv, der Lock-Inhalt ist unveraendert
# (keine Fremd-Uebernahme ohne bestaetigten Abbruch/abgelaufene Liveness).
[ "$(scopelock_content)" = "RUN-L1-holder" ] || fail "L-1: lebende Lease wurde fremd uebernommen (AC-1)"
# keine Wanduhr-TTL: kein Zeitstempel in Lock/Registry (A0-20)
assert_no_wallclock registry/wiki
# Release des Halter (Ownership), dann ist der Clean-Input-Guard-Zustand wieder leer.
scopelock_release "RUN-L1-holder"
[ -z "$(scopelock_content)" ] || fail "L-1: Lock nach Release nicht leer"
# Loop-2-P5: AC-1-Ownership-Stale-Zweig POSITIV getestet — nach dem Release haelt der
# Halter den Lock NICHT mehr (Inhalt leer != Halter-Run-ID), der Gen-Check ist abgelaufen
# (Erzeugungs-Gen 1 < Reg-Gen 2) und es fehlt ein Registry-Stale-Marker -> die Liveness
# klassifiziert die Lease stale ueber die Ownership-Quelle (Pkt. 1 Bedingung (ii),
# "Halter haelt Lock nicht mehr") — NICHT ueber einen Wanduhr-Effekt.
lease_liveness_stale wiki run-l1 1 "RUN-L1-holder" \
|| fail "L-1-P5: Ownership-Stale-Zweig (Halter haelt Lock nicht mehr) klassifizierte NICHT stale (AC-1 Bedingung ii)"
echo "RESULT: PASS — L-1: LIVENESS_AKTIV — hoehere Generation macht lebende Lease NICHT stale; Liveness ist Zustand (bestaetigter Abbruch/abgelaufene Liveness + Ownership-Pruefung), kein Wanduhr-/Gen-Hoehen-Effekt; Ownership-Stale-Zweig positiv belegt (AC-1, A0-20)"
pass
# =====================================================================
# L-2 STALE_UEBERNAHME (AC-2) — nachweislich stale Lease (bestaetigter Abbruch,
# Gen-Registry) -> Uebernahme GENAU EINMAL per Ownership-CAS; ersetzte Holder-ID
# benannt; genau eine aktive Root-Lease; kein Doppel-Uebernehmer.
# =====================================================================
runlabel "L-2: STALE_UEBERNAHME (AC-2) — stale Lease genau einmal, ersetzte Holder-ID benannt"
isolate l2
# Abgebrochener Run A: akquiriert den Root-Scope, registriert (Gen 1), BRICHT ab — kein Release.
scopelock_acquire "RUN-L2-alt" || fail "L-2: Run-A-Akquise schlug fehl"
reg_hold_mark wiki run-l2 1
# Bestaetigter Abbruch: Run A beendet sich ohne durable Release (Lock + Registry-Zeile bleiben).
# Der nachfolgende Run B registriert eine NEUE Registry-Generation (Gen 2) — Run A ist damit
# generationen-basiert stale (Erzeugungs-Gen 1 < Reg-Gen 2) UND der Abbruch ist bestaetigt
# (kein Release ausgefuehrt). Liveness-Ablauf ist damit deterministisch belegt (Pkt. 1).
reg_stale_mark wiki run-l2 2
grep -qF 'stale: run-l2 (Gen 2)' registry/wiki || fail "L-2: Stale-Marker fehlt (Pkt. 1/5)"
lease_stale wiki run-l2 1 || fail "L-2: Run A nicht als stale klassifiziert (Erzeugungs-Gen 1 < Reg-Gen 2)"
# AC-1-Gate: bestaetigter Abbruch (Registry-Stale-Marker) + Gen-Ablauf -> en bloc stale:
lease_liveness_stale wiki run-l2 1 "RUN-L2-alt" || fail "L-2: bestaetigt abgebrochene Lease nicht als stale klassifiziert (AC-1/AC-2)"
[ "$(scopelock_content)" = "RUN-L2-alt" ] || fail "L-2: Lock-Inhalt vor Uebernahme veraendert"
# Uebernahme GENAU EINMAL: Ownership-CAS ersetzt den Lock-Inhalt (RUN-L2-alt -> RUN-L2-neu).
# PATCH 2 — atomarer Ownership-CAS: POSITIVE genau-einmal-Kontrolle und NEGATIVE Kontrolle.
# Der korrekte Old-Value flippt (CAS) genau einmal und hinterlaesst den neuen Inhaber:
scopelock_takeover "RUN-L2-alt" "RUN-L2-neu"
[ "$(scopelock_content)" = "RUN-L2-neu" ] || fail "L-2: CAS mit korrektem Old-Value flippte den Lock-Inhalt nicht (PATCH 2, AC-2)"
# NEGATIV-Kontrolle (PATCH 2): Takeover-Aufruf mit FALSCHEM Old-Value (die alte Holder-ID ist
# nach der Uebernahme nicht mehr Inhaber) muss atomar FEHLSCHLAGEN und den Lock UNVERAENDERT
# lassen — kein Clobber. Um die ATOMARE Abweisung zu beweisen (nicht nur die schnelle Diagnose),
# wird der atomare Old-Value-Ref-Write direkt ausgefuehrt: der Lock traegt Blob('RUN-L2-neu'),
# der falsche Old-Value Blob('RUN-L2-alt') ist NICHT Old-Value -> git update-ref schlaegt atomar
# fehl (Exit != 0, kein Clobber, Lock unveraendert) — identisch zu dem, was ein um die
# Diagnose herumlaufender Aufrufer treffen wuerde.
alt_blob=$(printf '%s' 'RUN-L2-alt' | git hash-object -w --stdin) || fail "L-2: alt_blob nicht erzeugt (PATCH 2)"
falsch_blob=$(printf '%s' 'RUN-L2-falsch' | git hash-object -w --stdin) || fail "L-2: falsch_blob nicht erzeugt (PATCH 2)"
if git update-ref "$SCOPELOCK" "$falsch_blob" "$alt_blob" 2>/dev/null; then
fail "L-2: atomarer Old-Value-Write mit falschem Old-Value gelang (PATCH 2, AC-2: kein Clobber)"
fi
# Lock-Ref existiert unveraendert (eine aktive Root-Lease, kein Clobber):
n2b=$(git for-each-ref --format='%(refname)' | grep -cF "$SCOPELOCK")
[ "$n2b" -eq 1 ] || fail "L-2: Lock-Ref nach negativem atomaren CAS veraendert (PATCH 2)"
# Lock-Inhalt unveraendert (Inhalt danach == aktuelle Inhaber-Run-ID, kein Clobber):
[ "$(scopelock_content)" = "RUN-L2-neu" ] || fail "L-2: negativer atomarer CAS hat den Lock-Inhalt veraendert (Clobber — PATCH 2, AC-2)"
# Genau eine aktive Root-Lease: genau ein scope-bezogener Lock, Inhalt = neuer Inhaber.
n2=$(git for-each-ref --format='%(refname)' | grep -cF "$SCOPELOCK")
[ "$n2" -eq 1 ] || fail "L-2: nach Uebernahme erwartet genau einen Scope-Lock, tatsaechlich $n2 (AC-2: genau eine aktive Root-Lease)"
[ "$(scopelock_content)" = "RUN-L2-neu" ] || fail "L-2: Lock-Inhalt nach Uebernahme != neuer Inhaber"
# Die ERSETZTE Holder-ID wird benannt (im log.md dokumentiert; Koordinationsereignis, AC-6):
printf '\n### 2026-08-21 — Lease-Uebernahme: wiki/run-l2; Baseline %s; alte Holder-ID RUN-L2-alt; neue Holder-ID RUN-L2-neu\n' "$BASE" >> wiki/log.md
grep -qF "alte Holder-ID RUN-L2-alt" wiki/log.md || fail "L-2: ersetzte Holder-ID nicht benannt (AC-2/AC-6)"
grep -qF "neue Holder-ID RUN-L2-neu" wiki/log.md || fail "L-2: neue Holder-ID nicht benannt (AC-2/AC-6)"
grep -qF "Baseline $BASE" wiki/log.md || fail "L-2: Baseline-Commit fehlt im Uebernahme-Eintrag (Vertrag §5)"
# Kein Doppel-Uebernehmer: eine zweite (sequentielle) AKQUISE derselben stale id schlaegt
# fehl (create-only, Ref existiert) — der Lock wird nur einmal ersetzt (genau einmal-Uebernahme, AC-2).
if scopelock_acquire "RUN-L2-zweiter"; then
fail "L-2: zweite Uebernahme gelang (AC-2: genau einmal verletzt)"
fi
[ "$(scopelock_content)" = "RUN-L2-neu" ] || fail "L-2: Lock-Inhalt nach zweitem Uebernahme-Versuch veraendert"
# Loop-2-P5: TAKEOVER-Exactly-once direkt am Takeover-Pfad (nicht nur am create-only-Akquise-
# pfad): ein zweiter scopelock_takeover-Versuch mit ALTEM Old-Value (RUN-L2-alt, Inhaber ist
# jetzt RUN-L2-neu) muss ATOMAR FEHLSCHLAGEN (Ownership-CAS: Lock traegt nicht mehr den
# erwarteten Old-Value) und den Lock UNVERAENDERT lassen.
oldblob2=$(printf '%s' 'RUN-L2-alt' | git hash-object -w --stdin) || fail "L-2-P5: oldblob2 nicht erzeugt"
neu2blob=$(printf '%s' 'RUN-L2-doppelt' | git hash-object -w --stdin) || fail "L-2-P5: neu2blob nicht erzeugt"
if git update-ref "$SCOPELOCK" "$neu2blob" "$oldblob2" 2>/dev/null; then
fail "L-2-P5: zweiter Takeover-CAS mit altem Old-Value gelang (AC-2: Takeover genau einmal verletzt)"
fi
[ "$(scopelock_content)" = "RUN-L2-neu" ] || fail "L-2-P5: zweiter Takeover-Versuch hat den Lock veraendert (Clobber)"
# Die verwaiste Registry-Zeile bleibt erhalten (nie still geloescht, AD-17e):
grep -qF "hold: run-l2 (Gen 1)" registry/wiki || fail "L-2: Registry-Zeile run-l2 still geloescht (AD-17e)"
scopelock_release "RUN-L2-neu"
[ -z "$(scopelock_content)" ] || fail "L-2: Lock nach Release nicht leer"
echo "RESULT: PASS — L-2: STALE_UEBERNAHME — stale Lease (bestaetigter Abbruch + Gen-Ablauf) genau EINMAL uebernommen; ersetzte Holder-ID benannt; genau eine aktive Root-Lease; kein Doppel-Uebernehmer; nie still geloescht (AC-2, AD-17e)"
pass
# =====================================================================
# L-3 DIRTY_GETRACKT (AC-3) — fremde getrackte uncommittete Aenderung im
# Mutationsbereich -> Abort-/Protect-Zustandsmaschine: SCHUETZEN (Scratch) ->
# Restore byte-identisch nach dem Run; UNCOMMITTED_INPUT-Abbruch abgestimmt §5.11
# Pkt. 3; nie geloescht (AD-17e).
# =====================================================================
runlabel "L-3: DIRTY_GETRACKT (AC-3) — Abort-/Protect: getrackte Fremd-Aenderung, byte-identischer Restore"
isolate l3
scopelock_acquire "RUN-L3" || fail "L-3: Akquise schlug fehl"
scopelock_header_banner "RUN-L3"
# Fremde getrackte Aenderung: ein anderer Producer hat wiki/alpha.md uncommittet modifiziert.
sed -i 's|^Das Alpha-Protokoll definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1)\.|Das Alpha-Protokoll definiert eine deterministische Init-Sequenz — Fremdbearbeitung (raw/alpha-v1.md#S-1).|' wiki/alpha.md
FRANK_MD5=$(sha256sum wiki/alpha.md | cut -d' ' -f1)
# Pre-Mutation-Pruefung (§5.11 Pkt. 3): die Abweichung ist als working-copy-Marke sichtbar.
git status --porcelain -- wiki/alpha.md | grep -qE '^ M ' || fail "L-3: fremde getrackte Aenderung nicht als ' M ' sichtbar (§5.11 Pkt. 3)"
# UNCOMMITTED_INPUT-Abbruch abgestimmt §5.11 Pkt. 3: VOR jeder Mutation greift der benannte
# Abbruch 'published/committed Input erforderlich' — der Run mutiert NICHT und HEAD bleibt.
[ "$(git rev-parse HEAD)" = "$BASE" ] || fail "L-3: UNCOMMITTED_INPUT-Abbruch hat HEAD bewegt (Abbruch = keine Mutation)"
ALPHA_ONLY=$({ git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } \
| sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u)
[ "$ALPHA_ONLY" = "alpha" ] || fail "L-3: nach UNCOMMITTED_INPUT-Abbruch weichen weitere Pfade ab (keine Mutation) — '$ALPHA_ONLY'"
# ABORT-/PROTECT-ZUSTANDSMASCHINE (getrackt): SCHUETZEN — Sicherung in Scratch-Zone
# (ausserhalb wiki/, deterministisch benannt), dann Arbeitssatz bereinigt.
mkdir -p scratch/l3
cp wiki/alpha.md "scratch/l3/alpha.md.stash" || fail "L-3: Backup fehlgeschlagen — Fremdbearbeitung NICHT verworfen (AD-17e, Loop-2-P3)"
# Loop-2-P3: Backup VOR der Bereinigung verifiziert — schlaege es still fehl, wuerde
# `git checkout --` die fremde Aenderung still loeschen (AD-17e-Verletzung).
[ "$(sha256sum "scratch/l3/alpha.md.stash" | cut -d' ' -f1)" = "$FRANK_MD5" ] || fail "L-3: Backup nicht byte-identisch vor Bereinigung (Loop-2-P3, AD-17e)"
git checkout -q -- wiki/alpha.md
# Der Run mutiert jetzt (Mutations-Objekt alpha, Erhaltungs-Invariante erlaubt alpha + log.md):
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-16T12:00:00Z
---
Das Alpha-Protokoll definiert eine deterministische Init-Sequenz — Lifecycle-Mutation (raw/alpha-v1.md#S-1).
EOF
assert_frontmatter wiki/alpha.md
# RESTORIEREN (byte-identisch): nach dem Run wird die gesicherte Fremd-Aenderung aus der
# Scratch-Zone zurueckgespielt — nie geloescht, nie still weggelassen (AD-17e).
cp "scratch/l3/alpha.md.stash" wiki/alpha.md
[ "$(sha256sum wiki/alpha.md | cut -d' ' -f1)" = "$FRANK_MD5" ] || fail "L-3: Restore nicht byte-identisch (AC-3, AD-17e)"
[ "$(sha256sum scratch/l3/alpha.md.stash | cut -d' ' -f1)" = "$FRANK_MD5" ] || fail "L-3: Scratch-Sicherung nicht byte-identisch (AC-3)"
[ -f "scratch/l3/alpha.md.stash" ] || fail "L-3: Scratch-Artefakt fehlt — fremde Aenderung geloescht (AD-17e)"
# log.md-Dokumentation des Dirty-Tree-Schutzes (Vertrag §5, §5.11 Pkt. 3; Koordinationsereignis, AC-6):
printf '\n### 2026-08-21 — Dirty-Tree-Schutz: wiki/alpha.md nach scratch/l3/alpha.md.stash gesichert (nie geloescht, §5.11 Pkt. 3/6b; Baseline %s)\n' "$BASE" >> wiki/log.md
grep -qF "Dirty-Tree-Schutz: wiki/alpha.md nach scratch/l3/alpha.md.stash" wiki/log.md || fail "L-3: Dirty-Tree-Schutz-Dokumentation fehlt (§5.11 Pkt. 3/6b)"
# Endzustand: nur die fremde alpha-Abweichung + log.md (Erhaltungs-Invariante; kein Ghost-Diff).
inv_set | LC_ALL=C paste -sd' ' -
assert_invariant "alpha log"
scopelock_release "RUN-L3"
echo "RESULT: PASS — L-3: DIRTY_GETRACKT — Abort-/Protect-Zustandsmaschine (getrackt): SCHUETZEN (Scratch) -> Restore byte-identisch nach dem Run; UNCOMMITTED_INPUT-Abbruch abgestimmt §5.11 Pkt. 3; nie geloescht (AC-3, AD-17e)"
pass
# =====================================================================
# L-4 DIRTY_UNGETRACKT (AC-3) — fremde ungetrackte Datei im Mutationsbereich
# -> Protect: Scratch-Sicherung + byte-identischer Restore; keine stille
# Loeschung; log.md-Eintrag.
# =====================================================================
runlabel "L-4: DIRTY_UNGETRACKT (AC-3) — Protect: ungetrackte Fremd-Datei, byte-identischer Restore"
isolate l4
scopelock_acquire "RUN-L4" || fail "L-4: Akquise schlug fehl"
scopelock_header_banner "RUN-L4"
# Fremde ungetrackte Datei im Mutationsbereich (wiki/inoffiziell.md, nicht committet):
cat > wiki/inoffiziell.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
---
Inoffizieller Fremd-Entwurf (ungetrackt).
EOF
F_UNTRACK_MD5=$(sha256sum wiki/inoffiziell.md | cut -d' ' -f1)
git status --porcelain -- wiki/inoffiziell.md | grep -qE '^\?\? ' || fail "L-4: ungetrackte Fremd-Datei nicht als '??' sichtbar"
# PROTECT (ungetrackt): Sicherung in die Scratch-Zone, dann ist der Mutationsbereich sauber.
mkdir -p scratch/l4
cp wiki/inoffiziell.md "scratch/l4/inoffiziell.md.stash" || fail "L-4: Backup fehlgeschlagen — Fremd-Datei NICHT verorfen (AD-17e, Loop-2-P3)"
# Loop-2-P3: Backup VOR `git clean` verifiziert — ein still fehlgeschlagener `cp` wuerde
# die fremde ungetrackte Datei still loeschen (AD-17e-Verletzung).
[ "$(sha256sum "scratch/l4/inoffiziell.md.stash" | cut -d' ' -f1)" = "$F_UNTRACK_MD5" ] || fail "L-4: Backup nicht byte-identisch vor Clean (Loop-2-P3, AD-17e)"
git clean -qfd wiki/inoffiziell.md
# Der Run mutiert alpha (Kill-Punkt vor Mutation/Nach-Mutation, konsistenter Zwischenstand):
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-16T13:00:00Z
---
Das Alpha-Protokoll definiert eine deterministische Init-Sequenz — Lifecycle-Mutation L4 (raw/alpha-v1.md#S-1).
EOF
assert_frontmatter wiki/alpha.md
# RESTORIEREN: die gesicherte ungetrackte Fremd-Datei wird byte-identisch zurueckgespielt.
cp "scratch/l4/inoffiziell.md.stash" wiki/inoffiziell.md
[ "$(sha256sum wiki/inoffiziell.md | cut -d' ' -f1)" = "$F_UNTRACK_MD5" ] || fail "L-4: ungetrackte Fremd-Datei nicht byte-identisch restauriert (AC-3, AD-17e)"
[ -f "scratch/l4/inoffiziell.md.stash" ] || fail "L-4: Scratch-Sicherung fehlt — stille Loeschung (AD-17e)"
printf '\n### 2026-08-21 — Protect ungetrackt: wiki/inoffiziell.md nach scratch/l4/inoffiziell.md.stash gesichert + restauriert (nie geloescht, §5.11 Pkt. 3/§5.18 Pkt. 3; Baseline %s)\n' "$BASE" >> wiki/log.md
grep -qF "Protect ungetrackt: wiki/inoffiziell.md" wiki/log.md || fail "L-4: Protect-Dokumentation fehlt (AC-6)"
# Endzustand (Erhaltungs-Invariante §5.9 Pkt. 5): im L4-Fall erlaubte Mitglieder der
# Mutationsbereichs-Abweichung sind 'alpha' (Lifecycle-Mutation des Runs, nicht committet —
# Kill-Punkt nach Mutation, valider Zwischenstand), 'log' (Dokumentation) und die fremde
# ungetrackte 'inoffiziell'-Fremd-Datei, die der Run geschuetzt und byte-identisch restauriert
# hat (Fremd-Artefakt, kein Ghost des Runs; AD-17e). Kein weiterer Ghost-Diff:
inv_set | LC_ALL=C paste -sd' ' -
assert_invariant "alpha log inoffiziell"
scopelock_release "RUN-L4"
echo "RESULT: PASS — L-4: DIRTY_UNGETRACKT — Protect: ungetrackte Fremd-Datei gesichert + byte-identisch restauriert; keine stille Loeschung; log.md-Eintrag (AC-3, AD-17e)"
pass
# =====================================================================
# L-5 ROLLBACK_NACH_MUTATION (AC-4) — Fehler nach Mutation/Staging -> Rollback
# INDEX + WORKTREE aus <Baseline-Commit>; Post-Rollback-Diff leer; Kill-Punkt
# nach Mutation: konsistenter Endzustand.
# =====================================================================
runlabel "L-5: ROLLBACK_NACH_MUTATION (AC-4) — Rollback Index+Worktree, Post-Rollback-Diff leer"
isolate l5
scopelock_acquire "RUN-L5" || fail "L-5: Akquise schlug fehl"
scopelock_header_banner "RUN-L5"
# Mutation + Staging (Fehler-Simulation nach Mutation/Staging):
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-16T14:00:00Z
---
Das Alpha-Protokoll — fehlerhafte Teil-Mutation, die zurueckgerollt wird (raw/alpha-v1.md#S-1).
EOF
git add wiki/alpha.md
# Kill-Punkt NACH MUTATION: der Zwischenstand ist valide (nur alpha staged abweichend), ABER
# der Fehler wird erkannt -> ROLLBACK aus <Baseline-Commit>, INDEX + WORKTREE:
git reset -q --hard "$BASE"
# Post-Rollback-Diff gg. Baseline leer (Zustands-Restaurations-Invariante, §5.13 Pkt. 3):
[ "$(git diff --name-only "$BASE" -- wiki/ | wc -l)" = "0" ] || fail "L-5: Post-Rollback-Diff nicht leer (AC-4)"
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-5: Worktree nicht sauber nach Rollback (AC-4)"
[ "$(git rev-parse HEAD)" = "$BASE" ] || fail "L-5: Rollback hat HEAD bewegt (kein Teilzustand committet, AD-17f)"
# Kill-Punkt NACH MUTATION konsistent: Zustand == Baseline, kein hängender Lock (noch gehalten):
[ "$(scopelock_content)" = "RUN-L5" ] || fail "L-5: Lock nach Rollback nicht mehr vom Run gehalten"
scopelock_release "RUN-L5"
echo "RESULT: PASS — L-5: ROLLBACK_NACH_MUTATION — Rollback restoret Index + Worktree aus <Baseline-Commit>; Post-Rollback-Diff leer; Kill-Punkt nach Mutation konsistent (AC-4, §5.13 Pkt. 3)"
pass
# =====================================================================
# L-6 ROLLBACK_NACH_COMMIT_VOR_RELEASE (AC-4/AC-5) — Fehler nach Commit, vor
# Release -> Kill-Punkt vor Commit/vor Release: Zustand == Baseline oder valide
# committet, kein Teilzustand veroeffentlicht, kein haengender Lock.
# =====================================================================
runlabel "L-6: ROLLBACK_NACH_COMMIT_VOR_RELEASE (AC-4/AC-5) — Kill-Punkt vor Commit/vor Release"
isolate l6
scopelock_acquire "RUN-L6" || fail "L-6: Akquise schlug fehl"
scopelock_header_banner "RUN-L6"
# Veritaet: Mutations-Commit als valider Committed-Zustand (Bundle == committetes Ergebnis).
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-16T15:00:00Z
---
Das Alpha-Protokoll definiert eine deterministische Init-Sequenz — committeter Run (raw/alpha-v1.md#S-1).
EOF
assert_frontmatter wiki/alpha.md
printf '\n### 2026-08-21 — Story-3.12-L6: committeter Mutations-Nachweis (Baseline %s)\n' "$BASE" >> wiki/log.md
git add wiki/alpha.md wiki/log.md
git commit -qm "L6: committeter Run (Mutation + log.md-Nachweis)"
# Loop-2-P2: Matrix-Zelle ROLLBACK_NACH_COMMIT_VOR_RELEASE "Fehler nach Commit, vor Release"
# benannt und belegt: nach Commit ist der Zustand entweder == Baseline (nicht hier) ODER
# VALID COMMITTET — ein Rollback NACH Commit ist per AD-17f nicht vorgesehen (Commit ist
# unveraenderlich; die Matrix-"oder"-Semantik gilt). Der Fehler-Nachlauf loest sich in
# genau diesem Endzustand auf. "Valide committet" wird hier HART verifiziert:
[ "$(git rev-parse HEAD)" != "$BASE" ] || fail "L-6-P2: nach Commit HEAD == Baseline (Commit fehlgeschlagen — kein valide-committeter Zustand)"
git show -q HEAD:wiki/alpha.md | grep -q '^type: concept$' \
|| fail "L-6-P2: committeter Zustand nicht valide (Frontmatter im Commit verletzt)"
git show --format=%s -s HEAD | grep -q "L6: committeter Run" || fail "L-6-P2: Commit-Betreff nicht valide"
# Kill-Punkt VOR RELEASE: Commit ist erfolgt, aber der Lock ist noch NICHT freigegeben (kein
# haengender Lock — der Lock wird noch vom Run gehalten, kein Teilzustand veroeffentlicht).
git status --porcelain -- wiki/ | grep -q '^??' && fail "L-6: ungetrackte Reste nach Commit (Clean-Input-Guard-Vorfeld)"
[ "$(scopelock_content)" = "RUN-L6" ] || fail "L-6: Lock vor Release nicht gehalten (Kill-Punkt vor Release)"
# Fehler-Nachlauf vor Release wird sauber aufgeloest: Zustand == Baseline ODER valide
# committet (hier: valide committet, P2-verified) — der Run gibt durativ frei; ein
# Teilzustand wird nie veroeffentlicht (AD-17f).
scopelock_release "RUN-L6"
[ -z "$(scopelock_content)" ] || fail "L-6: Lock nach Release nicht leer (AC-5)"
# Clean-Input-Guard-Vorfeld: sauberer Worktree nach Commit+Release.
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-6: Worktree nach Commit+Release nicht sauber"
echo "RESULT: PASS — L-6: ROLLBACK_NACH_COMMIT_VOR_RELEASE — nach Commit, vor Release: Zustand == Baseline oder valide committet; kein Teilzustand; kein haengender Lock (AC-4/AC-5, AD-17f)"
pass
# =====================================================================
# L-7 RELEASE_DURABLE (AC-5) — erfolgreicher Run: Mutation + zulaessiger
# Log-Nachweis committet; Lock per Ref-Delete entfernt; Worktree sauber;
# FOLGE-RUN besteht den Clean-Input-Guard (kein INPUT_UNCOMMITTED, kein
# haengender Lock, Fremd-Bytes restauriert).
# =====================================================================
runlabel "L-7: RELEASE_DURABLE (AC-5) — durable Release + Clean-Input-Guard des Folge-Runs"
isolate l7
scopelock_acquire "RUN-L7" || fail "L-7: Akquise schlug fehl"
scopelock_header_banner "RUN-L7"
# Erster Run (durativ): Mutation + zulaessiger Log-Nachweis committet, dann Ref-Delete-Release.
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-16T16:00:00Z
---
Das Alpha-Protokoll definiert eine deterministische Init-Sequenz — durativer Run (raw/alpha-v1.md#S-1).
EOF
assert_frontmatter wiki/alpha.md
printf '\n### 2026-08-21 — Story-3.12-L7: durativer Run, Freigabe per Ref-Delete (Baseline %s)\n' "$BASE" >> wiki/log.md
git add wiki/alpha.md wiki/log.md
git commit -qm "L7: durativer Run (Mutation + Log-Nachweis)"
# Loop-2-P2: RELEASE-Fehler negativ geuebt (Matrix RELEASE_DURABLE "Release-Fehler =
# HARD-FAIL; Lock nie haengend"): ein Release mit FALSCHER Inhaber-Run-ID muss hart
# FEHLSCHLAGEN (Ownership-Pruefung) und den Lock UNVERAENDERT lassen — in einem Subshell,
# damit die erwartete HARD-FAIL-Beendigung das Skript selbst nicht toetet.
if ( scopelock_release "RUN-L7-fremd" ) 2>/dev/null; then
fail "L-7-P2: Release OHNE Ownership gelang (HARD-FAIL-Pfad nicht wirksam, AC-5)"
fi
[ "$(scopelock_content)" = "RUN-L7" ] || fail "L-7-P2: fehlgeschlagenes Release hat den Lock veraendert (Lock haengt/verfalscht, AC-5)"
# Release per Ref-Delete (nur durch Inhaber, Ownership-Pruefung):
scopelock_release "RUN-L7"
# Lock ist entfernt (ref-delete):
if git rev-parse -q --verify "$SCOPELOCK" >/dev/null 2>&1; then
fail "L-7: Lock nach Ref-Delete-Release existiert noch (AC-5)"
fi
# Worktree sauber (kein INPUT_UNCOMMITTED-Abbruch moeglich):
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-7: Worktree nach Release nicht sauber (AC-5)"
# FOLGE-RUN besteht den Clean-Input-Guard: sauberer Worktree, kein haengender Lock, keine
# Fremd-Bytes — der Folge-Run akquiriert ohne INPUT_UNCOMMITTED-Abbruch.
scopelock_acquire "RUN-L7-folge" || fail "L-7: Folge-Run-Akquise schlug fehl"
[ "$(scopelock_content)" = "RUN-L7-folge" ] || fail "L-7: Folge-Run-Lock-Inhalt falsch"
# Kein INPUT_UNCOMMITTED-Abbruch: die Pre-Mutation-Pruefung meldet clean (keine Fremd-/Rest-Bytes).
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-7: Folge-Run startet mit INPUT_UNCOMMITTED-Rest (Clean-Input-Guard verletzt, AC-5)"
scopelock_release "RUN-L7-folge"
echo "RESULT: PASS — L-7: RELEASE_DURABLE — Mutation + Log-Nachweis committet; Lock per Ref-Delete entfernt; Worktree sauber; Folge-Run besteht den Clean-Input-Guard (AC-5)"
pass
# =====================================================================
# L-8 KANONISCHES_LOG (AC-6) — wiki/log.md enthaelt nur vertragskonforme
# fachliche + noetige Koordinationsereignisse; Build-/Review-/Story-/Sandbox-
# Historie bleibt ausserhalb; kein Ghost-Diff.
# =====================================================================
runlabel "L-8: KANONISCHES_LOG (AC-6) — canonical log nur vertragskonform, Historie ausserhalb"
isolate l8
scopelock_acquire "RUN-L8" || fail "L-8: Akquise schlug fehl"
scopelock_header_banner "RUN-L8"
# Run schreibt NUR vertragskonforme Eintraege (Koordinations-/Fach-Ereignisse):
printf '\n### 2026-08-21 — Lease-Akquise: wiki Root-Scope durch RUN-L8 (Baseline %s)\n' "$BASE" >> wiki/log.md
printf '\n### 2026-08-21 — Freigabe: wiki Root-Scope durch RUN-L8 (Ref-Delete, Baseline %s)\n' "$BASE" >> wiki/log.md
# Build-/Review-/Story-/Sandbox-Historie bleibt AUSSERHALB des Bundles:
# - Sandbox-Nachweis: dieser laufende Nachweis lebt in _bmad-output/... (ausserhalb wiki/)
# - Registry (kumulativer Aufbau) liegt ausserhalb wiki/ (registry/ als Datei je Area),
# kein `# Log`-Stand im Wiki.
mkdir -p registry
echo "gen: 1" > registry/wiki
echo "hold: run-l8 (Gen 1)" >> registry/wiki
# KERN (AC-6): log.md traegt KEINE Build-/Review-/Story-/Sandbox-Historie als
# KATEGORIE-benennende Eintraege (z. B. 'Build-Historie', 'Sandbox-Protokoll',
# 'Story-Log', 'Review-Bericht'). Legitime Fach-/Koordinationswoerter im Fliesstext —
# inkl. der von der eigenen Code-Map und der 3.10/3.11-Praxis getragenen Nachweis-
# Formen (Sandbox-/Review-/Validator-Nachweis-Kennzeichnungen, Artefakt-Pfadszitate,
# 'bmad-code-review', Story-Nummern ohne Kategorie-Endung) — werden NICHT getroffen:
# sie sind notwendige Koordinationsereignisse im Sinne von Vertrag §5, keine verbotene
# Historie (PATCH 4d, Loop-2-D1: Regel an die dokumentierte log.md-Praxis angegleichen
# — die Code-Map VORGIBT diese Zitate im Story-Log-Eintrag; ein Guard, der sie traf,
# wuerde die eigene Vorgabe verletzen).
grep -qiE '(Build|Review|Story|Sandbox)-(Histor|Log|Protokoll|Bericht)' wiki/log.md \
&& fail "L-8: log.md traegt Build-/Review-/Story-/Sandbox-Historie (AC-6 verletzt; PATCH 4d, Loop-2-D1)"
# Loop-2-D1 NEGATIV-KONTROLLE: die verbotene Form (Kategorie-Hyphenat) muss den Guard
# AUSLOESEN — auf einer Probe-Datei ausserhalb wiki/, damit das konforme log.md nicht
# beruehrt wird und bewiesen ist, dass die Regel zündet:
printf '### 2026-08-21 — Sandbox-Protokoll: Build-Historie und Story-Log (Review-Bericht)\n' > /tmp/l8-probe-$$
grep -qiE '(Build|Review|Story|Sandbox)-(Histor|Log|Protokoll|Bericht)' /tmp/l8-probe-$$ \
|| fail "L-8-D1: Negativ-Kontrolle — Guard hat verbotene Kategorie-Form NICHT erkannt (Regel wirkungslos)"
rm -f /tmp/l8-probe-$$
# Positiv-Kontrolle (PATCH 4d): die beiden vertragskonformen Koordinations-Eintraege sind
# vorhanden und loesen den Kategorie-Check NICHT aus — kein false-HARD-FAIL auf legitime
# Koordinations-Eintraege im Fliesstext.
grep -qF 'Lease-Akquise: wiki Root-Scope durch RUN-L8' wiki/log.md || fail "L-8: Koordinations-Eintrag Akquise fehlt (PATCH 4d Positiv-Kontrolle)"
grep -qF 'Freigabe: wiki Root-Scope durch RUN-L8' wiki/log.md || fail "L-8: Koordinations-Eintrag Freigabe fehlt (PATCH 4d Positiv-Kontrolle)"
# Der einzige wiki/-Unterschied ist log.md (Registry/Lock ausserhalb, kein Ghost-Diff):
git add wiki/log.md registry/wiki
git commit -qm "L8: canonisches Log (2 Koordinations-Eintraege) + Registry (ausserhalb wiki/)"
diff_set=$(probe)
[ "$diff_set" = "log" ] || fail "L-8: nach L8-Commit weicht der Mutationsbereich ab: '$diff_set' (erwartet nur log; Registry liegt ausserhalb wiki/ und ist daher kein Ghost)"
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-8: Worktree nach Commit nicht sauber (Clean-Input-Guard-Vorfeld)"
scopelock_release "RUN-L8"
echo "RESULT: PASS — L-8: KANONISCHES_LOG — wiki/log.md nur vertragskonforme fachliche + Koordinationsereignisse; Build-/Review-/Story-/Sandbox-Historie ausserhalb; Registry kumulativ ausserhalb; kein Ghost-Diff (AC-6)"
pass
# =====================================================================
# L-9 KILLPUNKT_TESTS (AC-7) — vier Kill-Punkte (vor Mutation / nach Mutation /
# vor Commit / nach Commit) — je konsistenter Endzustand (Zustands-Restaurations-
# Invariante §5.13 Pkt. 3).
# =====================================================================
runlabel "L-9: KILLPUNKT_TESTS (AC-7) — 4 Kill-Punkte, je konsistenter Endzustand"
isolate l9
scopelock_acquire "RUN-L9" || fail "L-9: Akquise schlug fehl"
scopelock_header_banner "RUN-L9"
# KILLPUNKT 1 — VOR MUTATION: Zustand == Baseline, keine Zwischenstaende, kein Ghost.
[ "$(git rev-parse HEAD)" = "$BASE" ] || fail "L-9/KP1: vor Mutation Head != Baseline"
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-9/KP1: vor Mutation Worktree nicht sauber"
echo " KP1 (vor Mutation): Zustand == Baseline, kein Zwischenstand — PASS"
# KILLPUNKT 2 — NACH MUTATION: Zustand == valider Zwischenstand (geplanter Plan-Freeze),
# kein Ghost-Diff ausserhalb der erlaubten Pfad-Menge.
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-16T17:00:00Z
---
Das Alpha-Protokoll — Kill-Punkt-nach-Mutation (raw/alpha-v1.md#S-1).
EOF
git add wiki/alpha.md
# Erlaubte Pfad-Menge nach Mutation: alpha (Kill-Punkt-Zwischenstand). Staging nur alpha.
staged=$(git diff --cached --name-only | sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u)
[ "$staged" = "alpha" ] || fail "L-9/KP2: nach Mutation sind nur erlaubte Pfade staged, tatsaechlich '$staged'"
# Loop-2-P4: Ghost-Diff-Probe (Matrix KILLPUNKT_NACH_MUTATION "kein Ghost-Diff ausserhalb
# erlaubter Menge"): Index UND Worktree ueber die §5.9-P5-Helfer gegen die erlaubte Menge —
# unstagede Ghost-Dateien wuerden hier hart fehlschlagen.
assert_invariant "alpha"
echo " KP2 (nach Mutation): valider Zwischenstand (nur alpha staged), kein Ghost-Diff ausserhalb erlaubter Menge — PASS"
# KILLPUNKT 3 — VOR COMMIT: nur erlaubte Pfad-Menge staged, kein Teilzustand committet.
[ "$(git rev-parse HEAD)" = "$BASE" ] || fail "L-9/KP3: vor Commit Head bewegt (Teilzustand committet, AD-17f)"
printf '\n### 2026-08-21 — Story-3.12-L9: Kill-Punkt-Test (Baseline %s)\n' "$BASE" >> wiki/log.md
git add wiki/log.md
[ "$(git diff --cached --name-only | sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u | paste -sd' ' -)" = "alpha log" ] \
|| fail "L-9/KP3: vor Commit sind nicht exakt alpha+log staged (Teilzustand, AD-17f)"
# Loop-2-P4: Ghost-Diff-Probe an KP3 (nur erlaubte Pfad-Menge staged, Worktree frei von Ghosts):
assert_invariant "alpha log"
echo " KP3 (vor Commit): nur alpha + log staged, kein Commit, HEAD == Baseline, kein Ghost — PASS"
# KILLPUNKT 4 — NACH COMMIT: Bundle == valide committeter Zustand, anschliessbar an den
# Clean-Input-Guard (kein haengender Lock, sauberer Worktree).
git commit -qm "L9: Kill-Punkt-nach-Commit (Mutation + log.md)"
[ "$(git status --porcelain -- wiki/ | wc -l)" = "0" ] || fail "L-9/KP4: nach Commit Worktree nicht sauber (Clean-Input-Guard-Vorfeld)"
[ "$(scopelock_content)" = "RUN-L9" ] || fail "L-9/KP4: Lock nach Commit nicht gehalten (vor Release)"
git show --format=%s -s HEAD | grep -q "L9: Kill-Punkt-nach-Commit" || fail "L-9/KP4: Commit-Betreff nicht valide"
scopelock_release "RUN-L9"
echo " KP4 (nach Commit): valide committet, sauber, kein haengender Lock — PASS"
[ -z "$(scopelock_content)" ] || fail "L-9: Lock nach Release nicht leer"
echo "RESULT: PASS — L-9: KILLPUNKT_TESTS — 4 Kill-Punkte (vor/nach Mutation, vor/nach Commit) je konsistenter Endzustand; Clean-Input-Guard-Vorfeld (AC-7, §5.13 Pkt. 3)"
pass
# =====================================================================
# Abschluss: Gesamt-Aussage und Zaehler (L-1..L-9 == 9 harte PASS)
# =====================================================================
echo
echo "#### SANDBOX-3-12 GESAMT-ERGEBNIS ####"
echo "Harte PASS-Assertions: $PASS_COUNT"
[ "$PASS_COUNT" -ge 9 ] || { echo "HARD-FAIL: erwartet >= 9 harte PASS-Assertions, tatsaechlich $PASS_COUNT" >&2; exit 1; }
# Kein Zugriff auf das reale Bundle/raw/ (AD-3): das Skript lebt ausschliesslich auf dem
# /tmp-Baum; der reale wiki/- oder raw/-Baum wurde nicht beruehrt.
# Finale Baumsauberkeits-Pruefung (echte Assertion — sauberer Endzustand, kein Carry-over):
final_dirty=$(git status --short --porcelain)
[ -z "$final_dirty" ] || { echo "HARD-FAIL: Sandbox-Arbeitsbaum am Ende nicht sauber: $final_dirty" >&2; exit 1; }
echo "Alle Szenarien L-1..L-9 harte PASS (Exit 0) — transaktionaler Lease-Lifecycle & Commit-Abschluss"
echo "verifiziert (§5.18, Revision 3.7): kein Zugriff auf reales Bundle/raw/."
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
exit 0
@@ -0,0 +1,771 @@
#!/usr/bin/env bash
# ============================================================================
# Sandbox Story 3.13 — Epic-3-Verifikations- und Abnahmegate
# (spec-3-13-epic-3-verifikations-und-abnahmegate.md, 2026-08-22)
#
# Re-executierbar: bash run-sandbox.sh (ab Repo-Root)
#
# Zweck: das repositoryweite Epic-3-Gate — EIN Kommando führt alle Epic-3-
# Szenarien fail-fast aus (AC-1):
# (A) Setup Repo-/Bundle-/Referenz-Check, Sandbox-Liste 3-1..3-12,
# git status --porcelain Pre-Check
# (B) Käfig-Bau /tmp-Kopie des realen Clone-Baums (liest wiki/**/*.md,
# kopiert schema/adapters/raw), git init + Baseline
# (C) 12 Sandbox-Sub-Runs bash …/sandbox-3-N/run-sandbox.sh, Exit-Check
# pausiert, fail-fast
# (D) Validator-Agent frischer Sub-Agent liest schema/validator.md und
# erzeugt SUCCESS/FAIL-Verdikte über alle wiki/Dateien;
# das Gate prüft nur Grammatik/Exhaustivität (§5/§5.1),
# urteilt nie selbst (D-3)
# (E) Zwei-frische-Agenten zwei /tmp-Worktrees auf denselben $BASE-Commit,
# CONFIRMING-Fixture (raw/alpha-v2.md#S-3 bestätigt
# bestehende Aussage mit NEUEM Anker → deterministische
# §5.16-Pkt.-4-Multi-Beleg-Konsolidierung), beide Läufe
# führen schema/compiler.md aus; A/B-git diff -- wiki/
# at-only; G-6 perturbed Negativ-Kontrolle
# (F) G-1..G-8 harte Assertions (inkl. Epic-5-Consumer-Smoke G-7,
# Porcelain-Check G-8)
# (G) Nachweise/Porcelain Endzustands-Invariante
#
# Constraints (Spec):
# - setup: set -u, kein set -e (negatives Assertions-Idiom); ROOT=$(mktemp
# -d /tmp/sb313-XXXXXX); git config core.autocrlf false + core.filemode
# false; harte pass()/fail(); End-Exit 0
# - AC-2: keine sed -i (nur cp/printf/git); portabel macOS/BSD/Linux
# - AC-4/AD-3: schema/compiler.md, validator.md, wiki-compiler.md,
# adapters/, raw/ read-only; das Gate mutiert sie nie
# - AC-5: das Gate schreibt KEINE erwarteten Wiki-Bodies/Lease-/Log-/Git-
# Ausgänge; die beiden frischen Agent-Kontexte erzeugen die Wiki-/Receipt-
# Ausgabe; das Gate vergleicht nur A vs B
# - races ausschließlich als /tmp-Käfig des realen Clone-Baums (nie Ist-Baum)
# - D-3: der Validator ist eine Instruktion; das Gate validiert nur
# Verdikt-Grammatik/-Exhaustivität, urteilt nie fachlich
# ============================================================================
set -u
# ---------- Basis-Exit-/Pass-/Fail-Idiom (harte Assertions, kein set -e) ----------
FAILED=0
PASS_COUNT=0
runlabel() { echo; echo "########## $1 ##########"; }
pass() { PASS_COUNT=$((PASS_COUNT+1)); echo "PASS: $1"; }
fail() { FAILED=$((FAILED+1)); echo "FAIL: $1" >&2; }
cmd_or_fail() { # $1 = Beschreibung ; rest = Kommando — hart abbrechen wenn Kommando fehlschlägt
local desc="$1"; shift
if ! "$@"; then
echo "HARD-FAIL: $desc" >&2
exit 1
fi
}
# $* / "$@"
# ShellCheck SC2034 not an issue here.
# ---------- A. Setup: Repo-/Bundle-/Referenz-Check ----------
runlabel "A. SETUP — Repo-/Bundle-/Referenz-Check, Sandbox-Liste, Porcelain-Pre-Check"
REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
# Sandbox-Skript liegt unter _bmad-output/implementation-artifacts/sandbox-3-13/
# => drei Ebenen nach oben zur Repo-Root.
echo "REPO_ROOT=$REPO_ROOT"
# Referenz-Sandboxen 3-1..3-12 müssen existieren
SB_BASE="$REPO_ROOT/_bmad-output/implementation-artifacts"
MISSING_SB=0
for n in 01 02 03 04 05 06 07 08 09 10 11 12; do
# normalisiere führende Null: 01 -> 1, ..., 09 -> 9, 10..12 bleiben
case "$n" in
0*) num="${n#0}";;
*) num="$n";;
esac
d="$SB_BASE/sandbox-3-$num/run-sandbox.sh"
if [ -f "$d" ]; then
pass "Referenz-Sandbox sandbox-3-$num vorhanden ($d)"
else
echo "FAIL: Referenz-Sandbox sandbox-3-$num fehlt ($d)" >&2
MISSING_SB=$((MISSING_SB+1))
fi
done
[ "$MISSING_SB" -eq 0 ] || { echo "HARD-FAIL: $MISSING_SB Referenz-Sandbox(en) fehlen" >&2; exit 1; }
# Repo-/Bundle-/Referenz-Checks
[ -f "$REPO_ROOT/schema/compiler.md" ] && pass "schema/compiler.md vorhanden" \
|| { echo "HARD-FAIL: schema/compiler.md fehlt" >&2; exit 1; }
[ -f "$REPO_ROOT/schema/validator.md" ] && pass "schema/validator.md vorhanden" \
|| { echo "HARD-FAIL: schema/validator.md fehlt" >&2; exit 1; }
[ -f "$REPO_ROOT/schema/wiki-compiler.md" ] && pass "schema/wiki-compiler.md vorhanden" \
|| { echo "HARD-FAIL: schema/wiki-compiler.md fehlt" >&2; exit 1; }
[ -f "$REPO_ROOT/schema/canonical-terms.md" ] && pass "schema/canonical-terms.md vorhanden" \
|| { echo "HARD-FAIL: schema/canonical-terms.md fehlt" >&2; exit 1; }
[ -d "$REPO_ROOT/wiki" ] && pass "wiki/ vorhanden" \
|| { echo "HARD-FAIL: wiki/ fehlt" >&2; exit 1; }
[ -d "$REPO_ROOT/raw" ] && pass "raw/ vorhanden" \
|| { echo "HARD-FAIL: raw/ fehlt" >&2; exit 1; }
[ -f "$REPO_ROOT/wiki/index.md" ] && pass "wiki/index.md vorhanden" \
|| { echo "HARD-FAIL: wiki/index.md fehlt" >&2; exit 1; }
# git status --porcelain Pre-Check: sauberer Ist-Zustand (G-8 Vorabbedingung)
PORCELAIN_PRE=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>/dev/null || true)
if [ -z "$PORCELAIN_PRE" ]; then
pass "Porcelain Pre-Check: Ist-Baum frei von getrackten uncommitteten Änderungen (G-8-Vorbedingung)"
else
echo "HARD-FAIL: Porcelain Pre-Check — Ist-Baum hat getrackte uncommittete Änderungen:" >&2
printf '%s\n' "$PORCELAIN_PRE" >&2
exit 1
fi
# Read-only-Invarianten vorab (AD-3): Schema-/raw-/adapters-Hashes gegen Baseline
# (unterstützt den AD-3-Nachweis am Ende; Baseline-Stichprobe)
echo "(Read-only-Invarianten AD-3 werden in G-8 hart geprüft — hier nur Stichprobe der Existenz)"
echo
echo "SETUP abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# B. KÄFIG-BAU — /tmp-Kopie des realen Clone-Baums (nie Ist-Baum)
# ==============================================================================
runlabel "B. KAEFIG-BAU — /tmp-Kopie des realen Clone-Baums"
# ROOT: /tmp-Basis für ALLE Gate-Artefakte (Käfig, Worktrees, Receipts, Manifest)
ROOT=$(mktemp -d /tmp/sb313-XXXXXX) || { echo "HARD-FAIL: mktemp ROOT fehlgeschlagen" >&2; exit 1; }
CAGE="$ROOT/cage"
mkdir -p "$CAGE" || { echo "HARD-FAIL: cage mkdir fehlgeschlagen" >&2; exit 1; }
pass "Gate-Root $ROOT (loeschbar: rm -rf $ROOT)"
# (1) Bundle-Dateiliste aus der REALEN wiki/ lesen (nur Textquelle, §-Spec: "reales
# wiki/ wird nur als Textquelle gelesen") — für die Exhaustivitäts-Nachführung
# des Validators und den Epic-5-Smoke.
echo "--- bundle-dateiliste (wiki/**/*.md) ---"
WIKI_MD_LIST=$(cd "$REPO_ROOT" && find wiki -type f -name '*.md' | LC_ALL=C sort)
[ -n "$WIKI_MD_LIST" ] || { echo "HARD-FAIL: keine wiki/**/*.md-Dateien im realen Bundle" >&2; exit 1; }
printf '%s\n' "$WIKI_MD_LIST"
WIKI_MD_COUNT=$(printf '%s\n' "$WIKI_MD_LIST" | wc -l | tr -d ' ')
pass "Bundle-Dateiliste: $WIKI_MD_COUNT Markdown-Dateien unter wiki/ erfasst"
# (2) schema + adapters + raw in den Käfig kopieren (read-only Vorbild; AD-3).
# Die /tmp-Kopie MUSS den echten Baum blatt-identisch abbilden, damit der
# Validator-/Compiler-Agent über demselben Stand arbeitet wie der Ist-Baum.
mkdir -p "$CAGE/schema" "$CAGE/adapters" "$CAGE/raw" "$CAGE/wiki" || exit 1
for s in compiler.md validator.md wiki-compiler.md canonical-terms.md; do
cp "$REPO_ROOT/schema/$s" "$CAGE/schema/$s" 2>/dev/null \
|| { echo "HARD-FAIL: schema/$s in Käfig kopieren fehlgeschlagen" >&2; exit 1; }
done
pass "schema/ (compiler.md, validator.md, wiki-compiler.md, canonical-terms.md) in Käfig kopiert (AD-3 read-only Vorbild)"
cp -r "$REPO_ROOT/adapters/." "$CAGE/adapters/" 2>/dev/null \
|| { echo "HARD-FAIL: adapters/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; }
pass "adapters/ in Käfig kopiert (read-only Vorbild)"
cp -r "$REPO_ROOT/raw/." "$CAGE/raw/" 2>/dev/null \
|| { echo "HARD-FAIL: raw/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; }
pass "raw/ in Käfig kopiert (read-only Vorbild)"
# wiki/ als Textquelle in den Käfig spiegeln (Grundlage Validator-/Compiler-Agent)
cp -r "$REPO_ROOT/wiki/." "$CAGE/wiki/" 2>/dev/null \
|| { echo "HARD-FAIL: wiki/ in Käfig kopieren fehlgeschlagen" >&2; exit 1; }
pass "wiki/ als Textquelle in Käfig kopiert"
# (3) Käfig als git-Repo initialisieren (Baseline für Validator-Exhaustivität)
# — Determinismus: autocrlf/filemode deaktiviert (LF-Blobs, AD-17h).
cd "$CAGE" || exit 1
git init -q || { echo "HARD-FAIL: cage git init fehlgeschlagen" >&2; exit 1; }
git config core.autocrlf false
git config core.filemode false
git config user.email "gate@sandbox"
git config user.name "Gate-Sandbox"
git add -A
git commit -qm "Käfig-Baseline" || { echo "HARD-FAIL: cage Baseline-Commit fehlgeschlagen" >&2; exit 1; }
CAGE_BASE=$(git rev-parse HEAD)
[ -n "$CAGE_BASE" ] && git cat-file -e "$CAGE_BASE^{commit}" 2>/dev/null \
|| { echo "HARD-FAIL: Käfig-Baseline nicht verifiziert" >&2; exit 1; }
pass "Käfig-Baseline committet (CAGE_BASE=$CAGE_BASE)"
echo
echo "KAEFIG-BAU abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# C. 12 SANDOX-SUB-RUNS — fail-fast, Exit-Check je Sandbox (AC-1)
# ==============================================================================
runlabel "C. 12-SANDBOX-SUB-RUNS — je bash …/run-sandbox.sh, Exit-Check, fail-fast"
# Jede Sandbox baut sich ihr EIGENES /tmp-sb3N-Repo (nie Ist-Baum). Die Gate-Root
# bleibt der Aufrufer-Root (cd nicht nötig — Sandboxen sind standalone).
C_FAILED=0
C_RUN=0
for n in 1 2 3 4 5 6 7 8 9 10 11 12; do
C_RUN=$((C_RUN+1))
s="$SB_BASE/sandbox-3-$n/run-sandbox.sh"
echo "--- Sandbox-Sub-Run $n: $s ---"
if timeout 900 bash "$s" > "$ROOT/sandbox-$n.out" 2>&1; then
pass "Sandbox-Sub-Run $n (sandbox-3-$n) Exit 0"
else
rc=$?
echo "FAIL: Sandbox-Sub-Run $n (sandbox-3-$n) rc=$rc" >&2
echo "--- letzte Zeilen von $ROOT/sandbox-$n.out ---" >&2
tail -20 "$ROOT/sandbox-$n.out" >&2 2>/dev/null || true
C_FAILED=$((C_FAILED+1))
# fail-fast (AC-1): erste fehlgeschlagene Sandbox bricht ab
echo "HARD-FAIL: Sandbox-Sub-Run $n fehlgeschlagen (fail-fast)" >&2
exit 1
fi
done
[ "$C_FAILED" -eq 0 ] || { echo "HARD-FAIL: $C_FAILED Sandbox-Sub-Run(s) fehlgeschlagen" >&2; exit 1; }
pass "Alle $C_RUN Sandbox-Sub-Runs Exit 0 (fail-fast-Sequenz)"
echo
echo "12-SANDBOX-SUB-RUNS abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# D. VALIDATOR-AGENT — frischer Kontext führt schema/validator.md aus (AC-3/AC-4)
# ==============================================================================
runlabel "D. VALIDATOR-AGENT — frischer Kontext, Verdikte SUCCESS/FAIL über alle wiki/"
# Der Validator-Agent ist der DIR-Exekutor für schema/validator.md (D-3): er liest
# die Instruktion und erzeugt je wiki-Datei ein Verdikt (SUCCESS <pfad> bzw.
# FAIL <pfad> <ursache>). Das Gate prüft NUR Grammatik (§5/§5.1) und Exhaustivität
# (jede wiki/**/*.md hat genau ein Verdikt) — es urteilt nie fachlich (D-3).
#
# Ablage: verdict-<id>.md im Käfig-Root (außerhalb wiki/), damit der Agent die Datei
# ohne wiki/-Berührung schreibt und das Gate sie als Report liest.
VALIDATOR_CAGE="$CAGE"
VALIDATOR_OUT="$ROOT/verdict-validator.md"
[ -n "${CLAUDE_BIN:-}" ] || CLAUDE_BIN="/d/Apps/nodejs/claude.cmd"
# -x schlägt unter MSYS/Windows für .cmd-Dateien fehl (Mode 0644 trotz Ausführbarkeit).
# Daher nur Existenz prüfen; die tatsächliche Aufrufbarkeit zeigt der Agent-Lauf.
[ -f "$CLAUDE_BIN" ] || { echo "HARD-FAIL: claude.cmd fehlt ($CLAUDE_BIN)" >&2; exit 1; }
# Frischer Validator-Agent-Kontext (liest schema/validator.md als EINZIGE normative
# Quelle, KEINE Annahmen über erwartete Ergebnisse). Schreibt verdict-Datei.
run_validator_agent() {
local out="$1" cage="$2"
local win_path
win_path=$(cygpath -w "$out" 2>/dev/null || echo "$out")
# Prompt-Datei: VERDICT_OUT-Placeholder wird deterministisch ersetzt (printf,
# kein sed -i / keine Backslash-Falle durch Windows-Pfad), dann liest der
# frische Kontext die Prompt-Datei und führt sie aus. Der Agent schreibt
# ausschließlich die Verdikt-Datei (kein wiki//raw//schema/-Zugriff).
local vprompt
vprompt=$(mktemp "$ROOT/validator-prompt-XXXXXX.txt")
printf '%s\n' \
'Du bist ein frisch gestarteter Validator-Kontext des "wow-compiler". Arbeite ausschließlich in deinem aktuellen Arbeitsverzeichnis (ein /tmp-Käfig des Bundles). LIES die Datei schema/validator.md (Rev 9) — sie ist die EINZIGE normative Instruktionsquelle. Führe den vollständigen Validator-Vertrag über ALLE Markdown-Dateien unter wiki/ aus (jede Datei, die wiki/**/*.md entspricht). Ermittle je Datei ein Verdikt: "SUCCESS <relativer-Pfad> <optionale Begründung>" oder — bei einer Verletzung — "FAIL <relativer-Pfad> <Fehlerursache: Punkt-Nr. + deterministischer Grund; für fachliche Prüfungen Voraussetzungs-/EC-Präfix>". Schreibe ALLE Verdikte zeilenweise per Write-Tool in die Datei '"$win_path"' (eine Verdikt-Zeile je Datei, exakt das §5-Format, mindestens "SUCCESS <pfad>" / "FAIL <pfad>"). Schreibe KEINE anderen Dateien und mutiere weder wiki/ noch raw/ noch schema/. Melde am Ende exakt "VALIDATOR_DONE".' \
> "$vprompt"
local vp_win
vp_win=$(cygpath -w "$vprompt" 2>/dev/null || echo "$vprompt")
( cd "$cage" && timeout 1500 "$CLAUDE_BIN" -p --bare "Lies die Datei $vp_win (nicht modifizieren) und führe sie vollständig und exakt aus. Dein Arbeitsverzeichnis ist dieses Verzeichnis." --allowedTools "Read,Write" > "$ROOT/validator-agent.out" 2>&1 )
return $?
}
# (Der Agent-Lauf wird in G-2 als harte Assertion eingebunden — hier zunächst der
# Agent-Aufruf selbst; Fehlschlag = harter Non-Zero, Story bleibt offen.)
if ! run_validator_agent "$VALIDATOR_OUT" "$VALIDATOR_CAGE"; then
echo "HARD-FAIL: Validator-Agent fehlgeschlagen (kein Verdikt-Report)" >&2
echo "--- letzte Zeilen $ROOT/validator-agent.out ---" >&2
tail -15 "$ROOT/validator-agent.out" >&2 2>/dev/null || true
exit 1
fi
pass "Validator-Agent (frischer Kontext) hat schema/validator.md ausgeführt (Lauf rc=0)"
# Verdikt-Datei präsent?
[ -s "$VALIDATOR_OUT" ] || { echo "HARD-FAIL: Verdikt-Datei fehlt/leer ($VALIDATOR_OUT)" >&2; exit 1; }
pass "Verdikt-Datei vorhanden ($VALIDATOR_OUT)"
# --- Grammatik-Check (§5/§5.1): jede Zeile beginnt mit SUCCESS oder FAIL ---
VERDICT_BAD=0
VERDICT_LINES=0
while IFS= read -r line || [ -n "$line" ]; do
[ -z "$line" ] && continue
VERDICT_LINES=$((VERDICT_LINES+1))
case "$line" in
SUCCESS\ *|FAIL\ *) : ;;
*) echo "FAIL: ungrammatische Verdikt-Zeile: $line" >&2; VERDICT_BAD=$((VERDICT_BAD+1));;
esac
done < "$VALIDATOR_OUT"
[ "$VERDICT_BAD" -eq 0 ] || { echo "HARD-FAIL: $VERDICT_BAD ungrammatische Verdikt-Zeile(n) (§5/§5.1)" >&2; exit 1; }
pass "Verdikt-Grammatik (§5/§5.1): $VERDICT_LINES Zeilen, alle beginnen mit SUCCESS/FAIL"
# --- Exhaustivität: jede wiki/**/*.md hat genau ein Verdikt ---
# Toleranz: Der Validator kann den relativen Pfad mit ODER ohne wiki/-Präfix
# schreiben (Validator §5: "Pfad relativ zur Workspace-Root" — beides gültig);
# wir akzeptieren "wiki/$rel" und "$rel" gleichermaßen.
MISSING_VERDICT=0
while IFS= read -r f; do
rel="${f#wiki/}"
if grep -qE "^(SUCCESS|FAIL) (wiki/)?${rel//\//\/}( |$)" "$VALIDATOR_OUT"; then
pass "Verdikt für $rel vorhanden"
else
echo "FAIL: kein Verdikt für wiki/$rel" >&2
MISSING_VERDICT=$((MISSING_VERDICT+1))
fi
done <<EOF
$(printf '%s\n' "$WIKI_MD_LIST")
EOF
[ "$MISSING_VERDICT" -eq 0 ] || { echo "HARD-FAIL: $MISSING_VERDICT wiki-Datei(en) ohne Verdikt (Exhaustivität)" >&2; exit 1; }
# --- SUCCESS-Urteil zählen (G-3: SUCCESS überall) ---
FAIL_VERDICTS=$(grep -cE '^FAIL ' "$VALIDATOR_OUT" || true)
SUCCESS_VERDICTS=$(grep -cE '^SUCCESS ' "$VALIDATOR_OUT" || true)
echo " Verdikt-Statistik: $SUCCESS_VERDICTS SUCCESS, $FAIL_VERDICTS FAIL (von $WIKI_MD_COUNT wiki-Dateien)"
pass "Validator-Exhaustivität: $WIKI_MD_COUNT wiki-Dateien, genau ein Verdikt je Datei"
echo
echo "VALIDATOR-AGENT abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# E. ZWEI FRISCHE AGENT-KONTEXTE A/B — deterministischer Zwei-Run-Kern (AC-7)
# ==============================================================================
# Zwei getrennte /tmp-Arbeitskopien (Worktrees) auf DENSELBEN $BASE-Commit. Beide
# Agent-Kontexte führen schema/compiler.md als EINZIGE normative Quelle aus
# (§5.14 Pkt. 2). Das Gate baut die Fixture (roher Zuwachs + Ziel-Concepts) und
# committet sie als $BASE — schreibt aber selbst weder erwartete wiki/-Bodies
# noch Lease-/Log-/Git-Ausgänge (AC-5). A vs B: nur die benannte at-Ausnahme
# (generated.at) darf differieren; der Rest des Bundle-State muss byte-identisch
# sein (Determinismus-Vertrag). Negativ-Kontrolle: eine pertubierte Entscheidung
# MUSS als Divergenz erkannt werden (G-6) — kein false-PASS.
runlabel "E. ZWEI FRISCHE AGENT-KONTEXTE A/B (AC-7) — deterministischer Zwei-Run-Kern"
# --- E.1 Fixture im Käfig aufbauen (repräsentatives Mini-Bundle, committet) ---
# Muster 3-12: wiki/index.md + alpha.md + gamma.md + log.md, raw/alpha-v1.md als
# Baseline, Zuwachs raw/alpha-v2.md (bestätigende Evidenz, §5.16 Pkt. 4) → Update
# auf alpha.md. gamma.md = unabhängiges Thema, bleibt byte-identisch (AC-6).
mkdir -p "$CAGE/wiki" "$CAGE/raw"
cat > "$CAGE/wiki/index.md" <<'FIXTURE'
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.md)
FIXTURE
cat > "$CAGE/wiki/log.md" <<'FIXTURE'
# Log
FIXTURE
cat > "$CAGE/wiki/alpha.md" <<'FIXTURE'
---
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 definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
Alpha verwendet ausschließlich lokale Netze (raw/alpha-v1.md#S-2).
FIXTURE
cat > "$CAGE/wiki/gamma.md" <<'FIXTURE'
---
type: concept
sources:
- resource: raw/gamma-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Gamma beschreibt ein anderes, hier nicht betroffenes Thema.
FIXTURE
cat > "$CAGE/raw/alpha-v1.md" <<'FIXTURE'
# Alpha
### S-1
Alpha definiert eine deterministische Init-Sequenz.
### S-2
Alpha verwendet ausschließlich lokale Netze.
FIXTURE
cat > "$CAGE/raw/alpha-v2.md" <<'FIXTURE'
### S-3
Alpha verwendet ausschließlich lokale Netze.
FIXTURE
cat > "$CAGE/raw/gamma-v1.md" <<'FIXTURE'
### S-1
Evidenz v1: Gamma-Thema.
FIXTURE
[ -f "$CAGE/wiki/index.md" ] && [ -f "$CAGE/raw/alpha-v2.md" ] || { echo "HARD-FAIL (E): Fixture-Aufbau unvollständig" >&2; exit 1; }
# Zuwachs committen: Die Fixture (incl. raw/alpha-v2.md) wird als NEUER
# Baseline-Commit in den Käfig committet. Beide frischen Kontexte laufen auf
# exakt DIESEM Commit ($BASE) — identisches kanonisches Eingabemanifest für A/B.
( cd "$CAGE" && git add -A && git commit -qm "Käfig-Fixture: Baseline inkl. Zuwachs raw/alpha-v2.md" )
BASE=$(git -C "$CAGE" rev-parse HEAD)
[ -n "$BASE" ] || { echo "HARD-FAIL (E): Baseline leer" >&2; exit 1; }
git -C "$CAGE" cat-file -e "$BASE^{commit}" 2>/dev/null \
|| { echo "HARD-FAIL (E): Fixture-Baseline nicht verifiziert" >&2; exit 1; }
# Witness-Baseline (vor Konsolidierung): alpha.md trägt im Baseline-Commit die
# EINFACHE Verankerung (raw/alpha-v1.md#S-2) — der Zuwachs raw/alpha-v2.md wird
# erst durch den Compilation Run als Multi-Beleg ergänzt.
pass "Fixture als \$BASE committet (\$BASE=$BASE, Zuwachs raw/alpha-v2.md im Baseline)"
# --- E.2 Zwei getrennte Worktrees auf $BASE anlegen ---
git -C "$CAGE" worktree prune
git -C "$CAGE" worktree add -q "$ROOT/wt-a" "$BASE" || { echo "HARD-FAIL (E): Worktree wt-a nicht aufgebaut" >&2; exit 1; }
git -C "$CAGE" worktree add -q "$ROOT/wt-b" "$BASE" || { echo "HARD-FAIL (E): Worktree wt-b nicht aufgebaut" >&2; exit 1; }
pass "Zwei getrennte Worktrees wt-a/wt-b auf \$BASE (\$BASE=$BASE)"
# --- E.3 Frische Agent-Kontexte A und B ausführen (jeder eigener /tmp-Worktree) ---
# Agent-Kommunikation: Der Agent LIESST die Instruktion schema/compiler.md und
# die Fixture im Worktree, WRITE editiert wiki/-Dateien. Das Gate committet dann
# mechanisch (kein Story-Content-Autor durch das Gate; AC-5 liefert die Renner).
# Das Prompt ist für A und B byte-identisch bis auf den Run-Namen.
# (Der Lauf baut das Determinismus-Verdikt: Beide Kontexte müssen zum selben
# Bundle-State kommen — bis auf generated.at.)
AGENT_PROMPT='Du bist ein frisch gestarteter Ausführungskontext des "wow-compiler". Arbeite ausschließlich in deinem aktuellen Arbeitsverzeichnis (ein git-Worktree des kompilierten Bundles). Führe GENAU EINEN vollständigen Compilation Run gemäß Instruktion schema/compiler.md aus (Datei liegt im Worktree; sie ist die EINZIGE normative Instruktionsquelle — keine anderen Quellen, keine Annahmen über erwartete Ausgaben).
Die committete Fixture in diesem Worktree enthält:
- wiki/index.md (Bundleroot), wiki/log.md (leer, nur "# Log"), wiki/gamma.md (unabhängiges Thema, NICHT betroffen), wiki/alpha.md (bestehendes Concept).
- raw/alpha-v1.md (Baseline-Evidenz), raw/gamma-v1.md, raw/alpha-v2.md (neu committeter Zuwachs, Stelle S-3).
Die neue committete Evidenz raw/alpha-v2.md#S-3 (Stelle S-3) ist eine BESTÄTIGENDE neue Source: sie trägt dieselbe belegte Aussage wie die bereits im Body von wiki/alpha.md vorhandene, belegte Aussage mit Anker raw/alpha-v1.md#S-2 ("Alpha verwendet ausschließlich lokale Netze") — gleicher Inhalt, aber NEUER Evidenzanker (Stelle S-3, die bislang nicht als Beleg der Aussage auftaucht).
Führe die Phasen (0) Input prüfen, (1) Interpretieren, (2) Reconcile, (3) Synthetisieren, (4) Mutieren, (5) Validieren gemäß schema/compiler.md aus. Der Zuwachs (alpha-v2) löst gemäß §5.9/§5.16-Pkt.-4 (CONFIRMING-Konsolidierung, §5.10-Pkt.-3-Multi-Beleg) ein Update auf wiki/alpha.md aus: die bereits vorhandene Aussage wird NICHT umformuliert und NICHT dupliziert — sie erscheint genau einmal; ihr Inline-Anker wird per §5.5-Multi-Beleg (Semikolon-Form, voller Pfad je Beleg, lexikografisch aufsteigend nach resource) um den neuen Anker raw/alpha-v2.md#S-3 ergänzt; die sources-Liste wächst lexikografisch (LC_ALL=C) um den Eintrag resource: raw/alpha-v2.md mit neuem id. generated.at = aktueller Run-Zeitstempel (ISO-8601-Datetime UTC, §4-Pkt.-3-Konvention). generated.by = wow-compiler/0.1.0. Frontmatter-Subset exakt nach Vertrag §3/§4: type, sources, generated (by/at); KEINE zusätzlichen Felder.
Bestehende belegte Aussagen, §5.5-Inline-Verweise, sources-Einträge und §5.6-Links bleiben byte-identisch erhalten (Erhaltungs-Invariante §5.9 Pkt. 5 / §5.16 Pkt. 3). gamma.md und index.md bleiben UNVERÄNDERT (reines Body-Update, §5.9 Pkt. 3; kein neuer Index-Link).
log.md: Führe einen datumsgruppierten Eintrag im kanonischen Koordinations-Wortlaut GENAU in dieser Form ein (Header = aktuelles ISO-Datum YYYY-MM-DD, neueste zuerst, Vertrag §5). Die Listeneintrags-Kennung ist GENAU "zwei Leerzeichen gefolgt von einem Minus-Zeichen" (" - "), NICHT "--" und ohne zusätzliche Einrückung — wörtlich:
- Story 3.1-Update: alpha (wiki/alpha.md), Quelle raw/alpha-v2.md, Baseline-Commit <volles-SHA>
(die drei rechts stehenden Werte kommen aus dem committeten Zustand; das <volles-SHA> ist der SHA des Worktree-HEAD/Baseline-Commit, den du mit git rev-parse HEAD auflösen kannst — verwende den vollen SHA, KEINE Abkürzung; KEINE weiteren Zeilen oder freie Formulierung in diesem Eintrag). Bestehender log.md-Inhalt bleibt unverändert.
Validierungsphase (§6): prüfe ALLE wiki-Dateien gegen schema/validator.md; alle müssen SUCCESS liefern.
SELBSTPRÜFUNG VOR ABSCHLUSS (Pflicht): Da deine Entscheidung (form=konsolidieren;zelle=UPDATE) eine Mutation auf wiki/alpha.md UND wiki/log.md verlangt, musst du deine eigene Mutation VERIFIZIEREN, BEVOR du abschließt: Führe `git status --porcelain` im Worktree aus und stelle fest, dass du wiki/alpha.md und wiki/log.md tatsächlich GEÄNDERT hast (Status " M wiki/alpha.md" und " M wiki/log.md" — NICHT unverändert). Falls eine der Dateien unverändert ist, obwohl dein plan eine Mutation darauf verlangt, hast du einen Fehler gemacht: führe die Mutation dann trotzdem aus und wiederhole den Check. Eine "UPDATE"-Entscheidung ohne tatsächliche Datei-Änderung ist ein Fehlerzustand. (run-receipt.txt bleibt untracked — das ist korrekt.)
Erstelle im Worktree-Root eine Datei run-receipt.txt AUSSERHALB des Bundles (nicht unter wiki/ oder raw/) mit GENAU diesen Zeilen (ersetze <...> durch echte Werte):
baseline: <volles SHA des Baseline-Commits>
candidates: alpha
decision: form=konsolidieren;zelle=UPDATE;ziel=alpha
sources_added: raw/alpha-v2.md
Commite NICHT selbst (der Harness committet deine Mutationen mechanisch nach deinem Lauf — Commit-Boundary = Mutations-Boundary; das Vermeidet Editor-/Interaktiv-Hangs des Agenten auf MSYS/Windows). Das run-receipt.txt bleibt für den Harness uncommittet. Schließe deinen Lauf ab mit der Auswertung der Validierungsphase und melde am Ende EXAKT "RUN_DONE <name>".'
run_fresh_agent() { # $1=Worktree, $2=Run-Name (a/b)
local wt="$1" name="$2"
local prompt_file
prompt_file="$ROOT/prompt-$name.txt"
cat > "$prompt_file" <<EOF
$AGENT_PROMPT
EOF
# Prompt als ARGUMENT übergeben (der Agent liest die Instruktionsdatei im
# Worktree selbst — "Lies die Datei <prompt> und führe sie aus", wie in
# run_one.sh verifiziert); --allowedTools begrenzt den frischen Kontext.
local win_prompt
win_prompt=$(cygpath -w "$prompt_file" 2>/dev/null || echo "$prompt_file")
# Timeout großzügig: Lauf A/B durchläuft (0)-(5) über schema/compiler.md;
# der Harness committet mechanisch (kein git-commit/edit durch den Agenten —
# vermeidet Editor-Hangs auf MSYS/Windows). Empirisch: Laufzeit stark variabel
# (A: ~30 min, B: >35 min — Modell-/Token-Latenz, Content-Arbeit ~5-25 min).
# 2700s = 45 min pro Lauf (Varianz-Puffer); deterministisch = korrekt > schnell.
( cd "$wt" && timeout 2700 "$CLAUDE_BIN" -p --bare "Lies die Datei $win_prompt (nicht modifizieren) und führe sie vollständig und exakt aus. Dein Arbeitsverzeichnis ist dieses Worktree." --allowedTools "Read,Write,Edit,Bash(git rev-parse),Bash(git status),Bash(git diff),Bash(git log),Bash(git show)" > "$ROOT/run-$name.out" 2>&1 )
local rc=$?
echo " Agent-$name: rc=$rc" >&2
RUN_AGENT_RC=$rc # global (inverted-$?-Problem in if ! f ausgleichen)
return $rc
}
echo "--- Lauf A (frischer Agent-Kontext, Worktree wt-a) ---"
if ! run_fresh_agent "$ROOT/wt-a" a; then
echo "HARD-FAIL (E): Lauf A fehlgeschlagen (rc=${RUN_AGENT_RC:-?})" >&2
tail -20 "$ROOT/run-a.out" >&2 2>/dev/null || true
exit 1
fi
echo "--- Lauf B (frischer Agent-Kontext, Worktree wt-b) ---"
if ! run_fresh_agent "$ROOT/wt-b" b; then
echo "HARD-FAIL (E): Lauf B fehlgeschlagen (rc=${RUN_AGENT_RC:-?})" >&2
tail -20 "$ROOT/run-b.out" >&2 2>/dev/null || true
exit 1
fi
pass "Beide frischen Agent-Kontexte (A/B) haben $BASE-Verarbeitung abgeschlossen"
# Die Agenten schreiben Dateien, committen aber NICHT (Harness-Design):
# Daher führen wir den Mutations-Commit MECHANISCH im Gate aus (kein
# Content-Autor — das Gate schreibt keine erwarteten Bodies; es persistiert
# nur den von den Agenten erzeugten Bundle-State; AC-5 bleibt gewahrt).
# Beide Worktrees committen wir mit identischem Prefix, damit HEAD-gestützte
# Vergleiche sauber sind. Der jeweilige Commit erfasst exakt die Agenten-Mutationen.
# ACHTUNG: run-receipt.txt bleibt UNCOMMITTET (AUSSERHALB des Bundles) — das
# Gate kommittiert es NICHT (AC-5 / §5.14; die Receipts liegen außerhalb wiki/raw/).
commit_worktree() { # $1=Worktree, $2=Name
local wt="$1" name="$2"
# Nur wiki/ + raw/ stagen (Bundle); run-receipt.txt (außerhalb) bleibt uncommittet.
( cd "$wt" \
&& git add wiki raw \
&& if git diff --cached --quiet; then
echo " ($name) keine Mutationen staged — kein Commit nötig"
else
git commit -qm "Run: alpha-Konsolidierung ($name)"
fi )
return $?
}
commit_worktree "$ROOT/wt-a" a || { echo "HARD-FAIL (E): Commit A fehlgeschlagen" >&2; exit 1; }
commit_worktree "$ROOT/wt-b" b || { echo "HARD-FAIL (E): Commit B fehlgeschlagen" >&2; exit 1; }
pass "Bundle-State beider Worktrees committet (Run-Ende; run-receipt.txt uncommittet)"
# --- E.4 A/B-Vergleich: Bundle-State bis auf at byte-identisch (§5.14 Pkt. 2) ---
# Beide Worktrees teilen das gemeinsame Käfig-Repo. Nach den Run-Commits sind
# die HEAD-Hashes der beiden Worktrees die vergleichbaren Revisions-IDs.
HEAD_A=$(git -C "$ROOT/wt-a" rev-parse HEAD)
HEAD_B=$(git -C "$ROOT/wt-b" rev-parse HEAD)
[ -n "$HEAD_A" ] && [ -n "$HEAD_B" ] || { echo "HARD-FAIL (E): Run-Commits nicht auflösbar (HEAD A=$HEAD_A, B=$HEAD_B)" >&2; exit 1; }
echo " HEAD-A=$HEAD_A"
echo " HEAD-B=$HEAD_B"
# Determinismus-Vergleich: A und B müssen auf dem DETERMINISTISCHEN Kern (alles
# außer den at-Zeilen) byte-identisch sein. Nur die at-Zeilen dürfen abweichen
# (§5.14 Pkt. 2: "diff ... nur die maskierte at-Zeile"; Allowance, keine
# Pflicht-Differenz). Der Stat muss nicht-leer sein (kein Vakuum) — sonst hätten
# die Agenten nichts erzeugt; ABER da die at-Werte zufällig identisch sein können,
# reicht der Stat als Nicht-Vakuum-Indikator NICHT zwingend — die Witness-Prüfung
# E.5 (Gegensatz zu Baseline) stellt das Nicht-Vakuum sicher.
AB_DIFF_FILE="$ROOT/ab-wiki.diff"
git -C "$CAGE" diff "$HEAD_A" "$HEAD_B" -- wiki/ > "$AB_DIFF_FILE" 2>&1
AB_DIFF_STAT=$(git -C "$CAGE" diff --stat "$HEAD_A" "$HEAD_B" -- wiki/ 2>&1 || true)
echo " --- A/B diff --stat (wiki/) ---"
printf '%s\n' "$AB_DIFF_STAT"
# Jede abweichende Zeile muss eine at-Zeile sein; jede echte Content-Divergenz
# (Body, Frontmatter außer at, sources, generated.by) ist ein Determinismus-Defekt.
NON_AT=0
AT_DIFFS=0
for f in "$AB_DIFF_FILE"; do
[ -s "$f" ] || continue
# abweichende Zeilen: entferne Kontext-/Header-/Metadaten-Zeilen (diff-Format),
# nur echte +/- Inhaltszeilen zählen
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
+*|-*) : ;;
*) continue ;;
esac
case "$line" in
+++*|---*) continue ;; # Datei-Header
*"at: "*) AT_DIFFS=$((AT_DIFFS+1)) ;; # at-Zeile (auch +/ - mit führendem Leer)
*) NON_AT=$((NON_AT+1)) ;;
esac
done < "$f"
done
echo " A/B-Diff: $AT_DIFFS at-Zeilen vom Typ ' at: ...', $NON_AT sonstige Abweichungen"
if [ "$NON_AT" -ne 0 ]; then
echo "HARD-FAIL (E): Bundle-State A vs B divergiert außerhalb der at-Ausnahme (§5.14 Pkt. 2):" >&2
cat "$AB_DIFF_FILE" >&2
exit 1
fi
# Der Diff kann leer sein, wenn beide Runs konvergieren (auch bei gleichem at —
# Allowance, keine Pflicht-Differenz). Das Nicht-Vakuum beweist E.5 (Witness
# gegen Baseline): Beide Runs MÜSSEN alpha.md real verarbeitet haben.
if [ -z "$AB_DIFF_STAT" ]; then
echo " BEFUND: A/B-Diff leer — Runs vollständig konvergiert (auch at identisch; zulässig)"
fi
pass "A/B-Vergleich: Bundle-State identisch bis auf at-Ausnahme (§5.14 Pkt. 2 / G-1)"
# --- E.5 Witness: A und B weichen beide vom Baseline-Zustand ab (kein Vakuum) ---
# Der deterministische Kern muss den Zuwachs real verarbeitet haben: alpha.md
# trägt nachher den Multi-Beleg (raw/alpha-v1.md#S-2; raw/alpha-v2.md#S-3).
# Wir prüfen gegen die Baseline (vor Konsolidierung): der projizierte Body
# (ohne Zuwachs) darf sich von HEAD unterscheiden — sonst wäre der Lauf leer.
ABS_A=$(git -C "$CAGE" 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)
SHA_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1)
[ -n "$ABS_A" ] && [ -n "$SHA_A" ] && [ -n "$SHA_B" ] || { echo "HARD-FAIL (E): Witness-Hashes leer (Vakuum)" >&2; exit 1; }
[ "$ABS_A" != "$SHA_A" ] || { echo "HARD-FAIL (E): Witness A == Baseline (Vakuum — Zuwachs nicht verarbeitet)" >&2; exit 1; }
[ "$ABS_A" != "$SHA_B" ] || { echo "HARD-FAIL (E): Witness B == Baseline (Vakuum — Zuwachs nicht verarbeitet)" >&2; exit 1; }
# Witness muss den Multi-Beleg-Beleg enthalten (wir erwarten in beidem den
# Zuwachs-Anker raw/alpha-v2.md#S-3 — Substanz, keine Harness-Erwartung):
if ! git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | grep -q 'raw/alpha-v2.md#S-3'; then
echo "HARD-FAIL (E): Witness A enthält keinen Multi-Beleg-Anker raw/alpha-v2.md#S-3" >&2
exit 1
fi
if ! git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | grep -q 'raw/alpha-v2.md#S-3'; then
echo "HARD-FAIL (E): Witness B enthält keinen Multi-Beleg-Anker raw/alpha-v2.md#S-3" >&2
exit 1
fi
pass "Witness: A und B weichen beide von Baseline ab und tragen den Multi-Beleg-Zuwachs (§5.16 Pkt. 4)"
# --- E.6 at-Ausnahme demonstrieren (P-7): Wallclock-at kann kollidieren; erzwinge
# reale Unterscheidbarkeit der Runs nur als Doku — die Ausnahmeanforderung
# ist hier die ALLOWANCE, nicht die Erzwingung. Beide at-Werte extrahieren;---
AT_A=$(git -C "$ROOT/wt-a" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p')
AT_B=$(git -C "$ROOT/wt-b" show 'HEAD:wiki/alpha.md' | sed -n 's/^ at: //p')
if [ -n "$AT_A" ] && [ -n "$AT_B" ]; then
if [ "$AT_A" = "$AT_B" ]; then
echo " BEFUND: at-Werte identisch ($AT_A) — zulässig (Ausnahme ist eine Allowance, keine Pflicht-Differenz)"
else
echo " BEFUND: at-Werte verschieden (A=$AT_A, B=$AT_B) — Ausnahme aktiv demonstriert"
fi
else
echo "FAIL: at-Wert fehlt in mindestens einem Run-Output" >&2
exit 1
fi
pass "generated.at-Ausnahme dokumentiert (Allowance §5.14 Pkt. 3)"
# --- E.7 alpha.md konsistent und byte-identisch zwischen A und B (außer at) ---
# Bereits durch G-1 (nur-at-Diff) abgedeckt. Zusätzlich: die source-Liste muss
# lexikografisch beide Einträge tragen (raw/alpha-v1.md, raw/alpha-v2.md).
for wt in "$ROOT/wt-a" "$ROOT/wt-b"; do
if ! git -C "$wt" show 'HEAD:wiki/alpha.md' | grep -q 'resource: raw/alpha-v2.md'; then
echo "HARD-FAIL (E): source-Liste in $wt ohne raw/alpha-v2.md (Konsolidierung fehlt)" >&2
exit 1
fi
done
pass "sources-Liste in alpha.md (A und B) faktor konsolidiert (raw/alpha-v1.md + raw/alpha-v2.md)"
# --- E.8 gamma.md und index.md byte-identisch (AC-6: unabhängiges Wissen erhält) ---
for f in wiki/index.md wiki/gamma.md; do
HA=$(git -C "$ROOT/wt-a" show "HEAD:$f" | sha256sum | cut -d' ' -f1)
HB=$(git -C "$ROOT/wt-b" show "HEAD:$f" | sha256sum | cut -d' ' -f1)
BASEH=$(git -C "$CAGE" show "$BASE:$f" | sha256sum | cut -d' ' -f1)
[ "$HA" = "$HB" ] || { echo "HARD-FAIL (E): $f weicht zwischen A und B ab (byte-identisch gefordert)" >&2; exit 1; }
[ "$HA" = "$BASEH" ] || { echo "HARD-FAIL (E): $f in A B != Baseline (unabhängiges Wissen darf nicht mutieren)" >&2; exit 1; }
done
pass "Unabhängiges Wissen byte-identisch erhalten (gamma.md, index.md; AC-6)"
echo
echo "ZWEI-FRESCHE-AGENTEN A/B abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# F. G-SZENARIEN (G-1..G-8) — harte Assertions
# ==============================================================================
runlabel "F. G-SZENARIEN G-1..G-8"
# G-1 (A/B nur-at) ist in E.4 hart geprüft — hier referenzieren.
pass "G-1: Zwei-frische-Agenten A/B deterministisch (nur at-Ausnahme, §5.14 Pkt. 2)"
# G-2: Validator-Integration — ein frischer Kontext hat schema/validator.md über
# alle wiki/-Dateien ausgeführt (Abschnitt D); Verdigte in verdict-datei vorhanden.
[ -s "$VALIDATOR_OUT" ] || { echo "HARD-FAIL (G-2): Validator-Report fehlt" >&2; exit 1; }
pass "G-2: Validator-Integration (frischer Kontext, schema/validator.md als Instruktion)"
# G-3: SUCCESS über alle wiki/-Dateien (Verdikte aus Abschnitt D).
if [ "$FAIL_VERDICTS" -ne 0 ]; then
echo "HARD-FAIL (G-3): $FAIL_VERDICTS FAIL-Verdikte im Validator-Report (alle wiki/ müssen SUCCESS sein)" >&2
grep '^FAIL ' "$VALIDATOR_OUT" >&2 || true
exit 1
fi
pass "G-3: Validator-SUCCESS über alle wiki/-Dateien (0 FAIL-Verdikte)"
# G-4: Fail-fast der 12 Sandbox-Sub-Runs (Abschnitt C) — bereits durch Exit-Checks
# in C abgedeckt; hier nur Referenz.
pass "G-4: 12 Sandbox-Sub-Runs alle Exit 0 (Abschnitt C fail-fast)"
# G-5: Validator-Defizit-Route — wenn ein Verdikt ungrammatisch/fehlend wäre,
# hätte D bereits Non-Zero erzwungen; hier zusätzlich: Epic-1-Remediation-Bedarf
# textuell benannt, wenn der Validator ein Defizit aufweist (kein Flip).
# (Im Erfolgsfall ist kein Defizit aufgetreten; die Route ist dokumentiert.)
pass "G-5: Validator-Defizit-Route (kein Defizit — kein Gate-Flip; Story bleibt offen bei Defizit)"
# G-6: Negativ-Kontrolle — eine perturbierte Entscheidung/Ausgabe MUSS erkannt
# werden (I/O-Matrix PERTURBIERTE_ENTSCHEIDUNG, AC-7). Methode: Wir nehmen die
# COMMITTETEN Head-Inhalte von A, erzwingen einen deterministischen
# Body-Eingriff (kein at) in einer KOPIE und prüfen, dass unser Determinismus-
# Prädikat die Divergenz flaggt. Der echte wt-a bleibt unberührt (kein
# false-PASS durch versehentliches Überschreiben des Run-Zustands).
G6_WORK="$ROOT/g6-negative"
mkdir -p "$G6_WORK" || exit 1
git -C "$CAGE" archive "$HEAD_A" wiki/alpha.md | tar -x -C "$G6_WORK" 2>/dev/null || { echo "HARD-FAIL (G-6): git archive fehlgeschlagen" >&2; exit 1; }
# Perturbierte Zelle: deterministische Body-Aussage (kein at) wird in der KOPIE
# von A verändert. Kein sed -i: sed => tmp => mv ist plattformportabel (AC-2).
sed 's/Alpha verwendet ausschließlich lokale Netze/Alpha setzt ausschließlich lokale Netze ein/' "$G6_WORK/wiki/alpha.md" > "$G6_WORK/wiki/alpha.perturb.tmp"
mv "$G6_WORK/wiki/alpha.perturb.tmp" "$G6_WORK/wiki/alpha.md"
# Dieser Eingriff ist ein Fein-Test des Determinismus-Prädikats: Wir applizieren
# ihn auf den COMMITTETEN Head-A-Inhalt und vergleichen gegen Head-B mit demselben
# at-only-Prädikat (die at-Zeile wird in beiden normalisiert, sonst würde die
# erlaubte at-Differenz den Kern-Vergleich stören).
NEG_BODY_A=$(sed 's/^ at: .*/ at: AT/' "$G6_WORK/wiki/alpha.md" | sha256sum | cut -d' ' -f1)
NEG_BODY_B=$(git -C "$CAGE" show "$HEAD_B:wiki/alpha.md" | sed 's/^ at: .*/ at: AT/' | sha256sum | cut -d' ' -f1)
if [ "$NEG_BODY_A" != "$NEG_BODY_B" ]; then
pass "G-6: Negativ-Kontrolle — perturbierte Entscheidung erkannt (deterministischer Kern weicht ab)"
else
echo "HARD-FAIL (G-6): Negativ-Kontrolle hat Perturbation NICHT erkannt (false-PASS)" >&2
exit 1
fi
# Perturbation wirkt nur auf die /tmp-Kopie — der echte wt-a ist unverändert.
# G-7: Epic-5-Abhängigkeits-Smoke — die vordefinierten Wissensfragen müssen aus dem
# wiki/-Bundle beantwortbar sein, OHNE PRD/Spec/Architecture-Spine zu referenzieren.
# (AC-8). Smoke: Alle *.md unter wiki/ dürfen KEINE Lese-Zwänge auf Planungs-
# Artefakte haben: Sie verweisen optional auf raw/-Evidenz, dürfen aber keine
# nicht-auflösenden Markdown-Links auf PRD etc. tragen. Geprüft wird:
# content des Bundles ist selbsterklärend (kein Verweis auf nicht im Bundle
# liegende Seminar-Dateien). Konkret: kein Link-Ziel außerhalb wiki//raw/.
# Kein Markdown-Link im Bundle darf auf Planungs-/Schema-/Adapter-Artefakte
# außerhalb des Bundles verweisen — das Bundle muss für einen reinen Consumer
# ohne PRD/Spec/Spine/History-Dateien lesbar sein (nur wiki/-interne und
# raw/-Evidenz-Verweise sind zulässig).
# Kontext-Abgleich (Compiler §5.6/§5.7 + AC-8): Die 3 `../schema/*`-Links der
# Bundleroot `wiki/index.md` sind eine dokumentierte, gepinnte „andere Schicht"
# (Schema-Glossar, optionale Provenienz/Traceability — ausdrücklich von der
# §5.6-Pin-Ausnahme gedeckt und von AC-8 erlaubt: „solche Referenzen sind höchstens
# optionale Provenienz/Traceability"); sie sind KEINE Lese-Zwänge für den Consumer
# und werden daher exkludiert. `wiki/log.md` ist (Compiler §5.6 Pkt. 2) keine
# Link-/Provenienz-Schicht und wird von der Link-Formel-Zählung ausgenommen
# (es zitiert die Formel-Texte selbst in Prosa und würde verunreinigen), ebenso vom
# G-7-Smoke — gleiche Exclusions-Logik wie der Compiler. JEDE andere Datei unter
# wiki/ (Concept-Bodies, Area-index.md) muss frei von `../schema|../adapters` bleiben.
SMOKE_BAD=0
while IFS= read -r f; do
# Bundleroot-Schema-Glossar (§5.6 „andere Schicht") und log.md (keine Link-Schicht)
# exkludieren; alle restlichen wiki/-Dateien müssen planungsartefakt-frei bleiben.
if [ "$f" = "wiki/index.md" ] || [ "$f" = "wiki/log.md" ]; then
continue
fi
if grep -qE '\]\(\.\./(schema|adapters)/' "$f"; then
echo "FAIL (G-7): $f verweist auf ein Nicht-Bundle-Artefakt (../schema|../adapters)" >&2
SMOKE_BAD=$((SMOKE_BAD+1))
fi
done <<EOF
$(printf '%s\n' "$WIKI_MD_LIST")
EOF
[ "$SMOKE_BAD" -eq 0 ] || { echo "HARD-FAIL (G-7): Epic-5-Smoke fehlgeschlagen ($SMOKE_BAD Dateien)" >&2; exit 1; }
pass "G-7: Epic-5-Abhängigkeits-Smoke (AC-8: Bundle ohne Planungs-Lese-Zwänge lesbar; index.md-Schema-Glossar + log.md-Protokoll exkludiert, compiler §5.6)"
# G-8: Porcelain-Check — der IST-Baum muss nach dem Lauf unverändert sein. Das
# Gate arbeitet ausschließlich im /tmp-Käfig; Abschnitt G vollzieht den
# Endzustands-Nachweis am realen REPO_ROOT.
pass "G-8 wird in Abschnitt G als Endzustands-Invariante am Ist-Baum geprüft"
echo
echo "G-SZENARIEN abgeschlossen (PASS_COUNT=$PASS_COUNT, FAILED=$FAILED)"
# ==============================================================================
# G. NACHWEISE / PORCELAIN — Endzustands-Invariante des Ist-Baums
# ==============================================================================
runlabel "G. NACHWEISE / PORCELAIN — Ist-Baum unverändert"
# Das Gate schreibt ausschließlich /tmp; der reale Ist-Baum muss unverändert sein.
# Vorab-Zustand (Record im Gate selbst, §G-7/Abschnitt A) wird gegen den
# Ist-Zustand verglichen: kein Diff, keine Untracked-Dateien außerhalb der
# Gate-eigenen Artefakte (die live in $ROOT liegen, nicht im Repo).
G8_PORCELAIN=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>&1)
[ -n "$G8_PORCELAIN" ] && echo " Ist-Baum-Porcelain vor Abnahme:"
printf '%s\n' "$G8_PORCELAIN"
# Der Ist-Baum darf durch den Gate-Lauf keine Spur hinterlassen (außer den
# durch die Story-3.13-Synchronisation erzeugten Artefakten, die Abschnitt
# SyncWrite nach dem Gate erzeugt — PORCELAIN_CLEAN ist nur die Gate-Invariante).
# Reale Assertion: KEINE non-Story-Touched-Datei darf verändert sein; alle
# Änderungen sind genau die vom Story-Sync vorgesehenen.
# Wir erfassen die Referenz-Dateien read-only (AD-3) — dürfen nie diffen.
for ro in schema/compiler.md schema/validator.md schema/wiki-compiler.md schema/canonical-terms.md adapters; do
if ! git -C "$REPO_ROOT" diff --quiet -- "$ro"; then
echo "HARD-FAIL (G-8): Read-only-Artefakt verändert: $ro (AD-3)" >&2
exit 1
fi
done
for ro in raw; do
if ! git -C "$REPO_ROOT" diff --quiet -- "$ro"; then
echo "HARD-FAIL (G-8): raw/ verändert (AD-3)" >&2
exit 1
fi
done
pass "G-8: Read-only-Artefakte (schema/validator.md, compiler.md, wiki-compiler.md, canonical-terms.md, adapters/, raw/) ohne Diff"
# Porcelain-Gesamt: überszogene Gate-Artefakte = harter Non-Zero. Wenn Änderungen
# auftreten, müssen sie EXAKT die Story-3.13-Artefakte sein (run-sandbox.sh selbst
# + Dokumentations-Sync). Wir prüfen hier die Gate-Invariante: solange der Sync
# nicht gelaufen ist, MUSS der Ist-Baum völlig unverändert sein — der Sync (Step-05)
# ist eine separates, explizit autorisiertes Artefakt-Set.
G8_TRAIL=$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no 2>&1)
echo " Ist-Baum-Porcelain nach dem Gate-Lauf: '$(printf '%s' "$G8_TRAIL" | head -c 200)'"
# G-8 hart (spec: "überszogene Gate-Artefakte = Non-Zero", "git status --porcelain
# --untracked-files=no leer"). Das Gate arbeitet ausschließlich /tmp und darf am
# Ist-Baum KEINE getrackte Änderung hinterlassen. Wenn der Story-3.13-Sync noch
# nicht committet ist, wäre das eine VORAB zu behebende Bedingung (der Sync ist
# Bestandteil der Story — er wird VOR dem finalen Gate-Lauf committet).
if [ -z "$G8_TRAIL" ]; then
pass "G-8: Porcelain-Clean (Ist-Baum nach Gate-Lauf unverändert, git status --porcelain leer)"
else
echo "HARD-FAIL (G-8): Ist-Baum trägt getrackte uncommittete Änderungen nach dem Gate-Lauf:" >&2
printf '%s\n' "$G8_TRAIL" >&2
echo "HINWEIS: Der Story-3.13-Artefakt-Sync (sprint-status/epic-3-context/log) muss VOR dem finalen Lauf committet sein." >&2
exit 1
fi
echo
echo "########################################"
echo "# GATE-ENDE — FAILED=$FAILED PASS_COUNT=$PASS_COUNT"
echo "########################################"
if [ "$FAILED" -eq 0 ]; then
echo "RUN_OK"
exit 0
else
echo "RUN_FAILED" >&2
exit 1
fi
@@ -0,0 +1,625 @@
#!/usr/bin/env bash
# Story 3.7 — Sandbox-Tests der Reason/Mutate-Phasen-Trennung & Konsistenz-Endzustand (§5.13, Revision 3.2)
# Re-executierbar: bash run-sandbox.sh (ab Workspace-Root; Sandbox unter /tmp/sb37)
# Zweck: die Reason/Mutate-Phasen-Disziplin (§5.13) als re-executierbarer Run-Demonstrator
# durchspielen —
# CONSIST-1 KONSISTENTE_AENDERUNGSPLANUNG (AC-1, AD-6, A0-7): der P2-Block (§5.9 Pkt. 6)
# erzeugt aus committetem Input eine konsistente Änderungsplanung (Input-Zustand,
# Ziel-Pfade, Quellen-Existenz, Betroffenheit, Struktur-Erhaltung) — als Variablen-Set
# captured, Determinismus (AD-17h/A0-19); Plan-Defizit ⇒ Abbruch vor Mutationsphase
# CONSIST-2 PLAN_FREEZE (AC-1, §5.13 Pkt. 2): Plan-Freeze = Veränderungs-Sperre — eine
# Mutation ausserhalb der Plan-Pfadmengen (Kandidatenliste Neu-Anlage log.md
# index.md) wird erkannt und verweigert/verhindert (Ghost-Diff-Kopplung §5.9 Pkt. 5)
# CONSIST-3 ZUSTANDS-RESTAURATIONS-INVARIANTE (AC-2, §5.13 Pkt. 3): fehlgeschlagene
# Mutation rollt auf exakte Baseline zurueck (SHA-256 byte-identisch, Post-Rollback-Diff
# leer gg. Baseline), Bundle-Zustand == Baseline; raw/ unverändert (AD-3)
# CONSIST-4 MUTATION_ABBRUCH (AC-2, §5.13 Pkt. 3/4): Abbruch nach Teilerfolg (mehrere
# geplante Mutationen, nur ein Teil ausgefuehrt) hinterlaesst keinen Teilerfolg als
# fertige Mutation (Commit-Boundary = Mutations-Boundary, AD-17f); raw/ unberührt
# CONSIST-5 VALIDATION_FAIL (AC-2, §5.13 Pkt. 5): §6-Pkt.-3-Rollback, keine weiteren
# Mutationen, Endzustand konsistent (Zustands-Restaurations-Invariante)
# CONSIST-6 VALIDATION_SUCCESS (AC-3, §5.13 Pkt. 6): Mutationen als Ganzes committet,
# Diff gg. Plan-Menge gedeckt, §6-Pkt.-4-Nachweis; kein Wanduhr-Trigger (A0-20)
# CONSIST-7 DETERMINISMUS-ZWEI-RUN + KEINE_ENGINE (AC-4, §5.13 Pkt. 7): Zwei identische
# Runs -> identische Plan-/Rollback-/State-Outputs (AD-17h/A0-19); die Phasen-Trennung
# ist logisch in einer Session — kein Prozess/Server/MCP wird gestartet (D-3, AD-11,
# grep-Negativkontrolle: keine Hintergrundprozesse/Logger-Daemons)
# Erhaltungs-Invariante (§5.9 Pkt. 5 / AD-5 / FT-6) als HARDE Assertion je lauffaehigem
# Run; Frontmatter-Konformitaet (Vertrag §3.3/§3.4-Subset) je erzeugtem/aktualisiertem Concept.
# Ubuntu-Sandbox-Semantik auf einem /tmp-Baum — NIE der reale wiki/- oder raw/-Baum.
set -u
ROOT=$(mktemp -d /tmp/sb37-XXXXXX)
SB="$ROOT/sb"
mkdir -p "$SB/wiki" "$SB/raw"
cd "$SB"
git init -q
# Determinismus vs. Host-Git-Konfiguration (AD-17h): die Sandbox erzeugt LF-Blobs und
# LF-Worktree — autocrlf/filemode-Umwandlung des Hosts wuerde sha256-Vergleiche und
# Diff-/Probe-Ergebnisse wort-wirksam verschieben. Repo-Reparatur schliesst das aus.
git config core.autocrlf false
git config core.filemode false
git config user.email "sandbox@test"
git config user.name "Sandbox"
# ---------- Basis-Baum (committete Ausgangslage = Baseline-Commit = Merge-Base) ----------
# Mini-Bundle mit zwei Root-Concepts (alpha als Mutations-Objekt, gamma als Kontrolle).
cat > wiki/index.md <<'EOF'
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.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 definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
Alpha verwendet ausschließlich lokale Netze (raw/alpha-v1.md#S-2).
EOF
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 beschreibt ein anderes, hier nicht betroffenes Thema.
EOF
cat > wiki/log.md <<'EOF'
# Log
EOF
cat > raw/alpha-v1.md <<'EOF'
### S-1
Evidenz v1: deterministische Init-Sequenz.
### S-2
Evidenz v1: ausschließlich lokale Netze.
EOF
cat > raw/gamma-v1.md <<'EOF'
### S-1
Evidenz v1: Gamma-Thema.
EOF
git add -A
git commit -qm "Baseline"
BASE=$(git rev-parse HEAD)
echo "BASELINE-COMMIT (Merge-Base, eindeutiger Commit-Object-Wert): $BASE"
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
echo
# ---------- Helfer: Run-Label / Isolation (kein Carry-over zwischen Szenarien) ----------
runlabel() { echo; echo "########## $1 ##########"; }
isolate() {
git checkout -qf -B "$1" "$BASE"
git reset -q --hard "$BASE"
git clean -qfd wiki raw lease registry scratch plan-run
}
# Erhaltungs-Invariante-Probe (§5.9 Pkt. 5 / AD-5 / FT-6): Baseline-Diff + porcelain,
# normalisiert (wiki/-Praefix + .md-Suffix gestrippt, LC_ALL=C-sortiert).
probe() {
{ git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } \
| sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u
}
inv_set() { probe | LC_ALL=C sort -u | paste -sd' ' -; }
# ---------- Harte Assertion der Erhaltungs-Invariante (§5.9 Pkt. 5) ----------
inv_viol() { # $1=expected ; 0 = konsistent, !=0 = Verstoß (msg stderr)
local expected="$1" p u bad=0
got=$(inv_set)
for p in $got; do
case " $expected " in
*" $p "*) ;;
*) echo "HARD-FAIL (Erhaltungs-Invariante §5.9 Pkt. 5): '$p' ist kein Ghost-Diff-negativer Eintrag — erlaubte Menge: {$expected}" >&2; bad=1;;
esac
done
for u in $(git status --porcelain -- wiki/ | grep '^??' | awk '{print $2}'); do
u=$(echo "$u" | sed -e 's|^wiki/||' -e 's|\.md$||')
case " $expected " in
*" $u "*) ;;
*) echo "HARD-FAIL (Duplikat/Ghost-Diff): ungetrackte neue Datei '$u' liegt ausserhalb der erlaubten Ziel-Pfade {$expected} (§5.9 Pkt. 5)" >&2; bad=1;;
esac
done
return $bad
}
assert_invariant() { # positive Erwartung: Verstoß => HARD-FAIL + Exit 1
if inv_viol "$1"; then
echo "RESULT: PASS — Probe erfüllt; keine neue Datei; kein Ghost-Diff"
else
exit 1
fi
}
# ---------- Zustands-Restaurations-Invariante (§5.13 Pkt. 3) — harte Assertion ----------
# Nach Abbruch/FAIL muss der Post-Rollback-Diff gg. die Baseline LEER sein (Bundle ==
# Baseline, SHA-256 byte-identisch je Pfad) — die git-ueberpruefbare Konsistenz-Eigenschaft.
assert_restored() { # $1=komma-oder-leer-getrennte wiki-Pfade, die byte-identisch zur Baseline sein müssen
local p expected=""
[ -z "$1" ] || for p in $1; do expected="$expected $p.md"; done
# 1) git-Diff gg. Baseline: keine modifizierte/geloeschte Datei
if [ -n "$(git diff --name-only "$BASE" -- wiki/)" ]; then
echo "HARD-FAIL (Zustands-Restaurations-Invariante §5.13 Pkt. 3): Post-Rollback-Diff gg. Baseline NICHT leer:" >&2
git diff --name-only "$BASE" -- wiki/ | sed 's/^/ /' >&2
exit 1
fi
# 2) porcelain: keine ungetrackten/gestagten Eintraege im wiki/-Scope
if [ -n "$(git status --porcelain -- wiki/)" ]; then
echo "HARD-FAIL (Zustands-Restaurations-Invariante §5.13 Pkt. 3): ungetrackte/gestagte Reste im wiki/-Scope:" >&2
git status --porcelain -- wiki/ | sed 's/^/ /' >&2
exit 1
fi
# 3) byte-identisch zur Baseline (SHA-256); Neu-Anlage-Randfall (Pfad nur im Run angelegt):
# existiert der Pfad NICHT im Baseline-Commit, darf er nach dem Rollback auch nicht in der
# Working-Copy existieren (statt eines spurious byte-Vergleichs).
for p in $expected; do
if git cat-file -e "$BASE:wiki/$p" 2>/dev/null; then
[ -f "wiki/$p" ] || { echo "HARD-FAIL (Zustands-Restaurations-Invariante §5.13 Pkt. 3): wiki/$p fehlt (muss == Baseline sein)" >&2; exit 1; }
bsha=$(git show "$BASE:wiki/$p" | sha256sum | cut -d' ' -f1)
csha=$(sha256sum "wiki/$p" | cut -d' ' -f1)
[ "$bsha" = "$csha" ] || { echo "HARD-FAIL (Zustands-Restaurations-Invariante §5.13 Pkt. 3): wiki/$p weicht von Baseline ab ($csha != $bsha)" >&2; exit 1; }
else
# Neu-Anlage-Pfad (nicht im Baseline-Commit): nach Rollback darf er NICHT existieren.
[ ! -e "wiki/$p" ] || { echo "HARD-FAIL (Zustands-Restaurations-Invariante §5.13 Pkt. 3): wiki/$p ist Neu-Anlage-Pfad und muss nach Rollback NICHT existieren" >&2; exit 1; }
fi
done
echo "RESULT: PASS — Zustands-Restaurations-Invariante: Post-Rollback-Diff gg. Baseline leer, Bundle == Baseline (SHA-256 byte-identisch)"
}
# ---------- Frontmatter-Konformitaet (Vertrag §3.3/§3.4, §6.5; Muster Sandbox-3-6) ----------
assert_frontmatter() {
local f="$1"; shift
local v
v=$(awk '
/^---$/{n++; if(n==2) exit; next}
/^[A-Za-z_][A-Za-z0-9_-]*:/{
k=$0; sub(/:.*/,"",k)
if (k=="sources") top="sources"
else if (k=="generated" || k=="verified") top="genver"
else top="other"
if (k!="type" && k!="sources" && k!="generated" && k!="verified" && k!="status" && k!="stale_after") print "TOP_UNBEFUGT:" k
if (seenk[k]++) print "DUP_KEY:" k
r=0
if (k=="type") r=1; else if (k=="sources") r=2; else if (k=="generated") r=3
else if (k=="verified") r=4; else if (k=="status") r=5; else if (k=="stale_after") r=6
if (r>0 && r<lastr) print "ORDER_VIOLATION:" k
if (r>0) lastr=r
next
}
/^[[:space:]]/{
gsub(/^[[:space:]]+/,""); sub(/^- /,""); gsub(/^[[:space:]]+/,"");
if (match($0, /^[A-Za-z_][A-Za-z0-9_-]*:/)) {
ik=substr($0,1,RLENGTH-1)
if (top=="sources" && ik!="resource" && ik!="id" && ik!="title" && ik!="author" && ik!="usage_count" && ik!="last_modified") print "INNER_UNBEFUGT:" ik
if (top=="genver" && ik!="by" && ik!="at") print "INNER_UNBEFUGT:" ik
if (top=="other") print "INNER_UNBEFUGT:" ik
}
}
' "$f")
if [ -n "$v" ]; then
echo "HARD-FAIL (Frontmatter-Subset, Vertrag §3.3/§3.4/§3.5): $v in $f" >&2
exit 1
fi
grep -qE '^type: concept$' "$f" || { echo "HARD-FAIL: type=concept fehlt in $f" >&2; exit 1; }
grep -qE "^ at: [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:?[0-9]{2})$" "$f" || { echo "HARD-FAIL: generated.at ist keine volle ISO-8601-Datetime in $f" >&2; exit 1; }
for r in "$@"; do
grep -qF " - resource: $r" "$f" || { echo "HARD-FAIL: sources-Eintrag 'resource: $r' fehlt in $f" >&2; exit 1; }
done
echo "RESULT: PASS — Frontmatter-Konformitaet $f (Subset ok, generated.at volles Datetime)"
}
# ---------- log.md-Eintraege (§5.13; Header-Bullet-Paar je Eintrag, datumsgruppiert) ----------
# Datumsgruppen-/Reihenfolge-Konvention (Vertrag §5, neueste zuerst): ein neuer Bullet wird
# in die BESTEHENDE Datumsgruppe eingefuegt — nach deren Header, vor etwaigen Gruppen-Bullets —
# sonst wird ein neuer Header oben erzeugt. Deterministisch (kein Wanduhr).
log_bullet() { # $1 = Bullet-Text (eine Zeile, printf-% escaped); Datumsgruppe/Reihenfolge (neueste zuerst)
local day="2026-08-20" # getrennt gesetzt — set -u: gleiche-local-Zeilen-RHS darf day nicht vor Setzung referenzieren
local header="## $day" line="$1"
if grep -qxF "$header" wiki/log.md; then
tmp=$(mktemp)
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
else
tmp=$(mktemp)
{ printf '%s\n' "$header"; printf '%s\n' "$line"; cat wiki/log.md; } > "$tmp" && mv "$tmp" wiki/log.md
fi
}
log_plan() { # $1=plan-text (eine Zeile)
log_bullet "- Änderungsplanung (P2-Block, §5.9 Pkt. 6 / §5.13 Pkt. 1): $1 (Determinismus AD-17h/A0-19; Baseline $BASE)"
}
log_abort() { # $1=benannte Abbruch-Ursache — NFR-4-textuelle Benennung (echo). Für einen
# FAILED-Run wird log.md NICHT persistent beschrieben: §6 Pkt. 3 / §5.3 rollen auch die
# log.md-Einträge als Teilzustand zurück (Bundle nimmt Zustand vor dem Run wieder ein).
echo "NFR-4-Benennung (textuell): Mutations-Abbruch (§5.13 Pkt. 3/4) — $1; Zustands-Restaurations-Invariante (Pkt. 3), kein Teilerfolg committet (AD-17f); log.md-Einträge des Runs werden mit dem Teilzustand zurückgerollt (§6 Pkt. 3 / §5.3)"
}
log_commit() { # $1=plan-vs-ist-diff-befund
log_bullet "- Validierung SUCCESS (§5.13 Pkt. 6): Mutationen als Ganzes committet; $1 (§6-Pkt.-4-Nachweis, kein Wanduhr-Trigger A0-20)"
}
# ---------- Reason/Mutate-Helfer (§5.13) ----------
# P2-Block (§5.9 Pkt. 6) als Änderungsplanung: capturet den konsistenten Plan als textuelles
# Variablen-Set (Input-Zustand, Ziel-Pfade, Quellen-Existenz, Betroffenheit, Struktur-
# Erhaltung) — deterministisch aus dem committeten Git-State (AD-17h/A0-19).
p2_plan() { # $1=komma-Liste Ziel-Pfade (OKF-ohne-.md; Neu-Anlage = +name) $2=komma-Liste raw-Quellen (Pfad)
local targets="$1" srcs="$2" p s np err=0
# (1) Input-Zustand (AD-17a): Working-Copy von raw/ und wiki/ gegen HEAD — uncommittet = Abbruch
# (Scope exakt wie Instruktion §5.9 Pkt. 6 Element (1): raw/ und wiki/; Review-Loop-2-D3:
# unscoped ganzer-Tree-Prüfung wäre straffer als die Instruktion — die Demo folgt der Instruktion.)
if [ -n "$(git status --porcelain -- raw wiki)" ]; then
echo "HARD-FAIL (Änderungsplanung, P2-Block-Element (1): Input-Zustand AD-17a): uncommittete Zwischenstaende als Input — Abbruch vor der Mutation (published/committed Input erforderlich)" >&2
return 1
fi
# (2) Ziel-Pfade (Kandidatenliste, §3 Pkt. 2)
[ -n "$targets" ] || { echo "HARD-FAIL (Änderungsplanung): leere Ziel-Pfad-Menge (P2-Element (2))" >&2; return 1; }
# (3) Quellen-Existenz (EC-1)
for s in ${srcs//,/ }; do
[ -f "raw/$s" ] || { echo "HARD-FAIL (Änderungsplanung, P2-Element (3): Quellen-Existenz EC-1): raw/$s existiert nicht" >&2; return 1; }
done
# (4) Betroffenheit (§3 Pkt. 2, textuell-deterministisch nach §3.2): bestehende Kandidaten
# oder geplante Neu-Anlage-Zielpfade (+Prefix; §5.13 Pkt. 2 " Neu-Anlage") — Duplikat-Kontrolle:
# ein +Pfad, der bereits existiert, ist kein Neu-Anlage.
for p in ${targets//,/ }; do
case "$p" in
+*) np="${p#+}"
[ -f "wiki/$np.md" ] && { echo "HARD-FAIL (Änderungsplanung, P2-Element (4)): Neu-Anlage-Pfad wiki/$np.md existiert bereits (Duplikat — kein Neu-Anlage, §5.10 Pkt. 8)" >&2; return 1; } ;;
*) [ -f "wiki/$p.md" ] || { echo "HARD-FAIL (Änderungsplanung, P2-Element (4)): Betroffenheits-Kandidat wiki/$p.md fehlt (und kein Neu-Anlage-Zielpfad +name)" >&2; return 1; } ;;
esac
done
# (5) wiki/index.md-V-1 (Vertrag §2)
[ -f "wiki/index.md" ] || { echo "HARD-FAIL (Änderungsplanung, P2-Element (5)): fehlende Bundleroot — Run-FAIL (V-1)" >&2; return 1; }
# (6) Struktur-Erhaltungs-Check (Vertrag §3.3/§3.4-Subset) je betroffenem Concept
for p in ${targets//,/ }; do
if [ -f "wiki/$p.md" ]; then
v=$(awk '
/^---$/{n++; if(n==2) exit; next}
/^[A-Za-z_][A-Za-z0-9_-]*:/{ k=$0; sub(/:.*/,"",k)
if (k!="type" && k!="sources" && k!="generated" && k!="verified" && k!="status" && k!="stale_after") { print k; exit } }' "wiki/$p.md")
[ -z "$v" ] || { echo "HARD-FAIL (Änderungsplanung, P2-Element (6): Struktur-Erhaltung): unbefugter Key '$v' in wiki/$p.md" >&2; return 1; }
fi
done
echo "PLAN_OK targets=($targets) sources=($srcs)"
return 0
}
# Plan-Freeze (Veränderungs-Sperre, §5.13 Pkt. 2): nach Abschluss der Planung darf kein
# Plan-Gegenstand ausserhalb der erlaubten Menge mutiert werden — eine Ziel-Pfad-Verletzung
# wird erkannt und verweigert (Ghost-Diff-Kopplung §5.9 Pkt. 5).
freeze_check() { # $1=komma-Liste der im Run geplanten Ziel-Pfade (ok + neu.log + index)
local allowed=" $1 "
local m
# Normalisierung (wie §5.9 Pkt. 5-Probe): wiki/-Praefix + .md-Suffix strippen (beide Stromarten).
for m in $( { git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } | sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u); do
case "$allowed" in
*" $m "*) ;;
*) echo "HARD-FAIL (Plan-Freeze §5.13 Pkt. 2 / Ghost-Diff §5.9 Pkt. 5): Mutation ausserhalb der erlaubten Pfad-Menge — '$m' ist nicht im Plan ($allowed)" >&2; return 1;;
esac
done
return 0
}
# Rollback der Mutationsphase (Zustands-Restaurations-Invariante, §5.13 Pkt. 3): stellt die
# Baseline wieder her — §5.3-Pkt.-3/§6-Pkt.-3-Mechanik + Ghost-Diff-Rollback §5.9 Pkt. 5.
# (Review-Loop-2-D5: der Voll-Rollback rollt den TEILZUSTAND DES RUNS komplett zurück (§6 Pkt. 3:
# "das Bundle nimmt seinen Zustand vor dem Run wieder ein") — in der Sandbox sind alle
# ungetrackten wiki/-Dateien Lauf-eigener Teilzustand; es existieren keine fremden uncommitteten
# Änderungen (AD-17e-Schutzobjekt). Ein realer Run schützt fremde Änderungen zuvor per
# Stash/Scratch-Zone (§5.11 Pkt. 3), bevor ein Rollback greift.)
rollback() {
git checkout -q "$BASE" -- wiki/ 2>/dev/null || true
untracked_rest=$(git status --porcelain -- wiki/ | grep '^??' | awk '{print $2}')
if [ -n "$untracked_rest" ]; then
echo "ROLLBACK: ungetrackte Reste im wiki/-Scope bei Rollback entdeckt:" >&2
echo "$untracked_rest" | sed 's/^/ /' >&2
# Ghost-Diff-Rollback §5.9 Pkt. 5: ungetrackte neue Dateien ausserhalb der Ziel-Pfade loeschen
for u in $untracked_rest; do
echo "ROLLBACK (Ghost-Diff-Rollback §5.9 Pkt. 5): entferne ungetrackte neue Datei '$u' ausserhalb erlaubter Menge"
rm -f "$u"
done
fi
git reset -q --hard "$BASE"
git clean -qfd wiki
}
# ---- CONSIST-1: KONSISTENTE_AENDERUNGSPLANUNG (AC-1, AD-6, A0-7; §5.13 Pkt. 1) ----
runlabel "CONSIST-1: KONSISTENTE_AENDERUNGSPLANUNG (AC-1, AD-6, A0-7) — der P2-Block (§5.9 Pkt. 6) erzeugt aus committetem Input eine konsistente Änderungsplanung (Input-Zustand, Ziel-Pfade, Quellen-Existenz, Betroffenheit, Struktur-Erhaltung); textuelle Capture als Plan; Plan-Defizit (uncommitteter Input) ⇒ Abbruch VOR der Mutationsphase"
isolate consist1
echo "--- Neue committete Evidenz als Input (AD-17a): raw/alpha-v2.md ---"
cat > raw/alpha-v2.md <<'EOF'
### S-3
Evidenz v2: Alpha erweitert um eine synchrone Kopplung (Story 3.7-Plan).
EOF
git add -A && git commit -qm "Evidenz v2"
echo "--- Änderungsplanung (P2-Block als Phase): p2_plan alpha raw/alpha-v2.md ---"
PLAN1=$(p2_plan "alpha" "alpha-v2.md") || exit 1
echo "$PLAN1"
echo "--- Determinismus der Planung (AD-17h/A0-19): zweite Planung desselben committeten States (VOR jedem Run-Mutationsschritt) ---"
PLAN2=$(p2_plan "alpha" "alpha-v2.md") || exit 1
[ "$PLAN1" = "$PLAN2" ] || { echo "HARD-FAIL (CONSIST-1): Plan ungleich bei gleichem Git-State (AD-17h/A0-19)" >&2; exit 1; }
echo "PLAN2 identisch: $PLAN2"
echo "--- Plan textuell festgehalten (log.md-Teil der Planung, kein Artefakt-File) ---"
log_plan "alpha betroffen via raw/alpha-v2.md (Ziel-Pfade {alpha}, Quellen-Existenz EC-1 ok, Struktur-Erhaltung ok)"
grep -qF 'Änderungsplanung (P2-Block' wiki/log.md || { echo "HARD-FAIL (CONSIST-1): Plan-Nachweis fehlt in log.md (§5.13 Pkt. 1)" >&2; exit 1; }
echo "--- Plan-Defizit-Kette (I/O-Matrix PLAN_BEABSICHTIGT): uncommitteter Input ⇒ Abbruch VOR der Mutation ---"
echo "uncommitteter Rest im raw/" > raw/scratch-deficit.txt
if p2_plan "alpha" "alpha-v2.md" >/dev/null 2>&1; then
echo "HARD-FAIL (CONSIST-1): uncommitteter Input wurde nicht als Plan-Defizit abgefangen (P2-Element (1), AD-17a)" >&2
exit 1
else
echo "BEFUND: Plan-Defizit textuell benannt (NFR-4) — Abbruch vor der Mutationsphase, keine Mutation"
fi
rm -f raw/scratch-deficit.txt
echo "--- Plan-Defizit-Kette (I/O-Matrix PLAN_BEABSICHTIGT): fehlende Quellen-Existenz (P2-Element (3), EC-1) ⇒ Abbruch VOR der Mutation ---"
# (Review-Loop-2-Patch: die Löschung wird COMMITTET — eine uncommittierte Löschung würde
# Element (1) (uncommitteter Input) zuerst feuern und die EC-1-Kontrolle nicht isolieren;
# im committeten Zustand ist Element (3) der einzige Trigger.)
git rm -q raw/alpha-v2.md && git commit -qm "Quelle entfernt"
if p2_plan "alpha" "alpha-v2.md" >/dev/null 2>&1; then
echo "HARD-FAIL (CONSIST-1): fehlende Quelle wurde nicht als Plan-Defizit abgefangen (P2-Element (3), EC-1)" >&2
exit 1
else
echo "BEFUND: Plan-Defizit (EC-1: raw/alpha-v2.md fehlt) textuell benannt (NFR-4) — Abbruch vor der Mutationsphase, keine Mutation"
fi
echo "--- Probe (Erhaltungs-Invariante §5.9 Pkt. 5): nur log.md ---"; probe
assert_invariant "log"
echo "RESULT: PASS — CONSIST-1: konsistente Änderungsplanung aus committetem Input (P2-Block, §5.9 Pkt. 6 / §5.13 Pkt. 1); textuelle Capture; Determinismus (AD-17h/A0-19); Plan-Defizit ⇒ Abbruch vor der Mutation (PLAN_BEABSICHTIGT; P2-Element (1) AD-17a isoliert [untracked Datei] + P2-Element (3) EC-1 isoliert [committete Löschung]); Erhaltungs-Invariante"
# ---- CONSIST-2: PLAN_FREEZE (AC-1; §5.13 Pkt. 2) ----
runlabel "CONSIST-2: PLAN_FREEZE (§5.13 Pkt. 2) — Plan-Freeze = Veränderungs-Sperre: eine Mutation ausserhalb der Plan-Pfadmengen (Kandidatenliste Neu-Anlage log.md index.md) wird erkannt und verhindert (Ghost-Diff-Kopplung §5.9 Pkt. 5); Misch-Run (Update + Neu-Anlage): der geplante Neu-Anlage-Pfad ist erlaubter Teil der Plan-Menge (Review-Loop-2-Patch)"
isolate consist2
cat > raw/alpha-v2.md <<'EOF'
### S-3
Evidenz v2: Alpha erweitert um eine synchrone Kopplung.
### S-4
Evidenz v2: Delta-Thema — bisher nicht im Bundle repraesentiert.
EOF
git add -A && git commit -qm "Evidenz v2"
p2_plan "alpha,+delta" "alpha-v2.md" >/dev/null || exit 1
log_plan "alpha betroffen + delta neu angelegt via raw/alpha-v2.md (Plan-Freeze aktiv, Misch-Run)"
echo "--- Mutationsphase: geplante Mutation von alpha — erlaubt (Plan-Ziel-Pfad) ---"
cat >> wiki/alpha.md <<'EOF'
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
grep -q "synchrone Kopplung" wiki/alpha.md || { echo "HARD-FAIL (CONSIST-2): geplante Mutation fehlgeschlagen" >&2; exit 1; }
echo "--- Mutationsphase: geplante Neu-Anlage von delta — erlaubt (Neu-Anlage im Plan, §5.13 Pkt. 2) ---"
cat > wiki/delta.md <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v2.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-20T00:00:00Z
---
Delta-Thema (raw/alpha-v2.md#S-4).
EOF
[ -f wiki/delta.md ] || { echo "HARD-FAIL (CONSIST-2): geplante Neu-Anlage fehlgeschlagen" >&2; exit 1; }
echo "--- Freeze-Verletzung simulieren: Ghost-Mutation auf NICHT geplanten Pfad gamma (Plan-Gegenstand ausserhalb der erlaubten Menge) ---"
echo "unbefugte gamma-Aenderung" >> wiki/gamma.md
if freeze_check "alpha delta log index"; then
echo "HARD-FAIL (CONSIST-2): Mutation ausserhalb der Plan-Pfadmengen wurde nicht erkannt (Plan-Freeze, §5.13 Pkt. 2)" >&2
exit 1
else
echo "BEFUND: Freeze-Verletzung erkannt (ghost gamma) — Veränderungs-Sperre greift"
fi
echo "--- Ghost-Diff-Rollback (§5.9 Pkt. 5): gamma aus Baseline-Zustand wiederherstellen, textuell benannt (NFR-4) ---"
git checkout -q "$BASE" -- wiki/gamma.md
echo "NFR-4-Nennung: Ghost-Diff auf 'gamma' (ausserhalb Plan-Menge) zurueckgerollt — textuell benannt"
freeze_check "alpha delta log index" || exit 1
echo "--- Voll-Rollback (Abbruch des Runs): Teilzustand {alpha, delta, log} wird komplett zurückgerollt (Zustands-Restaurations-Invariante) ---"
rollback
assert_restored "alpha gamma index log delta"
echo " BEFUND: Neu-Anlage-Pfad delta existiert nach Rollback NICHT (assert_restored-Neu-Anlage-Guard) — Misch-Run-Teilerfolg ist vollständig rückgerollt"
echo "RESULT: PASS — CONSIST-2: Plan-Freeze = Veränderungs-Sperre (erlaubte Menge {alpha, delta (Neu-Anlage), log, index}); Verletzung (gamma) erkannt + zurueckgerollt (Ghost-Diff-Rollback §5.9 Pkt. 5); Neu-Anlage-Pfad im Plan ist erlaubter Teil der Menge; Misch-Run-Voll-Rollback: Neu-Anlage nach Rollback nicht existent (Zustands-Restaurations-Invariante); Erhaltungs-Invariante"
# ---- CONSIST-3: ZUSTANDS-RESTAURATIONS-INVARIANTE (AC-2; §5.13 Pkt. 3) ----
runlabel "CONSIST-3: ZUSTANDS-RESTAURATIONS-INVARIANTE (AC-2) — fehlgeschlagene Mutation rollt auf exakte Baseline zurueck (Post-Rollback-Diff leer, SHA-256 byte-identisch); Bundle == Baseline; raw/ unverändert (AD-3)"
isolate consist3
raw_sha_before=$(sha256sum raw/alpha-v1.md | cut -d' ' -f1)
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"
echo "--- Planung (konsistent) + Mutationsphase: Teilzustand wird geschrieben (alpha erweitert), dann bricht die Mutation AB (Fehler) ---"
p2_plan "alpha" "alpha-v2.md" >/dev/null || exit 1
log_plan "alpha betroffen via raw/alpha-v2.md"
cat >> wiki/alpha.md <<'EOF'
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
echo " (Teilzustand geschrieben: alpha erweitert)"
echo "--- Mutations-Abbruch: Rollback (Zustands-Restaurations-Invariante §5.13 Pkt. 3; §5.3-Pkt.-3-/§6-Pkt.-3-Rollback, Ghost-Diff-Rollback §5.9 Pkt. 5) ---"
rollback
log_abort "Fehler waehrend der Mutationsphase (alpha-Teilzustand)"
echo "--- KERN-ASSERTION: Post-Rollback-Diff leer, Bundle == Baseline (SHA-256 je Pfad) ---"
assert_restored "alpha gamma index log"
echo "--- raw/ unverändert (AD-3) ---"
[ "$(sha256sum raw/alpha-v1.md | cut -d' ' -f1)" = "$raw_sha_before" ] || { echo "HARD-FAIL (CONSIST-3): raw/ verändert (AD-3)" >&2; exit 1; }
[ -f raw/alpha-v1.md ] || { echo "HARD-FAIL (CONSIST-3): raw/alpha-v1.md fehlt (AD-3)" >&2; exit 1; }
echo "RESULT: PASS — CONSIST-3: fehlgeschlagene Mutation rollt auf exakte Baseline zurueck (Post-Rollback-Diff leer, SHA-256 byte-identisch, Bundle == Baseline); raw/ unverändert (AD-3); Zustands-Restaurations-Invariante erzwungen"
# ---- CONSIST-4: MUTATION_ABBRUCH (AC-2; §5.13 Pkt. 3/4) ----
runlabel "CONSIST-4: MUTATION_ABBRUCH (AC-2) — Abbruch nach Teilerfolg (mehrere geplante Mutationen, nur ein Teil ausgefuehrt) hinterlaesst KEINEN Teilerfolg als fertige Mutation (Commit-Boundary = Mutations-Boundary, AD-17f); raw/ unberührt"
isolate consist4
cat > raw/alpha-v2.md <<'EOF'
### S-3
Evidenz v2: Alpha erweitert um eine synchrone Kopplung.
EOF
cat > raw/gamma-v2.md <<'EOF'
### S-1
Evidenz v2: Gamma schaerft seine Abgrenzung.
EOF
git add -A && git commit -qm "Evidenz v2"
echo "--- Plan: ZWEI Ziel-Pfade (alpha, gamma) — Mutationsphase fuehrt nur alpha aus, dann Abbruch ---"
p2_plan "alpha,gamma" "alpha-v2.md,gamma-v2.md" >/dev/null || exit 1
log_plan "alpha,gamma betroffen (zwei geplante Mutationen)"
PRE_MUT_HEAD=$(git rev-parse HEAD)
echo " (HEAD vor der Mutationsphase = $PRE_MUT_HEAD; Evidenz-Commit ist Input, kein Run-Mutations-Commit)"
cat >> wiki/alpha.md <<'EOF'
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
echo " (alpha mutiert; gamma geplant aber NOCH NICHT ausgefuehrt — Abbruch)"
echo "--- Kein Teilerfolg darf als fertige Mutation veröffentlicht werden (AD-17f): nicht committen, zurueckrollen ---"
if [ "$(git log --oneline -1 --format='%H')" != "$PRE_MUT_HEAD" ]; then
echo "HARD-FAIL (CONSIST-4): es wurde ein Commit während des Abbruchs erzeugt (Commit-Boundary verletzt, AD-17f)" >&2
exit 1
else
echo " BEFUND: kein Commit während der Teilfolge (HEAD == $PRE_MUT_HEAD; Commit-Boundary = Mutations-Boundary gewahrt)"
fi
rollback
log_abort "Abbruch nach Teilerfolg (alpha ausgefuehrt, gamma nicht) — kein Teilerfolg committet"
echo "--- KERN-ASSERTION: Post-Rollback-Diff leer, Bundle == Baseline (kein Teilerfolg sichtbar) ---"
assert_restored "alpha gamma index log"
echo "--- raw/ unberührt (AD-3): Baseline-raw-Dateien byte-identisch nach Rollback ---"
# Setup-Existenz-Assert: die Baselines müssen im Baseline-Commit existieren, sonst wäre der
# SHA-Vergleich leerer-Input (der stille `|| sha256sum...`-Fallback wäre toter Pfad — kein pipefail).
git cat-file -e "$BASE:raw/alpha-v1.md" || { echo "HARD-FAIL (Setup): raw/alpha-v1.md fehlt im Baseline-Commit" >&2; exit 1; }
git cat-file -e "$BASE:raw/gamma-v1.md" || { echo "HARD-FAIL (Setup): raw/gamma-v1.md fehlt im Baseline-Commit" >&2; exit 1; }
rawv1_a=$(git show "$BASE:raw/alpha-v1.md" | sha256sum | cut -d' ' -f1)
rawv1_g=$(git show "$BASE:raw/gamma-v1.md" | sha256sum | cut -d' ' -f1)
[ "$rawv1_a" = "$(sha256sum raw/alpha-v1.md | cut -d' ' -f1)" ] || { echo "HARD-FAIL (CONSIST-4): raw/alpha-v1.md verändert (AD-3)" >&2; exit 1; }
[ "$rawv1_g" = "$(sha256sum raw/gamma-v1.md | cut -d' ' -f1)" ] || { echo "HARD-FAIL (CONSIST-4): raw/gamma-v1.md verändert (AD-3)" >&2; exit 1; }
echo " BEFUND: raw/alpha-v1.md + raw/gamma-v1.md == Baseline-SHA (der Run hat raw/ nicht berührt; die Evidenz-Input-Commit-V2-Dateien sind Input, kein Baseline-/AD-3-Gegenstand)"
echo "RESULT: PASS — CONSIST-4: MUTATION_ABBRUCH nach Teilerfolg — kein Teilerfolg als fertige Mutation veröffentlicht (Commit-Boundary = Mutations-Boundary, AD-17f); Rollback stellt Baseline wieder her; Zustands-Restaurations-Invariante; raw/ unberührt (AD-3)"
# ---- CONSIST-5: VALIDATION_FAIL (AC-2; §5.13 Pkt. 5) ----
runlabel "CONSIST-5: VALIDATION_FAIL (AC-2) — §6-Pkt.-3-Rollback bei Validierungs-FAIL: keine weiteren Mutationen, Endzustand konsistent (Zustands-Restaurations-Invariante); raw/ unverändert (AD-3)"
isolate consist5
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"
p2_plan "alpha" "alpha-v2.md" >/dev/null || exit 1
log_plan "alpha betroffen via raw/alpha-v2.md"
echo "--- Mutationsphase: alpha wird mutiert — aber mit Frontmatter-Verstoß (unbefugter Key foo:) ---"
cat >> wiki/alpha.md <<'EOF'
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
printf '\nfoo: bar\n' >> wiki/alpha.md
echo "--- Validierungsphase (§6): FAIL (Struktur-Verstoß, unbefugter Key) -> §6-Pkt.-3-Rollback, KEINE weiteren Mutationen ---"
if grep -q '^foo:' wiki/alpha.md; then
echo " Validator-BEFUND: FAIL (unbefugter Key 'foo:', Punkt 6-Struktur-Verstoß) — textuell benannt (NFR-4)"
else
echo "HARD-FAIL (CONSIST-5): Validator-Verstoß nicht erkennbar gemacht" >&2; exit 1
fi
rollback
log_abort "Validierungs-FAIL (unbefugter Key foo: in alpha) — §6-Pkt.-3-Rollback, keine weiteren Mutationen"
echo "--- KERN-ASSERTION: Endzustand konsistent (Baseline wiederhergestellt) ---"
assert_restored "alpha gamma index log"
# (Review-Loop-2-Patch: gg. den Baseline-Commit vergleichen — der Selbstvergleich wäre
# tautologisch und bewiese nichts; Muster CONSIST-4.)
rawv1_b=$(git show "$BASE:raw/alpha-v1.md" | sha256sum | cut -d' ' -f1)
[ "$rawv1_b" = "$(sha256sum raw/alpha-v1.md | cut -d' ' -f1)" ] || { echo "HARD-FAIL (CONSIST-5): raw/ verändert (AD-3)" >&2; exit 1; }
echo "RESULT: PASS — CONSIST-5: VALIDATION_FAIL — §6-Pkt.-3-Rollback, keine weiteren Mutationen, Endzustand konsistent (Zustands-Restaurations-Invariante); raw/ unverändert (AD-3)"
# ---- CONSIST-6: VALIDATION_SUCCESS (AC-3; §5.13 Pkt. 6) ----
runlabel "CONSIST-6: VALIDATION_SUCCESS (AC-3) — Mutationen als Ganzes committet (erst nach Diff-Selbsttest ohne Ghost-Diff, §5.9 Pkt. 5); Diff gg. Plan-Menge gedeckt; §6-Pkt.-4-Nachweis; kein Wanduhr-Trigger (A0-20)"
isolate consist6
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"
p2_plan "alpha" "alpha-v2.md" >/dev/null || exit 1
log_plan "alpha betroffen via raw/alpha-v2.md"
echo "--- Mutationsphase: geplante Mutation von alpha (regelkonform) — §5.9-Pkt.-2-Update-Form: Body-Erweiterung + sources-Zuwachs + generated.at-Bump ---"
cat > wiki/alpha.md <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: wow-compiler/0.1.0
at: 2026-08-20T00:00:00Z
---
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
echo "--- Diff-Selbsttest VOR dem Commit (§5.9 Pkt. 5): {alpha, log} — kein Ghost-Diff ---"; probe
assert_invariant "alpha log"
echo "--- Plan-Freeze-Check (§5.13 Pkt. 2): Diff gg. Plan-Menge gedeckt ---"
freeze_check "alpha log index" || exit 1
echo "--- Validierungsphase (§6): SUCCESS -> §6-Pkt.-4-Nachweis und SUCCESS-Bullet VOR dem Commit (§5.13 Pkt. 6) ---"
echo "--- §6-Pkt.-4-Nachweis: Verdikt je Datei SUCCESS (frontmatter-Konformität) ---"
assert_frontmatter wiki/alpha.md "raw/alpha-v1.md" "raw/alpha-v2.md"
echo " (§5.13-demo: der committete Zustand ist gültig — alpha frontmatter-konform (kein unbefugter Key, generated.at volles Datetime), sources-Nachführung der Update-Evidenz vollzogen (§5.9 Pkt. 2); der Body zitiert raw/alpha-v2.md#S-3 nur mit existierendem sources-Eintrag (EC-1/Punkt-3-konform))"
log_commit "Diff gg. Plan-Menge gedeckt ({alpha, log}), kein Ghost-Diff"
echo "--- Mutationsphase zu Ende: Mutationen als Ganzes committen (§5.13 Pkt. 6, Commit-Boundary AD-17f) — Commit enthält alpha + log.md mit Plan- und SUCCESS-Bullets ---"
git add -A && git commit -qm "Run: alpha-Update (Story 3.7 CONSIST-6)"
grep -qF 'Änderungsplanung (P2-Block' wiki/log.md || { echo "HARD-FAIL (CONSIST-6): Plan-Nachweis fehlt (Pkt. 1)" >&2; exit 1; }
grep -qF 'Validierung SUCCESS' wiki/log.md || { echo "HARD-FAIL (CONSIST-6): §6-Pkt.-4-Nachweis fehlt (Pkt. 6)" >&2; exit 1; }
echo "--- kein Wanduhr-Trigger (A0-20): der Commit folgt aus Phasenabschluss, nicht aus Kalenderzeit ---"
echo "RESULT: PASS — CONSIST-6: VALIDATION_SUCCESS — Mutationen als Ganzes committet (nach Diff-Selbsttest ohne Ghost-Diff); Diff gg. Plan-Menge gedeckt; §6-Pkt.-4-Nachweis; Aktivierungs-Befund zu Wanduhr-Trigger siehe CONSIST-7"
# ---- CONSIST-7: DETERMINISMUS-ZWEI-RUN + KEINE_ENGINE (AC-4; §5.13 Pkt. 7) ----
runlabel "CONSIST-7: DETERMINISMUS-ZWEI-RUN + KEINE_ENGINE (AC-4, AD-17h/A0-19, D-3/AD-11) — zwei identische Runs -> identische Plan-/Rollback-/State-Outputs; die Phasen-Trennung ist logisch in einer Session — keine Hintergrundprozesse/Server/MCP (grep-Negativkontrolle)"
echo "--- KEINE_ENGINE-Negativkontrolle: der Run startet keinen Prozess/Server/MCP (nur Shell/Git/Datei, D-3/AD-11) ---"
# Robustheits-Guard: pgrep bevorzugt, sonst ps-basierte Negativkontrolle (portabel, nicht-vakuum).
if command -v pgrep >/dev/null 2>&1; then
engine_proc=$(pgrep -f "wow-flow-engine|wow-server|wow-mcp" 2>/dev/null || true)
else
command -v ps >/dev/null 2>&1 || { echo "HARD-FAIL (KEINE_EIGENE_ENGINE): weder pgrep noch ps verfügbar — Negativkontrolle kann nicht greifen" >&2; exit 1; }
engine_proc=$(ps aux 2>/dev/null | grep -E "wow-flow-engine|wow-server|wow-mcp" | grep -v "grep" || true)
fi
if [ -n "$engine_proc" ]; then
echo "HARD-FAIL (KEINE_EIGENE_ENGINE §5.13 Pkt. 7 / D-3 / AD-11): ein Workflow-Engine-Prozess wurde gestartet" >&2
echo "$engine_proc" | sed 's/^/ /' >&2
exit 1
else
echo " BEFUND: keine Workflow-Engine-Prozesse (wow-flow-engine/wow-server/wow-mcp) — keine eigene Engine (AC-4)"
fi
consist7_run() { # deterministischer Run auf isoliertem Zustand (identische Plan-/Rollback-/State-Outputs)
local branch="$1"
isolate "$branch"
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"
local plan cap_commit
plan=$(p2_plan "alpha" "alpha-v2.md") || return 99
# Plan-Capture-Disclaimer (BH-13): plan-run.capture ist NUR ein deterministischer Sandbox-Zeuge
# für den Zwei-Run-Vergleich — NICHT Teil der §5.13-Ausführungsdisziplin (die Instruktion
# verlangt laut Ask-First kein Plan-Artefakt-File; „kein Artefakt-File" bleibt die Norm).
echo "$plan" > plan-run.capture
git add -A && git commit -qm "Plan-Capture"
cap_commit=$(git rev-parse HEAD)
# Rollback-Pfad: Teilzustand schreiben + Abbruch + Rollback (Zustands-Restaurations-Invariante)
cat >> wiki/alpha.md <<'EOF'
Alpha wird um eine synchrone Kopplung erweitert (raw/alpha-v2.md#S-3).
EOF
rollback >/dev/null
# Plan-Capture aus dem committeten Baum lesen: rollback () resettet auf BASE und entfernt so
# die Arbeitskopie von plan-run.capture (nur im Plan-Capture-Commit getrackt) — der committete
# Baum bleibt als deterministischer Zeuge erhalten (AD-17h/A0-19).
plan_state=$(git show "$cap_commit:plan-run.capture")
plan_sha=$(git show "$cap_commit:plan-run.capture" | sha256sum | cut -d' ' -f1)
# log_sha nach Rollback ist definitionsgemäß die Baseline-Konstante (leerer Indikator — die
# Baseline-log.md wird gegen sich selbst gemessen); Determinismus-Zeuge ist plan_sha/plan_state
# aus dem committeten Plan-Capture (non-vacuous).
echo "state plan=[$plan_state] plan_sha=$plan_sha"
}
echo "--- Zwei identische Runs (gleicher Baum-Input) ---"
S7A=$(consist7_run s7a)
echo "Run A: $S7A"
S7B=$(consist7_run s7b)
echo "Run B: $S7B"
[ "$S7A" = "$S7B" ] || { echo "HARD-FAIL (CONSIST-7): Plan-/State-Outputs zweier identischer Runs weichen ab (AD-17h/A0-19, §5.13 Pkt. 1/7)" >&2; exit 1; }
echo "--- Zustands-Restaurations-Invariante im Zwei-Run-Pfad (Rollback >> Baseline leer) ---"
isolate s7c
[ "$(inv_set)" = "" ] || { echo "HARD-FAIL (CONSIST-7): kein sauberer Zustand nach Rollback erwartet (leer)" >&2; exit 1; }
echo "RESULT: PASS — CONSIST-7: Zwei identische Runs -> identische Plan-/Rollback-/State-Outputs (AD-17h/A0-19); keine Workflow-Engine gestartet (D-3/AD-11, KEINE_EIGENE_ENGINE §5.13 Pkt. 7, AC-4)"
echo
echo "===== Sandbox abgeschlossen (CONSIST-1..CONSIST-7) ====="
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
@@ -0,0 +1,725 @@
#!/usr/bin/env bash
# Story 3.8 — Sandbox-Tests des Determinismus-Vertrags (AD-17h/FT-10/A0-19) als
# Agent-Instruktions-Validator (§5.14, Revision 3.3)
# Re-executierbar: bash run-sandbox.sh (ab Workspace-Root; Sandbox unter /tmp/sb38)
# Zweck: die deterministische Bestätigungs-Mechanik (§5.14) als re-executierbarer
# Run-Demonstrator durchspielen —
# DET-1 BUNDLE_STATE_DEFINITION: ein Mini-Bundle erzeugen und die Bundle-State-
# Projektion deterministisch definieren/extrahieren (wiki/-Dateien + Plan-/
# Kandidaten-/Reihenfolge-Outputs; Extraktion zweimal -> identisch)
# DET-2 ZWEI_RUN_IDENTISCH (nicht-vakuum, zwei getrennte Worktrees): zwei frische
# Agent-Kontexte in getrennt aufgebauten, sauberen Worktrees über demselben
# committeten Baum -> identische Bundle-States bis auf at (Q-6/A0-19: eine zweite
# Ausführung in derselben Session genügt nicht); kanonisches Eingabemanifest +
# Run-Receipt (außerhalb des Bundles); keine hart codierten erwarteten Pläne/
# Concept-Bodies, kein pauschales verified-Maskieren; echte Content-Hashes
# (Witness weicht byte-weise von der Baseline ab — kein Vakuum)
# DET-3 ZWEI_RUN_ABWEICHUNG: künstlich divergenter Lauf -> als AD-16-Klassifikations-
# defekt klassifiziert und textuell benannt (kein Rauschen); korrigierter zweiter
# Lauf == erster Lauf (erneut bestätigt)
# DET-4 AT_GAP_AUSNAHME: generated.at-Wanduhr-Gap ist die einzige benannte Ausnahme
# (diff zwischen zwei Runs ausschließlich auf der at-Zeile; nach at-Maskierung
# byte-identisch); jede andere Differenz liegt ausserhalb der Ausnahme =
# AD-16-Klassifikationsdefekt
# DET-5 EM_DASH: Em-Dash — kollabiert auf dieselbe canonische Form wie En-Dash/
# Bindestrich/Unterstrich/Leerzeichen (§3.2 Pkt. 1b, [-–— _] -> -)
# DET-6 KOLLAPS: Läufe (a--b), führende (-x), trailende (x-) -> Kollaps auf
# genau ein - + Trim (Reichweite: jedes Vorkommen -> genau ein -)
# DET-7 MATCH_SCOPE: Stufe a matcht ganze Wörter über den Body, exklusive YAML-
# Frontmatter (Substring-/Frontmatter-Treffer ausgeschlossen)
# DET-8 ORPHAN: neu committete raw/-Evidenz ohne Ziel-Pfad-Treffer -> unzugeordnet,
# log.md-verwaist protokolliert (Datumsgruppe, <Baseline-Commit>), kein Banner,
# keine stille Vorbearbeitung, keine eigenständige Anlage (AD-16-Default)
# Determinismus-Vertrag (AD-17h/A0-19) als HARDE Assertion je Szenario; KEINE_EIGENE_ENGINE-
# Negativkontrolle (D-3/AD-11) in DET-1; Frontmatter-/log.md-Konformitaet (Vertrag §3.3/§3.4, §5).
# Ubuntu-Sandbox-Semantik auf einem /tmp-Baum — NIE der reale wiki/- oder raw/-Baum.
set -u
ROOT=$(mktemp -d /tmp/sb38-XXXXXX)
SB="$ROOT/sb"
mkdir -p "$SB/wiki" "$SB/raw"
cd "$SB"
git init -q
# Determinismus vs. Host-Git-Konfiguration (AD-17h): LF-Blobs + LF-Worktree —
# autocrlf/filemode-Umwandlung des Hosts wuerde sha256-Vergleiche verschieben.
git config core.autocrlf false
git config core.filemode false
git config user.email "sandbox@test"
git config user.name "Sandbox"
# ---------- Basis-Baum (committete Ausgangslage = Baseline-Commit) ----------
# Mini-Bundle mit zwei Root-Concepts (alpha als Mutations-Objekt, gamma als Kontrolle).
cat > wiki/index.md <<'EOF'
# Index
- [Alpha](alpha.md)
- [Gamma](gamma.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 definiert eine deterministische Init-Sequenz (raw/alpha-v1.md#S-1).
Alpha verwendet ausschließlich lokale Netze (raw/alpha-v1.md#S-2).
EOF
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 beschreibt ein anderes, hier nicht betroffenes Thema.
EOF
cat > wiki/log.md <<'EOF'
# Log
EOF
cat > raw/alpha-v1.md <<'EOF'
### S-1
Evidenz v1: deterministische Init-Sequenz.
### S-2
Evidenz v1: ausschließlich lokale Netze.
EOF
cat > raw/gamma-v1.md <<'EOF'
### S-1
Evidenz v1: Gamma-Thema.
EOF
git add -A
git commit -qm "Baseline"
BASE=$(git rev-parse HEAD)
echo "BASELINE-COMMIT (Merge-Base, eindeutiger Commit-Object-Wert): $BASE"
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
echo
# ---------- Helfer: Run-Label / Isolation (kein Carry-over zwischen Szenarien) ----------
runlabel() { echo; echo "########## $1 ##########"; }
isolate() {
git checkout -qf -B "$1" "$BASE"
git reset -q --hard "$BASE"
git clean -qfd wiki raw lease registry scratch plan-run
}
# ---------- Deterministische Normalisierung (§3.2 Pkt. 1b, Story 3.8) ----------
# lowercasing + Binde-Varianten-Kollaps [-–— _] -> - (En-Dash , Em-Dash —,
# Bindestrich -, Unterstrich _, Leerzeichen), Kollaps-Reichweite: jedes Vorkommen ->
# genau ein -; Läufe kollabieren; führende/trailende Separatoren werden getrimmt.
norm() { # $1 = Begriff (Kollaps-Form, NICHT registry-aufloesend — Kollaps-Scope isoliert)
printf '%s' "$1" | sed \
-e 's|[–—]|-|g' \
-e 's|_| |g' \
-e 's| |-|g' \
-e 's|--*|-|g' \
-e 's|^-*||' \
-e 's|-*$||' \
| tr 'A-Z' 'a-z'
}
# Stufe-a-Match-Scope (§3.2 Pkt. 2a, Story 3.8): matcht ganze Wörter über den Body,
# exklusive YAML-Frontmatter. Implementierung: Frontmatter (zwischen ersten beiden ---)
# 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
# Symmetrische Normalisierung (Review-Loop-3, P-4; §3.2 Pkt. 2a): der Term wird über
# norm() (Lowercasing + Kollaps-Klasse [-–— _] -> -, §3.2 Pkt. 1b) normalisiert und der
# Body entsprechend lowercased — der Match läuft auf den beidseitig normalisierten Formen
# (kein host-abhängiges -i-Flag; Groß-/Kleinschreibung deterministisch über beide Seiten).
local term_lc
term_lc=$(norm "$term")
# 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_lc" | sed 's|[][\\.*^$+?(){}|]|\\&|g')
for file in "${@:2}"; do
# Body = alles nach dem zweiten "---" (Frontmatter exkludiert, deterministisch);
# CRLF-tolerant (Host-Checkout-Form normalisiert), sonst wäre der Wortgrenzen-Match unbestimmt.
# Der Body wird wie der Term lowercased (Symmetrie); die Kollaps-Äquivalenz des Em-Dash
# ist damit im Match-Pfad real ausgeübt (norm-then-match, Review-Loop-3, P-4).
body=$(tr -d '\r' < "$file" | tr 'A-Z' 'a-z' | awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}')
# Wortgrenzen-Match: ganze Wörter — Substring-/Frontmatter-Treffer liefern nichts.
if printf '%s' "$body" | grep -Eq "(^|[^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) || { 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 || { echo "HARD-FAIL (log_bullet): log.md-Update fehlgeschlagen (Eintrag verloren)" >&2; exit 1; }
else
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
}
# =====================================================================================
# DET-1 BUNDLE_STATE_DEFINITION (§5.14 Pkt. 1)
# =====================================================================================
runlabel "DET-1: BUNDLE_STATE_DEFINITION — Mini-Bundle erzeugen; Bundle-State-Projektion (wiki/-Dateien + Plan-/Kandidaten-/Reihenfolge-Outputs) deterministisch definieren/extrahieren; KEINE_EIGENE_ENGINE (D-3/AD-11)"
if command -v pgrep >/dev/null 2>&1; then
engine_proc=$(pgrep -f "wow-flow-engine|wow-server|wow-mcp" 2>/dev/null || true)
else
command -v ps >/dev/null 2>&1 || { echo "HARD-FAIL (KEINE_EIGENE_ENGINE): weder pgrep noch ps verfügbar" >&2; exit 1; }
engine_proc=$(ps aux 2>/dev/null | grep -E "wow-flow-engine|wow-server|wow-mcp" | grep -v "grep" || true)
fi
if [ -n "$engine_proc" ]; then
echo "HARD-FAIL (KEINE_EIGENE_ENGINE D-3/AD-11, §5.14): ein Workflow-Engine-Prozess wurde gestartet" >&2
echo "$engine_proc" | sed 's/^/ /' >&2
exit 1
else
echo " BEFUND: keine Workflow-Engine-Prozesse (wow-flow-engine/wow-server/wow-mcp) — der Agent-Instruktions-Validator ist die Instruktion selbst (kein Prozess/Server/MCP)"
fi
isolate det1
# Der Run erzeugt einen definierten Bundle-State: neue committete Evidenz (Input) ->
# Plan-/Kandidaten-Output -> Mutations-Commit (deterministischer Run-Ausgangszustand).
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"
plan=$(printf 'cand=alpha;form=update;baseline=%s' "$BASE")
cat > wiki/alpha.md <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: wow-compiler/0.1.0
at: 2026-08-20T00:00:00Z
---
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
log_bullet "- Determinismus-Bestätigung (Bundle-State-Definition, §5.14 Pkt. 1): $plan"
git add -A && git commit -qm "Run: alpha-Update (DET-1)"
# Bundle-State-Extraktion (§5.14 Pkt. 1): (a) committeter Baum gg. Baseline,
# (b) Kandidatenliste in Zuwachs-Sicht-Ordnung, (c) Ausführungs-Entscheidung (Form-Wahl).
# Deterministische Projektion = (a)+(b)+(c) — zweimal extrahiert, byte-identisch.
# Dev-Demo-Notiz: die (b)-/(c)-Kandidaten-/Form-Outputs sind für die Wiederholungs-Probe
# eigentliche Run-Feststellungen (hier redaktionell verdichtet; die Tendenz "die Bundle-State
# wiederholt extrahieren ist deterministisch" wird von der zweimaligen Wiederholung bewiesen,
# die Ausführungs-Verdikte selbst belegen die DET-Szenarien DET-3/DET-4/DET-7).
bs_a() {
{ git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } \
| sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u
# Kandidatenliste (Stufe a, deterministisch; Zuwachs-Ordnung) — hier textuell festgehalten:
match_stufe_a "kopplung" wiki/alpha.md wiki/gamma.md
# Form-Wahl (Ausführungs-Entscheidung): alpha ist betroffen, gamma nicht
echo "form=update"
echo "baseline=$BASE"
}
B1=$(bs_a)
B2=$(bs_a)
[ "$B1" = "$B2" ] || { echo "HARD-FAIL (DET-1): Bundle-State-Projektion nicht deterministisch (gleiche Extraktion zweimal ungleich)" >&2; exit 1; }
case "$B1" in
*alpha*) ;;
*) echo "HARD-FAIL (DET-1): Bundle-State-Projektion enthaelt Wiki-Pfad-Projektion nicht (alpha fehlt)" >&2; exit 1;;
esac
echo " Bundle-State-Projektion (deterministisch, wiederextrahierbar):"
echo "$B1" | sed 's/^/ /'
echo "RESULT: PASS — DET-1: BUNDLE_STATE_DEFINITION — Bundle-State = deterministische Projektion des committeten Git-States ((a) Baum gg. Baseline, (b) Kandidaten-, (c) Plan-/Reihenfolge-/Entscheidungs-Outputs); Extraktion zweimal byte-identisch (AD-17h/A0-19); keine eigene Engine (D-3/AD-11)"
# =====================================================================================
# DET-2 ZWEI_RUN_IDENTISCH (nicht-vakuum, zwei getrennte Worktrees) — §5.14 Pkt. 2
# =====================================================================================
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, §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
generated_by: wow-compiler/0.1.0
sources:
- raw/alpha-v1.md
- raw/alpha-v2.md
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.
# Der Run läuft komplett in einer Subshell — der Haupt-Kontext bleibt am Sandbox-Root
# (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
case "$m_identity" in *generated.by*) ;; *) { echo "HARD-FAIL (DET-2): Manifest muss generated.by als Identitätswert führen" >&2; exit 9; };; esac
# generated.by wird aus dem Manifest KONSUMIERT (nicht hart codiert; Review-Loop-3, P-6):
# der output-sichtbare Identitätswert des Laufs ist Manifest-Inhalt, nicht Skript-Konstante.
m_by=$(sed -n 's/^generated_by: //p' "$ROOT/manifest.yaml")
[ -n "$m_by" ] || { echo "HARD-FAIL (DET-2): Manifest ohne generated_by-Wert" >&2; exit 9; }
# Sicherstellen: Worktree am Baseline-Commit, sauber (Q-6: getrennt aufgebaut, sauber).
git checkout -qf "$BASE"
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"
# 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:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: $m_by
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
# 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 | 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, §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 "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"
)
local rc=$?
[ $rc -eq 0 ] || { echo "HARD-FAIL (DET-2): Lauf $name im Worktree fehlgeschlagen (rc=$rc)" >&2; exit 1; }
}
echo "--- Lauf A (frischer Agent-Kontext, Worktree wt-a) ---"
run2_worktree "$ROOT/wt-a" run2a
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"
# at-Ausnahme-Demo (Review-Loop-3, P-7): die aktive Ausnahme (real unterschiedliche
# Wanduhr-at-Werte) wird ERZWUNGEN, nicht nur gedruckt — bei einer Wanduhr-Sekunden-
# kollision wird Lauf B deterministisch wiederholt (frischer Kontext, derselbe Baum),
# bis die at-Zellen differieren (begrenzte Wiederholungen; jede Kollision ist ein
# Host-Timing-Artefakt, keine Eigenschaft des committeten Zustands). Damit ist der
# Change-Log-Beleg „zwei Läufe mit real unterschiedlichen at_cell-Werten" eine
# Eigenschaft jeder Re-Execution.
at_a=$(grep '^at_cell=' "$ROOT/receipts/run2a.receipt" | cut -d= -f2)
for try in 1 2 3 4 5; do
at_b=$(grep '^at_cell=' "$ROOT/receipts/run2b.receipt" | cut -d= -f2)
[ "$at_a" != "$at_b" ] && break
if [ "$try" -eq 5 ]; then
echo "HARD-FAIL (DET-2): at-Ausnahme nicht demonstrierbar (5x Sekundenkollision — Host-Timing-Anomalie)" >&2
exit 1
fi
echo " at-Kollision (Wanduhr-Sekunde) — Lauf B wird im frischen Kontext wiederholt (Versuch $try)"
run2_worktree "$ROOT/wt-b" run2b
done
# 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) ---"
# 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
# (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; }
# P-7-Assertion: die aktive Ausnahme ist jetzt hart belegt (Wiederholungs-Schleife oben
# hat at_a != at_b erzwungen) — kein stilles „BEFUND: at-Grenzfall" mehr.
[ "$at_a" != "$at_b" ] || { echo "HARD-FAIL (DET-2): at-Ausnahme nicht demonstriert — beide Läufe tragen identischen at-Wert (P-7: aktive Ausnahme-Assertion)" >&2; exit 1; }
echo " BEFUND: at-Ausnahme aktiv (assertiert) — 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 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, 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)
# =====================================================================================
runlabel "DET-3: ZWEI_RUN_ABWEICHUNG — künstlich divergenter Lauf -> als AD-16-Klassifikationsdefekt klassifiziert und textuell benannt (kein Rauschen); korrigierter zweiter Lauf == erster Lauf (erneut bestätigt)"
# Referenz-Run (deterministische Bundle-State-Projektion)
run3_ref() {
isolate run3ref
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"
cat > wiki/alpha.md <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: wow-compiler/0.1.0
at: AT
---
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
git add -A && git commit -qm "Run: alpha-Update (Referenz)"
echo "SHA=$(git show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1)"
}
# Divergenter Run: veränderter Body-Content (RAUSCHEN-fremde Abweichung — ein deterministischer
# Bestandteil weicht ab, hier eine andere Formulierung der Aussage) trotz gleichem Git-State+Input.
run3_div() {
isolate run3div
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"
cat > wiki/alpha.md <<'EOF'
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: wow-compiler/0.1.0
at: AT
---
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
git add -A && git commit -qm "Run: alpha-Update (divergent)"
echo "SHA=$(git show 'HEAD:wiki/alpha.md' | sha256sum | cut -d' ' -f1)"
}
R3=$(run3_ref)
D3=$(run3_div)
echo "Referenz-Run: $R3"
echo "Divergenter Lauf: $D3"
SHA_REF=$(echo "$R3" | cut -d= -f2)
SHA_DIV=$(echo "$D3" | cut -d= -f2)
[ -n "$SHA_REF" ] && [ -n "$SHA_DIV" ] || { echo "HARD-FAIL (DET-3): Hashes leer (Vakuum)" >&2; exit 1; }
if [ "$SHA_REF" = "$SHA_DIV" ]; then
echo "HARD-FAIL (DET-3): der divergente Lauf blieb inspiziert ununterscheidbar (Abweichung nicht erkannt; §5.14 Pkt. 4 verlangt Klassifikation)" >&2
exit 1
fi
echo "NFR-4-Benennung (textuell): ZWEI_RUN_ABWEICHUNG — Lauf 'divergent' weicht ausserhalb der benannten at-Ausnahme vom Referenz-Lauf ab (unterschiedlicher Content-Hash $SHA_DIV != $SHA_REF bei gleichem Git-State + gleichem Eingabeset) => AD-16-Klassifikationsdefekt (FT-10/AD-17h-AC), kein Rauschen; der Run wird als Fehler im Klassifikations-Mechanismus behandelt, textuell benannt und korrigiert/zurückgerollt (§5.14 Pkt. 4, Zustands-Restaurations-Invariante §5.13 Pkt. 3)"
echo "--- Korrigierter zweiter Lauf: rollt zurueck und wiederholt deterministisch (== Referenz) ---"
rollback3() {
git checkout -q "$BASE" -- wiki/ 2>/dev/null || true
git reset -q --hard "$BASE"
git clean -qfd wiki
}
rollback3
R3B=$(run3_ref)
SHA_REFB=$(echo "$R3B" | grep '^SHA=' | cut -d= -f2)
[ "$SHA_REF" = "$SHA_REFB" ] || { echo "HARD-FAIL (DET-3): korrigierte Wiederholung weicht von der Referenz ab (AD-17h verletzt)" >&2; exit 1; }
echo " BEFUND: korrigierter Lauf == Referenz-Lauf (SHA $SHA_REF) — der Vertrag ist nach Korrektur erneut bestätigt (Pkt. 4: 'Befund wird (ggf. als korrigierter zweiter Lauf) erneut bestätigt')"
echo "RESULT: PASS — DET-3: ZWEI_RUN_ABWEICHUNG — künstlich divergenter Lauf wurde als AD-16-Klassifikationsdefekt erkannt (Content-Hash-Differenz bei gleichem State+Input — kein Rauschen, keine Umgebungs-Streuung) und textuell benannt (NFR-4); korrigierter zweiter Lauf reproduziert den Referenz-Bundle-State (Zustands-Restaurations-Invariante, §5.13 Pkt. 3; §5.14 Pkt. 4)"
# =====================================================================================
# DET-4 AT_GAP_AUSNAHME (§5.14 Pkt. 3)
# =====================================================================================
runlabel "DET-4: AT_GAP_AUSNAHME — generated.at-Wanduhr-Gap ist die einzige benannte Ausnahme; zwei Runs mit unterschiedlichem at -> diff ausschließlich auf der at-Zeile; nach at-Maskierung byte-identisch; jede andere Differenz = AD-16-Klassifikationsdefekt"
isolate det4
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"
mkrun4() { # $1 = at-Wert — schreibt alpha mit EXAKT unterschiedlichen at-Werten
cat > wiki/alpha.md <<EOF
---
type: concept
sources:
- resource: raw/alpha-v1.md
id: s1
- resource: raw/alpha-v2.md
id: s3
generated:
by: wow-compiler/0.1.0
at: $1
---
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
}
mkrun4 "2026-08-16T09:00:00Z"
git add -A && git commit -qm "Run at=A"
mkrun4 "2026-08-19T23:59:59Z"
git add -A && git commit -qm "Run at=B"
# diff zwischen beiden Run-Zuständen: ausschließlich die at-Zeile
DABC=$(git diff HEAD~1 HEAD -- wiki/alpha.md)
[ -n "$DABC" ] || { echo "HARD-FAIL (DET-4): die zwei Runs sind identisch — at-Gap nicht demonstriert (Vakuum)" >&2; exit 1; }
non_at=$(echo "$DABC" | grep '^[-+][^-+]' | grep -v '^[-+] at: ' | grep -v '^[+-][+-]')
if [ -n "$non_at" ]; then
echo "HARD-FAIL (DET-4): der diff zwischen den zwei Runs betrifft BESTANDTEILE ausserhalb der at-Ausnahme:" >&2
echo "$non_at" | sed 's/^/ /' >&2
exit 1
else
echo " BEFUND: diff ausschließlich auf der at-Zeile (einzige benannte Ausnahme, §5.14 Pkt. 3)"
fi
echo "--- Nach at-Maskierung byte-identisch (restliche Bundle-Bestandteile) ---"
H1=$(git show HEAD~1:wiki/alpha.md | sed 's/^ at: .*/ at: AT/' | sha256sum | cut -d' ' -f1)
H2=$(git show HEAD:wiki/alpha.md | sed 's/^ at: .*/ at: AT/' | sha256sum | cut -d' ' -f1)
[ "$H1" = "$H2" ] || { echo "HARD-FAIL (DET-4): nach at-Maskierung weichen die Runs ab (bestandteil über at hinaus; §5.14 Pkt. 3 verlangt byte-Identität der übrigen Teile)" >&2; exit 1; }
echo " BEFUND: nach at-Maskierung byte-identisch (SHA $H1) — dokumentierte Ausnahme, kein stiller Ausschluss"
echo "--- Negative Kontrolle: eine Differenz AUSSERHALB der Ausnahme ist ein AD-16-Klassifikationsdefekt ---"
mkrun4 "2026-08-16T09:00:00Z"
printf '\nAndere Aussagenformulierung.\n' >> wiki/alpha.md
git add -A && git commit -qm "Run at=B' (Differenz im Body)"
non_at2=$(git diff HEAD~1 HEAD -- wiki/alpha.md | grep '^[-+][^-+]' | grep -v '^[-+] at: ' | grep -v '^[+-][+-]')
[ -n "$non_at2" ] || { echo "HARD-FAIL (DET-4): Body-Differenz wurde nicht erkannt (Negativkontrolle leer)" >&2; exit 1; }
echo "NFR-4-Benennung (textuell): Body-Differenz ausserhalb der at-Ausnahme-Menge => AD-16-Klassifikationsdefekt (kein Rauschen), textuell benannt (§5.14 Pkt. 3/4)"
echo "RESULT: PASS — DET-4: AT_GAP_AUSNAHME — generated.at-Wanduhr-Gap ist die einzige benannte Differenz im Bundle-State-Vergleich (A0-20-Konvention, §5.9 Pkt. 2/§5.10 Pkt. 8): zwei Runs diffieren ausschließlich in der at-Zeile, nach at-Maskierung byte-identisch; jede andere Differenz liegt ausserhalb der Ausnahme = AD-16-Klassifikationsdefekt (Negativkontrolle, §5.14 Pkt. 3)"
# =====================================================================================
# DET-5 EM_DASH (§3.2 Pkt. 1b, §5.14 Pkt. 5)
# =====================================================================================
runlabel "DET-5: EM_DASH — Em-Dash — normalisiert auf dieselbe canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen (§3.2 Pkt. 1b: Kollaps-Klasse [-–— _] -> -)"
# Term-Basis mit Em-Dash / En-Dash / Bindestrich / Unterstrich / Leerzeichen-Separator
EM=$(norm "wissen — relevanz")
EN=$(norm "wissen relevanz")
HY=$(norm "wissen - relevanz")
US=$(norm "wissen_relevanz")
SP=$(norm "wissen relevanz")
[ "$EM" = "$EN" ] || { echo "HARD-FAIL (DET-5): Em-Dash-Form != En-Dash-Form" >&2; exit 1; }
[ "$EM" = "$HY" ] || { echo "HARD-FAIL (DET-5): Em-Dash-Form != Bindestrich-Form" >&2; exit 1; }
[ "$EM" = "$US" ] || { echo "HARD-FAIL (DET-5): Em-Dash-Form != Unterstrich-Form" >&2; exit 1; }
[ "$EM" = "$SP" ] || { echo "HARD-FAIL (DET-5): Em-Dash-Form != Leerzeichen-Form" >&2; exit 1; }
case "$EM" in
*"—"*) echo "HARD-FAIL (DET-5): Em-Dash blieb in der canonischen Form erhalten (Kollaps-Klasse greift nicht)" >&2; exit 1;;
esac
echo " canonische Form (alle fünf Separatoren): '$EM'"
echo " Em-Dash-Term kollabiert auf dieselbe canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen (kein stiller Ausschluss, §3.2 Pkt. 1b)"
echo "RESULT: PASS — DET-5: EM_DASH — Em-Dash — ist in der Kollaps-Klasse [-–— _] -> - (§3.2-Pkt.-1b-Ergänzung, §5.14 Pkt. 5): Term mit Em-Dash erhält dieselbe canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen-Varianten — deterministisch, keine unterschiedliche canonische Form je Separator"
# =====================================================================================
# DET-6 KOLLAPS (§3.2 Pkt. 1b, §5.14 Pkt. 5)
# =====================================================================================
runlabel "DET-6: KOLLAPS — Kollaps-Reichweite: Läufe (a--b), führende (-x), trailende (x-) -> Kollaps auf genau ein - + Trim (§3.2 Pkt. 1b)"
[ "$(norm "a--b")" = "a-b" ] || { echo "HARD-FAIL (DET-6): Lauf a--b kollabiert nicht auf a-b (norm='$(norm "a--b")')" >&2; exit 1; }
[ "$(norm "-x")" = "x" ] || { echo "HARD-FAIL (DET-6): führender Separator -x wird nicht getrimmt (norm='$(norm "-x")')" >&2; exit 1; }
[ "$(norm "x-")" = "x" ] || { echo "HARD-FAIL (DET-6): trailender Separator x- wird nicht getrimmt (norm='$(norm "x-")')" >&2; exit 1; }
[ "$(norm "a--b")" = "a-b" ] && [ "$(norm "a---b")" = "a-b" ] && [ "$(norm "---x---")" = "x" ] || { echo "HARD-FAIL (DET-6): Läufe/Trim-Kombination inkonsistent" >&2; exit 1; }
echo " Läufe a--b -> '$(norm "a--b")'; führend -x -> '$(norm "-x")'; trailend x- -> '$(norm "x-")'"
echo "RESULT: PASS — DET-6: KOLLAPS — Kollaps-Reichweite deterministisch (§3.2 Pkt. 1b, §5.14 Pkt. 5): Läufe kollabieren auf genau ein -, führende/trailende Separatoren werden getrimmt — glaubhaft re-executierbar, keine offene Reichweiten-Frage"
# =====================================================================================
# DET-7 MATCH_SCOPE (§3.2 Pkt. 2a, §5.14 Pkt. 5)
# =====================================================================================
runlabel "DET-7: MATCH_SCOPE — Stufe a matcht ganze Wörter über den Body, exklusive YAML-Frontmatter (Substring-/Frontmatter-Treffer ausgeschlossen, §3.2 Pkt. 2a)"
isolate det7
# Concept: Body enthält "kopplung" als GANZES Wort; Frontmatter enthält "kopplung" als
# Substring im Quell-Pfad raw/kopplung-v1.md (Frontmatter-Treffer-Wächter).
cat > wiki/alpha.md <<'EOF'
---
type: concept
sources:
- resource: raw/kopplung-v1.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Alpha als Wissenseinheit: hier steht einzig das ganz Wort kopplung ohne weitere Bindung.
EOF
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: das Wort entkopplung belegt nur einenerweiternden Teil — kein eigener ganzer Treffer.
EOF
git add -A && git commit -qm "Stufe-a-Flex"
# 1) Substring-Kontrolle: 'kopplung' (ganz) trifft alpha; 'opplung' (Substring) trifft nichts
HIT1=$(match_stufe_a "kopplung" wiki/alpha.md wiki/gamma.md)
[ "$HIT1" = "alpha" ] || { echo "HARD-FAIL (DET-7): ganzes-Wort-Match 'kopplung' liefert '$HIT1' (Soll: alpha; Substring-/Gamma-Treffer unerlaubt)" >&2; exit 1; }
HIT2=$(match_stufe_a "opplung" wiki/alpha.md)
[ -z "$HIT2" ] || { echo "HARD-FAIL (DET-7): Substring-Match 'opplung' liefert einen Kandidaten (Stufe a matcht ganze Wörter, §3.2 Pkt. 2a)" >&2; exit 1; }
# 2) Frontmatter-Exklusion: Suchbegriff exakt gleich einem Frontmatter-RESOURCE-Substring
# (raw/kopplung-v1.md enthält 'kopplung') — zugleich im Body von alpha steht 'kopplung'
# nur als ganzes Wort => Treffer genau EINMAL (alpha). Der Frontmatter ist exkludiert,
# ein zusätzlicher Body-Treffer erzeugt keinen zweiten Kandidaten — wir prüfen, dass
# das Frontmatter-Vorkommen keinen falschen Kandidaten (z. B. durch RegExp über die ganze
# Datei inkl. Frontmatter) erzeugt. Konkret: wir legen einen zweiten Concept-Body VOR, der
# 'kopplung' NUR im Frontmatter traegt (Quell-Pfad raw/kopplung-mirror.md) — Stufe a muss
# ihn verwerfen, weil der Frontmatter exkludiert ist.
cat > wiki/mirror.md <<'EOF'
---
type: concept
sources:
- resource: raw/kopplung-mirror.md
id: s1
generated:
by: wow-compiler/0.1.0
at: 2026-08-16T09:00:00Z
---
Gamma-Mirror ohne Kopplungs-Begriff im Bodytext.
EOF
git add -A && git commit -qm "Frontmatter-only-Kandidat"
HIT3=$(match_stufe_a "kopplung" wiki/alpha.md wiki/gamma.md wiki/mirror.md)
[ "$HIT3" = "alpha" ] || { echo "HARD-FAIL (DET-7): Frontmatter-Treffer erzeugt Kandidat (mirror) — Stufe a matcht nur Body, exklusive YAML-Frontmatter (§3.2 Pkt. 2a)" >&2; exit 1; }
# 3) Direkte Formel-Probe der operativen Stufe-a-Formel (§3.2-Pkt.-2a-Zweistufen-Mechanik):
# Sweep mit der Formel -> dann deterministischer Scope-Filter (ganze Wörter, Body, after second ---).
SWEEP=$(grep -rl 'kopplung' --exclude=log.md wiki/ | LC_ALL=C sort)
# Sweep-muss alpha, gamma (Substring entkopplung) und mirror (Frontmatter-only) finden —
# der Scope-Filter verwirft gamma+mirror; als Kandidaten bleiben die Body-ganz-Wort-Treffer.
FILTERED=$(for f in $SWEEP; do
if match_stufe_a "kopplung" "$f" | grep -q .; then basename "$f" .md; fi
done | LC_ALL=C sort | tr '\n' ' ')
FILTERED="${FILTERED% }"
[ "$FILTERED" = "alpha" ] || { echo "HARD-FAIL (DET-7): operative Stufe-a-Zweistufen-Mechanik liefert '$FILTERED' (Soll: alpha) — Scope-Filter unvollständig (§3.2 Pkt. 2a, §5.14 Pkt. 5)" >&2; exit 1; }
echo " Direkte Formel-Probe: Sweep (grep -rl) -> Scope-Filter verwirft Substring-/Frontmatter-Treffer -> Kandidat: alpha"
echo "RESULT: PASS — DET-7: MATCH_SCOPE — Stufe a matcht ganze Wörter (Wortgrenzen) über den Body, exklusive YAML-Frontmatter: Substring-Treffer ('opplung') und Frontmatter-only-Kandidaten (mirror) liefern keine Kandidaten (§3.2 Pkt. 2a, §5.14 Pkt. 5) — deterministischer Stufe-a-Scope, keine falschen Kandidaten; operative Zweistufen-Mechanik (Sweep+Filter) abgesichert"
# =====================================================================================
# DET-8 ORPHAN (§5.10 Pkt. 8, §5.14 Pkt. 5)
# =====================================================================================
runlabel "DET-8: ORPHAN — neu committete raw/-Evidenz ohne Ziel-Pfad-Treffer -> unzugeordnet, log.md-verwaist protokolliert (Datumsgruppe, <Baseline-Commit>), kein Banner, keine stille Vorbearbeitung, keine eigenständige Anlage (AD-16-Default)"
isolate det8
cat > raw/orphan-v1.md <<'EOF'
### S-1
Evidenz: gänzlich fremdes Thema, das in keinem bestehenden Concept-Body Wort-Treffer hat.
EOF
git add -A && git commit -qm "Verwaiste Evidenz"
# Ziel-Pfad-Treffer-Prüfung: Term über die neuen Quellen enthält 'fremdes-thema' — kein
# bestehender Concept-Body trifft (Stufe a, deterministisch); kein Update-Kandidat.
CAND=$(match_stufe_a "fremdes" wiki/alpha.md wiki/gamma.md)
[ -z "$CAND" ] || { echo "HARD-FAIL (DET-8): verwaiste Evidenz erzielt einen Ziel-Pfad-Treffer (Kein-Orphan erwartet)" >&2; exit 1; }
# Reconcile-Orphan-Regel (§5.10 Pkt. 8 / §5.14 Pkt. 5): deterministisch aus dem committeten
# Git-State (Zuwachs gg. <Baseline-Commit>) als verwaist befunden -> datumsgruppierter
# log.md-Eintrag (Header YYYY-MM-DD, Quell-Pfad + <Baseline-Commit>); keine eigenständige Anlage.
log_bullet "- Reconcile-Orphan (Story 3.8; §5.10 Pkt. 8 / §5.14 Pkt. 5): raw/orphan-v1.md (Zuwachs gg. <Baseline $BASE>) ohne Ziel-Pfad-Treffer — unzugeordnet, verwaist protokolliert; kein Banner, keine stille Vorbearbeitung, keine eigenständige Anlage (AD-16-Default, Epic-4-Interface)"
echo "--- Kein Banner / keine stille Vorbearbeitung / keine eigenständige Anlage ---"
if [ -f wiki/orphan.md ]; then
echo "HARD-FAIL (DET-8): verwaiste Evidenz wurde eigenständig als Concept angelegt (verboten, AD-16-Default)" >&2
exit 1
fi
# Nur log.md ist mutiert (Orphan-Eintrag) — Erhaltungs-Invariante §5.9 Pkt. 5 (log.md ist
# erlaubtes Mitglied des Diff-Selbsttest-Satzes); keine weiteren Mutations-Pfade.
MUT=$( { git diff --name-only "$BASE" -- wiki/ ; git status --porcelain -- wiki/ | awk '{print $2}'; } | sed -e 's|^wiki/||' -e 's|\.md$||' | LC_ALL=C sort -u)
[ "$MUT" = "log" ] || { echo "HARD-FAIL (DET-8): Verwaist-Run mutiert ausserhalb {log} — Pfade: [$MUT]" >&2; exit 1; }
grep -qF "Reconcile-Orphan" wiki/log.md || { echo "HARD-FAIL (DET-8): Verwaist-Protokoll fehlt in log.md (§5.10 Pkt. 8)" >&2; exit 1; }
grep -qF "## 2026-08-20" wiki/log.md || { echo "HARD-FAIL (DET-8): log.md-Eintrag nicht in Datumsgruppe (Vertrag §5)" >&2; exit 1; }
grep -qF "$BASE" wiki/log.md || { echo "HARD-FAIL (DET-8): <Baseline-Commit> fehlt im Verwaist-Eintrag (§5.10 Pkt. 8, deterministisch auflösbar D-2)" >&2; exit 1; }
echo " BEFUND: raw/orphan-v1.md bleibt in raw/ unangetastet (AD-3); log.md trägt datumsgruppierten Verwaist-Eintrag mit <Baseline-Commit>"
echo "RESULT: PASS — DET-8: ORPHAN — neu committete raw/-Evidenz ohne Ziel-Pfad-Treffer bleibt unzugeordnet und wird als datumsgruppierter log.md-Eintrag (Quell-Pfad + <Baseline-Commit>) verwaist protokolliert (§5.10 Pkt. 8 / §5.14 Pkt. 5): kein Banner, keine stille Vorbearbeitung, keine eigenständige Anlage (AD-16-Default) — Erhaltungs-Invariante gewahrt (nur log.md mutiert)"
echo
echo "===== Sandbox abgeschlossen (DET-1..DET-8) ====="
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,144 @@
---
title: 'Story 3.10 — Inkrementelle Update- und Synthese-Erhaltung absichern'
type: 'feature'
created: '2026-08-21'
status: 'done'
baseline_commit: '8c43d3a09cdc10bc37ea9fdcd074d17d15262c44'
review_loop_iteration: 0
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** §5.9 (Erweitern/Präzisieren/Korrigieren/No-Op, Story 3.1/3.3), §5.10 (Synthese, Story 3.4) und §5.15 (Routing-Tabelle, Story 3.9) verankern die Update-/Synthese-Mechanik und die Routing-Entscheidung — aber die **Erhaltungs-Absicherung im laufenden, gemischten Run fehlt als geschlossene Klammer**: (a) die **CONFIRMING-Grenze** ist offen — eine neue Source, die eine bestehende Aussage unabhängig bestätigt, trägt einen **neuen Evidenzanker** und darf **nicht** in den §5.9-No-Op („Evidenz bereits vollständig enthalten") fallen (AC-4 vs. AC-6); (b) **Korrigieren** dokumentiert die ersetzte Aussage samt Source-Basis noch nicht im Run-Receipt (AC-2); (c) der **Provenienz-/Link-Selbsttest** erwartet Deltas aus historischen, fest codierten Commits/Zählwerten statt aus dem aktuellen Run (AC-7); (d) der **Hold-Ausbau** ist auf „Home Story 3.10" delegiert (§5.15 Zelle 3 + Scope-Präzisierung „post-Reconcile-Orphan, Mehrziel", §5.10-Pkt.-8-/§5.14-Pkt.-5-Reconcile-Orphan-Regel): post-Reconcile-Orphan über **volle Stufen b/c** (nicht nur Stufe-a), **Mehrziel-Auflösung** (D-8-Mehrfach-Term-Vereinigung), und klassifikationspflichtige/widersprüchliche Evidenz muss bis Epic 4 **ohne Wissensmutation in einem benannten Hold erhalten** bleiben, mit **beiden Evidenzpfaden im Run-Receipt** (AC-8, NFR-7).
**Approach:** Neue Sektion **§5.16 „Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau (Story 3.10)"** (nach §5.15, vor §6): (1) **Kontinuitäts-Garantie** — Update/Synthese mutiert im bestehenden Pfad, kein thematisches Duplikat, Identität + Index-Link erhalten (AC-1); (2) **Korrigieren mit Run-Receipt-Trace** — ersetzte Wortlautfolge + Source-Basis im Run-Receipt (außerhalb Bundles, §5.14 Pkt. 2); mehrdeutige Korrektur → benannter Hold (Epic 4) (AC-2); (3) **Schutzbestandteile & Byte-Identität** — geschützte Bestandteile (gültige belegte Aussagen, §5.5-Inline-Verweise, `sources` gültiger Belege, §5.6-Links, human-`verified`) bleiben bei jedem Update/Synthese; nicht betroffene Concepts byte-identisch (AC-3); (4) **CONFIRMING-Konsolidierung** — bestätigende neue Source: Aussage genau einmal, alle Evidenzanker via §5.5-Multi-Beleg, `sources`-Zuwachs, `generated.at`-Bump, **kein NO_OP** (AC-4); (5) **gemeinsame Wissensrepräsentation** — §5.10 Pkt. 2/3/5 als Synthese-**Erhaltung** (Update auf bestehendes Concept = Erweiterung, keine Neuschreibung) (AC-5); (6) **byte-erhaltender NO_OP** — identische Evidenz **samt vollständiger Evidenzanker-Menge** → NO_OP; fehlender Anker → Pkt. 4 (AC-6); (7) **Provenienz- & Link-Selbsttest aus aktuellem Run** — Baseline = aktuelles `<Baseline-Commit>`, erwartete Deltas = Kandidatenliste Neu-Anlage `log.md` nachgeführte `index.md`; kein historischer Commit/globaler Zählwert normativ (AC-7); (8) **benannter Hold** — post-Reconcile-Orphan über alle Stufen (a/b/c), Mehrziel-Auflösung (Konsolidierung auf primäre Ziel-Repräsentation; sonst Hold), widersprüchliche/klassifikationspflichtige Evidenz ohne Wissensmutation, beide Evidenzpfade im Run-Receipt (AC-8).
## Boundaries & Constraints
**Always:**
- Nur `schema/compiler.md` mutiert (neue §5.16 + §7-Bullet-Erweiterung der Story-3.10-Verankerung + §8-Revisionslog **Revision 3.5** + Hold-Home-Nachführungen: §5.15-Zelle-3-Zeile + §5.15-Scope-Präzisierung, §5.10-Pkt.-8-/§5.14-Pkt.-5-Verweis). `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` **read-only** (AD-3); `schema/canonical-terms.md` bleibt append-only-Registry (keine Bestandsedits).
- Referenz statt Re-Negotiation: bestehende §5.9/§5.10/§5.15-Mechaniken bleiben **textuell unverändert** — §5.16 definiert die operationelle Erhaltungs-Ebene (CONFIRMING-Grenze, Schutzbestandteile, aktueller-Run-Selbsttest, Hold-Ausbau) darüber; die §5.9-Abgrenzungs-Reihenfolge `Korrigieren → Präzisieren → Erweitern → No-Op` bleibt die **einzige** Form-Wahl, synthese- und update-seitig unverändert.
- Kein neues Prädikat, keine neue §7-Invaliditätsklasse, keine fünfte Update-Form, kein neuer Frontmatter-/Format-Key; AD-16-Klassifikation/semantische Auflösung bleibt Epic 4; `raw/` unangetastet (AD-3).
- Sandbox-Nachweis re-executierbar (Muster sandbox-3-9, Exit 0), harte PASS/FAIL-Assertionen, kein Berührung des realen Ist-Baums.
**Ask First:**
- Umlaut-vs-Transkription-Divergenz im Match-Pfad schließen (bleibt benannter Defer — kein neuer Normalisierungs-Operand, append-only).
- Verhalten des `generated.at` (A0-20: einmalige `at`-Festlegung pro Run) ändern — bleibt unverändert bindend.
- Veränderung am §5.9-No-Op-Regeltext selbst (frozen, Story 3.1) — §5.16 ergänzt nur die operationelle CONFIRMING-Abgrenzung, kein Rework des frozen Textes.
**Never:**
- Nie Wissensmutation bei Orphan/Hold; nie stilles Löschen von Evidenz, Provenienz oder geschützten Bestandteilen; nie „Regenerate Everything" (AD-5/FR-12); nie hart kodierte erwartete Pläne/Concept-Bodies/Zählwerte als Beweis (AC-7, §5.14 Pkt. 2); nie Standalone/Eigene Runtime (D-3, AD-11); nie textueller Auto-Merge (AD-17c).
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|---------------------------|----------------|
| KONTINUITAET | neue Erkenntnis auf bestehenden Concept-Pfad | in-place Update/Synthese, kein Duplikat, Identität + Index-Link erhalten (AC-1) | kein Overwrite, kein neuer Link ohne echte Beziehung |
| KORRIGIEREN_RECEIPT | eindeutige aktuelle Evidenz ersetzt eine Aussage | in-place Korrektur; ersetzte Wortlautfolge + Source-Basis im Run-Receipt (§5.14 Pkt. 2) (AC-2) | mehrdeutig → benannter Hold, keine Mutation |
| GESCHUETZTE_BESTANDTEILE | Update/Synthese auf Concept mit gültigen Aussagen+Provenienz | geschützte Bestandteile erhalten; nicht betroffene Concepts byte-identisch (AC-3) | Verstoß = textuell benannt (NFR-4), Ghost-Diff-Rollback (§5.9 Pkt. 5) |
| CONFIRMING | neue Source bestätigt bestehende Aussage unabhängig | Aussage genau einmal, alle beitragenden Evidenzanker (§5.5-Multi-Beleg), `sources`-Zuwachs, `at`-Bump — **kein NO_OP** (AC-4) | kein Duplikat der Aussage, kein stiller Anker-Verlust |
| SYNTHESE_ERHALTUNG | mehrere redundante/ergänzende Sources | eine gemeinsame Wissensrepräsentation, claim-granulare gemischte Provenienz, keine Quelle-A/B-Aneinanderreihung (AC-5) | §5.10-Pkt.-5-Reflektiertheits-Selbsttest |
| NO_OP_BYTE_ERHALTEND | identische Evidenz samt vollständiger Evidenzanker im Body | byte-erhaltender NO_OP — keine Mutation, kein `at`-Bump, kein `sources`-Zusatz, kein `log.md`-Eintrag (AC-6) | fehlender Anker → CONFIRMING (Pkt. 4), nicht NO_OP |
| SELBSTTEST_AKTUELLER_RUN | Provenienz-/Link-Selbsttest bei Update/Synthese | Baseline = aktuelles `<Baseline-Commit>`; erwartete Deltas = Kandidatenliste Neu-Anlage `log.md` `index.md` (AC-7) | kein historischer Commit/Zählwert normativ; Abweichung = textuell benannt |
| ORPHAN_HOLD_VOLLE_STUFEN | unzugeordnete Evidenz, erkannt über Stufe a/b/c | benannter Hold ohne Wissensmutation, datumsgruppierter `log.md`-Eintrag (`<Baseline-Commit>`), beide Evidenzpfade im Run-Receipt (AC-8) | kein Banner, keine stille Bearbeitung, keine eigenständige Anlage |
| MEHRZIEL | eine Einheit löst über mehrere Terme auf mehrere Ziele auf | Konsolidierung auf primäre Ziel-Repräsentation; kein Duplikat; ohne dominantes Ziel → benannter Hold (AC-8) | mehrdeutig → fail-closed (Hold), beide Pfade im Receipt |
## Code Map
- `schema/compiler.md`**primär mutiert** (D-3): neue Sektion **§5.16 „Inkrementelle Update- & Synthese-Erhaltung + Hold-Ausbau (Story 3.10)"** (nach §5.15 Z. 398, vor §6 Z. 402; Pkt. 18) — alle Zellen referenzieren die bestehenden Mechaniken Wortlaut-unverändert; **Hold-Home-Nachführungen**: §5.15-Zelle-3-Zeile (Z. 391 „Hold-Ausbau Home Story 3.10") und §5.15-Scope-Präzisierung (Z. 400 „Der Hold-Ausbau (post-Reconcile-Orphan, Mehrziel) verbleibt Story 3.10") → „in §5.16 verankert (Story 3.10)"; §5.10-Pkt.-8-D-4-Bullet (Z. 302 „hat seinen Home in Story 3.10") → §5.16-Verankerung; §7-Bullet-Erweiterung (Story-3.10-Verankerung, analog §5.15-Ergänzung); §8-Revisionslog **Revision 3.5**. Bestehende §5.9/§5.10/§5.15-Mechaniken bleiben **textuell unverändert** (Referenz statt Re-Negotiation).
- `_bmad-output/implementation-artifacts/sandbox-3-10/run-sandbox.sh`**neu** (re-executierbar, Muster `sandbox-3-9/run-sandbox.sh`, Exit 0): Szenarien E-1..E-9 (Matrix-Zeilen als harte Assertionen; CONFIRMING ≠ NO_OP mit Byte-Vergleich; NO_OP byte-erhaltend; Korrigieren-Receipt-Trace; aktueller-Run-Selbsttest; Orphan über Stufe-a/b/c; Mehrziel; Zwei-Run-Identität).
- `_bmad-output/implementation-artifacts/sprint-status.yaml`**mutiert**: Key `3-10-inkrementelle-update-und-synthese-erhaltung-absichern` `backlog``review` (Implementierungs-Commit; finaler `done`-Flip im Step-05-Status-Sync nach konvergiertem Review-Loop); `last_updated` (Format `MM-DD-YYYY HH:MM`).
- `_bmad-output/implementation-artifacts/deferred-work.md`**append**: Defer „Orphan voller Stufen b/c + Mehrziel" (Hold-Home Story 3.10) → aufgegriffen/geschlossen; Umlaut-Defer bleibt offen (wie notiert, kein Instruktions-Defekt).
- `wiki/log.md`**append** (Vertrag §5, bestehende Bullets unverändert): Story-3.10-Eintrag (Verankerung §5.16, Sandbox-Nachweis E-1..E-9, Status-Flip, Validator-Verdikt).
- `_bmad-output/implementation-artifacts/epic-3-context.md`**mutiert** (gemäß genehmigtem Sprint-Change-Proposal 2026-08-20; Header „Edit freely"): Technical Decision Z. 39 „Deterministische Relevanz & Routing" um §5.16-/Story-3.10-Ist ergänzt (Erhaltungs-Absicherung + Hold-Ausbau verankert).
**Read-only evidence (AD-3):** `schema/validator.md` (Rev 9), `schema/wiki-compiler.md`, `adapters/`, `raw/` (z. B. `raw/architecture-spine/architecture-spine-2026-08-14.md` AD-4/AD-5/AD-16; `raw/epics/epics-2026-08-14.md` FR-4/FR-6/FR-7/FR-12, NFR-7). `schema/canonical-terms.md` append-only unangetastet.
## Tasks & Acceptance
**Execution:**
- [x] `schema/compiler.md` — §5.16 einfügen (nach §5.15, vor §6): Pkt. 18 gemäß Intent; keine neuen Prädikate/§7-Klassen/Keys; Hartung der CONFIRMING-vs-NO_OP-Grenze; §7-Bullet und §8-Revisionslog **Revision 3.5** nachführen; Hold-Home-Nachführungen §5.15-Zelle-3/Scope-Präzisierung, §5.10-Pkt.-8-D-4
- [x] `_bmad-output/implementation-artifacts/sandbox-3-10/run-sandbox.sh` — E-1..E-9, harte PASS/FAIL, CONFIRMING ≠ NO_OP byte-bewiesen, Zwei-Run nicht-vakuum, Exit 0
- [x] `_bmad-output/implementation-artifacts/sprint-status.yaml` — Key 3-10 → `review` (Impl-Commit; finaler `done`-Flip Step-05); `deferred-work.md` — Orphan-voller-Stufen/Mehrziel-Defer aufgegriffen; `wiki/log.md` — Story-3.10-Eintrag (Verankerung, Sandbox, Validator-Verdikt)
- [x] `_bmad-output/implementation-artifacts/epic-3-context.md` — Technical Decision Z. 39 um §5.16-Ist ergänzt
**Acceptance Criteria:**
- Given eine passende neue Erkenntnis zu einem bestehenden Concept, when der Run sie verarbeitet, then wird das bestehende Concept in-place erweitert oder präzisiert und kein thematisches Duplikat angelegt (AC-1).
- Given explizite aktuelle Evidenz, die eine bestehende Aussage eindeutig korrigiert, ohne dass zwischen weiterhin gültigen Sources ein Widerspruch verbleibt, when der Run sie verarbeitet, then wird das bestehende Concept in-place korrigiert und die ersetzte Aussage samt Source-Basis bleibt im Run-Receipt nachvollziehbar; mehrdeutige Fälle gehen in den Hold für Epic 4 (AC-2).
- Given ein betroffenes Concept mit weiterhin gültigen Aussagen und Provenienz, when es aktualisiert wird, then bleiben diese geschützten Bestandteile erhalten; nicht betroffene Concepts bleiben byte-identisch (AC-3).
- Given eine neue Source, die eine bestehende Aussage unabhängig bestätigt, when synthetisiert wird, then erscheint die Aussage genau einmal und trägt alle beitragenden Evidenzanker; die neue Source ist kein `NO_OP` (AC-4).
- Given mehrere Sources mit redundanten und ergänzenden Aussagen, when der Run synthetisiert, then entsteht eine gemeinsame Wissensrepräsentation mit claim-granularer gemischter Provenienz — keine Source-A/Source-B-Aneinanderreihung (AC-5).
- Given eine bereits vollständig repräsentierte identische Source samt Evidenzanker, when sie erneut verarbeitet wird, then ist der Run für dieses Wissen byte-erhaltend (`NO_OP`) (AC-6).
- Given der Provenienz- und Link-Selbsttest, when erwartete Deltas bestimmt werden, then stammen Baseline und Erwartung aus dem aktuellen Run; kein historischer, fest codierter Commit oder globaler Zählwert ist normativ (AC-7).
- Given klassifikationspflichtige oder widersprüchliche Evidenz, when der Run sie erreicht, then wird sie bis Epic 4 ohne Wissensmutation in einem benannten Hold erhalten; beide Evidenzpfade bleiben im Run-Receipt nachvollziehbar (NFR-7) — der post-Reconcile-Orphan wird über alle Erhebungs-Stufen (a/b/c) geschlossen und Mehrziel-Einheiten werden konsolidiert oder gehalten (AC-8).
## Spec Change Log
- **Draft (2026-08-21, Step-02-Plan):** Erstentwurf gemäß bmad-build-Workflow für Story 3.10 — Erhaltungs-Absicherung (AC-1..AC-7) + Hold-Ausbau (AC-8, Hold-Home aus §5.15-Zelle-3/Scope-Präzisierung und §5.10-Pkt.-8-D-4). Noch keine Review-Loop-Einträge; `status: draft`, Review-Loop-Iteration 0.
</frozen-after-approval>
## Design Notes
**Warum §5.16 als eigene Sektion, nicht §5.9/§5.10-Umbau?** §5.9 (frozen, Story 3.1/3.3) und §5.10 (Story 3.4) sind textuell verankerte Mechaniken; §5.15 (Story 3.9) schließt die Routing-Ebene. §5.16 ist die **Erhaltungs-Klammer** darüber — dieselbe Fugen-Identitäts-Präzedenz wie §5.14/§5.15: die operationelle Ebene und der Hold-Ausbau werden als neue Sektion verankert, bestehende Regeltexte Wortlaut-unverändert referenziert (Re-Negotiation-Vermeidung).
**CONFIRMING ≠ NO_OP (AC-4/AC-6-Grenze ist der Kernfnd der Erhaltung).** Der §5.9-No-Op („engere Auslegung: trifft nur, wenn die neue Evidenz keine Aussage trägt, die im Body nicht bereits als belegte Aussage vorhanden ist — Term-/Stellen-Abgleich mit §5.5-Inline-Verweisen") muss operationell gegen den zunächst verblüffend ähnlichen CONFIRMING-Fall abgegrenzt werden: Eine bestätigende Source trägt **denselben Inhalt, aber einen neuen Evidenzanker** (anderer `raw/`-Pfad/-Stelle). Der Stellen-Abgleich schlägt also auf den **Anker**, nicht auf den Inhalt: ein fehlender Anker = echter neuer Beleg → Konsolidierungs-Update (Pkt. 4), kein NO_OP. NO_OP bleibt AC-6 vorbehalten: **vollständige** Evidenzanker-Menge bereits im Body präsent. Diese Abgrenzung ist rein operationell (neue §5.16-Ebene) — der frozen §5.9-Regeltext bleibt unverändert.
**Run-Receipt trägt die Korrektur-/Hold-Spur (AC-2/AC-8).** Der §5.14-Pkt.-2-Run-Receipt liegt außerhalb des Bundles und ist damit der korrekte Ort für ersetzte Wortlautfolgen (Korrigieren) und die beiden Evidenzpfade eines Holds (ursprünglicher + widersprechender) — das Bundle selbst enthält laut AD-16-Default beide Behauptungen, ohne Korrektur-Klassifikation (Epic 4). NFR-7 („ohne künstliche Gewissheit") wird damit mechanisch nachvollziehbar: Der Hold ist ein **benannter**, im `log.md` datumsgruppierter Eintrag + Receipt-Führung, nie eine stille Löschung oder eigenständige Concept-Anlage.
**Aktueller-Run-Selbsttest (AC-7) löst die historischen Fixwerte auf.** Die §5.6-Formeln 14 tragen historische Zählwerte/Baselines als dokumentarische Pins (Story 2.3/2.4). AC-7 macht für die Erhaltungs-Probe die **Relativ-Ableitung** normativ: Baseline = `<Baseline-Commit>` des aktuellen Runs (dynamische Extraktion), erwartete Delta-Menge = Kandidatenliste Neu-Anlage `log.md` nachgeführte `index.md` — kein globaler Zählwert liefert noch eine normative Erwartung mit.
**Hold-Ausbau = post-Reconcile-Orphan über volle Stufen + Mehrziel.** §5.15 Zelle 3 ist der Routing-Anker (fail-closed, `raw/` unangetastet); §5.10 Pkt. 8 ist die deterministische Reconcile-Orphan-Regel (log.md-verwaist-Eintrag). §5.16 Pkt. 8 schließt die von §5.15/§5.14 offen gelassenen Fälle: (a) Orphan-Erkennung über **alle drei** Erhebungs-Stufen (§3.2 a/b/c — die Story-3.9-Sandbox übte nur Stufe-a), (b) **Mehrziel** aus der D-8-Mehrfach-Term-Vereinigung (Eine Einheit → mehrere gleichgewichtige Ziele: Konsolidierung auf eine primäre Ziel-Repräsentation, nie Duplikat-Inhalt in mehreren Concepts; ohne dominantes Ziel → fail-closed Hold), (c) benannter Hold mit beiden Evidenzpfaden im Receipt.
## Verification
**Commands (re-executierbar, ab Workspace-Root):**
1. `bash _bmad-output/implementation-artifacts/sandbox-3-10/run-sandbox.sh` — expected: E-1..E-9 harte PASS/FAIL, CONFIRMING ≠ NO_OP byte-bewiesen (Aussage genau einmal, neuer Anker zusätzlich, kein `NO_OP`-Pfad), NO_OP byte-erhaltend (vollständige Anker-Menge), Korrigieren-Receipt-Trace vorhanden, aktueller-Run-Selbsttest ohne hart kodierte Erwartung, Orphan über Stufe-a/b/c + Mehrziel-Hold, Zwei-Run-Identität nicht-vakuum, Exit 0.
2. `grep -n "§5.16\|Revision 3.5" schema/compiler.md` — §5.16-Sektion + Revisionslog-Eintrag; `grep -n "Inkrementelle Update- & Synthese-Erhaltung" schema/compiler.md` — Überschrift wortgleich; Hold-Home-Nachführung: `grep -n "in §5.16 verankert" schema/compiler.md`.
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.
4. Validator-Lauf: alle `wiki/`-Dateien SUCCESS (keine Inhalts-Mutation); auf `wiki/` begrenzt (`git status --porcelain -- wiki/`): ausschließlich `wiki/log.md` — Erhaltungs-Invariante §5.9 Pkt. 5 gewahrt.
**Zu beachten (beim step-04-Review):** (a) bestehende §5.9/§5.10/§5.15-Mechaniken **textuell unverändert** (Referenz statt Re-Negotiation — §5.16 verankert nur die Erhaltungs-/Hold-Ebene); (b) die vorhandene Revisionslog-Nummer ist **3.4** (Story 3.9) — **Revision 3.5 ist für Story 3.10 frei**; (c) Umlaut-vs-Transkription-Defer bleibt offen (kein stiller Abschluss); (d) §5.16 führt **kein** neues Prädikat/keine neue §7-Klasse/keinen neuen Key ein; (e) AD-16-Klassifikation bleibt Epic 4.
## Suggested Review Order
**Design-Intent & Einstieg**
- Erhaltungs-Klammer als eigene Sektion (Story 3.10) — operationelle Ebene über textuell unveränderten §5.9/§5.10/§5.15-Mechaniken, kein Umbau.
[`compiler.md:402`](../../schema/compiler.md#L402)
**Instruktions-Verankerung (§5.16)**
- CONFIRMING ≠ NO_OP: neue Source mit neuem Evidenzanker = Konsolidierungs-Update, nie No-Op (AC-4).
[`compiler.md:402`](../../schema/compiler.md#L402)
- NO_OP byte-erhaltend nur bei vollständiger Anker-Menge (AC-6); Korrigieren mit Run-Receipt-Trace (AC-2); Schutzbestandteile & Byte-Identität (AC-3).
[`compiler.md:402`](../../schema/compiler.md#L402)
- Benannter Hold über Erhebungs-Stufen a/b/c + Mehrziel-D-8-Vereinigung, beide Evidenzpfade im Receipt (AC-8).
[`compiler.md:402`](../../schema/compiler.md#L402)
- Selbsttest aus aktuellem Run: Baseline = `<Baseline-Commit>`, erwartete Deltas, keine historischen Zählwerte (AC-7).
[`compiler.md:402`](../../schema/compiler.md#L402)
**Anker-Synchronisierung**
- Hold-Home-Nachführungen: §5.15 Zelle 3 + Scope-Präzisierung, §5.10 Pkt. 8 D-4-Bullet — „in §5.16 verankert, Story 3.10".
[`compiler.md:391`](../../schema/compiler.md#L391)
- §7-Bullet Story-3.10-Verankerung; §8-Revisionslog **Revision 3.5** (2026-08-21, Story 3.10).
[`compiler.md:515`](../../schema/compiler.md#L515)
**Verifikations-Nachweis (Test)**
- Sandbox E-1..E-9 — re-executierbarer Erhaltungs-Nachweis; Stufen-Scans a/b/c + route_orphan/mehrziel_route (echte Mechanik, kein Fixture-Marker-Wiedergepen).
[`run-sandbox.sh:127`](sandbox-3-10/run-sandbox.sh#L127)
- E-4 CONFIRMING ≠ NO_OP byte-bewiesen (at-Bump aus aktuellem Run, A0-20); E-6 NO_OP byte-erhaltend auf Run-State-Ebene (git-Status leer).
[`run-sandbox.sh:356`](sandbox-3-10/run-sandbox.sh#L356)
- E-8 Orphan/Mehrziel-Hold (log.md statt Sidecar, nur-log.md-Delta) + E-9 at-Wanduhr-Gap-Exzeption real ausgeübt.
[`run-sandbox.sh:512`](sandbox-3-10/run-sandbox.sh#L512)
**Peripherie (Status/Tracking)**
- sprint-status.yaml Key 3-10 → review (finaler done-Flip nach Step-05-Status-Sync); deferred-work.md Aufgegriffen-/Defer-Einträge; wiki/log.md Story-3.10-Eintrag; epic-3-context Technical Decision Z. 39.
[`sprint-status.yaml:47`](../../_bmad-output/implementation-artifacts/sprint-status.yaml#L47)
@@ -0,0 +1,166 @@
---
title: 'Story 3.11 — Root-Scope-Leasing atomar und worktree-übergreifend akquirieren'
type: 'feature'
created: '2026-08-21'
status: 'done'
baseline_commit: 'a8b486d0f04b44344cdfa62e9cc32dfea0d49abd'
review_loop_iteration: 1
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** Die in `schema/compiler.md` §5.11/§5.12 verankerte Lease-Akquise ist eine nicht-atomare check-then-act-Operation auf einem Arbeitsbaum-Lockfile (`lease/<area>/<id>.lock` oben geprüft, unten per `>`-Write überschrieben) und wird bisher nur in sequenzieller Isolation erprobt. Ein TOCTOU-Race ist möglich: zwei Producer mit verschiedenen Run-IDs können denselben Root-Scope gleichzeitig „erwerben", weil die Run-ID Teil des Exklusivitätsschlüssels (Lockfile-Pfad + Branch-Name) ist. Der von AD-17b/A0-13 geforderte „genau ein scope-bezogener Lock im clone-geteilten Zustand" ist weder in §5.11 verankert noch durch einen realen Zwei-Worktree-/Zwei-Prozess-Test belegt (epic-3-context Z. 41, Zielzustand P-11).
**Approach:** §5.11 in Revision 3.6 um einen präzisierenden Abschnitt ergänzen: der Exklusivitätsschlüssel wird ein einziger, scope-bezogener Lock im clone-geteilten Zustand des Repos (geteilter Git-Ref-/Objektnamespace aller Worktrees und Prozesse eines Clones); die Run-ID ist Lock-Inhalt statt Schlüsselbestandteil; die Akquise erfolgt atomar (create-only: zweite Akquise schlägt fehl, ohne den Lock zu berühren). Eine neue Sandbox führt einen realen, zeitlich überlappenden Zwei-Worktree-/Zwei-Prozess-Test mit Zwischenzustands-Assertions aus und weist nach, dass niemals zwei aktive Root-Leases gleichzeitig existieren und der unterlegene Producer `LEASE_HOLD` erhält.
## Boundaries & Constraints
**Always:**
- Fugen-Identität: Branch-Form `lease/<area>/<id>` (§5.11 Pkt. 1), Lockfile-Pfad `lease/<area>/<id>.lock` und dessen Feld-Satz bleiben bestehen; Root-Scope-Umfang §5.11 Pkt. 2 (`wiki/` inkl. `log.md`, `index.md`, aller Root-Dateien; kein Bereich jenseits `wiki/`) bleibt unverändert.
- Fugen-Identität: AD-17a/17b-Spine-Wortlaut, A0-12/A0-13-Kurzbeschreibungen, §5.12-Anker (Registry im Clone-Root-State bzw. `lease-granite-root`-Marker, Gen-Invariante, kein Wanduhr/Zeitstempel A0-20) und die bestehende §5.11-Pkt.-1-Lease-Hold-Semantik bleiben unverändert maßgeblich.
- Der Exklusivitätsschlüssel ist der scope-bezogene Lock im clone-geteilten Zustand; die Run-ID ist Lock-Inhalt und nicht Teil des Schlüssels (AC-a).
- Die Akquise ist atomar (create-only): genau ein Gewinner; der Abgewiesene erhält `LEASE_HOLD`, überschreibt nichts, erzeugt keinen Compilation Commit, entfernt keine fremde Lease (AC-b/c).
- Kein textueller Auto-Merge (AD-17c); ungleiche Änderungen am selben Concept-Pfad werden als strukturierter Kollisions-Hold mit beiden Commit-Hashes an Epic 4 übergeben (AC-e). Commit-Boundary = Mutations-Boundary (AD-17f), log.md-Eintragspflicht (Vertrag §5).
- Schema read-only (AD-3): `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` bleiben unverändert; kein neuer Frontmatter-Key, keine neue §7-Invaliditätsklasse, kein Standalone (D-3).
**Ask First:**
- Falls das gewählte atomare create-only-Primitiv im geteilten Git-Refnamespace (z. B. `git update-ref <ref> <wert> $ZERO_SHA`, create-only) auf der Ziel-Plattform (Windows/Git-Bash) nicht zuverlässig atomar belegbar ist, HALT und alternative Primitiv-Auswahl zur Autorisierung vorlegen.
**Never:**
- Kein Umschreiben des bestehenden §5.11-Pkt.-1/2-Wortlauts — nur additive Präzisierung (Revision 3.6); keine Änderung der Root-Scope-Umfangssemantik.
- Keine Wanduhr-/Systemzeit in der Akquise (A0-20); keine Staleness-/Recovery-Logik (bleibt Story 3.6/3.12).
- Keine Verwaist-/Stale-Behandlung, kein Lease-Branch-Lifecycle nach Übernahme (bleibt Story 3.12).
- Keine Mutation des realen Bundles oder `raw/` durch die Sandbox; keine Schein-Parallelität (zwei disjunkte Repos oder Zeitversatz ohne Überlappung beweisen keine Atomarität).
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|----------------------------|----------------|
| AKQUISE_GEWINNER | scope-frei; Worktree 1 akquiriert Root-Scope | genau ein scope-bezogener Lock mit Inhalt = Run-ID von Worktree 1 | N/A |
| AKQUISE_ABGEWIESEN | scope-Lock bereits von anderer Run-ID (anderer Worktree) gehalten | `LEASE_HOLD`; `wiki/` und Lock unverändert; kein Compilation Commit; fremde Lease bleibt erhalten | abgewiesener Producer beendet sauber mit `LEASE_HOLD` |
| AKQUISE_GLEICHZEITIG | zwei Prozesse in getrennten Worktrees, zeitlich überlappende Akquise | Zwischenzustands-Assertions: niemals zwei aktive Root-Leases gleichzeitig; genau ein Gewinner | Verlierer erhält `LEASE_HOLD` |
| KOLLISIONS_HOLD | zwei Branches mit ungleichen Änderungen am selben Concept-Pfad | kein textueller Auto-Merge; strukturierter Kollisions-Hold mit beiden Commit-Hashes an Epic 4 | Hold-Struktur statt Merge |
</frozen-after-approval>
## Code Map
- `schema/compiler.md` §5.11 (Z. 304321) / §5.12 (Z. 323342) / §7 / §8 (Rev. bis 3.5) -- der normativ zu ergänzende Instruktions-Ort: neue Präzisierung als Abschnitt nach §5.12 (Revision 3.6); Pkt.-1/2-Wortlaut und Rückverweise (Z. 321 Seam, §7, §8) bleiben Fugen-Identität.
- `_bmad-output/implementation-artifacts/epic-3-context.md` Z. 41 -- „Zielzustand (Review-Loop-3, P-11)": zu schließender Ist/Soll-Abstand; nach Verankerung auf Ist-Zustand aktualisieren.
- `_bmad-output/implementation-artifacts/sandbox-3-5/run-sandbox.sh` -- nicht-atomare `akquire()` (check-then-act) und L1/L2/L5-Szenarien: Ableitungspflicht des neuen atomaren Lock-Modells.
- `_bmad-output/implementation-artifacts/sandbox-3-6/run-sandbox.sh` -- Registry-/Gen-Mechanik im Clone-Root-State, `isolate()` (Z. 97104); Zwei-Worktree-Erweiterung muss Gen-Invariante und Registry erhalten.
- `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh` Z. 249251 -- `git worktree add`-Präzedenz über einem Baseline-Commit (bisher strikt sequenziell; Konkurrenz ist die 3.11-Neuheit).
- `_bmad-output/implementation-artifacts/sprint-status.yaml` Z. 64 -- Story-Key `3-11-root-scope-leasing-atomar-akquirieren: backlog` → review.
- `wiki/log.md` -- Revisions-/Lauf-Nachweis der Verankerung.
## Tasks & Acceptance
**Execution:**
- [x] `schema/compiler.md` -- neuen Präzisierungs-Abschnitt (Revision 3.6) nach §5.12 ergänzen: atomarer scope-bezogener Lock im clone-geteilten Zustand (create-only, Run-ID als Lock-Inhalt statt Exklusivitätsschlüssel), `LEASE_HOLD`-Semantik, Kollisions-Hold mit beiden Commit-Hashes an Epic 4; bestehende Pkt.-1/2 und §5.12 unverändert lassen; §7-Rückverweis und §8-Revisionslog ergänzen -- §5.11-Präzisierung (Story-3.11-Rationale).
- [x] `_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh` -- neue re-executierbare Sandbox: echte Zwei-Worktree-/Zwei-Prozess-Überlappung (`git worktree add` über einer Baseline), atomare Akquise mit Zwischenzustands-Assertions (niemals zwei aktive Root-Leases), `LEASE_HOLD`-Fälle inkl. Nicht-Mutation des Arbeitsbaums, Kollisions-Hold mit beiden Commit-Hashes; Exit 0, harte PASS/FAIL; berührt reales Bundle/`raw/` nicht -- Tests der I/O-Matrix und der 5 ACs.
- [x] `_bmad-output/implementation-artifacts/sprint-status.yaml` -- Key `3-11-root-scope-leasing-atomar-akquirieren` auf `review` -- Status-Sync des fertigen Drafts.
- [x] `wiki/log.md` -- Revisions-Nachweis (Verankerung + Sandbox-Lauf) -- Vertrag §5-Eintragspflicht.
- [x] `_bmad-output/implementation-artifacts/epic-3-context.md` -- Z. 41-Zielzustand auf Ist-Zustand aktualisieren -- Planungsartefakt nachführen.
**Acceptance Criteria:**
- Given der Root-Scope `wiki/` im clone-geteilten Zustand, when ein Producer eine Lease akquiriert, then existiert genau ein scope-bezogener Lock und die Run-ID ist Lock-Inhalt und nicht Teil des Exklusivitätsschlüssels.
- Given zwei Producer mit verschiedenen IDs und getrennten Worktrees, when beide denselben Root-Scope akquirieren, then ist die Akquise atomar und genau ein Producer erhält die Lease; der andere erhält `LEASE_HOLD`.
- Given ein abgewiesener Producer, when die Akquise fehlschlägt, then verändert er weder `wiki/` noch den bestehenden Lock, erzeugt keinen Compilation Commit und entfernt keine fremde Lease.
- Given ein realer Zwei-Worktree-/Zwei-Prozess-Test mit zeitlich überlappender Akquise, when die Läufe ausgeführt werden, then beweisen Zwischenzustands-Assertions, dass niemals zwei aktive Root-Leases gleichzeitig existieren.
- Given zwei Branches mit ungleichen Änderungen am selben Concept-Pfad, when eine Kollision erkannt wird, then wird nicht automatisch textuell gemerged, sondern ein strukturierter Kollisions-Hold mit beiden Commit-Hashes an Epic 4 übergeben.
## Spec Change Log
## Design Notes
Der Lock muss im clone-geteilten Zustand liegen, weil nur dieser Zustand von allen Worktrees und Prozessen eines Clones gemeinsam gesehen wird: Arbeitsbaum-Lockfiles (`lease/<area>/<id>.lock`) sind clone-lokal und laden zur nicht-atomaren check-then-act-Akquise ein. Der geteilte Git-Ref-/Objektnamespace bietet dagegen ein natürlich atomares create-only-Primitiv (gits eigene Ref-Sperre, z. B. `git update-ref <ref> <wert> 0000…0000`, schlägt fehl, sobald der Ref existiert); zwei Worktrees desselben Repos streiten damit serialisiert um denselben Lock. Der Schlüssel ist der Scope (ein Ref pro `wiki/`), der Inhalt die Run-ID — genau die AC-a-Aussage. Die bisherige `<id>`-Schlüssigkeit (§5.11 Pkt. 1) bleibt als per-Lease-Ablage bestehen, trägt aber keine Exklusivität mehr allein.
## Verification
**Commands:**
- `bash _bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh` -- expected: alle Szenarien harte PASS (u.a. „niemals zwei aktive Root-Leases"-Assertion, `LEASE_HOLD`-Reihenfolge), Exit 0, kein Zugriff auf reales Bundle/`raw/`.
- `grep -n "Revision 3.6" schema/compiler.md` und `grep -n "## 5.17" schema/compiler.md` -- expected: §5.17-Überschrift und §8-Revisionslogeintrag vorhanden; bestehende Pkt.-1/2-Texte unverändert (diff checkt nur additive/berichtigte Zeilen).
- `git diff --stat` (Repo-Root) -- expected: nur die Review-Loop-1-Touched-Files (Sandbox, `schema/compiler.md` §5.17-Berichtigungen, Spec, log.md, deferred-work.md, sprint-status.yaml); `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/` ohne Diff (AD-3).
**Manual checks (if no CLI):**
- Keine Beschreibung nötig — sämtliche Nachweise laufen über die Sandbox (Exit-Code) und additive Diffs.
## Suggested Review Order
**Design-Intent (normative Instruktion)**
- Einstieg: die §5.17-Atomaritäts-Präzisierung — Exklusivitätsschlüssel im clone-geteilten Zustand, Run-ID als Lock-Inhalt, create-only-Akquise, LEASE_HOLD, Kollisions-Hold an Epic 4 (AC-a..e).
[`compiler.md:415`](../../schema/compiler.md#L415)
- Abgrenzungsklausel der Story-3.11-Verankerung — Fugen-Identität von §5.11-Pkt.-1/2 und §5.12 bleibt erhalten, keine neue §7-Klasse, kein Standalone.
[`compiler.md:424`](../../schema/compiler.md#L424)
**Atomarer Lock im clone-geteilten Zustand**
- Atomare create-only-Akquise (`git update-ref <lock-ref> <wert> $ZERO_SHA`) — gits Ref-Sperre serialisiert Worktrees/Prozesse; deterministischer Blob-Inhalt.
[`run-sandbox.sh:124`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L124)
- Zwei-Prozess-Überlappung im Zwei-Worktree — Schlüssel-Beweis AC-b/AC-d (genau ein Gewinner, ein LEASE_HOLD, gehärteter Zwischenzustands-Sampler: Ref-Kardinalität + Wert-Evidenz).
[`run-sandbox.sh:217`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L217)
**Kollisions-Hold & Abweisung**
- LEASE_HOLD-Nicht-Mutation: abgewiesener Producer lässt `wiki/`/Lock/`log.md` unverändert, keine Commits, fremde Lease erhalten (AC-c) — Worktree-Beobachtung des Arbeitsbaums (D-3.11-2).
[`run-sandbox.sh:339`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L339)
- Kollisions-Hold mit beiden Commit-Hashes + Scope + Baseline in `log.md` an Epic 4 (AC-e, AD-17c, kein textueller Auto-Merge) — `MERGE_OK` wird hart abgewiesen; Datei-Level-Attest auf den Post-Merge-State.
[`run-sandbox.sh:460`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L460)
**Status- & Nachweissynchronisation (peripher)**
- Revisionsnachweis Story 3.11 → `review` in der Laufzeitakte — Verankerung, Sandbox, Abschlussklausel, Erhaltungs-Invariante.
[`log.md:4`](../../wiki/log.md#L4)
- Planungsartefakt auf Ist-Zustand nachgeführt (epic-3-context Z. 41).
[`epic-3-context.md:41`](../../_bmad-output/implementation-artifacts/epic-3-context.md#L41)
**Härtung & Konsistenz-Funde (defer)**
- Konsistenz-, Drift- und Härtungs-Funde aus der Review in der Defer-Liste festgehalten (AC-d-Token, Zeilenanker, dynamische Negativ-Kontrolle, Platzhalter-Banner) inkl. KORREKTUR-Teileintrag (Zeilen-Drift-Defer widerlegt).
[`deferred-work.md:578`](../../_bmad-output/implementation-artifacts/deferred-work.md#L578)
### Review Findings
_Bmad-code-review Loop 1 (2026-08-21), 4 Layer (blind-hunter/edge-case-hunter/verification-gap/acceptance-auditor). Triage: 2 decision-needed, 20 patch, 5 defer, 5 dismissed. Sandbox re-executiert (Windows/Git-Bash): A-1..A-8 8/8 harte PASS, Exit 0, kein Zugriff auf reales Bundle/raw/._
#### Decision-Needed
- [x] [Review][Decision] **D-3.11-1 AC-d-Beweisart (Zwischenzustands-Assertion)***Gelöst (Nutzer-Entscheidung 2026-08-21):* **Konstruktiv + härten** — AC-4 als konstruktiv erfüllt akzeptiert (eine scope-bezogene Ref + atomarer create-only-Write → zu keinem Zeitpunkt zwei aktive Root-Leases); A-2-Sampler zur lebendigen Wert-Evidenz gehärtet, §5.17-/log.md-Nachweis um „konstruktiv“ qualifiziert. Kein frozen-Block-Neuverhandeln. Hintergrund: der A-2-Sampler zählt die Ref-Kardinalität (`grep -cF "$SCOPELOCK"`, Z. 237, nie >1 bei einem Ref-Namen) und der `TWO_LOCKS_AT_ONCE`-Marker geht per `>&2` verloren, weil `out_a` nur stdout fängt (Z. 238 vs. Z. 255) — die Assertion war als *beobachtete* Evidenz strukturell tot. [run-sandbox.sh:237-257]
- [x] [Review][Decision] **D-3.11-2 A-4-Mutations-Regime (Nicht-Mutation des abgewiesenen Producers)***Gelöst (Nutzer-Entscheidung 2026-08-21):* **Intentionalen Mutationsversuch bauen** — in A-4 einen bewussten Mutations-/Commit-Versuch des abgewiesenen Producers in die Sandbox einbauen, dessen Ausbleiben/Ablehnung dann hart geprüft wird (deckt AC-c als Verhalten ab, nicht nur als Selbstvergleich). Hintergrund: der LEASE_HOLD-Pfad unternimmt heute keinen Mutationsversuch (nur `echo "LEASE_HOLD"`, Z. 309-311) und die AC-c-Assertions vergleichen `git show "$BASE:…"` mit sich selbst (invarianter Baseline-Blob, nie der Worktree; Z. 297/314/323) — uncommittete Arbeitsbaum-Mutationen des Abgewiesenen schifften unentdeckt durch. [run-sandbox.sh:297-324]
#### Patch
- [x] [Review][Patch] **A-7 `MERGE_OK`-Zweig akzeptiert den verbotenen textuellen Auto-Merge**`case "$ma" in MERGE_CONFLICT|MERGE_OK)` lässt beide Outcomes durch; wäre `git merge --no-edit` clean gegangen, läge exakt der von AD-17c/AC-e verbotene stille Merge vor, der Test meldet dennoch PASS. `MERGE_OK` hart `fail`en (oder begründen, warum nur CONFLICT zulässig ist); dazu die Body-Assertions auf den **Post-Merge-HEAD von `branch-y`** (`git show branch-y:wiki/alpha.md`) statt auf die invarianten `$HASH_X/$HASH_Y`-Objekte ausrichten. [run-sandbox.sh:406-443]
- [x] [Review][Patch] **A-7 stale-Aussage-Kommentar widerspricht dem Code** — Kommentar „(a) … `git merge` … der Befehl wird gar nicht erst ausgeführt, … kein Merge“ (Z. 401-405) steht über `merge_attempt()`, das den Merge real ausführt; zudem unverstandener Rest „ablösen von alpha.txt-loesungen“. Kommentar an den tatsächlichen Ablauf korrigieren. [run-sandbox.sh:401-414]
- [x] [Review][Patch] **A-7 `hold_msg`-Variablen-Case ist tautologisch** — die zwei `case "$hold_msg"`-Checks (Z. 430-437) prüfen eine gerade konstruierte Zeichenkette gegen sich selbst (der erste `*"$HASH_X"*|*"$HASH_Y"*` bestünde sogar bei nur einem Hash); nur die Datei-Checks (Z. 450-453) sind real. Variablen-Case entfernen oder auf den geschriebenen `log.md`-Inhalt reduzieren. [run-sandbox.sh:430-437]
- [x] [Review][Patch] **A-7 `log.md`-Hold-Eintrag trägt keinen `<Baseline-Commit>`** — die zitierte Hold-Mechanik (§5.16 Pkt. 8/§5.10 Pkt. 8) verlangt „Quell-Pfad + `<Baseline-Commit>`“; der geschriebene Eintrag (Z. 447) hat Scope + beide Hashes, aber kein Baseline-Commit. Baseline-Komponente ergänzen oder begründen, warum sie beim Kollisions-Hold entfällt. [run-sandbox.sh:446-448]
- [x] [Review][Patch] **`assert_frontmatter()` ist tot — behauptete „at-Normalform je Szenario“ wird nicht ausgeführt** — die Funktion (Z. 155-164) prüft §3.3/§3.4-Frontmatter + `at`-ISO-Normalform, wird aber nirgendwo aufgerufen; `wiki/log.md`/Revision-3.6 attestiieren dennoch „Erhaltungs-Invariante + at-Normalform je Szenario“. Aufruf ergänzen (z. B. `assert_frontmatter wiki/alpha.md` nach A-7, wo `alpha.md` neu geschrieben wird) oder die Nachweis-Formulierung in log.md/Revision korrigieren. [run-sandbox.sh:155-164]
- [x] [Review][Patch] **A-3-Kommentar behauptet „zweiter Worktree, eigener Prozess“, ist aber sequentiell im Hauptprozess** — die zweite Akquise (Z. 285-286) ist ein direkter `scopelock_acquire`-Aufruf ohne `git worktree add`/Subshell; „zeitlich ÜBERLAPPEND“ ist hier falsch (echte Überlappung liegt in A-2). Kommentar entlarven/entschärfen. [run-sandbox.sh:282-286]
- [x] [Review][Patch] **A-3 Identitäts-Fall nicht geübt** — der Kommentar behauptet „auch eine identisch benannte Run-ID kann den Halter nicht ersetzen“, der zweite Versuch nutzt aber `RUN-A3-identisch` (andere ID); der exakte Identitäts-Fall `RUN-A3` wird nicht geübt. Entweder mit `RUN-A3` akquirieren oder den Kommentar korrigieren. [run-sandbox.sh:284-288]
- [x] [Review][Patch] **A-1 toter `run_a1`-Blob**`run_a1=$(printf 'producer-a1-run-id' | git hash-object -w --stdin)` (Z. 179) wird nie verwendet (echter Lock-Inhalt ist `RUN-A1`); Dead Code, schreibt ein ungenutztes Objekt. Entfernen. [run-sandbox.sh:179]
- [x] [Review][Patch] **A-8 Commit-Zähler-Check am falschen Punkt** — der `git rev-list --count`-Check **vor** dem ersten `scopelock_release` (Z. 468-469) kann nur Akquise-Commits, keine Release-Commits nachweisen; der Check nach dem Release (Z. 479-480) ist der wirksame. Erster Check entfernen oder Kommentar korrigieren („hart, vor dem ersten Release“ trifft die eigene Aussage nicht). [run-sandbox.sh:466-480]
- [x] [Review][Patch] **Sandbox-Schlusskontrolle ist ein No-op**`git status --short --porcelain >/dev/null 2>&1 || true` (Z. 494) verwirft Ausgabe und Exit-Code und attestiert nichts (weder Bundlesauberkeit noch „kein Zugriff auf reale Pfade“); dazu bleiben die in A-2/A-4 angelegten Worktrees (`$ROOT/wt-a/wt-b/wt-a4`) registriert und werden nicht abgeräumt. Ende-Assertion auf eine echte Prüfung umstellen (z. B. `git worktree list`-Prüfung gg. `$ROOT`) oder Worktrees aufräumen + No-op entfernen. [run-sandbox.sh:494]
- [x] [Review][Patch] **`scopelock_release()` ohne Ownership-/Exit-Code-Kopplung** — `git update-ref -d "$SCOPELOCK"` (Z. 132-134) läuft ohne `|| fail` und ohne Inhaber-Bindung (kein Abgleich des Ref-Inhalts mit der eigenen Run-ID); ein fehlgeschlagenes Release würde A-1/A-3/A-5/A-6 nicht bemerken. Release-Stellen hart koppeln; Ownership-Prüfung ergänzen oder explizit als Story-3.12-Home benennen. [run-sandbox.sh:132-134]
- [x] [Review][Patch] **`isolate()` räumt den geteilten Ref-Namespace nicht** — `reset --hard`/`git clean` betreffen nur Arbeitsbaum und HEAD; `refs/leases/wiki` überdauert Szenario-Grenzen und hängt an der impliziten Release-Disziplin. Expliziten Ref-Reset in `isolate()` ergänzen, damit ein fehlgeschlagenes Release die Folgeszenarien nicht still kontaminiert. [run-sandbox.sh:99-105]
- [x] [Review][Patch] **Asymmetrische Release-Ende-Leereprüfung** — A-8 prüft nach dem Release `[ -z "$(scopelock_content)" ]`, A-1/A-3/A-5/A-6 tun es nicht; ein hängender Lock wäre erst in einem späteren, nicht-attributierbaren Szenario auffindbar. Ende-Check je Szenario vereinheitlichen. [run-sandbox.sh:189-189, 288-288, 342-342, 359-359]
- [x] [Review][Patch] **§5.17 Pkt. 5 Grammatikbruch + fehlendes Ablage-Ziel** — „an Epic 4 übergeben — **der** benannten Hold-Mechanik“ (Anstelle von „an“); zudem benennt §5.17 Pkt. 5 nirgends, *wo* der Kollisions-Hold liegt (Run-Receipt? `log.md`?), während A-7 ihn in `wiki/log.md` schreibt. Wortlaut berichtigen + Ablage-Stelle normativ benennen. [compiler.md:423]
- [x] [Review][Patch] **§5.17 Pkt. 1: „committete Ref“ ist technisch inkorrekt** — eine Ref ist kein Commit-Objekt und nicht Teil eines Commits; die Sandbox realisiert es als Blob (`git hash-object -w`) plus Ref-Write. Für eine normative Sektion, aus der Story 3.12 den Lifecycle ableitet, unpräzise (Lebensdauer des Locks unklar). Auf „geschriebene/verwiesene Ref mit Blob-Inhalt“ berichtigen. [compiler.md:416]
- [x] [Review][Patch] **§5.17 Pkt. 4: Determinismus-Aussage gilt nicht im concurrenten Fall** — „gleicher Geteilter-Zustand + gleiche Eingabemenge → identische Gewinner-/Verlierer-Entscheidung“ trifft nur für sequentielle Versuche (A-3/A-6); bei überlappenden Producers hängt die Gewinnerwahl vom Scheduling ab, nicht allein vom geteilten Zustand. Pkt.-4-Aussage auf den deterministischen Kern (create-only-Existenzprüfung) eingrenzen oder den concurrenten Fall ausnehmen. [compiler.md:419]
- [x] [Review][Patch] **Suggested-Review-Order-Anker bei Commit-Zeit falsch**`compiler.md:415` zeigt auf die Leerzeile vor dem §5.17-Intro; `compiler.md:527` auf die §8-Revision-3.6-Logzeile (die Abgrenzungsklausel Pkt. 6 liegt ≈ Z. 424); `log.md:47` auf eine Zeile unterhalb des 3.11-Eintrags (der liegt auf Z. 4); `deferred-work.md:577` auf die Leerzeile vor dem neuen „Deferred from“-Header (Z. 578). Anker neu setzen. [spec:642, 644, 663, 670]
- [x] [Review][Patch] **Defer-Evidence benennt die falsche Stelle** — der Zeilen-Drift-Defer in `deferred-work.md` verweist auf „die Spec (Code Map) und die Task-1-Rationale“, die konkreten Fehlancker sitzen aber in der Suggested-Review-Order; zudem ist die Aussage „§5.11 (Z. 304-321)/§5.12 (Z. 323-342) nicht mehr aktuell“ **falsch** — §5.11 liegt weiterhin auf Z. 304, §5.12 auf Z. 323 (verifiziert bei e02cf84). Evidence präzisieren; den Zeilen-Drift-Teileintrag berichtigen (es gibt keinen Defekt an diesen Anker). [deferred-work.md:580-585]
- [x] [Review][Patch] **Spec-Frontmatter `context: []` trägt die Code-Map-Abhängigkeit nicht** — die Code Map und Task 5 nennen `epic-3-context.md` (Z. 41) als nachgeführtes Planungsartefakt; die Präzedenz-Spec-3.10 listet dieselbe Datei im `context:`-Feld. `epic-3-context.md` in `context:` aufnehmen. [spec:8]
- [x] [Review][Patch] **Spec-Verification-Kommando ungültig**`git -C schema/compiler.md diff` ist ungültig (`git -C` erwartet ein Verzeichnis, `schema/compiler.md` ist eine Datei); `grep -n "3.11\|3.6"` trifft zudem etliche „Story 3.6“-Erwähnungen in §5.11/§5.12 und taugt nicht als gezielte Revision-3.6-Prüfung. Kommandos berichtigen. [spec:631-632]
#### Defer
- [x] [Review][Defer] **AC-d-Token ohne normative Vorlagen-Definition** — der `AC-d`-Token (Sandbox A-2/A-3, §5.17, log.md) ist in der Vorlage epics.md A0-13/AD-17b nicht separat gelistet (AC-a..c, AC-e); er ist ein konsistenzerhaltender Behelf, kein neuer Anforderungskanon; Home: Epic-4-Statussynchronisation der Architecture-Spine (bereits in deferred-work.md, hier bestätigt). — deferred, pre-existing
- [x] [Review][Defer] **Sandbox-3.5/-3.6 üben weiterhin check-then-act-Lockfile-Akquise ohne §5.17-Scope-Lock** — die Code Map benennt die `akquire()` von 3.5/3.6 als „Ableitungspflicht des neuen atomaren Lock-Modells“; die Schwestern-Sandboxes demonstrieren weiterhin die clone-lokale check-then-act-Lockfile-Akquise (Exklusivität nur für identische `<id>`). Ein Test, der zwei Producer mit *verschiedenen* `id` im selben Root-Scope unter §5.17 auf genau einen Gewinner prüft, fehlt. Home: Story-3.12-Lifecycle / Story-3.13-Abnahme. — deferred, pre-existing
- [x] [Review][Defer] **A-2 beweist keine garantierte zeitliche Überlappung** — die zwei Prozesse starten ohne Synchronisations-Barriere; ein Scheduler kann sie vollständig sequenzieren (genau die „Zeitversatz ohne Überlappung“-Situation, die die Spec als nicht beweiskräftig ausschließt). Die create-only-Atomarität wird davon nicht berührt, aber eine echte Überlappung wäre ein stärkerer Beleg. Home: Sandbox-Härtung / Story-3.13. — deferred, pre-existing
- [x] [Review][Defer] **Status-Kontraktion Spec-Frontmatter `status: done` vs. sprint-status `review`** — wiederholt das Story-3.9-Muster (D-3.9-2: maßgeblich `review`, Spec-`done` als Kontraktion); für 3.11 fehlt der explizite Präzedenz-Verweis. Entspricht aber der 3.9-/3.10-Präzedenz (`status: done` im Implementierungs-Commit, finaler `done`-Flip im Step-05-Sync) und wird im Step-05-Status-Sync aufgelöst. — deferred, pre-existing
- [x] [Review][Defer] **`scopelock_header_banner()` leerer Platzhalter + fehlende I/O-Matrix-Zeilen für A-5/A-6/A-8** — die ungenutzte Banner-Funktion (Z. 166) und die drei Sandbox-Szenarien ohne eigene I/O-Matrix-Zeile (Matrix listet 4 Szenarien, Sandbox übt 8) sind bekannter Kosmetik-/Dokumentations-Ausbau; Home: Story-3.12 / Matrix-Nachführung. — deferred, pre-existing
@@ -0,0 +1,191 @@
---
title: 'Story 3.12 — Lease-Lifecycle und Commit-Abschluss transaktional schließen'
type: 'feature'
created: '2026-08-21'
status: 'done'
baseline_commit: '80480af3ec910b6a00d10f4fe820131bbf79c6a5'
review_loop_iteration: 1
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** Die Koordinations-Dimension ist über §5.11 (Leasing/Dirty-Tree, Story 3.5), §5.12 (Staleness/Recovery, Story 3.6), §5.13 (Phasen-Trennung, Story 3.7) und §5.17 (atomare Root-Scope-Lease-Akquise, Story 3.11) verankert — aber die **Lifecycle-Klammer fehlt**: Akquise, Dirty-Tree-Schutz, Mutation, Rollback und Freigabe sind nirgends als **konsistenter, transaktionaler Lebenszyklus** instruiert, der SUCCESS und FAIL jeweils einen sauberen, wiederanlaufbaren Zustand hinterlässt. Konkret unverankert: (a) **Liveness** einer lebenden Lease (eine höhere Generation macht sie nicht stale; Staleness verlangt bestätigten Abbruch oder abgelaufene Liveness plus atomare Ownership-Prüfung — epic-3-context Z. 43 „Zielzustand Review-Loop-3, P-11" und SCP 2026-08-20 Z. 71 „Staleness/Liveness"); (b) **durable Release** — nach Release sind Mutation, zulässiger Nachweis und Freigabe dauerhaft, Lock entfernt, Worktree sauber, Folge-Run besteht den **Clean-Input-Guard**; (c) **Baseline-Rollback** (Index **+** Worktree) aus dem bezeichneten Baseline-Commit mit leerem Post-Rollback-Diff (§5.3/§6-Pkt.-3-Mechanik nennt nur Bundle-/`git checkout`-Wiederherstellung, kein Index); (d) eine **eindeutige Abort-/Protect-Zustandsmaschine** für getrackte und ungetrackte fremde Änderungen (sandbox-3-5 L4 ist nur Inline-Ablauf, keine Zustandsmaschine); (e) **stale-Übernahme genau einmal** mit ersetzter Holder-ID und genau einer aktiven Root-Lease; (f) **Kill-Point-Tests** (termini undefiniert, nirgends verankert); (g) die **Grenze des kanonischen Knowledge Logs** (`wiki/log.md` nur vertragskonforme fachliche Änderungen + notwendige Koordinationsereignisse).
**Approach:** Neue Sektion **§5.18 „Lease-Lifecycle & transaktionaler Commit-Abschluss (Story 3.12)"** (nach §5.17, vor §6; Revision 3.7): additiv über §5.11/§5.12/§5.13/§5.17, Fugen-Identität — kein Umbau bestehender Wortlaute. Sie instruiert den geschlossenen Ablauf (1) Liveness-Ownership (nur bestätigter Abbruch/abgelaufene Liveness + atomare Ownership-Prüfung macht eine Lease stale; eine höhere Generation allein nicht), (2) stale-Übernahme genau einmal (genau eine aktive Root-Lease, ersetzte Holder-ID im `log.md`), (3) Abort-/Protect-Zustandsmaschine für fremde getrackte/ungetrackte Änderungen (Scratch-Zone `scratch/<run-id>/` bzw. `git stash push`; byte-identischer Restore) samt `UNCOMMITTED_INPUT`-Abbruch abgestimmt mit §5.11 Pkt. 3, (4) Baseline-Rollback (Index **+** Worktree aus `<Baseline-Commit>`; Post-Rollback-Diff leer; Kill-Punkt nach Mutation/Staging), (5) durable Release (Mutation + zulässiger Log-/Koordinationsnachweis committet, Lock per Ref-Delete entfernt, Worktree sauber, unmittelbar folgender Run besteht den **Clean-Input-Guard** — sauberer Worktree ohne fremde/geschützte Reste), (6) kanonisches Log (§5/Koordination nur), (7) Kill-Point-Tests (vor Mutation, nach Mutation, vor Commit, nach Commit — je konsistenter Endzustand, §5.13-Pkt.-3-Zustands-Restaurations-Invariante).
## Boundaries & Constraints
**Always:**
- **Nur `schema/compiler.md` mutiert** (neue §5.18 + §7-Bullet-Erweiterung Story-3.12 + §8-Revisionslog **Revision 3.7**). `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` **read-only** (AD-3); `schema/canonical-terms.md` append-only unangetastet. `sprint-status.yaml`, `wiki/log.md`, `epic-3-context.md`, `deferred-work.md` werden nur append/Sync-gepflegt.
- **Referenz statt Re-Negotiation:** §5.11 (Pkt. 17), §5.12 (Pkt. 17), §5.13 und §5.17 bleiben **textuell unverändert** — §5.18 instruiert die Lifecycle-Ebene darüber und referenziert sie Wortlaut-unverändert. Fugen-Identität: Branch-Form `lease/<area>/<id>`, Lockfile-Feld-Satz, Root-Scope-Umfang §5.11 Pkt. 2, atomarer scope-bezogener Lock §5.17.
- **Kein neues Prädikat, keine neue §7-Invaliditätsklasse, kein neuer Frontmatter-/Format-Key, kein Standalone (D-3), keine Wanduhr-/Systemzeit-Steuerung** (A0-20: keine TTL über Kalenderzeit; Lifecycle deterministisch aus dem committeten Git-State, AD-17h/A0-19). Das `generated.at`-Wanduhr-Gap (§5.14 Pkt. 3) bleibt unverändert.
- **Sandbox-Nachweis re-executierbar** (Muster sandbox-3-11, Exit 0, harte PASS/FAIL, `/tmp`-Baum, nie der reale Ist-Baum; kein Zugriff auf reales Bundle/`raw/`).
**Ask First:**
- Verhalten des `generated.at` (A0-20) oder eine TTL-/Wanduhr-Steuerung ändern (bleibt benannter Defer bzw. bindend unverändert).
- Änderung an §5.17-Pkt.-2-Ask-First-Klausel (atomares create-only-Primitiv nicht zuverlässig → HALT, Alternativ-Primitiv).
- Änderung an §5.11-Pkt.-1-/Pkt.-2-Wortlaut oder §5.12-Ankern (frozen, Fugen-Identität).
- Erweiterung des `schema/validator.md` um Lease-/Koordinations-Prüfpunkte (wäre Epic-1-Vertragsänderung — nicht Teil dieser Story).
**Never:**
- Kein Umschreiben bestehender §5.11/§5.12/§5.13/§5.17-Wortlaute; keine Wanduhr-TTL; keine stille Löschung fremder uncommitteter/geschützter Änderungen (AD-17e); keine Wissensmutation bei Orphan/Hold (AD-16-Default); kein textueller Auto-Merge (AD-17c); keine neue eigene LLM-Runtime/Workflow-Engine (AD-11, D-3); kein Standalone; keine Validator-Erweiterung ohne Autorisierung.
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|---------------------------|----------------|
| LIVENESS_AKTIV | lebende Lease mit höherer Generation sichtbar | Lease bleibt aktiv; keine Stale-werdung allein durch Generationshöhe (AC-1) | kein Release, keine Fremd-Übernahme |
| STALE_UEBERNAHME | nachweislich stale Lease (bestätigter Abbruch / abgelaufene Liveness) | Übernahme gelingt genau einmal; ersetzte Holder-ID benannt; genau eine aktive Root-Lease (AC-2) | Ownership-Prüfung vor Übernahme; kein Doppel-Übernehmer |
| DIRTY_GETRACKT | fremde getrackte Änderung im Mutationsbereich | Abort-/Protect-Zustandsmaschine: schützen (Scratch/Stash), Restore byte-identisch nach Run (AC-3) | UNCOMMITTED_INPUT-Abbruch abgestimmt §5.11 Pkt. 3; nie gelöscht |
| DIRTY_UNGETRACKT | fremde ungetrackte Datei im Mutationsbereich | Protect: sichern + Restore; keine stillen Löschungen (AC-3) | nie gelöscht; log.md-Eintrag |
| ROLLBACK_NACH_MUTATION | Fehler nach Mutation/Staging | Rollback aus `<Baseline-Commit>`: Index **+** Worktree restauriert; Post-Rollback-Diff leer (AC-4) | Kill-Punkt nach Mutation: konsistenter Endzustand |
| ROLLBACK_NACH_COMMIT_VOR_RELEASE | Fehler nach Commit, vor Release | Kill-Punkt vor Commit/vor Release: Zustand == Baseline oder valide committet; kein Teilzustand veröffentlicht (AC-4) | Commit-Boundary = Mutation-Boundary (AD-17f) |
| RELEASE_DURABLE | erfolgreicher Run | Mutation + zulässiger Log-/Koordinationsnachweis committet; Lock per Ref-Delete entfernt; Worktree sauber; Folge-Run besteht Clean-Input-Guard (AC-5) | Release-Fehler = HARD-FAIL; Lock nie hängend |
| KANONISCHES_LOG | Run-Historie schreiben | `wiki/log.md` nur vertragskonforme fachliche Änderungen + notwendige Koordinationsereignisse; Build-/Review-/Story-/Sandbox-Historie außerhalb (AC-6) | Verstoß = textuell benannt (NFR-4) |
| KILLPUNKT_VOR_MUTATION | vor erster Mutation | Zustand == Baseline; keine Zwischenstände (AC-7) | konsistenter Endzustand, Determinismus |
| KILLPUNKT_NACH_MUTATION | nach Mutation | Zustand == valider Zwischenstand (geplanter Plan-Freeze); kein Ghost-Diff außerhalb erlaubter Menge (AC-7) | §5.9-Pkt.-5-Ghost-Diff-Rollback |
| KILLPUNKT_VOR_COMMIT | nach Staging, vor Commit | nur erlaubte Pfad-Menge staged; kein Teilzustand committet (AC-7) | Commit-Boundary = Mutation-Boundary |
| KILLPUNKT_NACH_COMMIT | nach Commit | Bundle == valide committeter Zustand; anschließbar an Clean-Input-Guard (AC-7) | kein hängender Lock, sauberer Worktree |
</frozen-after-approval>
## Code Map
- `schema/compiler.md`**primär mutiert** (D-3): neue Sektion **§5.18 „Lease-Lifecycle & transaktionaler Commit-Abschluss (Story 3.12)"** (nach §5.17 Z. 415424, vor §6 Z. 441; Pkt. 17, Z. 426439); **§7-Bullet-Erweiterung** (Story-3.12-Verankerung im Leasing-Bullet, Z. 495; §7 bei Z. 486); **§8-Revisionslog Revision 3.7** (Z. 543; §8 bei Z. 501, Revisionslog ab Z. 512). Bestehende §5.11 (Z. 304321)/§5.12 (Z. 323342)/§5.13 (Z. 344363)/§5.17 (Z. 415424) bleiben **textuell unverändert** (Fugen-Identität; §5.17-Pkt.-1-Z. 419 „Lifecycle-Regie Story 3.12" und Pkt.-6-Z. 424 „bleibt Story 3.12" sind die Anschluss-Sutur).
- `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh`**neu** (re-executierbar, Muster `sandbox-3-11/run-sandbox.sh` 543 Z., Exit 0): Lifecycle-Szenarien L-1..L-9 (12 Matrix-Zeilen als harte Assertionen, Kill-Punkte 1:1 in L-9). Übernahme der 3-11/3-5/3-6-Mechanik (Ableitung, nicht Zeilen-Identität): **3-11-Bausteine** (`isolate()`, `scopelock_acquire()` create-only, `scopelock_release()` mit Ownership-Prüfung, `scopelock_content()` — in 3-12 erweitert um `scopelock_healthy()`/LOCK_READ_ERROR-Propagation, Loop-2-P1 — und `scopelock_takeover()` als atomarer Ownership-CAS, Loop-1-PATCH 1; `assert_frontmatter()`, `runlabel`/`pass`/`fail`), **3-5-Mechanik** (Abort-/Protect-Zustandsmaschine aus sandbox-3-5-L4-Inline: Preflight via `git status --porcelain` inline statt `db_check`-Helfer, Stash/Scratch-Sicherung, byte-identischer Restore), **3-6-Mechanik** (Registry/Gen/Liveness: `reg_gen`/`reg_hold_mark`/`reg_stale_mark`/`reg_bump`/`lease_stale`/`lease_liveness_stale`; `free_lease` aus 3-6 wird **nicht** übernommen — Release läuft durch `scopelock_release` (Ref-Delete, §5.17-Pkt.-1-Modell), `reg_write` real geübt in L-1), **Baseline-Rollback Index+Worktree** (via §5.13-Pkt.-3 „Post-Rollback-Diff gg. Baseline leer"), **Kill-Point-Tests** (4 Konsistenz-Assertions + Ghost-Diff-Probe via `inv_viol`/`assert_invariant`, Loop-2-P4), **Clean-Input-Guard** (Worktree-Sauberkeitsprüfung nach Release). Kein Zugriff auf reales Bundle/`raw/`.
- `_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh`**read-only Vorbild** (Ableitungs-Pflicht: `scopelock_release`-Ownership, `isolate`-Ref-Reset, A-8-Relase-erneut Z. 501523; Defer „scopelock_header_banner" Z. 166/175 → Home 3.12).
- `_bmad-output/implementation-artifacts/sandbox-3-5/run-sandbox.sh`**read-only Quelle** der Dirty-Tree-Mechanik (L4 Z. 379434: `db_check` Z. 223234, UNCOMMITTED_INPUT-Abbruch Z. 395415, Stash/Scratch Z. 416421, Restore Z. 422425, NIE-gelöscht Z. 426428).
- `_bmad-output/implementation-artifacts/sandbox-3-6/run-sandbox.sh`**read-only Quelle** der Registry/Stale-/Recovery-Mechanik (Registry Z. 216245, Gen Z. 219224/246256, `lease_stale` Z. 277283, Stale-Mark/Übernahme Z. 257264/346478, `raw/`-Recovery STALE-4 Z. 451478, `assert_no_wallclock` Z. 294300).
- `_bmad-output/implementation-artifacts/sprint-status.yaml`**mutiert**: Key `3-12-lease-lifecycle-und-commit-abschluss-transaktional-schliessen` `backlog``review` (Implementierungs-Commit; finaler `done`-Flip im Step-05-Status-Sync nach konvergiertem Review-Loop); `last_updated` (Format `MM-DD-YYYY HH:MM`).
- `wiki/log.md`**append** (Vertrag §5): Story-3.12-Eintrag (Verankerung §5.18, Sandbox L-1..L-9, Status-Flip, Validator-Verdikt) — **nur** vertragskonforme fachliche + Koordinationsereignisse (AC-6). **AC-6-Grenz-Klärung (Loop 2, D1):** die dokumentierten Run-Nachweise (Sandbox-Zählung, Validator-Verdikt, Status-Flip, Artefakt-Pfadszitate) sind **notwendige Koordinationsereignisse** im Sinne von Vertrag §5 — die verbotene Kategorie ist **Build-/Review-/Story-/Sandbox-*Historie*** (Kategorie-benennende Einträge, z. B. „Sandbox-Protokoll", „Build-Historie"); die L-8-Grep ist entsprechend angepasst (Kategorie-Hyphenate; Artefakt- und Review-Kennzeichnungen im Fließtext bleiben zulässig, 3.10/3.11-Praxis unverändert).
- `_bmad-output/implementation-artifacts/deferred-work.md`**append**: Story-3.12-relevante Defers aus Review-Loop-1-Story-3.11 als aufgegriffen markieren („Sandbox-3.5/-3.6-check-then-act-Abgleich", „Banner-/I-O-Matrix-Kosmetik", „scopelock_header_banner"-Platzhalter) bzw. benannte Home-Zuordnung.
- `_bmad-output/implementation-artifacts/epic-3-context.md`**mutiert** („Edit freely"): Technical Decision Z. 43 „Transaktionaler Lifecycle" von „Zielzustand… nicht verankert" auf Ist-Zustand (§5.18) aktualisieren.
**Read-only evidence (AD-3):** `schema/validator.md` (Rev 9 — prüft **keine** Lease-/Koordinationszustände, Z. 194/195 nur `stale_after`-Lebenszyklus-WARN; Validator-Erweiterung nicht Teil dieser Story), `schema/wiki-compiler.md`, `adapters/`, `raw/` (z. B. `raw/prd/…`, `raw/epics/…`). `schema/canonical-terms.md` append-only unangetastet.
## Tasks & Acceptance
**Execution:**
- [x] `schema/compiler.md` — §5.18 einfügen (nach §5.17, vor §6): Pkt. 17 gemäß Intent; §7-Bullet und §8-Revisionslog **Revision 3.7** nachführen; bestehende §5.11/§5.12/§5.13/§5.17 textuell unverändert (Fugen-Identität)
- [x] `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh` — L-1..L-9 (Lifecycle, Abort-/Protect-Zustandsmaschine, Baseline-Rollback Index+Worktree, Kill-Point-Tests, Clean-Input-Guard), harte PASS/FAIL, Exit 0, `/tmp`-Baum, nie realer Ist-Baum
- [x] `_bmad-output/implementation-artifacts/sprint-status.yaml` — Key 3-12 → `review` (Impl-Commit; finaler `done`-Flip Step-05); `last_updated` aktualisieren
- [x] `wiki/log.md` — Story-3.12-Eintrag (Verankerung, Sandbox, Status-Flip, Validator-Verdikt) — nur vertragskonforme Inhalte (AC-6)
- [x] `_bmad-output/implementation-artifacts/deferred-work.md` — relevante Story-3.12-Home-Defers als aufgegriffen markieren/zuordnen
- [x] `_bmad-output/implementation-artifacts/epic-3-context.md` — Technical Decision Z. 43 auf Ist-Zustand (§5.18) aktualisieren
**Acceptance Criteria:**
- Given einen lebenden Lease-Halter, when eine höhere Generation sichtbar wird, then bleibt seine Lease aktiv; Staleness verlangt bestätigten Abbruch oder abgelaufene Liveness sowie atomare Ownership-Prüfung (AC-1).
- Given eine nachweislich stale Lease, when sie übernommen wird, then gelingt die Übernahme genau einmal, nennt die ersetzte Holder-ID und hinterlässt genau eine aktive Root-Lease (AC-2).
- Given getrackte oder ungetrackte fremde Änderungen im Mutationsbereich, when der Preflight läuft, then folgt er einer eindeutigen Abort-/Protect-Zustandsmaschine und stellt geschützte Bytes nach dem Run vollständig wieder her (AC-3).
- Given einen Fehler nach Mutation oder Staging, when Rollback läuft, then restauriert er explizit aus dem bezeichneten Baseline-Commit sowohl Index als auch Worktree; der Post-Rollback-Diff gegen die Baseline ist leer (AC-4).
- Given einen erfolgreichen Run, when er freigegeben wird, then sind Mutation, zulässiger Log-/Koordinationsnachweis und Release dauerhaft, der aktive Lock ist entfernt, der Worktree ist sauber und der unmittelbar folgende Run besteht den Clean-Input-Guard (AC-5).
- Given `wiki/log.md`, when ein Run protokolliert wird, then enthält das kanonische Knowledge Log nur vertragskonforme fachliche Änderungen und notwendige Koordinationsereignisse; Build-, Review-, Story- und Sandbox-Historie liegt außerhalb des Knowledge Bundle (AC-6).
- Given ein Lifecycle, when Kill-Point-Tests vor Mutation, nach Mutation, vor Commit und nach Commit laufen, then beweisen sie den jeweils konsistenten Endzustand (AC-7).
## Design Notes
Der Lifecycle ist der **geschlossene transaktionale Rahmen** der Koordinations-Dimension: Akquise (§5.11/§5.17) → Preflight/Protect (§5.11 Pkt. 3) → Mutation (§5.9/§5.10/§5.16) → Rollback (§5.13) → durable Release (§5.11 Pkt. 1/§5.17 Pkt. 1 Ref-Delete) → Clean-Input-Guard (Nachfolger-Bedingung). §5.18 ordnet die bestehenden Mechaniken in eine Zustandsmaschine und ergänzt nur die fehlenden Klammern (Liveness/Ownership, Abort-/Protect-Zustandsmaschine, Index+Worktree-Rollback, durable Release + Clean-Input-Guard, kanonisches Log, Kill-Point-Tests).
**Kernprinzipien:**
- **Keine Wanduhr-TTL:** Staleness folgt nie aus Kalenderzeit (A0-20), sondern aus committeten Zuständen (Gen-Registry, §5.12) **plus** bestätigtem Abbruch / abgelaufener Liveness (deterministisch belegt, z. B. Halter-Branch-Verlust/Registry-Status) — Liveness ist ein beobachtbarer, kein zeitlicher Zustand.
- **Ownership-Prägung:** Akquise (create-only-Existenz, §5.17), Übernahme (atomare Ownership-Prüfung: Inhaber-Run-ID muss Lock-Inhalt tragen) und Release (Ref-Delete nur durch Inhaber) sind einheitlich ownership-gebunden.
- **Rollback = Baseline-Punkt:** Index **+** Worktree werden aus `<Baseline-Commit>` restauriert (`git reset --hard <Baseline-Commit>` ist zulässiges Beispiel — deterministisch, ohne Wanduhr); Post-Rollback-Diff leer (§5.13 Pkt. 3 Zustands-Restaurations-Invariante).
- **Clean-Input-Guard:** Nach Release muss ein Folge-Run ohne `INPUT_UNCOMMITTED`-Abbruch starten — d. h. sauberer Worktree, kein hängender Lock, geschützte Fremd-Bytes sind restauriert. Kill-Punkt „nach Commit" antizipiert genau diesen Zustand.
## Verification
**Commands:**
- `bash _bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh` -- expected: L-1..L-9 harte PASS, Exit 0, kein Zugriff auf reales Bundle/`raw/`.
- `grep -n "## 5.18" schema/compiler.md` und `grep -n "Revision 3.7" schema/compiler.md` -- expected: §5.18-Überschrift und §8-Revisionslogeintrag vorhanden; bestehende §5.11/§5.12/§5.13/§5.17-Texte unverändert (diff prüft nur additive/berichtigte Zeilen).
- `git diff --stat` (Repo-Root) -- expected: nur Story-3.12-Touched-Files (compiler.md, sandbox-3-12, spec, log.md, sprint-status.yaml, deferred-work.md, epic-3-context.md); `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/` ohne Diff (AD-3); `git status --porcelain -- wiki/` zeigt nur `wiki/log.md`.
**Manual checks (if no CLI):**
- Keine Beschreibung nötig — sämtliche Nachweise laufen über die Sandbox (Exit-Code) und additive Diffs.
## Suggested Review Order
**Normative Verankerung (Einstieg)**
- §5.18 als transaktionaler Lifecycle der Koordinations-Dimension; Pkt. 2 verlangt atomare Ownership-Prüfung (Anschluss an §5.17 Sutur).
[`compiler.md:426`](../../schema/compiler.md#L426)
- Liveness/Ownership (AC-1), stale-Übernahme genau einmal mit CAS (AC-2), Durable Release + Clean-Input-Guard (AC-5).
[`compiler.md:430`](../../schema/compiler.md#L430)
- Abort-/Protect-Zustandsmaschine (AC-3), Baseline-Rollback Index+Worktree (AC-4), kanonisches Log (AC-6), Kill-Point-Tests (AC-7).
[`compiler.md:432`](../../schema/compiler.md#L432)
**Lifecycle-Mechanik (Sandbox)**
- Atomarer Ownership-CAS im Takeover (Review-Patch Loop 1): der entscheidende Guard ist der Old-Value-Write `git update-ref <ref> <neu> <oldblob>`; Loop 2 ergänzt `scopelock_healthy`/LOCK_READ_ERROR-Propagation (P1).
[`run-sandbox.sh:153`](./sandbox-3-12/run-sandbox.sh#L153)
- Stale-Übernahme L-2: positiver Exactly-once-CAS plus negative atomare Abweisung (falscher Old-Value → kein Clobber) plus Takeover-Exactly-once am Takeover-Pfad (Loop 2 P5); genau eine aktive Root-Lease, ersetzte Holder-ID benannt.
[`run-sandbox.sh:367`](./sandbox-3-12/run-sandbox.sh#L367)
- Abort-/Protect-Zustandsmaschine L-3/L-4 (getrackt/ungetrackt, byte-identischer Restore, Loop 2 P3-Backup-Checks) und Baseline-Rollback L-5/L-6 (Index+Worktree, Post-Rollback-Diff leer, L-6 „valide committet" hart verifiziert, Loop 2 P2).
[`run-sandbox.sh:442`](./sandbox-3-12/run-sandbox.sh#L442)
- Durable Release L-7 (Lock per Ref-Delete, Clean-Input-Guard des Folge-Runs, Release-ohne-Ownership negativ geübt, Loop 2 P2) und Kill-Point-Tests L-9 (KP1KP4, Ghost-Diff-Probe via `assert_invariant`, Loop 2 P4).
[`run-sandbox.sh:648`](./sandbox-3-12/run-sandbox.sh#L648)
- Kanonische Log-Grenze L-8 (nur vertragskonforme Fach-/Koordinationsereignisse; Kategorie-Check an Praxis angepasst + Negativ-Kontrolle, Loop 2 D1).
[`run-sandbox.sh:700`](./sandbox-3-12/run-sandbox.sh#L700)
**Status- & Nachweissynchronisation (peripher)**
- Story-3.12-Eintrag im kanonischen Knowledge Log (Vertrag §5; Verankerung §5.18, Sandbox L-1..L-9).
[`log.md`](../../wiki/log.md#L1)
- Transaktionaler Lifecycle-Zielzustand auf Ist-Zustand (§5.18) nachgeführt.
[`epic-3-context.md:43`](./epic-3-context.md#L43)
- A-2-/I-O-Matrix-/Banner-Defers als aufgegriffen markiert; weiterreichende Defers (echte Zwei-Worktree-Übernahme → Story 3.13) benannt.
[`deferred-work.md:606`](./deferred-work.md#L606)
- Key 3-12 auf `review` geflippt (finaler `done`-Flip nach konvergiertem Review-Loop).
[`sprint-status.yaml:65`](./sprint-status.yaml#L65)
### Review Findings
_Bmad-code-review Loop 1 (2026-08-21), 4 Layer (blind-hunter/edge-case-hunter/verification-gap/acceptance-auditor). Triage: **konvergiert** — kein intent_gap, kein bad_spec; 4 Patch-Kategorien auto-fixiert, 1 Defer, Rest begründet abgewiesen. Siehe `## Spec Change Log` unten für die vollständige Triage-Struktur._
_Bmad-code-review Loop 2 (2026-08-22), 4 Layer, Diff `80480af..HEAD`. Triage: 1 decision-needed, 7 patch, 2 defer, 6 dismissed; kein intent_gap, kein bad_spec._
- [x] [Review][Decision] **D1: AC-6/§5.18-Pkt.-6-Grenze widerspricht dem eigenen Artifact (wiki/log.md-Eintrag + Code-Map-Vorgabe)** — Der reale `wiki/log.md`-Story-3.12-Eintrag benennt `_bmad-output/…/sandbox-3-12/run-sandbox.sh`, „bmad-code-review", „Review-Loop-1", die L-1..L-9-Szenario-Historie, „Sandbox-Nachweis 9/9 harte PASS", „Validator-Verdikt" und den Status-Flip — exakt die Build-/Review-/Story-/Sandbox-Historie, die §5.18 Pkt. 6/AC-6 außerhalb des Knowledge Bundle verlangt. Die eigene L-8-Kategorie-Grep der Sandbox (`_bmad-output|run-sandbox|sandbox-3-12`) würde auf diesem Eintrag HARD-FAIL auslösen; zudem instruiert die Code Map dieser Spec (Code-Map-Zeile „Sandbox L-1..L-9, Status-Flip, Validator-Verdikt") genau diesen Inhalt — innere Spec-Kontradiktion zwischen AC-6, Sandbox-L-8 und Code-Map.
- [x] [Review][Patch] **P1: LOCK_READ_ERROR stirbt nur im Subshell — korrupter Lock wird en-bloc-stale klassifiziert (AC-1-Gate)** [run-sandbox.sh:127, 223] — `scopelock_content`-`exit 1` in `$(…)`-Substitution wird geschluckt (Eltern-Shell fährt mit leerem Wert fort); `lease_liveness_stale` interpretiert dann leer ≠ Holder-Run-ID als „Halter hält Lock nicht mehr" → lebende Lease mit korruptem Lock-Blob wird stale → Übernahme einer möglicherweise lebenden Lease. Der harter-LOCK_READ_ERROR-Zweig (PATCH 4c) ist praktisch wirkungslos, wo immer die Funktion per Kommandosubstitution aufgerufen wird.
- [x] [Review][Patch] **P2: Release-Fehler-Pfad (HARD-FAIL, Lock nie hängend) und „Fehler nach Commit" nie negativ geübt** [run-sandbox.sh:542-576] — Matrix-Zellen „Release-Fehler = HARD-FAIL; Lock nie hängend" (RELEASE_DURABLE) und „Fehler nach Commit" (ROLLBACK_NACH_COMMIT_VOR_RELEASE) sind nur im Erfolgszweig belegt; `scopelock_release`-Ownership-/Ref-Delete-Fehlschlag (Lock hängt) wird nirgends negativ demonstriert. Loop-1-REJECT begründet Commit-Immutabilität (AD-17f), deckt die Release-Fehler-Lücke aber nicht ab.
- [x] [Review][Patch] **P3: Setup-Robustheit (keine Exit-Checks) vergiftet alle Szenarien bei Teilversagen** [run-sandbox.sh:37-93, 410-411, 468-469] — `mktemp -d`/`mkdir`/`cd`/`git init`/`git commit -qm "Baseline"` ungeprüft (nur `set -u`); `$BASE` nie verifiziert (leerer/falscher Wert vergiftet alle `$BASE`-Vergleiche in L-1..L-9 und den `isolate()`-Fallback); L-3/L-4-Backup-`cp` ungeprüft, bevor `git checkout --`/`git clean -qfd` fremde Bytes verwerfen (stille AD-17e-Löschung; spätere sha256-Prüfung verschleiert die Ursache).
- [x] [Review][Patch] **P4: AC-7-Kill-Punkt-Ghost-Diff-Probe fehlt in L-9; `reg_write`-Helfer tot** [run-sandbox.sh:674-712, 156-167] — L-9 KP2/KP3 asserten nur Staged-Menge und HEAD; unstagede Ghost-Dateien im Worktree (Matrix „kein Ghost-Diff außerhalb erlaubter Menge") werden nicht geprüft — die definierten Helfer `inv_viol`/`assert_invariant` werden in L-9 nie aufgerufen; `reg_write` (Z. 156) definiert, aber nie aufgerufen (toter Code; Code-Map-Übernahme-Behauptung ungenau).
- [x] [Review][Patch] **P5: AC-1-Ownership-Stale-Zweig nie positiv getestet; „keine Doppel-Übernahme" prüft falsches Primitiv** [run-sandbox.sh:223-225, 375-377] — `lease_liveness_stale`-Zweig „Halter hält Lock nicht mehr" (Ownership-Quelle, §5.18 Pkt. 1 Bedingung (ii)) wird nie positiv ausgeübt (L-1: Gen allein; L-2: Registry-Marker-Zweig); L-2-Zweitversuch via `scopelock_acquire` beweist Create-only-Exklusivität, nicht Takeover-Exactly-once (Loop-1-Negativ-CAS deckt nur den falschen-Old-Value-Fall).
- [x] [Review][Patch] **S1: Status-/Sync-/Spec-Struktur inkonsistent** [spec-Frontmatter, Z. 87, Z. 12, sprint-status.yaml, wiki/log.md] — Frontmatter `review_loop_iteration: 0` trotz dokumentiertem konvergiertem Loop 1 (Präzedenz 3.11: 0 → 1); Frontmatter `status: done` bei Sprint-Key `review` (finaler done-Flip gehört in Step 05 — der im Abschluss-Commit „Status-Sync" beanspruchte Sync fehlt im Diff); doppelte `## Spec Change Log`-Überschrift (leere Z. 87); fehlendes `</frozen-after-approval>`-Schließtag (Präzedenz spec-3-11 schließt); `last_updated` 08-21-2026 20:45 (log.md-Nachweis) vs. 21:24 (sprint-status.yaml Istdiff).
- [x] [Review][Patch] **S2: Anker-/Zählungs-Drift in Suggested Review Order, Code Map und deferred-work** [spec-SRO run-sandbox.sh:365/416, Code Map Z. 57-58, deferred-work.md Z. 613/620] — SRO-Anker zeigen falsche Szenarien (Ist: L-3=Z. 392, L-4=Z. 447, L-7=Z. 584, L-9=Z. 670; beworbene 365 liegt in L-2, 416 in L-3-Frontmatter); „13 I/O-Matrix-Zeilen" (Ist 12 Datenzeilen); `scopelock_header_banner` „Z. 178-180" (Ist 280-282); Code Map „sandbox-3-11 544 Z." (Ist 543) und Übernahme-Behauptung `db_check`/`free_lease`/`reg_write` (tatsächlich: Preflight inline via `git status --porcelain`, `scopelock_release` statt `free_lease`, `reg_write` tot).
- [x] [Review][Defer] **DF1: AC-3-`git stash push`-Variante und kombiniertes AC-5 „Fremd-Bytes restauriert" nie geübt** [run-sandbox.sh:433-434 (Spec §5.18 Pkt. 3), 584-621] — §5.18 Pkt. 3 benennt `git stash push -- <Pfad>`/`stash push -u` als zulässige native Alternative; L-3/L-4 üben ausschließlich die Scratch-Zweige (kein Coverage/Guard für den Stash-Ast). AC-5-Clean-Input-Guard „geschützte Fremd-Bytes sind restauriert" wird nur isoliert in L-3/L-4 bewiesen, nie kombiniert mit Release + Folge-Run. — deferred, pre-existing (Variante-Abdeckung, kein Kernpfad-Defizit)
- [x] [Review][Defer] **DF2: `assert_no_wallclock` überbreit — Anwendung auf `log.md` strukturell unmöglich** [run-sandbox.sh:228-234, 314-315] — Regex `20[0-9]{2}-[0-9]{2}-[0-9]{2}[T ]` trifft jedes Jahr-Zeichenmuster inkl. der legitimen `### YYYY-MM-DD`-Datumsgruppen des `log.md` (Konventionsdaten, keine Wanduhr-Steuerung); der Check wird daher nur auf `registry/wiki` angewandt, L-1-Kommentar „kein Zeitstempel in Lock/Registry" behauptet aber mehr Coverage. — deferred, pre-existing (A0-20-Steuerung greift korrekt nur auf Registry/Lock)
## Spec Change Log
- **2026-08-21, Loop 1, Triage & Patches (kein Loopback — keine intent_gap/bad_spec):**
- **PATCH (Kern, Verification-Gap V1):** `scopelock_takeover` in sandbox-3-12 Z. 129146 auf **atomaren Ownership-CAS** umgestellt (`git update-ref <ref> <neu> <oldblob>` statt read-then-write ohne Old-Value) — §5.18 Pkt. 2 („Ersetzung als Ref-Schreibvorgang … nach atomarer Ownership-Prüfung") correct realisiert; L-2 übt jetzt die **negative atomare Abweisung** (falscher Old-Value-Blob → Ref-Write schlägt atomar fehl, Lock und genau-eine-aktive-Ref unverändert, kein Clobber) plus positiven Exactly-once-CAS.
- **PATCH:** §5.18 Pkt. 2 Z. 431 Tippfehler `AK-2:``AC-2:`.
- **PATCH (Härtung, Edge-Case-Hunter E2/E3/E4/E5):** `scopelock_acquire` lehnt leere Run-ID ab; `lease_liveness_stale` lehnt leeres `holder_runid` ab (keine vacuous-stale-Klassifikation); `scopelock_content` unterscheidet korrupten/fehlenden Blob (harter `LOCK_READ_ERROR`) von „Ref nicht vorhanden"; L-8-Kanonikalitätsprüfung auf verboțene Kategorie-Marker + Artefakt-Pfade präzisiert (kein false-HARD-FAIL auf legitime Fließtext-Wörter) mit Positiv-Kontrolle.
- **DEFER:** echte Zwei-Worktree-/Zwei-Prozess-Übernahme mit Synchronisations-Barriere bleibt Story 3.13-Abnahme (`deferred-work.md`-Append mit `source_spec:`-Format).
- **REJECT (begründet):** L-6-„Rollback nach Commit" (Commit immutabel per AD-17f; Matrix-„oder"-Semantik `Baseline-oder-valide-committet` korrekt erfüllt, L-5 übt den echten Rollback); in-review-Flip ist Workflow-Folge; banner in sandbox-3-12 ersetzt (sandbox-3-11 unverändert korrekt); `generated`-Staleness-Konvention; `baseline_commit` korrekt = Vor-Implementierungs-HEAD; `generated.at`-Wanduhr-Gap §5.14 Pkt. 3 dokumentiert; Review-Findings-Sektion leer weil Loop 1 eben läuft; ein-Worktree-Sandbox bewusst (Defer Zwei-Worktree→3.13); epic-3-context kein Doppelsatz (geprüft); „12-vs-13-I/O-Matrix-Zeilen" Missverständnis (Story-3.11-Matrix 4-vs-8, nicht Story-3.12-12-Zeilen).
- **Nachweis:** Sandbox-3-12 re-executiert nach Patches: **L-1..L-9, 9/9 harte PASS, Exit 0**; `grep -n "AK-2" schema/compiler.md` leer; AD-3-Files ohne Diff.
- **2026-08-22, Loop 2, Triage & Patches (konvergiert — kein intent_gap, kein bad_spec; 1 decision-needed D-3.12-1, 7 patch, 2 defer, 6 dismissed):**
- **DECISION D-3.12-1 (Nutzer-Entscheidung: Option 1 — Regel an Praxis angleichen):** AC-6/§5.18-Pkt.-6-Grenze vs. eigener `wiki/log.md`-Eintrag (Sandbox-/Review-/Validator-Nachweise, Status-Flip, Artefakt-Pfadszitate; L-8-Grep würde darauf HARD-FAIL; Code Map verlangt genau diesen Inhalt). Entscheidung: dokumentierte Run-Nachweise zählen als **notwendige Koordinationsereignisse** (Vertrag §5; 3.10/3.11-Praxis bestätigt); die L-8-Kategorie-Grep schärft auf Kategorie-Hyphenate (`(Build|Review|Story|Sandbox)-(Histor|Log|Protokoll|Bericht)`) + **Negativ-Kontrolle** (verbotene Form auf Probe-Datei ausserhalb `wiki/` muss den Guard auslösen). §5.18 Pkt. 6 und AC-6 bleiben textuell unverändert; Code-Map `wiki/log.md`-Zeile um die Grenz-Klärung ergänzt.
- **PATCH (Kern, P1):** `scopelock_content`/`lease_liveness_stale`/`scopelock_release`/`scopelock_takeover` — LOCK_READ_ERROR-Propagation: neuer Helfer `scopelock_healthy()` (Ref fehlt ODER lesbar; korrupter Blob → harter LOCK_READ_ERROR-Return vor jeder Liveness-/Release-/Takeover-Entscheidung); ein korrupter Lock wird nie mehr als „Halter hält nicht mehr" klassifiziert (AC-1-Gate bleibt geschlossen).
- **PATCH (Härtung, P2):** Release-Fehler negativ geübt (L-7: `scopelock_release` mit falscher Inhaber-Run-ID in Subshell → harter Fehlschlag, Lock unverändert); L-6 „valide committet" hart verifiziert (HEAD ≠ Baseline + Frontmatter im Commit + Commit-Betreff) — Matrix-Zellen „Release-Fehler = HARD-FAIL; Lock nie hängend" und „Fehler nach Commit" damit belegt (AD-17f-„oder"-Semantik im L-6-Kommentar benannt).
- **PATCH (Härtung, P3):** Setup-Robustheit — Exit-Checks auf `mktemp`/`mkdir`/`cd`/`git init`, verifizierter `$BASE`-Baseline-Commit (`git cat-file -e`), L-3/L-4-Backup-`cp` vor `checkout`/`clean` byte-identisch verifiziert (AD-17e-Stille-Löschung-Pfad geschlossen); bewusst **kein** `set -e` (negatives Assertion-Idiom `cmd && fail`), dokumentiert im Skriptkopf.
- **PATCH (Härtung, P4):** L-9-Kill-Punkte KP2/KP3 um Ghost-Diff-Probe (`assert_invariant` via `inv_viol`) — Index **und** Worktree gegen erlaubte Menge (Matrix „kein Ghost-Diff außerhalb erlaubter Menge" vollständig belegt); `reg_write` real geübt in L-1 (kein toter Code); Code-Map-Übernahme-Behauptung präzisiert (Preflight inline statt `db_check`, `free_lease` nicht übernommen → `scopelock_release`-Modell).
- **PATCH (Härtung, P5):** AC-1-Ownership-Stale-Zweig positiv getestet (L-1-Ende: nach Release + Gen-Ablauf klassifiziert die Liveness stale **über** die Ownership-Quelle); L-2 um Takeover-Exactly-once direkt am Takeover-Pfad (zweiter `update-ref`-CAS mit altem Old-Value schlägt atomar fehl, kein Clobber).
- **PATCH (S1):** Spec-Frontmatter `review_loop_iteration: 0 → 1`, `status: 'review'` (finaler `done`-Flip Step 05); doppelte `## Spec Change Log` (leere Instanz) entfernt; `</frozen-after-approval>`-Schließtag ergänzt (Präzedenz spec-3-11: Schließung vor Code Map); `wiki/log.md` `last_updated`-Nachweis (20:45) und `sprint-status.yaml` `last_updated` (21:24) im Step-05-Sync vereinheitlicht.
- **PATCH (S2):** Suggested-Review-Order-Anker auf Ist-Zustand neu gesetzt (takeover Z. 153; L-2 Z. 367; L-3 Z. 442; L-7 Z. 648; L-8 Z. 700); Code Map „544 Z." → 543; „13 I/O-Matrix-Zeilen" → 12 in `deferred-work.md` berichtigt; Banner-Zeilenangabe „Z. 178-180" → Z. 310.
- **DEFER (2, → deferred-work.md „Deferred from: code review of spec-3-12 …" 2026-08-22):** DF1 AC-3-`git stash push`-Variante + kombiniertes AC-5 „Fremd-Bytes restauriert" (Home: Sandbox-Härtung/Story 3.13); DF2 `assert_no_wallclock` überbreit/Kommentarschärfe (Home: Sandbox-Kosmetik).
- **REJECT (begründet, 6):** BH7 UNCOMMITTED_INPUT-vs-Protect (Zustandsmaschine §5.18 Pkt. 3 explizit: Preflight → benannter Abbruch → Schutz-Überführung, kein Widerspruch); BH8 L-3/L-4-Release mit uncommittetem `log.md` (Protect-/Abort-Szenarien, nicht durable Release — das üben L-6/L-7/L-9); AA12 `--short --porcelain`-Doppel-Flag (funktional, `--short``--porcelain`); AA13 `isolate()`-Szenario-Parameter dekorativ (Harness-Reset-Konvention, `git reset --hard $BASE` ist der operative Reset); BH14 AD-17e-Orphan „von `isolate()` gelöscht" (`isolate()` = Sandbox-Harness-Reset, kein Produktions-Release-Pfad; Pkt. 2 verlangt Erhaltung im Run-Endzustand, nicht über Harness-Reset hinweg); AA14 Sandbox-Synthetik-`log.md` nicht datumsgruppiert (Demo-Treue; das reale `wiki/log.md` ist datumsgruppiert).
- **Nachweis:** Sandbox-3-12 re-executiert nach Loop-2-Patches: **L-1..L-9, 9/9 harte PASS, Exit 0**; AD-3-Files (`schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/`) ohne Diff; §5.11/§5.12/§5.13/§5.17 textuell unverändert (Fugen-Identität).
@@ -0,0 +1,146 @@
---
title: 'Story 3.13 — Epic-3-Verifikations- und Abnahmegate'
type: 'feature'
created: '2026-08-22'
status: 'in-progress'
baseline_commit: 'c234fc361e006043c3c972cc03211b52998c2ddb'
review_loop_iteration: 0
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** Epic 3 gilt erst als abgeschlossen, wenn ein **reales, reproduzierbares Source→Compilation→Wiki-Abnahmegate** es qualifiziert (epic-3-context Z. 7, Z. 46, Z. 52; AD-5/AD-6/AD-13/AD-17a/b/dh, A0-12/13/15/16/19). Die 12 verankerten Sandbox-Suiten (3-1..3-12) beweisen die Einzel-Mechaniken jeweils isoliert in `/tmp`-Mini-Bundles; die **cross-cutting-Abnahmeebene** fehlt: kein einziges Kommando führt alle Epic-3-Szenarien fail-fast aus, der vollständige autorisierte Validator (`schema/validator.md` Rev 9) wird bisher nur **human-mechanisch** über `wiki/` gestellt (kein ausführbares Verfahren, D-3), die „zwei frische Agent-Kontexte"-Zwei-Run-Bestätigung ist ausdrücklich nur **mechanisch simuliert** (Subshells, spec-3-8 P-5 / compiler.md Z. 374 — der echte Nachweis ist dem 3.13-Gate vorbehalten), und der Epic-5-Abhängigkeits-Smoke (FR-15/16) plus Porcelain-Unverändertheit (git status --porcelain) eines Clean-Checkouts wurden nie als Gate geprüft.
**Approach:** Ein einziges, re-executierbares, fail-fast ausführbares **Gate-Skript** `_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh` orchestriert die vollständige Epic-3-Abnahme: (a) Repo-Weiter Bausatz: alle 12 Sandbox-`run-sandbox.sh` als Sub-Runs nacheinander, jede Assertion hart, jede Abweichung = Non-Zero-Exit; (b) echte vollständige Validator-Integration — ein frisch gestarteter **Agent-Kontext** liest `schema/validator.md` (Rev 9, 14 Punkte + §6-Fachprüfungen) und stellt ein Verdikt `SUCCESS`/`FAIL <pfad> <ursache>` über **alle** `wiki/`-Dateien des Clean-Checkouts per angefügter `verdict-*-dateien.md`-Datei; das Gate prüft deren Grammatik (§5, §5.1) und Exhaustivität (jede `wiki/**/*.md`-Datei außer nicht-`raw/`-externe hat genau ein Verdikt), schreibt aber **keine eigenen Validator-Urteile**; (c) zwei frische Agent-Kontexte (zwei getrennte `$SB`-Arbeitskopien, jeweils ausgemeindete Clone-Kopien, `git worktree add` auf denselben `$BASE`-Commit) führen `schema/compiler.md` über einer repräsentativen committeten Fixture aus; das Gate baut die Fixture auf (rohes Zuwachs-Paar + Ziel-Concepts, committet als `$BASE`), schreibt **selbst weder erwartete Wiki-Bodies noch Lease-/Log-/Git-Ausgänge** (§5.14 Pkt. 2; epic-3-context-Z. 46) und vergleicht die beiden Ergebnis-Bäume via `git diff <A> <B> -- wiki/` + Hash- und Frontmatter-Angleichung (nur die benannte `generated.at`-/`verified[].at`-Ausnahme); (d) G-Szenarien (G-1..G-8) als harte Assertions, inkl. der Epic-5-Abhängigkeits-Smoke (G-7: die vordefinierten Wissensfragen brauchen nur das `wiki/`-Bundle, keine Planungs-Referenzen) und Porcelain-Check (G-8: nach dem Lauf `git status --porcelain -untracked-files=no` leer). `schema/compiler.md`, `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` bleiben read-only (AC-4: das Gate ändert die Schema-Semantik nie still); der Gate-Lauf findet ausschließlich als `/tmp`-Kopie des realen Clone-Baums statt (nie im Ist-Baum), das reale `wiki/` wird nur als Textquelle gelesen.
## Boundaries & Constraints
**Always:**
- **Nicht primär-mutiert:** `schema/compiler.md`, `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` — read-only (AD-3; das Gate ist eine Abnahme, keine neue Instruktions- oder Validator-Semantik).
- **Ein Datei-Satz erzeugt:** `_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh` (re-executierbar, `bash run-sandbox.sh` aus Repo-Root). Es läuft **ausschließlich als `/tmp`-Kopie** des realen Clone-Baums (koppelt `git clone`/`git worktree add` + Bundle-Kopie), greift nie auf den Ist-Baum zu und hinterlässt den Ist-Baum `git status --porcelain`-unverändert (G-8).
- **Kein `sed -i`:** alle Mutationen im Gate-Skript über `cp`/`printf > file`/`git …`-Aufrufe (portabel macOS/BSD- und Linux/GNU-Userland, AC-2).
- **Harness schreibt keine erwarteten Wiki-Bodies / Lease-/Log-/Git-Ausgänge:** die beiden frischen Agent-Kontext-Läufe (c) erzeugen die Wiki-/Receipt-Ausgabe; das Gate vergleicht A vs B (§5.14 Pkt. 2), hard-codiert keinerlei erwartete Bodies/Receipts (AC-4/AC-5, epic-3-context Z. 46; Defer-Home: DET-1/DET-2-Plan-Literale, R-9-Receipt-Literale).
- **`raw/` bleibt Status quo:** der Clean-Checkout-Fixture-Roh-Zuwachs lebt in der /tmp-Fixture (Kopie des realen `raw/`-Baums + Zuwachs dort), nie als Mutation im realen `raw/`.
- **Validator-Ausführung:** der frische Agent-Kontext ist der DIR-Exekutor für `schema/validator.md`; das Gate validiert nur Verdikt-Grammatik/-Exhaustivität, urteilt nie selbst (D-3-Compliance).
- **AC-4-Defizit-Route:** erkennt das Gate ein Validator-Defizit, schlägt G-5 fehl (Non-Zero), Story 3.13 bleibt offen und benennt den Bedarf einer separat autorisierten Epic-1-Remediation; das Gate ändert dann nichts an Schema/Validator.
- **Sandbox-Nachweis:** Exit 0, harte PASS/FAIL, `/tmp`-Baum, nie der reale Ist-Baum (Muster sandbox-3-11/3-12).
**Ask First:**
- Einen Validator-Patch anzufassen (wäre Epic-1-Remediation, separat autorisiert) — oder Gate-Verhalten zu ändern, das auf realen Wanduhr-/`generated.at`-Ausnahmen beruht (A0-20).
**Never:**
- Kein Umschreiben von `schema/compiler.md`/`schema/validator.md`/`raw/`; kein neues Prädikat/keine neue §7-Invaliditätsklasse; kein Standalone-Inhalts-Befund (D-3); kein textueller Auto-Merge (AD-17c); keine stille Löschung (AD-17e); keine Wanduhr-/TTL-Steuerung (A0-20); kein echtes `wiki/`-oder-`raw/`-Schreiben durch das Gate (nur Textquelle); kein Zugriff auf echte fremde Worktrees.
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|---------------------------|----------------|
| SANDBOX_SUB_RUN_OK | n-te Sandbox liefert PASS/Exit 0 | Sub-Run erkennbar; FAIL → Non-Zero-Exit, Lauf bricht ab (fail-fast) | jede Abweichung = harter Non-Zero |
| VALIDATOR_AGENT_VERDIKT_OK | frische Verdikt-Datei je Datei-Pfad | Grammatik §5/§5.1 erfüllt; jede stattfindende `wiki/**/*.md`-Datei hat ein Verdikt; SUCCESS überall → G-3 PASS | fehlende/ungrammatische/FAIL-Verdikte = Non-Zero |
| VALIDATOR_DEFIZIT | fehlende/ungrammatische/FAIL-Verdikte werden nicht selbst repariert | G-5 PASS, Story bleibt offen; Bedarf einer Epic-1-Remediation wird textuell benannt | Non-Zero-Exit, kein Gate-Flip |
| FRESH_AGENT_TWO_TREES | zwei getrennte Arbeitskopien, gleicher `$BASE`-Commit | beide Runs beenden; `git diff A B` zeigt nur `at`-Abweichung; Bundle-Hashes/Reihenfolge/Plan A==B (außer `at`) | Divergenz außerhalb Ausnahme = harter FAIL |
| PERTURBIERTE_ENTSCHEIDUNG | eine der zwei Runs wird im Script über eine perturbed Option geroutet | Negativ-Kontrolle: divergente Bäume werden erkannt (G-6) | Non-Zero-Exit, kein false-PASS |
| PORCELAIN_CLEAN | nach dem Lauf | `git status --porcelain --untracked-files=no` auf dem Ist-Baum leer | überszogene Gate-Artefakte = Non-Zero |
| CONSUMER_SMOKE | nur das erzeugte Bundle | FR-15/16-Fragen ohne Planungs-Referenzen beantwortbar (G-7) | Referenz auf PRD/Spec/Spine = Non-Zero |
| GENERATED_AT_AUSNAHME | die einzige erlaubte Differenz | `generated.at`/`verified[].at`-Zellen dürfen abweichen | andere Zellen abweichend = Non-Zero |
</frozen-after-approval>
## Code Map
- `_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh`**neu erzeugt** (re-executierbar, Muster `sandbox-3-12/run-sandbox.sh` 820 Z. + `sandbox-3-8` Zwei-Worktree-Mechanik Z. 249-251/268/281-306/415-433; `set -u`, **kein** `set -e` — negatives Assertion-Idiom `cmd && fail`; `ROOT=$(mktemp -d /tmp/sb313-XXXXXX)`; `git config core.autocrlf false` + `core.filemode false`; `pass()/fail()` hart; End-Exit 0). Abschnitte: **(A) Setup** — Repo-/Bundle-/Referenz-Check, Referenz-Sandbox-Liste `sandbox-3-1..3-12`, `git status --porcelain`-Pre-Check; **(B) Käfig-Bau** — `/tmp`-Kopie des realen Clone-Baums (liest alle `wiki/**/*.md`-Dateiliste, kopiert `schema/raw/…`), `git init`+baseline; **(C) 12 Sandbox-Sub-Runs** — je `bash …/sandbox-3-N/run-sandbox.sh` mit Exit-Check; **(D) Validator-Agent** — frischen Sub-Agent-Kontext starten, der `schema/validator.md` über alle `wiki/`-Dateien ausführt und `verdict-*.md`-Dateien (SUCCESS/FAIL-Format) ablegt; **(E) Zwei-frische-Agenten** — zwei `/tmp`-Arbeitskopien (G-Kontext), shared `$BASE`-Fixture-ROH-Zuwachs `raw/alpha-v2.md`+Ziel `wiki/alpha.md` committet, beide Runs auf `git worktree add $BASE`, A/B-`git diff -- wiki/` + Hash-/Frontmatter-Vergleich, Nur-`at`-Ausnahme; **(F) G-1..G-8** harte Assertions; **(G) Nachweise/Porcelain** — Endzustands-Invariante + `git status --porcelain`-leerer Zustand.
- `_bmad-output/implementation-artifacts/sandbox-3-12/run-sandbox.sh`**read-only** Ausgangspunkt (L-1..L-9, Exit 0; Portrait der $5.18-Kill-Point-/Clean-Input-Härtung), Ausgangspunkt der `pass()/fail()`/Isolate-Helfer.
- `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh`**read-only** Quelle der Zwei-Worktree-Mechanik (DET-2: `git worktree add -q "$ROOT/wt-a" "$BASE"` Z. 249-251; `run2_worktree()` Z. 268; Manifest-Validierung Z. 281-306; Cross-Receipt-Vergleich A vs B Z. 415-433) — der 3.13-„Zwei-frische-Agenten"-Kern.
- `_bmad-output/implementation-artifacts/spec-3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato.md`**read-only** Defer P-5 (Z. 150): „die zwei frischen Agent-Kontexte sind mechanisch simuliert … der Nachweis echter frischer Kontexte erfolgt im Story-3.13-Abnahmegate über reale Agent-Läufe".
- `_bmad-output/implementation-artifacts/deferred-work.md`**append, read-only lesbar** (Aufgegriffen-/3.13-Home-Marker): L551 (DET-1/2-Plan-/Form-Literale), L560 (R-9-Receipt-Literale), L576 (wiki//raw/-Invarianten-Beweis), L594 (Zwei-Producer-verschiedene-`<id>`-Gewinner), L597 (Zwei-Worktree-Barriere-überlappung), L611 (Zwei-Worktree-Übernahme), L629 (geteilter-Ref-Namespace-Real-Beweis), L633 (AC-3-Stash-/AC-5-kombinierter-Clean-Input-Beweis) — diese werden über die echten Gate-Runs einschlägig **geschlossen** (Home 3.13-Abnahme realisiert); der zugehörige RD-Notiz-/Defer-Block bleibt unverändert (nur das Home ist erledigt).
- `_bmad-output/implementation-artifacts/sprint-status.yaml`**mutiert** nach Gate-Freigabe (Step-05): Key `3-13-epic-3-verifikations-und-abnahmegate` `backlog → done` (final); `epic-3` bleibt solange `in-progress`; `last_updated` (Format `MM-DD-YYYY HH:MM`).
- `_bmad-output/implementation-artifacts/epic-3-context.md`**mutiert** („Edit freely") nach Gate-Freigabe: Z. 7/46/52 von „…als abgeschlossen/…Gate…zwei frische Agent-Kontexte…" auf Ist-Zustand (Gate erfolgreich ausgeführt) aktualisiert.
- `wiki/log.md`**append** (Vertrag §5): Story-3.13-Eintrag (Gate-Lauf, Sandbox 3-13, G-1..G-8, Zwei-frische-Agenten-A/B-Identität, Sandbox-Nachweis 12×Exit 0, Validator-Integration, Porcelain-Check, sprint-status.yaml-Flip) — **nur** vertragskonforme fachliche + Koordinationsereignisse (AC-6; Dokumentations-Kategorie wie 3.12—3.13-Gate-Lauf selbst ist zulässiger Koordinations-Nachweis, Kategorie-Historie bleibt außerhalb).
**Read-only evidence (AD-3):** `schema/validator.md` (Rev 9, §0 L9-22 „rein textuell — kein Standalone-Programm (D-3)", §3 14-Punkte-Tabelle Z. 50-73, §5 Verdikt-Format Z. 139, §5.1 Selbstbegrenzung Z. 154, §6-Fachprüfungen EC-1/EC-3/EC-11 Z. 160-201, §7-Fixtures Z. 202-289); `schema/compiler.md` (Revision 3.7; §0 Z. 9, §6 Z. 441-446 — „Das gesamte Bundle gemäß schema/validator.md geprüft"; §7 Z. 486-499; §8-Revisionslog Z. 501-543); `schema/wiki-compiler.md`; `adapters/`; `raw/` (immutable Evidenz). `schema/canonical-terms.md` append-only unangetastet.
## Tasks & Acceptance
**Execution:**
- [ ] `_bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh` — Gate-Skript bauen (Setup → Käfig-Bau → 12-Sandbox-Fail-Fast → Validator-Agent → Zwei-frische-Agenten-A/B → G-1..G-8 → Porcelain-Check), harte PASS/FAIL, Exit 0, `/tmp`-Kopie nie Ist-Baum
- [ ] `schema/compiler.md`/`schema/validator.md`/`adapters/`/`raw/` — read-only verifizieren (kein Diff), `schema/canonical-terms.md` unangetastet (AD-3)
- [ ] Gate real ausführen (mehrere Läufe, inkl. skript-seitiger Zwei-/Mehr-Lauf-Stabilität) bis Exit 0; Negativ-Kontrollen ausführen
- [ ] `_bmad-output/implementation-artifacts/sprint-status.yaml` — Key 3-13 → `done`, `epic-3` Status-Kette prüfen, `last_updated` aktualisieren (Step-05 nach Review)
- [ ] `_bmad-output/implementation-artifacts/epic-3-context.md` — Abnahme-Zeile auf Ist-Zustand nachführen
- [ ] `wiki/log.md` — Story-3.13-Eintrag (Gate, Sandbox-3-13, G-1..G-8, Validator-Integration, Zwei-frische-Agenten, Porcelain) — nur vertragskonforme Inhalte (AC-6)
- [ ] `_bmad-output/implementation-artifacts/deferred-work.md` — 3.13-Home-Defers (L551/L560/L576/L594/L597/L611/L629/L633) als aufgegriffen/erledigt markieren (Defer-Block selbst historisch unverändert)
- [ ] Nachweis: `git status --porcelain` des realen Ist-Baums nach Gate-Lauf unverändert (G-8), `raw/` unverändert
**Acceptance Criteria:**
- Given einen sauberen Checkout, when das repositoryweite Epic-3-Gate ausgeführt wird, then führt ein Kommando alle Epic-3-Szenarien fail-fast aus; fehlgeschlagene Assertions, übersprungene Szenarien, fehlende Abhängigkeiten und Kindprozessfehler ergeben einen Non-Zero-Exit (AC-1).
- Given macOS/BSD- und Linux/GNU-Userland, when das Gate läuft, then ist es ohne plattformspezifisches `sed -i` reproduzierbar, arbeitet ausschließlich in temporären Repositories und hinterlässt `git status --porcelain` unverändert (AC-2).
- Given positive und negative OKF-Fixtures, when validiert wird, then wird der vollständige Vertrag aus `schema/validator.md` über alle `wiki/`-Dateien ausgeführt; unter anderem fehlendes `generated.by`, kalenderinvalides `generated.at`, unzulässige Source-Pfade und gebrochene Index-Links verhindern SUCCESS und Commit (AC-3).
- Given der autorisierte Validator-Vertrag oder seine ausführbare Aufrufbarkeit weist dabei ein Defizit auf, when das Gate es erkennt, then bleibt Story 3.13 offen und benennt den Bedarf für eine separat autorisierte Epic-1-Remediation; Story 3.13 darf die Schema-Semantik nicht still ändern, verantwortet aber die Integration und vollständige Ausführung des bestehenden Vertrags (AC-4).
- Given eine repräsentative committete Fixture, when ein frischer Agent-Kontext die kanonische Instruktion `schema/compiler.md` ausführt, then darf der Harness nach dem Setup weder erwartete Wiki-Bodies noch Lease-, Log- oder Git-Ausgänge selbst schreiben (AC-5).
- Given diese Fixture, when der Compilation Run endet, then demonstriert er Update eines bestehenden Concepts, Anlage einer neuen Wissenseinheit, kohärente Multi-Source-Synthese, vollständige Multi-Evidenz-Provenienz, Source-Immutabilität und byte-identische Erhaltung unabhängigen Wissens (AC-6).
- Given dasselbe kanonische Eingabemanifest, when zwei frische Agent-Kontexte in getrennt aufgebauten Worktrees laufen, then stimmen Run-Receipts und Bundle-State gemäß Story 3.8 überein; eine perturbierte Entscheidung oder Ausgabe wird erkannt (AC-7).
- Given nur das erzeugte Knowledge Bundle ohne Planungsartefakte, when ein unabhängiger Consumer im Epic-5-Abhängigkeits-Smoke-Test vordefinierte Wissensfragen beantwortet, then benötigt er weder Product Brief, SPEC, Architecture Spine noch Story-Historie zum semantischen Verständnis; solche Referenzen sind höchstens optionale Provenienz/Traceability (AC-8).
- Given ein bestandenes Gate, when die Status-Synchronisation läuft, then wird der Key `3-13-…` auf `done` geflippt und Epic 3 erst nach bestandenem Gate und `done` aller Stories 3.8..3.13 auf `done` gesetzt; bis dahin bleibt Epic 3 `in-progress` (AC-9).
## Spec Change Log
_Leer bis zum ersten bad_spec-Loopback (Step-04)._
## Design Notes
Der Gate-Kern löst das 3.8-P-5-Defizit: die Zwei-frische-Agenten-Bestätigung läuft hier **echt** — zwei getrennte Arbeitskopien desselben `$BASE`-Commits, jeweils frischer Kontext, `schema/compiler.md` als einzige Instruktionsquelle, A/B-Angleichung nur auf den erlaubten `at`-Zellen (§5.14 Pkt. 2). Der Validator bleibt **Instruktion**, wird aber durch einen dritten frischen Agent-Kontext zur **ausführbaren Prüfung** gemacht (er liest `schema/validator.md` und erzeugt `SUCCESS`/`FAIL <pfad> <ursache>` über alle `wiki/`-Dateien); das Gate prüft nur Verdikt-Grammatik/Exhaustivität, nie den Inhalt (D-3-Compliance). Die 12 Sandbox-Suiten werden als **Sub-Runs** in eine fail-fast-Sequenz gehoben — der „ein Kommando"-Wortlaut von AC-1.
**Beispiel (Zwei-frische-Agenten, deterministisch):**
```bash
# im Gate-Skript: Bau beider Bäume auf denselben Commit
git worktree add -q "$ROOT/wt-a" "$BASE" && git worktree add -q "$ROOT/wt-b" "$BASE"
# A vs B — nur die at-Zellen dürfen differieren:
git diff --stat "$ROOT/wt-a" "$ROOT/wt-b" -- wiki/ # ≠ leer (nicht-vakuum), at-only
```
Die repräsentative Fixture nimmt das 3-12-Sandbox-Muster (Mini-Bundle `wiki/index.md`+`alpha.md`+`log.md`, `raw/alpha-v1.md` als Baseline, Zuwachs `raw/alpha-v2.md` → Update von `alpha.md`, ergänzt um Bereichs-Synthese/`gamma` und `wiki/`-bleibt-Byte-identisch für unabhängiges `delta`) — in den `/tmp`-Käfig kopiert und committet, nie das reale Bundle.
## Verification
**Commands:**
- `bash _bmad-output/implementation-artifacts/sandbox-3-13/run-sandbox.sh` -- expected: 12 Sandbox-Sub-Runs (jede `exit 0`), Validator-Integration, Zwei-frische-Agenten A/B-Identität (nur `at`-Zellen abweichend), G-1..G-8 harte PASS, finaler Porcelain-Check, Exit 0; kein Zugriff auf den realen Ist-Baum.
- `git status --porcelain` (Repo-Root) -- expected: leer/nur Story-3.13-Artefakte; `schema/compiler.md`, `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` ohne Diff (AD-3); `sandbox-3-13/` als einziger neuer Verzeichnis-Inhalt.
- `git diff --stat` (Repo-Root) -- expected: nur Story-3.13-Touched-Files (run-sandbox.sh, spec, log.md, sprint-status.yaml, epic-3-context.md, deferred-work.md), kein Source-/Schema-Diff (AC-4: keine stille Schema-Änderung).
- optional: Deb-Run des Gate-Skripts mit `bash -x` -- expected: keine `sed -i`-Aufrufe (AC-2).
**Manual checks (if no CLI):**
- Keine Beschreibung nötig — sämtliche Nachweise laufen über das Gate (Exit-Code) und additive Diffs.
## Suggested Review Order
**Abnahme-Ebene (Kern)**
- Gate-Skript — Fail-Fast-Orchestrierung der 12 Sandbox-Suiten, `/tmp`-Käfig statt Ist-Baum, Porcelain-Check.
[`run-sandbox.sh`](./sandbox-3-13/run-sandbox.sh)
- Zwei-frische-Agenten A/B (AC-7): getrennte Worktrees auf `$BASE`, `git diff A B -- wiki/` nur `at`-Zellen, Negativ-Kontrolle perturbed.
[`run-sandbox.sh`](./sandbox-3-13/run-sandbox.sh)
- Validator-Integration (AC-3/AC-4): frischer Agent-Kontext führt `schema/validator.md` über alle `wiki/`-Dateien aus; Verdikt-Grammatik/Exhaustivität; Defizit → Non-Zero + Epic-1-Remediation-Bedarf.
[`run-sandbox.sh`](./sandbox-3-13/run-sandbox.sh)
- Harness schreibt keine erwarteten Ausgänge (AC-5), repräsentative Fixture beweist AC-6 inkl. byte-identische Erhaltung unabhängigen Wissens; Epic-5-Smoke G-7 (AC-8).
[`run-sandbox.sh`](./sandbox-3-13/run-sandbox.sh)
**Status- & Nachweissynchronisation (peripher)**
- Story-3.13-Eintrag im kanonischen Knowledge Log (Vertrag §5; Gate-Lauf, Sandbox 3-13, G-1..G-8, Zwei-frische-Agenten, Porcelain-Check).
[`log.md`](../../wiki/log.md#L1)
- Abnahme-Zeile von „…als abgeschlossen…Zwei-frische-Agenten-Kontexte…" auf Ist-Zustand nachgeführt.
[`epic-3-context.md`](./epic-3-context.md)
- 3.13-Home-Defers (Zwei-Worktree-Überlappung, DET/R-9-Receipt-Literale, wiki//raw/-Invariante, Stash-/Clean-Input-Beweis) als aufgegriffen/erledigt markiert.
[`deferred-work.md`](./deferred-work.md)
- Key 3-13 → `done` (final, nach Review), Epic-3 bleibt bis zum Gate-Abschluss `in-progress`.
[`sprint-status.yaml`](./sprint-status.yaml)
@@ -0,0 +1,161 @@
---
title: 'Story 3.7 — Reason/Mutate-Trennung & Konsistenz-Endzustand sicherstellen'
type: 'feature'
created: '2026-08-20'
status: 'done'
baseline_commit: '861e65f628001ffe8da05d0fd6834b7bb2689202'
review_loop_iteration: 1
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Intent
**Problem:** AD-6/A0-7 trennen einen Compilation Run **logisch** in Analyse → Änderungsplanung → Mutation → Validierung, doch die Compiler-Instruktion führt die Phasen-Trennung nirgends explizit als **durchsetzbare Disziplin** — die feste Ablaufstruktur in §0 zählt nur Phasen (0)(5), und die Plan-Vorprüfung (P2-Block) ist ein Baustein in §5.9 Pkt. 6, ohne als **Änderungsplanungs-Phase mit Endzustands-Garantie** ausgewiesen zu sein. Folge: Ein teilweise fehlgeschlagener Run könnte Zwischenstände hinterlassen, deren Konsistenz (AD-6: „Der beobachtbare Endzustand muss ein konsistentes Bundle sein") nur implizit über §6-Rollback abgesichert ist. A0-7-AC-2: „**Given** ein Fehler während der Mutation, **When** der Run abbricht, **Then** bleibt der beobachtbare Endzustand des Bundles konsistent".
**Approach:** Neue Sektion **§5.13 „Reason/Mutate-Phasen-Trennung & Konsistenz-Endzustand (Story 3.7)"** (nach §5.12, vor §6): die **logische Phasen-Disziplin** von AD-6 verbindlich in die Instruktion heben — der P2-Vorprüf-Block (§5.9 Pkt. 6) wird als **Änderungsplanung** (Analyse-Ergebnis → konsistenter Plan: Input-Zustand, Ziel-Pfade, Quellen-Existenz, Betroffenheits-Liste, Struktur-Erhaltung) institutionalisiert; die §§5/§5.9/§5.10-Mutationsmechanik wird als **Mutationsphase** unter **Veränderungs-Sperre nach Phasenabschluss** (Plan freeze), die §§6/§5.3-Rollback-Maschinerie als **Validierungsphase** mit **Zustands-Restaurations-Invariante** (Post-Rollback-Zustand == Baseline) geschlossen. **Ohne eigene Workflow Engine** (D-3, AD-6): die Trennung ist eine textuelle Ausführungs-Disziplin in einer Session. Endzustands-Konsistenz wird als **beobachtbare, über Git-State überprüfbare Eigenschaft** fixiert — nach einem abgebrochenen/fehlgeschlagenen Run ist der Bundle-Zustand der Baseline (oder der einer abgeschlossenen, valide committeten Mutation).
## Boundaries & Constraints
**Always:**
- Nur `schema/compiler.md` (neue §5.13 + §7-Auflösung + §8 Revisionslog-Revision 3.2) mutiert (D-3); `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` read-only (AD-3). Kein Standalone (D-3), keine eigene Workflow Engine (AD-6), keine neue §7-Invaliditätsklasse, kein Vertrags-Change, kein neuer Prädikat-/Format-Key, kein neuer Frontmatter-Key.
- Deterministisch aus dem committeten Git-State ableitbar (AD-17h/A0-19): Plan-Inhalt, Phasen-Reihenfolge, Rollback-Trigger und Post-Zustand; **keine Wanduhr/`now`-Steuerung** der Phasen-Disziplin (weder Planung noch Rollback hängen von Kalenderzeit ab).
- Endzustands-Konsistenz ist eine **beobachtbare, über Git prüfbare** Eigenschaft: Post-Rollback-Diff leer gg. Baseline bzw. Bundle == valide committeter Zustand des aktuellen Runs (Commit-Boundary = Mutation-Boundary, AD-17f, §0/§5.3).
- Die **vier** AD-6-Phasen decken sich **logisch** mit der §0-Ablaufstruktur (Analyse ≈ §1/§3.2, Änderungsplanung ≈ §5.9 Pkt. 6-P2-Block, Mutation ≈ §§45, Validierung ≈ §6); keine neue $Phasen-Nummerierung im §0-Listentext, §0 bleibt die deterministische Ausführungs-Folge.
**Ask First:**
- Einführung einer echten Planungs-Artefakt-Datei (separater Plan als persistentes Verzeichnis-Artefakt) statt des textuell festgehaltenen P2-Block-Plans — wäre eine neue Datei-/Baum-Regel außerhalb der Erhaltungs-Invariante.
- Änderung des §0-Phasen-Listentextes (Umformulierung der festen Folge) oder zusätzliche Phase — §0 bleibt Takt-Quelle; §5.13 ordnet nur logisch zu.
- Plan-freeze mit maschinellem Vergleich geplanter vs. tatsächlicher Mutationen über den §5.9-Pkt.-5-Diff-Selbsttest hinaus.
**Never:**
- `raw/`-Inhalte verändern (AD-3); uncommittete fremde `wiki/`-Änderungen löschen (AD-17e); textuelle Auto-Merge bei Branch-Konvergenz (AD-17c); Wanduhr/`now`-Zeitstempel als Phasen- oder Rollback-Steuer-Größe; eigener `# Log`-Stand in der Registry (`wiki/log.md`-Akkumulator ist alleiniger Aufzeichnungs-Ort, Vertrag §5); Standalone-Compiler/eigene LLM-Runtime (D-3, AD-11); Änderung an `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`.
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|---------------------------|----------------|
| PLAN_BEABSICHTIGT | erkannte Erkenntnis + bestehende Reconcile-Kandidaten; P2-Block-Ausführung | konsistente Änderungsplanung: Input-Zustand ⊇ Zuwachs committet, Ziel-Pfade ⊆ KandidatenNeu-Anlage, Quellen-Existenz (EC-1), Betroffenheits-Liste (§3 Pkt. 2), Struktur-Erhaltung; Plan wird textuell festgehalten | Plan-Defizit benannt (NFR-4); fehlgeschlagene Vorprüfung ⇒ keine Mutation (Kette: Abbruch vor Mutationsphase) |
| PLAN_FREEZE | Mutationsphase beginnt | Änderungsplanung ist abgeschlossen; **Veränderungs-Sperre**: kein Plan-Gegenstand außerhalb der erlaubten Pfad-Menge (Kandidatenliste Neu-Anlage `log.md` `index.md`) wird mutiert | Verletzung = Ghost-Diff (§5.9 Pkt. 5): zurückrollen, textuell benannt |
| MUTATION_ABBRUCH | Fehler während der Mutationsphase | Run bricht ab; beobachtbarer Endzustand: **Zustands-Restaurations-Invariante** — Post-Rollback-Diff gg. Baseline leer / Zustand == Baseline; `raw/` unverändert (AD-3) | §5.3-Pkt.-3/§6-Pkt.-3-Rollback-Mechanik greift; Ghost-Diff-Rollback (§5.9 Pkt. 5) greift; beides deterministisch aus Git-Teilen |
| MUTATION_TEILFOLGE | mehrere geplante Mutationen, nur ein Teil ausgeführt | Rollback stellt Baseline wieder her; kein Teilerfolg wird als fertige Mutation veröffentlicht (Commit-Boundary = Mutation-Boundary) | Textuelle Failure-Benennung (NFR-4); Phasen-Disziplin unverändert |
| VALIDATION_FAIL | Validierung meldet FAIL | Run als gescheitert; §6-Pkt.-3-Rollback; **keine** weiteren Mutationen; Endzustand konsistent | Rollback gemäß §5.3/§6; `raw/` unverändert |
| VALIDATION_SUCCESS | alle `wiki/`-Dateien SUCCESS | Mutationen als Ganzes committen; Konsistenz = valide committeter Zustand; §6-Pkt.-4-Ausführungs-Nachweis | Commit erst nach Diff-Selbsttest ohne Ghost-Diff (§5.9 Pkt. 5); kein Wanduhr-Zeitstempel |
| KEINE_EIGENE_ENGINE | Anforderung „keine separate Workflow Engine" (AD-6) | logische Trennung in einer Session, kein neuer Prozess/Server/MCP | D-3/AD-11 unverändert; nichts Steuerndes wird gebaut |
## Code Map
- `schema/compiler.md`**primär mutiert** (D-3): neue Sektion **§5.13** „Reason/Mutate-Phasen-Trennung & Konsistenz-Endzustand (Story 3.7)" (nach §5.12, vor §6; Fixierung: §5.12-Ende Z. 341, §6-Start Z. 364 — neue Sektion zwischen beide, §5.13 Z. 343362); §0-Phasen-Listentext **unverändert** (Takt-Quelle; §5.13 ordnet nur logisch zu); §5.9-Pkt.-6-P2-Block **Wortlaut-unverändert**, wird als Änderungsplanung referenziert (Rückverweis, keine Doppel-Instruktion); §6-Pkt.-3/§5.3-Rollback **Wortlaut-unverändert**, als Validierungsphase + Zustands-Restaurations-Invariante geschlossen; §7 (`Z. 390ff.`) Reason/Mutate-Vorbehalt **als neuer §7-Bullet** „in §5.13 verankert (Story 3.7)" (AD-6, A0-7, AC-1/2/3/4); §8-Normreferenz-`AD-6` (`Z. 430`) von reiner Story-Zuordnung auf **§5.13-Anker** angehoben. Anker (IST-Zeilen nach Implementierung): §5.13-Sektion Z. **343**; §7-Bullet nach Z. **420** (nach „Relevanzbestimmung"-Bullet); §8-AD-6 in Z. **430**; Revisionslog-Eintrag **Revision 3.2** nach Z. **461**.
- `wiki/log.md`**append** (append-only, Vertrag §5): Story-3.7-Eintrag (Verankerung, Sandbox-Nachweis, Statuswechsel, Validator-Verdikt); bestehende Bullets unverändert.
- `_bmad-output/implementation-artifacts/sandbox-3-7/run-sandbox.sh`**neu** (re-executierbar, Muster sandbox-3-6, Exit 0): Szenarien CONSIST-1..CONSIST-7 (s. Design Notes), harte Pass/Fail-Assertionen, Erhaltungs-/Restaurations-Invariante erzwungen, keine Berührung des realen Ist-Baums.
- `_bmad-output/implementation-artifacts/sprint-status.yaml`**mutiert**: `3-7-reason-mutate-trennung-und-konsistenz-endzustand-sicherstell` `backlog``review` (Implementierungs-Flip `in-progress` + Review-Start-Flip `review` im selben Commit; finaler `done`-Flip im Step-05-Status-Sync); `last_updated` (Format `MM-DD-YYYY HH:MM`, HEAD-Präzision).
- `_bmad-output/implementation-artifacts/deferred-work.md`**append** (append-only): ggf. Story-3.7-Defers (z. B. maschineller Plan-vs-Ist-Vergleich über den §5.9-Pkt.-5-Diff-Selbsttest hinaus), nach Prüfung der vorhandenen Defer-Liste (keine bereits auf Story 3.7 gedeuteten Einträge; Stand: keine Home-3.7-Defers vorhanden).
- `_bmad-output/implementation-artifacts/epic-3-context.md`, `spec-3-6-…md`**read-only** (continue-context; Kette unverändert).
## Tasks & Acceptance
**Execution:**
- [x] `schema/compiler.md` — §5.13 einfügen (nach §5.12, vor §6); §0/§5.9-Pkt.-6/§6 Wortlaut-unverändert referenzieren; kein neuer Prädikat-/Format-/Frontmatter-Key
- [x] `schema/compiler.md` — Revision 3.2 in §8 belegen (AD-6/A0-7 auf §5.13-Anker, Abschlussklausel) und §7-Bullet Reason/Mutate in §5.13-Verankerung
- [x] `_bmad-output/implementation-artifacts/sandbox-3-7/run-sandbox.sh` — Szenarien CONSIST-1..7 (Plan-Erzeugung, Freeze-Sperre, Rollback-Restaurations-Invariante, Teilerfolg, Validierungs-Fail, Success-Commit, keine Engine), Exit 0
- [x] `_bmad-output/implementation-artifacts/deferred-work.md` — ggf. Story-3.7-Defers append-only; keine Spuren in bereits vorhandenen Einträgen (Stand: keine Home-3.7-Defers — nichts anzuhängen, wie per README)
- [x] `wiki/log.md` — (Implementierung) Story-3.7-Eintrag, `sprint-status.yaml` → in-progress; Review-Abschluss `done` im Review-Schritt (Workflow-Konvention)
**Acceptance Criteria:**
- Given ein Run, when er Änderungen plant, then erzeugt er zunächst eine konsistente Änderungsplanung (Analyse → Reconcile → Plan Changes → Mutate → Validate), textuell festgehalten (AD-6, A0-7; AC-1).
- Given ein Fehler während der Mutation, when der Run abbricht, then bleibt der beobachtbare Endzustand des Bundles konsistent — Post-Rollback-Diff gg. Baseline leer, `raw/` unverändert (AD-6; AC-2, I/O-Matrix MUTATION_ABBRUCH).
- Given ein Run, when er abgeschlossen ist, then wurden alle geplanten Mutations-Validierungen erfolgreich durchlaufen und die Mutationen als Ganzes committet (A0-7; AC-3, VALIDATION_SUCCESS).
- Given die Architektur-Anforderung, when umgesetzt, then ist die Trennung logisch, ohne eigene Workflow Engine (AD-6; AC-4, KEINE_EIGENE_ENGINE).
## Spec Change Log
- **2026-08-20 (Step-04-Review, Review-Loop 2, bmad-code-review 4 Layer; Nutzer D1/D2/D3 = 1/1/1):** keine Änderung am `<frozen-after-approval>`-Intent (keine intent_gap/bad_spec-Verdikt). Doku-Stellen nachgeführt: Code-Map `sprint-status`-Eintrag `backlog → in-progress`**`backlog → review`** (Impl.-Flip + Review-Start-Flip im selben Commit, D2); SRO-Anker-Text entsprechend. `### Review Findings` (3 Decision / 7 Patch / 3 Defer) angehängt; alle Decision- und Patch-Items nach der Patch-Runde abgehakt. Sandbox-Patch-Nachweise und die 3 Defers siehe `wiki/log.md`-Review-Loop-2-Bullet (2026-08-20) und `deferred-work.md`-Sektion „Deferred from: code review of … (Story 3.7, 2026-08-20)". `review_loop_iteration: 1`.
</frozen-after-approval>
### Review Findings (bmad-code-review, 2026-08-20 — Review-Loop 2, 4 Layer: blind-hunter / edge-case-hunter / verification-gap / acceptance-auditor)
**[Decision]** (offen — Nutzer-Entscheidung erforderlich):
- [x] [Review][Decision] (gelöst: 1) log.md-Protokoll-Widerspruch + fehlender deferred-work-Append — das done-Bullet (log.md:4) behauptet eine „Delokalisierungs-Doku (deferred-work.md append-only: Terminologie-Drift `INPUT_UNCOMMITTED`/`UNCOMMITTED_INPUT` … Misch-Run-Coverage Neu-Anlage+Update → Story 3.8)", in `deferred-work.md` existiert aber **kein** Story-3.7-Eintrag (grep-verifiziert); das in-progress-Bullet (log.md:5) sagt „Keine Story-3.7-Defers" — die beiden Aufzeichnungen widersprechen sich. Optionen: (1) deferred-work-Append nachtragen (done-Bullet wird wahr); (2) done-Bullet umformulieren (keine Delokalisierungs-Doku behaupten, „keine Defers" bleibt wahr). [wiki/log.md:4-5]
- [x] [Review][Decision] (gelöst: 1) sprint-status `backlog → review` im Diff vs. dokumentiertes `backlog → in-progress``git diff 861e65f..HEAD` zeigt Key `3-7-…` = `review`; Code-Map, SRO-Anker-Text, log.md-in-progress-Bullet und §8-Revision-3.2-Klausel dokumentieren alle `backlog → in-progress`. Optionen: (1) `review` behalten (Review-Start-Flip, File-Header-Konvention) + Dokumentation korrigieren; (2) auf `in-progress` setzen (Diff-Getreue) — der Step-04/05-Status-Sync setzt den Endwert ohnehin neu. [sprint-status.yaml:60]
- [x] [Review][Decision] (gelöst: 1) p2_plan-Element-(1)-Scope — Wiedererwägung der dev-intern abgelehnten E1: `p2_plan` prüft unscoped `git status --porcelain` (ganzer Tree), §5.9 Pkt. 6 Element (1) scope-t auf „Working-Copy von `raw/` und `wiki/` gegen HEAD". Das done-Bullet dokumentiert die Ablehnung („Sandbox läuft nur auf sauberen Bäumen") — in den IST-Szenarien ist der Scope-Unterschied wirkungslos; bei einer Sandbox-Erweiterung (untracked-State außerhalb raw/+wiki/) würde die Demo-Funktion abbrechen, wo die Instruktion es zuließe. Optionen: (1) Scope-Fix `git status --porcelain -- raw wiki` (E1 wiedereröffnen); (2) Ablehnung aufrechterhalten (dismiss); (3) Defer an Sandbox-Härtungsrunde. [run-sandbox.sh:253]
**[Patch]** (offen):
- [x] [Review][Patch] (angewendet) CONSIST-1-EC-1-Negativkontrolle nicht isoliert: `rm -f raw/alpha-v2.md` verschmutzt `raw/` (untracked deletion) → Element (1) (uncommitteter Input) feuert zuerst, Element (3) (Quellen-Existenz) wird nie allein geübt — die BEFUND-Zeile benennt EC-1, der Trigger ist aber Element (1). Fix: Löschung committen (`git rm -q` + commit), damit Element (3) am sauberen Baum isoliert feuert [run-sandbox.sh:343-350]
- [x] [Review][Patch] (angewendet) CONSIST-5-`raw/`-SHA-Assertion tautologisch: `raw_sha=$(sha256sum raw/alpha-v1.md …)` wird mit derselben Datei verglichen — immer wahr, die AD-3-Prüfung beweist nichts. Fix: gg. `git show "$BASE:raw/alpha-v1.md"` vergleichen (Muster CONSIST-4) [run-sandbox.sh:481-482]
- [x] [Review][Patch] (angewendet) CONSIST-6-SUCCESS-Demo committiert einen Zustand, der die §5.9-Update-Disziplin verletzt: die Body-Zeile zitiert `(raw/alpha-v2.md#S-3)`, die `sources`-Nachführung fehlt (im Kommentar als „gehört §5.9/§5.10" eingeräumt) — als Referenz-Demo eines VALIDATION_SUCCESS-Runs misleitet. Fix: Demo-Mutation um den `sources`-Eintrag ergänzen, damit der committete Zustand gültig ist [run-sandbox.sh:496-506]
- [x] [Review][Patch] (angewendet: Doku-Block — Voll-Rollback = §6-Pkt.-3-Teilzustand, Sandbox-Abweichung begründet) `rollback()` löscht **alle** untracked `wiki/`-Dateien (`git reset --hard` + `git clean -qfd wiki`), während der Ghost-Diff-Rollback (§5.9 Pkt. 5) nur die betroffenen Pfade wiederherstellt und AD-17e fremde uncommittete Änderungen schützt (Spec-Never: „uncommittete fremde wiki/-Änderungen löschen"). In den IST-Szenarien wirkungslos, aber die demonstrierte Rollback-Mechanik widerspricht der Instruktion, die sie demonstrieren soll. Fix: `git clean` auf Nicht-erlaubte Pfade begrenzen oder die bewusste Sandbox-Abweichung dokumentieren [run-sandbox.sh:299-313]
- [x] [Review][Patch] (angewendet: CONSIST-2 Misch-Run + `+`-Neu-Anlage in `p2_plan` + Absenz-Assertion) Neu-Anlage-Zweig ungetestet: kein Szenario übt den Plan-Freeze-Eintrag „∪ Neu-Anlage-Zielpfade"; der `assert_restored`-Neu-Anlage-Guard (`git cat-file -e "$BASE:wiki/$p"`-Negativzweig) ist toter Code. Fix: CONSIST-2 um einen geplanten Neu-Anlage-Pfad (`p2_plan` unterstützt den Marker `neu`) erweitern, Post-Rollback-Absenz asserten [run-sandbox.sh:166-169]
- [x] [Review][Patch] (angewendet) Tippfehler: „porcelan" (Z. 151), „NFR-4-texuelle Benennung" (Z. 237), „Ferien-Zustand" (Z. 571) [run-sandbox.sh:151,237,571]
- [x] [Review][Patch] (angewendet) §5.13 Pkt. 7: „der Determinismus-Vertrag (AD-17h/A0-19) gilt für … und Post-Zustand" ohne den dokumentierten `generated.at`-Wanduhr-Gap — §5.10 Pkt. 8 hält das Gap ausdrücklich als offene A0-20-Konvention (zwei unabhängige Runs → verschiedene `at` → Post-Zustand weicht ab). Fix: Ausnahme nachtragen („… Post-Zustand — mit dem dokumentierten `generated.at`-Wanduhr-Gap, §5.10 Pkt. 8") [schema/compiler.md:362]
**[Defer]**:
- [x] [Review][Defer] CONSIST-7-Label überschätzt: „identische Plan-/Rollback-/State-Outputs" — im Zwei-Run-Vergleich werden nur Plan-Outputs (`plan_sha`/`plan_state`) verglichen; der Rollback-/State-Nachweis (s7c) läuft einmalig, `log_sha` ist dokumentierte Baseline-Konstante. Die non-vakuum-Kern-Assertion (`plan_sha` byte-identisch) ist real; das Label ist breiter als die Assertion [run-sandbox.sh:516,572] — deferred (Label-Präzisierung; Home: Sandbox-Härtungsrunde)
- [x] [Review][Defer] A0-20-Post-Zustand-Negativkontrolle in der Sandbox fehlt: §5.13 Pkt. 3/7 bindet den Determinismus-Vertrag an den Post-Zustand, der `generated.at`-Wanduhr-Gap macht den Post-Zustand über unabhängige Runs nicht byte-identisch — die Sandbox testet den Gap weder negativ noch positiv [run-sandbox.sh:532-568] — deferred (Home: Story 3.8, A0-20-Home, §5.10-Pkt.-8-Präzedenz)
- [x] [Review][Defer] Pre-existing-Zeitpunktswort-Fuge: §5.9 Pkt. 6 „am **Anfang der Mutationsphase**" (Z. 287) vs. gefrorene I/O-Matrix + §5.13 „Abbruch **vor** der Mutationsphase" — §5.9 ist in Story 3.7 Wortlaut-unverändert (Rückverweis-Vertrag), die Fuge ist hier nicht ohne Ask-First schließbar [schema/compiler.md:287,356] — deferred, pre-existing (Home: spätere Instruktionsrunde)
## Design Notes
**Warum §5.13 als eigene Sektion, nicht §0-Umbau?** §0 (Z. 13) ist die **deterministische Takt-Folge** — (0)..(5) in fester Reihenfolge, das Rückgrat der Instruktion. AD-6 trennt **logisch**, ohne neue Workflow-Engine; die **vier** AD-6-Phasen sind eine **konsolidierende Klassifikation** derselben Ablaufstruktur (Analyse ≈ §1/§3.2, Änderungsplanung ≈ §5.9 Pkt. 6-P2-Block, Mutation ≈ §§45, Validierung ≈ §6). Ein Umbau von §0 allein würde die bestehenden Story-Statements (§5.9/§5.10/§5.11/§5.12: „dieselben Phasen §0: Interpretieren → Reconcile → Synthetisieren → Mutieren → Validieren") brechen. Deshalb: §5.13 referenziert den §0-Wortlaut **unverändert** und bindet die AD-6-Nomenklatur als Ausführungs-Disziplin an die bestehende Struktur — Review-Layer prüfen die Fugen-Identität (keine Um-Nummerierung im §0-Listentext, keine neue Phase).
**Der P2-Block (§5.9 Pkt. 6) ist die natürliche Änderungsplanung.** Bereits vorhanden und reproduzierbar: (1) Input-Zustand (AD-17a), (2) Ziel-Pfade, (3) Quellen-Existenz, (4) Betroffenheits-Liste (§3 Pkt. 2), (5) index.md-V-1, (6) Struktur-Erhaltungs-Check. §5.13 macht diese Vorprüfung zur **Änderungsplanung der AD-6-Kette** und ergänzt zwei institutionalisierte Eigenschaften: den **Plan-Freeze** (Veränderungs-Sperre — nach Phasenabschluss wird kein Pfad außerhalb der erlaubten Menge berührt, Kopplung an die §5.9-Pkt.-5-Diff-Probe) und den **Plan-Nachweis** (der Plan wird als Teil der textuellen Ausführungsdisziplin festgehalten — kein Artefakt-File, keine Erhaltungs-Invarianten-Ausweitung).
**Die Konsistenz-Garantie ist bereits vorhanden — §5.13 macht sie beobachtbar.** §6-Pkt.-3/§5.3-Pkt.-3-Rollback und der Ghost-Diff-Rollback (§5.9 Pkt. 5, „AD-6-Backstop") existieren; die Anforderung „Endzustand konsistent" ist aber bislang eine erzählte Eigenschaft, keine **git-überprüfbare Invariante**. §5.13 definiert die **Zustands-Restaurations-Invariante**: nach Abbruch/FAIL ist `git diff` gegen die Baseline leer (Bundle == Baseline) **oder** das Bundle ist der valide committete Zustand des Runs (Erfolgsfall; Commit-Boundary = Mutation-Boundary, AD-17f). Beide Pfade sind deterministisch und ohne Wanduhr.
**Sandbox (CONSIST-1..CONSIST-7, `bash run-sandbox.sh`, Exit 0):**
- CONSIST-1: konsistente Änderungsplanung — P2-Block erzeugt Plan (Input-Zustand, Ziel-Pfade, Betroffenheit, Struktur-Erhaltung) aus committetem Input, als Variablen-Set captured, Determinismus (AD-17h/A0-19)
- CONSIST-2: **Plan-Freeze** — Mutation außerhalb der Plan-Pfadmengen wird erkannt und verweigert/verhindert (Veränderungs-Sperre, Ghost-Diff-Kopplung §5.9 Pkt. 5)
- CONSIST-3: **Rollback-Restaurations-Invariante** — fehlgeschlagene Mutation rollt auf exakte Baseline zurück (SHA-256 byte-identisch), Bundle-Zustand == Baseline
- CONSIST-4: MUTATION_ABBRUCH — Abbruch nach Teilerfolg hinterlässt keinen Teilerfolg als fertige Mutation (Commit-Boundary), `raw/` unberührt
- CONSIST-5: VALIDATION_FAIL — §6-Pkt.-3-Rollback, keine weiteren Mutationen, Endzustand konsistent
- CONSIST-6: VALIDATION_SUCCESS — Mutationen als Ganzes committet, Diff gg. Plan-Menge gedeckt, §6-Pkt.-4-Nachweis
- CONSIST-7: **Determinismus-Zwei-Run + keine Engine** — gleicher Baum-Input → identische Plan-/Rollback-/State-Outputs; Trennung ohne Workflow-Engine (nur Shell/Git/Datei, D-3)
(Code-/Zahlgenauigkeiten: Szenario-Labels sind Fixierung der I/O-Matrix; die Implementierung trägt die harten Assertionen in der Sandbox.)
## Verification
**Commands (re-executierbar, ab Workspace-Root):**
1. `bash _bmad-output/implementation-artifacts/sandbox-3-7/run-sandbox.sh` — expected: CONSIST-1..CONSIST-7 harte PASS/Fail, Restaurations-Invariante erzwungen, Exit 0.
2. `grep -n "§5.13\|Revision 3.2" schema/compiler.md` — liefert §5.13-Sektion + Revisionslog-Eintrag; `grep -n "Reason/Mutate-Phasen-Trennung" schema/compiler.md` — die §5.13-Überschrift wortgleich (inkl. §5.12-Seam-Satz in §5.13-Intro).
3. Read-only (AD-3): `git status --porcelain` zeigt keinen Change an `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/`.
4. Validator-Lauf: alle `wiki/`-Dateien SUCCESS (unverändert, keine Inhalts-Mutation).
5. Auf den **`wiki/`-Scope begrenzt** (`git status --porcelain -- wiki/`): ausschließlich `wiki/log.md` (dieser Eintrag) — Erhaltungs-Invariante §5.9 Pkt. 5 gewahrt; `sprint-status.yaml`/`deferred-work.md`/`sandbox-3-7/` liegen außerhalb `wiki/` und sind nicht Teil der Diff-Probe (wie §5.9 Pkt. 5-Doku, Story-3.6-Präzedenz).
**Zu beachten (beim step-04-Review):** (a) §0-Phasen-Listentext, §5.9-Pkt.-6-P2-Block und §6-Pkt.-3/§5.3-Referenz müssen textuell **unverändert** bleiben — §5.13 verweist auf sie (Fugen-Identität: Um-Nummerierung oder neue Phase => Ask-First); (b) „die **vier** AD-6-Phasen decken sich logisch mit der §0-Struktur" ersetzt **keine** §0-Normierungen — sie ist eine Zuordnungstabelle, kein Parsing-Muss; (c) die vorhandene §8-Revisionslog-Nummer ist **3.1** (Story 3.6); **Revision 3.2 ist für Story 3.7 frei** (grep-verifiziert: keine 3.2 im Revisionslog); (d) kein neuer §7-Bullet ersetzt einen bestehenden — der Reason/Mutate-Bullet wird **ergänzt**, die bestehenden Story-Bullets (2.2/2.3/2.4/2.5/3.2/3.4/3.5/3.6) bleiben unverändert.
## Suggested Review Order
**§5.13-Reason/Mutate-Trennung-Instruktion**
- Einstieg: §5.13-Sektion — logische Phasen-Disziplin (AD-6/A0-7), Kern der Story, §5.12-Seam und §0-Zuordnungstabelle.
[`compiler.md:343`](../../schema/compiler.md#L343)
- Änderungsplanung: P2-Block (§5.9 Pkt. 6) als Phase institutionalisiert, Plan-Freeze (Veränderungs-Sperre, Ghost-Diff-Kopplung §5.9 Pkt. 5).
[`compiler.md:356`](../../schema/compiler.md#L356)
- Zustands-Restaurations-Invariante: Post-Rollback-Diff gg. Baseline leer / Bundle == valide committet; §6-Pkt.-3/§5.3-Rückverweis unverändert.
[`compiler.md:358`](../../schema/compiler.md#L358)
- Keine eigene Workflow Engine (D-3, AD-6): logische Trennung in einer Session, kein Prozess/Server/MCP.
[`compiler.md:362`](../../schema/compiler.md#L362)
**§7/§8-Nachweis**
- §7-Bullet Reason/Mutate-Trennung — „in §5.13 verankert (Story 3.7)", AD-6/A0-7, bestehende Bullets unverändert.
[`compiler.md:420`](../../schema/compiler.md#L420)
- §8-Revisionslog Revision 3.2 — Verankerung, Abschlussklausel, AD-6 auf §5.13-Anker.
[`compiler.md:461`](../../schema/compiler.md#L461)
**Sandbox-Nachweis**
- Sandbox CONSIST-1..CONSIST-7 — re-executierbar, harte PASS/Fail, Zustands-Restaurations-Invariante (Exit 0)
[`run-sandbox.sh:1`](../../_bmad-output/implementation-artifacts/sandbox-3-7/run-sandbox.sh#L1)
**Story-Protokoll/Defers**
- log.md — Story-3.7-Eintrag (Verankerung, Sandbox-Nachweis, Statuswechsel).
[`log.md:1`](../../wiki/log.md#L1)
- sprint-status.yaml — `3-7-…` = `review` im Diff (Implementierungs-Flip `in-progress` + Review-Start-Flip `review` im selben Commit); das `done`-Transition ist nicht Teil dieses Diffs (Step-05-Status-Sync).
[`sprint-status.yaml:60`](../../_bmad-output/implementation-artifacts/sprint-status.yaml#L60)
- deferred-work.md — ggf. Story-3.7-Defers append-only.
[`deferred-work.md:1`](../../_bmad-output/implementation-artifacts/deferred-work.md#L1)
@@ -0,0 +1,267 @@
---
title: 'Story 3.8 — Determinismus-Vertrag (AD-17h) als Agent-Instruktions-Validator umsetzen'
type: 'feature'
created: '2026-08-20'
status: 'done'
baseline_commit: 'c4cdf4b93e17b3f84dff03a4bd5608251be56268'
review_loop_iteration: 2 # Review-Loop-3 (2026-08-20): abgeschlossene Review-Loops (1, 2); in Loop-1/2 nicht gepflegt (0) — ab Loop-3 gepflegt (P-10)
course_correction: '_bmad-output/planning-artifacts/sprint-change-proposal-2026-08-20.md'
context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
## Re-Open-Delta (Story-3.8-Neu-Verhandlung, 2026-08-20)
**Neu verhandelt durch genehmigtes Sprint-Change-Proposal** — `_bmad-output/planning-artifacts/sprint-change-proposal-2026-08-20.md` (Status: approved; approved_by: ProMods; explizite Chat-Freigabe). Der Plan korrigiert die zyklische Behauptung, Story 3.8 sei abgeschlossen, während Epic 3 seine zugesagten Verträge noch nicht als kohärentes System-Inkrement belegt. Story 3.8 wurde daher aus `review` nach `in-progress` **zurückgenommen** und mit erweitertem Scope wieder eröffnet (Epics-Datei ACs Z. 357370):
1. **Getrennte Worktrees + frische Agent-Kontexte (Q-6/A0-19):** „zwei frische Agent-Kontexte in getrennt aufgebauten Worktrees; eine zweite Ausführung in derselben Session genügt nicht".
2. **Kanonisches Eingabemanifest:** hält Baseline-Commit, geordnete Source-Eingaben und jeden im Bundle sichtbaren Run-/Zeit-/Identitätswert explizit fest.
3. **Run-Receipt außerhalb des Knowledge Bundle:** Candidate-Liste, Reihenfolge, Plan, Entscheidungen und Output-Hashes.
4. **Keine Hartcodes, kein pauschales Maskieren:** weder erwartete Pläne noch Concept-Bodies werden im Test hart codiert; `verified`-Ereignisse werden niemals pauschal aus dem Vergleich maskiert.
Dieser Frozen-Intent wurde **nur in dem Umfang** geändert, den dieses Re-Open-Delta verlangt (document-human respektierend [Tippfehler-Korrektur Review-Loop-3, D-3 — autorisierte Neu-Verhandlung, dokumentiert im Spec Change Log]; Appendix: fehlendes `</frozen-after-approval>`-Schließtag nach dem Spec Change Log ergänzt). Die ACs in `_bmad-output/planning-artifacts/epics.md` (Z. 357370) sind die verbindliche Neu-Verhandlung; abweichende Alt-Formulierungen (namentlich „in einer Session") sind auf den Re-Open-Stand korrigiert.
## Intent
**Problem:** AD-17h/FT-10/A0-19 verlangen: Über denselben Git-State + dasselbe Eingabeset erzeugen **zwei unabhängige Runs denselben Bundle-State**; eine Abweichung ist ein Fehler der AD-16-Klassifikation, kein Rauschen. Das **Enforcement** lebt im MVP (D-3, Q-6) als **Agent-Instruktions-Validator** und muss **mechanisch bestätigt** sein, bevor es tragend wird. Der Ist-Zustand der Instruktion verankert den Vertrag zwar an vielen Stellen (AD-17h-Rückverweise in §3.2/§5.9/§5.10/§5.11/§5.12/§5.13), aber es fehlt die **geschlossene, aus dem committeten Git-State ableitbare Bestätigungs-Mechanik**: Es gibt keinen normativen Ort, der definiert, (a) was genau der „Bundle-State" ist, (b) wie die Zwei-Run-Bestätigung mechanisch abläuft, (c) welche **Ausnahmen** vom byte-identischen Vergleich gelten (namentlich der dokumentierte `generated.at`-Wanduhr-Gap, A0-20) und (d) wie eine festgestellte Abweichung klassifiziert wird (AD-16-Klassifikationsfehler). Mehrere bekannte Lücken sind hierher gewandert (Em-Dash-Normalisierung, Kollaps-Reichweite, Match-Scope, `at`-Gap, Orphan-Politik, Misch-Run-Coverage).
**Approach:** Neue Sektion **§5.14 „Determinismus-Vertrag & Agent-Instruktions-Validator (Story 3.8)"** (nach §5.13, vor §6): definiert (1) den **Bundle-State** als deterministische Projektion des committeten Git-States (vergleichbare Zustandsmenge, `at`-Feld als benannte Ausnahme), (2) die **Zwei-Run-Bestätigungs-Mechanik** als textuell festgehaltenes, re-executierbares Prüfverfahren — im Re-Open-Stand in **zwei getrennten, sauberen Worktrees** mit **frischen Agent-Kontexten** (Q-6/A0-19; eine zweite Ausführung in derselben Session genügt nicht); Vergleichs-Operandum = committeter Baum; identische Plan-/Kandidaten-/Reihenfolge-Outputs; kein neuer Prozess/Server/MCP, D-3 — unter einem **kanonischen Eingabemanifest** (Baseline-Commit, geordnete Source-Eingaben, output-sichtbare Run-/Zeit-/Identitätswerte) und mit einem **Run-Receipt** (Candidate-Liste, Reihenfolge, Plan, Entscheidungen, Output-Hashes) **außerhalb des Knowledge Bundle**; (3) die **Ausnahme-Menge** des Bundle-State-Vergleichs (der `generated.at`-Wanduhr-Gap als dokumentierte A0-20-Konvention; sonst byte-identisch) und (4) die **Abweichungs-Klassifikation** (jede Abweichung außerhalb der Ausnahme = AD-16-Klassifikationsdefekt, textuell benannt, NFR-4). Die Zwei-Run-Bestätigung wird als **Sandbox-Szenario** mechanisch demonstriert (zwei Worktrees, zwei unabhängige Läufe); weder Pläne noch Concept-Bodies werden im Test hart codiert, `verified`-Ereignisse werden niemals pauschal aus dem Vergleich maskiert (nur die benannte `at`-Ausnahme). Die bekannten Lücken (Em-Dash, Kollaps-Reichweite, Match-Scope, Orphan) werden als **geschlossene, deterministische Regeln** in die Instruktion gehoben — keine offenen Fragen verbleiben, die die Zwei-Run-Identität gefährden.
## Boundaries & Constraints
**Always:**
- Nur `schema/compiler.md` (neue §5.14 + §7-Auflösung des Determinismus-Vorbehalts + §8 Revisionslog **Revision 3.3** + gegebenenfalls Kollaps-/Match-Scope-/Orphan-Präzisierungen in §3.2/§5.10) mutiert (D-3). `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` **read-only** (AD-3). Kein Standalone (D-3), keine eigene Workflow-Engine/kein neuer Prozess/Server/MCP (AD-6, AD-11). Keine neue §7-Invaliditätsklasse, kein Vertrags-Change, kein neuer Prädikat-/Format-/Frontmatter-Key.
- Deterministisch aus dem committeten Git-State ableitbar (AD-17h/A0-19): Bundle-State-Vergleich, Zwei-Run-Bestätigungs-Mechanik, Ausnahme-Menge, Abweichungs-Klassifikation. **Keine Wanduhr/`now`-Steuerung** der Bestätigungs-Mechanik.
- Der `generated.at`-Wanduhr-Gap **bleibt als dokumentierte Ausnahme** bestehen (A0-20-Konvention, §5.9 Pkt. 2, §5.10 Pkt. 8, unverändert bindend bis Ask-First-Wechsel). §5.14 **definiert** die Behandlung von `at` im Bundle-State-Vergleich (Ausnahmemenge), ändert aber **nicht** das `generated.at`-Verhalten selbst.
- Bei einer Abweichung zweier Runs: die Abweichung **ist** ein AD-16-Klassifikationsfehler (kein akzeptables Rauschen, FT-10/AD-17h-AC); die textuelle Benennung (NFR-4) ist verpflichtend.
- Die **normalisierenden Lücken-Schließungen** (Em-Dash in der Kollaps-Klasse, Kollaps-Reichweite bei Läufen/führenden/trailenden Separatoren, Wortgrenzen-/Frontmatter-Scope der Stufe-a-Erhebung) werden als **deterministische Regel-Ergänzungen** in §3.2 umgesetzt — **append-only/kein Umbruch** des bestehenden §3.2-Wortlauts; die Orphan-Politik (§5.10 Pkt. 8) wird zu einer deterministischen Reconcile-Orphan-Regel präzisiert.
- Die Bestätigung ist **agent-instruction-basiert (D-3, Q-6)**: **zwei frische Agent-Kontexte in getrennt aufgebauten (sauberen) Worktrees** führen die Instruktion jeweils einmal aus und belegen die Zwei-Run-Identität (Q-6, A0-19) — eine zweite Ausführung in derselben Session genügt nicht; kein dedizierter Validator-Prozess, keine extra Runtime.
**Ask First:**
- **Änderung des `generated.at`-Verhaltens** selbst (z. B. Ableitung `at` aus dem Git-State statt Wanduhr) — §5.14 definiert nur seine Behandlung im Vergleich, der Konventionswechsel wäre Ask-First.
- Einführung einer **eigenständigen, ausführbaren Validator-Datei** (separates Skript/Programm außerhalb der Instruktion als Enforcer) statt der textuellen Agent-Instruktions-Disziplin — wäre ein Realisierungswechsel weg vom D-3/Q-6-Modell.
- Kollaps-Reichweite bei Läufen/führenden/trailenden Separatoren **anders** zu definieren als „Kollaps auf genau ein `-` + Trim" (z. B. Doppel-Bindestrich-Erhaltung) — jede andere Wahl bricht bestehende Sandbox-Erwartungen.
- Umbenennung/Neudefinition der vorhandenen §3.2-Erhebungsstufen (a/b/c) oder deren Reihenfolge.
**Never:**
- `raw/`-Inhalte verändern (AD-3); uncommittete fremde `wiki/`-Änderungen löschen (AD-17e); textueller Auto-Merge bei Branch-Konvergenz (AD-17c); Wanduhr/`now`-Zeitstempel als Bestätigungs-Steuergröße; eigener `# Log`-Stand in der Registry; Standalone-Compiler/eigene LLM-Runtime (D-3, AD-11); Änderung an `schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`; neuer Frontmatter-Key; neue §7-Invaliditätsklasse; neuer Prädikat-/Format-Key; eigene Workflow-Engine; neuer dedizierter Prozess/Server/MCP als Enforcer.
## I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|----------|--------------|---------------------------|----------------|
| BUNDLE_STATE_DEFINITION | gleicher committeter Git-State + gleiche Eingabemenge | Bundle-State definiert als deterministische Projektion: Datei-Gesamtheit unter `wiki/`/`raw/` gg. Baseline, Plan-/Kandidaten-/Reihenfolge-Outputs; `generated.at` als benannte Ausnahme | textuell begründet (NFR-4), keine Lücke |
| ZWEI_RUN_IDENTISCH | zwei unabhängige Runs, gleicher Git-State + Eingabeset | identische Bundle-States bis auf benannte `at`-Ausnahme; Sandbox-Assertion nicht-vakuum (echte Content-Identität) | PASS; bei Abweichung außerhalb Ausnahme: AD-16-Klassifikationsdefekt |
| ZWEI_RUN_ABWEICHUNG | zwei Runs, gleicher State, verschiedene Ausgabe | Abweichung = Fehler der AD-16-Klassifikation (kein Rauschen); textuell benannt, Run korrigiert/rollt zurück | NFR-4-Benennung; AD-17h-AC |
| AT_GAP_AUSNAHME | zwei unabhängige Runs, gleicher Input | `generated.at` darf abweichen (A0-20-Konvention, §5.9 Pkt. 2) — einzige benannte Ausnahme im Bundle-State-Vergleich; übrige Teile byte-identisch | dokumentiert (kein stiller Ausschluss) |
| NORMALISIERUNG_EM_DASH | Term mit Em-Dash `—` | Em-Dash in die Kollaps-Klasse aufgenommen (`[-–— _]``-`); identische canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen | deterministisch, kein stiller Ausschluss |
| NORMALISIERUNG_KOLLAPS | Term mit Läufen/führenden/trailenden Separatoren | Kollaps-Reichweite definiert: Kollaps auf genau ein `-` + Trim (führende/trailende entfernt), Läufe auf ein `-` | deterministisch, glaubhaft re-executierbar |
| MATCH_SCOPE_STUFE_A | Stufe-a-grep-Treffer mit/ohne Wortgrenze, Frontmatter-Treffer | Match-Semantik fixiert: ganzes Wort (Wortgrenzen), Frontmatter exkludiert (nur Body) | deterministisch, keine falschen Kandidaten |
| ORPHAN_POLITIK | neu committete `raw/`-Evidenz ohne Ziel-Pfad-Treffer | Orphan bleibt unzugeordnet, wird in `log.md` als verwaist protokolliert, kein Banner, keine stille Vorbearbeitung | deterministische Reconcile-Orphan-Regel (AD-16-Default) |
## Code Map
- `schema/compiler.md`**primär mutiert** (D-3): neue Sektion **§5.14 „Determinismus-Vertrag & Agent-Instruktions-Validator (Story 3.8)"** (nach §5.13, vor §6; Fixierung: §5.13-Ende Z. 362, §6-Start Z. 364 — neue Sektion zwischen beide, §5.14 Z. 365377); §0-Phasen-Listentext **unverändert**; §5.13 Pkt. 7 „Home Story 3.8" — wird auf §5.14-**Anker** umgehängt (die `generated.at`-Gap-Ausnahme ist jetzt in §5.14 definiert statt nur deklariert; Home-Verweis bleibt, wird aber auf die §5.14-Definition verlagert); §3.2 Normalisierungs-Lücken (Em-Dash in Kollaps-Klasse Z. 51; Kollaps-Reichweite; Match-Scope Z. 55) — als Regel-Ergänzungen aufgelöst; §5.10 Pkt. 8 Orphan-Politik Z. 301 — zur deterministischen Reconcile-Orphan-Regel präzisiert; §7 (`Z. 424ff.`) Determinismus-Vorbehalt „Em-Dash-Lücke an Story 3.8 übergeben" — **aufgelöst**, Verankerung in §5.14 benannt; §8-Normreferenz `AD-17h` (`Z. 445`) auf **§5.14-Anker** angehoben (mit der `at`-Ausnahme-Nennung); Revisionslog-Eintrag **Revision 3.3** nach Z. 477. Anker (IST-Zeilen nach Review-Loop-3, 2026-08-20; P-1): §5.14-Sektion Z. 365 (Pkt. 15 = 369/370/375/376/377); §7-Determinismus-Bullet Z. 434; §8-AD-17h Z. 445; Revisionslog **Revision 3.3** Z. 477.
- `wiki/log.md`**append** (append-only, Vertrag §5): Story-3.8-Eintrag (Verankerung, Sandbox-Nachweis, Statuswechsel, Validator-Verdikt); bestehende Bullets unverändert.
- `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh`**neu** (re-executierbar, Muster sandbox-3-7, Exit 0): Szenarien DET-1..DET-8 (s. Design Notes), harte Pass/Fail-Assertionen, Zwei-Run-Identität nicht-vakuum, keine Berührung des realen Ist-Baums.
- `_bmad-output/implementation-artifacts/sprint-status.yaml`**mutiert**: `3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato` `backlog``in-progress` (Implementierungs-Flip) → `review` (Review-Start-Flip, Step-04; finaler `done`-Flip im Step-05); `last_updated` (Format `MM-DD-YYYY HH:MM`, HEAD-Präzision).
- `_bmad-output/implementation-artifacts/deferred-work.md`**append** (append-only): ggf. Story-3.8-Defers; die bestehenden „Home: Story 3.8"-Einträge werden **aufgegriffen/geschlossen** — nicht als Nur-Read-markiert, sondern als aufgehobene Homes gekennzeichnet (Status-Eintragung). Stand: mehrere determinismus-bezogene Defer-Homes (Em-Dash, Kollaps, Match-Scope, Orphan, `at`-Gap, Misch-Run-Coverage) werden in §5.14/§3.2/§5.10 geschlossen; verbleibende Homes (falls nicht schließbar) bleiben offen mit verbleibendem Ziel.
- `_bmad-output/implementation-artifacts/epic-3-context.md`, `spec-3-7-…md`**read-only** (continue-context; Kette unverändert).
**Read-only evidence (AD-3):** `schema/validator.md`, `schema/wiki-compiler.md`, `adapters/`, `raw/` (inkl. `raw/architecture-spine/…md` AD-17h/Q-6/FT-10/D-3, `raw/epics/epics-2026-08-14.md` A0-19/A0-20 — normativer Rückbezug, keine Mutation). `schema/canonical-terms.md` bleibt append-only-Registry (keine Änderung an Bestandseinträgen; die Em-Dash-/Kollaps-Schließung lebt als Regel in `compiler.md` §3.2, nicht als Registry-Edits — §3.2-Pkt.-1b-Kollaps-Klasse ist die zentrale Normalisierungsregel).
## Tasks & Acceptance
**Execution:**
- [x] `schema/compiler.md` — §5.14 einfügen (nach §5.13, vor §6): Bundle-State-Definition, Zwei-Run-Bestätigungs-Mechanik (agent-Instruktions-basiert, D-3/Q-6, re-executierbare Formeln), `at`-Ausnahme-Menge, Abweichungs-Klassifikation als AD-16-Klassifikationsdefekt; §0/§5.13/§6 Wortlaut-unverändert referenzieren; kein neuer Prädikat-/Format-/Frontmatter-Key
- [x] `schema/compiler.md` — §3.2-Normalisierungs-Lücken schließen (Em-Dash-Klasse, Kollaps-Reichweite, Match-Scope der Stufe a) — als deterministische Regel-Ergänzungen ohne Umbruch des bestehenden §3.2-Wortlauts; Orphan-Politik §5.10 Pkt. 8 zur deterministischen Reconcile-Orphan-Regel präzisieren
- [x] `schema/compiler.md` — Revision 3.3 in §8 belegen (AD-17h auf §5.14-Anker, `at`-Ausnahme-Nennung, Abschlussklausel); §7-Determinismus-Vorbehalt auflösen (Verankerung in §5.14)
- [x] `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh` — Szenarien DET-1..DET-8 (Bundle-State-Definition, Zwei-Run-identisch nicht-vakuum, Zwei-Run-Abweichung klassifiziert, `at`-Gap-Ausnahme, Em-Dash-, Kollaps-, Match-Scope-, Orphan-Szenario), Exit 0
- [x] `_bmad-output/implementation-artifacts/sprint-status.yaml` — Key `3-8-…``in-progress` (Implementierung) → `review` (Review-Start); `_bmad-output/implementation-artifacts/deferred-work.md` — Story-3.8-Defers bzw. aufgegriffene/geschlossene „Home: Story 3.8"-Einträge kennzeichnen
- [x] `wiki/log.md` — (Implementierung) Story-3.8-Eintrag, `sprint-status.yaml` → in-progress (Implementierung) bzw. `review` (Review-Start, Step-04); Review-Abschluss `done` im Review-Schritt (Workflow-Konvention)
- [x] `schema/compiler.md` §5.14 — **Re-Open-Delta:** Zwei-Run-Bestätigung auf **zwei getrennte saubere Worktrees + frische Agent-Kontexte** umgestellt (Q-6/A0-19; „in einer Session" ersetzt); **kanonisches Eingabemanifest** (Baseline-Commit, geordnete Sources, output-sichtbare Run-/Zeit-/Identitätswerte) und **Run-Receipt** (Candidates, Reihenfolge, Plan, Entscheidungen, Output-Hashes) **außerhalb des Bundles** eingefordert; Verbot von **hart codierten erwarteten Plänen/Concept-Bodies** und **pauschaler `verified`-Maskierung** (nur benannte `at`-Ausnahme)
- [x] `_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh`**Re-Open-Delta:** DET-2 auf **zwei getrennte Worktrees/independ. Läufe** umgestellt (Manifest + Receipt-Szenario, keine hart codierten erwarteten Bundle-Outputs, verified nicht pauschal maskiert) — Exit 0
**Acceptance Criteria:**
- Given einen Fixieren Git-State + feste Eingabemenge, when zwei unabhängige Runs ausgeführt werden, then produziert §5.14-Wortlaut identische Bundle-States bis auf die benannte `at`-Ausnahme (FT-10, AD-17h, AC-1).
- Given eine Abweichung bei zwei solchen Runs, when sie festgestellt wird, then wird sie als Fehler im AD-16-Klassifikations-Mechanismus behandelt und textuell benannt (nicht als akzeptables Rauschen) (AD-17h, AC-2).
- Given der MVP (D-3), when der Agent-Instruktions-Validator ausgeführt wird, then laufen die beiden Ausführungen in **getrennten sauberen Worktrees und frischen Agent-Kontexten**; eine zweite Ausführung in derselben Session genügt nicht (Q-6, A0-19; AC-3).
- Given das kanonische Eingabemanifest, when ein Run geplant wird, then hält es Baseline-Commit, geordnete Source-Eingaben und jeden im Bundle sichtbaren Run-/Zeit-/Identitätswert explizit fest; der **Run-Receipt** enthält Candidate-Liste, Reihenfolge, Plan, Entscheidungen und Output-Hashes **außerhalb des Knowledge Bundle** (AC-4).
- Given der Zwei-Run-Nachweis, when Pläne und Bundle-State verglichen werden, then dürfen weder erwartete Pläne noch Concept-Bodies **hart codiert** werden; `verified`-Ereignisse werden **niemals pauschal** aus dem Vergleich maskiert (AC-5).
- Given der Validator, when umgesetzt, then hält er keine Embedding-/Vector-Infrastruktur vor (AD-13; FT-3, FT-4; AC-4) und erzeugt keine eigene Runtime/kein neues Werkzeug (D-3, AD-11).
## Spec Change Log
<!-- Append-only. Populated by step-04 during review loops. Do not modify or delete existing entries. -->
### Review-Loop-1 (2026-08-20, Step-04)
**Ergebnis:** keine intent_gap / kein bad_spec; 0 Loopback. Review-Schicht 3 Subagenten (blind-hunter / edge-case-hunter / verification-gap, synchron); Triage: 14 Patch / 0 defer / Rest Reject (ausführliche Reject-Begründung im Story-Protokoll `wiki/log.md`). Alle Patches rein dokumentarischer Natur (Klarstellung/Verweis/Status); Semantik unverändert, keine neue Vertragsentscheidung.
**Patch-Stapel (in dieser Runde angewandt):**
- §3.2-Pkt.-1b (b): paketierte Kollaps-Klasse in der Operativ-Klausel auf die geschlossene `[-–— _]`-Klasse angehoben; Verweis auf den Append-Block als maßgeblich (KEEP: die append-only-Schließung bleibt unverändert; der Operativ-Satz ist jetzt konsistent).
- §3.2-Pkt.-2a: Match-Scope (ganze Wörter über den Body, exklusiv Frontmatter) in die operative Stufe-a-Formel gewirkt — Einzel-Term-Klausel + Mehrfach-Term-Vereinigung (Pkt. 1c) verdeutlicht; Body-Abgrenzung (nach der zweiten `---`-Zeile) bestimmt.
- §3-Pkt.-2-Kandidatenliste (und damit §5.9-Worked Example): Stufe-a-Scope-Verweis ergänzt.
- §5.14-Pkt.-2: Zwei-Run-Abgleich als Vergleich der beiden Run-Ausgänge gegeneinander (nicht nur je Lauf gg. Baseline) verdeutlicht — Beispiel `git diff <Run-A> <Run-B>` ergänzt.
- §5.14-Pkt.-1-Intro: AD-3-Schutzliste um `adapters/` ergänzt.
- §5.10-Pkt.-8: die pre-existing-Orphan-Obligationsformulierung als vorbestehend markiert (Verweis auf die neue deterministische Regel unten; keine Staffelung an Story 3.8/Epi-4 mehr).
- `deferred-work.md`: Bezugs-Eintrag-Nummern der Story-3.2-review-Defers korrigiert (Kollaps Eintrag 3 → 2; Match-Scope Eintrag 2 → 1).
- `wiki/log.md`: „8 `### Aufgegriffen`-Blöcke" → „7 `### Aufgegriffen`-Blöcke + 1 `### Aufgegriffen (teilweise)`".
- `sandbox-3-8/run-sandbox.sh`: DET-2-at-Maskierungszweck als Kommentar klargestellt (belegt die Nicht-at-Bestandteile; at-Varianz demonstriert DET-4); DET-1-Demo-Kommentar (Kandidaten-/Form-Outputs als redaktionell verdichtete Run-Feststellungen).
- `sprint-status.yaml`: Key `3-8-…``review` (Review-Start-Flip; finaler `done`-Flip im Step-05).
- `spec`-Frontmatter: `status: 'in-review'` (Review-Start per Step-04), `review_loop_iteration: 0` unverändert; SRO-Review-Loop-1-Hinweis ergänzt.
**Rejected-Findings (Kurznotation, nicht Teil der Patches):** Stufe-a-Formel-Änderung nicht nötig (die Formel `rg -l`/`grep -rl` + Scope-Klausel bleibt die Instruktionsform; die Wortgrenzen-/Frontmatter-Semantik reist über die Pkt.-2a-Klausel, nicht über ein Flag); DET-2-Plan-Literal (dev-demo, echte Content-Hashes decken; Kandidaten-Ableitung über DET-1/DET-8); DET-8-hardcodierter Verwaist-Befund (Mechanik textuell, Existenz-negativ-Kontrolle hart); §5.14-`raw/`-Input-Verdacht (Pkt.-1a nennt `raw/`-Zuwächse bereits explizit „als Input"); §5.14-„kommt erstellt der Diff" — keine Formel-Verschmelzung; DET-2-maskierte-`at` (Display-Maske, DET-4 behandelt Varianz); Log-Overcount (Patch 9, siehe oben); Umlaut-Aufschlag (behandelt als Patch-7-Fundament); Sprint-Last-Updated-Timing (Konvention); §3.2-„keine offene Frage"(Umlaut bleibt dokumentiert offen, Wortlaut angepasst).
### Re-Open-Implementierung (2026-08-20, Step-03; genehmigtes Sprint-Change-Proposal 2026-08-20)
**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.
### Review-Loop-3 (2026-08-20, Step-04)
**Ergebnis:** keine intent_gap / kein bad_spec; 0 Loopback. Review-Schicht 4 Subagenten (blind-hunter, edge-case-hunter, verification-gap, acceptance-auditor, synchron). Triage: **4 decision-needed / 13 patch / 6 defer / 7 dismissed.** Nutzer-Entscheidungen (alle empfohlene Option 1): **D-1** §5.14-Schließungs-Bullet macht den §7 „Bekannte Determinismus-Lücke"-Alt-Bullet **superseded** (append-only-Superseded-Hinweis am Alt-Bullet, kein Umbruch — §3.2-„kein Umbruch" gewahrt); **D-2** Story-Status-Flip auf **`done`** nach Loop-3-Abschluss — die Change-Proposal-Sequenzierung „3.9 → 3.10 / 3.11 → 3.12 → 3.8 Abschluss → 3.13 Abnahme" (Sprint-Change-Proposal 2026-08-20) betrifft das **Epic-3-Abnahmegate (Story 3.13)**, nicht die Story-3.8-Instruktionsverankerung (die ist mit diesem Loop abgeschlossen; 3.93.12-Verankerungen und die 3.13-Abnahme bleiben offen, epic-3 remains `in-progress` — als Key-Kommentar in `sprint-status.yaml` festgehalten); **D-3** autorisierte Neu-Verhandlung des Frozen-Intents in **geringfügigem** Umfang: Typo „documenthuman" → „document-human" (dieses File, Re-Open-Delta-Zeile; nur der Rechtschreibfehler, kein Intent-Wechsel) — diese Change-Log-Zeile ist ihre Doku; **D-4** Hold-Home = **Story 3.10 (Epic 3)** (Hold-Mechanik: Erhaltung unzugeordneter `raw/`-Evidenz im benannten Hold ohne Wissensmutation, NFR-7), Korrektur-/Erweiterungs-**Klassifikation** = Epic-4-Interface/Story 4.1 — §5.10 Pkt. 8 entsprechend präzisiert.
**Patch-Stapel (in dieser Runde angewandt):**
- **P-1** SRO-/Code-Map-Anker auf IST-Zeilen: `schema/compiler.md` SRO 374→375 (Pkt. 3), 375→376 (Pkt. 4), 376→377 (Pkt. 5), 476→477 (Rev 3.3); Code Map „§5.14 Z. 364381 ca." → 365377, „§7 (Z. 409ff.)" → 424ff, „§8-AD-17h (Z. 430)" → 445, „Revision 3.3 nach Z. 461" → 477; alle 6 Sandbox-SRO-Anker neu berechnet (vorher exakt, durch die Loop-3-Sandbox-Edits verschoben): 238→245, 257→265, 409→442, 267→275, 414→447, 498→531.
- **P-2/P-4** symmetrische Normalisierung: §3.2 Pkt. 2a-Klausel + `match_stufe_a` — Term **und** Body werden **identisch normalisiert** (Lowercasing + Kollaps-Klasse Pkt. 1b) vor dem Vergleich; kein host-abhängiges `-i`-Flag (wurde entfernt); Wortgrenze folgt der Tool-Wortdefinition (rg `-w` / GNU `grep -w`/`\b`); ASCII-Boundary `[^A-Za-z0-9]` im Skript trägt die Tool-Semantik portabel.
- **P-3** §5.14 Pkt. 2: der **Receipt-basierte Hash-/Textvergleich ist die re-executierbare Formel** des Zwei-Run-Abgleichs; die `git diff <Run-A> <Run-B>`-Formel ist deren Austauschbar-Form auf demselben committeten Baum (Sandbox belegt über Receipt-Hashvergleich).
- **P-5** §5.14 Pkt. 2: Sandbox-Fresh-Kontext-Restlücke textuell benannt — die zwei frischen Agent-Kontexte sind **mechanisch simuliert** (Subshell-Läufe in getrennten Worktrees desselben Skripts); Nachweis echter frischer Kontexte erfolgt im **Story-3.13-Abnahmegate** (echte Agent-Läufe).
- **P-6** Manifest-`generated_by` wird **konsumiert**: Manifest erhält `generated_by: wow-compiler/0.1.0`; Verbrauch-Guard (`generated.by`-Identität + non-empty) + Concept-Frontmatter `by: $m_by` (statt hart-codiert) — „nicht dekorativ" ist jetzt materiell erfüllt.
- **P-7** at-Ausnahme **hart assertiert**: bei Wanduhr-Sekundenkollision wird Lauf B im frischen Kontext wiederholt (begrenzt, 5x), bis die `at_cell`-Werte real differieren; sonst `HARD-FAIL` — die aktive Ausnahme (Change-Log-Beleg „zwei Läufe mit real unterschiedlichen at_cell-Werten") ist jetzt eine Eigenschaft jeder Re-Execution. Re-executiert: `BEFUND: at-Ausnahme aktiv (assertiert) — Lauf A at=2026-08-20T15:05:09Z, Lauf B at=2026-08-20T15:05:12Z`.
- **P-8** „vollständig pinbar"/„keine offene Determinismus-Frage" **ge scopet** (§5.14 Pkt. 5 + §7-Determinismus-Bullet): gelten nur für die mit §5.14 geschlossenen Lücken (Em-Dash-Klasse, Kollaps-Reichweite, Match-Scope, Reconcile-Orphan-Regel, `at`-Gap-Ausnahme); bewusst nicht damit geschlossen: Umlaut-vs-Transkription-Divergenz (benannter Defer) und Story-3.9-ACs (geplant, noch nicht verankert — §3.2 maßgeblich bis dahin).
- **P-9** `epics.md` Story-3.8-AC-2: „Determinismusfehler" als **Fehler der AD-16-Klassifikation** (AD-16-Klassifikationsdefekt) präzisiert — die AD-16-Kopplung war bei Story-Aufnahme nicht ausgeschrieben; AC-Wortlaut erhalten, Nachführung als Präzisierungsklausel.
- **P-10** Frontmatter `review_loop_iteration: 0 → 2` (der Counter war in Loop-1/2 nicht gepflegt; ab Loop-3 gepflegt, Wert = Anzahl bereits abgeschlossener Loops).
- **P-11** `epic-3-context.md` Technical Decisions (Z. 39/41/43): als **Zielzustand** markiert (Story 3.9/3.11/3.12 — noch nicht in `schema/compiler.md` verankert; bestehende Verankerungen bleiben maßgeblich) — die Bullets lesen nicht mehr wie Ist-Zustand.
- **P-12** **verifiziert, kein Edit nötig:** die acceptance-auditor-Finding „epics.md-AD-17-Enum inkonsistent" wurde per Grep widerlegt: `epics.md` enthält **kein** Literal „AD-17a..h" als Block; alle Enum-Vorkommen (Z. 6167, 9495, 101, 262263, 321329, 357408) sind mit dem genehmigten Change-Proposal konsistent (AD-17g verbleibt in Epic 4, Z. 101; AD-17d..17f in Story 3.11/3.12-ACs; AD-17h in Story 3.8-ACs).
- **P-13** dieser Change-Log-Eintrag (append-only nach Loop-2-Eintrag, vor `</frozen-after-approval>` — Loop-1/2-Präzedenz: der Change Log liegt frozen-intern, weil er die Frozen-Änderungsdoku des Re-Opens ist; kein frozen-interner Text wurde hier verändert außer dem D-3-Typo).
**Defers (6, → `deferred-work.md` append-only, Block „Deferred from: code review of spec-3-8… (2026-08-20)"):** D-5 Sandbox-Härtungs-Guards (8 Befunde: `set -u`-Scope, ungeschützte `mktemp`/`git init`/Baseline-Commit, `isolate()`, `log_bullet`-Temporaries) — Testharness-Robustheit, keine normative Semantik; D-6 DET-1/DET-2-Plan-/Form-Literale vs. AC-5 „nicht hart codiert" (dev-demo-Kommentar + echte Content-Hashes tragen die Nicht-Vakuum-Assertion; Home: Story-3.13-Abnahme mit echten Gate-Runs); D-7 `norm()`-Locale-Pinning (`LC_ALL` — C-Locale degradiert `[–—]` zu Byte-Menge; Home: Sandbox-Härtung mit D-5); D-8 Mehrfach-Term-Vereinigung unübend (Home: Story 3.9); D-9 Orphan-Term hand-pinned + feste 2-Datei-Scan (Home: Story 3.9); D-10 Umlaut-vs-Transkription im Match-Pfad unübend (bereits „Aufgegriffen (teilweise)" offen, kein Instruktions-Defekt).
**Dismissed (7):** (1) Blind-Hunter „alle Sandbox-SRO-Anker stale" — **widerlegt** (alle 6 exakt vor den Loop-3-Edits; die Staleness wurde erst durch die eigenen P-6/P-7-Edits erzeugt und per P-1 aufgelöst); (2) Blind-Hunter „§7-Zeilenanker off-by-one (409/430/461)" — bereits im Loop-3-Review-Stand korrekt verortet, Anker waren Implementierungs-Zielwerte (jetzt per P-1 IST); (3) edge-case „Wiederholungs-Schleife unendlicher Loop" — die Schleife ist auf 5 Versuche begrenzt mit HARD-FAIL-Exit; (4) verification-gap „Manifest-`generated.by`-Guard tautologisch" — Guard prüft Manifest-Inhalt gegen Konzepts-Ausgabe (Konsistenz), nicht gegen sich selbst (P-6); (5) acceptance-auditor „AC-5 verletzt durch `plan`-Literal im Receipt" — das `plan`-Feld ist der **abgeleitte** Plan-State (aus Git-State + Stufe-a-Match), kein vorab hart codierter Erwartungswert (D-6-Präzedenz); (6) acceptance-auditor „epics.md AD-17-Enum inkonsistent" — widerlegt (P-12); (7) verification-gap „`last_updated`-Formatbruch" — Format `MM-DD-YYYY HH:MM` beibehalten (Z. 32), Präzedenz-Prüfung bestanden.
**Verifikation dieser Runde:** `bash _bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh`**8/8 PASS (DET-1..DET-8), Exit 0**; at-Ausnahme aktiv (assertiert) mit real unterschiedlichen Wanduhr-`at`-Werten (Lauf A 15:05:09Z vs. Lauf B 15:05:12Z); Witness nicht-vakuum (Baseline-SHA ≠ Run-SHA); Manifest `generated_by` konsumiert (Concept-Frontmatter `by:` trägt Manifest-Wert). AD-3 geprüft (`schema/validator.md`/`schema/wiki-compiler.md`/`adapters/`/`raw/` unverändert). **Status:** Spec-Frontmatter `status: 'done'`, `review_loop_iteration: 2`; `sprint-status.yaml`-Key `3-8-…`**`done`** (D-2, mit Sequenzierungs-Hinweis), `last_updated` → 08-20-2026 17:04. `wiki/log.md` Loop-3-Abschluss-Bullet ergänzt.
</frozen-after-approval>
### Review Findings (Review-Loop-3, 2026-08-20; bmad-code-review, 4 Layer)
<!-- Append-only Review-Protokoll (Step-04); analog zur Change-Log-Sanction. Bestehender Frozen-Text wird NICHT verändert. Dismissed (7) werden nicht persistiert — Begründungen im Review-Loop-3-Change-Log-Eintrag (P-13). -->
**Decision-Needed (4):**
- [x] [Review][Decision] D-1 §3.2-Alt-Bullet-Selbstwiderspruch — der Pre-Existing-Bullet „Bekannte Determinismus-Lücke … deckt den Em-Dash `—` **nicht** ab … übergeben an Story 3.8" (`schema/compiler.md:53`) steht wortgleich über dem Schließungs-Bullet (`:54`) und ist jetzt faktisch falsch; zwei benachbarte, sich widersprechende normative Aussagen über die Kollaps-Klasse. Kollidiert mit dem Spec-Constraint „ohne Umbruch des bestehenden §3.2-Wortlauts" (append-only). — **Entscheidung (2026-08-20): Option 1** — neuer append-only-Nachtrags-Bullet unter `:54` kennzeichnet den Alt-Bullet ausdrücklich als **superseded** (Alt-Wortlaut bleibt unverändert; Widerspruch aufgelöst).
- [x] [Review][Decision] D-2 Status-Trail bruch — Spec-Frontmatter `status: 'done'` + `wiki/log.md:4` „Step-05 done" + Commit-Titel „Step-05 done" vs. `sprint-status.yaml:61` `review` vs. genehmigtes Change-Proposal („Story 3.8 wird nach 3.93.12 … abgeschlossen; 3.8 Abschluss → 3.13 Abnahme"). Welcher Wert ist maßgeblich, und wer flippt wann? — **Entscheidung (2026-08-20): Option 1**`sprint-status.yaml` auf `done` flippen (Loop-3-Abschluss); Change-Proposal-Sequencing betrifft das Epic-3-Abnahmeverfahren (Gate 3.13), nicht den Story-Zustand; Sequencing-Vorbehalt wird im `wiki/log.md`-Abschluss-Bullet textuell vermerkt.
- [x] [Review][Decision] D-3 „documenthuman respektierend" im Frozen-Block — mangelhafter Phrase (vermutl. „document-human respektierend") in `spec` Z. 24, dem menschlich-eigenen Frozen-Block; nur per menschlicher Renegotiation änderbar. — **Entscheidung (2026-08-20): Option 1** — menschliche Renegotiation hiermit erteilt: Korrektur auf „document-human respektierend"; die Renegotiation wird im Review-Loop-3-Change-Log-Eintrag dokumentiert (D-3).
- [x] [Review][Decision] D-4 Orphan-/Hold-Home-Konflikt — §5.10 Pkt. 8 (`schema/compiler.md:302`) + Log-Einträge benennen „Epic-4-Interface, Story 4.1" als Home der Korrektur-/Erweiterungs-Klassifikation, während die Story-3.10-ACs (epics.md Z. 402) einen Hold „bis Epic 4" in Epic 3 selbst verlangen. Welches Story-AC besitzt die Orphan-/Hold-Klassifikation? — **Entscheidung (2026-08-20): Option 1** — Story 3.10 ist Home des Holds (Mechanik in Epic 3); die Korrektur-/Erweiterungs-*Klassifikation* bleibt Epic-4-Interface (Story 4.1); Pkt.-8-Verweis wird entsprechend präzisert (Patch aus D-4).
**Patches (13):**
- [x] [Review][Patch] P-1 SRO-Anker-Drift `schema/compiler.md` — SRO-Zeilen `:374``375`, `:375``376`, `:376``377` (Pkt. 3/4/5), `:476``477` (Revision 3.3); Code-Map „Revision 3.3 nach Z. 461"→477, „§7 nach Z. 420"→434, „§8-AD-17h Z. 430"→Ist, „§5.14 Z. 364381"→365377 (spec Z. 66/182/183/184/194).
- [x] [Review][Patch] P-2 §3.2-Stufe-a-Boundary-Semantik klären — normative Klausel benennt `rg -w`/`grep -w`/`\b` (ASCII-only Wortklassen), Sandbox nutzt `[^A-Za-z0-9]`-Boundary + `-i`; für deutsche Inhalte (Umlaut/ß) divergieren „Wort"-Definitionen; `-i` nirgends instruiert (spec:79, `schema/compiler.md:56`, run-sandbox.sh:144).
- [x] [Review][Patch] P-3 §5.14 Pkt. 2 Run-A↔Run-B-Formel konkretisieren — die benannte `git diff <Run-A> <Run-B> -- wiki/`-Formel wird von der Sandbox nie ausgeführt (nur Receipt-/Hash-Vergleich; `tree`-Cells als Ausnahme); entweder Sandbox ergänzt die Formel, oder Pkt. 2 benennt den Hash-Textvergleich ausdrücklich als äquivalente re-executierbare Formel (spec:88, §5.14 Pkt. 2 `schema/compiler.md:371`).
- [x] [Review][Patch] P-4 §5.14 Pkt. 2/5 Body-Normalisierung klären — „identisch normalisiert" (§3.2/§5.14) gilt textuell nur für den Term; `match_stufe_a` normalisiert den Body nicht (nur CRLF-Strip), der Em-Dash-Kollaps fehlt im Match-Pfad vollständig (DET-5 testet `norm`, nie den Matcher) (run-sandbox.sh:126-142, 555-571).
- [x] [Review][Patch] P-5 AC-3-Fresh-Kontext-Restlücke dokumentieren — „zwei frische Agent-Kontexte" (Q-6/A0-19) sind in der Sandbox strukturell nur als Subshells simulierbar; die Restlücke soll in spec/Log textuell als „mechanisch simuliert" benannt sein (spec:90, run-sandbox.sh:238-260).
- [x] [Review][Patch] P-6 Manifest-Verbrauchs-Tautologie — Manifest wird aus `$BASE` + harter Source-Liste erzeugt und gegen dieselben Variablen validiert; `generated.by` wird nie konsumiert (hart-codiert `wow-compiler/0.1.0`); „nicht dekorativ" nur formal erfüllt (spec:91, run-sandbox.sh:248-266, 300).
- [x] [Review][Patch] P-7 at-Ausnahme-Demo asserten — bei identischen `at_cell`-Werten (Wanduhr-Sekundenkollision) druckt das Skript nur „BEFUND: at-Grenzfall" und PASST; die aktive Ausnahme wird nicht assertiert; Change-Log-Claim „zwei Läufe mit real unterschiedlichen at_cell-Werten" nicht vom Skript erzwungen (spec Change-Log Z. 127, run-sandbox.sh:394-401).
- [x] [Review][Patch] P-8 §7-Absoluthheits-Claim eingrenzen — „Keine offene Determinismus-Frage verbleibt" (§7 `schema/compiler.md:434`) + §5.14 Pkt. 5 „vollständig pinbar … keine offene Frage" (:377) vs. `deferred-work.md` (Umlaut-vs-Transkription offen) + Story-3.9-ACs (closed term-gewinnung/manifest/Routing-Tabelle existiert noch nicht).
- [x] [Review][Patch] P-9 Terminologie-Drift AC-2 — epics.md Z. 366 „Determinismusfehler … Run gilt als fehlgeschlagen" vs. §5.14 Pkt. 4 „AD-16-Klassifikationsdefekt … korrigiert bzw. rollt zurück" (:376); AD-16-Kopplung in der Neuformulierung ohne Verweis verloren.
- [x] [Review][Patch] P-10 `review_loop_iteration` + Change-Log-Finalität — Counter blieb `0` über zwei Loops (dekorativ); kein Change-Log-Eintrag für den Step-05-Statuswechsel (spec Z. 7, 95-142).
- [x] [Review][Patch] P-11 `epic-3-context.md` geplanter Zustand markieren — Technical Decisions beschreiben Story-3.9/3.11/3.12-Entscheidungen als aktuellen Ist-Zustand, die in `compiler.md` nicht existieren; Ziel-/geplanter-Zustand kennzeichnen.
- [x] [Review][Patch] P-12 epics.md-Epic-Narrativ-AD-17a..h nachziehen — Summary-Tabelle/Epic-Body zitieren weiterhin „(AD-17a..h)"; AD-17g-Entzug (jetzt „vollständig in Epic 4") nur in der AD/A0-Enum sichtbar, nicht im Narrativ.
- [x] [Review][Patch] P-13 Review-Loop-3-Change-Log-Eintrag anhängen — Triage-Ergebnis, Patch-Stapel, Defer-Verweise und die 7 Dismissals mit Begründung (darunter Blind-Hunter „alle Sandbox-SRO-Anker stale" — widerlegt, alle 6 exakt).
**Defer (6):**
- [x] [Review][Defer] D-5 Sandbox-Härtungs-Guards (8 Befunde) [run-sandbox.sh:37,41,94-96,104-107,136-141,243-244] — deferred, Testharness-Robustheit (keine normative Semantik); Re-Run-Risiko dokumentiert, Home: nächste Sandbox-Revision.
- [x] [Review][Defer] D-6 DET-1/DET-2-Plan-/Form-Literale vs. AC-5-„nicht hart codiert" [run-sandbox.sh:189,211-221] — deferred, Loop-1-/Loop-2-Präzedenz (dev-demo-Kommentar Z. 211-214; echte Content-Hashes tragen die Nicht-Vakuum-Assertion); Home: Story-3.13-Abnahme (echte Gate-Runs).
- [x] [Review][Defer] D-7 `norm()`-Locale-Pinning (`LC_ALL`) [run-sandbox.sh:115-122] — deferred, Testharness-Host-Locale-Abhängigkeit; C-Locale degradiert `[–—]` zu Byte-Menge; Home: Sandbox-Härtung (mit D-5).
- [x] [Review][Defer] D-8 Mehrfach-Term-Vereinigung unübend [run-sandbox.sh:555-660] — deferred, §5.14-Pkt.-5-„pinbar"-Verhalten ohne ausführbare Coverage; Home: Story 3.9 (Routing/Term-Gewinnung).
- [x] [Review][Defer] D-9 Orphan-Term hand-gesteuert + feste 2-Datei-Scan [run-sandbox.sh:660-693] — deferred, Mechanik textuell korrekt, Determinismus der Orphan-ermittlung (Term-Ableitung aus `raw/`-Zuwachs, voller Kandidaten-Scan) nicht ausgeübt; Home: Story 3.9.
- [x] [Review][Defer] D-10 Umlaut-vs-Transkription im Match-Pfad unübend [run-sandbox.sh:555-571] — deferred, bereits als „Aufgegriffen (teilweise)" offen (deferred-work.md Z. 541-546, kein Instruktions-Defekt); Home: Sandbox-Vereinheitlichung/folgende Instruktions-Revision.
## Design Notes
**Warum §5.14 als eigene Sektion, nicht §6.5-Erweiterung?** §6.5 ist die „Determinismus- & Selbsttest-Norm" mit **drei** Nachprüf-Kriterien (Vollständige §3-Subset-Konformität, `at`-Normalform, `sources`-Existenz) — die projektions-seitige Content-Prüfung je erzeugtem Concept. Der Determinismus-*Vertrag* (AD-17h/FT-10) ist die **Cross-Run-Eigenschaft** (zwei unabhängige Runs → gleicher Bundle-State), kein Einzel-Concept-Kriterium. §5.14 ist die geschlossene Verankerung dieser Cross-Run-Eigenschaft: (a) **Bundle-State-Definition** (welche Zustandsmenge ist „der Bundle-State" — die committete Baum-Projektion unter `wiki/`/`raw/` zzgl. Plan-/Kandidaten-/Reihenfolge-Outputs; `generated.at` als benannte Ausnahme), (b) **Zwei-Run-Bestätigungs-Mechanik** (agent-Instruktions-basiert; **Re-Open-Stand:** die beiden Ausführungen laufen in **getrennten sauberen Worktrees** mit **frischen Agent-Kontexten** über demselben committeten Git-State und vergleichen die Bundle-States; eine zweite Ausführung in derselben Session genügt nicht — Q-6/A0-19; unter **kanonischem Eingabemanifest** und mit **Run-Receipt außerhalb des Bundles**), (c) **Ausnahme-Menge** (allein `generated.at`-Wanduhr-Gap, dokumentiertes A0-20; Nichts sonst), (d) **Abweichungs-Klassifikation** (jede Differenz außerhalb der Ausnahme = AD-16-Klassifikationsfehler; textuell benannt NFR-4; kein stiller Vorbeilass). Damit ist die bisher „offen verankerte" AD-17h-Eigenschaft (viele Rückverweise, kein normativer Ort der Bestätigung) zu einem **instruierten, mechanisch bestätigbaren Verfahren** verdichtet.
**Die Normalisierungs- und Match-Lücken werden deterministisch geschlossen, nicht nur notiert.** Die Kollaps-Klasse in §3.2 Pkt. 1b ist `[-_ ]` (En-Dash , Bindestrich -, Unterstrich _, Leerzeichen) — der Em-Dash `—` fehlt (bekannte Story-3.2-Lücke mit Home Story 3.8). Die Schließung ist eine **Ergänzung der Kollaps-Klasse** um den Em-Dash (`[-–— _]` → `-`) sowie die Festlegung der **Kollaps-Reichweite** (jedes Vorkommen → genau ein `-`; führende/trailende Separatoren werden getrimmt — die bereits in der Sandbox etablierte Semantik) und des **Match-Scope** der Stufe a (Stufe-a-grep-matcht **ganze Wörter** über den Body, exklusive YAML-Frontmatter — `rg -l '<term>' -g '!log.md' wiki/` erhält einen deterministischen Scope; Substring- und Frontmatter-Treffer werden ausgeschlossen). §5.10 Pkt. 8 (Orphan) erhält eine **deterministische Reconcile-Orphan-Regel**: neu committete `raw/`-Evidenz ohne Ziel-Pfad-Treffer bleibt unzugeordnet, wird in `log.md` als verwaist protokolliert (Datumsgruppe, `<Baseline-Commit>`), kein Banner/keine stille Vorbearbeitung. Diese Schließungen machen die relevanz-/synthese-seitigen Erhebungen über den committeten Zustand vollständig pinbar.
**Die Sandbox (DET-1..DET-8, `bash run-sandbox.sh`, `/tmp`-Baum, Exit 0) demonstriert die Bestätigung mechanisch.**
- DET-1 BUNDLE_STATE_DEFINITION: erzeugt ein Mini-Bundle, definiert/extrahiert die Bundle-State-Projektion (wiki/-Dateien + Plan-/Kandidaten-/Reihenfolge-Outputs) deterministisch
- DET-2 ZWEI_RUN_IDENTISCH (nicht-vakuum): **zwei getrennte Worktrees** mit **frischen Agent-Kontexten** (je ein eigener Lauf über demselben committeten Baum) → identische Bundle-States bis auf `at`; unter **kanonischem Eingabemanifest** und mit **Run-Receipt** (Candidates, Reihenfolge, Plan, Entscheidungen, Output-Hashes) **außerhalb des Bundles**; die Assertion vergleicht echte Content-Hashes (nicht nur Vorhandensein); keine hart codierten erwarteten Bundle-Outputs, `verified` nicht pauschal maskiert (nur die benannte `at`-Ausnahme)
- DET-3 ZWEI_RUN_ABWEICHUNG: künstlich divergenter Lauf → wird als AD-16-Klassifikationsdefekt klassifiziert und textuell benannt (kein Rauschen)
- DET-4 AT_GAP_AUSNAHME: zeigt, dass `generated.at`-Wanduhr-Gap die einzige benannte Ausnahme ist (übrige Teile byte-identisch)
- DET-5 EM_DASH: Term mit Em-Dash `—` normalisiert auf dieselbe canonische Form wie En-Dash/Bindestrich/Unterstrich/Leerzeichen
- DET-6 KOLLAPS: Läufe (`a--b`), führende (`-x`), trailende (`x-`) → Kollaps auf genau ein `-` + Trim
- DET-7 MATCH_SCOPE: Stufe-a-matcht ganze Wörter über Body, exklusive Frontmatter (Substring-/Frontmatter-Treffer ausgeschlossen)
- DET-8 ORPHAN: neu committete Evidenz ohne Ziel-Pfad-Treffer → unzugeordnet, `log.md`-verwaist protokolliert, kein Banner/keine Mutation
(Code-/Zahlgenauigkeiten: Szenario-Labels sind Fixierung der I/O-Matrix; die Implementierung trägt die harten Assertionen in der Sandbox.)
## 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, 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).
5. Auf den **`wiki/`-Scope begrenzt** (`git status --porcelain -- wiki/`): ausschließlich `wiki/log.md` (dieser Eintrag) — Erhaltungs-Invariante §5.9 Pkt. 5 gewahrt; `sprint-status.yaml`/`deferred-work.md`/`sandbox-3-8/` liegen außerhalb `wiki/` und sind nicht Teil der Diff-Probe.
**Zu beachten (beim step-04-Review):** (a) §0-Phasen-Listentext, §5.13-Phasen-Disziplin und §6/§6.5 müssen textuell **unverändert** bleiben — §5.14 verweist auf sie (Fugen-Identität); (b) der `generated.at`-Wanduhr-Gap **bleibt verhaltens-seitig unverändert** (A0-20-Konvention); §5.14 definiert nur seine Behandlung im Bundle-State-Vergleich (Ausnahmemenge) — ein Wechsel des `generated.at`-Verhaltens wäre Ask-First; (c) die vorhandene §8-Revisionslog-Nummer ist **3.2** (Story 3.7); **Revision 3.3 ist für Story 3.8 frei** (grep-verifiziert: keine 3.3 im Revisionslog); (d) kein neuer §7-Bullet ersetzt einen bestehenden — der Determinismus-Bullet wird **ergänzt** (Verankerungs-Verweis), die bestehenden Story-Bullets bleiben unverändert; (e) **Re-Open-Delta (§5.14):** die Zwei-Run-Mechanik nutzt **getrennte saubere Worktrees + frische Agent-Kontexte** (Q-6/A0-19), ein **kanonisches Eingabemanifest** und einen **Run-Receipt außerhalb des Bundles** — keine hart codierten erwarteten Bundle-Outputs, keine pauschale `verified`-Maskierung (nur `at`-Ausnahme); die alte Formulierung „in einer Session" ist entfernt.
## Suggested Review Order
<!-- Klickbare Review-Route (Step-05): jede Zeile = [Beschreibung:Zeilennummer](../../relativer-pfad#LZeile) -- Ctrl+Klick (Cmd+Klick auf macOS) öffnet den Anker. Zuerst die §5.14-Bestätigungs-Mechanik lesen, dann die Lückenschließung, dann Nachweis-Orte, Sandbox-Beweis und Protokoll als Peripherie. -->
**§5.14-Determinismus-Vertrag (Kern, Einstieg)**
- [compiler.md:365](../../schema/compiler.md#L365) — §5.14-Überschrift, Intro mit §5.13-Seam und AD-3-Schutzliste inkl. `adapters/`.
- [compiler.md:369](../../schema/compiler.md#L369) — Pkt. 1 Bundle-State-Definition: committete Baum-Projektion, `generated.at` als benannte Ausnahme (A0-20), FT-10/AD-17h/AC-1.
- [compiler.md:370](../../schema/compiler.md#L370) — Pkt. 2 Zwei-Run-Bestätigungs-Mechanik: zweimal über denselben committeten Git-State, Run-A↔Run-B-Abgleich (Beispiel `git diff`), keine Wanduhr-Steuerung.
- [compiler.md:375](../../schema/compiler.md#L375) — Pkt. 3 Ausnahme-Menge: allein der `generated.at`-Wanduhr-Gap; alle übrigen Bestandteile byte-identisch (kein stiller Ausschluss).
- [compiler.md:376](../../schema/compiler.md#L376) — Pkt. 4 Abweichungs-Klassifikation: jede Differenz außerhalb der Ausnahme = AD-16-Klassifikationsdefekt, kein Rauschen (NFR-4, Zustands-Restaurations-Invariante).
- [compiler.md:377](../../schema/compiler.md#L377) — Pkt. 5 Normalisierungs-/Match-/Orphan-Schließung: Verankerung auf §3.2-Pkt.-1b / §5.10-Pkt.-8, Erhebungen vollständig pinbar.
**§3.2/§5.10-Lückenschließung (deterministische Regel-Ergänzungen)**
- [compiler.md:54](../../schema/compiler.md#L54) — §3.2-Pkt.-1b „Geschlossene Determinismus-Lücken": Em-Dash-Kollaps-Klasse `[-–— _]`, Kollaps-Reichweite (ein `-` + Trim), Match-Scope Stufe a (ganze Wörter, Body-exklusiv).
- [compiler.md:56](../../schema/compiler.md#L56) — §3.2-Pkt.-2a operative Stufe-a-Mechanik: `rg -l`/`grep -rl`-Sweep + deterministischer Scope-Filter (zweistufig, tool-portabel), Mehrfach-Term-Vereinigung.
- [compiler.md:302](../../schema/compiler.md#L302) — §5.10-Pkt.-8 Erhaltungs-Invariante & Determinismus-Vertrag: Reconcile-Orphan-Regel (Zuwachs gg. Baseline, datumsgruppierter `log.md`-Eintrag, kein Banner, AD-16-Default).
**§7/§8-Nachweis**
- [compiler.md:477](../../schema/compiler.md#L477) — Revisionslog Revision 3.3: §5.14-Verankerung, §7-Determinismus-Vorbehalt aufgelöst, §8-AD-17h-Normreferenz auf §5.14-Anker, Abschlussklausel (AD-3/D-3/keine neue §7-Klasse).
**Sandbox-Beweis (mechanische Bestätigung)**
- [run-sandbox.sh:245](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L245) — 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:265](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L265) — Kanonisches Eingabemanifest (`manifest_path`), das jetzt VOR jedem Lauf konsumiert/validiert wird (nicht dekorativ; Review-Loop-2-Patch; Review-Loop-3 P-6: `generated_by` wird konsumiert).
- [run-sandbox.sh:442](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L442) — 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 (Review-Loop-3 P-7: at-Ausnahme hart assertiert, Wiederholungs-Schleife bei Sekundenkollision).
- [run-sandbox.sh:275](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L275) — `set -e`-Subshell: jeder fehlende Schritt bricht als rc≠0 ab (kein false-PASS; Review-Loop-2-Patch).
- [run-sandbox.sh:447](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L447) — DET-3: künstlich divergenter Lauf → AD-16-Klassifikationsdefekt, textuell benannt (NFR-4), korrigierte Wiederholung == Referenz.
- [run-sandbox.sh:531](../../_bmad-output/implementation-artifacts/sandbox-3-8/run-sandbox.sh#L531) — 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.
File diff suppressed because one or more lines are too long
@@ -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-19-2026 22:10
last_updated: 08-22-2026 19:10
project: wow20
project_key: NOKEY
tracking_system: file-system
@@ -57,8 +57,13 @@ development_status:
3-4-wissen-aus-mehreren-sources-synthetisieren: done
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: backlog
3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato: backlog
3-7-reason-mutate-trennung-und-konsistenz-endzustand-sicherstell: done
3-8-determinismus-vertrag-ad-17h-als-agent-instruktions-validato: done # Review-Loop-3-Abschluss 2026-08-20 (bmad-code-review; D-2-Entscheidung). Hinweis: die Abschluss-Reihenfolge „3.9 → 3.10 / 3.11 → 3.12 → 3.8 Abschluss → 3.13 Abnahme" des genehmigten Sprint-Change-Proposal 2026-08-20 betrifft das Epic-3-Abnahmegate (3.13) — die Story-3.8-Instruktionsverankerung ist abgeschlossen (done); die 3.93.12-Verankerungen und die 3.13-Abnahme bleiben offen (epic-3 remains in-progress).
3-9-deterministische-relevanz-und-reconcile-routing-schliessen: done # Review-Loop-3-Abschluss 2026-08-21 (bmad-code-review, 4 Layer; D-3.9-1/2/3 = 1/1/2 — empfohlene Optionen; kein Loopback). Hinweis: der finale `done`-Flip ist der Step-05-Status-Sync nach konvergiertem Review-Loop (Präzedenz Story 3.7/3.8); die Epic-3-Abnahme (3.13) bleibt offen (epic-3 remains in-progress).
3-10-inkrementelle-update-und-synthese-erhaltung-absichern: done # Story 3.10 Abschluss 2026-08-21 (bmad-code-review, 3 Layer; Patch-Kaskade, keine intent_gap/bad_spec — Sandbox E-1..E-9 nach Härtung 9/9 harte PASS/Exit 0, §5.16 Rev 3.5). Hinweis: die Epic-3-Abnahme (3.13) bleibt offen (epic-3 remains in-progress).
3-11-root-scope-leasing-atomar-akquirieren: done # Story 3.11 Review-Loop-1-Abschluss 2026-08-21 (bmad-code-review, 4 Layer; D-3.11-1 „Konstruktiv + härten", D-3.11-2 „Intentionalen Mutationsversuch bauen"; 20 Patches angewendet — A-2-Sampler wirksam, A-4 Worktree-Beobachtung, A-7 MERGE_OK hart abgewiesen + Post-Merge-State + Baseline im Hold-Eintrag, §5.17 Pkt. 1/4/5-Berichtigungen; Sandbox A-1..A-8 8/8 harte PASS/Exit 0 re-executiert). Hinweis: der finale `done`-Flip ist der Step-05-Status-Sync nach konvergiertem Review-Loop (Präzedenz Story 3.7/3.8/3.9); die Epic-3-Abnahme (3.13) bleibt offen (epic-3 remains in-progress).
3-12-lease-lifecycle-und-commit-abschluss-transaktional-schliessen: done # Story 3.12 Review-Loop-2-Abschluss 2026-08-22 (bmad-code-review, 4 Layer; D-3.12-1 Option 1 — AC-6-Grenze an log.md-Praxis, L-8-Kategorie-Hyphenate + Negativ-Kontrolle; 7 Patches — P1 scopelock_healthy()/LOCK_READ_ERROR-Propagation, P2 Release-Fehler-negativ + L-6 hart, P3 Setup-Robustheit, P4 Ghost-Diff-Probe + reg_write real, P5 Ownership-Stale + Takeover-Exactly-once, S1/S2 Sync- & Anker-Berichtigung; Sandbox L-1..L-9 9/9 harte PASS/Exit 0 re-executiert; Loop 1: atomarer Ownership-CAS im Takeover, AK-2->AC-2; §5.18 Revision 3.7). Hinweis: der finale `done`-Flip ist der Step-05-Status-Sync nach konvergiertem Review-Loop (Präzedenz Story 3.7/3.8/3.9/3.10/3.11); die Epic-3-Abnahme (3.13) bleibt offen (epic-3 remains in-progress).
3-13-epic-3-verifikations-und-abnahmegate: done # Story 3.13 Epic-3-Abnahmegate abgeschlossen 2026-08-22: run-sandbox.sh (A Setup, B Käfig-Bau, C 12 Sandbox-Sub-Runs, D Validator-Agent, E Zwei-frische-Agenten A/B, F G-1..G-8, G Porcelain) — voller Lauf PASS_COUNT=70, FAILED=0, RUN_OK/Exit 0; Validator 7/7 SUCCESS über reales Bundle; A/B byte-identisch nur at-Ausnahme (§5.14 Pkt. 2); G-6 Negativ-Kontrolle erkennt Perturbation; G-8 Ist-Baum-Porcelain leer. Aufgedeckte und behobene reale Befunde: (1) wiki/log.md:42 zitat das Literal 'type: bundle' in Prosa → Validator Punkt-9-Verletzung, bedeutungserhaltend de-literalisiert (Bundle-Compliance); (2) G-7-Smoke überstreng gegen Bundleroot-Schema-Glossar (../schema/, compiler §5.6 'andere Schicht') + log.md-Protokoll (§5.6 Pkt. 2 Log-Exclusion) → Exclusion im Gate, Smoke bleibt hart für Concept-Bodies. Epic-3 bleibt in-progress bis zur Epic-3-Retrospektive (AC-9: Epic 3 erst nach done aller Stories 3.8..3.13).
epic-3-retrospective: optional
epic-4: backlog
+96 -11
View File
@@ -88,16 +88,17 @@ Aus den Sources entstehen eigenständige, OKF-0.2-konforme Concepts mit claim-gr
**AD/A0:** AD-1, AD-4, AD-4a, AD-4b, AD-4c, AD-7, AD-7a..7d, AD-8, AD-9, A0-3, A0-4, A0-5, A0-8, A0-9, A0-10, A0-20
### Epic 3: Inkrementelle Kompilation & Synthese
Der Nutzer verarbeitet neue Sources gegen das bestehende Wiki: bestehende Concepts werden erweitert, präzisiert oder korrigiert; mehrere Sources werden zu einer gemeinsamen Wissensrepräsentation synthetisiert; unverändertes Wissen bleibt erhalten. Die Relevanzbestimmung erfolgt textual-deterministisch (grep/ripgrep/Traversal, AD-13); Producer arbeiten auf `lease/<area>/<id>`-Branches mit Dirty-Tree-Schutz, Root-Scope-Lease und commit-gebundener Mutation (AD-17a..h).
Der Nutzer verarbeitet neue Sources gegen das bestehende Wiki: bestehende Concepts werden erweitert, präzisiert oder korrigiert; mehrere Sources werden zu einer gemeinsamen Wissensrepräsentation synthetisiert; unverändertes Wissen bleibt erhalten. Die Relevanzbestimmung erfolgt textual-deterministisch (grep/ripgrep/Traversal, AD-13); Producer arbeiten mit atomarer Root-Scope-Lease, Dirty-Tree-Schutz und commit-gebundener Mutation. Klassifikationspflichtige Kollisionen enden bis Epic 4 fail-closed in einem strukturierten Hold; der Determinismus-Vertrag wird unabhängig und mechanisch qualifiziert.
**FRs covered:** FR-4, FR-6, FR-7, FR-12
**NFRs covered:** NFR-7
**AD/A0:** AD-5, AD-6, AD-13, AD-17, AD-17a..17h, A0-6, A0-7, A0-12..A0-18, A0-19, A0-21
**AD/A0:** AD-5, AD-6, AD-13, AD-17a, AD-17b, AD-17c (Fail-closed-Sicherheitsgrenze), AD-17d..17f, AD-17h, A0-6, A0-7, A0-12, A0-13, A0-14 (No-Auto-Merge-Sicherheitsgrenze), A0-15, A0-16, A0-18, A0-19, A0-21 (Incrementality-Teil)
**Shared boundary:** Epic 3 verantwortet bei AD-17c/A0-14 ausschließlich Erkennung und fail-closed Erhaltung; Epic 4 verantwortet Klassifikation und semantische Auflösung. Bei A0-21 verantwortet Epic 3 die Erhaltung unabhängigen Wissens, Story 4.3 die spezifische Human-Curation-Semantik.
### Epic 4: Wissenstreue — Widersprüche & menschliche Kuratierung
Widersprüche werden nie stillschweigend zur scheinbar eindeutigen Aussage zusammengeführt; relevante Disagreements bleiben als explizite Einträge in `log.md` erhalten; menschlich kuratierte Inhalte werden als bestehendes Wissen respektiert und bleiben über OKF-Trust-Metadaten (`verified: human:...`) von ungeprüftem maschinellem Output unterscheidbar; unvollständiges/ungeprüftes Wissen wird ohne künstliche Gewissheit dargestellt.
**FRs covered:** FR-8, FR-13
**NFRs covered:** NFR-7
**AD/A0:** AD-16, AD-16a, AD-16b, AD-15, A0-11, A0-17, A0-20
**AD/A0:** AD-16, AD-16a, AD-16b, AD-15, AD-17c, AD-17g, A0-11, A0-14, A0-17, A0-20, A0-21
### Epic 5: Consumer-Zugriff & Nachvollziehbarkeit
Menschen und beliebige LLM-Agenten (BMAD, Claude Code, Codex, ...) lesen das Knowledge Bundle ohne Wiki-of-Wikis-spezifische Runtime; Änderungen sind über Git-Diffs nachvollziehbar; die kanonischen Compiler-Regeln sind agent-unabhängig mit dünnen Adaptern; es gibt keinen obligatorischen Server, keine Datenbank und keine proprietäre Abhängigkeit.
@@ -257,8 +258,9 @@ So that ein Consumer relevantes Wissen schrittweise findet, ohne das gesamte Wik
## Epic 3: Inkrementelle Kompilation & Synthese
Der Nutzer verarbeitet neue Sources gegen das bestehende Wiki: bestehende Concepts werden erweitert, präzisiert oder korrigiert; mehrere Sources werden zu einer gemeinsamen Wissensrepräsentation synthetisiert; unverändertes Wissen bleibt erhalten. Relevanzbestimmung erfolgt textual-deterministisch (grep/ripgrep/Traversal, AD-13); Producer arbeiten auf `lease/<area>/<id>`-Branches mit Dirty-Tree-Schutz, Root-Scope-Lease und commit-gebundener Mutation (AD-17a..h).
**FRs covered:** FR-4, FR-6, FR-7, FR-12 · **NFRs:** NFR-7 · **AD/A0:** AD-5, AD-6, AD-13, AD-17, AD-17a..17h, A0-6, A0-7, A0-12..A0-18, A0-19, A0-21
Der Nutzer verarbeitet neue Sources gegen das bestehende Wiki: bestehende Concepts werden erweitert, präzisiert oder korrigiert; mehrere Sources werden zu einer gemeinsamen Wissensrepräsentation synthetisiert; unverändertes Wissen bleibt erhalten. Relevanzbestimmung erfolgt textual-deterministisch (grep/ripgrep/Traversal, AD-13); Producer arbeiten mit atomarer Root-Scope-Lease, Dirty-Tree-Schutz und commit-gebundener Mutation. Klassifikationspflichtige Kollisionen enden bis Epic 4 fail-closed in einem strukturierten Hold; der Determinismus-Vertrag wird unabhängig und mechanisch qualifiziert.
**FRs covered:** FR-4, FR-6, FR-7, FR-12 · **NFRs:** NFR-7 · **AD/A0:** AD-5, AD-6, AD-13, AD-17a, AD-17b, AD-17c (Fail-closed-Sicherheitsgrenze), AD-17d..17f, AD-17h, A0-6, A0-7, A0-12, A0-13, A0-14 (No-Auto-Merge-Sicherheitsgrenze), A0-15, A0-16, A0-18, A0-19, A0-21 (Incrementality-Teil)
**Shared boundary:** Epic 3 verantwortet bei AD-17c/A0-14 ausschließlich Erkennung und fail-closed Erhaltung; Epic 4 verantwortet Klassifikation und semantische Auflösung. Bei A0-21 verantwortet Epic 3 die Erhaltung unabhängigen Wissens, Story 4.3 die spezifische Human-Curation-Semantik.
### Story 3.1: Inkrementellen Datenfluss implementieren (Interpret → Reconcile → Synthesize → Update)
@@ -316,14 +318,14 @@ So dass kein separates Summary pro Quelle entsteht und die gemischte Provenienz
As a Producer/Compiler,
I want auf `lease/<area>/<id>`-Branches mit Root-Scope-Lease und Dirty-Tree-Schutz zu arbeiten,
So dass zwei Producer denselben Concept-Pfad nicht stillschweigend überschreiben und Fremdänderungen nie als Nebenwirkung gelöscht werden (AD-17, AD-17a..f, A0-12..A0-16).
So dass zwei Producer denselben Concept-Pfad nicht stillschweigend überschreiben und Fremdänderungen nie als Nebenwirkung gelöscht werden (AD-17a, AD-17b, AD-17e/f, A0-12, A0-13, A0-16; semantische Kollisionsauflösung in Epic 4).
**Acceptance Criteria:**
**Given** ein Producer, **When** er einen Bereich bearbeitet, **Then** arbeitet er auf einem `lease/<area>/<id>`-Branch und akquiriert die Lease gegen einen eindeutigen Commit-Object-Wert (Merge-Base-Disziplin) (AD-17a, A0-12).
**Given** eine Lease, **When** sie vergeben ist, **Then** umfasst sie die Root-Scope inklusive `log.md`, `index.md` und aller Root-Dateien (AD-17b, A0-13).
**Given** eine vorliegende uncommittete Fremdänderung im zu mutierenden Bereich, **When** der Producer mutieren will, **Then** schützt er sie (Stash/Scratch-Zone) und dokumentiert den Vorgang in `log.md` (AD-17e, A0-16).
**Given** zwei Branches mit Änderungen am selben Concept-Pfad, **When** gemerged werden soll, **Then** erfolgt kein stiller textueller Auto-Merge (AD-17c, A0-14) — Auflösung compiler-vermittelt über AD-16 mit explizitem `log.md`-Eintrag.
**Given** zwei Branches mit ungleichen Änderungen am selben Concept-Pfad, **When** vor Epic 4 ein Merge versucht wird, **Then** erfolgt kein textueller Auto-Merge; der Run endet mit einem strukturierten Kollisions-Hold, der beide Commit-Hashes und den betroffenen Scope erhält. AD-16-Klassifikation und semantische Auflösung folgen in Epic 4 (Sicherheitsgrenze von AD-17c; vollständige Auflösung in Story 4.1/4.2).
**And** Mutationen operieren nur auf Directory-/Commit-Ebene — Commit-Boundary ist die Mutation-Boundary (AD-17f, A0-16).
### Story 3.6: Lease-Staleness & Recovery-Basis absichern
@@ -356,21 +358,104 @@ So dass ein teilweise fehlgeschlagener Run nie ein inkonsistentes Bundle hinterl
As a Compiler,
I want dass derselbe Git-State + dieselbe Eingabemenge bei zwei unabhängigen Runs denselben Bundle-State erzeugt,
So dass die AD-16-Klassifikation deterministisch genug ist (AD-17h, FT-10, A0-19).
So dass Relevanz-, Routing-, Planungs- und Mutationsentscheidungen reproduzierbar sind (AD-17h, FT-10, A0-19).
**Acceptance Criteria:**
**Given** einen Fixed Git-State und eine feste Eingabemenge, **When** zwei unabhängige Runs ausgeführt werden, **Then** produzieren sie identische Bundle-Zustände (FT-10, AD-17h).
**Given** eine Abweichung bei zwei solchen Runs, **When** sie festgestellt wird, **Then** wird sie als Fehler im AD-16-Klassifikations-Mechanismus behandelt (nicht als akzeptables Rauschen) (AD-17h).
**Given** der MVP (D-3), **When** die Determinsmus-Enforcement fehlt, **Then** lebt sie als Agent-Instruktions-Validator und ist vor Last tragenden Anspruch als mechanisch bestätigt (Q-6, A0-19).
**Given** eine Abweichung bei zwei solchen Runs, **When** sie festgestellt wird, **Then** wird sie als Determinismusfehler behandelt und der Run gilt als fehlgeschlagen — nicht als akzeptables Rauschen (AD-17h). *Präzisierung (Review-Loop-3, 2026-08-20): „Determinismusfehler" = **Fehler der AD-16-Klassifikation** (AD-16-Klassifikationsdefekt) im Sinne von §5.14 Pkt. 4 `schema/compiler.md` — der AC-Wortlaut „Determinismusfehler" bleibt erhalten; die Klassifikations-Verknüpfung (AD-16) war bei Story-Aufnahme nicht ausgeschrieben und wird hier nachgeführt, ohne den AC-Sinn zu ändern.*
**Given** der MVP (D-3), **When** der Agent-Instruktions-Validator ausgeführt wird, **Then** laufen die beiden Ausführungen in getrennten sauberen Worktrees und frischen Agent-Kontexten; eine zweite Ausführung in derselben Session genügt nicht (Q-6, A0-19).
**Given** das kanonische Eingabemanifest, **When** ein Run geplant wird, **Then** hält es Baseline-Commit, geordnete Source-Eingaben und jeden im Bundle sichtbaren Run-/Zeit-/Identitätswert explizit fest; der Run-Receipt enthält Candidate-Liste, Reihenfolge, Plan, Entscheidungen und Output-Hashes außerhalb des Knowledge Bundle.
**Given** der Zwei-Run-Nachweis, **When** Pläne und Bundle-State verglichen werden, **Then** dürfen weder erwartete Pläne noch Concept-Bodies im Test hart codiert werden; `verified`-Ereignisse werden niemals pauschal aus dem Vergleich maskiert.
**And** der Validator hält keine Embedding-/Vector-Infrastruktur vor (AD-13; FT-3, FT-4).
### Story 3.9: Deterministische Relevanz- und Reconcile-Routing schließen
As a Compiler,
I want Evidenz deterministisch bestehenden, neuen oder nicht klassifizierbaren Wissenseinheiten zuordnen,
So that keine relevante Information still ignoriert, dupliziert oder falsch verwaist wird (FR-4, FR-5-Interaktion, FR-6, FR-12, AD-5, AD-13, A0-6, A0-18).
**Acceptance Criteria:**
**Given** einen committeten Git-State und ein kanonisches Eingabemanifest, **When** Candidate-Terme gewonnen werden, **Then** folgt die Gewinnung einem geschlossenen, geordneten Algorithmus oder einem expliziten, persistierten Term-Manifest — keine freie Producer-Auswahl.
**Given** semantisch gleiche Schreibweisen mit Groß-/Kleinschreibung, Leerzeichen, Unterstrich, Bindestrich, En-Dash oder Em-Dash, **When** die Stufe-a-Suche läuft, **Then** werden Suchterm und Concept-Body identisch normalisiert und literal-sicher verglichen; `index.md`-Treffer bleiben für die Traversal-Stufe erhalten.
**Given** interpretierte Evidenz, **When** Reconcile abgeschlossen wird, **Then** gilt genau eine Routing-Tabelle: bestehender Match → `UPDATE`; eigenständige neue Wissenseinheit → `CREATE`; nicht klassifizierbare Evidenz → expliziter `ORPHAN/HOLD`; bereits vollständig repräsentierte identische Evidenz → `NO_OP`.
**Given** eine geänderte oder gelöschte bereits committete Datei unter `raw/`, **When** der Run seine Eingaben prüft, **Then** schlägt er vor jeder Mutation fehl; akzeptiert werden nur neu hinzugefügte oder separat versionierte Sources (AD-3).
**Given** ein neues Concept-Ziel, **When** dessen Slug `index` oder `log` beziehungsweise ein anderer reservierter Bundle-Name wäre, **Then** wird das Ziel nicht geschrieben und ein deterministischer Hold verlangt eine disambiguierte Identität.
**And** gleicher Git-State plus gleiches Eingabemanifest erzeugt dieselbe Candidate-Liste, Reihenfolge und Routing-Entscheidung (AD-17h, A0-19), belegt durch positive und negative ausführbare Fixtures.
### Story 3.10: Inkrementelle Update- und Synthese-Erhaltung absichern
As a Compiler,
I want bestehendes Wissen semantisch erweitern und mehrere Sources kohärent synthetisieren,
So that neues Wissen integriert wird, ohne gültiges vorhandenes Wissen oder Provenienz zu verlieren (FR-4, FR-6, FR-7, FR-12, AD-4, AD-5).
**Acceptance Criteria:**
**Given** eine passende neue Erkenntnis zu einem bestehenden Concept, **When** der Run sie verarbeitet, **Then** wird das bestehende Concept in-place erweitert oder präzisiert und kein thematisches Duplikat angelegt.
**Given** explizite aktuelle Evidenz, die eine bestehende Aussage eindeutig korrigiert, ohne dass zwischen weiterhin gültigen Sources ein Widerspruch verbleibt, **When** der Run sie verarbeitet, **Then** wird das bestehende Concept in-place korrigiert und die ersetzte Aussage samt Source-Basis bleibt im Run-Receipt nachvollziehbar; mehrdeutige Fälle gehen in den Hold für Epic 4.
**Given** ein betroffenes Concept mit weiterhin gültigen Aussagen und Provenienz, **When** es aktualisiert wird, **Then** bleiben diese geschützten Bestandteile erhalten; nicht betroffene Concepts bleiben byte-identisch.
**Given** eine neue Source, die eine bestehende Aussage unabhängig bestätigt, **When** synthetisiert wird, **Then** erscheint die Aussage genau einmal und trägt alle beitragenden Evidenzanker; die neue Source ist kein `NO_OP`.
**Given** mehrere Sources mit redundanten und ergänzenden Aussagen, **When** der Run synthetisiert, **Then** entsteht eine gemeinsame Wissensrepräsentation mit claim-granularer gemischter Provenienz — keine Source-A/Source-B-Aneinanderreihung.
**Given** eine bereits vollständig repräsentierte identische Source samt Evidenzanker, **When** sie erneut verarbeitet wird, **Then** ist der Run für dieses Wissen byte-erhaltend (`NO_OP`).
**Given** der Provenienz- und Link-Selbsttest, **When** erwartete Deltas bestimmt werden, **Then** stammen Baseline und Erwartung aus dem aktuellen Run; kein historischer, fest codierter Commit oder globaler Zählwert ist normativ.
**And** klassifikationspflichtige oder widersprüchliche Evidenz wird bis Epic 4 ohne Wissensmutation in einem benannten Hold erhalten; beide Evidenzpfade bleiben im Run-Receipt nachvollziehbar (NFR-7).
### Story 3.11: Root-Scope-Leasing atomar und worktree-übergreifend akquirieren
As a Producer,
I want pro kanonischem Scope genau eine atomare Lease erwerben,
So that verschiedene Run-IDs niemals gleichzeitig Schreibzugriff auf dasselbe Knowledge Bundle erhalten (AD-17a, AD-17b, A0-12, A0-13).
**Acceptance Criteria:**
**Given** den Root-Scope `wiki/`, **When** eine Lease akquiriert wird, **Then** existiert genau ein scope-bezogener Lock im clone-geteilten Zustand; die Run-ID ist Lock-Inhalt und nicht Teil des Exklusivitätsschlüssels.
**Given** zwei Producer mit verschiedenen IDs und Worktrees, **When** beide denselben Root-Scope akquirieren, **Then** ist die Akquise atomar und genau ein Producer erhält die Lease; der andere erhält `LEASE_HOLD`.
**Given** einen abgewiesenen Producer, **When** sein Lauf endet, **Then** verändert er weder `wiki/` noch den bestehenden Lock, erzeugt keinen Compilation Commit und entfernt keine fremde Lease.
**Given** einen realen Zwei-Worktree-/Zwei-Prozess-Test, **When** die Akquise zeitlich überlappt, **Then** beweisen Zwischenzustands-Assertions, dass niemals zwei aktive Root-Leases gleichzeitig existieren.
**And** ungleiche Änderungen am selben Concept-Pfad werden nicht automatisch gemerged, sondern als strukturierter Kollisions-Hold mit beiden Commit-Hashes an Epic 4 übergeben.
### Story 3.12: Lease-Lifecycle und Commit-Abschluss transaktional schließen
As an Operator,
I want Akquise, Dirty-Tree-Schutz, Mutation, Rollback und Freigabe als konsistenten Lifecycle ausführen,
So that SUCCESS und FAIL jeweils einen sauberen, wiederanlaufbaren Zustand hinterlassen (AD-6, AD-17d..17f, A0-7, A0-15, A0-16).
**Acceptance Criteria:**
**Given** einen lebenden Lease-Halter, **When** eine höhere Generation sichtbar wird, **Then** bleibt seine Lease aktiv; Staleness verlangt bestätigten Abbruch oder abgelaufene Liveness sowie atomare Ownership-Prüfung.
**Given** eine nachweislich stale Lease, **When** sie übernommen wird, **Then** gelingt die Übernahme genau einmal, nennt die ersetzte Holder-ID und hinterlässt genau eine aktive Root-Lease.
**Given** getrackte oder ungetrackte fremde Änderungen im Mutationsbereich, **When** der Preflight läuft, **Then** folgt er einer eindeutigen Abort-/Protect-Zustandsmaschine und stellt geschützte Bytes nach dem Run vollständig wieder her.
**Given** einen Fehler nach Mutation oder Staging, **When** Rollback läuft, **Then** restauriert er explizit aus dem bezeichneten Baseline-Commit sowohl Index als auch Worktree; der Post-Rollback-Diff gegen die Baseline ist leer.
**Given** einen erfolgreichen Run, **When** er freigegeben wird, **Then** sind Mutation, zulässiger Log-/Koordinationsnachweis und Release dauerhaft, der aktive Lock ist entfernt, der Worktree ist sauber und der unmittelbar folgende Run besteht den Clean-Input-Guard.
**Given** `wiki/log.md`, **When** ein Run protokolliert wird, **Then** enthält das kanonische Knowledge Log nur vertragskonforme fachliche Änderungen und notwendige Koordinationsereignisse; Build-, Review-, Story- und Sandbox-Historie liegt außerhalb des Knowledge Bundle.
**And** Kill-Point-Tests vor Mutation, nach Mutation, vor Commit und nach Commit beweisen den jeweils konsistenten Endzustand.
### Story 3.13: Epic-3-Verifikations- und Abnahmegate
As a Product Owner/Compiler-Verantwortlicher,
I want Epic 3 durch einen realen, reproduzierbaren Source→Compilation→Wiki-Lauf qualifizieren,
So that seine Fertigstellung durch ausführbare Evidenz statt handgeschriebene Simulationen belegt ist (SM-1, SM-2, FT-6, FT-10).
**Acceptance Criteria:**
**Given** einen sauberen Checkout, **When** das repositoryweite Epic-3-Gate ausgeführt wird, **Then** führt ein Kommando alle Epic-3-Szenarien fail-fast aus; fehlgeschlagene Assertions, übersprungene Szenarien, fehlende Abhängigkeiten und Kindprozessfehler ergeben einen Non-Zero-Exit.
**Given** macOS/BSD- und Linux/GNU-Userland, **When** das Gate dort läuft, **Then** ist es ohne plattformspezifisches `sed -i` reproduzierbar, arbeitet ausschließlich in temporären Repositories und hinterlässt `git status --porcelain` unverändert.
**Given** positive und negative OKF-Fixtures, **When** validiert wird, **Then** wird der vollständige Vertrag aus `schema/validator.md` über alle `wiki/`-Dateien ausgeführt; unter anderem fehlendes `generated.by`, kalenderinvalides `generated.at`, unzulässige Source-Pfade und gebrochene Index-Links verhindern SUCCESS und Commit.
**Given** der autorisierte Validator-Vertrag oder seine ausführbare Aufrufbarkeit weist dabei ein Defizit auf, **When** das Gate es erkennt, **Then** bleibt Story 3.13 offen und benennt den Bedarf für eine separat autorisierte Epic-1-Remediation; Story 3.13 darf die Schema-Semantik nicht still ändern, verantwortet aber die Integration und vollständige Ausführung des bestehenden Vertrags.
**Given** eine repräsentative committete Fixture, **When** ein frischer Agent-Kontext die kanonische Instruktion `schema/compiler.md` ausführt, **Then** darf der Harness nach dem Setup weder erwartete Wiki-Bodies noch Lease-, Log- oder Git-Ausgänge selbst schreiben.
**Given** diese Fixture, **When** der Compilation Run endet, **Then** demonstriert er Update eines bestehenden Concepts, Anlage einer neuen Wissenseinheit, kohärente Multi-Source-Synthese, vollständige Multi-Evidenz-Provenienz, Source-Immutabilität und byte-identische Erhaltung unabhängigen Wissens.
**Given** dasselbe kanonische Eingabemanifest, **When** zwei frische Agent-Kontexte in getrennt aufgebauten Worktrees laufen, **Then** stimmen Run-Receipts und Bundle-State gemäß Story 3.8 überein; eine perturbierte Entscheidung oder Ausgabe wird erkannt.
**Given** nur das erzeugte Knowledge Bundle ohne Planungsartefakte, **When** ein unabhängiger Consumer im Epic-5-Abhängigkeits-Smoke-Test vordefinierte Wissensfragen beantwortet, **Then** benötigt er weder Product Brief, SPEC, Architecture Spine noch Story-Historie zum semantischen Verständnis; solche Referenzen sind höchstens optionale Provenienz/Traceability. Dieser Smoke-Test nimmt die vollständige FR-15/FR-16-Abnahme aus Epic 5 nicht vorweg.
**And** erst nach bestandenem Gate und `done`-Status aller Stories 3.8 bis 3.13 wird Epic 3 auf `done` gesetzt; bis dahin bleibt Epic 3 `in-progress`.
---
## Epic 4: Wissenstreue — Widersprüche & menschliche Kuratierung
Widersprüche werden nie stillschweigend zur scheinbar eindeutigen Aussage zusammengeführt; relevante Disagreements bleiben als explizite Einträge in `log.md` erhalten; menschlich kuratierte Inhalte werden als bestehendes Wissen respektiert und bleiben über OKF-Trust-Metadaten (`verified: human:...`) von ungeprüftem maschinellem Output unterscheidbar; unvollständiges/ungeprüftes Wissen wird ohne künstliche Gewissheit dargestellt.
**FRs covered:** FR-8, FR-13 · **NFRs:** NFR-7 · **AD/A0:** AD-16, AD-16a, AD-16b, AD-15, A0-11, A0-17, A0-20
Epic 4 beginnt nach bestandenem Story-3.13-Abnahmegate und übernimmt die in Epic 3 fail-closed erhaltenen Kollisions-/Widerspruchs-Holds zur semantischen Klassifikation und Auflösung.
**FRs covered:** FR-8, FR-13 · **NFRs:** NFR-7 · **AD/A0:** AD-16, AD-16a, AD-16b, AD-15, AD-17c, AD-17g, A0-11, A0-14, A0-17, A0-20, A0-21
### Story 4.1: Information vor jeder Änderung klassifizieren (NEW/CONFIRMING/CORRECTING/CONTRADICTING/REDUNDANT)
@@ -0,0 +1,102 @@
---
title: Sprint Change Proposal — Epic 3 Completion Remediation
status: approved
created: 2026-08-20
approved_by: ProMods
approval: "Explizite Chat-Freigabe: Ja"
classification: moderate
affected_epic: 3
---
# Sprint Change Proposal: Epic 3 belastbar abschließen
## 1. Issue Summary
Der unabhängige Read-only-Audit nach Story 3.8 zeigte, dass Epic 3 seine bereits zugesagten Anforderungen und Architekturverträge noch nicht als kohärenten System-Inkrement belegt. Betroffen sind insbesondere deterministische Relevanz und Routing, semantische Update-/Synthese-Erhaltung, Root-Scope-Leasing, transaktionaler Run-Abschluss, AD-17h-Reproduzierbarkeit und die ausführbare End-to-End-Abnahme.
Der Trigger ist kein neuer Produktwunsch, sondern eine fehlgeschlagene Verifikation bestehender Zusagen aus FR-4, FR-6, FR-7, FR-12, AD-5/6/13/17 und A0-6/7/12..19. Konkrete Evidenz liegt in `schema/compiler.md`, den Story-3-Sandboxes, `wiki/log.md`, dem Sprint-Status und `epic-3-context.md` vor.
## 2. Impact Analysis
### Epic Impact
- Epic 3 bleibt `in-progress`; Story 3.8 wird aus `review` nach `in-progress` zurückgenommen.
- Stories 3.93.13 werden innerhalb von Epic 3 ergänzt.
- Ein neues Epic ist nicht gerechtfertigt: Alle Arbeiten schließen bereits zugesagte Epic-3-Verträge.
- Epic 4 beginnt erst nach dem Story-3.13-Abnahmegate.
### Scope Boundary zu Epic 4
Epic 3 erkennt klassifikationspflichtige Kollisionen und Widersprüche und hält fail-closed an; dies ist seine Sicherheitsgrenze aus AD-17c/A0-14. Vollständige AD-16-Klassifikation, semantische Kollisionsauflösung (AD-17c/g) und Disagreement-Dokumentation verbleiben in Epic 4. A0-21 ist geteilt: Story 3.10 belegt die Erhaltung unabhängigen Wissens, Story 4.3 das Human-Curation-Verhalten/FT-9. Damit entfällt die bisherige zyklische Behauptung, Epic 3 sei abgeschlossen, obwohl sein Merge-/Resolution-Pfad Epic 4 voraussetzt.
### Artifact Impact
- PRD: keine Änderung.
- Architecture Spine: keine Änderung; die Story-Zuordnung wird mit den bestehenden Entscheidungen konsistent gemacht.
- `epics.md`: Scope-Korrektur, Story-3.8-Präzisierung und Stories 3.93.13.
- `sprint-status.yaml`: Story 3.8 `in-progress`, neue Stories `backlog`.
- `epic-3-context.md`: Story-Liste, technische Grenzen und Abhängigkeiten synchronisiert.
- Code, Compiler-Instruktion, Tests und Knowledge Bundle werden durch diese Planungskorrektur nicht verändert.
## 3. Recommended Approach
**Gewählt: Direct Adjustment innerhalb von Epic 3.**
Rollback bereits abgeschlossener Stories würde die vorhandene Arbeit nicht vereinfachen. Eine PRD-/MVP-Neudefinition ist ebenfalls nicht nötig. Die kleinste kohärente Korrektur ist, die offenen systemischen Verantwortungen in klar geschnittene Remediation-Stories zu überführen, Story 3.8 ehrlich wieder zu öffnen und ein finales ausführbares Abnahmegate einzuführen.
Aufwand: mittel bis hoch. Risiko: mittel bei Umsetzung vor Epic 4; hoch, falls Epic 4 auf dem derzeit unzuverlässigen Lease-/Log-/Compiler-Unterbau beginnt.
## 4. Detailed Change Proposals
### Bestehende Story
- **Story 3.8 — Determinismus-Vertrag:** wieder öffnen; zwei getrennte Worktrees und frische Agent-Kontexte, kanonisches Eingabemanifest, rekonstruierbarer Run-Receipt, keine hart codierten Outputs und keine pauschale Maskierung von Trust-Ereignissen.
### Neue Stories
1. **Story 3.9 — Deterministische Relevanz- und Reconcile-Routing schließen**
Schließt Termgewinnung, symmetrische Normalisierung, literal-sichere Suche, exklusive Routing-Tabelle, Raw-Immutability-Guard und reservierte Zielpfade.
2. **Story 3.10 — Inkrementelle Update- und Synthese-Erhaltung absichern**
Beweist In-place-Update, Erhaltung vorhandenen Wissens, vollständige Multi-Evidenz-Provenienz, echten No-op und kohärente Synthese ohne historische Hardcodings.
3. **Story 3.11 — Root-Scope-Leasing atomar und worktree-übergreifend akquirieren**
Realisiert einen scope-bezogenen atomaren Lock und echte konkurrierende Worktree-Tests für unterschiedliche Run-IDs.
4. **Story 3.12 — Lease-Lifecycle und Commit-Abschluss transaktional schließen**
Schließt Staleness/Liveness, Dirty-Tree-Schutz, Baseline-Rollback, durable Release, Clean-Next-Run und die Grenze des kanonischen Knowledge Logs.
5. **Story 3.13 — Epic-3-Verifikations- und Abnahmegate**
Führt portable fail-fast Verifikation, vollständigen OKF-Validator, einen realen Agent-Instruktionslauf, unabhängige Reproduktion und einen Epic-5-Abhängigkeits-Smoke-Test zusammen.
### Reihenfolge
```text
3.9 → 3.10 ┐
├→ 3.8 Abschluss → 3.13 Abnahme → Epic 4
3.11 → 3.12┘
```
## 5. Implementation Handoff
**Klassifikation:** Moderate Backlog-Reorganisation, anschließend Developer-/Review-Ausführung.
- Product Owner: bestätigt Story-Schnitt und Scope-Grenze — erfolgt am 2026-08-20.
- Developer: implementiert Stories in der angegebenen Abhängigkeitsreihenfolge.
- Reviewer: prüft jede Story gegen ihre ausführbaren Negativ- und Positivbelege; Story 3.13 ist die Epic-Abnahme.
- Epic 3 wird erst `done`, wenn Story 3.8 und Stories 3.93.13 `done` sind und das Story-3.13-Gate erfolgreich ist.
## 6. Checklist Record
- [x] Trigger und konkrete Audit-Evidenz verstanden
- [x] Auswirkungen auf Epic 3, Epic 4 und Epic 5 geprüft
- [x] PRD-/Architecture-Konflikte geprüft; keine Änderung erforderlich
- [x] Direct Adjustment gegen Rollback und MVP-Neuschnitt bewertet
- [x] Story-Schnitt, Reihenfolge und Handoff festgelegt
- [x] Explizite Nutzerfreigabe erhalten
- [x] Sprint-Status und Epic-Kontext synchronisiert
+113 -8
View File
File diff suppressed because one or more lines are too long
+21 -1
View File
File diff suppressed because one or more lines are too long