@riddledc/riddle-proof 0.5.13 → 0.5.15

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.13",
3
+ "version": "0.5.15",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -244,7 +244,7 @@ def score_route_candidate(path, tokens):
244
244
  def choose_target_path(explicit_path, prod_url, route_hints, tokens=None, explicit_source=''):
245
245
  explicit = (explicit_path or '').strip()
246
246
  explicit_source = (explicit_source or '').strip()
247
- is_meaningful_explicit = bool(explicit) and (explicit != '/' or explicit_source in ('user', 'tool_param', 'supervising_agent'))
247
+ is_meaningful_explicit = bool(explicit) and (explicit != '/' or bool(explicit_source))
248
248
  if is_meaningful_explicit:
249
249
  return explicit if explicit.startswith('/') else '/' + explicit
250
250
  candidates = route_candidates(route_hints, prod_url)
@@ -719,7 +719,7 @@ initial_target_path = choose_target_path(s.get('server_path', ''), s.get('prod_u
719
719
  selected_route = next((item for item in route_options if item.get('path') == initial_target_path), None)
720
720
  initial_hypothesis = {
721
721
  'target_path': initial_target_path,
722
- 'path_source': 'state.server_path' if (s.get('server_path') or '').strip() and (s.get('server_path') != '/' or server_path_source) else ((selected_route or {}).get('reason') or 'fallback root'),
722
+ 'path_source': ('state.server_path:' + server_path_source) if (s.get('server_path') or '').strip() and (s.get('server_path') != '/' or server_path_source) else ((selected_route or {}).get('reason') or 'fallback root'),
723
723
  'reference': requested_reference,
724
724
  'mode': s.get('mode', 'server'),
725
725
  'wait_for_selector': (s.get('wait_for_selector') or '').strip(),
@@ -7,7 +7,7 @@ while good captures produce a structured evidence packet that the supervising ag
7
7
  must assess before the wrapper routes back into author/implement/recon work or ship.
8
8
  """
9
9
 
10
- import json, os, sys
10
+ import json, os, sys, time
11
11
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
12
12
  from util import (
13
13
  append_capture_diagnostic,
@@ -83,6 +83,53 @@ def auto_screenshot_for_mode(verification_mode):
83
83
  return normalized_verification_mode(verification_mode) not in STRUCTURED_FIRST_MODES
84
84
 
85
85
 
86
+ def record_verify_phase(phase, status='running', summary=''):
87
+ global s
88
+ ts = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
89
+ try:
90
+ current = load_state()
91
+ except Exception:
92
+ current = dict(s)
93
+ runtime_step = current.get('current_runtime_step') if isinstance(current.get('current_runtime_step'), dict) else {}
94
+ if not runtime_step:
95
+ runtime_step = {
96
+ 'step': 'verify',
97
+ 'action': 'run',
98
+ 'status': 'running',
99
+ 'started_at': ts,
100
+ 'workflow_file': 'riddle-proof-verify.lobster',
101
+ }
102
+ runtime_step['step'] = 'verify'
103
+ runtime_step['action'] = 'run'
104
+ runtime_step['status'] = 'running'
105
+ runtime_step['workflow_file'] = 'riddle-proof-verify.lobster'
106
+ runtime_step['phase'] = phase
107
+ runtime_step['phase_status'] = status
108
+ if status == 'running':
109
+ runtime_step['phase_started_at'] = ts
110
+ runtime_step.pop('phase_finished_at', None)
111
+ else:
112
+ runtime_step['phase_finished_at'] = ts
113
+ if summary:
114
+ runtime_step['summary'] = summary
115
+ current['current_runtime_step'] = runtime_step
116
+ events = current.get('runtime_events') if isinstance(current.get('runtime_events'), list) else []
117
+ events.append({
118
+ 'ts': ts,
119
+ 'kind': 'workflow.phase.' + ('started' if status == 'running' else 'finished'),
120
+ 'step': 'verify',
121
+ 'phase': phase,
122
+ 'summary': summary or (phase + ' ' + status),
123
+ 'details': {'status': status},
124
+ })
125
+ current['runtime_events'] = events[-100:]
126
+ current['runtime_updated_at'] = ts
127
+ save_state(current)
128
+ for key in ('current_runtime_step', 'runtime_events', 'runtime_updated_at'):
129
+ if key in current:
130
+ s[key] = current[key]
131
+
132
+
86
133
  def payload_has_capture_artifacts(payload):
87
134
  if not isinstance(payload, dict):
88
135
  return False
@@ -108,6 +155,7 @@ def capture_payload_error(payload):
108
155
 
109
156
  def abort_capture_failure(state, results, expected_path, message, raw_payload):
110
157
  summary = 'After capture failed before usable proof artifacts were produced: ' + str(message).strip()
158
+ record_verify_phase('capture', 'failed', summary)
111
159
  observation = {
112
160
  'valid': False,
113
161
  'reason': summary,
@@ -979,16 +1027,20 @@ if existing_prod:
979
1027
  print('Prod baseline: ' + existing_prod)
980
1028
 
981
1029
  # AFTER (always from after worktree)
1030
+ record_verify_phase('build', 'running', 'Building after worktree for verify capture.')
982
1031
  print('Cleaning after .next cache...')
983
1032
  sp.run('rm -rf .next', shell=True, cwd=after_dir, capture_output=True)
984
1033
 
985
1034
  print('Building after worktree...')
986
1035
  br = sp.run(build_cmd, shell=True, cwd=after_dir, capture_output=True, text=True, timeout=600)
987
1036
  if br.returncode != 0:
1037
+ record_verify_phase('build', 'failed', 'After build failed: ' + br.stderr[:300])
988
1038
  raise SystemExit('After build failed: ' + br.stderr[:500])
1039
+ record_verify_phase('build', 'completed', 'After worktree build completed.')
989
1040
 
990
1041
  after_payload = {}
991
1042
  static_reason = should_use_static_preview(after_dir, s) if mode == 'server' else ''
1043
+ record_verify_phase('capture', 'running', 'Capturing after-proof evidence.')
992
1044
  if mode == 'server' and not static_reason:
993
1045
  build_dir, server_command, server_exclude = prepare_server_preview(after_dir, s)
994
1046
 
@@ -1051,8 +1103,10 @@ else:
1051
1103
  after_observation = evaluate_capture_quality(after_payload, expected_path, verification_mode)
1052
1104
  results['after']['observation'] = after_observation
1053
1105
  results['after']['supporting_artifacts'] = collect_supporting_artifacts(after_payload)
1106
+ record_verify_phase('capture', 'completed', 'After-proof capture completed.')
1054
1107
 
1055
1108
  # Structured proof summary
1109
+ record_verify_phase('assessment', 'running', 'Assessing verify evidence bundle.')
1056
1110
  s['verify_results'] = results
1057
1111
  s['stage'] = 'verify'
1058
1112
  assertions = s.get('parsed_assertions')
@@ -1211,6 +1265,7 @@ s['evidence_notes'] = [
1211
1265
  ]
1212
1266
 
1213
1267
  save_state(s)
1268
+ record_verify_phase('assessment', 'completed', 'Verify evidence assessment completed.')
1214
1269
 
1215
1270
  assessment_status = 'awaiting_supervising_agent' if s.get('verify_status') == 'evidence_captured' else 'capture_incomplete'
1216
1271
 
@@ -207,6 +207,30 @@ class FakeRiddle:
207
207
  'largeVisibleElements': [{'tag': 'button', 'text': 'Reset Game'}],
208
208
  }),
209
209
  }
210
+ if (
211
+ 'preview.example.com' in script
212
+ and '/pricing' not in script
213
+ and '/games/tic-tac-toe' not in script
214
+ and '/wrong' not in script
215
+ ):
216
+ return {
217
+ 'ok': True,
218
+ 'screenshots': [{'url': 'https://cdn.example.com/home-before.png'}],
219
+ 'outputs': [{'name': 'before.png', 'url': 'https://cdn.example.com/home-before.png'}],
220
+ 'console': state_console({
221
+ 'bodyTextLength': 180,
222
+ 'visibleTextSample': 'Riddle Proof homepage hero Start Free',
223
+ 'interactiveElements': 4,
224
+ 'visibleInteractiveElements': 4,
225
+ 'pathname': '/',
226
+ 'title': 'Riddle',
227
+ 'buttons': ['Start Free'],
228
+ 'headings': ['Riddle Proof'],
229
+ 'links': [],
230
+ 'canvasCount': 0,
231
+ 'largeVisibleElements': [{'tag': 'button', 'text': 'Start Free'}],
232
+ }),
233
+ }
210
234
  raise AssertionError(f'unexpected riddle_script payload: {script}')
211
235
  raise AssertionError(f'unexpected invoke_retry tool: {tool}')
212
236
 
@@ -581,6 +605,53 @@ def run_recon_prefers_route_literals_over_import_paths():
581
605
  shutil.rmtree(tempdir, ignore_errors=True)
582
606
 
583
607
 
608
+ def run_recon_prefers_hint_root_over_single_route_literal():
609
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-hint-root-'))
610
+ state_path = tempdir / 'state.json'
611
+ try:
612
+ route_snippet = "export const routes = [{ path: '/docs/riddle-proof/markdown', element: <Docs /> }];\n"
613
+ state = base_state(tempdir, reference='before')
614
+ make_project(tempdir / 'before', route_snippet)
615
+ make_project(tempdir / 'after', route_snippet)
616
+ state.update({
617
+ 'server_path': '/',
618
+ 'server_path_source': 'hint_cache',
619
+ 'capture_hint': {
620
+ 'source': 'hint_cache',
621
+ 'applied_fields': ['server_path'],
622
+ 'selected': {'server_path': '/'},
623
+ },
624
+ 'change_request': 'Make a tiny harmless homepage copy tweak',
625
+ 'success_criteria': 'The homepage hero copy reflects the tiny tweak.',
626
+ })
627
+ write_state(state_path, state)
628
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
629
+
630
+ fake = FakeRiddle()
631
+ load_util_with_fake(fake)
632
+ load_module('recon_hint_root_preference', RECON_PATH)
633
+ after_recon = json.loads(state_path.read_text())
634
+
635
+ current_plan = after_recon['recon_results']['current_plan']
636
+ candidate_paths = [item['path'] for item in current_plan['route_candidates']]
637
+ assert current_plan['target_path'] == '/', current_plan
638
+ assert current_plan['path_source'] == 'state.server_path:hint_cache', current_plan
639
+ assert '/docs/riddle-proof/markdown' in candidate_paths, candidate_paths
640
+ details = after_recon['recon_results']['attempt_history'][-1]['observations']['before']['details']
641
+ assert details['observed_path'] == '/'
642
+ assert 'Start Free' in details['visible_text_sample'], details
643
+ assert details['buttons'] == ['Start Free'], details
644
+
645
+ return {
646
+ 'ok': True,
647
+ 'target_path': current_plan['target_path'],
648
+ 'path_source': current_plan['path_source'],
649
+ 'candidate_paths': candidate_paths,
650
+ }
651
+ finally:
652
+ shutil.rmtree(tempdir, ignore_errors=True)
653
+
654
+
584
655
  def run_author_applies_supervisor_packet():
585
656
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-supervisor-apply-'))
586
657
  state_path = tempdir / 'state.json'
@@ -697,6 +768,13 @@ def run_verify_requests_supervisor_assessment():
697
768
  assert after_details['observed_path_raw'] == '/s/pv-after/pricing', after_details
698
769
  assert 'Buy Now' in after_details['visible_text_sample'], after_details
699
770
  assert after_details['buttons'] == ['Buy Now'], after_details
771
+ runtime_events = after_verify.get('runtime_events') or []
772
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
773
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
774
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'capture' for event in runtime_events)
775
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'capture' for event in runtime_events)
776
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'assessment' for event in runtime_events)
777
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'assessment' for event in runtime_events)
700
778
 
701
779
  return {
702
780
  'ok': True,
@@ -1182,6 +1260,7 @@ if __name__ == '__main__':
1182
1260
  'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
1183
1261
  'recon_then_author_request': run_recon_then_author_request(),
1184
1262
  'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),
1263
+ 'recon_hint_root_preference': run_recon_prefers_hint_root_over_single_route_literal(),
1185
1264
  'author_applies_supervisor_packet': run_author_applies_supervisor_packet(),
1186
1265
  'verify_requests_supervisor_assessment': run_verify_requests_supervisor_assessment(),
1187
1266
  'verify_structured_evidence_without_screenshot': run_verify_structured_evidence_without_screenshot(),