@riddledc/riddle-proof 0.5.42 → 0.5.44
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/{chunk-U4VNN2CT.js → chunk-4ASMX4R6.js} +26 -5
- package/dist/{chunk-VSLU6XNI.js → chunk-MJD37CLH.js} +42 -8
- package/dist/{chunk-DWQK7J4U.js → chunk-XNRMMQAY.js} +3 -1
- package/dist/diagnostics.cjs +3 -1
- package/dist/diagnostics.js +1 -1
- package/dist/engine-harness.cjs +67 -14
- package/dist/engine-harness.js +2 -2
- package/dist/index.cjs +70 -15
- package/dist/index.js +3 -3
- package/dist/proof-run-core.cjs +26 -5
- package/dist/proof-run-core.js +1 -1
- package/dist/proof-run-engine.cjs +28 -7
- package/dist/proof-run-engine.js +3 -3
- package/package.json +1 -1
- package/runtime/lib/author.py +1 -0
- package/runtime/lib/riddle_core_call.mjs +22 -0
- package/runtime/lib/util.py +6 -1
- package/runtime/lib/verify.py +126 -0
- package/runtime/tests/recon_verify_smoke.py +44 -0
|
@@ -381,6 +381,21 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
381
381
|
if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
|
|
382
382
|
return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
|
|
383
383
|
}
|
|
384
|
+
function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
|
|
385
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
386
|
+
const status = String(visualDelta.status || "").trim();
|
|
387
|
+
const reason = `${String(visualDelta.reason || "")}
|
|
388
|
+
${blocker}`.toLowerCase();
|
|
389
|
+
if (status === "unmeasured") {
|
|
390
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
391
|
+
return "comparator_fetch_blocked";
|
|
392
|
+
}
|
|
393
|
+
return "visual_delta_unmeasured";
|
|
394
|
+
}
|
|
395
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
396
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
397
|
+
return "semantic_proof_failed";
|
|
398
|
+
}
|
|
384
399
|
function requiredBaselineLabelsForState(state = {}) {
|
|
385
400
|
const reference = normalizedReference(state);
|
|
386
401
|
const labels = [];
|
|
@@ -570,7 +585,7 @@ var CHECKPOINT_CONTRACT_SPECS = {
|
|
|
570
585
|
description: "JSON assessment with decision, summary, recommended_stage, continue_with_stage, escalation_target, and reasons."
|
|
571
586
|
}],
|
|
572
587
|
response_schema: {
|
|
573
|
-
decision: ["ready_to_ship", "needs_richer_proof"],
|
|
588
|
+
decision: ["ready_to_ship", "needs_richer_proof", "revise_capture"],
|
|
574
589
|
summary: "string",
|
|
575
590
|
recommended_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
576
591
|
continue_with_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
@@ -773,9 +788,15 @@ function mergeStateFromParams(statePath, params) {
|
|
|
773
788
|
const readyBlocker = assessment?.decision === "ready_to_ship" ? visualDeltaShipGateReason({ ...state, proof_assessment: assessment, proof_assessment_source: assessment.source }) : null;
|
|
774
789
|
if (readyBlocker) {
|
|
775
790
|
assessment.blocked_decision = assessment.decision;
|
|
776
|
-
assessment.decision = "
|
|
777
|
-
|
|
778
|
-
|
|
791
|
+
assessment.decision = "revise_capture";
|
|
792
|
+
assessment.recommended_stage = "verify";
|
|
793
|
+
assessment.continue_with_stage = "verify";
|
|
794
|
+
assessment.evidence_collection_incomplete = true;
|
|
795
|
+
assessment.recovery_stage = "verify";
|
|
796
|
+
assessment.recovery_reason = readyBlocker;
|
|
797
|
+
assessment.evidence_issue_code = visualDeltaEvidenceIssueCode(state, readyBlocker);
|
|
798
|
+
assessment.visual_delta = visualDeltaForState(state);
|
|
799
|
+
assessment.suggested_repair = "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.";
|
|
779
800
|
const blockers = Array.isArray(assessment.blockers) ? assessment.blockers : [];
|
|
780
801
|
assessment.blockers = [...blockers, readyBlocker];
|
|
781
802
|
}
|
|
@@ -794,7 +815,7 @@ function mergeStateFromParams(statePath, params) {
|
|
|
794
815
|
}
|
|
795
816
|
appendProofSummaryLine(state, `Supervising proof assessment: ${state.proof_assessment.decision || "unknown"}`);
|
|
796
817
|
if (readyBlocker) {
|
|
797
|
-
appendProofSummaryLine(state, `Ready-to-ship assessment
|
|
818
|
+
appendProofSummaryLine(state, `Ready-to-ship assessment routed to evidence recovery: ${readyBlocker}`);
|
|
798
819
|
}
|
|
799
820
|
if (state.proof_assessment_summary) {
|
|
800
821
|
appendProofSummaryLine(state, `Assessment summary: ${state.proof_assessment_summary}`);
|
|
@@ -25,8 +25,10 @@ import {
|
|
|
25
25
|
recordValue
|
|
26
26
|
} from "./chunk-W7VTDN4T.js";
|
|
27
27
|
import {
|
|
28
|
+
visualDeltaForState,
|
|
29
|
+
visualDeltaRequiredForState,
|
|
28
30
|
visualDeltaShipGateReason
|
|
29
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-4ASMX4R6.js";
|
|
30
32
|
|
|
31
33
|
// src/engine-harness.ts
|
|
32
34
|
import { execFileSync } from "child_process";
|
|
@@ -315,14 +317,36 @@ function proofAssessmentVisualBlocker(state, payload) {
|
|
|
315
317
|
proof_assessment_source: source
|
|
316
318
|
});
|
|
317
319
|
}
|
|
318
|
-
function
|
|
320
|
+
function visualDeltaBlockerCode(state, blocker) {
|
|
321
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
322
|
+
const status = nonEmptyString(visualDelta.status);
|
|
323
|
+
const reason = `${nonEmptyString(visualDelta.reason) || ""}
|
|
324
|
+
${blocker}`.toLowerCase();
|
|
325
|
+
if (status === "unmeasured") {
|
|
326
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
327
|
+
return "comparator_fetch_blocked";
|
|
328
|
+
}
|
|
329
|
+
return "visual_delta_unmeasured";
|
|
330
|
+
}
|
|
331
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
332
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
333
|
+
return "semantic_proof_failed";
|
|
334
|
+
}
|
|
335
|
+
function visualDeltaEvidenceRecoveryAssessment(state, payload, blocker) {
|
|
336
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
319
337
|
const blockers = Array.isArray(payload.blockers) ? payload.blockers.filter((item) => typeof item === "string") : [];
|
|
320
338
|
return {
|
|
321
339
|
...payload,
|
|
322
340
|
blocked_decision: payload.decision || "ready_to_ship",
|
|
323
|
-
decision: "
|
|
324
|
-
recommended_stage:
|
|
325
|
-
continue_with_stage:
|
|
341
|
+
decision: "revise_capture",
|
|
342
|
+
recommended_stage: "verify",
|
|
343
|
+
continue_with_stage: "verify",
|
|
344
|
+
evidence_collection_incomplete: true,
|
|
345
|
+
recovery_stage: "verify",
|
|
346
|
+
recovery_reason: blocker,
|
|
347
|
+
evidence_issue_code: visualDeltaBlockerCode(state, blocker),
|
|
348
|
+
visual_delta: Object.keys(visualDelta).length ? visualDelta : null,
|
|
349
|
+
suggested_repair: "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.",
|
|
326
350
|
blockers: [...blockers, blocker]
|
|
327
351
|
};
|
|
328
352
|
}
|
|
@@ -887,14 +911,24 @@ async function routeCheckpoint(request, state, result, agent, input) {
|
|
|
887
911
|
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
888
912
|
}, payload);
|
|
889
913
|
if (visualBlocker) {
|
|
914
|
+
const recoveryAssessment = visualDeltaEvidenceRecoveryAssessment({
|
|
915
|
+
...context.fullRiddleState || {},
|
|
916
|
+
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
917
|
+
}, payload, visualBlocker);
|
|
890
918
|
recordEvent(state, {
|
|
891
|
-
kind: "agent.proof_assessment.
|
|
919
|
+
kind: "agent.proof_assessment.evidence_recovery_required",
|
|
892
920
|
checkpoint,
|
|
893
921
|
stage: "verify",
|
|
894
922
|
summary: visualBlocker,
|
|
895
|
-
details: {
|
|
923
|
+
details: compactRecord({
|
|
924
|
+
evidence_collection_incomplete: true,
|
|
925
|
+
recovery_stage: "verify",
|
|
926
|
+
evidence_issue_code: recoveryAssessment.evidence_issue_code || null,
|
|
927
|
+
visual_delta: recoveryAssessment.visual_delta || null,
|
|
928
|
+
proof_assessment: recoveryAssessment
|
|
929
|
+
})
|
|
896
930
|
});
|
|
897
|
-
return { next: proofAssessmentContinuation(result,
|
|
931
|
+
return { next: proofAssessmentContinuation(result, recoveryAssessment) };
|
|
898
932
|
}
|
|
899
933
|
if (effectiveShipMode(request, input.config) !== "ship" && proofAssessmentRequestsShip(payload)) {
|
|
900
934
|
return {
|
|
@@ -96,14 +96,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
96
96
|
const record = isRecord(payload) ? payload : {};
|
|
97
97
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
98
98
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
99
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
99
100
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
100
101
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
101
102
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
103
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
102
104
|
return {
|
|
103
105
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
104
106
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
105
107
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
106
|
-
result_keys:
|
|
108
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
107
109
|
artifact_json: sortedKeys(artifactJson),
|
|
108
110
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
109
111
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/diagnostics.cjs
CHANGED
|
@@ -127,14 +127,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
127
127
|
const record = isRecord(payload) ? payload : {};
|
|
128
128
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
129
129
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
130
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
130
131
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
131
132
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
132
133
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
134
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
133
135
|
return {
|
|
134
136
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
135
137
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
136
138
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
137
|
-
result_keys:
|
|
139
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
138
140
|
artifact_json: sortedKeys(artifactJson),
|
|
139
141
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
140
142
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/diagnostics.js
CHANGED
package/dist/engine-harness.cjs
CHANGED
|
@@ -389,6 +389,21 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
389
389
|
if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
|
|
390
390
|
return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
|
|
391
391
|
}
|
|
392
|
+
function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
|
|
393
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
394
|
+
const status = String(visualDelta.status || "").trim();
|
|
395
|
+
const reason = `${String(visualDelta.reason || "")}
|
|
396
|
+
${blocker}`.toLowerCase();
|
|
397
|
+
if (status === "unmeasured") {
|
|
398
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
399
|
+
return "comparator_fetch_blocked";
|
|
400
|
+
}
|
|
401
|
+
return "visual_delta_unmeasured";
|
|
402
|
+
}
|
|
403
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
404
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
405
|
+
return "semantic_proof_failed";
|
|
406
|
+
}
|
|
392
407
|
function requiredBaselineLabelsForState(state = {}) {
|
|
393
408
|
const reference = normalizedReference(state);
|
|
394
409
|
const labels = [];
|
|
@@ -613,9 +628,15 @@ function mergeStateFromParams(statePath, params) {
|
|
|
613
628
|
const readyBlocker = assessment?.decision === "ready_to_ship" ? visualDeltaShipGateReason({ ...state, proof_assessment: assessment, proof_assessment_source: assessment.source }) : null;
|
|
614
629
|
if (readyBlocker) {
|
|
615
630
|
assessment.blocked_decision = assessment.decision;
|
|
616
|
-
assessment.decision = "
|
|
617
|
-
|
|
618
|
-
|
|
631
|
+
assessment.decision = "revise_capture";
|
|
632
|
+
assessment.recommended_stage = "verify";
|
|
633
|
+
assessment.continue_with_stage = "verify";
|
|
634
|
+
assessment.evidence_collection_incomplete = true;
|
|
635
|
+
assessment.recovery_stage = "verify";
|
|
636
|
+
assessment.recovery_reason = readyBlocker;
|
|
637
|
+
assessment.evidence_issue_code = visualDeltaEvidenceIssueCode(state, readyBlocker);
|
|
638
|
+
assessment.visual_delta = visualDeltaForState(state);
|
|
639
|
+
assessment.suggested_repair = "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.";
|
|
619
640
|
const blockers = Array.isArray(assessment.blockers) ? assessment.blockers : [];
|
|
620
641
|
assessment.blockers = [...blockers, readyBlocker];
|
|
621
642
|
}
|
|
@@ -634,7 +655,7 @@ function mergeStateFromParams(statePath, params) {
|
|
|
634
655
|
}
|
|
635
656
|
appendProofSummaryLine(state, `Supervising proof assessment: ${state.proof_assessment.decision || "unknown"}`);
|
|
636
657
|
if (readyBlocker) {
|
|
637
|
-
appendProofSummaryLine(state, `Ready-to-ship assessment
|
|
658
|
+
appendProofSummaryLine(state, `Ready-to-ship assessment routed to evidence recovery: ${readyBlocker}`);
|
|
638
659
|
}
|
|
639
660
|
if (state.proof_assessment_summary) {
|
|
640
661
|
appendProofSummaryLine(state, `Assessment summary: ${state.proof_assessment_summary}`);
|
|
@@ -908,7 +929,7 @@ var init_proof_run_core = __esm({
|
|
|
908
929
|
description: "JSON assessment with decision, summary, recommended_stage, continue_with_stage, escalation_target, and reasons."
|
|
909
930
|
}],
|
|
910
931
|
response_schema: {
|
|
911
|
-
decision: ["ready_to_ship", "needs_richer_proof"],
|
|
932
|
+
decision: ["ready_to_ship", "needs_richer_proof", "revise_capture"],
|
|
912
933
|
summary: "string",
|
|
913
934
|
recommended_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
914
935
|
continue_with_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
@@ -1713,10 +1734,10 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
|
|
|
1713
1734
|
const primaryShipGateNextAction = (shipGate) => {
|
|
1714
1735
|
const reasons = shipGate.reasons || [];
|
|
1715
1736
|
if (reasons.some((reason) => reason.includes("proof_assessment"))) {
|
|
1716
|
-
return "resume with riddle_proof_review using decision=ready_to_ship only after
|
|
1737
|
+
return "resume with riddle_proof_review using decision=ready_to_ship only after screenshots, semantic evidence, and required comparison metrics prove the request; otherwise choose needs_implementation, revise_capture, or needs_richer_proof for the specific missing stage";
|
|
1717
1738
|
}
|
|
1718
1739
|
if (reasons.some((reason) => reason.includes("visual_delta"))) {
|
|
1719
|
-
return "
|
|
1740
|
+
return "keep the run in verify/evidence recovery until a measured before/after visual_delta exists; choose revise_capture rather than ready_to_ship or generic needs_richer_proof for this visual proof";
|
|
1720
1741
|
}
|
|
1721
1742
|
if (reasons.some((reason) => reason.includes("after_cdn") || reason.includes("verify_status"))) {
|
|
1722
1743
|
return "rerun verify with stronger proof framing so after evidence is captured before shipping";
|
|
@@ -3692,14 +3713,36 @@ function proofAssessmentVisualBlocker(state, payload) {
|
|
|
3692
3713
|
proof_assessment_source: source
|
|
3693
3714
|
});
|
|
3694
3715
|
}
|
|
3695
|
-
function
|
|
3716
|
+
function visualDeltaBlockerCode(state, blocker) {
|
|
3717
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
3718
|
+
const status = nonEmptyString(visualDelta.status);
|
|
3719
|
+
const reason = `${nonEmptyString(visualDelta.reason) || ""}
|
|
3720
|
+
${blocker}`.toLowerCase();
|
|
3721
|
+
if (status === "unmeasured") {
|
|
3722
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
3723
|
+
return "comparator_fetch_blocked";
|
|
3724
|
+
}
|
|
3725
|
+
return "visual_delta_unmeasured";
|
|
3726
|
+
}
|
|
3727
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
3728
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
3729
|
+
return "semantic_proof_failed";
|
|
3730
|
+
}
|
|
3731
|
+
function visualDeltaEvidenceRecoveryAssessment(state, payload, blocker) {
|
|
3732
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
3696
3733
|
const blockers = Array.isArray(payload.blockers) ? payload.blockers.filter((item) => typeof item === "string") : [];
|
|
3697
3734
|
return {
|
|
3698
3735
|
...payload,
|
|
3699
3736
|
blocked_decision: payload.decision || "ready_to_ship",
|
|
3700
|
-
decision: "
|
|
3701
|
-
recommended_stage:
|
|
3702
|
-
continue_with_stage:
|
|
3737
|
+
decision: "revise_capture",
|
|
3738
|
+
recommended_stage: "verify",
|
|
3739
|
+
continue_with_stage: "verify",
|
|
3740
|
+
evidence_collection_incomplete: true,
|
|
3741
|
+
recovery_stage: "verify",
|
|
3742
|
+
recovery_reason: blocker,
|
|
3743
|
+
evidence_issue_code: visualDeltaBlockerCode(state, blocker),
|
|
3744
|
+
visual_delta: Object.keys(visualDelta).length ? visualDelta : null,
|
|
3745
|
+
suggested_repair: "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.",
|
|
3703
3746
|
blockers: [...blockers, blocker]
|
|
3704
3747
|
};
|
|
3705
3748
|
}
|
|
@@ -4264,14 +4307,24 @@ async function routeCheckpoint(request, state, result, agent, input) {
|
|
|
4264
4307
|
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
4265
4308
|
}, payload);
|
|
4266
4309
|
if (visualBlocker) {
|
|
4310
|
+
const recoveryAssessment = visualDeltaEvidenceRecoveryAssessment({
|
|
4311
|
+
...context.fullRiddleState || {},
|
|
4312
|
+
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
4313
|
+
}, payload, visualBlocker);
|
|
4267
4314
|
recordEvent(state, {
|
|
4268
|
-
kind: "agent.proof_assessment.
|
|
4315
|
+
kind: "agent.proof_assessment.evidence_recovery_required",
|
|
4269
4316
|
checkpoint,
|
|
4270
4317
|
stage: "verify",
|
|
4271
4318
|
summary: visualBlocker,
|
|
4272
|
-
details: {
|
|
4319
|
+
details: compactRecord({
|
|
4320
|
+
evidence_collection_incomplete: true,
|
|
4321
|
+
recovery_stage: "verify",
|
|
4322
|
+
evidence_issue_code: recoveryAssessment.evidence_issue_code || null,
|
|
4323
|
+
visual_delta: recoveryAssessment.visual_delta || null,
|
|
4324
|
+
proof_assessment: recoveryAssessment
|
|
4325
|
+
})
|
|
4273
4326
|
});
|
|
4274
|
-
return { next: proofAssessmentContinuation(result,
|
|
4327
|
+
return { next: proofAssessmentContinuation(result, recoveryAssessment) };
|
|
4275
4328
|
}
|
|
4276
4329
|
if (effectiveShipMode(request, input.config) !== "ship" && proofAssessmentRequestsShip(payload)) {
|
|
4277
4330
|
return {
|
package/dist/engine-harness.js
CHANGED
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
createDisabledRiddleProofAgentAdapter,
|
|
3
3
|
readRiddleProofRunStatus,
|
|
4
4
|
runRiddleProofEngineHarness
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MJD37CLH.js";
|
|
6
6
|
import "./chunk-RI25RGQP.js";
|
|
7
7
|
import "./chunk-7S7O3NKF.js";
|
|
8
8
|
import "./chunk-W7VTDN4T.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-4ASMX4R6.js";
|
|
10
10
|
export {
|
|
11
11
|
createDisabledRiddleProofAgentAdapter,
|
|
12
12
|
readRiddleProofRunStatus,
|
package/dist/index.cjs
CHANGED
|
@@ -389,6 +389,21 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
389
389
|
if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
|
|
390
390
|
return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
|
|
391
391
|
}
|
|
392
|
+
function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
|
|
393
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
394
|
+
const status = String(visualDelta.status || "").trim();
|
|
395
|
+
const reason = `${String(visualDelta.reason || "")}
|
|
396
|
+
${blocker}`.toLowerCase();
|
|
397
|
+
if (status === "unmeasured") {
|
|
398
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
399
|
+
return "comparator_fetch_blocked";
|
|
400
|
+
}
|
|
401
|
+
return "visual_delta_unmeasured";
|
|
402
|
+
}
|
|
403
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
404
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
405
|
+
return "semantic_proof_failed";
|
|
406
|
+
}
|
|
392
407
|
function requiredBaselineLabelsForState(state = {}) {
|
|
393
408
|
const reference = normalizedReference(state);
|
|
394
409
|
const labels = [];
|
|
@@ -613,9 +628,15 @@ function mergeStateFromParams(statePath, params) {
|
|
|
613
628
|
const readyBlocker = assessment?.decision === "ready_to_ship" ? visualDeltaShipGateReason({ ...state, proof_assessment: assessment, proof_assessment_source: assessment.source }) : null;
|
|
614
629
|
if (readyBlocker) {
|
|
615
630
|
assessment.blocked_decision = assessment.decision;
|
|
616
|
-
assessment.decision = "
|
|
617
|
-
|
|
618
|
-
|
|
631
|
+
assessment.decision = "revise_capture";
|
|
632
|
+
assessment.recommended_stage = "verify";
|
|
633
|
+
assessment.continue_with_stage = "verify";
|
|
634
|
+
assessment.evidence_collection_incomplete = true;
|
|
635
|
+
assessment.recovery_stage = "verify";
|
|
636
|
+
assessment.recovery_reason = readyBlocker;
|
|
637
|
+
assessment.evidence_issue_code = visualDeltaEvidenceIssueCode(state, readyBlocker);
|
|
638
|
+
assessment.visual_delta = visualDeltaForState(state);
|
|
639
|
+
assessment.suggested_repair = "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.";
|
|
619
640
|
const blockers = Array.isArray(assessment.blockers) ? assessment.blockers : [];
|
|
620
641
|
assessment.blockers = [...blockers, readyBlocker];
|
|
621
642
|
}
|
|
@@ -634,7 +655,7 @@ function mergeStateFromParams(statePath, params) {
|
|
|
634
655
|
}
|
|
635
656
|
appendProofSummaryLine(state, `Supervising proof assessment: ${state.proof_assessment.decision || "unknown"}`);
|
|
636
657
|
if (readyBlocker) {
|
|
637
|
-
appendProofSummaryLine(state, `Ready-to-ship assessment
|
|
658
|
+
appendProofSummaryLine(state, `Ready-to-ship assessment routed to evidence recovery: ${readyBlocker}`);
|
|
638
659
|
}
|
|
639
660
|
if (state.proof_assessment_summary) {
|
|
640
661
|
appendProofSummaryLine(state, `Assessment summary: ${state.proof_assessment_summary}`);
|
|
@@ -908,7 +929,7 @@ var init_proof_run_core = __esm({
|
|
|
908
929
|
description: "JSON assessment with decision, summary, recommended_stage, continue_with_stage, escalation_target, and reasons."
|
|
909
930
|
}],
|
|
910
931
|
response_schema: {
|
|
911
|
-
decision: ["ready_to_ship", "needs_richer_proof"],
|
|
932
|
+
decision: ["ready_to_ship", "needs_richer_proof", "revise_capture"],
|
|
912
933
|
summary: "string",
|
|
913
934
|
recommended_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
914
935
|
continue_with_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
@@ -1713,10 +1734,10 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
|
|
|
1713
1734
|
const primaryShipGateNextAction = (shipGate) => {
|
|
1714
1735
|
const reasons = shipGate.reasons || [];
|
|
1715
1736
|
if (reasons.some((reason) => reason.includes("proof_assessment"))) {
|
|
1716
|
-
return "resume with riddle_proof_review using decision=ready_to_ship only after
|
|
1737
|
+
return "resume with riddle_proof_review using decision=ready_to_ship only after screenshots, semantic evidence, and required comparison metrics prove the request; otherwise choose needs_implementation, revise_capture, or needs_richer_proof for the specific missing stage";
|
|
1717
1738
|
}
|
|
1718
1739
|
if (reasons.some((reason) => reason.includes("visual_delta"))) {
|
|
1719
|
-
return "
|
|
1740
|
+
return "keep the run in verify/evidence recovery until a measured before/after visual_delta exists; choose revise_capture rather than ready_to_ship or generic needs_richer_proof for this visual proof";
|
|
1720
1741
|
}
|
|
1721
1742
|
if (reasons.some((reason) => reason.includes("after_cdn") || reason.includes("verify_status"))) {
|
|
1722
1743
|
return "rerun verify with stronger proof framing so after evidence is captured before shipping";
|
|
@@ -4270,14 +4291,36 @@ function proofAssessmentVisualBlocker(state, payload) {
|
|
|
4270
4291
|
proof_assessment_source: source
|
|
4271
4292
|
});
|
|
4272
4293
|
}
|
|
4273
|
-
function
|
|
4294
|
+
function visualDeltaBlockerCode(state, blocker) {
|
|
4295
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
4296
|
+
const status = nonEmptyString(visualDelta.status);
|
|
4297
|
+
const reason = `${nonEmptyString(visualDelta.reason) || ""}
|
|
4298
|
+
${blocker}`.toLowerCase();
|
|
4299
|
+
if (status === "unmeasured") {
|
|
4300
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
4301
|
+
return "comparator_fetch_blocked";
|
|
4302
|
+
}
|
|
4303
|
+
return "visual_delta_unmeasured";
|
|
4304
|
+
}
|
|
4305
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
4306
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
4307
|
+
return "semantic_proof_failed";
|
|
4308
|
+
}
|
|
4309
|
+
function visualDeltaEvidenceRecoveryAssessment(state, payload, blocker) {
|
|
4310
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
4274
4311
|
const blockers = Array.isArray(payload.blockers) ? payload.blockers.filter((item) => typeof item === "string") : [];
|
|
4275
4312
|
return {
|
|
4276
4313
|
...payload,
|
|
4277
4314
|
blocked_decision: payload.decision || "ready_to_ship",
|
|
4278
|
-
decision: "
|
|
4279
|
-
recommended_stage:
|
|
4280
|
-
continue_with_stage:
|
|
4315
|
+
decision: "revise_capture",
|
|
4316
|
+
recommended_stage: "verify",
|
|
4317
|
+
continue_with_stage: "verify",
|
|
4318
|
+
evidence_collection_incomplete: true,
|
|
4319
|
+
recovery_stage: "verify",
|
|
4320
|
+
recovery_reason: blocker,
|
|
4321
|
+
evidence_issue_code: visualDeltaBlockerCode(state, blocker),
|
|
4322
|
+
visual_delta: Object.keys(visualDelta).length ? visualDelta : null,
|
|
4323
|
+
suggested_repair: "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.",
|
|
4281
4324
|
blockers: [...blockers, blocker]
|
|
4282
4325
|
};
|
|
4283
4326
|
}
|
|
@@ -4842,14 +4885,24 @@ async function routeCheckpoint(request, state, result, agent, input) {
|
|
|
4842
4885
|
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
4843
4886
|
}, payload);
|
|
4844
4887
|
if (visualBlocker) {
|
|
4888
|
+
const recoveryAssessment = visualDeltaEvidenceRecoveryAssessment({
|
|
4889
|
+
...context.fullRiddleState || {},
|
|
4890
|
+
verification_mode: context.fullRiddleState?.verification_mode || request.verification_mode
|
|
4891
|
+
}, payload, visualBlocker);
|
|
4845
4892
|
recordEvent(state, {
|
|
4846
|
-
kind: "agent.proof_assessment.
|
|
4893
|
+
kind: "agent.proof_assessment.evidence_recovery_required",
|
|
4847
4894
|
checkpoint,
|
|
4848
4895
|
stage: "verify",
|
|
4849
4896
|
summary: visualBlocker,
|
|
4850
|
-
details: {
|
|
4897
|
+
details: compactRecord({
|
|
4898
|
+
evidence_collection_incomplete: true,
|
|
4899
|
+
recovery_stage: "verify",
|
|
4900
|
+
evidence_issue_code: recoveryAssessment.evidence_issue_code || null,
|
|
4901
|
+
visual_delta: recoveryAssessment.visual_delta || null,
|
|
4902
|
+
proof_assessment: recoveryAssessment
|
|
4903
|
+
})
|
|
4851
4904
|
});
|
|
4852
|
-
return { next: proofAssessmentContinuation(result,
|
|
4905
|
+
return { next: proofAssessmentContinuation(result, recoveryAssessment) };
|
|
4853
4906
|
}
|
|
4854
4907
|
if (effectiveShipMode(request, input.config) !== "ship" && proofAssessmentRequestsShip(payload)) {
|
|
4855
4908
|
return {
|
|
@@ -5172,14 +5225,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
5172
5225
|
const record = isRecord(payload) ? payload : {};
|
|
5173
5226
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
5174
5227
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
5228
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
5175
5229
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
5176
5230
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
5177
5231
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
5232
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
5178
5233
|
return {
|
|
5179
5234
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
5180
5235
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
5181
5236
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
5182
|
-
result_keys:
|
|
5237
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
5183
5238
|
artifact_json: sortedKeys(artifactJson),
|
|
5184
5239
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
5185
5240
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/index.js
CHANGED
|
@@ -20,12 +20,12 @@ import {
|
|
|
20
20
|
createCaptureDiagnostic,
|
|
21
21
|
redactForProofDiagnostics,
|
|
22
22
|
summarizeCaptureArtifacts
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-XNRMMQAY.js";
|
|
24
24
|
import {
|
|
25
25
|
createDisabledRiddleProofAgentAdapter,
|
|
26
26
|
readRiddleProofRunStatus,
|
|
27
27
|
runRiddleProofEngineHarness
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-MJD37CLH.js";
|
|
29
29
|
import {
|
|
30
30
|
RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
|
|
31
31
|
RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
extractPlayabilityEvidence,
|
|
68
68
|
isRiddleProofPlayabilityMode
|
|
69
69
|
} from "./chunk-NSWT3VSV.js";
|
|
70
|
-
import "./chunk-
|
|
70
|
+
import "./chunk-4ASMX4R6.js";
|
|
71
71
|
export {
|
|
72
72
|
DEFAULT_DIAGNOSTIC_ARRAY_LIMIT,
|
|
73
73
|
DEFAULT_DIAGNOSTIC_HISTORY_LIMIT,
|
package/dist/proof-run-core.cjs
CHANGED
|
@@ -440,6 +440,21 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
440
440
|
if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
|
|
441
441
|
return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
|
|
442
442
|
}
|
|
443
|
+
function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
|
|
444
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
445
|
+
const status = String(visualDelta.status || "").trim();
|
|
446
|
+
const reason = `${String(visualDelta.reason || "")}
|
|
447
|
+
${blocker}`.toLowerCase();
|
|
448
|
+
if (status === "unmeasured") {
|
|
449
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
450
|
+
return "comparator_fetch_blocked";
|
|
451
|
+
}
|
|
452
|
+
return "visual_delta_unmeasured";
|
|
453
|
+
}
|
|
454
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
455
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
456
|
+
return "semantic_proof_failed";
|
|
457
|
+
}
|
|
443
458
|
function requiredBaselineLabelsForState(state = {}) {
|
|
444
459
|
const reference = normalizedReference(state);
|
|
445
460
|
const labels = [];
|
|
@@ -629,7 +644,7 @@ var CHECKPOINT_CONTRACT_SPECS = {
|
|
|
629
644
|
description: "JSON assessment with decision, summary, recommended_stage, continue_with_stage, escalation_target, and reasons."
|
|
630
645
|
}],
|
|
631
646
|
response_schema: {
|
|
632
|
-
decision: ["ready_to_ship", "needs_richer_proof"],
|
|
647
|
+
decision: ["ready_to_ship", "needs_richer_proof", "revise_capture"],
|
|
633
648
|
summary: "string",
|
|
634
649
|
recommended_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
635
650
|
continue_with_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
@@ -832,9 +847,15 @@ function mergeStateFromParams(statePath, params) {
|
|
|
832
847
|
const readyBlocker = assessment?.decision === "ready_to_ship" ? visualDeltaShipGateReason({ ...state, proof_assessment: assessment, proof_assessment_source: assessment.source }) : null;
|
|
833
848
|
if (readyBlocker) {
|
|
834
849
|
assessment.blocked_decision = assessment.decision;
|
|
835
|
-
assessment.decision = "
|
|
836
|
-
|
|
837
|
-
|
|
850
|
+
assessment.decision = "revise_capture";
|
|
851
|
+
assessment.recommended_stage = "verify";
|
|
852
|
+
assessment.continue_with_stage = "verify";
|
|
853
|
+
assessment.evidence_collection_incomplete = true;
|
|
854
|
+
assessment.recovery_stage = "verify";
|
|
855
|
+
assessment.recovery_reason = readyBlocker;
|
|
856
|
+
assessment.evidence_issue_code = visualDeltaEvidenceIssueCode(state, readyBlocker);
|
|
857
|
+
assessment.visual_delta = visualDeltaForState(state);
|
|
858
|
+
assessment.suggested_repair = "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.";
|
|
838
859
|
const blockers = Array.isArray(assessment.blockers) ? assessment.blockers : [];
|
|
839
860
|
assessment.blockers = [...blockers, readyBlocker];
|
|
840
861
|
}
|
|
@@ -853,7 +874,7 @@ function mergeStateFromParams(statePath, params) {
|
|
|
853
874
|
}
|
|
854
875
|
appendProofSummaryLine(state, `Supervising proof assessment: ${state.proof_assessment.decision || "unknown"}`);
|
|
855
876
|
if (readyBlocker) {
|
|
856
|
-
appendProofSummaryLine(state, `Ready-to-ship assessment
|
|
877
|
+
appendProofSummaryLine(state, `Ready-to-ship assessment routed to evidence recovery: ${readyBlocker}`);
|
|
857
878
|
}
|
|
858
879
|
if (state.proof_assessment_summary) {
|
|
859
880
|
appendProofSummaryLine(state, `Assessment summary: ${state.proof_assessment_summary}`);
|
package/dist/proof-run-core.js
CHANGED
|
@@ -422,6 +422,21 @@ function visualDeltaShipGateReason(state = {}) {
|
|
|
422
422
|
if (reason) return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof: ${reason}`;
|
|
423
423
|
return `visual_delta.status=${status} blocks ready_to_ship for visual/UI proof`;
|
|
424
424
|
}
|
|
425
|
+
function visualDeltaEvidenceIssueCode(state = {}, blocker = "") {
|
|
426
|
+
const visualDelta = visualDeltaForState(state || {});
|
|
427
|
+
const status = String(visualDelta.status || "").trim();
|
|
428
|
+
const reason = `${String(visualDelta.reason || "")}
|
|
429
|
+
${blocker}`.toLowerCase();
|
|
430
|
+
if (status === "unmeasured") {
|
|
431
|
+
if (reason.includes("fetch") || reason.includes("allowlist") || reason.includes("registered domain") || reason.includes("high risk") || reason.includes("comparator")) {
|
|
432
|
+
return "comparator_fetch_blocked";
|
|
433
|
+
}
|
|
434
|
+
return "visual_delta_unmeasured";
|
|
435
|
+
}
|
|
436
|
+
if (status === "measured" && visualDelta.passed === false) return "semantic_proof_failed";
|
|
437
|
+
if (visualDeltaRequiredForState(state || {})) return "visual_delta_unmeasured";
|
|
438
|
+
return "semantic_proof_failed";
|
|
439
|
+
}
|
|
425
440
|
function requiredBaselineLabelsForState(state = {}) {
|
|
426
441
|
const reference = normalizedReference(state);
|
|
427
442
|
const labels = [];
|
|
@@ -611,7 +626,7 @@ var CHECKPOINT_CONTRACT_SPECS = {
|
|
|
611
626
|
description: "JSON assessment with decision, summary, recommended_stage, continue_with_stage, escalation_target, and reasons."
|
|
612
627
|
}],
|
|
613
628
|
response_schema: {
|
|
614
|
-
decision: ["ready_to_ship", "needs_richer_proof"],
|
|
629
|
+
decision: ["ready_to_ship", "needs_richer_proof", "revise_capture"],
|
|
615
630
|
summary: "string",
|
|
616
631
|
recommended_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
617
632
|
continue_with_stage: ["ship", "author", "implement", "recon", "verify"],
|
|
@@ -814,9 +829,15 @@ function mergeStateFromParams(statePath, params) {
|
|
|
814
829
|
const readyBlocker = assessment?.decision === "ready_to_ship" ? visualDeltaShipGateReason({ ...state, proof_assessment: assessment, proof_assessment_source: assessment.source }) : null;
|
|
815
830
|
if (readyBlocker) {
|
|
816
831
|
assessment.blocked_decision = assessment.decision;
|
|
817
|
-
assessment.decision = "
|
|
818
|
-
|
|
819
|
-
|
|
832
|
+
assessment.decision = "revise_capture";
|
|
833
|
+
assessment.recommended_stage = "verify";
|
|
834
|
+
assessment.continue_with_stage = "verify";
|
|
835
|
+
assessment.evidence_collection_incomplete = true;
|
|
836
|
+
assessment.recovery_stage = "verify";
|
|
837
|
+
assessment.recovery_reason = readyBlocker;
|
|
838
|
+
assessment.evidence_issue_code = visualDeltaEvidenceIssueCode(state, readyBlocker);
|
|
839
|
+
assessment.visual_delta = visualDeltaForState(state);
|
|
840
|
+
assessment.suggested_repair = "Keep the same Riddle Proof run in evidence/comparison recovery: repair or retry the visual comparator/fetch path, wait for artifact readiness if applicable, or produce a measured visual_delta artifact before proof review can mark ready_to_ship.";
|
|
820
841
|
const blockers = Array.isArray(assessment.blockers) ? assessment.blockers : [];
|
|
821
842
|
assessment.blockers = [...blockers, readyBlocker];
|
|
822
843
|
}
|
|
@@ -835,7 +856,7 @@ function mergeStateFromParams(statePath, params) {
|
|
|
835
856
|
}
|
|
836
857
|
appendProofSummaryLine(state, `Supervising proof assessment: ${state.proof_assessment.decision || "unknown"}`);
|
|
837
858
|
if (readyBlocker) {
|
|
838
|
-
appendProofSummaryLine(state, `Ready-to-ship assessment
|
|
859
|
+
appendProofSummaryLine(state, `Ready-to-ship assessment routed to evidence recovery: ${readyBlocker}`);
|
|
839
860
|
}
|
|
840
861
|
if (state.proof_assessment_summary) {
|
|
841
862
|
appendProofSummaryLine(state, `Assessment summary: ${state.proof_assessment_summary}`);
|
|
@@ -1711,10 +1732,10 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
|
|
|
1711
1732
|
const primaryShipGateNextAction = (shipGate) => {
|
|
1712
1733
|
const reasons = shipGate.reasons || [];
|
|
1713
1734
|
if (reasons.some((reason) => reason.includes("proof_assessment"))) {
|
|
1714
|
-
return "resume with riddle_proof_review using decision=ready_to_ship only after
|
|
1735
|
+
return "resume with riddle_proof_review using decision=ready_to_ship only after screenshots, semantic evidence, and required comparison metrics prove the request; otherwise choose needs_implementation, revise_capture, or needs_richer_proof for the specific missing stage";
|
|
1715
1736
|
}
|
|
1716
1737
|
if (reasons.some((reason) => reason.includes("visual_delta"))) {
|
|
1717
|
-
return "
|
|
1738
|
+
return "keep the run in verify/evidence recovery until a measured before/after visual_delta exists; choose revise_capture rather than ready_to_ship or generic needs_richer_proof for this visual proof";
|
|
1718
1739
|
}
|
|
1719
1740
|
if (reasons.some((reason) => reason.includes("after_cdn") || reason.includes("verify_status"))) {
|
|
1720
1741
|
return "rerun verify with stronger proof framing so after evidence is captured before shipping";
|
package/dist/proof-run-engine.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
validateShipGate,
|
|
15
15
|
workflowFile,
|
|
16
16
|
writeState
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4ASMX4R6.js";
|
|
18
18
|
|
|
19
19
|
// src/proof-run-engine.ts
|
|
20
20
|
import { execFileSync } from "child_process";
|
|
@@ -768,10 +768,10 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
|
|
|
768
768
|
const primaryShipGateNextAction = (shipGate) => {
|
|
769
769
|
const reasons = shipGate.reasons || [];
|
|
770
770
|
if (reasons.some((reason) => reason.includes("proof_assessment"))) {
|
|
771
|
-
return "resume with riddle_proof_review using decision=ready_to_ship only after
|
|
771
|
+
return "resume with riddle_proof_review using decision=ready_to_ship only after screenshots, semantic evidence, and required comparison metrics prove the request; otherwise choose needs_implementation, revise_capture, or needs_richer_proof for the specific missing stage";
|
|
772
772
|
}
|
|
773
773
|
if (reasons.some((reason) => reason.includes("visual_delta"))) {
|
|
774
|
-
return "
|
|
774
|
+
return "keep the run in verify/evidence recovery until a measured before/after visual_delta exists; choose revise_capture rather than ready_to_ship or generic needs_richer_proof for this visual proof";
|
|
775
775
|
}
|
|
776
776
|
if (reasons.some((reason) => reason.includes("after_cdn") || reason.includes("verify_status"))) {
|
|
777
777
|
return "rerun verify with stronger proof framing so after evidence is captured before shipping";
|
package/package.json
CHANGED
package/runtime/lib/author.py
CHANGED
|
@@ -154,6 +154,7 @@ def author_request_payload(state, reference, baselines, current_plan, hypothesis
|
|
|
154
154
|
'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 set window.__riddleProofEvidence.playability or playability_evidence with version riddle-proof.playability.v1.',
|
|
155
155
|
'For data/audio/log/metric/custom proof, screenshots are optional; set window.__riddleProofEvidence inside page.evaluate to a JSON-serializable object with the measured observations the verifier should judge.',
|
|
156
156
|
'Do not assign globalThis.__riddleProofEvidence, window.__riddleProofEvidence, or self.__riddleProofEvidence outside page.evaluate; the Riddle worker context may not expose those globals safely.',
|
|
157
|
+
'When page.evaluate needs data from the outer Playwright script, pass exactly one serializable argument object; do not pass multiple positional arguments.',
|
|
157
158
|
'Do not begin capture_script with page.goto unless an in-app navigation is genuinely required after the preview opens the target route.',
|
|
158
159
|
'Only escalate to the human after the supervising agent concludes the workflow is genuinely stuck or not converging.',
|
|
159
160
|
],
|
|
@@ -136,6 +136,28 @@ async function run(tool, args) {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
if (tool === "riddle_visual_diff") {
|
|
140
|
+
const {
|
|
141
|
+
async,
|
|
142
|
+
options,
|
|
143
|
+
stealth,
|
|
144
|
+
timeout_sec,
|
|
145
|
+
...visualDiffOptions
|
|
146
|
+
} = args;
|
|
147
|
+
const script = `return await visualDiff(${JSON.stringify(visualDiffOptions)});`;
|
|
148
|
+
return core.runWithDefaults(config, {
|
|
149
|
+
url: args.url_before,
|
|
150
|
+
script,
|
|
151
|
+
options: { ...(options ?? {}), returnResult: true },
|
|
152
|
+
stealth,
|
|
153
|
+
timeout_sec: timeout_sec ?? 60,
|
|
154
|
+
async,
|
|
155
|
+
}, {
|
|
156
|
+
include: ["result", "console", "visual_diff"],
|
|
157
|
+
returnAsync: !!async,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
if (tool === "riddle_run") {
|
|
140
162
|
return core.runWithDefaults(config, args.payload ?? args, {
|
|
141
163
|
include: ["screenshot", "console", "result"],
|
package/runtime/lib/util.py
CHANGED
|
@@ -14,6 +14,7 @@ RIDDLE_DIRECT_TOOLS = {
|
|
|
14
14
|
'riddle_build_preview',
|
|
15
15
|
'riddle_build_preview_status',
|
|
16
16
|
'riddle_script',
|
|
17
|
+
'riddle_visual_diff',
|
|
17
18
|
'riddle_run',
|
|
18
19
|
}
|
|
19
20
|
CAPTURE_ARTIFACT_JSON_LIMIT = 256 * 1024
|
|
@@ -767,7 +768,7 @@ def enrich_capture_payload(payload):
|
|
|
767
768
|
artifact_json = dict(enriched.get('_artifact_json') or {})
|
|
768
769
|
artifact_errors = dict(enriched.get('_artifact_errors') or {})
|
|
769
770
|
|
|
770
|
-
for name in ('console.json', 'proof.json'):
|
|
771
|
+
for name in ('console.json', 'proof.json', 'visual-diff.json'):
|
|
771
772
|
if name in artifact_json or name in artifact_errors:
|
|
772
773
|
continue
|
|
773
774
|
item = capture_output_item(enriched, name)
|
|
@@ -798,6 +799,10 @@ def enrich_capture_payload(payload):
|
|
|
798
799
|
enriched['result'] = result
|
|
799
800
|
break
|
|
800
801
|
|
|
802
|
+
visual_diff_json = artifact_json.get('visual-diff.json')
|
|
803
|
+
if isinstance(visual_diff_json, dict) and not enriched.get('visual_diff'):
|
|
804
|
+
enriched['visual_diff'] = visual_diff_json
|
|
805
|
+
|
|
801
806
|
return enriched
|
|
802
807
|
|
|
803
808
|
|
package/runtime/lib/verify.py
CHANGED
|
@@ -55,13 +55,24 @@ MIN_VISUAL_CHANGED_PIXELS = 5000
|
|
|
55
55
|
VISUAL_DELTA_PERCENT_KEYS = {
|
|
56
56
|
'change_pct', 'change_percent', 'changed_percent', 'percent_changed',
|
|
57
57
|
'diff_percent', 'visual_delta_percent', 'pixel_change_percent',
|
|
58
|
+
'changepercent', 'changedpercent', 'percentchanged', 'diffpercent',
|
|
59
|
+
'visualdeltapercent', 'pixelchangepercent', 'difference_percent',
|
|
60
|
+
'differencepercent', 'diff_percentage', 'diffpercentage',
|
|
61
|
+
'mismatch_percent', 'mismatchpercent', 'percentage', 'percent',
|
|
58
62
|
}
|
|
59
63
|
VISUAL_DELTA_RATIO_KEYS = {
|
|
60
64
|
'change_ratio', 'changed_ratio', 'diff_ratio', 'visual_delta_ratio',
|
|
65
|
+
'changeratio', 'changedratio', 'diffratio', 'visualdeltaratio',
|
|
66
|
+
'difference_ratio', 'differenceratio', 'mismatch_ratio', 'mismatchratio',
|
|
61
67
|
}
|
|
62
68
|
VISUAL_CHANGED_PIXEL_KEYS = {
|
|
63
69
|
'changed_pixels', 'changed_pixel_count', 'changedpixels',
|
|
64
70
|
'diff_pixels', 'pixel_delta', 'visual_delta_pixels',
|
|
71
|
+
'diffpixels', 'pixeldelta', 'visualdeltapixels', 'different_pixels',
|
|
72
|
+
'differentpixels', 'mismatch_pixels', 'mismatchpixels', 'pixels_changed',
|
|
73
|
+
'pixelschanged', 'diff_pixel_count', 'diffpixelcount',
|
|
74
|
+
'pixel_diff_count', 'pixeldiffcount', 'different_pixel_count',
|
|
75
|
+
'differentpixelcount', 'num_different_pixels', 'numdifferentpixels',
|
|
65
76
|
}
|
|
66
77
|
VISUAL_TOTAL_PIXEL_KEYS = {
|
|
67
78
|
'total_pixels', 'total_pixel_count', 'pixel_count', 'totalpixels',
|
|
@@ -800,6 +811,7 @@ def extract_visual_delta(payload):
|
|
|
800
811
|
payload = enrich_capture_payload(payload)
|
|
801
812
|
result = payload.get('result') if isinstance(payload, dict) else {}
|
|
802
813
|
proof_json = payload.get('_proof_json') if isinstance(payload, dict) else {}
|
|
814
|
+
artifact_json = payload.get('_artifact_json') if isinstance(payload, dict) else {}
|
|
803
815
|
proof_evidence = extract_proof_evidence(payload)
|
|
804
816
|
screenshot_url = extract_screenshot_url(payload)
|
|
805
817
|
artifact_summary = summarize_capture_artifacts(payload)
|
|
@@ -807,6 +819,11 @@ def extract_visual_delta(payload):
|
|
|
807
819
|
payload if isinstance(payload, dict) else {},
|
|
808
820
|
result if isinstance(result, dict) else {},
|
|
809
821
|
proof_json if isinstance(proof_json, dict) else {},
|
|
822
|
+
payload.get('visual_diff') if isinstance(payload, dict) and isinstance(payload.get('visual_diff'), dict) else {},
|
|
823
|
+
payload.get('visualDiff') if isinstance(payload, dict) and isinstance(payload.get('visualDiff'), dict) else {},
|
|
824
|
+
result.get('visual_diff') if isinstance(result, dict) and isinstance(result.get('visual_diff'), dict) else {},
|
|
825
|
+
result.get('visualDiff') if isinstance(result, dict) and isinstance(result.get('visualDiff'), dict) else {},
|
|
826
|
+
artifact_json.get('visual-diff.json') if isinstance(artifact_json, dict) and isinstance(artifact_json.get('visual-diff.json'), dict) else {},
|
|
810
827
|
proof_evidence,
|
|
811
828
|
]
|
|
812
829
|
|
|
@@ -899,6 +916,114 @@ def extract_visual_delta(payload):
|
|
|
899
916
|
}
|
|
900
917
|
|
|
901
918
|
|
|
919
|
+
def visual_delta_baseline_candidate(state, results):
|
|
920
|
+
reference = str(state.get('requested_reference') or state.get('reference') or 'both').strip().lower()
|
|
921
|
+
baseline = (results.get('baseline') or {}) if isinstance(results, dict) else {}
|
|
922
|
+
candidates = []
|
|
923
|
+
if reference in ('before', 'both', ''):
|
|
924
|
+
candidates.append(('before', (baseline.get('before') or {}).get('url') if isinstance(baseline.get('before'), dict) else ''))
|
|
925
|
+
if reference in ('prod', 'production', 'both'):
|
|
926
|
+
candidates.append(('prod', (baseline.get('prod') or {}).get('url') if isinstance(baseline.get('prod'), dict) else ''))
|
|
927
|
+
candidates.extend([
|
|
928
|
+
('before', state.get('before_cdn') or ''),
|
|
929
|
+
('prod', state.get('prod_cdn') or ''),
|
|
930
|
+
])
|
|
931
|
+
for label, url in candidates:
|
|
932
|
+
text = str(url or '').strip()
|
|
933
|
+
if text:
|
|
934
|
+
return {'label': label, 'url': text}
|
|
935
|
+
return {'label': '', 'url': ''}
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def add_visual_delta_diagnostic(visual_delta, key, value):
|
|
939
|
+
updated = dict(visual_delta or {})
|
|
940
|
+
diagnostic = dict(updated.get('diagnostic') or {})
|
|
941
|
+
diagnostic[key] = value
|
|
942
|
+
updated['diagnostic'] = diagnostic
|
|
943
|
+
return updated
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def measure_visual_delta_against_baseline(state, results, after_payload, current_visual_delta):
|
|
947
|
+
if not visual_delta_applies(state.get('verification_mode')):
|
|
948
|
+
return current_visual_delta
|
|
949
|
+
if isinstance(current_visual_delta, dict) and current_visual_delta.get('status') == 'measured':
|
|
950
|
+
return current_visual_delta
|
|
951
|
+
|
|
952
|
+
baseline = visual_delta_baseline_candidate(state, results)
|
|
953
|
+
before_url = baseline.get('url') or ''
|
|
954
|
+
after_url = extract_screenshot_url(after_payload, 'after-proof') or str(state.get('after_cdn') or '').strip()
|
|
955
|
+
if not before_url or not after_url:
|
|
956
|
+
missing = []
|
|
957
|
+
if not before_url:
|
|
958
|
+
missing.append('baseline screenshot')
|
|
959
|
+
if not after_url:
|
|
960
|
+
missing.append('after screenshot')
|
|
961
|
+
return add_visual_delta_diagnostic(
|
|
962
|
+
current_visual_delta,
|
|
963
|
+
'visual_diff_fallback',
|
|
964
|
+
{
|
|
965
|
+
'status': 'skipped',
|
|
966
|
+
'reason': 'missing ' + ' and '.join(missing),
|
|
967
|
+
'baseline_label': baseline.get('label') or '',
|
|
968
|
+
'before_url_present': bool(before_url),
|
|
969
|
+
'after_url_present': bool(after_url),
|
|
970
|
+
},
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
args = {
|
|
974
|
+
'url_before': before_url,
|
|
975
|
+
'url_after': after_url,
|
|
976
|
+
'delay_ms': 250,
|
|
977
|
+
'timeout_sec': 60,
|
|
978
|
+
}
|
|
979
|
+
try:
|
|
980
|
+
payload = invoke_retry('riddle_visual_diff', args, retries=2, timeout=180)
|
|
981
|
+
append_capture_diagnostic(state, 'visual_delta', 'riddle_visual_diff', args, payload)
|
|
982
|
+
except Exception as exc:
|
|
983
|
+
return add_visual_delta_diagnostic(
|
|
984
|
+
current_visual_delta,
|
|
985
|
+
'visual_diff_fallback',
|
|
986
|
+
{
|
|
987
|
+
'status': 'error',
|
|
988
|
+
'error': type(exc).__name__ + ': ' + str(exc)[:300],
|
|
989
|
+
'baseline_label': baseline.get('label') or '',
|
|
990
|
+
},
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
measured = extract_visual_delta(payload)
|
|
994
|
+
if isinstance(measured, dict) and measured.get('status') == 'measured':
|
|
995
|
+
measured['source'] = 'riddle_visual_diff'
|
|
996
|
+
measured['comparison'] = {
|
|
997
|
+
'baseline_label': baseline.get('label') or '',
|
|
998
|
+
'before_url': before_url,
|
|
999
|
+
'after_url': after_url,
|
|
1000
|
+
}
|
|
1001
|
+
if isinstance(payload, dict):
|
|
1002
|
+
diff_url = extract_screenshot_url(payload, 'visual-diff') or extract_screenshot_url(payload, 'diff')
|
|
1003
|
+
if diff_url:
|
|
1004
|
+
measured['comparison']['diff_url'] = diff_url
|
|
1005
|
+
measured['artifact_summary'] = summarize_capture_artifacts(payload)
|
|
1006
|
+
return measured
|
|
1007
|
+
|
|
1008
|
+
updated = add_visual_delta_diagnostic(
|
|
1009
|
+
current_visual_delta,
|
|
1010
|
+
'visual_diff_fallback',
|
|
1011
|
+
{
|
|
1012
|
+
'status': 'unmeasured',
|
|
1013
|
+
'baseline_label': baseline.get('label') or '',
|
|
1014
|
+
'before_url': before_url,
|
|
1015
|
+
'after_url': after_url,
|
|
1016
|
+
'payload_ok': payload.get('ok') if isinstance(payload, dict) else None,
|
|
1017
|
+
'payload_error': str(payload.get('error') or payload.get('stderr') or '')[:500] if isinstance(payload, dict) else '',
|
|
1018
|
+
'artifact_summary': summarize_capture_artifacts(payload) if isinstance(payload, dict) else {},
|
|
1019
|
+
},
|
|
1020
|
+
)
|
|
1021
|
+
reason = str(updated.get('reason') or '').strip()
|
|
1022
|
+
suffix = ' Riddle visual_diff fallback ran but did not publish a recognizable numeric delta.'
|
|
1023
|
+
updated['reason'] = (reason + suffix).strip() if reason else suffix.strip()
|
|
1024
|
+
return updated
|
|
1025
|
+
|
|
1026
|
+
|
|
902
1027
|
def visual_delta_applies(verification_mode):
|
|
903
1028
|
return screenshot_required_for_mode(verification_mode)
|
|
904
1029
|
|
|
@@ -1443,6 +1568,7 @@ def build_evidence_bundle(state, results, after_payload, after_observation, requ
|
|
|
1443
1568
|
if visual_delta_applies(state.get('verification_mode'))
|
|
1444
1569
|
else {'status': 'not_applicable', 'passed': None, 'reason': 'Verification mode does not require visual delta gating.'}
|
|
1445
1570
|
)
|
|
1571
|
+
visual_delta = measure_visual_delta_against_baseline(state, results, after_payload, visual_delta)
|
|
1446
1572
|
semantic_context = build_semantic_context(state, results, after_observation, expected_path)
|
|
1447
1573
|
artifact_contract = artifact_contract_for_mode(state.get('verification_mode'))
|
|
1448
1574
|
artifact_production = artifact_production_summary(after_payload, supporting)
|
|
@@ -569,6 +569,50 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
|
|
|
569
569
|
assert unmeasured_delta['diagnostic']['after_screenshot_present'] is True
|
|
570
570
|
assert 'After screenshot artifact is present' in unmeasured_delta['reason']
|
|
571
571
|
|
|
572
|
+
visual_diff_delta = namespace['extract_visual_delta']({
|
|
573
|
+
'ok': True,
|
|
574
|
+
'outputs': [{'name': 'visual-diff.json', 'url': 'https://cdn.example.com/visual-diff.json'}],
|
|
575
|
+
'_artifact_json': {
|
|
576
|
+
'visual-diff.json': {
|
|
577
|
+
'changePercent': '1.45',
|
|
578
|
+
'diffPixelCount': 14094,
|
|
579
|
+
'totalPixels': 972000,
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
})
|
|
583
|
+
assert visual_diff_delta['status'] == 'measured'
|
|
584
|
+
assert visual_diff_delta['passed'] is True
|
|
585
|
+
assert visual_diff_delta['change_percent'] == 1.45
|
|
586
|
+
assert visual_diff_delta['changed_pixels'] == 14094
|
|
587
|
+
|
|
588
|
+
fallback_calls = []
|
|
589
|
+
|
|
590
|
+
def fake_invoke_retry(tool, args, retries=3, timeout=180):
|
|
591
|
+
fallback_calls.append({'tool': tool, 'args': args, 'retries': retries, 'timeout': timeout})
|
|
592
|
+
return {
|
|
593
|
+
'ok': True,
|
|
594
|
+
'visual_diff': {
|
|
595
|
+
'diffPercentage': 0.82,
|
|
596
|
+
'differentPixels': 7970,
|
|
597
|
+
'totalPixels': 972000,
|
|
598
|
+
},
|
|
599
|
+
'outputs': [{'name': 'visual-diff.png', 'url': 'https://cdn.example.com/visual-diff.png'}],
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
namespace['invoke_retry'] = fake_invoke_retry
|
|
603
|
+
namespace['append_capture_diagnostic'] = lambda *args, **kwargs: None
|
|
604
|
+
fallback_delta = namespace['measure_visual_delta_against_baseline'](
|
|
605
|
+
{'verification_mode': 'visual', 'requested_reference': 'before', 'before_cdn': 'https://cdn.example.com/before.png'},
|
|
606
|
+
{'baseline': {'before': {'url': 'https://cdn.example.com/before.png'}}},
|
|
607
|
+
{'ok': True, 'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after.png'}]},
|
|
608
|
+
unmeasured_delta,
|
|
609
|
+
)
|
|
610
|
+
assert fallback_delta['status'] == 'measured'
|
|
611
|
+
assert fallback_delta['source'] == 'riddle_visual_diff'
|
|
612
|
+
assert fallback_delta['comparison']['before_url'] == 'https://cdn.example.com/before.png'
|
|
613
|
+
assert fallback_delta['comparison']['after_url'] == 'https://cdn.example.com/after.png'
|
|
614
|
+
assert fallback_calls[0]['tool'] == 'riddle_visual_diff'
|
|
615
|
+
|
|
572
616
|
canvas_payload = {
|
|
573
617
|
'bodyTextLength': 7,
|
|
574
618
|
'visibleTextSample': 'Luge',
|