Pfadbudget: Seiten- und raw-Pfade bleiben unter 160 Zeichen, damit ein Windows-Checkout ohne lange Pfade funktioniert #163

Closed
opened 2026-09-30 20:49:34 +00:00 by torben · 2 comments
Owner

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

  • 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.
torben added the prio/plannedsize/Sarea/kbkind/build labels 2026-09-30 20:49:34 +00:00
torben added size/M and removed size/S labels 2026-09-30 20:50:43 +00:00
Author
Owner

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.
Author
Owner

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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#163