@riddledc/riddle-proof 0.8.11 → 0.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/advanced/engine-harness.cjs +97 -12
  2. package/dist/advanced/engine-harness.js +2 -2
  3. package/dist/advanced/index.cjs +98 -12
  4. package/dist/advanced/index.d.cts +2 -2
  5. package/dist/advanced/index.d.ts +2 -2
  6. package/dist/advanced/index.js +4 -4
  7. package/dist/advanced/proof-run-core.cjs +31 -1
  8. package/dist/advanced/proof-run-core.d.cts +1 -1
  9. package/dist/advanced/proof-run-core.d.ts +1 -1
  10. package/dist/advanced/proof-run-core.js +3 -1
  11. package/dist/advanced/proof-run-engine.cjs +46 -12
  12. package/dist/advanced/proof-run-engine.d.cts +2 -2
  13. package/dist/advanced/proof-run-engine.d.ts +2 -2
  14. package/dist/advanced/proof-run-engine.js +2 -2
  15. package/dist/advanced/runner.js +2 -2
  16. package/dist/{chunk-5N5QFI2S.js → chunk-7GZY5PLT.js} +31 -1
  17. package/dist/{chunk-46DDSZJR.js → chunk-JBY2SU5U.js} +18 -12
  18. package/dist/{chunk-5N6MQCLC.js → chunk-NGX4SUQN.js} +1 -1
  19. package/dist/{chunk-BBUO7HM4.js → chunk-RTLA6CPP.js} +53 -1
  20. package/dist/{chunk-2PXL3RDB.js → chunk-SZUC4MDN.js} +1 -1
  21. package/dist/cli/index.js +3 -3
  22. package/dist/cli.cjs +97 -12
  23. package/dist/cli.js +3 -3
  24. package/dist/engine-harness.cjs +97 -12
  25. package/dist/engine-harness.js +2 -2
  26. package/dist/index.cjs +97 -12
  27. package/dist/index.js +3 -3
  28. package/dist/{proof-run-core-CE0jx7wL.d.cts → proof-run-core-CrpYH-qH.d.cts} +6 -3
  29. package/dist/{proof-run-core-CE0jx7wL.d.ts → proof-run-core-CrpYH-qH.d.ts} +6 -3
  30. package/dist/proof-run-core.cjs +31 -1
  31. package/dist/proof-run-core.d.cts +1 -1
  32. package/dist/proof-run-core.d.ts +1 -1
  33. package/dist/proof-run-core.js +3 -1
  34. package/dist/{proof-run-engine-B7DCPzpK.d.cts → proof-run-engine-C6vYAZd8.d.cts} +4 -4
  35. package/dist/{proof-run-engine-BomAcXhA.d.ts → proof-run-engine-h9C1lC0w.d.ts} +4 -4
  36. package/dist/proof-run-engine.cjs +46 -12
  37. package/dist/proof-run-engine.d.cts +2 -2
  38. package/dist/proof-run-engine.d.ts +2 -2
  39. package/dist/proof-run-engine.js +2 -2
  40. package/dist/runner.js +2 -2
  41. package/package.json +1 -1
  42. package/runtime/lib/verify.py +97 -11
  43. package/runtime/tests/recon_verify_smoke.py +154 -4
  44. package/runtime/tests/trust_boundary_regression.py +12 -0
@@ -1,4 +1,4 @@
1
- import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CE0jx7wL.cjs';
1
+ import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CrpYH-qH.cjs';
2
2
 
