@riddledc/riddle-proof 0.8.11 → 0.8.13

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-C8FDUhle.d.cts} +5 -2
  29. package/dist/{proof-run-core-CE0jx7wL.d.ts → proof-run-core-C8FDUhle.d.ts} +5 -2
  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-BomAcXhA.d.ts → proof-run-engine-By7oLsF-.d.ts} +1 -1
  35. package/dist/{proof-run-engine-B7DCPzpK.d.cts → proof-run-engine-D80hVFMf.d.cts} +1 -1
  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 +266 -22
  43. package/runtime/tests/recon_verify_smoke.py +291 -4
  44. package/runtime/tests/trust_boundary_regression.py +18 -0
@@ -325,6 +325,70 @@ class FakeRiddle:
325
325
  'proof.json': {'script_error': message},
326
326
  },
327
327
  }
328
+ if 'pricingQueryHashStructuredNegativeControl' in script:
329
+ page_state = {
330
+ 'bodyTextLength': 260,
331
+ 'visibleTextSample': 'Pricing One rate Browser Compute Example Costs',
332
+ 'interactiveElements': 8,
333
+ 'visibleInteractiveElements': 8,
334
+ 'pathname': '/pricing/',
335
+ 'search': '',
336
+ 'hash': '',
337
+ 'title': 'Pricing',
338
+ 'buttons': [],
339
+ 'headings': ['Pricing', 'Browser Compute'],
340
+ 'links': [{'text': 'Pricing', 'href': '/pricing/?rp_probe=1#pricing-probe'}],
341
+ 'canvasCount': 0,
342
+ 'largeVisibleElements': [{'tag': 'main', 'text': 'Pricing'}],
343
+ }
344
+ proof_evidence = {
345
+ 'version': 'riddle-proof.interaction.v1',
346
+ 'probe': 'query-hash-dropped-negative-control',
347
+ 'negativeControl': True,
348
+ 'routeExpectationSource': 'capture_script.expectedUrl',
349
+ 'expectedUrl': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
350
+ 'expectedHref': '/pricing/?rp_probe=1#pricing-probe',
351
+ 'intentionalObservedUrl': 'https://riddledc.com/pricing/',
352
+ 'start': {'href': 'https://riddledc.com/', 'pathname': '/', 'search': '', 'hash': ''},
353
+ 'action': {
354
+ 'type': 'rewrite-pricing-link-click-then-drop-query-hash',
355
+ 'afterClickHref': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
356
+ 'afterClickPathname': '/pricing/',
357
+ 'afterClickSearch': '?rp_probe=1',
358
+ 'afterClickHash': '#pricing-probe',
359
+ 'expectedNavigationReached': True,
360
+ },
361
+ 'terminal': {
362
+ 'href': 'https://riddledc.com/pricing/',
363
+ 'pathname': '/pricing/',
364
+ 'search': '',
365
+ 'hash': '',
366
+ },
367
+ 'assertions': {
368
+ 'expectedUrlPreserved': True,
369
+ 'expectedUrlReachedBeforeDrop': True,
370
+ 'routeExpectationSourceIsCaptureScriptExpectedUrl': True,
371
+ 'terminalIntentionallyDroppedQueryHash': True,
372
+ 'terminalUrlMismatchIsIntentional': True,
373
+ 'terminalMainVisible': True,
374
+ },
375
+ 'errors': [],
376
+ }
377
+ return {
378
+ 'ok': True,
379
+ 'screenshots': [{'url': 'https://cdn.example.com/pricing-negative-control.png'}],
380
+ 'outputs': [{'name': 'after-pricing-negative-control.png', 'url': 'https://cdn.example.com/pricing-negative-control.png'}],
381
+ 'result': {'pageState': page_state, 'proofEvidence': proof_evidence},
382
+ 'console': [
383
+ 'RIDDLE_PROOF_STATE:' + json.dumps(page_state),
384
+ 'RIDDLE_PROOF_EVIDENCE:' + json.dumps(proof_evidence),
385
+ ],
386
+ 'visual_diff': {
387
+ 'diffPercentage': 1.2,
388
+ 'differentPixels': 12000,
389
+ 'totalPixels': 972000,
390
+ },
391
+ }
328
392
  if 'pricingQueryHashPassesWithPageStateHashGap' in script:
