Files
CASAN/AINative_OKR_CASAN5/packages/casan-harness/scripts/bash/provider-usage-fetch.sh
T
thanhnvandClaude Opus 4.8 3adc48ed82 feat(plan-01): Phase 4a — make harness location-independent (facade-free capable)
Resolve every root by marker walk-up instead of a fixed depth that only lands on the
app via the .specify compat symlink, so the harness runs correctly when invoked by its
real packages/casan-harness path — proven by a full gate run via that path: 64/0/0.

- 95 scripts/tests: PROJECT_ROOT/ROOT "$SCRIPT_DIR/../.."-style computations -> $CASAN_APP_ROOT.
- 6 leaf scripts (infra-lab, context-validate, secrets-scan, path-guard, toolchain-verify,
  phase2-sourcegen) now source casan-paths + use CASAN_APP_ROOT.
- run-casan4: source casan-paths as a package sibling (facade-independent), PROJECT_ROOT=CASAN_APP_ROOT.
- 8 Python files: project_root()/REPO_ROOT/bundle_root walk UP for the .specify marker
  (control-plane-settings, loop_common, model-call, context-compress, test-integrity,
  bundle-integrity, traceability-matrix; generate-* fixed earlier).
- evidence-pack-build.py + traceability-matrix.py: domain refs -> apps/okr/domain
  (input/, corpus/redteam-vectors.jsonl, traceability-map.json).
- ci-harness-gate.sh: export CASAN_TESTS_DIR/CASAN_TEST_MANIFEST/CASAN_BUNDLE_ROOT so the
  integrity Python resolves via the harness root regardless of invocation path; ROOT=CASAN_APP_ROOT.
- Remove the domain compat symlinks from packages/casan-harness/security (redteam-corpus,
  redteam-vectors, benign-corpus) — packages now holds NO domain data.

Both invocation paths pass (compat facade still present): .specify/... and packages/...

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 10:50:50 +09:00

118 lines
4.2 KiB
Bash

#!/usr/bin/env bash
set -uo pipefail
# CASAN H6 — provider usage telemetry API fetch (D2).
# Pulls usage records from a provider usage HTTP API (production: the
# OpenAI/Anthropic usage endpoints) and imports them into provider-usage.jsonl
# through the same schema gate as import-provider-telemetry.sh.
# Fail-loud by design: unreachable API or invalid schema => non-zero exit and
# NOTHING is imported (all-or-nothing, no partial/dirty telemetry).
#
# Usage: provider-usage-fetch.sh <api-url> [out-jsonl]
#
# Greppable outputs:
# PROVIDER_TELEMETRY_FETCHED | PROVIDER_USAGE_INVALID | PROVIDER_API_UNREACHABLE
API_URL="${1:-}"
if [[ -z "$API_URL" ]]; then
echo "Usage: provider-usage-fetch.sh <api-url> [out-jsonl]" >&2
exit 64
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/casan-paths.sh"
PROJECT_ROOT="$CASAN_APP_ROOT"
OUT="${2:-$CASAN_STATE_ROOT/logs/level5/provider-usage.jsonl}"
mkdir -p "$(dirname "$OUT")"
# SEC-13 (M-09): SSRF guard on the fetch URL. ALWAYS reject non-http(s) schemes
# (file://, gopher://, dict://, … metadata exfil). In enforced mode additionally
# require the host to be in the provider allowlist and block internal/link-local
# IPs — dev keeps loopback mocks working (http://127.0.0.1 test servers).
if ! python3 - "$API_URL" <<'PY'
import ipaddress, os, sys
from urllib.parse import urlparse
url = sys.argv[1]
u = urlparse(url)
scheme = (u.scheme or "").lower()
host = (u.hostname or "").lower()
enforced = os.environ.get("CASAN_PROFILE") == "prod" or os.environ.get("CASAN_SSRF_STRICT") == "1"
allow = [h.strip().lower() for h in os.environ.get(
"CASAN_PROVIDER_HOST_ALLOWLIST", "api.openai.com,api.anthropic.com").split(",") if h.strip()]
def die(reason):
sys.stderr.write(f"PROVIDER_URL_REJECTED {reason} url={url}\n")
sys.exit(1)
if scheme not in ("http", "https"):
die(f"scheme_not_allowed:{scheme or 'none'}") # blocks file:// et al (all modes)
if not host:
die("no_host")
if enforced:
if scheme != "https":
die("plaintext_http_not_allowed_in_prod")
if host in ("localhost",) or host.endswith(".internal") or host.endswith(".local"):
die(f"internal_host:{host}")
try:
ip = ipaddress.ip_address(host)
if (ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved
or ip.is_multicast or ip.is_unspecified):
die(f"internal_ip:{host}")
except ValueError:
pass # a hostname, not a literal IP
if allow and host not in allow:
die(f"host_not_in_allowlist:{host}")
sys.exit(0)
PY
then
echo "PROVIDER_API_SSRF_BLOCKED url=$API_URL (telemetry NOT imported)" >&2
exit 1
fi
BODY="$(mktemp)"
trap 'rm -f "$BODY"' EXIT
if ! curl -sS -m 10 --retry 2 --retry-delay 1 -f "$API_URL" -o "$BODY" 2>/dev/null; then
echo "PROVIDER_API_UNREACHABLE url=$API_URL (telemetry NOT imported)" >&2
exit 1
fi
python - "$BODY" "$OUT" "$API_URL" <<'PY'
import json
import sys
from datetime import datetime, timezone
src, out, url = sys.argv[1], sys.argv[2], sys.argv[3]
try:
data = json.load(open(src, encoding="utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError):
raise SystemExit("PROVIDER_USAGE_INVALID response is not JSON")
records = data if isinstance(data, list) else [data]
required = ["provider", "model", "run_id", "step", "input_tokens", "output_tokens",
"total_tokens", "cost_usd", "latency_ms", "status"]
for idx, rec in enumerate(records):
if not isinstance(rec, dict):
raise SystemExit(f"PROVIDER_USAGE_INVALID record[{idx}] is not an object")
missing = [key for key in required if key not in rec]
if missing:
raise SystemExit(f"PROVIDER_USAGE_INVALID record[{idx}] missing={missing}")
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
with open(out, "a", encoding="utf-8") as fh:
for rec in records:
fh.write(json.dumps({
"timestamp": now,
"harness": "L5-provider-telemetry",
"telemetry_source": "provider_api",
"api_endpoint": url,
**rec,
}) + "\n")
print(f"PROVIDER_TELEMETRY_FETCHED count={len(records)} url={url} output={out}")
PY
RC=$?
if [[ "$RC" -ne 0 ]]; then
echo "PROVIDER_USAGE_INVALID import rejected (nothing written)" >&2
exit 1
fi