@riddledc/riddle-proof 0.5.19 → 0.5.21

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -10,8 +10,8 @@ The calling agent owns the planner step between attempts by resuming the workflo
10
10
  with updated state inputs such as server_path or wait_for_selector.
11
11
  """
12
12
 
13
- import json, os, re, sys
14
- from urllib.parse import urlparse
13
+ import json, os, re, sys, time
14
+ from urllib.parse import parse_qsl, urlparse
15
15
 
16
16
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
17
  from util import ( # noqa: E402
@@ -44,6 +44,53 @@ if not after_dir or not os.path.exists(after_dir):
44
44
  raise SystemExit('after_worktree not found. Run setup first.')
45
45
 
46
46
 
47
+ def record_recon_phase(phase, status='running', summary=''):
48
+ global s
49
+ ts = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
50
+ try:
51
+ current = load_state()
52
+ except Exception:
53
+ current = dict(s)
54
+ runtime_step = current.get('current_runtime_step') if isinstance(current.get('current_runtime_step'), dict) else {}
55
+ if not runtime_step:
56
+ runtime_step = {
57
+ 'step': 'recon',
58
+ 'action': 'run',
59
+ 'status': 'running',
60
+ 'started_at': ts,
61
+ 'workflow_file': 'riddle-proof-recon.lobster',
62
+ }
63
+ runtime_step['step'] = 'recon'
64
+ runtime_step['action'] = 'run'
65
+ runtime_step['status'] = 'running'
66
+ runtime_step['workflow_file'] = 'riddle-proof-recon.lobster'
67
+ runtime_step['phase'] = phase
68
+ runtime_step['phase_status'] = status
69
+ if status == 'running':
70
+ runtime_step['phase_started_at'] = ts
71
+ runtime_step.pop('phase_finished_at', None)
72
+ else:
73
+ runtime_step['phase_finished_at'] = ts
74
+ if summary:
75
+ runtime_step['summary'] = summary
76
+ current['current_runtime_step'] = runtime_step
77
+ events = current.get('runtime_events') if isinstance(current.get('runtime_events'), list) else []
78
+ events.append({
79
+ 'ts': ts,
80
+ 'kind': 'workflow.phase.' + ('started' if status == 'running' else 'finished'),
81
+ 'step': 'recon',
82
+ 'phase': phase,
83
+ 'summary': summary or (phase + ' ' + status),
84
+ 'details': {'status': status},
85
+ })
86
+ current['runtime_events'] = events[-100:]
87
+ current['runtime_updated_at'] = ts
88
+ save_state(current)
89
+ for key in ('current_runtime_step', 'runtime_events', 'runtime_updated_at'):
90
+ if key in current:
91
+ s[key] = current[key]
92
+
93
+
47
94
  def run(cmd, cwd, timeout=30):
48
95
  return sp.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True, timeout=timeout)
49
96
 
@@ -208,7 +255,7 @@ def route_candidates(route_hints, prod_url=''):
208
255
 
209
256
  parsed_prod = urlparse((prod_url or '').strip())
210
257
  if parsed_prod.path and parsed_prod.path.strip() and parsed_prod.path != '/':
211
- add(parsed_prod.path, 'prod_url path')
258
+ add(parsed_prod.path + (('?' + parsed_prod.query) if parsed_prod.query else ''), 'prod_url path')
212
259
 
213
260
  for hint in route_hints:
214
261
  code_fragment = hint.split(':', 2)[-1]
@@ -377,17 +424,54 @@ def has_enriched_page_state(page_state):
377
424
 
378
425
 
379
426
  def normalize_observed_path(value):
380
- path = (value or '').strip()
381
- if not path:
427
+ raw = str(value or '').strip()
428
+ if not raw:
382
429
  return ''
383
- path = path.split('?', 1)[0].split('#', 1)[0]
430
+ parsed = urlparse(raw.split('#', 1)[0])
431
+ path = parsed.path or ''
432
+ query = parsed.query or ''
384
433
  if not path.startswith('/'):
385
- parsed = urlparse(path)
386
- path = parsed.path or path
434
+ path = '/' + path.lstrip('/')
387
435
  parts = path.split('/')
388
436
  if len(parts) >= 4 and parts[1] == 's':
389
437
  path = '/' + '/'.join(parts[3:])
390
- return path.rstrip('/') or '/'
438
+ path = path.rstrip('/') or '/'
439
+ return path + (('?' + query) if query else '')
440
+
441
+
442
+ def observed_location_from_page_state(page_state):
443
+ if not isinstance(page_state, dict):
444
+ return ''
445
+ pathname = str(page_state.get('pathname') or '').strip()
446
+ search = str(page_state.get('search') or '').strip()
447
+ if search and not search.startswith('?'):
448
+ search = '?' + search
449
+ if pathname:
450
+ return pathname + search
451
+ return str(page_state.get('href') or '').strip()
452
+
453
+
454
+ def route_matches_expected(expected_path, observed_path):
455
+ expected = normalize_observed_path(expected_path)
456
+ observed = normalize_observed_path(observed_path)
457
+ if not expected or not observed:
458
+ return False
459
+ expected_parsed = urlparse(expected)
460
+ observed_parsed = urlparse(observed)
461
+ expected_pathname = expected_parsed.path.rstrip('/') or '/'
462
+ observed_pathname = observed_parsed.path.rstrip('/') or '/'
463
+ if observed_pathname != expected_pathname:
464
+ return False
465
+ expected_query = parse_qsl(expected_parsed.query, keep_blank_values=True)
466
+ if not expected_query:
467
+ return True
468
+ observed_query = parse_qsl(observed_parsed.query, keep_blank_values=True)
469
+ remaining = list(observed_query)
470
+ for pair in expected_query:
471
+ if pair not in remaining:
472
+ return False
473
+ remaining.remove(pair)
474
+ return True
391
475
 
392
476
 
393
477
  def build_probe_capture_script(base_script='', screenshot_label=''):
@@ -424,6 +508,8 @@ def build_probe_capture_script(base_script='', screenshot_label=''):
424
508
  ' canvasCount: document.querySelectorAll("canvas").length,',
425
509
  ' largeVisibleElements,',
426
510
  ' pathname: window.location.pathname,',
511
+ ' search: window.location.search,',
512
+ ' href: window.location.href,',
427
513
  ' title: document.title,',
428
514
  ' };',
429
515
  '});',
@@ -470,7 +556,7 @@ def evaluate_capture_quality(payload, expected_path):
470
556
 
471
557
  page_state = extract_page_state(payload)
472
558
  if isinstance(page_state, dict):
473
- raw_observed_path = page_state.get('pathname', '')
559
+ raw_observed_path = observed_location_from_page_state(page_state)
474
560
  details.update({
475
561
  'body_text_length': page_state.get('bodyTextLength', 0),
476
562
  'interactive_elements': page_state.get('interactiveElements', 0),
@@ -520,8 +606,7 @@ def evaluate_capture_quality(payload, expected_path):
520
606
  reasons.append('page has console/runtime errors')
521
607
 
522
608
  observed_path = normalize_observed_path(details.get('observed_path'))
523
- normalized_expected = (expected_path or '').rstrip('/') or '/'
524
- if expected_path and observed_path and observed_path != normalized_expected:
609
+ if expected_path and observed_path and not route_matches_expected(expected_path, observed_path):
525
610
  raw_observed = details.get('observed_path_raw') or details.get('observed_path') or observed_path
526
611
  reasons.append(f'wrong route: expected {expected_path}, got {raw_observed}')
527
612
 
@@ -549,10 +634,14 @@ def clean_next_cache(project_dir):
549
634
 
550
635
  def build_project(project_dir, label):
551
636
  build_cmd = s.get('build_command', 'npm run build')
637
+ phase = label + '_build'
638
+ record_recon_phase(phase, 'running', 'Building ' + label + ' workspace for recon capture.')
552
639
  print('Building ' + label + ' workspace...')
553
640
  result = sp.run(build_cmd, shell=True, cwd=project_dir, capture_output=True, text=True, timeout=600)
554
641
  if result.returncode != 0:
642
+ record_recon_phase(phase, 'failed', label.capitalize() + ' build failed during recon.')
555
643
  raise SystemExit(label.capitalize() + ' build failed during recon: ' + result.stderr[:500])
644
+ record_recon_phase(phase, 'completed', label.capitalize() + ' workspace build completed for recon.')
556
645
  return {
557
646
  'stdout': result.stdout[-500:],
558
647
  'stderr': result.stderr[-500:],
@@ -589,8 +678,11 @@ def capture_workspace_baseline(project_dir, label, plan, capture_script=''):
589
678
  server_args['wait_for_selector'] = wait_for_selector
590
679
  apply_auth_context(s, server_args)
591
680
 
681
+ capture_phase = label + '_capture'
682
+ record_recon_phase(capture_phase, 'running', 'Capturing ' + label + ' recon baseline evidence.')
592
683
  shot = invoke_retry('riddle_server_preview', server_args, retries=2, timeout=420)
593
684
  append_capture_diagnostic(s, label, 'riddle_server_preview', server_args, shot)
685
+ record_recon_phase(capture_phase, 'completed', label.capitalize() + ' recon baseline capture completed.')
594
686
  return {
595
687
  'source': label + '_worktree',
596
688
  'mode': 'server',
@@ -606,6 +698,8 @@ def capture_workspace_baseline(project_dir, label, plan, capture_script=''):
606
698
  state_for_capture['wait_for_selector'] = wait_for_selector
607
699
  if static_reason:
608
700
  print('Recon capture (' + label + ') using static preview fallback: ' + static_reason)
701
+ capture_phase = label + '_capture'
702
+ record_recon_phase(capture_phase, 'running', 'Capturing ' + label + ' recon baseline evidence.')
609
703
  capture = capture_static_preview(state_for_capture, project_dir, label, build_probe_capture_script(capture_script, label), timeout=300, target_path=target_path)
610
704
  raw = (capture.get('raw') or {}).get('capture') or {}
611
705
  append_capture_diagnostic(
@@ -615,6 +709,7 @@ def capture_workspace_baseline(project_dir, label, plan, capture_script=''):
615
709
  {'target_path': target_path, 'static_fallback_reason': static_reason},
616
710
  raw,
617
711
  )
712
+ record_recon_phase(capture_phase, 'completed', label.capitalize() + ' recon baseline capture completed.')
618
713
  preview_id_key = label + '_preview_id'
619
714
  if capture.get('preview_id'):
620
715
  s[preview_id_key] = capture.get('preview_id', '')
@@ -639,12 +734,14 @@ def capture_prod_baseline(prod_url, plan, capture_script=''):
639
734
  args = {'script': script, 'timeout_sec': 60}
640
735
  apply_auth_context(s, args)
641
736
  print('Recon capture (prod) at ' + target_url)
737
+ record_recon_phase('prod_capture', 'running', 'Capturing production recon baseline evidence.')
642
738
  shot = invoke_retry('riddle_script', args, retries=3, timeout=180)
643
739
  append_capture_diagnostic(s, 'prod', 'riddle_script', args, shot)
740
+ record_recon_phase('prod_capture', 'completed', 'Production recon baseline capture completed.')
644
741
  return {
645
742
  'source': 'prod_url',
646
743
  'mode': 'remote',
647
- 'path': urlparse(target_url).path or (plan.get('target_path') or '/'),
744
+ 'path': normalize_observed_path(target_url) or (plan.get('target_path') or '/'),
648
745
  'capture_url': target_url,
649
746
  'url': extract_screenshot_url(shot, 'prod'),
650
747
  'static_fallback_reason': '',
@@ -8,6 +8,7 @@ must assess before the wrapper routes back into author/implement/recon work or s
8
8
  """