329
393
  page_state = {
330
394
  'bodyTextLength': 260,
@@ -345,7 +409,12 @@ class FakeRiddle:
345
409
  'version': 'riddle-proof.interaction.v1',
346
410
  'start': {'href': 'https://riddledc.com/'},
347
411
  'action': {'type': 'click', 'target': 'Pricing'},
348
- 'terminal': {'href': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe'},
412
+ 'terminal': {
413
+ 'pathname': '/pricing/',
414
+ 'search': '?rp_probe=1',
415
+ 'hash': '#pricing-probe',
416
+ 'href': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
417
+ },
349
418
  'afterUrl': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
350
419
  'routeMatched': True,
351
420
  'assertions': {
@@ -480,6 +549,36 @@ class FakeRiddle:
480
549
  'proof.json': {'script_error': message},
481
550
  },
482
551
  }
552
+ if 'interactionThrownError' in script:
553
+ message = 'Error: intentional-riddle-proof-0811-thrown-error'
554
+ page_state = {
555
+ 'bodyTextLength': 180,
556
+ 'visibleTextSample': 'Riddle Proof homepage hero Start Free',
557
+ 'interactiveElements': 4,
558
+ 'visibleInteractiveElements': 4,
559
+ 'pathname': '/',
560
+ 'search': '',
561
+ 'hash': '',
562
+ 'title': 'Riddle',
563
+ 'buttons': ['Start Free'],
564
+ 'headings': ['Riddle Proof'],
565
+ 'links': [],
566
+ 'canvasCount': 0,
567
+ 'largeVisibleElements': [{'tag': 'h1', 'text': 'Riddle Proof'}],
568
+ }
569
+ return {
570
+ 'ok': True,
571
+ 'screenshots': [{'url': 'https://cdn.example.com/thrown-error.png'}],
572
+ 'outputs': [{'name': 'after-thrown-error.png', 'url': 'https://cdn.example.com/thrown-error.png'}],
573
+ 'result': {'pageState': page_state},
574
+ 'console': [
575
+ 'RIDDLE_PROOF_STATE:' + json.dumps(page_state),
576
+ 'Uncaught exception: ' + message,
577
+ ],
578
+ '_artifact_json': {
579
+ 'proof.json': {'script_error': message},
580
+ },
581
+ }
483
582
  if 'after-proof' in script:
484
583
  after_url = 'https://cdn.example.com/after-artifact' if 'noVisualDelta' in script else 'https://cdn.example.com/after.png'
485
584
  outputs = [{'name': 'after.png', 'url': after_url}]
@@ -2568,6 +2667,58 @@ def run_verify_interaction_terminal_route_from_proof_evidence():
2568
2667
  shutil.rmtree(tempdir, ignore_errors=True)
2569
2668
 
2570
2669
 
2670
+ def run_verify_interaction_proof_evidence_overrides_stale_expected_path():
2671
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-stale-route-'))
2672
+ state_path = tempdir / 'state.json'
2673
+ try:
2674
+ state = base_state(tempdir, reference='before')
2675
+ state.update({
2676
+ 'recon_status': 'ready_for_proof_plan',
2677
+ 'author_status': 'ready',
2678
+ 'proof_plan_status': 'ready',
2679
+ 'implementation_status': 'changes_detected',
2680
+ 'verification_mode': 'interaction',
2681
+ 'server_path': '/',
2682
+ 'expected_terminal_path': '/state',
2683
+ 'before_cdn': 'https://cdn.example.com/before-home.png',
2684
+ 'proof_plan': 'Start at /, click Proof, and verify the terminal /proof/ route.',
2685
+ 'capture_script': "clickedProofNavigation(); await saveScreenshot('after-proof');",
2686
+ 'supervisor_author_packet': {
2687
+ 'proof_plan': 'Click Proof and prove the terminal route.',
2688
+ 'capture_script': "clickedProofNavigation(); await saveScreenshot('after-proof');",
2689
+ 'refined_inputs': {
2690
+ 'server_path': '/',
2691
+ 'expected_terminal_path': '/state',
2692
+ },
2693
+ },
2694
+ 'recon_results': {
2695
+ 'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
2696
+ },
2697
+ })
2698
+ write_state(state_path, state)
2699
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
2700
+
2701
+ fake = FakeRiddle()
2702
+ load_util_with_fake(fake)
2703
+ load_module('verify_interaction_stale_route_uses_evidence', VERIFY_PATH)
2704
+ after_verify = json.loads(state_path.read_text())
2705
+
2706
+ assert after_verify['verify_status'] == 'evidence_captured'
2707
+ assert after_verify['route_expectation']['source'] == 'proof_evidence_contract'
2708
+ assert after_verify['route_expectation']['expected_path'] == '/proof'
2709
+ route = after_verify['proof_assessment_request']['semantic_context']['route']
2710
+ assert route['expected_after_path'] == '/proof'
2711
+ assert route['after_observed_path'] == '/proof'
2712
+ assert 'wrong route' not in after_verify['verify_results']['after']['observation']['reason']
2713
+ return {
2714
+ 'ok': True,
2715
+ 'expected_path': after_verify['route_expectation']['expected_path'],
2716
+ 'source': after_verify['route_expectation']['source'],
2717
+ }
2718
+ finally:
2719
+ shutil.rmtree(tempdir, ignore_errors=True)
2720
+
2721
+
2571
2722
  def run_verify_interaction_reverse_terminal_route_from_proof_evidence():
2572
2723
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-reverse-'))
2573
2724
  state_path = tempdir / 'state.json'
@@ -2751,9 +2902,11 @@ def run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence():
2751
2902
  assert after_verify['route_expectation']['expected_query'] == 'rp_probe=1'
2752
2903
  assert after_verify['route_expectation']['expected_hash'] == '#pricing-probe'
2753
2904
  capture_quality = request['capture_quality']
2754
- assert capture_quality['decision'] in ('revise_capture', 'failed_proof_evidence', 'visual_delta_unmeasured')
2755
- assert request['recommended_stage'] in ('author', 'verify')
2756
- assert request['continue_with_stage'] in ('author', 'verify')
2905
+ assert capture_quality['decision'] == 'failed_interaction_capture'
2906
+ assert request['recommended_stage'] is None
2907
+ assert request['continue_with_stage'] is None
2908
+ assert capture_quality['blocking'] is True
2909
+ assert capture_quality['terminal_blocker'] is True
2757
2910
  quality_text = json.dumps(capture_quality, sort_keys=True)
2758
2911
  assert 'page.waitForURL: Timeout 15000ms exceeded' in quality_text
2759
2912
  assert after_verify['proof_assessment_request'] == {}
@@ -2778,6 +2931,7 @@ def run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence():
2778
2931
  'ok': True,
2779
2932
  'summary': request['summary'],
2780
2933
  'recommended_stage': request['recommended_stage'],
2934
+ 'blocking': capture_quality['blocking'],
2781
2935
  }
