@riddledc/riddle-proof 0.7.93 → 0.7.95
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/README.md +25 -3
- package/dist/{chunk-QLBOFUON.js → chunk-5OCJKEHE.js} +188 -25
- package/dist/cli.cjs +203 -29
- package/dist/cli.js +16 -5
- package/dist/index.cjs +188 -25
- package/dist/index.js +1 -1
- package/dist/profile.cjs +188 -25
- package/dist/profile.d.cts +3 -1
- package/dist/profile.d.ts +3 -1
- package/dist/profile.js +1 -1
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- package/package.json +1 -1
package/dist/profile.cjs
CHANGED
|
@@ -63,6 +63,8 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
63
63
|
"selector_count_equals",
|
|
64
64
|
"selector_count_equal",
|
|
65
65
|
"selector_count_eq",
|
|
66
|
+
"selector_text_visible",
|
|
67
|
+
"selector_text_absent",
|
|
66
68
|
"selector_text_order",
|
|
67
69
|
"frame_text_visible",
|
|
68
70
|
"frame_url_equals",
|
|
@@ -776,7 +778,7 @@ function normalizeCheck(input, index) {
|
|
|
776
778
|
if (!isSupportedCheckType(type)) {
|
|
777
779
|
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
778
780
|
}
|
|
779
|
-
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue(input.selector)) {
|
|
781
|
+
if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue(input.selector)) {
|
|
780
782
|
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
781
783
|
}
|
|
782
784
|
if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
|
|
@@ -792,7 +794,7 @@ function normalizeCheck(input, index) {
|
|
|
792
794
|
if (type === "frame_url_matches" && !stringValue(input.pattern)) {
|
|
793
795
|
throw new Error(`checks[${index}] frame_url_matches requires pattern.`);
|
|
794
796
|
}
|
|
795
|
-
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
797
|
+
if ((type === "text_visible" || type === "text_absent" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
796
798
|
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
797
799
|
}
|
|
798
800
|
if ((type === "url_search_param_equals" || type === "url_search_param_absent") && !stringValue(input.param) && !stringValue(input.search_param) && !stringValue(input.searchParam) && !stringValue(input.key)) {
|
|
@@ -826,6 +828,12 @@ function normalizeCheck(input, index) {
|
|
|
826
828
|
`checks[${index}] allowed_statuses`
|
|
827
829
|
) : void 0;
|
|
828
830
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
831
|
+
const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
832
|
+
const expectedContentType = isLinkStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
|
|
833
|
+
const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
|
|
834
|
+
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
835
|
+
`checks[${index}] allowed_content_types`
|
|
836
|
+
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
829
837
|
if (isLinkStatusCheck) {
|
|
830
838
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
831
839
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -867,6 +875,8 @@ function normalizeCheck(input, index) {
|
|
|
867
875
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
868
876
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
869
877
|
require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
878
|
+
min_bytes: minBytes,
|
|
879
|
+
allowed_content_types: allowedContentTypes,
|
|
870
880
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
871
881
|
max_overflow_px: numberValue(input.max_overflow_px),
|
|
872
882
|
timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
|
|
@@ -973,15 +983,40 @@ function linkStatusIsAllowed(status, check) {
|
|
|
973
983
|
const allowed = linkStatusAllowedStatuses(check);
|
|
974
984
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
975
985
|
}
|
|
986
|
+
function linkStatusObservedBytes(result) {
|
|
987
|
+
const bytes = numberValue(result.bytes);
|
|
988
|
+
const contentLength = numberValue(result.content_length);
|
|
989
|
+
return Math.max(bytes ?? 0, contentLength ?? 0) || void 0;
|
|
990
|
+
}
|
|
991
|
+
function normalizeLinkStatusContentType(value) {
|
|
992
|
+
const normalized = value?.split(";")[0]?.trim().toLowerCase();
|
|
993
|
+
return normalized || void 0;
|
|
994
|
+
}
|
|
995
|
+
function linkStatusContentTypeOk(result, check) {
|
|
996
|
+
const expected = check.allowed_content_types;
|
|
997
|
+
if (!expected?.length) return true;
|
|
998
|
+
const actual = normalizeLinkStatusContentType(stringValue(result.content_type));
|
|
999
|
+
if (!actual) return false;
|
|
1000
|
+
return expected.some((contentType) => {
|
|
1001
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
1002
|
+
if (!normalized) return false;
|
|
1003
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
1004
|
+
return actual === normalized;
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
976
1007
|
function linkStatusResultOk(result, check) {
|
|
977
1008
|
const status = numberValue(result.status);
|
|
978
1009
|
if (!linkStatusIsAllowed(status, check)) return false;
|
|
979
1010
|
if (stringValue(result.error)) return false;
|
|
980
1011
|
if (result.ok === false) return false;
|
|
1012
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
981
1013
|
if (check.require_nonzero_bytes) {
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
1014
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
1015
|
+
if (observedBytes === void 0 || observedBytes <= 0) return false;
|
|
1016
|
+
}
|
|
1017
|
+
if (check.min_bytes !== void 0) {
|
|
1018
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
1019
|
+
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
985
1020
|
}
|
|
986
1021
|
return true;
|
|
987
1022
|
}
|
|
@@ -1010,7 +1045,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1010
1045
|
status: numberValue(result.status) ?? null,
|
|
1011
1046
|
method: stringValue(result.method) ?? null,
|
|
1012
1047
|
error: stringValue(result.error) ?? null,
|
|
1013
|
-
|
|
1048
|
+
content_type: stringValue(result.content_type) ?? null,
|
|
1049
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
1050
|
+
min_bytes: check.min_bytes ?? null,
|
|
1051
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
1014
1052
|
}));
|
|
1015
1053
|
if (stringValue(linkEvidence.error)) {
|
|
1016
1054
|
failures.push({ code: "link_status_capture_failed", error: stringValue(linkEvidence.error) ?? "" });
|
|
@@ -1047,6 +1085,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
1047
1085
|
failed_count: failures.length,
|
|
1048
1086
|
truncated: linkEvidence.truncated === true,
|
|
1049
1087
|
max_links: numberValue(linkEvidence.max_links) ?? check.max_links ?? 100,
|
|
1088
|
+
min_bytes: check.min_bytes ?? null,
|
|
1089
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1050
1090
|
status_counts: statusCounts,
|
|
1051
1091
|
failures: failures.slice(0, 20)
|
|
1052
1092
|
};
|
|
@@ -1058,9 +1098,11 @@ function textSequenceForCheck(viewport, check) {
|
|
|
1058
1098
|
const key = selectorKey(check);
|
|
1059
1099
|
const sequence = viewport.text_sequences?.[key];
|
|
1060
1100
|
if (isRecord(sequence)) {
|
|
1101
|
+
const visibleMatchTexts = Array.isArray(sequence.visible_match_texts) ? sequence.visible_match_texts : [];
|
|
1102
|
+
const matchTexts = Array.isArray(sequence.match_texts) ? sequence.match_texts : [];
|
|
1061
1103
|
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
1062
1104
|
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
1063
|
-
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
1105
|
+
const candidates = visibleMatchTexts.length ? visibleMatchTexts : matchTexts.length ? matchTexts : visibleTexts.length ? visibleTexts : texts;
|
|
1064
1106
|
return candidates.map((text) => String(text).replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
1065
1107
|
}
|
|
1066
1108
|
return [];
|
|
@@ -1394,6 +1436,35 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1394
1436
|
message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
|
|
1395
1437
|
};
|
|
1396
1438
|
}
|
|
1439
|
+
if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
|
|
1440
|
+
const key = selectorKey(check);
|
|
1441
|
+
const expectedVisible = check.type === "selector_text_visible";
|
|
1442
|
+
const results = viewports.map((viewport) => {
|
|
1443
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
1444
|
+
const matches = texts.filter((text) => matchText(text, check));
|
|
1445
|
+
return {
|
|
1446
|
+
viewport: viewport.name,
|
|
1447
|
+
selector_count: viewport.selectors?.[key]?.count || 0,
|
|
1448
|
+
visible_count: viewport.selectors?.[key]?.visible_count || 0,
|
|
1449
|
+
matched_count: matches.length,
|
|
1450
|
+
matched: matches.length > 0,
|
|
1451
|
+
samples: matches.slice(0, 3).map((text) => text.slice(0, 240))
|
|
1452
|
+
};
|
|
1453
|
+
});
|
|
1454
|
+
const failed = results.filter((result) => result.matched !== expectedVisible).length;
|
|
1455
|
+
return {
|
|
1456
|
+
type: check.type,
|
|
1457
|
+
label: checkLabel(check),
|
|
1458
|
+
status: failed ? "failed" : "passed",
|
|
1459
|
+
evidence: {
|
|
1460
|
+
selector: key,
|
|
1461
|
+
text: check.text || null,
|
|
1462
|
+
pattern: check.pattern || null,
|
|
1463
|
+
viewports: results.map((result) => toJsonValue(result))
|
|
1464
|
+
},
|
|
1465
|
+
message: failed ? `Selector ${key} text assertion failed in ${failed} viewport(s).` : void 0
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1397
1468
|
if (check.type === "selector_text_order") {
|
|
1398
1469
|
const key = selectorKey(check);
|
|
1399
1470
|
const expectedTexts = check.expected_texts || [];
|
|
@@ -1404,7 +1475,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1404
1475
|
viewport: viewport.name,
|
|
1405
1476
|
matched: match.matched,
|
|
1406
1477
|
matched_positions: match.positions,
|
|
1407
|
-
visible_texts: texts.slice(0, 20)
|
|
1478
|
+
visible_texts: texts.slice(0, 20).map((text) => text.slice(0, 240))
|
|
1408
1479
|
};
|
|
1409
1480
|
});
|
|
1410
1481
|
const failed = results.filter((result) => !result.matched).length;
|
|
@@ -1544,6 +1615,8 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1544
1615
|
min_count: check.min_count ?? null,
|
|
1545
1616
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1546
1617
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1618
|
+
min_bytes: check.min_bytes ?? null,
|
|
1619
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1547
1620
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1548
1621
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1549
1622
|
},
|
|
@@ -2114,9 +2187,11 @@ function textSequenceForCheck(viewport, check) {
|
|
|
2114
2187
|
const key = check.selector || "";
|
|
2115
2188
|
const sequence = viewport.text_sequences && viewport.text_sequences[key];
|
|
2116
2189
|
if (sequence && typeof sequence === "object") {
|
|
2190
|
+
const visibleMatchTexts = Array.isArray(sequence.visible_match_texts) ? sequence.visible_match_texts : [];
|
|
2191
|
+
const matchTexts = Array.isArray(sequence.match_texts) ? sequence.match_texts : [];
|
|
2117
2192
|
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
2118
2193
|
const texts = Array.isArray(sequence.texts) ? sequence.texts : [];
|
|
2119
|
-
const candidates = visibleTexts.length ? visibleTexts : texts;
|
|
2194
|
+
const candidates = visibleMatchTexts.length ? visibleMatchTexts : matchTexts.length ? matchTexts : visibleTexts.length ? visibleTexts : texts;
|
|
2120
2195
|
return candidates.map((text) => String(text || "").replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
2121
2196
|
}
|
|
2122
2197
|
return [];
|
|
@@ -2145,15 +2220,41 @@ function linkStatusIsAllowed(status, check) {
|
|
|
2145
2220
|
const allowed = linkStatusAllowedStatuses(check);
|
|
2146
2221
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2147
2222
|
}
|
|
2223
|
+
function linkStatusObservedBytes(result) {
|
|
2224
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2225
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
2226
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
2227
|
+
return observed > 0 ? observed : undefined;
|
|
2228
|
+
}
|
|
2229
|
+
function normalizeLinkStatusContentType(value) {
|
|
2230
|
+
if (typeof value !== "string") return undefined;
|
|
2231
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
2232
|
+
return normalized || undefined;
|
|
2233
|
+
}
|
|
2234
|
+
function linkStatusContentTypeOk(result, check) {
|
|
2235
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
2236
|
+
const actual = normalizeLinkStatusContentType(result.content_type);
|
|
2237
|
+
if (!actual) return false;
|
|
2238
|
+
return check.allowed_content_types.some((contentType) => {
|
|
2239
|
+
const normalized = normalizeLinkStatusContentType(contentType);
|
|
2240
|
+
if (!normalized) return false;
|
|
2241
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
2242
|
+
return actual === normalized;
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2148
2245
|
function linkStatusResultOk(result, check) {
|
|
2149
2246
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2150
2247
|
if (!linkStatusIsAllowed(result.status, check)) return false;
|
|
2151
2248
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2152
2249
|
if (result.ok === false) return false;
|
|
2250
|
+
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
2153
2251
|
if (check.require_nonzero_bytes === true) {
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2252
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2253
|
+
if (observedBytes === undefined || observedBytes <= 0) return false;
|
|
2254
|
+
}
|
|
2255
|
+
if (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) {
|
|
2256
|
+
const observedBytes = linkStatusObservedBytes(result);
|
|
2257
|
+
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
2157
2258
|
}
|
|
2158
2259
|
return true;
|
|
2159
2260
|
}
|
|
@@ -2181,11 +2282,10 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2181
2282
|
status: typeof result.status === "number" && Number.isFinite(result.status) ? result.status : null,
|
|
2182
2283
|
method: typeof result.method === "string" ? result.method : null,
|
|
2183
2284
|
error: typeof result.error === "string" ? result.error : null,
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
: null,
|
|
2285
|
+
content_type: typeof result.content_type === "string" ? result.content_type : null,
|
|
2286
|
+
bytes: linkStatusObservedBytes(result) ?? null,
|
|
2287
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2288
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2189
2289
|
}));
|
|
2190
2290
|
if (typeof linkEvidence.error === "string" && linkEvidence.error.trim()) {
|
|
2191
2291
|
failures.push({ code: "link_status_capture_failed", error: linkEvidence.error });
|
|
@@ -2220,6 +2320,8 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
2220
2320
|
failed_count: failures.length,
|
|
2221
2321
|
truncated: linkEvidence.truncated === true,
|
|
2222
2322
|
max_links: typeof linkEvidence.max_links === "number" ? linkEvidence.max_links : check.max_links || 100,
|
|
2323
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2324
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2223
2325
|
status_counts: linkEvidence.status_counts && typeof linkEvidence.status_counts === "object" && !Array.isArray(linkEvidence.status_counts) ? linkEvidence.status_counts : {},
|
|
2224
2326
|
failures: failures.slice(0, 20),
|
|
2225
2327
|
};
|
|
@@ -2754,6 +2856,31 @@ function assessProfile(profile, evidence) {
|
|
|
2754
2856
|
});
|
|
2755
2857
|
continue;
|
|
2756
2858
|
}
|
|
2859
|
+
if (check.type === "selector_text_visible" || check.type === "selector_text_absent") {
|
|
2860
|
+
const selector = check.selector || "";
|
|
2861
|
+
const expectedVisible = check.type === "selector_text_visible";
|
|
2862
|
+
const results = checkViewports.map((viewport) => {
|
|
2863
|
+
const texts = textSequenceForCheck(viewport, check);
|
|
2864
|
+
const matches = texts.filter((text) => textMatches(text, check));
|
|
2865
|
+
return {
|
|
2866
|
+
viewport: viewport.name,
|
|
2867
|
+
selector_count: viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0,
|
|
2868
|
+
visible_count: viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0,
|
|
2869
|
+
matched_count: matches.length,
|
|
2870
|
+
matched: matches.length > 0,
|
|
2871
|
+
samples: matches.slice(0, 3).map((text) => text.slice(0, 240)),
|
|
2872
|
+
};
|
|
2873
|
+
});
|
|
2874
|
+
const failed = results.filter((result) => result.matched !== expectedVisible).length;
|
|
2875
|
+
checks.push({
|
|
2876
|
+
type: check.type,
|
|
2877
|
+
label: check.label || check.type,
|
|
2878
|
+
status: failed ? "failed" : "passed",
|
|
2879
|
+
evidence: { selector, text: check.text || null, pattern: check.pattern || null, viewports: results },
|
|
2880
|
+
message: failed ? "Selector " + selector + " text assertion failed in " + failed + " viewport(s)." : undefined,
|
|
2881
|
+
});
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2757
2884
|
if (check.type === "selector_text_order") {
|
|
2758
2885
|
const selector = check.selector || "";
|
|
2759
2886
|
const expectedTexts = check.expected_texts || [];
|
|
@@ -2764,7 +2891,7 @@ function assessProfile(profile, evidence) {
|
|
|
2764
2891
|
viewport: viewport.name,
|
|
2765
2892
|
matched: match.matched,
|
|
2766
2893
|
matched_positions: match.positions,
|
|
2767
|
-
visible_texts: texts.slice(0, 20),
|
|
2894
|
+
visible_texts: texts.slice(0, 20).map((text) => text.slice(0, 240)),
|
|
2768
2895
|
};
|
|
2769
2896
|
});
|
|
2770
2897
|
const failed = results.filter((result) => !result.matched).length;
|
|
@@ -2891,6 +3018,8 @@ function assessProfile(profile, evidence) {
|
|
|
2891
3018
|
min_count: check.min_count ?? null,
|
|
2892
3019
|
allowed_statuses: linkStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2893
3020
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
3021
|
+
min_bytes: check.min_bytes ?? null,
|
|
3022
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
2894
3023
|
viewports: summaries,
|
|
2895
3024
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
2896
3025
|
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
@@ -3968,6 +4097,7 @@ async function selectorStats(selector) {
|
|
|
3968
4097
|
async function selectorTextSequence(selector) {
|
|
3969
4098
|
return page.locator(selector).evaluateAll((elements) => {
|
|
3970
4099
|
const compact = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 240);
|
|
4100
|
+
const matchText = (value) => String(value || "").replace(/\s+/g, " ").trim().slice(0, 8000);
|
|
3971
4101
|
const isVisible = (element) => {
|
|
3972
4102
|
const style = window.getComputedStyle(element);
|
|
3973
4103
|
const rect = element.getBoundingClientRect();
|
|
@@ -3976,6 +4106,7 @@ async function selectorTextSequence(selector) {
|
|
|
3976
4106
|
const rows = elements.map((element, index) => ({
|
|
3977
4107
|
index,
|
|
3978
4108
|
text: compact(element.innerText || element.textContent || ""),
|
|
4109
|
+
match_text: matchText(element.innerText || element.textContent || ""),
|
|
3979
4110
|
visible: isVisible(element),
|
|
3980
4111
|
}));
|
|
3981
4112
|
return {
|
|
@@ -3983,8 +4114,10 @@ async function selectorTextSequence(selector) {
|
|
|
3983
4114
|
visible_count: rows.filter((row) => row.visible).length,
|
|
3984
4115
|
texts: rows.map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
3985
4116
|
visible_texts: rows.filter((row) => row.visible).map((row) => row.text).filter(Boolean).slice(0, 40),
|
|
4117
|
+
match_texts: rows.map((row) => row.match_text).filter(Boolean).slice(0, 40),
|
|
4118
|
+
visible_match_texts: rows.filter((row) => row.visible).map((row) => row.match_text).filter(Boolean).slice(0, 40),
|
|
3986
4119
|
};
|
|
3987
|
-
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
4120
|
+
}).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
3988
4121
|
}
|
|
3989
4122
|
function linkProbeMaxLinks(check) {
|
|
3990
4123
|
const value = Number(check.max_links || check.maxLinks || check.limit || 100);
|
|
@@ -4034,6 +4167,28 @@ function linkProbeAllowed(status, check) {
|
|
|
4034
4167
|
: null;
|
|
4035
4168
|
return allowed ? allowed.includes(status) : status >= 200 && status < 400;
|
|
4036
4169
|
}
|
|
4170
|
+
function linkProbeObservedBytes(result) {
|
|
4171
|
+
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
4172
|
+
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
4173
|
+
const observed = Math.max(bytes || 0, contentLength || 0);
|
|
4174
|
+
return observed > 0 ? observed : undefined;
|
|
4175
|
+
}
|
|
4176
|
+
function normalizeLinkProbeContentType(value) {
|
|
4177
|
+
if (typeof value !== "string") return undefined;
|
|
4178
|
+
const normalized = (value.split(";")[0] || "").trim().toLowerCase();
|
|
4179
|
+
return normalized || undefined;
|
|
4180
|
+
}
|
|
4181
|
+
function linkProbeContentTypeAllowed(result, check) {
|
|
4182
|
+
if (!Array.isArray(check.allowed_content_types) || !check.allowed_content_types.length) return true;
|
|
4183
|
+
const actual = normalizeLinkProbeContentType(result.content_type);
|
|
4184
|
+
if (!actual) return false;
|
|
4185
|
+
return check.allowed_content_types.some((contentType) => {
|
|
4186
|
+
const normalized = normalizeLinkProbeContentType(contentType);
|
|
4187
|
+
if (!normalized) return false;
|
|
4188
|
+
if (normalized.endsWith("/*")) return actual.startsWith(normalized.slice(0, -1));
|
|
4189
|
+
return actual === normalized;
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4037
4192
|
function linkProbeResponseFields(response, method) {
|
|
4038
4193
|
const contentLengthHeader = response.headers && typeof response.headers.get === "function" ? response.headers.get("content-length") : null;
|
|
4039
4194
|
const contentLength = contentLengthHeader && /^\d+$/.test(contentLengthHeader) ? Number(contentLengthHeader) : null;
|
|
@@ -4048,6 +4203,7 @@ function linkProbeResponseFields(response, method) {
|
|
|
4048
4203
|
}
|
|
4049
4204
|
async function probeLinkStatus(candidate, check) {
|
|
4050
4205
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4206
|
+
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
4051
4207
|
const allowGetFallback = check.allow_get_fallback !== false;
|
|
4052
4208
|
const result = {
|
|
4053
4209
|
url: candidate.url,
|
|
@@ -4065,7 +4221,7 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4065
4221
|
};
|
|
4066
4222
|
const applyResponse = async (response, method, readBytes) => {
|
|
4067
4223
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4068
|
-
if (readBytes
|
|
4224
|
+
if (readBytes) {
|
|
4069
4225
|
try {
|
|
4070
4226
|
const buffer = await response.arrayBuffer();
|
|
4071
4227
|
result.bytes = buffer.byteLength;
|
|
@@ -4074,7 +4230,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4074
4230
|
}
|
|
4075
4231
|
}
|
|
4076
4232
|
result.ok = linkProbeAllowed(result.status, check)
|
|
4077
|
-
&& (
|
|
4233
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4234
|
+
&& (!requireNonzeroBytes || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4235
|
+
&& (minBytes === null || ((linkProbeObservedBytes(result) || 0) >= minBytes))
|
|
4078
4236
|
&& !result.error;
|
|
4079
4237
|
};
|
|
4080
4238
|
try {
|
|
@@ -4091,9 +4249,9 @@ async function probeLinkStatus(candidate, check) {
|
|
|
4091
4249
|
method: "GET",
|
|
4092
4250
|
redirect: "follow",
|
|
4093
4251
|
cache: "no-store",
|
|
4094
|
-
headers: { Range: "bytes=0-
|
|
4252
|
+
headers: { Range: "bytes=0-" + String((minBytes || 1) - 1) },
|
|
4095
4253
|
});
|
|
4096
|
-
await applyResponse(response, "GET", requireNonzeroBytes);
|
|
4254
|
+
await applyResponse(response, "GET", requireNonzeroBytes || minBytes !== null);
|
|
4097
4255
|
return result;
|
|
4098
4256
|
} catch (error) {
|
|
4099
4257
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -4147,7 +4305,10 @@ async function collectLinkStatus(check) {
|
|
|
4147
4305
|
status: result.status,
|
|
4148
4306
|
method: result.method,
|
|
4149
4307
|
error: result.error,
|
|
4150
|
-
|
|
4308
|
+
content_type: result.content_type,
|
|
4309
|
+
bytes: linkProbeObservedBytes(result) || null,
|
|
4310
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4311
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4151
4312
|
}));
|
|
4152
4313
|
const statusCounts = {};
|
|
4153
4314
|
for (const result of results) {
|
|
@@ -4161,6 +4322,8 @@ async function collectLinkStatus(check) {
|
|
|
4161
4322
|
same_origin_only: linkProbeSameOriginOnly(check),
|
|
4162
4323
|
dedupe: linkProbeDedupe(check),
|
|
4163
4324
|
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4325
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4326
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4164
4327
|
allowed_statuses: Array.isArray(check.allowed_statuses) && check.allowed_statuses.length
|
|
4165
4328
|
? check.allowed_statuses
|
|
4166
4329
|
: typeof check.expected_status === "number"
|
|
@@ -4720,7 +4883,7 @@ async function captureViewport(viewport) {
|
|
|
4720
4883
|
) {
|
|
4721
4884
|
selectors[check.selector] = await selectorStats(check.selector);
|
|
4722
4885
|
}
|
|
4723
|
-
if (check.type === "selector_text_order" && check.selector) {
|
|
4886
|
+
if ((check.type === "selector_text_order" || check.type === "selector_text_visible" || check.type === "selector_text_absent") && check.selector) {
|
|
4724
4887
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
4725
4888
|
text_sequences[check.selector] = await selectorTextSequence(check.selector);
|
|
4726
4889
|
}
|
package/dist/profile.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -138,6 +138,8 @@ interface RiddleProofProfileCheck {
|
|
|
138
138
|
same_origin_only?: boolean;
|
|
139
139
|
dedupe?: boolean;
|
|
140
140
|
require_nonzero_bytes?: boolean;
|
|
141
|
+
min_bytes?: number;
|
|
142
|
+
allowed_content_types?: string[];
|
|
141
143
|
allow_get_fallback?: boolean;
|
|
142
144
|
max_overflow_px?: number;
|
|
143
145
|
timeout_ms?: number;
|
package/dist/profile.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -138,6 +138,8 @@ interface RiddleProofProfileCheck {
|
|
|
138
138
|
same_origin_only?: boolean;
|
|
139
139
|
dedupe?: boolean;
|
|
140
140
|
require_nonzero_bytes?: boolean;
|
|
141
|
+
min_bytes?: number;
|
|
142
|
+
allowed_content_types?: string[];
|
|
141
143
|
allow_get_fallback?: boolean;
|
|
142
144
|
max_overflow_px?: number;
|
|
143
145
|
timeout_ms?: number;
|
package/dist/profile.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveRiddleProofProfileTimeoutSec,
|
|
20
20
|
slugifyRiddleProofProfileName,
|
|
21
21
|
summarizeRiddleProofProfileResult
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-5OCJKEHE.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
292
292
|
blocking?: boolean;
|
|
293
293
|
details?: Record<string, unknown>;
|
|
294
294
|
ok: boolean;
|
|
295
|
-
action: "
|
|
295
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
296
296
|
state_path: string;
|
|
297
297
|
stage: any;
|
|
298
298
|
summary: string;
|
|
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
382
382
|
continueWithStage?: WorkflowStage | null;
|
|
383
383
|
blocking?: boolean;
|
|
384
384
|
details?: Record<string, unknown>;
|
|
385
|
-
action: "
|
|
385
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
386
386
|
state_path: string;
|
|
387
387
|
stage: any;
|
|
388
388
|
checkpoint: string;
|
|
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
659
659
|
error?: undefined;
|
|
660
660
|
} | {
|
|
661
661
|
ok: boolean;
|
|
662
|
-
action: "
|
|
662
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|
|
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
292
292
|
blocking?: boolean;
|
|
293
293
|
details?: Record<string, unknown>;
|
|
294
294
|
ok: boolean;
|
|
295
|
-
action: "
|
|
295
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
296
296
|
state_path: string;
|
|
297
297
|
stage: any;
|
|
298
298
|
summary: string;
|
|
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
382
382
|
continueWithStage?: WorkflowStage | null;
|
|
383
383
|
blocking?: boolean;
|
|
384
384
|
details?: Record<string, unknown>;
|
|
385
|
-
action: "
|
|
385
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
386
386
|
state_path: string;
|
|
387
387
|
stage: any;
|
|
388
388
|
checkpoint: string;
|
|
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
|
|
|
659
659
|
error?: undefined;
|
|
660
660
|
} | {
|
|
661
661
|
ok: boolean;
|
|
662
|
-
action: "
|
|
662
|
+
action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|