Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
9.5 KiB
Row 185: every activate backs up the stopped store
Brief: docs/plans/2026-10-20_activate-always-backs-up.md (blob 2877ca401c, 52ce57dfe). Gitea #1683, P19
task 403. Owner Darkwing, reviewer Filbert, gate owner Sage. It is the precondition for mos-0928 Part A
(agents/sage/work/window-plan/repin-rows-177-185.md).
Files
| File | What |
|---|---|
build.patch |
the change, git diff 52ce57dfe over the paths below |
candidate-manifest.sha256 |
one line per changed path |
- Base:
52ce57dfe. Line numbers are at the base unless marked as the candidate's.scripts/stack-release.shhas not changed since9f5153deb, so the brief's numbers match. - Changed:
scripts/stack-release.sh,packages/cli/tests/stack-release.test.mjsanddocs/TOOLS.md. Nothing outside the brief's list.
Design
The run (change 1)
The migrate branch of run_steps (:835-842) now reads:
if [ "$STORE_SCHEMA" != "$T_SCHEMA" ]; then
in_chain "$STORE_SCHEMA" || rollback_in_run store-newer
[ "$MIGRATE" = 1 ] || rollback_in_run migration-needs-flag
fi
backup_store || rollback_in_run backup
if [ "$STORE_SCHEMA" != "$T_SCHEMA" ]; then
TAKEN="$ACT"
node .../migrate-schema.mjs ... || rollback_in_run migrate-failed
say "migrated the store to $T_SCHEMA"
fi
It sits inside if [ -e "$STORE" ], after the store_unclean and schema reads (:831-833), so:
- a store with a
-walor-shmstill rolls backstore-uncleanwith no backup; - a missing store takes no backup;
- a failed backup rolls back with
backupas before. TAKEN is still empty then, so nothing is restored.
store-newer and migration-needs-flag still roll back before the backup. Those runs never touch the store,
so a backup there would only add a directory, and T6 and T9 keep asserting that no backups/stack appears. I
chose this over "backup first, then the checks" because it keeps the backup tied to a run that goes on to
install the target. The cost is one more if on the schema test.
TAKEN and BACKUP (change 2)
Before this row backup_store set both (:367), because the only backup was a migration's. They now mean two
things:
BACKUPis the backup the run names.backup_storesets it on every backup, solog_line(:52) writes it into every line after the backup, andfrom_log backup(:146) finds it on the ok line that made the copy live.rollback_failed(:571) names it too.TAKENis the backuprollback_in_run(:543) restores. Only the migrate branch sets it, right before the migration. After a gate failure on a same-schema activate the store is the one the run started from, plus whatever the target's bus wrote while it ran. Restoring would throw those writes away for no reason, and every restore moves the live store to a post-attempt directory. So that rollback puts back the units and the wrapper and leaves the store.
The two comments at backup_store and rollback_in_run say this.
--plan (change 3)
plan prints one line after the migration or restore block, for activate only:
- with a store file:
backup: the run backs up the stopped store to <dataRoot>/backups/stack/<activation>/ before any migration, with the real data root; - with no store file:
backup: none; there is no store file.
The activation id is made by the run, so the plan prints <activation> as written. The migration lines are
unchanged. They already say "after a backup of the stopped store". The line is printed on the S_CHANGED
path too (the bus stopped during the read), since a store file is there. For store-newer and
migration-needs-flag the plan already says the run would roll back. I did not add a third form for those.
Rollback and pm-on (change 4)
Unchanged. do_rollback already lets a same-schema rollback take --restore (:891-922): it checks the
backup's SCHEMA against the target's, its DATA_ROOT and STAMP, and the restore guard. A same-schema activate's
backup has SCHEMA equal to the store's, so test 3 needed no script change. pm-on does not call run_steps migrate.
docs/TOOLS.md (change 5)
"Pinned stack update" steps 5 to 7:
- step 5: every activate takes the backup before any migration, the log line names it, a missing store takes
none, and
store-neweror a migration without--migraterolls back first; - step 6: the backup is taken on every activate and restored only when a migration ran; after a same-schema activate the store stays as the failed run left it;
- step 7: a same-schema rollback can take
--restorewith the activate's backup.
Step 3 lists what the plan names and does not mention the new line. The brief assigns steps 5 to 7 only, so I left step 3 for Sage.
Existing tests that change
| Test | Why |
|---|---|
| T5 | asserted no backups/stack after the same-schema B to C1 activate. Now asserts the directory holds exactly the activation's backup (brief test 1 says to update it). |
| T11 | the rollback-failed message now ends , backup <path> after a same-schema activate. Also asserts the log's backup and that no post-attempt store appears. |
| T26 | exact plan output for B to C: the backup line after the needs: lines. The same-schema includes check has the line between migration: none and commits:. |
| T29 | exact plan output for the stopped-during-read case: the backup line after target schema: 3h. |
T6 and T9 are unchanged (above). T7, T8, T13, T14, T14b, T43 and T45 do migrating activates and still pass
as written: T8's migration case still restores. T23, T27, T36 and T44 read plan lines by offset from a line
the backup line comes after, or from the end. T43's [c, c-1] listing has only migrating activates, since
rollbacks take no backup.
The fixture gets one fault, start-writes-row-<copy>: the copy's bus start writes one event before the holder
opens the store. T48 uses it so the store after the failed gate differs from the backup, which makes a restore
visible by sha.
New tests (candidate lines, after T46)
- T47: a same-schema B to C1 activate leaves
backups/stack/<act>/with SHA256SUMS, DATA_ROOT equal to the data root, SCHEMA equal to the store's (3g) and a STAMP. The backup's sum is the store's sha before the run. The run printsbackup: <dir>, and the ok log line'sbackupis the activation. - T48: a gate failure (
healthz-C1) on the same activate, with the target's start writing one row. It rolls backhealthz. The store sha after the rollback equals the store at the stop after the failed gate (the fixture's stop snapshot), that sha differs from the backup's, and no post-attempt store appears. Units, wrapper and pointer are back. - T49: a same-schema
rollback --restore <act>with no rows after STAMP restores and goes live on B at 3g. The store is the backup and the C1 store is kept as the post-attempt store. With oneinstance_namesrow after STAMP it refusespost-backup-eventswith0 event(s), 1 instance_names row(s)and changes nothing, and--events 1restores. - T50:
activate C1 --planprints exactly onebackup:line, the store-file form, and writes nothing (units, wrapper, pointer, store, the data tree and the log). A rollback plan has no backup line. - T51: with the bus stopped and the store files removed, the plan prints
backup: none; there is no store file, and the activate passes with nobackupsdirectory, nobackup:line and a nullbackupin the log.
Mutants
run185.sh runs stack-release.test.mjs whole, then each mutant against T5, T11 and T47 to T51
(--test-name-pattern='^(T5|T11) |row 185'). Each edits the worktree script after a checked backup and puts it
back by the backup's sha.
| Mutant | Expected killers |
|---|---|
| m1: the backup only when the schemas differ (today's code) | T5, T47, T49, T11 |
| m2: TAKEN set by every backup | T48, T11 |
| m3: BACKUP left empty on a same-schema activate | T47, T48, T11 |
Runs
Slot from sage-1233, 2026-10-20T06:46:32.784Z to 06:51:52.217Z. TMPDIR ~/darkwing-scratch/r185/tmp.
| Run | Result | Failing tests |
|---|---|---|
stack-release.test.mjs |
125 tests, 125 pass, 0 fail | none |
| m1: the backup only when the schemas differ | killed | T5, T11, T47, T48, T49 (both subtests) |
| m2: TAKEN set by every backup | killed | T11, T48 |
| m3: BACKUP empty on a same-schema activate | killed | T11, T47, T48 |
T48 also fails under m1, at its log assertion: the line's backup is null. stack-release.sh was back at its
candidate sha after the mutants.
Readers and hash pins
git grep over packages/*/tests/** and scripts/test-*.sh for stack-release and TOOLS.md, full output:
the only reader of scripts/stack-release.sh is stack-release.test.mjs itself (SR, :273, and the copied
script at :838). Nothing reads docs/TOOLS.md, and no test pins a sha256 of any of the three paths.
Candidate
- Base
52ce57dfe.52ce57dfe..origin/refactor(b5d662cab) touches onlyagents/sage/work/window-plan/repin-rows-177-185.md.build.patchapplies cleanly to both (git apply --cached --checkon a scratch index). build.patch: sha256 8f2293de151c1c3b4d96ac6d494f7d9f67e849d8f51b804c6cdad799c4269ae6, patch-id (stable) 774cd23186d54eda08be028f7c39b5212cd1ed75, 3 files, 121 insertions, 16 deletions.candidate-manifest.sha256: sha256 bf87dbba645b5c4443670e4a81f44a99afedad6ab16ae0f125d6ab8de27bce3b.
| Path | sha256 |
|---|---|
scripts/stack-release.sh |
dccb4a10d5b9a6f680a5bdc06277abf39f7d438c0500cb44d5b1c45012de21bc |
packages/cli/tests/stack-release.test.mjs |
94b398246cd87cec282e13147b244cdfa85ea5c98a7bf06a468e1b387692df08 |
docs/TOOLS.md |
a0ea0f560cd2683c2dd38bdeb88fd4bf339be771e0436e623ba554fcb34d6d54 |