diff --git a/packages/casan-harness/scripts/bash/goal-orchestrator.py b/packages/casan-harness/scripts/bash/goal-orchestrator.py index 09e9590..46863ca 100644 --- a/packages/casan-harness/scripts/bash/goal-orchestrator.py +++ b/packages/casan-harness/scripts/bash/goal-orchestrator.py @@ -802,8 +802,13 @@ def run(job_path: str) -> int: job = load_json(job_path) audit_hash = audit(job, "failed") emit(goal_id, "H5-governance", "pass", "Failed decision anchored", {"audit_hash": audit_hash}) - emit(goal_id, "H6-agentops", "error", "Orchestration failed", {"reason": reason, "latency_ms": int((time.monotonic() - started) * 1000)}) - emit(goal_id, "H7-orchestration", "blocked", "Goal workflow stopped", {"reason": reason}) + # Keep gate semantics distinct. H2 may report a patch-contract failure, + # while H6/H7 must report their own lifecycle state and retain the H2 + # error only as causal evidence. Reusing `reason` here made the UI show + # goal_patch_missing as if telemetry and orchestration independently + # failed the patch contract. + emit(goal_id, "H6-agentops", "error", "goal_orchestration_failed", {"root_reason": reason, "latency_ms": int((time.monotonic() - started) * 1000)}) + emit(goal_id, "H7-orchestration", "blocked", "goal_workflow_stopped", {"root_reason": reason}) update_job(job_path, audit_hash=audit_hash) return 2