5 Commits
Author SHA1 Message Date
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
9 changed files with 1284 additions and 122 deletions
@@ -589,3 +589,46 @@ Noch nicht adressierte, aber real erkannte Arbeit — gesammelt aus Reviews. Ein
- source_spec: `spec-3-11-root-scope-leasing-atomar-akquirieren.md` - 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. 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. 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).
@@ -40,7 +40,7 @@ Der Compiler verarbeitet neues Source Material gegen das bestehende Knowledge Bu
- **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). - **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).* - **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. - **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. *Zielzustand (Review-Loop-3, P-11): geplant für Story 3.12 (Sprint-Change-Proposal 2026-08-20) — die transaktionale Lifecycle-Präzisierung (Abort-/Protect-Zustandsmaschine, Liveness-Prüfung) ist noch nicht in `schema/compiler.md` §5.11/§5.12 verankert; die bestehende Verankerung bleibt unverändert maßgeblich.* - **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. - **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. - **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. - **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.
@@ -102,6 +102,10 @@ isolate() {
git checkout -qf "$BASE" git checkout -qf "$BASE"
git reset -q --hard "$BASE" git reset -q --hard "$BASE"
git clean -qfd wiki raw lease registry scratch plan-run 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) ---------- # ---------- Atomare Root-Scope-Lease-Akquise (§5.17 Pkt. 1/2) ----------
@@ -125,10 +129,15 @@ scopelock_acquire() { # $1 = Run-ID (Lock-Inhalt; PRODUCER sichtbar)
git update-ref "$SCOPELOCK" "$val" "$ZERO" git update-ref "$SCOPELOCK" "$val" "$ZERO"
} 2>/dev/null } 2>/dev/null
} }
scopelock_release() { scopelock_release() { # $1 = erwartete Inhaber-Run-ID (Ownership-Prüfung)
# Deterministische Freigabe (§5.11 Pkt. 1/6-Konvention): nur der Inhaber gibt den # Deterministische Freigabe (§5.11 Pkt. 1/6-Konvention): NUR der Inhaber gibt den
# Scope-Lock frei; solange er existiert, ist die Akquise gesperrt (A-6). # Scope-Lock frei — Ownership: der Ref-Inhalt muss die Run-ID des Aufrufers tragen
git update-ref -d "$SCOPELOCK" # (§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() { scopelock_content() {
# Lock-Inhalt als Text (Run-ID des aktuell HALTENDEN Producers) — documentierender # Lock-Inhalt als Text (Run-ID des aktuell HALTENDEN Producers) — documentierender
@@ -175,8 +184,8 @@ fail() { echo "HARD-FAIL: $1" >&2; exit 1; }
# ===================================================================================== # =====================================================================================
runlabel "A-1: EXKLUSIVITAETS_SCHLUESSEL" runlabel "A-1: EXKLUSIVITAETS_SCHLUESSEL"
isolate isolate
# Producer akquiriert den Root-Scope mit Run-ID als Lock-Inhalt. # Producer akquiriert den Root-Scope mit Run-ID als Lock-Inhalt. (Review-Loop-1: die tote
run_a1=$(printf 'producer-a1-run-id' | git hash-object -w --stdin) # 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" 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). # 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 case "$SCOPELOCK" in *RUN-A1*|*run-a1*|*a1*) fail "A-1: Run-ID im Exklusivitätsschlüssel (Ref-Name) — AC-a verletzt";; esac
@@ -185,7 +194,8 @@ 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" [ "$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). # 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_content)" = "RUN-A1" ] || fail "A-1: Lock-Inhalt != Run-ID (AC-a)"
scopelock_release 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)" 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 pass
@@ -223,21 +233,34 @@ run_acquire_in_wt() { # $1 = Worktree ; $2 = Run-ID
) )
} }
# Einziger Lock-Entscheidungs-Einstiegspunkt sind die zwei Prozesse; die Ergebnis-Zeilen # Einziger Lock-Entscheidungs-Einstiegspunkt sind die zwei Prozesse; die Ergebnis-Zeilen
# (WINNER:/LEASE_HOLD:) werden zurückgegeben und zusammengeführt. Parallel sammelt ein # (WINNER:/LEASE_HOLD:) werden zurückgegeben und zusammengeführt. Parallel taktet ein
# Zwischenzustands-Sampler WÄHREND der Überlappung die Menge der existierenden Scope-Locks # Zwischenzustands-Sampler WÄHREND der Überlappung den scope-bezogenen Lock (AC-d).
# (je Sample genau ein Wert — niemals zwei aktive Root-Leases; 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=$( out_a=$(
( (
run_acquire_in_wt "$ROOT/wt-a" "RUN-A2-a" & run_acquire_in_wt "$ROOT/wt-a" "RUN-A2-a" &
run_acquire_in_wt "$ROOT/wt-b" "RUN-A2-b" & run_acquire_in_wt "$ROOT/wt-b" "RUN-A2-b" &
# Zwischenzustands-Assertion (AC-d): während die zwei Prozesse konkurrieren, wird der seen_vals=""
# scope-bezogene Lock-Sampler in schneller Folge getaktet; jeder Sample-Read sieht die for i in $(seq 1 25); do
# Ref als EINEN Ref (eine scope-bezogene Ref je Scope — niemals zwei aktive Werte).
for i in $(seq 1 6); do
cnt=$(git for-each-ref --format='%(refname)' | grep -cF "$SCOPELOCK") cnt=$(git for-each-ref --format='%(refname)' | grep -cF "$SCOPELOCK")
[ "$cnt" -gt 1 ] && { echo "TWO_LOCKS_AT_ONCE:i$i" >&2; } [ "${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 sleep 0.002
done done
nvals=0
for v in $seen_vals; do nvals=$((nvals+1)); done
[ "$nvals" -le 1 ] || echo "TWO_VALUES_IN_WINDOW"
wait wait
wait wait
) )
@@ -249,11 +272,16 @@ winners=$(printf '%s\n' "$out_a" | grep -c '^WINNER:')
holds=$(printf '%s\n' "$out_a" | grep -c '^LEASE_HOLD:') holds=$(printf '%s\n' "$out_a" | grep -c '^LEASE_HOLD:')
[ "$winners" -eq 1 ] || fail "A-2: erwartet genau einen Gewinner, tatsaechlich $winners (AC-b)" [ "$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)" [ "$holds" -eq 1 ] || fail "A-2: erwartet genau ein LEASE_HOLD, tatsaechlich $holds (AC-b)"
# Kein Zwischenzustand mit zwei aktiven Locks (Sampler-Fund; AC-d erfüllt durch die # Kein Zwischenzustand mit zwei aktiven Locks (Sampler-Fund, D-3.11-1):
# Ref-Struktur: es existiert genau EIN scope-bezogener Ref-Name, dessen Werte atomar # - TWO_LOCKS_AT_ONCE: zwei Scope-Lock-Refs gleichzeitig (per-Run-Ref-Regression),
# geschrieben werden — zu keinem Zeitpunkt zwei aktive Root-Leases). # - 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 if printf '%s\n' "$out_a" | grep -q 'TWO_LOCKS_AT_ONCE'; then
fail "A-2: Zwischenzustand mit zwei aktiven Root-Leases beobachtet (AC-d)" 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 fi
# Der Lock-Inhalt ist die Run-ID des GEWINNERS (der abgewiesenen Run-ID wurde nie geschrieben). # 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) winner_id=$(printf '%s\n' "$out_a" | sed -n 's/^WINNER://p' | head -1)
@@ -265,8 +293,13 @@ winner_id=$(printf '%s\n' "$out_a" | sed -n 's/^WINNER://p' | head -1)
# Scope-Locks ist nach dem Lauf eins. # Scope-Locks ist nach dem Lauf eins.
n2=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l) 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" [ "$n2" -eq 1 ] || fail "A-2: nach überlappendem Lauf erwartet genau einen Scope-Lock, tatsaechlich $n2"
scopelock_release scopelock_release "$winner_id"
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 (AC-b/AC-d)" [ -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 pass
# ===================================================================================== # =====================================================================================
@@ -279,51 +312,64 @@ isolate
scopelock_acquire "RUN-A3" || fail "A-3: Akquise ins Leere schlug fehl" 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=$(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" [ "$n3" -eq 1 ] || fail "A-3: erwartet genau einen scope-bezogenen Lock, tatsaechlich $n3"
# Eine zweite, zeitlich ÜBERLAPPENDE Akquise (zweiter Worktree, eigener Prozess) schlägt # Eine zweite, SEQUENTIELLE Akquise im selben Prozess (echte Überlappung deckt A-2 ab)
# fehl — der Lock wird nicht berührt (create-only: kein Überschreiben, kein Datenaustausch, # schlägt fehl — der Lock wird nicht berührt (create-only: kein Überschreiben, kein
# auch eine identisch benannte Run-ID kann den Halter nicht ersetzen). # Datenaustausch). Der exakte Identitäts-Fall: dieselbe Run-ID wie der Halter kann den
scopelock_acquire "RUN-A3-identisch" && fail "A-3: zweite Akquise (identische Run-ID) haette fehlschlagen muessen (kein Ueberschreiben)" # 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_content)" = "RUN-A3" ] || fail "A-3: Lock-Inhalt nach abgewiesener zweiter Akquise veraendert"
scopelock_release 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)" 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 pass
# ===================================================================================== # =====================================================================================
# A-4 LEASE_HOLD_NICHT_MUTATION — AC-c # A-4 LEASE_HOLD_NICHT_MUTATION — AC-c
# ===================================================================================== # =====================================================================================
runlabel "A-4: LEASE_HOLD_NICHT_MUTATION (abgewiesener Producer veraendert nichts)" runlabel "A-4: LEASE_HOLD_NICHT_MUTATION (abgewiesener Producer: Mutation + Commit ABGEWEHRT)"
isolate isolate
scopelock_acquire "RUN-A4-fremd" || fail "A-4: erste Akquise schlug fehl" scopelock_acquire "RUN-A4-fremd" || fail "A-4: erste Akquise schlug fehl"
alpha_before=$(git show "$BASE:wiki/alpha.md" | sha256sum) # Abgewiesener Producer (zweiter Worktree) UNTERNEHMT einen (intentionalen) Mutations- und
log_bytes_before=$(git show "$BASE:wiki/log.md" | wc -c) # Commit-Versuch gegen wiki/ und wird durch die LEASE_HOLD-Semantik (§5.17 Pkt. 3) gestoppt:
# Abgewiesener Producer (zweiter Worktree) versucht Mutation + Commit im abgewiesenen Zweig: # die Mutation wird NICHT committet, der Worktree bleibt sauber, kein Compilation-Commit,
# Verändern weder wiki/ noch den Lock, KEIN Compilation-Commit, keine fremde Lease-Entfernung. # 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 prune
git worktree add -q "$ROOT/wt-a4" "$BASE" || fail "A-4: Worktree wt-a4 nicht aufgebaut" git worktree add -q "$ROOT/wt-a4" "$BASE" || fail "A-4: Worktree wt-a4 nicht aufgebaut"
out_a4=$( out_a4=$(
cd "$ROOT/wt-a4" cd "$ROOT/wt-a4"
if scopelock_acquire "RUN-A4-neu"; then if scopelock_acquire "RUN-A4-neu"; then
echo "UNEXPECTED_WIN"; echo "UNEXPECTED_WIN"
else else
# LEASE_HOLD-Pfad: der abgewiesene Producer tut NICHTS (keine Mutation), beendet sauber. # 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" echo "LEASE_HOLD"
fi fi
) )
[ "$out_a4" = "LEASE_HOLD" ] || fail "A-4: abgewiesener Producer wurde nicht LEASE_HOLD" [ "$out_a4" = "LEASE_HOLD" ] || fail "A-4: abgewiesener Producer wurde nicht LEASE_HOLD"
# wiki/ unverändert (Baseline-Inhalte; im abgewiesenen Worktree ist nichts committet worden): # Arbeitsbaum-Beobachtung im abgewiesenen Worktree (statt Baseline-Blob-Selbstvergleich):
alpha_after=$(cd "$ROOT/wt-a4" && git show "$BASE:wiki/alpha.md" | sha256sum) # kein uncommitteter Mutation-Rest, kein Commit über der Baseline.
[ "$alpha_before" = "$alpha_after" ] || fail "A-4: wiki/alpha.md veraendert trotz LEASE_HOLD (AC-c)" 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): # 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=$(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" [ "$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): # 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_content)" = "RUN-A4-fremd" ] || fail "A-4: fremde Lease wurde entfernt/ueberschrieben (AC-c)"
# log.md byte-unverändert (Baseline-Größe) — kein Eintrag durch den abgewiesenen Producer scopelock_release "RUN-A4-fremd"
# (in wt-a4 ist HEAD=$BASE, da dort nichts committet wurde): [ -z "$(scopelock_content)" ] || fail "A-4: Lock nach Release nicht leer"
log_bytes_after=$(cd "$ROOT/wt-a4" && git show "HEAD:wiki/log.md" | wc -c) # Worktree aufräumen (Review-Loop-1: keine verwaisten Worktree-Registrierungen im geteilten Namespace):
[ "$log_bytes_after" = "$log_bytes_before" ] || fail "A-4: wiki/log.md veraendert trotz LEASE_HOLD (AC-c)" git worktree remove -f "$ROOT/wt-a4" || true
scopelock_release 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)"
echo "RESULT: PASS — A-4: LEASE_HOLD_NICHT_MUTATION — abgewiesener Producer: wiki/ unveraendert, kein Compilation-Commit, fremde Lease (Lock-Inhalt) unangetastet, sauberes LEASE_HOLD-Ende (AC-c)"
pass pass
# ===================================================================================== # =====================================================================================
@@ -339,7 +385,8 @@ scopelock_acquire "RUN-A5" || fail "A-5: Akquise schlug fehl"
[ "$(scopelock_content)" = "RUN-A5" ] || fail "A-5: Lock-Inhalt != Run-ID" [ "$(scopelock_content)" = "RUN-A5" ] || fail "A-5: Lock-Inhalt != Run-ID"
n5=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l) n5=$(git for-each-ref --format='%(refname)' | grep -F "$SCOPELOCK" | wc -l)
[ "$n5" -eq 1 ] || fail "A-5: erwartet genau einen Scope-Lock" [ "$n5" -eq 1 ] || fail "A-5: erwartet genau einen Scope-Lock"
scopelock_release 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)" 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 pass
@@ -354,7 +401,8 @@ scopelock_acquire "RUN-A6-bestaendig" || fail "A-6: erste Akquise schlug fehl"
out_a6=$(scopelock_acquire "RUN-A6-spaet" && echo "UNEXPECTED_WIN" || echo "LEASE_HOLD") 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" [ "$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_content)" = "RUN-A6-bestaendig" ] || fail "A-6: Lock-Inhalt (fremde Run-ID) veraendert durch spaete Abweisung"
scopelock_release 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)" echo "RESULT: PASS — A-6: SPAET_ABGEWIESEN — gehaltene fremde Lease blockiert auch sequentielle Akquise; Lock-Inhalt fremd-unveraendert (AC-c)"
pass pass
@@ -377,6 +425,9 @@ generated:
--- ---
Alpha-Variante X: deterministische Init-Sequenz mit Synchron-Kopplung (raw/alpha-v1.md#S-1). Alpha-Variante X: deterministische Init-Sequenz mit Synchron-Kopplung (raw/alpha-v1.md#S-1).
EOF 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 add wiki/alpha.md
git commit -qm "Branch-X: ungleiche Aenderung an alpha.md" git commit -qm "Branch-X: ungleiche Aenderung an alpha.md"
HASH_X=$(git rev-parse HEAD) HASH_X=$(git rev-parse HEAD)
@@ -394,63 +445,52 @@ generated:
--- ---
Alpha-Variante Y: deterministische Init-Sequenz OHNE Synchron-Kopplung (raw/alpha-v1.md#S-1). Alpha-Variante Y: deterministische Init-Sequenz OHNE Synchron-Kopplung (raw/alpha-v1.md#S-1).
EOF EOF
assert_frontmatter wiki/alpha.md
git add wiki/alpha.md git add wiki/alpha.md
git commit -qm "Branch-Y: ungleiche Aenderung an alpha.md" git commit -qm "Branch-Y: ungleiche Aenderung an alpha.md"
HASH_Y=$(git rev-parse HEAD) HASH_Y=$(git rev-parse HEAD)
# Kein textueller Auto-Merge (AD-17c): drei Merge-Versuche (cherry-pick / merge ff / merge) # Kein textueller Auto-Merge (AD-17c): die Kollision wird durch gleichen Pfad + ungleiche
# muessen alle OHNE stille textuelle Konsolidierung enden — der Run erkennt die Kollision. # Blobs an derselben Zeilenregion EINDEUTIG erkennbar. Der Merge-Versuch wird auf branch-y
# (a) git merge auf branch-y == "ablösen von alpha.txt-loesungen" — der Befehl wird gar nicht # real ausgeführt und MUSS mit einem Konflikt enden: ein erfolgreicher (still textuell
# erst ausgeführt, die Kollision ist durch gleichen Pfad + ungleiche Blobs EINDEUTIG # konsolidierender) Merge wäre genau der von AD-17c/AC-e verbotene Auto-Merge — Review-Loop-1:
# erkennbar: kein Merge, kein Rebase — Text bleibt variantenrein (kein mischer). # `MERGE_OK` wird daher HART abgewiesen und der Merge wieder abgebrochen.
merge_attempt() { git checkout -q -f branch-y
git checkout -q -f branch-y ma="MERGE_CONFLICT"
if git merge --no-edit "$HASH_X" >/dev/null 2>&1; then if git merge --no-edit "$HASH_X" >/dev/null 2>&1; then
echo "MERGE_OK" ma="MERGE_OK"
else fi
# Textuelle Kollision bleibt unaufgelöst: im Index steht kein gemischter Body. if [ "$ma" = "MERGE_OK" ]; then
echo "MERGE_CONFLICT" # Ein stummer textueller Auto-Merge ist verboten (AD-17c) — Merge-Commit wieder entfernen,
fi # damit branch-y variantenrein bleibt, und hart fehlschlagen:
} git merge --abort >/dev/null 2>&1 || git reset -q --hard "$HASH_Y"
ma=$(merge_attempt) fail "A-7: git merge endete CLEAN (still textueller Auto-Merge) — AD-17c/AC-e verletzt"
# Ob der git-Merge-Versuch mit Konflikt endet (MERGE_CONFLICT) ODER — falls der Host-Branch fi
# denselben Pfad hält — der Merge nicht still textuell konsolidiert, ist die AD-17c-Aussage # Kollisions-Zustand auflösen, ohne ein Merge-Ergebnis zu committen (branch-y variantenrein):
# erfüllt: wir führen NIE ein $ git merge mit automatischer Text-Konsolidierung aus. Der git reset -q --hard "$HASH_Y"
# strukturierte Hold ist die VERBINDLICHE Reaktion des Runs: # Post-Merge-State-Assertion: branch-y steht WIEDER exakt auf seiner Variante — kein
case "$ma" in # Merge-Commit, kein gemischter Body (statt des invarianten $HASH_X/$HASH_Y-Objekt-Vergleichs):
MERGE_CONFLICT|MERGE_OK) [ "$(git rev-parse branch-y)" = "$HASH_Y" ] || fail "A-7: branch-y-HEAD hat sich verschoben (Merge-Commit Spur)"
# Verbindliche Run-Reaktion: strukturierter Kollisions-Hold mit beiden Commit-Hashes y_body=$(git show branch-y:wiki/alpha.md | grep -c 'Alpha-Variante Y:')
# und Scope an Epic 4 (benannte Hold-Mechanik §5.16 Pkt. 8/§5.10 Pkt. 8, §5.17 Pkt. 5):
# kein Merge-Ergebnis wird committet; die beiden Varianten bleiben als Commits erhalten.
;;
*) fail "A-7: unerwarteter Merge-Versuch-Zustand: $ma" ;;
esac
# Der Hold trägt BEIDE Commit-Hashes und den Scope (deterministisch auflösbar, AC-e):
hold_msg="KOLLISIONS_HOLD scope=wiki/alpha.md branch-x=$HASH_X branch-y=$HASH_Y"
case "$hold_msg" in
*"$HASH_X"*|*"$HASH_Y"*) ;; # beide Hashes geführt
*) fail "A-7: Hold trägt nicht beide Commit-Hashes" ;;
esac
case "$hold_msg" in
*"$HASH_X"*"$HASH_Y"*) ;; # BEIDE gefordert (nicht nur einer)
*) fail "A-7: Hold trägt nicht BEIDE Commit-Hashes (AC-e)" ;;
esac
# Textueller Auto-Merge ist verboten: der Body von branch-y bleibt byte-identisch zu seiner
# Variante (keine still gemergte Mischung aus X und Y eingespielt):
y_body=$(git show "$HASH_Y:wiki/alpha.md" | grep -c 'Alpha-Variante Y:')
[ "$y_body" -eq 1 ] || fail "A-7: branch-y-Body nicht variantenrein (Auto-Merge-Spur)" [ "$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=$(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)" [ "$x_body" -eq 1 ] || fail "A-7: branch-x-Body nicht variantenrein (Auto-Merge-Spur)"
# Der log.md-Hold-Eintrag (zur Datumsgruppe, Vertrag §5) trägt Quell-Pfad + Baseline-Commit: # 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" git checkout -q -f "$BASE"
printf '\n### 2026-08-21 — %s\n' "$hold_msg" >> wiki/log.md 2>/dev/null || \ 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; } { 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 # Hard aus der DATEI assertieren (nicht aus der Quell-Variablen), dass beide Commit-Hashes
# + Scope wirklich im geschriebenen Eintrag landen (§5.16-Pkt.-8-/Vertrag-§5-Form): # + 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 \ 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; } || { 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 \ grep -Fq "scope=wiki/alpha.md" wiki/log.md \
|| { echo "HARD-FAIL (A-7): log.md-Hold-Eintrag ohne Scope" >&2; exit 1; } || { 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 " 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)" 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 pass
@@ -462,23 +502,24 @@ runlabel "A-8: FREIGABE_ERNEUT (nach Release wieder genau ein Lock)"
isolate isolate
scopelock_acquire "RUN-A8-erster" || fail "A-8: erste Akquise schlug fehl" 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" [ "$(scopelock_content)" = "RUN-A8-erster" ] || fail "A-8: Lock-Inhalt != erste Run-ID"
# Der Scope-Lock bedarf keiner Commits — ein nebenbei committender Release-Pfad wäre # Release mit Ownership (nur der Inhaber gibt frei) + sofortige Leere-Assertion:
# hiermit entdeckt (hart, vor dem ersten Release): 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=$(git rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
[ "$commits_a8" = "0" ] || fail "A-8: Release erzeugte Commits ($commits_a8) — AC-c verletzt" [ "$commits_a8" = "0" ] || fail "A-8: Akquise/Release erzeugte Commits ($commits_a8) — AC-c verletzt"
scopelock_release
[ -z "$(scopelock_content)" ] || fail "A-8: Lock nach Release nicht leer"
# Nachfolgender Producer erwirbt denselben Root-Scope atomar erneut: # Nachfolgender Producer erwirbt denselben Root-Scope atomar erneut:
scopelock_acquire "RUN-A8-zweiter" || fail "A-8: zweite Akquise nach Release schlug fehl" 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=$(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" [ "$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" [ "$(scopelock_content)" = "RUN-A8-zweiter" ] || fail "A-8: Lock-Inhalt != zweite Run-ID"
# Release-Semantik bleibt deterministisch — nur der Inhaber gibt frei (der Inhalt ist bekannt). # Zweites Release mit Ownership; Commit-Zähl-Assert am Block-Ende (nach zweitem Release):
# Commit-Zähl-Assert am Block-Ende (nach zweitem Release): der gesamte Akquise-/Release-Zyklus # der gesamte Akquise-/Release-Zyklus hat keine Commits erzeugt (AC-c; A-4-Konvention):
# hat keine Commits erzeugt (AC-c; A-4-Konvention Z. 317): 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=$(git rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
[ "$commits_a8" = "0" ] || fail "A-8: Release erzeugte Commits ($commits_a8) — AC-c verletzt" [ "$commits_a8" = "0" ] || fail "A-8: Akquise/Release-Zyklus erzeugte Commits ($commits_a8) — AC-c verletzt"
scopelock_release
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)" 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 pass
@@ -491,7 +532,11 @@ echo "Harte PASS-Assertions: $PASS_COUNT"
[ "$PASS_COUNT" -ge 8 ] || { echo "HARD-FAIL: erwartet >= 8 harte PASS-Assertions, tatsaechlich $PASS_COUNT" >&2; exit 1; } [ "$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 # 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). # /tmp-Baum; der reale wiki//raw/-Baum wurde nicht berührt (Sandbox-Selbstbindung).
git status --short --porcelain >/dev/null 2>&1 || true # 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 "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 "clone-geteilten Zustand verifiziert (§5.17, Revision 3.6): kein Zugriff auf reales Bundle/raw/."
echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)" echo "Sandbox-Root: $ROOT (loeschbar: rm -rf $ROOT)"
@@ -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
@@ -4,8 +4,9 @@ type: 'feature'
created: '2026-08-21' created: '2026-08-21'
status: 'done' status: 'done'
baseline_commit: 'a8b486d0f04b44344cdfa62e9cc32dfea0d49abd' baseline_commit: 'a8b486d0f04b44344cdfa62e9cc32dfea0d49abd'
review_loop_iteration: 0 review_loop_iteration: 1
context: [] context:
- '_bmad-output/implementation-artifacts/epic-3-context.md'
--- ---
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates"> <frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
@@ -82,8 +83,8 @@ Der Lock muss im clone-geteilten Zustand liegen, weil nur dieser Zustand von all
**Commands:** **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/`. - `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 "3.11\|3.6" schema/compiler.md` und `grep -n "Revision 3.6"` -- expected: neuer Abschnitt und Revisionslogeintrag vorhanden; bestehende Pkt.-1/2-Texte unverändert (diff checkt nur additive Zeilen). - `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 -C _bmad-output diff` vs. `git -C schema/compiler.md diff` -- expected: nur additive Änderung an `schema/compiler.md`; `validator.md`/`wiki-compiler.md`/`adapters/`/`raw/` ohne Diff. - `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):** **Manual checks (if no CLI):**
- Keine Beschreibung nötig — sämtliche Nachweise laufen über die Sandbox (Exit-Code) und additive Diffs. - Keine Beschreibung nötig — sämtliche Nachweise laufen über die Sandbox (Exit-Code) und additive Diffs.
@@ -95,30 +96,71 @@ Der Lock muss im clone-geteilten Zustand liegen, weil nur dieser Zustand von all
- 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). - 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) [`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. - 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:527`](../../schema/compiler.md#L527) [`compiler.md:424`](../../schema/compiler.md#L424)
**Atomarer Lock im clone-geteilten Zustand** **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. - Atomare create-only-Akquise (`git update-ref <lock-ref> <wert> $ZERO_SHA`) — gits Ref-Sperre serialisiert Worktrees/Prozesse; deterministischer Blob-Inhalt.
[`run-sandbox.sh:120`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L120) [`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, Zwischenzustands-Sampler). - 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:207`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L207) [`run-sandbox.sh:217`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L217)
**Kollisions-Hold & Abweisung** **Kollisions-Hold & Abweisung**
- LEASE_HOLD-Nicht-Mutation: abgewiesener Producer lässt `wiki/`/Lock/`log.md` unverändert, keine Commits, fremde Lease erhalten (AC-c) — inkl. gehärteter `log.md`-Assertion. - 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:292`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L292) [`run-sandbox.sh:339`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L339)
- Kollisions-Hold mit beiden Commit-Hashes + Scope in `log.md` an Epic 4 (AC-e, AD-17c, kein textueller Auto-Merge) — Datei-Level-Attest nachgeschärft. - 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:362`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L362) [`run-sandbox.sh:460`](../../_bmad-output/implementation-artifacts/sandbox-3-11/run-sandbox.sh#L460)
**Status- & Nachweissynchronisation (peripher)** **Status- & Nachweissynchronisation (peripher)**
- Revisionsnachweis Story 3.11 → `review` in der Laufzeitakte — Verankerung, Sandbox, Abschlussklausel, Erhaltungs-Invariante. - Revisionsnachweis Story 3.11 → `review` in der Laufzeitakte — Verankerung, Sandbox, Abschlussklausel, Erhaltungs-Invariante.
[`log.md:47`](../../wiki/log.md#L47) [`log.md:4`](../../wiki/log.md#L4)
- Planungsartefakt auf Ist-Zustand nachgeführt (epic-3-context Z. 41). - 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) [`epic-3-context.md:41`](../../_bmad-output/implementation-artifacts/epic-3-context.md#L41)
**Härtung & Konsistenz-Funde (defer)** **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). - 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:577`](../../_bmad-output/implementation-artifacts/deferred-work.md#L577) [`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).
@@ -29,7 +29,7 @@
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) # - 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 # - Retrospective appends its action items to action_items; the status view surfaces open ones
generated: 08-14-2026 00:00 generated: 08-14-2026 00:00
last_updated: 08-21-2026 12:30 last_updated: 08-22-2026 07:44
project: wow20 project: wow20
project_key: NOKEY project_key: NOKEY
tracking_system: file-system tracking_system: file-system
@@ -61,8 +61,8 @@ development_status:
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-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-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-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: review # Story 3.11 Review-Bereitschaft 2026-08-21 (§5.17 Verankerung, Revision 3.6; Sandbox A-1..A-8 8/8 harte PASS/Exit 0 — Zwei-Worktree-/Zwei-Prozess-Überlappung mit Zwischenzustands-Assertions „niemals zwei aktive Root-Leases", LEASE_HOLD-Nicht-Mutation, Kollisions-Hold mit beiden Commit-Hashes). 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: backlog 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: backlog 3-13-epic-3-verifikations-und-abnahmegate: backlog
epic-3-retrospective: optional epic-3-retrospective: optional
+21 -5
View File
File diff suppressed because one or more lines are too long
+5
View File
File diff suppressed because one or more lines are too long