From b64aa316ab4fa911a4b4422dfdf39ad75ec96620 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 21 Oct 2026 21:11:49 -0500 Subject: [PATCH] docs(row253): store repair patch without context whitespace --- .../gander/work/queue-253/c4/REPAIR-README.md | 2 + agents/gander/work/queue-253/c4/REPAIR.diff | 241 +++--------------- 2 files changed, 42 insertions(+), 201 deletions(-) diff --git a/agents/gander/work/queue-253/c4/REPAIR-README.md b/agents/gander/work/queue-253/c4/REPAIR-README.md index 16d159c4e..4b0098863 100644 --- a/agents/gander/work/queue-253/c4/REPAIR-README.md +++ b/agents/gander/work/queue-253/c4/REPAIR-README.md @@ -40,3 +40,5 @@ N1: the fake reads its mode with the Bash file-read builtin form. N2: fixture cl ## Proposed next gate On Sage CLEAR after static approval: both touched files whole, M1-M39 with passing focused bases and byte-identical restoration, CLI whole, WebUI whole and the ten shell files. Previous completed results are historical only because product blobs changed. Use a gXXXXXX TMPDIR under /mnt/storage/scratch/tmp, pipefail and tee, a busy check before every launch, and log/wrapper names that do not match the busy detector. Stop and report according to the new grant. + +REPAIR.diff uses zero context so blank context lines in the stored patch do not trigger whitespace checks on the record itself. The full matching snapshots provide surrounding context. diff --git a/agents/gander/work/queue-253/c4/REPAIR.diff b/agents/gander/work/queue-253/c4/REPAIR.diff index 5b0ecc09d..90dc6fe43 100644 --- a/agents/gander/work/queue-253/c4/REPAIR.diff +++ b/agents/gander/work/queue-253/c4/REPAIR.diff @@ -1,21 +1,12 @@ --- a/scripts/case2-unit.sh +++ b/scripts/case2-unit.sh -@@ -16,7 +16,10 @@ - # a unit file into place. Root only stats files under and reads bounded - # stdout from a config/target query run as the stack user through sudo. Root - # never imports copy code. This adds sudo and GNU timeout to the installer +@@ -19 +19,4 @@ -# dependencies. It renders the default instance only. +# dependencies. The inner query supervisor runs after the uid drop: 5 s inside, +# 7 s on root's wait. Fixed system Bash, setsid, sleep, timeout and head are +# resolved and root-validated. Only the config query uses this supervisor. +# It renders the default instance only. - # - # The library profile takes exactly these serve flags, each once: - # --port N (1..1023) --business ID (the unit's) --public-host NAME -@@ -151,18 +154,141 @@ - fi - - # Row253: bounded unprivileged metadata, parsed by root without running copy code. +@@ -153,0 +157,113 @@ +# row253-supervisor-begin +IFS= read -r -d '' TESTER_SUPERVISOR <<'MOSAIC_TESTER_SUPERVISOR' || : +set +m @@ -129,7 +120,7 @@ + *) echo inner-command ;; + esac +} - tester_capture() ( +@@ -155,5 +271,11 @@ - local seconds="$1" maximum="$2" file child="" rc=0; shift 2 - file="$(mktemp)" || exit 1 - trap 'if [ -n "$child" ]; then kill -KILL -- "-$child" 2>/dev/null || :; wait "$child" 2>/dev/null || :; fi; rm -f -- "$file"' EXIT @@ -146,46 +137,25 @@ + maximum="$1"; head_bin="$2"; shift 2 + "$@" 2>/dev/null | "$head_bin" -c "$((maximum + 1))" + ' -- "$maximum" "$TS_HEAD" sudo -u "$user" -H env -i "HOME=$home" "PATH=$path" "$TS_BASH" -c "$TESTER_SUPERVISOR" -- "$inner" "$TS_SETSID" "$TS_SLEEP" "$@" > "$file" 2> "$notice" & - child=$! - wait "$child" 2>/dev/null || rc=$? - kill -KILL -- "-$child" 2>/dev/null || : +@@ -163 +285,4 @@ - [ "$rc" = 0 ] && [ "$(wc -c < "$file")" -le "$maximum" ] || exit 1 + # Only GNU timeout and the trusted head can write this root-side channel. + if [ "$rc" = 137 ] && [[ "$(<"$notice")" == *': sending signal KILL to command '* ]]; then exit 82; fi + [ "$(wc -c < "$file")" -le "$maximum" ] || exit 83 + [ "$rc" = 0 ] || { case "$rc" in 81|85|86|87|88) exit "$rc" ;; *) exit 87 ;; esac; } - cat -- "$file" - ) +@@ -165,0 +291 @@ + - TESTER_QUERY='import {readFileSync} from "node:fs";import {pathToFileURL} from "node:url";const m=await import(pathToFileURL(process.argv[1]).href);const s=m.loadSystem();console.log(JSON.stringify({dataRoot:s.dataRoot,socket:readFileSync(process.argv[2],"utf8").includes("MOSAIC_TESTER_SOCKET_V1")}));' - tester_facts() { - local LC_ALL=C pattern='^\{"dataRoot":"([A-Za-z0-9/._@+-]+)","socket":(true|false)\}$' -@@ -256,7 +382,7 @@ - if [ "$PROFILE" = factory ]; then - if [ "$tester_socket" = 1 ]; then - local query +@@ -259 +385 @@ - query="$(tester_capture 5 4096 sudo -u "$USER_NAME" -H env -i "HOME=$HOME_DIR" "PATH=$UNIT_PATH" "$NODE" --input-type=module -e "$TESTER_QUERY" "$COPY/packages/cli/src/config.mjs" "$COPY/packages/webui/src/cli.mjs")" || die "tester config query failed" + query="$(tester_capture 5 7 4096 "$USER_NAME" "$HOME_DIR" "$UNIT_PATH" "$NODE" --input-type=module -e "$TESTER_QUERY" "$COPY/packages/cli/src/config.mjs" "$COPY/packages/webui/src/cli.mjs")" || die "tester config query $(tester_capture_reason "$?")" - tester_facts "$query" || die "tester config query has invalid facts" - [ "$TS_MARKED" = true ] || die "the target does not support --tester-socket" - tester_location "$TS_DATA" "$HOME_DIR" "$USER_UID" "" || die "tester path refuses $TS_REASON" --- a/scripts/case1-accept.sh +++ b/scripts/case1-accept.sh -@@ -16,6 +16,10 @@ - # opens claude-session.env. A bounded config/target query imports the copy only as the stack - # user, with sudo and an empty environment. Root parses its two facts in bash. P10 uses the - # fixed system Python, also as the stack user, for a read-only SO_PEERCRED observation. +@@ -18,0 +19,4 @@ +# The config/probe supervisor is fixed root-validated system Bash plus setsid +# and sleep, after the uid drop: query inner/outer 5/7 s, probe 2/4 s. Root +# independently bounds its sudo wait. Legacy AS_USER talk and diagnostics below +# retain their old behavior and are NOT covered by this supervisor (F5 follow-up). - # - # One line per check, ` `, then `count N` for each mark and a - # result line. F checks are `pass` or `FAIL`. P checks are `closed`, `accepted ` or -@@ -108,18 +112,141 @@ - trap 'rm -rf -- "$WORK"' EXIT - - # Row253: bounded unprivileged metadata, parsed by root without running copy code. +@@ -110,0 +115,113 @@ +# row253-supervisor-begin +IFS= read -r -d '' TESTER_SUPERVISOR <<'MOSAIC_TESTER_SUPERVISOR' || : +set +m @@ -299,7 +269,7 @@ + *) echo inner-command ;; + esac +} - tester_capture() ( +@@ -112,5 +229,11 @@ - local seconds="$1" maximum="$2" file child="" rc=0; shift 2 - file="$(mktemp)" || exit 1 - trap 'if [ -n "$child" ]; then kill -KILL -- "-$child" 2>/dev/null || :; wait "$child" 2>/dev/null || :; fi; rm -f -- "$file"' EXIT @@ -316,53 +286,30 @@ + maximum="$1"; head_bin="$2"; shift 2 + "$@" 2>/dev/null | "$head_bin" -c "$((maximum + 1))" + ' -- "$maximum" "$TS_HEAD" sudo -u "$user" -H env -i "HOME=$home" "PATH=$path" "$TS_BASH" -c "$TESTER_SUPERVISOR" -- "$inner" "$TS_SETSID" "$TS_SLEEP" "$@" > "$file" 2> "$notice" & - child=$! - wait "$child" 2>/dev/null || rc=$? - kill -KILL -- "-$child" 2>/dev/null || : +@@ -120 +243,4 @@ - [ "$rc" = 0 ] && [ "$(wc -c < "$file")" -le "$maximum" ] || exit 1 + # Only GNU timeout and the trusted head can write this root-side channel. + if [ "$rc" = 137 ] && [[ "$(<"$notice")" == *': sending signal KILL to command '* ]]; then exit 82; fi + [ "$(wc -c < "$file")" -le "$maximum" ] || exit 83 + [ "$rc" = 0 ] || { case "$rc" in 81|85|86|87|88) exit "$rc" ;; *) exit 87 ;; esac; } - cat -- "$file" - ) +@@ -122,0 +249 @@ + - TESTER_QUERY='import {readFileSync} from "node:fs";import {pathToFileURL} from "node:url";const m=await import(pathToFileURL(process.argv[1]).href);const s=m.loadSystem();console.log(JSON.stringify({dataRoot:s.dataRoot,socket:readFileSync(process.argv[2],"utf8").includes("MOSAIC_TESTER_SOCKET_V1")}));' - tester_facts() { - local LC_ALL=C pattern='^\{"dataRoot":"([A-Za-z0-9/._@+-]+)","socket":(true|false)\}$' -@@ -193,7 +320,7 @@ - MOSAIC_UID="$(uid_of "$S_USER")" - [[ "$MOSAIC_UID" =~ ^[1-9][0-9]*$ ]] || refuse "stack account has no non-root uid" - [ "$(db passwd "$S_USER" | cut -d: -f6)" = "$S_HOME" ] || refuse "saved home differs from account home" +@@ -196 +323 @@ -QUERY="$(tester_capture 5 4096 sudo -u "$S_USER" -H env -i "HOME=$S_HOME" "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin" "$NODE" --input-type=module -e "$TESTER_QUERY" "$COPY/packages/cli/src/config.mjs" "$COPY/packages/webui/src/cli.mjs")" || refuse "tester config query failed" +QUERY="$(tester_capture 5 7 4096 "$S_USER" "$S_HOME" "$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin" "$NODE" --input-type=module -e "$TESTER_QUERY" "$COPY/packages/cli/src/config.mjs" "$COPY/packages/webui/src/cli.mjs")" || refuse "tester config query $(tester_capture_reason "$?")" - tester_facts "$QUERY" || refuse "tester config query has invalid facts" - [ "$TS_MARKED" = true ] || refuse "target does not support --tester-socket" - tester_location "$TS_DATA" "$S_HOME" "$MOSAIC_UID" "$ROOT" observe || refuse "tester path refuses $TS_REASON" -@@ -533,11 +660,11 @@ - before="$(tester_snapshot)" || why=metadata - python="$(tester_python)" || why=interpreter - if [ -z "$why" ]; then +@@ -536 +663 @@ - peer="$(tester_capture 3 128 sudo -u "$S_USER" -H env -i "HOME=$S_HOME" "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin" "$python" -I -S -c "$TESTER_PEER" "$TESTER_PATH")" || why=probe + peer="$(tester_capture 2 4 128 "$S_USER" "$S_HOME" "$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin" "$python" -I -S -c "$TESTER_PEER" "$TESTER_PATH")" || why="probe-$(tester_capture_reason "$?")" - if [[ "$peer" =~ $pattern ]]; then - pid="${BASH_REMATCH[1]}" uid="${BASH_REMATCH[2]}" gid="${BASH_REMATCH[3]}" - [ "$pid" = "$CON_PID" ] && [ "$uid" = "$MOSAIC_UID" ] && [ "$gid" -le 2147483647 ] || why=peer +@@ -540 +667 @@ - else why=probe; fi + else [ -n "$why" ] || why=probe; fi - after="$(tester_snapshot)" || why=metadata - [ "$before" = "$after" ] || why=changed - fi --- a/packages/cli/tests/case-sudo-fixture.mjs +++ b/packages/cli/tests/case-sudo-fixture.mjs -@@ -1,22 +1,34 @@ - // Shared fake process model for the two Case host-script tests. No real sudo. - // Call case_sudo_run RECORD_DIR COMMAND... after the caller's argv checks. - // Missing session-mode preserves the original exec-style fake. +@@ -3,0 +4,3 @@ +import { spawn } from 'node:child_process'; +import { readFileSync, readdirSync } from 'node:fs'; + - export const CASE_SUDO_SESSION = String.raw` +@@ -4,0 +8,13 @@ +case_sudo_entry() { + local record="$1" here sid; shift + here="$BASHPID" @@ -376,17 +323,13 @@ + fi + exec "$@" +} - case_sudo_run() { +@@ -6,2 +22,3 @@ - local record="$1" child="" rc=0; shift - if [ "$(cat "$record/session-mode" 2>/dev/null)" != hop ]; then exec "$@"; fi + local record="$1" child="" rc=0 mode=exec; shift + [ ! -f "$record/session-mode" ] || mode="$(<"$record/session-mode")" + if [ "$mode" != hop ]; then case_sudo_entry "$record" "$@"; return "$?"; fi - # The fake stays as parent, just as sudo does on its pty path. KILL cannot - # be caught or forwarded. The child creates the separate terminal session. - trap '[ -z "$child" ] || kill -TERM -- "-$child" 2>/dev/null || :' TERM - trap '[ -z "$child" ] || kill -HUP -- "-$child" 2>/dev/null || :' HUP - trap '[ -z "$child" ] || kill -INT -- "-$child" 2>/dev/null || :' INT +@@ -13,7 +30,2 @@ - /usr/bin/setsid /bin/bash -c ' - record="$1"; shift - here="$BASHPID" @@ -396,24 +339,12 @@ - ' -- "$record" "$@" & + export -f case_sudo_entry + /usr/bin/setsid /bin/bash -c 'case_sudo_entry "$@"' -- "$record" "$@" & - child=$! - # An interrupted wait returns while the child still runs; keep waiting. - while :; do -@@ -28,7 +40,9 @@ - `; - - export function liveRecordedSessions(recordFile, readFile, procDirs) { +@@ -31 +43,3 @@ - const sessions = new Set(readFile(recordFile).trim().split('\n').filter(Boolean).map(line=>Number(line.trim().split(/\s+/)[1]))); + // Exec fake entries share the test runner's session. Only self-led sessions + // are isolated and safe to scan here; exec coverage also checks exact PIDs. + const sessions = new Set(readFile(recordFile).trim().split('\n').filter(Boolean).map(line=>line.trim().split(/\s+/).map(Number)).filter(([pid,sid])=>pid===sid).map(([,sid])=>sid)); - const live=[]; - for(const entry of procDirs()){ - if(!/^\d+$/.test(entry))continue; -@@ -39,3 +53,90 @@ - } - return live; - } +@@ -41,0 +56,87 @@ + +export function recordedPids(file) { + try{return readFileSync(file,'utf8').trim().split('\n').filter(Boolean).map(line=>line.trim().split(/\s+/).map(Number));} @@ -503,38 +434,23 @@ +} --- a/packages/cli/tests/case2-unit.test.mjs +++ b/packages/cli/tests/case2-unit.test.mjs -@@ -13,7 +13,7 @@ - import { businessDoc, writeJson } from "../../business/tests/helpers.mjs"; - import { binds, prepareRun, sandboxArgv, sandboxProblem } from "../../seat/src/sandbox.mjs"; - import { tmp } from "./helpers.mjs"; +@@ -16 +16 @@ -import { CASE_SUDO_SESSION, liveRecordedSessions } from "./case-sudo-fixture.mjs"; +import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource } from "./case-sudo-fixture.mjs"; - - const REPO = fileURLToPath(new URL("../../../", import.meta.url)); - const UNIT = join(REPO, "scripts", "case2-unit.sh"); -@@ -93,7 +93,7 @@ - echo "$BASHPID" >>'${root}/query-pids'; wait; exit - fi - if [ -f '${root}/query-orphan' ]; then +@@ -96 +96 @@ - (trap '' TERM; exec sleep 60) /dev/null 2>&1 & + (trap '' TERM; here="$BASHPID"; read -r sid < <(/bin/ps -o sid= -p "$here"); printf '%s %s\\n' "$here" "$sid" >'${root}/query-owned'; exec sleep 60) /dev/null 2>&1 & - echo "$!" >'${root}/query-pids' - cat '${root}/query-output'; exit 0 - fi -@@ -186,41 +186,62 @@ - } - }); - +@@ -189,2 +189,4 @@ -// Draft counterexample for DESIGN-C4. Written after sage-2307, not run. -test('row253 C4 query cleanup handles both exec and sudo session-hop fakes', async t=>{ +// C4 cases run through both sudo process models. Exec shares the runner's +// session, so its session-wide check would be vacuous; exact recorded PIDs +// are checked in both modes. Hop additionally isolates the fake session. +for(const kind of ['sleep','signals','member']) test(`row253 C4 ${kind} query has exact inner reason and cleans all members`,async t=>{ - for(const mode of ['exec','hop']){ +@@ -192 +194 @@ - const h=hub(t),pids=join(h.root,'session-pids'),queryPids=join(h.root,'query-pids'),copy=join(copyDir(h),'packages/cli/src/config.mjs'); + const h=hub(t),sessions=join(h.root,'session-pids'),queries=join(h.root,'query-pids'),files=[sessions,queries]; - writeFileSync(join(h.root,'session-mode'),mode); +@@ -194,21 +196,10 @@ - writeFileSync(pids,''); - writeFileSync(copy,`import fs from 'node:fs';process.on('SIGTERM',()=>{});process.on('SIGHUP',()=>{});fs.appendFileSync(${JSON.stringify(queryPids)},process.pid+'\\n');const s=fs.readFileSync('/proc/self/stat','utf8');const sid=Number(s.slice(s.lastIndexOf(') ')+2).split(' ')[3]);if(sid===process.pid)fs.appendFileSync(${JSON.stringify(pids)},process.pid+' '+sid+'\\n');export function loadSystem(){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0);}`); - t.after(()=>{ @@ -566,7 +482,7 @@ + assert.deepEqual(await pollFixtureGone(files),[],mode); + if(kind==='member')assert.ok(read(proof).includes('('+member.split('/').at(-1)+') '),'member comm with newline and closing parenthesis was observed'); + assert.equal(existsSync(h.log),false,mode);await stopGuard(); - } +@@ -215,0 +207,30 @@ +}); + +test('row253 C4 overflow is output-limit in both sudo modes',async t=>{ @@ -597,77 +513,36 @@ + const extract=s=>s.split('# row253-supervisor-begin\n')[1]?.split('# row253-supervisor-end')[0]; + const a=extract(read(UNIT)),b=extract(read(join(REPO,'scripts/case1-accept.sh'))); + assert.ok(a?.length>100);assert.equal(a,b); - }); - - test('row253 query reaps its group even when a child closes stdout before the query returns',async t=>{ - const h=hub(t);writeFileSync(join(h.root,'query-orphan'),''); - writeFileSync(join(h.root,'query-output'),JSON.stringify({dataRoot:h.dataRoot,socket:true})); - let pid; +@@ -222,2 +243,2 @@ - t.after(()=>{if(pid)try{process.kill(pid,'SIGKILL');}catch{}}); - const r=unit(h,{profile:'factory'}); + const capture=cleanupFixture(t,[join(h.root,'query-owned')]); + const r=unit(h,{profile:'factory'});capture(); - pid=Number(read(join(h.root,'query-pids')).trim()); - assert.equal(r.code,0,r.out+r.err); - let state='gone'; -@@ -232,8 +253,8 @@ - assert.ok(state==='gone'||state==='Z',`query descendant ${pid} still ${state}`); - }); - +@@ -235,2 +256,2 @@ -test('row253 config parser is strict and output capture is bounded',()=>{ - const source=read(UNIT), body=source.slice(source.indexOf('tester_capture() ('),source.indexOf('tester_location() {')); +test('row253 config parser is strict',()=>{ + const source=read(UNIT), body=source.slice(source.indexOf('tester_facts() {'),source.indexOf('tester_location() {')); - const valid=spawnSync('/bin/bash',['-c',body+'\ntester_facts "$1" && printf "%s %s" "$TS_DATA" "$TS_MARKED"','--','{"dataRoot":"/srv/mosaic/data","socket":true}'],{encoding:'utf8'}); - assert.equal(valid.status,0);assert.equal(valid.stdout,'/srv/mosaic/data true'); - const facts=['{"dataRoot":"/srv/mosaic/data","socket":true,"extra":1}','{"dataRoot":4,"socket":true}','{"dataRoot":"/srv/mosaic/data","socket":"true"}','{"dataRoot":"/srv/mosaic/data","socket":true}\nP10 forged closed']; -@@ -241,8 +262,6 @@ - const r=spawnSync('/bin/bash',['-c',body+'\ntester_facts "$1"','--',fact]); - assert.notEqual(r.status,0,fact); - } +@@ -244,2 +264,0 @@ - const r=spawnSync('/bin/bash',['-c',body+'\ntester_capture 1 32 /usr/bin/head -c 100 /dev/zero']); - assert.notEqual(r.status,0);assert.equal(r.stdout.length,0); - }); - - function refuses(r, pattern) { --- a/packages/cli/tests/case1-accept.test.mjs +++ b/packages/cli/tests/case1-accept.test.mjs -@@ -11,7 +11,7 @@ - import { dirname, join } from "node:path"; - import { fileURLToPath } from "node:url"; - import { tmp } from "./helpers.mjs"; +@@ -14 +14 @@ -import { CASE_SUDO_SESSION } from "./case-sudo-fixture.mjs"; +import { CASE_SUDO_SESSION, recordedLivePids, killRecordedPids, fixtureGuard, cleanupFixture, pollFixtureGone, blockingQuerySource } from "./case-sudo-fixture.mjs"; - - const REPO = fileURLToPath(new URL("../../../", import.meta.url)); - const SCRIPT = join(REPO, "scripts", "case1-accept.sh"); -@@ -87,7 +87,8 @@ - cmd="$1"; shift - args=(); for a in "$@"; do if [[ "$a" == /* ]] && [ -e "$ROOT_DIR$a" ]; then args+=("$ROOT_DIR$a"); else args+=("$a"); fi; done - export HOME="$ROOT_DIR/srv/mosaic" PATH="$np$PATH" +@@ -90 +90,2 @@ -case_sudo_run "$HC" "$ROOT_DIR$cmd" "\${args[@]}"`, +[ ! -e "$ROOT_DIR$cmd" ] || cmd="$ROOT_DIR$cmd" +case_sudo_run "$HC" "$cmd" "\${args[@]}"`, - }; - - // Executables inside the tree, run by the fake sudo or by the script itself. Node marks a run outside -@@ -97,7 +98,11 @@ - exec "$REAL_NODE" "$@"`; - // The real Python payload runs against a mock socket object, never an OS listener. - const PYTHON_STUB = `[ -f "$HC/peer-output" ] && { cat "$HC/peer-output"; exit "$(cat "$HC/peer-status")"; } +@@ -100 +101,5 @@ -[ -f "$HC/peer-hang" ] && { echo "$BASHPID" > "$HC/peer-pid"; exec sleep 60; } +[ -f "$HC/peer-hang" ] && { + here="$BASHPID"; read -r sid < <(/bin/ps -o sid= -p "$here") + echo "$here" > "$HC/peer-pid"; printf '%s %s\\n' "$here" "$sid" >> "$HC/query-pids" + trap '' TERM HUP; exec sleep 60 +} - if [ -f "$HC/peer-change" ]; then cat "$HC/peer-change" > "$HC/stat-socket"; fi - if [ -f "$HC/peer-pid-change" ]; then cat "$HC/peer-pid-change" > "$HC/show-mosaic-webui.service"; fi - exec /usr/bin/python3 -I -S -c ' -@@ -301,6 +306,64 @@ - } - }); - +@@ -303,0 +309,58 @@ +test('row253 C4 acceptance query inner reason holds under both sudo models',async t=>{ + for(const mode of ['exec','hop']){ + const h=host(t),files=[join(h.hc,'session-pids'),join(h.hc,'query-pids')]; @@ -726,47 +601,23 @@ + } +}); + - // One fault per F check; each makes that check, and only that one, fail. - const F_FAULTS = { - F1: (h) => proc(h.root, 100, { ppid: 1, uid: 0, exe: NODE, comm: "node" }), --- a/packages/webui/README.md +++ b/packages/webui/README.md -@@ -125,9 +125,9 @@ - With `--instance NAME` (row 190) every verb works on - `mosaic-webui-NAME.service`, which sets NAME's `MOSAIC_CONFIG`; install - checks NAME's `mosaic-bus-NAME@` units for the copy. render and install then +@@ -128 +128 @@ -need `--port`, and 7330, 7332 or 7333 as `--port` or `--tester-port` exits 4 +need `--port`, and 7330, 7332 or 7333 as the main or legacy tester port exits 4 - with port-reserved. For every instance, install refuses a `--port` or +@@ -130 +130 @@ -`--tester-port` that another installed Console unit in the unit directory +legacy tester port that another installed Console unit in the unit directory - already takes. That refusal is port-claimed, exit 1, and writes nothing. A - unit with no `--port` holds 7332. See - [TOOLS.md, Stack instances](../../docs/TOOLS.md#stack-instances---instance-name-row-190). -@@ -141,7 +141,7 @@ - - ## Sign-in in the Console (Q175 phase 1, #1690) - +@@ -144 +144 @@ -`serve --sso [--send] [--tester-port N]` makes the Console an OpenID Connect +`serve --sso [--send] [--tester-socket]` makes the Console an OpenID Connect - relying party. It reads `console.sso` from the system config (`publicOrigin` - and one or more providers, each with its issuer, client id, a client secret - slot, the signing algorithms it accepts and the subjects allowed in). The -@@ -173,7 +173,7 @@ - - The page shows Sign out only under `--sso`. It posts to `/auth/logout`, which - ends the session and goes to the provider's end-session endpoint when it has - one, or to `/auth/signed-out`. +@@ -176 +176 @@ -- With `--tester-port`, the tester listener refuses any request carrying an +- With `--tester-socket`, the tester listener refuses any request carrying an - `Authorization` header, a `__Host-mosaic_session` or `__Host-mosaic_login` - cookie, or an `x-authentik-*` header (403 `tester listener takes no - sign-in`). It never serves `/auth/*`. --- a/docs/plans/DEFERRED.md +++ b/docs/plans/DEFERRED.md -@@ -270,20 +270,6 @@ - (`:1538`, `:1689`, `:1691`). For a later row that edits - `stack-release.sh`. Webby N1 on row 238 r1 (REVIEW-r1.md, 433a95292, - #1744). (2026-10-21) +@@ -273,14 +272,0 @@ -- **Gate runs mount the host's pi auth file.** `compose.yaml:43` mounts - `${PI_AUTH_FILE:-/home/jwoltje/.pi/agent/auth.json}` read-only into - the agent container. No suite sets `PI_AUTH_FILE`, so the @@ -781,13 +632,7 @@ - the lane's `cleanEnv`, and a test that no gate step mounts the host - default. It needs its own row. Webby N4 on the row 197 proof driver - (bba870ee9), traced by Herb (herb-0051). (2026-10-21) - - **Lane image prep inspects a base that BuildKit never tagged.** - (N7 of the row 197 proof.) After the Compose build, - `scripts/testing/lane-image.mjs:41` (`prepareImage`) runs -@@ -360,6 +346,40 @@ - do nothing yet. The row that adds the Console library routes decides - whether the tester listener gets `library.sync`. Duckworth's view is - that it shouldn't, because sync is a human write. (2026-10-21) +@@ -362,0 +349,34 @@ +- **Console library routes: four library codes still map to 502.** + (Dewey N2 on the row 254 manifest, #1759 comment 30173.) Row 254 + gave six library codes a STATUS entry in `packages/webui/src/bus.mjs`. @@ -822,13 +667,7 @@ + credential and a real `release.sh activate`, and records both results on + the re-pin row. Morgana's wording (row 244 BUILD.md), Webby N2 on row 244 + r1 (#1757). (2026-10-21) - - ## Queue - -@@ -425,3 +445,17 @@ - at a time (Filbert F2; Darkwing and Filbert T1 on #1522; - flows.test.mjs). - The frozen-cgroup test (Filbert F1) stays open above. +@@ -427,0 +448,14 @@ +- Gate runs mount the host's pi auth file (Webby N4 on the row 197 proof + driver, 059577886): closed by row 244 (#1757), landing e69260a3c. The + suites and the lane's `cleanEnv` export a 0600 fixture `PI_AUTH_FILE`,