Files
chemenu/raw/notes/Conversation Transcript - Write-Once Frontmatter Fields and touch --set Session 2026-08-31.md
torben 18ae28f918
CI / verify (push) Failing after 32s
Release / release (push) Successful in 38s
Chemenu 2.1.0 - deterministischer Wissenskompiler
Chemenu kompiliert Rohnotizen zu einem verlinkten, quellengebundenen Wiki:
raw/ -> types/ + tools/ -> kb/ -> reports/. Was mechanisch ist, macht
tools/wikitool; was Urteil braucht, macht ein Agent unter Contracts, deren
Grenzen in Code durchgesetzt sind statt im Prompt.

Dieser Commit ist der Startpunkt der oeffentlichen Historie. Die vorherige
Entwicklung fand in einer privaten Instanz statt und ist nicht Teil dieses
Repositorys; ihre Erzaehlung steht vollstaendig in CHANGES.md, das mit 44
Eintraegen von 0.1.0 bis 2.1.0 erhalten geblieben ist.

Der mitgelieferte Korpus ist ein Testbett und eine Demo: 170 Seiten ueber den
Stack selbst - Gates, Lint, Versionierung, Suche, das Wiki-Muster. Er
dokumentiert das Werkzeug mit den eigenen Mitteln des Werkzeugs.

Lizenz: AGPL-3.0 fuer den Stack (tools/, types/), CC-BY-4.0 fuer die Inhalte.
Die Grenze zwischen beiden ist der Dateiplan, den dist export berechnet -
siehe NOTICE.
2026-09-01 16:26:14 +02:00

7.9 KiB

Conversation Transcript - Write-Once Frontmatter Fields and touch --set Session

Source: Claude Code session (claude-opus-5), llm-wiki-test1 workspace Collected: 2026-08-31 Participant: Torben Fidelity: faithful summary transcript, not a verbatim log. Torben's three design decisions are recorded verbatim as the options he was shown and the ones he chose; command output quoted below is real; the assistant's reasoning is condensed. No second-hand material. No credentials appeared in the session. One of two transcripts cut from this stretch of the session; the other covers the Mass-Update Gate's counting rule and the measured iteration-budget calibration.

Covers Gitea issue #14, implemented as touch --set/--add/--remove in stack 1.4.0 (dbe2f73). Issue #14 closed, #16 opened for the part deliberately left out.


Turn 1 - "Führe 14 aus und lege mir die nötigen Entscheidungen jeweils mit Hintergrund vor"

The assistant read touch.py before proposing anything, and found the groundwork already in place: validate_fields() validates per field rather than per document, which is exactly what a --set needs. Its docstring explains why - whole-document validation would refuse to bump modified: on a page that is invalid for some unrelated reason, "which is exactly the page most in need of maintenance".

Three decisions were put to Torben, each with the trade-off spelled out.

Decision 1 - which fields may --set write?

Option Consequence
Denylist (chosen) Everything the schema declares, minus a short reasoned block list. A field added to a type-spec works immediately.
Allowlist A maintained set of writable fields. Safer against surprises, but every new schema field needs a code change.
Denylist minus the already-flagged fields Also blocks summary/provenance/confidence-base, which have their own options. Prevents two ways to do one thing, at the cost of a surprising refusal.

Torben chose the denylist. The argument that carried it: an allowlist is a second copy of the schema, and the copy is the one that drifts - AGENTS.md invariant 8 applied to a constant.

Blocked, each naming the command that owns the field instead:

  • type: - changes the page's schema and its directory; that is page-lifecycle.md.
  • confidence: - derived from confidence_base by decay, not authored.
  • related:/sources:/entities:/concepts: - xref maintains the reverse direction and the body bullets; a bare frontmatter write would leave the other half stale.

Decision 2 - what does --set tags=a,b mean for a list that already has values?

Option Consequence
Replace only One rule. Adding a single tag means naming the whole list.
Replace plus --add/--remove (chosen) Three options instead of one, but a single element can be changed without reading the list first. Leaves open what --remove does with an absent element.

Torben chose replace plus --add/--remove.

