@riddledc/riddle-proof 0.7.98 → 0.7.100

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.
@@ -803,6 +803,19 @@ function normalizeCheck(input, index) {
803
803
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
804
804
  `checks[${index}] allowed_content_types`
805
805
  ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
806
+ const bodyContains = isHttpStatusCheck ? normalizeStringList(
807
+ input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
808
+ `checks[${index}] body_contains`
809
+ ) : void 0;
810
+ const bodyNotContains = isHttpStatusCheck ? normalizeStringList(
811
+ input.body_not_contains ?? input.bodyNotContains ?? input.expected_body_not_contains ?? input.expectedBodyNotContains ?? input.response_body_not_contains ?? input.responseBodyNotContains ?? input.body_absent ?? input.bodyAbsent,
812
+ `checks[${index}] body_not_contains`
813
+ ) : void 0;
814
+ const bodyNotPatterns = isHttpStatusCheck ? normalizeStringList(
815
+ input.body_not_patterns ?? input.bodyNotPatterns ?? input.expected_body_not_patterns ?? input.expectedBodyNotPatterns ?? input.response_body_not_patterns ?? input.responseBodyNotPatterns ?? input.body_forbidden_patterns ?? input.bodyForbiddenPatterns,
816
+ `checks[${index}] body_not_patterns`
817
+ ) : void 0;
818
+ if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
806
819
  if (isLinkStatusCheck) {
807
820
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
808
821
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -825,6 +838,9 @@ function normalizeCheck(input, index) {
825
838
  headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
826
839
  body: isHttpStatusCheck ? stringValue(input.body) : void 0,
827
840
  body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
841
+ body_contains: bodyContains,
842
+ body_not_contains: bodyNotContains,
843
+ body_not_patterns: bodyNotPatterns,
828
844
  expected_texts: expectedTexts,
829
845
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
830
846
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
@@ -996,6 +1012,24 @@ function linkStatusContentTypeOk(result, check) {
996
1012
  return actual === normalized;
997
1013
  });
998
1014
  }
1015
+ function httpStatusBodyContainsFailures(result, check) {
1016
+ const expected = check.body_contains?.filter(Boolean) ?? [];
1017
+ if (!expected.length) return [];
1018
+ const observed = isRecord(result.body_contains) ? result.body_contains : {};
1019
+ return expected.filter((text) => observed[text] !== true);
1020
+ }
1021
+ function httpStatusBodyNotContainsFailures(result, check) {
1022
+ const forbidden = check.body_not_contains?.filter(Boolean) ?? [];
1023
+ if (!forbidden.length) return [];
1024
+ const observed = isRecord(result.body_not_contains) ? result.body_not_contains : {};
1025
+ return forbidden.filter((text) => observed[text] !== false);
1026
+ }
1027
+ function httpStatusBodyNotPatternFailures(result, check) {
1028
+ const forbidden = check.body_not_patterns?.filter(Boolean) ?? [];
1029
+ if (!forbidden.length) return [];
1030
+ const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
1031
+ return forbidden.filter((pattern) => observed[pattern] !== false);
1032
+ }
999
1033
  function linkStatusResultOk(result, check) {
1000
1034
  const status = numberValue(result.status);
1001
1035
  if (!httpStatusIsAllowed(status, check)) return false;
@@ -1010,6 +1044,9 @@ function linkStatusResultOk(result, check) {
1010
1044
  const observedBytes = linkStatusObservedBytes(result);
1011
1045
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
1012
1046
  }
1047
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
1048
+ if (httpStatusBodyNotContainsFailures(result, check).length) return false;
1049
+ if (httpStatusBodyNotPatternFailures(result, check).length) return false;
1013
1050
  return true;
1014
1051
  }
1015
1052
  function httpStatusEvidenceForCheck(viewport, check) {
@@ -1031,6 +1068,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
1031
1068
  };
1032
1069
  }
1033
1070
  const failures = [];
