Compare commits

...
7 changed files with 122 additions and 4 deletions

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
# 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.
Round 4 base c515273db, row 240's landing (`base.txt`). Branch `launchpad/row238-r4`, one commit.
- Round 1 was `launchpad/row238` bec7e499e on 011d41ba6, approved by Webby (webby-0049). It was built on
76134dfde and rebased before its first push, after row 199 (cb5fdb064) added 6 lines to `docs/TOOLS.md`.
- Rows 232 (e65a3ab4d) and 233 (57f59e0c3) then landed on all 3 paths (sage-1710, sage-1755). Round 2 was a
local rebase onto 57f59e0c3 (ca3d30e5c, never pushed). The merge was clean. `sso_checks` moved from about
:838 to :920 with its body unchanged except for this row's hunk. 233 moved the shared test helpers into
`stack-release-fixture.mjs`, and the new subtest is still under T57.
- Row 234 (c771a36f4) then added two lines to T46's `untimed` map (`host_grants`, `hosts`, now :919-920).
Round 3 is the same patch on c771a36f4 with those lines kept. The code diff on the 3 paths has the same
`git patch-id --stable` on both bases (b9d0bb0ee3c7). Pushed as `launchpad/row238-r3` a33597c33 and
approved by Webby in round 2 (webby-0059, rev 2707).
- Row 240 (c515273db) then changed `docs/TOOLS.md` at :837 and :854, away from this row's hunk at :599
(sage-1803). Round 4 is the same patch on c515273db: `docs/TOOLS.md` is the landed blob plus the one
hunk, and `scripts/stack-release.sh` (100755) and `stack-release.test.mjs` keep round 3's blobs. The
patch-id is still b9d0bb0ee3c7. No test run for this doc-only merge; Sage's landing gate covers it.
Row 240 also changed `stack-upgrade.test.mjs`, so the round 2 result for that file is from the older blob.
`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)
Logs are in `/mnt/storage/scratch/launchpad-r238/`.
- Round 1: `stack-release.test.mjs` whole, 193/193 on 76134dfde (`sr-whole.log`) and on 011d41ba6
(`sr-whole-2.log`). `stack-instances.test.mjs` whole, 23/23 (`si.log`).
- Round 2, on ca3d30e5c (57f59e0c3), each file singly on Sage's slot (sage-1761): `stack-release.test.mjs`
196/196, `stack-release-external.test.mjs` 25/25, `stack-upgrade.test.mjs` 20/20 (`r2-*.log`, 241/0 by
awk). Sage counts these as pre-merge (sage-1769).
- Round 3, on this blob singly on Sage's slot (sage-1794): `stack-release.test.mjs` 196/196, 06:34:22Z to
06:42:57Z (`r3-stack-release.log`). The other two files and
`stack-release-fixture.mjs` are the same on 57f59e0c3 and c771a36f4, so their round 2 results stand.
- A grep of `packages/*/tests/*` and `scripts/test-*.sh` finds the hint text only in `stack-release.test.mjs`
(`:1538` default, `:1689` and `:1691` 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 <tree> [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.
+1
View File
@@ -0,0 +1 @@
c515273db7b364736527493ed170d0f088eb506e
@@ -0,0 +1,3 @@
abdd641ea5a411347e3644286821c6485a6220e2a3de0f5ee95dcc37ba2c75ca docs/TOOLS.md
4eeae9f672aecccdf853c377fb10847ce2656dbc566720bddfece435c1755d3b packages/cli/tests/stack-release.test.mjs
1896d6518a502d340dfe30d402102b8c5cd5d8520757387b018b636755eb5d36 scripts/stack-release.sh
@@ -0,0 +1,37 @@
# Row 238 (#1744) mutant: `python3 mutants.py <tree> [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')
+2 -1
View File
@@ -599,7 +599,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 `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 `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 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 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 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` uses `lstat`, `stat` and `realpath` on the slot and never reads it. `--plan`
@@ -1683,6 +1683,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`)); 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 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") --- // --- rehearse (D2, row 163; the brief's "Stack rehearsal on a scratch root") ---
+4 -3
View File
@@ -929,12 +929,13 @@ sso_checks() {
sso_supported || { check_refuse sso-target "$TO's Console does not support --sso"; return 0; } 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 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 # 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. # written and the stack on another copy, the first step is the re-pin without --sso. On a named
local first="" # 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 if ! out="$(node -e "$SSO_JS" -- "$COPIES/$TO" "$cfg" 2>&1)"; then
out="${out%%$'\n'*}" out="${out%%$'\n'*}"
[ "$out" != "there is no console.sso in the system config" ] || [ "$P_COPY" = "$TO" ] || [ "$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" check_refuse sso-config "$TO's Console would refuse its --sso start: $out$first"
return 0 return 0
fi fi