#!/usr/bin/env bash

# How much of each agent subscription is gone, in the bar.
#
# The Claude collector is the only thing in Panama that reads an authentication
# token, so most of what is pinned here is about that rather than about the
# number:
#
#   1. The token never reaches argv. A credential passed as an argument is
#      world-readable in /proc/<pid>/cmdline for as long as the request takes,
#      which is the same rule the password and MOK paths already follow. The
#      collector makes its request in-process, so there is no child to leak it
#      through -- and this proves no child is spawned.
#   2. The token never reaches the output. Neither the panel nor anyone reading
#      the state directory has any business seeing a credential, and neither
#      does the collector's own cache.
#   3. It NEVER refreshes the token and never writes to the credentials file.
#      That token expires hourly and Claude Code refreshes it on demand; two
#      processes rotating one credential means being silently signed out of
#      Claude Code by a status widget, and no bar indicator is worth that.
#   4. An expired token is reported honestly and no request is made with it.
#   5. The widget hides unless it was asked for AND there are real numbers. An
#      indicator reading "unknown" is worse than an empty space.
#
# And, since the collectors became a fan-out over a directory of records:
#
#   6. A collector the user switched off does not run, and its stale record
#      does not linger.
#   7. A record is replaced only by a whole, parseable one.
#   8. The replacement is atomic -- mktemp and mv, never a redirect into the
#      file a reader is watching.
#
# Hermetic throughout. The usage endpoint is a local HTTP fixture, the codex
# app-server is a stub, and the fan-out runs stub collectors: nothing here
# reaches Anthropic or starts a real agent.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
scripts="$repo_dir/config/dot/quickshell/scripts"
claude="$scripts/panama-agent-usage-claude"
codex="$scripts/panama-agent-usage-codex"
updater="$scripts/panama-agent-usage-update"
service="$repo_dir/config/dot/quickshell/services/AgentUsage.qml"
widget="$repo_dir/config/dot/quickshell/modules/bar/AgentUsageWidget.qml"
panel="$repo_dir/config/dot/quickshell/modules/bar/AgentUsagePanel.qml"
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
aliases="$repo_dir/config/dot/quickshell/config/Settings.qml"

findings=()
note() { findings+=("$1"); }

for required in "$claude" "$codex" "$updater"; do
    [[ -x "$required" ]] || {
        printf 'agent usage contract: %s is not executable\n' "$required" >&2
        exit 1
    }
done
[[ -f "$panel" ]] || { printf 'agent usage contract: missing %s\n' "$panel" >&2; exit 1; }

