#!/usr/bin/env bash
# exec-plane-probe — measure the execution-plane insulation invariant on this laptop:
# "no non-interactive model/build process runs here" (docs/specs/2026-08-06-execution-plane-insulation.md).
#
# Read-only. Samples cgroup membership over a window and prints the evidence rows plus a
# verdict. Nothing is killed, signalled, throttled or written to.
#
# Exit: 0 = PURE (nothing non-interactive observed), 1 = IMPURE (evidence printed), 2 = usage.
set -euo pipefail

readonly SELF="${0##*/}"
readonly INVENTORY_DOC="docs/specs/2026-08-07-babysitter-demotion-inventory.md"

# Slices that hold non-interactive agent/build work. Everything else under the user manager
# — human.slice (the owner's interactive session), interactive.slice, app.slice,
# session.slice — is exempt by construction and is never sampled.
readonly WORK_SLICES=("agent.slice" "build.slice" "agent-seat.slice")
# Scopes that carry agent/build work even when placed outside the work slices.
readonly WORK_SCOPE_RE='^(confine-agent|confine-build|v2-child-dispatch)-.+\.scope$'

usage() {
  cat >&2 <<EOF
usage: $SELF [--window DURATION] [--interval DURATION] [--once] [--json]

  --window DURATION    total sampling window (default 15m; s/m/h suffix)
  --interval DURATION  delay between samples (default 30s)
  --once               take a single sample and exit
  --json               emit the evidence as JSON instead of a table

Exit 0 = PURE, 1 = IMPURE, 2 = usage error.
EOF
  exit 2
}

die() { echo "$SELF: $*" >&2; exit 2; }

parse_duration() { # DURATION -> seconds
  local raw="$1" num unit
  [[ "$raw" =~ ^([0-9]+)([smh]?)$ ]] || die "invalid duration: $raw"
  num="${BASH_REMATCH[1]}"; unit="${BASH_REMATCH[2]:-s}"
  case "$unit" in
    s) echo "$num" ;;
    m) echo $((num * 60)) ;;
    h) echo $((num * 3600)) ;;
  esac
}

cgroup_root() {
  if [[ -n "${EXEC_PLANE_CGROUP_ROOT:-}" ]]; then
    echo "$EXEC_PLANE_CGROUP_ROOT"
    return
  fi
  local uid; uid="$(id -u)"
  echo "/sys/fs/cgroup/user.slice/user-${uid}.slice/user@${uid}.service"
}

read_num() { # FILE FIELD-EXTRACTOR -> integer, 0 when unreadable
  local file="$1"
  [[ -r "$file" ]] || { echo 0; return; }
  local value; value="$(head -n1 "$file" 2>/dev/null || true)"
  [[ "$value" =~ ^[0-9]+$ ]] && echo "$value" || echo 0
}

cpu_usec() { # SCOPE-DIR -> cumulative cpu usage in microseconds
  local file="$1/cpu.stat" value=0
  if [[ -r "$file" ]]; then
    value="$(awk '/^usage_usec /{print $2; exit}' "$file" 2>/dev/null || true)"
  fi
  [[ "$value" =~ ^[0-9]+$ ]] && echo "$value" || echo 0
}

proc_count() { # SCOPE-DIR -> number of processes in the cgroup
  local file="$1/cgroup.procs"
  [[ -r "$file" ]] || { echo 0; return; }
  awk 'END {print NR+0}' "$file" 2>/dev/null || echo 0
}

