#!/usr/bin/env bash set -uo pipefail # CASAN_MANAGED_PROMPT_ENTRYPOINT # Project-owned governed chat. The DevKit installer places the project identity # in .casan/prompt-policy.json; this launcher refuses to run when that contract # or its repository instruction boundary has drifted. SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SELF_DIR/.." && pwd)" CHAT="$ROOT/packages/casan-harness/scripts/bash/chat-turn.py" VERIFY="$ROOT/packages/casan-harness/scripts/bash/prompt-enforcement-verify.sh" POLICY="$ROOT/.casan/prompt-policy.json" if [[ ! -f "$CHAT" || ! -f "$VERIFY" || ! -f "$POLICY" ]]; then echo "casan-chat: prompt enforcement runtime is incomplete; reinstall CASAN DevKit" >&2 exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "casan-chat: python3 is required" >&2 exit 1 fi if ! bash "$VERIFY" --root "$ROOT" --quiet; then echo "casan-chat: prompt enforcement verification failed closed" >&2 exit 1 fi PROJECT="$(python3 - "$POLICY" <<'PY' import json, sys print(json.load(open(sys.argv[1], encoding="utf-8"))["project_id"]) PY )" DOMAIN_REL="$(python3 - "$POLICY" <<'PY' import json, sys print(json.load(open(sys.argv[1], encoding="utf-8"))["domain_root"]) PY )" export CASAN_APP_ROOT="$ROOT" export CASAN_DOMAIN_ROOT="$ROOT/$DOMAIN_REL" export CASAN_PROJECT_ID="$PROJECT" ACTOR="${CASAN_CHAT_ACTOR:-${USER:-member}}" ROLE="${CASAN_CHAT_ROLE:-viewer}" AGENT="${CASAN_CHAT_AGENT:-}" SKILL="${CASAN_CHAT_SKILL:-}" MODEL_PROVIDER="${CASAN_CHAT_MODEL_PROVIDER:-}" CHAT_ID="${CASAN_CHAT_ID:-$PROJECT-$(date +%Y%m%d-%H%M%S)-$$}" usage() { cat <<'EOF' Usage: bin/casan-chat "normal prompt" bin/casan-chat # interactive mode Every accepted prompt is submitted to CASAN chat-turn and produces H1-H7 evidence. Direct Claude, ChatGPT, Codex or Copilot UI prompts are outside this enforcement boundary and must not be described as CASAN-certified. Optional environment variables: CASAN_CHAT_ACTOR CASAN_CHAT_ROLE CASAN_CHAT_AGENT CASAN_CHAT_SKILL CASAN_CHAT_MODEL_PROVIDER CASAN_CHAT_ID EOF } run_turn() { local message="$1" local raw_file rc raw_file="$(mktemp)" local command=(python3 "$CHAT" ask --message "$message" --actor "$ACTOR" --role "$ROLE" --project "$PROJECT" --chat-id "$CHAT_ID") [[ -n "$AGENT" ]] && command+=(--agent "$AGENT") [[ -n "$SKILL" ]] && command+=(--skill "$SKILL") [[ -n "$MODEL_PROVIDER" ]] && command+=(--model-provider "$MODEL_PROVIDER") set +e "${command[@]}" >"$raw_file" rc=$? set -e python3 - "$raw_file" "$ROOT" <<'PY' import json import pathlib import sys raw_path = pathlib.Path(sys.argv[1]) root = pathlib.Path(sys.argv[2]) lines = [line for line in raw_path.read_text(encoding="utf-8", errors="replace").splitlines() if line.strip()] try: result = json.loads(lines[-1]) except (IndexError, json.JSONDecodeError): print(raw_path.read_text(encoding="utf-8", errors="replace")) raise SystemExit(0) answer = result.get("answer") or result.get("reason") or result.get("decision") or "No answer returned." print(answer) print() trace_id = result.get("trace_id", "") certified = result.get("certified", False) decision = result.get("decision", "UNKNOWN") mode = result.get("mode", "UNKNOWN") print(f"CASAN decision={decision} mode={mode} certified={str(bool(certified)).lower()} trace_id={trace_id or 'n/a'}") if trace_id: trace = root / ".specify" / "logs" / "trace-events" / f"{trace_id}.jsonl" print(f"CASAN evidence={trace}") PY if [[ "$rc" -eq 0 ]]; then local certification trace_id certified certification="$(python3 - "$raw_file" <<'PY' import json, pathlib, sys lines = [line for line in pathlib.Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines() if line.strip()] try: result = json.loads(lines[-1]) except (IndexError, json.JSONDecodeError): print("|false") else: print(f"{result.get('trace_id', '')}|{str(bool(result.get('certified', False))).lower()}") PY )" trace_id="${certification%%|*}" certified="${certification#*|}" if [[ -z "$trace_id" || "$certified" != "true" ]]; then echo "casan-chat: runtime returned success without a certified trace" >&2 rc=2 elif ! bash "$VERIFY" --root "$ROOT" --project "$PROJECT" --trace-id "$trace_id"; then echo "casan-chat: per-prompt certification verification failed closed" >&2 rc=2 fi fi rm -f "$raw_file" return "$rc" } if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage exit 0 fi if [[ "$#" -gt 0 ]]; then run_turn "$*" exit $? fi echo "CASAN governed chat — project=$PROJECT role=$ROLE actor=$ACTOR" echo "Every accepted turn writes H1-H7 evidence. Type /quit to exit." while true; do printf 'casan> ' IFS= read -r message || break case "$message" in /quit|/exit) break ;; '') continue ;; esac run_turn "$message" || true done