--- type: types/source.md source_type: notes author: Claude Code (claude-opus-5) raw_files: [raw/notes/Conversation Transcript - Write-Once Frontmatter Fields and touch --set Session 2026-08-31.md] source_language: en date: 2026-08-31 tags: [wikitool, cli, frontmatter, touch, schema, idempotenz, gitea] entities: [wikitool, Chemenu, Gitea, AGENTS.md] concepts: [Write-Once Frontmatter Fields, Denylist over Allowlist] summary: 'Sitzung, die write-once-Frontmatterfelder reparierbar macht: touch bekommt --set/--add/--remove ueber eine Denylist statt einer Allowlist, ein idempotentes --remove und einen bewusst engen Scope (Stack 1.4.0, Gitea-Issue #14)' --- # Source: Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31 **Autor:** Claude Code (claude-opus-5) **Datum:** 2026-08-31 **Raw-Dateien:** raw/notes/Conversation Transcript - Write-Once Frontmatter Fields and touch --set Session 2026-08-31.md **Typ:** Notes ## Zusammenfassung Das Transkript ist eine zusammenfassende Rekonstruktion der Sitzung, kein wörtliches Protokoll. Die zitierten Befehlsausgaben sind echt, Torbens drei Entwurfsentscheidungen sind samt der ihm vorgelegten Optionen festgehalten, die Begründungen des Assistenten sind verdichtet. Es ist eines von zwei Transkripten dieses Sitzungsabschnitts; das zweite behandelt die Zählregel des Mass-Update Gate und die gemessene Kalibrierung des Iteration Budget und wird getrennt eingelesen. Gegenstand ist Gitea-Issue #14, ausgeliefert als `touch --set/--add/--remove` in Stack-Version `1.4.0` (Commit `dbe2f73`, 9 Dateien, 674 Tests grün). Der Defekt, den es schließt: ein Feld, das `new` einmal geschrieben hat, war danach nicht mehr erreichbar. `touch` kannte nur die Felder `modified`, `summary`, `provenance` und `confidence_base`; Handeditierung ist genau das, was der Stack verhindern soll; Löschen und Neuanlegen zerreißt jede bestehende Referenz auf die Seite; und `new` ist nicht idempotent, das Zeitfenster für den richtigen Wert war also genau einen Befehl breit. Den bleibenden Wert der Sitzung tragen drei Entscheidungen, die Torben mit ausgewiesenen Trade-offs vorgelegt wurden: Denylist statt Allowlist für die schreibbaren Felder, Ersetzen plus `--add`/`--remove` für Listenfelder, und ein bewusst enger Auslieferungsschnitt, der `raw rename` als Issue #16 abspaltet. ## Kernaussagen - **Die Vorarbeit lag schon im Code.** `validate_fields()` in `touch.py` validiert **pro Feld** statt pro Dokument - genau die Form, die ein `--set` braucht. Der Grund steht im Docstring: eine Validierung über das ganze Dokument würde sich weigern, `modified:` auf einer Seite zu bumpen, die aus einem unbeteiligten Grund ungültig ist, also auf der Seite, die Wartung am dringendsten braucht. - **Entscheidung 1 - Denylist statt Allowlist.** Schreibbar ist alles, was das Schema für den Seitentyp deklariert, abzüglich einer kurzen begründeten Sperrliste. Das Argument, das den Ausschlag gab: eine gepflegte Allowlist ist eine zweite Kopie des Schemas, und die Kopie ist die Seite, die driftet - Invariante 8 aus `AGENTS.md`, angewandt auf eine Konstante. Der Preis der Allowlist wäre gewesen, dass jedes neue Schema-Feld eine Codeänderung braucht. - **Gesperrt sind vier Gruppen, jede mit dem Befehl benannt, dem das Feld gehört:** `type:` ändert Schema *und* Verzeichnis der Seite und gehört nach `page-lifecycle.md`; `confidence:` ist aus `confidence_base` abgeleitet und nicht autorisiert; `related:`, `sources:`, `entities:` und `concepts:` gehören `xref`, weil ein blanker Frontmatter-Schreibvorgang die Gegenrichtung und die Body-Bullets stehen ließe. - **Entscheidung 2 - Ersetzen plus `--add`/`--remove`.** Reines Ersetzen wäre eine Regel gewesen, hätte aber verlangt, für ein einzelnes Tag die ganze Liste zu nennen. Der Preis der gewählten Variante sind drei Optionen statt einer. - **Die Teilfrage, was `--remove` mit einem nicht vorhandenen Element tut, entschied der Assistent selbst und wies das aus:** es gelingt und wird gemeldet. Idempotent wie `xref remove`, weil ein Reparaturbefehl, der sich beim zweiten Lauf verweigert, nicht skriptbar ist - aber nie stillschweigend, weil ein stiller No-op genauso aussieht wie eine gelungene Entfernung, und genau so verbirgt sich ein vertippter Elementname. - **Entscheidung 3 - enger Schnitt.** Nur `touch --set`; die Dateiverschiebung bleibt zweistufig (`git mv`, dann `touch --set raw_files=…`). Die Alternative, `raw rename` mitzuliefern, hätte Issue #14 vollständig geschlossen und den Zwischenzustand vermieden, zum Preis von `size/M`: Rückwärtssuche über alle `raw_files:`-Referenzen, Verhalten bei mehreren Besitzern, Contract-Zeilen für zwei Befehle. `raw rename` wurde Issue #16 (`prio/2`, `size/S`). - **Zwei Kommandos, eine Implementierung.** `_coerce_set_value`, `_parse_set_fields` und `_check_raw_files_exist` wanderten aus `new_page.py` nach `commands/_util.py` und verloren ihren führenden Unterstrich. Ohne das hätte `touch --set` den Komma-Defekt aus Issue #12 am ersten Tag geerbt; ein Test deckt genau diesen Fall ab, mit einer Rohdatei, deren Name ein Komma enthält, referenziert über `\,` und als ein Pfad zurückgelesen. - **Der Existenzcheck für `raw_files:` gilt auch für `touch`,** identisch zu dem, den `new` ausführt. Er ist I/O und keine Datenform, also kann kein Schema ihn ausdrücken. - **Die beiden Ablehnungen sind bewusst unterschiedlich formuliert.** Ein gesperrtes Feld ist ein Routing-Problem, die Meldung nennt deshalb den zuständigen Befehl. Ein unbekanntes Feld ist ein Tippfehler oder der falsche Seitentyp, die Meldung listet deshalb auf, welche Felder die Seite tatsächlich hat - der Nutzwert liegt darin, zu erfahren, dass `tags` gemeint war. - **Eine Falle im Testaufbau, einmal beseitigt.** `test_touch.py` rief den Typer-Callback direkt mit vollständiger Argumentliste auf; drei neue Optionen brachen sieben Aufrufstellen mit `TypeError: 'OptionInfo' object is not iterable`, weil ein direkt aufgerufener Callback für jedes ausgelassene Argument ein `OptionInfo`-Objekt bekommt. Die Tests laufen jetzt über einen `_touch(**overrides)`-Helper, der jede Option belegt; die nächste Option kostet eine Zeile statt sieben. - **Der Beleg am realen Korpus:** [[Diff-Reviewable Agent Edits]] war Stunden zuvor von einem Ingest angelegt worden, dessen `--set tags=`-Wert ein Komma am Ende trug, worauf alles hinter dem Trennzeichen verlorenging. Der Subagent hatte alle drei Auswege korrekt geprüft und verworfen - `touch` konnte `tags:` nicht setzen, Handeditierung kommt Invariante 1 zu nahe, `rm` plus `new` hätte die `concepts:`-Referenz der Source-Seite zerrissen. Die Seite behielt `[agent-workflow]` dauerhaft, wegen eines Kommas. Mit `touch --add` trägt sie jetzt `[agent-workflow, context-engineering, tooling]`. - **Der zweite Aufruf desselben `--add` meldete "already up to date; nothing to change",** der `--remove` eines nicht vorhandenen Elements meldete "not present, nothing removed" und endete ebenfalls erfolgreich - die zugesagte Idempotenz, an der Kommandozeile gezeigt. - **Dass dies eine Rate und kein Einzelfall war:** drei Fehlschläge in drei aufeinanderfolgenden Ingests desselben Tages, an zwei verschiedenen Feldern, von drei verschiedenen Agenten. Einer davon war ein nachgestelltes Komma. - **Ergebnis:** `1.4.0` als MINOR (neue Fähigkeit, rückwärtskompatibel), Commit `dbe2f73` über 9 Dateien, 674 Tests grün in der normalen und in der gehärteten Umgebung, Issue #14 geschlossen mit den drei Entscheidungen im Protokoll, Issue #16 eröffnet. ## Aufgaben - [ ] Gitea-Issue #16 - `raw rename`, das `git mv` und jede referenzierende Source-Seite in einem Schritt erledigt (`prio/2`, `size/S`) ## Nicht übernommen - **Die Modul- und Funktionsnamen der Testumbauten** über den `_touch`-Helper hinaus. Der Umbau selbst ist eine dauerhafte Aussage über die Testschnittstelle, die einzelnen sieben Aufrufstellen sind es nicht. - **Der vollständige Wortlaut der beiden Fehlermeldungen.** Übernommen ist die Entwurfsregel dahinter - Routing-Problem nennt den Befehl, Tippfehler nennt die vorhandenen Felder -, weil der Wortlaut sich ändern kann und die Regel nicht. - **Das Schwestertranskript desselben Sitzungsabschnitts** (Zählregel des Mass-Update Gate, gemessene Kalibrierung des Iteration Budget). Es wird getrennt eingelesen und bekommt eine eigene Source-Seite; hier stünde es unbelegt. - **Die Einzelheiten der 674 Tests.** Übernommen sind die Gesamtzahl und die Testfalle, die sich daran zeigte. - **Issue #14 als eigene Seite.** Es ist geschlossen, und sein Ergebnis steht auf [[wikitool]], [[Write-Once Frontmatter Fields]] und [[Detect-Repair Asymmetry]]. ## Verwandte Entities - [[wikitool]] - [[Chemenu]] - [[Gitea]] - [[AGENTS.md]] ## Verwandte Concepts - [[Denylist over Allowlist]] - [[Detect-Repair Asymmetry]] - [[Diff-Reviewable Agent Edits]] ## Beziehungen ## Siehe auch