@riddledc/riddle-proof 0.5.15 → 0.5.17
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 +1 -1
- package/runtime/lib/util.py +35 -0
- package/runtime/lib/verify.py +150 -4
- package/runtime/tests/recon_verify_smoke.py +47 -0
package/package.json
CHANGED
package/runtime/lib/util.py
CHANGED
|
@@ -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):
|
package/runtime/lib/verify.py
CHANGED
|
@@ -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,
|
|
@@ -624,6 +625,111 @@ def collect_supporting_artifacts(payload):
|
|
|
624
625
|
}
|
|
625
626
|
|
|
626
627
|
|
|
628
|
+
def artifact_contract_for_mode(verification_mode):
|
|
629
|
+
mode = normalized_verification_mode(verification_mode)
|
|
630
|
+
return {
|
|
631
|
+
'verification_mode': mode,
|
|
632
|
+
'required': {
|
|
633
|
+
'baseline_context': True,
|
|
634
|
+
'route_semantics': True,
|
|
635
|
+
'screenshot': screenshot_required_for_mode(mode),
|
|
636
|
+
'proof_evidence': proof_evidence_required_for_mode(mode),
|
|
637
|
+
},
|
|
638
|
+
'preferred': {
|
|
639
|
+
'page_state': True,
|
|
640
|
+
'structured_payload': mode in STRUCTURED_FIRST_MODES or proof_evidence_required_for_mode(mode),
|
|
641
|
+
'visual_delta': visual_delta_applies(mode),
|
|
642
|
+
},
|
|
643
|
+
'optional': {
|
|
644
|
+
'console_summary': True,
|
|
645
|
+
'json_artifacts': True,
|
|
646
|
+
'image_outputs': True,
|
|
647
|
+
},
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def artifact_production_summary(payload, supporting):
|
|
652
|
+
artifact_summary = summarize_capture_artifacts(payload)
|
|
653
|
+
return {
|
|
654
|
+
'output_names': [str(item.get('name') or '') for item in (artifact_summary.get('outputs') or [])[:20]],
|
|
655
|
+
'screenshot_names': [str(item.get('name') or '') for item in (artifact_summary.get('screenshots') or [])[:10]],
|
|
656
|
+
'artifact_json': list(artifact_summary.get('artifact_json') or []),
|
|
657
|
+
'artifact_error_names': sorted((artifact_summary.get('artifact_errors') or {}).keys()),
|
|
658
|
+
'image_output_count': len(supporting.get('image_outputs') or []),
|
|
659
|
+
'data_output_count': len(supporting.get('data_outputs') or []),
|
|
660
|
+
'other_output_count': len(supporting.get('other_outputs') or []),
|
|
661
|
+
'console_entries': int(supporting.get('console_entries') or 0),
|
|
662
|
+
'structured_result_keys': list(supporting.get('structured_result_keys') or []),
|
|
663
|
+
'proof_evidence_present': bool(supporting.get('proof_evidence_present')),
|
|
664
|
+
'has_structured_payload': bool(supporting.get('has_structured_payload')),
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def artifact_signal_availability(state, after_observation, supporting, visual_delta, required_baseline_present, semantic_context):
|
|
669
|
+
details = (after_observation or {}).get('details') or {}
|
|
670
|
+
route = (semantic_context or {}).get('route') or {}
|
|
671
|
+
return {
|
|
672
|
+
'baseline_context': bool(required_baseline_present),
|
|
673
|
+
'route_semantics': bool(route.get('after_observed_path') or details.get('observed_path')),
|
|
674
|
+
'screenshot': bool(details.get('has_screenshot')),
|
|
675
|
+
'page_state': bool(
|
|
676
|
+
details.get('visible_text_sample')
|
|
677
|
+
or details.get('headings')
|
|
678
|
+
or details.get('buttons')
|
|
679
|
+
or details.get('links')
|
|
680
|
+
or details.get('semantic_anchor_count')
|
|
681
|
+
),
|
|
682
|
+
'structured_payload': bool(supporting.get('has_structured_payload')),
|
|
683
|
+
'proof_evidence': bool(supporting.get('proof_evidence_present')),
|
|
684
|
+
'visual_delta': bool((visual_delta or {}).get('status') not in ('', None, 'not_applicable')),
|
|
685
|
+
'console_summary': bool(supporting.get('console_entries')),
|
|
686
|
+
'json_artifacts': bool(supporting.get('data_outputs')),
|
|
687
|
+
'image_outputs': bool(supporting.get('image_outputs')),
|
|
688
|
+
'assertions': bool(state.get('parsed_assertions')),
|
|
689
|
+
'success_criteria': bool((state.get('success_criteria') or '').strip()),
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def artifact_usage_summary(state, after_observation, supporting, visual_delta, required_baseline_present, semantic_context, evidence_basis):
|
|
694
|
+
contract = artifact_contract_for_mode(state.get('verification_mode'))
|
|
695
|
+
available = artifact_signal_availability(
|
|
696
|
+
state,
|
|
697
|
+
after_observation,
|
|
698
|
+
supporting,
|
|
699
|
+
visual_delta,
|
|
700
|
+
required_baseline_present,
|
|
701
|
+
semantic_context,
|
|
702
|
+
)
|
|
703
|
+
capture_quality = []
|
|
704
|
+
details = (after_observation or {}).get('details') or {}
|
|
705
|
+
if details.get('has_screenshot'):
|
|
706
|
+
capture_quality.append('screenshot')
|
|
707
|
+
if available.get('page_state'):
|
|
708
|
+
capture_quality.append('page_state')
|
|
709
|
+
if available.get('console_summary'):
|
|
710
|
+
capture_quality.append('console_summary')
|
|
711
|
+
if available.get('structured_payload'):
|
|
712
|
+
capture_quality.append('structured_payload')
|
|
713
|
+
if available.get('proof_evidence'):
|
|
714
|
+
capture_quality.append('proof_evidence')
|
|
715
|
+
if available.get('visual_delta'):
|
|
716
|
+
capture_quality.append('visual_delta')
|
|
717
|
+
|
|
718
|
+
required_signals = [key for key, enabled in (contract.get('required') or {}).items() if enabled]
|
|
719
|
+
preferred_signals = [key for key, enabled in (contract.get('preferred') or {}).items() if enabled]
|
|
720
|
+
optional_signals = [key for key, enabled in (contract.get('optional') or {}).items() if enabled]
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
'required_signals': required_signals,
|
|
724
|
+
'preferred_signals': preferred_signals,
|
|
725
|
+
'optional_signals': optional_signals,
|
|
726
|
+
'available_signals': [key for key, enabled in available.items() if enabled],
|
|
727
|
+
'missing_required_signals': [key for key in required_signals if not available.get(key)],
|
|
728
|
+
'capture_quality_signals': capture_quality,
|
|
729
|
+
'supervisor_review_signals': list(evidence_basis or []),
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
|
|
627
733
|
def evaluate_capture_quality(payload, expected_path, verification_mode='proof'):
|
|
628
734
|
payload = enrich_capture_payload(payload)
|
|
629
735
|
mode = normalized_verification_mode(verification_mode)
|
|
@@ -862,6 +968,17 @@ def build_evidence_bundle(state, results, after_payload, after_observation, requ
|
|
|
862
968
|
else {'status': 'not_applicable', 'passed': None, 'reason': 'Verification mode does not require visual delta gating.'}
|
|
863
969
|
)
|
|
864
970
|
semantic_context = build_semantic_context(state, results, after_observation, expected_path)
|
|
971
|
+
artifact_contract = artifact_contract_for_mode(state.get('verification_mode'))
|
|
972
|
+
artifact_production = artifact_production_summary(after_payload, supporting)
|
|
973
|
+
artifact_usage = artifact_usage_summary(
|
|
974
|
+
state,
|
|
975
|
+
after_observation,
|
|
976
|
+
supporting,
|
|
977
|
+
visual_delta,
|
|
978
|
+
required_baseline_present,
|
|
979
|
+
semantic_context,
|
|
980
|
+
[],
|
|
981
|
+
)
|
|
865
982
|
return {
|
|
866
983
|
'verification_mode': normalized_verification_mode(state.get('verification_mode')),
|
|
867
984
|
'reference': state.get('requested_reference') or state.get('reference', 'both'),
|
|
@@ -869,6 +986,9 @@ def build_evidence_bundle(state, results, after_payload, after_observation, requ
|
|
|
869
986
|
'required_baseline_present': required_baseline_present,
|
|
870
987
|
'baseline': results.get('baseline') or {},
|
|
871
988
|
'semantic_context': semantic_context,
|
|
989
|
+
'artifact_contract': artifact_contract,
|
|
990
|
+
'artifact_production': artifact_production,
|
|
991
|
+
'artifact_usage': artifact_usage,
|
|
872
992
|
'after': {
|
|
873
993
|
'screenshot_url': state.get('after_cdn') or '',
|
|
874
994
|
'observation': after_observation,
|
|
@@ -909,6 +1029,26 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
|
|
|
909
1029
|
if has_success_criteria:
|
|
910
1030
|
evidence_basis.append('success-criteria')
|
|
911
1031
|
|
|
1032
|
+
artifact_contract = (evidence_bundle or {}).get('artifact_contract') if isinstance(evidence_bundle, dict) else None
|
|
1033
|
+
if not isinstance(artifact_contract, dict):
|
|
1034
|
+
artifact_contract = artifact_contract_for_mode(verification_mode)
|
|
1035
|
+
artifact_production = (evidence_bundle or {}).get('artifact_production') if isinstance(evidence_bundle, dict) else None
|
|
1036
|
+
if not isinstance(artifact_production, dict):
|
|
1037
|
+
artifact_production = artifact_production_summary(payload, supporting)
|
|
1038
|
+
artifact_usage = artifact_usage_summary(
|
|
1039
|
+
state,
|
|
1040
|
+
after_observation,
|
|
1041
|
+
supporting,
|
|
1042
|
+
visual_delta,
|
|
1043
|
+
required_baseline_present,
|
|
1044
|
+
semantic_context,
|
|
1045
|
+
evidence_basis,
|
|
1046
|
+
)
|
|
1047
|
+
if isinstance(evidence_bundle, dict):
|
|
1048
|
+
evidence_bundle['artifact_contract'] = artifact_contract
|
|
1049
|
+
evidence_bundle['artifact_production'] = artifact_production
|
|
1050
|
+
evidence_bundle['artifact_usage'] = artifact_usage
|
|
1051
|
+
|
|
912
1052
|
return {
|
|
913
1053
|
'status': 'needs_supervising_agent_assessment',
|
|
914
1054
|
'verification_mode': verification_mode,
|
|
@@ -920,6 +1060,9 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
|
|
|
920
1060
|
'semantic_context': semantic_context,
|
|
921
1061
|
'evidence_bundle': evidence_bundle or {},
|
|
922
1062
|
'evidence_basis': evidence_basis,
|
|
1063
|
+
'artifact_contract': artifact_contract,
|
|
1064
|
+
'artifact_production': artifact_production,
|
|
1065
|
+
'artifact_usage': artifact_usage,
|
|
923
1066
|
'instructions': [
|
|
924
1067
|
'The supervising agent owns proof assessment. Inspect the recon baseline(s), after evidence, and any structured artifacts together.',
|
|
925
1068
|
'Decide whether the evidence is ready_to_ship or should continue internally through author, implement, or recon.',
|
|
@@ -1028,14 +1171,17 @@ if existing_prod:
|
|
|
1028
1171
|
|
|
1029
1172
|
# AFTER (always from after worktree)
|
|
1030
1173
|
record_verify_phase('build', 'running', 'Building after worktree for verify capture.')
|
|
1031
|
-
print('Cleaning after .next cache...')
|
|
1032
|
-
sp.run('rm -rf .next', shell=True, cwd=after_dir, capture_output=True)
|
|
1033
|
-
|
|
1034
1174
|
print('Building after worktree...')
|
|
1035
|
-
|
|
1175
|
+
build_attempt = run_project_build(after_dir, build_cmd, timeout=600, clean_cache_dir='.next')
|
|
1176
|
+
br = build_attempt.get('result')
|
|
1177
|
+
if build_attempt.get('clean_retry_used'):
|
|
1178
|
+
print('Verify build recovered after cleaning .next cache.')
|
|
1036
1179
|
if br.returncode != 0:
|
|
1037
1180
|
record_verify_phase('build', 'failed', 'After build failed: ' + br.stderr[:300])
|
|
1038
1181
|
raise SystemExit('After build failed: ' + br.stderr[:500])
|
|
1182
|
+
if build_attempt.get('attempts'):
|
|
1183
|
+
s['verify_build_attempts'] = build_attempt['attempts']
|
|
1184
|
+
s['verify_build_clean_retry_used'] = bool(build_attempt.get('clean_retry_used'))
|
|
1039
1185
|
record_verify_phase('build', 'completed', 'After worktree build completed.')
|
|
1040
1186
|
|
|
1041
1187
|
after_payload = {}
|
|
@@ -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'
|
|
@@ -758,6 +790,20 @@ def run_verify_requests_supervisor_assessment():
|
|
|
758
790
|
assert semantic_context['after']['headings'] == ['Pricing'], semantic_context
|
|
759
791
|
assert 'semantic-context' in after_verify['proof_assessment_request']['evidence_basis']
|
|
760
792
|
assert after_verify['proof_assessment_request']['evidence_bundle']['semantic_context']['after']['buttons'] == ['Buy Now']
|
|
793
|
+
artifact_contract = after_verify['proof_assessment_request']['artifact_contract']
|
|
794
|
+
assert artifact_contract['required']['baseline_context'] is True
|
|
795
|
+
assert artifact_contract['required']['screenshot'] is True
|
|
796
|
+
artifact_production = after_verify['proof_assessment_request']['artifact_production']
|
|
797
|
+
assert artifact_production['image_output_count'] >= 1
|
|
798
|
+
assert artifact_production['proof_evidence_present'] is False
|
|
799
|
+
artifact_usage = after_verify['proof_assessment_request']['artifact_usage']
|
|
800
|
+
assert artifact_usage['missing_required_signals'] == []
|
|
801
|
+
assert 'after-capture' in artifact_usage['supervisor_review_signals']
|
|
802
|
+
assert 'baseline_context' in artifact_usage['required_signals']
|
|
803
|
+
assert 'route_semantics' in artifact_usage['available_signals']
|
|
804
|
+
assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_contract']['required']['screenshot'] is True
|
|
805
|
+
assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_production']['image_output_count'] >= 1
|
|
806
|
+
assert after_verify['proof_assessment_request']['evidence_bundle']['artifact_usage']['missing_required_signals'] == []
|
|
761
807
|
assert 'capture success is not proof' in '\n'.join(after_verify['proof_assessment_request']['instructions'])
|
|
762
808
|
assert after_verify['verify_decision_request']['continue_with_stage'] is None
|
|
763
809
|
assert after_verify['verify_results']['baseline']['before']['source'] == 'recon'
|
|
@@ -1257,6 +1303,7 @@ if __name__ == '__main__':
|
|
|
1257
1303
|
'capture_artifact_enrichment': run_capture_artifact_enrichment(),
|
|
1258
1304
|
'capture_diagnostics_redaction': run_capture_diagnostics_redact_sensitive_values(),
|
|
1259
1305
|
'apply_auth_context': run_apply_auth_context_passes_supported_auth_payloads(),
|
|
1306
|
+
'run_project_build_retries_after_clean_failure': run_project_build_retries_after_clean_failure(),
|
|
1260
1307
|
'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
|
|
1261
1308
|
'recon_then_author_request': run_recon_then_author_request(),
|
|
1262
1309
|
'recon_route_literal_preference': run_recon_prefers_route_literals_over_import_paths(),
|