#!/usr/bin/env bash
# quote-engine-gate — PreToolUse gate on Bash.
#
# The account's quoting method is applied by two persisted scripts,
# quoting/engine.mjs (pricing) and quoting/render.mjs (documents). They must run
# ONLY through their MCP tools — quote-engine-run and quote-render — which apply
# the reconcile / margin-leak / link-liveness gates and write the figures and
# render receipts. Running either script directly from a Bash command bypasses
# all of that (the SiteDesk 2026-07-25 bypass: a repricing dispatched to
# content-producer that ran `node quoting/engine.mjs` and re-rendered by hand).
# This gate refuses that Bash call and points the agent at the tools. It fires
# only on an execution of the persisted script (a JS runtime invoking it, or the
# script run at command position), never on a read like `cat quoting/engine.mjs`.
# A mode=verify reproduction must also run through the quote-engine-run tool, so
# a hand-run `node quoting/engine.mjs mode=verify` is gated too.
#
# Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on an
# uninspectable call (Bash is a general-purpose tool and the agent never controls
# this hook's stdin, so failing open opens no bypass while never bricking
# unrelated commands). No task numbers or internal refs in operator-visible text.

set -uo pipefail

# No envelope (tty or empty stdin) → allow: cannot inspect, must not brick Bash.
if [ -t 0 ]; then exit 0; fi
INPUT=$(cat)
[ -z "$INPUT" ] && exit 0

# Print "block" when the Bash command runs a persisted quoting script directly
# (and is not a mode=verify run), or nothing for any other call.
DECISION=$(INPUT="$INPUT" python3 - <<'PY' 2>/dev/null || true
import os, json, re, sys
try:
    d = json.loads(os.environ["INPUT"])
except Exception:
    sys.exit(0)
if (d.get("tool_name", "") or "") != "Bash":
    sys.exit(0)
cmd = (d.get("tool_input", {}) or {}).get("command", "") or ""
if not cmd:
    sys.exit(0)
script = r'quoting/(engine|render)\.mjs\b'
# Block only an EXECUTION of the persisted script, not a read/inspect of it:
#  - a JS runtime invoking it: `node|nodejs|bun|deno|ts-node ... quoting/engine.mjs`
#  - the script run directly at command position: `./quoting/engine.mjs`, or the
#    path as a bare command after a separator.
# A read (`cat|grep|git diff|head|wc quoting/engine.mjs`) leaves the path as an
# argument, not at command position, so it is not matched and passes.
# Verify runs are NOT carved out: `mode=verify` must also go through the
# quote-engine-run tool, never a hand-run of the persisted script.
executed = (
    re.search(r'\b(?:node|nodejs|bun|deno|ts-node)\b[^;&|\n]*' + script, cmd)
    or re.search(r'(?:^|[;&|]\s*)\.?/?' + script, cmd)
)
# Known fail-open gap (accepted, consistent with this gate's soft posture): a
# cd-relative run like `cd quoting && node engine.mjs` drops the `quoting/`
# prefix and is not matched.
if not executed:
    sys.exit(0)
print("block")
PY
)

[ "$DECISION" = "block" ] || exit 0

echo "[quote-engine] op=bypass reason=direct-script-exec" >&2
echo "Blocked: the quoting engine and render scripts must run through their tools, not directly. Use the quote-engine-run tool to price a job (it files the figures) and the quote-render tool to produce the documents (it files them behind the reconcile, margin-leak and link-liveness gates). Running quoting/engine.mjs or quoting/render.mjs by hand skips those gates and files no receipt." >&2
exit 2
