Files
Jarvis/scripts/capture-anthropic-sse.sh
james.maesandClaude Opus 4.7 1d6d4b268e feat(08-02): SSE + NDJSON fixture corpora + runners + capture script (Task 2)
Plan 08-02 Task 2 — OBS-04 pillars (b) Anthropic SSE and (c-fixture)
Ollama NDJSON / OpenAI-compat. Fixture-only; no network.

- Corpora/sse-anthropic/: 9 fixtures (.sse) covering CLAUDE.md Opus 4.7
  footguns: happy-text, text-then-tool-use (split input_json_delta),
  thinking-then-text, refusal (first-class stop_reason), mid-delta-disconnect
  (partial_tool_use_at_disconnect), ping-spam (swallow), unknown-event,
  cache-hit (cache_read_input_tokens), empty-input-json-delta.
- Corpora/ndjson-ollama/: 6 fixtures (.ndjson). 4 native + 2 OpenAI-compat.
  Locks the CLAUDE.md transport gotcha — `tool_calls` arriving on the chunk
  PRECEDING `done: true` (text-then-tool-call, parallel-tool-calls).
- SSEFixtureCorpus / NDJSONFixtureCorpus loaders + their manifests indexing
  expectedEventCount + expectedEventTags per fixture.
- SSEFixtureRunner / NDJSONFixtureRunner drive MockLLMProvider, byte-replay
  fixture data through the production AnthropicProvider / OllamaProvider
  decoders without `@testable import` and without modifying the providers.
- LLMEventTag stable case-name projection — payload-agnostic structural
  comparison between manifest and decoded stream.
- MockLLMProvider gains a `.ollamaOpenAICompat` FixtureKind so OpenAI-compat
  fixtures route to OllamaProvider(useOpenAICompat: true) and exercise the
  OpenAICompatDecoder. FixtureURLProtocol now keys its registry by request
  host; each MockLLMProvider picks a per-process-unique
  `fixture-<uuid>.test` host so parallel swift-testing runs do not trample
  each other's fixture registration (prior single-global state caused every
  fixture to receive happy-text bytes).
- swift-testing parameterized SSEFixtureCorpusTests + NDJSONFixtureCorpusTests
  with one @Test per fixture (per-arg diagnostics) plus footgun-coverage
  sentinel tests.
- jarvis-eval gains `corpus-sse` and `corpus-ndjson` subcommands.
- scripts/capture-anthropic-sse.sh — operator helper. CRITICAL D-07: Two
  layers of redaction. (1) sed pipeline rewrites any Authorization: Bearer
  / x-api-key: header line to <header>: <REDACTED> BEFORE the bytes hit
  disk. (2) Post-write grep for sk-ant-, AKIA, ghp_, sk-<32+> patterns
  with abort-and-delete on any match. Pre-commit hook (Plan 08-04) is the
  third layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 09:36:43 -05:00

67 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# scripts/capture-anthropic-sse.sh
#
# Operator helper: record a live Anthropic SSE response into a fixture file
# under packages/Harness/Corpora/sse-anthropic/<fixture-name>.sse.
#
# CRITICAL D-07 invariant: Authorization: Bearer <token> and x-api-key: <token>
# headers MUST be redacted BEFORE the bytes hit disk. Two-layer defense:
# 1. sed pipeline rewrites any echoed Authorization / x-api-key line to
# `<header>: <REDACTED>` before the file is written.
# 2. Post-write grep for known API-key patterns (sk-ant-, AKIA, ghp_,
# sk-<32+>) — if anything matches, abort and delete the partial file.
# A pre-commit hook (Plan 08-04) is the third layer.
#
# Usage:
# ANTHROPIC_API_KEY=$(security find-generic-password \
# -s 'com.koftwentytwo.jarvis.anthropic' -w) \
# scripts/capture-anthropic-sse.sh <fixture-name> <prompt-file>
#
# Example:
# scripts/capture-anthropic-sse.sh tool-use-fresh prompts/tool-use.txt
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
: "${REPO:=$(cd "$SCRIPT_DIR/.." && pwd)}"
: "${ANTHROPIC_API_KEY:?ANTHROPIC_API_KEY env var required}"
FIXTURE_NAME="${1:?fixture name required}"
PROMPT_FILE="${2:?prompt file required}"
OUT_DIR="${REPO}/packages/Harness/Corpora/sse-anthropic"
OUT_PATH="${OUT_DIR}/${FIXTURE_NAME}.sse"
mkdir -p "$OUT_DIR"
PROMPT="$(cat "$PROMPT_FILE")"
# Capture, redact, write. The sed pipeline runs BEFORE the redirect so the
# raw key never lands on disk. Both Authorization: Bearer ... and x-api-key:
# ... patterns are rewritten to <header>: <REDACTED>. Case-insensitive match
# (gI flag) — Anthropic returns headers verbatim in some echo modes.
curl -sN https://api.anthropic.com/v1/messages \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: extended-cache-ttl-2025-04-11" \
-H "content-type: application/json" \
-H "accept: text/event-stream" \
-d "$(jq -n --arg p "$PROMPT" \
'{model:"claude-opus-4-7",max_tokens:1024,stream:true,messages:[{role:"user",content:$p}]}')" \
| sed -E 's/(Authorization:[[:space:]]*Bearer[[:space:]]+)[^[:space:]]+/\1<REDACTED>/gI' \
| sed -E 's/(x-api-key:[[:space:]]*)[^[:space:]]+/\1<REDACTED>/gI' \
> "$OUT_PATH"
echo "Wrote: $OUT_PATH"
# Layer 2: scan the committed bytes for any known key pattern. If any
# pattern matches, we either failed to redact or the API key leaked into
# the response body. Either way, abort and delete the file.
if grep -E "sk-ant-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9]{32,}" "$OUT_PATH" >/dev/null 2>&1; then
echo "ERROR: API key pattern detected in $OUT_PATH after redaction. Aborting." >&2
rm -f "$OUT_PATH"
exit 1
fi
echo "OK: no API key patterns detected in $OUT_PATH"