incoming/: die Rohablage wird abgeleitet statt von Hand einsortiert #58

Closed
opened 2026-09-04 19:26:32 +00:00 by torben · 2 comments
Owner

Umgesetzt und ausgeliefert in 4.8.0-beta.3, Commit 36d2128. Aufgeworfen und entschieden in den Sitzungen vom 2026-09-04, gebaut am 2026-09-05.

Das Problem, das es gab

raw/CONTRACT.mds Routing-Tabelle war eine Regel für Menschen: wer eine Datei ablegte, wählte articles//documents//notes//assets/ selbst. Zwei Folgen:

  • Es skalierte nicht. Vier flache Verzeichnisse für alles, was je eingelesen wird. Der Stack hatte sonst nirgends eine Ablageentscheidung, die ein Mensch trifft — „nie ein Verzeichnis von Hand wählen" (AGENTS.md § Routing) galt für kb/, raw/ war ausgenommen, ohne dass das je jemand entschieden hätte.
  • Zusammengehörige Dateien waren nicht als solche erkennbar. Beim Muster „PDF hochladen, nach Markdown konvertieren" entstehen zwei Dateien für eine logische Quelle; dass sie zusammengehören, stand ausschließlich in raw_files: der Source-Seite.

Nicht das Problem war die Tiefe selbst: config.iter_raw_files macht rglob("*"), provenance.py und sources coverage arbeiten auf repo-relativen Pfaden — raw/documents/handbuch/original.pdf funktionierte schon vorher ohne Codeänderung. Flach waren nur die Contract-Tabelle und dist_cmd.RAW_SUBDIRS. Es fehlte kein Mechanismus, es fehlte eine Zuständigkeit.

Ebenfalls nicht das Problem war der Bestand: die 29 Rohdateien liegen flach innerhalb ihrer Unterverzeichnisse (articles/ 3, documents/ 1, notes/ 25; assets/ leer und mangels Datei nicht getrackt). Unter Entscheidung 3 sind das eindateiige Quellen in vorschriftsmäßiger Form. Es wurde nichts umsortiert.

Der Weg, wie er jetzt läuft

Mehrdateiige Quelle:

incoming/documents/handbuch.pdf              Ablage deklariert den Typ
   ↓  Ingest-Sitzung konvertiert
incoming/documents/handbuch.pdf + handbuch.md
   ↓  wikitool raw accept incoming/documents/handbuch.pdf incoming/documents/handbuch.md
raw/documents/handbuch/handbuch.pdf          zwei Dateien → Bundle-Verzeichnis
raw/documents/handbuch/handbuch.md
   ↓  wikitool new source --set raw_files=…

Eindateiige Quelle:

incoming/notes/besprechung.md
   ↓  wikitool raw accept incoming/notes/besprechung.md
raw/notes/besprechung.md                     eine Datei → kein Verzeichnis

Die Entscheidungen

1. Der Eingang ist ein Top-Level-incoming/ im Repo, gitignoriert. Nicht außerhalb, nicht unter raw/.