# Emits "unit<TAB>pids<TAB>memory_bytes<TAB>cpu_usec" for every non-interactive workload alive
# now. A unit is exempt when it sits under an interactive slice, counted when it sits under a
# work slice or carries a work-scope name wherever it was parked.
sample_once() {
  local root="$1" dir unit relative label slice
  while IFS= read -r dir; do
    relative="${dir#"$root"/}"
    unit="${dir##*/}"
    case "/$relative" in
      */human.slice/*|*/interactive.slice/*|*/app.slice/*|*/session.slice/*) continue ;;
    esac
    label=""
    for slice in "${WORK_SLICES[@]}"; do
      case "/$relative" in
        */"$slice"/*) label="$slice/$unit" ;;
      esac
    done
    if [[ -z "$label" ]]; then
      [[ "$unit" =~ $WORK_SCOPE_RE ]] || continue
      label="stray/$unit"
    fi
    printf '%s\t%s\t%s\t%s\n' "$label" "$(proc_count "$dir")" \
      "$(read_num "$dir/memory.current")" "$(cpu_usec "$dir")"
  done < <(find "$root" -mindepth 2 -maxdepth 4 \( -name '*.scope' -o -name '*.service' \) -type d 2>/dev/null | sort)
}

main() {
  local window_raw="15m" interval_raw="30s" once=0 json=0
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --window) [[ $# -ge 2 ]] || usage; window_raw="$2"; shift 2 ;;
      --interval) [[ $# -ge 2 ]] || usage; interval_raw="$2"; shift 2 ;;
      --once) once=1; shift ;;
      --json) json=1; shift ;;
      -h|--help) usage ;;
      *) usage ;;
    esac
  done

  local window interval root
  window="$(parse_duration "$window_raw")"
  interval="$(parse_duration "$interval_raw")"
  [[ "$interval" -gt 0 ]] || die "--interval must be greater than zero"
  root="$(cgroup_root)"
  [[ -d "$root" ]] || die "cgroup root not found: $root"
  [[ "$once" -eq 1 ]] && window=0

  # An associative array with no keys reads as unset under `set -u`, so the workload count is
  # tracked in its own counter and every key expansion below is guarded by it.
  local -A first_seen last_seen samples peak_pids peak_mem cpu_first cpu_last
  local started elapsed=0 sample_count=0 violations=0
  started="$(date +%s)"

  while :; do
    sample_count=$((sample_count + 1))
    local unit pids mem cpu
    while IFS=$'\t' read -r unit pids mem cpu; do
      [[ -n "$unit" ]] || continue
      if [[ -z "${first_seen[$unit]:-}" ]]; then
        first_seen[$unit]="$elapsed"; peak_pids[$unit]=0; peak_mem[$unit]=0
        cpu_first[$unit]="$cpu"; samples[$unit]=0
        violations=$((violations + 1))
      fi
      last_seen[$unit]="$elapsed"
      cpu_last[$unit]="$cpu"
      samples[$unit]=$(( samples[$unit] + 1 ))
      (( pids > peak_pids[$unit] )) && peak_pids[$unit]="$pids"
      (( mem > peak_mem[$unit] )) && peak_mem[$unit]="$mem"
    done < <(sample_once "$root")

    elapsed=$(( $(date +%s) - started ))
    (( elapsed + interval > window )) && break
    sleep "$interval"
  done

  if [[ "$json" -eq 1 ]]; then
    emit_json "$root" "$window" "$interval" "$sample_count" "$violations"
  else
    emit_table "$root" "$window" "$interval" "$sample_count" "$violations"
  fi
  [[ "$violations" -eq 0 ]]
}

emit_table() {
  local root="$1" window="$2" interval="$3" sample_count="$4" violations="$5"
  echo "exec-plane-probe: ${sample_count} samples over ${window}s (interval ${interval}s)"
  echo "cgroup root: $root"
  echo "exempt by construction: human.slice (owner session), interactive.slice, app.slice, session.slice"
  echo
  if [[ "$violations" -eq 0 ]]; then
    echo "VERDICT: PURE — no non-interactive agent or build workload observed on this laptop."
    echo "Guards tagged 'release-on: laptop-purity' in $INVENTORY_DOC are eligible for removal review."
    echo "Guards tagged 'release-on: never' stay regardless of this result."
    return
  fi
  printf 'VERDICT: IMPURE — %d non-interactive workload(s) observed. Evidence:\n\n' "$violations"
  # CPU_SEC is a delta across samples, so a single-sample run (--once) can only ever
  # report 0 and a live workload reads as inert. CPU_TOTAL is the cgroup's cumulative
  # usage and is the only CPU evidence that means anything in that mode.
  printf '%-52s %7s %7s %10s %12s %12s\n' UNIT SAMPLES PEAKPID PEAKMEM_MB CPU_SEC CPU_TOTAL
  local unit cpu_delta
  for unit in "${!first_seen[@]}"; do
    cpu_delta=$(( (cpu_last[$unit] - cpu_first[$unit]) / 1000000 ))
    printf '%-52s %7s %7s %10s %12s %12s\n' "$unit" "${samples[$unit]}" "${peak_pids[$unit]}" \
      $(( peak_mem[$unit] / 1048576 )) "$cpu_delta" $(( cpu_last[$unit] / 1000000 ))
  done | sort
  echo
  echo "No guard in $INVENTORY_DOC releases while this reads IMPURE."
}

emit_json() {
  local root="$1" window="$2" interval="$3" sample_count="$4" violations="$5"
  printf '{"root":"%s","windowSeconds":%s,"intervalSeconds":%s,"samples":%s,"verdict":"%s","workloads":[' \
    "$root" "$window" "$interval" "$sample_count" \
    "$( [[ "$violations" -eq 0 ]] && echo PURE || echo IMPURE )"
  local unit first=1 cpu_delta
  for unit in $( [[ "$violations" -eq 0 ]] || printf '%s\n' "${!first_seen[@]}" | sort); do
    cpu_delta=$(( (cpu_last[$unit] - cpu_first[$unit]) / 1000000 ))
    [[ "$first" -eq 1 ]] || printf ','
    first=0
    printf '{"unit":"%s","samplesPresent":%s,"firstSeenSeconds":%s,"lastSeenSeconds":%s,"peakPids":%s,"peakMemoryBytes":%s,"cpuSeconds":%s,"cpuSecondsTotal":%s}' \
      "$unit" "${samples[$unit]}" "${first_seen[$unit]}" "${last_seen[$unit]}" \
      "${peak_pids[$unit]}" "${peak_mem[$unit]}" "$cpu_delta" $(( cpu_last[$unit] / 1000000 ))
  done
  printf ']}\n'
}

main "$@"
