d49513bda6
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
174 lines
12 KiB
Markdown
174 lines
12 KiB
Markdown
# 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](../AGENTS.md),
|
|
[kb/CONTRACT.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.
|
|
|
|
<!-- wikitool:toc -->
|
|
## Contents
|
|
|
|
- [Two different kinds of truth](#two-different-kinds-of-truth)
|
|
- [Why silent overwrite is the failure being designed against](#why-silent-overwrite-is-the-failure-being-designed-against)
|
|
- [Why the boundary is a predicate rather than a list](#why-the-boundary-is-a-predicate-rather-than-a-list)
|
|
- [Why a `.template`, not just an absent file](#why-a-template-not-just-an-absent-file)
|
|
- [Where the file boundary used to strain](#where-the-file-boundary-used-to-strain)
|
|
- [The consequence in practice](#the-consequence-in-practice)
|
|
<!-- /wikitool:toc -->
|
|
|
|
## 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](../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](../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](../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](../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](../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](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.
|