9
9
 
10
10
  import json, os, sys, time
11
+ from urllib.parse import parse_qsl, urlparse
11
12
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12
13
  from util import (
13
14
  append_capture_diagnostic,
@@ -248,6 +249,8 @@ def build_probe_capture_script(base_script='', verification_mode='proof'):
248
249
  ' canvasCount: document.querySelectorAll("canvas").length,',
249
250
  ' largeVisibleElements,',
250
251
  ' pathname: window.location.pathname,',
252
+ ' search: window.location.search,',
253
+ ' href: window.location.href,',
251
254
  ' title: document.title,',
252
255
  ' };',
253
256
  '});',
@@ -575,16 +578,54 @@ def has_enriched_page_state(page_state):
575
578
 
576
579
 
577
580
  def normalize_observed_path(value):
578
- path = (value or '').strip()
579
- if not path:
581
+ raw = str(value or '').strip()
582
+ if not raw:
580
583
  return ''
581
- path = path.split('?', 1)[0].split('#', 1)[0]
584
+ parsed = urlparse(raw.split('#', 1)[0])
585
+ path = parsed.path or ''
586
+ query = parsed.query or ''
582
587
  if not path.startswith('/'):
583
588
  path = '/' + path.lstrip('/')
584
589
  parts = path.split('/')
585
590
  if len(parts) >= 4 and parts[1] == 's':
586
591
  path = '/' + '/'.join(parts[3:])
587
- return path.rstrip('/') or '/'
592
+ path = path.rstrip('/') or '/'
593
+ return path + (('?' + query) if query else '')
594
+
595
+
596
+ def observed_location_from_page_state(page_state):
597
+ if not isinstance(page_state, dict):
598
+ return ''
599
+ pathname = str(page_state.get('pathname') or '').strip()
600
+ search = str(page_state.get('search') or '').strip()
601
+ if search and not search.startswith('?'):
602
+ search = '?' + search
603
+ if pathname:
604
+ return pathname + search
605
+ return str(page_state.get('href') or '').strip()
606
+
607
+
608
+ def route_matches_expected(expected_path, observed_path):
609
+ expected = normalize_observed_path(expected_path)
610
+ observed = normalize_observed_path(observed_path)
611
+ if not expected or not observed:
612
+ return False
613
+ expected_parsed = urlparse(expected)
614
+ observed_parsed = urlparse(observed)
615
+ expected_pathname = expected_parsed.path.rstrip('/') or '/'
616
+ observed_pathname = observed_parsed.path.rstrip('/') or '/'
617
+ if observed_pathname != expected_pathname:
618
+ return False
619
+ expected_query = parse_qsl(expected_parsed.query, keep_blank_values=True)
620
+ if not expected_query:
621
+ return True
622
+ observed_query = parse_qsl(observed_parsed.query, keep_blank_values=True)
623
+ remaining = list(observed_query)
624
+ for pair in expected_query:
625
+ if pair not in remaining:
626
+ return False
627
+ remaining.remove(pair)
628
+ return True
588
629
 
589
630
 
590
631
  def collect_supporting_artifacts(payload):
@@ -767,7 +808,7 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
767
808
 
768
809
  page_state = extract_page_state(payload)
769
810
  if isinstance(page_state, dict):
770
- raw_observed_path = page_state.get('pathname', '')
811
+ raw_observed_path = observed_location_from_page_state(page_state)
771
812
  details.update({
772
813
  'body_text_length': page_state.get('bodyTextLength', 0),
773
814
  'interactive_elements': page_state.get('interactiveElements', 0),
@@ -830,8 +871,7 @@ def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
830
871
  reasons.append('page has console/runtime errors')
831
872
 
832
873
  observed_path = normalize_observed_path(details.get('observed_path'))
833
- normalized_expected = (expected_path or '').rstrip('/') or '/'
834
- if isinstance(page_state, dict) and expected_path and observed_path and observed_path != normalized_expected:
874
+ if isinstance(page_state, dict) and expected_path and observed_path and not route_matches_expected(expected_path, observed_path):
835
875
  raw_observed = details.get('observed_path_raw') or details.get('observed_path') or observed_path
836
876
  reasons.append(f'wrong route: expected {expected_path}, got {raw_observed}')
837
877
 
@@ -182,6 +182,27 @@ class FakeRiddle:
182
182
  'outputs': [{'name': 'prod.png', 'url': 'https://cdn.example.com/prod.png'}],
183
183
  'console': ['RIDDLE_PROOF_STATE:{"bodyTextLength":180,"interactiveElements":4,"pathname":"/pricing","title":"Prod"}'],
184
184
  }
185
+ if 'prod.example.com/games/drum-sequencer' in script:
186
+ page_state = {
187
+ 'bodyTextLength': 240,
188
+ 'visibleTextSample': 'Neon Step Sequencer Monkberry Moon Delight Mix Board Play All',
189
+ 'interactiveElements': 8,
190
+ 'visibleInteractiveElements': 8,
191
+ 'pathname': '/games/drum-sequencer',
192
+ 'search': '?song=monkberry-moon-delight-tab&mix=profile',
193
+ 'title': 'Neon Step Sequencer',
194
+ 'buttons': ['Play All', 'Shuffle'],
195
+ 'headings': ['Neon Step Sequencer'],
196
+ 'links': [],
197
+ 'canvasCount': 1,
198
+ 'largeVisibleElements': [{'tag': 'canvas', 'text': ''}],
199
+ }
200
+ return {
201
+ 'ok': True,
202
+ 'screenshots': [{'url': 'https://cdn.example.com/sequencer-prod.png'}],
203
+ 'outputs': [{'name': 'prod.png', 'url': 'https://cdn.example.com/sequencer-prod.png'}],
204
+ 'console': ['RIDDLE_PROOF_STATE:' + json.dumps(page_state)],
205
+ }
185
206
  if 'preview.example.com' in script and '/pricing' in script:
186
207
  return {
187
208
  'ok': True,
@@ -189,6 +210,27 @@ class FakeRiddle:
189
210
  'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/before.png'}],
190
211
  'console': ['RIDDLE_PROOF_STATE:{"bodyTextLength":180,"interactiveElements":4,"pathname":"/pricing","title":"Before"}'],
191
212
  }
