framework: make tool discoverability, workspace placement and model tiering mechanical #1174
@@ -56,6 +56,11 @@ steps:
|
|||||||
# and sandboxes a throwaway git repo, so it resolves no real credentials and
|
# and sandboxes a throwaway git repo, so it resolves no real credentials and
|
||||||
# joins CI directly rather than the exclusions file.
|
# joins CI directly rather than the exclusions file.
|
||||||
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
|
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
|
||||||
|
# Hermetic behavioural regression for the PreToolUse wrapper guard: proves
|
||||||
|
# it still blocks the three mistakes AND still lets reads, unwrapped
|
||||||
|
# endpoints and ordinary commands through. Both directions are asserted —
|
||||||
|
# a guard that over-blocks gets routed around, which fails just as hard.
|
||||||
|
- bash packages/mosaic/framework/tools/git/test-wrapper-guard.sh
|
||||||
|
|
||||||
# Blocking gate (#791): a framework upgrade must never write or delete an
|
# Blocking gate (#791): a framework upgrade must never write or delete an
|
||||||
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test-wrapper-guard.sh — hermetic behavioural regression for wrapper-guard.sh.
|
||||||
|
#
|
||||||
|
# Resolves no credentials, touches no network, and creates no repository: the
|
||||||
|
# guard reads a hook payload on stdin and answers with an exit code, so the whole
|
||||||
|
# contract is testable from fixtures.
|
||||||
|
#
|
||||||
|
# The fixtures are written to a temp file rather than passed inline, and this is
|
||||||
|
# not stylistic. The guard inspects the literal text of the Bash command it is
|
||||||
|
# handed. A test that embeds `git clone ... $HOME` inside its own command line
|
||||||
|
# trips the guard on the harness instead of on the fixture — which is exactly
|
||||||
|
# what happened the first time this was checked by hand. Substring matching over
|
||||||
|
# whole command text is the guard's deliberate fail-closed posture; a test that
|
||||||
|
# does not account for it silently measures the wrong thing.
|
||||||
|
#
|
||||||
|
# Exit: 0 = every fixture behaved as specified · 1 = at least one did not
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
GUARD="${1:-$HERE/wrapper-guard.sh}"
|
||||||
|
[ -x "$GUARD" ] || { printf 'test-wrapper-guard: not executable: %s\n' "$GUARD" >&2; exit 2; }
|
||||||
|
|
||||||
|
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||||
|
FIXTURES="$TMP/fixtures.tsv"
|
||||||
|
|
||||||
|
# Each line: <expected-exit> TAB <hook payload> TAB <what it proves>
|
||||||
|
# 0 = allowed, 2 = blocked.
|
||||||
|
{
|
||||||
|
printf '2\t{"tool_input":{"command":"git clone https://example.invalid/x ~/wt"}}\tcheckout into $HOME is refused\n'
|
||||||
|
printf '2\t{"tool_input":{"command":"git worktree add ~/wt topic"}}\tworktree into $HOME is refused\n'
|
||||||
|
printf '0\t{"tool_input":{"command":"git clone https://example.invalid/x /src/wt"}}\tcheckout onto a work filesystem is fine\n'
|
||||||
|
printf '0\t{"tool_input":{"command":"curl -s -X GET https://git.example.invalid/api/v1/repos/a/b/pulls/1"}}\treads are never blocked\n'
|
||||||
|
printf '2\t{"tool_input":{"command":"curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/pulls/1/reviews"}}\treview write has a wrapper\n'
|
||||||
|
printf '2\t{"tool_input":{"command":"curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/pulls/1/merge"}}\tmerge write has a wrapper\n'
|
||||||
|
printf '2\t{"tool_input":{"command":"curl -X POST -d @b https://api.github.com/repos/a/b/issues"}}\tGitHub host is covered too\n'
|
||||||
|
printf '0\t{"tool_input":{"command":"curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/releases"}}\tan endpoint with no wrapper passes\n'
|
||||||
|
printf '2\t{"tool_input":{"command":"curl -X POST -d {\\"event\\":\\"APPROVE\\"} https://example.invalid/x"}}\tthe APPROVE token is caught anywhere\n'
|
||||||
|
printf '0\t{"tool_input":{"command":"ls -la /src"}}\tordinary commands are untouched\n'
|
||||||
|
printf '0\t{"tool_input":{"command":"MOSAIC_WRAPPER_OVERRIDE=1 curl -X POST -d @b https://git.example.invalid/api/v1/repos/a/b/pulls"}}\tbreak-glass works\n'
|
||||||
|
printf '0\t{"tool_input":{}}\tan empty payload does not block the session\n'
|
||||||
|
} > "$FIXTURES"
|
||||||
|
|
||||||
|
fail=0 n=0
|
||||||
|
while IFS=$'\t' read -r want payload why; do
|
||||||
|
[ -n "${want:-}" ] || continue
|
||||||
|
n=$((n + 1))
|
||||||
|
printf '%s' "$payload" | "$GUARD" >/dev/null 2>&1
|
||||||
|
got=$?
|
||||||
|
if [ "$got" = "$want" ]; then
|
||||||
|
printf 'ok %s\n' "$why"
|
||||||
|
else
|
||||||
|
printf 'FAIL %s (want exit %s, got %s)\n' "$why" "$want" "$got"
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done < "$FIXTURES"
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
if [ "$fail" -eq 0 ]; then
|
||||||
|
printf 'wrapper-guard: %d/%d fixtures behaved as specified.\n' "$n" "$n"
|
||||||
|
else
|
||||||
|
cat <<'EOF'
|
||||||
|
wrapper-guard drifted from its contract.
|
||||||
|
|
||||||
|
A guard that blocks too much gets routed around, and a guard that blocks too
|
||||||
|
little is decoration. Both directions are failures here, which is why the
|
||||||
|
allowed cases are asserted as hard as the blocked ones.
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
exit "$fail"
|
||||||
@@ -39,7 +39,14 @@ CMD="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null |
|
|||||||
case "$CMD" in *MOSAIC_WRAPPER_OVERRIDE=1*) exit 0 ;; esac
|
case "$CMD" in *MOSAIC_WRAPPER_OVERRIDE=1*) exit 0 ;; esac
|
||||||
[ "${MOSAIC_WRAPPER_OVERRIDE:-0}" = "1" ] && exit 0
|
[ "${MOSAIC_WRAPPER_OVERRIDE:-0}" = "1" ] && exit 0
|
||||||
|
|
||||||
W="$HOME/.config/mosaic/tools/git"
|
# The wrappers this guard points at are its own siblings. Resolving relative to
|
||||||
|
# this file — rather than to a hardcoded $HOME/.config/mosaic — means the guard
|
||||||
|
# names the wrappers from the same install it was launched from, and that it
|
||||||
|
# still works from a repo checkout with no installed mosaic home (which is how it
|
||||||
|
# is exercised in CI). $HOME remains the fallback for a guard invoked by an
|
||||||
|
# absolute path from somewhere unusual.
|
||||||
|
W="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
[ -x "$W/pr-review.sh" ] || W="$HOME/.config/mosaic/tools/git"
|
||||||
|
|
||||||
# ---- 1. checkout into $HOME ------------------------------------------------
|
# ---- 1. checkout into $HOME ------------------------------------------------
|
||||||
if printf '%s' "$CMD" | grep -Eq 'git[^|;&]*(clone|worktree[[:space:]]+add)'; then
|
if printf '%s' "$CMD" | grep -Eq 'git[^|;&]*(clone|worktree[[:space:]]+add)'; then
|
||||||
|
|||||||
Reference in New Issue
Block a user