The open sub-question - --remove on an element that is not there - the assistant decided itself and said so: it succeeds and reports it. Idempotent like xref remove, because a repair command that refuses to run twice is one nobody dares script; but never silent, because a silent no-op looks exactly like a successful removal, and that is how a mistyped element name hides.

Decision 3 - does this pass also deliver raw rename?

Option Consequence
touch --set only (chosen) Stays size/S. The file move becomes possible in two steps (git mv, then touch --set raw_files=…). The remaining gap gets its own issue.
Both in one release Closes #14 fully and removes the intermediate state, at size/M: backward search over all raw_files: references, behaviour with multiple owners, contract lines for two commands.

Torben chose the narrower pass. raw rename became issue #16 (prio/2, size/S).


Turn 2 - implementation

What moved

_coerce_set_value, _parse_set_fields and _check_raw_files_exist left new_page.py for commands/_util.py and lost their leading underscores. Two commands, one implementation - otherwise touch --set would have inherited the comma bug from #12 on day one. A test covers exactly that: an existing raw file whose name contains a comma, referenced through \,, read back as a single path.

raw_files: written by touch gets the same filesystem existence check new performs. It is I/O, not a data shape, so no schema can express it.

Two refusals, deliberately worded differently

A blocked field is a routing problem, so the message names the command that owns it. An unknown field is a typo or the wrong page type, so the message lists what the page actually has - the value is learning that tags was meant:

$ tools/wikitool touch --page "Diff-Reviewable Agent Edits" --set "sources=Source - X"
ERROR `sources` cannot be set with --set: page-reference field - use `wikitool xref add` / `xref remove`

$ tools/wikitool touch --page "Diff-Reviewable Agent Edits" --set "tag=x"
ERROR Type types/concept.md declares no field 'tag'.
  Settable fields for this page: concept_type, confidence_base, created,
  modified, provenance, summary, tags

A test-harness trap, fixed once

The existing test_touch.py called the Typer callback directly with a full argument list, so three new options broke seven call sites with TypeError: 'OptionInfo' object is not iterable. A callback invoked directly receives OptionInfo objects for whatever the caller omits - the same hazard dist_cmd.py avoids by keeping its logic beside the wrapper. The tests now go through a _touch(**overrides) helper that supplies every option, so the next option costs one line rather than seven.


Turn 3 - the repair that proves it

kb/concepts/Diff-Reviewable Agent Edits.md had been created hours earlier by an ingest whose --set tags= value carried a trailing comma; everything after the separator was lost. That subagent correctly worked through all three ways out and rejected each: touch could not set tags:, hand-editing frontmatter comes too close to invariant 1, and rm + new would have broken the concepts: reference the source page already held. The page kept [agent-workflow] permanently, because of a comma.

Against the real corpus:

$ tools/wikitool touch --page "Diff-Reviewable Agent Edits" --add "tags=context-engineering,tooling"
  tags: added 'context-engineering', 'tooling'
OK Touched kb/concepts/Diff-Reviewable Agent Edits.md

$ tools/wikitool touch --page "Diff-Reviewable Agent Edits" --add "tags=context-engineering" --no-date
OK 'Diff-Reviewable Agent Edits' already up to date; nothing to change.

$ tools/wikitool touch --page "Diff-Reviewable Agent Edits" --remove "tags=vertippt" --no-date
  tags: not present, nothing removed: 'vertippt'
OK 'Diff-Reviewable Agent Edits' already up to date; nothing to change.

The page now carries [agent-workflow, context-engineering, tooling].


The defect this closed, stated once

A field new wrote once - tags:, raw_files:, source_url: - was afterwards unreachable. touch did not know it; hand-editing frontmatter is what the tool exists to prevent; deleting and recreating the page breaks every reference already pointing at it. And new is not idempotent, so the window to get the value right was exactly one command wide.

The evidence that this was a rate rather than an accident: three failures across three consecutive ingests on the same day, at two different fields, by three different agents. One of them was a trailing comma.


Outcome

Artifact Result
Stack version 1.4.0 (MINOR - new capability, backwards compatible)
Commit dbe2f73 - 9 files
Tests 674 passing, in the normal and the hardened environment
Gitea #14 closed with the three decisions recorded; #16 opened (prio/2, size/S)
Corpus The unrepairable page repaired