213
+ if 'preview.example.com' in script and '/games/drum-sequencer' in script:
214
+ page_state = {
215
+ 'bodyTextLength': 240,
216
+ 'visibleTextSample': 'Neon Step Sequencer Monkberry Moon Delight Mix Board Play All',
217
+ 'interactiveElements': 8,
218
+ 'visibleInteractiveElements': 8,
219
+ 'pathname': '/s/pv-before/games/drum-sequencer',
220
+ 'search': '?song=monkberry-moon-delight-tab&mix=profile',
221
+ 'title': 'Neon Step Sequencer',
222
+ 'buttons': ['Play All', 'Shuffle'],
223
+ 'headings': ['Neon Step Sequencer'],
224
+ 'links': [],
225
+ 'canvasCount': 1,
226
+ 'largeVisibleElements': [{'tag': 'canvas', 'text': ''}],
227
+ }
228
+ return {
229
+ 'ok': True,
230
+ 'screenshots': [{'url': 'https://cdn.example.com/sequencer-before.png'}],
231
+ 'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/sequencer-before.png'}],
232
+ 'console': state_console(page_state),
233
+ }
192
234
  if 'preview.example.com' in script and '/games/tic-tac-toe' in script:
193
235
  return {
194
236
  'ok': True,
@@ -408,6 +450,35 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
408
450
  assert runtime_error_quality['details']['has_errors'] is True
409
451
  assert 'console/runtime errors' in runtime_error_quality['reason']
410
452
 
453
+ query_payload = dict(telemetry_payload)
454
+ query_payload.update({
455
+ 'visibleTextSample': 'Neon Step Sequencer Monkberry Moon Delight Mix Board Play All',
456
+ 'pathname': '/s/pv-after/games/drum-sequencer',
457
+ 'search': '?song=monkberry-moon-delight-tab&mix=profile&utm_source=test',
458
+ 'title': 'Neon Step Sequencer',
459
+ 'headings': ['Neon Step Sequencer'],
460
+ 'buttons': ['Play All'],
461
+ 'canvasCount': 1,
462
+ })
463
+ query_quality = namespace['evaluate_capture_quality']({
464
+ 'ok': True,
465
+ 'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
466
+ 'outputs': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
467
+ 'console': ['RIDDLE_PROOF_STATE:' + json.dumps(query_payload)],
468
+ }, '/games/drum-sequencer?mix=profile&song=monkberry-moon-delight-tab', 'visual')
469
+ assert query_quality['valid'] is True, query_quality
470
+ assert query_quality['details']['observed_path'] == '/games/drum-sequencer?song=monkberry-moon-delight-tab&mix=profile&utm_source=test'
471
+ assert query_quality['details']['observed_path_raw'] == '/s/pv-after/games/drum-sequencer?song=monkberry-moon-delight-tab&mix=profile&utm_source=test'
472
+
473
+ missing_query_quality = namespace['evaluate_capture_quality']({
474
+ 'ok': True,
475
+ 'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
476
+ 'outputs': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
477
+ 'console': ['RIDDLE_PROOF_STATE:' + json.dumps({**query_payload, 'search': '?song=monkberry-moon-delight-tab'})],
478
+ }, '/games/drum-sequencer?mix=profile&song=monkberry-moon-delight-tab', 'visual')
479
+ assert missing_query_quality['valid'] is False
480
+ assert 'wrong route' in missing_query_quality['reason']
481
+
411
482
  strong_delta = namespace['extract_visual_delta']({
412
483
  'ok': True,
413
484
  'result': {
@@ -659,6 +730,49 @@ def run_recon_then_author_request():
659
730
  shutil.rmtree(tempdir, ignore_errors=True)
660
731
 
661
732
 
733
+ def run_recon_preserves_query_route():
734
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-query-route-'))
735
+ state_path = tempdir / 'state.json'
736
+ try:
737
+ query_route = '/games/drum-sequencer?song=monkberry-moon-delight-tab&mix=profile'
738
+ state = base_state(
739
+ tempdir,
740
+ reference='both',
741
+ prod_url='https://prod.example.com' + query_route,
742
+ )
743
+ state.update({
744
+ 'server_path': query_route,
745
+ 'change_request': 'Make a tiny harmless visible sequencer helper-copy change.',
746
+ 'success_criteria': 'The Monkberry profile sequencer route is loaded and visible.',
747
+ })
748
+ write_state(state_path, state)
749
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
750
+
751
+ fake = FakeRiddle()
752
+ load_util_with_fake(fake)
753
+ load_module('recon_query_route', RECON_PATH)
754
+ after_recon = json.loads(state_path.read_text())
755
+
756
+ latest_attempt = after_recon['recon_results']['attempt_history'][-1]
757
+ before = latest_attempt['captured_baselines']['before']
758
+ prod = latest_attempt['captured_baselines']['prod']
759
+ assert before['path'] == query_route
760
+ assert prod['path'] == query_route
761
+ assert before['observation']['valid'] is True, before['observation']
762
+ assert prod['observation']['valid'] is True, prod['observation']
763
+ assert before['observation']['details']['observed_path'] == query_route
764
+ assert prod['observation']['details']['observed_path'] == query_route
765
+ assert 'wrong route' not in before['observation']['reason']
766
+ assert 'wrong route' not in prod['observation']['reason']
767
+ return {
768
+ 'ok': True,
769
+ 'before_observed_path': before['observation']['details']['observed_path'],
770
+ 'prod_observed_path': prod['observation']['details']['observed_path'],
771
+ }
772
+ finally:
773
+ shutil.rmtree(tempdir, ignore_errors=True)
774
+
775
+
662
776
  def run_recon_prefers_route_literals_over_import_paths():
663
777
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-route-literals-'))
664
778
  state_path = tempdir / 'state.json'
@@ -1369,6 +1483,7 @@ if __name__ == '__main__':
1369
1483
  'implement_records_detection_when_changes_missing': run_implement_records_detection_when_changes_missing(),
1370
1484
  'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
1371
1485
  'recon_then_author_request': run_recon_then_author_request(),
1486
+ 'recon_preserves_query_route': run_recon_preserves_query_route(),
1372
1487
  'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),
1373
1488
  'recon_hint_root_preference': run_recon_prefers_hint_root_over_single_route_literal(),
1374
1489
  'author_applies_supervisor_packet': run_author_applies_supervisor_packet(),