@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/cli.cjs CHANGED
@@ -6922,6 +6922,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6922
6922
  "frame_no_horizontal_overflow",
6923
6923
  "text_visible",
6924
6924
  "text_absent",
6925
+ "http_status",
6925
6926
  "link_status",
6926
6927
  "artifact_link_status",
6927
6928
  "route_inventory",
@@ -7671,19 +7672,34 @@ function normalizeCheck(input, index) {
7671
7672
  if (type === "route_inventory" && !expectedRoutes?.length) {
7672
7673
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
7673
7674
  }
7675
+ const isHttpStatusCheck = type === "http_status";
7674
7676
  const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
7675
- const expectedStatus = isLinkStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
7676
- const allowedStatuses = isLinkStatusCheck ? normalizeHttpStatuses(
7677
+ const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
7678
+ const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
7679
+ if (isHttpStatusCheck && !requestUrl) {
7680
+ throw new Error(`checks[${index}] http_status requires url.`);
7681
+ }
7682
+ const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
7683
+ if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
7684
+ throw new Error(`checks[${index}] http_status method must contain only letters.`);
7685
+ }
7686
+ const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
7687
+ const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
7688
+ const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
7677
7689
  input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
7678
7690
  `checks[${index}] allowed_statuses`
7679
7691
  ) : void 0;
7680
7692
  const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
