#!/usr/bin/env bash
# audit-log.sh  -  append a single PAT-lookup audit event.
#
# Per section18 of REFACTOR_PLAN_v3.7.md: every PAT lookup writes a structured event
# to ~/.claude/logs/multi-agent/audit.jsonl. Repo URLs are SHA-256 hashed
# (corporate paths are sensitive). Identity is logged by name (user's own debug).
#
# Usage:
#   audit-log.sh <event> <service> <identity> [repo_url] [success_bool] [extra=value ...]
#
# Examples:
#   audit-log.sh pat_lookup bitbucket ${USER} https://bitbucket.example.com/proj/repo true
#   audit-log.sh pat_lookup github mmerterden git@github.com:user/repo.git true scope=repo,read:org
#   audit-log.sh pat_missing jira ${USER} "" false reason=keychain_empty
#
# Failure mode: silent  -  telemetry must never block the pipeline.

set -uo pipefail

if [ "$#" -lt 3 ]; then
  echo "usage: audit-log.sh <event> <service> <identity> [repo_url] [success_bool] [extra=value ...]" >&2
  exit 64
fi

EVENT="$1"; shift
SERVICE="$1"; shift
IDENTITY="$1"; shift
REPO_URL="${1:-}"; [ "$#" -gt 0 ] && shift
SUCCESS="${1:-true}"; [ "$#" -gt 0 ] && shift
# success is emitted as a raw JSON boolean at line ~70. A non-bool 5th arg
# would produce invalid JSON ("success":yes-please) and break json.loads on
# the whole line. Coerce anything that isn't a literal bool to the documented
# default (true) so the audit line always parses.
case "$SUCCESS" in
  true|false) ;;
  *) SUCCESS=true ;;
esac

AUDIT_FILE="${AUDIT_FILE:-$HOME/.claude/logs/multi-agent/audit.jsonl}"
mkdir -p "$(dirname "$AUDIT_FILE")" 2>/dev/null || exit 0

# Escape a string for embedding inside a JSON double-quoted value.
json_escape() {
  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}

# SHA-256 hash the repo URL (never log plaintext per section18.2.2)
hash_url() {
  local url="$1"
  [ -z "$url" ] && { echo ""; return; }
  # sha256sum first: it is the coreutils tool, present on Linux and in slim containers
  # where `shasum` (a perl script) is not.
  if command -v sha256sum >/dev/null 2>&1; then
    printf '%s' "$url" | sha256sum | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    printf '%s' "$url" | shasum -a 256 | awk '{print $1}'
  else
    # The previous fallback printed `unhashed:<url>`, writing the plaintext remote into
    # the audit log - the exact thing this hashing exists to prevent, and it would have
    # carried any credentials embedded in the remote URL with it. Losing one field beats
    # leaking one.
    echo "unhashable-no-sha256-tool"
  fi
}

REPO_SHA=$(hash_url "$REPO_URL")
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

# Build extras
EXTRAS=""
SEP=""
for kv in "$@"; do
  KEY="${kv%%=*}"
  VAL="${kv#*=}"
  case "$VAL" in
    true|false) JSON_VAL="$VAL" ;;
    ''|*[!0-9]*) JSON_VAL="\"$(json_escape "$VAL")\"" ;;
    *) JSON_VAL="$VAL" ;;
  esac
  EXTRAS="${EXTRAS}${SEP}\"$(json_escape "$KEY")\":${JSON_VAL}"
  SEP=","
done

LINE="{\"ts\":\"${TS}\",\"event\":\"$(json_escape "$EVENT")\",\"service\":\"$(json_escape "$SERVICE")\",\"identity\":\"$(json_escape "$IDENTITY")\",\"repo_url_sha\":\"${REPO_SHA}\",\"success\":${SUCCESS}"
[ -n "$EXTRAS" ] && LINE="${LINE},${EXTRAS}"
LINE="${LINE}}"

echo "$LINE" >> "$AUDIT_FILE" 2>/dev/null || true

# Opportunistic rotation. audit-log-rotate.sh documents a launchd/cron install,
# but nothing ever told a user to set that up, so an unrotated trail would grow
# without bound now that credential-store.sh writes an event on every PAT lookup.
# Self-triggering removes the operator step: check a cheap size threshold and hand
# off to the real rotator, which is atomic against concurrent appenders (it moves
# the live inode first). Silent and non-blocking - telemetry never fails a run.
ROTATE_AT_BYTES="${AUDIT_ROTATE_AT_BYTES:-1048576}"
_audit_size() {
  case "$(uname -s)" in
    Darwin|*BSD*) stat -f %z "$1" 2>/dev/null || echo 0 ;;
    *)            stat -c %s "$1" 2>/dev/null || echo 0 ;;
  esac
}
if [ "${AUDIT_SELF_ROTATE:-1}" = "1" ] && [ -f "$AUDIT_FILE" ]; then
  SIZE="$(_audit_size "$AUDIT_FILE")"
  if [ "${SIZE:-0}" -ge "$ROTATE_AT_BYTES" ]; then
    ROTATOR="$(dirname "${BASH_SOURCE[0]:-$0}")/audit-log-rotate.sh"
    # The rotator derives AUDIT_FILE from AUDIT_DIR, so pass the DIRECTORY.
    # Handing it AUDIT_FILE would leave it rotating the default path while a
    # caller-overridden trail kept growing.
    [ -f "$ROTATOR" ] &&
      AUDIT_DIR="$(dirname "$AUDIT_FILE")" bash "$ROTATOR" >/dev/null 2>&1 || true
  fi
fi