work="$(mktemp -d)"
server_pid=""
cleanup() {
    [[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null
    rm -rf "$work"
}
trap cleanup EXIT

stub="$work/bin"
mkdir -p "$stub" "$work/claude" "$work/cache" "$work/collectors" "$work/usage"

readonly SECRET='sk-fixture-token-must-never-appear'

# ── The endpoint, served locally ────────────────────────────────────────────
#
# A real request to a real socket, so what the collector puts in the header is
# observable -- and so nothing in this file can accidentally reach Anthropic.

cat >"$work/server.py" <<'PY'
import http.server
import os
import sys

work = sys.argv[1]


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        with open(os.path.join(work, "headers.txt"), "a") as handle:
            handle.write((self.headers.get("Authorization") or "") + "\n")
        try:
            status = int(open(os.path.join(work, "status")).read().strip())
        except Exception:
            status = 200
        body = open(os.path.join(work, "payload.json"), "rb").read()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        if status == 429:
            self.send_header("Retry-After", "30")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *args):
        pass


server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
with open(os.path.join(work, "port"), "w") as handle:
    handle.write(str(server.server_address[1]))
server.serve_forever()
PY

printf '200\n' >"$work/status"
: >"$work/headers.txt"
cat >"$work/payload.json" <<'JSON'
{
  "five_hour": {"utilization": 42, "resets_at": "2099-08-22T14:00:00Z"},
  "seven_day": {"utilization": 71, "resets_at": "2099-08-27T00:00:00Z"},
  "rate_limit_tier": "default_claude_max_5x",
  "limits": [
    {"kind": "weekly_scoped", "percent": 31, "resets_at": "2099-08-27T00:00:00Z",
     "scope": {"model": {"display_name": "Fable 5", "id": "claude-fable-5"}}}
  ]
}
JSON

python3 "$work/server.py" "$work" &
server_pid=$!
for _ in $(seq 1 50); do
    [[ -s "$work/port" ]] && break
    sleep 0.1
done
[[ -s "$work/port" ]] || { printf 'agent usage contract: the endpoint fixture did not start\n' >&2; exit 1; }
endpoint="http://127.0.0.1:$(cat "$work/port")/usage"

# A recording curl. Nothing should ever invoke it: the collector makes its
# request in-process. If it is called, the argv it was called with is evidence.
cat >"$stub/curl" <<STUB
#!/usr/bin/env bash
printf '%s\n' "\$*" >>"$work/curl-argv"
exit 1
STUB
chmod +x "$stub/curl"
: >"$work/curl-argv"

credentials() {
    local expires="$1"
    cat >"$work/credentials.json" <<CREDS
{"claudeAiOauth":{"accessToken":"$SECRET","refreshToken":"refresh-fixture",
"expiresAt":$expires,"rateLimitTier":"default_claude_max_5x","subscriptionType":"max"}}
CREDS
}

# The collector prints its record; the fan-out is what writes files. Both are
# exercised, so both are seamed.
run_claude() {
    PATH="$stub:$PATH" \
    CLAUDE_CONFIG_DIR="$work/claude" \
    PANAMA_AGENT_CREDENTIALS="$work/credentials.json" \
    PANAMA_AGENT_USAGE_ENDPOINT="${FIXTURE_ENDPOINT:-$endpoint}" \
    PANAMA_AGENT_USAGE_CACHE="$work/cache" \
        "$claude" "$@" 2>"$work/claude-stderr"
}

future_ms=$(( ($(date +%s) + 3600) * 1000 ))

# ── 4. An expired token is honest, and makes no request ─────────────────────

credentials 1000
record="$(run_claude --force)"
[[ "$(jq -r .usageStatusText <<<"$record")" == "Sign-in expired" ]] \
    || note "an expired token did not report as expired (got: $(jq -r .usageStatusText <<<"$record"))"
[[ -s "$work/headers.txt" ]] \
    && note 'a request was made with an expired token'

# A credential file that is not there at all is a different sentence.
mv "$work/credentials.json" "$work/credentials.away"
record="$(run_claude --force)"
[[ "$(jq -r .usageStatusText <<<"$record")" == "Waiting for auth" ]] \
    || note "a missing sign-in did not report as waiting (got: $(jq -r .usageStatusText <<<"$record"))"
[[ -s "$work/headers.txt" ]] \
    && note 'a request was made with no token at all'
mv "$work/credentials.away" "$work/credentials.json"

# ── 1, 2 & 3. What happens to the token ─────────────────────────────────────

credentials "$future_ms"
before="$(sha256sum "$work/credentials.json" | cut -d' ' -f1)"
record="$(run_claude --force)"

[[ "$(jq -r .ready <<<"$record")" == "true" ]] \
    || note "a valid token did not produce a reading (status: $(jq -r .usageStatusText <<<"$record"))"

grep -qF "Bearer $SECRET" "$work/headers.txt" \
    || note 'the token did not reach the Authorization header, so the request was not authenticated'

[[ -s "$work/curl-argv" ]] \
    && note 'the collector shelled out to curl, where /proc makes an argument world-readable'

grep -qF "$SECRET" <<<"$record" \
    && note 'the token appears in the record the panel reads'
grep -qrF "$SECRET" "$work/cache" \
    && note "the token appears in the collector's own cache"
grep -qF "$SECRET" "$work/claude-stderr" \
    && note 'the token appears on stderr, where the journal keeps it'

[[ "$(sha256sum "$work/credentials.json" | cut -d' ' -f1)" == "$before" ]] \
    || note 'the collector modified the credentials file'

# Comments stripped: the header explains at length that it does not refresh,
# and matching that is matching documentation.
uncommented() { python3 - "$1" <<'PY'
import re, sys
text = open(sys.argv[1], encoding="utf-8").read()
# Module docstring first, then whole-line comments.
text = re.sub(r'^\s*(?:"""|\'\'\')(?:.|\n)*?(?:"""|\'\'\')', "", text, count=1)
print("\n".join(line for line in text.split("\n") if not line.lstrip().startswith("#")))
PY
}

uncommented "$claude" | grep -qE 'refreshToken|refresh_token|grant_type' \
    && note 'the collector touches the refresh token, which can sign Claude Code out'
uncommented "$claude" | grep -qE 'credentials.*\.write_text|open\([^)]*credentials[^)]*"w"' \
    && note 'the collector writes to the credentials file'

# ── The reading it produced ─────────────────────────────────────────────────
#
# The endpoint reports percentages already. Treating them as 0..1 fractions and
# multiplying is how the bar once came to read 1500%.

# Whole percents: jq prints 1 and 1.0 for the same number, and a contract that
# fails on the spelling of a float teaches nothing.
percent_for() { jq -r --arg label "$1" '.limits[] | select(.label == $label) | .percent * 100 | round' <<<"$record"; }

[[ "$(percent_for 'Session (5-hour)')" == "42" ]] \
    || note "the five-hour reading is $(percent_for 'Session (5-hour)')% for a reported 42%"
[[ "$(percent_for 'Weekly (7-day)')" == "71" ]] \
    || note "the weekly reading is $(percent_for 'Weekly (7-day)')% for a reported 71%"

# The model-scoped window lives only in the `limits` array. A collector reading
# the flat buckets alone drops a limit the account is really spending against.
[[ "$(percent_for 'Fable 5 Weekly')" == "31" ]] \
    || note 'the model-scoped limit from the limits array was not read'

[[ "$(jq -r .tierLabel <<<"$record")" == "Max 5x" ]] \
    || note "the plan label is $(jq -r .tierLabel <<<"$record"), not the Max 5x the credential names"

# Nothing the panel shows may fall outside the range a percentage has, whatever
# the endpoint says.
cat >"$work/payload.json" <<'JSON'
{"five_hour": {"utilization": 1500}, "seven_day": {"utilization": -4}}
JSON
rm -f "$work/cache/claude-limits.json"
record="$(run_claude --force)"
[[ "$(percent_for 'Session (5-hour)')" == "100" ]] \
    || note "an out-of-range reading was not clamped (got: $(percent_for 'Session (5-hour)'))"
[[ -z "$(percent_for 'Weekly (7-day)')" ]] \
    || note 'a negative reading was reported as a percentage rather than dropped'

# A payload that speaks the older fraction convention is read as fractions.
cat >"$work/payload.json" <<'JSON'
{"five_hour": {"utilization": 0.42}, "seven_day": {"utilization": 0.71}}
JSON
rm -f "$work/cache/claude-limits.json"
record="$(run_claude --force)"
[[ "$(percent_for 'Session (5-hour)')" == "42" ]] \
    || note "a fraction-scaled payload was misread as $(percent_for 'Session (5-hour)')%"

# ── A cached limit outlives its probe, but not its window ───────────────────
#
# Once a window has reset, a cached figure describes a period that is over. A
# stale 78% on a fresh week is a lie the panel must not tell.

dead_endpoint="http://127.0.0.1:1/usage"

cat >"$work/cache/claude-limits.json" <<'JSON'
{"fetchedAtMs":0,"limits":[{"label":"Weekly (7-day)","percent":0.78,"resetsAt":"2099-01-01T00:00:00+00:00"}]}
JSON
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ "$(percent_for 'Weekly (7-day)')" == "78" ]] \
    || note 'a cached limit whose window is still open was thrown away'

