Files
jason.woltje 158205c5cc lead: commit darkwing's untracked evidence (348 files: queue-137 queue-141 queue-150 queue-162 queue-165 queue-166 queue-167 queue-169 queue-179 queue-180 queue-181 queue-182 queue-185 queue-186 queue-187 queue-188 queue-190 queue-93)
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.
2026-11-02 15:05:14 -06:00

116 lines
7.9 KiB
Diff

diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index e0447341c..3a9de2aa7 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -404,9 +404,12 @@ refuses `capture-failed` before it stops anything. Adopt uses the same rule.
full id.
3. Run `activate ID BUSINESS --plan` and take the output to Mos. It names the
store's schema and the target's, any migration with `--migrate` and the
- decision 96 condition 2 line (Jason present), the commits the run adds,
- and the three install commands. It refuses `copies-unrelated` when neither
- copy is an ancestor of the other.
+ decision 96 condition 2 line (Jason present), the backup, the commits the
+ run adds, and the three install commands. The `backup:` line gives the
+ directory the run backs the stopped store up to, or why it takes none:
+ there is no store file, or the run rolls back with `store-newer` or
+ `migration-needs-flag` before the backup. It refuses `copies-unrelated`
+ when neither copy is an ancestor of the other.
4. Mos removes any bus drop-in that overrides ExecStart or WorkingDirectory;
`activate` refuses `drop-in-override` and names the file.
5. With Mos's go, `activate ID BUSINESS`, plus `--migrate` when the plan
diff --git a/packages/cli/tests/stack-release.test.mjs b/packages/cli/tests/stack-release.test.mjs
index e85968006..a2c0e720c 100644
--- a/packages/cli/tests/stack-release.test.mjs
+++ b/packages/cli/tests/stack-release.test.mjs
@@ -380,6 +380,7 @@ async function seed(e,{copy=B,pm=false,store='3g',pointer:how='adopt'}={}){
const rolledBack=(code,to=B)=>`stack-release: ${code}; rolled back to ${to}, which passed the gate\n`;
const planBackup=(e)=>`backup: the run backs up the stopped store to ${e.data}/backups/stack/<activation>/ before any migration`;
+const planNoBackup='backup: none; the run rolls back before the backup';
const planTail=(e,to,pm=false)=>[`bus: scripts/bus-service.sh install --copy ${to} --copies ${COPIES}${pm?' --pm':''} --claude ${e.claude}`,`Console: scripts/webui-service.sh install --copy ${to} --copies ${COPIES} --port ${e.port} --business acme`,`CLI: scripts/stack-copy.sh wrapper ${to} --copies ${COPIES} --bin ${e.bin}`];
// --- The T table ---
@@ -643,9 +644,9 @@ test('T26 --plan lists the commits an activate adds, after the migration lines',
'migration: 3g to 3h, after a backup of the stopped store','migration steps: 3g to 3h',`needs: --migrate (${mig})`,'needs: Jason present for this run (decision 96 condition 2)'];
const adds=commitLines(`${B}..${C}`);
assert.equal(adds.length,2);assert.match(adds[0],/^ {2}[0-9a-f]{7,} row 904: C$/);assert.match(adds[1],/^ {2}[0-9a-f]{7,} row 903: C1$/);
- for(const [flags,mig] of [[[],'not given; the run would roll back with migration-needs-flag'],[['--migrate'],'given']]){
+ for(const [flags,mig,backup] of [[[],'not given; the run would roll back with migration-needs-flag',planNoBackup],[['--migrate'],'given',planBackup(e)]]){
const r=await run(e,'activate',C,'acme','--plan',...flags);assert.equal(r.code,0,r.err);
- assert.equal(r.out,[...head(mig),planBackup(e),'commits: the run adds 2 (newest first, at most 200):',...adds,...planTail(e,C),''].join('\n'));
+ assert.equal(r.out,[...head(mig),backup,'commits: the run adds 2 (newest first, at most 200):',...adds,...planTail(e,C),''].join('\n'));
}
const same=await run(e,'activate',C1,'acme','--plan');assert.equal(same.code,0,same.err);
assert.ok(same.out.includes(`target schema: 3g\nmigration: none; the store is at the target's version\n${planBackup(e)}\ncommits: the run adds 1 (newest first, at most 200):\n`),same.out);
@@ -1373,6 +1374,31 @@ test('T51 row 185 test 5: an activate with no store file takes no backup',async(
const l=logLines(e).at(-1);assert.deepEqual([l.result,l.backup],['ok',null]);
});
+// Row 186 (#1684): the activate plan prints the backup line only when the run reaches backup_store.
+// Each case plans, checks nothing was written, then runs and checks a backup exists exactly when the plan said so.
+test('T52 row 186: the activate plan\'s backup line says what the run will do',async(t)=>{
+ const newer='migration: none possible; 3z is not in 3h\'s migration chain, so the run would roll back with store-newer';
+ const cases=[
+ ['same schema','3g',C1,[],'migration: none; the store is at the target\'s version',true,0],
+ ['same schema with --migrate','3g',C1,['--migrate'],'migration: none; the store is at the target\'s version',true,0],
+ ['in chain with --migrate','3g',C,['--migrate'],'needs: --migrate (given)',true,0],
+ ['in chain without --migrate','3g',C,[],'needs: --migrate (not given; the run would roll back with migration-needs-flag)',false,1],
+ ['outside the chain','3z',C,[],newer,false,1],
+ ['outside the chain with --migrate','3z',C,['--migrate'],newer,false,1],
+ ];
+ for(const [name,store,to,flags,why,backup,code] of cases)await t.test(name,async(t)=>{
+ const e=await mk(t);await seed(e,{store});
+ const before=fp(e),data=tree(e.data),n=calls(e).length,logs=logLines(e).length;
+ const p=await run(e,'activate',to,'acme','--plan',...flags);assert.equal(p.code,0,p.err);assert.equal(p.err,'');
+ const out=p.out.split('\n');assert.ok(out.includes(why),p.out);
+ assert.deepEqual(out.filter((x)=>x.startsWith('backup')),[backup?planBackup(e):planNoBackup]);
+ assert.deepEqual(fp(e),before);assert.deepEqual(tree(e.data),data);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
+ assert.ok(!existsSync(join(e.data,'backups')));
+ const r=await run(e,'activate',to,'acme',...flags,'--timeout','3');assert.equal(r.code,code,r.out+r.err);
+ assert.equal(existsSync(join(e.data,'backups','stack')),backup);assert.equal(logLines(e).at(-1).backup??null,backup?logLines(e).at(-1).activation:null);
+ });
+});
+
// Row 178: proof metadata uses temp fixture files, never the installed slot.
async function seedProof(e) {
await seed(e);
diff --git a/scripts/stack-release.sh b/scripts/stack-release.sh
index f8051d81c..5779b64f7 100755
--- a/scripts/stack-release.sh
+++ b/scripts/stack-release.sh
@@ -797,6 +797,9 @@ plan() {
echo "store schema: $S_SCHEMA ($S_FROM)"
echo "target schema: $T_SCHEMA"
[ "$S_SCHEMA" = "$P_SCHEMA" ] || [ -n "$S_CHANGED" ] || echo "note: the pointer says $P_SCHEMA; the run reads the stopped store and goes by that"
+ # Row 186: set when run_steps would roll back with store-newer or migration-needs-flag, which
+ # come before backup_store. A read the bus stopped under judges nothing, so it keeps the backup line.
+ local early=""
if [ -n "$S_CHANGED" ]; then
: # no migration or restore line from a read the bus stopped under
elif [ "$S_SCHEMA" = "$T_SCHEMA" ]; then
@@ -806,17 +809,21 @@ plan() {
echo "migration steps: $(plan_steps)"
echo "needs: --migrate$([ "$MIGRATE" = 1 ] && echo ' (given)' || echo ' (not given; the run would roll back with migration-needs-flag)')"
echo "needs: Jason present for this run (decision 96 condition 2)"
+ [ "$MIGRATE" = 1 ] || early=1
elif [ "$OP" = activate ]; then
echo "migration: none possible; $S_SCHEMA is not in $T_SCHEMA's migration chain, so the run would roll back with store-newer"
+ early=1
else
echo "restore: the store must go back to $T_SCHEMA; needs --restore $(from_log backup || echo ACTIVATION)"
fi
if [ "$OP" != activate ]; then
: # rollback takes no backup of its own
- elif [ -e "$STORE" ]; then
- echo "backup: the run backs up the stopped store to $DATA/backups/stack/<activation>/ before any migration"
- else
+ elif [ ! -e "$STORE" ]; then
echo "backup: none; there is no store file"
+ elif [ -n "$early" ]; then
+ echo "backup: none; the run rolls back before the backup"
+ else
+ echo "backup: the run backs up the stopped store to $DATA/backups/stack/<activation>/ before any migration"
fi
plan_commits
[ "$P_PENDING" = 0 ] || echo "pm: the pointer is pending (activate --pm-after-harness); the bus runs without its PM"