#!/usr/bin/env bash set -euo pipefail # CASAN Level 5 unified harness wrapper. # Usage: # casan-harness.sh [action-name] [-- ...] # # Flow: # H4 input security -> H5 governance -> H6 metrics around execution/cache -> H4 output filter INPUT_FILE="${1:-}" FINAL_OUTPUT="${2:-}" ACTION_NAME="${3:-agent_step}" shift 3 || true if [[ "${1:-}" == "--" ]]; then shift fi if [[ -z "$INPUT_FILE" || -z "$FINAL_OUTPUT" ]]; then echo "Usage: casan-harness.sh [action-name] [-- ...]" >&2 exit 64 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" TMP_DIR="$PROJECT_ROOT/.specify/logs/tmp" CACHE_DIR="$PROJECT_ROOT/.specify/logs/idempotency" mkdir -p "$TMP_DIR" "$CACHE_DIR" "$(dirname "$FINAL_OUTPUT")" hash_text() { if command -v sha256sum >/dev/null 2>&1; then sha256sum | awk '{print $1}' else shasum -a 256 | awk '{print $1}' fi } CMD_STR="${*:-no_cmd}" INPUT_HASH="$(cat "$INPUT_FILE" | hash_text)" CMD_HASH="$(printf '%s' "$CMD_STR" | hash_text)" IDEMPOTENCY_KEY="$(printf '%s|%s|%s' "$INPUT_HASH" "$CMD_HASH" "$ACTION_NAME" | hash_text)" CACHE_META="$CACHE_DIR/$IDEMPOTENCY_KEY.json" CACHE_OUT="$CACHE_DIR/$IDEMPOTENCY_KEY.output" TRACE_SUFFIX="$(date +%s)-$$" SAFE_INPUT="$TMP_DIR/security-input-$TRACE_SUFFIX.txt" APPROVED_INPUT="$TMP_DIR/governance-approved-$TRACE_SUFFIX.txt" RAW_OUTPUT="$TMP_DIR/raw-output-$TRACE_SUFFIX.txt" "$SCRIPT_DIR/security-check.sh" "$INPUT_FILE" "$SAFE_INPUT" input "$SCRIPT_DIR/governance-check.sh" "$SAFE_INPUT" "$APPROVED_INPUT" "$ACTION_NAME" # H2 tool registry gate is in the line of fire for side-effecting actions: # it enforces idempotency key, per-agent permission, and rollback strategy # before the command is allowed to execute. The wrapper already derived a # content-addressed idempotency key above. case "$ACTION_NAME" in write_code|migration|deploy|db_write|external_api|write_file) CASAN_IDEMPOTENCY_KEY="$IDEMPOTENCY_KEY" "$SCRIPT_DIR/tool-registry-gate.sh" "$ACTION_NAME" ;; esac # WP-S5: wrap command execution under a hard wall-clock timeout (tool-exec.sh). # Prevents runaway or hung tool calls from blocking the pipeline indefinitely. TOOL_TIMEOUT="${CASAN_TOOL_TIMEOUT_SECONDS:-30}" if [[ -f "$CACHE_META" && -f "$CACHE_OUT" ]]; then "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- bash -c 'cp "$1" "$CASAN_OUTPUT"' _ "$CACHE_OUT" CACHE_STATUS="cached" elif [[ "$#" -gt 0 ]]; then "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- \ "$SCRIPT_DIR/tool-exec.sh" "$TOOL_TIMEOUT" -- "$@" CACHE_STATUS="stored" else "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" CACHE_STATUS="stored" fi "$SCRIPT_DIR/security-check.sh" "$RAW_OUTPUT" "$FINAL_OUTPUT" output if [[ "$CACHE_STATUS" == "stored" ]]; then cat < "$CACHE_META" { "idempotency_key": "$IDEMPOTENCY_KEY", "timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", "action": "$ACTION_NAME", "command": "$(printf '%s' "$CMD_STR" | sed 's/"/\\"/g')", "output_hash": "$(cat "$FINAL_OUTPUT" | hash_text)" } EOF cp "$FINAL_OUTPUT" "$CACHE_OUT" fi echo "CASAN_HARNESS_COMPLETE cache=$CACHE_STATUS key=$IDEMPOTENCY_KEY output=$FINAL_OUTPUT"