diff --git a/CHANGES.md b/CHANGES.md index 8756188..32235d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse. --- -## 7.1.0-beta.18 - 2026-09-26 - Command records, Content migrations group: one line per cause, examples, prohibitions +## 7.1.0-beta.19 - 2026-09-26 - Command records, Private instances group: one line per cause, examples, prohibitions **Author:** Torben Nehmer @@ -87,6 +87,7 @@ concern - readable here, never shipped as something to parse. - Command records, Types, instructions and docs group: one line per cause, examples, prohibitions - Command records, Telemetry group: examples, the missing --fail-on-error exit line - Command records, Content migrations group: one line per cause, examples, prohibitions +- Command records, Private instances group: one line per cause, examples, prohibitions ### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them @@ -316,6 +317,13 @@ never advance the version with `baseline --force` or by editing `.wikitool-kb.js behind counting marker pairs rather than comparing their names, and behind an offered migration ignoring the chain, moved into the `verify` and `done` docstrings. +### Command records, Private instances group: one line per cause, examples, prohibitions + +`upstream merge` and `upstream verify` rewritten the same way; text only. `upstream merge`'s +single paragraph is now one bullet per step of the merge, and its exit-1 causes - including the +fetch failure, a failing git step inside the open merge, and the post-commit leak, which its old +record mentioned only in passing - each carry their own reaction. + --- ## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join diff --git a/VERSION b/VERSION index b69f385..4f97afe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0-beta.18 +7.1.0-beta.19 diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index 1752d16..99fab0e 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -3039,18 +3039,51 @@ Take a stack update into a private instance's branch, machinery only. - budget: counted - network: no +**EXAMPLES** + +- `tools/wikitool upstream merge` +- `tools/wikitool upstream merge --remote upstream --branch main --no-fetch` + **EXIT STATUS** - 0 success -- 1 Dirty working tree, a merge already in progress, the remote does not resolve, git refused to open the merge at all (unrelated histories), or a real conflict remains in `tools/`/`types/`/`instructions/` after the content stages and stack-owned paths were restored +- 1 Dirty working tree, or a merge already in progress +- 1 The remote does not resolve, the fetch failed, or `HEAD` does not resolve +- 1 git refused to open the merge at all (unrelated histories); nothing was touched +- 1 A real conflict remains in `tools/`/`types/`/`instructions/` after the content stages and stack-owned paths were restored; the merge is left open +- 1 A git step failed inside the open merge (`git checkout MERGE_HEAD -- ` or `git commit --no-edit`) +- 1 The postcheck after the commit found a leak; the merge commit already exists **ON FAILURE** -- Dirty working tree, a merge already in progress, the remote does not resolve, git refused to open the merge at all (unrelated histories), or a real conflict remains in `tools/`/`types/`/`instructions/` after the content stages and stack-owned paths were restored -> **Not idempotent, and not safe to retry unchanged.** For a dirty tree or an in-progress merge: fix the named precondition and retry once. For a real conflict: **do not retry, do not force** - resolve the named paths by hand (take the upstream side, or re-file the local change as an issue against the public repo per `instructions/private-instance.md`) and either `git commit --no-edit` yourself or `git merge --abort`. If the postcheck after commit finds a leak, the merge commit already exists and is **not** rolled back automatically - inspect it by hand; this is a bug report, not a retry +- Dirty working tree, or a merge already in progress -> Fix the named precondition and retry once +- The remote does not resolve, the fetch failed, or `HEAD` does not resolve -> Fix `--remote`/`--branch` or the repository state, then retry once +- git refused to open the merge at all (unrelated histories); nothing was touched -> Do not retry unchanged - report it to the user +- A real conflict remains in `tools/`/`types/`/`instructions/` after the content stages and stack-owned paths were restored; the merge is left open -> **Do not retry, do not force** - resolve the named paths by hand (take the upstream side, or re-file the local change as an issue against the public repo per `instructions/private-instance.md`) and either `git commit --no-edit` yourself or `git merge --abort` +- A git step failed inside the open merge (`git checkout MERGE_HEAD -- ` or `git commit --no-edit`) -> Do not retry unchanged - inspect the open merge by hand +- The postcheck after the commit found a leak; the merge commit already exists -> It is **not** rolled back automatically - inspect it by hand; this is a bug report, not a retry + +**NEVER** + +- Never retry a failed merge unchanged, and never force. **NOTES** -The code procedure behind `instructions/private-instance.md` § "Taking a stack update". Refuses on a dirty working tree, a merge already in progress, or a remote that does not resolve; WARNs (does not block) when `.wikitool-remotes.json` is absent, pointing at the setup step that arms it. Fetches `/` (unless `--no-fetch`) and reports "already up to date" if nothing new exists. Otherwise opens `git merge --no-commit --no-ff /` - and stops, untouched, if git refused to open a merge at all (unrelated histories), since without a `MERGE_HEAD` every stack path would read as "the upstream deleted it". Then forces every content stage (`kb/`, `raw/`, `work/`, `reports/`) back to the local side by removing **only the paths tracked in either tree** and checking `HEAD`'s back out - never the stage directory wholesale, because `reports/` is gitignored apart from its contract and holds local, non-recomputable data (telemetry traces `eval score` reads, saved eval and lint reports) that no merge has business deleting. Then restores from the upstream side exactly the paths `chemenu.ownership.is_stack_owned` recognises as machinery (`/CONTRACT.md`, and anything ending `.template` under a content stage) - including a deletion, if the upstream removed one. A real conflict left in `tools/`, `types/` or `instructions/` after that leaves the merge open, uncommitted, and exits 1 rather than guessing. Commits with `git commit --no-edit`, then re-checks the resulting range with the same logic as `upstream verify`; a finding there is a loud, uncommitted-nothing-rolled-back error, because the merge commit already exists and needs a human's eyes, not an automatic repair. Never pushes. Not idempotent - see the tool error contract below +- Refuses on a dirty working tree, a merge already in progress, or a remote that does not resolve. WARNs (does not block) when `.wikitool-remotes.json` is absent, pointing at the setup step that arms it. +- Fetches `/` (unless `--no-fetch`) and reports "already up to date" if nothing new exists. +- Otherwise opens `git merge --no-commit --no-ff /`, and stops, untouched, if git refused to open a merge at all (unrelated histories). +- Forces every content stage (`kb/`, `raw/`, `work/`, `reports/`) back to the local side by removing **only the paths tracked in either tree** and checking `HEAD`'s back out - never the stage directory wholesale, so untracked and ignored local data under a stage (telemetry traces, saved eval and lint reports) is never deleted. +- Then restores from the upstream side exactly the machinery paths - `/CONTRACT.md` and anything ending `.template` under a content stage - including a deletion, if the upstream removed one. +- A real conflict left in `tools/`, `types/` or `instructions/` after that leaves the merge open, uncommitted, and exits 1 rather than guessing. +- Commits with `git commit --no-edit`, then re-checks the resulting range with the `upstream verify` check; a finding there is a loud error, and the merge commit is not rolled back. +- Never pushes. +- Not idempotent, and not safe to retry unchanged. + +**SEE ALSO** + +- `instructions/private-instance.md` § "Taking a stack update" - the procedure this implements +- `wikitool upstream verify` - the same check on any revision range +- `wikitool publish` - pushes the merge afterwards #### `upstream verify` @@ -3068,18 +3101,37 @@ Compare two revisions: did anything under a content stage change except through - budget: exempt - network: no +**EXAMPLES** + +- `tools/wikitool upstream verify --since HEAD~1` +- `tools/wikitool upstream verify --since v7.0.0 --until HEAD` + **EXIT STATUS** - 0 success -- 1 A leak was found (content changed under a content stage through a path that is not stack-owned), or `--since`/`--until` is not a revision in this repository +- 1 A leak: content changed under a content stage through a path that is not stack-owned +- 1 `--since`/`--until` is not a revision in this repository **ON FAILURE** -- A leak was found (content changed under a content stage through a path that is not stack-owned), or `--since`/`--until` is not a revision in this repository -> A finding is not fixed by re-running - it names the paths that leaked. Fix the revision argument and retry for the second case +- A leak: content changed under a content stage through a path that is not stack-owned -> A finding is not fixed by re-running - it names the paths that leaked +- `--since`/`--until` is not a revision in this repository -> Fix the revision argument and retry + +**NEVER** + +- Never re-run to make a leak finding go away. **NOTES** -Shares its check with `upstream merge`'s own postcheck, so a hand-resolved merge conflict, or a `dist upgrade`, can be verified the same way. Exit 1 with the offending paths if anything leaked; otherwise reports which stack-owned paths legitimately moved. Read-only and exempt from the Iteration Budget Gate, like `migrate verify` +- Compares `--since` with `--until` (default `HEAD`): did anything under a content stage change except through a stack-owned path? +- The same check `upstream merge` runs after its commit, so a hand-resolved merge conflict, or a `dist upgrade`, can be verified the same way. +- Exits 1 with the offending paths if anything leaked; otherwise reports which stack-owned paths legitimately moved. +- Read-only and exempt from the Iteration Budget Gate. + +**SEE ALSO** + +- `wikitool upstream merge` - runs this check after its commit +- `instructions/private-instance.md` - the private-instance workflow ### Instance health diff --git a/tools/chemenu/commands/upstream_cmd.py b/tools/chemenu/commands/upstream_cmd.py index 21a11d5..1567765 100644 --- a/tools/chemenu/commands/upstream_cmd.py +++ b/tools/chemenu/commands/upstream_cmd.py @@ -250,42 +250,75 @@ def _merge_success_message( atomic="**No** - can leave an open, uncommitted merge behind on refusal after fetching", budget=cli_contract.Budget.COUNTED, ), - notes="The code procedure behind `instructions/private-instance.md` § \"Taking a stack " - "update\". Refuses on a dirty working tree, a merge already in progress, or a remote that " - "does not resolve; WARNs (does not block) when `.wikitool-remotes.json` is absent, pointing " - "at the setup step that arms it. Fetches `/` (unless `--no-fetch`) and " - "reports \"already up to date\" if nothing new exists. Otherwise opens " - "`git merge --no-commit --no-ff /` - and stops, untouched, if git refused " - "to open a merge at all (unrelated histories), since without a `MERGE_HEAD` every stack " - "path would read as \"the upstream deleted it\". Then forces every content stage (`kb/`, " - "`raw/`, `work/`, `reports/`) back to the local side by removing **only the paths tracked " - "in either tree** and checking `HEAD`'s back out - never the stage directory wholesale, " - "because `reports/` is gitignored apart from its contract and holds local, non-recomputable " - "data (telemetry traces `eval score` reads, saved eval and lint reports) that no merge has " - "business deleting. Then restores from the upstream side exactly the paths " - "`chemenu.ownership.is_stack_owned` recognises as machinery (`/CONTRACT.md`, and " - "anything ending `.template` under a content stage) - including a deletion, if the upstream " - "removed one. A real conflict left in `tools/`, `types/` or `instructions/` after that " - "leaves the merge open, uncommitted, and exits 1 rather than guessing. Commits with " - "`git commit --no-edit`, then re-checks the resulting range with the same logic as " - "`upstream verify`; a finding there is a loud, uncommitted-nothing-rolled-back error, " - "because the merge commit already exists and needs a human's eyes, not an automatic repair. " - "Never pushes. Not idempotent - see the tool error contract below", - failures=(cli_contract.Failure( - label="", - cause="Dirty working tree, a merge already in progress, the remote does not resolve, " - "git refused to open the merge at all (unrelated histories), or a real conflict remains " - "in `tools/`/`types/`/`instructions/` after the content stages and stack-owned paths " - "were restored", - reaction="**Not idempotent, and not safe to retry unchanged.** For a dirty tree or an " - "in-progress merge: fix the named precondition and retry once. For a real conflict: " - "**do not retry, do not force** - resolve the named paths by hand (take the upstream " - "side, or re-file the local change as an issue against the public repo per " - "`instructions/private-instance.md`) and either `git commit --no-edit` yourself or " - "`git merge --abort`. If the postcheck after commit finds a leak, the merge commit " - "already exists and is **not** rolled back automatically - inspect it by hand; this is " - "a bug report, not a retry", - ),), + notes=( + "Refuses on a dirty working tree, a merge already in progress, or a remote that does " + "not resolve. WARNs (does not block) when `.wikitool-remotes.json` is absent, pointing " + "at the setup step that arms it.", + "Fetches `/` (unless `--no-fetch`) and reports \"already up to date\" " + "if nothing new exists.", + "Otherwise opens `git merge --no-commit --no-ff /`, and stops, " + "untouched, if git refused to open a merge at all (unrelated histories).", + "Forces every content stage (`kb/`, `raw/`, `work/`, `reports/`) back to the local " + "side by removing **only the paths tracked in either tree** and checking `HEAD`'s back " + "out - never the stage directory wholesale, so untracked and ignored local data under " + "a stage (telemetry traces, saved eval and lint reports) is never deleted.", + "Then restores from the upstream side exactly the machinery paths - `/CONTRACT.md` " + "and anything ending `.template` under a content stage - including a deletion, if the " + "upstream removed one.", + "A real conflict left in `tools/`, `types/` or `instructions/` after that leaves the " + "merge open, uncommitted, and exits 1 rather than guessing.", + "Commits with `git commit --no-edit`, then re-checks the resulting range with the " + "`upstream verify` check; a finding there is a loud error, and the merge commit is not " + "rolled back.", + "Never pushes.", + "Not idempotent, and not safe to retry unchanged.", + ), + failures=( + cli_contract.Failure( + cause="Dirty working tree, or a merge already in progress", + reaction="Fix the named precondition and retry once", + ), + cli_contract.Failure( + cause="The remote does not resolve, the fetch failed, or `HEAD` does not resolve", + reaction="Fix `--remote`/`--branch` or the repository state, then retry once", + ), + cli_contract.Failure( + cause="git refused to open the merge at all (unrelated histories); nothing was " + "touched", + reaction="Do not retry unchanged - report it to the user", + ), + cli_contract.Failure( + cause="A real conflict remains in `tools/`/`types/`/`instructions/` after the " + "content stages and stack-owned paths were restored; the merge is left open", + reaction="**Do not retry, do not force** - resolve the named paths by hand (take the " + "upstream side, or re-file the local change as an issue against the public repo per " + "`instructions/private-instance.md`) and either `git commit --no-edit` yourself or " + "`git merge --abort`", + ), + cli_contract.Failure( + cause="A git step failed inside the open merge (`git checkout MERGE_HEAD -- ` " + "or `git commit --no-edit`)", + reaction="Do not retry unchanged - inspect the open merge by hand", + ), + cli_contract.Failure( + cause="The postcheck after the commit found a leak; the merge commit already exists", + reaction="It is **not** rolled back automatically - inspect it by hand; this is a bug " + "report, not a retry", + ), + ), + examples=( + "tools/wikitool upstream merge", + "tools/wikitool upstream merge --remote upstream --branch main --no-fetch", + ), + never=( + "Never retry a failed merge unchanged, and never force.", + ), + see_also=( + "`instructions/private-instance.md` § \"Taking a stack update\" - the procedure this " + "implements", + "`wikitool upstream verify` - the same check on any revision range", + "`wikitool publish` - pushes the merge afterwards", + ), )) @app.command("merge") def merge_command( @@ -431,17 +464,37 @@ def _verify_success_message(stack_moved: list[str], since: str, until: str) -> s atomic="Read-only", budget=cli_contract.Budget.EXEMPT, ), - notes="Shares its check with `upstream merge`'s own postcheck, so a hand-resolved merge " - "conflict, or a `dist upgrade`, can be verified the same way. Exit 1 with the offending " - "paths if anything leaked; otherwise reports which stack-owned paths legitimately moved. " - "Read-only and exempt from the Iteration Budget Gate, like `migrate verify`", - failures=(cli_contract.Failure( - label="", - cause="A leak was found (content changed under a content stage through a path that is " - "not stack-owned), or `--since`/`--until` is not a revision in this repository", - reaction="A finding is not fixed by re-running - it names the paths that leaked. Fix the " - "revision argument and retry for the second case", - ),), + notes=( + "Compares `--since` with `--until` (default `HEAD`): did anything under a content stage " + "change except through a stack-owned path?", + "The same check `upstream merge` runs after its commit, so a hand-resolved merge " + "conflict, or a `dist upgrade`, can be verified the same way.", + "Exits 1 with the offending paths if anything leaked; otherwise reports which " + "stack-owned paths legitimately moved.", + "Read-only and exempt from the Iteration Budget Gate.", + ), + failures=( + cli_contract.Failure( + cause="A leak: content changed under a content stage through a path that is not " + "stack-owned", + reaction="A finding is not fixed by re-running - it names the paths that leaked", + ), + cli_contract.Failure( + cause="`--since`/`--until` is not a revision in this repository", + reaction="Fix the revision argument and retry", + ), + ), + examples=( + "tools/wikitool upstream verify --since HEAD~1", + "tools/wikitool upstream verify --since v7.0.0 --until HEAD", + ), + never=( + "Never re-run to make a leak finding go away.", + ), + see_also=( + "`wikitool upstream merge` - runs this check after its commit", + "`instructions/private-instance.md` - the private-instance workflow", + ), )) @app.command("verify") def verify_command(