Files
chemenu/docs/ownership-and-templates.md
T
torben a9703520a7
CI / verify (push) Successful in 53s
Release / release (push) Successful in 35s
dist export-Doku: Typverzeichnis-Behauptungen nach #67 korrigiert (schliesst #93)
Files changed:
- CHANGES.md
- VERSION
- docs/ownership-and-templates.md
- tools/CONTRACT.md
- tools/chemenu/commands/dist_cmd.py
2026-09-11 13:41:19 +02:00

7.8 KiB

Ownership and Templates

Chemenu ships two kinds of files side by side, and at a glance they look the same: both are plain markdown, both sit in the repo root or under kb/, both get read at session start. But a stack upgrade treats them completely differently. Some - AGENTS.md, kb/CONTRACT.md, the per-stage contracts - are identical in every instance that runs this stack and are the next release's to replace (with one caveat about local edits, below). Others - USER.md, SOUL.md, kb/CONVENTIONS.md, ENVIRONMENT.md - describe one particular instance, and overwriting them would silently erase a choice someone made on purpose.

Two different kinds of truth

The stack-owned files describe how the tool works. kb/CONTRACT.md opens by saying it holds what tools/wikitool enforces or what follows mechanically from how it operates - see kb/CONTRACT.md, lines 10-13. That kind of statement doesn't vary by instance: the compiler behaves the same way regardless of who is running it, so the sentence describing that behavior can be copied byte-for-byte into every checkout without becoming wrong anywhere.

The instance-owned files describe a choice: which language pages are written in, what tone the agent takes, who the operator is, which git remote is authoritative, which MCP servers are reachable. None of that follows from the tool's mechanics - two instances of the identical stack can answer all of these differently and both be correct. AGENTS.md § Personalization frames the split the same way for kb/CONTRACT.md versus kb/CONVENTIONS.md: "the split is by who may change the sentence, not by what it is about." A rule about page structure could in principle have been written per-instance too, but then every instance answering "not German" to setup would be hand-editing a file the stack also ships, and the next dist export merge would hand the instance's own file back to it, discarding the customization.

Why silent overwrite is the failure being designed against

A stack update is meant to be a routine, low-risk operation: pull the latest release, get whatever fixes and features shipped since the last one. That only stays low-risk if the update knows which files it's allowed to touch. If USER.md or kb/CONVENTIONS.md were treated the same as AGENTS.md - shipped and periodically re-copied - an upgrade would quietly replace a description of this operator, in this language, with whatever placeholder or default the stack maintainers wrote. The damage wouldn't be loud: nothing crashes, the files still parse, the agent just starts acting on the wrong premises until someone notices the voice or the language changed.

Keeping the boundary at the file level, rather than trying to merge changes within a shared file, means an upgrade never has to guess which lines are "stack" and which are "instance" - the file itself already answers that.

Why the boundary is a predicate rather than a list

For a while the boundary was written down as a list of paths - once in dist_cmd.py, once in the merge procedure a private instance was told to run by hand, and once in the check that procedure ended with. Three copies of one fact, which is the shape AGENTS.md invariant 8 exists to forbid, and they drifted exactly as predicted: the hand-run procedure was still naming three paths after the collection contracts had moved to the instance's side of the line, so it discarded upstream changes to files it had never heard of, while its own final check excluded the same three paths and therefore reported success.

chemenu/ownership.py replaced the lists with one question - is this path, under a content stage, the stack's or the instance's? - answered by shape rather than by enumeration: <stage>/CONTRACT.md, and anything ending .template. Both consumers ask it, so dist export and wikitool upstream merge cannot disagree, and a machinery file added under a content stage tomorrow is recognised by both without either being edited. The deeper point is not the deduplication: a list has to be maintained by whoever remembers it exists, and the failure mode when nobody does is silence, because a path the list has never heard of simply looks like content.

Why a .template, not just an absent file

The mechanism for instance-owned content is a .template file the distribution ships instead of the real one - USER.md.template, SOUL.md.template, kb/CONVENTIONS.md.template, ENVIRONMENT.md.template. An alternative would have been to ship nothing at all and let a brand-new instance start from a blank page. The template exists because a blank page doesn't tell instructions/setup-instance.md what shape the answer should take, and it gives nothing for a validator to check afterward.

A template carries a placeholder value - a sentinel - in the fields that need a real answer. Setup interviews the operator and replaces the sentinel with what they actually said. That gives doctor a mechanical way to tell "personalized" from "not yet": a file that still contains the sentinel hasn't been through setup, regardless of whether the file exists. That's also why ENVIRONMENT.md only warrants a WARN rather than a FAIL when absent - see AGENTS.md § Environment - while a missing or unfilled USER.md/SOUL.md/kb/CONVENTIONS.md is a harder failure: ENVIRONMENT.md describes one checkout among possibly several and is gitignored for that reason, so its absence is a normal state rather than a sign setup was skipped.

The consequence in practice

An upgrade sorts every shipped path into three categories, not two - and the third one only becomes visible once an upgrade is a command rather than a hand-run copy:

  • Verbatim files - AGENTS.md, kb/CONTRACT.md, the per-stage contracts, everything under tools/, types/ and instructions/ - are the release's to replace.
  • .template-sourced files - USER.md, SOUL.md, kb/CONVENTIONS.md, each kb/<name>/COLLECTION.md, ENVIRONMENT.md, the root: kb type-specs - are never written by an upgrade at all. The distribution ships only the .template beside them, so the filled file is out of reach by construction rather than by a rule someone has to remember.
  • Seeded-once files - .wikitool-kb.json, CHANGES.md, kb/log.md, raw/.gitkeep - are written into a new instance by dist export and belong to the instance from then on. They are the awkward category: they sit in the release stamp's file list like any other shipped file, so an upgrade has to exclude them deliberately (chemenu.ownership.is_export_stub and is_upgrade_preserved). An upgrade that re-seeded them would reset the record of which migrations ran, or erase the changelog the instance wrote for itself.

The first category carries a caveat that the word "verbatim" hides. It says who decides the content, not that overwriting is always safe: an instance can still have edited a verbatim file

  • a patched tools/, a locally adjusted instruction - and an upgrade assuming otherwise would destroy that silently. Avoiding that assumption is the whole reason dist export records a sha256 per shipped file in .wikitool-release.json. wikitool dist upgrade compares every candidate path against the digest recorded when it was installed, overwrites only what still matches, and refuses rather than overwrite what does not.

So the practical rule is narrower than "overwrite the verbatim files, leave the rest alone": overwrite the verbatim files this instance has not touched, never write the other two categories, and make a locally changed file a decision someone takes deliberately instead of one an upgrade takes for them. The template-sourced files were filled in once, by a person, for a reason, and nothing about a newer release of the stack's mechanics gives it standing to override that.