1b5ffea854
Files changed: - CHANGES.md - VERSION - docs/ownership-and-templates.md - instructions/gates.md - instructions/private-instance.md - tools/CONTRACT.md - tools/chemenu/commands/upstream_cmd.py - tools/chemenu/tests/test_upstream_cmd.py
91 lines
6.0 KiB
Markdown
91 lines
6.0 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 can simply be overwritten by the next release. 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](../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.
|
|
|
|
## The consequence in practice
|
|
|
|
Running a stack upgrade against an existing instance boils down to: overwrite the verbatim
|
|
files, leave the `.template`-sourced files alone. The verbatim files are safe to replace
|
|
wholesale because they were never instance-specific to begin with - identical content going
|
|
back in changes nothing an instance actually decided. 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.
|