Unter raw/ war es unmöglich: .gitignore endet mit dem Backstop !raw/**, und docs_verify.check_ignored_content schlägt auf jede ignorierte Datei unter raw|kb|work fehl — ein raw/incoming/ wäre entweder committed oder docs verify rot. Gegen „außerhalb des Repos" entschied der Nachweis: „wird nie committed" ist über docs_verify.REQUIRED_IGNORE_CANARIES mechanisch belegbar (git check-ignore --no-index beantwortet die Frage über den Regelsatz, ohne dass die Datei existieren muss); für einen Pfad außerhalb des Baums existiert dieses Beweismittel nicht, und er bräuchte zusätzlich eine Konfigurationsfläche, die es nirgends gibt.

Gilt auch für #32. Dessen schärfere Anforderung („eine Quarantäne, die kein Kommando der normalen Pipeline liest") trägt nicht der Ort, sondern die Grenze zwischen Sitzung und Pipeline-Kommando — siehe § Verhältnis zu #32.

2. Der Typ wird über Unterverzeichnisse im Eingang deklariert, nicht über ein Flag. incoming/ spiegelt RAW_SUBDIRS. Kein --type-Argument.

Keine Rückkehr zur Handsortierung, weil zwei Entscheidungen auseinanderfallen: was für ein Dokument das ist (eine Klassifikation, die kein Werkzeug ableiten kann — eine .md kann notes/ oder articles/ sein) und wo die Datei am Ende liegt (Unterverzeichnis, Bundle ja/nein, Bundle-Name, die Bewegung). Der Mensch liefert nur die erste, aus einem Vierer-Vokabular. Drei Vorteile gegenüber dem Flag: die Erklärung wird abgegeben, wenn der Mensch das Dokument in der Hand hat, und überlebt Sitzungswechsel; ein Argument weniger ist ein Fehlweg weniger; eine Datei direkt in incoming/ kann abgelehnt werden, während ein Flag immer gesetzt ist, richtig oder falsch.

3. Ein Bundle-Verzeichnis entsteht nur bei Bedarf — ab der zweiten Datei. Eine Datei → Datei, mehrere → Verzeichnis.

Das war die Kehre gegenüber der ursprünglichen Skizze („einheitlich"). Entscheidend war die Folge für den Bestand: unter „einheitlich" wären die 29 flachen Dateien eine datierte Ausnahme im Contract gewesen oder eine Umsortierung mit 29 Bewegungen plus referenzierenden Seiten — über der Mass-Update-Schwelle, also ein eigener Publish mit Freigabe. Unter „nur bei Bedarf" stellt sich der Fall nicht.

4. Der Bundle-Name kommt aus dem Stem der Primärdatei (erstes Argument, bzw. die bereits liegende Datei im Wachstumsfall), nicht aus dem Titel der Source-Seite — letzteres würde raw/ an kb/ koppeln.

5. Ein Bundle trägt den Typ der Quelle, nicht der einzelnen Datei. Ein Diagramm zu einer documents/-Quelle wird aus incoming/documents/ befördert, nicht aus incoming/assets/; assets/ ist für Quellen, die selbst ein Asset sind. Steht als Satz in raw/CONTRACT.md.

6. --page ist optional, außer im Wachstumsfall. Ohne --page bewegt accept nur und druckt die Zielpfade für new source --set raw_files=…. Mit --page erweitert es raw_files: einer bestehenden Seite; entsteht dadurch die zweite Datei, zieht es die Bundle-Beförderung der bereits liegenden Datei im selben Aufruf nach.

Die Rückwärtssuche musste nicht gebaut werden: provenance.source_pages_by_raw_file() invertiert raw_files: über alle Source-Seiten und trägt schon duplicate_raw_file_owners. Sie wird benutzt statt eines „ich weiß ja, welche Seite gemeint ist"-Kurzschlusses — eine zu bewegende Rohdatei mit mehr als einem Owner wird abgelehnt, statt die andere Seite unbemerkt zu brechen.

7. RAW_SUBDIRS ist die einzige Quelle der Liste. Sie speist vier Stellen: Eingangsverzeichnisse, raw/-Verzeichnisse, Contract-Tabelle, .gitkeep-Schleife in dist export. docs verify (check_raw_subdirs) hält die Tabelle beidseitig dagegen (Invariante 8).

8. Die Version ist MINOR. Die ursprüngliche Vermutung „eine erzwungene Umsortierung wäre die Major-Zeile" hielt dem Drop-in-Test nicht stand: eine Umsortierung dieser Instanz wäre eine Korpus-Operation und erreicht keine fremde Instanz. Boundary-crossing wäre es erst, wenn etwas die Bundle-Form validiert; nichts tut das, und unter Entscheidung 3 wird ohnehin nichts umsortiert.

Umsetzungsnotizen

  • Path.rename() statt git mv. Das in #16 als geteiltes Kleinstprimitiv erwartete „git mv versuchen, bei not under version control auf mv zurückfallen" wird hier nicht gebraucht: die Quelle in incoming/ ist per Definition nie getrackt, und page_ops.rename_command/move_command bewegen kb/-Dateien längst mit schlichtem Path.rename(), weil publishs git add -A eine inhaltsgleiche Bewegung ohnehin als Rename erkennt. Bewusst dem bestehenden Muster gefolgt statt einen neuen git-Subprozess-Helfer einzuführen.
  • doctor wurde nicht erweitert. Kein Akzeptanzkriterium verlangte es, und der Fall, um den es ginge (frischer Klon ohne incoming/), ist über instructions/bootstrap.md Schritt 2 abgedeckt. doctor meldet weiterhin nur und legt nie an — der Satz in Schritt 2 sagt das ausdrücklich.
  • Menschendoku über die Kriterienliste hinaus. README.md beschrieb den Ingest-Einstieg noch als „drop a file into raw/" (Verzeichnisbaum, Quickstart, „Curate sources", erste Ingestion, wiki-ingest-Zeile) — nachgezogen, weil AGENTS.md § Changelog eine Stack-Änderung erst mit der Menschendoku als fertig zählt. docs/pipeline-rationale.md bekam einen Satz: incoming/ liegt auf der Diesseits-Seite der Vertrauensgrenze, die Grenze ist die Beförderung nach raw/, nicht der Moment der Ablage. INSTALL.md, INSTALL-MCP.md, EVALS.md und tools/README.md wurden geprüft und brauchten nichts.

Verhältnis zu #32

#32 skizziert dieselbe Mechanik, kommt aber von der anderen Seite: dort fremd eingereichtes Material, das nicht ungeprüft in ein öffentliches Repo darf, hier eigenes, das der Nutzer nicht selbst einsortieren soll. Ein Mechanismus, zwei Auslöser.

  • Der Lesezugriff ist gegenläufig. #32 verlangt eine Quarantäne, „die kein Kommando der normalen Pipeline liest"; ein lokales incoming/ will, dass die Ingest-Sitzung hineinsieht. Aufgelöst über die Grenze Sitzung vs. Pipeline-Kommando — und das war gratis: lint und sources coverage gehen ausschließlich über config.iter_raw_files(config.RAW_DIR), alles außerhalb raw/ ist per Konstruktion unsichtbar, ohne Ausschlussliste.
  • Die Abhängigkeit war nicht dieselbe. #32 ist status/blocked auf #19; der lokale Ablagepfad hing an nichts davon.

raw accept steht jetzt; #32 konsumiert es und ergänzt, was nur der Fremdeinreichung eigen ist: Auth, Kontingente, Manifest mit Einreicherzurechnung, Ablehnungspfad.

Verhältnis zu #16

Keine Abhängigkeit, in keiner Richtung — auch nicht die, die #16s Kommentar vom 2026-09-04 20:56 behauptete. Dort ist die Richtigstellung als Kommentar hinterlegt: raw accept befördert eine Datei, die noch keine Seite referenziert, und der eine referenzierende Fall benutzt die vorhandene Inversion aus provenance.py. Der Bestandspunkt stellte sich unter Entscheidung 3 gar nicht.

Was #16 aus dieser Umsetzung erbt: die raw-Kommandogruppe existiert jetzt (tools/chemenu/commands/raw_cmd.py, registriert in cli.py), raw rename hängt sich nur noch ein.

Abgrenzung (unverändert eingehalten)

  • Kein Konvertierungs-Werkzeug — PDF→Markdown macht die Sitzung, nicht wikitool.
  • Keine Auth, keine Kontingente, kein Einreicher-Manifest — das ist #32.
  • Kein Löschen aus raw/; raw/ bleibt immutabel, hier entstand nur ein Weg hinein.
  • Keine Umsortierung des Bestands.

Akzeptanzkriterien

  • Nach raw accept sind die beförderten Dateien byte-identisch mit dem, was im Eingang lag, und der Eingang enthält sie nicht mehr — Test vergleicht Inhalte (test_single_file_needs_no_bundle, test_two_files_bundle_under_the_first_files_stem).
  • Eine einzeln beförderte Datei landet als raw/<sub>/<name> ohne Zwischenverzeichnis; mehrere in einem Aufruf als raw/<sub>/<stem>/<name>.
  • Eine Datei direkt in incoming/ wird mit Exit 1 abgelehnt und nennt die zulässigen Verzeichnisse; nichts wird bewegt (test_file_directly_in_incoming_is_rejected, dazu test_unknown_type_subdir_is_rejected, test_nested_too_deep_is_rejected).
  • Eine Datei in incoming/ erzeugt kein uncovered_raw_files-Finding (test_promoted_file_in_incoming_is_never_reported_uncovered).
  • git check-ignore --no-index meldet einen Pfad unter incoming/ als ignoriert, geprüft über docs_verify.REQUIRED_IGNORE_CANARIES (test_incoming_inbox_is_ignored, plus das bestehende test_no_content_is_gitignored, das die Kanarie mitprüft).
  • Zu keinem Zeitpunkt zeigt eine raw_files:-Referenz ins Leere; im Wachstumsfall nennt raw_files: danach alle Dateien unter neuen Pfaden, lint meldet weder broken_raw_refs noch uncovered_raw_files (test_growth_case_no_broken_or_uncovered_refs_afterwards).
  • Ein Wachstumsfall auf eine Rohdatei mit mehr als einem Owner wird mit Exit 1 abgelehnt und bewegt nichts (test_growth_case_rejects_a_multi_owner_raw_file).
  • dist_cmd.RAW_SUBDIRS und die Routing-Tabelle beschreiben dieselbe Liste; docs verify schlägt in beiden Richtungen fehl (check_raw_subdirs, test_raw_subdirs_mismatch_is_reported).
  • dist export legt je RAW_SUBDIRS-Element raw/<sub>/.gitkeep und incoming/<sub>/.gitkeep an (test_plan_creates_matching_incoming_subdirs, test_export_into_a_fresh_directory_works).
  • instructions/bootstrap.md legt die Eingangsverzeichnisse an (neuer Schritt 2, mit dem Grund: gitignoriert, doctor meldet nur).
  • raw/CONTRACT.md beschreibt Eingang, Bundle-Regel und den Typ-des-Bundles-Satz; die Routing-Tabelle liest sich als abgeleitetes Verhalten.
  • tools/CONTRACT.md trägt Zeilen für raw accept und seinen Fehlerkontrakt; docs verify erzwingt beide Richtungen.
  • Der wiki-ingest-Skill kennt den Weg über den Eingang (neuer Schritt 1, Schritte neu nummeriert, Kommandoliste ergänzt).
  • Changelog-Eintrag, MINOR4.8.0-beta.3 (Kandidat, kein Release).

Verifiziert

  • tools/wikitool docs verify — OK, 52 Kommandos dokumentiert, 11 Ignore-Kanarien klar.
  • tools/wikitool instructions verify — OK, 20 Instructions und 7 Skills gültig, 14 publizierte Kopien identisch.
  • pytest -q lokal: 1032 passed, davon 19 neu in tools/chemenu/tests/test_raw_cmd.py plus 4 in test_docs_verify.py/test_dist_cmd.py.
  • Manueller End-to-End-Lauf über die echte CLI: --dry-run, dann echte Beförderung incoming/notes/… → raw/notes/…, sources coverage meldete die Datei danach korrekt als ungedeckt (keine Source-Seite); Testdatei wieder entfernt.
  • CI zu 36d2128: Lauf 182 (verify) grün — Tests, Coverage, Verify-Tree, Version-Gate, dist export und der Frisch-Instanz-Replay des Exports. Lauf 183 (release) grün und hat korrekt kein Release geschnitten: VERSION trägt -beta.3, jüngstes Release bleibt v4.7.4.

Schließt #58.

**Umgesetzt und ausgeliefert** in `4.8.0-beta.3`, Commit `36d2128`. Aufgeworfen und entschieden in den Sitzungen vom 2026-09-04, gebaut am 2026-09-05. ## Das Problem, das es gab `raw/CONTRACT.md`s Routing-Tabelle war eine **Regel für Menschen**: wer eine Datei ablegte, wählte `articles/`/`documents/`/`notes/`/`assets/` selbst. Zwei Folgen: - **Es skalierte nicht.** Vier flache Verzeichnisse für alles, was je eingelesen wird. Der Stack hatte sonst nirgends eine Ablageentscheidung, die ein Mensch trifft — „nie ein Verzeichnis von Hand wählen" (AGENTS.md § Routing) galt für `kb/`, `raw/` war ausgenommen, ohne dass das je jemand entschieden hätte. - **Zusammengehörige Dateien waren nicht als solche erkennbar.** Beim Muster „PDF hochladen, nach Markdown konvertieren" entstehen zwei Dateien für eine logische Quelle; dass sie zusammengehören, stand ausschließlich in `raw_files:` der Source-Seite. Nicht das Problem war **die Tiefe selbst**: `config.iter_raw_files` macht `rglob("*")`, `provenance.py` und `sources coverage` arbeiten auf repo-relativen Pfaden — `raw/documents/handbuch/original.pdf` funktionierte schon vorher ohne Codeänderung. Flach waren nur die Contract-Tabelle und `dist_cmd.RAW_SUBDIRS`. Es fehlte kein Mechanismus, es fehlte eine Zuständigkeit. Ebenfalls nicht das Problem war **der Bestand**: die 29 Rohdateien liegen flach *innerhalb* ihrer Unterverzeichnisse (`articles/` 3, `documents/` 1, `notes/` 25; `assets/` leer und mangels Datei nicht getrackt). Unter Entscheidung 3 sind das eindateiige Quellen in vorschriftsmäßiger Form. **Es wurde nichts umsortiert.** ## Der Weg, wie er jetzt läuft Mehrdateiige Quelle: ``` incoming/documents/handbuch.pdf Ablage deklariert den Typ ↓ Ingest-Sitzung konvertiert incoming/documents/handbuch.pdf + handbuch.md ↓ wikitool raw accept incoming/documents/handbuch.pdf incoming/documents/handbuch.md raw/documents/handbuch/handbuch.pdf zwei Dateien → Bundle-Verzeichnis raw/documents/handbuch/handbuch.md ↓ wikitool new source --set raw_files=… ``` Eindateiige Quelle: ``` incoming/notes/besprechung.md ↓ wikitool raw accept incoming/notes/besprechung.md raw/notes/besprechung.md eine Datei → kein Verzeichnis ``` ## Die Entscheidungen **1. Der Eingang ist ein Top-Level-`incoming/` im Repo, gitignoriert.** Nicht außerhalb, nicht unter `raw/`. Unter `raw/` war es unmöglich: `.gitignore` endet mit dem Backstop `!raw/**`, und `docs_verify.check_ignored_content` schlägt auf jede ignorierte Datei unter `raw|kb|work` fehl — ein `raw/incoming/` wäre entweder committed oder `docs verify` rot. Gegen „außerhalb des Repos" entschied der Nachweis: „wird nie committed" ist über `docs_verify.REQUIRED_IGNORE_CANARIES` mechanisch belegbar (`git check-ignore --no-index` beantwortet die Frage über den *Regelsatz*, ohne dass die Datei existieren muss); für einen Pfad außerhalb des Baums existiert dieses Beweismittel nicht, und er bräuchte zusätzlich eine Konfigurationsfläche, die es nirgends gibt. **Gilt auch für #32.** Dessen schärfere Anforderung („eine Quarantäne, die kein Kommando der normalen Pipeline liest") trägt nicht der Ort, sondern die Grenze zwischen *Sitzung* und *Pipeline-Kommando* — siehe § Verhältnis zu #32. **2. Der Typ wird über Unterverzeichnisse im Eingang deklariert, nicht über ein Flag.** `incoming/` spiegelt `RAW_SUBDIRS`. Kein `--type`-Argument. Keine Rückkehr zur Handsortierung, weil zwei Entscheidungen auseinanderfallen: **was für ein Dokument das ist** (eine Klassifikation, die kein Werkzeug ableiten kann — eine `.md` kann `notes/` oder `articles/` sein) und **wo die Datei am Ende liegt** (Unterverzeichnis, Bundle ja/nein, Bundle-Name, die Bewegung). Der Mensch liefert nur die erste, aus einem Vierer-Vokabular. Drei Vorteile gegenüber dem Flag: die Erklärung wird abgegeben, wenn der Mensch das Dokument in der Hand hat, und überlebt Sitzungswechsel; ein Argument weniger ist ein Fehlweg weniger; eine Datei direkt in `incoming/` kann abgelehnt werden, während ein Flag immer gesetzt ist, richtig oder falsch. **3. Ein Bundle-Verzeichnis entsteht nur bei Bedarf — ab der zweiten Datei.** Eine Datei → Datei, mehrere → Verzeichnis. Das war die Kehre gegenüber der ursprünglichen Skizze („einheitlich"). Entscheidend war die Folge für den Bestand: unter „einheitlich" wären die 29 flachen Dateien eine datierte Ausnahme im Contract gewesen oder eine Umsortierung mit 29 Bewegungen plus referenzierenden Seiten — über der Mass-Update-Schwelle, also ein eigener Publish mit Freigabe. Unter „nur bei Bedarf" stellt sich der Fall nicht. **4. Der Bundle-Name kommt aus dem Stem der Primärdatei** (erstes Argument, bzw. die bereits liegende Datei im Wachstumsfall), nicht aus dem Titel der Source-Seite — letzteres würde `raw/` an `kb/` koppeln. **5. Ein Bundle trägt den Typ der *Quelle*, nicht der einzelnen Datei.** Ein Diagramm zu einer `documents/`-Quelle wird aus `incoming/documents/` befördert, nicht aus `incoming/assets/`; `assets/` ist für Quellen, die selbst ein Asset sind. Steht als Satz in `raw/CONTRACT.md`. **6. `--page` ist optional, außer im Wachstumsfall.** Ohne `--page` bewegt `accept` nur und druckt die Zielpfade für `new source --set raw_files=…`. Mit `--page` erweitert es `raw_files:` einer bestehenden Seite; entsteht dadurch die zweite Datei, zieht es die Bundle-Beförderung der bereits liegenden Datei im selben Aufruf nach. Die Rückwärtssuche musste nicht gebaut werden: `provenance.source_pages_by_raw_file()` invertiert `raw_files:` über alle Source-Seiten und trägt schon `duplicate_raw_file_owners`. Sie wird benutzt statt eines „ich weiß ja, welche Seite gemeint ist"-Kurzschlusses — eine zu bewegende Rohdatei mit mehr als einem Owner wird abgelehnt, statt die andere Seite unbemerkt zu brechen. **7. `RAW_SUBDIRS` ist die einzige Quelle der Liste.** Sie speist vier Stellen: Eingangsverzeichnisse, `raw/`-Verzeichnisse, Contract-Tabelle, `.gitkeep`-Schleife in `dist export`. `docs verify` (`check_raw_subdirs`) hält die Tabelle beidseitig dagegen (Invariante 8). **8. Die Version ist MINOR.** Die ursprüngliche Vermutung „eine erzwungene Umsortierung wäre die Major-Zeile" hielt dem Drop-in-Test nicht stand: eine Umsortierung *dieser* Instanz wäre eine Korpus-Operation und erreicht keine fremde Instanz. Boundary-crossing wäre es erst, wenn etwas die Bundle-Form *validiert*; nichts tut das, und unter Entscheidung 3 wird ohnehin nichts umsortiert. ## Umsetzungsnotizen - **`Path.rename()` statt `git mv`.** Das in #16 als geteiltes Kleinstprimitiv erwartete „`git mv` versuchen, bei *not under version control* auf `mv` zurückfallen" wird hier **nicht** gebraucht: die Quelle in `incoming/` ist per Definition nie getrackt, und `page_ops.rename_command`/`move_command` bewegen `kb/`-Dateien längst mit schlichtem `Path.rename()`, weil `publish`s `git add -A` eine inhaltsgleiche Bewegung ohnehin als Rename erkennt. Bewusst dem bestehenden Muster gefolgt statt einen neuen git-Subprozess-Helfer einzuführen. - **`doctor` wurde nicht erweitert.** Kein Akzeptanzkriterium verlangte es, und der Fall, um den es ginge (frischer Klon ohne `incoming/`), ist über `instructions/bootstrap.md` Schritt 2 abgedeckt. `doctor` meldet weiterhin nur und legt nie an — der Satz in Schritt 2 sagt das ausdrücklich. - **Menschendoku über die Kriterienliste hinaus.** `README.md` beschrieb den Ingest-Einstieg noch als „drop a file into `raw/`" (Verzeichnisbaum, Quickstart, „Curate sources", erste Ingestion, `wiki-ingest`-Zeile) — nachgezogen, weil AGENTS.md § Changelog eine Stack-Änderung erst mit der Menschendoku als fertig zählt. `docs/pipeline-rationale.md` bekam einen Satz: `incoming/` liegt auf der Diesseits-Seite der Vertrauensgrenze, die Grenze ist die Beförderung nach `raw/`, nicht der Moment der Ablage. `INSTALL.md`, `INSTALL-MCP.md`, `EVALS.md` und `tools/README.md` wurden geprüft und brauchten nichts. ## Verhältnis zu #32 #32 skizziert dieselbe Mechanik, kommt aber von der anderen Seite: dort **fremd eingereichtes** Material, das nicht ungeprüft in ein öffentliches Repo darf, hier **eigenes**, das der Nutzer nicht selbst einsortieren soll. Ein Mechanismus, zwei Auslöser. - **Der Lesezugriff ist gegenläufig.** #32 verlangt eine Quarantäne, „die kein Kommando der normalen Pipeline liest"; ein lokales `incoming/` will, dass die Ingest-Sitzung hineinsieht. Aufgelöst über die Grenze *Sitzung* vs. *Pipeline-Kommando* — und das war gratis: `lint` und `sources coverage` gehen ausschließlich über `config.iter_raw_files(config.RAW_DIR)`, alles außerhalb `raw/` ist per Konstruktion unsichtbar, ohne Ausschlussliste. - **Die Abhängigkeit war nicht dieselbe.** #32 ist `status/blocked` auf #19; der lokale Ablagepfad hing an nichts davon. **`raw accept` steht jetzt; #32 konsumiert es** und ergänzt, was nur der Fremdeinreichung eigen ist: Auth, Kontingente, Manifest mit Einreicherzurechnung, Ablehnungspfad. ## Verhältnis zu #16 **Keine Abhängigkeit, in keiner Richtung** — auch nicht die, die #16s Kommentar vom 2026-09-04 20:56 behauptete. Dort ist die Richtigstellung als Kommentar hinterlegt: `raw accept` befördert eine Datei, die noch keine Seite referenziert, und der eine referenzierende Fall benutzt die vorhandene Inversion aus `provenance.py`. Der Bestandspunkt stellte sich unter Entscheidung 3 gar nicht. Was #16 aus dieser Umsetzung **erbt**: die `raw`-Kommandogruppe existiert jetzt (`tools/chemenu/commands/raw_cmd.py`, registriert in `cli.py`), `raw rename` hängt sich nur noch ein. ## Abgrenzung (unverändert eingehalten) - Kein Konvertierungs-Werkzeug — PDF→Markdown macht die Sitzung, nicht `wikitool`. - Keine Auth, keine Kontingente, kein Einreicher-Manifest — das ist #32. - Kein Löschen aus `raw/`; `raw/` bleibt immutabel, hier entstand nur ein Weg **hinein**. - Keine Umsortierung des Bestands. ## Akzeptanzkriterien - [x] Nach `raw accept` sind die beförderten Dateien byte-identisch mit dem, was im Eingang lag, und der Eingang enthält sie nicht mehr — Test vergleicht Inhalte (`test_single_file_needs_no_bundle`, `test_two_files_bundle_under_the_first_files_stem`). - [x] Eine einzeln beförderte Datei landet als `raw/<sub>/<name>` ohne Zwischenverzeichnis; mehrere in einem Aufruf als `raw/<sub>/<stem>/<name>`. - [x] Eine Datei direkt in `incoming/` wird mit Exit 1 abgelehnt und nennt die zulässigen Verzeichnisse; nichts wird bewegt (`test_file_directly_in_incoming_is_rejected`, dazu `test_unknown_type_subdir_is_rejected`, `test_nested_too_deep_is_rejected`). - [x] Eine Datei in `incoming/` erzeugt kein `uncovered_raw_files`-Finding (`test_promoted_file_in_incoming_is_never_reported_uncovered`). - [x] `git check-ignore --no-index` meldet einen Pfad unter `incoming/` als ignoriert, geprüft über `docs_verify.REQUIRED_IGNORE_CANARIES` (`test_incoming_inbox_is_ignored`, plus das bestehende `test_no_content_is_gitignored`, das die Kanarie mitprüft). - [x] Zu keinem Zeitpunkt zeigt eine `raw_files:`-Referenz ins Leere; im Wachstumsfall nennt `raw_files:` danach alle Dateien unter neuen Pfaden, `lint` meldet weder `broken_raw_refs` noch `uncovered_raw_files` (`test_growth_case_no_broken_or_uncovered_refs_afterwards`). - [x] Ein Wachstumsfall auf eine Rohdatei mit mehr als einem Owner wird mit Exit 1 abgelehnt und bewegt nichts (`test_growth_case_rejects_a_multi_owner_raw_file`). - [x] `dist_cmd.RAW_SUBDIRS` und die Routing-Tabelle beschreiben dieselbe Liste; `docs verify` schlägt in beiden Richtungen fehl (`check_raw_subdirs`, `test_raw_subdirs_mismatch_is_reported`). - [x] `dist export` legt je `RAW_SUBDIRS`-Element `raw/<sub>/.gitkeep` **und** `incoming/<sub>/.gitkeep` an (`test_plan_creates_matching_incoming_subdirs`, `test_export_into_a_fresh_directory_works`). - [x] `instructions/bootstrap.md` legt die Eingangsverzeichnisse an (neuer Schritt 2, mit dem Grund: gitignoriert, `doctor` meldet nur). - [x] `raw/CONTRACT.md` beschreibt Eingang, Bundle-Regel und den Typ-des-Bundles-Satz; die Routing-Tabelle liest sich als abgeleitetes Verhalten. - [x] `tools/CONTRACT.md` trägt Zeilen für `raw accept` und seinen Fehlerkontrakt; `docs verify` erzwingt beide Richtungen. - [x] Der `wiki-ingest`-Skill kennt den Weg über den Eingang (neuer Schritt 1, Schritte neu nummeriert, Kommandoliste ergänzt). - [x] Changelog-Eintrag, **MINOR** — `4.8.0-beta.3` (Kandidat, kein Release). ## Verifiziert - `tools/wikitool docs verify` — OK, 52 Kommandos dokumentiert, 11 Ignore-Kanarien klar. - `tools/wikitool instructions verify` — OK, 20 Instructions und 7 Skills gültig, 14 publizierte Kopien identisch. - `pytest -q` lokal: **1032 passed**, davon 19 neu in `tools/chemenu/tests/test_raw_cmd.py` plus 4 in `test_docs_verify.py`/`test_dist_cmd.py`. - Manueller End-to-End-Lauf über die echte CLI: `--dry-run`, dann echte Beförderung `incoming/notes/… → raw/notes/…`, `sources coverage` meldete die Datei danach korrekt als ungedeckt (keine Source-Seite); Testdatei wieder entfernt. - CI zu `36d2128`: Lauf **182** (`verify`) grün — Tests, Coverage, Verify-Tree, Version-Gate, `dist export` und der Frisch-Instanz-Replay des Exports. Lauf **183** (`release`) grün und hat korrekt **kein** Release geschnitten: `VERSION` trägt `-beta.3`, jüngstes Release bleibt `v4.7.4`. Schließt #58.
torben added the prio/plannedsize/Marea/kbkind/decision labels 2026-09-04 19:26:32 +00:00
Author
Owner

Changelog: Alle sechs offenen Fragen entschieden, Body auf den Entwurfsstand umgeschrieben, kind/decisionkind/build.

Zwei Entscheidungen fielen anders aus als der ursprüngliche Body vorschlug:

  • Bundle-Verzeichnis nur bei Bedarf statt einheitlich. Damit löst sich der Bestandsfall auf — die 29 flachen Dateien sind eindateiige Quellen in vorschriftsmäßiger Form, keine Altlast. Kein Umsortieren, kein Mass-Update-Publish, keine datierte Ausnahme im Contract.
  • Typ über Unterverzeichnisse in incoming/ statt über ein --type-Flag. Die Klassifikation wird abgegeben, wenn der Mensch das Dokument in der Hand hat, und überlebt den Sitzungswechsel; das Flag entfällt ersatzlos.

Weiter entschieden: Eingang als Top-Level-incoming/ im Repo (gilt auch für #32), Bundle-Name aus dem Stem der Primärdatei, Bundle trägt den Typ der Quelle, --page optional außer im Wachstumsfall, RAW_SUBDIRS bleibt einzige Quelle der Liste.

Drei Korrekturen gegen den alten Body: die Version ist MINOR, nicht MAJOR — eine Umsortierung dieser Instanz ist eine Korpus-Operation und erreicht keine fremde Instanz. Die 29 Dateien liegen nicht „flach in raw/", sondern flach innerhalb ihrer Unterverzeichnisse (3/1/25, assets/ leer). Und die Rückwärtssuche für den Wachstumsfall muss nicht gebaut werden — provenance.source_pages_by_raw_file() gibt es seit langem und lint benutzt sie bereits.

Neu ergänzt: § Verhältnis zu #16 hält fest, dass keine Abhängigkeit in beide Richtungen besteht, und notiert den Korrekturbedarf am dortigen Kommentar vom 2026-09-04 20:56, der nach diesen Entscheidungen in beiden Hälften nicht mehr gilt.

**Changelog:** Alle sechs offenen Fragen entschieden, Body auf den Entwurfsstand umgeschrieben, `kind/decision` → `kind/build`. Zwei Entscheidungen fielen anders aus als der ursprüngliche Body vorschlug: - **Bundle-Verzeichnis nur bei Bedarf** statt einheitlich. Damit löst sich der Bestandsfall auf — die 29 flachen Dateien sind eindateiige Quellen in vorschriftsmäßiger Form, keine Altlast. Kein Umsortieren, kein Mass-Update-Publish, keine datierte Ausnahme im Contract. - **Typ über Unterverzeichnisse in `incoming/`** statt über ein `--type`-Flag. Die Klassifikation wird abgegeben, wenn der Mensch das Dokument in der Hand hat, und überlebt den Sitzungswechsel; das Flag entfällt ersatzlos. Weiter entschieden: Eingang als Top-Level-`incoming/` im Repo (gilt auch für #32), Bundle-Name aus dem Stem der Primärdatei, Bundle trägt den Typ der Quelle, `--page` optional außer im Wachstumsfall, `RAW_SUBDIRS` bleibt einzige Quelle der Liste. Drei Korrekturen gegen den alten Body: die Version ist **MINOR**, nicht MAJOR — eine Umsortierung dieser Instanz ist eine Korpus-Operation und erreicht keine fremde Instanz. Die 29 Dateien liegen nicht „flach in `raw/`", sondern flach innerhalb ihrer Unterverzeichnisse (3/1/25, `assets/` leer). Und die Rückwärtssuche für den Wachstumsfall muss nicht gebaut werden — `provenance.source_pages_by_raw_file()` gibt es seit langem und `lint` benutzt sie bereits. Neu ergänzt: § Verhältnis zu #16 hält fest, dass keine Abhängigkeit in beide Richtungen besteht, und notiert den Korrekturbedarf am dortigen Kommentar vom 2026-09-04 20:56, der nach diesen Entscheidungen in beiden Hälften nicht mehr gilt.
torben added kind/build and removed kind/decision labels 2026-09-04 21:54:59 +00:00
Author
Owner

Changelog: Body auf den Endstand umgeschrieben und geschlossen. Alle 14 Akzeptanzkriterien abgehakt, jeweils mit dem Test benannt, der sie hält. Das Problem steht jetzt im Präteritum, die acht Entscheidungen als getroffen.

Neu gegenüber dem Entwurfsstand ist der Abschnitt Umsetzungsnotizen mit drei Punkten, die beim Bauen entschieden wurden und aus keinem Kriterium folgten: Path.rename() statt des aus #16 erwarteten git mv-Primitivs (Quelle in incoming/ ist nie getrackt, und page_ops bewegt kb/-Dateien längst genauso); doctor bewusst nicht erweitert (der Fall liegt bei bootstrap.md Schritt 2); und die Menschendoku über die Kriterienliste hinaus — README.md beschrieb den Einstieg noch als „drop a file into raw/", docs/pipeline-rationale.md bekam einen Satz zur Vertrauensgrenze, INSTALL*.md/EVALS.md/tools/README.md geprüft und unverändert.

Der Korrekturbedarf an #16 ist erledigt statt nur vermerkt: Kommentar dort stellt beide Hälften richtig und hält fest, was #16 aus dieser Umsetzung erbt (die raw-Kommandogruppe existiert jetzt).

Verifikation im Body benannt: docs verify, instructions verify, 1032 lokale Tests, ein manueller CLI-Durchlauf, CI-Läufe 182 (verify, grün) und 183 (release, grün und korrekt ohne Release für den -beta.3-Kandidaten).

**Changelog:** Body auf den Endstand umgeschrieben und geschlossen. Alle 14 Akzeptanzkriterien abgehakt, jeweils mit dem Test benannt, der sie hält. Das Problem steht jetzt im Präteritum, die acht Entscheidungen als getroffen. Neu gegenüber dem Entwurfsstand ist der Abschnitt **Umsetzungsnotizen** mit drei Punkten, die beim Bauen entschieden wurden und aus keinem Kriterium folgten: `Path.rename()` statt des aus #16 erwarteten `git mv`-Primitivs (Quelle in `incoming/` ist nie getrackt, und `page_ops` bewegt `kb/`-Dateien längst genauso); `doctor` bewusst nicht erweitert (der Fall liegt bei `bootstrap.md` Schritt 2); und die Menschendoku über die Kriterienliste hinaus — `README.md` beschrieb den Einstieg noch als „drop a file into `raw/`", `docs/pipeline-rationale.md` bekam einen Satz zur Vertrauensgrenze, `INSTALL*.md`/`EVALS.md`/`tools/README.md` geprüft und unverändert. Der Korrekturbedarf an #16 ist erledigt statt nur vermerkt: [Kommentar dort](https://gitea.nehmer.net/torben/chemenu/issues/16#issuecomment-1031) stellt beide Hälften richtig und hält fest, was #16 aus dieser Umsetzung erbt (die `raw`-Kommandogruppe existiert jetzt). Verifikation im Body benannt: `docs verify`, `instructions verify`, 1032 lokale Tests, ein manueller CLI-Durchlauf, CI-Läufe 182 (`verify`, grün) und 183 (`release`, grün und korrekt ohne Release für den `-beta.3`-Kandidaten).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#58