2782
2936
  finally:
2783
2937
  shutil.rmtree(tempdir, ignore_errors=True)
@@ -2844,6 +2998,136 @@ def run_verify_interaction_query_hash_pass_uses_proof_evidence_route():
2844
2998
  shutil.rmtree(tempdir, ignore_errors=True)
2845
2999
 
2846
3000
 
3001
+ def run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route():
3002
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-explicit-expected-url-mismatch-'))
3003
+ state_path = tempdir / 'state.json'
3004
+ try:
3005
+ state = base_state(tempdir, reference='before')
3006
+ state.update({
3007
+ 'recon_status': 'ready_for_proof_plan',
3008
+ 'author_status': 'ready',
3009
+ 'proof_plan_status': 'ready',
3010
+ 'implementation_status': 'changes_detected',
3011
+ 'verification_mode': 'interaction',
3012
+ 'server_path': '/',
3013
+ 'before_cdn': 'https://cdn.example.com/before-home.png',
3014
+ 'proof_plan': 'Start at /, click Pricing, and intentionally prove the query/hash route mismatch.',
3015
+ 'capture_script': "pricingQueryHashStructuredNegativeControl();",
3016
+ 'supervisor_author_packet': {
3017
+ 'proof_plan': 'Use expectedUrl as the route expectation and return structured evidence for the dropped query/hash terminal URL.',
3018
+ 'capture_script': "pricingQueryHashStructuredNegativeControl();",
3019
+ 'refined_inputs': {
3020
+ 'server_path': '/',
3021
+ },
3022
+ },
3023
+ 'recon_results': {
3024
+ 'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
3025
+ },
3026
+ })
3027
+ write_state(state_path, state)
3028
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
3029
+
3030
+ fake = FakeRiddle()
3031
+ load_util_with_fake(fake)
3032
+ load_module('verify_interaction_explicit_expected_url_blocks_dropped_terminal_route', VERIFY_PATH)
3033
+ after_verify = json.loads(state_path.read_text())
3034
+
3035
+ request = after_verify['verify_decision_request']
3036
+ assert after_verify['verify_status'] == 'capture_incomplete'
3037
+ assert after_verify['merge_recommendation'] == 'do-not-merge'
3038
+ assert after_verify['route_expectation']['source'] == 'proof_evidence_contract'
3039
+ assert after_verify['route_expectation']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
3040
+ assert after_verify['route_expectation']['expected_query'] == 'rp_probe=1'
3041
+ assert after_verify['route_expectation']['expected_hash'] == '#pricing-probe'
3042
+ assert request['recommended_stage'] is None
3043
+ assert request['continue_with_stage'] is None
3044
+ capture_quality = request['capture_quality']
3045
+ assert capture_quality['decision'] == 'failed_interaction_capture'
3046
+ assert capture_quality['blocking'] is True
3047
+ assert capture_quality['terminal_blocker'] is True
3048
+ assert capture_quality['mismatch']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
3049
+ assert capture_quality['mismatch']['observed_after_path'] in ('/pricing', '/pricing/')
3050
+ assert 'Interaction proof terminal route mismatch' in capture_quality['summary']
3051
+ assert after_verify['proof_assessment_request'] == {}
3052
+ observation = request['latest_observation']
3053
+ assert observation['valid'] is False
3054
+ assert 'wrong route' in observation['reason']
3055
+ supporting = after_verify['verify_results']['after']['supporting_artifacts']
3056
+ assert supporting['proof_evidence_present'] is True
3057
+ assert supporting['has_structured_payload'] is True
3058
+ route = after_verify['evidence_bundle']['semantic_context']['route']
3059
+ assert route['expected_terminal_query'] == 'rp_probe=1'
3060
+ assert route['expected_terminal_hash'] == '#pricing-probe'
3061
+ assert route['after_observed_path'] == '/pricing'
3062
+ assert route['after_observed_query'] == ''
3063
+ assert route['after_observed_hash'] == ''
3064
+ return {
3065
+ 'ok': True,
3066
+ 'decision': capture_quality['decision'],
3067
+ 'summary': capture_quality['summary'],
3068
+ }
3069
+ finally:
3070
+ shutil.rmtree(tempdir, ignore_errors=True)
3071
+
3072
+
3073
+ def run_verify_interaction_thrown_error_terminal_blocker():
3074
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-thrown-error-'))
3075
+ state_path = tempdir / 'state.json'
3076
+ try:
3077
+ state = base_state(tempdir, reference='before')
3078
+ state.update({
3079
+ 'recon_status': 'ready_for_proof_plan',
3080
+ 'author_status': 'ready',
3081
+ 'proof_plan_status': 'ready',
3082
+ 'implementation_status': 'changes_detected',
3083
+ 'verification_mode': 'interaction',
3084
+ 'server_path': '/',
3085
+ 'before_cdn': 'https://cdn.example.com/before-home.png',
3086
+ 'proof_plan': 'Run a diagnostic interaction script that intentionally throws.',
3087
+ 'capture_script': "interactionThrownError();",
3088
+ 'recon_results': {
3089
+ 'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
3090
+ },
3091
+ })
3092
+ write_state(state_path, state)
3093
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
3094
+
3095
+ fake = FakeRiddle()
3096
+ load_util_with_fake(fake)
3097
+ load_module('verify_interaction_thrown_error_terminal_blocker', VERIFY_PATH)
3098
+ after_verify = json.loads(state_path.read_text())
3099
+
3100
+ assert after_verify['verify_status'] == 'capture_incomplete'
3101
+ assert after_verify['merge_recommendation'] == 'do-not-merge'
3102
+ assert after_verify['proof_assessment_request'] == {}
3103
+ capture_quality = after_verify['verify_decision_request']['capture_quality']
3104
+ assert capture_quality['decision'] == 'failed_interaction_capture'
3105
+ assert capture_quality['recommended_stage'] is None
3106
+ assert capture_quality['continue_with_stage'] is None
3107
+ assert capture_quality['blocking'] is True
3108
+ assert capture_quality['terminal_blocker'] is True
3109
+ capture_quality_text = json.dumps(capture_quality, sort_keys=True)
3110
+ assert 'intentional-riddle-proof-0811-thrown-error' in capture_quality_text
3111
+ assert after_verify['structured_interaction_capture_failure_summary']
3112
+ evidence = after_verify['evidence_bundle']['proof_evidence']
3113
+ if isinstance(evidence, list):
3114
+ evidence = next(
3115
+ record for record in evidence_records(evidence)
3116
+ if record.get('version') == 'riddle-proof.interaction.capture-failure.v1'
3117
+ )
3118
+ assert evidence['version'] == 'riddle-proof.interaction.capture-failure.v1'
3119
+ assert evidence['checks']['scriptCompleted'] is False
3120
+ assert evidence['checks']['authoredEvidenceReturned'] is False
3121
+ assert 'intentional-riddle-proof-0811-thrown-error' in evidence['capture_error']
3122
+ return {
3123
+ 'ok': True,
3124
+ 'decision': capture_quality['decision'],
3125
+ 'blocking': capture_quality['blocking'],
3126
+ }
3127
+ finally:
3128
+ shutil.rmtree(tempdir, ignore_errors=True)
3129
+
3130
+
2847
3131
  def run_verify_capture_retry_surfaces_script_timeout():
