fix(TASK-046): keep inspection leniency off mutating and writing flags
The look-don't-touch bypass was keyed on load_with_interpolation's pre-existing bool, which mutating early paths (--authenticate, --auth-mcp, mcp manage, vault flags) already passed as true meaning only 'skip vault interpolation' — so they silently lost their first-run config creation. Split the parameter: skip_interpolation keeps the historical meaning, a distinct inspection flag alone gates the missing-config default/wizard bypass. inspection_only is now computed from an explicit read-only whitelist: --sync-models is excluded (it downloads and writes models-override.yaml) and returns to the bootstrap/strict path, keeping only its historical interpolation-skip and vault leniency. mcp/vault inspection sub-conditions are hoisted and shared so mutating flag combos never inherit the bypass. Coverage: sandbox vault contract tests, a sync-models bootstrap-path pin, a --sync-models scenario in usage-probe-inspection.sh, and a PTY wizard regression probe (scripts/usage-probe-wizard.sh).
This commit is contained in:
6 files changed
+269
-32
No files matched your search
@@ -82,6 +82,29 @@ probe_flag allow-empty --list-bundles
|
||||
probe_flag allow-empty --list-secrets
|
||||
probe_flag allow-empty --mcp-list
|
||||
|
||||
# --sync-models writes models-override.yaml, so it must NOT behave like an
|
||||
# inspection flag: on a fresh empty config dir it takes the bootstrap path
|
||||
# (builtins written) and, off a terminal, fails on the missing config.
|
||||
# IS_SANDBOX is stripped: the host path is the one under test, and a leaked
|
||||
# sandbox flag would reroute the missing config to the wizard prompt.
|
||||
echo "== --sync-models on a pristine COYOTE_CONFIG_DIR: bootstrap path, strict failure =="
|
||||
sync_cfg="$(mktemp -d "$WORKDIR/cfg.XXXXXX")"
|
||||
sync_out="$WORKDIR/sync-out.txt"
|
||||
sync_rc=0
|
||||
env -u IS_SANDBOX -u COYOTE_PROVIDER -u COYOTE_PLATFORM COYOTE_CONFIG_DIR="$sync_cfg" \
|
||||
timeout 30 "$BIN" --sync-models </dev/null >"$sync_out" 2>&1 || sync_rc=$?
|
||||
if [ "$sync_rc" = "124" ]; then
|
||||
fail "--sync-models: hung (likely waiting on a prompt) on an empty config dir"
|
||||
else
|
||||
if [ "$sync_rc" = "0" ]; then
|
||||
fail "--sync-models: expected the strict missing-config failure on an empty config dir, got rc=0"
|
||||
fi
|
||||
sync_nwritten="$(find "$sync_cfg" -mindepth 1 | wc -l | tr -d ' ')"
|
||||
if [ "$sync_nwritten" = "0" ]; then
|
||||
fail "--sync-models: expected the builtins bootstrap to populate the empty config dir, found nothing"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$failures" != "0" ]; then
|
||||
echo
|
||||
echo "$failures inspection-flag scenario(s) FAILED" >&2
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Black-box usage-pattern probe for the first-run wizard on a real PTY.
|
||||
#
|
||||
# Contract under test (companion to scripts/usage-probe-inspection.sh, which
|
||||
# intentionally cannot assert this because it needs a pty):
|
||||
# Plain `coyote` (no flags, no message) on a TTY with an EMPTY
|
||||
# COYOTE_CONFIG_DIR must still reach the first-run wizard — the
|
||||
# look-don't-touch bypass for inspection flags must NOT swallow the
|
||||
# interactive setup path. We assert the wizard's first prompt
|
||||
# ("No config file, create a new one?") appears within the timeout; the
|
||||
# run is then killed, so the exit code is not asserted.
|
||||
#
|
||||
# Usage: scripts/usage-probe-wizard.sh
|
||||
# Requires: a `coyote` binary on PATH or at ./target/debug/coyote or
|
||||
# ./target/release/coyote, and the `script` utility (util-linux or BSD).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BIN="${COYOTE_BIN:-}"
|
||||
if [ -z "$BIN" ]; then
|
||||
if [ -x "$REPO_ROOT/target/debug/coyote" ]; then
|
||||
BIN="$REPO_ROOT/target/debug/coyote"
|
||||
elif [ -x "$REPO_ROOT/target/release/coyote" ]; then
|
||||
BIN="$REPO_ROOT/target/release/coyote"
|
||||
else
|
||||
echo "No coyote binary found; run 'cargo build --bin coyote' first." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v script > /dev/null 2>&1; then
|
||||
echo "SKIP: 'script' utility not available; cannot allocate a pty" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
WORKDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
CFG="$WORKDIR/cfg"
|
||||
mkdir -p "$CFG"
|
||||
OUT="$WORKDIR/out.txt"
|
||||
|
||||
echo "== Plain \`coyote\` on a pty with an empty COYOTE_CONFIG_DIR reaches the wizard =="
|
||||
|
||||
# `script` allocates a real pty for the child, so IS_STDOUT_TERMINAL is true
|
||||
# inside even in headless CI. IS_SANDBOX is stripped to exercise the plain
|
||||
# host path from the recipe. The wizard blocks on its prompt forever, so a
|
||||
# timeout kill (rc 124) is the expected way out.
|
||||
rc=0
|
||||
if script --version 2>/dev/null | grep -q util-linux; then
|
||||
env -u IS_SANDBOX COYOTE_CONFIG_DIR="$CFG" \
|
||||
timeout 15 script -qec "$BIN" /dev/null > "$OUT" 2>&1 < /dev/null || rc=$?
|
||||
else
|
||||
# BSD/macOS script: command goes after the typescript file.
|
||||
env -u IS_SANDBOX COYOTE_CONFIG_DIR="$CFG" \
|
||||
timeout 15 script -q "$OUT" "$BIN" < /dev/null > /dev/null 2>&1 || rc=$?
|
||||
fi
|
||||
|
||||
if ! grep -aq "No config file, create a new one?" "$OUT"; then
|
||||
echo "FAIL: wizard prompt never appeared (rc=$rc); output was:" >&2
|
||||
head -c 500 "$OUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: first-run wizard prompt appeared on a pty with an empty config dir"
|
||||
echo
|
||||
echo "ALL SCENARIOS PASSED"
|
||||
Reference in new issue
Block a user