@riddledc/riddle-proof 0.5.32 → 0.5.34

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 (40) hide show
  1. package/dist/{chunk-R4BPJNAM.js → chunk-4YCWZVBN.js} +101 -8
  2. package/dist/{chunk-LIDYNU7Q.js → chunk-7R6ZQE3X.js} +39 -2
  3. package/dist/{chunk-TMMKRKY5.js → chunk-J2MERROF.js} +1 -0
  4. package/dist/{chunk-F6VFKS7K.js → chunk-MQ2BHHLX.js} +2 -2
  5. package/dist/{chunk-7ZJAUEUN.js → chunk-OASB3CYU.js} +6 -1
  6. package/dist/chunk-ODORKNSO.js +121 -0
  7. package/dist/engine-harness.cjs +143 -9
  8. package/dist/engine-harness.js +4 -3
  9. package/dist/index.cjs +271 -11
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +24 -7
  13. package/dist/openclaw.cjs +5 -0
  14. package/dist/openclaw.js +2 -2
  15. package/dist/proof-run-core.cjs +104 -8
  16. package/dist/proof-run-core.d.cts +24 -1
  17. package/dist/proof-run-core.d.ts +24 -1
  18. package/dist/proof-run-core.js +7 -1
  19. package/dist/proof-run-engine.cjs +101 -8
  20. package/dist/proof-run-engine.d.cts +24 -0
  21. package/dist/proof-run-engine.d.ts +24 -0
  22. package/dist/proof-run-engine.js +4 -1
  23. package/dist/proof-session.cjs +151 -0
  24. package/dist/proof-session.d.cts +37 -0
  25. package/dist/proof-session.d.ts +37 -0
  26. package/dist/proof-session.js +18 -0
  27. package/dist/result.cjs +1 -0
  28. package/dist/result.js +1 -1
  29. package/dist/runner.cjs +6 -0
  30. package/dist/runner.js +3 -3
  31. package/dist/state.cjs +5 -0
  32. package/dist/state.js +2 -2
  33. package/dist/types.d.cts +67 -1
  34. package/dist/types.d.ts +67 -1
  35. package/package.json +7 -2
  36. package/runtime/lib/preflight.py +42 -2
  37. package/runtime/lib/ship.py +55 -1
  38. package/runtime/lib/util.py +237 -0
  39. package/runtime/lib/verify.py +93 -6
  40. package/runtime/tests/recon_verify_smoke.py +181 -3
@@ -204,10 +204,13 @@ class FakeRiddle:
204
204
  ],
205
205
  }
206
206
  if 'after-proof' in script:
