Teilpaket von #140, D32 Teil 1 (Betreiber, 2026-09-30). Teil 2, die Prüfung der Ordnerlänge in Preflight und doctor, liegt in #151.
Erledigt in 8.0.0-beta.12 (04aebde, Umsetzung) und 8.0.0-beta.13 (5a73172, Nachtrag raw/CONTRACT.md aus dem Close-out). Verifiziert: voller pytest-Lauf (1781 bestanden, 3 übersprungen), docs verify, instructions verify, Demo-Korpus ohne long_paths-Befund; CI grün auf beiden Commits (Runs 464, 465, 466, 467).
Warum
Auf dem Windows-Zielsystem sind lange Pfade ausgeschaltet (#140 T8: LongPathsEnabled = 0, core.longpaths nicht gesetzt). Einschalten braucht Adminrechte, die die Zielgruppe nicht hat. Damit gilt MAX_PATH: 259 Zeichen, einschließlich des Installationsordners.
Die Titelregel aus #155 kannte keine Länge. Ein Titel, der unter Linux problemlos entstand, konnte deshalb einen Windows-Checkout derselben Instanz brechen: git checkout oder Python scheiterten an der Datei. Das ist dieselbe Fehlerklasse wie ein verbotenes Zeichen, nur über die Länge.
längster Pfad im venv: 156 Zeichen ab Checkout-Wurzel (tools/.venv/…/icalendar/tests/__pycache__/…pyc)
Das Budget von 160 Zeichen liegt also über dem Bestand und in der Größenordnung des venv. Zusammen mit der Ordnergrenze von 95 Zeichen aus #151 bleibt der Checkout unter 259.
Regel (D32)
Pfadbudget: Der Pfad einer Datei relativ zur Instanzwurzel, mit / geschrieben, ist höchstens 160 lang.
Gezählt wird in UTF-16-Codeeinheiten, so wie Windows MAX_PATH zählt: len(p.encode("utf-16-le")) // 2. Ein Zeichen außerhalb der BMP, etwa ein Emoji, zählt also doppelt.
Die Zahl steht einmal im Code, als Konstante PATH_BUDGET in tools/chemenu/titles.py neben der übrigen Titelregel. Normativ steht die Regel einmal, in kb/CONTRACT.md § Titles are identifiers; raw/CONTRACT.md und instructions/page-lifecycle.md verweisen nur darauf.
Was gebaut wurde
titles.py:PATH_BUDGET = 160, path_length() und die reine Funktion path_budget_problem(rel_path) -> str | None. Die Meldung nennt Länge, Budget und die Anzahl Zeichen, um die der Pfad kürzer werden muss. In commands/_util.py misst path_budget_problem_for() einen absoluten Pfad, und check_path_budget() verweigert mit Exit 1 und einer befehlsspezifischen Abhilfe.
Verweigern vor jedem Schreibzugriff, mit Exit 1:
new, für jede Wurzel, vor check_target_free (und damit auch vor dem Tracker-Schreibzugriff bei new project).
rename im Umbenennungsmodus, auch bei --dry-run. --from wird nicht geprüft, damit rename die Abhilfe für eine zu lange Seite bleibt.
move: Einzelpfad verweigert; --reconcile überspringt ein solches Ziel und nennt es neben den belegten Zielen.
raw accept: jedes Ziel der Verschiebeliste, also auch eine beim Bündeln eingefaltete Bestandsdatei. Abhilfe: die Datei in incoming/ umbenennen. --replaces schreibt an einen bestehenden Ort und bleibt ungeprüft.
lint: neuer Befund long_paths („Long Paths“) mit {path, length} über kb/ und raw/. Advisory, nicht in HARD_ERROR_KEYS: Ein Bestand über dem Budget bricht keinen Lint-Lauf und braucht keine Migration. Der Bericht nennt rename als Abhilfe.
Records und Doku: Records von new, rename, move, raw accept und lint ergänzt, tools/CONTRACT.md neu erzeugt; kb/CONTRACT.md § Titles are identifiers mit dem Budget; instructions/page-lifecycle.md Rename-Absatz; README.md § Naming; raw/CONTRACT.md § Getting a file in (Nachtrag im Close-out).
Version: Bump im 8.0.0-Kandidaten (beta.12), --breaking-Text um das Pfadbudget ergänzt; Migration weiterhin keine. Der Nachtrag lief als eigener Patch-Bump (beta.13).
Akzeptanzkriterien
new mit einem Titel, dessen Zielpfad 161 Einheiten lang ist, endet mit Exit 1, nennt Länge und Budget, und es entsteht keine Datei. Bei 160 Einheiten geht es.
Ein Titel mit einem Emoji wird in UTF-16-Einheiten gezählt (Test an der Grenze).
rename --to auf einen zu langen Pfad wird verweigert, auch mit --dry-run. rename --from <zu lang> --to <kurz> geht.
move auf einen zu langen Zielpfad wird verweigert; move --reconcile überspringt ihn und nennt ihn.
raw accept einer Datei, deren Ziel unter raw/ zu lang wäre, endet mit Exit 1, und incoming/ sowie raw/ sind danach unverändert (auch mit --dry-run).
lint meldet eine eingeschleuste Seite über dem Budget unter long_paths; lint --fail-on-error endet trotzdem mit 0.
Der Demo-Korpus hat keinen long_paths-Befund.
kb/CONTRACT.md nennt das Budget; tools/CONTRACT.md ist neu erzeugt; pytest, docs verify und instructions verify sind grün.
Abhängigkeiten
Keine. #151 prüft die andere Hälfte (Ordnerlänge ≤ 95). Beide Zahlen gehören zusammen: Wer eine ändert, prüft die andere – der Kommentar an PATH_BUDGET sagt das auch im Code.
Teilpaket von #140, **D32 Teil 1** (Betreiber, 2026-09-30). Teil 2, die Prüfung der Ordnerlänge in Preflight und `doctor`, liegt in #151.
**Erledigt** in 8.0.0-beta.12 (`04aebde`, Umsetzung) und 8.0.0-beta.13 (`5a73172`, Nachtrag `raw/CONTRACT.md` aus dem Close-out). Verifiziert: voller `pytest`-Lauf (1781 bestanden, 3 übersprungen), `docs verify`, `instructions verify`, Demo-Korpus ohne `long_paths`-Befund; CI grün auf beiden Commits (Runs 464, 465, 466, 467).
## Warum
Auf dem Windows-Zielsystem sind lange Pfade ausgeschaltet (#140 T8: `LongPathsEnabled = 0`, `core.longpaths` nicht gesetzt). Einschalten braucht Adminrechte, die die Zielgruppe nicht hat. Damit gilt MAX_PATH: **259 Zeichen, einschließlich des Installationsordners.**
Die Titelregel aus #155 kannte keine Länge. Ein Titel, der unter Linux problemlos entstand, konnte deshalb einen Windows-Checkout derselben Instanz brechen: `git checkout` oder Python scheiterten an der Datei. Das ist dieselbe Fehlerklasse wie ein verbotenes Zeichen, nur über die Länge.
Gemessen im Repo (2026-09-30):
- längster Pfad unter `kb/`: 133 Zeichen (`kb/sources/transcripts/Source - Conversation - … Session 2026-08-31.md`)
- längster Pfad unter `raw/`: 122 Zeichen
- längster Pfad im venv: 156 Zeichen ab Checkout-Wurzel (`tools/.venv/…/icalendar/tests/__pycache__/…pyc`)
Das Budget von 160 Zeichen liegt also über dem Bestand und in der Größenordnung des venv. Zusammen mit der Ordnergrenze von 95 Zeichen aus #151 bleibt der Checkout unter 259.
## Regel (D32)
- **Pfadbudget:** Der Pfad einer Datei relativ zur Instanzwurzel, mit `/` geschrieben, ist höchstens **160** lang.
- **Gezählt wird in UTF-16-Codeeinheiten,** so wie Windows MAX_PATH zählt: `len(p.encode("utf-16-le")) // 2`. Ein Zeichen außerhalb der BMP, etwa ein Emoji, zählt also doppelt.
- **Die Zahl steht einmal im Code,** als Konstante `PATH_BUDGET` in `tools/chemenu/titles.py` neben der übrigen Titelregel. Normativ steht die Regel einmal, in `kb/CONTRACT.md` § Titles are identifiers; `raw/CONTRACT.md` und `instructions/page-lifecycle.md` verweisen nur darauf.
## Was gebaut wurde
1. **`titles.py`:** `PATH_BUDGET = 160`, `path_length()` und die reine Funktion `path_budget_problem(rel_path) -> str | None`. Die Meldung nennt Länge, Budget und die Anzahl Zeichen, um die der Pfad kürzer werden muss. In `commands/_util.py` misst `path_budget_problem_for()` einen absoluten Pfad, und `check_path_budget()` verweigert mit Exit 1 und einer befehlsspezifischen Abhilfe.
2. **Verweigern vor jedem Schreibzugriff**, mit Exit 1:
- `new`, für jede Wurzel, vor `check_target_free` (und damit auch vor dem Tracker-Schreibzugriff bei `new project`).
- `rename` im Umbenennungsmodus, auch bei `--dry-run`. **`--from` wird nicht geprüft,** damit `rename` die Abhilfe für eine zu lange Seite bleibt.
- `move`: Einzelpfad verweigert; `--reconcile` überspringt ein solches Ziel und nennt es neben den belegten Zielen.
- `raw accept`: jedes Ziel der Verschiebeliste, also auch eine beim Bündeln eingefaltete Bestandsdatei. Abhilfe: die Datei in `incoming/` umbenennen. `--replaces` schreibt an einen bestehenden Ort und bleibt ungeprüft.
3. **`lint`:** neuer Befund `long_paths` („Long Paths“) mit `{path, length}` über `kb/` und `raw/`. **Advisory, nicht in `HARD_ERROR_KEYS`:** Ein Bestand über dem Budget bricht keinen Lint-Lauf und braucht keine Migration. Der Bericht nennt `rename` als Abhilfe.
4. **Records und Doku:** Records von `new`, `rename`, `move`, `raw accept` und `lint` ergänzt, `tools/CONTRACT.md` neu erzeugt; `kb/CONTRACT.md` § Titles are identifiers mit dem Budget; `instructions/page-lifecycle.md` Rename-Absatz; `README.md` § Naming; `raw/CONTRACT.md` § Getting a file in (Nachtrag im Close-out).
5. **Version:** Bump im 8.0.0-Kandidaten (beta.12), `--breaking`-Text um das Pfadbudget ergänzt; Migration weiterhin keine. Der Nachtrag lief als eigener Patch-Bump (beta.13).
## Akzeptanzkriterien
- [x] `new` mit einem Titel, dessen Zielpfad 161 Einheiten lang ist, endet mit Exit 1, nennt Länge und Budget, und es entsteht keine Datei. Bei 160 Einheiten geht es.
- [x] Ein Titel mit einem Emoji wird in UTF-16-Einheiten gezählt (Test an der Grenze).
- [x] `rename --to` auf einen zu langen Pfad wird verweigert, auch mit `--dry-run`. `rename --from <zu lang> --to <kurz>` geht.
- [x] `move` auf einen zu langen Zielpfad wird verweigert; `move --reconcile` überspringt ihn und nennt ihn.
- [x] `raw accept` einer Datei, deren Ziel unter `raw/` zu lang wäre, endet mit Exit 1, und `incoming/` sowie `raw/` sind danach unverändert (auch mit `--dry-run`).
- [x] `lint` meldet eine eingeschleuste Seite über dem Budget unter `long_paths`; `lint --fail-on-error` endet trotzdem mit 0.
- [x] Der Demo-Korpus hat keinen `long_paths`-Befund.
- [x] `kb/CONTRACT.md` nennt das Budget; `tools/CONTRACT.md` ist neu erzeugt; `pytest`, `docs verify` und `instructions verify` sind grün.
## Abhängigkeiten
Keine. #151 prüft die andere Hälfte (Ordnerlänge ≤ 95). Beide Zahlen gehören zusammen: Wer eine ändert, prüft die andere – der Kommentar an `PATH_BUDGET` sagt das auch im Code.
Body auf den Endstand gebracht: alle Akzeptanzkriterien abgehakt, Stand 8.0.0-beta.12 (Pfadbudget in new/rename/move/raw accept, lint Long Paths advisory). README § Naming ergänzt.
Body auf den Endstand gebracht: alle Akzeptanzkriterien abgehakt, Stand 8.0.0-beta.12 (Pfadbudget in `new`/`rename`/`move`/`raw accept`, `lint` Long Paths advisory). README § Naming ergänzt.
Close-out: Body auf Endstand („Umfang“ → „Was gebaut wurde“, Verifikation mit CI-Runs 464–467 benannt). Nachtrag raw/CONTRACT.md § Getting a file in als 8.0.0-beta.13 (5a73172). CI grün auf beiden Commits – geschlossen.
Close-out: Body auf Endstand („Umfang“ → „Was gebaut wurde“, Verifikation mit CI-Runs 464–467 benannt). Nachtrag `raw/CONTRACT.md` § Getting a file in als 8.0.0-beta.13 (`5a73172`). CI grün auf beiden Commits – geschlossen.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Teilpaket von #140, D32 Teil 1 (Betreiber, 2026-09-30). Teil 2, die Prüfung der Ordnerlänge in Preflight und
doctor, liegt in #151.Erledigt in 8.0.0-beta.12 (
04aebde, Umsetzung) und 8.0.0-beta.13 (5a73172, Nachtragraw/CONTRACT.mdaus dem Close-out). Verifiziert: vollerpytest-Lauf (1781 bestanden, 3 übersprungen),docs verify,instructions verify, Demo-Korpus ohnelong_paths-Befund; CI grün auf beiden Commits (Runs 464, 465, 466, 467).Warum
Auf dem Windows-Zielsystem sind lange Pfade ausgeschaltet (#140 T8:
LongPathsEnabled = 0,core.longpathsnicht gesetzt). Einschalten braucht Adminrechte, die die Zielgruppe nicht hat. Damit gilt MAX_PATH: 259 Zeichen, einschließlich des Installationsordners.Die Titelregel aus #155 kannte keine Länge. Ein Titel, der unter Linux problemlos entstand, konnte deshalb einen Windows-Checkout derselben Instanz brechen:
git checkoutoder Python scheiterten an der Datei. Das ist dieselbe Fehlerklasse wie ein verbotenes Zeichen, nur über die Länge.Gemessen im Repo (2026-09-30):
kb/: 133 Zeichen (kb/sources/transcripts/Source - Conversation - … Session 2026-08-31.md)raw/: 122 Zeichentools/.venv/…/icalendar/tests/__pycache__/…pyc)Das Budget von 160 Zeichen liegt also über dem Bestand und in der Größenordnung des venv. Zusammen mit der Ordnergrenze von 95 Zeichen aus #151 bleibt der Checkout unter 259.
Regel (D32)
/geschrieben, ist höchstens 160 lang.len(p.encode("utf-16-le")) // 2. Ein Zeichen außerhalb der BMP, etwa ein Emoji, zählt also doppelt.PATH_BUDGETintools/chemenu/titles.pyneben der übrigen Titelregel. Normativ steht die Regel einmal, inkb/CONTRACT.md§ Titles are identifiers;raw/CONTRACT.mdundinstructions/page-lifecycle.mdverweisen nur darauf.Was gebaut wurde
titles.py:PATH_BUDGET = 160,path_length()und die reine Funktionpath_budget_problem(rel_path) -> str | None. Die Meldung nennt Länge, Budget und die Anzahl Zeichen, um die der Pfad kürzer werden muss. Incommands/_util.pymisstpath_budget_problem_for()einen absoluten Pfad, undcheck_path_budget()verweigert mit Exit 1 und einer befehlsspezifischen Abhilfe.new, für jede Wurzel, vorcheck_target_free(und damit auch vor dem Tracker-Schreibzugriff beinew project).renameim Umbenennungsmodus, auch bei--dry-run.--fromwird nicht geprüft, damitrenamedie Abhilfe für eine zu lange Seite bleibt.move: Einzelpfad verweigert;--reconcileüberspringt ein solches Ziel und nennt es neben den belegten Zielen.raw accept: jedes Ziel der Verschiebeliste, also auch eine beim Bündeln eingefaltete Bestandsdatei. Abhilfe: die Datei inincoming/umbenennen.--replacesschreibt an einen bestehenden Ort und bleibt ungeprüft.lint: neuer Befundlong_paths(„Long Paths“) mit{path, length}überkb/undraw/. Advisory, nicht inHARD_ERROR_KEYS: Ein Bestand über dem Budget bricht keinen Lint-Lauf und braucht keine Migration. Der Bericht nenntrenameals Abhilfe.new,rename,move,raw acceptundlintergänzt,tools/CONTRACT.mdneu erzeugt;kb/CONTRACT.md§ Titles are identifiers mit dem Budget;instructions/page-lifecycle.mdRename-Absatz;README.md§ Naming;raw/CONTRACT.md§ Getting a file in (Nachtrag im Close-out).--breaking-Text um das Pfadbudget ergänzt; Migration weiterhin keine. Der Nachtrag lief als eigener Patch-Bump (beta.13).Akzeptanzkriterien
newmit einem Titel, dessen Zielpfad 161 Einheiten lang ist, endet mit Exit 1, nennt Länge und Budget, und es entsteht keine Datei. Bei 160 Einheiten geht es.rename --toauf einen zu langen Pfad wird verweigert, auch mit--dry-run.rename --from <zu lang> --to <kurz>geht.moveauf einen zu langen Zielpfad wird verweigert;move --reconcileüberspringt ihn und nennt ihn.raw accepteiner Datei, deren Ziel unterraw/zu lang wäre, endet mit Exit 1, undincoming/sowieraw/sind danach unverändert (auch mit--dry-run).lintmeldet eine eingeschleuste Seite über dem Budget unterlong_paths;lint --fail-on-errorendet trotzdem mit 0.long_paths-Befund.kb/CONTRACT.mdnennt das Budget;tools/CONTRACT.mdist neu erzeugt;pytest,docs verifyundinstructions verifysind grün.Abhängigkeiten
Keine. #151 prüft die andere Hälfte (Ordnerlänge ≤ 95). Beide Zahlen gehören zusammen: Wer eine ändert, prüft die andere – der Kommentar an
PATH_BUDGETsagt das auch im Code.Body auf den Endstand gebracht: alle Akzeptanzkriterien abgehakt, Stand 8.0.0-beta.12 (Pfadbudget in
new/rename/move/raw accept,lintLong Paths advisory). README § Naming ergänzt.Close-out: Body auf Endstand („Umfang“ → „Was gebaut wurde“, Verifikation mit CI-Runs 464–467 benannt). Nachtrag
raw/CONTRACT.md§ Getting a file in als 8.0.0-beta.13 (5a73172). CI grün auf beiden Commits – geschlossen.