stack-release: round 3 on row 190's landing, --sso for a named instance tested, T53/T55 usage checks carry --sso (row 200, #1698, sage-1556, sage-1569)

This commit is contained in:
2026-10-20 18:18:22 -05:00
parent d395b6c95b
commit 9df40878fc
6 changed files with 96 additions and 26 deletions
+57 -13
View File
@@ -3,15 +3,18 @@
Launchpad, 2026-10-20. Author Launchpad, reviewer Webby, gate owner Sage. Issue #1698. Brief
`docs/plans/2026-10-20_stack-release-sso.md` (blob 426336b177f6). Rulings sage-1389 (R1 uses the activation onto
the current copy, plus a same-copy test and a latest-set mutant), sage-1403 and sage-1407 (N4), mos-1056 item 5
(N4 is a window acceptance item), sage-1432 (round 2 for webby-0028 N1 and N2).
(N4 is a window acceptance item), sage-1432 (round 2 for webby-0028 N1 and N2), sage-1556 (round 3: rebase onto row 190's
landing 00d4cc1f6, and `--sso` for a named instance).
Base 4d487dfdc (`base.txt`): aa1bd209e plus row 192's two commits picked. 48359dbfa is 83b32d8d8 (patch-id
Base 00d4cc1f6 since round 3 (`base.txt`), row 190's landing, which has row 192 (4429a104f) below it. Rounds 1
and 2 were built on 4d487dfdc: aa1bd209e plus row 192's two commits picked. 48359dbfa is 83b32d8d8 (patch-id
8d11f48a9e03) and 4d487dfdc is r2 37582e820 (patch-id 0e32af8aa2a4 on both). The webui tree at the base matches
`launchpad/row192` at 37582e820 path for path. Candidate: r1 5dc0b5534 and the round 2 commit on `launchpad/row200`.
`launchpad/row192` at 37582e820 path for path. Rounds 1 and 2 are on `launchpad/row200`;
round 3 is on `launchpad/row200-r3` (see Round 3 below).
`candidate-manifest.sha256` lists its 3 paths; the packet files are not in it. Drift check at commit time:
`git log aa1bd209e..origin/refactor` (origin/refactor 9ba31b5b3 at round 2) touches none of the 3 paths.
round 3, origin/refactor is 00d4cc1f6 itself, so `git log 00d4cc1f6..origin/refactor` is empty.
No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, `~/.mosaic`, a live unit or a live slot. T56 runs on
No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, `~/.mosaic`, a live unit or a live slot. T57 runs on
the scratch roots and seeded pointers rows 187 and 189 use. Its config and slot sit in the test's temp dir,
and the slot carries the `proofFile` read watch, so a run that opened it would fail the test.
@@ -20,7 +23,7 @@ and the slot carries the `proofFile` read watch, so a run that opened it would f
| Path | Change |
| --- | --- |
| `scripts/stack-release.sh` | `--sso` on activate (usage, parse, `sso_for_target`), `sso_checks` and `slot_problem` (item 2), `sso_base` (rollback flags, item 3), `config_checks` (item 3 and N4), the `sso:` plan line and step lines. |
| `packages/cli/tests/stack-release.test.mjs` | Fixture copies gain row 192's `--sso` marker, an old loader without `console`, and a C copy whose `ALGS` lack RS256. T56, 10 subtests (2 in round 2). |
| `packages/cli/tests/stack-release.test.mjs` | Fixture copies gain row 192's `--sso` marker, an old loader without `console`, and a C copy whose `ALGS` lack RS256. T57 (T56 before round 3), 11 subtests (2 in round 2, 1 in round 3). |
| `docs/TOOLS.md` | Usage line and two paragraphs after row 189's `--no-send`. |
## What it does
@@ -28,7 +31,7 @@ and the slot carries the `proofFile` read watch, so a run that opened it would f
1. **Item 1.** `activate ID BUSINESS --sso` drops `--public-host`, `--auth-header`, `--allow-user` and any proof
pair, and puts `--sso` after the other flags, before `--send` and `--tester-port`, as `webui-service.sh`
renders it (`sso_for_target`, `:775`). It may name the live copy once. After that, both a plain activate and
an `--sso` activate refuse `already-active`. Later activates carry `--sso` (`sso: carried`; T56 covers
an `--sso` activate refuse `already-active`. Later activates carry `--sso` (`sso: carried`; T57 covers
activate, not `pm-on`, which carries the pointer's flags as it does for every flag).
`--plan` prints `sso: on; drops ...; carries ...`, plus the proof line's `dropped; --sso drops header ...`.
`--sso` with a pair, with `--no-proof` or on another verb is a usage error.
@@ -78,10 +81,12 @@ For the row 200 window plan, word for word:
- `node --test --test-name-pattern='^T56' tests/stack-release.test.mjs`: 10/10 on the round 2 tree
(`/mnt/storage/scratch/launchpad-r200/t56-r2c.log`). Round 1 was 8/8 (`t56-6.log`).
- The whole file, the cli suite and every `scripts/test-*.sh` go to Sage's gate. T1 to T55 have not run on
this tree.
- Round 3, this tree: `node --test tests/stack-release.test.mjs`, the whole file, 192/192
(`/mnt/storage/scratch/launchpad-r200/sr-whole-r3b.log`); T57 alone 11/11 (`t57-r3a.log`);
`tests/stack-instances.test.mjs` 23/23 (`si-r3.log`). No other test reads the 3 paths or pins their hashes.
- The cli suite and every `scripts/test-*.sh` go to Sage's gate.
## Mutants (`mutants.py`, run as `python3 mutants.py <tree> [N ...]`, each against T56)
## Mutants (`mutants.py`, run as `python3 mutants.py <tree> [N ...]`, each against T57; T56 in rounds 1 and 2)
| # | Mutant | Result |
| --- | --- | --- |
@@ -97,13 +102,15 @@ For the row 200 window plan, word for word:
| 10 | `slot_problem` compares the unresolved `REPO` (round 2, N1) | not run, Sage's gate |
| 11 | `proof_file_problem` compares the unresolved `REPO` (round 2, N1) | not run, Sage's gate |
| 12 | `config_checks` passes a config that doesn't parse (round 2, N2) | not run; equivalent if it survives, guard kept (sage-1446) |
| 13 | `sso_checks` reads the default config, not `MOSAIC_CONFIG` (round 3) | not run, Sage's gate |
| 14 | `sso_base` reads `show-mosaic-webui.service.txt`, not the instance unit's (round 3) | not run, Sage's gate |
9/9 killed (`/mnt/storage/scratch/launchpad-r200/mutants.log`). The script restored `stack-release.sh`, and it
matches its pre-run sha256. No mutant hit the 300 s timeout. This batch ran without Sage's slot (sage-1414),
while Darkwing's gate was running, so Sage reruns all 9 on the row 200 gate and doesn't take these results
(sage-1423). Mutants 10 to 12 are new in round 2 and have not run. Sage runs 1 to 12 on the gate (sage-1446). Mutant 5's
pattern now names the `holds` message, because the round 2 message also starts with `config-console-key "$cfg`.
Every pattern matches once on the round 2 tree.
Every pattern matches once on the round 2 tree, and all 14 match once on the round 3 tree.
## Choices
@@ -137,7 +144,7 @@ Every pattern matches once on the round 2 tree.
## Round 2 (sage-1432: N1 and N2; N3 and N4 stay notes)
- **N1.** `slot_problem` (`:847`) and `proof_file_problem` (`:989`) now compare the slot's real path with
`realpath -m` of both `REPO` and `COPIES`, and refuse "cannot be resolved" if either fails. T56's
`realpath -m` of both `REPO` and `COPIES`, and refuse "cannot be resolved" if either fails. T57's
"round 2 (webby-0028 N1)" subtest runs the script through a symlink to the fixture repo
(`repo-link.200/scripts/stack-release.sh`). A proof file inside the repository is refused `proof-file-invalid`,
and a slot whose secrets directory links into the repository is refused `sso-slot` "is inside the repository
@@ -146,13 +153,50 @@ Every pattern matches once on the round 2 tree.
(exits 2) and refuses `config-console-key` "cannot be read as JSON" on the second. But webby-0028's case
doesn't reach it: `load_config` (common.sh, through `scripts/mosaic-config.mjs env`) exits 1 on a config that
doesn't parse, before anything is read or stopped. So the refusal is a guard for a config that changes after
`load_config` ran, and no test reaches it without a race. The T56 subtest "round 2 (webby-0028 N2)" checks
`load_config` ran, and no test reaches it without a race. The T57 subtest "round 2 (webby-0028 N2)" checks
what does happen: activate, rollback and `activate --plan` each exit 1 with "common: configuration load
failed", the pointer, the start/stop log and the activation log are unchanged, and activate works after the
config is restored. Mutant 12 drops the guard and is expected to survive as equivalent for that reason. sage-1446:
the guard stays, and mutant 12 is equivalent if it survives, the same call as row 192's mutant 23.
- **N4.** The back-out line is in the window plan section above.
## Round 3 (sage-1556: rebase onto 00d4cc1f6, `--sso` for a named instance)
- **Rebase.** Row 190 landed as 00d4cc1f6 and changed all 3 paths. The 5 round 1 and 2 commits are picked onto
it on a new branch, `launchpad/row200-r3` (no force-push, no branch delete, #426(a)). `launchpad/row200` stays
as it was. `docs/TOOLS.md` and the test file applied cleanly. `scripts/stack-release.sh` conflicted in `plan()`
and in the `--plan` lines of `do_activate` and `do_rollback`. The merge keeps both sides: the `sso:` plan line
comes before the Console line, the Console and CLI lines carry row 190's `$flags` (`--instance NAME`), and
`--plan` runs `ports_unclaimed` after `console_for_target` and before `plugin_checks` and `config_checks`.
Functions on this tree: `sso_base` `:738`, `sso_for_target` `:785`, `sso_checks` `:827`, `slot_problem`
`:857`, `config_checks` `:877`, `proof_file_problem` `:999`.
- **Named instance: no code change was needed.** Row 190's `instance_select` exports `MOSAIC_CONFIG` as NAME's
config, and the named Console unit sets `Environment=MOSAIC_CONFIG` to the same file. So `sso_checks` and
`config_checks` read `~/.config/mosaic-NAME/config.json`, and the slot path is
`~/.config/mosaic-NAME/secrets/mosaic-stack/<slot>`, the directory the Console's `readSlot` reads under that
unit. `sso_base` reads `show-$CONSOLE_UNIT.txt`, so a named rollback reads `show-mosaic-webui-NAME.service.txt`
from NAME's own set. The pointer, the log and the sets are in NAME's data root. One comment (`:734`) named the
default unit's file and now says `show-$CONSOLE_UNIT.txt`.
- **Test.** New T57 subtest "round 3 (sage-1556)": instance `alpha`. A missing slot refuses `sso-slot` and names
alpha's slot path. `--plan` prints `--instance alpha` on the Console line. `activate C1 acme --sso` writes
`--sso` to `mosaic-webui-alpha.service` (with `MOSAIC_CONFIG` set to alpha's config), leaves the default
Console unit unwritten, and status matches. After the key is removed, `rollback` takes its flags from alpha's
arrival set, `show-mosaic-webui-alpha.service.txt`. The slot carries the read watch. Mutants 13 and 14 undo
the two instance-specific reads.
- **Rename.** Row 190 also named its test T56, so this row's test is now T57; `mutants.py` runs `^T57`, and the
in-repository temp dirs are `t57-slot-` and `t57-proof-`.
- **Defect from round 1, fixed.** Round 1 added `| --sso` to the activate usage line, but T53 test 6 (row 187)
and T55 test 5 (row 189) still matched the old line, so 4 tests failed (2 subtests and their parents). Rounds
1 and 2 ran only this row's test, so the failure went unseen. The round 2 tree d4ec998cc fails the same 4
(`r2-t53t55.log`), and the first whole-file run on this tree was 188/192 (`sr-whole-r3.log`). Both regexes now
include `| --sso` (`:1672`, `:1831`; T57's own check is `:1940`). Webby: please check these two lines; the
round 2 approve did not cover them (sage-1569). A grep of `packages/*/tests` and `scripts/test-*.sh` for the
usage text (`--no-proof]`, `proof-file PATH`, `usage: stack-release`) finds no third copy, and finds no
64-hex pin on any of the 3 paths. The only test files that name `stack-release` are `stack-release.test.mjs`
and `stack-instances.test.mjs` (a comment); both ran as whole files (above).
- **Not changed (note).** The N4 first step reads "activate ID without --sso first"; for a named instance the
operator also passes `--instance NAME`, as with every verb. The message doesn't repeat it.
## Not done here
- No change to the live pointer, units or config. The phase 2 window is Sage's and Mos's.
+1 -1
View File
@@ -1 +1 @@
4d487dfdc468c58e3790976c3d4276fe60816fbe
00d4cc1f6987d5eec340361c17efc2b3e58d1505
@@ -1,3 +1,3 @@
6ee1adb3951aeb1e3862cb119c12b600b88641f325912c09840b841761aa12dd docs/TOOLS.md
9b231532d14b21d0d39ca89035d9067e827e18cc1cf3dfe298bef840ae87ff0c packages/cli/tests/stack-release.test.mjs
d24e75e9927623ad106b55bce82e40ddf7677d483626fb46923c3b3c7c1148ad scripts/stack-release.sh
4bf241979d5cf2e83a2f209adbf3acb1a8ea091c5c384b27b5e3462fa348d9d0 docs/TOOLS.md
959567d9bf6c2435640060fcb33890a344ead7f7cbc1f8019e45ae2498fcdd4e packages/cli/tests/stack-release.test.mjs
63073fbea264549694b54cc1ae7d46ba9dbe8c5d72670da87985a5223c06ac3f scripts/stack-release.sh
+4 -2
View File
@@ -1,11 +1,11 @@
# Row 200 (#1698) mutants: `python3 mutants.py <tree> [N ...]`. Each applies to the tree's scripts/stack-release.sh,
# runs T56 in packages/cli/tests/stack-release.test.mjs and restores the file from a saved copy, checked by sha256.
# runs T57 in packages/cli/tests/stack-release.test.mjs and restores the file from a saved copy, checked by sha256.
# Killed = test exit != 0.
import hashlib, os, subprocess, sys, signal
R = os.path.abspath(sys.argv[1])
S = os.path.join(R, 'scripts', 'stack-release.sh')
if not os.path.isfile(S): sys.exit(f'ABORT no scripts/stack-release.sh under {sys.argv[1]}')
T, PAT = 'tests/stack-release.test.mjs', '^T56'
T, PAT = 'tests/stack-release.test.mjs', '^T57'
M = [
('no sso-target refusal (item 2)', [(" sso_supported || { check_refuse sso-target", " true || { check_refuse sso-target")]),
('no sso-config refusal (item 2)', [(' check_refuse sso-config "$TO', ' : "$TO')]),
@@ -19,6 +19,8 @@ M = [
('slot_problem compares the unresolved REPO (round 2, webby-0028 N1)', [(' case "$real" in "$repo" | "$repo"/* | "$copies" | "$copies"/*) echo "is inside the repository or the copies"; return ;; esac\n [ "$size" != 0 ]', ' case "$real" in "$REPO" | "$REPO"/* | "$copies" | "$copies"/*) echo "is inside the repository or the copies"; return ;; esac\n [ "$size" != 0 ]')]),
('proof_file_problem compares the unresolved REPO (round 2, webby-0028 N1)', [(' case "$real" in "$repo" | "$repo"/* | "$copies" | "$copies"/*) echo "is inside the repository or the copies"; return ;; esac\n [ "$mode" = 600 ]', ' case "$real" in "$REPO" | "$REPO"/* | "$copies" | "$copies"/*) echo "is inside the repository or the copies"; return ;; esac\n [ "$mode" = 600 ]')]),
('config_checks passes a config that does not parse (round 2, webby-0028 N2; expected equivalent)', [(' [ "$key" != 1 ] || return 0', ' [ "$key" = 0 ] || return 0')]),
('sso_checks reads the default config, not the instance config (round 3, sage-1556)', [(' local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out id slot path why', ' local cfg="$HOME/.config/mosaic-dev/config.json" out id slot path why')]),
('sso_base reads the default Console\'s show file, not the instance unit\'s (round 3, sage-1556)', [(' local f="show-$CONSOLE_UNIT.txt" argv=""', ' local f="show-mosaic-webui.service.txt" argv=""')]),
]
sha = lambda b: hashlib.sha256(b).hexdigest()
only = sys.argv[2:]
+30 -6
View File
@@ -1669,7 +1669,7 @@ test('T53 row 187: activate sets, carries and drops the proof pair',async(t)=>{
['activate',C1,'acme','--plan','--proof-file',f]]){
const r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof\]/);
assert.match(usageText,/\[--proof-header NAME --proof-file PATH \| --no-proof \| --sso\]/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
@@ -1828,7 +1828,7 @@ test('T55 row 189: activate --no-send drops --send, and rollback and pm-on carry
['rehearse','acme','--backup','/tmp/x','--port','7400','--no-send']]){
const r=await prun(e,...args);assert.equal(r.code,4,args.join(' '));assert.equal(r.out,'');assert.equal(r.err,usageText);
}
assert.match(usageText,/\| --no-proof\] \[--no-send\]/);
assert.match(usageText,/\| --no-proof \| --sso\] \[--no-send\]/);
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).length,logs);
await noLeak(e);
});
@@ -1885,7 +1885,7 @@ function setEdit(set,name,from,to,sums=true){
if(sums){const s=join(set,'SHA256SUMS');chmodSync(s,0o600);writeFileSync(s,readFileSync(s,'utf8').replace(new RegExp(`^[0-9a-f]{64}( \\./${name.replace(/[.@]/g,'\\$&')})$`,'m'),`${sha(f)}$1`))}
}
test('T56 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso',async(t)=>{
test('T57 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso',async(t)=>{
await t.test('item 1: --sso drops the preview set and the pair, puts --sso before --send and the tester port, and the plan names what it drops and carries',async(t)=>{
const e=await mk(t),base=await seedPreview(e,undefined,['--send']),f=await proofFile(e),pre=noSend(base);
let r=await prun(e,'activate',B,'acme','--tester-port','7333',...pair(f));assert.equal(r.code,0,r.out+r.err);
@@ -1966,7 +1966,7 @@ test('T56 row 200: activate --sso, its refusals before any stop, and the rollbac
put(0o600,'');
await refused(e,['activate',C,'acme','--sso'],'sso-slot',bad('is empty'));
// The slot directory resolving into the repository: the file is no link, but its real path is.
const inRepo=mkdtempSync(join(repo,'t56-slot-'));t.after(()=>rmSync(inRepo,{recursive:true,force:true}));
const inRepo=mkdtempSync(join(repo,'t57-slot-'));t.after(()=>rmSync(inRepo,{recursive:true,force:true}));
writeFileSync(join(inRepo,SLOT),'x\n',{mode:0o600});chmodSync(join(inRepo,SLOT),0o600);
const secrets=dirname(slot);rmSync(secrets,{recursive:true});symlinkSync(inRepo,secrets);
await refused(e,['activate',C,'acme','--sso'],'sso-slot',bad('is inside the repository or the copies'));
@@ -2048,11 +2048,11 @@ test('T56 row 200: activate --sso, its refusals before any stop, and the rollbac
await t.test('round 2 (webby-0028 N1): run through a symlinked repository path, a slot or a proof file inside the repository still refuses',async(t)=>{
const link=join(base,'repo-link.200');if(!existsSync(link))symlinkSync(repo,link);
const e=await mk(t);e.sr=join(link,'scripts','stack-release.sh');await seedPreview(e);
const f=await proofFile(e,'proof.200',0o600,mkdtempSync(join(repo,'t56-proof-')));t.after(()=>rmSync(dirname(f),{recursive:true,force:true}));
const f=await proofFile(e,'proof.200',0o600,mkdtempSync(join(repo,'t57-proof-')));t.after(()=>rmSync(dirname(f),{recursive:true,force:true}));
await refused(e,['activate',C1,'acme',...pair(f)],'proof-file-invalid',`--proof-file ${f} is inside the repository or the copies`);
await ssoConfig(e,{slot:false});
let r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
const inRepo=mkdtempSync(join(repo,'t56-slot-'));t.after(()=>rmSync(inRepo,{recursive:true,force:true}));
const inRepo=mkdtempSync(join(repo,'t57-slot-'));t.after(()=>rmSync(inRepo,{recursive:true,force:true}));
writeFileSync(join(inRepo,SLOT),'x\n',{mode:0o600});chmodSync(join(inRepo,SLOT),0o600);
const secrets=dirname(slotPath(e));rmSync(secrets,{recursive:true});symlinkSync(inRepo,secrets);
await refused(e,['activate',C1,'acme','--sso'],'sso-slot',`console.sso provider a client secret slot ${slotPath(e)} is inside the repository or the copies`);
@@ -2072,6 +2072,30 @@ test('T56 row 200: activate --sso, its refusals before any stop, and the rollbac
const r=await prun(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err);
await noLeak(e);
});
await t.test('round 3 (sage-1556): a named instance turns --sso on with its own config, slot and Console unit, and rolls back from its own set',async(t)=>{
const e=await mk(t),a=await named(e,'alpha');await seed(a);
const show=`show-${a.con}.txt`,unit=()=>readFileSync(join(a.units,a.con),'utf8').split('\n').find((l)=>l.startsWith('ExecStart='));
assert.ok(slotPath(a).startsWith(join(a.env.HOME,'.config','mosaic-alpha','secrets','mosaic-stack')+'/'),slotPath(a));
await ssoConfig(a,{slot:false});
await refused(a,['activate',C1,'acme','--sso'],'sso-slot',`console.sso provider a client secret slot ${slotPath(a)} is missing`);
await proofFile(a,SLOT,0o600,dirname(slotPath(a)));
const want=['--port',a.port,'--business','acme','--sso'];
const out=await planOnly(a,0,'activate',C1,'acme','--sso');
assert.equal(out.find((l)=>l.startsWith('Console: ')),`Console: scripts/webui-service.sh install --instance alpha --copy ${C1} --copies ${COPIES} ${want.join(' ')}`);
let r=await prun(a,'activate',C1,'acme','--sso');assert.equal(r.code,0,r.out+r.err);
assert.equal(pointer(a).copy,C1);assert.deepEqual(pointer(a).console,want);
assert.ok(unit().endsWith(` ${want.join(' ')}`),unit());
assert.ok(readFileSync(join(a.units,a.con),'utf8').includes(`\nEnvironment=MOSAIC_CONFIG=${a.cfg}\n`));
assert.ok(!existsSync(join(e.units,CON)),'the default Console unit was written');
r=await prun(a,'status','acme');assert.equal(r.code,0,r.err);assert.match(r.out,/unit flags: match the pointer/);
setKey(a,undefined);
const set=arrivalSet(a,C1);assert.ok(existsSync(join(set,show)),show);
assert.deepEqual(ssoLines(await planOnly(a,0,'rollback','acme')),[`sso: dropped; ${B}'s Console does not support --sso, so the rollback takes the Console flags from ${set}/${show}, with --send and --tester-port as the pointer has them`]);
r=await prun(a,'rollback','acme');assert.equal(r.code,0,r.out+r.err);
assert.equal(pointer(a).copy,B);assert.deepEqual(pointer(a).console,want.slice(0,-1));
assert.ok(stepText(a).includes(`Z drop --sso; ${B}'s Console does not support it, so the Console flags come from ${set}/${show}\n`));
await noLeak(a);
});
});
// --- rehearse (D2, row 163; the brief's "Stack rehearsal on a scratch root") ---
+1 -1
View File
@@ -731,7 +731,7 @@ has_flag() { local f="$1"; shift; [[ " $* " == *" $f "* ]]; }
# pointer no longer holds the preview set that Console ran with, so the flags come from the rollback
# set of the activation that moved the stack onto the pointer's copy from the rollback target. A
# same-copy re-pin after it has a set of its own, holding the same copy's earlier flags, so the
# pointer's own activation is the wrong set (sage-1389). The set's show-mosaic-webui.service.txt is
# pointer's own activation is the wrong set (sage-1389). The set's show-$CONSOLE_UNIT.txt is
# the Console unit as it was before that run, checked against the set's SHA256SUMS. Sets
# BASE_CONSOLE (the flags console_for_target starts from), SSO_PLAN and SSO_STEP. SSO_NO_TARGET
# skips sso_checks after a refusal here, so --plan lists that refusal alone.