feat(log): unified CASAN_LOG_LEVEL across Boss, agent step, harness wrapper

Add a shared log taxonomy (error<warn<info<debug<trace, default info) wired
through all three layers, plus a machine-readable per-step run log.

- scripts/casan-log.mjs + .specify/scripts/bash/casan-log.sh: shared logger
  ([LEVEL] ts [component] msg), stderr-only so stdout contracts and exit codes
  are byte-identical. Node side includes trace-level redaction (secret/PII).
- run-casan-pipeline.mjs: per-STEP info line, loop-activation warnings
  (BACK-TO-PLAN, FAIL->STEP6), debug harness rc + trace_id, trace payload
  excerpt, end-of-run 13-STEP summary table, and one JSONL line/step in
  .specify/logs/pipeline-run.jsonl. New --dry-run stubs all agents but keeps
  the real wrapper in the loop (deterministic, offline, full 13 STEP + loops).
- casan-step.mjs: debug logs for judge verdict, checkpoint, rollback; logger
  import degrades to noop when the file is copied standalone (T1 test).
- casan-harness.sh: debug-log each phase H4-in -> H5 -> [H2-gate] -> H6-exec
  -> H4-out with its rc; optional CASAN_PHASE_REPORT JSON for the Boss.

Default level (info) keeps output close to before; behavior opt-in via env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-03 10:04:55 +09:00
co-authored by Claude Opus 4.8
parent 2f06662f5d
commit 1b61d7f381
5 changed files with 403 additions and 23 deletions
@@ -27,6 +27,40 @@ TMP_DIR="$PROJECT_ROOT/.specify/logs/tmp"
CACHE_DIR="$PROJECT_ROOT/.specify/logs/idempotency"
mkdir -p "$TMP_DIR" "$CACHE_DIR" "$(dirname "$FINAL_OUTPUT")"
# Shared log taxonomy (error<warn<info<debug<trace via CASAN_LOG_LEVEL).
# debug shows each harness phase with its rc; stderr only, stdout untouched.
# shellcheck source=casan-log.sh
source "$SCRIPT_DIR/casan-log.sh"
# Optional machine-readable phase report (one JSON object per invocation).
# The Boss orchestrator sets CASAN_PHASE_REPORT per step so it can log per-
# harness rc at debug level and persist them into pipeline-run.jsonl.
PHASE_REPORT="${CASAN_PHASE_REPORT:-}"
PHASE_LOG=""
CACHE_STATUS="none"
record_phase() { # <phase-name> <rc>
casan_log debug harness "action=$ACTION_NAME phase=$1 rc=$2"
PHASE_LOG="${PHASE_LOG:+$PHASE_LOG,}{\"phase\":\"$1\",\"rc\":$2}"
}
write_phase_report() {
[[ -n "$PHASE_REPORT" ]] || return 0
printf '{"action":"%s","cache":"%s","phases":[%s]}\n' \
"$ACTION_NAME" "$CACHE_STATUS" "$PHASE_LOG" > "$PHASE_REPORT" 2>/dev/null || true
}
run_phase() { # <phase-name> <command...> — preserves the failing rc exactly
local phase="$1"; shift
local rc=0
"$@" || rc=$?
record_phase "$phase" "$rc"
if [[ "$rc" -ne 0 ]]; then
write_phase_report
exit "$rc"
fi
}
hash_text() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum | awk '{print $1}'
@@ -47,8 +81,9 @@ 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"
casan_log debug harness "action=$ACTION_NAME input=$INPUT_FILE output=$FINAL_OUTPUT key=${IDEMPOTENCY_KEY:0:12}…"
run_phase "H4-in" "$SCRIPT_DIR/security-check.sh" "$INPUT_FILE" "$SAFE_INPUT" input
run_phase "H5" "$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
@@ -56,7 +91,7 @@ RAW_OUTPUT="$TMP_DIR/raw-output-$TRACE_SUFFIX.txt"
# 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"
run_phase "H2-gate" env CASAN_IDEMPOTENCY_KEY="$IDEMPOTENCY_KEY" "$SCRIPT_DIR/tool-registry-gate.sh" "$ACTION_NAME"
;;
esac
@@ -69,18 +104,18 @@ export CASAN_STEP_NAME="${CASAN_STEP_NAME:-$ACTION_NAME}"
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"
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- bash -c 'cp "$1" "$CASAN_OUTPUT"' _ "$CACHE_OUT"
elif [[ "$#" -gt 0 ]]; then
"$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT" -- \
CACHE_STATUS="stored"
run_phase "H6-exec" "$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"
run_phase "H6-exec" "$SCRIPT_DIR/agent-metrics.sh" "$APPROVED_INPUT" "$RAW_OUTPUT"
fi
"$SCRIPT_DIR/security-check.sh" "$RAW_OUTPUT" "$FINAL_OUTPUT" output
run_phase "H4-out" "$SCRIPT_DIR/security-check.sh" "$RAW_OUTPUT" "$FINAL_OUTPUT" output
if [[ "$CACHE_STATUS" == "stored" ]]; then
cat <<EOF > "$CACHE_META"
@@ -95,4 +130,6 @@ EOF
cp "$FINAL_OUTPUT" "$CACHE_OUT"
fi
write_phase_report
casan_log debug harness "action=$ACTION_NAME complete cache=$CACHE_STATUS"
echo "CASAN_HARNESS_COMPLETE cache=$CACHE_STATUS key=$IDEMPOTENCY_KEY output=$FINAL_OUTPUT"