The Console is an OIDC relying party on oauth4webapi 3.8.8 (pinned, root lock). console.sso in config.json, client secrets from slot files, session cookie, public Host only on the human listener, tester listener refuses every sign-in header. Rulings sage-1340 F1/F2/F4 and sage-1347 F7/F8. Tests use a fake OP on loopback only. Packet: agents/launchpad/work/queue-192/BUILD.md.
213 lines
11 KiB
Bash
Executable File
213 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Fast, sandboxed selftests for the configuration layer.
|
|
#
|
|
# No Docker, no network, no credentials: every case runs against a
|
|
# temporary config via MOSAIC_CONFIG. Suitable for frequent local runs.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
SANDBOX="$(mktemp -d)"
|
|
trap 'rm -rf "$SANDBOX"' EXIT
|
|
|
|
PASS=0
|
|
# Status colors: terminal-only, NO_COLOR-respecting; plain when piped.
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m'
|
|
else
|
|
C_OK=""; C_FAIL=""; C_RESET=""
|
|
fi
|
|
FAIL=0
|
|
|
|
check() {
|
|
if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi
|
|
}
|
|
|
|
# expect_exit NAME EXPECTED_RC -- command...
|
|
expect_exit() {
|
|
local name="$1" expected="$2"
|
|
shift 3 # name, expected, "--"
|
|
local rc
|
|
"$@" >/dev/null 2>&1
|
|
rc=$?
|
|
if [ "$rc" -eq "$expected" ]; then
|
|
PASS=$((PASS + 1))
|
|
echo "${C_OK}OK${C_RESET} $name (exit $rc)"
|
|
else
|
|
FAIL=$((FAIL + 1))
|
|
echo "${C_FAIL}FAIL${C_RESET} $name (exit $rc, expected $expected)"
|
|
fi
|
|
}
|
|
|
|
CONFIG_OP="node scripts/mosaic-config.mjs"
|
|
|
|
valid_body() {
|
|
cat <<EOF
|
|
{"configVersion":1,"environment":"development","dataRoot":"$1","execution":{"backend":"docker","provider":"zai","model":"glm-5.3-flash"}}
|
|
EOF
|
|
}
|
|
|
|
cfg() { printf '%s' "$2" > "$SANDBOX/$1"; }
|
|
|
|
DATA_ROOT="$SANDBOX/data"
|
|
|
|
# --- adapter selection (M4) ---
|
|
cfg default-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
MOSAIC_CONFIG="$SANDBOX/default-adapter.json" $CONFIG_OP validate | grep -q '"adapter": "pi"'
|
|
check "absent adapter defaults to pi" $?
|
|
|
|
cfg mock-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"mock"}}'
|
|
expect_exit "adapter mock validates" 0 -- env MOSAIC_CONFIG="$SANDBOX/mock-adapter.json" $CONFIG_OP validate
|
|
|
|
cfg bad-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"claude"}}'
|
|
expect_exit "unsupported adapter exits 2" 2 -- env MOSAIC_CONFIG="$SANDBOX/bad-adapter.json" $CONFIG_OP validate
|
|
|
|
MOSAIC_CONFIG="$SANDBOX/mock-adapter.json" $CONFIG_OP env | grep -q "MOSAIC_ADAPTER='mock'"
|
|
check "env exports adapter" $?
|
|
|
|
# --- bootstrap ---
|
|
rm -f "$SANDBOX/config.json"
|
|
expect_exit "bootstrap creates default when absent" 0 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/config.json" $CONFIG_OP bootstrap
|
|
[ -f "$SANDBOX/config.json" ] && { PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} bootstrap wrote config file"; } \
|
|
|| { FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} bootstrap wrote config file"; }
|
|
|
|
SUM_BEFORE=$(sha256sum "$SANDBOX/config.json" | cut -d' ' -f1)
|
|
MTIME_BEFORE=$(stat -c %Y "$SANDBOX/config.json")
|
|
sleep 1.1
|
|
expect_exit "bootstrap is idempotent on existing config" 0 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/config.json" $CONFIG_OP bootstrap
|
|
SUM_AFTER=$(sha256sum "$SANDBOX/config.json" | cut -d' ' -f1)
|
|
MTIME_AFTER=$(stat -c %Y "$SANDBOX/config.json")
|
|
if [ "$SUM_BEFORE" = "$SUM_AFTER" ] && [ "$MTIME_BEFORE" = "$MTIME_AFTER" ]; then
|
|
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} bootstrap did not rewrite existing config"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} bootstrap rewrote existing config"
|
|
fi
|
|
|
|
# --- validate ---
|
|
expect_exit "validate missing config exits 3" 3 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/absent.json" $CONFIG_OP validate
|
|
|
|
cfg invalid.json '{'
|
|
expect_exit "malformed JSON exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/invalid.json" $CONFIG_OP validate
|
|
|
|
cfg badversion.json '{"configVersion":2,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported configVersion exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badversion.json" $CONFIG_OP validate
|
|
|
|
cfg unknownkey.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","extra":true,"execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unknown top-level key exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/unknownkey.json" $CONFIG_OP validate
|
|
|
|
cfg unknownexec.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","extra":1}}'
|
|
expect_exit "unknown execution key exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/unknownexec.json" $CONFIG_OP validate
|
|
|
|
cfg badbackend.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"podman","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported backend exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badbackend.json" $CONFIG_OP validate
|
|
|
|
cfg badenv.json '{"configVersion":1,"environment":"staging","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "unsupported environment exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/badenv.json" $CONFIG_OP validate
|
|
|
|
cfg relative.json '{"configVersion":1,"environment":"development","dataRoot":"relative/path","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "relative dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/relative.json" $CONFIG_OP validate
|
|
|
|
cfg traversal.json '{"configVersion":1,"environment":"development","dataRoot":"/tmp/../home/x","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "non-canonical dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/traversal.json" $CONFIG_OP validate
|
|
|
|
cfg root.json '{"configVersion":1,"environment":"development","dataRoot":"/","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "filesystem root dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/root.json" $CONFIG_OP validate
|
|
|
|
cfg home.json '{"configVersion":1,"environment":"development","dataRoot":"'$HOME'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "home directory dataRoot exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/home.json" $CONFIG_OP validate
|
|
|
|
cfg cfgdir.json '{"configVersion":1,"environment":"development","dataRoot":"'$SANDBOX'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
expect_exit "dataRoot containing config dir exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/cfgdir.json" $CONFIG_OP validate
|
|
|
|
cfg ctrlchar.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"z\nai","model":"m"}}'
|
|
expect_exit "control character in provider exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/ctrlchar.json" $CONFIG_OP validate
|
|
|
|
cfg symlink.json 'placeholder'
|
|
ln -sf "$SANDBOX/invalid.json" "$SANDBOX/symlink.json"
|
|
expect_exit "symlinked config file exits 2" 2 -- \
|
|
env MOSAIC_CONFIG="$SANDBOX/symlink.json" $CONFIG_OP validate
|
|
|
|
# --- env resolution ---
|
|
cfg valid.json "$(valid_body "$DATA_ROOT")"
|
|
EVAL_OUT="$(MOSAIC_CONFIG="$SANDBOX/valid.json" $CONFIG_OP env)" || true
|
|
if eval "$EVAL_OUT" 2>/dev/null && [ "$MOSAIC_DATA_ROOT" = "$DATA_ROOT" ] \
|
|
&& [ "$MOSAIC_PROVIDER" = "zai" ] && [ "$MOSAIC_MODEL" = "glm-5.3-flash" ]; then
|
|
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} env exports resolve correctly"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} env exports resolve correctly"
|
|
fi
|
|
|
|
# --- Q175 phase 1 (#1690), design test 25: the optional console key ---
|
|
# Shape only. The slot path is a FIFO, so a loader that opened the slot would block and the
|
|
# timeout would fail the case; no case reads a slot file.
|
|
mkdir -p "$SANDBOX/secrets/mosaic-stack"
|
|
mkfifo "$SANDBOX/secrets/mosaic-stack/console-sso-test"
|
|
sso_body() {
|
|
printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m"},"console":%s}' "$DATA_ROOT" "$1"
|
|
}
|
|
# provider [ALGS] [CLIENT_SECRET]: one provider entry; an empty argument takes the valid value.
|
|
provider() {
|
|
local algs='["RS256"]' secret='{"slot":"console-sso-test"}'
|
|
[ -n "${1:-}" ] && algs="$1"
|
|
[ -n "${2:-}" ] && secret="$2"
|
|
printf '{"id":"fake","issuer":"https://op.test/app/","clientId":"client-1","clientSecret":%s,"algs":%s,"subjects":[{"sub":"user-1","name":"jason.woltje"}]}' "$secret" "$algs"
|
|
}
|
|
cfg sso-valid.json "$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider)"']}}')"
|
|
OUT="$(MOSAIC_CONFIG="$SANDBOX/sso-valid.json" timeout 20 $CONFIG_OP validate 2>&1)"
|
|
[ $? -eq 0 ] && printf '%s' "$OUT" | grep -q '"publicOrigin": "https://console.test"'
|
|
check "valid console.sso loads and is in the output" $?
|
|
|
|
# expect_refusal NAME PATH-IN-MESSAGE BODY: exit 2, and the message names the bad key path.
|
|
expect_refusal() {
|
|
cfg sso-bad.json "$2"
|
|
local err rc
|
|
err="$(MOSAIC_CONFIG="$SANDBOX/sso-bad.json" timeout 20 $CONFIG_OP validate 2>&1 >/dev/null)"
|
|
rc=$?
|
|
[ "$rc" -eq 2 ] && printf '%s' "$err" | grep -qF -- "$3"
|
|
check "$1 (exit $rc)" $?
|
|
}
|
|
expect_refusal "console.sso HS256 alg exits 2 naming the path" \
|
|
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider '["HS256"]')"']}}')" 'console.sso.providers[0].algs[0] must be one of'
|
|
expect_refusal "console.sso alg none exits 2 naming the path" \
|
|
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider '["RS256","none"]')"']}}')" 'console.sso.providers[0].algs[1] must be one of'
|
|
expect_refusal "console.sso missing slot field exits 2 naming the path" \
|
|
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider '' '{}')"']}}')" 'console.sso.providers[0].clientSecret.slot is required'
|
|
expect_refusal "console.sso slot name outside the pattern exits 2 naming the path" \
|
|
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider '' '{"slot":"../x"}')"']}}')" 'console.sso.providers[0].clientSecret.slot must match'
|
|
expect_refusal "unknown key under console exits 2 naming the path" \
|
|
"$(sso_body '{"sso":{"publicOrigin":"https://console.test","providers":['"$(provider)"']},"extra":1}')" 'console.extra is not a supported key'
|
|
|
|
# Without the key: the output is exactly what the loader printed before phase 1 (sage-1340, F2).
|
|
cfg no-console.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}'
|
|
EXPECTED="$(printf '{\n "configVersion": 1,\n "environment": "development",\n "dataRoot": "%s",\n "execution": {\n "backend": "docker",\n "provider": "zai",\n "model": "m",\n "adapter": "pi"\n }\n}' "$DATA_ROOT")"
|
|
[ "$(MOSAIC_CONFIG="$SANDBOX/no-console.json" timeout 20 $CONFIG_OP validate)" = "$EXPECTED" ]
|
|
check "config without console loads with unchanged output" $?
|
|
|
|
# --- validation must not modify the file ---
|
|
SUM_INVALID_BEFORE=$(sha256sum "$SANDBOX/invalid.json" | cut -d' ' -f1)
|
|
MOSAIC_CONFIG="$SANDBOX/invalid.json" $CONFIG_OP validate >/dev/null 2>&1
|
|
SUM_INVALID_AFTER=$(sha256sum "$SANDBOX/invalid.json" | cut -d' ' -f1)
|
|
if [ "$SUM_INVALID_BEFORE" = "$SUM_INVALID_AFTER" ]; then
|
|
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} failed validation modified nothing"
|
|
else
|
|
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} failed validation modified the file"
|
|
fi
|
|
|
|
echo
|
|
echo "selftest: $PASS passed, $FAIL failed"
|
|
[ "$FAIL" -eq 0 ]
|