2848
3132
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-capture-timeout-'))
2849
3133
  state_path = tempdir / 'state.json'
@@ -3262,11 +3546,14 @@ if __name__ == '__main__':
3262
3546
  'verify_capture_retry': run_verify_capture_retry(),
3263
3547
  'remote_audit_verify_uses_default_capture_script': run_remote_audit_verify_uses_default_capture_script(),
3264
3548
  'verify_interaction_terminal_route_from_proof_evidence': run_verify_interaction_terminal_route_from_proof_evidence(),
3549
+ 'verify_interaction_proof_evidence_overrides_stale_expected_path': run_verify_interaction_proof_evidence_overrides_stale_expected_path(),
3265
3550
  'verify_interaction_reverse_terminal_route_from_proof_evidence': run_verify_interaction_reverse_terminal_route_from_proof_evidence(),
3266
3551
  'verify_interaction_prose_route_noise_uses_proof_evidence': run_verify_interaction_prose_route_noise_uses_proof_evidence(),
3267
3552
  'verify_interaction_hash_terminal_route_from_proof_evidence': run_verify_interaction_hash_terminal_route_from_proof_evidence(),
3268
3553
  'verify_interaction_authored_query_hash_mismatch_blocks_with_evidence': run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence(),
3269
3554
  'verify_interaction_query_hash_pass_uses_proof_evidence_route': run_verify_interaction_query_hash_pass_uses_proof_evidence_route(),
