feat(install): upgrade an install without clobbering what the project changed - #4
Merged
Conversation
… changed
The core is copied into each project byte-for-byte, so a change here reached them only through a manual
`--force` per project — and `--force` cannot tell a stale copy from one the project edited, so the
choice was between overwriting local fixes and never updating at all. With one project that is
invisible. At five it means five versions of the loop running under one name, and a harness-improver
proposal applied here reaching none of them.
install.sh upgrade <target> [--dry-run] [--force]
install.sh upgrade --all [--dry-run]
install.sh installs
upgrade re-applies the shape the project was installed with — stack, tools, skills, agents, scripts,
hooks — read back from .ai-tooling.json. Changing that shape stays a re-install, deliberately.
The distinction that makes it safe is a hash per installed file, recorded in the manifest at write
time. Each destination is then one of four things:
current identical to canonical — nothing to do
stale differs from canonical, matches the recorded hash → nobody here touched it → replaced
modified differs from both → this project edited it → reported and KEPT
unknown no recorded hash (pre-2.3.0 manifest, or a file install skipped) → treated as modified
A kept file carries its OLD hash into the new manifest. Re-hashing a file we deliberately did not write
would record the local edit as ours, and the next upgrade would read it as merely stale and overwrite
it — losing the change the previous run protected. Caught by running upgrade three times in a row
against an edited file; the test asserts the edit survives all three.
Exit is non-zero while any modified file is outstanding: an upgrade that left files behind is not fully
applied. --force overwrites them and exits 0.
- A registry (~/.config/ai-tooling/installs, ADW_REGISTRY to relocate) is appended at install time so
`--all` has something to iterate. A path that has gone away is reported and skipped, never removed
behind the operator's back.
- The manifest gains files{} plus the agents/scripts/rules flags, and is written by python3 rather than
printf — a hash map is not worth hand-rolling JSON for.
- doctor reports installed vs canonical version and says BEHIND with the command to fix it.
- file_hash degrades across shasum → sha256sum → openssl; a machine with none of them reports every
differing file as modified rather than guessing.
VERSION 2.2.0 → 2.3.0, and the discipline that goes with it: the version moves with every core change,
because that is what lets doctor and installs tell which projects are behind.
Tests: 27 new assertions in tests/cases/install-upgrade.sh, driving a real install into a throwaway
repo from a throwaway copy of this checkout, then moving that copy ahead. Covers the manifest shape,
the registry, the four classifications, the dry run writing nothing, the three-upgrade regression
above, --force, and four misuse paths. ADW_REGISTRY keeps the operator's real registry out of it.
Also widens the portability rule: comments and double-quoted strings are stripped before scanning for
GNU-only tools, and a line guarded by `command -v` is compliant — `file_hash` names sha256sum in both
a guard and a warning string, and neither is a portability defect. The blind spot this accepts (a call
hidden inside a quoted command string) is written down in the test.
Suite: 143 passed, 0 failed, 1 known gap, under bash 5.2 and 3.2.
Refs RUS-192
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #3. Closes RUS-192.
The problem
The core is copied into each project byte-for-byte, so a change here reached them only through a manual
--forceper project. And--forcecannot tell a stale copy from one the project edited, so the choicewas between overwriting local fixes and never updating at all.
With one project that is invisible. At five it means five versions of the loop running under one name,
and a
harness-improverproposal applied here reaching none of them.The change
upgradere-applies the shape the project was installed with — stack, tools, skills, agents, scripts,hooks — read back from
.ai-tooling.json. Changing that shape stays a re-install, deliberately.What makes it safe is a hash per installed file, recorded in the manifest at write time. Each
destination is then one of four things:
currentstalemodifiedunknownExit is non-zero while any modified file is outstanding — an upgrade that left files behind is not fully
applied.
--forceoverwrites them and exits 0.The bug this nearly shipped with
A kept file has to carry its old hash into the new manifest. Re-hashing a file we deliberately did
not write records the local edit as ours, and the next upgrade reads it as merely stale and overwrites
it — losing the change the previous run protected.
Found by running
upgradethree times against an edited file and watching the classification flip frommodifiedtostaleon the second. The test now asserts the edit survives all three runs.Also
~/.config/ai-tooling/installs,ADW_REGISTRYto relocate) is appended at install time so--allhas something to iterate. A path that has gone away is reported and skipped, never removedbehind the operator's back.
doctorreports installed vs canonical version and saysBEHINDwith the command to fix it.file_hashdegradesshasum→sha256sum→openssl; with none of them every differing file isreported as modified rather than guessed at.
python3now — a hash map is not worth hand-rolling JSON for.VERSION2.2.0 → 2.3.0, and the discipline with it: the version moves with every core change, becausethat is what lets
doctorandinstallstell which projects are behind.How to verify
The case drives a real install into a throwaway repo from a throwaway copy of this checkout, then moves
that copy ahead — no mocking of the thing under test. By hand:
Not verified
--allhas only ever run over a one-entry registry. The loop is trivial, but "trivial" is what therest of this PR is about.
ahead artificially, one file at a time.
filesmap, so the first upgrade of an existing install classifieseverything as
unknown→ modified → kept, and reports a long list. That is the safe direction,but it means the first upgrade of a real project will need
--forceafter you have read the list.Worth knowing before running it on something you care about.
doctor,uninstalland hook registration remain untested, as the runner's footer now says.