@riddledc/riddle-proof 0.7.97 → 0.7.99

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/profile.cjs CHANGED
@@ -72,6 +72,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
72
72
  "frame_no_horizontal_overflow",
73
73
  "text_visible",
74
74
  "text_absent",
75
+ "http_status",
75
76
  "link_status",
76
77
  "artifact_link_status",
77
78
  "route_inventory",
@@ -821,19 +822,34 @@ function normalizeCheck(input, index) {
821
822
  if (type === "route_inventory" && !expectedRoutes?.length) {
822
823
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
823
824
  }
825
+ const isHttpStatusCheck = type === "http_status";
824
826
  const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
825
- const expectedStatus = isLinkStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
826
- const allowedStatuses = isLinkStatusCheck ? normalizeHttpStatuses(
827
+ const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
828
+ const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
829
+ if (isHttpStatusCheck && !requestUrl) {
830
+ throw new Error(`checks[${index}] http_status requires url.`);
831
+ }
832
+ const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
833
+ if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
834
+ throw new Error(`checks[${index}] http_status method must contain only letters.`);
835
+ }
836
+ const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
837
+ const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
838
+ const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
827
839
  input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
828
840
  `checks[${index}] allowed_statuses`
829
841
  ) : void 0;
830
842
  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(
843
+ const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
844
+ const expectedContentType = isStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
845
+ const allowedContentTypes = isStatusCheck ? normalizeStringList(
834
846
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
835
847
  `checks[${index}] allowed_content_types`
836
848
  ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
849
+ const bodyContains = isHttpStatusCheck ? normalizeStringList(
850
+ input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
851
+ `checks[${index}] body_contains`
852
+ ) : void 0;
837
853
  if (isLinkStatusCheck) {
838
854
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
839
855
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -851,6 +867,12 @@ function normalizeCheck(input, index) {
851
867
  expected_url: expectedUrl,
852
868
  expected_routes: expectedRoutes,
853
869
  selector: stringValue(input.selector),
870
+ url: isHttpStatusCheck ? requestUrl : void 0,
871
+ method: isHttpStatusCheck ? method || "GET" : void 0,
872
+ headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
873
+ body: isHttpStatusCheck ? stringValue(input.body) : void 0,
874
+ body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
875
+ body_contains: bodyContains,
854
876
  expected_texts: expectedTexts,
855
877
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
856
878
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
@@ -874,7 +896,7 @@ function normalizeCheck(input, index) {
874
896
  max_links: maxLinks,
875
897
  same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
876
898
  dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
877
- require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
899
+ require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
878
900
  min_bytes: minBytes,
879
901
  allowed_content_types: allowedContentTypes,
880
902
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
@@ -973,14 +995,32 @@ function selectorKey(check) {
973
995
  function linkStatusSelector(check) {
974
996
  return check.selector || check.link_selector || "a[href]";
975
997
  }
976
- function linkStatusAllowedStatuses(check) {
998
+ function httpStatusRequestUrl(check, baseUrl) {
999
+ const rawUrl = check.url || "";
1000
+ if (!rawUrl) return "";
1001
+ try {
1002
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
1003
+ } catch {
1004
+ return rawUrl;
1005
+ }
1006
+ }
1007
+ function httpStatusMethod(check) {
1008
+ return (check.method || "GET").toUpperCase();
1009
+ }
1010
+ function httpStatusKey(check, baseUrl) {
1011
+ return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
1012
+ }
1013
+ function httpStatusAllowedStatuses(check) {
977
1014
  if (check.allowed_statuses?.length) return check.allowed_statuses;
978
1015
  if (check.expected_status !== void 0) return [check.expected_status];
979
1016
  return void 0;
980
1017
  }
981
- function linkStatusIsAllowed(status, check) {
1018
+ function linkStatusAllowedStatuses(check) {
1019
+ return httpStatusAllowedStatuses(check);
1020
+ }
1021
+ function httpStatusIsAllowed(status, check) {
982
1022
  if (status === void 0) return false;
983
- const allowed = linkStatusAllowedStatuses(check);
1023
+ const allowed = httpStatusAllowedStatuses(check);
984
1024
  return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
985
1025
  }
986
1026
  function linkStatusObservedBytes(result) {
@@ -1004,9 +1044,15 @@ function linkStatusContentTypeOk(result, check) {
1004
1044
  return actual === normalized;
1005
1045
  });
1006
1046
  }
1047
+ function httpStatusBodyContainsFailures(result, check) {
1048
+ const expected = check.body_contains?.filter(Boolean) ?? [];
1049
+ if (!expected.length) return [];
1050
+ const observed = isRecord(result.body_contains) ? result.body_contains : {};
1051
+ return expected.filter((text) => observed[text] !== true);
1052
+ }
1007
1053
  function linkStatusResultOk(result, check) {
1008
1054
  const status = numberValue(result.status);
1009
- if (!linkStatusIsAllowed(status, check)) return false;
1055
+ if (!httpStatusIsAllowed(status, check)) return false;
1010
1056
  if (stringValue(result.error)) return false;
1011
1057
  if (result.ok === false) return false;
1012
1058
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -1018,8 +1064,64 @@ function linkStatusResultOk(result, check) {
1018
1064
  const observedBytes = linkStatusObservedBytes(result);
1019
1065
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
1020
1066
  }
1067
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
1021
1068
  return true;
1022
1069
  }
1070
+ function httpStatusEvidenceForCheck(viewport, check) {
1071
+ const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
1072
+ return isRecord(evidence) ? evidence : void 0;
1073
+ }
1074
+ function summarizeHttpStatusEvidence(viewport, check) {
1075
+ const key = httpStatusKey(check, viewport.url);
1076
+ const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
1077
+ if (!statusEvidence) {
1078
+ return {
1079
+ viewport: viewport.name,
1080
+ key,
1081
+ url: httpStatusRequestUrl(check, viewport.url),
1082
+ method: httpStatusMethod(check),
1083
+ ok: false,
1084
+ status: null,
1085
+ failures: [{ code: "http_status_evidence_missing" }]
1086
+ };
1087
+ }
1088
+ const failures = [];
1089
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
1090
+ if (!linkStatusResultOk(statusEvidence, check)) {
1091
+ failures.push({
1092
+ code: "http_status_failed",
1093
+ url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
1094
+ status: numberValue(statusEvidence.status) ?? null,
1095
+ method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
1096
+ error: stringValue(statusEvidence.error) ?? null,
1097
+ content_type: stringValue(statusEvidence.content_type) ?? null,
1098
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1099
+ allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
1100
+ min_bytes: check.min_bytes ?? null,
1101
+ allowed_content_types: check.allowed_content_types ?? null,
1102
+ body_contains: check.body_contains ?? null,
1103
+ body_contains_missing: bodyContainsMissing,
1104
+ body_sample: stringValue(statusEvidence.body_sample) ?? null
1105
+ });
1106
+ }
1107
+ return {
1108
+ viewport: viewport.name,
1109
+ key,
1110
+ url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
1111
+ method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
1112
+ status: numberValue(statusEvidence.status) ?? null,
1113
+ status_text: stringValue(statusEvidence.status_text) ?? null,
1114
+ ok: failures.length === 0,
1115
+ error: stringValue(statusEvidence.error) ?? null,
1116
+ content_type: stringValue(statusEvidence.content_type) ?? null,
1117
+ content_length: numberValue(statusEvidence.content_length) ?? null,
1118
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1119
+ body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
1120
+ body_contains_missing: bodyContainsMissing,
1121
+ body_sample: stringValue(statusEvidence.body_sample) ?? null,
1122
+ failures
1123
+ };
1124
+ }
1023
1125
  function linkStatusEvidenceForCheck(viewport, check) {
1024
1126
  const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
1025
1127
  return isRecord(evidence) ? evidence : void 0;
@@ -1610,6 +1712,29 @@ function assessCheckFromEvidence(check, evidence) {
1610
1712
  message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
1611
1713
  };
1612
1714
  }
1715
+ if (check.type === "http_status") {
1716
+ const url = httpStatusRequestUrl(check, evidence.target_url);
1717
+ const method = httpStatusMethod(check);
1718
+ const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
1719
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
1720
+ return {
1721
+ type: check.type,
1722
+ label: checkLabel(check),
1723
+ status: failed.length ? "failed" : "passed",
1724
+ evidence: {
1725
+ url,
1726
+ method,
1727
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
1728
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
1729
+ min_bytes: check.min_bytes ?? null,
1730
+ allowed_content_types: check.allowed_content_types ?? null,
1731
+ body_contains: check.body_contains ?? [],
1732
+ viewports: summaries.map((summary) => toJsonValue(summary)),
1733
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
1734
+ },
1735
+ message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
1736
+ };
1737
+ }
1613
1738
  if (check.type === "link_status" || check.type === "artifact_link_status") {
1614
1739
  const selector = linkStatusSelector(check);
1615
1740
  const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -2219,16 +2344,37 @@ function textOrderMatch(texts, expectedTexts) {
2219
2344
  function linkStatusSelector(check) {
2220
2345
  return check.selector || check.link_selector || "a[href]";
2221
2346
  }
2222
- function linkStatusAllowedStatuses(check) {
2347
+ function httpStatusRequestUrl(check, baseUrl) {
2348
+ const rawUrl = check.url || "";
2349
+ if (!rawUrl) return "";
2350
+ try {
2351
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
2352
+ } catch {
2353
+ return rawUrl;
2354
+ }
2355
+ }
2356
+ function httpStatusMethod(check) {
2357
+ return String(check.method || "GET").toUpperCase();
2358
+ }
2359
+ function httpStatusKey(check, baseUrl) {
2360
+ return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
2361
+ }
2362
+ function httpStatusAllowedStatuses(check) {
2223
2363
  if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
2224
2364
  if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
2225
2365
  return undefined;
2226
2366
  }
2227
- function linkStatusIsAllowed(status, check) {
2367
+ function linkStatusAllowedStatuses(check) {
2368
+ return httpStatusAllowedStatuses(check);
2369
+ }
2370
+ function httpStatusIsAllowed(status, check) {
2228
2371
  if (typeof status !== "number" || !Number.isFinite(status)) return false;
2229
- const allowed = linkStatusAllowedStatuses(check);
2372
+ const allowed = httpStatusAllowedStatuses(check);
2230
2373
  return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
2231
2374
  }
2375
+ function linkStatusIsAllowed(status, check) {
2376
+ return httpStatusIsAllowed(status, check);
2377
+ }
2232
2378
  function linkStatusObservedBytes(result) {
2233
2379
  const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
2234
2380
  const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
@@ -2251,9 +2397,17 @@ function linkStatusContentTypeOk(result, check) {
2251
2397
  return actual === normalized;
2252
2398
  });
2253
2399
  }
2400
+ function httpStatusBodyContainsFailures(result, check) {
2401
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
2402
+ if (!expected.length) return [];
2403
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
2404
+ ? result.body_contains
2405
+ : {};
2406
+ return expected.filter((text) => observed[text] !== true);
2407
+ }
2254
2408
  function linkStatusResultOk(result, check) {
2255
2409
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
2256
- if (!linkStatusIsAllowed(result.status, check)) return false;
2410
+ if (!httpStatusIsAllowed(result.status, check)) return false;
2257
2411
  if (typeof result.error === "string" && result.error.trim()) return false;
2258
2412
  if (result.ok === false) return false;
2259
2413
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -2265,8 +2419,62 @@ function linkStatusResultOk(result, check) {
2265
2419
  const observedBytes = linkStatusObservedBytes(result);
2266
2420
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
2267
2421
  }
2422
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
2268
2423
  return true;
2269
2424
  }
2425
+ function summarizeHttpStatusEvidence(viewport, check) {
2426
+ const key = httpStatusKey(check, viewport && viewport.url);
2427
+ const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
2428
+ if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
2429
+ return {
2430
+ viewport: viewport && viewport.name,
2431
+ key,
2432
+ url: httpStatusRequestUrl(check, viewport && viewport.url),
2433
+ method: httpStatusMethod(check),
2434
+ ok: false,
2435
+ status: null,
2436
+ failures: [{ code: "http_status_evidence_missing" }],
2437
+ };
2438
+ }
2439
+ const failures = [];
2440
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
2441
+ if (!linkStatusResultOk(statusEvidence, check)) {
2442
+ failures.push({
2443
+ code: "http_status_failed",
2444
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
2445
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
2446
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
2447
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
2448
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2449
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2450
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
2451
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
2452
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
2453
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
2454
+ body_contains_missing: bodyContainsMissing,
2455
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2456
+ });
2457
+ }
2458
+ return {
2459
+ viewport: viewport && viewport.name,
2460
+ key,
2461
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
2462
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
2463
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
2464
+ status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
2465
+ ok: failures.length === 0,
2466
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
2467
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2468
+ content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
2469
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2470
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
2471
+ ? statusEvidence.body_contains
2472
+ : null,
2473
+ body_contains_missing: bodyContainsMissing,
2474
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2475
+ failures,
2476
+ };
2477
+ }
2270
2478
  function summarizeLinkStatusEvidence(viewport, check) {
2271
2479
  const selector = linkStatusSelector(check);
2272
2480
  const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
@@ -3022,6 +3230,31 @@ function assessProfile(profile, evidence) {
3022
3230
  });
3023
3231
  continue;
3024
3232
  }
3233
+ if (check.type === "http_status") {
3234
+ const url = httpStatusRequestUrl(check, evidence.target_url);
3235
+ const method = httpStatusMethod(check);
3236
+ const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
3237
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
3238
+ checks.push({
3239
+ type: check.type,
3240
+ label: check.label || check.type,
3241
+ status: failed.length ? "failed" : "passed",
3242
+ evidence: {
3243
+ url,
3244
+ method,
3245
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
3246
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
3247
+ min_bytes: check.min_bytes ?? null,
3248
+ allowed_content_types: check.allowed_content_types ?? null,
3249
+ viewports: summaries,
3250
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures)
3251
+ ? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
3252
+ : []),
3253
+ },
3254
+ message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
3255
+ });
3256
+ continue;
3257
+ }
3025
3258
  if (check.type === "link_status" || check.type === "artifact_link_status") {
3026
3259
  const selector = linkStatusSelector(check);
3027
3260
  const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -4219,6 +4452,72 @@ function linkProbeResponseFields(response, method) {
4219
4452
  content_length: contentLength,
4220
4453
  };
4221
4454
  }
4455
+ async function collectHttpStatus(check) {
4456
+ const url = httpStatusRequestUrl(check, page.url() || targetUrl);
4457
+ const method = httpStatusMethod(check);
4458
+ const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
4459
+ ? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
4460
+ : {};
4461
+ let body;
4462
+ if (check.body_json !== undefined) {
4463
+ body = JSON.stringify(check.body_json);
4464
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
4465
+ } else if (typeof check.body === "string") {
4466
+ body = check.body;
4467
+ }
4468
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
4469
+ const options = {
4470
+ method,
4471
+ redirect: "follow",
4472
+ cache: "no-store",
4473
+ headers,
4474
+ };
4475
+ if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
4476
+ const result = {
4477
+ version: "riddle-proof.http-status.v1",
4478
+ url,
4479
+ method,
4480
+ status: null,
4481
+ ok: false,
4482
+ error: null,
4483
+ request_body_bytes: typeof body === "string" ? body.length : 0,
4484
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
4485
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
4486
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
4487
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
4488
+ };
4489
+ try {
4490
+ const response = await fetch(url, options);
4491
+ Object.assign(result, linkProbeResponseFields(response, method));
4492
+ result.url = url;
4493
+ result.status_text = response.statusText || "";
4494
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0;
4495
+ if (shouldReadBody) {
4496
+ try {
4497
+ const buffer = await response.arrayBuffer();
4498
+ result.bytes = buffer.byteLength;
4499
+ if (bodyContains.length) {
4500
+ const text = new TextDecoder().decode(buffer);
4501
+ result.body_sample = text.slice(0, 1000);
4502
+ result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
4503
+ }
4504
+ } catch (error) {
4505
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
4506
+ }
4507
+ }
4508
+ result.ok = linkProbeAllowed(result.status, check)
4509
+ && linkProbeContentTypeAllowed(result, check)
4510
+ && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
4511
+ && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
4512
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
4513
+ && !result.error;
4514
+ return result;
4515
+ } catch (error) {
4516
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
4517
+ result.ok = false;
4518
+ return result;
4519
+ }
4520
+ }
4222
4521
  async function probeLinkStatus(candidate, check) {
4223
4522
  const requireNonzeroBytes = check.require_nonzero_bytes === true;
4224
4523
  const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
@@ -4911,6 +5210,7 @@ async function captureViewport(viewport) {
4911
5210
  const frames = {};
4912
5211
  const text_sequences = {};
4913
5212
  const text_matches = {};
5213
+ const http_statuses = {};
4914
5214
  const link_statuses = {};
4915
5215
  for (const check of profile.checks || []) {
4916
5216
  if (!profileCheckAppliesToViewport(check, viewport)) continue;
@@ -4937,6 +5237,10 @@ async function captureViewport(viewport) {
4937
5237
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
4938
5238
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
4939
5239
  }
5240
+ if (check.type === "http_status") {
5241
+ const key = httpStatusKey(check, page.url() || targetUrl);
5242
+ http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
5243
+ }
4940
5244
  if (check.type === "link_status" || check.type === "artifact_link_status") {
4941
5245
  const selector = linkStatusSelector(check);
4942
5246
  link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
@@ -5004,6 +5308,7 @@ async function captureViewport(viewport) {
5004
5308
  frames,
5005
5309
  text_sequences,
5006
5310
  text_matches,
5311
+ http_statuses,
5007
5312
  link_statuses,
5008
5313
  route_inventory: routeInventory,
5009
5314
  setup_action_results: setupActionResults,
@@ -5053,6 +5358,19 @@ function buildProfileEvidence(currentViewports) {
5053
5358
  ),
5054
5359
  })),
5055
5360
  })),
5361
+ http_status: currentViewports
5362
+ .filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
5363
+ .map((viewport) => ({
5364
+ viewport: viewport.name,
5365
+ requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
5366
+ key,
5367
+ url: statusSet && statusSet.url,
5368
+ method: statusSet && statusSet.method,
5369
+ status: statusSet && statusSet.status,
5370
+ ok: statusSet && statusSet.ok === true,
5371
+ error: statusSet && statusSet.error,
5372
+ })),
5373
+ })),
5056
5374
  link_status: currentViewports
5057
5375
  .filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
5058
5376
  .map((viewport) => ({
@@ -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_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"];
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", "http_status", "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];
@@ -114,6 +114,12 @@ interface RiddleProofProfileCheck {
114
114
  expected_url?: string;
115
115
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
116
116
  selector?: string;
117
+ url?: string;
118
+ method?: string;
119
+ headers?: Record<string, string>;
120
+ body?: string;
121
+ body_json?: JsonValue;
122
+ body_contains?: string[];
117
123
  expected_texts?: string[];
118
124
  link_selector?: string;
119
125
  source_selector?: string;
@@ -205,6 +211,7 @@ interface RiddleProofProfileViewportEvidence {
205
211
  frames?: Record<string, Record<string, JsonValue>>;
206
212
  text_sequences?: Record<string, Record<string, JsonValue>>;
207
213
  text_matches?: Record<string, boolean>;
214
+ http_statuses?: Record<string, Record<string, JsonValue>>;
208
215
  link_statuses?: Record<string, Record<string, JsonValue>>;
209
216
  route_inventory?: Record<string, JsonValue>;
210
217
  setup_action_results?: Array<Record<string, JsonValue>>;
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_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"];
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", "http_status", "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];
@@ -114,6 +114,12 @@ interface RiddleProofProfileCheck {
114
114
  expected_url?: string;
115
115
  expected_routes?: RiddleProofProfileRouteInventoryRoute[];
116
116
  selector?: string;
117
+ url?: string;
118
+ method?: string;
119
+ headers?: Record<string, string>;
120
+ body?: string;
121
+ body_json?: JsonValue;
122
+ body_contains?: string[];
117
123
  expected_texts?: string[];
118
124
  link_selector?: string;
119
125
  source_selector?: string;
@@ -205,6 +211,7 @@ interface RiddleProofProfileViewportEvidence {
205
211
  frames?: Record<string, Record<string, JsonValue>>;
206
212
  text_sequences?: Record<string, Record<string, JsonValue>>;
207
213
  text_matches?: Record<string, boolean>;
214
+ http_statuses?: Record<string, Record<string, JsonValue>>;
208
215
  link_statuses?: Record<string, Record<string, JsonValue>>;
209
216
  route_inventory?: Record<string, JsonValue>;
210
217
  setup_action_results?: Array<Record<string, JsonValue>>;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-G3UY3SHZ.js";
22
+ } from "./chunk-W5PBTTMJ.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.97",
3
+ "version": "0.7.99",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",