1071
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
1072
+ const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
1073
+ const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
1034
1074
  if (!linkStatusResultOk(statusEvidence, check)) {
1035
1075
  failures.push({
1036
1076
  code: "http_status_failed",
@@ -1042,7 +1082,14 @@ function summarizeHttpStatusEvidence(viewport, check) {
1042
1082
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1043
1083
  allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
1044
1084
  min_bytes: check.min_bytes ?? null,
1045
- allowed_content_types: check.allowed_content_types ?? null
1085
+ allowed_content_types: check.allowed_content_types ?? null,
1086
+ body_contains: check.body_contains ?? null,
1087
+ body_contains_missing: bodyContainsMissing,
1088
+ body_not_contains: check.body_not_contains ?? null,
1089
+ body_not_contains_found: bodyNotContainsFound,
1090
+ body_not_patterns: check.body_not_patterns ?? null,
1091
+ body_not_patterns_found: bodyNotPatternsFound,
1092
+ body_sample: stringValue(statusEvidence.body_sample) ?? null
1046
1093
  });
1047
1094
  }
1048
1095
  return {
@@ -1057,6 +1104,13 @@ function summarizeHttpStatusEvidence(viewport, check) {
1057
1104
  content_type: stringValue(statusEvidence.content_type) ?? null,
1058
1105
  content_length: numberValue(statusEvidence.content_length) ?? null,
1059
1106
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1107
+ body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
1108
+ body_contains_missing: bodyContainsMissing,
1109
+ body_not_contains: isRecord(statusEvidence.body_not_contains) ? toJsonValue(statusEvidence.body_not_contains) : null,
1110
+ body_not_contains_found: bodyNotContainsFound,
1111
+ body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
1112
+ body_not_patterns_found: bodyNotPatternsFound,
1113
+ body_sample: stringValue(statusEvidence.body_sample) ?? null,
1060
1114
  failures
1061
1115
  };
1062
1116
  }
@@ -1666,6 +1720,9 @@ function assessCheckFromEvidence(check, evidence) {
1666
1720
  require_nonzero_bytes: check.require_nonzero_bytes === true,
1667
1721
  min_bytes: check.min_bytes ?? null,
1668
1722
  allowed_content_types: check.allowed_content_types ?? null,
1723
+ body_contains: check.body_contains ?? [],
1724
+ body_not_contains: check.body_not_contains ?? [],
1725
+ body_not_patterns: check.body_not_patterns ?? [],
1669
1726
  viewports: summaries.map((summary) => toJsonValue(summary)),
1670
1727
  failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
1671
1728
  },
@@ -2334,6 +2391,14 @@ function linkStatusContentTypeOk(result, check) {
2334
2391
  return actual === normalized;
2335
2392
  });
2336
2393
  }
2394
+ function httpStatusBodyContainsFailures(result, check) {
2395
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
2396
+ if (!expected.length) return [];
2397
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
2398
+ ? result.body_contains
2399
+ : {};
2400
+ return expected.filter((text) => observed[text] !== true);
2401
+ }
2337
2402
  function linkStatusResultOk(result, check) {
2338
2403
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
2339
2404
  if (!httpStatusIsAllowed(result.status, check)) return false;
@@ -2348,6 +2413,7 @@ function linkStatusResultOk(result, check) {
2348
2413
  const observedBytes = linkStatusObservedBytes(result);
2349
2414
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
2350
2415
  }
2416
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
2351
2417
  return true;
2352
2418
  }
2353
2419
  function summarizeHttpStatusEvidence(viewport, check) {
@@ -2365,6 +2431,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
2365
2431
  };
2366
2432
  }
2367
2433
  const failures = [];
2434
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
2368
2435
  if (!linkStatusResultOk(statusEvidence, check)) {
2369
2436
  failures.push({
2370
2437
  code: "http_status_failed",
@@ -2377,6 +2444,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
2377
2444
  allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
2378
2445
  min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
2379
2446
  allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
2447
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
2448
+ body_contains_missing: bodyContainsMissing,
2449
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2380
2450
  });
2381
2451
  }
2382
2452
  return {
@@ -2391,6 +2461,11 @@ function summarizeHttpStatusEvidence(viewport, check) {
2391
2461
  content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2392
2462
  content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
2393
2463
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2464
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
2465
+ ? statusEvidence.body_contains
2466
+ : null,
2467
+ body_contains_missing: bodyContainsMissing,
2468
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2394
2469
  failures,
2395
2470
  };
2396
2471
  }
@@ -4384,6 +4459,9 @@ async function collectHttpStatus(check) {
4384
4459
  } else if (typeof check.body === "string") {
4385
4460
  body = check.body;
4386
4461
  }
