#!/usr/bin/env bash
set -euo pipefail

# Invariant: Internal Workflow/API observation must consume storeerr.InternalCause
# at the structured boundary only — never in public errors.Is/As classification,
# HTTP/Exec JSON, prompts, or protected-selector responses — and the observation
# regression suites must exist and cover it. Machine-checkable so the declared
# production behavior and its tests cannot silently disappear (see #t2665/#t2684).
#
# Checks:
#   1. internal/api/error_observation.go still surfaces storeerr.InternalCause in
#      errorForObservation (structured log/trace/ObservedServerError only).
#   2. api.ObservedErrorWithCause still exists and returns the public error plus
#      the separate internal cause for structured logging.
#   3. The Workflow Step handler still logs the internal cause as a separate
#      slog "internal_cause" attribute (never inside the public "error" attr).
#   4. Internal causes are NOT consumed anywhere in the public JSON/transport
#      classification path (no storeerr.InternalCause reachable from the public
#      problem/hint/JSON projection or protected-selector responses).
#   5. Both observation regression suites still exist and assert the core
#      invariant (internal cause observed, public JSON stays stable).

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

if ! command -v rg >/dev/null 2>&1; then
  echo "FAIL: ripgrep (rg) is required" >&2
  exit 2
fi

status=0

fail() {
  echo "FAIL: $1" >&2
  status=1
}

ok() {
  echo "OK: $1"
}

# 1. Structured observation boundary consumes storeerr.InternalCause.
if rg -q 'storeerr\.InternalCause' internal/api/error_observation.go; then
  ok "errorForObservation consumes storeerr.InternalCause at the structured boundary"
else
  fail "internal/api/error_observation.go must consume storeerr.InternalCause in errorForObservation"
fi

if rg -q 'func ObservedErrorWithCause' internal/api/error_observation.go; then
  ok "ObservedErrorWithCause exists"
else
  fail "internal/api/error_observation.go must declare api.ObservedErrorWithCause"
fi

# 2. The Workflow Step handler logs the internal cause separately.
if rg -q 'ObservedErrorWithCause' internal/api/agent/api_workflow_steps.go \
  && rg -q '"internal_cause"' internal/api/agent/api_workflow_steps.go; then
  ok "Workflow Step handler logs internal_cause as a separate slog attribute"
else
  fail "api_workflow_steps.go must log the internal cause separately via ObservedErrorWithCause"
fi

# 3. Internal causes must NOT be consumed in the public classification/transport
#    path. The public Workflow error projection (problem/hint/JSON) and the
#    protected-selector classifiers must not call storeerr.InternalCause.
public_hits="$(
  rg -n 'storeerr\.InternalCause' \
    internal/api/workflow/plan internal/api/workflow/binding internal/api/workflow/invocation internal/api/workflow/run \
    internal/api/agent/api_workflow_plans.go internal/api/agent/api_workflow_bindings.go internal/api/agent/api_workflow_runs.go internal/api/agent/api_workflow_invocation.go internal/api/agent/api_workflow_steps.go internal/api/agent/workflow_plan_transport.go internal/api/agent/workflow_binding_transport.go internal/api/agent/workflow_invocation_transport.go internal/api/teamworkflow internal/api/agent/api_workflow_trigger.go 2>/dev/null || true
)"
if [[ -z "$public_hits" ]]; then
  ok "no storeerr.InternalCause in public classification/transport/protected-selector paths"
else
  echo "$public_hits"
  fail "storeerr.InternalCause must not appear in public classification/transport/protected-selector paths"
fi

# 4. The public Error/problem projection must never name the internal cause.
if rg -q 'func .*Error.*(Problem|Hint|Error)\(\).*InternalCause|InternalCause.*(Problem|Hint)' internal/workflow/publicerr internal/api 2>/dev/null; then
  fail "public error projection must not use InternalCause to build problem/hint text"
else
  ok "public problem/hint projection never derives from InternalCause"
fi

# 5. Both observation regression suites exist and assert the invariant.
if [[ -f internal/api/error_observation_test.go ]] \
  && rg -q 'errorForObservation|ObservedErrorWithCause|InternalCause' internal/api/error_observation_test.go \
  && [[ -f internal/api/agent/workflow_observation_test.go ]] \
  && rg -q 'ObservedErrorWithCause|storeerr.InternalCause' internal/api/agent/workflow_observation_test.go; then
  ok "both observation regression suites exist and cover InternalCause"
else
  fail "observation regression suites (internal/api/error_observation_test.go and internal/api/agent/workflow_observation_test.go) must exist and cover InternalCause"
fi

# 6. The invariant itself must be wired into the fast harness so it cannot
#    silently disappear.
if rg -q 'internal-cause-observation|internal_cause_observation|internal-cause' scripts/harness-fast.sh guardrails 2>/dev/null; then
  ok "internal-cause observation invariant is wired into the fast harness"
else
  fail "the internal-cause observation invariant must be referenced by scripts/harness-fast.sh or a guardrails registry"
fi

if [[ "$status" != "0" ]]; then
  echo "internal-cause observation invariant FAILED" >&2
  exit 1
fi
echo "internal-cause observation invariant OK"
