From bec7e499ec067a3627dd5cf2c0454008edcf17a8 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Tue, 20 Oct 2026 20:08:09 -0500 Subject: [PATCH] stack-release: the sso-config first step carries --instance NAME on a named instance (row 238, #1744) --- agents/launchpad/work/queue-238/BUILD.md | 47 +++++++++++++++++++ agents/launchpad/work/queue-238/base.txt | 1 + .../work/queue-238/candidate-manifest.sha256 | 3 ++ agents/launchpad/work/queue-238/mutants.py | 37 +++++++++++++++ docs/TOOLS.md | 3 +- packages/cli/tests/stack-release.test.mjs | 8 ++++ scripts/stack-release.sh | 7 +-- 7 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 agents/launchpad/work/queue-238/BUILD.md create mode 100644 agents/launchpad/work/queue-238/base.txt create mode 100644 agents/launchpad/work/queue-238/candidate-manifest.sha256 create mode 100644 agents/launchpad/work/queue-238/mutants.py diff --git a/agents/launchpad/work/queue-238/BUILD.md b/agents/launchpad/work/queue-238/BUILD.md new file mode 100644 index 000000000..285d16239 --- /dev/null +++ b/agents/launchpad/work/queue-238/BUILD.md @@ -0,0 +1,47 @@ +# Row 238: the sso-config hint names the instance (row 200 follow-up N1) + +Launchpad, 2026-10-21. Author Launchpad, reviewer Webby, gate owner Sage. Issue #1744. Brief "Brief: row 200 +follow-up N1, the sso-config hint names the instance" in `docs/plans/2026-10-20_stack-release-sso.md` (76134dfde), +from Webby's row 200 round 3 note N1 (comment 29772), assigned in sage-1597. + +Base 011d41ba6 (`base.txt`), which has row 200's landing 9e1085816 below it. Built on 76134dfde, then rebased +before the first push onto 011d41ba6, because row 199 (cb5fdb064) added 6 lines to `docs/TOOLS.md`; no +conflict. Branch `launchpad/row238`. +`candidate-manifest.sha256` lists the 3 changed paths; the packet files are not in it. + +No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, `~/.mosaic`, a live unit or a live slot. The new +subtest runs on row 190's `named()` scratch instance under the test's temp HOME. + +## Files + +| Path | Change | +| --- | --- | +| `scripts/stack-release.sh` | `sso_checks`: the N4 first step puts `${INSTANCE_FLAGS[*]}` after `activate $TO` in both commands. | +| `packages/cli/tests/stack-release.test.mjs` | T57 subtest "row 238 (#1744)": the sso-config refusal and the `--plan` line on instance `alpha`. | +| `docs/TOOLS.md` | The `sso-config` paragraph says both commands carry `--instance NAME` on a named instance. | + +## What it does + +On a named instance the hint now reads `activate ID --instance NAME without --sso first, then write +console.sso, then run activate ID --instance NAME --sso`. `INSTANCE_FLAGS` is empty for the default instance, +so `inst` is empty and the default text is unchanged byte for byte. T57's item 2 subtest still asserts the +default text exactly. The flag goes right after `ID`, the same place the other entry points take it from +`INSTANCE_FLAGS`. BUSINESS is not in the hint before or after this row. + +## Tests run (this tree) + +- `node --test tests/stack-release.test.mjs`, the whole file: 193/193 on 76134dfde + (`/mnt/storage/scratch/launchpad-r238/sr-whole.log`) and 193/193 again after the rebase onto 011d41ba6 + (`sr-whole-2.log`), with the new subtest and T57's default-text check. +- `tests/stack-instances.test.mjs`, whole: 23/23 (`si.log`). It names `stack-release` only in a comment. +- A grep of `packages/*/tests/` and `scripts/test-*.sh` finds the hint text only in `stack-release.test.mjs` + (`:1951` default, `:2102` and `:2104` named) and no 64-hex pin on the 3 paths. +- The cli suite, every `scripts/test-*.sh` and the mutant go to Sage's slot. + +## Mutants (`mutants.py`, run as `python3 mutants.py [N ...]`, against T57) + +| # | Mutant | Result | +| --- | --- | --- | +| 1 | the first step drops `--instance NAME` (`inst=""`) | not run, Sage's slot | + +The pattern matches once on this tree. diff --git a/agents/launchpad/work/queue-238/base.txt b/agents/launchpad/work/queue-238/base.txt new file mode 100644 index 000000000..e5efd2127 --- /dev/null +++ b/agents/launchpad/work/queue-238/base.txt @@ -0,0 +1 @@ +011d41ba6a703be3c6c8d70996aa780223aa3b00 diff --git a/agents/launchpad/work/queue-238/candidate-manifest.sha256 b/agents/launchpad/work/queue-238/candidate-manifest.sha256 new file mode 100644 index 000000000..9e74fe00f --- /dev/null +++ b/agents/launchpad/work/queue-238/candidate-manifest.sha256 @@ -0,0 +1,3 @@ +868dcf320abcd745aad33a15a3d2ae212902b4dd4f763f8d8fd23cca46c9da74 docs/TOOLS.md +b169c6c0a8e6fe947669d3f4e161ff200d596fb0a7706ee4d3f81cd742e542ca packages/cli/tests/stack-release.test.mjs +8b0193de39ac13c38e5c0e3e96df6d2204acb80c214f5c6d2eb240ae7bd3c33c scripts/stack-release.sh diff --git a/agents/launchpad/work/queue-238/mutants.py b/agents/launchpad/work/queue-238/mutants.py new file mode 100644 index 000000000..5a0021b9c --- /dev/null +++ b/agents/launchpad/work/queue-238/mutants.py @@ -0,0 +1,37 @@ +# Row 238 (#1744) mutant: `python3 mutants.py [N ...]`. Each applies to the tree's scripts/stack-release.sh, +# 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', '^T57' +M = [ + ('the sso-config first step drops --instance NAME (row 238)', [(' local first="" inst="${INSTANCE_FLAGS[*]:+ ${INSTANCE_FLAGS[*]}}"', ' local first="" inst=""')]), +] +sha = lambda b: hashlib.sha256(b).hexdigest() +only = sys.argv[2:] +os.chdir(os.path.join(R, 'packages', 'cli')) +survived = [] +for i, (name, pairs) in enumerate(M, 1): + if only and str(i) not in only: continue + orig = open(S, 'rb').read(); h = sha(orig) + src = orig.decode() + for a, b in pairs: + if src.count(a) != 1: sys.exit(f'ABORT {i} {name}: pattern found {src.count(a)} times') + src = src.replace(a, b) + try: + open(S, 'w').write(src); os.chmod(S, 0o755) + p = subprocess.Popen(['node', '--test', f'--test-name-pattern={PAT}', T], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, start_new_session=True) + try: out, _ = p.communicate(timeout=300) + except subprocess.TimeoutExpired: + os.killpg(p.pid, signal.SIGKILL); out, _ = p.communicate(); out += b'\nTIMEOUT' + text = out.decode(errors='replace') + ran = [l for l in text.splitlines() if l.startswith('ℹ fail ')] + verdict = 'KILLED' if p.returncode != 0 else 'SURVIVED' + if verdict == 'SURVIVED': survived.append(name) + print(f'{i:2} {verdict} {name} [{ran[0] if ran else "no count"}]', flush=True) + finally: + open(S, 'wb').write(orig); os.chmod(S, 0o755) + if sha(open(S, 'rb').read()) != h: sys.exit(f'ABORT restore failed for {S}') +print('survived:', survived or 'none') diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 8f1032e84..9324c76ba 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -582,7 +582,8 @@ error. Before anything stops, a run whose Console flags hold `--sso` refuses `sso-target` for a target Console without it, `sso-config` when the target's own `checkConsole` refuses the config's `console` key (or there is none, in which case, from another copy, it names the first step: activate the target without -`--sso`, then write `console.sso`), and `sso-slot` for each provider's slot file +`--sso`, then write `console.sso`; on a named instance both commands carry +`--instance NAME`), and `sso-slot` for each provider's slot file that is missing, a symlink, not a regular file, not owned by this uid, has group or other bits, resolves inside the repository or the copies, or is empty. It uses `lstat`, `stat` and `realpath` on the slot and never reads it. `--plan` diff --git a/packages/cli/tests/stack-release.test.mjs b/packages/cli/tests/stack-release.test.mjs index 3380c1801..cc746914e 100644 --- a/packages/cli/tests/stack-release.test.mjs +++ b/packages/cli/tests/stack-release.test.mjs @@ -2096,6 +2096,14 @@ test('T57 row 200: activate --sso, its refusals before any stop, and the rollbac 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); }); + await t.test('row 238 (#1744): on a named instance the sso-config first step names --instance NAME in both commands',async(t)=>{ + const e=await mk(t),a=await named(e,'alpha');await seed(a); + assert.equal(JSON.parse(readFileSync(a.cfg,'utf8')).console,undefined); + await refused(a,['activate',C1,'acme','--sso'],'sso-config',`${C1}'s Console would refuse its --sso start: there is no console.sso in the system config; activate ${C1} --instance alpha without --sso first, then write console.sso, then run activate ${C1} --instance alpha --sso`); + const out=await planOnly(a,3,'activate',C1,'acme','--sso'); + assert.ok(out.includes(`refuses sso-config: ${C1}'s Console would refuse its --sso start: there is no console.sso in the system config; activate ${C1} --instance alpha without --sso first, then write console.sso, then run activate ${C1} --instance alpha --sso`),out.join('\n')); + await noLeak(a); + }); }); // --- rehearse (D2, row 163; the brief's "Stack rehearsal on a scratch root") --- diff --git a/scripts/stack-release.sh b/scripts/stack-release.sh index d954675ab..984952d0a 100755 --- a/scripts/stack-release.sh +++ b/scripts/stack-release.sh @@ -836,12 +836,13 @@ sso_checks() { sso_supported || { check_refuse sso-target "$TO's Console does not support --sso"; return 0; } local cfg="${MOSAIC_CONFIG:-$HOME/.config/mosaic-dev/config.json}" out id slot path why # N4 (webby-0019, sage-1403): a pre-phase-1 loader refuses a console key, so with the key not yet - # written and the stack on another copy, the first step is the re-pin without --sso. - local first="" + # written and the stack on another copy, the first step is the re-pin without --sso. On a named + # instance both commands carry --instance NAME (row 238). + local first="" inst="${INSTANCE_FLAGS[*]:+ ${INSTANCE_FLAGS[*]}}" if ! out="$(node -e "$SSO_JS" -- "$COPIES/$TO" "$cfg" 2>&1)"; then out="${out%%$'\n'*}" [ "$out" != "there is no console.sso in the system config" ] || [ "$P_COPY" = "$TO" ] || - first="; activate $TO without --sso first, then write console.sso, then run activate $TO --sso" + first="; activate $TO$inst without --sso first, then write console.sso, then run activate $TO$inst --sso" check_refuse sso-config "$TO's Console would refuse its --sso start: $out$first" return 0 fi