Commit 43c3f0503b1bca667ee96f4912bcf7384ed13597

Authored by bboonnee
1 parent 25ee708e

Give test_script verify failures their own blocked_reason

ConsistencyCheckFailedError is now raised by both the PRD-consistency
check and the test_script mechanical compile-check (07a0a27), but
scheduler._blocked_reason_for still labeled every instance
'prd_consistency_check_failed', misleadingly blaming the PRD for
test_script node failures. Distinguishes by node_id substring match
(see docstring for the ponytail ceiling/upgrade path).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
src/scheduler.py
... ... @@ -89,6 +89,25 @@ _ROUTABLE_EXCEPTIONS = tuple(exc for exc, _ in _BLOCKED_REASON_FOR_EXCEPTION)
89 89  
90 90  
91 91 def _blocked_reason_for(exc):
  92 + """2026-08-07: `ConsistencyCheckFailedError` used to be raised only by
  93 + the PRD-internal-contradiction check, but commit 07a0a27 (2026-08-06)
  94 + generalized the same exception/retry machinery to the test_script
  95 + mechanical compile-check too -- without this, both show up in the
  96 + console as the same misleading 'prd_consistency_check_failed' label
  97 + (see docs/todo/validator-test-script-narrative-output-issue.md), even
  98 + when the real failure has nothing to do with the PRD.
  99 + ponytail: distinguishes by substring-matching the failing node's LLM-
  100 + chosen node_id (e.g. 'n_test_script'/'validator_test_script') rather
  101 + than a real typed discriminator threaded through verify_fn -- good
  102 + enough because every real graph_manifest observed so far names its
  103 + test_script node descriptively; ceiling is a node named ambiguously
  104 + silently falling back to the PRD label. Upgrade path if that's ever
  105 + wrong in practice: have verify_fn return a third 'kind' value instead
  106 + of inferring from node_id text."""
  107 + if isinstance(exc, work_graph_orchestrate.ConsistencyCheckFailedError):
  108 + if "test_script" in (exc.node_id or ""):
  109 + return "test_script_verification_failed"
  110 + return "prd_consistency_check_failed"
92 111 for exc_type, reason in _BLOCKED_REASON_FOR_EXCEPTION:
93 112 if isinstance(exc, exc_type):
94 113 return reason
... ...
src/test_scheduler.py
... ... @@ -349,6 +349,20 @@ class ConsistencyCheckFailedRoutingTest(RoutableExceptionTestBase):
349 349 self.assertEqual(result["blocked_reason"], "prd_consistency_check_failed")
350 350 self._assert_task_blocked(result["task_id"], "prd_consistency_check_failed")
351 351  
  352 + def test_test_script_node_gets_its_own_reason_not_the_prd_one(self):
  353 + # 2026-08-07: same exception class, but raised by the test_script
  354 + # mechanical compile-check (docs/todo/validator-test-script-
  355 + # narrative-output-issue.md), not the PRD-consistency check -- the
  356 + # console must not blame the PRD for an unrelated failure.
  357 + result = self._run_with_raising_lifecycle(
  358 + scheduler.work_graph_orchestrate.ConsistencyCheckFailedError(
  359 + "boom", node_id="validator_test_script"
  360 + )
  361 + )
  362 + self.assertEqual(result["final_status"], "blocked_pending_human")
  363 + self.assertEqual(result["blocked_reason"], "test_script_verification_failed")
  364 + self._assert_task_blocked(result["task_id"], "test_script_verification_failed")
  365 +
352 366  
353 367 class UnroutedExceptionStillBlocksTaskTest(RoutableExceptionTestBase):
354 368 def test_other_exceptions_block_the_task_instead_of_leaving_it_stuck(self):
... ...