4462
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
4463
+ const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
4464
+ const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
4387
4465
  const options = {
4388
4466
  method,
4389
4467
  redirect: "follow",
@@ -4409,11 +4487,18 @@ async function collectHttpStatus(check) {
4409
4487
  Object.assign(result, linkProbeResponseFields(response, method));
4410
4488
  result.url = url;
4411
4489
  result.status_text = response.statusText || "";
4412
- const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
4490
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0;
4413
4491
  if (shouldReadBody) {
4414
4492
  try {
4415
4493
  const buffer = await response.arrayBuffer();
4416
4494
  result.bytes = buffer.byteLength;
4495
+ if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
4496
+ const text = new TextDecoder().decode(buffer);
4497
+ result.body_sample = text.slice(0, 1000);
4498
+ if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
4499
+ if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
4500
+ if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
4501
+ }
4417
4502
  } catch (error) {
4418
4503
  result.error = String(error && error.message ? error.message : error).slice(0, 500);
4419
4504
  }
@@ -4422,6 +4507,9 @@ async function collectHttpStatus(check) {
4422
4507
  && linkProbeContentTypeAllowed(result, check)
4423
4508
  && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
4424
4509
  && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
4510
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
4511
+ && (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
4512
+ && (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
4425
4513
  && !result.error;
4426
4514
  return result;
4427
4515
  } catch (error) {
package/dist/cli.cjs CHANGED
@@ -7696,6 +7696,19 @@ function normalizeCheck(input, index) {
7696
7696
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
7697
7697
  `checks[${index}] allowed_content_types`
7698
7698
  ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
7699
+ const bodyContains = isHttpStatusCheck ? normalizeStringList(
7700
+ input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
7701
+ `checks[${index}] body_contains`
7702
+ ) : void 0;
7703
+ const bodyNotContains = isHttpStatusCheck ? normalizeStringList(
7704
+ input.body_not_contains ?? input.bodyNotContains ?? input.expected_body_not_contains ?? input.expectedBodyNotContains ?? input.response_body_not_contains ?? input.responseBodyNotContains ?? input.body_absent ?? input.bodyAbsent,
7705
+ `checks[${index}] body_not_contains`
7706
+ ) : void 0;
7707
+ const bodyNotPatterns = isHttpStatusCheck ? normalizeStringList(
7708
+ input.body_not_patterns ?? input.bodyNotPatterns ?? input.expected_body_not_patterns ?? input.expectedBodyNotPatterns ?? input.response_body_not_patterns ?? input.responseBodyNotPatterns ?? input.body_forbidden_patterns ?? input.bodyForbiddenPatterns,
7709
+ `checks[${index}] body_not_patterns`
7710
+ ) : void 0;
7711
+ if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
7699
7712
  if (isLinkStatusCheck) {
7700
7713
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
7701
7714
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -7718,6 +7731,9 @@ function normalizeCheck(input, index) {
7718
7731
  headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
7719
7732
  body: isHttpStatusCheck ? stringValue2(input.body) : void 0,
7720
7733
  body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
7734
+ body_contains: bodyContains,
7735
+ body_not_contains: bodyNotContains,
7736
+ body_not_patterns: bodyNotPatterns,
7721
7737
  expected_texts: expectedTexts,
7722
7738
  link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
7723
7739
  source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
@@ -7889,6 +7905,24 @@ function linkStatusContentTypeOk(result, check) {
7889
7905
  return actual === normalized;
7890
7906
  });
7891
7907
  }
7908
+ function httpStatusBodyContainsFailures(result, check) {
7909
+ const expected = check.body_contains?.filter(Boolean) ?? [];
7910
+ if (!expected.length) return [];
7911
+ const observed = isRecord(result.body_contains) ? result.body_contains : {};
7912
+ return expected.filter((text) => observed[text] !== true);
7913
+ }
7914
+ function httpStatusBodyNotContainsFailures(result, check) {
7915
+ const forbidden = check.body_not_contains?.filter(Boolean) ?? [];
7916
+ if (!forbidden.length) return [];
7917
+ const observed = isRecord(result.body_not_contains) ? result.body_not_contains : {};
7918
+ return forbidden.filter((text) => observed[text] !== false);
7919
+ }
7920
+ function httpStatusBodyNotPatternFailures(result, check) {
7921
+ const forbidden = check.body_not_patterns?.filter(Boolean) ?? [];
7922
+ if (!forbidden.length) return [];
7923
+ const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
7924
+ return forbidden.filter((pattern) => observed[pattern] !== false);
7925
+ }
7892
7926
  function linkStatusResultOk(result, check) {
7893
7927
  const status = numberValue(result.status);
7894
7928
  if (!httpStatusIsAllowed(status, check)) return false;
@@ -7903,6 +7937,9 @@ function linkStatusResultOk(result, check) {
7903
7937
  const observedBytes = linkStatusObservedBytes(result);
7904
7938
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
7905
7939
  }
7940
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
7941
+ if (httpStatusBodyNotContainsFailures(result, check).length) return false;
7942
+ if (httpStatusBodyNotPatternFailures(result, check).length) return false;
7906
7943
  return true;
7907
7944
  }
7908
7945
  function httpStatusEvidenceForCheck(viewport, check) {
@@ -7924,6 +7961,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
7924
7961
  };
7925
7962
  }
7926
7963
  const failures = [];
7964
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
7965
+ const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
7966
+ const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
7927
7967
  if (!linkStatusResultOk(statusEvidence, check)) {
7928
7968
  failures.push({
7929
7969
  code: "http_status_failed",
@@ -7935,7 +7975,14 @@ function summarizeHttpStatusEvidence(viewport, check) {
7935
7975
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
7936
7976
  allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
7937
7977
  min_bytes: check.min_bytes ?? null,
7938
- allowed_content_types: check.allowed_content_types ?? null
7978
+ allowed_content_types: check.allowed_content_types ?? null,
7979
+ body_contains: check.body_contains ?? null,
7980
+ body_contains_missing: bodyContainsMissing,
7981
+ body_not_contains: check.body_not_contains ?? null,
7982
+ body_not_contains_found: bodyNotContainsFound,
7983
+ body_not_patterns: check.body_not_patterns ?? null,
7984
+ body_not_patterns_found: bodyNotPatternsFound,
7985
+ body_sample: stringValue2(statusEvidence.body_sample) ?? null
7939
7986
  });
7940
7987
  }
7941
7988
  return {
@@ -7950,6 +7997,13 @@ function summarizeHttpStatusEvidence(viewport, check) {
7950
7997
  content_type: stringValue2(statusEvidence.content_type) ?? null,
7951
7998
  content_length: numberValue(statusEvidence.content_length) ?? null,
7952
7999
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
8000
+ body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
8001
+ body_contains_missing: bodyContainsMissing,
8002
+ body_not_contains: isRecord(statusEvidence.body_not_contains) ? toJsonValue(statusEvidence.body_not_contains) : null,
8003
+ body_not_contains_found: bodyNotContainsFound,
8004
+ body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
8005
+ body_not_patterns_found: bodyNotPatternsFound,
8006
+ body_sample: stringValue2(statusEvidence.body_sample) ?? null,
7953
8007
  failures
7954
8008
  };
7955
8009
  }
@@ -8559,6 +8613,9 @@ function assessCheckFromEvidence(check, evidence) {
8559
8613
  require_nonzero_bytes: check.require_nonzero_bytes === true,
8560
8614
  min_bytes: check.min_bytes ?? null,
8561
8615
  allowed_content_types: check.allowed_content_types ?? null,
8616
+ body_contains: check.body_contains ?? [],
8617
+ body_not_contains: check.body_not_contains ?? [],
8618
+ body_not_patterns: check.body_not_patterns ?? [],
8562
8619
  viewports: summaries.map((summary) => toJsonValue(summary)),
8563
8620
  failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
8564
8621
  },
@@ -9211,6 +9268,14 @@ function linkStatusContentTypeOk(result, check) {
9211
9268
  return actual === normalized;
9212
9269
  });
9213
9270
  }
9271
+ function httpStatusBodyContainsFailures(result, check) {
9272
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
9273
+ if (!expected.length) return [];
9274
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
9275
+ ? result.body_contains
9276
+ : {};
9277
+ return expected.filter((text) => observed[text] !== true);
9278
+ }
9214
9279
  function linkStatusResultOk(result, check) {
9215
9280
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
9216
9281
  if (!httpStatusIsAllowed(result.status, check)) return false;
@@ -9225,6 +9290,7 @@ function linkStatusResultOk(result, check) {
9225
9290
  const observedBytes = linkStatusObservedBytes(result);
9226
9291
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
9227
9292
  }
9293
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
9228
9294
  return true;
9229
9295
  }
9230
9296
  function summarizeHttpStatusEvidence(viewport, check) {
@@ -9242,6 +9308,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
9242
9308
  };
9243
9309
  }
9244
9310
  const failures = [];
9311
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
9245
9312
  if (!linkStatusResultOk(statusEvidence, check)) {
9246
9313
  failures.push({
9247
9314
  code: "http_status_failed",
@@ -9254,6 +9321,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
9254
9321
  allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
9255
9322
  min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
9256
9323
  allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
9324
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
9325
+ body_contains_missing: bodyContainsMissing,
9326
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
9257
9327
  });
9258
9328
  }
9259
9329
  return {
@@ -9268,6 +9338,11 @@ function summarizeHttpStatusEvidence(viewport, check) {
9268
9338
  content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
9269
9339
  content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
9270
9340
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
9341
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
9342
+ ? statusEvidence.body_contains
9343
+ : null,
9344
+ body_contains_missing: bodyContainsMissing,
9345
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
9271
9346
  failures,
9272
9347
  };
9273
9348
  }
@@ -11261,6 +11336,9 @@ async function collectHttpStatus(check) {
11261
11336
  } else if (typeof check.body === "string") {
11262
11337
  body = check.body;
11263
11338
  }
11339
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
11340
+ const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
11341
+ const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
11264
11342
  const options = {
11265
11343
  method,
11266
11344
  redirect: "follow",
@@ -11286,11 +11364,18 @@ async function collectHttpStatus(check) {
11286
11364
  Object.assign(result, linkProbeResponseFields(response, method));
11287
11365
  result.url = url;
11288
11366
  result.status_text = response.statusText || "";
11289
- const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
11367
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0;
11290
11368
  if (shouldReadBody) {
11291
11369
  try {
11292
11370
  const buffer = await response.arrayBuffer();
11293
11371
  result.bytes = buffer.byteLength;
11372
+ if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
11373
+ const text = new TextDecoder().decode(buffer);
11374
+ result.body_sample = text.slice(0, 1000);
11375
+ if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
11376
+ if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
11377
+ if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
11378
+ }
11294
11379
  } catch (error) {
11295
11380
  result.error = String(error && error.message ? error.message : error).slice(0, 500);
11296
11381
  }
@@ -11299,6 +11384,9 @@ async function collectHttpStatus(check) {
11299
11384
  && linkProbeContentTypeAllowed(result, check)
11300
11385
  && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
11301
11386
  && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
11387
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
11388
+ && (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
11389
+ && (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
11302
11390
  && !result.error;
11303
11391
  return result;
11304
11392
  } catch (error) {
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-OMTWNL4B.js";
13
+ } from "./chunk-TRNLSX3U.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -9532,6 +9532,19 @@ function normalizeCheck(input, index) {
9532
9532
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
9533
9533
  `checks[${index}] allowed_content_types`
9534
9534
  ) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
9535
+ const bodyContains = isHttpStatusCheck ? normalizeStringList(
9536
+ input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
9537
+ `checks[${index}] body_contains`
9538
+ ) : void 0;
9539
+ const bodyNotContains = isHttpStatusCheck ? normalizeStringList(
9540
+ input.body_not_contains ?? input.bodyNotContains ?? input.expected_body_not_contains ?? input.expectedBodyNotContains ?? input.response_body_not_contains ?? input.responseBodyNotContains ?? input.body_absent ?? input.bodyAbsent,
9541
+ `checks[${index}] body_not_contains`
9542
+ ) : void 0;
9543
+ const bodyNotPatterns = isHttpStatusCheck ? normalizeStringList(
9544
+ input.body_not_patterns ?? input.bodyNotPatterns ?? input.expected_body_not_patterns ?? input.expectedBodyNotPatterns ?? input.response_body_not_patterns ?? input.responseBodyNotPatterns ?? input.body_forbidden_patterns ?? input.bodyForbiddenPatterns,
9545
+ `checks[${index}] body_not_patterns`
9546
+ ) : void 0;
9547
+ if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
9535
9548
  if (isLinkStatusCheck) {
9536
9549
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
9537
9550
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -9554,6 +9567,9 @@ function normalizeCheck(input, index) {
9554
9567
  headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
9555
9568
  body: isHttpStatusCheck ? stringValue5(input.body) : void 0,
9556
9569
  body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
9570
+ body_contains: bodyContains,
9571
+ body_not_contains: bodyNotContains,
9572
+ body_not_patterns: bodyNotPatterns,
9557
9573
  expected_texts: expectedTexts,
9558
9574
  link_selector: stringValue5(input.link_selector) || stringValue5(input.linkSelector),
9559
9575
  source_selector: stringValue5(input.source_selector) || stringValue5(input.sourceSelector),
@@ -9725,6 +9741,24 @@ function linkStatusContentTypeOk(result, check) {
9725
9741
  return actual === normalized;
9726
9742
  });
9727
9743
  }
9744
+ function httpStatusBodyContainsFailures(result, check) {
9745
+ const expected = check.body_contains?.filter(Boolean) ?? [];
9746
+ if (!expected.length) return [];
9747
+ const observed = isRecord2(result.body_contains) ? result.body_contains : {};
9748
+ return expected.filter((text) => observed[text] !== true);
9749
+ }
9750
+ function httpStatusBodyNotContainsFailures(result, check) {
9751
+ const forbidden = check.body_not_contains?.filter(Boolean) ?? [];
9752
+ if (!forbidden.length) return [];
9753
+ const observed = isRecord2(result.body_not_contains) ? result.body_not_contains : {};
9754
+ return forbidden.filter((text) => observed[text] !== false);
9755
+ }
9756
+ function httpStatusBodyNotPatternFailures(result, check) {
9757
+ const forbidden = check.body_not_patterns?.filter(Boolean) ?? [];
9758
+ if (!forbidden.length) return [];
9759
+ const observed = isRecord2(result.body_not_patterns) ? result.body_not_patterns : {};
9760
+ return forbidden.filter((pattern) => observed[pattern] !== false);
9761
+ }
9728
9762
  function linkStatusResultOk(result, check) {
9729
9763
  const status = numberValue3(result.status);
9730
9764
  if (!httpStatusIsAllowed(status, check)) return false;
@@ -9739,6 +9773,9 @@ function linkStatusResultOk(result, check) {
9739
9773
  const observedBytes = linkStatusObservedBytes(result);
9740
9774
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
9741
9775
  }
9776
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
9777
+ if (httpStatusBodyNotContainsFailures(result, check).length) return false;
9778
+ if (httpStatusBodyNotPatternFailures(result, check).length) return false;
9742
9779
  return true;
9743
9780
  }
9744
9781
  function httpStatusEvidenceForCheck(viewport, check) {
@@ -9760,6 +9797,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
9760
9797
  };
9761
9798
  }
9762
9799
  const failures = [];
9800
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
9801
+ const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
9802
+ const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
9763
9803
  if (!linkStatusResultOk(statusEvidence, check)) {
9764
9804
  failures.push({
9765
9805
  code: "http_status_failed",
@@ -9771,7 +9811,14 @@ function summarizeHttpStatusEvidence(viewport, check) {
9771
9811
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
9772
9812
  allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
9773
9813
  min_bytes: check.min_bytes ?? null,
9774
- allowed_content_types: check.allowed_content_types ?? null
9814
+ allowed_content_types: check.allowed_content_types ?? null,
9815
+ body_contains: check.body_contains ?? null,
9816
+ body_contains_missing: bodyContainsMissing,
9817
+ body_not_contains: check.body_not_contains ?? null,
9818
+ body_not_contains_found: bodyNotContainsFound,
9819
+ body_not_patterns: check.body_not_patterns ?? null,
9820
+ body_not_patterns_found: bodyNotPatternsFound,
9821
+ body_sample: stringValue5(statusEvidence.body_sample) ?? null
9775
9822
  });
9776
9823
  }
9777
9824
  return {
@@ -9786,6 +9833,13 @@ function summarizeHttpStatusEvidence(viewport, check) {
9786
9833
  content_type: stringValue5(statusEvidence.content_type) ?? null,
9787
9834
  content_length: numberValue3(statusEvidence.content_length) ?? null,
9788
9835
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
9836
+ body_contains: isRecord2(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
9837
+ body_contains_missing: bodyContainsMissing,
9838
+ body_not_contains: isRecord2(statusEvidence.body_not_contains) ? toJsonValue(statusEvidence.body_not_contains) : null,
9839
+ body_not_contains_found: bodyNotContainsFound,
9840
+ body_not_patterns: isRecord2(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
9841
+ body_not_patterns_found: bodyNotPatternsFound,
9842
+ body_sample: stringValue5(statusEvidence.body_sample) ?? null,
9789
9843
  failures
9790
9844
  };
9791
9845
  }
@@ -10395,6 +10449,9 @@ function assessCheckFromEvidence(check, evidence) {
10395
10449
  require_nonzero_bytes: check.require_nonzero_bytes === true,
10396
10450
  min_bytes: check.min_bytes ?? null,
10397
10451
  allowed_content_types: check.allowed_content_types ?? null,
10452
+ body_contains: check.body_contains ?? [],
10453
+ body_not_contains: check.body_not_contains ?? [],
10454
+ body_not_patterns: check.body_not_patterns ?? [],
10398
10455
  viewports: summaries.map((summary) => toJsonValue(summary)),
10399
10456
  failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue5(summary.viewport) ?? null, failure })) : [])
10400
10457
  },
@@ -11063,6 +11120,14 @@ function linkStatusContentTypeOk(result, check) {
11063
11120
  return actual === normalized;
11064
11121
  });
11065
11122
  }
11123
+ function httpStatusBodyContainsFailures(result, check) {
11124
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
11125
+ if (!expected.length) return [];
11126
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
11127
+ ? result.body_contains
11128
+ : {};
11129
+ return expected.filter((text) => observed[text] !== true);
11130
+ }
11066
11131
  function linkStatusResultOk(result, check) {
11067
11132
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
11068
11133
  if (!httpStatusIsAllowed(result.status, check)) return false;
@@ -11077,6 +11142,7 @@ function linkStatusResultOk(result, check) {
11077
11142
  const observedBytes = linkStatusObservedBytes(result);
11078
11143
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
11079
11144
  }
11145
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
11080
11146
  return true;
11081
11147
  }
11082
11148
  function summarizeHttpStatusEvidence(viewport, check) {
@@ -11094,6 +11160,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
11094
11160
  };
11095
11161
  }
11096
11162
  const failures = [];
11163
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
11097
11164
  if (!linkStatusResultOk(statusEvidence, check)) {
11098
11165
  failures.push({
11099
11166
  code: "http_status_failed",
@@ -11106,6 +11173,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
11106
11173
  allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
11107
11174
  min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
11108
11175
  allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
11176
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
11177
+ body_contains_missing: bodyContainsMissing,
11178
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
11109
11179
  });
11110
11180
  }
11111
11181
  return {
@@ -11120,6 +11190,11 @@ function summarizeHttpStatusEvidence(viewport, check) {
11120
11190
  content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
11121
11191
  content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
11122
11192
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
11193
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
11194
+ ? statusEvidence.body_contains
11195
+ : null,
11196
+ body_contains_missing: bodyContainsMissing,
11197
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
11123
11198
  failures,
11124
11199
  };
11125
11200
  }
@@ -13113,6 +13188,9 @@ async function collectHttpStatus(check) {
13113
13188
  } else if (typeof check.body === "string") {
13114
13189
  body = check.body;
13115
13190
  }
13191
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
13192
+ const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
13193
+ const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
13116
13194
  const options = {
13117
13195
  method,
13118
13196
  redirect: "follow",
@@ -13138,11 +13216,18 @@ async function collectHttpStatus(check) {
13138
13216
  Object.assign(result, linkProbeResponseFields(response, method));
13139
13217
  result.url = url;
13140
13218
  result.status_text = response.statusText || "";
13141
- const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
13219
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0;
13142
13220
  if (shouldReadBody) {
13143
13221
  try {
13144
13222
  const buffer = await response.arrayBuffer();
13145
13223
  result.bytes = buffer.byteLength;
13224
+ if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
13225
+ const text = new TextDecoder().decode(buffer);
13226
+ result.body_sample = text.slice(0, 1000);
13227
+ if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
13228
+ if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
13229
+ if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
13230
+ }
13146
13231
  } catch (error) {
13147
13232
  result.error = String(error && error.message ? error.message : error).slice(0, 500);
13148
13233
  }
@@ -13151,6 +13236,9 @@ async function collectHttpStatus(check) {
13151
13236
  && linkProbeContentTypeAllowed(result, check)
13152
13237
  && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
13153
13238
  && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
13239
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
13240
+ && (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
13241
+ && (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
13154
13242
  && !result.error;
13155
13243
  return result;
13156
13244
  } catch (error) {
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-OMTWNL4B.js";
61
+ } from "./chunk-TRNLSX3U.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -846,6 +846,19 @@ function normalizeCheck(input, index) {
846
846
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
847
847
  `checks[${index}] allowed_content_types`
848
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;
853
+ const bodyNotContains = isHttpStatusCheck ? normalizeStringList(
854
+ input.body_not_contains ?? input.bodyNotContains ?? input.expected_body_not_contains ?? input.expectedBodyNotContains ?? input.response_body_not_contains ?? input.responseBodyNotContains ?? input.body_absent ?? input.bodyAbsent,
855
+ `checks[${index}] body_not_contains`
856
+ ) : void 0;
857
+ const bodyNotPatterns = isHttpStatusCheck ? normalizeStringList(
858
+ input.body_not_patterns ?? input.bodyNotPatterns ?? input.expected_body_not_patterns ?? input.expectedBodyNotPatterns ?? input.response_body_not_patterns ?? input.responseBodyNotPatterns ?? input.body_forbidden_patterns ?? input.bodyForbiddenPatterns,
859
+ `checks[${index}] body_not_patterns`
860
+ ) : void 0;
861
+ if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
849
862
  if (isLinkStatusCheck) {
850
863
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
851
864
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -868,6 +881,9 @@ function normalizeCheck(input, index) {
868
881
  headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
869
882
  body: isHttpStatusCheck ? stringValue(input.body) : void 0,
870
883
  body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
884
+ body_contains: bodyContains,
885
+ body_not_contains: bodyNotContains,
886
+ body_not_patterns: bodyNotPatterns,
871
887
  expected_texts: expectedTexts,
872
888
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
873
889
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
@@ -1039,6 +1055,24 @@ function linkStatusContentTypeOk(result, check) {
1039
1055
  return actual === normalized;
1040
1056
  });
1041
1057
  }
1058
+ function httpStatusBodyContainsFailures(result, check) {
1059
+ const expected = check.body_contains?.filter(Boolean) ?? [];
1060
+ if (!expected.length) return [];
1061
+ const observed = isRecord(result.body_contains) ? result.body_contains : {};
1062
+ return expected.filter((text) => observed[text] !== true);
1063
+ }
1064
+ function httpStatusBodyNotContainsFailures(result, check) {
1065
+ const forbidden = check.body_not_contains?.filter(Boolean) ?? [];
1066
+ if (!forbidden.length) return [];
1067
+ const observed = isRecord(result.body_not_contains) ? result.body_not_contains : {};
1068
+ return forbidden.filter((text) => observed[text] !== false);
1069
+ }
1070
+ function httpStatusBodyNotPatternFailures(result, check) {
1071
+ const forbidden = check.body_not_patterns?.filter(Boolean) ?? [];
1072
+ if (!forbidden.length) return [];
1073
+ const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
1074
+ return forbidden.filter((pattern) => observed[pattern] !== false);
1075
+ }
1042
1076
  function linkStatusResultOk(result, check) {
1043
1077
  const status = numberValue(result.status);
1044
1078
  if (!httpStatusIsAllowed(status, check)) return false;
@@ -1053,6 +1087,9 @@ function linkStatusResultOk(result, check) {
1053
1087
  const observedBytes = linkStatusObservedBytes(result);
1054
1088
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
1055
1089
  }
1090
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
1091
+ if (httpStatusBodyNotContainsFailures(result, check).length) return false;
1092
+ if (httpStatusBodyNotPatternFailures(result, check).length) return false;
1056
1093
  return true;
1057
1094
  }
1058
1095
  function httpStatusEvidenceForCheck(viewport, check) {
@@ -1074,6 +1111,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
1074
1111
  };
1075
1112
  }
1076
1113
  const failures = [];
1114
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
1115
+ const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
1116
+ const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
1077
1117
  if (!linkStatusResultOk(statusEvidence, check)) {
1078
1118
  failures.push({
1079
1119
  code: "http_status_failed",
@@ -1085,7 +1125,14 @@ function summarizeHttpStatusEvidence(viewport, check) {
1085
1125
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1086
1126
  allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
1087
1127
  min_bytes: check.min_bytes ?? null,
1088
- allowed_content_types: check.allowed_content_types ?? null
1128
+ allowed_content_types: check.allowed_content_types ?? null,
1129
+ body_contains: check.body_contains ?? null,
1130
+ body_contains_missing: bodyContainsMissing,
1131
+ body_not_contains: check.body_not_contains ?? null,
1132
+ body_not_contains_found: bodyNotContainsFound,
1133
+ body_not_patterns: check.body_not_patterns ?? null,
1134
+ body_not_patterns_found: bodyNotPatternsFound,
1135
+ body_sample: stringValue(statusEvidence.body_sample) ?? null
1089
1136
  });
1090
1137
  }
1091
1138
  return {
@@ -1100,6 +1147,13 @@ function summarizeHttpStatusEvidence(viewport, check) {
1100
1147
  content_type: stringValue(statusEvidence.content_type) ?? null,
1101
1148
  content_length: numberValue(statusEvidence.content_length) ?? null,
1102
1149
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
1150
+ body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
1151
+ body_contains_missing: bodyContainsMissing,
1152
+ body_not_contains: isRecord(statusEvidence.body_not_contains) ? toJsonValue(statusEvidence.body_not_contains) : null,
1153
+ body_not_contains_found: bodyNotContainsFound,
1154
+ body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
1155
+ body_not_patterns_found: bodyNotPatternsFound,
1156
+ body_sample: stringValue(statusEvidence.body_sample) ?? null,
1103
1157
  failures
1104
1158
  };
1105
1159
  }
@@ -1709,6 +1763,9 @@ function assessCheckFromEvidence(check, evidence) {
1709
1763
  require_nonzero_bytes: check.require_nonzero_bytes === true,
1710
1764
  min_bytes: check.min_bytes ?? null,
1711
1765
  allowed_content_types: check.allowed_content_types ?? null,
1766
+ body_contains: check.body_contains ?? [],
1767
+ body_not_contains: check.body_not_contains ?? [],
1768
+ body_not_patterns: check.body_not_patterns ?? [],
1712
1769
  viewports: summaries.map((summary) => toJsonValue(summary)),
1713
1770
  failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
1714
1771
  },
@@ -2377,6 +2434,14 @@ function linkStatusContentTypeOk(result, check) {
2377
2434
  return actual === normalized;
2378
2435
  });
2379
2436
  }
2437
+ function httpStatusBodyContainsFailures(result, check) {
2438
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
2439
+ if (!expected.length) return [];
2440
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
2441
+ ? result.body_contains
2442
+ : {};
2443
+ return expected.filter((text) => observed[text] !== true);
2444
+ }
2380
2445
  function linkStatusResultOk(result, check) {
2381
2446
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
2382
2447
  if (!httpStatusIsAllowed(result.status, check)) return false;
@@ -2391,6 +2456,7 @@ function linkStatusResultOk(result, check) {
2391
2456
  const observedBytes = linkStatusObservedBytes(result);
2392
2457
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
2393
2458
  }
2459
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
2394
2460
  return true;
2395
2461
  }
2396
2462
  function summarizeHttpStatusEvidence(viewport, check) {
@@ -2408,6 +2474,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
2408
2474
  };
2409
2475
  }
2410
2476
  const failures = [];
2477
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
2411
2478
  if (!linkStatusResultOk(statusEvidence, check)) {
2412
2479
  failures.push({
2413
2480
  code: "http_status_failed",
@@ -2420,6 +2487,9 @@ function summarizeHttpStatusEvidence(viewport, check) {
2420
2487
  allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
2421
2488
  min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
2422
2489
  allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
2490
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
2491
+ body_contains_missing: bodyContainsMissing,
2492
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2423
2493
  });
2424
2494
  }
2425
2495
  return {
@@ -2434,6 +2504,11 @@ function summarizeHttpStatusEvidence(viewport, check) {
2434
2504
  content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
2435
2505
  content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
2436
2506
  bytes: linkStatusObservedBytes(statusEvidence) ?? null,
2507
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
2508
+ ? statusEvidence.body_contains
2509
+ : null,
2510
+ body_contains_missing: bodyContainsMissing,
2511
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
2437
2512
  failures,
2438
2513
  };
2439
2514
  }
@@ -4427,6 +4502,9 @@ async function collectHttpStatus(check) {
4427
4502
  } else if (typeof check.body === "string") {
4428
4503
  body = check.body;
4429
4504
  }
4505
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
4506
+ const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
4507
+ const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
4430
4508
  const options = {
4431
4509
  method,
4432
4510
  redirect: "follow",
@@ -4452,11 +4530,18 @@ async function collectHttpStatus(check) {
4452
4530
  Object.assign(result, linkProbeResponseFields(response, method));
4453
4531
  result.url = url;
4454
4532
  result.status_text = response.statusText || "";
4455
- const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
4533
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0;
4456
4534
  if (shouldReadBody) {
4457
4535
  try {
4458
4536
  const buffer = await response.arrayBuffer();
4459
4537
  result.bytes = buffer.byteLength;
4538
+ if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
4539
+ const text = new TextDecoder().decode(buffer);
4540
+ result.body_sample = text.slice(0, 1000);
4541
+ if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
4542
+ if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
4543
+ if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
4544
+ }
4460
4545
  } catch (error) {
4461
4546
  result.error = String(error && error.message ? error.message : error).slice(0, 500);
4462
4547
  }
@@ -4465,6 +4550,9 @@ async function collectHttpStatus(check) {
4465
4550
  && linkProbeContentTypeAllowed(result, check)
4466
4551
  && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
4467
4552
  && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
4553
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
4554
+ && (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
4555
+ && (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
4468
4556
  && !result.error;
4469
4557
  return result;
4470
4558
  } catch (error) {
@@ -119,6 +119,9 @@ interface RiddleProofProfileCheck {
119
119
  headers?: Record<string, string>;
120
120
  body?: string;
121
121
  body_json?: JsonValue;
122
+ body_contains?: string[];
123
+ body_not_contains?: string[];
124
+ body_not_patterns?: string[];
122
125
  expected_texts?: string[];
123
126
  link_selector?: string;
124
127
  source_selector?: string;
package/dist/profile.d.ts CHANGED
@@ -119,6 +119,9 @@ interface RiddleProofProfileCheck {
119
119
  headers?: Record<string, string>;
120
120
  body?: string;
121
121
  body_json?: JsonValue;
122
+ body_contains?: string[];
123
+ body_not_contains?: string[];
124
+ body_not_patterns?: string[];
122
125
  expected_texts?: string[];
123
126
  link_selector?: string;
124
127
  source_selector?: string;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-OMTWNL4B.js";
22
+ } from "./chunk-TRNLSX3U.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.98",
3
+ "version": "0.7.100",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",