207
+ outputs = [{'name': 'after.png', 'url': 'https://cdn.example.com/after.png'}]
208
+ if 'proof-session' in script:
209
+ outputs.append({'name': 'proof-session.json', 'url': 'https://cdn.example.com/proof-session.json'})
207
210
  return {
208
211
  'ok': True,
209
212
  'screenshots': [{'url': 'https://cdn.example.com/after.png'}],
210
- 'outputs': [{'name': 'after.png', 'url': 'https://cdn.example.com/after.png'}],
213
+ 'outputs': outputs,
211
214
  'console': state_console({
212
215
  'bodyTextLength': 180,
213
216
  'visibleTextSample': 'Pricing CTA Buy Now',
@@ -637,6 +640,105 @@ def run_preflight_records_prod_reference_skip_reason():
637
640
  shutil.rmtree(tempdir, ignore_errors=True)
638
641
 
639
642
 
643
+ def run_preflight_resumes_visual_proof_session():
644
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-preflight-session-'))
645
+ args_path = tempdir / 'args.json'
646
+ state_path = tempdir / 'state.json'
647
+ repo_dir = tempdir / 'repo'
648
+ try:
649
+ make_project(repo_dir, "export const routes = [{ path: '/pricing', element: <Pricing /> }];\n")
650
+ util = load_module('util_proof_session_builder', UTIL_PATH)
651
+ parent_session = util.build_visual_proof_session({
652
+ 'repo': 'example/repo',
653
+ 'server_path': '/pricing',
654
+ 'reference': 'before',
655
+ 'verification_mode': 'visual',
656
+ 'target_image_url': 'https://cdn.example.com/spec.png',
657
+ 'target_image_hash': 'sha256:spec',
658
+ 'viewport_matrix': [
659
+ {'name': 'mobile', 'width': 390, 'height': 844},
660
+ {'name': 'desktop', 'width': 1280, 'height': 900},
661
+ ],
662
+ 'deterministic_setup': {'seed': 'pricing-visual-v1'},
663
+ 'parsed_assertions': [{'kind': 'text', 'contains': 'Buy Now'}],
664
+ 'proof_plan': 'Capture pricing route and compare CTA visual state.',
665
+ 'capture_script': "await page.waitForSelector('[data-testid=pricing-cta]'); await saveScreenshot('after-proof');",
666
+ 'wait_for_selector': '[data-testid=pricing-cta]',
667
+ }, route='/pricing', observed_after_path='/pricing', status='evidence_captured')
668
+
669
+ args_path.write_text(json.dumps({
670
+ 'repo': 'example/repo',
671
+ 'repo_dir': str(repo_dir),
672
+ 'mode': 'static',
673
+ 'change_request': 'Continue the pricing visual iteration',
674
+ 'commit_message': 'Continue the pricing visual iteration',
675
+ 'build_command': BUILD_SCRIPT,
676
+ 'build_output': 'build',
677
+ 'allow_static_preview_fallback': True,
678
+ 'resume_session': json.dumps(parent_session),
679
+ }, indent=2))
680
+ with temporary_env(
681
+ RIDDLE_PROOF_ARGS_FILE=str(args_path),
682
+ RIDDLE_PROOF_STATE_FILE=str(state_path),
683
+ ):
684
+ sys.modules.pop('util', None)
685
+ load_module('preflight_resume_session', PREFLIGHT_PATH)
686
+ after_preflight = json.loads(state_path.read_text())
687
+ assert after_preflight['proof_session_resume']['status'] == 'accepted'
688
+ assert after_preflight['proof_session_resume']['applied_fields']
689
+ assert after_preflight['parent_proof_session']['session_id'] == parent_session['session_id']
690
+ assert after_preflight['server_path'] == '/pricing'
691
+ assert after_preflight['server_path_source'] == 'proof_session'
692
+ assert after_preflight['reference'] == 'before'
693
+ assert after_preflight['verification_mode'] == 'visual'
694
+ assert after_preflight['target_image_url'] == 'https://cdn.example.com/spec.png'
695
+ assert after_preflight['target_image_hash'] == 'sha256:spec'
696
+ assert after_preflight['viewport_matrix'][0]['name'] == 'mobile'
697
+ assert after_preflight['deterministic_setup']['seed'] == 'pricing-visual-v1'
698
+ assert after_preflight['parsed_assertions'][0]['contains'] == 'Buy Now'
699
+ assert after_preflight['proof_plan_status'] == 'ready'
700
+ assert after_preflight['author_status'] == 'ready'
701
+
702
+ mismatch_args_path = tempdir / 'mismatch-args.json'
703
+ mismatch_state_path = tempdir / 'mismatch-state.json'
704
+ mismatch_args_path.write_text(json.dumps({
705
+ 'repo': 'example/repo',
706
+ 'repo_dir': str(repo_dir),
707
+ 'mode': 'static',
708
+ 'reference': 'before',
709
+ 'verification_mode': 'visual',
710
+ 'change_request': 'Continue the pricing visual iteration',
711
+ 'commit_message': 'Continue the pricing visual iteration',
712
+ 'build_command': BUILD_SCRIPT,
713
+ 'build_output': 'build',
714
+ 'allow_static_preview_fallback': True,
715
+ 'server_path': '/wrong',
716
+ 'resume_session': json.dumps(parent_session),
717
+ }, indent=2))
718
+ with temporary_env(
719
+ RIDDLE_PROOF_ARGS_FILE=str(mismatch_args_path),
720
+ RIDDLE_PROOF_STATE_FILE=str(mismatch_state_path),
721
+ ):
722
+ try:
723
+ sys.modules.pop('util', None)
724
+ load_module('preflight_resume_session_mismatch', PREFLIGHT_PATH)
725
+ except SystemExit as exc:
726
+ assert 'fingerprint mismatch' in str(exc), exc
727
+ else:
728
+ raise AssertionError('route mismatch should reject resumed proof session')
729
+ mismatch_state = json.loads(mismatch_state_path.read_text())
730
+ assert mismatch_state['proof_session_resume']['status'] == 'fingerprint_mismatch'
731
+ assert any(item['key'] == 'route' for item in mismatch_state['proof_session_resume']['mismatches'])
732
+
733
+ return {
734
+ 'ok': True,
735
+ 'parent_session_id': parent_session['session_id'],
736
+ 'fingerprint': after_preflight['proof_session_resume']['fingerprint'],
737
+ }
738
+ finally:
739
+ shutil.rmtree(tempdir, ignore_errors=True)
740
+
741
+
640
742
  def base_state(tempdir: Path, *, reference='before', prod_url=''):
641
743
  before_dir = tempdir / 'before'
642
744
  after_dir = tempdir / 'after'
@@ -1157,6 +1259,7 @@ def run_verify_requests_supervisor_assessment():
1157
1259
  try:
1158
1260
  state = base_state(tempdir, reference='both', prod_url='https://prod.example.com/pricing')
1159
1261
  state.update({
1262
+ 'repo': 'example/repo',
1160
1263
  'recon_status': 'ready_for_proof_plan',
1161
1264
  'author_status': 'ready',
1162
1265
  'proof_plan_status': 'ready',
@@ -1200,17 +1303,19 @@ def run_verify_requests_supervisor_assessment():
1200
1303
  artifact_contract = after_verify['proof_assessment_request']['artifact_contract']
1201
1304
  assert artifact_contract['required']['baseline_context'] is True
1202
1305
  assert artifact_contract['required']['screenshot'] is True
1306
+ assert artifact_contract['required']['visual_delta'] is True
1203
1307
  artifact_production = after_verify['proof_assessment_request']['artifact_production']
1204
1308
  assert artifact_production['image_output_count'] >= 1
1205
1309
  assert artifact_production['proof_evidence_present'] is False
1206
1310
  artifact_usage = after_verify['proof_assessment_request']['artifact_usage']
1207
- assert artifact_usage['missing_required_signals'] == []
1311
+ assert artifact_usage['missing_required_signals'] == ['visual_delta']
1208
1312
  assert 'after-capture' in artifact_usage['supervisor_review_signals']
1209
1313
  assert 'baseline_context' in artifact_usage['required_signals']
1210
1314
  assert 'route_semantics' in artifact_usage['available_signals']
1315
+ assert 'visual_delta.status=unmeasured' in after_verify['proof_assessment_request']['hard_blockers'][0]
1211
1316
  assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_contract']['required']['screenshot'] is True
1212
1317
  assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_production']['image_output_count'] >= 1
1213
- assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_usage']['missing_required_signals'] == []
1318
+ assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_usage']['missing_required_signals'] == ['visual_delta']
1214
1319
  assert 'capture success is not proof' in '\n'.join(after_verify['proof_assessment_request']['instructions'])
1215
1320
  assert after_verify['verify_decision_request']['continue_with_stage'] is None
1216
1321
  assert after_verify['verify_results']['baseline']['before']['source'] == 'recon'
@@ -1221,6 +1326,21 @@ def run_verify_requests_supervisor_assessment():
1221
1326
  assert after_details['observed_path_raw'] == '/s/pv-after/pricing', after_details
1222
1327
  assert 'Buy Now' in after_details['visible_text_sample'], after_details
1223
1328
  assert after_details['buttons'] == ['Buy Now'], after_details
1329
+ proof_session = after_verify['proof_session']
1330
+ assert proof_session['version'] == 'riddle-proof.visual-session.v1'
1331
+ assert proof_session['repo'] == 'example/repo'
1332
+ assert proof_session['route']['path'] == '/pricing'
1333
+ assert proof_session['route']['observed_after_path'] == '/pricing'
1334
+ assert proof_session['artifacts']['before'] == 'https://cdn.example.com/before.png'
1335
+ assert proof_session['artifacts']['prod'] == 'https://cdn.example.com/prod.png'
1336
+ assert proof_session['artifacts']['after'] == 'https://cdn.example.com/after.png'
1337
+ assert proof_session['artifacts']['session'] == 'https://cdn.example.com/proof-session.json'
1338
+ assert proof_session['capture']['wait_for_selector'] == '[data-testid=pricing-cta]'
1339
+ assert proof_session['fingerprint_basis']['route'] == '/pricing'
1340
+ assert after_verify['proof_session_fingerprint'] == proof_session['fingerprint']
1341
+ assert after_verify['proof_session_artifact_url'] == 'https://cdn.example.com/proof-session.json'
1342
+ assert after_verify['evidence_bundle']['proof_session']['fingerprint'] == proof_session['fingerprint']
1343
+ assert after_verify['proof_assessment_request']['evidence_bundle']['proof_session']['fingerprint'] == proof_session['fingerprint']
1224
1344
  runtime_events = after_verify.get('runtime_events') or []
1225
1345
  assert any(event.get('kind') == 'workflow.phase.started' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
1226
1346
  assert any(event.get('kind') == 'workflow.phase.finished' and event.get('step') == 'verify' and event.get('phase') == 'build' for event in runtime_events)
@@ -1573,6 +1693,62 @@ def run_ship_missing_supervisor_gate():
1573
1693
  shutil.rmtree(tempdir, ignore_errors=True)
1574
1694
 
1575
1695
 
1696
+ def run_ship_blocks_unmeasured_visual_delta():
1697
+ tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-ship-visual-delta-'))
1698
+ state_path = tempdir / 'state.json'
1699
+ try:
1700
+ state = base_state(tempdir, reference='before')
1701
+ state.update({
1702
+ 'recon_status': 'ready_for_proof_plan',
1703
+ 'author_status': 'ready',
1704
+ 'proof_plan_status': 'ready',
1705
+ 'implementation_status': 'changes_detected',
1706
+ 'verification_mode': 'visual',
1707
+ 'verify_status': 'evidence_captured',
1708
+ 'before_cdn': 'https://cdn.example.com/before.png',
1709
+ 'after_cdn': 'https://cdn.example.com/after.png',
1710
+ 'proof_assessment': {
1711
+ 'decision': 'ready_to_ship',
1712
+ 'summary': 'The screenshots look good.',
1713
+ 'source': 'supervising_agent',
1714
+ },
1715
+ 'proof_assessment_source': 'supervising_agent',
1716
+ 'evidence_bundle': {
1717
+ 'verification_mode': 'visual',
1718
+ 'artifact_contract': {
1719
+ 'required': {
1720
+ 'baseline_context': True,
1721
+ 'route_semantics': True,
1722
+ 'screenshot': True,
1723
+ 'visual_delta': True,
1724
+ },
1725
+ },
1726
+ 'after': {
1727
+ 'screenshot_url': 'https://cdn.example.com/after.png',
1728
+ 'observation': {'valid': True, 'reason': 'ok'},
1729
+ 'visual_delta': {
1730
+ 'status': 'unmeasured',
1731
+ 'passed': None,
1732
+ 'reason': 'No measured before/after visual delta was found in proof evidence.',
1733
+ },
1734
+ },
1735
+ },
1736
+ })
1737
+ write_state(state_path, state)
1738
+ os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
1739
+
1740
+ try:
1741
+ load_module('ship_blocks_unmeasured_visual_delta', SHIP_PATH)
1742
+ except SystemExit as exc:
1743
+ message = str(exc)
1744
+ assert 'visual_delta.status=unmeasured' in message, message
1745
+ assert 'blocks ready_to_ship' in message, message
1746
+ return {'ok': True, 'error': message}
1747
+ raise AssertionError('ship should have failed when visual delta was unmeasured')
1748
+ finally:
1749
+ shutil.rmtree(tempdir, ignore_errors=True)
1750
+
1751
+
1576
1752
  def run_ship_accepts_structured_after_evidence():
1577
1753
  tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-ship-structured-'))
1578
1754
  state_path = tempdir / 'state.json'
@@ -1808,6 +1984,7 @@ def run_ship_resolves_real_pr_branch():
1808
1984
  if __name__ == '__main__':
1809
1985
  payload = {
1810
1986
  'preflight_reference_skip_reason': run_preflight_records_prod_reference_skip_reason(),
1987
+ 'preflight_resume_visual_proof_session': run_preflight_resumes_visual_proof_session(),
1811
1988
  'capture_artifact_enrichment': run_capture_artifact_enrichment(),
1812
1989
  'capture_diagnostics_redaction': run_capture_diagnostics_redact_sensitive_values(),
1813
1990
  'apply_auth_context': run_apply_auth_context_passes_supported_auth_payloads(),
@@ -1829,6 +2006,7 @@ if __name__ == '__main__':
1829
2006
  'verify_capture_retry': run_verify_capture_retry(),
1830
2007
  'missing_baseline_guard': run_verify_missing_baseline(),
1831
2008
  'ship_supervisor_gate': run_ship_missing_supervisor_gate(),
2009
+ 'ship_blocks_unmeasured_visual_delta': run_ship_blocks_unmeasured_visual_delta(),
1832
2010
  'ship_structured_after_evidence': run_ship_accepts_structured_after_evidence(),
1833
2011
  'ship_discord_thread_target': run_ship_discord_thread_target(),
1834
2012
  'ship_filters_tool_noise_when_staging': run_ship_filters_tool_noise_when_staging(),