@riddledc/riddle-proof 0.8.3 → 0.8.4
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/adapters/openclaw.js +4 -4
- package/dist/advanced/engine-harness.cjs +33 -11
- package/dist/advanced/engine-harness.js +4 -4
- package/dist/advanced/index.cjs +33 -11
- package/dist/advanced/index.d.cts +1 -1
- package/dist/advanced/index.d.ts +1 -1
- package/dist/advanced/index.js +6 -6
- package/dist/advanced/proof-run-engine.cjs +28 -8
- package/dist/advanced/proof-run-engine.d.cts +1 -1
- package/dist/advanced/proof-run-engine.d.ts +1 -1
- package/dist/advanced/proof-run-engine.js +1 -1
- package/dist/advanced/runner.js +4 -4
- package/dist/checkpoint.cjs +3 -1
- package/dist/checkpoint.js +1 -1
- package/dist/{chunk-SKIAZTQ7.js → chunk-4FOHZ7JG.js} +3 -1
- package/dist/{chunk-U4FUFBSH.js → chunk-FMOYUYH2.js} +1 -1
- package/dist/{chunk-YB5ACBZE.js → chunk-IP64JLLR.js} +5 -5
- package/dist/{chunk-OIFHYMHP.js → chunk-LZFCIHDT.js} +2 -2
- package/dist/{chunk-TZ3YMCDM.js → chunk-P22V26PS.js} +28 -8
- package/dist/{chunk-SMBZT46I.js → chunk-RBWSCU6V.js} +1 -1
- package/dist/{chunk-ZX45XGDJ.js → chunk-UIJ7X63P.js} +1 -1
- package/dist/{chunk-TNCDVE5O.js → chunk-YZUVEJ5B.js} +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/cli.cjs +33 -11
- package/dist/cli.js +5 -5
- package/dist/engine-harness.cjs +33 -11
- package/dist/engine-harness.js +4 -4
- package/dist/index.cjs +33 -11
- package/dist/index.js +5 -5
- package/dist/openclaw.js +4 -4
- package/dist/{proof-run-engine-Rkd_hXB-.d.cts → proof-run-engine-B7DCPzpK.d.cts} +3 -3
- package/dist/{proof-run-engine-DxWW1VX1.d.ts → proof-run-engine-BomAcXhA.d.ts} +3 -3
- package/dist/proof-run-engine.cjs +28 -8
- package/dist/proof-run-engine.d.cts +1 -1
- package/dist/proof-run-engine.d.ts +1 -1
- package/dist/proof-run-engine.js +1 -1
- package/dist/run-card.js +2 -2
- package/dist/runner.js +4 -4
- package/dist/spec/checkpoint.cjs +3 -1
- package/dist/spec/checkpoint.js +1 -1
- package/dist/spec/index.cjs +3 -1
- package/dist/spec/index.js +3 -3
- package/dist/spec/run-card.js +2 -2
- package/dist/spec/state.js +3 -3
- package/dist/state.js +3 -3
- package/package.json +1 -1
- package/runtime/lib/util.py +57 -0
- package/runtime/lib/verify.py +128 -5
- package/runtime/tests/recon_verify_smoke.py +116 -0
package/dist/runner.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runRiddleProof
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-UIJ7X63P.js";
|
|
4
|
+
import "./chunk-YZUVEJ5B.js";
|
|
5
|
+
import "./chunk-FMOYUYH2.js";
|
|
6
6
|
import "./chunk-RV6LK7HU.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-4FOHZ7JG.js";
|
|
8
8
|
import "./chunk-VY4Y5U57.js";
|
|
9
9
|
import "./chunk-MLKGABMK.js";
|
|
10
10
|
export {
|
package/dist/spec/checkpoint.cjs
CHANGED
|
@@ -121,7 +121,7 @@ function responseSchemaForAuthorPacket() {
|
|
|
121
121
|
summary: { type: "string" },
|
|
122
122
|
payload: {
|
|
123
123
|
type: "object",
|
|
124
|
-
description: "For decision=author_packet, provide the proof packet itself or {author_packet:{...}} with proof_plan and
|
|
124
|
+
description: "For decision=author_packet, provide the proof packet itself or {author_packet:{...}} with proof_plan, capture_script, and refined_inputs.expected_terminal_path when the proof changes route, query, or hash."
|
|
125
125
|
},
|
|
126
126
|
reasons: { type: "array", items: { type: "string" } },
|
|
127
127
|
continue_with_stage: { type: "string", enum: ["author", "recon"] },
|
|
@@ -428,9 +428,11 @@ function buildAuthorCheckpointPacket(input) {
|
|
|
428
428
|
repo: input.request.repo || fullState.repo,
|
|
429
429
|
branch: input.request.branch || fullState.branch,
|
|
430
430
|
verification_mode: input.request.verification_mode || fullState.verification_mode,
|
|
431
|
+
success_criteria: fullState.success_criteria,
|
|
431
432
|
reference: input.request.reference || fullState.reference,
|
|
432
433
|
server_path: fullState.server_path,
|
|
433
434
|
wait_for_selector: fullState.wait_for_selector,
|
|
435
|
+
route_expectation: jsonCloneRecord(fullState.route_expectation),
|
|
434
436
|
author_summary: fullState.author_summary,
|
|
435
437
|
author_request: jsonCloneRecord(authorRequest),
|
|
436
438
|
recon_baseline_understanding: jsonCloneRecord(fullState.recon_baseline_understanding),
|
package/dist/spec/checkpoint.js
CHANGED
package/dist/spec/index.cjs
CHANGED
|
@@ -344,7 +344,7 @@ function responseSchemaForAuthorPacket() {
|
|
|
344
344
|
summary: { type: "string" },
|
|
345
345
|
payload: {
|
|
346
346
|
type: "object",
|
|
347
|
-
description: "For decision=author_packet, provide the proof packet itself or {author_packet:{...}} with proof_plan and
|
|
347
|
+
description: "For decision=author_packet, provide the proof packet itself or {author_packet:{...}} with proof_plan, capture_script, and refined_inputs.expected_terminal_path when the proof changes route, query, or hash."
|
|
348
348
|
},
|
|
349
349
|
reasons: { type: "array", items: { type: "string" } },
|
|
350
350
|
continue_with_stage: { type: "string", enum: ["author", "recon"] },
|
|
@@ -651,9 +651,11 @@ function buildAuthorCheckpointPacket(input) {
|
|
|
651
651
|
repo: input.request.repo || fullState.repo,
|
|
652
652
|
branch: input.request.branch || fullState.branch,
|
|
653
653
|
verification_mode: input.request.verification_mode || fullState.verification_mode,
|
|
654
|
+
success_criteria: fullState.success_criteria,
|
|
654
655
|
reference: input.request.reference || fullState.reference,
|
|
655
656
|
server_path: fullState.server_path,
|
|
656
657
|
wait_for_selector: fullState.wait_for_selector,
|
|
658
|
+
route_expectation: jsonCloneRecord(fullState.route_expectation),
|
|
657
659
|
author_summary: fullState.author_summary,
|
|
658
660
|
author_request: jsonCloneRecord(authorRequest),
|
|
659
661
|
recon_baseline_understanding: jsonCloneRecord(fullState.recon_baseline_understanding),
|
package/dist/spec/index.js
CHANGED
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
normalizePrLifecycleState,
|
|
11
11
|
normalizeRunParams,
|
|
12
12
|
setRunStatus
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-YZUVEJ5B.js";
|
|
14
14
|
import {
|
|
15
15
|
RIDDLE_PROOF_RUN_CARD_VERSION,
|
|
16
16
|
createRiddleProofRunCard
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-FMOYUYH2.js";
|
|
18
18
|
import {
|
|
19
19
|
RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
|
|
20
20
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
normalizeCheckpointResponse,
|
|
31
31
|
proofContractFromAuthorCheckpointResponse,
|
|
32
32
|
statePathsForRunState
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-4FOHZ7JG.js";
|
|
34
34
|
import {
|
|
35
35
|
applyTerminalMetadata,
|
|
36
36
|
compactRecord,
|
package/dist/spec/run-card.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RIDDLE_PROOF_RUN_CARD_VERSION,
|
|
3
3
|
createRiddleProofRunCard
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-FMOYUYH2.js";
|
|
5
|
+
import "../chunk-4FOHZ7JG.js";
|
|
6
6
|
import "../chunk-VY4Y5U57.js";
|
|
7
7
|
import "../chunk-MLKGABMK.js";
|
|
8
8
|
export {
|
package/dist/spec/state.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
normalizePrLifecycleState,
|
|
10
10
|
normalizeRunParams,
|
|
11
11
|
setRunStatus
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import "../chunk-
|
|
14
|
-
import "../chunk-
|
|
12
|
+
} from "../chunk-YZUVEJ5B.js";
|
|
13
|
+
import "../chunk-FMOYUYH2.js";
|
|
14
|
+
import "../chunk-4FOHZ7JG.js";
|
|
15
15
|
import "../chunk-VY4Y5U57.js";
|
|
16
16
|
import "../chunk-MLKGABMK.js";
|
|
17
17
|
export {
|
package/dist/state.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
normalizePrLifecycleState,
|
|
10
10
|
normalizeRunParams,
|
|
11
11
|
setRunStatus
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-YZUVEJ5B.js";
|
|
13
|
+
import "./chunk-FMOYUYH2.js";
|
|
14
|
+
import "./chunk-4FOHZ7JG.js";
|
|
15
15
|
import "./chunk-VY4Y5U57.js";
|
|
16
16
|
import "./chunk-MLKGABMK.js";
|
|
17
17
|
export {
|
package/package.json
CHANGED
package/runtime/lib/util.py
CHANGED
|
@@ -710,6 +710,60 @@ def invoke(tool, args, timeout=180):
|
|
|
710
710
|
return {'ok': False, 'error': r.stdout[:300], 'stderr': r.stderr[:300]}
|
|
711
711
|
|
|
712
712
|
|
|
713
|
+
def compact_error_text(value, depth=0):
|
|
714
|
+
if depth > 4 or value is None:
|
|
715
|
+
return ''
|
|
716
|
+
if isinstance(value, str):
|
|
717
|
+
return value
|
|
718
|
+
if isinstance(value, (int, float, bool)):
|
|
719
|
+
return str(value)
|
|
720
|
+
if isinstance(value, list):
|
|
721
|
+
return '\n'.join(compact_error_text(item, depth + 1) for item in value[:12])
|
|
722
|
+
if isinstance(value, dict):
|
|
723
|
+
parts = []
|
|
724
|
+
priority_keys = (
|
|
725
|
+
'error', 'message', 'script_error', 'stderr', 'stdout', 'raw',
|
|
726
|
+
'details', 'result', 'capture', 'proof', '_proof_json',
|
|
727
|
+
)
|
|
728
|
+
for key in priority_keys:
|
|
729
|
+
if key in value:
|
|
730
|
+
text = compact_error_text(value.get(key), depth + 1)
|
|
731
|
+
if text:
|
|
732
|
+
parts.append(text)
|
|
733
|
+
if not parts:
|
|
734
|
+
for item in list(value.values())[:12]:
|
|
735
|
+
text = compact_error_text(item, depth + 1)
|
|
736
|
+
if text:
|
|
737
|
+
parts.append(text)
|
|
738
|
+
return '\n'.join(parts)
|
|
739
|
+
return ''
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def non_retryable_riddle_script_error(result):
|
|
743
|
+
text = compact_error_text(result).lower()
|
|
744
|
+
if not text:
|
|
745
|
+
return False
|
|
746
|
+
playwright_timeout = (
|
|
747
|
+
'timeout' in text
|
|
748
|
+
and (
|
|
749
|
+
'locator.' in text
|
|
750
|
+
or 'page.waitforurl' in text
|
|
751
|
+
or 'page.waitforselector' in text
|
|
752
|
+
or 'waiting for selector' in text
|
|
753
|
+
or 'waiting for navigation' in text
|
|
754
|
+
or 'getbyrole' in text
|
|
755
|
+
or 'getbytext' in text
|
|
756
|
+
or 'scrollintoviewifneeded' in text
|
|
757
|
+
)
|
|
758
|
+
)
|
|
759
|
+
deterministic_playwright_error = (
|
|
760
|
+
'strict mode violation' in text
|
|
761
|
+
or 'selector_not_found' in text
|
|
762
|
+
or 'index_out_of_range' in text
|
|
763
|
+
)
|
|
764
|
+
return playwright_timeout or deterministic_playwright_error
|
|
765
|
+
|
|
766
|
+
|
|
713
767
|
def invoke_retry(tool, args, retries=3, timeout=180):
|
|
714
768
|
"""Call an OpenClaw tool with automatic retries on failure."""
|
|
715
769
|
last_result = None
|
|
@@ -720,6 +774,9 @@ def invoke_retry(tool, args, retries=3, timeout=180):
|
|
|
720
774
|
if result.get('ok') or result.get('outputs') or result.get('screenshots'):
|
|
721
775
|
return result
|
|
722
776
|
print(f'invoke_retry({tool}) attempt {attempt}/{retries} failed: {str(result.get("error", "no output"))[:200]}')
|
|
777
|
+
if tool == 'riddle_script' and non_retryable_riddle_script_error(result):
|
|
778
|
+
print('invoke_retry(riddle_script) stopping early for deterministic Playwright script error')
|
|
779
|
+
return result
|
|
723
780
|
if attempt < retries:
|
|
724
781
|
import time
|
|
725
782
|
time.sleep(5)
|
package/runtime/lib/verify.py
CHANGED
|
@@ -1845,16 +1845,17 @@ def normalize_observed_path(value):
|
|
|
1845
1845
|
raw = str(value or '').strip()
|
|
1846
1846
|
if not raw:
|
|
1847
1847
|
return ''
|
|
1848
|
-
parsed = urlparse(raw
|
|
1848
|
+
parsed = urlparse(raw)
|
|
1849
1849
|
path = parsed.path or ''
|
|
1850
1850
|
query = parsed.query or ''
|
|
1851
|
+
fragment = parsed.fragment or ''
|
|
1851
1852
|
if not path.startswith('/'):
|
|
1852
1853
|
path = '/' + path.lstrip('/')
|
|
1853
1854
|
parts = path.split('/')
|
|
1854
1855
|
if len(parts) >= 4 and parts[1] == 's':
|
|
1855
1856
|
path = '/' + '/'.join(parts[3:])
|
|
1856
1857
|
path = path.rstrip('/') or '/'
|
|
1857
|
-
return path + (('?' + query) if query else '')
|
|
1858
|
+
return path + (('?' + query) if query else '') + (('#' + fragment) if fragment else '')
|
|
1858
1859
|
|
|
1859
1860
|
|
|
1860
1861
|
def observed_location_from_page_state(page_state):
|
|
@@ -1862,10 +1863,13 @@ def observed_location_from_page_state(page_state):
|
|
|
1862
1863
|
return ''
|
|
1863
1864
|
pathname = str(page_state.get('pathname') or '').strip()
|
|
1864
1865
|
search = str(page_state.get('search') or '').strip()
|
|
1866
|
+
hash_value = str(page_state.get('hash') or page_state.get('fragment') or '').strip()
|
|
1865
1867
|
if search and not search.startswith('?'):
|
|
1866
1868
|
search = '?' + search
|
|
1869
|
+
if hash_value and not hash_value.startswith('#'):
|
|
1870
|
+
hash_value = '#' + hash_value
|
|
1867
1871
|
if pathname:
|
|
1868
|
-
return pathname + search
|
|
1872
|
+
return pathname + search + hash_value
|
|
1869
1873
|
return str(page_state.get('href') or '').strip()
|
|
1870
1874
|
|
|
1871
1875
|
|
|
@@ -1889,9 +1893,29 @@ def route_matches_expected(expected_path, observed_path):
|
|
|
1889
1893
|
if pair not in remaining:
|
|
1890
1894
|
return False
|
|
1891
1895
|
remaining.remove(pair)
|
|
1896
|
+
if expected_parsed.fragment and observed_parsed.fragment != expected_parsed.fragment:
|
|
1897
|
+
return False
|
|
1892
1898
|
return True
|
|
1893
1899
|
|
|
1894
1900
|
|
|
1901
|
+
def route_parts(value):
|
|
1902
|
+
normalized = normalize_observed_path(value)
|
|
1903
|
+
if not normalized:
|
|
1904
|
+
return {
|
|
1905
|
+
'href': '',
|
|
1906
|
+
'pathname': '',
|
|
1907
|
+
'query': '',
|
|
1908
|
+
'hash': '',
|
|
1909
|
+
}
|
|
1910
|
+
parsed = urlparse(normalized)
|
|
1911
|
+
return {
|
|
1912
|
+
'href': normalized,
|
|
1913
|
+
'pathname': parsed.path.rstrip('/') or '/',
|
|
1914
|
+
'query': parsed.query or '',
|
|
1915
|
+
'hash': ('#' + parsed.fragment) if parsed.fragment else '',
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
|
|
1895
1919
|
EXPLICIT_TERMINAL_PATH_KEYS = (
|
|
1896
1920
|
'expected_terminal_path', 'expectedTerminalPath',
|
|
1897
1921
|
'expected_terminal_route', 'expectedTerminalRoute',
|
|
@@ -1980,6 +2004,16 @@ def terminal_path_from_record(record, depth=0):
|
|
|
1980
2004
|
return ''
|
|
1981
2005
|
|
|
1982
2006
|
|
|
2007
|
+
def terminal_path_from_text(value):
|
|
2008
|
+
if not isinstance(value, str):
|
|
2009
|
+
return ''
|
|
2010
|
+
for match in re.findall(r"""['"`](/[^'"`\s]+[?#][^'"`\s]*)['"`]""", value):
|
|
2011
|
+
candidate = path_candidate(match)
|
|
2012
|
+
if candidate:
|
|
2013
|
+
return candidate
|
|
2014
|
+
return ''
|
|
2015
|
+
|
|
2016
|
+
|
|
1983
2017
|
def interaction_assertions_pass(value):
|
|
1984
2018
|
for record in proof_evidence_records(value):
|
|
1985
2019
|
if any(record.get(key) is False for key in (
|
|
@@ -2039,29 +2073,58 @@ def interaction_terminal_path_from_state(state):
|
|
|
2039
2073
|
candidate = terminal_path_from_record(state.get(key))
|
|
2040
2074
|
if candidate:
|
|
2041
2075
|
return candidate, key
|
|
2076
|
+
for key in (
|
|
2077
|
+
'expected_terminal_path',
|
|
2078
|
+
'expected_after_path',
|
|
2079
|
+
'capture_script',
|
|
2080
|
+
'proof_plan',
|
|
2081
|
+
'success_criteria',
|
|
2082
|
+
'change_request',
|
|
2083
|
+
):
|
|
2084
|
+
candidate = path_candidate(state.get(key)) or terminal_path_from_text(state.get(key))
|
|
2085
|
+
if candidate:
|
|
2086
|
+
return candidate, key
|
|
2042
2087
|
return '', ''
|
|
2043
2088
|
|
|
2044
2089
|
|
|
2045
2090
|
def expected_path_for_verify(state, start_path, proof_evidence):
|
|
2046
2091
|
mode = normalized_verification_mode(state.get('verification_mode'))
|
|
2047
2092
|
normalized_start = normalize_observed_path(start_path) or '/'
|
|
2093
|
+
start_parts = route_parts(normalized_start)
|
|
2048
2094
|
if mode not in INTERACTION_MODES:
|
|
2049
2095
|
return normalized_start, {
|
|
2050
2096
|
'mode': mode,
|
|
2051
2097
|
'source': 'recon_start_path',
|
|
2052
2098
|
'start_path': normalized_start,
|
|
2053
2099
|
'expected_path': normalized_start,
|
|
2100
|
+
'start_pathname': start_parts['pathname'],
|
|
2101
|
+
'start_query': start_parts['query'],
|
|
2102
|
+
'start_hash': start_parts['hash'],
|
|
2103
|
+
'expected_pathname': start_parts['pathname'],
|
|
2104
|
+
'expected_query': start_parts['query'],
|
|
2105
|
+
'expected_hash': start_parts['hash'],
|
|
2054
2106
|
}
|
|
2055
2107
|
candidate, source = interaction_terminal_path_from_state(state)
|
|
2056
2108
|
if not candidate:
|
|
2057
2109
|
candidate, source = interaction_terminal_path_from_evidence(proof_evidence)
|
|
2058
2110
|
expected = candidate or normalized_start
|
|
2111
|
+
expected_parts = route_parts(expected)
|
|
2059
2112
|
return expected, {
|
|
2060
2113
|
'mode': mode,
|
|
2061
2114
|
'source': source or 'recon_start_path',
|
|
2062
2115
|
'start_path': normalized_start,
|
|
2063
2116
|
'expected_path': expected,
|
|
2064
2117
|
'terminal_path': expected if expected != normalized_start else '',
|
|
2118
|
+
'start_pathname': start_parts['pathname'],
|
|
2119
|
+
'start_query': start_parts['query'],
|
|
2120
|
+
'start_hash': start_parts['hash'],
|
|
2121
|
+
'expected_href': expected_parts['href'],
|
|
2122
|
+
'expected_pathname': expected_parts['pathname'],
|
|
2123
|
+
'expected_query': expected_parts['query'],
|
|
2124
|
+
'expected_hash': expected_parts['hash'],
|
|
2125
|
+
'terminal_pathname': expected_parts['pathname'] if expected != normalized_start else '',
|
|
2126
|
+
'terminal_query': expected_parts['query'] if expected != normalized_start else '',
|
|
2127
|
+
'terminal_hash': expected_parts['hash'] if expected != normalized_start else '',
|
|
2065
2128
|
}
|
|
2066
2129
|
|
|
2067
2130
|
|
|
@@ -2391,6 +2454,19 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
|
|
|
2391
2454
|
'reasons': reasons,
|
|
2392
2455
|
}
|
|
2393
2456
|
|
|
2457
|
+
details = after_observation.get('details') if isinstance(after_observation.get('details'), dict) else {}
|
|
2458
|
+
route_mismatch = None
|
|
2459
|
+
reason_text = str(after_observation.get('reason') or '')
|
|
2460
|
+
if 'wrong route' in reason_text:
|
|
2461
|
+
expected = details.get('expected_path') or ''
|
|
2462
|
+
observed = details.get('observed_path_raw') or details.get('observed_path') or ''
|
|
2463
|
+
if expected or observed:
|
|
2464
|
+
route_mismatch = {
|
|
2465
|
+
'field': 'route',
|
|
2466
|
+
'expected_path': expected,
|
|
2467
|
+
'observed_after_path': observed,
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2394
2470
|
if proof_evidence_blocker:
|
|
2395
2471
|
reasons.append(proof_evidence_blocker)
|
|
2396
2472
|
decision = 'missing_proof_evidence'
|
|
@@ -2399,19 +2475,36 @@ def build_capture_retry_decision(after_observation, required_baseline_present, p
|
|
|
2399
2475
|
reasons.append('The capture reached usable page context, but the proof evidence explicitly failed its own required audio gate.')
|
|
2400
2476
|
else:
|
|
2401
2477
|
reasons.append('The capture reached usable page context, but the proof script did not emit the structured evidence required for this verification mode.')
|
|
2478
|
+
if route_mismatch:
|
|
2479
|
+
reasons.append(
|
|
2480
|
+
'Route mismatch also present: expected after capture path ' +
|
|
2481
|
+
(route_mismatch.get('expected_path') or '(unknown)') +
|
|
2482
|
+
', observed ' +
|
|
2483
|
+
(route_mismatch.get('observed_after_path') or '(unknown)') +
|
|
2484
|
+
'.'
|
|
2485
|
+
)
|
|
2402
2486
|
reasons.append('Return to author so the capture script can expose passing proof evidence before verify asks for a supervising-agent judgment.')
|
|
2487
|
+
summary = proof_evidence_blocker
|
|
2488
|
+
if route_mismatch:
|
|
2489
|
+
summary += (
|
|
2490
|
+
' Route mismatch: expected ' +
|
|
2491
|
+
(route_mismatch.get('expected_path') or '(unknown)') +
|
|
2492
|
+
', got ' +
|
|
2493
|
+
(route_mismatch.get('observed_after_path') or '(unknown)') +
|
|
2494
|
+
'.'
|
|
2495
|
+
)
|
|
2403
2496
|
return {
|
|
2404
2497
|
'decision': decision,
|
|
2405
|
-
'summary':
|
|
2498
|
+
'summary': summary,
|
|
2406
2499
|
'recommended_stage': 'author',
|
|
2407
2500
|
'continue_with_stage': 'author',
|
|
2408
2501
|
'reasons': reasons,
|
|
2502
|
+
'mismatch': route_mismatch,
|
|
2409
2503
|
}
|
|
2410
2504
|
|
|
2411
2505
|
reason = after_observation.get('reason') or 'after capture is not usable yet'
|
|
2412
2506
|
reasons.append('The after evidence is not usable yet: ' + reason)
|
|
2413
2507
|
recommended_stage = 'recon' if 'wrong route' in reason else 'author'
|
|
2414
|
-
details = after_observation.get('details') if isinstance(after_observation.get('details'), dict) else {}
|
|
2415
2508
|
error_messages = [
|
|
2416
2509
|
str(item).strip()
|
|
2417
2510
|
for item in (details.get('capture_error_messages') or [])
|
|
@@ -2528,6 +2621,9 @@ def build_semantic_context(state, results, after_observation, expected_path):
|
|
|
2528
2621
|
after_semantic = semantic_observation('after', after_observation)
|
|
2529
2622
|
expected_start_path = state.get('expected_start_path') or expected_path
|
|
2530
2623
|
route_expectation = state.get('route_expectation') if isinstance(state.get('route_expectation'), dict) else {}
|
|
2624
|
+
expected_parts = route_parts(expected_path)
|
|
2625
|
+
start_parts = route_parts(expected_start_path)
|
|
2626
|
+
after_parts = route_parts(after_semantic.get('observed_path_raw') or after_semantic.get('observed_path') or '')
|
|
2531
2627
|
return {
|
|
2532
2628
|
'expected_path': expected_path,
|
|
2533
2629
|
'expected_start_path': expected_start_path,
|
|
@@ -2540,10 +2636,20 @@ def build_semantic_context(state, results, after_observation, expected_path):
|
|
|
2540
2636
|
'expected_after_path': expected_path,
|
|
2541
2637
|
'expected_start_path': expected_start_path,
|
|
2542
2638
|
'expected_terminal_path': route_expectation.get('terminal_path') or '',
|
|
2639
|
+
'expected_pathname': expected_parts['pathname'],
|
|
2640
|
+
'expected_query': expected_parts['query'],
|
|
2641
|
+
'expected_hash': expected_parts['hash'],
|
|
2642
|
+
'expected_start_pathname': start_parts['pathname'],
|
|
2643
|
+
'expected_start_query': start_parts['query'],
|
|
2644
|
+
'expected_start_hash': start_parts['hash'],
|
|
2645
|
+
'expected_terminal_query': route_expectation.get('terminal_query') or route_expectation.get('expected_query') or '',
|
|
2646
|
+
'expected_terminal_hash': route_expectation.get('terminal_hash') or route_expectation.get('expected_hash') or '',
|
|
2543
2647
|
'expectation_source': route_expectation.get('source') or '',
|
|
2544
2648
|
'before_observed_path': before_semantic.get('observed_path') or before.get('path') or '',
|
|
2545
2649
|
'prod_observed_path': prod_semantic.get('observed_path') or prod.get('path') or '',
|
|
2546
2650
|
'after_observed_path': after_semantic.get('observed_path') or '',
|
|
2651
|
+
'after_observed_query': after_parts['query'],
|
|
2652
|
+
'after_observed_hash': after_parts['hash'],
|
|
2547
2653
|
},
|
|
2548
2654
|
'before': before_semantic,
|
|
2549
2655
|
'prod': prod_semantic,
|
|
@@ -3051,6 +3157,15 @@ if expected_start_path and expected_start_path != expected_path:
|
|
|
3051
3157
|
summary_lines.append('Expected terminal proof path: ' + expected_path)
|
|
3052
3158
|
else:
|
|
3053
3159
|
summary_lines.append('Expected proof path from recon: ' + expected_path)
|
|
3160
|
+
route_expected_query = (s.get('route_expectation') or {}).get('expected_query') or ''
|
|
3161
|
+
route_expected_hash = (s.get('route_expectation') or {}).get('expected_hash') or ''
|
|
3162
|
+
if route_expected_query or route_expected_hash:
|
|
3163
|
+
summary_lines.append(
|
|
3164
|
+
'Expected terminal query/hash: ' +
|
|
3165
|
+
('?' + route_expected_query if route_expected_query else '(none)') +
|
|
3166
|
+
' ' +
|
|
3167
|
+
(route_expected_hash if route_expected_hash else '(none)')
|
|
3168
|
+
)
|
|
3054
3169
|
summary_lines.append('After observation: ' + after_observation['reason'])
|
|
3055
3170
|
supporting = results['after'].get('supporting_artifacts') or {}
|
|
3056
3171
|
if supporting.get('has_structured_payload'):
|
|
@@ -3064,6 +3179,14 @@ if supporting.get('has_structured_payload'):
|
|
|
3064
3179
|
summary_lines.append('Structured after evidence: ' + ('; '.join(basis) if basis else 'present'))
|
|
3065
3180
|
observed_path = (after_observation.get('details') or {}).get('observed_path') or expected_path
|
|
3066
3181
|
summary_lines.append('Observed after path: ' + observed_path)
|
|
3182
|
+
observed_parts = route_parts((after_observation.get('details') or {}).get('observed_path_raw') or observed_path)
|
|
3183
|
+
if observed_parts.get('query') or observed_parts.get('hash') or route_expected_query or route_expected_hash:
|
|
3184
|
+
summary_lines.append(
|
|
3185
|
+
'Observed after query/hash: ' +
|
|
3186
|
+
('?' + observed_parts.get('query') if observed_parts.get('query') else '(none)') +
|
|
3187
|
+
' ' +
|
|
3188
|
+
(observed_parts.get('hash') if observed_parts.get('hash') else '(none)')
|
|
3189
|
+
)
|
|
3067
3190
|
details = after_observation.get('details') or {}
|
|
3068
3191
|
if details.get('headings'):
|
|
3069
3192
|
summary_lines.append('Visible headings: ' + '; '.join(str(item) for item in details.get('headings', [])[:6]))
|
|
@@ -249,6 +249,47 @@ class FakeRiddle:
|
|
|
249
249
|
'largeVisibleElements': [{'tag': 'button', 'text': 'Buy Now'}],
|
|
250
250
|
}),
|
|
251
251
|
}
|
|
252
|
+
if 'clickedSkipHashNavigation' in script:
|
|
253
|
+
page_state = {
|
|
254
|
+
'bodyTextLength': 180,
|
|
255
|
+
'visibleTextSample': 'Riddle Proof homepage main content',
|
|
256
|
+
'interactiveElements': 4,
|
|
257
|
+
'visibleInteractiveElements': 4,
|
|
258
|
+
'pathname': '/',
|
|
259
|
+
'search': '',
|
|
260
|
+
'hash': '#main-content',
|
|
261
|
+
'title': 'Riddle',
|
|
262
|
+
'buttons': ['Start Free'],
|
|
263
|
+
'headings': ['Riddle Proof'],
|
|
264
|
+
'links': [{'text': 'Skip to main content', 'href': '#main-content'}],
|
|
265
|
+
'canvasCount': 0,
|
|
266
|
+
'largeVisibleElements': [{'tag': 'main', 'text': 'Riddle Proof'}],
|
|
267
|
+
}
|
|
268
|
+
proof_evidence = {
|
|
269
|
+
'before': {'href': 'https://riddledc.com/'},
|
|
270
|
+
'action': 'clicked Skip to main content',
|
|
271
|
+
'after': {'href': 'https://riddledc.com/#main-content'},
|
|
272
|
+
'assertions': {
|
|
273
|
+
'startedOnHome': True,
|
|
274
|
+
'hashPreserved': True,
|
|
275
|
+
'mainContentFocused': True,
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
'ok': True,
|
|
280
|
+
'screenshots': [{'url': 'https://cdn.example.com/hash-after.png'}],
|
|
281
|
+
'outputs': [{'name': 'after-hash.png', 'url': 'https://cdn.example.com/hash-after.png'}],
|
|
282
|
+
'result': {'pageState': page_state, 'proofEvidence': proof_evidence},
|
|
283
|
+
'console': [
|
|
284
|
+
'RIDDLE_PROOF_STATE:' + json.dumps(page_state),
|
|
285
|
+
'RIDDLE_PROOF_EVIDENCE:' + json.dumps(proof_evidence),
|
|
286
|
+
],
|
|
287
|
+
'visual_diff': {
|
|
288
|
+
'diffPercentage': 1.2,
|
|
289
|
+
'differentPixels': 12000,
|
|
290
|
+
'totalPixels': 972000,
|
|
291
|
+
},
|
|
292
|
+
}
|
|
252
293
|
if 'clickedProofNavigation' in script:
|
|
253
294
|
page_state = {
|
|
254
295
|
'bodyTextLength': 180,
|
|
@@ -1376,6 +1417,34 @@ def run_project_build_retries_after_clean_failure():
|
|
|
1376
1417
|
shutil.rmtree(tempdir)
|
|
1377
1418
|
|
|
1378
1419
|
|
|
1420
|
+
def run_invoke_retry_stops_on_playwright_locator_timeout():
|
|
1421
|
+
util = load_module('util_retry_timeout', UTIL_PATH)
|
|
1422
|
+
calls = []
|
|
1423
|
+
original_invoke = util.invoke
|
|
1424
|
+
original_sleep = util.time.sleep
|
|
1425
|
+
|
|
1426
|
+
def fake_invoke(tool, args, timeout=180):
|
|
1427
|
+
calls.append({'tool': tool, 'args': args, 'timeout': timeout})
|
|
1428
|
+
return {
|
|
1429
|
+
'ok': False,
|
|
1430
|
+
'error': 'locator.scrollIntoViewIfNeeded: Timeout 30000ms exceeded',
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
try:
|
|
1434
|
+
util.invoke = fake_invoke
|
|
1435
|
+
util.time.sleep = lambda _seconds: None
|
|
1436
|
+
result = util.invoke_retry('riddle_script', {'script': 'await page.locator("a").click();'}, retries=3, timeout=60)
|
|
1437
|
+
assert result['ok'] is False
|
|
1438
|
+
assert len(calls) == 1, calls
|
|
1439
|
+
calls.clear()
|
|
1440
|
+
generic = util.invoke_retry('riddle_preview', {'directory': '/tmp/nope'}, retries=3, timeout=60)
|
|
1441
|
+
assert generic['ok'] is False
|
|
1442
|
+
assert len(calls) == 3, calls
|
|
1443
|
+
finally:
|
|
1444
|
+
util.invoke = original_invoke
|
|
1445
|
+
util.time.sleep = original_sleep
|
|
1446
|
+
|
|
1447
|
+
|
|
1379
1448
|
def run_implement_records_detection_when_changes_missing():
|
|
1380
1449
|
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-implement-missing-'))
|
|
1381
1450
|
state_path = tempdir / 'state.json'
|
|
@@ -2381,6 +2450,51 @@ def run_verify_interaction_reverse_terminal_route_from_proof_evidence():
|
|
|
2381
2450
|
shutil.rmtree(tempdir, ignore_errors=True)
|
|
2382
2451
|
|
|
2383
2452
|
|
|
2453
|
+
def run_verify_interaction_hash_terminal_route_from_proof_evidence():
|
|
2454
|
+
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-interaction-hash-'))
|
|
2455
|
+
state_path = tempdir / 'state.json'
|
|
2456
|
+
try:
|
|
2457
|
+
state = base_state(tempdir, reference='before')
|
|
2458
|
+
state.update({
|
|
2459
|
+
'recon_status': 'ready_for_proof_plan',
|
|
2460
|
+
'author_status': 'ready',
|
|
2461
|
+
'proof_plan_status': 'ready',
|
|
2462
|
+
'implementation_status': 'changes_detected',
|
|
2463
|
+
'verification_mode': 'interaction',
|
|
2464
|
+
'server_path': '/',
|
|
2465
|
+
'before_cdn': 'https://cdn.example.com/before-home.png',
|
|
2466
|
+
'proof_plan': 'Start at /, click the skip link, and verify the terminal /#main-content route.',
|
|
2467
|
+
'capture_script': "clickedSkipHashNavigation(); await saveScreenshot('after-hash');",
|
|
2468
|
+
'recon_results': {
|
|
2469
|
+
'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
|
|
2470
|
+
},
|
|
2471
|
+
})
|
|
2472
|
+
write_state(state_path, state)
|
|
2473
|
+
os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
|
|
2474
|
+
|
|
2475
|
+
fake = FakeRiddle()
|
|
2476
|
+
load_util_with_fake(fake)
|
|
2477
|
+
load_module('verify_interaction_hash_terminal_route', VERIFY_PATH)
|
|
2478
|
+
after_verify = json.loads(state_path.read_text())
|
|
2479
|
+
|
|
2480
|
+
assert after_verify['verify_status'] == 'evidence_captured'
|
|
2481
|
+
assert after_verify['route_expectation']['expected_path'] == '/#main-content'
|
|
2482
|
+
assert after_verify['route_expectation']['expected_hash'] == '#main-content'
|
|
2483
|
+
route = after_verify['proof_assessment_request']['semantic_context']['route']
|
|
2484
|
+
assert route['expected_after_path'] == '/#main-content'
|
|
2485
|
+
assert route['expected_terminal_hash'] == '#main-content'
|
|
2486
|
+
assert route['after_observed_path'] == '/#main-content'
|
|
2487
|
+
assert route['after_observed_hash'] == '#main-content'
|
|
2488
|
+
assert 'wrong route' not in after_verify['verify_results']['after']['observation']['reason']
|
|
2489
|
+
return {
|
|
2490
|
+
'ok': True,
|
|
2491
|
+
'expected_path': after_verify['route_expectation']['expected_path'],
|
|
2492
|
+
'after_observed_hash': route['after_observed_hash'],
|
|
2493
|
+
}
|
|
2494
|
+
finally:
|
|
2495
|
+
shutil.rmtree(tempdir, ignore_errors=True)
|
|
2496
|
+
|
|
2497
|
+
|
|
2384
2498
|
def run_verify_capture_retry_surfaces_script_timeout():
|
|
2385
2499
|
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-capture-timeout-'))
|
|
2386
2500
|
state_path = tempdir / 'state.json'
|
|
@@ -2778,6 +2892,7 @@ if __name__ == '__main__':
|
|
|
2778
2892
|
'capture_diagnostics_redaction': run_capture_diagnostics_redact_sensitive_values(),
|
|
2779
2893
|
'apply_auth_context': run_apply_auth_context_passes_supported_auth_payloads(),
|
|
2780
2894
|
'run_project_build_retries_after_clean_failure': run_project_build_retries_after_clean_failure(),
|
|
2895
|
+
'invoke_retry_stops_on_playwright_locator_timeout': run_invoke_retry_stops_on_playwright_locator_timeout(),
|
|
2781
2896
|
'implement_records_detection_when_changes_missing': run_implement_records_detection_when_changes_missing(),
|
|
2782
2897
|
'implement_ignores_tool_noise_when_detecting_changes': run_implement_ignores_tool_noise_when_detecting_changes(),
|
|
2783
2898
|
'verify_quality_ignores_proof_telemetry_console_text': run_verify_quality_ignores_proof_telemetry_console_text(),
|
|
@@ -2798,6 +2913,7 @@ if __name__ == '__main__':
|
|
|
2798
2913
|
'remote_audit_verify_uses_default_capture_script': run_remote_audit_verify_uses_default_capture_script(),
|
|
2799
2914
|
'verify_interaction_terminal_route_from_proof_evidence': run_verify_interaction_terminal_route_from_proof_evidence(),
|
|
2800
2915
|
'verify_interaction_reverse_terminal_route_from_proof_evidence': run_verify_interaction_reverse_terminal_route_from_proof_evidence(),
|
|
2916
|
+
'verify_interaction_hash_terminal_route_from_proof_evidence': run_verify_interaction_hash_terminal_route_from_proof_evidence(),
|
|
2801
2917
|
'verify_capture_retry_surfaces_script_timeout': run_verify_capture_retry_surfaces_script_timeout(),
|
|
2802
2918
|
'missing_baseline_guard': run_verify_missing_baseline(),
|
|
2803
2919
|
'ship_supervisor_gate': run_ship_missing_supervisor_gate(),
|