3555
+ 'verify_interaction_explicit_expected_url_blocks_dropped_terminal_route': run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route(),
3556
+ 'verify_interaction_thrown_error_terminal_blocker': run_verify_interaction_thrown_error_terminal_blocker(),
3270
3557
  'verify_capture_retry_surfaces_script_timeout': run_verify_capture_retry_surfaces_script_timeout(),
3271
3558
  'missing_baseline_guard': run_verify_missing_baseline(),
3272
3559
  'ship_supervisor_gate': run_ship_missing_supervisor_gate(),
@@ -25,6 +25,12 @@ CASES = [
25
25
  'function': 'run_verify_interaction_terminal_route_from_proof_evidence',
26
26
  'expected_terminal': 'pass',
27
27
  },
28
+ {
29
+ 'name': 'route-change-retry-state-drift-ignored',
30
+ 'covers': ['route-changing interactions', 'proof-evidence-present'],
31
+ 'function': 'run_verify_interaction_proof_evidence_overrides_stale_expected_path',
32
+ 'expected_terminal': 'pass',
33
+ },
28
34
  {
29
35
  'name': 'route-change-reverse-pass',
30
36
  'covers': ['route-changing interactions'],
@@ -49,6 +55,12 @@ CASES = [
49
55
  'function': 'run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence',
50
56
  'expected_terminal': 'specific_blocker',
51
57
  },
58
+ {
59
+ 'name': 'query-hash-dropped-structured-negative-blocker',
60
+ 'covers': ['query/hash/trailing-slash URLs', 'invalid browser evidence', 'proof-evidence-present'],
61
+ 'function': 'run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route',
62
+ 'expected_terminal': 'specific_blocker',
63
+ },
52
64
  {
53
65
  'name': 'same-page-hash-pass',
54
66
  'covers': ['same-page hashes'],
@@ -67,6 +79,12 @@ CASES = [
67
79
  'function': 'run_verify_preserves_proof_evidence_on_capture_script_error',
68
80
  'expected_terminal': 'specific_blocker',
69
81
  },
82
+ {
83
+ 'name': 'interaction-thrown-error-specific-blocker',
84
+ 'covers': ['thrown errors', 'invalid browser evidence'],
85
+ 'function': 'run_verify_interaction_thrown_error_terminal_blocker',
86
+ 'expected_terminal': 'specific_blocker',
87
+ },
70
88
  {
71
89
  'name': 'structured-proof-without-screenshot-pass',
72
90
  'covers': ['proof-evidence-present'],