@riddledc/riddle-proof 0.8.12 → 0.8.14
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/dist/advanced/engine-harness.cjs +101 -31
- package/dist/advanced/engine-harness.js +1 -1
- package/dist/advanced/index.cjs +101 -31
- package/dist/advanced/index.d.cts +2 -2
- package/dist/advanced/index.d.ts +2 -2
- package/dist/advanced/index.js +2 -2
- package/dist/advanced/proof-run-core.d.cts +1 -1
- package/dist/advanced/proof-run-core.d.ts +1 -1
- package/dist/advanced/proof-run-engine.cjs +101 -31
- package/dist/advanced/proof-run-engine.d.cts +2 -2
- package/dist/advanced/proof-run-engine.d.ts +2 -2
- package/dist/advanced/proof-run-engine.js +1 -1
- package/dist/{chunk-SZUC4MDN.js → chunk-EGZT3EVL.js} +1 -1
- package/dist/{chunk-JBY2SU5U.js → chunk-WJZYRUNV.js} +101 -31
- package/dist/cli/index.js +2 -2
- package/dist/cli.cjs +101 -31
- package/dist/cli.js +2 -2
- package/dist/engine-harness.cjs +101 -31
- package/dist/engine-harness.js +1 -1
- package/dist/index.cjs +101 -31
- package/dist/index.js +1 -1
- package/dist/{proof-run-core-CrpYH-qH.d.ts → proof-run-core-C8FDUhle.d.cts} +1 -1
- package/dist/{proof-run-core-CrpYH-qH.d.cts → proof-run-core-C8FDUhle.d.ts} +1 -1
- package/dist/proof-run-core.d.cts +1 -1
- package/dist/proof-run-core.d.ts +1 -1
- package/dist/{proof-run-engine-h9C1lC0w.d.ts → proof-run-engine-By7oLsF-.d.ts} +4 -4
- package/dist/{proof-run-engine-C6vYAZd8.d.cts → proof-run-engine-D80hVFMf.d.cts} +4 -4
- package/dist/proof-run-engine.cjs +101 -31
- package/dist/proof-run-engine.d.cts +2 -2
- package/dist/proof-run-engine.d.ts +2 -2
- package/dist/proof-run-engine.js +1 -1
- package/package.json +1 -1
- package/runtime/lib/verify.py +179 -11
- package/runtime/tests/recon_verify_smoke.py +142 -0
- package/runtime/tests/trust_boundary_regression.py +6 -0
- /package/dist/{chunk-RTLA6CPP.js → chunk-YFRPFV4U.js} +0 -0
package/runtime/lib/verify.py
CHANGED
|
@@ -2029,7 +2029,7 @@ def interaction_proof_route_match(expected_path, proof_evidence):
|
|
|
2029
2029
|
return None
|
|
2030
2030
|
for record in proof_evidence_records_deep(proof_evidence):
|
|
2031
2031
|
flag = explicit_route_match_flag(record)
|
|
2032
|
-
candidate =
|
|
2032
|
+
candidate = observed_terminal_path_from_record(record)
|
|
2033
2033
|
if candidate and route_matches_expected(expected, candidate):
|
|
2034
2034
|
return {
|
|
2035
2035
|
'matched': True,
|
|
@@ -2042,23 +2042,64 @@ def interaction_proof_route_match(expected_path, proof_evidence):
|
|
|
2042
2042
|
|
|
2043
2043
|
|
|
2044
2044
|
EXPLICIT_TERMINAL_PATH_KEYS = (
|
|
2045
|
+
'expected_url', 'expectedUrl',
|
|
2046
|
+
'expected_href', 'expectedHref',
|
|
2045
2047
|
'expected_terminal_path', 'expectedTerminalPath',
|
|
2046
2048
|
'expected_terminal_url', 'expectedTerminalUrl',
|
|
2049
|
+
'expected_terminal_href', 'expectedTerminalHref',
|
|
2047
2050
|
'expected_terminal_route', 'expectedTerminalRoute',
|
|
2051
|
+
'expected_route', 'expectedRoute',
|
|
2048
2052
|
'terminal_path', 'terminalPath',
|
|
2049
2053
|
'terminal_url', 'terminalUrl',
|
|
2054
|
+
'terminal_href', 'terminalHref',
|
|
2050
2055
|
'terminal_route', 'terminalRoute',
|
|
2051
2056
|
'expected_after_path', 'expectedAfterPath',
|
|
2052
2057
|
'expected_after_url', 'expectedAfterUrl',
|
|
2058
|
+
'expected_after_href', 'expectedAfterHref',
|
|
2053
2059
|
'expected_after_route', 'expectedAfterRoute',
|
|
2054
2060
|
'after_path', 'afterPath',
|
|
2055
2061
|
'after_url', 'afterUrl',
|
|
2062
|
+
'after_href', 'afterHref',
|
|
2056
2063
|
'after_route', 'afterRoute',
|
|
2057
2064
|
'expected_final_path', 'expectedFinalPath',
|
|
2058
2065
|
'expected_final_url', 'expectedFinalUrl',
|
|
2066
|
+
'expected_final_href', 'expectedFinalHref',
|
|
2059
2067
|
'expected_final_route', 'expectedFinalRoute',
|
|
2060
2068
|
'final_path', 'finalPath',
|
|
2061
2069
|
'final_url', 'finalUrl',
|
|
2070
|
+
'final_href', 'finalHref',
|
|
2071
|
+
'final_route', 'finalRoute',
|
|
2072
|
+
)
|
|
2073
|
+
EXPECTED_TERMINAL_PATH_KEYS = (
|
|
2074
|
+
'expected_url', 'expectedUrl',
|
|
2075
|
+
'expected_href', 'expectedHref',
|
|
2076
|
+
'expected_path', 'expectedPath',
|
|
2077
|
+
'expected_route', 'expectedRoute',
|
|
2078
|
+
'expected_terminal_path', 'expectedTerminalPath',
|
|
2079
|
+
'expected_terminal_url', 'expectedTerminalUrl',
|
|
2080
|
+
'expected_terminal_href', 'expectedTerminalHref',
|
|
2081
|
+
'expected_terminal_route', 'expectedTerminalRoute',
|
|
2082
|
+
'expected_after_path', 'expectedAfterPath',
|
|
2083
|
+
'expected_after_url', 'expectedAfterUrl',
|
|
2084
|
+
'expected_after_href', 'expectedAfterHref',
|
|
2085
|
+
'expected_after_route', 'expectedAfterRoute',
|
|
2086
|
+
'expected_final_path', 'expectedFinalPath',
|
|
2087
|
+
'expected_final_url', 'expectedFinalUrl',
|
|
2088
|
+
'expected_final_href', 'expectedFinalHref',
|
|
2089
|
+
'expected_final_route', 'expectedFinalRoute',
|
|
2090
|
+
)
|
|
2091
|
+
OBSERVED_TERMINAL_PATH_KEYS = (
|
|
2092
|
+
'terminal_path', 'terminalPath',
|
|
2093
|
+
'terminal_url', 'terminalUrl',
|
|
2094
|
+
'terminal_href', 'terminalHref',
|
|
2095
|
+
'terminal_route', 'terminalRoute',
|
|
2096
|
+
'after_path', 'afterPath',
|
|
2097
|
+
'after_url', 'afterUrl',
|
|
2098
|
+
'after_href', 'afterHref',
|
|
2099
|
+
'after_route', 'afterRoute',
|
|
2100
|
+
'final_path', 'finalPath',
|
|
2101
|
+
'final_url', 'finalUrl',
|
|
2102
|
+
'final_href', 'finalHref',
|
|
2062
2103
|
'final_route', 'finalRoute',
|
|
2063
2104
|
)
|
|
2064
2105
|
FULL_LOCATION_PATH_KEYS = (
|
|
@@ -2087,6 +2128,19 @@ AFTER_STATE_KEYS = (
|
|
|
2087
2128
|
'final', 'final_state', 'finalState',
|
|
2088
2129
|
'expected_final', 'expectedFinal',
|
|
2089
2130
|
)
|
|
2131
|
+
EXPECTED_STATE_KEYS = (
|
|
2132
|
+
'expected', 'expectation',
|
|
2133
|
+
'route_expectation', 'routeExpectation',
|
|
2134
|
+
'expected_after', 'expectedAfter',
|
|
2135
|
+
'expected_terminal', 'expectedTerminal',
|
|
2136
|
+
'expected_final', 'expectedFinal',
|
|
2137
|
+
)
|
|
2138
|
+
OBSERVED_STATE_KEYS = (
|
|
2139
|
+
'observed', 'actual',
|
|
2140
|
+
'after', 'after_state', 'afterState',
|
|
2141
|
+
'terminal', 'terminal_state', 'terminalState',
|
|
2142
|
+
'final', 'final_state', 'finalState',
|
|
2143
|
+
)
|
|
2090
2144
|
EVIDENCE_CONTAINER_KEYS = (
|
|
2091
2145
|
'proofEvidence', 'proof_evidence',
|
|
2092
2146
|
'interactionEvidence', 'interaction_evidence',
|
|
@@ -2113,10 +2167,10 @@ def path_candidate(value):
|
|
|
2113
2167
|
return ''
|
|
2114
2168
|
|
|
2115
2169
|
|
|
2116
|
-
def
|
|
2170
|
+
def record_path_candidate_for_keys(record, keys, allow_location_keys=False):
|
|
2117
2171
|
if not isinstance(record, dict):
|
|
2118
2172
|
return ''
|
|
2119
|
-
keys = list(
|
|
2173
|
+
keys = list(keys)
|
|
2120
2174
|
if allow_location_keys:
|
|
2121
2175
|
keys.extend(LOCATION_PATH_KEYS)
|
|
2122
2176
|
for key in keys:
|
|
@@ -2126,6 +2180,10 @@ def record_path_candidate(record, allow_location_keys=False):
|
|
|
2126
2180
|
return ''
|
|
2127
2181
|
|
|
2128
2182
|
|
|
2183
|
+
def record_path_candidate(record, allow_location_keys=False):
|
|
2184
|
+
return record_path_candidate_for_keys(record, EXPLICIT_TERMINAL_PATH_KEYS, allow_location_keys)
|
|
2185
|
+
|
|
2186
|
+
|
|
2129
2187
|
def terminal_path_from_record(record, depth=0):
|
|
2130
2188
|
if not isinstance(record, dict) or depth > 4:
|
|
2131
2189
|
return ''
|
|
@@ -2168,6 +2226,74 @@ def terminal_path_from_record(record, depth=0):
|
|
|
2168
2226
|
return ''
|
|
2169
2227
|
|
|
2170
2228
|
|
|
2229
|
+
def expected_terminal_path_from_record(record, depth=0):
|
|
2230
|
+
if not isinstance(record, dict) or depth > 4:
|
|
2231
|
+
return ''
|
|
2232
|
+
candidate = record_path_candidate_for_keys(record, EXPECTED_TERMINAL_PATH_KEYS)
|
|
2233
|
+
if candidate:
|
|
2234
|
+
return candidate
|
|
2235
|
+
for key in EXPECTED_STATE_KEYS:
|
|
2236
|
+
value = record.get(key)
|
|
2237
|
+
if isinstance(value, dict):
|
|
2238
|
+
candidate = (
|
|
2239
|
+
record_path_candidate_for_keys(value, EXPECTED_TERMINAL_PATH_KEYS, allow_location_keys=True)
|
|
2240
|
+
or expected_terminal_path_from_record(value, depth + 1)
|
|
2241
|
+
)
|
|
2242
|
+
if candidate:
|
|
2243
|
+
return candidate
|
|
2244
|
+
elif isinstance(value, list):
|
|
2245
|
+
for item in value:
|
|
2246
|
+
candidate = expected_terminal_path_from_record(item, depth + 1)
|
|
2247
|
+
if candidate:
|
|
2248
|
+
return candidate
|
|
2249
|
+
for key in EVIDENCE_CONTAINER_KEYS:
|
|
2250
|
+
value = record.get(key)
|
|
2251
|
+
if isinstance(value, dict):
|
|
2252
|
+
candidate = expected_terminal_path_from_record(value, depth + 1)
|
|
2253
|
+
if candidate:
|
|
2254
|
+
return candidate
|
|
2255
|
+
elif isinstance(value, list):
|
|
2256
|
+
for item in value:
|
|
2257
|
+
candidate = expected_terminal_path_from_record(item, depth + 1)
|
|
2258
|
+
if candidate:
|
|
2259
|
+
return candidate
|
|
2260
|
+
return ''
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
def observed_terminal_path_from_record(record, depth=0):
|
|
2264
|
+
if not isinstance(record, dict) or depth > 4:
|
|
2265
|
+
return ''
|
|
2266
|
+
candidate = record_path_candidate_for_keys(record, OBSERVED_TERMINAL_PATH_KEYS)
|
|
2267
|
+
if candidate:
|
|
2268
|
+
return candidate
|
|
2269
|
+
for key in OBSERVED_STATE_KEYS:
|
|
2270
|
+
value = record.get(key)
|
|
2271
|
+
if isinstance(value, dict):
|
|
2272
|
+
candidate = (
|
|
2273
|
+
record_path_candidate_for_keys(value, OBSERVED_TERMINAL_PATH_KEYS, allow_location_keys=True)
|
|
2274
|
+
or observed_terminal_path_from_record(value, depth + 1)
|
|
2275
|
+
)
|
|
2276
|
+
if candidate:
|
|
2277
|
+
return candidate
|
|
2278
|
+
elif isinstance(value, list):
|
|
2279
|
+
for item in value:
|
|
2280
|
+
candidate = observed_terminal_path_from_record(item, depth + 1)
|
|
2281
|
+
if candidate:
|
|
2282
|
+
return candidate
|
|
2283
|
+
for key in EVIDENCE_CONTAINER_KEYS:
|
|
2284
|
+
value = record.get(key)
|
|
2285
|
+
if isinstance(value, dict):
|
|
2286
|
+
candidate = observed_terminal_path_from_record(value, depth + 1)
|
|
2287
|
+
if candidate:
|
|
2288
|
+
return candidate
|
|
2289
|
+
elif isinstance(value, list):
|
|
2290
|
+
for item in value:
|
|
2291
|
+
candidate = observed_terminal_path_from_record(item, depth + 1)
|
|
2292
|
+
if candidate:
|
|
2293
|
+
return candidate
|
|
2294
|
+
return ''
|
|
2295
|
+
|
|
2296
|
+
|
|
2171
2297
|
def text_path_candidate(value):
|
|
2172
2298
|
if not isinstance(value, str):
|
|
2173
2299
|
return ''
|
|
@@ -2421,17 +2547,20 @@ def interaction_capture_failure_evidence_summary(proof_evidence):
|
|
|
2421
2547
|
|
|
2422
2548
|
def interaction_terminal_path_from_evidence(proof_evidence):
|
|
2423
2549
|
for record in proof_evidence_records(proof_evidence):
|
|
2424
|
-
candidate =
|
|
2550
|
+
candidate = expected_terminal_path_from_record(record)
|
|
2425
2551
|
if candidate:
|
|
2426
2552
|
return candidate, 'proof_evidence_contract'
|
|
2427
|
-
|
|
2553
|
+
route_evidence_passed = interaction_assertions_pass(proof_evidence)
|
|
2554
|
+
if not route_evidence_passed:
|
|
2555
|
+
for record in proof_evidence_records_deep(proof_evidence):
|
|
2556
|
+
if interaction_assertions_pass(record) or explicit_route_match_flag(record) is True:
|
|
2557
|
+
route_evidence_passed = True
|
|
2558
|
+
break
|
|
2559
|
+
if route_evidence_passed:
|
|
2428
2560
|
for record in proof_evidence_records(proof_evidence):
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
candidate = record_path_candidate(value, allow_location_keys=True)
|
|
2433
|
-
if candidate:
|
|
2434
|
-
return candidate, 'proof_evidence_after_state'
|
|
2561
|
+
candidate = observed_terminal_path_from_record(record)
|
|
2562
|
+
if candidate:
|
|
2563
|
+
return candidate, 'proof_evidence_contract'
|
|
2435
2564
|
return '', ''
|
|
2436
2565
|
|
|
2437
2566
|
|
|
@@ -2469,6 +2598,16 @@ def proof_evidence_should_override_state_terminal_path(state_candidate, evidence
|
|
|
2469
2598
|
return False
|
|
2470
2599
|
if not state_candidate:
|
|
2471
2600
|
return True
|
|
2601
|
+
state_parts = route_parts(state_candidate)
|
|
2602
|
+
evidence_parts = route_parts(evidence_candidate)
|
|
2603
|
+
if (
|
|
2604
|
+
state_parts.get('pathname') == evidence_parts.get('pathname')
|
|
2605
|
+
and (
|
|
2606
|
+
(evidence_parts.get('query') and not state_parts.get('query'))
|
|
2607
|
+
or (evidence_parts.get('hash') and not state_parts.get('hash'))
|
|
2608
|
+
)
|
|
2609
|
+
):
|
|
2610
|
+
return True
|
|
2472
2611
|
if route_matches_expected(state_candidate, evidence_candidate):
|
|
2473
2612
|
return False
|
|
2474
2613
|
if interaction_assertions_pass(proof_evidence):
|
|
@@ -2953,6 +3092,25 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
|
|
|
2953
3092
|
and route_expectation.get('terminal_path')
|
|
2954
3093
|
and route_expectation_source != 'recon_start_path'
|
|
2955
3094
|
)
|
|
3095
|
+
if authored_terminal_route:
|
|
3096
|
+
expected = route_mismatch.get('expected_path') or ''
|
|
3097
|
+
observed = route_mismatch.get('observed_after_path') or ''
|
|
3098
|
+
summary = 'Interaction proof terminal route mismatch: expected ' + (expected or '(unknown)') + ', got ' + (observed or '(unknown)') + '.'
|
|
3099
|
+
if error_messages:
|
|
3100
|
+
reasons.append('Capture script error: ' + error_messages[0][:500])
|
|
3101
|
+
summary += ' Capture script error: ' + error_messages[0][:300]
|
|
3102
|
+
reasons.append('Route mismatch: expected after capture path ' + (expected or '(unknown)') + ', observed ' + (observed or '(unknown)') + '.')
|
|
3103
|
+
reasons.append('The terminal route came from authored interaction proof evidence, so the captured browser evidence is the terminal blocker instead of an author retry loop.')
|
|
3104
|
+
return {
|
|
3105
|
+
'decision': 'failed_interaction_capture',
|
|
3106
|
+
'summary': summary,
|
|
3107
|
+
'recommended_stage': None,
|
|
3108
|
+
'continue_with_stage': None,
|
|
3109
|
+
'blocking': True,
|
|
3110
|
+
'terminal_blocker': True,
|
|
3111
|
+
'reasons': reasons,
|
|
3112
|
+
'mismatch': route_mismatch,
|
|
3113
|
+
}
|
|
2956
3114
|
recommended_stage = 'recon' if 'wrong route' in reason and not authored_terminal_route else 'author'
|
|
2957
3115
|
mismatch = None
|
|
2958
3116
|
if recommended_stage == 'recon':
|
|
@@ -3742,6 +3900,16 @@ has_judgable_failed_interaction_evidence = (
|
|
|
3742
3900
|
and not proof_evidence_blocker
|
|
3743
3901
|
and not visual_delta_recovery
|
|
3744
3902
|
)
|
|
3903
|
+
route_expectation = s.get('route_expectation') if isinstance(s.get('route_expectation'), dict) else {}
|
|
3904
|
+
authored_terminal_route_mismatch = (
|
|
3905
|
+
verification_mode in INTERACTION_MODES
|
|
3906
|
+
and 'wrong route' in str(after_observation.get('reason') or '')
|
|
3907
|
+
and bool(route_expectation.get('terminal_path'))
|
|
3908
|
+
and str(route_expectation.get('source') or '') != 'recon_start_path'
|
|
3909
|
+
)
|
|
3910
|
+
if authored_terminal_route_mismatch:
|
|
3911
|
+
has_judgable_failed_interaction_evidence = False
|
|
3912
|
+
summary_lines.append('Structured interaction route gate: authored terminal route mismatch is a terminal capture blocker.')
|
|
3745
3913
|
has_good_evidence = (
|
|
3746
3914
|
required_baseline_present
|
|
3747
3915
|
and (after_observation.get('valid') or has_judgable_failed_interaction_evidence)
|
|
@@ -325,6 +325,75 @@ class FakeRiddle:
|
|
|
325
325
|
'proof.json': {'script_error': message},
|
|
326
326
|
},
|
|
327
327
|
}
|
|
328
|
+
if 'pricingQueryHashStructuredNegativeControl' in script:
|
|
329
|
+
page_state = {
|
|
330
|
+
'bodyTextLength': 260,
|
|
331
|
+
'visibleTextSample': 'Pricing One rate Browser Compute Example Costs',
|
|
332
|
+
'interactiveElements': 8,
|
|
333
|
+
'visibleInteractiveElements': 8,
|
|
334
|
+
'pathname': '/pricing/',
|
|
335
|
+
'search': '',
|
|
336
|
+
'hash': '',
|
|
337
|
+
'title': 'Pricing',
|
|
338
|
+
'buttons': [],
|
|
339
|
+
'headings': ['Pricing', 'Browser Compute'],
|
|
340
|
+
'links': [{'text': 'Pricing', 'href': '/pricing/?rp_probe=1#pricing-probe'}],
|
|
341
|
+
'canvasCount': 0,
|
|
342
|
+
'largeVisibleElements': [{'tag': 'main', 'text': 'Pricing'}],
|
|
343
|
+
}
|
|
344
|
+
proof_evidence = {
|
|
345
|
+
'version': 'riddle-proof.interaction.v1',
|
|
346
|
+
'probe': 'query-hash-dropped-negative-control',
|
|
347
|
+
'negativeControl': True,
|
|
348
|
+
'routeExpectationSource': 'capture_script.expectedUrl',
|
|
349
|
+
'expectedUrl': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
|
|
350
|
+
'expectedHref': '/pricing/?rp_probe=1#pricing-probe',
|
|
351
|
+
'intentionalObservedUrl': 'https://riddledc.com/pricing/',
|
|
352
|
+
'start': {'href': 'https://riddledc.com/', 'pathname': '/', 'search': '', 'hash': ''},
|
|
353
|
+
'action': {
|
|
354
|
+
'type': 'rewrite-pricing-link-click-then-drop-query-hash',
|
|
355
|
+
'afterClickHref': 'https://riddledc.com/pricing/?rp_probe=1#pricing-probe',
|
|
356
|
+
'afterClickPathname': '/pricing/',
|
|
357
|
+
'afterClickSearch': '?rp_probe=1',
|
|
358
|
+
'afterClickHash': '#pricing-probe',
|
|
359
|
+
'expectedNavigationReached': True,
|
|
360
|
+
},
|
|
361
|
+
'terminal': {
|
|
362
|
+
'href': 'https://riddledc.com/pricing/',
|
|
363
|
+
'pathname': '/pricing/',
|
|
364
|
+
'search': '',
|
|
365
|
+
'hash': '',
|
|
366
|
+
},
|
|
367
|
+
'assertions': {
|
|
368
|
+
'expectedUrlReachedBeforeDrop': True,
|
|
369
|
+
'expectedUrlStillPresentAtTerminal': False,
|
|
370
|
+
'queryDropped': True,
|
|
371
|
+
'hashDropped': True,
|
|
372
|
+
'routeExpectationSourceIsCaptureScriptExpectedUrl': True,
|
|
373
|
+
'shouldTerminalizeAsFailedInteractionCapture': True,
|
|
374
|
+
'terminalMainVisible': True,
|
|
375
|
+
},
|
|
376
|
+
'checks': {
|
|
377
|
+
'routeMatches': False,
|
|
378
|
+
'specificMismatchDetected': True,
|
|
379
|
+
},
|
|
380
|
+
'errors': [],
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
'ok': True,
|
|
384
|
+
'screenshots': [{'url': 'https://cdn.example.com/pricing-negative-control.png'}],
|
|
385
|
+
'outputs': [{'name': 'after-pricing-negative-control.png', 'url': 'https://cdn.example.com/pricing-negative-control.png'}],
|
|
386
|
+
'result': {'pageState': page_state, 'proofEvidence': proof_evidence},
|
|
387
|
+
'console': [
|
|
388
|
+
'RIDDLE_PROOF_STATE:' + json.dumps(page_state),
|
|
389
|
+
'RIDDLE_PROOF_EVIDENCE:' + json.dumps(proof_evidence),
|
|
390
|
+
],
|
|
391
|
+
'visual_diff': {
|
|
392
|
+
'diffPercentage': 1.2,
|
|
393
|
+
'differentPixels': 12000,
|
|
394
|
+
'totalPixels': 972000,
|
|
395
|
+
},
|
|
396
|
+
}
|
|
328
397
|
if 'pricingQueryHashPassesWithPageStateHashGap' in script:
|
|
329
398
|
page_state = {
|
|
330
399
|
'bodyTextLength': 260,
|
|
@@ -2934,6 +3003,78 @@ def run_verify_interaction_query_hash_pass_uses_proof_evidence_route():
|
|
|
2934
3003
|
shutil.rmtree(tempdir, ignore_errors=True)
|
|
2935
3004
|
|
|
2936
3005
|
|
|
3006
|
+
def run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route():
|
|
3007
|
+
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-explicit-expected-url-mismatch-'))
|
|
3008
|
+
state_path = tempdir / 'state.json'
|
|
3009
|
+
try:
|
|
3010
|
+
state = base_state(tempdir, reference='before')
|
|
3011
|
+
state.update({
|
|
3012
|
+
'recon_status': 'ready_for_proof_plan',
|
|
3013
|
+
'author_status': 'ready',
|
|
3014
|
+
'proof_plan_status': 'ready',
|
|
3015
|
+
'implementation_status': 'changes_detected',
|
|
3016
|
+
'verification_mode': 'interaction',
|
|
3017
|
+
'server_path': '/',
|
|
3018
|
+
'before_cdn': 'https://cdn.example.com/before-home.png',
|
|
3019
|
+
'proof_plan': 'Start at /, click Pricing, and intentionally prove the query/hash route mismatch.',
|
|
3020
|
+
'capture_script': "pricingQueryHashStructuredNegativeControl();",
|
|
3021
|
+
'supervisor_author_packet': {
|
|
3022
|
+
'proof_plan': 'Use expectedUrl as the route expectation and return structured evidence for the dropped query/hash terminal URL.',
|
|
3023
|
+
'capture_script': "pricingQueryHashStructuredNegativeControl();",
|
|
3024
|
+
'refined_inputs': {
|
|
3025
|
+
'server_path': '/',
|
|
3026
|
+
},
|
|
3027
|
+
},
|
|
3028
|
+
'recon_results': {
|
|
3029
|
+
'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
|
|
3030
|
+
},
|
|
3031
|
+
})
|
|
3032
|
+
write_state(state_path, state)
|
|
3033
|
+
os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
|
|
3034
|
+
|
|
3035
|
+
fake = FakeRiddle()
|
|
3036
|
+
load_util_with_fake(fake)
|
|
3037
|
+
load_module('verify_interaction_explicit_expected_url_blocks_dropped_terminal_route', VERIFY_PATH)
|
|
3038
|
+
after_verify = json.loads(state_path.read_text())
|
|
3039
|
+
|
|
3040
|
+
request = after_verify['verify_decision_request']
|
|
3041
|
+
assert after_verify['verify_status'] == 'capture_incomplete'
|
|
3042
|
+
assert after_verify['merge_recommendation'] == 'do-not-merge'
|
|
3043
|
+
assert after_verify['route_expectation']['source'] == 'proof_evidence_contract'
|
|
3044
|
+
assert after_verify['route_expectation']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
|
|
3045
|
+
assert after_verify['route_expectation']['expected_query'] == 'rp_probe=1'
|
|
3046
|
+
assert after_verify['route_expectation']['expected_hash'] == '#pricing-probe'
|
|
3047
|
+
assert request['recommended_stage'] is None
|
|
3048
|
+
assert request['continue_with_stage'] is None
|
|
3049
|
+
capture_quality = request['capture_quality']
|
|
3050
|
+
assert capture_quality['decision'] == 'failed_interaction_capture'
|
|
3051
|
+
assert capture_quality['blocking'] is True
|
|
3052
|
+
assert capture_quality['terminal_blocker'] is True
|
|
3053
|
+
assert capture_quality['mismatch']['expected_path'] == '/pricing?rp_probe=1#pricing-probe'
|
|
3054
|
+
assert capture_quality['mismatch']['observed_after_path'] in ('/pricing', '/pricing/')
|
|
3055
|
+
assert 'Interaction proof terminal route mismatch' in capture_quality['summary']
|
|
3056
|
+
assert after_verify['proof_assessment_request'] == {}
|
|
3057
|
+
observation = request['latest_observation']
|
|
3058
|
+
assert observation['valid'] is False
|
|
3059
|
+
assert 'wrong route' in observation['reason']
|
|
3060
|
+
supporting = after_verify['verify_results']['after']['supporting_artifacts']
|
|
3061
|
+
assert supporting['proof_evidence_present'] is True
|
|
3062
|
+
assert supporting['has_structured_payload'] is True
|
|
3063
|
+
route = after_verify['evidence_bundle']['semantic_context']['route']
|
|
3064
|
+
assert route['expected_terminal_query'] == 'rp_probe=1'
|
|
3065
|
+
assert route['expected_terminal_hash'] == '#pricing-probe'
|
|
3066
|
+
assert route['after_observed_path'] == '/pricing'
|
|
3067
|
+
assert route['after_observed_query'] == ''
|
|
3068
|
+
assert route['after_observed_hash'] == ''
|
|
3069
|
+
return {
|
|
3070
|
+
'ok': True,
|
|
3071
|
+
'decision': capture_quality['decision'],
|
|
3072
|
+
'summary': capture_quality['summary'],
|
|
3073
|
+
}
|
|
3074
|
+
finally:
|
|
3075
|
+
shutil.rmtree(tempdir, ignore_errors=True)
|
|
3076
|
+
|
|
3077
|
+
|
|
2937
3078
|
def run_verify_interaction_thrown_error_terminal_blocker():
|
|
2938
3079
|
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-thrown-error-'))
|
|
2939
3080
|
state_path = tempdir / 'state.json'
|
|
@@ -3416,6 +3557,7 @@ if __name__ == '__main__':
|
|
|
3416
3557
|
'verify_interaction_hash_terminal_route_from_proof_evidence': run_verify_interaction_hash_terminal_route_from_proof_evidence(),
|
|
3417
3558
|
'verify_interaction_authored_query_hash_mismatch_blocks_with_evidence': run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence(),
|
|
3418
3559
|
'verify_interaction_query_hash_pass_uses_proof_evidence_route': run_verify_interaction_query_hash_pass_uses_proof_evidence_route(),
|
|
3560
|
+
'verify_interaction_explicit_expected_url_blocks_dropped_terminal_route': run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route(),
|
|
3419
3561
|
'verify_interaction_thrown_error_terminal_blocker': run_verify_interaction_thrown_error_terminal_blocker(),
|
|
3420
3562
|
'verify_capture_retry_surfaces_script_timeout': run_verify_capture_retry_surfaces_script_timeout(),
|
|
3421
3563
|
'missing_baseline_guard': run_verify_missing_baseline(),
|
|
@@ -55,6 +55,12 @@ CASES = [
|
|
|
55
55
|
'function': 'run_verify_interaction_authored_query_hash_mismatch_blocks_with_evidence',
|
|
56
56
|
'expected_terminal': 'specific_blocker',
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
'name': 'query-hash-dropped-structured-negative-blocker',
|
|
60
|
+
'covers': ['query/hash/trailing-slash URLs', 'invalid browser evidence', 'proof-evidence-present'],
|
|
61
|
+
'function': 'run_verify_interaction_explicit_expected_url_blocks_dropped_terminal_route',
|
|
62
|
+
'expected_terminal': 'specific_blocker',
|
|
63
|
+
},
|
|
58
64
|
{
|
|
59
65
|
'name': 'same-page-hash-pass',
|
|
60
66
|
'covers': ['same-page hashes'],
|
|
File without changes
|