3
3
  declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, resolvedConfig?: ReturnType<typeof resolveConfig>): Promise<{
4
4
  ok: boolean;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -1,4 +1,4 @@
1
- import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CE0jx7wL.js';
1
+ import { W as WorkflowParams, r as resolveConfig, P as PluginConfig, a as WorkflowStage } from './proof-run-core-CrpYH-qH.js';
2
2
 
3
3
  declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, resolvedConfig?: ReturnType<typeof resolveConfig>): Promise<{
4
4
  ok: boolean;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -505,6 +505,25 @@ function visualDeltaShipGateReason(state = {}) {
505
505
  if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
506
506
  return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
507
507
  }
508
+ function proofAssessmentHardBlockersForState(state = {}) {
509
+ const request = objectValue(state?.proof_assessment_request);
510
+ const blockers = [];
511
+ const add = (value) => {
512
+ if (typeof value !== "string") return;
513
+ const trimmed = value.trim();
514
+ if (trimmed && !blockers.includes(trimmed)) blockers.push(trimmed);
515
+ };
516
+ if (Array.isArray(request.hard_blockers)) {
517
+ for (const blocker of request.hard_blockers) add(blocker);
518
+ }
519
+ add(state?.structured_interaction_capture_failure_summary);
520
+ add(state?.structured_interaction_failure_summary);
521
+ const mergeRecommendation = String(state?.merge_recommendation || "").trim();
522
+ if (mergeRecommendation === "do-not-merge" && blockers.length) {
523
+ add("merge_recommendation=do-not-merge because the proof bundle contains hard blockers.");
524
+ }
525
+ return blockers;
526
+ }
508
527
  function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
509
528
  const visualDelta = visualDeltaForState(state || {});
510
529
  const status = String(visualDelta.status || "").trim();
@@ -539,6 +558,7 @@ function validateShipGate(state = {}) {
539
558
  const visualDelta = visualDeltaForState(state);
540
559
  const visualDeltaRequired = visualDeltaRequiredForState(state);
541
560
  const visualDeltaBlocker = visualDeltaShipGateReason(state);
561
+ const hardBlockers = proofAssessmentHardBlockersForState(state);
542
562
  const reasons = [];
543
563
  if (!["before", "prod", "both"].includes(reference)) {
544
564
  reasons.push(`reference must be before, prod, or both; got ${reference}`);
@@ -570,6 +590,9 @@ function validateShipGate(state = {}) {
570
590
  if (visualDeltaBlocker) {
571
591
  reasons.push(visualDeltaBlocker);
572
592
  }
593
+ for (const blocker of hardBlockers) {
594
+ reasons.push(`proof hard blocker prevents ready_to_ship: ${blocker}`);
595
+ }
573
596
  return {
574
597
  ok: reasons.length === 0,
575
598
  reasons,
@@ -586,7 +609,8 @@ function validateShipGate(state = {}) {
586
609
  proof_assessment_source: proofAssessment.source,
587
610
  visual_delta_required: visualDeltaRequired,
588
611
  visual_delta_status: typeof visualDelta.status === "string" ? visualDelta.status : null,
589
- visual_delta_passed: typeof visualDelta.passed === "boolean" ? visualDelta.passed : null
612
+ visual_delta_passed: typeof visualDelta.passed === "boolean" ? visualDelta.passed : null,
613
+ hard_blockers: hardBlockers
590
614
  }
591
615
  };
592
616
  }
@@ -700,6 +724,10 @@ var CHECKPOINT_CONTRACT_SPECS = {
700
724
  }],
701
725
  required_state: ["verify_decision_request"]
702
726
  },
727
+ verify_capture_blocked: {
728
+ purpose: "Verify capture produced conclusive failed browser evidence and should stop instead of retrying proof authoring.",
729
+ required_state: ["verify_decision_request"]
730
+ },
703
731
  verify_supervisor_judgment: {
704
732
  purpose: "Supervising agent judges whether captured evidence proves the change is ready to ship.",
705
733
  accepted_inputs: [{
@@ -1284,14 +1312,18 @@ function verifyAssessment(state) {
1284
1312
  };
1285
1313
  }
1286
1314
  if (state?.verify_status === "capture_incomplete") {
1315
+ const captureQuality = verifyDecision?.capture_quality || {};
1316
+ const terminalBlocker = captureQuality?.terminal_blocker === true || captureQuality?.blocking === true;
1317
+ const recommendedStage = terminalBlocker ? null : verifyDecision?.continue_with_stage || verifyDecision?.recommended_stage || "author";
1318
+ const continueWithStage = terminalBlocker ? null : verifyDecision?.continue_with_stage || verifyDecision?.recommended_stage || "author";
1287
1319
  return {
1288
- decision: verifyDecision?.capture_quality?.decision || "revise_capture",
1320
+ decision: captureQuality?.decision || "revise_capture",
1289
1321
  summary: verifyDecision?.summary || "Verify needs another internal capture iteration before the evidence can be judged.",
1290
- recommendedStage: verifyDecision?.continue_with_stage || verifyDecision?.recommended_stage || "author",
1291
- continueWithStage: verifyDecision?.continue_with_stage || verifyDecision?.recommended_stage || "author",
1322
+ recommendedStage,
1323
+ continueWithStage,
1292
1324
  escalationTarget: "agent",
1293
- reasons: Array.isArray(verifyDecision?.capture_quality?.reasons) ? verifyDecision.capture_quality.reasons : [],
1294
- raw: verifyDecision?.capture_quality || verifyDecision,
1325
+ reasons: Array.isArray(captureQuality?.reasons) ? captureQuality.reasons : [],
1326
+ raw: captureQuality || verifyDecision,
1295
1327
  source: "workflow_capture"
1296
1328
  };
1297
1329
  }
@@ -2511,7 +2543,9 @@ ${implementRes.stderr || ""}`;
2511
2543
  convergenceSignals
2512
2544
  };
2513
2545
  if (verifyStatus !== "evidence_captured") {
2514
- if ((verifyContinueWithStage || verifyRecommendedStage || "author") === "author") {
2546
+ const captureQuality = verifyDecisionRequest?.capture_quality || {};
2547
+ const captureTerminalBlocker = captureQuality?.terminal_blocker === true || captureQuality?.blocking === true;
2548
+ if (!captureTerminalBlocker && (verifyContinueWithStage || verifyRecommendedStage || "author") === "author") {
2515
2549
  updateState(config.statePath, (currentState) => {
2516
2550
  currentState.author_status = "needs_authoring";
2517
2551
  currentState.proof_plan_status = "needs_authoring";
@@ -2519,7 +2553,7 @@ ${implementRes.stderr || ""}`;
2519
2553
  });
2520
2554
  state = readState(config.statePath);
2521
2555
  }