cat >"$work/cache/claude-limits.json" <<'JSON'
{"fetchedAtMs":0,"limits":[{"label":"Weekly (7-day)","percent":0.78,"resetsAt":"2000-01-01T00:00:00+00:00"}]}
JSON
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ -z "$(percent_for 'Weekly (7-day)')" ]] \
    && [[ "$(jq -r .usageStatusText <<<"$record")" == "Claude limits unavailable" ]] \
    || note 'a cached limit was reported after its window had already reset'

# ── A transport failure is not an answer ────────────────────────────────────
#
# Nothing reached a server: retry sooner than the interval. An HTTP status IS a
# server, and pestering it is how a rate limit becomes a ban.

rm -f "$work/cache/claude-limits.json"
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ "$(jq -r '.retryAdvised // false' <<<"$record")" == "true" ]] \
    || note 'a transport failure did not ask the shell to retry sooner'

printf '500\n' >"$work/status"
record="$(run_claude --force)"
[[ "$(jq -r '.retryAdvised // false' <<<"$record")" == "false" ]] \
    || note 'an HTTP error asked for a fast retry, which turns a bad status into a hammer'

printf '429\n' >"$work/status"
record="$(run_claude --force)"
grep -q 'retry after 30s' <<<"$(jq -r .authHelpText <<<"$record")" \
    || note "a 429 did not carry its retry-after into the help text (got: $(jq -r .authHelpText <<<"$record"))"
