@riddledc/riddle-proof 0.7.1 → 0.7.2

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.
@@ -120,7 +120,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
120
120
  target_image_hash: string;
121
121
  viewport_matrix_json: string;
122
122
  deterministic_setup_json: string;
123
- reference: "prod" | "before" | "both";
123
+ reference: "before" | "prod" | "both";
124
124
  base_branch: string;
125
125
  before_ref: string;
126
126
  allow_static_preview_fallback: string;
@@ -120,7 +120,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
120
120
  target_image_hash: string;
121
121
  viewport_matrix_json: string;
122
122
  deterministic_setup_json: string;
123
- reference: "prod" | "before" | "both";
123
+ reference: "before" | "prod" | "both";
124
124
  base_branch: string;
125
125
  before_ref: string;
126
126
  allow_static_preview_fallback: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -14,6 +14,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
14
  from util import (
15
15
  append_capture_diagnostic,
16
16
  apply_auth_context,
17
+ build_capture_script,
17
18
  build_visual_proof_session,
18
19
  capture_proof_session_seed,
19
20
  capture_static_preview,
@@ -21,6 +22,7 @@ from util import (
21
22
  has_auth_context,
22
23
  invoke,
23
24
  invoke_retry,
25
+ join_url_path,
24
26
  load_state,
25
27
  prepare_server_preview,
26
28
  record_successful_capture_hint,
@@ -86,6 +88,82 @@ def capture_script_saves_screenshot(script):
86
88
  return 'saveScreenshot' in (script or '')
87
89
 
88
90
 
91
+ def explicitly_false(value):
92
+ if value is False:
93
+ return True
94
+ if isinstance(value, str):
95
+ return value.strip().lower() in ('false', '0', 'no', 'off')
96
+ return False
97
+
98
+
99
+ def audit_no_diff_mode(state):
100
+ implementation_mode = str(state.get('implementation_mode') or '').strip().lower()
101
+ workflow_mode = str(state.get('workflow_mode') or '').strip().lower()
102
+ return (
103
+ state.get('implementation_status') == 'not_required'
104
+ or implementation_mode in ('none', 'audit', 'no_implementation', 'no-implementation')
105
+ or workflow_mode == 'audit'
106
+ or explicitly_false(state.get('require_diff'))
107
+ or explicitly_false(state.get('allow_code_changes'))
108
+ )
109
+
110
+
111
+ def implementation_ready_for_verify(state):
112
+ if audit_no_diff_mode(state):
113
+ return True
114
+ return state.get('implementation_status') in ('changes_detected', 'completed')
115
+
116
+
117
+ def visual_delta_required_for_state(state):
118
+ return visual_delta_applies(state.get('verification_mode')) and not audit_no_diff_mode(state)
119
+
120
+
121
+ def audit_current_capture_url(state, prod_url, expected_path):
122
+ target = (prod_url or '').strip()
123
+ server_path = (state.get('server_path') or '').strip()
124
+ if target:
125
+ parsed = urlparse(target)
126
+ if server_path and server_path != '/' and (not parsed.path or parsed.path == '/'):
127
+ return join_url_path(target, server_path)
128
+ return target
129
+ fallback = (state.get('target_url') or state.get('url') or '').strip()
130
+ if fallback:
131
+ return fallback
132
+ return ''
133
+
134
+
135
+ def capture_current_target(state, target_url, label, capture_script, timeout=300):
136
+ script = build_capture_script(target_url, capture_script, label, state.get('wait_for_selector', ''))
137
+ args = {'script': script, 'timeout_sec': 60}
138
+ apply_auth_context(state, args)
139
+ shot = invoke_retry('riddle_script', args, retries=3, timeout=max(timeout, 120))
140
+ screenshots = shot.get('screenshots') or []
141
+ url = screenshots[0].get('url', '') if screenshots else extract_screenshot_url(shot, label)
142
+ return {
143
+ 'ok': bool(url),
144
+ 'capture_url': target_url,
145
+ 'url': url,
146
+ 'raw': shot,
147
+ }
148
+
149
+
150
+ def baseline_payload_from_recon(state, results):
151
+ baseline = (results.get('baseline') or {}) if isinstance(results, dict) else {}
152
+ selected = baseline.get('prod') or baseline.get('before') or {}
153
+ url = selected.get('url') if isinstance(selected, dict) else ''
154
+ if not url:
155
+ return {'ok': False, 'error': 'Audit/no-diff verify has no current target URL and no recon screenshot to reuse.'}
156
+ return {
157
+ 'ok': True,
158
+ 'screenshots': [{'name': 'after-proof', 'url': url}],
159
+ 'result': {
160
+ 'audit_no_diff_reused_recon_baseline': True,
161
+ 'baseline_source': selected.get('source') or 'recon',
162
+ 'path': selected.get('path') or state.get('server_path') or '',
163
+ },
164
+ }
165
+
166
+
89
167
  def normalized_verification_mode(value):
90
168
  return ((value or 'proof').strip().lower() or 'proof')
91
169
 
@@ -1143,7 +1221,7 @@ def add_visual_delta_diagnostic(visual_delta, key, value):
1143
1221
 
1144
1222
 
1145
1223
  def measure_visual_delta_against_baseline(state, results, after_payload, current_visual_delta):
1146
- if not visual_delta_applies(state.get('verification_mode')):
1224
+ if not visual_delta_required_for_state(state):
1147
1225
  return current_visual_delta
1148
1226
  if isinstance(current_visual_delta, dict) and current_visual_delta.get('status') == 'measured':
1149
1227
  return current_visual_delta
@@ -1438,6 +1516,14 @@ def artifact_contract_for_mode(verification_mode):
1438
1516
  }
1439
1517
 
1440
1518
 
1519
+ def artifact_contract_for_state(state):
1520
+ contract = artifact_contract_for_mode(state.get('verification_mode'))
1521
+ if audit_no_diff_mode(state):
1522
+ contract['required']['visual_delta'] = False
1523
+ contract['optional']['visual_delta'] = True
1524
+ return contract
1525
+
1526
+
1441
1527
  def artifact_production_summary(payload, supporting):
1442
1528
  artifact_summary = summarize_capture_artifacts(payload)
1443
1529
  return {
@@ -1484,7 +1570,7 @@ def artifact_signal_availability(state, after_observation, supporting, visual_de
1484
1570
 
1485
1571
 
1486
1572
  def artifact_usage_summary(state, after_observation, supporting, visual_delta, required_baseline_present, semantic_context, evidence_basis):
1487
- contract = artifact_contract_for_mode(state.get('verification_mode'))
1573
+ contract = artifact_contract_for_state(state)
1488
1574
  available = artifact_signal_availability(
1489
1575
  state,
1490
1576
  after_observation,
@@ -1810,14 +1896,21 @@ def build_evidence_bundle(state, results, after_payload, after_observation, requ
1810
1896
  proof_evidence = extract_proof_evidence(after_payload)
1811
1897
  playability_assessment = assess_playability_evidence(proof_evidence)
1812
1898
  playability_evidence = extract_playability_evidence(proof_evidence)
1813
- visual_delta = (
1814
- extract_visual_delta(after_payload)
1815
- if visual_delta_applies(state.get('verification_mode'))
1816
- else {'status': 'not_applicable', 'passed': None, 'reason': 'Verification mode does not require visual delta gating.'}
1817
- )
1899
+ if audit_no_diff_mode(state):
1900
+ visual_delta = {
1901
+ 'status': 'not_applicable',
1902
+ 'passed': None,
1903
+ 'reason': 'Audit/no-diff verification judges current target evidence directly and does not require a before/after implementation delta.',
1904
+ }
1905
+ else:
1906
+ visual_delta = (
1907
+ extract_visual_delta(after_payload)
1908
+ if visual_delta_applies(state.get('verification_mode'))
1909
+ else {'status': 'not_applicable', 'passed': None, 'reason': 'Verification mode does not require visual delta gating.'}
1910
+ )
1818
1911
  visual_delta = measure_visual_delta_against_baseline(state, results, after_payload, visual_delta)
1819
1912
  semantic_context = build_semantic_context(state, results, after_observation, expected_path)
1820
- artifact_contract = artifact_contract_for_mode(state.get('verification_mode'))
1913
+ artifact_contract = artifact_contract_for_state(state)
1821
1914
  artifact_production = artifact_production_summary(after_payload, supporting)
1822
1915
  artifact_usage = artifact_usage_summary(
1823
1916
  state,
@@ -1918,7 +2011,7 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
1918
2011
 
1919
2012
  artifact_contract = (evidence_bundle or {}).get('artifact_contract') if isinstance(evidence_bundle, dict) else None
1920
2013
  if not isinstance(artifact_contract, dict):
1921
- artifact_contract = artifact_contract_for_mode(verification_mode)
2014
+ artifact_contract = artifact_contract_for_state(state)
1922
2015
  artifact_production = (evidence_bundle or {}).get('artifact_production') if isinstance(evidence_bundle, dict) else None
1923
2016
  if not isinstance(artifact_production, dict):
1924
2017
  artifact_production = artifact_production_summary(payload, supporting)
@@ -1935,7 +2028,7 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
1935
2028
  evidence_bundle['artifact_contract'] = artifact_contract
1936
2029
  evidence_bundle['artifact_production'] = artifact_production
1937
2030
  evidence_bundle['artifact_usage'] = artifact_usage
1938
- visual_delta_blocker = visual_delta_blocker_for_mode(verification_mode, visual_delta)
2031
+ visual_delta_blocker = '' if audit_no_diff_mode(state) else visual_delta_blocker_for_mode(verification_mode, visual_delta)
1939
2032
  hard_blockers = [visual_delta_blocker] if visual_delta_blocker else []
1940
2033
  if verification_mode in PLAYABILITY_MODES and not supporting.get('playability_ready'):
1941
2034
  assessment = supporting.get('playability_assessment') or {}
@@ -1946,6 +2039,30 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
1946
2039
  + (f': {detail}' if detail else '.')
1947
2040
  )
1948
2041
 
2042
+ instructions = [
2043
+ 'The supervising agent owns proof assessment. Inspect the recon baseline(s), after evidence, and any structured artifacts together.',
2044
+ 'Decide whether the evidence is ready_to_ship or should continue internally through author, implement, or recon.',
2045
+ 'Hard blockers cannot be overridden by supervisor judgment; if hard_blockers is non-empty, do not choose ready_to_ship.',
2046
+ 'Do not mark ready_to_ship if the before/prod baseline is blank, shell-only, generic, or not visibly tied to the requested feature.',
2047
+ 'Use semantic_context.route plus headings/buttons/text anchors to ground route and content judgment before treating a screenshot as wrong-route.',
2048
+ 'For visual/UI modes, use screenshots plus after_observation.details.visible_text_sample, headings, buttons, links, canvas_count, and large_visible_elements to explain what the proof actually shows.',
2049
+ ]
2050
+ if audit_no_diff_mode(state):
2051
+ instructions.append(
2052
+ 'Audit/no-diff mode intentionally has implementation_status=not_required and visual_delta.status=not_applicable; judge the current target evidence directly instead of requiring an implementation diff.'
2053
+ )
2054
+ else:
2055
+ instructions.append(
2056
+ 'For visual/UI polish, capture success is not proof. If visual_delta.status is unmeasured, missing, not_applicable, or measured with passed=false, choose needs_implementation or needs_richer_proof instead of ready_to_ship.'
2057
+ )
2058
+ instructions.extend([
2059
+ 'For playable/gameplay proof, screenshots are supporting evidence only. Do not mark ready_to_ship unless playability_assessment.passed is true and the proof shows accepted input, state/time progression, and playfield/canvas pixel motion.',
2060
+ 'For data/audio/log/metrics/custom modes, judge the structured evidence bundle and proof_evidence_sample directly; screenshots are optional supporting context.',
2061
+ 'The summary must name the concrete change, the target route/UI, what changed in after evidence, and why the stop condition is satisfied.',
2062
+ 'Only set escalation_target=human when you conclude the workflow has hit a real wall or is not converging.',
2063
+ 'Pass the judgment back via proof_assessment_json and resume the workflow.',
2064
+ ])
2065
+
1949
2066
  return {
1950
2067
  'status': 'needs_supervising_agent_assessment',
1951
2068
  'verification_mode': verification_mode,
@@ -1961,20 +2078,7 @@ def build_supervisor_assessment_request(state, payload, after_observation, requi
1961
2078
  'artifact_production': artifact_production,
1962
2079
  'artifact_usage': artifact_usage,
1963
2080
  'hard_blockers': hard_blockers,
1964
- 'instructions': [
1965
- 'The supervising agent owns proof assessment. Inspect the recon baseline(s), after evidence, and any structured artifacts together.',
1966
- 'Decide whether the evidence is ready_to_ship or should continue internally through author, implement, or recon.',
1967
- 'Hard blockers cannot be overridden by supervisor judgment; if hard_blockers is non-empty, do not choose ready_to_ship.',
1968
- 'Do not mark ready_to_ship if the before/prod baseline is blank, shell-only, generic, or not visibly tied to the requested feature.',
1969
- 'Use semantic_context.route plus headings/buttons/text anchors to ground route and content judgment before treating a screenshot as wrong-route.',
1970
- 'For visual/UI modes, use screenshots plus after_observation.details.visible_text_sample, headings, buttons, links, canvas_count, and large_visible_elements to explain what the proof actually shows.',
1971
- 'For visual/UI polish, capture success is not proof. If visual_delta.status is unmeasured, missing, not_applicable, or measured with passed=false, choose needs_implementation or needs_richer_proof instead of ready_to_ship.',
1972
- 'For playable/gameplay proof, screenshots are supporting evidence only. Do not mark ready_to_ship unless playability_assessment.passed is true and the proof shows accepted input, state/time progression, and playfield/canvas pixel motion.',
1973
- 'For data/audio/log/metrics/custom modes, judge the structured evidence bundle and proof_evidence_sample directly; screenshots are optional supporting context.',
1974
- 'The summary must name the concrete change, the target route/UI, what changed in after evidence, and why the stop condition is satisfied.',
1975
- 'Only set escalation_target=human when you conclude the workflow has hit a real wall or is not converging.',
1976
- 'Pass the judgment back via proof_assessment_json and resume the workflow.',
1977
- ],
2081
+ 'instructions': instructions,
1978
2082
  'response_schema': {
1979
2083
  'decision': 'ready_to_ship | needs_richer_proof | revise_capture | needs_recon | needs_implementation',
1980
2084
  'summary': 'string',
@@ -1992,14 +2096,22 @@ capture_script = (s.get('capture_script') or '').strip()
1992
2096
  if not capture_script:
1993
2097
  raise SystemExit('capture_script not set in state. Recon should finish homework first, then verify should receive the real capture plan.')
1994
2098
 
1995
- if s.get('implementation_status') not in ('changes_detected', 'completed'):
2099
+ no_implementation_mode = audit_no_diff_mode(s)
2100
+ if not implementation_ready_for_verify(s):
1996
2101
  raise SystemExit('Implementation not recorded. Make the code changes and run riddle-proof-implement before verify.')
2102
+ if no_implementation_mode and s.get('implementation_status') != 'not_required':
2103
+ s['implementation_status'] = 'not_required'
2104
+ s['implementation_mode'] = s.get('implementation_mode') or 'none'
2105
+ if 'require_diff' not in s:
2106
+ s['require_diff'] = False
2107
+ if 'allow_code_changes' not in s:
2108
+ s['allow_code_changes'] = False
1997
2109
 
1998
2110
  mode = s.get('mode', 'server')
1999
2111
  reference = s.get('requested_reference') or s.get('reference', 'both')
2000
2112
  prod_url = (s.get('prod_url') or '').strip()
2001
2113
  after_dir = s.get('after_worktree', '').strip()
2002
- if not after_dir or not os.path.exists(after_dir):
2114
+ if not no_implementation_mode and (not after_dir or not os.path.exists(after_dir)):
2003
2115
  raise SystemExit('after_worktree not found. Run setup first.')
2004
2116
 
2005
2117
  build_cmd = s.get('build_command', 'npm run build')
@@ -2069,82 +2181,122 @@ if existing_before:
2069
2181
  if existing_prod:
2070
2182
  print('Prod baseline: ' + existing_prod)
2071
2183
 
2072
- # AFTER (always from after worktree)
2073
- record_verify_phase('build', 'running', 'Building after worktree for verify capture.')
2074
- print('Building after worktree...')
2075
- build_attempt = run_project_build(after_dir, build_cmd, timeout=600, clean_cache_dir='.next')
2076
- br = build_attempt.get('result')
2077
- if build_attempt.get('clean_retry_used'):
2078
- print('Verify build recovered after cleaning .next cache.')
2079
- if br.returncode != 0:
2080
- record_verify_phase('build', 'failed', 'After build failed: ' + br.stderr[:300])
2081
- raise SystemExit('After build failed: ' + br.stderr[:500])
2082
- if build_attempt.get('attempts'):
2083
- s['verify_build_attempts'] = build_attempt['attempts']
2084
- s['verify_build_clean_retry_used'] = bool(build_attempt.get('clean_retry_used'))
2085
- record_verify_phase('build', 'completed', 'After worktree build completed.')
2086
-
2087
2184
  after_payload = {}
2088
- static_reason = should_use_static_preview(after_dir, s) if mode == 'server' else ''
2089
- record_verify_phase('capture', 'running', 'Capturing after-proof evidence.')
2090
- if mode == 'server' and not static_reason:
2091
- build_dir, server_command, server_exclude = prepare_server_preview(after_dir, s)
2092
-
2093
- server_args = {
2094
- 'directory': build_dir,
2095
- 'image': s['server_image'],
2096
- 'command': server_command,
2097
- 'port': int(s['server_port']),
2098
- 'wait_until': 'domcontentloaded',
2099
- 'readiness_timeout': 180,
2100
- 'timeout': 300,
2101
- 'env': {'PORT': str(s['server_port']), 'HOSTNAME': '0.0.0.0'},
2102
- 'exclude': server_exclude,
2103
- }
2104
- if s.get('server_path'):
2105
- server_args['path'] = s['server_path']
2106
- server_args['readiness_path'] = '/' if has_auth_context(s) else s['server_path']
2107
- if s.get('color_scheme'):
2108
- server_args['color_scheme'] = s['color_scheme']
2109
- if s.get('wait_for_selector'):
2110
- server_args['wait_for_selector'] = s['wait_for_selector']
2111
- apply_auth_context(s, server_args)
2112
- server_args['script'] = probe_capture_script
2113
-
2114
- print('Running after server preview from: ' + build_dir)
2115
- shot = invoke_retry('riddle_server_preview', server_args, retries=3, timeout=420)
2116
- append_capture_diagnostic(s, 'after', 'riddle_server_preview', server_args, shot)
2117
- after_payload = shot
2118
- capture_error = capture_payload_error(after_payload)
2119
- if capture_error:
2120
- abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2121
- url = extract_screenshot_url(shot, 'after-proof')
2122
- if not url:
2123
- print('WARNING: After server preview no screenshot.')
2124
- results['after'] = {'screenshots': [{'url': url}] if url else [], 'raw': shot}
2125
- s['after_cdn'] = url
2126
-
2185
+ if no_implementation_mode:
2186
+ record_verify_phase('build', 'completed', 'Audit/no-diff mode skips after-worktree build.')
2187
+ current_url = audit_current_capture_url(s, prod_url, expected_path)
2188
+ record_verify_phase('capture', 'running', 'Capturing current target evidence for audit/no-diff verify.')
2189
+ if current_url:
2190
+ print('Audit/no-diff verify capture at: ' + current_url)
2191
+ capture = capture_current_target(s, current_url, 'after-proof', probe_capture_script, timeout=300)
2192
+ after_payload = capture.get('raw') or capture
2193
+ append_capture_diagnostic(
2194
+ s,
2195
+ 'after',
2196
+ 'riddle_script',
2197
+ {'target_url': current_url, 'audit_no_diff': True},
2198
+ after_payload,
2199
+ )
2200
+ capture_error = capture_payload_error(after_payload)
2201
+ if capture_error:
2202
+ abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2203
+ results['after'] = {'screenshots': [{'url': capture.get('url', '')}] if capture.get('url') else [], 'raw': after_payload}
2204
+ s['after_cdn'] = capture.get('url', '')
2205
+ s['after_capture_source'] = 'audit_current_target'
2206
+ s['audit_current_url'] = current_url
2207
+ else:
2208
+ print('Audit/no-diff verify has no current target URL; reusing recon screenshot as current evidence.')
2209
+ after_payload = baseline_payload_from_recon(s, results)
2210
+ append_capture_diagnostic(
2211
+ s,
2212
+ 'after',
2213
+ 'recon_baseline_reuse',
2214
+ {'audit_no_diff': True},
2215
+ after_payload,
2216
+ )
2217
+ capture_error = capture_payload_error(after_payload)
2218
+ if capture_error:
2219
+ abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2220
+ url = extract_screenshot_url(after_payload, 'after-proof')
2221
+ results['after'] = {'screenshots': [{'url': url}] if url else [], 'raw': after_payload}
2222
+ s['after_cdn'] = url
2223
+ s['after_capture_source'] = 'audit_recon_baseline'
2127
2224
  else:
2128
- if static_reason:
2129
- print('Static preview fallback for after capture: ' + static_reason)
2130
- old_id = s.get('after_preview_id', '')
2131
- if old_id:
2132
- invoke('riddle_preview_delete', {'id': old_id}, timeout=30)
2133
- capture = capture_static_preview(s, after_dir, 'after', probe_capture_script, timeout=300, target_path=s.get('server_path', ''))
2134
- s['after_preview_id'] = capture.get('preview_id', '')
2135
- after_payload = ((capture.get('raw') or {}).get('capture') or (capture.get('raw') or {}) or capture)
2136
- append_capture_diagnostic(
2137
- s,
2138
- 'after',
2139
- 'riddle_static_preview',
2140
- {'target_path': s.get('server_path', ''), 'static_fallback_reason': static_reason},
2141
- after_payload,
2142
- )
2143
- capture_error = capture_payload_error(after_payload)
2144
- if capture_error:
2145
- abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2146
- results['after'] = {'screenshots': [{'url': capture.get('url', '')}] if capture.get('url') else [], 'raw': capture.get('raw')}
2147
- s['after_cdn'] = capture.get('url', '')
2225
+ # AFTER (from after worktree)
2226
+ record_verify_phase('build', 'running', 'Building after worktree for verify capture.')
2227
+ print('Building after worktree...')
2228
+ build_attempt = run_project_build(after_dir, build_cmd, timeout=600, clean_cache_dir='.next')
2229
+ br = build_attempt.get('result')
2230
+ if build_attempt.get('clean_retry_used'):
2231
+ print('Verify build recovered after cleaning .next cache.')
2232
+ if br.returncode != 0:
2233
+ record_verify_phase('build', 'failed', 'After build failed: ' + br.stderr[:300])
2234
+ raise SystemExit('After build failed: ' + br.stderr[:500])
2235
+ if build_attempt.get('attempts'):
2236
+ s['verify_build_attempts'] = build_attempt['attempts']
2237
+ s['verify_build_clean_retry_used'] = bool(build_attempt.get('clean_retry_used'))
2238
+ record_verify_phase('build', 'completed', 'After worktree build completed.')
2239
+
2240
+ static_reason = should_use_static_preview(after_dir, s) if mode == 'server' else ''
2241
+ record_verify_phase('capture', 'running', 'Capturing after-proof evidence.')
2242
+ if mode == 'server' and not static_reason:
2243
+ build_dir, server_command, server_exclude = prepare_server_preview(after_dir, s)
2244
+
2245
+ server_args = {
2246
+ 'directory': build_dir,
2247
+ 'image': s['server_image'],
2248
+ 'command': server_command,
2249
+ 'port': int(s['server_port']),
2250
+ 'wait_until': 'domcontentloaded',
2251
+ 'readiness_timeout': 180,
2252
+ 'timeout': 300,
2253
+ 'env': {'PORT': str(s['server_port']), 'HOSTNAME': '0.0.0.0'},
2254
+ 'exclude': server_exclude,
2255
+ }
2256
+ if s.get('server_path'):
2257
+ server_args['path'] = s['server_path']
2258
+ server_args['readiness_path'] = '/' if has_auth_context(s) else s['server_path']
2259
+ if s.get('color_scheme'):
2260
+ server_args['color_scheme'] = s['color_scheme']
2261
+ if s.get('wait_for_selector'):
2262
+ server_args['wait_for_selector'] = s['wait_for_selector']
2263
+ apply_auth_context(s, server_args)
2264
+ server_args['script'] = probe_capture_script
2265
+
2266
+ print('Running after server preview from: ' + build_dir)
2267
+ shot = invoke_retry('riddle_server_preview', server_args, retries=3, timeout=420)
2268
+ append_capture_diagnostic(s, 'after', 'riddle_server_preview', server_args, shot)
2269
+ after_payload = shot
2270
+ capture_error = capture_payload_error(after_payload)
2271
+ if capture_error:
2272
+ abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2273
+ url = extract_screenshot_url(shot, 'after-proof')
2274
+ if not url:
2275
+ print('WARNING: After server preview no screenshot.')
2276
+ results['after'] = {'screenshots': [{'url': url}] if url else [], 'raw': shot}
2277
+ s['after_cdn'] = url
2278
+
2279
+ else:
2280
+ if static_reason:
2281
+ print('Static preview fallback for after capture: ' + static_reason)
2282
+ old_id = s.get('after_preview_id', '')
2283
+ if old_id:
2284
+ invoke('riddle_preview_delete', {'id': old_id}, timeout=30)
2285
+ capture = capture_static_preview(s, after_dir, 'after', probe_capture_script, timeout=300, target_path=s.get('server_path', ''))
2286
+ s['after_preview_id'] = capture.get('preview_id', '')
2287
+ after_payload = ((capture.get('raw') or {}).get('capture') or (capture.get('raw') or {}) or capture)
2288
+ append_capture_diagnostic(
2289
+ s,
2290
+ 'after',
2291
+ 'riddle_static_preview',
2292
+ {'target_path': s.get('server_path', ''), 'static_fallback_reason': static_reason},
2293
+ after_payload,
2294
+ )
2295
+ capture_error = capture_payload_error(after_payload)
2296
+ if capture_error:
2297
+ abort_capture_failure(s, results, expected_path, capture_error, after_payload)
2298
+ results['after'] = {'screenshots': [{'url': capture.get('url', '')}] if capture.get('url') else [], 'raw': capture.get('raw')}
2299
+ s['after_cdn'] = capture.get('url', '')
2148
2300
 
2149
2301
  after_observation = evaluate_capture_quality(after_payload, expected_path, verification_mode)
2150
2302
  results['after']['observation'] = after_observation
@@ -2237,7 +2389,7 @@ if s.get('proof_session_artifact_url'):
2237
2389
  visual_delta = ((evidence_bundle.get('after') or {}).get('visual_delta') or {})
2238
2390
  if visual_delta.get('status') != 'not_applicable':
2239
2391
  summary_lines.append('Visual delta gate: ' + compact_value(visual_delta, limit=700))
2240
- visual_delta_blocker = visual_delta_blocker_for_mode(s.get('verification_mode'), visual_delta)
2392
+ visual_delta_blocker = '' if no_implementation_mode else visual_delta_blocker_for_mode(s.get('verification_mode'), visual_delta)
2241
2393
  if visual_delta_blocker:
2242
2394
  summary_lines.append('Visual delta hard gate: ' + visual_delta_blocker)
2243
2395
 
@@ -2257,7 +2409,7 @@ visual_delta_recovery = build_visual_delta_recovery_decision(
2257
2409
  s.get('verification_mode'),
2258
2410
  visual_delta,
2259
2411
  visual_delta_blocker,
2260
- )
2412
+ ) if not no_implementation_mode else None
2261
2413
  if visual_delta_recovery:
2262
2414
  summary_lines.append('Visual delta recovery: ' + visual_delta_recovery['summary'])
2263
2415
 
@@ -2285,15 +2437,19 @@ if has_good_evidence:
2285
2437
  s['proof_assessment'] = {}
2286
2438
  s['proof_assessment_source'] = None
2287
2439
  s['proof_assessment_request'] = supervisor_request
2440
+ next_stage_options = ['verify', 'author', 'recon'] if no_implementation_mode else ['verify', 'author', 'implement', 'ship', 'recon']
2441
+ fields_agent_may_update = ['proof_assessment_json', 'capture_script', 'server_path', 'wait_for_selector', 'proof_plan', 'assertions_json']
2442
+ if not no_implementation_mode:
2443
+ fields_agent_may_update.append('implementation_notes')
2288
2444
  s['verify_decision_request'] = {
2289
2445
  'status': s['verify_status'],
2290
2446
  'summary': 'Verify captured usable evidence and is waiting for supervising-agent proof assessment.',
2291
2447
  'expected_path': expected_path,
2292
2448
  'latest_observation': after_observation,
2293
- 'next_stage_options': ['verify', 'author', 'implement', 'ship', 'recon'],
2449
+ 'next_stage_options': next_stage_options,
2294
2450
  'recommended_stage': None,
2295
2451
  'continue_with_stage': None,
2296
- 'fields_agent_may_update': ['proof_assessment_json', 'capture_script', 'server_path', 'wait_for_selector', 'implementation_notes', 'proof_plan', 'assertions_json'],
2452
+ 'fields_agent_may_update': fields_agent_may_update,
2297
2453
  'assessment_request': supervisor_request,
2298
2454
  'instructions': [
2299
2455
  'Inspect the recon baseline(s), after evidence, and any structured artifacts together.',
@@ -2305,6 +2461,7 @@ if has_good_evidence:
2305
2461
  summary_lines.append('Proof next stage: supervising agent decides after reviewing the evidence packet')
2306
2462
  else:
2307
2463
  capture_retry = visual_delta_recovery or build_capture_retry_decision(after_observation, required_baseline_present, proof_evidence_blocker)
2464
+ next_stage_options = ['author', 'verify', 'recon'] if no_implementation_mode else ['author', 'verify', 'implement', 'recon']
2308
2465
  s['verify_status'] = 'capture_incomplete'
2309
2466
  s['merge_recommendation'] = 'do-not-merge'
2310
2467
  s['proof_assessment'] = {}
@@ -2316,7 +2473,7 @@ else:
2316
2473
  'expected_path': expected_path,
2317
2474
  'latest_observation': after_observation,
2318
2475
  'capture_quality': capture_retry,
2319
- 'next_stage_options': ['author', 'verify', 'implement', 'recon'],
2476
+ 'next_stage_options': next_stage_options,
2320
2477
  'recommended_stage': capture_retry.get('recommended_stage') or 'author',
2321
2478
  'continue_with_stage': capture_retry.get('continue_with_stage') or 'author',
2322
2479
  'fields_agent_may_update': ['capture_script', 'server_path', 'wait_for_selector', 'proof_plan'],
@@ -15,8 +15,27 @@ steps:
15
15
  s = json.load(open(state_file))
16
16
  if not s.get('workspace_ready'):
17
17
  raise SystemExit('Workspace not ready. Run riddle-proof-setup first.')
18
- if s.get('implementation_status') not in ('changes_detected', 'completed'):
18
+ implementation_mode = str(s.get('implementation_mode') or '').strip().lower()
19
+ workflow_mode = str(s.get('workflow_mode') or '').strip().lower()
20
+ require_diff = s.get('require_diff')
21
+ allow_code_changes = s.get('allow_code_changes')
22
+ def explicitly_false(value):
23
+ return value is False or str(value).strip().lower() in ('false', '0', 'no', 'off')
24
+ no_implementation_mode = (
25
+ s.get('implementation_status') == 'not_required'
26
+ or implementation_mode in ('none', 'audit', 'no_implementation', 'no-implementation')
27
+ or workflow_mode == 'audit'
28
+ or explicitly_false(require_diff)
29
+ or explicitly_false(allow_code_changes)
30
+ )
31
+ if not no_implementation_mode and s.get('implementation_status') not in ('changes_detected', 'completed'):
19
32
  raise SystemExit('Implementation not recorded. Run riddle-proof-implement after making code changes.')
33
+ if no_implementation_mode:
34
+ s['implementation_status'] = 'not_required'
35
+ s['implementation_mode'] = s.get('implementation_mode') or 'none'
36
+ s.setdefault('require_diff', False)
37
+ s.setdefault('allow_code_changes', False)
38
+ json.dump(s, open(state_file, 'w'), indent=2)
20
39
  print('VERIFY — ' + s.get('mode', 'server').upper())
21
40
  print('Proof goal: ' + s.get('change_request', ''))
22
41
  print('Success criteria: ' + (s.get('success_criteria') or '(none provided)'))
@@ -27,7 +46,10 @@ steps:
27
46
  if s.get('prod_cdn'):
28
47
  print('Prod baseline: ' + s.get('prod_cdn'))
29
48
  after_dir = s.get('after_worktree', '')
30
- print('After worktree: ' + after_dir + (' (exists)' if os.path.exists(after_dir) else ' (MISSING)'))
49
+ if no_implementation_mode:
50
+ print('After worktree: not required for audit/no-diff verify')
51
+ else:
52
+ print('After worktree: ' + after_dir + (' (exists)' if os.path.exists(after_dir) else ' (MISSING)'))
31
53
  PYEOF
32
54
 
33
55
  - id: run_verify