2522
- const checkpointName = "verify_capture_retry";
2556
+ const checkpointName = captureTerminalBlocker ? "verify_capture_blocked" : "verify_capture_retry";
2523
2557
  const summary = stringValue(proofAssessment.summary) || "Verify ran, but the proof packet still needs internal capture-plan work before it should ship.";
2524
2558
  recordAttempt("verify", "checkpoint", summary, {
2525
2559
  autoApproved: verifyRes.autoApproved || false,
@@ -2532,11 +2566,11 @@ ${implementRes.stderr || ""}`;
2532
2566
  summary,
2533
2567
  {
2534
2568
  ok: true,
2535
- nextActions: ["inspect_after_capture", "continue_internal_loop_with_checkpoint", "return_to_recon_if_baseline_is_wrong"],
2569
+ nextActions: captureTerminalBlocker ? ["inspect_after_capture", "report_specific_browser_evidence_blocker", "start_a_new_run_after_the_product_or_script_is_fixed"] : ["inspect_after_capture", "continue_internal_loop_with_checkpoint", "return_to_recon_if_baseline_is_wrong"],
2536
2570
  advanceOptions: verifyLoopAdvanceOptions,
2537
- recommendedAdvanceStage: verifyRecommendedStage || "author",
2538
- continueWithStage: verifyContinueWithStage || "author",
2539
- blocking: false,
2571
+ recommendedAdvanceStage: captureTerminalBlocker ? null : verifyRecommendedStage || "author",
2572
+ continueWithStage: captureTerminalBlocker ? null : verifyContinueWithStage || "author",
2573
+ blocking: captureTerminalBlocker,
2540
2574
  details: verifyDetails,
2541
2575
  verifyStatus,
2542
2576
  verifySummary,
@@ -1,2 +1,2 @@
1
- import './proof-run-core-CE0jx7wL.cjs';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-B7DCPzpK.cjs';
1
+ import './proof-run-core-CrpYH-qH.cjs';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-C6vYAZd8.cjs';
@@ -1,2 +1,2 @@
1
- import './proof-run-core-CE0jx7wL.js';
2
- export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-BomAcXhA.js';
1
+ import './proof-run-core-CrpYH-qH.js';
2
+ export { R as RiddleProofEngine, c as createRiddleProofEngine, e as executeWorkflow } from './proof-run-engine-h9C1lC0w.js';
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createRiddleProofEngine,
3
3
  executeWorkflow
4
- } from "./chunk-46DDSZJR.js";
5
- import "./chunk-5N5QFI2S.js";
4
+ } from "./chunk-JBY2SU5U.js";
5
+ import "./chunk-7GZY5PLT.js";
6
6
  import "./chunk-MLKGABMK.js";
7
7
  export {
8
8
  createRiddleProofEngine,
package/dist/runner.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runRiddleProof
3
- } from "./chunk-5N6MQCLC.js";
3
+ } from "./chunk-NGX4SUQN.js";
4
4
  import "./chunk-YZUVEJ5B.js";
5
5
  import "./chunk-FMOYUYH2.js";
6
- import "./chunk-5N5QFI2S.js";
6
+ import "./chunk-7GZY5PLT.js";
7
7
  import "./chunk-4FOHZ7JG.js";
8
8
  import "./chunk-VY4Y5U57.js";
9
9
  import "./chunk-MLKGABMK.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.8.11",
3
+ "version": "0.8.12",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -2061,7 +2061,24 @@ EXPLICIT_TERMINAL_PATH_KEYS = (
2061
2061
  'final_url', 'finalUrl',
2062
2062
  'final_route', 'finalRoute',
2063
2063
  )
2064
- LOCATION_PATH_KEYS = ('path', 'pathname', 'route', 'url', 'href')
2064
+ FULL_LOCATION_PATH_KEYS = (
2065
+ 'url', 'href',
2066
+ 'hrefNormalized', 'href_normalized',
2067
+ 'terminalUrl', 'terminal_url',
2068
+ 'afterUrl', 'after_url',
2069
+ 'finalUrl', 'final_url',
2070
+ 'currentUrl', 'current_url',
2071
+ 'pathWithSearchAndHash', 'path_with_search_and_hash',
2072
+ 'fullPath', 'full_path',
2073
+ )
2074
+ PARTIAL_LOCATION_PATH_KEYS = (
2075
+ 'route',
2076
+ 'path',
2077
+ 'pathname',
2078
+ 'normalizedPath', 'normalized_path',
2079
+ 'rawPath', 'raw_path',
2080
+ )
2081
+ LOCATION_PATH_KEYS = FULL_LOCATION_PATH_KEYS + PARTIAL_LOCATION_PATH_KEYS
2065
2082
  AFTER_STATE_KEYS = (
2066
2083
  'after', 'after_state', 'afterState',
2067
2084
  'expected_after', 'expectedAfter',
@@ -2383,6 +2400,25 @@ def failed_interaction_evidence_summary(proof_evidence):
2383
2400
  return summary
2384
2401
 
2385
2402
 
2403
+ def interaction_capture_failure_evidence_summary(proof_evidence):
2404
+ for record in proof_evidence_records_deep(proof_evidence):
2405
+ if not isinstance(record, dict):
2406
+ continue
2407
+ version = str(record.get('version') or '').strip()
2408
+ source = str(record.get('source') or '').strip()
2409
+ if version != 'riddle-proof.interaction.capture-failure.v1' and source != 'verify_capture_failure':
2410
+ continue
2411
+ summary = str(record.get('evidence_summary') or '').strip() or 'Interaction capture failed before usable authored proof evidence was emitted.'
2412
+ failures = collect_interaction_failed_assertions(record)
2413
+ if failures:
2414
+ summary += ' Failed checks: ' + ', '.join(failures[:8]) + '.'
2415
+ error = str(record.get('capture_error') or record.get('error') or '').strip()
2416
+ if error:
2417
+ summary += ' Capture script error: ' + error[:300]
2418
+ return summary
2419
+ return ''
2420
+
2421
+
2386
2422
  def interaction_terminal_path_from_evidence(proof_evidence):
2387
2423
  for record in proof_evidence_records(proof_evidence):
2388
2424
  candidate = terminal_path_from_record(record)
@@ -2428,6 +2464,23 @@ def interaction_terminal_path_from_state(state):
2428
2464
  return '', ''
2429
2465
 
2430
2466
 
2467
+ def proof_evidence_should_override_state_terminal_path(state_candidate, evidence_candidate, proof_evidence):
2468
+ if not evidence_candidate:
2469
+ return False
2470
+ if not state_candidate:
2471
+ return True
2472
+ if route_matches_expected(state_candidate, evidence_candidate):
2473
+ return False
2474
+ if interaction_assertions_pass(proof_evidence):
2475
+ return True
2476
+ for record in proof_evidence_records_deep(proof_evidence):
2477
+ if interaction_assertions_pass(record):
2478
+ return True
2479
+ if explicit_route_match_flag(record) is True:
2480
+ return True
2481
+ return False
2482
+
2483
+
2431
2484
  def expected_path_for_verify(state, start_path, proof_evidence):
2432
2485
  mode = normalized_verification_mode(state.get('verification_mode'))
2433
2486
  normalized_start = normalize_observed_path(start_path) or '/'
@@ -2445,9 +2498,14 @@ def expected_path_for_verify(state, start_path, proof_evidence):
2445
2498
  'expected_query': start_parts['query'],
2446
2499
  'expected_hash': start_parts['hash'],
2447
2500
  }
2448
- candidate, source = interaction_terminal_path_from_state(state)
2501
+ state_candidate, state_source = interaction_terminal_path_from_state(state)
2502
+ evidence_candidate, evidence_source = interaction_terminal_path_from_evidence(proof_evidence)
2503
+ if proof_evidence_should_override_state_terminal_path(state_candidate, evidence_candidate, proof_evidence):
2504
+ candidate, source = evidence_candidate, evidence_source
2505
+ else:
2506
+ candidate, source = state_candidate, state_source
2449
2507
  if not candidate:
2450
- candidate, source = interaction_terminal_path_from_evidence(proof_evidence)
2508
+ candidate, source = evidence_candidate, evidence_source
2451
2509
  expected = candidate or normalized_start
2452
2510
  expected_parts = route_parts(expected)
2453
2511
  return expected, {
@@ -2838,10 +2896,17 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
2838
2896
 
2839
2897
  if proof_evidence_blocker:
2840
2898
  reasons.append(proof_evidence_blocker)
2841
- decision = 'missing_proof_evidence'
2899
+ interaction_capture_blocker = (
2900
+ proof_evidence_blocker.startswith('Interaction capture ')
2901
+ or 'Interaction capture failed before usable authored proof evidence was emitted' in proof_evidence_blocker
2902
+ or 'Interaction capture reached a different terminal route' in proof_evidence_blocker
2903
+ )
2904
+ decision = 'failed_interaction_capture' if interaction_capture_blocker else 'missing_proof_evidence'
2842
2905
  if 'proof_evidence_present=false' in proof_evidence_blocker:
2843
2906
  decision = 'failed_proof_evidence'
2844
2907
  reasons.append('The capture reached usable page context, but the proof evidence explicitly failed its own required audio gate.')
2908
+ elif interaction_capture_blocker:
2909
+ reasons.append('The capture produced conclusive structured interaction-failure evidence, so this run should block with that specific browser evidence instead of re-authoring in a loop.')
2845
2910
  else:
2846
2911
  reasons.append('The capture reached usable page context, but the proof script did not emit the structured evidence required for this verification mode.')
2847
2912
  if route_mismatch:
@@ -2852,7 +2917,10 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
2852
2917
  (route_mismatch.get('observed_after_path') or '(unknown)') +
2853
2918
  '.'
2854
2919
  )
2855
- reasons.append('Return to author so the capture script can expose passing proof evidence before verify asks for a supervising-agent judgment.')
2920
+ if interaction_capture_blocker:
2921
+ reasons.append('Do not ask the authoring loop to infer a new route; the captured browser evidence is the terminal blocker.')
2922
+ else:
2923
+ reasons.append('Return to author so the capture script can expose passing proof evidence before verify asks for a supervising-agent judgment.')
2856
2924
  summary = proof_evidence_blocker
2857
2925
  if route_mismatch:
2858
2926
  summary += (
@@ -2868,8 +2936,10 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
2868
2936
  return {
2869
2937
  'decision': decision,
2870
2938
  'summary': summary,
2871
- 'recommended_stage': 'author',
2872
- 'continue_with_stage': 'author',
2939
+ 'recommended_stage': None if interaction_capture_blocker else 'author',
2940
+ 'continue_with_stage': None if interaction_capture_blocker else 'author',
2941
+ 'blocking': bool(interaction_capture_blocker),
2942
+ 'terminal_blocker': bool(interaction_capture_blocker),
2873
2943
  'reasons': reasons,
2874
2944
  'mismatch': route_mismatch,
2875
2945
  }
@@ -3645,12 +3715,17 @@ if proof_evidence_required_for_mode(s.get('verification_mode')):
3645
3715
  summary_lines.append('Structured proof evidence gate: ' + proof_evidence_blocker)
3646
3716
 
3647
3717
  structured_interaction_failure_summary = ''
3718
+ structured_interaction_capture_failure_summary = ''
3648
3719
  proof_evidence = evidence_bundle.get('proof_evidence')
3649
3720
  if verification_mode in INTERACTION_MODES and proof_evidence is not None:
3650
3721
  structured_interaction_failure_summary = failed_interaction_evidence_summary(proof_evidence)
3722
+ structured_interaction_capture_failure_summary = interaction_capture_failure_evidence_summary(proof_evidence)
3651
3723
  if structured_interaction_failure_summary:
3652
3724
  summary_lines.append('Structured interaction evidence gate: ' + structured_interaction_failure_summary)
3725
+ if structured_interaction_capture_failure_summary:
3726
+ summary_lines.append('Structured interaction capture blocker: ' + structured_interaction_capture_failure_summary)
3653
3727
  s['structured_interaction_failure_summary'] = structured_interaction_failure_summary
3728
+ s['structured_interaction_capture_failure_summary'] = structured_interaction_capture_failure_summary
3654
3729
 
3655
3730
  visual_delta_recovery = build_visual_delta_recovery_decision(
3656
3731
  s.get('verification_mode'),
@@ -3662,6 +3737,7 @@ if visual_delta_recovery:
3662
3737
 
3663
3738
  has_judgable_failed_interaction_evidence = (
3664
3739
  bool(structured_interaction_failure_summary)
3740
+ and not structured_interaction_capture_failure_summary
3665
3741
  and required_baseline_present
3666
3742
  and not proof_evidence_blocker
3667
3743
  and not visual_delta_recovery
@@ -3670,6 +3746,7 @@ has_good_evidence = (
3670
3746
  required_baseline_present
3671
3747
  and (after_observation.get('valid') or has_judgable_failed_interaction_evidence)
3672
3748
  and not proof_evidence_blocker
3749
+ and not structured_interaction_capture_failure_summary
3673
3750
  and not visual_delta_recovery
3674
3751
  )
3675
3752
 
@@ -3726,7 +3803,12 @@ if has_good_evidence:
3726
3803
  summary_lines.append('Proof assessment: awaiting supervising agent judgment')
3727
3804
  summary_lines.append('Proof next stage: supervising agent decides after reviewing the evidence packet')
3728
3805
  else:
3729
- capture_retry = build_capture_retry_decision(after_observation, required_baseline_present, proof_evidence_blocker, s.get('route_expectation') or {})
3806
+ capture_retry = build_capture_retry_decision(
3807
+ after_observation,
3808
+ required_baseline_present,
3809
+ proof_evidence_blocker or structured_interaction_capture_failure_summary,
3810
+ s.get('route_expectation') or {},
3811
+ )
3730
3812
  if visual_delta_recovery:
3731
3813
  observation_reason = str(after_observation.get('reason') or '')
3732
3814
  observation_details = after_observation.get('details') if isinstance(after_observation.get('details'), dict) else {}
@@ -3735,6 +3817,7 @@ else:
3735
3817
  or 'console/runtime errors' in observation_reason
3736
3818
  or (observation_details.get('capture_error_messages') or [])
3737
3819
  or proof_evidence_blocker
3820
+ or structured_interaction_capture_failure_summary
3738
3821
  )
3739
3822
  if has_primary_capture_failure:
3740
3823
  capture_retry['visual_delta_recovery'] = visual_delta_recovery
@@ -3742,6 +3825,9 @@ else:
3742
3825
  else:
3743
3826
  capture_retry = visual_delta_recovery
3744
3827
  next_stage_options = ['author', 'verify', 'recon'] if no_implementation_mode else ['author', 'verify', 'implement', 'recon']
3828
+ capture_terminal_blocker = bool(capture_retry.get('terminal_blocker') or capture_retry.get('blocking'))
3829
+ recommended_stage = None if capture_terminal_blocker else (capture_retry.get('recommended_stage') or 'author')
3830
+ continue_with_stage = None if capture_terminal_blocker else (capture_retry.get('continue_with_stage') or 'author')
3745
3831
  s['verify_status'] = 'capture_incomplete'
3746
3832
  s['merge_recommendation'] = 'do-not-merge'
3747
3833
  s['proof_assessment'] = {}
@@ -3756,8 +3842,8 @@ else:
3756
3842
  'latest_observation': after_observation,
3757
3843
  'capture_quality': capture_retry,
3758
3844
  'next_stage_options': next_stage_options,
3759
- 'recommended_stage': capture_retry.get('recommended_stage') or 'author',
3760
- 'continue_with_stage': capture_retry.get('continue_with_stage') or 'author',
3845
+ 'recommended_stage': recommended_stage,
3846
+ 'continue_with_stage': continue_with_stage,
3761
3847
  'fields_agent_may_update': ['capture_script', 'server_path', 'wait_for_selector', 'proof_plan'],
3762
3848
  'instructions': [
3763
3849
  'The after-proof evidence packet is incomplete, so use the recommended stage before proof review.',
@@ -3767,7 +3853,7 @@ else:
3767
3853
  ],
3768
3854
  }
3769
3855
  summary_lines.append('Proof assessment: not yet possible because the after capture is still incomplete')
3770
- summary_lines.append('Proof next stage: ' + str(capture_retry.get('recommended_stage') or 'author'))
3856
+ summary_lines.append('Proof next stage: blocked' if capture_terminal_blocker else 'Proof next stage: ' + str(recommended_stage or 'author'))
3771
3857
 
3772
3858
  s['verify_summary'] = '\n'.join(summary_lines)
3773
3859
  s['proof_summary'] = s['verify_summary']