printf '200\n' >"$work/status"

# ── The Codex collector asks read-only, and reads the right field ───────────

cat >"$stub/codex" <<'STUB'
#!/usr/bin/env python3
import json
import os
import sys

with open(os.environ["CODEX_STUB_ARGV"], "w") as handle:
    handle.write(" ".join(sys.argv[1:]))

RESULTS = {
    "initialize": {"userAgent": "stub"},
    "account/read": {"account": {"type": "chatgpt", "planType": "prolite"}},
    "account/rateLimits/read": {
        "rateLimits": {
            "planType": "prolite",
            "primary": {"usedPercent": 55, "windowDurationMins": 10080, "resetsAt": 4102444800},
            "secondary": None,
        },
        "rateLimitsByLimitId": {
            "codex": {"limitName": None, "primary": {"usedPercent": 55, "windowDurationMins": 10080}},
            "codex_spark": {
                "limitName": "Spark",
                "primary": {"usedPercent": 12, "windowDurationMins": 300, "resetsAt": 4102444800},
                "secondary": None,
            },
        },
    },
}

for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        message = json.loads(line)
    except Exception:
        continue
    if "id" not in message:
        continue
    print(json.dumps({"id": message["id"], "result": RESULTS.get(message.get("method"), {})}), flush=True)
STUB
chmod +x "$stub/codex"

session_day="$(date +%Y/%m/%d)"
session_date="$(date +%Y-%m-%d)"
session_offset="$(date +%:z)"
mkdir -p "$work/codex/sessions/$session_day"
{
    printf '%s\n' '{"type":"turn_context","payload":{"model":"gpt-5.6-sol"}}'
    jq -nc --arg timestamp "${session_date}T10:00:00${session_offset}" \
        '{type:"token_count",timestamp:$timestamp,payload:{type:"token_count",info:{total_token_usage:{input_tokens:999999,output_tokens:999999},last_token_usage:{input_tokens:1200,cached_input_tokens:1000,output_tokens:300}}}}'
    jq -nc --arg timestamp "${session_date}T10:05:00${session_offset}" \
        '{type:"token_count",timestamp:$timestamp,payload:{type:"token_count",info:{total_token_usage:{input_tokens:1999999,output_tokens:1999999},last_token_usage:{input_tokens:1200,cached_input_tokens:1000,output_tokens:300}}}}'
} >"$work/codex/sessions/$session_day/session.jsonl"

codex_record="$(
    PATH="$stub:$PATH" \
    CODEX_HOME="$work/codex" \
    CODEX_STUB_ARGV="$work/codex-argv" \
    PANAMA_AGENT_CODEX_BIN="$stub/codex" \
    PANAMA_AGENT_USAGE_CACHE="$work/cache" \
        "$codex" --force 2>/dev/null
)"

grep -q -- '-s read-only' "$work/codex-argv" \
    || note "the codex app-server was not asked for read-only ($(cat "$work/codex-argv" 2>/dev/null))"
