Files
chemenu/docs/ownership-and-templates.md
T
torben d49513bda6
CI / verify (push) Failing after 43s
Release / release (push) Successful in 34s
types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert (schliesst #104)
Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- docs/ownership-and-templates.md
- instructions/migrations/6.0.0-type-guidance-split.md
- instructions/setup-instance.md
- tools/CONTRACT.md
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/types_cmd.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_dist_upgrade.py
- tools/chemenu/tests/test_type_resolver.py
- tools/chemenu/tests/test_types_cmd.py
- tools/chemenu/type_resolver.py
- tools/chemenu/types_core.py
- types/comparison.guidance.md
- types/comparison.md
- types/concept.guidance.md
- types/concept.md
- types/entity.guidance.md
- types/entity.md
- types/source.guidance.md
- types/source.md
- types/type-guidance.md
- types/type-guidance.schema.yaml
- types/type-spec.md
2026-09-15 18:23:38 +02:00

12 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.

Contents

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.

Where the file boundary used to strain

"The file itself already answers that" held for every file above except one shape: a root: kb type-spec used to carry two audiences inside one file.

Its authoring guidance - when to use this type, what each frontmatter field means, how to cite - was instruction to an agent. It read like the stack's own prose because it was the stack's own prose: a later release that learned something about writing entity pages would want to improve it everywhere. Its ## Template block and its layout: titles were the opposite: they became the literal headings of pages this instance writes, in the language this instance chose, and no release had any business touching them.

The same file is where the language question comes apart from the ownership one, and for the same reason: ownership decides who may change a line, its reader decides what language it is in - which is why a root: kb type-spec still keeps English prose around a template block written in its own language. language-boundaries.md has that argument; this page is about ownership alone.

Ownership is per file, so a file carrying both audiences had to give both halves to whoever owned it. The template half was correct that way. The guidance half paid for it: an instance that adopted its type-specs at setup never received an improvement to the guidance again, because dist upgrade wrote the .template beside the adopted file and never the file itself. Nothing broke, and nothing reported it - the instance simply kept reading the guidance it was handed the day it was created.

That was not an argument against the per-file boundary; the boundary is what makes an upgrade safe at all, and merging inside a shared file is the failure the whole section above is about. It was an argument that this particular file was cut in the wrong place - so it was cut again. A root: kb type-spec may now declare guidance:, a repo-relative path to a second, stack-owned file (types/<name>.guidance.md) holding exactly the half that used to be stranded: when to use the type, when not to, and mechanism-level advice that holds for every instance. That file ships verbatim and upgrades like any other machinery file, whether or not the type-spec that links it has ever been adopted. types/type-spec.md §§ "Who owns a type-spec" and "Anatomy of a type" hold the current shape; tools/wikitool types describe <name> composes both files into one answer, so an agent asking for a type's contract never needs to know it comes from more than one file. An instance that adopted its type-specs before this split existed takes it as an offered migration rather than something an upgrade applies on its own - the same reasoning as any other instance-owned file in the middle category below, spelled out for this one case because it is the case that motivated the category existing at all.

A type-spec that declares no guidance: - one an instance writes entirely for itself - is unaffected: it is still described from its own body alone, the way every type-spec worked before guidance: existed. The split is optional exactly where there is no stack-owned improvement to receive.

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. This is where a root: kb type-spec's optional types/<name>.guidance.md sits: verbatim, even though the type-spec it documents (below) is not.
  • .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.