docs: propose row253 C4 session supervisor and draft hop fake
This commit is contained in:
@@ -157,3 +157,15 @@ Grant sage-2279 run1: both intended RED cases observed. First installer GREEN fi
|
||||
Grant sage-2279 run1 ended on unexpected failure 2, reported to Sage and Mos as gander-0073, slot returned 2026-10-22T00:34:10Z. Acceptance whole 27/1 in 41.962 s: the broad new od fake intercepted F3 random-token generation as well as the ELF header read. Narrowed only that fake to -An -tx1 -N4; all other calls exec /usr/bin/od. The 12-hex assertion stays unchanged. Product blobs have not changed during the window. No tests run after the stop, and no checks/full suites/shell files started. run1/SUMMARY.json sums all raw logs; sweep count 0. Need a new CLEAR before any rerun.
|
||||
|
||||
Sage-2287 approves both test-only fixture fixes and requests their commit with unchanged assertions. Commit contains the two prepared row253 test files and packet, including both old/new test SHA pairs and raw run1 results. Product scripts, README and root BUILD-LOG remain uncommitted. PORT-AUDIT.md/json refresh current line numbers after the od fake grew two lines. The earlier audit above describes the pre-fix candidate. Next CLEAR scope: both whole touched files, M1-M33, cli and webui whole and ten shell scripts. Intended RED carries under unchanged product bytes; no further test has run.
|
||||
|
||||
## C4 static gap and addendum
|
||||
|
||||
Grant sage-2297: installer whole36/0 in16.449s, acceptance whole28/0 in37.550s, M1-M33 killed33/33 (bases11.808s, changed runs41.202s), CLI whole755/0 in597.084s. Webui was deliberately interrupted after4.736s for a static C4 gap; it is not a failed test under sage-2307 item5 and has no completed total. Ten shells never started. Slot returned with sweep0 as gander-0076. run2/SUMMARY.json is script-summed from the preserved results and raw logs.
|
||||
|
||||
The current outer GNU timeout kills its own group, but sudo(8) Process model documents a pty monitor creating a different terminal session. Signal handling says SIGKILL cannot be relayed. The exec fake does not cover that path. No host experiment ran. Duckworth-0109/0110 at2c49dd80c confirms the static gap. DESIGN.md remains unchanged; DESIGN-C4.md proposes the after-uid supervisor and independent outer bound, states the stalled-supervisor residual, compares runtime options and records the old talk watchdog as a proposed bounded follow-up.
|
||||
|
||||
Sage-2307/2312 authorizes writing the fake and addendum without tests. The shared case-sudo-fixture.mjs is now imported by both host-script tests. It preserves exec mode and offers a parent/setsid/catchable-signal hop mode with PID/SID records. A new installer counterexample blocks a TERM/HUP-resistant copy under both modes and checks session/PID cleanup; its explicit fixture cleanup only kills recorded fixture PIDs. This draft fake has not run, and no syntax execution is claimed for it. The two script product blobs remain exactly the granted bytes; test bytes now differ for the draft counterexample and shared seam. No repair is implemented.
|
||||
|
||||
The next action is Duckworth's static ruling on DESIGN-C4.md and the fake commit, not a test run. If product changes, the completed gate results are historical only and the whole gate reruns on Sage's next CLEAR. C4 now means killed at the inner bound and root never waits past the outer bound; a stopped/killed supervisor and setsid escape remain the same-uid residual. No root cross-session killer is proposed.
|
||||
|
||||
For the static pass, c4/*.sh.txt are non-executable snapshots of the exact granted product source. FROZEN-PRODUCT.json pins their equality to the uncommitted source blobs. They are review records, not an implemented repair or a product commit.
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# Row253 C4 addendum: the supervisor belongs after sudo
|
||||
|
||||
Draft for Duckworth's static ruling. DESIGN.md remains unchanged. Authority:
|
||||
duckworth-0109/0110, RULING-c4.md at 2c49dd80c, and sage-2307/2312.
|
||||
No repaired product code or new test has run. Product scripts still have the
|
||||
original frozen bytes; this addendum does not claim they satisfy C4.
|
||||
|
||||
## Finding and evidence
|
||||
|
||||
The current tester_capture puts GNU timeout --signal=KILL outside sudo, then
|
||||
kills that timeout's process group. It works for the exec-style sudo fake.
|
||||
sudo(8), **Process model**, says the pty monitor creates a new terminal session
|
||||
and forks the command there. **Signal handling** (called Signals in the local
|
||||
rendering) says SIGKILL cannot be caught and relayed. Therefore an outer group
|
||||
kill does not establish that the query in the new session is killed.
|
||||
|
||||
Evidence was a static read of the installed sudo(8)/sudoers(5) documentation,
|
||||
then Duckworth's independent ruling against sudo 1.9.17p2. No real sudo, host
|
||||
experiment, live unit or live-port experiment ran. The shared fake below is
|
||||
written but unrun. It models the session boundary, not authentication or a pty.
|
||||
|
||||
## Proposed trusted supervisor and runtime trade
|
||||
|
||||
Recommend a reviewed inline Bash supervisor, run **after the uid drop**, using
|
||||
root-validated fixed system Bash, setsid, sleep and kill/builtins. These are
|
||||
host tools, never binaries or scripts from the selected copy. Resolve system
|
||||
symlinks to their real targets; require executable root-owned ELF files and
|
||||
safe root-owned ancestors, outside homes. Do not use PATH lookup for these
|
||||
supervisor tools. env -i removes BASH_ENV and other injected runtime settings.
|
||||
Add setsid to the documented host dependencies. No new configuration knob.
|
||||
|
||||
This is a proposal under R1's root-validated system-binary option, pending
|
||||
Duckworth's approval of Bash rather than the example GNU timeout. The exact
|
||||
inline supervisor must be identical in both host scripts, with budgets and
|
||||
command argv passed separately. No helper is imported from the copy.
|
||||
|
||||
Alternatives weighed:
|
||||
|
||||
- GNU timeout alone inside sudo fixes the session hop but kills one process
|
||||
group. It misses a grandchild that uses setpgid without setsid. It needs the
|
||||
same owned-session cleanup as the proposed supervisor.
|
||||
- Validated --node with inline builtins can implement the same supervisor.
|
||||
The installer already validates it. Acceptance deliberately reports unsafe
|
||||
--node paths through F9/F10; making that binary a required trusted supervisor
|
||||
would change those existing outcomes or need another pinned runtime.
|
||||
- Fixed root-validated system Python can fork a session, multiplex bounded
|
||||
output and scan /proc cleanly. It is already used for P10, but making it
|
||||
mandatory for the config query changes missing/unsafe Python from a failed
|
||||
P10 observation into a preflight refusal. Preserve that behavior here.
|
||||
|
||||
## R1: own a query session, not sudo's group
|
||||
|
||||
The supervisor starts in sudo's possibly separate session as the stack uid.
|
||||
It forks the query child, then setsid occurs in that child through the fixed
|
||||
system setsid binary. The supervisor itself may already be a group leader,
|
||||
so it must not attempt setsid in its own process. Disable Bash job control;
|
||||
the newly forked child is not a group leader, so setsid need not fork again.
|
||||
The child's PID from $! is the kernel session id. No PID comes from copy output.
|
||||
|
||||
Run a separate five-second watchdog inside the uid boundary. On expiry, on a
|
||||
supervisor catchable signal, on output refusal, and after normal query exit:
|
||||
|
||||
1. Scan numeric /proc/*/stat entries for the owned session id. Parse after the
|
||||
final closing parenthesis, so spaces, parentheses and newlines in comm do
|
||||
not shift the kernel fields. Use the kernel session field, not ancestry.
|
||||
2. SIGKILL every live PID in that session, including different process groups.
|
||||
Repeat until no running members remain or a fixed 250 ms cleanup budget
|
||||
expires. Never select a process from a different session.
|
||||
3. Wait for the direct child and the watchdog. Refuse on a cleanup limit or
|
||||
read/parse error that prevents establishing cleanup. Zombies are not running;
|
||||
only direct children can be reaped here. Tests pin that distinction.
|
||||
|
||||
A five-second deadline concerns the inner query. The root-side deadline is
|
||||
independent. Query bytes are accepted only after success and completed cleanup.
|
||||
The query can never authorize a root kill: its stdout supplies no PID/session.
|
||||
The supervisor runs without root authority and kills only its owned session.
|
||||
|
||||
## R2: independent root bound and bounded output
|
||||
|
||||
Root waits at most seven seconds for the metadata sudo call. Keep GNU timeout
|
||||
outside sudo for this separate purpose. Keep the 4096-byte cap plus one-byte
|
||||
overflow detection. All partial output is discarded on any refusal.
|
||||
|
||||
Give outer timeout its own operator reason, distinct from inner timeout,
|
||||
child failure, output limit and cleanup refusal. A child-supplied exit value
|
||||
alone must not masquerade as proof the outer timer fired. Capture GNU timeout's
|
||||
trusted verbose signal notice separately, with sudo/helper stderr redirected
|
||||
away from that channel. The root-owned notice file is outside the user's
|
||||
home. Validate the combination of notice and status; no eval or source.
|
||||
|
||||
Apply the same primitive to the new P10 peer probe, with inner two seconds,
|
||||
outer four seconds, a 128-byte output cap and the existing one-second socket
|
||||
connect timeout. Preserve its fixed system-Python safety check and failed-P10
|
||||
behavior. All observed stdout still requires successful cleanup and exact
|
||||
pid/uid/gid validation. Neither path sends application bytes to the socket.
|
||||
|
||||
## R3: narrowed claim and residual
|
||||
|
||||
C4 now means: killed at the inner bound, and root never waits past the outer
|
||||
bound. A hostile same-uid query can kill/stop the supervisor or setsid away.
|
||||
A hang in the trusted supervisor has the same outer-bound outcome. In that
|
||||
case root refuses and discards stdout; the separate session may remain.
|
||||
This is the existing DESIGN.md:97-99 same-uid limit, not a new closure claim.
|
||||
No root-side cross-session killer is added. It would require another row.
|
||||
|
||||
The stalled-supervisor fake must prove outer timing, its own refusal reason
|
||||
and no use of partial stdout. It then explicitly kills its recorded leftover
|
||||
session as fixture cleanup, naming that step as the R3 residual. It must not
|
||||
claim the product cleaned that session. Other normal-supervisor cases assert
|
||||
no running members of the recorded query session remain after the call.
|
||||
|
||||
## R4 and F5: headers and remaining legacy calls
|
||||
|
||||
Both headers will name the inner supervisor, its runtime/tool validation,
|
||||
five/seven-second query bounds and two/four-second peer-probe bounds.
|
||||
|
||||
The old F3 talk path (currently timeout 330 around AS_USER talk) has the same
|
||||
sudo session-hop issue. Propose recording it as a bounded follow-up, rather
|
||||
than changing it in this slice: it predates the socket metadata/probe helpers,
|
||||
drives a 300-second functional conversation and simultaneous F5 scan, and has
|
||||
existing stdout/stderr diagnostics. It is not part of the new five-second
|
||||
config query. This is an explicit F5 scope choice for Duckworth/Sage to accept
|
||||
or reject, not a claim the old talk timer kills a separate session. Likewise,
|
||||
the other legacy AS_USER diagnostics are not newly supervised by this row.
|
||||
|
||||
## Shared fake and next evidence
|
||||
|
||||
packages/cli/tests/case-sudo-fixture.mjs provides the one process-model seam.
|
||||
Both existing test files import it. Mode exec retains the prior fake. Mode
|
||||
hop keeps a parent, forks through setsid, records kernel PID and SID, and
|
||||
relays TERM/HUP/INT only. It cannot relay KILL. The installer draft case makes
|
||||
the copy ignore TERM/HUP and block; it checks recorded query PIDs and the
|
||||
separate fake session, then explicitly cleans fixture-owned PIDs on failure.
|
||||
This is written but not run, and is expected to expose the old product gap.
|
||||
|
||||
Before the next whole gate, complete both-mode cases for timeout, TERM/HUP,
|
||||
setpgid grandchild, overflow, and the distinct stalled-supervisor residual.
|
||||
Both host-script paths use this seam. Add changes for absent inner supervisor,
|
||||
absent outer bound, leader-group-only cleanup and unbounded read. The setpgid
|
||||
case must kill the group-only change. Record every restore and raw log.
|
||||
|
||||
Duckworth reviews this addendum and fake statically first. Product code then
|
||||
implements the approved mechanism. All product changes void prior results
|
||||
under sage-2307/mos-1200. Only Sage's next CLEAR permits a diagnostic or gate
|
||||
run. The next gate is both whole touched files, all checks, CLI/WebUI whole
|
||||
and all ten shell files. Existing RED evidence carries only if applicable
|
||||
product bytes remain equal. No host deployment is authorized by this packet.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"agents/gander/work/queue-253/c4/case1-accept.sh.txt": {
|
||||
"source": "scripts/case1-accept.sh",
|
||||
"sha256": "1302cfc659ff9073035b99d7b4071aa2c75070da142bb201d2f58a66e113d71f"
|
||||
},
|
||||
"agents/gander/work/queue-253/c4/case2-unit.sh.txt": {
|
||||
"source": "scripts/case2-unit.sh",
|
||||
"sha256": "a84bb266d52a5c078171582e2f9a511becf56421ae218266e3238d704727f6dd"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 195 (#1693): the Case 1 acceptance on 10.1.1.53, design r7 section 5
|
||||
# (agents/darkwing/work/queue-195/DESIGN.md). Deviations and decisions: agents/darkwing/work/queue-195/BUILD.md.
|
||||
#
|
||||
# case1-accept.sh --business ID --args FILE --claude-pin PIN --node-pin vN.N.N
|
||||
#
|
||||
# Root runs it on the Case 1 host after the install window, from a root-owned checkout. It reads the
|
||||
# user, home, copy, node, claude, main port and tester socket marker from the args file, which must pass stack-upgrade.sh's
|
||||
# rules and be a factory file for --business. Every command that runs the stack's code runs as
|
||||
# `sudo -u USER -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin` from /. Root itself reads ss,
|
||||
# /proc, the account database and file metadata, and runs these: systemctl show and is-enabled (F1,
|
||||
# P11), curl on the Console's loopback healthz (F2), sudo -l (P1, P3), sudo -u jarvis bwrap --version
|
||||
# and hostcheck.sh --check (P9), and node to parse managed-settings.json (F9). F9 runs node only when
|
||||
# --node is the pinned path and it and every directory above it are root's with no group or other
|
||||
# write. The script changes nothing on the host but the message F3 sends and its reply, and it never
|
||||
# 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.
|
||||
#
|
||||
# One line per check, `<id> <path> <mark> <evidence>`, then `count <mark> N` for each mark and a
|
||||
# result line. F checks are `pass` or `FAIL`. P checks are `closed`, `accepted <ruling>` or
|
||||
# `unnamed`, which means no Q182, Q183 or Mos ruling names the path; each unnamed line goes to Mos.
|
||||
#
|
||||
# Exit 0 when every F check passes, 1 when one fails, 2 on usage or a refusal (a bad args file, or
|
||||
# --root misused). A P check never changes the exit.
|
||||
#
|
||||
# Test seam, for packages/cli/tests/case1-accept.test.mjs only. Root takes none:
|
||||
# --root DIR prefixes the args file, /etc, /proc, the stack's home, the host check and node, and
|
||||
# reads the account database from DIR/etc/passwd and DIR/etc/group instead of getent
|
||||
set -uo pipefail
|
||||
|
||||
usage() { echo "usage: case1-accept.sh --business ID --args FILE --claude-pin PIN --node-pin vN.N.N" >&2; exit 2; }
|
||||
refuse() { echo "case1-accept: refusing: $*" >&2; exit 2; }
|
||||
BUSINESS="" ARGS_FILE="" CLAUDE_PIN="" NODE_PIN="" ROOT=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--business) [ $# -ge 2 ] && [[ "$2" =~ ^[a-z0-9][a-z0-9-]*$ ]] || usage; BUSINESS="$2"; shift 2 ;;
|
||||
--args) [ $# -ge 2 ] && [[ "$2" =~ ^/[A-Za-z0-9/._+-]+$ ]] || usage; ARGS_FILE="$2"; shift 2 ;;
|
||||
--claude-pin) [ $# -ge 2 ] && [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || usage; CLAUDE_PIN="$2"; shift 2 ;;
|
||||
--node-pin) [ $# -ge 2 ] && [[ "$2" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || usage; NODE_PIN="$2"; shift 2 ;;
|
||||
--root) [ $# -ge 2 ] && [[ "$2" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [ -d "$2" ] || usage; ROOT="${2%/}"; shift 2 ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$BUSINESS" ] && [ -n "$ARGS_FILE" ] && [ -n "$CLAUDE_PIN" ] && [ -n "$NODE_PIN" ] || usage
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
[ -z "$ROOT" ] || refuse "--root is a test seam, and root takes none"
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
else
|
||||
[ -n "$ROOT" ] || refuse "run it as root"
|
||||
fi
|
||||
cd / || refuse "cannot cd to /"
|
||||
|
||||
# --- The args file, by stack-upgrade.sh's read_args rules, plus the serve ports ---
|
||||
|
||||
f="$ROOT$ARGS_FILE"
|
||||
[ -f "$f" ] && [ ! -L "$f" ] || refuse "no regular file $ARGS_FILE"
|
||||
st="$(stat -c '%u %a' -- "$f")" || refuse "cannot stat $ARGS_FILE"
|
||||
[ "${st% *}" = "$(id -u)" ] && (( (8#${st#* } & 8#022) == 0 )) ||
|
||||
refuse "$ARGS_FILE must be $(id -un)'s with no group or other write (it is uid ${st% *}, mode ${st#* })"
|
||||
mapfile -t ARGS < "$f" || refuse "cannot read $ARGS_FILE"
|
||||
S_USER="" S_HOME="" A_BUSINESS="" NODE="" COPY_ID="" CLAUDE="" PROFILE="" PORT="" TESTER_SOCKET=0
|
||||
copies=0 past=""
|
||||
for ((i = 0; i < ${#ARGS[@]}; i++)); do
|
||||
a="${ARGS[i]}" v="${ARGS[i + 1]:-}"
|
||||
if [ -n "$past" ]; then
|
||||
case "$a" in
|
||||
--port) PORT="$v"; i=$((i + 1)) ;;
|
||||
--business) [ "$v" = "$BUSINESS" ] || refuse "serve business differs"; i=$((i + 1)) ;;
|
||||
--tester-socket) TESTER_SOCKET=$((TESTER_SOCKET + 1)) ;;
|
||||
--tester-port) refuse "case1-accept.sh retires tester TCP; use --tester-socket" ;;
|
||||
*) refuse "unknown serve flag $a" ;;
|
||||
esac
|
||||
continue
|
||||
fi
|
||||
case "$a" in
|
||||
--) past=1 ;;
|
||||
--pm) ;;
|
||||
--user) S_USER="$v"; i=$((i + 1)) ;;
|
||||
--home) S_HOME="$v"; i=$((i + 1)) ;;
|
||||
--business) A_BUSINESS="$v"; i=$((i + 1)) ;;
|
||||
--node) NODE="$v"; i=$((i + 1)) ;;
|
||||
--claude) CLAUDE="$v"; i=$((i + 1)) ;;
|
||||
--profile) PROFILE="$v"; i=$((i + 1)) ;;
|
||||
--copy) COPY_ID="$v"; copies=$((copies + 1)); i=$((i + 1)) ;;
|
||||
*) refuse "$ARGS_FILE has $a before --, which case2-unit.sh does not take" ;;
|
||||
esac
|
||||
done
|
||||
[ "$copies" = 1 ] || refuse "$ARGS_FILE needs exactly one --copy before --, not $copies"
|
||||
[[ "$S_USER" =~ ^[a-z_][a-z0-9_-]*$ ]] && [[ "$S_HOME" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$A_BUSINESS" =~ ^[a-z0-9][a-z0-9-]*$ ]] &&
|
||||
[[ "$NODE" =~ ^/[A-Za-z0-9/._+-]+$ ]] && [[ "$COPY_ID" =~ ^[0-9a-f]{40}$ ]] && [[ "$CLAUDE" =~ ^/[A-Za-z0-9/._+@-]+$ ]] ||
|
||||
refuse "$ARGS_FILE lacks a plain --user, --home, --business, --node, --claude or --copy"
|
||||
[ "$PROFILE" = factory ] || refuse "$ARGS_FILE has --profile '${PROFILE}', not factory"
|
||||
[ "$A_BUSINESS" = "$BUSINESS" ] || refuse "$ARGS_FILE is for business $A_BUSINESS, not $BUSINESS"
|
||||
[[ "$PORT" =~ ^[1-9][0-9]{0,3}$ ]] && [ "$PORT" -le 1023 ] || refuse "$ARGS_FILE lacks a privileged numeric --port after --"
|
||||
[ "$TESTER_SOCKET" = 1 ] || refuse "$ARGS_FILE needs one bare --tester-socket after --"
|
||||
|
||||
COPY="$S_HOME/.local/share/mosaic-stack/copies/$COPY_ID"
|
||||
DATA_ROOT="$S_HOME/.mosaic-dev"
|
||||
CRED="$S_HOME/.config/mosaic-dev/secrets/claude-session.env"
|
||||
BUSINESS_FILE="$S_HOME/.config/mosaic-dev/businesses/$BUSINESS.json"
|
||||
BUS_UNIT="mosaic-bus@$BUSINESS.service" CON_UNIT=mosaic-webui.service
|
||||
HOSTCHECK=/usr/local/lib/mosaic-stack/hostcheck.sh
|
||||
SETTINGS=/etc/claude-code/managed-settings.json
|
||||
LABEL_OUTER='bwrap (enforce)' LABEL_CHILD='bwrap//&unpriv_bwrap (enforce)'
|
||||
AS_USER=(sudo -u "$S_USER" -H env "PATH=$(dirname "$NODE"):/usr/local/bin:/usr/bin:/bin")
|
||||
WORK="$(mktemp -d)" || refuse "cannot make a temp dir"
|
||||
trap 'rm -rf -- "$WORK"' EXIT
|
||||
|
||||
# Row253: bounded unprivileged metadata, parsed by root without running copy code.
|
||||
tester_capture() (
|
||||
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
|
||||
trap 'exit 1' HUP INT TERM
|
||||
timeout --signal=KILL "${seconds}s" /bin/bash -c 'set -o pipefail; maximum="$1"; shift; "$@" | head -c "$((maximum + 1))"' -- "$maximum" "$@" > "$file" 2>/dev/null &
|
||||
child=$!
|
||||
wait "$child" 2>/dev/null || rc=$?
|
||||
kill -KILL -- "-$child" 2>/dev/null || :
|
||||
[ "$rc" = 0 ] && [ "$(wc -c < "$file")" -le "$maximum" ] || exit 1
|
||||
cat -- "$file"
|
||||
)
|
||||
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)\}$'
|
||||
[[ "$1" =~ $pattern ]] || return 1
|
||||
TS_DATA="${BASH_REMATCH[1]}" TS_MARKED="${BASH_REMATCH[2]}"
|
||||
}
|
||||
tester_location() {
|
||||
local LC_ALL=C data="$1" home="$2" uid="$3" prefix="$4" part at="" st
|
||||
TS_REASON="" TS_SOCKET=""
|
||||
case "$data" in /*) ;; *) TS_REASON=not-absolute; return 1 ;; esac
|
||||
case "$data" in *[!A-Za-z0-9/._@+-]*) TS_REASON=not-ascii; return 1 ;; esac
|
||||
case "$data/" in */./*|*/../*|*//* ) TS_REASON=not-normalized; return 1 ;; esac
|
||||
[ "${data%/}" = "$data" ] || { TS_REASON=not-normalized; return 1; }
|
||||
case "$data" in "$home"/*) ;; *) TS_REASON=outside-home; return 1 ;; esac
|
||||
case "$home/" in /home/*|/root/*|/run/user/*|/tmp/*|/var/tmp/*) TS_REASON=protected-private-home; return 1 ;; esac
|
||||
TS_SOCKET="$data/console/tester.sock"
|
||||
[ "${#TS_SOCKET}" -le 107 ] || { TS_REASON=too-long; return 1; }
|
||||
local -a parts
|
||||
IFS=/ read -r -a parts <<< "$data"
|
||||
for part in "${parts[@]}"; do
|
||||
[ -n "$part" ] || continue
|
||||
at="$at/$part"
|
||||
[ ! -L "$prefix$at" ] || { TS_REASON=symlink-component; return 1; }
|
||||
if [ -e "$prefix$at" ]; then
|
||||
[ -d "$prefix$at" ] || { TS_REASON=component-type; return 1; }
|
||||
fi
|
||||
done
|
||||
[ "${5:-}" != observe ] || return 0
|
||||
local parent="$prefix$data/console"
|
||||
[ ! -L "$parent" ] || { TS_REASON=symlink-parent; return 1; }
|
||||
if [ -e "$parent" ]; then
|
||||
[ -d "$parent" ] || { TS_REASON=parent-type; return 1; }
|
||||
st="$(stat -c '%u %a' -- "$parent")" || { TS_REASON=parent-stat; return 1; }
|
||||
[ "${st% *}" = "$uid" ] || { TS_REASON=parent-owner; return 1; }
|
||||
[[ "${st#* }" =~ ^[0-7]{3,4}$ ]] && (( (8#${st#* } & 8#077) == 0 )) || { TS_REASON=parent-mode; return 1; }
|
||||
fi
|
||||
}
|
||||
# tester-location-end
|
||||
|
||||
|
||||
# --- Output ---
|
||||
|
||||
MARKS=(pass FAIL closed "accepted Q182+Q183" "accepted Q183" "accepted mos-1096" unnamed)
|
||||
declare -A COUNT=()
|
||||
F_FAILED=0
|
||||
# Text the stack's user wrote, made safe to print: printable ASCII, one line, at most 200 bytes.
|
||||
clean() { tr -c '[:print:]' ' ' <<< "$1" | cut -c1-200 | sed 's/ *$//'; }
|
||||
line() { local id="$1" path="$2" mark="$3"; shift 3; echo "$id $path $mark $*"; COUNT[$mark]=$(( ${COUNT[$mark]:-0} + 1 )); }
|
||||
fpass() { line "$1" "$2" pass "${@:3}"; }
|
||||
ffail() { line "$1" "$2" FAIL "${@:3}"; F_FAILED=1; }
|
||||
|
||||
# --- The account database: getent on the host, the seam's files under --root ---
|
||||
|
||||
db() {
|
||||
if [ -n "$ROOT" ]; then
|
||||
if [ $# -gt 1 ]; then awk -F: -v k="$2" '$1 == k' "$ROOT/etc/$1"; else cat "$ROOT/etc/$1"; fi
|
||||
else
|
||||
getent "$@"
|
||||
fi
|
||||
}
|
||||
uid_of() { db passwd "$1" | cut -d: -f3; }
|
||||
gid_of() { db passwd "$1" | cut -d: -f4; }
|
||||
# The group ids USER belongs to, as `id -G USER` prints them: the primary gid, then each group
|
||||
# whose member list names USER.
|
||||
groups_of() {
|
||||
local g; g="$(gid_of "$1")"
|
||||
[ -n "$g" ] || return 0
|
||||
{ echo "$g"; db group | awk -F: -v u="$1" '{ n = split($4, m, ","); for (i = 1; i <= n; i++) if (m[i] == u) print $3 }'; } | awk '!seen[$0]++'
|
||||
}
|
||||
names_of_groups() { local g; for g in "$@"; do db group | awk -F: -v g="$g" '$3 == g { print $1 }'; done; }
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
TESTER_PATH="$TS_SOCKET"
|
||||
|
||||
# --- systemd, /proc and listeners ---
|
||||
|
||||
# KEY=VALUE lines for UNIT's properties.
|
||||
unit_show() { local u="$1"; shift; systemctl show "$u" "${@/#/-p}" 2>/dev/null; }
|
||||
prop() { sed -n "s/^$2=//p" <<< "$1" | tail -n 1; }
|
||||
# The real, effective, saved and filesystem uids of PID, space-separated.
|
||||
proc_uids() { awk '$1 == "Uid:" { print $2, $3, $4, $5 }' "$ROOT/proc/$1/status" 2>/dev/null; }
|
||||
proc_label() { local l; l="$(cat "$ROOT/proc/$1/attr/current" 2>/dev/null)" || return 1; printf '%s' "${l%$'\n'}"; }
|
||||
# One `ADDRESS PIDS` line per TCP listener on port $1, PIDS comma-separated.
|
||||
listeners() {
|
||||
ss -Hltnp 2>/dev/null | awk -v p="$1" '{
|
||||
n = split($4, a, ":"); if (a[n] != p) next
|
||||
addr = substr($4, 1, length($4) - length(p) - 1); pids = ""; s = $0
|
||||
while (match(s, /pid=[0-9]+/)) { pids = pids (pids == "" ? "" : ",") substr(s, RSTART + 4, RLENGTH - 4); s = substr(s, RSTART + RLENGTH) }
|
||||
print addr, pids
|
||||
}'
|
||||
}
|
||||
# 0 when port $1 has a listener and every one is on 127.0.0.1, held by pid $2 alone.
|
||||
loopback_by() {
|
||||
local addr pids p any=0
|
||||
[ -n "$2" ] && [ "$2" != 0 ] || return 1
|
||||
while read -r addr pids; do
|
||||
[ -n "$addr" ] || continue
|
||||
any=1
|
||||
[ "$addr" = 127.0.0.1 ] || return 1
|
||||
for p in ${pids//,/ }; do [ "$p" = "$2" ] || return 1; done
|
||||
[ -n "$pids" ] || return 1
|
||||
done <<< "$(listeners "$1")"
|
||||
[ "$any" = 1 ]
|
||||
}
|
||||
listener_text() { local t; t="$(listeners "$1" | tr '\n' ';')"; echo "${t:-none}"; }
|
||||
|
||||
BUS_SHOW="$(unit_show "$BUS_UNIT" ActiveState User MainPID)"
|
||||
CON_SHOW="$(unit_show "$CON_UNIT" ActiveState User MainPID)"
|
||||
CON_PID="$(prop "$CON_SHOW" MainPID)"
|
||||
|
||||
# --- F checks ---
|
||||
|
||||
check_f1() {
|
||||
local u show state user pid uids why=""
|
||||
for u in "$BUS_UNIT" "$CON_UNIT"; do
|
||||
if [ "$u" = "$BUS_UNIT" ]; then show="$BUS_SHOW"; else show="$CON_SHOW"; fi
|
||||
state="$(prop "$show" ActiveState)" user="$(prop "$show" User)" pid="$(prop "$show" MainPID)"
|
||||
uids="$(proc_uids "${pid:-0}")"
|
||||
[ "$state" = active ] || { why+="$u is ${state:-unknown}; "; continue; }
|
||||
[ "$user" = "$S_USER" ] || { why+="$u has User=${user:-unset}; "; continue; }
|
||||
[ -n "$MOSAIC_UID" ] && [ "$uids" = "$MOSAIC_UID $MOSAIC_UID $MOSAIC_UID $MOSAIC_UID" ] ||
|
||||
why+="$u MainPID ${pid:-none} has uids '${uids:-unreadable}', not $S_USER's ${MOSAIC_UID:-unknown}; "
|
||||
done
|
||||
if [ -z "$why" ]; then fpass F1 units-user "$BUS_UNIT and $CON_UNIT are active as $S_USER (uid $MOSAIC_UID)"
|
||||
else ffail F1 units-user "${why%; }"; fi
|
||||
}
|
||||
|
||||
check_f2() {
|
||||
local rc=0
|
||||
curl -fsS --max-time 10 -o /dev/null "http://127.0.0.1:$PORT/healthz" 2>/dev/null || rc=$?
|
||||
if [ "$rc" != 0 ]; then ffail F2 console-loopback "curl http://127.0.0.1:$PORT/healthz exited $rc"
|
||||
elif ! loopback_by "$PORT" "$CON_PID"; then ffail F2 console-loopback "port $PORT listeners are '$(listener_text "$PORT")', not 127.0.0.1 by the Console's MainPID ${CON_PID:-none}"
|
||||
else fpass F2 console-loopback "healthz answers, and $PORT is bound to 127.0.0.1 only, by MainPID $CON_PID"; fi
|
||||
}
|
||||
|
||||
# F5's scan: an outer /usr/bin/bwrap owned by the stack's user with a claude descendant. Sets F5_OK
|
||||
# when one pair has both labels exactly, F5_WHY to the last mismatch otherwise.
|
||||
F5_OK="" F5_WHY="no /usr/bin/bwrap process of $S_USER with a claude descendant was seen while the reply was pending"
|
||||
scan_f5() {
|
||||
local pid ppid uids kids q c argv0 comm lo lc
|
||||
local -A parent=() children=() bw=()
|
||||
# One read of every PPid line and one of every exe link, so a scan stays short on a busy host.
|
||||
while read -r pid ppid; do
|
||||
parent[$pid]="$ppid" children[$ppid]+=" $pid"
|
||||
done < <(grep -H '^PPid:' "$ROOT"/proc/[0-9]*/status 2>/dev/null | sed -n 's#^.*/proc/\([0-9]*\)/status:PPid:[[:space:]]*\([0-9]*\)$#\1 \2#p')
|
||||
while read -r pid; do
|
||||
[[ "$pid" =~ ^[0-9]+$ ]] && [ -n "${parent[$pid]:-}" ] && bw[$pid]=1
|
||||
done < <(find "$ROOT/proc" -mindepth 2 -maxdepth 2 -name exe -lname /usr/bin/bwrap -printf '%h\n' 2>/dev/null | sed 's#.*/##')
|
||||
for pid in "${!bw[@]}"; do
|
||||
[ -z "${bw[${parent[$pid]}]:-}" ] || continue
|
||||
uids="$(proc_uids "$pid")"
|
||||
[ -n "$MOSAIC_UID" ] && [ "${uids%% *}" = "$MOSAIC_UID" ] || continue
|
||||
# Every descendant, breadth first.
|
||||
kids=() q=("$pid")
|
||||
while [ ${#q[@]} -gt 0 ]; do
|
||||
for c in ${children[${q[0]}]:-}; do kids+=("$c") q+=("$c"); done
|
||||
q=("${q[@]:1}")
|
||||
done
|
||||
for c in "${kids[@]}"; do
|
||||
argv0="$(tr '\0' '\n' < "$ROOT/proc/$c/cmdline" 2>/dev/null | sed -n 1p)"
|
||||
comm="$(cat "$ROOT/proc/$c/comm" 2>/dev/null)"
|
||||
[[ "${argv0##*/}" == claude* ]] || [[ "$comm" == claude* ]] || continue
|
||||
lo="$(proc_label "$pid")" lc="$(proc_label "$c")"
|
||||
if [ "$lo" = "$LABEL_OUTER" ] && [ "$lc" = "$LABEL_CHILD" ]; then
|
||||
F5_OK="bwrap $pid reads '$lo' and its claude descendant $c reads '$lc'"; return 0
|
||||
fi
|
||||
F5_WHY="bwrap $pid reads '$lo' and its claude descendant $c reads '$lc', not '$LABEL_OUTER' and '$LABEL_CHILD'"
|
||||
done
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# F3 and F5 together: the message goes out in the background and the scan runs until it is answered.
|
||||
check_f3_f5() {
|
||||
local marker text rc=0 out err n tpid
|
||||
marker="case1-accept-$(od -An -N6 -tx1 /dev/urandom | tr -d ' \n')"
|
||||
text="$marker: this is the Case 1 acceptance; reply with one short line."
|
||||
timeout 330 "${AS_USER[@]}" "$COPY/scripts/mosaic" talk chief "$text" --business "$BUSINESS" --tester --wait 300 > "$WORK/talk.out" 2> "$WORK/talk.err" &
|
||||
tpid=$!
|
||||
while :; do
|
||||
scan_f5 && break
|
||||
kill -0 "$tpid" 2>/dev/null || break
|
||||
sleep 0.5
|
||||
done
|
||||
wait "$tpid" || rc=$?
|
||||
# Both the sent line and the reply are on stdout; an error is on stderr.
|
||||
out="$(cat "$WORK/talk.out")" err="$(cat "$WORK/talk.err")"
|
||||
n="$(grep -c . <<< "$out")"
|
||||
if [ "$rc" = 0 ] && grep -q "^sent [^ ]* to chief" <<< "$out" && [ "$n" -ge 2 ]; then
|
||||
fpass F3 tester-reply "chief replied to $marker"
|
||||
else
|
||||
ffail F3 tester-reply "talk exited $rc with $n lines on stdout; last: $(clean "$(tail -n 1 <<< "${err:-$out}")")"
|
||||
fi
|
||||
if [ -n "$F5_OK" ]; then F5_LINE=(fpass "$F5_OK"); else F5_LINE=(ffail "$F5_WHY"); fi
|
||||
}
|
||||
|
||||
check_f4() {
|
||||
local rc=0 out
|
||||
out="$("${AS_USER[@]}" "$COPY/scripts/case2-precheck.sh" --business "$BUSINESS" --profile factory 2>&1)" || rc=$?
|
||||
if [ "$rc" = 0 ]; then fpass F4 precheck "case2-precheck.sh --profile factory exits 0"
|
||||
else ffail F4 precheck "case2-precheck.sh --profile factory exited $rc; last: $(clean "$(tail -n 1 <<< "$out")")"; fi
|
||||
}
|
||||
|
||||
check_f6() {
|
||||
local want="/opt/mosaic-stack/claude-$CLAUDE_PIN/" v
|
||||
[[ "$CLAUDE" == "$want"* ]] || { ffail F6 claude-pin "--claude $CLAUDE is not under $want"; return; }
|
||||
[[ "$CLAUDE/" != */../* ]] || { ffail F6 claude-pin "--claude $CLAUDE has a .. segment"; return; }
|
||||
v="$("${AS_USER[@]}" "$CLAUDE" --version 2>/dev/null | sed -n 1p)"
|
||||
if [ "$v" = "$CLAUDE_PIN (Claude Code)" ]; then fpass F6 claude-pin "$CLAUDE prints '$v'"
|
||||
else ffail F6 claude-pin "$CLAUDE --version prints '$(clean "$v")', not '$CLAUDE_PIN (Claude Code)'"; fi
|
||||
}
|
||||
|
||||
# The PM check confirm runs (stack-release.sh pm_external): exactly one live launches list entry for
|
||||
# launch.by, for this business. The stack's user runs the whole check and prints one result line.
|
||||
F7_JS='
|
||||
const {readFileSync}=require("node:fs");const {spawnSync}=require("node:child_process");
|
||||
const [file,cli,b]=process.argv.slice(1);
|
||||
let by;
|
||||
try{by=JSON.parse(readFileSync(file,"utf8")).launch?.by}catch(e){console.log(`fail cannot read ${file}: ${e.code??"not JSON"}`);process.exit(0)}
|
||||
if(typeof by!=="string"||!by){console.log("fail the business file has no launch.by");process.exit(0)}
|
||||
const r=spawnSync(cli,["launches","list","--json"],{encoding:"utf8"});
|
||||
if(r.status!==0){console.log(`fail launches list exited ${r.status}`);process.exit(0)}
|
||||
let l;try{l=JSON.parse(r.stdout)}catch{console.log("fail launches list printed no JSON");process.exit(0)}
|
||||
const live=Array.isArray(l)?l.filter((x)=>x&&x.instance===by&&x.live===true):[];
|
||||
console.log(live.length!==1?`fail ${live.length} live launches of ${by}`:live[0].business!==b?`fail the live launch of ${by} is for ${live[0].business}`:`ok ${by} run ${live[0].run}`);'
|
||||
check_f7() {
|
||||
local out
|
||||
out="$("${AS_USER[@]}" "$NODE" -e "$F7_JS" -- "$BUSINESS_FILE" "$COPY/scripts/mosaic" "$BUSINESS" 2>/dev/null | sed -n 1p)"
|
||||
if [[ "$out" =~ ^ok\ [a-z0-9][a-z0-9-]*\ run\ [A-Za-z0-9._-]+$ ]]; then fpass F7 pm-live "one live launch: ${out#ok }"
|
||||
else ffail F7 pm-live "$(clean "${out#fail }")"; fi
|
||||
}
|
||||
|
||||
check_f8() {
|
||||
local s
|
||||
s="$(stat -c '%U %a %F' -- "$ROOT$CRED" 2>/dev/null)" || s="missing"
|
||||
if [ ! -L "$ROOT$CRED" ] && [ "$s" = "$S_USER 600 regular file" ]; then fpass F8 credential-file "$CRED is '$s'"
|
||||
else ffail F8 credential-file "$CRED is '$s', not '$S_USER 600 regular file'"; fi
|
||||
}
|
||||
|
||||
# Empty when $1 and every directory above it, up to /, are the running user's (root's on the host) with
|
||||
# no group or other write: case2-unit.sh's root_only rule for --node, checked again before root runs it.
|
||||
# Otherwise the first path that breaks the rule. A symlink breaks it, because its mode is 777.
|
||||
not_root_only() {
|
||||
local p="$1" st
|
||||
while :; do
|
||||
st="$(stat -c '%u %a' -- "$ROOT$p" 2>/dev/null)" || { echo "$p (cannot stat)"; return; }
|
||||
[ "${st% *}" = "$(id -u)" ] && (( (8#${st#* } & 8#022) == 0 )) || { echo "$p (uid ${st% *}, mode ${st#* })"; return; }
|
||||
[ "$p" = / ] && return
|
||||
p="$(dirname -- "$p")"
|
||||
done
|
||||
}
|
||||
|
||||
check_f9() {
|
||||
local s v bad
|
||||
s="$(stat -c '%U:%G %a %F' -- "$ROOT$SETTINGS" 2>/dev/null)" || s="missing"
|
||||
[ ! -L "$ROOT$SETTINGS" ] && [ "$s" = "root:root 644 regular file" ] || { ffail F9 autoupdater-off "$SETTINGS is '$s', not 'root:root 644 regular file'"; return; }
|
||||
# Root runs node here, so it runs only the pinned path, and only when nothing but root can replace it.
|
||||
[ "$NODE" = "/opt/mosaic-stack/node-$NODE_PIN/bin/node" ] || { ffail F9 autoupdater-off "root won't run --node $NODE: it is not the --node-pin path"; return; }
|
||||
bad="$(not_root_only "$NODE")"
|
||||
[ -z "$bad" ] || { ffail F9 autoupdater-off "root won't run --node $NODE: $bad is not root's alone"; return; }
|
||||
v="$("$ROOT$NODE" -e 'const s=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(JSON.stringify(s?.env?.DISABLE_AUTOUPDATER??null))' -- "$ROOT$SETTINGS" 2>/dev/null)" || v="unreadable"
|
||||
if [ "$v" = '"1"' ]; then fpass F9 autoupdater-off "$SETTINGS is root:root 0644 with env.DISABLE_AUTOUPDATER \"1\""
|
||||
else ffail F9 autoupdater-off "$SETTINGS env.DISABLE_AUTOUPDATER is $v, not \"1\""; fi
|
||||
}
|
||||
|
||||
check_f10() {
|
||||
local want="/opt/mosaic-stack/node-$NODE_PIN/bin/node" v
|
||||
[ "$NODE" = "$want" ] || { ffail F10 node-pin "--node $NODE is not $want"; return; }
|
||||
v="$("${AS_USER[@]}" "$NODE" --version 2>/dev/null | sed -n 1p)"
|
||||
if [ "$v" = "$NODE_PIN" ]; then fpass F10 node-pin "$NODE prints $v"
|
||||
else ffail F10 node-pin "$NODE --version prints '$(clean "$v")', not $NODE_PIN"; fi
|
||||
}
|
||||
|
||||
check_f11() {
|
||||
local d="$ROOT$DATA_ROOT/discord" found
|
||||
if [ -L "$d" ]; then ffail F11 no-discord "$DATA_ROOT/discord is a symlink"; return; fi
|
||||
[ -d "$d" ] || { fpass F11 no-discord "$DATA_ROOT/discord is absent"; return; }
|
||||
found="$(find "$d" -maxdepth 1 -name '*.json' -printf '%f ' 2>/dev/null)"
|
||||
if [ -z "$found" ]; then fpass F11 no-discord "$DATA_ROOT/discord holds no binding"
|
||||
else ffail F11 no-discord "$DATA_ROOT/discord holds bindings: ${found% }"; fi
|
||||
}
|
||||
|
||||
# --- P checks ---
|
||||
|
||||
check_p1() {
|
||||
local out
|
||||
out="$(sudo -l -U "$S_USER" 2>&1)"
|
||||
if grep -q "is not allowed to run sudo" <<< "$out"; then line P1 "$S_USER-sudo" closed "sudo -l: $(clean "$(sed -n 1p <<< "$out")")"
|
||||
else line P1 "$S_USER-sudo" unnamed "sudo -l: $(clean "$(tr '\n' ' ' <<< "$out")")"; fi
|
||||
}
|
||||
|
||||
check_p2() {
|
||||
local gs keys="$ROOT$S_HOME/.ssh/authorized_keys" kstate n
|
||||
gs="$(groups_of "$S_USER" | tr '\n' ' ')"; gs="${gs% }"
|
||||
n="$(wc -w <<< "$gs")"
|
||||
if [ ! -e "$keys" ] && [ ! -L "$keys" ]; then kstate=absent
|
||||
elif [ -f "$keys" ] && [ ! -L "$keys" ] && [ ! -s "$keys" ]; then kstate=empty
|
||||
else kstate=present; fi
|
||||
if [ "$n" = 1 ] && [ "$kstate" != present ]; then line P2 "$S_USER-groups-keys" closed "id -G is '$gs'; authorized_keys is $kstate"
|
||||
else line P2 "$S_USER-groups-keys" unnamed "id -G is '$gs'; authorized_keys is $kstate"; fi
|
||||
}
|
||||
|
||||
check_p3() {
|
||||
local n
|
||||
n="$(sudo -l -U localadmin 2>&1 | grep -c NOPASSWD)"
|
||||
line P3 localadmin-sudo "accepted Q183" "sudo -l -U localadmin lists $n NOPASSWD lines"
|
||||
}
|
||||
|
||||
check_p4() { line P4 pve-root-pbs "accepted Q182+Q183" "root on pve over VM 1008, and PBS backups that hold $S_HOME with the credential; this script can't see pve"; }
|
||||
|
||||
check_p5() {
|
||||
local gs reach
|
||||
if [ -z "$(uid_of jarvis)" ]; then line P5 jarvis-reach closed "no jarvis account"; return; fi
|
||||
gs="$(names_of_groups $(groups_of jarvis) | tr '\n' ' ')"
|
||||
reach="$(tr ' ' '\n' <<< "$gs" | grep -xE 'sudo|docker|lxd' | tr '\n' ' ')"
|
||||
if [ -z "$reach" ]; then line P5 jarvis-reach closed "jarvis is in none of sudo, docker and lxd (groups: ${gs% })"
|
||||
else line P5 jarvis-reach unnamed "jarvis is in ${reach% }"; fi
|
||||
}
|
||||
|
||||
check_p6() {
|
||||
local g m members bad=0
|
||||
for g in sudo docker lxd; do
|
||||
members="$(db group "$g" | cut -d: -f4)"
|
||||
for m in ${members//,/ }; do
|
||||
[ "$g" = sudo ] && [ "$m" = localadmin ] && continue
|
||||
line P6 "group-$g" unnamed "$m is a member of $g"; bad=1
|
||||
done
|
||||
done
|
||||
[ "$bad" = 1 ] || line P6 group-members closed "sudo holds only localadmin, and docker and lxd have no members"
|
||||
}
|
||||
|
||||
check_p7() {
|
||||
local found
|
||||
found="$(grep -rlw -- "$S_USER" "$ROOT/etc/sudoers.d" 2>/dev/null | sed "s#^$ROOT##" | tr '\n' ' ')"
|
||||
if [ -z "$found" ]; then line P7 sudoers-d closed "no file under /etc/sudoers.d names $S_USER"
|
||||
else line P7 sudoers-d unnamed "files naming $S_USER: ${found% }"; fi
|
||||
}
|
||||
|
||||
check_p8() {
|
||||
local other
|
||||
other="$(listeners 7331)$(listeners 7333)"
|
||||
if loopback_by "$PORT" "$CON_PID" && [ -z "$other" ]; then
|
||||
line P8 lan-listeners closed "$PORT is on 127.0.0.1 only by MainPID $CON_PID; no TCP listener on 7331 or 7333; tester uses $TESTER_PATH"
|
||||
else
|
||||
line P8 lan-listeners unnamed "$PORT: $(listener_text "$PORT"); 7333: $(listener_text 7333); 7331: $(listener_text 7331); Console MainPID ${CON_PID:-none}"
|
||||
fi
|
||||
}
|
||||
|
||||
check_p9() {
|
||||
local out n missing=() rc=0 err mg others
|
||||
out="$("$ROOT$HOSTCHECK" --check 2>/dev/null)"
|
||||
for n in 1 2 3 4 5 6; do grep -q "^ok $n " <<< "$out" || missing+=("hostcheck item $n has no ok line")
|
||||
done
|
||||
err="$(sudo -u jarvis /usr/bin/bwrap --version 2>&1 >/dev/null)" || rc=$?
|
||||
[ "$rc" != 0 ] && grep -q "Permission denied" <<< "$err" || missing+=("sudo -u jarvis /usr/bin/bwrap --version exited $rc without Permission denied")
|
||||
mg="$(gid_of "$S_USER")"
|
||||
[ -n "$mg" ] && [ -z "$(db group "$S_USER" | cut -d: -f4)" ] || missing+=("group $S_USER lists members, or $S_USER has no group")
|
||||
others="$(db passwd | awk -F: -v g="$mg" -v u="$S_USER" '$4 == g && $1 != u { print $1 }' | tr '\n' ' ')"
|
||||
[ -z "$others" ] || missing+=("other accounts have $S_USER's gid $mg: ${others% }")
|
||||
if [ ${#missing[@]} = 0 ]; then
|
||||
line P9 bwrap-userns "accepted mos-1096" "hostcheck items 1-6 ok, jarvis is denied bwrap, and group $S_USER has no other member"
|
||||
else
|
||||
for n in "${missing[@]}"; do line P9 bwrap-userns unnamed "$n"; done
|
||||
fi
|
||||
}
|
||||
|
||||
# P10 is an observation of mutable same-uid state, never a closure ruling.
|
||||
TESTER_PEER='import socket,struct,sys
|
||||
s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
|
||||
try:
|
||||
s.settimeout(1)
|
||||
s.connect(sys.argv[1])
|
||||
p=struct.unpack("3i",s.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12))
|
||||
print(*p)
|
||||
finally:
|
||||
s.close()'
|
||||
tester_snapshot() {
|
||||
local parent="${TESTER_PATH%/*}" p s pid
|
||||
[ ! -L "$ROOT$parent" ] && [ ! -L "$ROOT$TESTER_PATH" ] || return 1
|
||||
p="$(stat -c '%u %a %F %d %i' -- "$ROOT$parent" 2>/dev/null)" || return 1
|
||||
s="$(stat -c '%u %a %F %d %i' -- "$ROOT$TESTER_PATH" 2>/dev/null)" || return 1
|
||||
[[ "$p" =~ ^$MOSAIC_UID\ 700\ directory\ [0-9]+\ [0-9]+$ ]] || return 1
|
||||
[[ "$s" =~ ^$MOSAIC_UID\ 600\ socket\ [0-9]+\ [0-9]+$ ]] || return 1
|
||||
pid="$(prop "$(unit_show "$CON_UNIT" MainPID)" MainPID)"
|
||||
[[ "$pid" =~ ^[1-9][0-9]*$ ]] && [ "$pid" = "$CON_PID" ] || return 1
|
||||
[ "$(proc_uids "$pid")" = "$MOSAIC_UID $MOSAIC_UID $MOSAIC_UID $MOSAIC_UID" ] || return 1
|
||||
printf '%s|%s|%s' "$pid" "$p" "$s"
|
||||
}
|
||||
tester_python() {
|
||||
local real bad magic
|
||||
# Resolve the system entry, then validate the target and every ancestor.
|
||||
[ -f "$ROOT/usr/bin/python3" ] && [ -x "$ROOT/usr/bin/python3" ] || return 1
|
||||
real="$(realpath -e -- "$ROOT/usr/bin/python3" 2>/dev/null)" || return 1
|
||||
[ -z "$ROOT" ] || real="${real#"$ROOT"}"
|
||||
case "$real" in /home/*|/root/*|/run/user/*|"$S_HOME"/*) return 1 ;; esac
|
||||
bad="$(not_root_only "$real")"; [ -z "$bad" ] || return 1
|
||||
bad="$(not_root_only /usr/bin)"; [ -z "$bad" ] || return 1
|
||||
magic="$(od -An -tx1 -N4 -- "$ROOT$real" 2>/dev/null | tr -d ' \n')"
|
||||
[ "$magic" = 7f454c46 ] || return 1
|
||||
printf '%s' "$real"
|
||||
}
|
||||
check_p10() {
|
||||
local floor before after python peer pid uid gid why="" pattern='^([1-9][0-9]{0,9}) ([0-9]{1,10}) ([0-9]{1,10})$'
|
||||
floor="$(cat "$ROOT/proc/sys/net/ipv4/ip_unprivileged_port_start" 2>/dev/null)"
|
||||
[[ "$floor" =~ ^[0-9]{1,5}$ ]] && [ "$floor" -gt "$PORT" ] || why=floor
|
||||
before="$(tester_snapshot)" || why=metadata
|
||||
python="$(tester_python)" || why=interpreter
|
||||
if [ -z "$why" ]; then
|
||||
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
|
||||
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
|
||||
else why=probe; fi
|
||||
after="$(tester_snapshot)" || why=metadata
|
||||
[ "$before" = "$after" ] || why=changed
|
||||
fi
|
||||
if [ -z "$why" ]; then
|
||||
line P10 tester-socket-squat unnamed "boundary=observed path=$TESTER_PATH parent=0700 owner=$MOSAIC_UID socket=0600 peer_pid=$pid peer_uid=$uid floor=$floor port=$PORT; hostile same-uid substitution remains"
|
||||
else
|
||||
line P10 tester-socket-squat unnamed "boundary=failed reason=$why path=$TESTER_PATH MainPID=${CON_PID:-none}; hostile same-uid substitution remains"
|
||||
fi
|
||||
}
|
||||
|
||||
check_p11() {
|
||||
local en before
|
||||
en="$(systemctl is-enabled mosaic-hostcheck.service 2>/dev/null)"
|
||||
before="$(prop "$(unit_show mosaic-hostcheck.service Before)" Before)"
|
||||
if [ "$en" = enabled ] && grep -qxF -- "$BUS_UNIT" <<< "${before// /$'\n'}"; then
|
||||
line P11 hostcheck-before-bus closed "mosaic-hostcheck.service is enabled, and its Before= names $BUS_UNIT"
|
||||
else
|
||||
line P11 hostcheck-before-bus unnamed "mosaic-hostcheck.service is '${en:-unknown}', Before='$before'; it needs enabled and $BUS_UNIT"
|
||||
fi
|
||||
}
|
||||
|
||||
check_f1
|
||||
check_f2
|
||||
F5_LINE=()
|
||||
check_f3_f5
|
||||
check_f4
|
||||
"${F5_LINE[0]}" F5 bwrap-label "${F5_LINE[@]:1}"
|
||||
check_f6
|
||||
check_f7
|
||||
check_f8
|
||||
check_f9
|
||||
check_f10
|
||||
check_f11
|
||||
check_p1; check_p2; check_p3; check_p4; check_p5; check_p6
|
||||
check_p7; check_p8; check_p9; check_p10; check_p11
|
||||
|
||||
for m in "${MARKS[@]}"; do echo "count $m ${COUNT[$m]:-0}"; done
|
||||
if [ "$F_FAILED" = 0 ]; then echo "case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos"; exit 0; fi
|
||||
echo "case1-accept: an F check failed, so the row fails"
|
||||
exit 1
|
||||
@@ -0,0 +1,484 @@
|
||||
#!/usr/bin/env bash
|
||||
# Case 2 row C2-13a (design 9.3, 10 step 2c): render and install the two
|
||||
# system units a hub runs as, [email protected] (enabled as
|
||||
# mosaic-bus@<business>.service) and mosaic-webui.service, from the existing
|
||||
# templates, which it doesn't change.
|
||||
#
|
||||
# case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH
|
||||
# [--profile factory|library] [--pm] [--claude PATH]
|
||||
# -- SERVE FLAGS
|
||||
#
|
||||
# Root runs it from a root-owned checkout of the reviewed commit outside
|
||||
# <home>, never from the release, because <user> can write the release. It
|
||||
# writes both units under /etc/systemd/system as root:root 0644, runs
|
||||
# `systemctl daemon-reload`, enables both, then starts the bus and then the
|
||||
# Console (Q189). Both come back after a boot on their own. It never links
|
||||
# a unit file into place. Root only stats files under <home> 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
|
||||
# dependencies. 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
|
||||
# --auth-header HEADER --allow-user ID --send
|
||||
# --proof-header NAME --proof-file PATH
|
||||
# The factory profile takes --port N (1..1023) and --business ID, and an
|
||||
# optional bare --tester-socket marker. Retired tester TCP refuses. Only factory takes
|
||||
# --pm and --claude.
|
||||
#
|
||||
# On a re-install the installed units must equal the render in everything
|
||||
# but the copy id, and both must name one copy, so a re-pin or rollback
|
||||
# re-renders the pair together. A unit that was already running stays on its
|
||||
# old copy, since `systemctl start` leaves it alone; the script says so and
|
||||
# that the bus and Console must restart together, and restarts nothing.
|
||||
#
|
||||
# Test seams, for packages/cli/tests/case2-unit.test.mjs only:
|
||||
# CASE2_UNIT_DIR the unit directory, default /etc/systemd/system;
|
||||
# the root and root-owned checks apply only there
|
||||
# CASE2_SYSTEMCTL the systemctl command, default systemctl
|
||||
# CASE2_GETENT the getent command, default getent
|
||||
# CASE2_BUS_TEMPLATE default packages/cli/systemd/mosaic-bus.service.in
|
||||
# CASE2_WEBUI_TEMPLATE default packages/webui/systemd/mosaic-webui.service.in
|
||||
#
|
||||
# Exit 0 done, 1 refused, 4 usage.
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEFAULT_UNIT_DIR=/etc/systemd/system
|
||||
UNIT_DIR="${CASE2_UNIT_DIR:-$DEFAULT_UNIT_DIR}"
|
||||
SYSTEMCTL="${CASE2_SYSTEMCTL:-systemctl}"
|
||||
GETENT="${CASE2_GETENT:-getent}"
|
||||
BUS_TEMPLATE="${CASE2_BUS_TEMPLATE:-$REPO/packages/cli/systemd/mosaic-bus.service.in}"
|
||||
WEBUI_TEMPLATE="${CASE2_WEBUI_TEMPLATE:-$REPO/packages/webui/systemd/mosaic-webui.service.in}"
|
||||
[email protected]
|
||||
WEBUI_UNIT=mosaic-webui.service
|
||||
|
||||
usage() { echo 'usage: case2-unit.sh --user NAME --home DIR --business ID --copy ID --node PATH [--profile factory|library] [--pm] [--claude PATH] -- SERVE FLAGS' >&2; exit 4; }
|
||||
die() { echo "case2-unit: $*" >&2; exit 1; }
|
||||
# A value the unit carries as itself: no space, %, $, quote, backslash, & or |.
|
||||
plain() { case "$1" in *[!A-Za-z0-9/._+-]*) return 1 ;; esac; }
|
||||
abs_plain() {
|
||||
case "$1" in /*) ;; *) die "$2 must be an absolute path: $1" ;; esac
|
||||
plain "$1" || die "$2 contains a character the unit cannot carry: $1"
|
||||
case "$1/" in */./*|*/../*|?*//*) die "$2 must be a normalized path: $1" ;; esac
|
||||
[ "$1" = / ] || [ "${1%/}" = "$1" ] || die "$2 must not end in /: $1"
|
||||
}
|
||||
|
||||
PROFILE=library USER_NAME='' HOME_DIR='' BUSINESS='' COPY_ID='' NODE='' PM=0 CLAUDE='' CLAUDE_SET=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--profile|--user|--home|--business|--copy|--node|--claude)
|
||||
[ $# -ge 2 ] || usage
|
||||
case "$1" in
|
||||
--profile) PROFILE="$2" ;; --user) USER_NAME="$2" ;; --home) HOME_DIR="$2" ;;
|
||||
--business) BUSINESS="$2" ;; --copy) COPY_ID="$2" ;; --node) NODE="$2" ;;
|
||||
--claude) CLAUDE="$2"; CLAUDE_SET=1 ;;
|
||||
esac
|
||||
shift 2 ;;
|
||||
--pm) PM=1; shift ;;
|
||||
--) shift; break ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
SERVE=("$@")
|
||||
[ -n "$USER_NAME" ] && [ -n "$HOME_DIR" ] && [ -n "$BUSINESS" ] && [ -n "$COPY_ID" ] && [ -n "$NODE" ] || usage
|
||||
case "$PROFILE" in factory|library) ;; *) die "--profile must be factory or library: $PROFILE" ;; esac
|
||||
if [ "$PROFILE" = library ]; then
|
||||
[ "$PM" = 0 ] || die "the library profile refuses --pm"
|
||||
[ "$CLAUDE_SET" = 0 ] || die "the library profile refuses --claude"
|
||||
fi
|
||||
|
||||
if [ "$UNIT_DIR" = "$DEFAULT_UNIT_DIR" ]; then
|
||||
[ "$(id -u)" = 0 ] || die "writing $DEFAULT_UNIT_DIR needs root"
|
||||
fi
|
||||
|
||||
# The user, its uid, primary group and home, from the passwd database.
|
||||
[[ "$USER_NAME" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "--user must be a user name: $USER_NAME"
|
||||
PW="$("$GETENT" passwd "$USER_NAME")" || die "no user $USER_NAME in the passwd database"
|
||||
IFS=: read -r _ _ USER_UID USER_GID _ PW_HOME _ <<<"$PW"
|
||||
[[ "$USER_UID" =~ ^[0-9]+$ ]] && [ "$USER_UID" -ne 0 ] || die "--user must not be root: $USER_NAME"
|
||||
abs_plain "$HOME_DIR" '--home'
|
||||
[ "$HOME_DIR" = "$PW_HOME" ] || die "--home $HOME_DIR is not $USER_NAME's home in the passwd database ($PW_HOME)"
|
||||
GR="$("$GETENT" group "$USER_GID")" || die "no group $USER_GID in the group database"
|
||||
GROUP="${GR%%:*}"
|
||||
[[ "$GROUP" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "the primary group of $USER_NAME is not a group name: $GROUP"
|
||||
|
||||
case "$BUSINESS" in [a-z0-9]*) ;; *) die "--business must be a business id: $BUSINESS" ;; esac
|
||||
case "$BUSINESS" in *[!a-z0-9._-]*) die "--business must be a business id: $BUSINESS" ;; esac
|
||||
[ "${#BUSINESS}" -le 64 ] || die "--business must be a business id: $BUSINESS"
|
||||
|
||||
# The copy: copy_root's rule (scripts/stack-copy.sh:8, :22), under <home>.
|
||||
COPIES="$HOME_DIR/.local/share/mosaic-stack/copies"
|
||||
[[ "$COPY_ID" =~ ^[0-9a-f]{40}$ ]] || die "--copy must be a full 40-character lower-case commit id: $COPY_ID"
|
||||
COPY="$COPIES/$COPY_ID"
|
||||
[ -d "$COPY" ] && [ ! -L "$COPY" ] || die "no copy directory $COPY"
|
||||
[ -f "$COPY/scripts/mosaic" ] || die "no copy $COPY_ID in $COPIES: $COPY/scripts/mosaic is missing"
|
||||
|
||||
# Owned by root and writable by no one else, the file and every directory above it.
|
||||
root_only() {
|
||||
local p="$1" what="$2" owner mode
|
||||
while :; do
|
||||
owner="$(stat -c '%u' -- "$p")" || die "cannot stat $p"
|
||||
mode="$(stat -c '%a' -- "$p")" || die "cannot stat $p"
|
||||
[ "$owner" = 0 ] || die "$what: $p is not owned by root"
|
||||
(( (8#$mode & 8#022) == 0 )) || die "$what: $p is writable by its group or others"
|
||||
[ "$p" = / ] && break
|
||||
p="$(dirname -- "$p")"
|
||||
done
|
||||
}
|
||||
|
||||
# --node: an absolute path to a root-owned regular file outside <home>.
|
||||
abs_plain "$NODE" '--node'
|
||||
case "$NODE/" in "$HOME_DIR"/*) die "--node must be outside $HOME_DIR: $NODE" ;; esac
|
||||
[ ! -L "$NODE" ] || die "--node must not be a symlink; pass its target: $NODE"
|
||||
[ -f "$NODE" ] && [ -x "$NODE" ] || die "--node must be an executable regular file: $NODE"
|
||||
root_only "$NODE" '--node'
|
||||
|
||||
# --claude follows the --node rule, so nothing running as <user> can replace
|
||||
# the binary the bus runs.
|
||||
if [ "$CLAUDE_SET" = 1 ]; then
|
||||
abs_plain "$CLAUDE" '--claude'
|
||||
case "$CLAUDE/" in "$HOME_DIR"/*) die "--claude must be outside $HOME_DIR: $CLAUDE" ;; esac
|
||||
[ ! -L "$CLAUDE" ] || die "--claude must not be a symlink; pass its target: $CLAUDE"
|
||||
[ -f "$CLAUDE" ] && [ -x "$CLAUDE" ] || die "--claude must be an executable regular file: $CLAUDE"
|
||||
root_only "$CLAUDE" '--claude'
|
||||
fi
|
||||
|
||||
# The checkout the templates come from, when installing for real.
|
||||
if [ "$UNIT_DIR" = "$DEFAULT_UNIT_DIR" ]; then
|
||||
case "$REPO/" in "$HOME_DIR"/*) die "run this from a root-owned checkout outside $HOME_DIR, not $REPO" ;; esac
|
||||
root_only "$BUS_TEMPLATE" 'bus template'
|
||||
root_only "$WEBUI_TEMPLATE" 'Console template'
|
||||
fi
|
||||
|
||||
# Row253: bounded unprivileged metadata, parsed by root without running copy code.
|
||||
tester_capture() (
|
||||
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
|
||||
trap 'exit 1' HUP INT TERM
|
||||
timeout --signal=KILL "${seconds}s" /bin/bash -c 'set -o pipefail; maximum="$1"; shift; "$@" | head -c "$((maximum + 1))"' -- "$maximum" "$@" > "$file" 2>/dev/null &
|
||||
child=$!
|
||||
wait "$child" 2>/dev/null || rc=$?
|
||||
kill -KILL -- "-$child" 2>/dev/null || :
|
||||
[ "$rc" = 0 ] && [ "$(wc -c < "$file")" -le "$maximum" ] || exit 1
|
||||
cat -- "$file"
|
||||
)
|
||||
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)\}$'
|
||||
[[ "$1" =~ $pattern ]] || return 1
|
||||
TS_DATA="${BASH_REMATCH[1]}" TS_MARKED="${BASH_REMATCH[2]}"
|
||||
}
|
||||
tester_location() {
|
||||
local LC_ALL=C data="$1" home="$2" uid="$3" prefix="$4" part at="" st
|
||||
TS_REASON="" TS_SOCKET=""
|
||||
case "$data" in /*) ;; *) TS_REASON=not-absolute; return 1 ;; esac
|
||||
case "$data" in *[!A-Za-z0-9/._@+-]*) TS_REASON=not-ascii; return 1 ;; esac
|
||||
case "$data/" in */./*|*/../*|*//* ) TS_REASON=not-normalized; return 1 ;; esac
|
||||
[ "${data%/}" = "$data" ] || { TS_REASON=not-normalized; return 1; }
|
||||
case "$data" in "$home"/*) ;; *) TS_REASON=outside-home; return 1 ;; esac
|
||||
case "$home/" in /home/*|/root/*|/run/user/*|/tmp/*|/var/tmp/*) TS_REASON=protected-private-home; return 1 ;; esac
|
||||
TS_SOCKET="$data/console/tester.sock"
|
||||
[ "${#TS_SOCKET}" -le 107 ] || { TS_REASON=too-long; return 1; }
|
||||
local -a parts
|
||||
IFS=/ read -r -a parts <<< "$data"
|
||||
for part in "${parts[@]}"; do
|
||||
[ -n "$part" ] || continue
|
||||
at="$at/$part"
|
||||
[ ! -L "$prefix$at" ] || { TS_REASON=symlink-component; return 1; }
|
||||
if [ -e "$prefix$at" ]; then
|
||||
[ -d "$prefix$at" ] || { TS_REASON=component-type; return 1; }
|
||||
fi
|
||||
done
|
||||
local parent="$prefix$data/console"
|
||||
[ ! -L "$parent" ] || { TS_REASON=symlink-parent; return 1; }
|
||||
if [ -e "$parent" ]; then
|
||||
[ -d "$parent" ] || { TS_REASON=parent-type; return 1; }
|
||||
st="$(stat -c '%u %a' -- "$parent")" || { TS_REASON=parent-stat; return 1; }
|
||||
[ "${st% *}" = "$uid" ] || { TS_REASON=parent-owner; return 1; }
|
||||
[[ "${st#* }" =~ ^[0-7]{3,4}$ ]] && (( (8#${st#* } & 8#077) == 0 )) || { TS_REASON=parent-mode; return 1; }
|
||||
fi
|
||||
}
|
||||
# tester-location-end
|
||||
|
||||
# The serve flags, checked as scripts/webui-service.sh serve_flags checks
|
||||
# them (:201-264), and kept in the order given. That script compares the
|
||||
# proof file's owner to `id -u`, which is root here, so this copy has its own.
|
||||
serve_flags() {
|
||||
local -A seen=()
|
||||
local flags='' name value
|
||||
local port='' public_host='' auth_header='' allow_user='' proof_header='' proof_file='' tester_socket=0
|
||||
local i=0 n=${#SERVE[@]}
|
||||
while [ "$i" -lt "$n" ]; do
|
||||
name="${SERVE[$i]}"
|
||||
case "$name" in
|
||||
--tester-port) die "case2-unit.sh retires tester TCP; use --tester-socket" ;;
|
||||
--tester-socket) [ "$PROFILE" = factory ] || die "the library profile refuses --tester-socket" ;;
|
||||
--checkout) die "refusing --checkout: Case 2 has no queue panel" ;;
|
||||
--board) die "refusing --board: Case 2 runs no control board" ;;
|
||||
--sso) die "refusing --sso: the Console signs people in through the proxy, with the preview set and the proof pair (Q175 phase 2 is a follow-up)" ;;
|
||||
--port|--business) ;;
|
||||
--public-host|--auth-header|--allow-user|--proof-header|--proof-file|--send)
|
||||
[ "$PROFILE" = library ] || die "the factory profile refuses $name: its host has no proxy" ;;
|
||||
*) die "unknown serve flag: $name" ;;
|
||||
esac
|
||||
[ -z "${seen[$name]:-}" ] || die "serve flag $name given twice"
|
||||
seen[$name]=1
|
||||
if [ "$name" = --tester-socket ]; then tester_socket=1; flags="$flags --tester-socket"; i=$((i + 1)); continue; fi
|
||||
if [ "$name" = --send ]; then flags="$flags --send"; i=$((i + 1)); continue; fi
|
||||
[ $((i + 1)) -lt "$n" ] || die "serve flag $name needs a value"
|
||||
value="${SERVE[$((i + 1))]}"
|
||||
case "$name" in
|
||||
--port) port="$value"
|
||||
[[ "$port" =~ ^[1-9][0-9]{0,3}$ ]] && [ "$port" -le 1023 ] || die "--port must be 1..1023, so only the unit's capability can bind it: $port" ;;
|
||||
--business) [ "$value" = "$BUSINESS" ] || die "serve --business $value is not the unit's business $BUSINESS" ;;
|
||||
--public-host) public_host="$value"
|
||||
case "$value" in [A-Za-z0-9]*) ;; *) die "public host must be a hostname, no port or scheme: $value" ;; esac
|
||||
case "$value" in *[!A-Za-z0-9.-]*) die "public host must be a hostname, no port or scheme: $value" ;; esac ;;
|
||||
--auth-header) auth_header="$value"
|
||||
case "$value" in [A-Za-z0-9]*) ;; *) die "auth header must be a header name: $value" ;; esac
|
||||
case "$value" in *[!A-Za-z0-9-]*) die "auth header must be a header name: $value" ;; esac ;;
|
||||
--allow-user) allow_user="$value"
|
||||
case "$value" in [A-Za-z0-9]*) ;; *) die "allowed user must start with a letter or digit: $value" ;; esac
|
||||
case "$value" in *[!A-Za-z0-9._@+-]*) die "allowed user contains a character the unit cannot carry: $value" ;; esac ;;
|
||||
--proof-header) proof_header="$value"
|
||||
[[ "$value" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,63}$ ]] || die "invalid proxy proof header" ;;
|
||||
--proof-file) proof_file="$value" ;;
|
||||
esac
|
||||
flags="$flags $name $value"
|
||||
i=$((i + 2))
|
||||
done
|
||||
local required=(--port --business)
|
||||
[ "$PROFILE" = factory ] || required+=(--public-host --auth-header --allow-user --send --proof-header --proof-file)
|
||||
for name in "${required[@]}"; do
|
||||
[ -n "${seen[$name]:-}" ] || die "serve flag $name is required"
|
||||
done
|
||||
if [ "$PROFILE" = factory ]; then
|
||||
if [ "$tester_socket" = 1 ]; then
|
||||
local query
|
||||
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"
|
||||
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"
|
||||
fi
|
||||
echo "${flags# }"; return 0
|
||||
fi
|
||||
# Lead decision 101: the uid header carries the Authentik uid.
|
||||
if [ "${auth_header,,}" = x-authentik-uid ] && ! [[ "$allow_user" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
die "with --auth-header $auth_header the allowed user must be an Authentik uid, 64 lower-case hex characters (lead decision 101): $allow_user"
|
||||
fi
|
||||
case "${proof_header,,}" in host|origin|x-mosaic-tester|"${auth_header,,}") die "proxy proof header must differ from Host, Origin, tester and auth headers" ;; esac
|
||||
# The proof file: metadata only, never opened (design 9.3).
|
||||
local secrets="$HOME_DIR/.config/mosaic-dev/secrets" real owner mode
|
||||
abs_plain "$proof_file" 'proxy proof file'
|
||||
[ ! -L "$proof_file" ] || die "proxy proof file must not be a symlink: $proof_file"
|
||||
[ -f "$proof_file" ] || die "proxy proof file must be a regular file: $proof_file"
|
||||
real="$(realpath -e -- "$proof_file")" || die "cannot resolve proxy proof file: $proof_file"
|
||||
[ "$real" = "$proof_file" ] || die "proxy proof file path must have no symlink in it: $proof_file"
|
||||
case "$proof_file" in "$secrets"/*) ;; *) die "proxy proof file must be under $secrets, outside the data root: $proof_file" ;; esac
|
||||
owner="$(stat -c '%u' -- "$proof_file")" || die "cannot stat proxy proof file"
|
||||
mode="$(stat -c '%a' -- "$proof_file")" || die "cannot stat proxy proof file"
|
||||
[ "$owner" = "$USER_UID" ] || die "proxy proof file must be owned by $USER_NAME (uid $USER_UID): $proof_file"
|
||||
[ "$mode" = 600 ] || [ "$mode" = 400 ] || die "proxy proof file must be mode 0600 or 0400, not $mode: $proof_file"
|
||||
echo "${flags# }"
|
||||
}
|
||||
# PATH: the node's directory first, then the default list without it. Unlike
|
||||
# scripts/webui-service.sh:303-305 this always puts node's directory first,
|
||||
# so the bus's `exec node` (scripts/mosaic) finds the same node.
|
||||
NODE_DIR="$(dirname -- "$NODE")"
|
||||
UNIT_PATH="$NODE_DIR"
|
||||
for d in /usr/local/bin /usr/bin /bin; do [ "$d" = "$NODE_DIR" ] || UNIT_PATH="$UNIT_PATH:$d"; done
|
||||
FLAGS="$(serve_flags)" || exit 1
|
||||
|
||||
HARDENING="ProtectSystem=strict
|
||||
ReadWritePaths=$HOME_DIR
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes"
|
||||
CAPS="AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE"
|
||||
# The default instance, as scripts/instance.sh:85-107 renders it: no
|
||||
# MOSAIC_CONFIG line, and the session env under %h, which becomes <home>.
|
||||
SESSION_ENV='%h/.config/mosaic-dev/secrets/claude-session.env'
|
||||
ENV_FILE="-$HOME_DIR/.config/mosaic-dev/secrets/claude-session.env"
|
||||
PM_FLAGS=''
|
||||
[ "$PM" = 0 ] || PM_FLAGS=' --pm'
|
||||
CLAUDE_ENV=''
|
||||
[ "$CLAUDE_SET" = 0 ] || CLAUDE_ENV="Environment=MOSAIC_CLAUDE_BIN=$CLAUDE"
|
||||
|
||||
# One unit from its template. kind is bus or webui.
|
||||
render() {
|
||||
local kind="$1" template="$2" text tokens
|
||||
[ -f "$template" ] && [ ! -L "$template" ] || die "template missing or not a regular file: $template"
|
||||
text="$(sed -e "s|@REPO@|$COPY|g" -e "s|@PATH@|$UNIT_PATH|g" -e "s|@NODE@|$NODE|g" \
|
||||
-e "s|@PM_FLAGS@|$PM_FLAGS|g" -e "s|^@CLAUDE_ENV@\$|$CLAUDE_ENV|" \
|
||||
-e '/^@CONFIG_ENV@$/d' -e "s|@SESSION_ENV@|$SESSION_ENV|g" \
|
||||
-e "s|%h|$HOME_DIR|g" -- "$template")"
|
||||
# Checked before @FLAGS@ goes in, since an allowed user may hold @.
|
||||
tokens="$(printf '%s\n' "$text" | grep -oE '@[A-Z_]+@' || true)"
|
||||
if [ "$kind" = webui ]; then
|
||||
[ "$tokens" = @FLAGS@ ] || die "$template: a token other than one @FLAGS@ is left after the render: $(echo $tokens)"
|
||||
text="${text//@FLAGS@/$FLAGS}"
|
||||
else
|
||||
[ -z "$tokens" ] || die "$template: a token is left after the render: $(echo $tokens)"
|
||||
fi
|
||||
if printf '%s\n' "$text" | grep -q '%h'; then die "$template: %h is left after the render"; fi
|
||||
local add="User=$USER_NAME\nGroup=$GROUP\n${HARDENING//$'\n'/\\n}\n"
|
||||
[ "$kind" = bus ] || add="$add${CAPS//$'\n'/\\n}\n"
|
||||
# The added lines go at the end of [Service], before its trailing blank
|
||||
# lines. In [Install], WantedBy=multi-user.target replaces
|
||||
# WantedBy=default.target in both units, so both start at boot (Q189).
|
||||
printf '%s\n' "$text" | awk -v add="$add" '
|
||||
function blanks_out() { for (; blanks > 0; blanks--) print "" }
|
||||
/^\[.*\]$/ {
|
||||
if (sec == "[Service]") printf "%s", add
|
||||
sec = $0
|
||||
blanks_out(); print; next
|
||||
}
|
||||
$0 == "" { blanks++; next }
|
||||
{ blanks_out() }
|
||||
sec == "[Install]" && $0 == "WantedBy=default.target" { $0 = "WantedBy=multi-user.target" }
|
||||
{ print }
|
||||
END { if (sec == "[Service]") printf "%s", add; blanks_out() }'
|
||||
}
|
||||
|
||||
# The unit's settings as section<TAB>key<TAB>value, one per line, keys
|
||||
# trimmed as systemd trims them. A continued line refuses, so no setting
|
||||
# hides in one.
|
||||
settings() {
|
||||
awk '
|
||||
{ sub(/^[ \t]+/, ""); sub(/[ \t]+$/, "") }
|
||||
$0 == "" || /^[#;]/ { next }
|
||||
/\\$/ { print "CONTINUED"; exit }
|
||||
/^\[.*\]$/ { sec = $0; next }
|
||||
{
|
||||
i = index($0, "="); if (i == 0) { print "NOKEY\t" $0; next }
|
||||
k = substr($0, 1, i - 1); v = substr($0, i + 1)
|
||||
sub(/[ \t]+$/, "", k); sub(/^[ \t]+/, "", v)
|
||||
print sec "\t" k "\t" v
|
||||
}'
|
||||
}
|
||||
|
||||
# Refuses unless one unit's fixed lines hold. what names the unit in messages,
|
||||
# copy is the copy directory it should run from.
|
||||
check_unit() {
|
||||
local kind="$1" text="$2" what="$3" copy="$4" s
|
||||
s="$(printf '%s\n' "$text" | settings)"
|
||||
! printf '%s\n' "$s" | grep -qx CONTINUED || die "$what: a line continues onto the next"
|
||||
! printf '%s\n' "$s" | grep -q '^NOKEY' || die "$what: a line is neither a section, a comment nor a setting"
|
||||
# %h in a system unit is root's home, not <user>'s.
|
||||
! printf '%s\n' "$text" | grep -q '%h' || die "$what: %h is left; a system unit reads it as root's home"
|
||||
local key
|
||||
for key in RestrictNamespaces PrivateUsers SystemCallFilter PermissionsStartOnly; do
|
||||
! printf '%s\n' "$s" | awk -F'\t' -v k="$key" '$2 == k { found = 1 } END { exit !found }' \
|
||||
|| die "$what: refusing $key=, which the hub's units must not hold"
|
||||
done
|
||||
# An Exec*= value whose leading prefix characters hold + or ! runs with
|
||||
# full privileges, whatever User= says.
|
||||
local bad
|
||||
bad="$(printf '%s\n' "$s" | awk -F'\t' '$2 ~ /^Exec/ { p = $3; sub(/[^-@:+!].*$/, "", p); if (p ~ /[+!]/) print $2 "=" $3 }')"
|
||||
[ -z "$bad" ] || die "$what: refusing an Exec line that runs with full privileges: $bad"
|
||||
# The fixed lines, each exactly once, in [Service].
|
||||
one() {
|
||||
local k="$1" v="$2" got
|
||||
got="$(printf '%s\n' "$s" | awk -F'\t' -v k="$k" '$2 == k { print $1 "\t" $3 }')"
|
||||
[ "$got" = "[Service] $v" ] || die "$what: $k= must be exactly once in [Service], as $k=$v"
|
||||
}
|
||||
none() {
|
||||
! printf '%s\n' "$s" | awk -F'\t' -v k="$1" '$2 == k { found = 1 } END { exit !found }' || die "$what: refusing $1=, which this unit must not hold"
|
||||
}
|
||||
one User "$USER_NAME"
|
||||
one Group "$GROUP"
|
||||
one WorkingDirectory "$copy"
|
||||
# Environment= lines: PATH, and MOSAIC_CLAUDE_BIN only with --claude.
|
||||
local envs want_envs="[Service] PATH=$UNIT_PATH"
|
||||
[ "$kind" = webui ] || [ -z "$CLAUDE_ENV" ] || want_envs="$want_envs
|
||||
[Service] MOSAIC_CLAUDE_BIN=$CLAUDE"
|
||||
envs="$(printf '%s\n' "$s" | awk -F'\t' '$2 == "Environment" { print $1 "\t" $3 }')"
|
||||
[ "$envs" = "$want_envs" ] || die "$what: the Environment= lines must be exactly: $(printf '%s' "$want_envs" | cut -f2 | tr '\n' ' ')"
|
||||
one NoNewPrivileges yes
|
||||
one ProtectSystem strict
|
||||
one ReadWritePaths "$HOME_DIR"
|
||||
one ProtectHome yes
|
||||
one PrivateTmp yes
|
||||
local order
|
||||
order="$(printf '%s\n' "$s" | awk -F'\t' '$2 ~ /^(ProtectSystem|ReadWritePaths|ProtectHome|PrivateTmp)$/ { printf "%s ", $2 }')"
|
||||
[ "$order" = "ProtectSystem ReadWritePaths ProtectHome PrivateTmp " ] || die "$what: the hardening lines must be in the order ProtectSystem, ReadWritePaths, ProtectHome, PrivateTmp"
|
||||
local wanted
|
||||
wanted="$(printf '%s\n' "$s" | awk -F'\t' '$2 == "WantedBy" { print $1 "\t" $3 }')"
|
||||
[ "$wanted" = "[Install] multi-user.target" ] || die "$what: WantedBy= must be exactly once, as WantedBy=multi-user.target"
|
||||
if [ "$kind" = bus ]; then
|
||||
one ExecStart "$copy/scripts/mosaic bus start %i$PM_FLAGS"
|
||||
one EnvironmentFile "$ENV_FILE"
|
||||
none AmbientCapabilities
|
||||
none CapabilityBoundingSet
|
||||
else
|
||||
one ExecStart "$NODE $copy/packages/webui/src/cli.mjs serve $FLAGS"
|
||||
none EnvironmentFile
|
||||
one AmbientCapabilities CAP_NET_BIND_SERVICE
|
||||
one CapabilityBoundingSet CAP_NET_BIND_SERVICE
|
||||
fi
|
||||
}
|
||||
|
||||
BUS_TEXT="$(render bus "$BUS_TEMPLATE")" || exit 1
|
||||
WEBUI_TEXT="$(render webui "$WEBUI_TEMPLATE")" || exit 1
|
||||
check_unit bus "$BUS_TEXT" "rendered $BUS_UNIT" "$COPY"
|
||||
check_unit webui "$WEBUI_TEXT" "rendered $WEBUI_UNIT" "$COPY"
|
||||
|
||||
# A re-install: each installed unit passes the same checks, both name one
|
||||
# copy, and each equals the render once its copy id is the new one.
|
||||
[ -d "$UNIT_DIR" ] && [ ! -L "$UNIT_DIR" ] || die "no unit directory $UNIT_DIR"
|
||||
installed_ids=''
|
||||
for pair in "bus:$BUS_UNIT:BUS_TEXT" "webui:$WEBUI_UNIT:WEBUI_TEXT"; do
|
||||
IFS=: read -r kind name var <<<"$pair"
|
||||
path="$UNIT_DIR/$name"
|
||||
[ ! -L "$path" ] || die "refusing: $path is a symlink"
|
||||
[ -e "$path" ] || continue
|
||||
[ -f "$path" ] || die "refusing: $path is not a regular file"
|
||||
text="$(cat -- "$path")"
|
||||
old="$(printf '%s\n' "$text" | settings | awk -F'\t' -v c="$COPIES/" '$2 == "WorkingDirectory" && index($3, c) == 1 { print substr($3, length(c) + 1) }')"
|
||||
[[ "$old" =~ ^[0-9a-f]{40}$ ]] || die "installed $path names no copy under $COPIES"
|
||||
check_unit "$kind" "$text" "installed $path" "$COPIES/$old"
|
||||
[ "$text" = "$(printf '%s\n' "${!var}" | sed "s|$COPY|$COPIES/$old|g")" ] \
|
||||
|| die "installed $path differs from the render in more than the copy id; the node path, serve flags, --pm and --claude must equal the installed ones"
|
||||
installed_ids="$installed_ids $old"
|
||||
done
|
||||
read -r -a ids <<<"$installed_ids"
|
||||
[ "${#ids[@]}" -lt 2 ] || [ "${ids[0]}" = "${ids[1]}" ] || die "the installed units name different copies, ${ids[0]} and ${ids[1]}"
|
||||
|
||||
# `systemctl start` leaves a running unit on the copy it started from, so
|
||||
# note which units run before the write (row 195 owns the restart).
|
||||
BUS_NAME="mosaic-bus@$BUSINESS.service"
|
||||
running=''
|
||||
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
|
||||
if "$SYSTEMCTL" is-active --quiet "$name"; then running="$running $name"; fi
|
||||
done
|
||||
|
||||
# Write each unit to a temp file in the unit directory, then rename.
|
||||
write_unit() {
|
||||
local name="$1" text="$2" tmp
|
||||
tmp="$(mktemp "$UNIT_DIR/.case2-unit.XXXXXX")"
|
||||
printf '%s\n' "$text" >"$tmp"
|
||||
chmod 0644 "$tmp"
|
||||
[ "$(id -u)" != 0 ] || chown root:root "$tmp"
|
||||
mv -fT -- "$tmp" "$UNIT_DIR/$name"
|
||||
}
|
||||
write_unit "$BUS_UNIT" "$BUS_TEXT"
|
||||
write_unit "$WEBUI_UNIT" "$WEBUI_TEXT"
|
||||
# Enable both, then start the bus, then the Console (design 10 step 2c).
|
||||
# serve calls the bus per request, so the units carry no After= or Wants=.
|
||||
"$SYSTEMCTL" daemon-reload || die "systemctl daemon-reload failed; the units are written, nothing is enabled or started"
|
||||
"$SYSTEMCTL" enable "$BUS_NAME" "$WEBUI_UNIT" || die "systemctl enable $BUS_NAME $WEBUI_UNIT failed; nothing is started"
|
||||
"$SYSTEMCTL" start "$BUS_NAME" || die "systemctl start $BUS_NAME failed; the Console is not started"
|
||||
"$SYSTEMCTL" start "$WEBUI_UNIT" || die "systemctl start $WEBUI_UNIT failed; the bus is running"
|
||||
if [ -z "$running" ]; then
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID; enabled and started $BUS_NAME, then $WEBUI_UNIT"
|
||||
exit 0
|
||||
fi
|
||||
echo "case2-unit: wrote $UNIT_DIR/$BUS_UNIT and $UNIT_DIR/$WEBUI_UNIT from copy $COPY_ID and enabled both"
|
||||
for name in "$BUS_NAME" "$WEBUI_UNIT"; do
|
||||
case " $running " in
|
||||
*" $name "*) echo "case2-unit: $name was already running, so it is still on ${ids[0]:+copy }${ids[0]:-the copy it started from}, not copy $COPY_ID" ;;
|
||||
*) echo "case2-unit: started $name on copy $COPY_ID" ;;
|
||||
esac
|
||||
done
|
||||
echo "case2-unit: restart $BUS_NAME and $WEBUI_UNIT together to put both on copy $COPY_ID; until then they may run different copies"
|
||||
@@ -100,8 +100,8 @@ def main():
|
||||
assert target.read_bytes() == original
|
||||
assert subprocess.check_output(['git','status','--porcelain','--untracked-files=no'],cwd=REPO) == status
|
||||
(args.out/'results.json').write_text(json.dumps(results,indent=2)+'\n')
|
||||
if failures >= 2:
|
||||
raise RuntimeError('two failures: end the window')
|
||||
if failures >= 1:
|
||||
raise RuntimeError('first unexpected failure: end the sage-2297 window')
|
||||
print(json.dumps(results,indent=2))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Row253 M1-M33 results
|
||||
|
||||
| Check | Outcome | Base seconds | Changed seconds |
|
||||
|---|---|---:|---:|
|
||||
| M1 | killed | 0.214 | 0.214 |
|
||||
| M2 | killed | 0.465 | 0.465 |
|
||||
| M3 | killed | 0.365 | 0.365 |
|
||||
| M4 | killed | 0.916 | 1.217 |
|
||||
| M5 | killed | 0.916 | 0.916 |
|
||||
| M6 | killed | 0.916 | 0.315 |
|
||||
| M7 | killed | 0.214 | 0.164 |
|
||||
| M8 | killed | 0.916 | 0.766 |
|
||||
| M9 | killed | 0.916 | 0.916 |
|
||||
| M10 | killed | 0.916 | 1.016 |
|
||||
| M11 | killed | 0.916 | 1.067 |
|
||||
| M12 | killed | 0.365 | 0.415 |
|
||||
| M13 | killed | 0.365 | 0.615 |
|
||||
| M14 | killed | 0.365 | 0.615 |
|
||||
| M15 | killed | 0.515 | 0.916 |
|
||||
| M16 | killed | 0.916 | 0.515 |
|
||||
| M17 | killed | 6.279 | 0.515 |
|
||||
| M18 | killed | 6.279 | 1.317 |
|
||||
| M19 | killed | 6.279 | 2.920 |
|
||||
| M20 | killed | 6.279 | 2.218 |
|
||||
| M21 | killed | 6.279 | 3.672 |
|
||||
| M22 | killed | 6.279 | 3.822 |
|
||||
| M23 | killed | 6.279 | 5.626 |
|
||||
| M24 | killed | 0.365 | 0.215 |
|
||||
| M25 | killed | 0.615 | 0.615 |
|
||||
| M26 | killed | 6.279 | 5.776 |
|
||||
| M27 | killed | 0.615 | 0.615 |
|
||||
| M28 | killed | 0.916 | 0.716 |
|
||||
| M29 | killed | 0.916 | 0.816 |
|
||||
| M30 | killed | 0.916 | 0.415 |
|
||||
| M31 | killed | 0.214 | 0.215 |
|
||||
| M32 | killed | 0.214 | 0.265 |
|
||||
| M33 | killed | 0.365 | 0.967 |
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"reason": "Static C4 sudo monitor/session gap: cannot establish SIGKILL group containment for a policy using a pty. No failing TAP entries observed before interruption.",
|
||||
"window_pid": 221862,
|
||||
"pipeline_group": 612045,
|
||||
"signal": "SIGTERM"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Run2 deliberate interruption
|
||||
|
||||
The raw Webui/whole-controller traceback says unexpected failure because the generic driver reports any nonzero pipeline status that way. This pipeline received SIGTERM from Gander for the static C4 gap; INTERRUPT.json records the owned group. Sage-2307 item5 rules this an interruption, not a test failure. CLI had completed 755/0 before Webui started; Webui has no completed total, and ten shells never started. Sweep count0. No run followed the stop.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"scripts/case1-accept.sh": "1302cfc659ff9073035b99d7b4071aa2c75070da142bb201d2f58a66e113d71f",
|
||||
"scripts/case2-unit.sh": "a84bb266d52a5c078171582e2f9a511becf56421ae218266e3238d704727f6dd"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"grant": "sage-2297",
|
||||
"stop": "Deliberate interruption for static C4 gap; sage-2307 says Webui interrupt is not a failure.",
|
||||
"green": [
|
||||
{
|
||||
"code": 0,
|
||||
"timeout": false,
|
||||
"seconds": 16.449,
|
||||
"log": "agents/gander/work/queue-253/run2/green/focused-1.log",
|
||||
"name": "focused-1",
|
||||
"expected_red": false,
|
||||
"counts": {
|
||||
"tests": 36,
|
||||
"pass": 36,
|
||||
"fail": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 0,
|
||||
"timeout": false,
|
||||
"seconds": 37.55,
|
||||
"log": "agents/gander/work/queue-253/run2/green/focused-2.log",
|
||||
"name": "focused-2",
|
||||
"expected_red": false,
|
||||
"counts": {
|
||||
"tests": 28,
|
||||
"pass": 28,
|
||||
"fail": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"whole": [
|
||||
{
|
||||
"code": 0,
|
||||
"timeout": false,
|
||||
"seconds": 597.084,
|
||||
"log": "agents/gander/work/queue-253/run2/whole/cli.log",
|
||||
"name": "cli",
|
||||
"expected_red": false,
|
||||
"counts": {
|
||||
"tests": 755,
|
||||
"pass": 755,
|
||||
"fail": 0,
|
||||
"skipped": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": -15,
|
||||
"timeout": false,
|
||||
"seconds": 4.736,
|
||||
"log": "agents/gander/work/queue-253/run2/whole/webui.log",
|
||||
"name": "webui",
|
||||
"expected_red": false,
|
||||
"counts": {}
|
||||
}
|
||||
],
|
||||
"changes": {
|
||||
"killed": 33,
|
||||
"survivors": 0,
|
||||
"timeouts": 0,
|
||||
"base_seconds": 11.808,
|
||||
"changed_seconds": 41.202
|
||||
},
|
||||
"shells": "NOT RUN",
|
||||
"sweep": {
|
||||
"count": 0,
|
||||
"processes": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 factory refuses retired TCP", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 factory refuses retired TCP and a value after the socket marker
|
||||
ok 1 - row253 factory refuses retired TCP and a value after the socket marker
|
||||
---
|
||||
duration_ms: 29.223639
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 141.584967
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 factory refuses retired TCP", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,37 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 factory refuses retired TCP and a value after the socket marker
|
||||
not ok 1 - row253 factory refuses retired TCP and a value after the socket marker
|
||||
---
|
||||
duration_ms: 17.019714
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:494:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
The input did not match the regular expression /case2-unit.sh retires tester TCP; use --tester-socket/. Input:
|
||||
|
||||
'case2-unit: unknown serve flag: --tester-port\n'
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
case2-unit: unknown serve flag: --tester-port
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:219:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:496:3)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 127.863578
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 820.638371
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-eR5it3/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-eR5it3/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 937.440456
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 840.318392
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-CGGJnM/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-CGGJnM/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 962.598357
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "a bad args file refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
ok 1 - row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
---
|
||||
duration_ms: 160.205676
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 276.702477
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "a bad args file refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,30 @@
|
||||
TAP version 13
|
||||
# Subtest: row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
not ok 1 - row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
---
|
||||
duration_ms: 141.954375
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:587:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'retired TCP'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: 'case1-accept: refusing: unknown serve flag --tester-port'
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:608:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 298.371364
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "a bad args file refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,64 @@
|
||||
TAP version 13
|
||||
# Subtest: row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
not ok 1 - row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
---
|
||||
duration_ms: 419.508161
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:587:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
no tester socket: F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-511942f73671
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
|
||||
0 !== 2
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 2
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:606:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 532.619824
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "a bad args file refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,64 @@
|
||||
TAP version 13
|
||||
# Subtest: row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
not ok 1 - row 195 accept: a bad args file refuses with exit 2 before any check, each for its own reason
|
||||
---
|
||||
duration_ms: 445.63961
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:587:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
socket value: F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-fed0bb351858
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
|
||||
0 !== 2
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 2
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:606:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 550.692311
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 acceptance rejects bad config", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 acceptance rejects bad config and unsupported copies before talk
|
||||
ok 1 - row253 acceptance rejects bad config and unsupported copies before talk
|
||||
---
|
||||
duration_ms: 323.110026
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 423.527216
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 acceptance rejects bad config", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,64 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 acceptance rejects bad config and unsupported copies before talk
|
||||
not ok 1 - row253 acceptance rejects bad config and unsupported copies before talk
|
||||
---
|
||||
duration_ms: 687.049369
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:263:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
failed: F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-e4b5e2852bf0
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
|
||||
0 !== 2
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 2
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:274:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 808.7987
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P8 refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P8 refuses both retired TCP and board listeners
|
||||
ok 1 - row253 P8 refuses both retired TCP and board listeners
|
||||
---
|
||||
duration_ms: 721.532961
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 829.227296
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P8 refuses", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,40 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P8 refuses both retired TCP and board listeners
|
||||
not ok 1 - row253 P8 refuses both retired TCP and board listeners
|
||||
---
|
||||
duration_ms: 331.920117
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:294:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
+ 'closed'
|
||||
- 'unnamed'
|
||||
]
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
0: 'unnamed'
|
||||
actual:
|
||||
0: 'closed'
|
||||
operator: 'deepStrictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:297:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 436.160921
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 6033.541448
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 6163.295312
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 365.277582
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'parent-mode'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-e2c81d87c9ad
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 456.731571
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 1114.429559
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'socket-mode'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-3dfa29c347db
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 1218.086888
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 2723.404154
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'peer-pid'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-03e5279e0659
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=555 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 2817.344607
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "library refuses a privileged-floor miss", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
|
||||
ok 1 - library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
|
||||
---
|
||||
duration_ms: 283.54782
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 396.947233
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "library refuses a privileged-floor miss", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
|
||||
not ok 1 - library refuses a privileged-floor miss, a bad proof pair and every flag outside its set
|
||||
---
|
||||
duration_ms: 263.486555
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:427:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-rCATvD/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-rCATvD/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:452:5)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 374.276814
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 2040.364423
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'peer'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-3f452d95b932
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=998 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 2137.533797
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 3470.827374
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'peer-malformed'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-e141fcddc5fe
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 3570.767703
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 3652.173017
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'python-not-elf'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-0b7706b37e56
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 3752.833252
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 5420.784703
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'changed-inode'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-df3dd7da99dd
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=1024 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 5534.524184
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 factory socket uses actual config", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 factory socket uses actual config and target-uid query without widening hardening
|
||||
ok 1 - row253 factory socket uses actual config and target-uid query without widening hardening
|
||||
---
|
||||
duration_ms: 157.642582
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 282.839864
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 factory socket uses actual config", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,35 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 factory socket uses actual config and target-uid query without widening hardening
|
||||
not ok 1 - row253 factory socket uses actual config and target-uid query without widening hardening
|
||||
---
|
||||
duration_ms: 25.1268
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:125:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
case2-unit: tester config query failed
|
||||
|
||||
|
||||
1 !== 0
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 0
|
||||
actual: 1
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:127:45)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 145.997307
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 socket observation uses configured root", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
ok 1 - row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
---
|
||||
duration_ms: 391.973008
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 509.073844
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 socket observation uses configured root", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,94 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
not ok 1 - row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
---
|
||||
duration_ms: 404.191055
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:222:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
The input did not match the regular expression /P10 tester-socket-squat unnamed boundary=observed path=\/srv\/mosaic\/custom-data\/console\/tester.sock/. Input:
|
||||
|
||||
'F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\n' +
|
||||
'F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\n' +
|
||||
'F3 tester-reply pass chief replied to case1-accept-1cbca2d166b5\n' +
|
||||
'F4 precheck pass case2-precheck.sh --profile factory exits 0\n' +
|
||||
"F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\n" +
|
||||
"F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\n" +
|
||||
'F7 pm-live pass one live launch: pm run r-0002\n' +
|
||||
"F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\n" +
|
||||
'F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\n' +
|
||||
'F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\n' +
|
||||
'F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\n' +
|
||||
'P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\n' +
|
||||
"P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\n" +
|
||||
'P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\n' +
|
||||
"P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\n" +
|
||||
'P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\n' +
|
||||
'P6 group-members closed sudo holds only localadmin, and docker and lxd have no members\n' +
|
||||
'P7 sudoers-d closed no file under /etc/sudoers.d names mosaic\n' +
|
||||
'P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/custom-data/console/tester.sock\n' +
|
||||
'P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\n' +
|
||||
'P10 tester-socket-squat unnamed boundary=failed reason=probe path=/srv/mosaic/custom-data/console/tester.sock MainPID=200; hostile same-uid substitution remains\n' +
|
||||
'P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\n' +
|
||||
'count pass 11\n' +
|
||||
'count FAIL 0\n' +
|
||||
'count closed 7\n' +
|
||||
'count accepted Q182+Q183 1\n' +
|
||||
'count accepted Q183 1\n' +
|
||||
'count accepted mos-1096 1\n' +
|
||||
'count unnamed 1\n' +
|
||||
'case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n'
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-1cbca2d166b5
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/custom-data/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=failed reason=probe path=/srv/mosaic/custom-data/console/tester.sock MainPID=200; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:228:10)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 514.820178
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 P10 refuses unsafe metadata", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,61 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
not ok 1 - row253 P10 refuses unsafe metadata, wrong peers and an unsafe Python without claiming closure
|
||||
---
|
||||
duration_ms: 5572.93195
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:235:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'floor'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-70a1a38a887d
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=observed path=/srv/mosaic/.mosaic-dev/console/tester.sock parent=0700 owner=997 socket=0600 peer_pid=200 peer_uid=997 floor=700 port=732; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:258:12)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 5679.97534
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 socket observation uses configured root", "packages/cli/tests/case1-accept.test.mjs"]
|
||||
@@ -0,0 +1,94 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
not ok 1 - row253 socket observation uses configured root, metadata and the Console peer without sending bytes
|
||||
---
|
||||
duration_ms: 428.873018
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:222:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
The input did not match the regular expression /P10 tester-socket-squat unnamed boundary=observed path=\/srv\/mosaic\/custom-data\/console\/tester.sock/. Input:
|
||||
|
||||
'F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\n' +
|
||||
'F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\n' +
|
||||
'F3 tester-reply pass chief replied to case1-accept-024f94e9d741\n' +
|
||||
'F4 precheck pass case2-precheck.sh --profile factory exits 0\n' +
|
||||
"F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\n" +
|
||||
"F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\n" +
|
||||
'F7 pm-live pass one live launch: pm run r-0002\n' +
|
||||
"F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\n" +
|
||||
'F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\n' +
|
||||
'F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\n' +
|
||||
'F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\n' +
|
||||
'P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\n' +
|
||||
"P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\n" +
|
||||
'P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\n' +
|
||||
"P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\n" +
|
||||
'P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\n' +
|
||||
'P6 group-members closed sudo holds only localadmin, and docker and lxd have no members\n' +
|
||||
'P7 sudoers-d closed no file under /etc/sudoers.d names mosaic\n' +
|
||||
'P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/custom-data/console/tester.sock\n' +
|
||||
'P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\n' +
|
||||
'P10 tester-socket-squat unnamed boundary=failed reason=peer path=/srv/mosaic/custom-data/console/tester.sock MainPID=200; hostile same-uid substitution remains\n' +
|
||||
'P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\n' +
|
||||
'count pass 11\n' +
|
||||
'count FAIL 0\n' +
|
||||
'count closed 7\n' +
|
||||
'count accepted Q182+Q183 1\n' +
|
||||
'count accepted Q183 1\n' +
|
||||
'count accepted mos-1096 1\n' +
|
||||
'count unnamed 1\n' +
|
||||
'case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n'
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)
|
||||
F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200
|
||||
F3 tester-reply pass chief replied to case1-accept-024f94e9d741
|
||||
F4 precheck pass case2-precheck.sh --profile factory exits 0
|
||||
F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'
|
||||
F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'
|
||||
F7 pm-live pass one live launch: pm run r-0002
|
||||
F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'
|
||||
F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"
|
||||
F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0
|
||||
F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent
|
||||
P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.
|
||||
P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent
|
||||
P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines
|
||||
P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve
|
||||
P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)
|
||||
P6 group-members closed sudo holds only localadmin, and docker and lxd have no members
|
||||
P7 sudoers-d closed no file under /etc/sudoers.d names mosaic
|
||||
P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/custom-data/console/tester.sock
|
||||
P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member
|
||||
P10 tester-socket-squat unnamed boundary=failed reason=peer path=/srv/mosaic/custom-data/console/tester.sock MainPID=200; hostile same-uid substitution remains
|
||||
P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]
|
||||
count pass 11
|
||||
count FAIL 0
|
||||
count closed 7
|
||||
count accepted Q182+Q183 1
|
||||
count accepted Q183 1
|
||||
count accepted mos-1096 1
|
||||
count unnamed 1
|
||||
case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case1-accept.test.mjs:228:10)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 526.984533
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,37 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 534.093046
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
The input did not match the regular expression /symlink/. Input:
|
||||
|
||||
'case2-unit: tester path refuses parent-mode\n'
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
actual: |-
|
||||
case2-unit: tester path refuses parent-mode
|
||||
|
||||
operator: 'match'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:219:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 635.83252
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 593.30111
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-2BFoyS/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-2BFoyS/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 730.485367
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "factory refuses duplicate socket", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: factory refuses duplicate socket, a missing --port or --business, and each proxy flag
|
||||
ok 1 - factory refuses duplicate socket, a missing --port or --business, and each proxy flag
|
||||
---
|
||||
duration_ms: 179.152253
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 292.532419
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "factory refuses duplicate socket", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: factory refuses duplicate socket, a missing --port or --business, and each proxy flag
|
||||
not ok 1 - factory refuses duplicate socket, a missing --port or --business, and each proxy flag
|
||||
---
|
||||
duration_ms: 148.376561
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:480:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-LvQyh8/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-LvQyh8/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:482:3)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 269.52288
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 217.933488
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-A4dIao/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-A4dIao/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 333.269816
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 config parser is strict", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 config parser is strict and output capture is bounded
|
||||
ok 1 - row253 config parser is strict and output capture is bounded
|
||||
---
|
||||
duration_ms: 21.390039
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 137.608023
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 config parser is strict", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,30 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 config parser is strict and output capture is bounded
|
||||
not ok 1 - row253 config parser is strict and output capture is bounded
|
||||
---
|
||||
duration_ms: 6.759471
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:204:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: '{"dataRoot":"/srv/mosaic/data","socket":true,"extra":1}'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 0
|
||||
actual: 0
|
||||
operator: 'notStrictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:211:10)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 140.68039
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 config parser is strict", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,30 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 config parser is strict and output capture is bounded
|
||||
not ok 1 - row253 config parser is strict and output capture is bounded
|
||||
---
|
||||
duration_ms: 25.857282
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:204:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'Expected "actual" to be strictly unequal to: 0'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 0
|
||||
actual: 0
|
||||
operator: 'notStrictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:214:9)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 164.452147
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 query reaps its group", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 query reaps its group even when a child closes stdout before the query returns
|
||||
ok 1 - row253 query reaps its group even when a child closes stdout before the query returns
|
||||
---
|
||||
duration_ms: 136.940126
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 267.726596
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 query reaps its group", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,28 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 query reaps its group even when a child closes stdout before the query returns
|
||||
not ok 1 - row253 query reaps its group even when a child closes stdout before the query returns
|
||||
---
|
||||
duration_ms: 717.025612
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:187:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: 'query descendant 220087 still S'
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: true
|
||||
actual: false
|
||||
operator: '=='
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:201:9)
|
||||
async Test.run (node:internal/test_runner/test:1409:7)
|
||||
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 887.16243
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 737.76089
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 848.593013
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 1002.228824
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-Ervs02/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-Ervs02/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 1115.046018
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 751.388897
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-Vp5Yfr/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-Vp5Yfr/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 856.638903
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,36 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 installer refuses unsafe location and query facts before unit writes
|
||||
not ok 1 - row253 installer refuses unsafe location and query facts before unit writes
|
||||
---
|
||||
duration_ms: 152.033039
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:134:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
expected a refusal, got 0: case2-unit: wrote /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-yqEaQt/units/[email protected] and /mnt/storage/scratch/tmp/gR3d8dO/case2-unit-yqEaQt/units/mosaic-webui.service from copy aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; enabled and started [email protected], then mosaic-webui.service
|
||||
|
||||
|
||||
0 !== 1
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 1
|
||||
actual: 0
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
refuses (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:218:10)
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:157:39)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 255.804009
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 protected homes", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,16 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 protected homes refuse before any filesystem access
|
||||
ok 1 - row253 protected homes refuse before any filesystem access
|
||||
---
|
||||
duration_ms: 12.15372
|
||||
type: 'test'
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 1
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 126.128325
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 protected homes", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
@@ -0,0 +1,34 @@
|
||||
TAP version 13
|
||||
# Subtest: row253 protected homes refuse before any filesystem access
|
||||
not ok 1 - row253 protected homes refuse before any filesystem access
|
||||
---
|
||||
duration_ms: 3.992043
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:179:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
Expected values to be strictly equal:
|
||||
|
||||
99 !== 0
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected: 0
|
||||
actual: 99
|
||||
operator: 'strictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/case2-unit.test.mjs:183:10)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 106.16688
|
||||
@@ -0,0 +1 @@
|
||||
["node", "--test", "--test-reporter=tap", "--test-name-pattern", "row253 installer refuses unsafe location and query facts", "packages/cli/tests/case2-unit.test.mjs"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user