grep -q -- 'app-server' "$work/codex-argv" \
    || note 'the codex collector did not start the app-server'

[[ "$(jq -r '.limits[] | select(.label == "Weekly (7-day)") | .percent * 100 | round' <<<"$codex_record")" == "55" ]] \
    || note 'the codex weekly limit was not read from the app-server'
[[ "$(jq -r '.limits[] | select(.label == "Spark Session (5-hour)") | .percent * 100 | round' <<<"$codex_record")" == "12" ]] \
    || note 'the codex model-scoped limit was not read'
[[ "$(jq -r .tierLabel <<<"$codex_record")" == "prolite" ]] \
    || note 'the codex plan was not read'

# total_token_usage is cumulative for the session. Counting it instead of
# last_token_usage makes usage grow quadratically: these two snapshots are
# 1500 tokens each, not four million.
[[ "$(jq -r .todayTotalTokens <<<"$codex_record")" == "3000" ]] \
    || note "the codex scan counted $(jq -r .todayTotalTokens <<<"$codex_record") tokens where the last-turn rule gives 3000"

# ── 6, 7 & 8. The fan-out ───────────────────────────────────────────────────

cat >"$work/collectors/panama-agent-usage-claude" <<STUB
#!/usr/bin/env bash
printf 'claude\n' >>"$work/ran"
printf '%s\n' '{"schemaVersion":1,"id":"claude","name":"Claude Code","ready":true,"limits":[]}'
STUB
cat >"$work/collectors/panama-agent-usage-codex" <<STUB
#!/usr/bin/env bash
printf 'codex\n' >>"$work/ran"
printf '%s\n' '{"schemaVersion":1,"id":"codex","name":"Codex","ready":true,"limits":[]}'
STUB
chmod +x "$work/collectors"/panama-agent-usage-*

run_update() {
    PANAMA_AGENT_USAGE_COLLECTORS="$work/collectors" \
    PANAMA_AGENT_USAGE_DIR="$work/usage" \
    PANAMA_SETTINGS="$work/settings.json" \
        "$updater" "$@" 2>"$work/update-stderr"
}

# Both on: both records appear.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":true}' >"$work/settings.json"
: >"$work/ran"
run_update
[[ -s "$work/usage/claude.json" && -s "$work/usage/codex.json" ]] \
    || note 'the fan-out did not write a record for every enabled collector'

# One off: it must not run at all. "Off" has to mean "no process" -- the
# collector is the thing that reads a credential and makes a request.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":false}' >"$work/settings.json"
: >"$work/ran"
run_update
grep -qx 'codex' "$work/ran" \
    && note 'a collector the user switched off still ran'
[[ -e "$work/usage/codex.json" ]] \
    && note "a disabled collector's stale record was left behind for the panel to show"

# Absent means default, and the default is on. `.key // true` would read false
# as absent, which is how a switched-off collector comes back to life.
printf '%s\n' '{}' >"$work/settings.json"
: >"$work/ran"
run_update
grep -qx 'codex' "$work/ran" \
    || note 'an unset per-agent preference was not treated as its default of on'

# 7. A record is replaced only by a whole, parseable one.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":true}' >"$work/settings.json"
run_update
good="$(sha256sum "$work/usage/codex.json" | cut -d' ' -f1)"
cat >"$work/collectors/panama-agent-usage-codex" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' 'Traceback (most recent call last):'
exit 1
STUB
chmod +x "$work/collectors/panama-agent-usage-codex"
run_update
[[ "$(sha256sum "$work/usage/codex.json" | cut -d' ' -f1)" == "$good" ]] \
    || note 'a collector that died mid-run replaced a good record with rubbish'
jq -e . >/dev/null 2>&1 <"$work/usage/codex.json" \
    || note 'the usage directory holds something that is not JSON'

# 8. Atomic, and tidy: no temp file survives a failure for the panel to read.
compgen -G "$work/usage/.*.??????" >/dev/null \
    && note 'the fan-out left a temporary record behind in the directory the panel watches'
