@riddledc/riddle-proof 0.5.14 → 0.5.16

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.14",
3
+ "version": "0.5.16",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -566,6 +566,41 @@ def git(cmd, cwd):
566
566
  return sp.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True)
567
567
 
568
568
 
569
+ def run_project_build(project_dir, build_cmd, timeout=600, clean_cache_dir='.next'):
570
+ """Build once with existing cache, then retry clean if the cached build fails."""
571
+ attempts = []
572
+ for clean_first in (False, True):
573
+ if clean_first and clean_cache_dir:
574
+ cache_path = os.path.join(project_dir, clean_cache_dir)
575
+ if os.path.exists(cache_path):
576
+ sp.run(
577
+ 'rm -rf ' + shell_quote(clean_cache_dir),
578
+ shell=True,
579
+ cwd=project_dir,
580
+ capture_output=True,
581
+ text=True,
582
+ )
583
+ result = sp.run(build_cmd, shell=True, cwd=project_dir, capture_output=True, text=True, timeout=timeout)
584
+ attempts.append({
585
+ 'clean_first': clean_first,
586
+ 'returncode': int(result.returncode),
587
+ 'stderr': (result.stderr or '')[:500],
588
+ })
589
+ if result.returncode == 0:
590
+ return {
591
+ 'result': result,
592
+ 'clean_retry_used': clean_first,
593
+ 'attempts': attempts,
594
+ }
595
+ if not clean_cache_dir or clean_first:
596
+ break
597
+ return {
598
+ 'result': result,
599
+ 'clean_retry_used': False,
600
+ 'attempts': attempts,
601
+ }
602
+
603
+
569
604
  def load_package_json(project_dir):
570
605
  package_json = os.path.join(project_dir, 'package.json')
571
606
  if not os.path.exists(package_json):