7681
- const minBytes = isLinkStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
7682
- const expectedContentType = isLinkStatusCheck ? stringValue2(input.expected_content_type) || stringValue2(input.expectedContentType) || stringValue2(input.content_type) || stringValue2(input.contentType) : void 0;
7683
- const allowedContentTypes = isLinkStatusCheck ? normalizeStringList(
7693
+ const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
7694
+ const expectedContentType = isStatusCheck ? stringValue2(input.expected_content_type) || stringValue2(input.expectedContentType) || stringValue2(input.content_type) || stringValue2(input.contentType) : void 0;
7695
+ const allowedContentTypes = isStatusCheck ? normalizeStringList(
7684
7696
  input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
7685
7697
  `checks[${index}] allowed_content_types`
7686
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;
7687
7703
  if (isLinkStatusCheck) {
7688
7704
  if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
7689
7705
  throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
@@ -7701,6 +7717,12 @@ function normalizeCheck(input, index) {
7701
7717
  expected_url: expectedUrl,
7702
7718
  expected_routes: expectedRoutes,
7703
7719
  selector: stringValue2(input.selector),
7720
+ url: isHttpStatusCheck ? requestUrl : void 0,
7721
+ method: isHttpStatusCheck ? method || "GET" : void 0,
7722
+ headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
7723
+ body: isHttpStatusCheck ? stringValue2(input.body) : void 0,
7724
+ body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
7725
+ body_contains: bodyContains,
7704
7726
  expected_texts: expectedTexts,
7705
7727
  link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
7706
7728
  source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
@@ -7724,7 +7746,7 @@ function normalizeCheck(input, index) {
7724
7746
  max_links: maxLinks,
7725
7747
  same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
7726
7748
  dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
7727
- require_nonzero_bytes: isLinkStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
7749
+ require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
7728
7750
  min_bytes: minBytes,
7729
7751
  allowed_content_types: allowedContentTypes,
7730
7752
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
@@ -7823,14 +7845,32 @@ function selectorKey(check) {
7823
7845
  function linkStatusSelector(check) {
7824
7846
  return check.selector || check.link_selector || "a[href]";
7825
7847
  }
7826
- function linkStatusAllowedStatuses(check) {
7848
+ function httpStatusRequestUrl(check, baseUrl) {
7849
+ const rawUrl = check.url || "";
7850
+ if (!rawUrl) return "";
7851
+ try {
7852
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
7853
+ } catch {
7854
+ return rawUrl;
7855
+ }
7856
+ }
7857
+ function httpStatusMethod(check) {
7858
+ return (check.method || "GET").toUpperCase();
7859
+ }
7860
+ function httpStatusKey(check, baseUrl) {
7861
+ return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
7862
+ }
7863
+ function httpStatusAllowedStatuses(check) {
7827
7864
  if (check.allowed_statuses?.length) return check.allowed_statuses;
7828
7865
  if (check.expected_status !== void 0) return [check.expected_status];
7829
7866
  return void 0;
7830
7867
  }
7831
- function linkStatusIsAllowed(status, check) {
7868
+ function linkStatusAllowedStatuses(check) {
7869
+ return httpStatusAllowedStatuses(check);
7870
+ }
7871
+ function httpStatusIsAllowed(status, check) {
7832
7872
  if (status === void 0) return false;
7833
- const allowed = linkStatusAllowedStatuses(check);
7873
+ const allowed = httpStatusAllowedStatuses(check);
7834
7874
  return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
7835
7875
  }
7836
7876
  function linkStatusObservedBytes(result) {
@@ -7854,9 +7894,15 @@ function linkStatusContentTypeOk(result, check) {
7854
7894
  return actual === normalized;
7855
7895
  });
7856
7896
  }
7897
+ function httpStatusBodyContainsFailures(result, check) {
7898
+ const expected = check.body_contains?.filter(Boolean) ?? [];
7899
+ if (!expected.length) return [];
7900
+ const observed = isRecord(result.body_contains) ? result.body_contains : {};
7901
+ return expected.filter((text) => observed[text] !== true);
7902
+ }
7857
7903
  function linkStatusResultOk(result, check) {
7858
7904
  const status = numberValue(result.status);
7859
- if (!linkStatusIsAllowed(status, check)) return false;
7905
+ if (!httpStatusIsAllowed(status, check)) return false;
7860
7906
  if (stringValue2(result.error)) return false;
7861
7907
  if (result.ok === false) return false;
7862
7908
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -7868,8 +7914,64 @@ function linkStatusResultOk(result, check) {
7868
7914
  const observedBytes = linkStatusObservedBytes(result);
7869
7915
  if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
7870
7916
  }
7917
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
7871
7918
  return true;
7872
7919
  }
7920
+ function httpStatusEvidenceForCheck(viewport, check) {
7921
+ const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
7922
+ return isRecord(evidence) ? evidence : void 0;
7923
+ }
7924
+ function summarizeHttpStatusEvidence(viewport, check) {
7925
+ const key = httpStatusKey(check, viewport.url);
7926
+ const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
7927
+ if (!statusEvidence) {
7928
+ return {
7929
+ viewport: viewport.name,
7930
+ key,
7931
+ url: httpStatusRequestUrl(check, viewport.url),
7932
+ method: httpStatusMethod(check),
7933
+ ok: false,
7934
+ status: null,
7935
+ failures: [{ code: "http_status_evidence_missing" }]
7936
+ };
7937
+ }
7938
+ const failures = [];
7939
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
7940
+ if (!linkStatusResultOk(statusEvidence, check)) {
7941
+ failures.push({
7942
+ code: "http_status_failed",
7943
+ url: stringValue2(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
7944
+ status: numberValue(statusEvidence.status) ?? null,
7945
+ method: stringValue2(statusEvidence.method) ?? httpStatusMethod(check),
7946
+ error: stringValue2(statusEvidence.error) ?? null,
7947
+ content_type: stringValue2(statusEvidence.content_type) ?? null,
7948
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
7949
+ allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
7950
+ min_bytes: check.min_bytes ?? null,
7951
+ allowed_content_types: check.allowed_content_types ?? null,
7952
+ body_contains: check.body_contains ?? null,
7953
+ body_contains_missing: bodyContainsMissing,
7954
+ body_sample: stringValue2(statusEvidence.body_sample) ?? null
7955
+ });
7956
+ }
7957
+ return {
7958
+ viewport: viewport.name,
7959
+ key,
7960
+ url: stringValue2(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
7961
+ method: stringValue2(statusEvidence.method) ?? httpStatusMethod(check),
7962
+ status: numberValue(statusEvidence.status) ?? null,
7963
+ status_text: stringValue2(statusEvidence.status_text) ?? null,
7964
+ ok: failures.length === 0,
7965
+ error: stringValue2(statusEvidence.error) ?? null,
7966
+ content_type: stringValue2(statusEvidence.content_type) ?? null,
7967
+ content_length: numberValue(statusEvidence.content_length) ?? null,
7968
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
7969
+ body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
7970
+ body_contains_missing: bodyContainsMissing,
7971
+ body_sample: stringValue2(statusEvidence.body_sample) ?? null,
7972
+ failures
7973
+ };
7974
+ }
7873
7975
  function linkStatusEvidenceForCheck(viewport, check) {
7874
7976
  const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
7875
7977
  return isRecord(evidence) ? evidence : void 0;
@@ -8460,6 +8562,29 @@ function assessCheckFromEvidence(check, evidence) {
8460
8562
  message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
8461
8563
  };
8462
8564
  }
8565
+ if (check.type === "http_status") {
8566
+ const url = httpStatusRequestUrl(check, evidence.target_url);
8567
+ const method = httpStatusMethod(check);
8568
+ const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
8569
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
8570
+ return {
8571
+ type: check.type,
8572
+ label: checkLabel(check),
8573
+ status: failed.length ? "failed" : "passed",
8574
+ evidence: {
8575
+ url,
8576
+ method,
8577
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
8578
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
8579
+ min_bytes: check.min_bytes ?? null,
8580
+ allowed_content_types: check.allowed_content_types ?? null,
8581
+ body_contains: check.body_contains ?? [],
8582
+ viewports: summaries.map((summary) => toJsonValue(summary)),
8583
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
8584
+ },
8585
+ message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
8586
+ };
8587
+ }
8463
8588
  if (check.type === "link_status" || check.type === "artifact_link_status") {
8464
8589
  const selector = linkStatusSelector(check);
8465
8590
  const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -9053,16 +9178,37 @@ function textOrderMatch(texts, expectedTexts) {
9053
9178
  function linkStatusSelector(check) {
9054
9179
  return check.selector || check.link_selector || "a[href]";
9055
9180
  }
9056
- function linkStatusAllowedStatuses(check) {
9181
+ function httpStatusRequestUrl(check, baseUrl) {
9182
+ const rawUrl = check.url || "";
9183
+ if (!rawUrl) return "";
9184
+ try {
9185
+ return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
9186
+ } catch {
9187
+ return rawUrl;
9188
+ }
9189
+ }
9190
+ function httpStatusMethod(check) {
9191
+ return String(check.method || "GET").toUpperCase();
9192
+ }
9193
+ function httpStatusKey(check, baseUrl) {
9194
+ return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
9195
+ }
9196
+ function httpStatusAllowedStatuses(check) {
9057
9197
  if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
9058
9198
  if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
9059
9199
  return undefined;
9060
9200
  }
9061
- function linkStatusIsAllowed(status, check) {
9201
+ function linkStatusAllowedStatuses(check) {
9202
+ return httpStatusAllowedStatuses(check);
9203
+ }
9204
+ function httpStatusIsAllowed(status, check) {
9062
9205
  if (typeof status !== "number" || !Number.isFinite(status)) return false;
9063
- const allowed = linkStatusAllowedStatuses(check);
9206
+ const allowed = httpStatusAllowedStatuses(check);
9064
9207
  return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
9065
9208
  }
9209
+ function linkStatusIsAllowed(status, check) {
9210
+ return httpStatusIsAllowed(status, check);
9211
+ }
9066
9212
  function linkStatusObservedBytes(result) {
9067
9213
  const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
9068
9214
  const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
@@ -9085,9 +9231,17 @@ function linkStatusContentTypeOk(result, check) {
9085
9231
  return actual === normalized;
9086
9232
  });
9087
9233
  }
9234
+ function httpStatusBodyContainsFailures(result, check) {
9235
+ const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
9236
+ if (!expected.length) return [];
9237
+ const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
9238
+ ? result.body_contains
9239
+ : {};
9240
+ return expected.filter((text) => observed[text] !== true);
9241
+ }
9088
9242
  function linkStatusResultOk(result, check) {
9089
9243
  if (!result || typeof result !== "object" || Array.isArray(result)) return false;
9090
- if (!linkStatusIsAllowed(result.status, check)) return false;
9244
+ if (!httpStatusIsAllowed(result.status, check)) return false;
9091
9245
  if (typeof result.error === "string" && result.error.trim()) return false;
9092
9246
  if (result.ok === false) return false;
9093
9247
  if (!linkStatusContentTypeOk(result, check)) return false;
@@ -9099,8 +9253,62 @@ function linkStatusResultOk(result, check) {
9099
9253
  const observedBytes = linkStatusObservedBytes(result);
9100
9254
  if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
9101
9255
  }
9256
+ if (httpStatusBodyContainsFailures(result, check).length) return false;
9102
9257
  return true;
9103
9258
  }
9259
+ function summarizeHttpStatusEvidence(viewport, check) {
9260
+ const key = httpStatusKey(check, viewport && viewport.url);
9261
+ const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
9262
+ if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
9263
+ return {
9264
+ viewport: viewport && viewport.name,
9265
+ key,
9266
+ url: httpStatusRequestUrl(check, viewport && viewport.url),
9267
+ method: httpStatusMethod(check),
9268
+ ok: false,
9269
+ status: null,
9270
+ failures: [{ code: "http_status_evidence_missing" }],
9271
+ };
9272
+ }
9273
+ const failures = [];
9274
+ const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
9275
+ if (!linkStatusResultOk(statusEvidence, check)) {
9276
+ failures.push({
9277
+ code: "http_status_failed",
9278
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
9279
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
9280
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
9281
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
9282
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
9283
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
9284
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
9285
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
9286
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
9287
+ body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
9288
+ body_contains_missing: bodyContainsMissing,
9289
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
9290
+ });
9291
+ }
9292
+ return {
9293
+ viewport: viewport && viewport.name,
9294
+ key,
9295
+ url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
9296
+ method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
9297
+ status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
9298
+ status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
9299
+ ok: failures.length === 0,
9300
+ error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
9301
+ content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
9302
+ content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
9303
+ bytes: linkStatusObservedBytes(statusEvidence) ?? null,
9304
+ body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
9305
+ ? statusEvidence.body_contains
9306
+ : null,
9307
+ body_contains_missing: bodyContainsMissing,
9308
+ body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
9309
+ failures,
9310
+ };
9311
+ }
9104
9312
  function summarizeLinkStatusEvidence(viewport, check) {
9105
9313
  const selector = linkStatusSelector(check);
9106
9314
  const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
@@ -9856,6 +10064,31 @@ function assessProfile(profile, evidence) {
9856
10064
  });
9857
10065
  continue;
9858
10066
  }
10067
+ if (check.type === "http_status") {
10068
+ const url = httpStatusRequestUrl(check, evidence.target_url);
10069
+ const method = httpStatusMethod(check);
10070
+ const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
10071
+ const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
10072
+ checks.push({
10073
+ type: check.type,
10074
+ label: check.label || check.type,
10075
+ status: failed.length ? "failed" : "passed",
10076
+ evidence: {
10077
+ url,
10078
+ method,
10079
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
10080
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
10081
+ min_bytes: check.min_bytes ?? null,
10082
+ allowed_content_types: check.allowed_content_types ?? null,
10083
+ viewports: summaries,
10084
+ failures: failed.flatMap((summary) => Array.isArray(summary.failures)
10085
+ ? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
10086
+ : []),
10087
+ },
10088
+ message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
10089
+ });
10090
+ continue;
10091
+ }
9859
10092
  if (check.type === "link_status" || check.type === "artifact_link_status") {
9860
10093
  const selector = linkStatusSelector(check);
9861
10094
  const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
@@ -11053,6 +11286,72 @@ function linkProbeResponseFields(response, method) {
11053
11286
  content_length: contentLength,
11054
11287
  };
11055
11288
  }
11289
+ async function collectHttpStatus(check) {
11290
+ const url = httpStatusRequestUrl(check, page.url() || targetUrl);
11291
+ const method = httpStatusMethod(check);
11292
+ const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
11293
+ ? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
11294
+ : {};
11295
+ let body;
11296
+ if (check.body_json !== undefined) {
11297
+ body = JSON.stringify(check.body_json);
11298
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
11299
+ } else if (typeof check.body === "string") {
11300
+ body = check.body;
11301
+ }
11302
+ const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
11303
+ const options = {
11304
+ method,
11305
+ redirect: "follow",
11306
+ cache: "no-store",
11307
+ headers,
11308
+ };
11309
+ if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
11310
+ const result = {
11311
+ version: "riddle-proof.http-status.v1",
11312
+ url,
11313
+ method,
11314
+ status: null,
11315
+ ok: false,
11316
+ error: null,
11317
+ request_body_bytes: typeof body === "string" ? body.length : 0,
11318
+ allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
11319
+ require_nonzero_bytes: check.require_nonzero_bytes === true,
11320
+ min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
11321
+ allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
11322
+ };
11323
+ try {
11324
+ const response = await fetch(url, options);
11325
+ Object.assign(result, linkProbeResponseFields(response, method));
11326
+ result.url = url;
11327
+ result.status_text = response.statusText || "";
11328
+ const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0;
11329
+ if (shouldReadBody) {
11330
+ try {
11331
+ const buffer = await response.arrayBuffer();
11332
+ result.bytes = buffer.byteLength;
11333
+ if (bodyContains.length) {
11334
+ const text = new TextDecoder().decode(buffer);
11335
+ result.body_sample = text.slice(0, 1000);
11336
+ result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
11337
+ }
11338
+ } catch (error) {
11339
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
11340
+ }
11341
+ }
11342
+ result.ok = linkProbeAllowed(result.status, check)
11343
+ && linkProbeContentTypeAllowed(result, check)
11344
+ && (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
11345
+ && (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
11346
+ && (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
11347
+ && !result.error;
11348
+ return result;
11349
+ } catch (error) {
11350
+ result.error = String(error && error.message ? error.message : error).slice(0, 500);
11351
+ result.ok = false;
11352
+ return result;
11353
+ }
11354
+ }
11056
11355
  async function probeLinkStatus(candidate, check) {
11057
11356
  const requireNonzeroBytes = check.require_nonzero_bytes === true;
11058
11357
  const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
@@ -11745,6 +12044,7 @@ async function captureViewport(viewport) {
11745
12044
  const frames = {};
11746
12045
  const text_sequences = {};
11747
12046
  const text_matches = {};
12047
+ const http_statuses = {};
11748
12048
  const link_statuses = {};
11749
12049
  for (const check of profile.checks || []) {
11750
12050
  if (!profileCheckAppliesToViewport(check, viewport)) continue;
@@ -11771,6 +12071,10 @@ async function captureViewport(viewport) {
11771
12071
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
11772
12072
  frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
11773
12073
  }
12074
+ if (check.type === "http_status") {
12075
+ const key = httpStatusKey(check, page.url() || targetUrl);
12076
+ http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
12077
+ }
11774
12078
  if (check.type === "link_status" || check.type === "artifact_link_status") {
11775
12079
  const selector = linkStatusSelector(check);
11776
12080
  link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
@@ -11838,6 +12142,7 @@ async function captureViewport(viewport) {
11838
12142
  frames,
11839
12143
  text_sequences,
11840
12144
  text_matches,
12145
+ http_statuses,
11841
12146
  link_statuses,
11842
12147
  route_inventory: routeInventory,
11843
12148
  setup_action_results: setupActionResults,
@@ -11887,6 +12192,19 @@ function buildProfileEvidence(currentViewports) {
11887
12192
  ),
11888
12193
  })),
11889
12194
  })),
12195
+ http_status: currentViewports
12196
+ .filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
12197
+ .map((viewport) => ({
12198
+ viewport: viewport.name,
12199
+ requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
12200
+ key,
12201
+ url: statusSet && statusSet.url,
12202
+ method: statusSet && statusSet.method,
12203
+ status: statusSet && statusSet.status,
12204
+ ok: statusSet && statusSet.ok === true,
12205
+ error: statusSet && statusSet.error,
12206
+ })),
12207
+ })),
11890
12208
  link_status: currentViewports
11891
12209
  .filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
11892
12210
  .map((viewport) => ({
@@ -12344,6 +12662,10 @@ function profileResultMarkdown(result) {
12344
12662
  if (linkStatusSummaryLines.length) {
12345
12663
  lines.push("", "## Link Status", "", ...linkStatusSummaryLines);
12346
12664
  }
12665
+ const httpStatusSummaryLines = profileHttpStatusSummaryMarkdown(result);
12666
+ if (httpStatusSummaryLines.length) {
12667
+ lines.push("", "## HTTP Status", "", ...httpStatusSummaryLines);
12668
+ }
12347
12669
  const environmentBlockerLines = profileEnvironmentBlockerMarkdown(result);
12348
12670
  if (environmentBlockerLines.length) {
12349
12671
  lines.push("", "## Environment Blocker", "", ...environmentBlockerLines);
@@ -12420,6 +12742,13 @@ function profileCheckMarkdownTarget(check) {
12420
12742
  if (selector && maxOverflow !== void 0) return `${markdownInlineCode(selector)} <= ${maxOverflow}px`;
12421
12743
  return selector ? markdownInlineCode(selector) : maxOverflow !== void 0 ? `<= ${maxOverflow}px` : void 0;
12422
12744
  }
12745
+ if (check.type === "http_status") {
12746
+ const url = cliString(evidence.url);
12747
+ const method = cliString(evidence.method);
12748
+ const statuses = Array.isArray(evidence.allowed_statuses) ? evidence.allowed_statuses.map((status) => cliString(status) || String(status)).filter(Boolean) : [];
12749
+ const target = [method, url ? markdownInlineCode(url) : ""].filter(Boolean).join(" ");
12750
+ return statuses.length ? `${target} -> ${statuses.join("/")}` : target || void 0;
12751
+ }
12423
12752
  if (check.type === "link_status" || check.type === "artifact_link_status") {
12424
12753
  const expectedCount = cliFiniteNumber(evidence.expected_count);
12425
12754
  const minCount = cliFiniteNumber(evidence.min_count);
@@ -12647,6 +12976,24 @@ function profileLinkStatusSummaryMarkdown(result) {
12647
12976
  }
12648
12977
  return lines;
12649
12978
  }
12979
+ function profileHttpStatusSummaryMarkdown(result) {
12980
+ const httpStatusChecks = result.checks.filter((check) => check.type === "http_status");
12981
+ const lines = [];
12982
+ for (const check of httpStatusChecks) {
12983
+ const evidence = cliRecord(check.evidence);
12984
+ if (!evidence) continue;
12985
+ const label = check.label || check.type;
12986
+ const url = cliString(evidence.url);
12987
+ const method = cliString(evidence.method) || "GET";
12988
+ const viewports = Array.isArray(evidence.viewports) ? evidence.viewports.map(cliRecord).filter((viewport) => Boolean(viewport)) : [];
12989
+ const statuses = viewports.map((viewport) => cliFiniteNumber(viewport.status)).map((status) => status === void 0 ? "error" : String(status));
12990
+ const failedTotal = Array.isArray(evidence.failures) ? evidence.failures.length : 0;
12991
+ lines.push(
12992
+ `- ${label}: ${method}${url ? ` ${markdownInlineCode(url)}` : ""}, statuses ${statuses.length ? statuses.join("/") : "unknown"}, failures ${failedTotal}`
12993
+ );
12994
+ }
12995
+ return lines;
12996
+ }
12650
12997
  function writeProfileOutput(outputDir, result) {
12651
12998
  if (!outputDir) return;
12652
12999
  (0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-G3UY3SHZ.js";
13
+ } from "./chunk-W5PBTTMJ.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
@@ -346,6 +346,10 @@ function profileResultMarkdown(result) {
346
346
  if (linkStatusSummaryLines.length) {
347
347
  lines.push("", "## Link Status", "", ...linkStatusSummaryLines);
348
348
  }
349
+ const httpStatusSummaryLines = profileHttpStatusSummaryMarkdown(result);
350
+ if (httpStatusSummaryLines.length) {
351
+ lines.push("", "## HTTP Status", "", ...httpStatusSummaryLines);
352
+ }
349
353
  const environmentBlockerLines = profileEnvironmentBlockerMarkdown(result);
350
354
  if (environmentBlockerLines.length) {
351
355
  lines.push("", "## Environment Blocker", "", ...environmentBlockerLines);
@@ -422,6 +426,13 @@ function profileCheckMarkdownTarget(check) {
422
426
  if (selector && maxOverflow !== void 0) return `${markdownInlineCode(selector)} <= ${maxOverflow}px`;
423
427
  return selector ? markdownInlineCode(selector) : maxOverflow !== void 0 ? `<= ${maxOverflow}px` : void 0;
424
428
  }
429
+ if (check.type === "http_status") {
430
+ const url = cliString(evidence.url);
431
+ const method = cliString(evidence.method);
432
+ const statuses = Array.isArray(evidence.allowed_statuses) ? evidence.allowed_statuses.map((status) => cliString(status) || String(status)).filter(Boolean) : [];
433
+ const target = [method, url ? markdownInlineCode(url) : ""].filter(Boolean).join(" ");
434
+ return statuses.length ? `${target} -> ${statuses.join("/")}` : target || void 0;
435
+ }
425
436
  if (check.type === "link_status" || check.type === "artifact_link_status") {
426
437
  const expectedCount = cliFiniteNumber(evidence.expected_count);
427
438
  const minCount = cliFiniteNumber(evidence.min_count);
@@ -649,6 +660,24 @@ function profileLinkStatusSummaryMarkdown(result) {
649
660
  }
650
661
  return lines;
651
662
  }
663
+ function profileHttpStatusSummaryMarkdown(result) {
664
+ const httpStatusChecks = result.checks.filter((check) => check.type === "http_status");
665
+ const lines = [];
666
+ for (const check of httpStatusChecks) {
667
+ const evidence = cliRecord(check.evidence);
668
+ if (!evidence) continue;
669
+ const label = check.label || check.type;
670
+ const url = cliString(evidence.url);
671
+ const method = cliString(evidence.method) || "GET";
672
+ const viewports = Array.isArray(evidence.viewports) ? evidence.viewports.map(cliRecord).filter((viewport) => Boolean(viewport)) : [];
673
+ const statuses = viewports.map((viewport) => cliFiniteNumber(viewport.status)).map((status) => status === void 0 ? "error" : String(status));
674
+ const failedTotal = Array.isArray(evidence.failures) ? evidence.failures.length : 0;
675
+ lines.push(
676
+ `- ${label}: ${method}${url ? ` ${markdownInlineCode(url)}` : ""}, statuses ${statuses.length ? statuses.join("/") : "unknown"}, failures ${failedTotal}`
677
+ );
678
+ }
679
+ return lines;
680
+ }
652
681
  function writeProfileOutput(outputDir, result) {
653
682
  if (!outputDir) return;
654
683
  mkdirSync(outputDir, { recursive: true });