@riddledc/riddle-proof 0.8.5 → 0.8.7
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/codex-exec-agent.cjs +31 -10
- package/dist/adapters/codex-exec-agent.js +1 -1
- package/dist/adapters/codex.cjs +31 -10
- package/dist/adapters/codex.js +1 -1
- package/dist/adapters/local-agent.cjs +31 -10
- package/dist/adapters/local-agent.js +1 -1
- package/dist/advanced/engine-harness.cjs +64 -7
- package/dist/advanced/engine-harness.js +2 -2
- package/dist/advanced/index.cjs +64 -7
- package/dist/advanced/index.js +4 -4
- package/dist/advanced/proof-run-core.cjs +63 -6
- package/dist/advanced/proof-run-core.js +1 -1
- package/dist/advanced/proof-run-engine.cjs +63 -6
- package/dist/advanced/proof-run-engine.js +2 -2
- package/dist/advanced/runner.js +2 -2
- package/dist/{chunk-GMZ57RRY.js → chunk-46DDSZJR.js} +1 -1
- package/dist/{chunk-RV6LK7HU.js → chunk-5N5QFI2S.js} +63 -6
- package/dist/{chunk-UIJ7X63P.js → chunk-5N6MQCLC.js} +1 -1
- package/dist/{chunk-BDFSMWTI.js → chunk-E7ATYSYS.js} +1 -1
- package/dist/{chunk-7F5LNUGR.js → chunk-PYCQNK66.js} +31 -10
- package/dist/{chunk-OD5UNE57.js → chunk-V6VZ3CAI.js} +2 -2
- package/dist/cli/index.js +4 -4
- package/dist/cli.cjs +100 -22
- package/dist/cli.js +4 -4
- package/dist/codex-exec-agent.cjs +31 -10
- package/dist/codex-exec-agent.js +1 -1
- package/dist/engine-harness.cjs +64 -7
- package/dist/engine-harness.js +2 -2
- package/dist/index.cjs +100 -22
- package/dist/index.js +4 -4
- package/dist/local-agent.cjs +31 -10
- package/dist/local-agent.js +1 -1
- package/dist/proof-run-core.cjs +63 -6
- package/dist/proof-run-core.js +1 -1
- package/dist/proof-run-engine.cjs +63 -6
- package/dist/proof-run-engine.js +2 -2
- package/dist/runner.js +2 -2
- package/package.json +1 -1
- package/runtime/lib/author.py +40 -1
- package/runtime/lib/verify.py +123 -1
- package/runtime/tests/recon_verify_smoke.py +82 -8
package/dist/proof-run-core.cjs
CHANGED
|
@@ -215,6 +215,56 @@ function writeState(statePath, state) {
|
|
|
215
215
|
function normalizeOptionalString(value) {
|
|
216
216
|
return typeof value === "string" ? value.trim() : void 0;
|
|
217
217
|
}
|
|
218
|
+
var INTERACTION_VERIFICATION_MODES = /* @__PURE__ */ new Set(["interaction", "interactive", "user_flow", "user-flow", "workflow"]);
|
|
219
|
+
function normalizeRoutePath(value) {
|
|
220
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
221
|
+
if (!raw) return "";
|
|
222
|
+
try {
|
|
223
|
+
const url = /^https?:\/\//i.test(raw) ? new URL(raw) : new URL(raw.startsWith("/") || raw.startsWith("?") || raw.startsWith("#") ? raw : `/${raw}`, "https://riddle-proof.local");
|
|
224
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
225
|
+
return `${pathname}${url.search}${url.hash}`;
|
|
226
|
+
} catch {
|
|
227
|
+
const hashSplit = raw.split("#");
|
|
228
|
+
const beforeHash = hashSplit.shift() || "";
|
|
229
|
+
const hash = hashSplit.length ? `#${hashSplit.join("#")}` : "";
|
|
230
|
+
const querySplit = beforeHash.split("?");
|
|
231
|
+
const rawPath = querySplit.shift() || "";
|
|
232
|
+
const query = querySplit.length ? `?${querySplit.join("?")}` : "";
|
|
233
|
+
const pathname = `/${rawPath}`.replace(/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
234
|
+
return `${pathname}${query}${hash}`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function isInteractionVerificationMode(value) {
|
|
238
|
+
return INTERACTION_VERIFICATION_MODES.has(typeof value === "string" ? value.trim().toLowerCase() : "");
|
|
239
|
+
}
|
|
240
|
+
function stringRecordValue(record, key) {
|
|
241
|
+
if (!record || typeof record !== "object") return "";
|
|
242
|
+
const value = record[key];
|
|
243
|
+
return typeof value === "string" ? value.trim() : "";
|
|
244
|
+
}
|
|
245
|
+
function appendStateWarning(state, key, warning) {
|
|
246
|
+
const existing = Array.isArray(state[key]) ? state[key].filter((item) => typeof item === "string") : [];
|
|
247
|
+
if (!existing.includes(warning)) state[key] = [...existing, warning];
|
|
248
|
+
}
|
|
249
|
+
function interactionStartPathForAuthorPacket(state, parsed, refined) {
|
|
250
|
+
return normalizeRoutePath(
|
|
251
|
+
stringRecordValue(state, "expected_start_path") || stringRecordValue(refined, "expected_start_path") || stringRecordValue(parsed.interaction_contract, "start_path") || stringRecordValue(parsed.proof_contract, "start_path") || stringRecordValue(state, "server_path") || "/"
|
|
252
|
+
) || "/";
|
|
253
|
+
}
|
|
254
|
+
function authorPacketServerPath(state, parsed, refined, serverPath, expectedTerminalPath) {
|
|
255
|
+
if (!isInteractionVerificationMode(state.verification_mode)) return serverPath;
|
|
256
|
+
const startPath = interactionStartPathForAuthorPacket(state, parsed, refined);
|
|
257
|
+
state.expected_start_path = startPath;
|
|
258
|
+
if (expectedTerminalPath && normalizeRoutePath(serverPath) === normalizeRoutePath(expectedTerminalPath) && normalizeRoutePath(serverPath) !== startPath) {
|
|
259
|
+
appendStateWarning(
|
|
260
|
+
state,
|
|
261
|
+
"author_warnings",
|
|
262
|
+
"Supervisor packet refined_inputs.server_path matched the terminal interaction route; kept the recon start route for capture."
|
|
263
|
+
);
|
|
264
|
+
return startPath;
|
|
265
|
+
}
|
|
266
|
+
return serverPath;
|
|
267
|
+
}
|
|
218
268
|
function knownEnvironmentIssuesFromNotes(notes) {
|
|
219
269
|
const text = notes.toLowerCase();
|
|
220
270
|
const issues = [];
|
|
@@ -873,17 +923,24 @@ function mergeStateFromParams(statePath, params) {
|
|
|
873
923
|
state.proof_contract = parsed.proof_contract;
|
|
874
924
|
}
|
|
875
925
|
const refined = parsed?.refined_inputs || {};
|
|
926
|
+
const expectedTerminalPath = normalizeOptionalString(
|
|
927
|
+
typeof refined?.expected_terminal_path === "string" ? refined.expected_terminal_path : typeof parsed?.expected_terminal_path === "string" ? parsed.expected_terminal_path : ""
|
|
928
|
+
) || "";
|
|
876
929
|
if (typeof refined?.server_path === "string") {
|
|
877
|
-
|
|
930
|
+
const refinedServerPath = normalizeOptionalString(refined.server_path) || "";
|
|
931
|
+
state.server_path = authorPacketServerPath(
|
|
932
|
+
state,
|
|
933
|
+
parsed,
|
|
934
|
+
refined,
|
|
935
|
+
refinedServerPath,
|
|
936
|
+
expectedTerminalPath
|
|
937
|
+
);
|
|
878
938
|
state.server_path_source = "supervising_agent";
|
|
879
939
|
}
|
|
880
940
|
if (typeof refined?.wait_for_selector === "string") state.wait_for_selector = normalizeOptionalString(refined.wait_for_selector) || "";
|
|
881
941
|
if (typeof refined?.reference === "string" && refined.reference.trim()) state.reference = refined.reference.trim();
|
|
882
|
-
if (
|
|
883
|
-
state.expected_terminal_path =
|
|
884
|
-
}
|
|
885
|
-
if (typeof parsed?.expected_terminal_path === "string") {
|
|
886
|
-
state.expected_terminal_path = normalizeOptionalString(parsed.expected_terminal_path) || "";
|
|
942
|
+
if (expectedTerminalPath) {
|
|
943
|
+
state.expected_terminal_path = expectedTerminalPath;
|
|
887
944
|
}
|
|
888
945
|
if (typeof parsed?.confidence === "string") state.supervisor_author_confidence = normalizeOptionalString(parsed.confidence) || null;
|
|
889
946
|
if (parsed?.rationale !== void 0) state.supervisor_author_rationale = parsed.rationale;
|
package/dist/proof-run-core.js
CHANGED
|
@@ -195,6 +195,56 @@ function writeState(statePath, state) {
|
|
|
195
195
|
function normalizeOptionalString(value) {
|
|
196
196
|
return typeof value === "string" ? value.trim() : void 0;
|
|
197
197
|
}
|
|
198
|
+
var INTERACTION_VERIFICATION_MODES = /* @__PURE__ */ new Set(["interaction", "interactive", "user_flow", "user-flow", "workflow"]);
|
|
199
|
+
function normalizeRoutePath(value) {
|
|
200
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
201
|
+
if (!raw) return "";
|
|
202
|
+
try {
|
|
203
|
+
const url = /^https?:\/\//i.test(raw) ? new URL(raw) : new URL(raw.startsWith("/") || raw.startsWith("?") || raw.startsWith("#") ? raw : `/${raw}`, "https://riddle-proof.local");
|
|
204
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
205
|
+
return `${pathname}${url.search}${url.hash}`;
|
|
206
|
+
} catch {
|
|
207
|
+
const hashSplit = raw.split("#");
|
|
208
|
+
const beforeHash = hashSplit.shift() || "";
|
|
209
|
+
const hash = hashSplit.length ? `#${hashSplit.join("#")}` : "";
|
|
210
|
+
const querySplit = beforeHash.split("?");
|
|
211
|
+
const rawPath = querySplit.shift() || "";
|
|
212
|
+
const query = querySplit.length ? `?${querySplit.join("?")}` : "";
|
|
213
|
+
const pathname = `/${rawPath}`.replace(/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
214
|
+
return `${pathname}${query}${hash}`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function isInteractionVerificationMode(value) {
|
|
218
|
+
return INTERACTION_VERIFICATION_MODES.has(typeof value === "string" ? value.trim().toLowerCase() : "");
|
|
219
|
+
}
|
|
220
|
+
function stringRecordValue(record, key) {
|
|
221
|
+
if (!record || typeof record !== "object") return "";
|
|
222
|
+
const value = record[key];
|
|
223
|
+
return typeof value === "string" ? value.trim() : "";
|
|
224
|
+
}
|
|
225
|
+
function appendStateWarning(state, key, warning) {
|
|
226
|
+
const existing = Array.isArray(state[key]) ? state[key].filter((item) => typeof item === "string") : [];
|
|
227
|
+
if (!existing.includes(warning)) state[key] = [...existing, warning];
|
|
228
|
+
}
|
|
229
|
+
function interactionStartPathForAuthorPacket(state, parsed, refined) {
|
|
230
|
+
return normalizeRoutePath(
|
|
231
|
+
stringRecordValue(state, "expected_start_path") || stringRecordValue(refined, "expected_start_path") || stringRecordValue(parsed.interaction_contract, "start_path") || stringRecordValue(parsed.proof_contract, "start_path") || stringRecordValue(state, "server_path") || "/"
|
|
232
|
+
) || "/";
|
|
233
|
+
}
|
|
234
|
+
function authorPacketServerPath(state, parsed, refined, serverPath, expectedTerminalPath) {
|
|
235
|
+
if (!isInteractionVerificationMode(state.verification_mode)) return serverPath;
|
|
236
|
+
const startPath = interactionStartPathForAuthorPacket(state, parsed, refined);
|
|
237
|
+
state.expected_start_path = startPath;
|
|
238
|
+
if (expectedTerminalPath && normalizeRoutePath(serverPath) === normalizeRoutePath(expectedTerminalPath) && normalizeRoutePath(serverPath) !== startPath) {
|
|
239
|
+
appendStateWarning(
|
|
240
|
+
state,
|
|
241
|
+
"author_warnings",
|
|
242
|
+
"Supervisor packet refined_inputs.server_path matched the terminal interaction route; kept the recon start route for capture."
|
|
243
|
+
);
|
|
244
|
+
return startPath;
|
|
245
|
+
}
|
|
246
|
+
return serverPath;
|
|
247
|
+
}
|
|
198
248
|
function knownEnvironmentIssuesFromNotes(notes) {
|
|
199
249
|
const text = notes.toLowerCase();
|
|
200
250
|
const issues = [];
|
|
@@ -853,17 +903,24 @@ function mergeStateFromParams(statePath, params) {
|
|
|
853
903
|
state.proof_contract = parsed.proof_contract;
|
|
854
904
|
}
|
|
855
905
|
const refined = parsed?.refined_inputs || {};
|
|
906
|
+
const expectedTerminalPath = normalizeOptionalString(
|
|
907
|
+
typeof refined?.expected_terminal_path === "string" ? refined.expected_terminal_path : typeof parsed?.expected_terminal_path === "string" ? parsed.expected_terminal_path : ""
|
|
908
|
+
) || "";
|
|
856
909
|
if (typeof refined?.server_path === "string") {
|
|
857
|
-
|
|
910
|
+
const refinedServerPath = normalizeOptionalString(refined.server_path) || "";
|
|
911
|
+
state.server_path = authorPacketServerPath(
|
|
912
|
+
state,
|
|
913
|
+
parsed,
|
|
914
|
+
refined,
|
|
915
|
+
refinedServerPath,
|
|
916
|
+
expectedTerminalPath
|
|
917
|
+
);
|
|
858
918
|
state.server_path_source = "supervising_agent";
|
|
859
919
|
}
|
|
860
920
|
if (typeof refined?.wait_for_selector === "string") state.wait_for_selector = normalizeOptionalString(refined.wait_for_selector) || "";
|
|
861
921
|
if (typeof refined?.reference === "string" && refined.reference.trim()) state.reference = refined.reference.trim();
|
|
862
|
-
if (
|
|
863
|
-
state.expected_terminal_path =
|
|
864
|
-
}
|
|
865
|
-
if (typeof parsed?.expected_terminal_path === "string") {
|
|
866
|
-
state.expected_terminal_path = normalizeOptionalString(parsed.expected_terminal_path) || "";
|
|
922
|
+
if (expectedTerminalPath) {
|
|
923
|
+
state.expected_terminal_path = expectedTerminalPath;
|
|
867
924
|
}
|
|
868
925
|
if (typeof parsed?.confidence === "string") state.supervisor_author_confidence = normalizeOptionalString(parsed.confidence) || null;
|
|
869
926
|
if (parsed?.rationale !== void 0) state.supervisor_author_rationale = parsed.rationale;
|
package/dist/proof-run-engine.js
CHANGED
package/dist/runner.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runRiddleProof
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5N6MQCLC.js";
|
|
4
4
|
import "./chunk-YZUVEJ5B.js";
|
|
5
5
|
import "./chunk-FMOYUYH2.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-5N5QFI2S.js";
|
|
7
7
|
import "./chunk-4FOHZ7JG.js";
|
|
8
8
|
import "./chunk-VY4Y5U57.js";
|
|
9
9
|
import "./chunk-MLKGABMK.js";
|
package/package.json
CHANGED
package/runtime/lib/author.py
CHANGED
|
@@ -9,6 +9,7 @@ Instead it does two things:
|
|
|
9
9
|
import json
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
|
+
from urllib.parse import urlparse
|
|
12
13
|
|
|
13
14
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
15
|
from util import load_state, save_state
|
|
@@ -34,6 +35,31 @@ def normalize_path(value):
|
|
|
34
35
|
return path
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
def normalize_route_path(value):
|
|
39
|
+
raw = (value or '').strip()
|
|
40
|
+
if not raw:
|
|
41
|
+
return ''
|
|
42
|
+
parsed = urlparse(raw)
|
|
43
|
+
path = parsed.path or raw
|
|
44
|
+
query = parsed.query or ''
|
|
45
|
+
fragment = parsed.fragment or ''
|
|
46
|
+
if '?' in path:
|
|
47
|
+
path, query_tail = path.split('?', 1)
|
|
48
|
+
query = query or query_tail.split('#', 1)[0]
|
|
49
|
+
if '#' in path:
|
|
50
|
+
path, fragment_tail = path.split('#', 1)
|
|
51
|
+
fragment = fragment or fragment_tail
|
|
52
|
+
if not path.startswith('/'):
|
|
53
|
+
path = '/' + path.lstrip('/')
|
|
54
|
+
path = path.rstrip('/') or '/'
|
|
55
|
+
return path + (('?' + query) if query else '') + (('#' + fragment) if fragment else '')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_interaction_mode(state):
|
|
59
|
+
mode = (state.get('verification_mode') or '').strip().lower()
|
|
60
|
+
return mode in ('interaction', 'interactive', 'user_flow', 'user-flow', 'workflow')
|
|
61
|
+
|
|
62
|
+
|
|
37
63
|
def first_non_empty(*values):
|
|
38
64
|
for value in values:
|
|
39
65
|
if isinstance(value, str) and value.strip():
|
|
@@ -158,6 +184,7 @@ def author_request_payload(state, reference, baselines, current_plan, hypothesis
|
|
|
158
184
|
'Keep capture_script concise Playwright statements.',
|
|
159
185
|
'For visual/UI proof, include saveScreenshot(\'after-proof\') exactly once.',
|
|
160
186
|
'For interaction proof, preserve the interaction contract and name the expected terminal route/state separately from the initial route.',
|
|
187
|
+
'For interaction proof, return a JSON-serializable evidence object with start route/state, terminal route/state, action, assertions, and matched UI text; catch waitForURL or selector timeouts and record them as failed assertions instead of throwing before evidence is emitted.',
|
|
161
188
|
'For playable/gameplay proof, start the experience, send keyboard or pointer input, sample state before/after, measure non-HUD playfield/canvas pixel deltas across time, and return a JSON-serializable evidence object with playability or playability_evidence version riddle-proof.playability.v1.',
|
|
162
189
|
'For data/audio/log/metric/custom proof, screenshots are optional; collect measurements inside page.evaluate, assign the result to an evidence variable, and return that evidence object from capture_script.',
|
|
163
190
|
'Do not assign globalThis.__riddleProofEvidence, window.__riddleProofEvidence, or self.__riddleProofEvidence in the worker context. Avoid global evidence assignment unless it is inside page.evaluate for compatibility with older packets.',
|
|
@@ -283,6 +310,17 @@ expected_terminal_path = normalize_path(first_non_empty(
|
|
|
283
310
|
supervisor_packet.get('expected_after_path'),
|
|
284
311
|
s.get('expected_terminal_path'),
|
|
285
312
|
))
|
|
313
|
+
author_warnings = []
|
|
314
|
+
if is_interaction_mode(s):
|
|
315
|
+
interaction_start_path = normalize_route_path(first_non_empty(s.get('expected_start_path'), default_path, s.get('server_path'), '/')) or '/'
|
|
316
|
+
refined_route = normalize_route_path(refined_path)
|
|
317
|
+
terminal_route = normalize_route_path(expected_terminal_path)
|
|
318
|
+
if terminal_route and refined_route == terminal_route and refined_route != interaction_start_path:
|
|
319
|
+
refined_path = interaction_start_path
|
|
320
|
+
author_warnings.append(
|
|
321
|
+
'Supervisor packet refined_inputs.server_path matched the terminal interaction route; kept the recon start route for capture.'
|
|
322
|
+
)
|
|
323
|
+
s['expected_start_path'] = interaction_start_path
|
|
286
324
|
confidence = provided_payload['confidence'] if provided_payload['confidence'] in ('high', 'medium', 'low') else 'medium'
|
|
287
325
|
rationale = sanitize_rationale(provided_payload['rationale'])
|
|
288
326
|
summary = provided_payload['summary'] or 'Supervising agent supplied the proof packet from recon observations.'
|
|
@@ -300,6 +338,7 @@ authored_packet = {
|
|
|
300
338
|
'interaction_contract': provided_payload['interaction_contract'],
|
|
301
339
|
'proof_contract': provided_payload['proof_contract'],
|
|
302
340
|
'rationale': rationale,
|
|
341
|
+
'warnings': author_warnings,
|
|
303
342
|
'confidence': confidence,
|
|
304
343
|
'mode': 'supervising_agent',
|
|
305
344
|
'model': ('supervising-agent:' + RUNTIME_MODEL_HINT) if RUNTIME_MODEL_HINT else 'supervising-agent',
|
|
@@ -327,7 +366,7 @@ s['author_mode'] = 'supervising_agent'
|
|
|
327
366
|
s['author_model'] = authored_packet['model']
|
|
328
367
|
s['author_confidence'] = confidence
|
|
329
368
|
s['author_rationale'] = rationale
|
|
330
|
-
s['author_warnings'] =
|
|
369
|
+
s['author_warnings'] = author_warnings
|
|
331
370
|
s['author_runtime_model_hint'] = RUNTIME_MODEL_HINT
|
|
332
371
|
s['author_packet'] = authored_packet
|
|
333
372
|
s['author_summary'] = summary
|
package/runtime/lib/verify.py
CHANGED
|
@@ -558,6 +558,83 @@ def extract_proof_evidence(payload):
|
|
|
558
558
|
return evidence
|
|
559
559
|
|
|
560
560
|
|
|
561
|
+
def attach_interaction_capture_failure_evidence(state, payload, expected_path, after_observation):
|
|
562
|
+
if not isinstance(payload, dict):
|
|
563
|
+
return payload, None
|
|
564
|
+
mode = normalized_verification_mode(state.get('verification_mode'))
|
|
565
|
+
if mode not in INTERACTION_MODES:
|
|
566
|
+
return payload, None
|
|
567
|
+
if extract_proof_evidence(payload) is not None:
|
|
568
|
+
return payload, None
|
|
569
|
+
|
|
570
|
+
details = after_observation.get('details') if isinstance(after_observation, dict) else {}
|
|
571
|
+
if not isinstance(details, dict):
|
|
572
|
+
details = {}
|
|
573
|
+
expected = normalize_observed_path(expected_path)
|
|
574
|
+
observed_raw = str(details.get('observed_path_raw') or details.get('observed_path') or '').strip()
|
|
575
|
+
observed = normalize_observed_path(observed_raw)
|
|
576
|
+
route_matches = route_matches_expected(expected, observed_raw or observed) if expected and (observed_raw or observed) else None
|
|
577
|
+
error_messages = [
|
|
578
|
+
str(item).strip()
|
|
579
|
+
for item in (details.get('capture_error_messages') or [])
|
|
580
|
+
if str(item).strip()
|
|
581
|
+
]
|
|
582
|
+
if route_matches is not False and not error_messages:
|
|
583
|
+
return payload, None
|
|
584
|
+
|
|
585
|
+
route_expectation = state.get('route_expectation') if isinstance(state.get('route_expectation'), dict) else {}
|
|
586
|
+
expected_parts = route_parts(expected)
|
|
587
|
+
observed_parts = route_parts(observed_raw or observed)
|
|
588
|
+
evidence = {
|
|
589
|
+
'version': 'riddle-proof.interaction.capture-failure.v1',
|
|
590
|
+
'synthetic': True,
|
|
591
|
+
'source': 'verify_capture_failure',
|
|
592
|
+
'mode': mode,
|
|
593
|
+
'passed': False,
|
|
594
|
+
'proofReady': False,
|
|
595
|
+
'authored_proof_evidence_present': False,
|
|
596
|
+
'evidence_summary': 'Interaction capture failed before the authored script emitted structured proof evidence.',
|
|
597
|
+
'expected': {
|
|
598
|
+
'path': expected,
|
|
599
|
+
'pathname': expected_parts.get('pathname') or '',
|
|
600
|
+
'query': expected_parts.get('query') or '',
|
|
601
|
+
'hash': expected_parts.get('hash') or '',
|
|
602
|
+
},
|
|
603
|
+
'observed': {
|
|
604
|
+
'path': observed,
|
|
605
|
+
'raw_path': observed_raw,
|
|
606
|
+
'pathname': observed_parts.get('pathname') or '',
|
|
607
|
+
'query': observed_parts.get('query') or '',
|
|
608
|
+
'hash': observed_parts.get('hash') or '',
|
|
609
|
+
},
|
|
610
|
+
'checks': {
|
|
611
|
+
'scriptCompleted': len(error_messages) == 0,
|
|
612
|
+
'routeMatches': bool(route_matches),
|
|
613
|
+
'authoredEvidenceReturned': False,
|
|
614
|
+
},
|
|
615
|
+
'route_expectation_source': route_expectation.get('source') or '',
|
|
616
|
+
}
|
|
617
|
+
if error_messages:
|
|
618
|
+
evidence['capture_error'] = error_messages[0][:1000]
|
|
619
|
+
if route_matches is False:
|
|
620
|
+
evidence['evidence_summary'] = (
|
|
621
|
+
'Interaction capture reached a different terminal route than expected before authored proof evidence was emitted.'
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
enriched = enrich_capture_payload(payload)
|
|
625
|
+
patched = dict(enriched)
|
|
626
|
+
result = dict(patched.get('result') or {})
|
|
627
|
+
result['proofEvidence'] = evidence
|
|
628
|
+
patched['result'] = result
|
|
629
|
+
console = list(patched.get('console') or [])
|
|
630
|
+
try:
|
|
631
|
+
console.append(PROOF_EVIDENCE_PREFIX + json.dumps(evidence, sort_keys=True))
|
|
632
|
+
except Exception:
|
|
633
|
+
pass
|
|
634
|
+
patched['console'] = console
|
|
635
|
+
return patched, evidence
|
|
636
|
+
|
|
637
|
+
|
|
561
638
|
def proof_evidence_records(value):
|
|
562
639
|
if isinstance(value, dict):
|
|
563
640
|
return [value]
|
|
@@ -1918,16 +1995,22 @@ def route_parts(value):
|
|
|
1918
1995
|
|
|
1919
1996
|
EXPLICIT_TERMINAL_PATH_KEYS = (
|
|
1920
1997
|
'expected_terminal_path', 'expectedTerminalPath',
|
|
1998
|
+
'expected_terminal_url', 'expectedTerminalUrl',
|
|
1921
1999
|
'expected_terminal_route', 'expectedTerminalRoute',
|
|
1922
2000
|
'terminal_path', 'terminalPath',
|
|
2001
|
+
'terminal_url', 'terminalUrl',
|
|
1923
2002
|
'terminal_route', 'terminalRoute',
|
|
1924
2003
|
'expected_after_path', 'expectedAfterPath',
|
|
2004
|
+
'expected_after_url', 'expectedAfterUrl',
|
|
1925
2005
|
'expected_after_route', 'expectedAfterRoute',
|
|
1926
2006
|
'after_path', 'afterPath',
|
|
2007
|
+
'after_url', 'afterUrl',
|
|
1927
2008
|
'after_route', 'afterRoute',
|
|
1928
2009
|
'expected_final_path', 'expectedFinalPath',
|
|
2010
|
+
'expected_final_url', 'expectedFinalUrl',
|
|
1929
2011
|
'expected_final_route', 'expectedFinalRoute',
|
|
1930
2012
|
'final_path', 'finalPath',
|
|
2013
|
+
'final_url', 'finalUrl',
|
|
1931
2014
|
'final_route', 'finalRoute',
|
|
1932
2015
|
)
|
|
1933
2016
|
LOCATION_PATH_KEYS = ('path', 'pathname', 'route', 'url', 'href')
|
|
@@ -1939,6 +2022,11 @@ AFTER_STATE_KEYS = (
|
|
|
1939
2022
|
'final', 'final_state', 'finalState',
|
|
1940
2023
|
'expected_final', 'expectedFinal',
|
|
1941
2024
|
)
|
|
2025
|
+
EVIDENCE_CONTAINER_KEYS = (
|
|
2026
|
+
'proofEvidence', 'proof_evidence',
|
|
2027
|
+
'interactionEvidence', 'interaction_evidence',
|
|
2028
|
+
'evidence',
|
|
2029
|
+
)
|
|
1942
2030
|
CONTRACT_STATE_KEYS = (
|
|
1943
2031
|
'interaction_contract', 'interactionContract',
|
|
1944
2032
|
'proof_contract', 'proofContract',
|
|
@@ -1990,6 +2078,17 @@ def terminal_path_from_record(record, depth=0):
|
|
|
1990
2078
|
candidate = terminal_path_from_record(item, depth + 1)
|
|
1991
2079
|
if candidate:
|
|
1992
2080
|
return candidate
|
|
2081
|
+
for key in EVIDENCE_CONTAINER_KEYS:
|
|
2082
|
+
value = record.get(key)
|
|
2083
|
+
if isinstance(value, dict):
|
|
2084
|
+
candidate = terminal_path_from_record(value, depth + 1)
|
|
2085
|
+
if candidate:
|
|
2086
|
+
return candidate
|
|
2087
|
+
elif isinstance(value, list):
|
|
2088
|
+
for item in value:
|
|
2089
|
+
candidate = terminal_path_from_record(item, depth + 1)
|
|
2090
|
+
if candidate:
|
|
2091
|
+
return candidate
|
|
1993
2092
|
for key in CONTRACT_STATE_KEYS:
|
|
1994
2093
|
value = record.get(key)
|
|
1995
2094
|
if isinstance(value, dict):
|
|
@@ -2004,11 +2103,25 @@ def terminal_path_from_record(record, depth=0):
|
|
|
2004
2103
|
return ''
|
|
2005
2104
|
|
|
2006
2105
|
|
|
2106
|
+
def text_path_candidate(value):
|
|
2107
|
+
if not isinstance(value, str):
|
|
2108
|
+
return ''
|
|
2109
|
+
raw = value.strip().rstrip('.,;:)]}')
|
|
2110
|
+
return path_candidate(raw)
|
|
2111
|
+
|
|
2112
|
+
|
|
2007
2113
|
def terminal_path_from_text(value):
|
|
2008
2114
|
if not isinstance(value, str):
|
|
2009
2115
|
return ''
|
|
2010
2116
|
for match in re.findall(r"""['"`](/[^'"`\s]+[?#][^'"`\s]*)['"`]""", value):
|
|
2011
|
-
candidate =
|
|
2117
|
+
candidate = text_path_candidate(match)
|
|
2118
|
+
if candidate:
|
|
2119
|
+
return candidate
|
|
2120
|
+
context_pattern = re.compile(
|
|
2121
|
+
r"""(?is)\b(?:expected\s+(?:terminal|after|final)|terminal|after|final)\b[^/\r\n]{0,120}['"`]?(/[^'"`\s,;)]*)"""
|
|
2122
|
+
)
|
|
2123
|
+
for match in context_pattern.findall(value):
|
|
2124
|
+
candidate = text_path_candidate(match)
|
|
2012
2125
|
if candidate:
|
|
2013
2126
|
return candidate
|
|
2014
2127
|
return ''
|
|
@@ -3113,6 +3226,15 @@ after_observation = evaluate_capture_quality(after_payload, expected_path, verif
|
|
|
3113
3226
|
details = after_observation.get('details') if isinstance(after_observation.get('details'), dict) else {}
|
|
3114
3227
|
details['viewport_matrix'] = after_viewport_matrix
|
|
3115
3228
|
after_observation['details'] = details
|
|
3229
|
+
after_payload, synthetic_interaction_failure_evidence = attach_interaction_capture_failure_evidence(s, after_payload, expected_path, after_observation)
|
|
3230
|
+
if synthetic_interaction_failure_evidence is not None:
|
|
3231
|
+
s['synthetic_interaction_failure_evidence'] = synthetic_interaction_failure_evidence
|
|
3232
|
+
if isinstance(results.get('after'), dict):
|
|
3233
|
+
results['after']['raw'] = after_payload
|
|
3234
|
+
after_observation = evaluate_capture_quality(after_payload, expected_path, verification_mode)
|
|
3235
|
+
details = after_observation.get('details') if isinstance(after_observation.get('details'), dict) else {}
|
|
3236
|
+
details['viewport_matrix'] = after_viewport_matrix
|
|
3237
|
+
after_observation['details'] = details
|
|
3116
3238
|
if after_viewport_matrix.get('status') == 'incomplete':
|
|
3117
3239
|
missing_names = [
|
|
3118
3240
|
str(item.get('name') or item.get('slug') or '').strip()
|
|
@@ -340,14 +340,18 @@ class FakeRiddle:
|
|
|
340
340
|
'largeVisibleElements': [{'tag': 'h1', 'text': 'Proof'}],
|
|
341
341
|
}
|
|
342
342
|
proof_evidence = {
|
|
343
|
-
'
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
'
|
|
348
|
-
'
|
|
349
|
-
'
|
|
350
|
-
|
|
343
|
+
'proofEvidence': {
|
|
344
|
+
'version': 'riddle-proof.interaction.v1',
|
|
345
|
+
'start': {'href': 'https://riddledc.com/'},
|
|
346
|
+
'action': {'type': 'click', 'target': 'Proof'},
|
|
347
|
+
'terminal': {'href': 'https://riddledc.com/proof/'},
|
|
348
|
+
'afterUrl': 'https://riddledc.com/proof/',
|
|
349
|
+
'assertions': {
|
|
350
|
+
'startedOnHome': True,
|
|
351
|
+
'clickedProofNavigation': True,
|
|
352
|
+
'terminalPathIsProof': True,
|
|
353
|
+
'proofContentVisible': True,
|
|
354
|
+
},
|
|
351
355
|
},
|
|
352
356
|
}
|
|
353
357
|
return {
|
|
@@ -1921,6 +1925,63 @@ def run_author_applies_supervisor_packet():
|
|
|
1921
1925
|
shutil.rmtree(tempdir, ignore_errors=True)
|
|
1922
1926
|
|
|
1923
1927
|
|
|
1928
|
+
def run_author_keeps_interaction_start_route():
|
|
1929
|
+
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-supervisor-interaction-start-'))
|
|
1930
|
+
state_path = tempdir / 'state.json'
|
|
1931
|
+
try:
|
|
1932
|
+
state = base_state(tempdir, reference='before')
|
|
1933
|
+
state.update({
|
|
1934
|
+
'recon_status': 'ready_for_proof_plan',
|
|
1935
|
+
'verification_mode': 'interaction',
|
|
1936
|
+
'server_path': '/',
|
|
1937
|
+
'expected_start_path': '/',
|
|
1938
|
+
'before_cdn': 'https://cdn.example.com/before-home.png',
|
|
1939
|
+
'recon_results': {
|
|
1940
|
+
'baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
|
|
1941
|
+
'current_plan': {'target_path': '/'},
|
|
1942
|
+
},
|
|
1943
|
+
'author_request': {
|
|
1944
|
+
'current_plan': {'target_path': '/'},
|
|
1945
|
+
'observed_baselines': {'before': {'path': '/', 'url': 'https://cdn.example.com/before-home.png'}},
|
|
1946
|
+
},
|
|
1947
|
+
'supervisor_author_packet': {
|
|
1948
|
+
'proof_plan': 'Start at /, click Proof, and verify the terminal /proof/ route.',
|
|
1949
|
+
'capture_script': "clickedProofNavigation(); await saveScreenshot('after-proof');",
|
|
1950
|
+
'refined_inputs': {
|
|
1951
|
+
'server_path': '/proof/',
|
|
1952
|
+
'expected_terminal_path': '/proof/',
|
|
1953
|
+
'wait_for_selector': '',
|
|
1954
|
+
'reference': 'before',
|
|
1955
|
+
},
|
|
1956
|
+
'rationale': ['The interaction starts on home and terminates on Proof.'],
|
|
1957
|
+
'confidence': 'high',
|
|
1958
|
+
'summary': 'Supervisor supplied the interaction proof packet.',
|
|
1959
|
+
},
|
|
1960
|
+
})
|
|
1961
|
+
write_state(state_path, state)
|
|
1962
|
+
os.environ['RIDDLE_PROOF_STATE_FILE'] = str(state_path)
|
|
1963
|
+
|
|
1964
|
+
fake = FakeRiddle()
|
|
1965
|
+
load_util_with_fake(fake)
|
|
1966
|
+
load_module('author_supervisor_interaction_start', AUTHOR_PATH)
|
|
1967
|
+
after_author = json.loads(state_path.read_text())
|
|
1968
|
+
|
|
1969
|
+
assert after_author['author_status'] == 'ready'
|
|
1970
|
+
assert after_author['server_path'] == '/'
|
|
1971
|
+
assert after_author['expected_start_path'] == '/'
|
|
1972
|
+
assert after_author['expected_terminal_path'] == '/proof/'
|
|
1973
|
+
assert after_author['author_packet']['refined_inputs']['server_path'] == '/'
|
|
1974
|
+
assert after_author['author_warnings']
|
|
1975
|
+
assert 'terminal interaction route' in after_author['author_warnings'][0]
|
|
1976
|
+
return {
|
|
1977
|
+
'ok': True,
|
|
1978
|
+
'server_path': after_author['server_path'],
|
|
1979
|
+
'expected_terminal_path': after_author['expected_terminal_path'],
|
|
1980
|
+
}
|
|
1981
|
+
finally:
|
|
1982
|
+
shutil.rmtree(tempdir, ignore_errors=True)
|
|
1983
|
+
|
|
1984
|
+
|
|
1924
1985
|
def run_verify_requests_supervisor_assessment():
|
|
1925
1986
|
tempdir = Path(tempfile.mkdtemp(prefix='riddle-proof-verify-supervisor-'))
|
|
1926
1987
|
state_path = tempdir / 'state.json'
|
|
@@ -2580,6 +2641,18 @@ def run_verify_interaction_authored_query_hash_mismatch_returns_author():
|
|
|
2580
2641
|
assert capture_quality['mismatch']['observed_after_path'] == '/pricing/'
|
|
2581
2642
|
assert 'page.waitForURL: Timeout 15000ms exceeded' in capture_quality['summary']
|
|
2582
2643
|
assert any('capture plan should be revised' in reason for reason in capture_quality['reasons'])
|
|
2644
|
+
supporting = after_verify['verify_results']['after']['supporting_artifacts']
|
|
2645
|
+
assert supporting['proof_evidence_present'] is True
|
|
2646
|
+
assert supporting['has_structured_payload'] is True
|
|
2647
|
+
synthetic_evidence = after_verify['evidence_bundle']['proof_evidence']
|
|
2648
|
+
assert synthetic_evidence['version'] == 'riddle-proof.interaction.capture-failure.v1'
|
|
2649
|
+
assert synthetic_evidence['passed'] is False
|
|
2650
|
+
assert synthetic_evidence['authored_proof_evidence_present'] is False
|
|
2651
|
+
assert synthetic_evidence['checks']['routeMatches'] is False
|
|
2652
|
+
assert synthetic_evidence['expected']['query'] == 'rp_probe=1'
|
|
2653
|
+
assert synthetic_evidence['expected']['hash'] == '#pricing-probe'
|
|
2654
|
+
assert synthetic_evidence['observed']['path'] == '/pricing'
|
|
2655
|
+
assert 'page.waitForURL: Timeout 15000ms exceeded' in synthetic_evidence['capture_error']
|
|
2583
2656
|
return {
|
|
2584
2657
|
'ok': True,
|
|
2585
2658
|
'summary': capture_quality['summary'],
|
|
@@ -2997,6 +3070,7 @@ if __name__ == '__main__':
|
|
|
2997
3070
|
'recon_hint_root_preference': run_recon_prefers_hint_root_over_single_route_literal(),
|
|
2998
3071
|
'capture_hint_rejects_route_specific_mode_only_match': run_capture_hint_rejects_route_specific_mode_only_match(),
|
|
2999
3072
|
'author_applies_supervisor_packet': run_author_applies_supervisor_packet(),
|
|
3073
|
+
'author_keeps_interaction_start_route': run_author_keeps_interaction_start_route(),
|
|
3000
3074
|
'verify_requests_supervisor_assessment': run_verify_requests_supervisor_assessment(),
|
|
3001
3075
|
'verify_routes_unmeasured_visual_delta_to_recovery': run_verify_routes_unmeasured_visual_delta_to_recovery(),
|
|
3002
3076
|
'verify_structured_evidence_without_screenshot': run_verify_structured_evidence_without_screenshot(),
|