@@ -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,
@@ -20,6 +20,7 @@ from util import (
20
20
  load_state,
21
21
  prepare_server_preview,
22
22
  record_successful_capture_hint,
23
+ run_project_build,
23
24
  save_state,
24
25
  should_use_static_preview,
25
26
  summarize_capture_artifacts,
@@ -83,6 +84,53 @@ def auto_screenshot_for_mode(verification_mode):
83
84
  return normalized_verification_mode(verification_mode) not in STRUCTURED_FIRST_MODES
84
85
 
85
86
 
87
+ def record_verify_phase(phase, status='running', summary=''):
88
+ global s
89
+ ts = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
90
+ try:
91
+ current = load_state()
92
+ except Exception:
93
+ current = dict(s)
94
+ runtime_step = current.get('current_runtime_step') if isinstance(current.get('current_runtime_step'), dict) else {}
95
+ if not runtime_step:
96
+ runtime_step = {
97
+ 'step': 'verify',
98
+ 'action': 'run',
99
+ 'status': 'running',
100
+ 'started_at': ts,
101
+ 'workflow_file': 'riddle-proof-verify.lobster',
102
+ }
103
+ runtime_step['step'] = 'verify'
104
+ runtime_step['action'] = 'run'
105
+ runtime_step['status'] = 'running'
106
+ runtime_step['workflow_file'] = 'riddle-proof-verify.lobster'
107
+ runtime_step['phase'] = phase
108
+ runtime_step['phase_status'] = status
109
+ if status == 'running':
110
+ runtime_step['phase_started_at'] = ts
111
+ runtime_step.pop('phase_finished_at', None)
112
+ else:
113
+ runtime_step['phase_finished_at'] = ts
114
+ if summary:
115
+ runtime_step['summary'] = summary
116
+ current['current_runtime_step'] = runtime_step
117
+ events = current.get('runtime_events') if isinstance(current.get('runtime_events'), list) else []
118
+ events.append({
119
+ 'ts': ts,
120
+ 'kind': 'workflow.phase.' + ('started' if status == 'running' else 'finished'),
121
+ 'step': 'verify',
122
+ 'phase': phase,
123
+ 'summary': summary or (phase + ' ' + status),
124
+ 'details': {'status': status},
125
+ })
126
+ current['runtime_events'] = events[-100:]
127
+ current['runtime_updated_at'] = ts
128
+ save_state(current)
129
+ for key in ('current_runtime_step', 'runtime_events', 'runtime_updated_at'):
130
+ if key in current:
131
+ s[key] = current[key]
132
+
133
+
86
134
  def payload_has_capture_artifacts(payload):
87
135
  if not isinstance(payload, dict):
88
136
  return False
@@ -108,6 +156,7 @@ def capture_payload_error(payload):
108
156
 
109
157
  def abort_capture_failure(state, results, expected_path, message, raw_payload):
110
158
  summary = 'After capture failed before usable proof artifacts were produced: ' + str(message).strip()
159
+ record_verify_phase('capture', 'failed', summary)
111
160
  observation = {
112
161
  'valid': False,
113
162
  'reason': summary,
@@ -979,16 +1028,23 @@ if existing_prod:
979
1028
  print('Prod baseline: ' + existing_prod)
980
1029
 
981
1030
  # AFTER (always from after worktree)
982
- print('Cleaning after .next cache...')
983
- sp.run('rm -rf .next', shell=True, cwd=after_dir, capture_output=True)
984
-
1031
+ record_verify_phase('build', 'running', 'Building after worktree for verify capture.')
985
1032
  print('Building after worktree...')
986
- br = sp.run(build_cmd, shell=True, cwd=after_dir, capture_output=True, text=True, timeout=600)
1033
+ build_attempt = run_project_build(after_dir, build_cmd, timeout=600, clean_cache_dir='.next')
1034
+ br = build_attempt.get('result')
1035
+ if build_attempt.get('clean_retry_used'):
1036
+ print('Verify build recovered after cleaning .next cache.')
987
1037
  if br.returncode != 0:
1038
+ record_verify_phase('build', 'failed', 'After build failed: ' + br.stderr[:300])
988
1039
  raise SystemExit('After build failed: ' + br.stderr[:500])
1040
+ if build_attempt.get('attempts'):
1041
+ s['verify_build_attempts'] = build_attempt['attempts']
1042
+ s['verify_build_clean_retry_used'] = bool(build_attempt.get('clean_retry_used'))
1043
+ record_verify_phase('build', 'completed', 'After worktree build completed.')
989
1044
 
990
1045
  after_payload = {}
991
1046
  static_reason = should_use_static_preview(after_dir, s) if mode == 'server' else ''
1047
+ record_verify_phase('capture', 'running', 'Capturing after-proof evidence.')
992
1048
  if mode == 'server' and not static_reason:
993
1049
  build_dir, server_command, server_exclude = prepare_server_preview(after_dir, s)
994
1050
 
@@ -1051,8 +1107,10 @@ else:
1051
1107
  after_observation = evaluate_capture_quality(after_payload, expected_path, verification_mode)
1052
1108
  results['after']['observation'] = after_observation
1053
1109
  results['after']['supporting_artifacts'] = collect_supporting_artifacts(after_payload)
1110
+ record_verify_phase('capture', 'completed', 'After-proof capture completed.')
1054
1111
 
1055
1112
  # Structured proof summary
1113
+ record_verify_phase('assessment', 'running', 'Assessing verify evidence bundle.')
1056
1114
  s['verify_results'] = results
1057
1115
  s['stage'] = 'verify'
1058
1116
  assertions = s.get('parsed_assertions')
@@ -1211,6 +1269,7 @@ s['evidence_notes'] = [
1211
1269
  ]
1212
1270
 
1213
1271
  save_state(s)
1272
+ record_verify_phase('assessment', 'completed', 'Verify evidence assessment completed.')
1214
1273
 
1215
1274
  assessment_status = 'awaiting_supervising_agent' if s.get('verify_status') == 'evidence_captured' else 'capture_incomplete'
1216
1275
 
@@ -508,6 +508,38 @@ def base_state(tempdir: Path, *, reference='before', prod_url=''):
508
508
  }
509
509
 
510
510
 
511
+ def run_project_build_retries_after_clean_failure():
512
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-build-retry-'))
513
+ cache_dir = tempdir / '.next'
514
+ cache_dir.mkdir(parents=True, exist_ok=True)
515
+ util = load_module('util_build_retry', UTIL_PATH)
516
+ original_run = util.sp.run
517
+ calls = []
518
+
519
+ def fake_run(cmd, *args, **kwargs):
520
+ calls.append(cmd)
521
+ if cmd == 'npm run build':
522
+ build_attempts = calls.count('npm run build')
523
+ if build_attempts == 1:
524
+ return sp.CompletedProcess(cmd, 1, '', 'stale cache')
525
+ return sp.CompletedProcess(cmd, 0, 'ok', '')
526
+ if cmd == 'rm -rf .next':
527
+ shutil.rmtree(cache_dir, ignore_errors=True)
528
+ return sp.CompletedProcess(cmd, 0, '', '')
529
+ raise AssertionError(f'unexpected command: {cmd}')
530
+
531
+ try:
532
+ util.sp.run = fake_run
533
+ result = util.run_project_build(str(tempdir), 'npm run build', timeout=30, clean_cache_dir='.next')
534
+ assert result['clean_retry_used'] is True
535
+ assert result['result'].returncode == 0
536
+ assert calls == ['npm run build', 'rm -rf .next', 'npm run build']
537
+ assert not cache_dir.exists()
538
+ finally:
539
+ util.sp.run = original_run
540
+ shutil.rmtree(tempdir)
541
+
542
+
511
543
  def run_recon_then_author_request():
512
544
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-supervisor-request-'))
513
545
  state_path = tempdir / 'state.json'
@@ -768,6 +800,13 @@ def run_verify_requests_supervisor_assessment():
768
800
  assert after_details['observed_path_raw'] == '/s/pv-after/pricing', after_details
769
801
  assert 'Buy Now' in after_details['visible_text_sample'], after_details
770
802
  assert after_details['buttons'] == ['Buy Now'], after_details
803
+ runtime_events = after_verify.get('runtime_events') or []
804
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
805
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
806
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'capture' for event in runtime_events)
807
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'capture' for event in runtime_events)
808
+ assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'assessment' for event in runtime_events)
809
+ assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'assessment' for event in runtime_events)
771
810
 
772
811
  return {
773
812
  'ok': True,
@@ -1250,6 +1289,7 @@ if __name__ == '__main__':
1250
1289
  'capture_artifact_enrichment': run_capture_artifact_enrichment(),
1251
1290
  'capture_diagnostics_redaction': run_capture_diagnostics_redact_sensitive_values(),
1252
1291
  'apply_auth_context': run_apply_auth_context_passes_supported_auth_payloads(),
1292
+ 'run_project_build_retries_after_clean_failure': run_project_build_retries_after_clean_failure(),
1253
1293
  'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
1254
1294
  'recon_then_author_request': run_recon_then_author_request(),
1255
1295
  'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),