From 9057b8bd61bf0749c3e649acb68d9eac1980561e Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Fri, 2 Oct 2026 19:18:49 +0200 Subject: [PATCH] docs(spec): echte Umlaute in LOCK-SYSTEM_de.md normalisieren (T-20260920-669390462) --- LOCK-SYSTEM_de.md | 436 +++++++++++++++++++++++----------------------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/LOCK-SYSTEM_de.md b/LOCK-SYSTEM_de.md index b60eab6..94335a3 100644 --- a/LOCK-SYSTEM_de.md +++ b/LOCK-SYSTEM_de.md @@ -2,10 +2,10 @@ > **Kanonisch ist diese Datei im Repo** `ellmos-ai/lock-master` als > `LOCK-SYSTEM_de.md`; die Fassung unter `/_scripts/LOCK-SYSTEM.md` ist ihre -> **Deploy-Kopie**. Aenderungen gehoeren in den Klon und werden von dort deployt +> **Deploy-Kopie**. Änderungen gehören in den Klon und werden von dort deployt > (`bin/check_deployment.py ` meldet Abweichung). Wer stattdessen die > Deploy-Kopie bearbeitet, baut die Gabelung wieder auf, die T-20260913-633163908 -> aufgeloest hat — damals waren beide Fassungen so weit auseinander, dass jede Regeln +> aufgelöst hat — damals waren beide Fassungen so weit auseinander, dass jede Regeln > trug, die der anderen fehlten. **Englische Fassung:** `LOCK-SYSTEM.md` im selben Repo > (Sprachstufe Core nach P-006: DE + EN). [C 2026-09-13] @@ -23,13 +23,13 @@ Zusätzlich kennt das System jetzt kooperative Team-/Community-Locks. Diese sper --- -## Ueberblick: Wer haelt gerade was? (beste Methoden) +## Überblick: Wer hält gerade was? (beste Methoden) -Den schnellsten Ueberblick ueber aktive Sperren — ohne jeden Projektordner einzeln zu oeffnen — liefern (in dieser Rangfolge): +Den schnellsten Überblick über aktive Sperren — ohne jeden Projektordner einzeln zu öffnen — liefern (in dieser Rangfolge): -1. **Agent mit MCP (am schnellsten, live):** ellmos-filecommander Dateisuche nach `LOCK*.txt` (`fc_search_files`, directory = gewuenschter Root). Findet alle Lock-Dateien index-gestuetzt in Sekunden (ganzes OneDrive in einem Aufruf getestet). Fuer sehr grosse Baeume nicht-blockierend via `fc_start_search`/`fc_get_search_results` (langsamer). -2. **Schnelles Nachschauen ohne Suche:** die auto-generierte `LOCK-CACHE.md` lesen — systemweit `_scripts/LOCK-CACHE.md`, nur Software `.SOFTWARE/LOCK-CACHE.md`. Wird per `lock_scan.py --write-cache` befuellt. -3. **Skriptgesteuert:** `PYTHONIOENCODING=utf-8 python "/_scripts/lock_scan.py"` (read-only Liste) bzw. `--write-cache` zum Aktualisieren der Caches. Ein voller rekursiver Scan ueber alle Roots ist ueber OneDrive langsam — daher der Cache. +1. **Agent mit MCP (am schnellsten, live):** ellmos-filecommander Dateisuche nach `LOCK*.txt` (`fc_search_files`, directory = gewünschter Root). Findet alle Lock-Dateien index-gestützt in Sekunden (ganzes OneDrive in einem Aufruf getestet). Für sehr große Bäume nicht-blockierend via `fc_start_search`/`fc_get_search_results` (langsamer). +2. **Schnelles Nachschauen ohne Suche:** die auto-generierte `LOCK-CACHE.md` lesen — systemweit `_scripts/LOCK-CACHE.md`, nur Software `.SOFTWARE/LOCK-CACHE.md`. Wird per `lock_scan.py --write-cache` befüllt. +3. **Skriptgesteuert:** `PYTHONIOENCODING=utf-8 python "/_scripts/lock_scan.py"` (read-only Liste) bzw. `--write-cache` zum Aktualisieren der Caches. Ein voller rekursiver Scan über alle Roots ist über OneDrive langsam — daher der Cache. Authoritativ bleiben immer die `LOCK*.txt`-Dateien selbst; der Cache ist nur ein abgeleiteter Schnell-Index. @@ -111,35 +111,35 @@ Die lokale GUI ist die Arbeitsfassung. Die nutzerneutrale, portable Variante wir --- -## Scope ueber Dateiname (AUTORITATIV ist der Dateiname) +## Scope über Dateiname (AUTORITATIV ist der Dateiname) ### Exclusive Locks (klassisch — sperren alle Systeme/Agenten) - `LOCK.txt` = ganzes Projekt gesperrt (scope = `project`). - `LOCK..txt` = nur diese Komponente gesperrt; freier Scope-Name (Unterbereich/Unterordner), z. B. `LOCK.software.txt`, `LOCK.web.txt`, `LOCK.dev.txt`, `LOCK.desktop.txt`, `LOCK.mobile.txt`, `LOCK.flutter_port.txt`. -- So koennen z. B. Desktop- und Mobile-Loops dasselbe Projekt parallel an verschiedenen Komponenten bearbeiten. -- Kein Systemname im Dateinamen noetig — der Lock blockiert alle. Das `host:`-Feld im Inhalt sagt informativ, wer sperrt. +- So können z. B. Desktop- und Mobile-Loops dasselbe Projekt parallel an verschiedenen Komponenten bearbeiten. +- Kein Systemname im Dateinamen nötig — der Lock blockiert alle. Das `host:`-Feld im Inhalt sagt informativ, wer sperrt. ### Team Locks (koordinieren Agenten eines Systems; blockieren andere Systeme) -Team-Locks sind eine eigene Kategorie: Sie koordinieren mehrere Agenten, die **auf demselben System** parallel an einem Projekt arbeiten (Anwesenheit, Datei-/Tool-Claims, Queue, Nachrichten). Fuer alle **anderen Systeme** wirken sie wie ein Exclusive Lock. +Team-Locks sind eine eigene Kategorie: Sie koordinieren mehrere Agenten, die **auf demselben System** parallel an einem Projekt arbeiten (Anwesenheit, Datei-/Tool-Claims, Queue, Nachrichten). Für alle **anderen Systeme** wirken sie wie ein Exclusive Lock. -**Warum pro System:** OneDrive-Sync hat 30s–5min Latenz — Echtzeit-Koordination ueber Systemgrenzen ist damit nicht zuverlaessig. Agenten auf demselben System teilen Dateisystem und Prozessraum und koennen direkt koordinieren. +**Warum pro System:** OneDrive-Sync hat 30s–5min Latenz — Echtzeit-Koordination über Systemgrenzen ist damit nicht zuverlässig. Agenten auf demselben System teilen Dateisystem und Prozessraum und können direkt koordinieren. **Neue Konvention (ab 2026-06-19):** -- `LOCK.team..txt` = Team-Lock fuer das gesamte Projekt auf einem System, z. B. `LOCK.team.LAPTOP.txt`. -- `LOCK.team...txt` = Team-Lock fuer eine Komponente, z. B. `LOCK.team.assets.ASUS-GEI.txt`. -- Wenn ein zweites System eintreten will: eigenen `LOCK.team...txt` anlegen, sofern die Scopes sich nicht ueberlappen. +- `LOCK.team..txt` = Team-Lock für das gesamte Projekt auf einem System, z. B. `LOCK.team.LAPTOP.txt`. +- `LOCK.team...txt` = Team-Lock für eine Komponente, z. B. `LOCK.team.assets.ASUS-GEI.txt`. +- Wenn ein zweites System eintreten will: eigenen `LOCK.team...txt` anlegen, sofern die Scopes sich nicht überlappen. -**Deprecated (ab 2026-06-19, bleibt gueltig waehrend Uebergang):** +**Deprecated (ab 2026-06-19, bleibt gültig während Übergang):** -- `LOCK.community....txt` — alte Konvention (z. B. `LOCK.community.loop.codex.WORKSTATION.txt`). Wird weiterhin als Lock erkannt und respektiert. **Neue Team-Locks nicht mehr in diesem Format anlegen** — stattdessen `LOCK.team.*` verwenden. Bestehende Community-Locks muessen nicht sofort umbenannt werden; sie laufen normal ab oder werden bei Gelegenheit auf das neue Format umgestellt. +- `LOCK.community....txt` — alte Konvention (z. B. `LOCK.community.loop.codex.WORKSTATION.txt`). Wird weiterhin als Lock erkannt und respektiert. **Neue Team-Locks nicht mehr in diesem Format anlegen** — stattdessen `LOCK.team.*` verwenden. Bestehende Community-Locks müssen nicht sofort umbenannt werden; sie laufen normal ab oder werden bei Gelegenheit auf das neue Format umgestellt. ### User Locks (nutzerbasierte Komplettsperre — nur der Nutzer entfernt sie) -User-Locks sind eine eigene, geschuetzte Kategorie (ab 2026-06-27). Sie sperren ein Projekt -dauerhaft, und **nur der Nutzer** (manuell oder ueber die Lock-Watcher-GUI) darf sie wieder +User-Locks sind eine eigene, geschützte Kategorie (ab 2026-06-27). Sie sperren ein Projekt +dauerhaft, und **nur der Nutzer** (manuell oder über die Lock-Watcher-GUI) darf sie wieder entfernen — Agenten und der Stale-Cleanup (`prune_stale_locks.py`) fassen sie **nie** an, auch wenn sie nominell abgelaufen sind. @@ -147,65 +147,65 @@ wenn sie nominell abgelaufen sind. - `LOCK.user..txt` = Komponente, nutzerbasiert gesperrt. - Marker-Segment `user` ist reserviert (analog `team`). Erkennung: `lock_utils.is_user_lock()`; Schutz: `lock_utils.is_protected_lock()` / `is_prunable()`. -- Anlegen/Entfernen am einfachsten ueber die Watcher-GUI (`http://127.0.0.1:8095`, Button +- Anlegen/Entfernen am einfachsten über die Watcher-GUI (`http://127.0.0.1:8095`, Button „Sperren/Rechte") oder per Vorlage `_scripts/LOCK_TEMPLATE.txt` mit `removable_by: user`. -- Fix 2026-07-03: geschuetzte Locks (User + Condition) laufen auch in `lock_utils.is_expired()` - nie mehr zeitbasiert ab — sie erschienen vorher nach nominellem Ablauf faelschlich als +- Fix 2026-07-03: geschützte Locks (User + Condition) laufen auch in `lock_utils.is_expired()` + nie mehr zeitbasiert ab — sie erschienen vorher nach nominellem Ablauf fälschlich als inaktiv in `active_locks()`/`lock_scan.py`, obwohl sie laut Spec weitergalten. ### Condition Locks (bedingungsbasierte, operationsbezogene Sperre — ab 2026-07-03) -Condition-Locks sperren **bis eine Bedingung erfuellt ist** statt bis eine Zeit ablaeuft, +Condition-Locks sperren **bis eine Bedingung erfüllt ist** statt bis eine Zeit abläuft, und typischerweise **nur bestimmte Operationen** statt das ganze Projekt. Anwendungsfall: -„Kein Zenodo-Upload von Paper X, bevor die Review-Nachtraege eingearbeitet sind" — die -Forschung am Projekt bleibt dabei voellig frei. +„Kein Zenodo-Upload von Paper X, bevor die Review-Nachträge eingearbeitet sind" — die +Forschung am Projekt bleibt dabei völlig frei. - `LOCK.condition.txt` = Bedingungssperre auf Projektebene. -- `LOCK.condition..txt` = Bedingungssperre fuer eine Komponente, +- `LOCK.condition..txt` = Bedingungssperre für eine Komponente, z. B. `LOCK.condition.zenodo-upload-paper3-4.txt`. - Marker-Segment `condition` ist reserviert (analog `user`/`team`). Erkennung: `lock_utils.is_condition_lock()`; Schutz: `is_protected_lock()`. - **Kein Zeitverfall:** `expires_after` ist wirkungslos; `prune_stale_locks.py` und Bulk-Unlock fassen Condition-Locks nie an. `lock_scan.py` zeigt statt Restzeit - „bis Bedingung erfuellt: …". -- **Pflichtfeld `release_condition:`** — prazise, nachpruefbar formulieren (WAS muss + „bis Bedingung erfüllt: …". +- **Pflichtfeld `release_condition:`** — präzise, nachprüfbar formulieren (WAS muss erledigt sein, WO ist es dokumentiert/nachweisbar). - **Feld `operations:`** (kommagetrennt) benennt die GESPERRTEN Operationen - (z. B. `operations: zenodo-upload`). Alles nicht Genannte bleibt ausdruecklich - erlaubt. Ohne `operations`-Feld wirkt der Lock gemaess `mode` auf den ganzen Scope. + (z. B. `operations: zenodo-upload`). Alles nicht Genannte bleibt ausdrücklich + erlaubt. Ohne `operations`-Feld wirkt der Lock gemäß `mode` auf den ganzen Scope. Helper: `lock_utils.locked_operations(path)`. - **Freigabe:** Anders als bei User-Locks darf **jeder Agent** den Lock entfernen, - der die `release_condition` nachweislich erfuellt hat — die Erfuellung ist beim + der die `release_condition` nachweislich erfüllt hat — die Erfüllung ist beim Entfernen im Projekt-Register (z. B. TODO/BEWEISNOTIZ) zu dokumentieren. - Bei Unsicherheit, ob die Bedingung erfuellt ist: NICHT entfernen, User fragen. + Bei Unsicherheit, ob die Bedingung erfüllt ist: NICHT entfernen, User fragen. ### Lockarten lassen sich im Dateinamen NICHT kombinieren [C 2026-09-03] -Ein Lock-Name traegt **genau einen** Typ-Marker, im ersten Segment. Namen, die wie eine +Ein Lock-Name trägt **genau einen** Typ-Marker, im ersten Segment. Namen, die wie eine Kombination aussehen — `LOCK.until.and.condition..txt`, `LOCK.until.or.condition..txt`, `LOCK.user.condition..txt` — sind **mehrdeutig** und gelten fail-closed: `is_expired()` gibt sie nie frei, `prune` fasst sie -nie an, und `lock_scan.py` gibt eine ausdrueckliche Warnung aus. +nie an, und `lock_scan.py` gibt eine ausdrückliche Warnung aus. **Warum fail-closed und nicht einfach abgelehnt:** Bis zu diesem Fix wurde so ein Name als ERSTER Marker plus seltsamer Scope gelesen (`until` + `and.condition.`), der zweite -Marker still ignoriert. Wer den Namen schrieb und „beides" meinte, bekam die **schwaechere** -der beiden Sperren — sie fiel, sobald die Frist ablief, Bedingung unerfuellt. Unbefristet +Marker still ignoriert. Wer den Namen schrieb und „beides" meinte, bekam die **schwächere** +der beiden Sperren — sie fiel, sobald die Frist ablief, Bedingung unerfüllt. Unbefristet halten ist das sichere Scheitern: es kann nur zu lange sperren, nie zu wenig. -Es zaehlen nur **ganze Segmente**. Ein Scope wie `publication-and-claim-edits` ist EIN -Segment und bleibt gueltig (real im Bestand vorhanden). +Es zählen nur **ganze Segmente**. Ein Scope wie `publication-and-claim-edits` ist EIN +Segment und bleibt gültig (real im Bestand vorhanden). -**Frist und Bedingung werden ueber die FELDER kombiniert, nicht ueber den Namen:** +**Frist und Bedingung werden über die FELDER kombiniert, nicht über den Namen:** ``` LOCK.until.winners-announcement.txt - not_before: 2026-10-08T12:00-07:00 # beendet das Bewachen (maschinell pruefbar) - release_condition: Verkuendung belegt auf # Beleg vor dem Entfernen + not_before: 2026-10-08T12:00-07:00 # beendet das Bewachen (maschinell prüfbar) + release_condition: Verkündung belegt auf # Beleg vor dem Entfernen ``` Die Frist beendet das *Bewachen*; die Bedingung sagt, was belegt sein muss, bevor die Datei -entfernt wird. Kombiniert werden beide ueber `release_mode` (siehe Feldtabelle oben und den +entfernt wird. Kombiniert werden beide über `release_mode` (siehe Feldtabelle oben und den Abschnitt „Frist UND/ODER Bedingung kombinieren" unten) — **umgesetzt** in dieser Fassung, T-20260903-476807738. @@ -218,48 +218,48 @@ T-20260903-476807738. ### Until Locks (Fristsperre — gilt bis zu einem absoluten Zeitpunkt, ab 2026-09-03) -Until-Locks gelten **bis zu einem festen Zeitpunkt**, den die Datei nennt — statt fuer eine -relative Dauer (`expires_after`, Default 24h) oder bis eine Freitext-Bedingung erfuellt ist. +Until-Locks gelten **bis zu einem festen Zeitpunkt**, den die Datei nennt — statt für eine +relative Dauer (`expires_after`, Default 24h) oder bis eine Freitext-Bedingung erfüllt ist. Anwendungsfall: Wettbewerbs-Sperren, bei denen der Freigabemoment ein Kalendertermin Wochen -spaeter ist, den niemand als `expires_after: 900h` schreiben will. +später ist, den niemand als `expires_after: 900h` schreiben will. - `LOCK.until.txt` = Fristsperre auf Projektebene. -- `LOCK.until..txt` = Fristsperre fuer eine Komponente, +- `LOCK.until..txt` = Fristsperre für eine Komponente, z. B. `LOCK.until.winners-announcement.txt`. - Marker-Segment `until` ist reserviert (analog `user`/`team`/`condition`). Erkennung: `lock_utils.is_until_lock()`; Zeitpunkt: `lock_utils.lock_not_before()`. - **Pflichtfeld `not_before:`** — absoluter ISO-Zeitstempel, mit oder ohne UTC-Offset: `2026-10-08T12:00-07:00` oder `2026-10-08 12:00`. Ein Wert mit Offset wird in Lokalzeit gewandelt, ein Wert ohne Offset gilt als Lokalzeit. **Offset immer angeben, wenn die Frist - in einer fremden Zeitzone verkuendet wurde** — genau dort passieren die Fehler. -- **Fail-closed:** Fehlt `not_before` oder ist es unparsbar, laeuft der Lock **nie** ab. Ein - Tippfehler kann nur zu lange sperren, nie zu frueh freigeben. + in einer fremden Zeitzone verkündet wurde** — genau dort passieren die Fehler. +- **Fail-closed:** Fehlt `not_before` oder ist es unparsbar, läuft der Lock **nie** ab. Ein + Tippfehler kann nur zu lange sperren, nie zu früh freigeben. - **`expires_after` ist wirkungslos.** Der absolute Zeitpunkt ersetzt es. **Diese Art trennt als erste zwei Dinge, die alle anderen zusammenfassen:** -| Art | laeuft zeitbasiert ab | vor Loeschung geschuetzt | +| Art | läuft zeitbasiert ab | vor Löschung geschützt | |---|---|---| | exclusive | ja, nach `expires_after` | nein | | user | nie | ja | | condition | nie | ja | | **until** | **ja, zum `not_before`** | **ja** | -Ein Waechter hoert damit **von selbst** auf, das Projekt zu bewachen, sobald der Zeitpunkt -ueberschritten ist (`is_expired()` wird wahr, `active_locks()` listet ihn nicht mehr). Die +Ein Wächter hört damit **von selbst** auf, das Projekt zu bewachen, sobald der Zeitpunkt +überschritten ist (`is_expired()` wird wahr, `active_locks()` listet ihn nicht mehr). Die **Datei bleibt trotzdem liegen**: `prune_stale_locks.py` und Bulk-Unlock fassen sie nie an, -denn Nutzerentscheidung und Belegpflicht ueberdauern die Frist. +denn Nutzerentscheidung und Belegpflicht überdauern die Frist. -- **Feld `release_condition:`** (empfohlen) — was zusaetzlich belegt sein muss, bevor die - Datei entfernt wird (z. B. „Gewinnerverkuendung tatsaechlich erfolgt, Quelle X"). Die Frist +- **Feld `release_condition:`** (empfohlen) — was zusätzlich belegt sein muss, bevor die + Datei entfernt wird (z. B. „Gewinnerverkündung tatsächlich erfolgt, Quelle X"). Die Frist beendet das *Bewachen*; sie beweist nicht, dass das Ereignis eingetreten ist. Wird ein Termin verschoben, bleibt der Lock fail-closed und `not_before` wird korrigiert. - **Feld `operations:`** wirkt wie bei Condition-Locks: es nennt die GESPERRTEN Operationen, - alles andere bleibt ausdruecklich erlaubt. -- **Entfernen** richtet sich nach dem, was der Lock selbst sagt. Schuetzt er eine + alles andere bleibt ausdrücklich erlaubt. +- **Entfernen** richtet sich nach dem, was der Lock selbst sagt. Schützt er eine Nutzerentscheidung, steht das in `release_condition` und nur der Nutzer entfernt ihn. - `lock_scan.py` zeigt dann „Frist abgelaufen - Waechter darf aufhoeren; Datei - bleibt fuer den Nutzer". + `lock_scan.py` zeigt dann „Frist abgelaufen - Wächter darf aufhören; Datei + bleibt für den Nutzer". - Vor Ablauf zeigt `lock_scan.py` Restzeit und Zeitpunkt, z. B. `842h13m (bis 2026-10-08T21:00)`. **Herkunft:** Der Bedarf wurde vorher zweimal improvisiert — `LOCK_FALLS_NOT_BEFORE:` und @@ -268,19 +268,19 @@ ausgewertet. Umgesetzt im Modul `lock-master` v1.6.0 (`pure-locking/lock_utils.p 12 Tests in `tests/test_until_lock_system.py`) und hier in die Produktivfassung portiert. **Frist UND/ODER Bedingung kombinieren — Feld `release_mode` (T-20260903-476807738):** -Kombiniert wird ueber FELDER, nicht ueber eine Namensgrammatik — die zunaechst erwogenen -Varianten (`LOCK.until.and.condition.*`, `LOCK.until.or.condition.*`, ...) wurden ausdruecklich +Kombiniert wird über FELDER, nicht über eine Namensgrammatik — die zunächst erwogenen +Varianten (`LOCK.until.and.condition.*`, `LOCK.until.or.condition.*`, ...) wurden ausdrücklich verworfen (siehe „Lockarten lassen sich im Dateinamen NICHT kombinieren" unten). Setzt ein -Until-Lock ZUSAETZLICH `release_condition`, entscheidet `release_mode`: +Until-Lock ZUSÄTZLICH `release_condition`, entscheidet `release_mode`: -- `all` (Default, rueckwaertskompatibel) — Frist UND Bedingung muessen erfuellt sein. Da - `release_condition` Freitext ist, kann das Werkzeug ihn nicht pruefen: `is_expired()` gibt - nach der Frist NICHT von allein frei (fail-closed), die Datei bleibt fuer die - Nutzer-/Agentenpruefung liegen — genau wie bisher bei Condition-Locks. -- `any` — was zuerst eintritt, gibt frei. Die Frist allein genuegt dem Werkzeug; die +- `all` (Default, rückwärtskompatibel) — Frist UND Bedingung müssen erfüllt sein. Da + `release_condition` Freitext ist, kann das Werkzeug ihn nicht prüfen: `is_expired()` gibt + nach der Frist NICHT von allein frei (fail-closed), die Datei bleibt für die + Nutzer-/Agentenprüfung liegen — genau wie bisher bei Condition-Locks. +- `any` — was zuerst eintritt, gibt frei. Die Frist allein genügt dem Werkzeug; die Bedingungsseite bleibt dann Sache eines Agenten/Menschen (das Werkzeug kann sie nicht - eigenstaendig aufloesen — das klar dokumentieren, sonst wirkt die Sperre staerker als sie ist). -- Ungueltiger Wert -> `all` (im Zweifel nie zu frueh freigeben). + eigenständig auflösen — das klar dokumentieren, sonst wirkt die Sperre stärker als sie ist). +- Ungültiger Wert -> `all` (im Zweifel nie zu früh freigeben). - Fehlt `release_condition` ganz, ist `release_mode` wirkungslos: reiner Datumscheck wie bisher. ### Allgemein @@ -293,11 +293,11 @@ Until-Lock ZUSAETZLICH `release_condition`, entscheidet `release_mode`: ## Rechtesystem: LOCK.permissions + Sofortsperrung (ab 2026-06-27) Agent-neutrales, ordner-bezogenes Rechtesystem neben den `LOCK*.txt` — von **allen** Agenten -(Claude, Codex, Gemini, Kimi) lesbar. Verwaltet vom Lock-Watcher (`_control-center`-Saeule). +(Claude, Codex, Gemini, Kimi) lesbar. Verwaltet vom Lock-Watcher (`_control-center`-Säule). ### `LOCK.permissions.json` — Rechte pro Projektordner -Syntax an `.claude/settings.json` angelehnt, aber agent-uebergreifend und ordner-scoped: +Syntax an `.claude/settings.json` angelehnt, aber agent-übergreifend und ordner-scoped: ```json { "format": "lock-permissions-v1", "default": "allow", @@ -306,7 +306,7 @@ Syntax an `.claude/settings.json` angelehnt, aber agent-uebergreifend und ordner ``` - Pattern: `Tool(glob)` (`Bash(...)`, `Read(...)`, `Write(...)`), `mcp__vendor__tool`, `*`. -- Praezedenz: `deny > ask > allow > default`. Auswertung: `_scripts/permissions.py::evaluate(perm, agent, action)`. +- Präzedenz: `deny > ask > allow > default`. Auswertung: `_scripts/permissions.py::evaluate(perm, agent, action)`. - Durchsetzung = freiwillige Konvention + GUI/Audit (wie `LOCK*.txt`). Vorlage: `_scripts/LOCK_PERMISSIONS_TEMPLATE.json`. @@ -316,14 +316,14 @@ Syntax an `.claude/settings.json` angelehnt, aber agent-uebergreifend und ordner (`lock_roots.json`) in einem Schritt: - `bulk_lock(roots, commit=False)` — Dry-Run per Default; idempotent (vorhandene Locks bleiben); - gesetzte Locks tragen `created_by: bulk` (exakte Ruecknahme via Session-Manifest). + gesetzte Locks tragen `created_by: bulk` (exakte Rücknahme via Session-Manifest). - `bulk_unlock(...)` — entfernt **nur** `created_by: bulk`-Locks; **niemals User-Locks**. -- Bedienung ueber die Watcher-GUI (Button „Sperren/Rechte", mit Dry-Run-Vorschau + Bestaetigung) +- Bedienung über die Watcher-GUI (Button „Sperren/Rechte", mit Dry-Run-Vorschau + Bestätigung) oder CLI `python bulk_lock.py lock|unlock --commit`. Kanonische, laufende Logik: `_scripts/` (Daemon-Import). Nutzerneutrale Spiegel: `.AI/.MODULES/lock-master` (Locks/Rechte) + `.AI/.MODULES/ticket-master` (Ticket/Doku-Helfer). -Zentraler Einstieg fuer alle LLMs: `.TOPICS/_control-center/MANIFEST.md`. +Zentraler Einstieg für alle LLMs: `.TOPICS/_control-center/MANIFEST.md`. --- @@ -333,18 +333,18 @@ Vorlage: `_scripts/LOCK_TEMPLATE.txt`. Zeilen mit `#` = Kommentar, Leerzeilen ig | Feld | Pflicht | Bedeutung | |---|---|---| -| `owner` | ja | Wer haelt die Sperre (Agent/User/Automation). | -| `created` | ja | ISO-Zeitstempel `YYYY-MM-DDTHH:MM` (Basis fuer Verfall). | -| `host` | optional | Maschinenname des Systems, das die Sperre haelt (z. B. `LAPTOP`, `MACSTUDIO`, `ASUS-GEI`). Ermoeglicht cross-system-Erkennung: WELCHES System sperrt gerade. Fehlt das Feld, gilt `host = None` — kein Fehler, rueckwaertskompatibel. | +| `owner` | ja | Wer hält die Sperre (Agent/User/Automation). | +| `created` | ja | ISO-Zeitstempel `YYYY-MM-DDTHH:MM` (Basis für Verfall). | +| `host` | optional | Maschinenname des Systems, das die Sperre hält (z. B. `LAPTOP`, `MACSTUDIO`, `ASUS-GEI`). Ermöglicht cross-system-Erkennung: WELCHES System sperrt gerade. Fehlt das Feld, gilt `host = None` — kein Fehler, rückwärtskompatibel. | | `expires_after` | optional | z. B. `24h` / `48h` / `90m`. Default = `24h`. | | `release_condition` | optional | Freitext: was muss passieren, damit frei wird. | -| `release_mode` | optional | Nur wirksam bei Until-Locks MIT zusaetzlichem `release_condition`: `all` (Default, rueckwaertskompatibel) \| `any`. Siehe unten. | -| `mode` | optional | `hard` (keine Aenderung, Default) \| `soft` (Lesen/Hinweis ok). | -| `purpose` | optional | Freitext: warum gesperrt / was laeuft gerade. | +| `release_mode` | optional | Nur wirksam bei Until-Locks MIT zusätzlichem `release_condition`: `all` (Default, rückwärtskompatibel) \| `any`. Siehe unten. | +| `mode` | optional | `hard` (keine Änderung, Default) \| `soft` (Lesen/Hinweis ok). | +| `purpose` | optional | Freitext: warum gesperrt / was läuft gerade. | | `scope` | optional | nur informativ; autoritativ ist der Dateiname. | -| `fence` | optional | **Vergabenummer** (Epoch-Mikrosekunden), von `lock_create.py` fuer kooperative Erkennung geschrieben. Der Halter merkt sie sich beim Erwerb und prueft sie vor jedem Schreiben — siehe Abschnitt „Fencing-Tokens“. Fehlt das Feld, ist es ein Lock aus der Zeit davor und wird behandelt wie bisher. | +| `fence` | optional | **Vergabenummer** (Epoch-Mikrosekunden), von `lock_create.py` für kooperative Erkennung geschrieben. Der Halter merkt sie sich beim Erwerb und prüft sie vor jedem Schreiben — siehe Abschnitt „Fencing-Tokens“. Fehlt das Feld, ist es ein Lock aus der Zeit davor und wird behandelt wie bisher. | -Fehlt `created` (oder unparsebar), gilt die Datei-mtime als Fallback fuer den Verfall. +Fehlt `created` (oder unparsebar), gilt die Datei-mtime als Fallback für den Verfall. --- @@ -354,32 +354,32 @@ Eine Frist allein verhindert den bekanntesten Fehler von Lease-Verfahren nicht (Martin Kleppmann, „How to do distributed locking“): 1. Agent A erwirbt den Lock und arbeitet. -2. A pausiert laenger als die TTL — Garbage Collection, Swap, Standby, haengender +2. A pausiert länger als die TTL — Garbage Collection, Swap, Standby, hängender Sync-Aufruf, unterbrochene SSH-Sitzung. -3. Die Sperre verfaellt. `prune_stale_locks.py` raeumt sie, oder Agent B erwirbt sie. -4. A arbeitet weiter. **A weiss nicht, dass er die Sperre verloren hat**, und schreibt +3. Die Sperre verfällt. `prune_stale_locks.py` räumt sie, oder Agent B erwirbt sie. +4. A arbeitet weiter. **A weiß nicht, dass er die Sperre verloren hat**, und schreibt in denselben Bereich wie B. -Der Fehler ist still: beide melden Erfolg, und ueber OneDrive kann daraus zusaetzlich +Der Fehler ist still: beide melden Erfolg, und über OneDrive kann daraus zusätzlich eine Konfliktkopie werden (Abweichung A7). Die Frist kann das nicht fangen, weil A nach dem Aufwachen gar nichts mehr liest. -**Kooperative Erkennung per Vergabenummer.** Jede Vergabe erhaelt eine Zahl im Feld -`fence:` (Epoch-Mikrosekunden, bei Uebernahme aufsteigend forciert). Der Halter merkt sie sich -und prueft sie **vor jedem Schreib- oder Push-Vorgang** gegen die Datei am Zielort. Eine -abweichende Nummer heisst: das Lease ist weitergegangen — Schreibvorgang ablehnen, nicht +**Kooperative Erkennung per Vergabenummer.** Jede Vergabe erhält eine Zahl im Feld +`fence:` (Epoch-Mikrosekunden, bei Übernahme aufsteigend forciert). Der Halter merkt sie sich +und prüft sie **vor jedem Schreib- oder Push-Vorgang** gegen die Datei am Zielort. Eine +abweichende Nummer heißt: das Lease ist weitergegangen — Schreibvorgang ablehnen, nicht heimlich fortsetzen. ``` python lock_create.py --scope docs --owner agent-A created: ...\LOCK.docs.txt fence: 1789902599751740 - hint: merke dir die Nummer und pruefe sie vor jedem Schreiben -- + hint: merke dir die Nummer und prüfe sie vor jedem Schreiben -- set LOCK_FENCE=1789902599751740 set LOCK_FENCE_FILE=...\LOCK.docs.txt ``` -Geprueft wird entweder im Prozess +Geprüft wird entweder im Prozess ```python status, grund = lock_utils.fence_status(lock_path, mein_fence) # "held" | "lost" | "unknown" @@ -392,99 +392,99 @@ python lock_scan.py --verify-fence --fence ``` **Was als „verloren“ gilt (fail-closed).** Alles, was nicht nachweislich noch der -eigene Anspruch ist: die Datei ist verschwunden; sie traegt eine hoehere (oder -niedrigere) Nummer; sie traegt gar keine Nummer mehr, weil ein aelterer Schreiber sie +eigene Anspruch ist: die Datei ist verschwunden; sie trägt eine höhere (oder +niedrigere) Nummer; sie trägt gar keine Nummer mehr, weil ein älterer Schreiber sie neu angelegt hat; oder der **eigene** Anspruch ist abgelaufen, obwohl die Datei noch daliegt — das Fenster zwischen Fristablauf und `prune`. -**Warum Uhrzeit und kein Zaehler.** Ein Zaehler braucht ein atomares Inkrement, das -alle Schreiber teilen. Ueber einen Cloud-Ordner mit 30 s bis 5 min Sync-Latenz gibt es +**Warum Uhrzeit und kein Zähler.** Ein Zähler braucht ein atomares Inkrement, das +alle Schreiber teilen. Über einen Cloud-Ordner mit 30 s bis 5 min Sync-Latenz gibt es das nicht: zwei Hosts lesen beide 5 und schreiben beide 6. Eine per Rename erzeugte -Sequenzdatei hat dieselbe Luecke — das Rename ist auf jedem Host fuer sich atomar, und -genau das hilft einem gemeinsamen Zaehler nicht. Wanduhr-Mikrosekunden vermeiden die -Notwendigkeit eines geteilten atomaren Zaehlers ueber synchronisierte Ordner. Die Monotonie -zwischen Hosts haengt dabei von der Uhrensynchronisation ab: Waehrend `new_fence(previous=...)` -bei Uebernahme die Vorgaengernummer uebersteigt, koennen Uhrenversatz oder Rueckspruenge die -Reihenfolge beeinflussen, wenn ein Host ohne Kenntnis des Vorgaengers neu erwirbt. +Sequenzdatei hat dieselbe Lücke — das Rename ist auf jedem Host für sich atomar, und +genau das hilft einem gemeinsamen Zähler nicht. Wanduhr-Mikrosekunden vermeiden die +Notwendigkeit eines geteilten atomaren Zählers über synchronisierte Ordner. Die Monotonie +zwischen Hosts hängt dabei von der Uhrensynchronisation ab: Während `new_fence(previous=...)` +bei Übernahme die Vorgängernummer übersteigt, können Uhrenversatz oder Rücksprünge die +Reihenfolge beeinflussen, wenn ein Host ohne Kenntnis des Vorgängers neu erwirbt. Das leiht sich keine neue Annahme: `expires_after` wird **ohnehin schon** gegen die -lokale Uhr jedes Hosts gerechnet, und das Contest-Verfahren ordnet Anspruechen +lokale Uhr jedes Hosts gerechnet, und das Contest-Verfahren ordnet Ansprüchen **ohnehin schon** nach `created` mit Host-Tiebreak. Driften die Uhren weit genug, um Fencing zu brechen, war der Verfall vorher kaputt. -### Was das zusagt — und was ausdruecklich nicht +### Was das zusagt — und was ausdrücklich nicht **Dies ist kooperative ERKENNUNG, keine Durchsetzung.** Die kleinere der beiden -Zusagen, bewusst: Die groessere ist auf diesem Medium nicht einloesbar, und eine -fruehere Fassung dieses Abschnitts hat sie trotzdem gegeben. +Zusagen, bewusst: Die größere ist auf diesem Medium nicht einlösbar, und eine +frühere Fassung dieses Abschnitts hat sie trotzdem gegeben. -**Erkannt wird** — fuer einen Schreiber, der fragt: die Sperre wurde weitergegeben -(hoehere Nummer), das Lease ist unter ihm abgelaufen, die Lockdatei ist verschwunden. +**Erkannt wird** — für einen Schreiber, der fragt: die Sperre wurde weitergegeben +(höhere Nummer), das Lease ist unter ihm abgelaufen, die Lockdatei ist verschwunden. **NICHT verhindert** wird der Schreibvorgang eines Halters, der die Sperre schon verloren hat. Genau darum geht es bei Kleppmann: um die **Pause zwischen der letzten -Pruefung und dem Schreiben**. A prueft, pausiert, verliert das Lease, B erwirbt und -schreibt, A's laengst freigegebener Schreibvorgang landet obendrauf. Alle haben -geprueft, die Daten sind trotzdem kaputt. Das zu schliessen verlangt, dass der -**Speicher** die Nummer als Teil des Schreibvorgangs prueft — ein atomares -Compare-and-Set am Ziel. Ein ueber einen Cloud-Ordner synchronisiertes Verzeichnis +Prüfung und dem Schreiben**. A prüft, pausiert, verliert das Lease, B erwirbt und +schreibt, A's längst freigegebener Schreibvorgang landet obendrauf. Alle haben +geprüft, die Daten sind trotzdem kaputt. Das zu schließen verlangt, dass der +**Speicher** die Nummer als Teil des Schreibvorgangs prüft — ein atomares +Compare-and-Set am Ziel. Ein über einen Cloud-Ordner synchronisiertes Verzeichnis kennt diese Operation nicht; keine Sorgfalt auf dieser Seite erzeugt sie. Ein -Wrapper „pruefen, schreiben, nachpruefen" verkleinert das Fenster, schliesst es -nicht: seine eigene Nachpruefung und sein Rollback haben dieselbe Luecke. +Wrapper „prüfen, schreiben, nachprüfen" verkleinert das Fenster, schließt es +nicht: seine eigene Nachprüfung und sein Rollback haben dieselbe Lücke. **Was der Aufrufer praktisch tun kann, um das Fenster klein zu halten:** -- unmittelbar vor dem Schreiben pruefen, nicht am Anfang der Funktion -- nach allem, was blockieren kann, erneut pruefen (Netz, Sync, Subprozess, Warten) -- die TTL an der tatsaechlichen Arbeitsdauer ausrichten, damit ein Stillstand - ablaeuft statt liegenzubleiben -- wo eine echte Garantie noetig ist, ein Medium mit bedingtem Schreiben waehlen — +- unmittelbar vor dem Schreiben prüfen, nicht am Anfang der Funktion +- nach allem, was blockieren kann, erneut prüfen (Netz, Sync, Subprozess, Warten) +- die TTL an der tatsächlichen Arbeitsdauer ausrichten, damit ein Stillstand + abläuft statt liegenzubleiben +- wo eine echte Garantie nötig ist, ein Medium mit bedingtem Schreiben wählen — eine Datenbankzeile, keine Datei in einem synchronisierten Ordner **Zweite Grenze:** Die Nummern sind wanduhrbasiert, die Ordnung zwischen Hosts also -nur so gut wie die Uhren-Uebereinstimmung. Das ist keine neue Annahme — `expires_after` -rechnet ohnehin gegen die lokale Uhr —, aber eine echte: Eine zurueckgestellte Uhr +nur so gut wie die Uhren-Übereinstimmung. Das ist keine neue Annahme — `expires_after` +rechnet ohnehin gegen die lokale Uhr —, aber eine echte: Eine zurückgestellte Uhr kann eine Nummer erzeugen, die nicht steigt. `new_fence(previous=...)` deckt den -Uebernahmefall ab; einen Host, dem nie gesagt wurde, was er abloest, deckt es nicht. +Übernahmefall ab; einen Host, dem nie gesagt wurde, was er ablöst, deckt es nicht. -**Belegt durch Gegenpruefung** (Codex, 2026-09-20, PR #8): Die Reproduktion -`pause_between_check_and_write` zeigt den Ablauf vollstaendig. Die fruehere Zusage, -ein pruefender Schreiber koenne sich „nicht mehr darueber irren, ob er die Sperre -haelt", war falsch und wurde entfernt statt abgeschwaecht. +**Belegt durch Gegenprüfung** (Codex, 2026-09-20, PR #8): Die Reproduktion +`pause_between_check_and_write` zeigt den Ablauf vollständig. Die frühere Zusage, +ein prüfender Schreiber könne sich „nicht mehr darüber irren, ob er die Sperre +hält", war falsch und wurde entfernt statt abgeschwächt. -**Der Push-Guard prueft dieselbe Bedingung.** +**Der Push-Guard prüft dieselbe Bedingung.** `~/.claude/hooks/lock_push_guard.py` wertet `LOCK_FENCE` und `LOCK_FENCE_FILE` aus und blockiert den Push, sobald der Anspruch nicht mehr gilt. Ohne gesetztes `LOCK_FENCE` -aendert sich am bisherigen Verhalten nichts; ist `LOCK_FENCE` gesetzt, aber +ändert sich am bisherigen Verhalten nichts; ist `LOCK_FENCE` gesetzt, aber `LOCK_FENCE_FILE` nicht, blockiert er ebenfalls — ein deklarierter, aber nicht -pruefbarer Anspruch ist kein Freibrief. +prüfbarer Anspruch ist kein Freibrief. -**Rueckwaertskompatibel in beide Richtungen.** Fehlt `fence`, gilt der bisherige -Zustand; bestehende Sperren brechen nicht und laufen unveraendert ab. Umgekehrt ist -`fence:` fuer jeden aelteren Leser nur eine weitere `key: value`-Zeile, die er wie alle +**Rückwärtskompatibel in beide Richtungen.** Fehlt `fence`, gilt der bisherige +Zustand; bestehende Sperren brechen nicht und laufen unverändert ab. Umgekehrt ist +`fence:` für jeden älteren Leser nur eine weitere `key: value`-Zeile, die er wie alle ihm unbekannten Felder ignoriert. `fence` fehlt ist **nicht** `fence: 0` — wer beides gleichsetzt, sperrt alte Locks aus. -**Bei Erneuerung** (`created` nachziehen, damit eine lange Arbeit nicht verfaellt) bleibt +**Bei Erneuerung** (`created` nachziehen, damit eine lange Arbeit nicht verfällt) bleibt `fence` stehen. Erneuert wird dasselbe Lease, nicht ein neues vergeben. Nur `--force` -zaehlt als neue Vergabe und bekommt eine neue Nummer — dort soll ein alter Halter ja +zählt als neue Vergabe und bekommt eine neue Nummer — dort soll ein alter Halter ja gerade auffliegen. --- ## Zwei Stufen der Geltung -**Stufe 1 — BEACHTEN (immer Pflicht, ueberall):** -Vor jeder Bearbeitung eines Projekts/Bereichs pruefen, ob fuer den betroffenen Bereich eine nicht-abgelaufene `LOCK*.txt` existiert (die projektweite `LOCK.txt` sperrt alles). Wenn ja und nicht abgelaufen → nicht anfassen (anderes Projekt waehlen oder warten). Das gilt systemweit fuer jeden Agenten in jeder Pipeline. +**Stufe 1 — BEACHTEN (immer Pflicht, überall):** +Vor jeder Bearbeitung eines Projekts/Bereichs prüfen, ob für den betroffenen Bereich eine nicht-abgelaufene `LOCK*.txt` existiert (die projektweite `LOCK.txt` sperrt alles). Wenn ja und nicht abgelaufen → nicht anfassen (anderes Projekt wählen oder warten). Das gilt systemweit für jeden Agenten in jeder Pipeline. -**Stufe 2 — ANLEGEN (Lock-by-default fuer echte Arbeit) [C 2026-06-29]:** -Lock **proaktiv VOR** Beginn einer nicht-trivialen Bearbeitung setzen — NICHT erst „falls jemand da ist". Begruendung: „noch keiner da" ist kein Freibrief, sondern der Normalfall, in dem DU der Erste bist, der sperrt; verzichten alle mit dieser Logik, arbeiten beliebig viele Agenten ungesperrt parallel. Zwei gleichzeitig startende Agenten sehen sonst beide „leer" → Kollision. Detection ersetzt den Lock NICHT (genau das loest der Lock). +**Stufe 2 — ANLEGEN (Lock-by-default für echte Arbeit) [C 2026-06-29]:** +Lock **proaktiv VOR** Beginn einer nicht-trivialen Bearbeitung setzen — NICHT erst „falls jemand da ist". Begründung: „noch keiner da" ist kein Freibrief, sondern der Normalfall, in dem DU der Erste bist, der sperrt; verzichten alle mit dieser Logik, arbeiten beliebig viele Agenten ungesperrt parallel. Zwei gleichzeitig startende Agenten sehen sonst beide „leer" → Kollision. Detection ersetzt den Lock NICHT (genau das löst der Lock). - **Pflicht in allen Dev-/Multi-Agent-Pipelines:** `.SOFTWARE`, `.RESEARCH`, `.ROBLOX`, `.AI`, `.UMBRUCH`, `.PRODUCTION` — autonome Loops und Sessions claimen ihr Projekt zu Arbeitsbeginn. - **Sensible Projekte:** Marker **`LOCK-Pflicht: ja`** auf Projektebene (`README.md`/`CLAUDE.md`/Statusdatei) → Anlegen ebenfalls Pflicht. -- **Ueberall sonst:** bei jeder nicht-trivialen Bearbeitung locken. **Ausgenommen** nur triviale/read-only/klar-persoenliche Einzeledits. -- **Harter Zusatz-Trigger:** Sobald Anzeichen anderer Aktivitaet sichtbar sind (fremder Lock, kuerzliche Fremd-Aenderungen im Working Tree, Multi-Host-Sync) → **immer** locken, auch in freiwilligen Gebieten. +- **Überall sonst:** bei jeder nicht-trivialen Bearbeitung locken. **Ausgenommen** nur triviale/read-only/klar-persönliche Einzeledits. +- **Harter Zusatz-Trigger:** Sobald Anzeichen anderer Aktivität sichtbar sind (fremder Lock, kürzliche Fremd-Änderungen im Working Tree, Multi-Host-Sync) → **immer** locken, auch in freiwilligen Gebieten. -**FALLBACK / Vorrang:** Systemweit gilt diese Spec als Default. Projektspezifische Vorgaben gehen lokal vor (speziellere Regel schlaegt allgemeinere): Ein Projekt darf strenger sein (z. B. ANLEGEN-Pflicht erklaeren) oder einen abweichenden Scope-Namen vorgeben. +**FALLBACK / Vorrang:** Systemweit gilt diese Spec als Default. Projektspezifische Vorgaben gehen lokal vor (speziellere Regel schlägt allgemeinere): Ein Projekt darf strenger sein (z. B. ANLEGEN-Pflicht erklären) oder einen abweichenden Scope-Namen vorgeben. --- @@ -492,64 +492,64 @@ Lock **proaktiv VOR** Beginn einer nicht-trivialen Bearbeitung setzen — NICHT `LOCK*.txt` ist nicht das Einzige, was eine Operation blockieren kann. Ein Git-Arbeitsbaum kann einen eigenen `pre-push`/`pre-commit`/`pre-receive`-Hook -tragen, der eine Operation rundheraus ablehnt — unabhaengig vom Lock-System und -fuer dieses unsichtbar. Das ist real vorgekommen: Ein liegengebliebener -`pre-push`-Hook aus einem laengst aufgehobenen Build-Week-Judging-Embargo +tragen, der eine Operation rundheraus ablehnt — unabhängig vom Lock-System und +für dieses unsichtbar. Das ist real vorgekommen: Ein liegengebliebener +`pre-push`-Hook aus einem längst aufgehobenen Build-Week-Judging-Embargo blockierte Pushes in einer OneDrive-geteilten `.git`-Instanz auf zwei -verschiedenen Hosts an zwei verschiedenen Tagen, waehrend +verschiedenen Hosts an zwei verschiedenen Tagen, während `lock_scan.py --check-dir` dasselbe Verzeichnis beide Male als „frei" meldete — -„frei" hiess eben immer nur „keine LOCK-Datei", nie „pushbar". - -`lock_scan.py --check-dir` meldet deshalb zusaetzlich jeden vorhandenen -`pre-push`/`pre-commit`/`pre-receive`-Hook des geprueften Verzeichnisses, -aufgeloest ueber `git rev-parse --git-path hooks` — ein `core.hooksPath`-Override -oder eine Worktree-eigene Config-Extension wird also genau so beruecksichtigt, -wie git selbst sie beruecksichtigen wuerde. Ein Hook, dessen Inhalt einer -bekannten Build-Week-Judging-Embargo-Signatur entspricht, wird ausdruecklich +„frei" hieß eben immer nur „keine LOCK-Datei", nie „pushbar". + +`lock_scan.py --check-dir` meldet deshalb zusätzlich jeden vorhandenen +`pre-push`/`pre-commit`/`pre-receive`-Hook des geprüften Verzeichnisses, +aufgelöst über `git rev-parse --git-path hooks` — ein `core.hooksPath`-Override +oder eine Worktree-eigene Config-Extension wird also genau so berücksichtigt, +wie git selbst sie berücksichtigen würde. Ein Hook, dessen Inhalt einer +bekannten Build-Week-Judging-Embargo-Signatur entspricht, wird ausdrücklich markiert (`[EMBARGO-SIGNATUR]` bzw. `"embargo": true` im JSON). Ist `core.hooksPath` konfiguriert, das Zielverzeichnis auf diesem Host aber nicht -vorhanden, fuehrt git dort stillschweigend gar keine Hooks aus — auch dieser +vorhanden, führt git dort stillschweigend gar keine Hooks aus — auch dieser strukturelle Fall wird gemeldet (`GUARD-HINWEIS:` bzw. `"hook": null`), statt -aussehen zu duerfen wie „keine Hooks konfiguriert". +aussehen zu dürfen wie „keine Hooks konfiguriert". -**Der Exit-0/1-Vertrag aendert sich dadurch NICHT.** Ein Guard-Hook wird als -Warnung neben dem Lock-Ergebnis gemeldet, nie als Sperre gewertet — ausser +**Der Exit-0/1-Vertrag ändert sich dadurch NICHT.** Ein Guard-Hook wird als +Warnung neben dem Lock-Ergebnis gemeldet, nie als Sperre gewertet — außer `--strict` ist gesetzt, dann bedeutet Exit 2: keine LOCK-Datei, aber ein -Guard-Hook vorhanden. Wer automatisiert pusht, prueft also beides: den Lock +Guard-Hook vorhanden. Wer automatisiert pusht, prüft also beides: den Lock **und** die Guard-Zeile. -**Exit 2 bedeutet zusaetzlich: PATH existiert nicht.** `Path.resolve()` -normalisiert einen nicht existierenden Pfad ohne Fehler — ohne diese Pruefung +**Exit 2 bedeutet zusätzlich: PATH existiert nicht.** `Path.resolve()` +normalisiert einen nicht existierenden Pfad ohne Fehler — ohne diese Prüfung las ein vertippter oder veralteter PATH stillschweigend als "frei". Live-Fall (T-20260922-626871087): `--check-dir .../.HACKATHONS/2026-roshambo` (ohne das Segment `.TOPICS`) meldete Exit 0, obwohl der echte Ordner ein aktives -`LOCK.user.txt` trug. Kein Zwillings-/Zwei-Baeume-Problem — die -Zwillingsaufloesung oben funktionierte bereits korrekt; der fehlende Pfad +`LOCK.user.txt` trug. Kein Zwillings-/Zwei-Bäume-Problem — die +Zwillingsauflösung oben funktionierte bereits korrekt; der fehlende Pfad wurde nur nie als Fehler erkannt. --- -## Umstrittene Locks: gleichzeitige Claims ueber einen synchronisierten Ordner +## Umstrittene Locks: gleichzeitige Claims über einen synchronisierten Ordner „Zwei Stufen der Geltung" benennt das Problem bereits: Zwei gleichzeitig -startende Agenten sehen beide einen leeren Ordner. Geloest ist bisher nur der +startende Agenten sehen beide einen leeren Ordner. Gelöst ist bisher nur der Fall, in dem einer nachweislich zuerst da war. Bei Cloud-Sync mit 30 s bis 5 min Latenz sehen **beide** einen leeren Ordner, beide legen an, und beide halten sich -fuer den Inhaber. Das ist kein Randfall: Zeitgesteuerte Automationen starten auf +für den Inhaber. Das ist kein Randfall: Zeitgesteuerte Automationen starten auf mehreren Hosts zur selben Uhrzeit. **Stufe 1 — exklusives Anlegen (immer aktiv).** `/_scripts/lock_create.py` legt die -Lockdatei exklusiv an (`open("x")`), statt erst zu pruefen und dann zu schreiben. -Zwischen Pruefung und Schreiben lag frueher ein Fenster, in dem ein zweiter -Prozess dieselbe Datei anlegt. `--force` ueberschreibt weiterhin bewusst. +Lockdatei exklusiv an (`open("x")`), statt erst zu prüfen und dann zu schreiben. +Zwischen Prüfung und Schreiben lag früher ein Fenster, in dem ein zweiter +Prozess dieselbe Datei anlegt. `--force` überschreibt weiterhin bewusst. **Stufe 2 — Contest-Verfahren (wird angewandt, wenn es sich lohnt).** ``` 1. ANLEGEN Lock exklusiv schreiben -2. QUARANTAENE warten (Default 300 s), bis der Sync beide Sichten angleicht +2. QUARANTÄNE warten (Default 300 s), bis der Sync beide Sichten angleicht 3. NACHLESEN Verzeichnis erneut lesen -4. ENTSCHEIDEN fruehestes `created` gewinnt; exakter Gleichstand -> lexikografisch +4. ENTSCHEIDEN frühestes `created` gewinnt; exakter Gleichstand -> lexikografisch kleinerer Hostname 5. VERLIERER eigenen Lock entfernen (Exit-Code 3), Bereich freigeben ``` @@ -558,8 +558,8 @@ Beide Seiten berechnen aus denselben Dateien dasselbe Ergebnis — kein Server, keine Datenbank, kein Echtzeitkanal. Das Warten **ist** der Mechanismus, keine Zierde: Ohne es liest das Nachlesen dieselbe unsynchronisierte Sicht wie zuvor. -**Wann es laeuft.** Nicht immer. Minuten Vorlauf sind fuer eine kurze Bearbeitung -teuer und fuer einen Automationslauf, der danach stundenlang arbeitet, billig. +**Wann es läuft.** Nicht immer. Minuten Vorlauf sind für eine kurze Bearbeitung +teuer und für einen Automationslauf, der danach stundenlang arbeitet, billig. Die Entscheidung trifft `should_contest()` anhand von Dauer und Umfang der geplanten Arbeit; Details und Aufrufwege stehen in der englischen Fassung (`LOCK-SYSTEM.md`, Abschnitt „Contested Locks"). @@ -568,17 +568,17 @@ geplanten Arbeit; Details und Aufrufwege stehen in der englischen Fassung ## Team-Locks: Koordination statt nur Sperre -Team-Locks sind eine **eigene Kategorie** neben Exclusive Locks. Sie dienen nicht nur dem gegenseitigen Ausschluss, sondern sind ein Koordinationsprotokoll fuer paralleles Arbeiten: Anwesenheit, Rollen, Datei-/Tool-Claims, Warteschlangen und Nachrichten. +Team-Locks sind eine **eigene Kategorie** neben Exclusive Locks. Sie dienen nicht nur dem gegenseitigen Ausschluss, sondern sind ein Koordinationsprotokoll für paralleles Arbeiten: Anwesenheit, Rollen, Datei-/Tool-Claims, Warteschlangen und Nachrichten. **Konzeptioneller Unterschied:** - **Exclusive Lock** = "Hier arbeitet jemand, alle anderen: Finger weg." -- **Team Lock** = "Hier arbeitet ein Team, das sich intern abstimmt. Fuer andere Systeme: Finger weg." +- **Team Lock** = "Hier arbeitet ein Team, das sich intern abstimmt. Für andere Systeme: Finger weg." ### Neue Konvention (ab 2026-06-19) ```text LOCK.team..txt # Team-Lock ganzes Projekt -LOCK.team...txt # Team-Lock fuer Komponente +LOCK.team...txt # Team-Lock für Komponente ``` Beispiele: @@ -588,7 +588,7 @@ LOCK.team.LAPTOP.txt # Laptop-Team arbeitet am Projekt LOCK.team.assets.ASUS-GEI.txt # ASUS-GEI-Team arbeitet an Assets ``` -### Deprecated Konvention (bleibt gueltig, nicht mehr neu anlegen) +### Deprecated Konvention (bleibt gültig, nicht mehr neu anlegen) ```text LOCK.community....txt @@ -601,25 +601,25 @@ LOCK.community.loop.codex.WORKSTATION.txt LOCK.root-docs.codex.WORKSTATION.txt ``` -**Migration:** Bestehende Community-Locks nicht sofort umbenennen (laufende Teams nicht stoeren). Bei natuerlichem Ablauf oder Neuanlage das neue `LOCK.team.*`-Format verwenden. Beide Formate werden vom Erkennungsregex und den Scripts erkannt. +**Migration:** Bestehende Community-Locks nicht sofort umbenennen (laufende Teams nicht stören). Bei natürlichem Ablauf oder Neuanlage das neue `LOCK.team.*`-Format verwenden. Beide Formate werden vom Erkennungsregex und den Scripts erkannt. ### Pflichtbereiche einer Team-Lockdatei 1. **Anwesenheitslog:** Loop-ID, Agent, Rolle, Hauptaufgabe, Startzeit. 2. **Datei-/Ordner-Claims plus Warteschlange:** Wer bearbeitet welche Datei, warum, und wer wartet danach? 3. **Tool-/Software-/MCP-Claims plus Warteschlange:** Studio-Fenster, MCP-Sessions, Browser, Renderer oder andere exklusive Tools. -4. **Nachrichten, Tipps, Lessons Learned und Anfragen:** kurze Uebergaben, Warnungen, offene Beobachtungen. +4. **Nachrichten, Tipps, Lessons Learned und Anfragen:** kurze Übergaben, Warnungen, offene Beobachtungen. ### Abstimmungsregeln - Beim Start eintragen, bevor bearbeitet wird. -- Rollen rotieren, wenn der Loop Rollenrotation verlangt; nicht stumpf dieselbe Rolle wie der direkte Vorgaenger waehlen. -- Bei belegten Ressourcen einen komplementaeren Slice waehlen. +- Rollen rotieren, wenn der Loop Rollenrotation verlangt; nicht stumpf dieselbe Rolle wie der direkte Vorgänger wählen. +- Bei belegten Ressourcen einen komplementären Slice wählen. - Nur Ressourcen claimen, die wirklich exklusiv sind; Skills, gelesene Doku und allgemeine Regeln werden nicht geclaimt. - Claims eng halten und bei Aufgabenwechseln aktualisieren. -- Wer eine Ressource in die Warteschlange setzt, hat nach Freigabe Vorrang vor spaeteren Eintraegen. +- Wer eine Ressource in die Warteschlange setzt, hat nach Freigabe Vorrang vor späteren Einträgen. - Beim Abschluss eigene Claims entfernen und Ergebnisse im passenden Log dokumentieren. -- Die gemeinsame Lockdatei nur loeschen, wenn keine andere aktive Anwesenheit mehr eingetragen ist. +- Die gemeinsame Lockdatei nur löschen, wenn keine andere aktive Anwesenheit mehr eingetragen ist. ### Herkunft und Referenzen @@ -628,13 +628,13 @@ Das Team-Lock-Konzept stammt aus der Roblox-Pipeline (parallele Codex-Loops mit - `.ROBLOX/TEAM_LOCK_VERFAHREN.md` — Roblox-Root-Destillat des Teamverfahrens. - `.ROBLOX/CODEX_AUTOMATION_PIPELINE.md` — Einbettung in die Roblox-Codex-Automationspipeline. -Neue Pipelines duerfen das Muster uebernehmen, wenn Teamarbeit mehr braucht als einen einfachen harten Projekt-Lock. +Neue Pipelines dürfen das Muster übernehmen, wenn Teamarbeit mehr braucht als einen einfachen harten Projekt-Lock. --- ## Lebenszyklus: BEACHTEN -> CLAIMEN -> FREIGEBEN -1. **BEACHTEN:** aktive Sperre fuer den Bereich pruefen (Stufe 1). +1. **BEACHTEN:** aktive Sperre für den Bereich prüfen (Stufe 1). 2. **CLAIMEN:** eigene `LOCK.txt` bzw. `LOCK..txt` **mit `lock_create.py` anlegen**: ``` @@ -643,13 +643,13 @@ Neue Pipelines duerfen das Muster uebernehmen, wenn Teamarbeit mehr braucht als ``` Das Werkzeug legt die Datei **exklusiv** an (`open("x")`, siehe Stufe 1 unter - „Umstrittene Locks"), fuellt die Pflichtfelder und vergibt die Fencing-Nummer. + „Umstrittene Locks"), füllt die Pflichtfelder und vergibt die Fencing-Nummer. **Von Hand aus `LOCK_TEMPLATE.txt` geschrieben, hat der Lock beides nicht** — kein Schutz gegen den zweiten Anleger im selben Moment und keine Nummer, an der - ein abgeloester Halter merkt, dass er die Sperre verlor. Die Vorlage bleibt die + ein abgelöster Halter merkt, dass er die Sperre verlor. Die Vorlage bleibt die Feldreferenz; sie ist kein Anlegeverfahren. [C 2026-09-20 T-20260920-699139879] -3. **FREIGEBEN:** die selbst angelegte Lock-Datei am Ende **selbst loeschen**. Aktives Freigeben durch den Ersteller ist Pflicht; der 24h-Verfall ist nur ein **Sicherheitsnetz** fuer vergessene Locks. Bei langer Laufzeit `created` erneuern, damit die Sperre nicht vorzeitig verfaellt. +3. **FREIGEBEN:** die selbst angelegte Lock-Datei am Ende **selbst löschen**. Aktives Freigeben durch den Ersteller ist Pflicht; der 24h-Verfall ist nur ein **Sicherheitsnetz** für vergessene Locks. Bei langer Laufzeit `created` erneuern, damit die Sperre nicht vorzeitig verfällt. --- @@ -662,59 +662,59 @@ Zentrale Scripts liegen in `C:\Users\User\OneDrive\_scripts\`. Unter Windows imm PYTHONIOENCODING=utf-8 python C:\Users\User\OneDrive\_scripts\lock_scan.py PYTHONIOENCODING=utf-8 python C:\Users\User\OneDrive\_scripts\lock_scan.py --json ``` -Listet Pfad, Scope, Owner, created und Restzeit aller nicht-abgelaufenen Locks ueber alle Roots aus `lock_roots.json`. +Listet Pfad, Scope, Owner, created und Restzeit aller nicht-abgelaufenen Locks über alle Roots aus `lock_roots.json`. -### Roots pflegen: haeufig frequentierte Ordner gehoeren IMMER hinein [U 2026-07-25] +### Roots pflegen: häufig frequentierte Ordner gehören IMMER hinein [U 2026-07-25] Ein Lock wirkt nur, wo auch gescannt wird. **Jeder Ordner, in dem mehrere Agenten, Automationen -oder Hosts regelmaessig arbeiten, muss in `lock_roots.json` stehen — auch ausserhalb von OneDrive.** -Wird ein Arbeitsbereich neu eingefuehrt oder verlagert, ist die Root-Liste im selben Arbeitsgang +oder Hosts regelmäßig arbeiten, muss in `lock_roots.json` stehen — auch außerhalb von OneDrive.** +Wird ein Arbeitsbereich neu eingeführt oder verlagert, ist die Root-Liste im selben Arbeitsgang nachzuziehen. **Lehrfall 2026-07-25:** Plan D verlagerte die Entwicklung aus OneDrive in Git-Klone unter -`C:\_Local_DEV\repos\`; die Lock-Roots blieben bei OneDrive. Folge: Zwei After-Care-Laeufe +`C:\_Local_DEV\repos\`; die Lock-Roots blieben bei OneDrive. Folge: Zwei After-Care-Läufe bearbeiteten dasselbe Repo im Abstand von 17 Minuten. Einer der beiden **hatte** sogar gelockt — -der Lock war nur fuer Scan und Watcher unsichtbar. Es fehlte also kein Mechanismus, sondern seine -Reichweite. Schlimmer noch: Die Luecke provozierte einen **Ersatzmechanismus** (ein eigener -`IN_PROGRESS`-Status in einer Rotations-Registry), der wieder zurueckgebaut werden musste. +der Lock war nur für Scan und Watcher unsichtbar. Es fehlte also kein Mechanismus, sondern seine +Reichweite. Schlimmer noch: Die Lücke provozierte einen **Ersatzmechanismus** (ein eigener +`IN_PROGRESS`-Status in einer Rotations-Registry), der wieder zurückgebaut werden musste. **Merksatz:** Wo gearbeitet wird, muss gescannt werden. Sonst entstehen unsichtbare Locks — und aus unsichtbaren Locks entstehen Parallelsysteme. -`C:\_Local_DEV\repos` ist seit 2026-07-25 als `shallow`-Root eingetragen (Locks gehoeren an den +`C:\_Local_DEV\repos` ist seit 2026-07-25 als `shallow`-Root eingetragen (Locks gehören an den Repo-Root, tiefer muss nicht gescannt werden). -### Ein frischer Klon sieht `LOCK.user.*` NICHT — immer den OneDrive-Zwilling mitpruefen [C 2026-09-13] +### Ein frischer Klon sieht `LOCK.user.*` NICHT — immer den OneDrive-Zwilling mitprüfen [C 2026-09-13] -Die Zwei-Baeume-Regel sagt, ein Lock am Klon `C:\_Local_DEV\repos\` **oder** am -OneDrive-Pfad binde gleichermassen. Daraus folgt eine Falle, die nicht offensichtlich ist: +Die Zwei-Bäume-Regel sagt, ein Lock am Klon `C:\_Local_DEV\repos\` **oder** am +OneDrive-Pfad binde gleichermaßen. Daraus folgt eine Falle, die nicht offensichtlich ist: **`LOCK.user.*` ist absichtlich nicht versioniert** (die Lockdatei sagt es selbst: "nicht committen"; `git ls-files` zeigt keinen Treffer). Ein **frisch geklontes** Repo kann den User-Lock deshalb gar nicht enthalten — dort steht nicht "kein Lock", dort steht **nichts**. -Wer nur den Klon prueft, liest aus einer leeren Stelle einen Zustand. +Wer nur den Klon prüft, liest aus einer leeren Stelle einen Zustand. **Belegfall 2026-09-10 (T-20260913-785936980):** WORKSTATION-LG hatte beide Klone frisch aus dem Remote geholt und notierte im Ticketverlauf "Der nutzergehaltene SentinelFleet-Lock ist -entfernt". Der Lock vom 27.08. lag unveraendert am OneDrive-Pfad; das Judging lief noch +entfernt". Der Lock vom 27.08. lag unverändert am OneDrive-Pfad; das Judging lief noch (Devpost "Winners announced soon"). Das Schutzgut blieb unversehrt, aber die Zeitklausel des -Judging-Holds wurde irrtuemlich ueberschritten. Es war kein Regelverstoss, sondern eine -Messluecke — und sie trifft **jeden** Host, der frisch klont. +Judging-Holds wurde irrtümlich überschritten. Es war kein Regelverstoß, sondern eine +Messlücke — und sie trifft **jeden** Host, der frisch klont. -**Seit 2026-09-13 loest `lock_scan.py --check-dir` den Zwilling selbst auf** und meldet dessen +**Seit 2026-09-13 löst `lock_scan.py --check-dir` den Zwilling selbst auf** und meldet dessen Locks mit (`(aus dem Zwilling im anderen Baum)`). Die Beziehung kommt aus der bereits vorhandenen, deklarierten `REPO.pointer.json` — kein Namensraten: | Richtung | Weg | |---|---| -| OneDrive -> Klon | `REPO.pointer.json` liegt im geprueften Verzeichnis (`local_locator.windows_default`) | -| Klon -> OneDrive | `TWIN-INDEX.json`, den `lock_scan.py --write-cache` aus denselben Verzeichnissen mitschreibt, die es ohnehin durchlaeuft | +| OneDrive -> Klon | `REPO.pointer.json` liegt im geprüften Verzeichnis (`local_locator.windows_default`) | +| Klon -> OneDrive | `TWIN-INDEX.json`, den `lock_scan.py --write-cache` aus denselben Verzeichnissen mitschreibt, die es ohnehin durchläuft | Konfiguriert wird das im Block `twin_resolution` in `lock_roots.json` (`clone_roots` + -`index_path`). **Fail-closed:** Liegt der gepruefte Pfad unter einem `clone_root` und der Index -fehlt, ist unlesbar, oder traegt der Zwilling eine kaputte `REPO.pointer.json`, meldet +`index_path`). **Fail-closed:** Liegt der geprüfte Pfad unter einem `clone_root` und der Index +fehlt, ist unlesbar, oder trägt der Zwilling eine kaputte `REPO.pointer.json`, meldet `--check-dir` **UNBESTIMMT mit Exit 1** — nicht "frei". Fehlt der Block `twin_resolution` ganz -(anderer Host, kein Spiegel), ist die Zwillingspruefung "nicht anwendbar" und aendert nichts. +(anderer Host, kein Spiegel), ist die Zwillingsprüfung "nicht anwendbar" und ändert nichts. **Nach einem Umzug, einem neuen Klon oder einem neuen Zwilling** den Index auffrischen: `lock_scan.py --write-cache`. Steht ein Repo nicht im Index, kennt `--check-dir` seinen @@ -723,30 +723,30 @@ Zwilling nicht. **Merksatz:** Eine leere Stelle ist kein Zustand. Bei einem frisch geklonten Repo beweist "keine LOCK-Datei" gar nichts — erst der Blick auf den Zwilling tut das. -**Abgelaufene Locks aufraeumen:** +**Abgelaufene Locks aufräumen:** ``` -# Erst trocken pruefen (loescht nichts): +# Erst trocken prüfen (löscht nichts): PYTHONIOENCODING=utf-8 python C:\Users\User\OneDrive\_scripts\prune_stale_locks.py --dry-run -# Tatsaechlich entfernen: +# Tatsächlich entfernen: PYTHONIOENCODING=utf-8 python C:\Users\User\OneDrive\_scripts\prune_stale_locks.py ``` Entfernt `LOCK*.txt` mit `now > created + expires_after`. Legacy `TEST.txt`/`TESTS.txt` bleiben unangetastet. Eigene Roots-Datei: `--roots-file `. -**Stale-Cleanup und Host-Erreichbarkeit (vorbereitet):** Abgelaufene Locks eines NICHT erreichbaren Hosts (z. B. Laptop offline, anderer Rechner heruntergefahren) koennen sicher freigegeben werden, sobald ihre Zeit abgelaufen ist — der normale Verfall greift bereits. Kuenftig kann der Cleanup optional die Host-Erreichbarkeit via Tailscale-Ping pruefen (`host_is_reachable(host)` → `True/False/None`), um auch noch nicht abgelaufene Locks eines dauerhaft unerreichbaren Systems frueher aufzuraeumen. Der Hook ist in `prune_stale_locks.py` vorbereitet (Stub-Funktion), aber noch nicht aktiv — bis dahin gilt: Locks laufen nach `expires_after` ab, egal ob der Halter erreichbar ist. +**Stale-Cleanup und Host-Erreichbarkeit (vorbereitet):** Abgelaufene Locks eines NICHT erreichbaren Hosts (z. B. Laptop offline, anderer Rechner heruntergefahren) können sicher freigegeben werden, sobald ihre Zeit abgelaufen ist — der normale Verfall greift bereits. Künftig kann der Cleanup optional die Host-Erreichbarkeit via Tailscale-Ping prüfen (`host_is_reachable(host)` → `True/False/None`), um auch noch nicht abgelaufene Locks eines dauerhaft unerreichbaren Systems früher aufzuräumen. Der Hook ist in `prune_stale_locks.py` vorbereitet (Stub-Funktion), aber noch nicht aktiv — bis dahin gilt: Locks laufen nach `expires_after` ab, egal ob der Halter erreichbar ist. **Schnell-Index (LOCK-CACHE.md) aktualisieren:** ``` PYTHONIOENCODING=utf-8 python C:\Users\User\OneDrive\_scripts\lock_scan.py --write-cache ``` -Schreibt zwei auto-generierte Caches: systemweit `_scripts/LOCK-CACHE.md` (alle Roots) und nur Software `.SOFTWARE/LOCK-CACHE.md` (Praefix-Filter auf `.SOFTWARE`). Beide sind abgeleitete Artefakte (nicht manuell editieren, in `.gitignore`); authoritativ bleiben die `LOCK*.txt` selbst. +Schreibt zwei auto-generierte Caches: systemweit `_scripts/LOCK-CACHE.md` (alle Roots) und nur Software `.SOFTWARE/LOCK-CACHE.md` (Präfix-Filter auf `.SOFTWARE`). Beide sind abgeleitete Artefakte (nicht manuell editieren, in `.gitignore`); authoritativ bleiben die `LOCK*.txt` selbst. -**Scan-Begrenzung (Performance):** `lock_roots.json` steuert `default_max_depth` (Default 4), `shallow_depth` (Default 2, fuer Roots mit `"shallow": true` wie `.WISSEN`) und `skip_dirs` (uebersprungene Verzeichnisse inkl. Unterbaum, z. B. `node_modules`, `.venv`, `.git`, `build`, `releases`). +**Scan-Begrenzung (Performance):** `lock_roots.json` steuert `default_max_depth` (Default 4), `shallow_depth` (Default 2, für Roots mit `"shallow": true` wie `.WISSEN`) und `skip_dirs` (übersprungene Verzeichnisse inkl. Unterbaum, z. B. `node_modules`, `.venv`, `.git`, `build`, `releases`). --- ## Bibliothek -`_scripts/lock_utils.py` ist die kanonische Format-/Scope-/Verfall-Bibliothek (Single Source of Truth). Pipeline-lokale Kopien (z. B. `.SOFTWARE/_tools/lock_utils.py`) sind nur duenne Shims, die hier re-exportieren — kein Zweitstandard. +`_scripts/lock_utils.py` ist die kanonische Format-/Scope-/Verfall-Bibliothek (Single Source of Truth). Pipeline-lokale Kopien (z. B. `.SOFTWARE/_tools/lock_utils.py`) sind nur dünne Shims, die hier re-exportieren — kein Zweitstandard. ---