grep -qE '>[[:space:]]*"\$USAGE_DIR/\$agent\.json"' "$updater" \
    && note 'the fan-out redirects straight into the file a reader is watching'
grep -q 'mktemp' "$updater" && grep -q 'mv "\$tmp"' "$updater" \
    || note 'the fan-out does not write through mktemp and mv, so a reader can catch a half-written record'

# ── 5. The widget hides itself, and the panel is what it opens ──────────────

qml_uncommented() { grep -v '^[[:space:]]*//' "$1"; }

grep -q 'Settings.showAgentUsage && AgentUsage.available' "$widget" \
    || note 'the widget does not gate on both the preference and having real numbers'
qml_uncommented "$widget" | grep -q 'interactive: false' \
    && note "the widget disables Pill's mouse area, so its click handlers never fire"
qml_uncommented "$widget" | grep -q 'onActivated' \
    || note 'left-clicking the widget does nothing'
qml_uncommented "$widget" | grep -q 'onSecondaryActivated: ShellState.openSettings' \
    || note 'right-clicking the widget no longer opens the settings that govern it'
qml_uncommented "$widget" | grep -q 'AgentUsagePanel' \
    || note 'the widget does not open the usage panel'

grep -q 'key: "showAgentUsage"' "$schema" || note 'there is no showAgentUsage preference'
grep -A2 'key: "showAgentUsage"' "$schema" | grep -q 'def: false' \
    || note 'the usage widget is on by default; it is a coding-tool readout, not general-purpose desktop furniture'
for key in showAgentUsage agentUsageClaude agentUsageCodex agentUsageRefreshMinutes; do
    grep -q "\"$key\"" "$aliases" \
        || note "Settings.qml does not alias $key, so the binding reads undefined"
done

# The two-tier colouring is better than one threshold and is not to be lost in
# a refactor: amber before it hurts, red when it is about to.
qml_uncommented "$widget" | grep -q 'AgentUsage.headline >= 90' \
    || note 'the widget lost its red tier at 90%'
qml_uncommented "$widget" | grep -q 'AgentUsage.headline >= 75' \
    || note 'the widget lost its amber tier at 75%'

# The collectors run on a timer measured in minutes, never on a repaint, and
# only while the readout was asked for.
python3 - "$service" <<'PY' || note 'the collectors are not run on a minute-scale timer driven by the preference'
import re
import sys

text = open(sys.argv[1], encoding="utf-8").read()
if not re.search(r"interval:\s*root\.refreshMinutes\s*\*\s*60\s*\*\s*1000", text):
    raise SystemExit(1)
if "Settings.agentUsageRefreshMinutes" not in text:
    raise SystemExit(1)
if not re.search(r"running:\s*Settings\.showAgentUsage", text):
    raise SystemExit(1)
PY

# The panel and the service read records; neither has any business knowing what
# a credential looks like.
for file in "$service" "$panel" "$widget"; do
    grep -qE 'credentials|accessToken|Authorization' "$file" \
        && note "$(basename "$file") mentions a credential; only the collector may"
done

# Nothing in the panel repaints on a clock. The one timer it owns advances a
# displayed time and only runs while the panel is open.
python3 - "$panel" <<'PY' || note 'the panel animates or ticks while it is closed'
import re
import sys

text = open(sys.argv[1], encoding="utf-8").read()
for block in re.findall(r"Timer\s*\{(.*?)\n    \}", text, re.S):
    if "running: root.visible" not in block:
        raise SystemExit(1)
if re.search(r"\b(SequentialAnimation|loops:\s*Animation\.Infinite)\b", text):
    raise SystemExit(1)
PY

# ── The retired collector stays retired ─────────────────────────────────────

[[ -e "$scripts/panama-agent-usage" ]] \
    && note 'the single-agent collector is still on disk; its consumers moved to the fan-out'
grep -rqF 'scripts/panama-agent-usage"' "$repo_dir/config" "$repo_dir/bin" 2>/dev/null \
    && note 'something still calls the retired single-agent collector'

if (( ${#findings[@]} > 0 )); then
    printf 'agent usage contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'agent usage contract: PASS\n'
