docs: dist upgrade - Stamp-Semantik nach --keep-local, drei Eigentumsklassen in docs/ (#7)
CI / verify (push) Successful in 54s
Release / release (push) Successful in 34s

Files changed:
- CHANGES.md
- VERSION
- docs/ownership-and-templates.md
- tools/CONTRACT.md
This commit is contained in:
2026-09-04 12:41:50 +02:00
parent cd81ba3d4f
commit 368438e48c
4 changed files with 59 additions and 10 deletions
+25 -1
View File
@@ -35,7 +35,7 @@ dev-checkout concern - readable here, never shipped as something to parse.
--- ---
## 4.5.0-beta.4 - 2026-09-04 - dist upgrade: apply a stack update, not just detect one (#7) ## 4.5.0-beta.5 - 2026-09-04 - dist upgrade: Stamp-Semantik nach --keep-local benannt, docs/ownership-and-templates.md auf drei Eigentumsklassen nachgezogen
**Author:** Torben Nehmer **Author:** Torben Nehmer
@@ -45,6 +45,7 @@ dev-checkout concern - readable here, never shipped as something to parse.
- upstream merge: combined-commit regression test (edit+add+delete+contract+template+contract-delete in one commit) - upstream merge: combined-commit regression test (edit+add+delete+contract+template+contract-delete in one commit)
- upstream merge: keep gitignored local data under a content stage, refuse a merge git never opened, report what actually changed - upstream merge: keep gitignored local data under a content stage, refuse a merge git never opened, report what actually changed
- dist upgrade: apply a stack update, not just detect one (#7) - dist upgrade: apply a stack update, not just detect one (#7)
- dist upgrade: Stamp-Semantik nach --keep-local benannt, docs/ownership-and-templates.md auf drei Eigentumsklassen nachgezogen
<!-- /wikitool:bumps --> <!-- /wikitool:bumps -->
Die Prosa zu 4.4.0 - Changelog-Eintrag, `docs/version-model.md`, `instructions/dev/version-parts.md`, Die Prosa zu 4.4.0 - Changelog-Eintrag, `docs/version-model.md`, `instructions/dev/version-parts.md`,
@@ -208,6 +209,29 @@ Collection-Templates umgeht, deren Namen eine fremde Instanz gar nicht hat - es
`upstream merge` und schreibt sie, was ein eigenes Issue gegen den Export wäre, keins gegen das `upstream merge` und schreibt sie, was ein eigenes Issue gegen den Export wäre, keins gegen das
Upgrade. Upgrade.
**Sechster Bump: die ungeprüfte Phase nachgeholt.** Der Abschluss des vorigen Bumps lief auf
Sonnet, und `stack-dev` Schritt 6 verlangt dort genau zwei Dinge, die kein Check erzwingt:
Issue-Body und `docs/`-Veralterung. Der Body war gemacht, die `docs/`-Prüfung nicht - sie wurde
benannt statt durchgeführt. Nachgeholt auf Opus, mit einem Fund.
`docs/ownership-and-templates.md` § „The consequence in practice" beschrieb ein Upgrade als
Zweiteilung: verbatim überschreiben, `.template`-gestützte Dateien liegen lassen - und begründete
den ersten Teil damit, dass verbatim ausgelieferte Dateien „safe to replace wholesale" seien,
weil sie „never instance-specific to begin with" waren. Genau diese Annahme trifft `dist upgrade`
nicht: eine Instanz *kann* eine verbatim ausgelieferte Datei angefasst haben, und die sha256 je
Datei existiert, um das zu erkennen, statt es vorauszusetzen. Dazu fehlte die dritte Klasse ganz -
die einmalig gesäten, danach instanzeigenen Pfade (`.wikitool-kb.json`, `CHANGES.md`,
`kb/log.md`, `raw/*/.gitkeep`), die im Stamp stehen wie jede andere Datei und deshalb aktiv
ausgeschlossen werden müssen. Die Seite nennt jetzt drei Klassen und die engere praktische Regel:
überschreibe die verbatim ausgelieferten Dateien, *die diese Instanz nicht angefasst hat*.
Dazu eine Präzisierung in `tools/CONTRACT.md`, die vorher nirgends stand: nach `--keep-local`
wird der neue Stamp trotzdem vollständig geschrieben, trägt also die Release-Summe auch für
Dateien, die bewusst *nicht* geschrieben wurden. Der Stamp ist die Vergleichsbasis für den
nächsten Lauf, kein wörtliches Inventar der Platte - und genau das hält eine übersprungene Datei
bei jedem weiteren Lauf als abweichend gemeldet, statt sie nach einmaligem Überspringen still als
aktuell zu führen.
--- ---
## 4.4.0 - 2026-09-03 - Versionskandidat statt Bump-pro-Release: VERSION traegt -beta.N, version release fixiert ## 4.4.0 - 2026-09-03 - Versionskandidat statt Bump-pro-Release: VERSION traegt -beta.N, version release fixiert
+1 -1
View File
@@ -1 +1 @@
4.5.0-beta.4 4.5.0-beta.5
+32 -7
View File
@@ -4,7 +4,8 @@ Chemenu ships two kinds of files side by side, and at a glance they look the sam
plain markdown, both sit in the repo root or under `kb/`, both get read at session start. But a 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), 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 [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`, 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 `SOUL.md`, `kb/CONVENTIONS.md`, `ENVIRONMENT.md` - describe one particular instance, and
overwriting them would silently erase a choice someone made on purpose. overwriting them would silently erase a choice someone made on purpose.
@@ -82,9 +83,33 @@ state rather than a sign setup was skipped.
## The consequence in practice ## The consequence in practice
Running a stack upgrade against an existing instance boils down to: overwrite the verbatim An upgrade sorts every shipped path into three categories, not two - and the third one only
files, leave the `.template`-sourced files alone. The verbatim files are safe to replace becomes visible once an upgrade is a command rather than a hand-run copy:
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 - **Verbatim files** - `AGENTS.md`, `kb/CONTRACT.md`, the per-stage contracts, everything under
once, by a person, for a reason, and nothing about a newer release of the stack's mechanics `tools/`, `types/` and `instructions/` - are the release's to replace.
gives it standing to override that. - **`.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.
+1 -1
View File
@@ -72,7 +72,7 @@ tools/wikitool <command> --help
| `eval sessions [--json]` | List the sessions that have a trace under `reports/telemetry/`, most recent first. Read-only and exempt from the Iteration Budget Gate | | `eval sessions [--json]` | List the sessions that have a trace under `reports/telemetry/`, most recent first. Read-only and exempt from the Iteration Budget Gate |
| `eval score [--session <id>] [--json] [--markdown out.md] [--save] [--fail-on-error]` | Score one traced session: structural state from `lint`'s own checks (L1) plus trajectory rules over the trace (L2) - was a refused call repeated unchanged, was a gate flag passed without that gate having refused anything, did a publish of `kb/` pages go unlogged. Defaults to the current session. `--save` writes `reports/evals/<date>/<session>.{json,md}`. Read-only over `kb/` and exempt from the budget; see [../EVALS.md](../EVALS.md) | | `eval score [--session <id>] [--json] [--markdown out.md] [--save] [--fail-on-error]` | Score one traced session: structural state from `lint`'s own checks (L1) plus trajectory rules over the trace (L2) - was a refused call repeated unchanged, was a gate flag passed without that gate having refused anything, did a publish of `kb/` pages go unlogged. Defaults to the current session. `--save` writes `reports/evals/<date>/<session>.{json,md}`. Read-only over `kb/` and exempt from the budget; see [../EVALS.md](../EVALS.md) |
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead | | `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
| `dist upgrade <source> [--dry-run] [--keep-local] [--prune] [--pre]` | Apply a stack update `dist export` produced - the write half of `version check`. Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, does not block, if it is absent), which must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/*/.gitkeep` - `chemenu.ownership.is_export_stub`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - `chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest for it: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten - the run aborts with the full list unless `--keep-local` says to proceed and leave every one of them untouched. A path in the old stamp but not the new one is reported as no longer part of the release and left alone unless `--prune` is passed, which removes it only if it is still unchanged since installation. Reports the migration chain the new machinery would owe (`chemenu.kb_state.chain` over the *new* tree's `instructions/migrations/`, read via a `directory` argument to `load_migrations`) but never runs any of it - there is no `migrate run`. Refuses before touching the source at all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, `.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* machinery, or the working tree is dirty (not being a git repository at all is a WARN, not a refusal). Refuses after reading the source when: it carries no `VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to the installed one (equal is a no-op success), or it is a pre-release (`-beta.N`) without `--pre`. Reports, but does not block on, a crossed compatibility boundary. Never touches git - no commit, no push (invariant 5). See Gitea #7 and `INSTALL.md` § "Eine Instanz aktualisieren" | | `dist upgrade <source> [--dry-run] [--keep-local] [--prune] [--pre]` | Apply a stack update `dist export` produced - the write half of `version check`. Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, does not block, if it is absent), which must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/*/.gitkeep` - `chemenu.ownership.is_export_stub`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - `chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest for it: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten - the run aborts with the full list unless `--keep-local` says to proceed and leave every one of them untouched. After a `--keep-local` run the new stamp is still written whole, so it records the release's digest for files that were deliberately *not* written: the stamp is the baseline for the next comparison, not a literal inventory of what is on disk. That is what keeps a skipped file diverging - and therefore reported - on every later run, rather than quietly reading as current once it has been skipped once. A path in the old stamp but not the new one is reported as no longer part of the release and left alone unless `--prune` is passed, which removes it only if it is still unchanged since installation. Reports the migration chain the new machinery would owe (`chemenu.kb_state.chain` over the *new* tree's `instructions/migrations/`, read via a `directory` argument to `load_migrations`) but never runs any of it - there is no `migrate run`. Refuses before touching the source at all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, `.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* machinery, or the working tree is dirty (not being a git repository at all is a WARN, not a refusal). Refuses after reading the source when: it carries no `VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to the installed one (equal is a no-op success), or it is a pre-release (`-beta.N`) without `--pre`. Reports, but does not block on, a crossed compatibility boundary. Never touches git - no commit, no push (invariant 5). See Gitea #7 and `INSTALL.md` § "Eine Instanz aktualisieren" |
| `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** | | `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** |
| `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate | | `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate |
| `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate | | `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate |