@riddledc/riddle-proof 0.7.149 → 0.7.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -605,6 +605,26 @@ The check records visible text for the selector in each viewport and reports
605
605
  matched counts plus short samples, which makes generated-command and evidence
606
606
  card audits easier to diagnose than global `text_visible` checks.
607
607
 
608
+ Use `observe_within` when the proof needs to catch a short-lived user-visible
609
+ state after setup actions, such as a combo badge, damage flash, transient
610
+ particle count, toast, or canvas-adjacent HUD update:
611
+
612
+ ```json
613
+ {
614
+ "type": "observe_within",
615
+ "selector": ".result-state",
616
+ "pattern": "Photon.*active",
617
+ "timeout_ms": 1500
618
+ }
619
+ ```
620
+
621
+ With `selector` plus `text` or `pattern`, the runner polls visible selector
622
+ text until it matches. With only `selector`, it polls for a visible matching
623
+ element. With only `text` or `pattern`, it polls the rendered page body. The
624
+ proof evidence records per-viewport match status, elapsed time, attempts,
625
+ selector counts when applicable, and a compact sample. `within_ms` is accepted
626
+ as an alias for `timeout_ms`; the default timeout is `2000`.
627
+
608
628
  Use `http_status` when the contract belongs to the fetched response itself:
609
629
  status code, content type, byte size, or raw body fragments from a markdown,
610
630
  JSON, YAML, robots, sitemap, or other machine-readable endpoint:
@@ -26,6 +26,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
26
26
  "selector_text_visible",
27
27
  "selector_text_absent",
28
28
  "selector_text_order",
29
+ "observe_within",
29
30
  "frame_text_visible",
30
31
  "frame_url_equals",
31
32
  "frame_url_matches",
@@ -1348,7 +1349,7 @@ function normalizeCheck(input, index) {
1348
1349
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
1349
1350
  }
1350
1351
  const isDialogCountCheck = isDialogCountCheckType(type);
1351
- if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue(input.selector)) {
1352
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent" || type === "observe_within" && !stringValue(input.text) && !stringValue(input.pattern)) && !stringValue(input.selector)) {
1352
1353
  throw new Error(`checks[${index}] ${type} requires selector.`);
1353
1354
  }
1354
1355
  if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
@@ -1489,7 +1490,7 @@ function normalizeCheck(input, index) {
1489
1490
  allowed_content_types: allowedContentTypes,
1490
1491
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
1491
1492
  max_overflow_px: numberValue(input.max_overflow_px),
1492
- timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
1493
+ timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
1493
1494
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
1494
1495
  run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
1495
1496
  run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
@@ -2007,6 +2008,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
2007
2008
  function textKey(check) {
2008
2009
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
2009
2010
  }
2011
+ function observeWithinTimeoutMs(check) {
2012
+ const raw = check.timeout_ms;
2013
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
2014
+ return 2e3;
2015
+ }
2016
+ function observeWithinKey(check) {
2017
+ const target = check.selector ? `selector:${check.selector}` : "page";
2018
+ const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
2019
+ return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
2020
+ }
2010
2021
  function textSequenceForCheck(viewport, check) {
2011
2022
  const key = selectorKey(check);
2012
2023
  const sequence = viewport.text_sequences?.[key];
@@ -2513,6 +2524,40 @@ function assessCheckFromEvidence(check, evidence) {
2513
2524
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
2514
2525
  };
2515
2526
  }
2527
+ if (check.type === "observe_within") {
2528
+ const key = observeWithinKey(check);
2529
+ const timeoutMs = observeWithinTimeoutMs(check);
2530
+ const results = viewports.map((viewport) => {
2531
+ const observation = viewport.observations?.[key];
2532
+ const matched = observation?.matched === true;
2533
+ return {
2534
+ viewport: viewport.name,
2535
+ matched,
2536
+ elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
2537
+ timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
2538
+ attempts: numberValue(observation?.attempts) ?? null,
2539
+ selector_count: numberValue(observation?.selector_count) ?? null,
2540
+ visible_count: numberValue(observation?.visible_count) ?? null,
2541
+ matched_count: numberValue(observation?.matched_count) ?? null,
2542
+ sample: stringValue(observation?.sample) ?? null,
2543
+ error: stringValue(observation?.error) ?? null
2544
+ };
2545
+ });
2546
+ const failed = results.filter((result) => !result.matched).length;
2547
+ return {
2548
+ type: check.type,
2549
+ label: checkLabel(check),
2550
+ status: failed ? "failed" : "passed",
2551
+ evidence: {
2552
+ selector: check.selector || null,
2553
+ text: check.text || null,
2554
+ pattern: check.pattern || null,
2555
+ timeout_ms: timeoutMs,
2556
+ viewports: results.map((result) => toJsonValue(result))
2557
+ },
2558
+ message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
2559
+ };
2560
+ }
2516
2561
  if (check.type === "frame_text_visible") {
2517
2562
  const key = selectorKey(check);
2518
2563
  const results = viewports.map((viewport) => {
@@ -4422,6 +4467,36 @@ function assessProfile(profile, evidence) {
4422
4467
  });
4423
4468
  continue;
4424
4469
  }
4470
+ if (check.type === "observe_within") {
4471
+ const key = observeWithinKey(check);
4472
+ const timeoutMs = observeWithinTimeoutMs(check);
4473
+ const results = checkViewports.map((viewport) => {
4474
+ const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
4475
+ ? viewport.observations[key]
4476
+ : {};
4477
+ return {
4478
+ viewport: viewport.name,
4479
+ matched: observation.matched === true,
4480
+ elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
4481
+ timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
4482
+ attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
4483
+ selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
4484
+ visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
4485
+ matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
4486
+ sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
4487
+ error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
4488
+ };
4489
+ });
4490
+ const failed = results.filter((result) => !result.matched).length;
4491
+ checks.push({
4492
+ type: check.type,
4493
+ label: check.label || check.type,
4494
+ status: failed ? "failed" : "passed",
4495
+ evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
4496
+ message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
4497
+ });
4498
+ continue;
4499
+ }
4425
4500
  if (check.type === "frame_text_visible") {
4426
4501
  const selector = check.selector || "";
4427
4502
  const results = checkViewports.map((viewport) => {
@@ -4854,6 +4929,19 @@ function ensureDialogHandler() {
4854
4929
  function textKey(check) {
4855
4930
  return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
4856
4931
  }
4932
+ function observeWithinTimeoutMs(check) {
4933
+ const raw = Number(check && check.timeout_ms);
4934
+ return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
4935
+ }
4936
+ function observeWithinKey(check) {
4937
+ const target = check && check.selector ? "selector:" + check.selector : "page";
4938
+ const expectation = check && check.pattern
4939
+ ? "pattern:" + check.pattern + "/" + (check.flags || "")
4940
+ : check && check.text
4941
+ ? "text:" + check.text
4942
+ : "visible";
4943
+ return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
4944
+ }
4857
4945
  function textMatches(sample, check) {
4858
4946
  if (check.pattern) {
4859
4947
  try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
@@ -6169,6 +6257,104 @@ async function selectorTextSequence(selector) {
6169
6257
  };
6170
6258
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
6171
6259
  }
6260
+ async function observeWithinSnapshot(check) {
6261
+ const payload = {
6262
+ selector: check.selector || "",
6263
+ text: check.text || "",
6264
+ pattern: check.pattern || "",
6265
+ flags: check.flags || "",
6266
+ wants_text: Boolean(check.text || check.pattern),
6267
+ };
6268
+ if (payload.selector) {
6269
+ return page.locator(payload.selector).evaluateAll((elements, input) => {
6270
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
6271
+ const matchText = (value) => {
6272
+ const source = compact(value);
6273
+ if (input.pattern) {
6274
+ try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
6275
+ }
6276
+ return source.includes(input.text || "");
6277
+ };
6278
+ const isVisible = (element) => {
6279
+ const style = window.getComputedStyle(element);
6280
+ const rect = element.getBoundingClientRect();
6281
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
6282
+ };
6283
+ const rows = elements.map((element, index) => {
6284
+ const text = compact(element.innerText || element.textContent || "");
6285
+ const visible = isVisible(element);
6286
+ return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
6287
+ });
6288
+ const visibleRows = rows.filter((row) => row.visible);
6289
+ const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
6290
+ const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
6291
+ return {
6292
+ selector: input.selector,
6293
+ text: input.text || null,
6294
+ pattern: input.pattern || null,
6295
+ selector_count: rows.length,
6296
+ visible_count: visibleRows.length,
6297
+ matched_count: matches.length,
6298
+ matched: matches.length > 0,
6299
+ sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
6300
+ };
6301
+ }, payload).catch((error) => ({
6302
+ selector: payload.selector,
6303
+ text: payload.text || null,
6304
+ pattern: payload.pattern || null,
6305
+ selector_count: 0,
6306
+ visible_count: 0,
6307
+ matched_count: 0,
6308
+ matched: false,
6309
+ sample: null,
6310
+ error: String(error && error.message ? error.message : error).slice(0, 500),
6311
+ }));
6312
+ }
6313
+ return page.evaluate((input) => {
6314
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
6315
+ const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
6316
+ let matched = false;
6317
+ if (input.pattern) {
6318
+ try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
6319
+ } else {
6320
+ matched = sample.includes(input.text || "");
6321
+ }
6322
+ return {
6323
+ selector: null,
6324
+ text: input.text || null,
6325
+ pattern: input.pattern || null,
6326
+ matched,
6327
+ matched_count: matched ? 1 : 0,
6328
+ sample: sample.slice(0, 240),
6329
+ };
6330
+ }, payload).catch((error) => ({
6331
+ selector: null,
6332
+ text: payload.text || null,
6333
+ pattern: payload.pattern || null,
6334
+ matched: false,
6335
+ matched_count: 0,
6336
+ sample: null,
6337
+ error: String(error && error.message ? error.message : error).slice(0, 500),
6338
+ }));
6339
+ }
6340
+ async function observeWithin(check) {
6341
+ const timeoutMs = observeWithinTimeoutMs(check);
6342
+ const startedAt = Date.now();
6343
+ let attempts = 0;
6344
+ let last = null;
6345
+ while (true) {
6346
+ attempts += 1;
6347
+ last = await observeWithinSnapshot(check);
6348
+ const elapsedMs = Date.now() - startedAt;
6349
+ if (last && last.matched === true) {
6350
+ return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
6351
+ }
6352
+ if (elapsedMs >= timeoutMs) {
6353
+ return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
6354
+ }
6355
+ await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
6356
+ }
6357
+ }
6172
6358
  function linkProbeMaxLinks(check) {
6173
6359
  const value = Number(check.max_links || check.maxLinks || check.limit || 100);
6174
6360
  return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
@@ -7221,6 +7407,7 @@ async function captureViewport(viewport) {
7221
7407
  const text_matches = {};
7222
7408
  const text_match_samples = {};
7223
7409
  const text_case_insensitive_samples = {};
7410
+ const observations = {};
7224
7411
  const http_statuses = {};
7225
7412
  const link_statuses = {};
7226
7413
  for (const check of profile.checks || []) {
@@ -7241,6 +7428,10 @@ async function captureViewport(viewport) {
7241
7428
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
7242
7429
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
7243
7430
  }
7431
+ if (check.type === "observe_within") {
7432
+ const key = observeWithinKey(check);
7433
+ observations[key] = observations[key] || await observeWithin(check);
7434
+ }
7244
7435
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
7245
7436
  const key = textKey(check);
7246
7437
  const sample = dom.body_text || dom.body_text_sample || "";
@@ -7334,6 +7525,7 @@ async function captureViewport(viewport) {
7334
7525
  text_matches,
7335
7526
  text_match_samples,
7336
7527
  text_case_insensitive_samples,
7528
+ observations,
7337
7529
  http_statuses,
7338
7530
  link_statuses,
7339
7531
  route_inventory: routeInventory,
package/dist/cli.cjs CHANGED
@@ -6975,6 +6975,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6975
6975
  "selector_text_visible",
6976
6976
  "selector_text_absent",
6977
6977
  "selector_text_order",
6978
+ "observe_within",
6978
6979
  "frame_text_visible",
6979
6980
  "frame_url_equals",
6980
6981
  "frame_url_matches",
@@ -8297,7 +8298,7 @@ function normalizeCheck(input, index) {
8297
8298
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
8298
8299
  }
8299
8300
  const isDialogCountCheck = isDialogCountCheckType(type);
8300
- if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue2(input.selector)) {
8301
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent" || type === "observe_within" && !stringValue2(input.text) && !stringValue2(input.pattern)) && !stringValue2(input.selector)) {
8301
8302
  throw new Error(`checks[${index}] ${type} requires selector.`);
8302
8303
  }
8303
8304
  if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
@@ -8438,7 +8439,7 @@ function normalizeCheck(input, index) {
8438
8439
  allowed_content_types: allowedContentTypes,
8439
8440
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
8440
8441
  max_overflow_px: numberValue(input.max_overflow_px),
8441
- timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
8442
+ timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
8442
8443
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
8443
8444
  run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
8444
8445
  run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
@@ -8956,6 +8957,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
8956
8957
  function textKey(check) {
8957
8958
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
8958
8959
  }
8960
+ function observeWithinTimeoutMs(check) {
8961
+ const raw = check.timeout_ms;
8962
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
8963
+ return 2e3;
8964
+ }
8965
+ function observeWithinKey(check) {
8966
+ const target = check.selector ? `selector:${check.selector}` : "page";
8967
+ const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
8968
+ return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
8969
+ }
8959
8970
  function textSequenceForCheck(viewport, check) {
8960
8971
  const key = selectorKey(check);
8961
8972
  const sequence = viewport.text_sequences?.[key];
@@ -9462,6 +9473,40 @@ function assessCheckFromEvidence(check, evidence) {
9462
9473
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
9463
9474
  };
9464
9475
  }
9476
+ if (check.type === "observe_within") {
9477
+ const key = observeWithinKey(check);
9478
+ const timeoutMs = observeWithinTimeoutMs(check);
9479
+ const results = viewports.map((viewport) => {
9480
+ const observation = viewport.observations?.[key];
9481
+ const matched = observation?.matched === true;
9482
+ return {
9483
+ viewport: viewport.name,
9484
+ matched,
9485
+ elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
9486
+ timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
9487
+ attempts: numberValue(observation?.attempts) ?? null,
9488
+ selector_count: numberValue(observation?.selector_count) ?? null,
9489
+ visible_count: numberValue(observation?.visible_count) ?? null,
9490
+ matched_count: numberValue(observation?.matched_count) ?? null,
9491
+ sample: stringValue2(observation?.sample) ?? null,
9492
+ error: stringValue2(observation?.error) ?? null
9493
+ };
9494
+ });
9495
+ const failed = results.filter((result) => !result.matched).length;
9496
+ return {
9497
+ type: check.type,
9498
+ label: checkLabel(check),
9499
+ status: failed ? "failed" : "passed",
9500
+ evidence: {
9501
+ selector: check.selector || null,
9502
+ text: check.text || null,
9503
+ pattern: check.pattern || null,
9504
+ timeout_ms: timeoutMs,
9505
+ viewports: results.map((result) => toJsonValue(result))
9506
+ },
9507
+ message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
9508
+ };
9509
+ }
9465
9510
  if (check.type === "frame_text_visible") {
9466
9511
  const key = selectorKey(check);
9467
9512
  const results = viewports.map((viewport) => {
@@ -11355,6 +11400,36 @@ function assessProfile(profile, evidence) {
11355
11400
  });
11356
11401
  continue;
11357
11402
  }
11403
+ if (check.type === "observe_within") {
11404
+ const key = observeWithinKey(check);
11405
+ const timeoutMs = observeWithinTimeoutMs(check);
11406
+ const results = checkViewports.map((viewport) => {
11407
+ const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
11408
+ ? viewport.observations[key]
11409
+ : {};
11410
+ return {
11411
+ viewport: viewport.name,
11412
+ matched: observation.matched === true,
11413
+ elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
11414
+ timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
11415
+ attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
11416
+ selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
11417
+ visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
11418
+ matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
11419
+ sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
11420
+ error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
11421
+ };
11422
+ });
11423
+ const failed = results.filter((result) => !result.matched).length;
11424
+ checks.push({
11425
+ type: check.type,
11426
+ label: check.label || check.type,
11427
+ status: failed ? "failed" : "passed",
11428
+ evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
11429
+ message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
11430
+ });
11431
+ continue;
11432
+ }
11358
11433
  if (check.type === "frame_text_visible") {
11359
11434
  const selector = check.selector || "";
11360
11435
  const results = checkViewports.map((viewport) => {
@@ -11787,6 +11862,19 @@ function ensureDialogHandler() {
11787
11862
  function textKey(check) {
11788
11863
  return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
11789
11864
  }
11865
+ function observeWithinTimeoutMs(check) {
11866
+ const raw = Number(check && check.timeout_ms);
11867
+ return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
11868
+ }
11869
+ function observeWithinKey(check) {
11870
+ const target = check && check.selector ? "selector:" + check.selector : "page";
11871
+ const expectation = check && check.pattern
11872
+ ? "pattern:" + check.pattern + "/" + (check.flags || "")
11873
+ : check && check.text
11874
+ ? "text:" + check.text
11875
+ : "visible";
11876
+ return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
11877
+ }
11790
11878
  function textMatches(sample, check) {
11791
11879
  if (check.pattern) {
11792
11880
  try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
@@ -13102,6 +13190,104 @@ async function selectorTextSequence(selector) {
13102
13190
  };
13103
13191
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
13104
13192
  }
13193
+ async function observeWithinSnapshot(check) {
13194
+ const payload = {
13195
+ selector: check.selector || "",
13196
+ text: check.text || "",
13197
+ pattern: check.pattern || "",
13198
+ flags: check.flags || "",
13199
+ wants_text: Boolean(check.text || check.pattern),
13200
+ };
13201
+ if (payload.selector) {
13202
+ return page.locator(payload.selector).evaluateAll((elements, input) => {
13203
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
13204
+ const matchText = (value) => {
13205
+ const source = compact(value);
13206
+ if (input.pattern) {
13207
+ try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
13208
+ }
13209
+ return source.includes(input.text || "");
13210
+ };
13211
+ const isVisible = (element) => {
13212
+ const style = window.getComputedStyle(element);
13213
+ const rect = element.getBoundingClientRect();
13214
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
13215
+ };
13216
+ const rows = elements.map((element, index) => {
13217
+ const text = compact(element.innerText || element.textContent || "");
13218
+ const visible = isVisible(element);
13219
+ return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
13220
+ });
13221
+ const visibleRows = rows.filter((row) => row.visible);
13222
+ const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
13223
+ const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
13224
+ return {
13225
+ selector: input.selector,
13226
+ text: input.text || null,
13227
+ pattern: input.pattern || null,
13228
+ selector_count: rows.length,
13229
+ visible_count: visibleRows.length,
13230
+ matched_count: matches.length,
13231
+ matched: matches.length > 0,
13232
+ sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
13233
+ };
13234
+ }, payload).catch((error) => ({
13235
+ selector: payload.selector,
13236
+ text: payload.text || null,
13237
+ pattern: payload.pattern || null,
13238
+ selector_count: 0,
13239
+ visible_count: 0,
13240
+ matched_count: 0,
13241
+ matched: false,
13242
+ sample: null,
13243
+ error: String(error && error.message ? error.message : error).slice(0, 500),
13244
+ }));
13245
+ }
13246
+ return page.evaluate((input) => {
13247
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
13248
+ const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
13249
+ let matched = false;
13250
+ if (input.pattern) {
13251
+ try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
13252
+ } else {
13253
+ matched = sample.includes(input.text || "");
13254
+ }
13255
+ return {
13256
+ selector: null,
13257
+ text: input.text || null,
13258
+ pattern: input.pattern || null,
13259
+ matched,
13260
+ matched_count: matched ? 1 : 0,
13261
+ sample: sample.slice(0, 240),
13262
+ };
13263
+ }, payload).catch((error) => ({
13264
+ selector: null,
13265
+ text: payload.text || null,
13266
+ pattern: payload.pattern || null,
13267
+ matched: false,
13268
+ matched_count: 0,
13269
+ sample: null,
13270
+ error: String(error && error.message ? error.message : error).slice(0, 500),
13271
+ }));
13272
+ }
13273
+ async function observeWithin(check) {
13274
+ const timeoutMs = observeWithinTimeoutMs(check);
13275
+ const startedAt = Date.now();
13276
+ let attempts = 0;
13277
+ let last = null;
13278
+ while (true) {
13279
+ attempts += 1;
13280
+ last = await observeWithinSnapshot(check);
13281
+ const elapsedMs = Date.now() - startedAt;
13282
+ if (last && last.matched === true) {
13283
+ return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
13284
+ }
13285
+ if (elapsedMs >= timeoutMs) {
13286
+ return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
13287
+ }
13288
+ await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
13289
+ }
13290
+ }
13105
13291
  function linkProbeMaxLinks(check) {
13106
13292
  const value = Number(check.max_links || check.maxLinks || check.limit || 100);
13107
13293
  return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
@@ -14154,6 +14340,7 @@ async function captureViewport(viewport) {
14154
14340
  const text_matches = {};
14155
14341
  const text_match_samples = {};
14156
14342
  const text_case_insensitive_samples = {};
14343
+ const observations = {};
14157
14344
  const http_statuses = {};
14158
14345
  const link_statuses = {};
14159
14346
  for (const check of profile.checks || []) {
@@ -14174,6 +14361,10 @@ async function captureViewport(viewport) {
14174
14361
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
14175
14362
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
14176
14363
  }
14364
+ if (check.type === "observe_within") {
14365
+ const key = observeWithinKey(check);
14366
+ observations[key] = observations[key] || await observeWithin(check);
14367
+ }
14177
14368
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
14178
14369
  const key = textKey(check);
14179
14370
  const sample = dom.body_text || dom.body_text_sample || "";
@@ -14267,6 +14458,7 @@ async function captureViewport(viewport) {
14267
14458
  text_matches,
14268
14459
  text_match_samples,
14269
14460
  text_case_insensitive_samples,
14461
+ observations,
14270
14462
  http_statuses,
14271
14463
  link_statuses,
14272
14464
  route_inventory: routeInventory,
@@ -14949,6 +15141,14 @@ function profileCheckMarkdownTarget(check) {
14949
15141
  if (check.type === "selector_text_order") {
14950
15142
  return selector ? `${markdownInlineCode(selector)} text order` : void 0;
14951
15143
  }
15144
+ if (check.type === "observe_within") {
15145
+ const textTarget = profileCheckTextTarget(evidence);
15146
+ const timeoutMs = cliFiniteNumber(evidence.timeout_ms);
15147
+ const withinLabel = timeoutMs === void 0 ? "within timeout" : `within ${timeoutMs}ms`;
15148
+ if (selector && textTarget) return `${markdownInlineCode(selector)} observes ${textTarget} ${withinLabel}`;
15149
+ if (selector) return `${markdownInlineCode(selector)} visible ${withinLabel}`;
15150
+ return textTarget ? `${textTarget} ${withinLabel}` : withinLabel;
15151
+ }
14952
15152
  if (check.type === "text_visible" || check.type === "text_absent") {
14953
15153
  return profileCheckTextTarget(evidence);
14954
15154
  }
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  profileStatusExitCode,
14
14
  resolveRiddleProofProfileTargetUrl,
15
15
  resolveRiddleProofProfileTimeoutSec
16
- } from "./chunk-QXQCG3WB.js";
16
+ } from "./chunk-QJJ3ISMK.js";
17
17
  import {
18
18
  createRiddleApiClient,
19
19
  parseRiddleViewport
@@ -507,6 +507,14 @@ function profileCheckMarkdownTarget(check) {
507
507
  if (check.type === "selector_text_order") {
508
508
  return selector ? `${markdownInlineCode(selector)} text order` : void 0;
509
509
  }
510
+ if (check.type === "observe_within") {
511
+ const textTarget = profileCheckTextTarget(evidence);
512
+ const timeoutMs = cliFiniteNumber(evidence.timeout_ms);
513
+ const withinLabel = timeoutMs === void 0 ? "within timeout" : `within ${timeoutMs}ms`;
514
+ if (selector && textTarget) return `${markdownInlineCode(selector)} observes ${textTarget} ${withinLabel}`;
515
+ if (selector) return `${markdownInlineCode(selector)} visible ${withinLabel}`;
516
+ return textTarget ? `${textTarget} ${withinLabel}` : withinLabel;
517
+ }
510
518
  if (check.type === "text_visible" || check.type === "text_absent") {
511
519
  return profileCheckTextTarget(evidence);
512
520
  }
package/dist/index.cjs CHANGED
@@ -8759,6 +8759,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8759
8759
  "selector_text_visible",
8760
8760
  "selector_text_absent",
8761
8761
  "selector_text_order",
8762
+ "observe_within",
8762
8763
  "frame_text_visible",
8763
8764
  "frame_url_equals",
8764
8765
  "frame_url_matches",
@@ -10081,7 +10082,7 @@ function normalizeCheck(input, index) {
10081
10082
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
10082
10083
  }
10083
10084
  const isDialogCountCheck = isDialogCountCheckType(type);
10084
- if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue5(input.selector)) {
10085
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent" || type === "observe_within" && !stringValue5(input.text) && !stringValue5(input.pattern)) && !stringValue5(input.selector)) {
10085
10086
  throw new Error(`checks[${index}] ${type} requires selector.`);
10086
10087
  }
10087
10088
  if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
@@ -10222,7 +10223,7 @@ function normalizeCheck(input, index) {
10222
10223
  allowed_content_types: allowedContentTypes,
10223
10224
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
10224
10225
  max_overflow_px: numberValue3(input.max_overflow_px),
10225
- timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
10226
+ timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs) ?? numberValue3(input.within_ms) ?? numberValue3(input.withinMs),
10226
10227
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
10227
10228
  run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
10228
10229
  run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
@@ -10740,6 +10741,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
10740
10741
  function textKey(check) {
10741
10742
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
10742
10743
  }
10744
+ function observeWithinTimeoutMs(check) {
10745
+ const raw = check.timeout_ms;
10746
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
10747
+ return 2e3;
10748
+ }
10749
+ function observeWithinKey(check) {
10750
+ const target = check.selector ? `selector:${check.selector}` : "page";
10751
+ const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
10752
+ return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
10753
+ }
10743
10754
  function textSequenceForCheck(viewport, check) {
10744
10755
  const key = selectorKey(check);
10745
10756
  const sequence = viewport.text_sequences?.[key];
@@ -11246,6 +11257,40 @@ function assessCheckFromEvidence(check, evidence) {
11246
11257
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
11247
11258
  };
11248
11259
  }
11260
+ if (check.type === "observe_within") {
11261
+ const key = observeWithinKey(check);
11262
+ const timeoutMs = observeWithinTimeoutMs(check);
11263
+ const results = viewports.map((viewport) => {
11264
+ const observation = viewport.observations?.[key];
11265
+ const matched = observation?.matched === true;
11266
+ return {
11267
+ viewport: viewport.name,
11268
+ matched,
11269
+ elapsed_ms: numberValue3(observation?.elapsed_ms) ?? null,
11270
+ timeout_ms: numberValue3(observation?.timeout_ms) ?? timeoutMs,
11271
+ attempts: numberValue3(observation?.attempts) ?? null,
11272
+ selector_count: numberValue3(observation?.selector_count) ?? null,
11273
+ visible_count: numberValue3(observation?.visible_count) ?? null,
11274
+ matched_count: numberValue3(observation?.matched_count) ?? null,
11275
+ sample: stringValue5(observation?.sample) ?? null,
11276
+ error: stringValue5(observation?.error) ?? null
11277
+ };
11278
+ });
11279
+ const failed = results.filter((result) => !result.matched).length;
11280
+ return {
11281
+ type: check.type,
11282
+ label: checkLabel(check),
11283
+ status: failed ? "failed" : "passed",
11284
+ evidence: {
11285
+ selector: check.selector || null,
11286
+ text: check.text || null,
11287
+ pattern: check.pattern || null,
11288
+ timeout_ms: timeoutMs,
11289
+ viewports: results.map((result) => toJsonValue(result))
11290
+ },
11291
+ message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
11292
+ };
11293
+ }
11249
11294
  if (check.type === "frame_text_visible") {
11250
11295
  const key = selectorKey(check);
11251
11296
  const results = viewports.map((viewport) => {
@@ -13155,6 +13200,36 @@ function assessProfile(profile, evidence) {
13155
13200
  });
13156
13201
  continue;
13157
13202
  }
13203
+ if (check.type === "observe_within") {
13204
+ const key = observeWithinKey(check);
13205
+ const timeoutMs = observeWithinTimeoutMs(check);
13206
+ const results = checkViewports.map((viewport) => {
13207
+ const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
13208
+ ? viewport.observations[key]
13209
+ : {};
13210
+ return {
13211
+ viewport: viewport.name,
13212
+ matched: observation.matched === true,
13213
+ elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
13214
+ timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
13215
+ attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
13216
+ selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
13217
+ visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
13218
+ matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
13219
+ sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
13220
+ error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
13221
+ };
13222
+ });
13223
+ const failed = results.filter((result) => !result.matched).length;
13224
+ checks.push({
13225
+ type: check.type,
13226
+ label: check.label || check.type,
13227
+ status: failed ? "failed" : "passed",
13228
+ evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
13229
+ message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
13230
+ });
13231
+ continue;
13232
+ }
13158
13233
  if (check.type === "frame_text_visible") {
13159
13234
  const selector = check.selector || "";
13160
13235
  const results = checkViewports.map((viewport) => {
@@ -13587,6 +13662,19 @@ function ensureDialogHandler() {
13587
13662
  function textKey(check) {
13588
13663
  return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
13589
13664
  }
13665
+ function observeWithinTimeoutMs(check) {
13666
+ const raw = Number(check && check.timeout_ms);
13667
+ return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
13668
+ }
13669
+ function observeWithinKey(check) {
13670
+ const target = check && check.selector ? "selector:" + check.selector : "page";
13671
+ const expectation = check && check.pattern
13672
+ ? "pattern:" + check.pattern + "/" + (check.flags || "")
13673
+ : check && check.text
13674
+ ? "text:" + check.text
13675
+ : "visible";
13676
+ return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
13677
+ }
13590
13678
  function textMatches(sample, check) {
13591
13679
  if (check.pattern) {
13592
13680
  try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
@@ -14902,6 +14990,104 @@ async function selectorTextSequence(selector) {
14902
14990
  };
14903
14991
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
14904
14992
  }
14993
+ async function observeWithinSnapshot(check) {
14994
+ const payload = {
14995
+ selector: check.selector || "",
14996
+ text: check.text || "",
14997
+ pattern: check.pattern || "",
14998
+ flags: check.flags || "",
14999
+ wants_text: Boolean(check.text || check.pattern),
15000
+ };
15001
+ if (payload.selector) {
15002
+ return page.locator(payload.selector).evaluateAll((elements, input) => {
15003
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
15004
+ const matchText = (value) => {
15005
+ const source = compact(value);
15006
+ if (input.pattern) {
15007
+ try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
15008
+ }
15009
+ return source.includes(input.text || "");
15010
+ };
15011
+ const isVisible = (element) => {
15012
+ const style = window.getComputedStyle(element);
15013
+ const rect = element.getBoundingClientRect();
15014
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
15015
+ };
15016
+ const rows = elements.map((element, index) => {
15017
+ const text = compact(element.innerText || element.textContent || "");
15018
+ const visible = isVisible(element);
15019
+ return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
15020
+ });
15021
+ const visibleRows = rows.filter((row) => row.visible);
15022
+ const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
15023
+ const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
15024
+ return {
15025
+ selector: input.selector,
15026
+ text: input.text || null,
15027
+ pattern: input.pattern || null,
15028
+ selector_count: rows.length,
15029
+ visible_count: visibleRows.length,
15030
+ matched_count: matches.length,
15031
+ matched: matches.length > 0,
15032
+ sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
15033
+ };
15034
+ }, payload).catch((error) => ({
15035
+ selector: payload.selector,
15036
+ text: payload.text || null,
15037
+ pattern: payload.pattern || null,
15038
+ selector_count: 0,
15039
+ visible_count: 0,
15040
+ matched_count: 0,
15041
+ matched: false,
15042
+ sample: null,
15043
+ error: String(error && error.message ? error.message : error).slice(0, 500),
15044
+ }));
15045
+ }
15046
+ return page.evaluate((input) => {
15047
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
15048
+ const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
15049
+ let matched = false;
15050
+ if (input.pattern) {
15051
+ try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
15052
+ } else {
15053
+ matched = sample.includes(input.text || "");
15054
+ }
15055
+ return {
15056
+ selector: null,
15057
+ text: input.text || null,
15058
+ pattern: input.pattern || null,
15059
+ matched,
15060
+ matched_count: matched ? 1 : 0,
15061
+ sample: sample.slice(0, 240),
15062
+ };
15063
+ }, payload).catch((error) => ({
15064
+ selector: null,
15065
+ text: payload.text || null,
15066
+ pattern: payload.pattern || null,
15067
+ matched: false,
15068
+ matched_count: 0,
15069
+ sample: null,
15070
+ error: String(error && error.message ? error.message : error).slice(0, 500),
15071
+ }));
15072
+ }
15073
+ async function observeWithin(check) {
15074
+ const timeoutMs = observeWithinTimeoutMs(check);
15075
+ const startedAt = Date.now();
15076
+ let attempts = 0;
15077
+ let last = null;
15078
+ while (true) {
15079
+ attempts += 1;
15080
+ last = await observeWithinSnapshot(check);
15081
+ const elapsedMs = Date.now() - startedAt;
15082
+ if (last && last.matched === true) {
15083
+ return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
15084
+ }
15085
+ if (elapsedMs >= timeoutMs) {
15086
+ return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
15087
+ }
15088
+ await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
15089
+ }
15090
+ }
14905
15091
  function linkProbeMaxLinks(check) {
14906
15092
  const value = Number(check.max_links || check.maxLinks || check.limit || 100);
14907
15093
  return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
@@ -15954,6 +16140,7 @@ async function captureViewport(viewport) {
15954
16140
  const text_matches = {};
15955
16141
  const text_match_samples = {};
15956
16142
  const text_case_insensitive_samples = {};
16143
+ const observations = {};
15957
16144
  const http_statuses = {};
15958
16145
  const link_statuses = {};
15959
16146
  for (const check of profile.checks || []) {
@@ -15974,6 +16161,10 @@ async function captureViewport(viewport) {
15974
16161
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
15975
16162
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
15976
16163
  }
16164
+ if (check.type === "observe_within") {
16165
+ const key = observeWithinKey(check);
16166
+ observations[key] = observations[key] || await observeWithin(check);
16167
+ }
15977
16168
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
15978
16169
  const key = textKey(check);
15979
16170
  const sample = dom.body_text || dom.body_text_sample || "";
@@ -16067,6 +16258,7 @@ async function captureViewport(viewport) {
16067
16258
  text_matches,
16068
16259
  text_match_samples,
16069
16260
  text_case_insensitive_samples,
16261
+ observations,
16070
16262
  http_statuses,
16071
16263
  link_statuses,
16072
16264
  route_inventory: routeInventory,
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  resolveRiddleProofProfileTimeoutSec,
63
63
  slugifyRiddleProofProfileName,
64
64
  summarizeRiddleProofProfileResult
65
- } from "./chunk-QXQCG3WB.js";
65
+ } from "./chunk-QJJ3ISMK.js";
66
66
  import {
67
67
  DEFAULT_RIDDLE_API_BASE_URL,
68
68
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -73,6 +73,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
73
73
  "selector_text_visible",
74
74
  "selector_text_absent",
75
75
  "selector_text_order",
76
+ "observe_within",
76
77
  "frame_text_visible",
77
78
  "frame_url_equals",
78
79
  "frame_url_matches",
@@ -1395,7 +1396,7 @@ function normalizeCheck(input, index) {
1395
1396
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
1396
1397
  }
1397
1398
  const isDialogCountCheck = isDialogCountCheckType(type);
1398
- if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent") && !stringValue(input.selector)) {
1399
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq" || type === "selector_text_visible" || type === "selector_text_absent" || type === "observe_within" && !stringValue(input.text) && !stringValue(input.pattern)) && !stringValue(input.selector)) {
1399
1400
  throw new Error(`checks[${index}] ${type} requires selector.`);
1400
1401
  }
1401
1402
  if ((type === "frame_text_visible" || type === "frame_url_equals" || type === "frame_url_matches" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
@@ -1536,7 +1537,7 @@ function normalizeCheck(input, index) {
1536
1537
  allowed_content_types: allowedContentTypes,
1537
1538
  allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
1538
1539
  max_overflow_px: numberValue(input.max_overflow_px),
1539
- timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
1540
+ timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs) ?? numberValue(input.within_ms) ?? numberValue(input.withinMs),
1540
1541
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
1541
1542
  run_clickthroughs: input.run_clickthroughs === false || input.runClickthroughs === false ? false : true,
1542
1543
  run_all_viewports: input.run_all_viewports === true || input.runAllViewports === true,
@@ -2054,6 +2055,16 @@ function summarizeLinkStatusEvidence(viewport, check) {
2054
2055
  function textKey(check) {
2055
2056
  return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
2056
2057
  }
2058
+ function observeWithinTimeoutMs(check) {
2059
+ const raw = check.timeout_ms;
2060
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.min(Math.round(raw), 6e4);
2061
+ return 2e3;
2062
+ }
2063
+ function observeWithinKey(check) {
2064
+ const target = check.selector ? `selector:${check.selector}` : "page";
2065
+ const expectation = check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : check.text ? `text:${check.text}` : "visible";
2066
+ return `${target}|${expectation}|within:${observeWithinTimeoutMs(check)}`;
2067
+ }
2057
2068
  function textSequenceForCheck(viewport, check) {
2058
2069
  const key = selectorKey(check);
2059
2070
  const sequence = viewport.text_sequences?.[key];
@@ -2560,6 +2571,40 @@ function assessCheckFromEvidence(check, evidence) {
2560
2571
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
2561
2572
  };
2562
2573
  }
2574
+ if (check.type === "observe_within") {
2575
+ const key = observeWithinKey(check);
2576
+ const timeoutMs = observeWithinTimeoutMs(check);
2577
+ const results = viewports.map((viewport) => {
2578
+ const observation = viewport.observations?.[key];
2579
+ const matched = observation?.matched === true;
2580
+ return {
2581
+ viewport: viewport.name,
2582
+ matched,
2583
+ elapsed_ms: numberValue(observation?.elapsed_ms) ?? null,
2584
+ timeout_ms: numberValue(observation?.timeout_ms) ?? timeoutMs,
2585
+ attempts: numberValue(observation?.attempts) ?? null,
2586
+ selector_count: numberValue(observation?.selector_count) ?? null,
2587
+ visible_count: numberValue(observation?.visible_count) ?? null,
2588
+ matched_count: numberValue(observation?.matched_count) ?? null,
2589
+ sample: stringValue(observation?.sample) ?? null,
2590
+ error: stringValue(observation?.error) ?? null
2591
+ };
2592
+ });
2593
+ const failed = results.filter((result) => !result.matched).length;
2594
+ return {
2595
+ type: check.type,
2596
+ label: checkLabel(check),
2597
+ status: failed ? "failed" : "passed",
2598
+ evidence: {
2599
+ selector: check.selector || null,
2600
+ text: check.text || null,
2601
+ pattern: check.pattern || null,
2602
+ timeout_ms: timeoutMs,
2603
+ viewports: results.map((result) => toJsonValue(result))
2604
+ },
2605
+ message: failed ? `Observation did not match within ${timeoutMs}ms in ${failed} viewport(s).` : void 0
2606
+ };
2607
+ }
2563
2608
  if (check.type === "frame_text_visible") {
2564
2609
  const key = selectorKey(check);
2565
2610
  const results = viewports.map((viewport) => {
@@ -4469,6 +4514,36 @@ function assessProfile(profile, evidence) {
4469
4514
  });
4470
4515
  continue;
4471
4516
  }
4517
+ if (check.type === "observe_within") {
4518
+ const key = observeWithinKey(check);
4519
+ const timeoutMs = observeWithinTimeoutMs(check);
4520
+ const results = checkViewports.map((viewport) => {
4521
+ const observation = viewport.observations && viewport.observations[key] && typeof viewport.observations[key] === "object"
4522
+ ? viewport.observations[key]
4523
+ : {};
4524
+ return {
4525
+ viewport: viewport.name,
4526
+ matched: observation.matched === true,
4527
+ elapsed_ms: typeof observation.elapsed_ms === "number" && Number.isFinite(observation.elapsed_ms) ? observation.elapsed_ms : null,
4528
+ timeout_ms: typeof observation.timeout_ms === "number" && Number.isFinite(observation.timeout_ms) ? observation.timeout_ms : timeoutMs,
4529
+ attempts: typeof observation.attempts === "number" && Number.isFinite(observation.attempts) ? observation.attempts : null,
4530
+ selector_count: typeof observation.selector_count === "number" && Number.isFinite(observation.selector_count) ? observation.selector_count : null,
4531
+ visible_count: typeof observation.visible_count === "number" && Number.isFinite(observation.visible_count) ? observation.visible_count : null,
4532
+ matched_count: typeof observation.matched_count === "number" && Number.isFinite(observation.matched_count) ? observation.matched_count : null,
4533
+ sample: typeof observation.sample === "string" && observation.sample.trim() ? observation.sample.trim() : null,
4534
+ error: typeof observation.error === "string" && observation.error.trim() ? observation.error.trim() : null,
4535
+ };
4536
+ });
4537
+ const failed = results.filter((result) => !result.matched).length;
4538
+ checks.push({
4539
+ type: check.type,
4540
+ label: check.label || check.type,
4541
+ status: failed ? "failed" : "passed",
4542
+ evidence: { selector: check.selector || null, text: check.text || null, pattern: check.pattern || null, timeout_ms: timeoutMs, viewports: results },
4543
+ message: failed ? "Observation did not match within " + timeoutMs + "ms in " + failed + " viewport(s)." : undefined,
4544
+ });
4545
+ continue;
4546
+ }
4472
4547
  if (check.type === "frame_text_visible") {
4473
4548
  const selector = check.selector || "";
4474
4549
  const results = checkViewports.map((viewport) => {
@@ -4901,6 +4976,19 @@ function ensureDialogHandler() {
4901
4976
  function textKey(check) {
4902
4977
  return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
4903
4978
  }
4979
+ function observeWithinTimeoutMs(check) {
4980
+ const raw = Number(check && check.timeout_ms);
4981
+ return Number.isFinite(raw) && raw > 0 ? Math.min(Math.round(raw), 60000) : 2000;
4982
+ }
4983
+ function observeWithinKey(check) {
4984
+ const target = check && check.selector ? "selector:" + check.selector : "page";
4985
+ const expectation = check && check.pattern
4986
+ ? "pattern:" + check.pattern + "/" + (check.flags || "")
4987
+ : check && check.text
4988
+ ? "text:" + check.text
4989
+ : "visible";
4990
+ return target + "|" + expectation + "|within:" + observeWithinTimeoutMs(check);
4991
+ }
4904
4992
  function textMatches(sample, check) {
4905
4993
  if (check.pattern) {
4906
4994
  try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
@@ -6216,6 +6304,104 @@ async function selectorTextSequence(selector) {
6216
6304
  };
6217
6305
  }).catch((error) => ({ count: 0, visible_count: 0, texts: [], visible_texts: [], match_texts: [], visible_match_texts: [], error: String(error && error.message ? error.message : error).slice(0, 500) }));
6218
6306
  }
6307
+ async function observeWithinSnapshot(check) {
6308
+ const payload = {
6309
+ selector: check.selector || "",
6310
+ text: check.text || "",
6311
+ pattern: check.pattern || "",
6312
+ flags: check.flags || "",
6313
+ wants_text: Boolean(check.text || check.pattern),
6314
+ };
6315
+ if (payload.selector) {
6316
+ return page.locator(payload.selector).evaluateAll((elements, input) => {
6317
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
6318
+ const matchText = (value) => {
6319
+ const source = compact(value);
6320
+ if (input.pattern) {
6321
+ try { return new RegExp(input.pattern, input.flags || "").test(source); } catch { return false; }
6322
+ }
6323
+ return source.includes(input.text || "");
6324
+ };
6325
+ const isVisible = (element) => {
6326
+ const style = window.getComputedStyle(element);
6327
+ const rect = element.getBoundingClientRect();
6328
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
6329
+ };
6330
+ const rows = elements.map((element, index) => {
6331
+ const text = compact(element.innerText || element.textContent || "");
6332
+ const visible = isVisible(element);
6333
+ return { index, text, visible, matched: input.wants_text ? matchText(text) : visible };
6334
+ });
6335
+ const visibleRows = rows.filter((row) => row.visible);
6336
+ const matches = input.wants_text ? visibleRows.filter((row) => row.matched) : visibleRows;
6337
+ const sampleRow = matches[0] || visibleRows[0] || rows[0] || null;
6338
+ return {
6339
+ selector: input.selector,
6340
+ text: input.text || null,
6341
+ pattern: input.pattern || null,
6342
+ selector_count: rows.length,
6343
+ visible_count: visibleRows.length,
6344
+ matched_count: matches.length,
6345
+ matched: matches.length > 0,
6346
+ sample: sampleRow && sampleRow.text ? sampleRow.text.slice(0, 240) : null,
6347
+ };
6348
+ }, payload).catch((error) => ({
6349
+ selector: payload.selector,
6350
+ text: payload.text || null,
6351
+ pattern: payload.pattern || null,
6352
+ selector_count: 0,
6353
+ visible_count: 0,
6354
+ matched_count: 0,
6355
+ matched: false,
6356
+ sample: null,
6357
+ error: String(error && error.message ? error.message : error).slice(0, 500),
6358
+ }));
6359
+ }
6360
+ return page.evaluate((input) => {
6361
+ const compact = (value) => String(value || "").replace(/\s+/g, " ").trim();
6362
+ const sample = compact(document.body ? document.body.innerText || document.body.textContent || "" : "");
6363
+ let matched = false;
6364
+ if (input.pattern) {
6365
+ try { matched = new RegExp(input.pattern, input.flags || "").test(sample); } catch { matched = false; }
6366
+ } else {
6367
+ matched = sample.includes(input.text || "");
6368
+ }
6369
+ return {
6370
+ selector: null,
6371
+ text: input.text || null,
6372
+ pattern: input.pattern || null,
6373
+ matched,
6374
+ matched_count: matched ? 1 : 0,
6375
+ sample: sample.slice(0, 240),
6376
+ };
6377
+ }, payload).catch((error) => ({
6378
+ selector: null,
6379
+ text: payload.text || null,
6380
+ pattern: payload.pattern || null,
6381
+ matched: false,
6382
+ matched_count: 0,
6383
+ sample: null,
6384
+ error: String(error && error.message ? error.message : error).slice(0, 500),
6385
+ }));
6386
+ }
6387
+ async function observeWithin(check) {
6388
+ const timeoutMs = observeWithinTimeoutMs(check);
6389
+ const startedAt = Date.now();
6390
+ let attempts = 0;
6391
+ let last = null;
6392
+ while (true) {
6393
+ attempts += 1;
6394
+ last = await observeWithinSnapshot(check);
6395
+ const elapsedMs = Date.now() - startedAt;
6396
+ if (last && last.matched === true) {
6397
+ return { ...last, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
6398
+ }
6399
+ if (elapsedMs >= timeoutMs) {
6400
+ return { ...(last || {}), matched: false, timeout_ms: timeoutMs, elapsed_ms: elapsedMs, attempts };
6401
+ }
6402
+ await page.waitForTimeout(Math.min(100, Math.max(25, timeoutMs - elapsedMs)));
6403
+ }
6404
+ }
6219
6405
  function linkProbeMaxLinks(check) {
6220
6406
  const value = Number(check.max_links || check.maxLinks || check.limit || 100);
6221
6407
  return Number.isInteger(value) && value > 0 ? Math.min(value, 500) : 100;
@@ -7268,6 +7454,7 @@ async function captureViewport(viewport) {
7268
7454
  const text_matches = {};
7269
7455
  const text_match_samples = {};
7270
7456
  const text_case_insensitive_samples = {};
7457
+ const observations = {};
7271
7458
  const http_statuses = {};
7272
7459
  const link_statuses = {};
7273
7460
  for (const check of profile.checks || []) {
@@ -7288,6 +7475,10 @@ async function captureViewport(viewport) {
7288
7475
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
7289
7476
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
7290
7477
  }
7478
+ if (check.type === "observe_within") {
7479
+ const key = observeWithinKey(check);
7480
+ observations[key] = observations[key] || await observeWithin(check);
7481
+ }
7291
7482
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
7292
7483
  const key = textKey(check);
7293
7484
  const sample = dom.body_text || dom.body_text_sample || "";
@@ -7381,6 +7572,7 @@ async function captureViewport(viewport) {
7381
7572
  text_matches,
7382
7573
  text_match_samples,
7383
7574
  text_case_insensitive_samples,
7575
+ observations,
7384
7576
  http_statuses,
7385
7577
  link_statuses,
7386
7578
  route_inventory: routeInventory,
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "observe_within", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "set_range_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_eval", "window_call", "window_call_until"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -319,6 +319,7 @@ interface RiddleProofProfileViewportEvidence {
319
319
  text_matches?: Record<string, boolean>;
320
320
  text_match_samples?: Record<string, string[]>;
321
321
  text_case_insensitive_samples?: Record<string, string[]>;
322
+ observations?: Record<string, Record<string, JsonValue>>;
322
323
  http_statuses?: Record<string, Record<string, JsonValue>>;
323
324
  link_statuses?: Record<string, Record<string, JsonValue>>;
324
325
  route_inventory?: Record<string, JsonValue>;
package/dist/profile.d.ts CHANGED
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
4
4
  declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
- declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "dialog_count_equals", "dialog_accept_count_equals", "dialog_dismiss_count_equals", "selector_text_visible", "selector_text_absent", "selector_text_order", "observe_within", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors", "no_console_warnings"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "set_range_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_eval", "window_call", "window_call_until"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -319,6 +319,7 @@ interface RiddleProofProfileViewportEvidence {
319
319
  text_matches?: Record<string, boolean>;
320
320
  text_match_samples?: Record<string, string[]>;
321
321
  text_case_insensitive_samples?: Record<string, string[]>;
322
+ observations?: Record<string, Record<string, JsonValue>>;
322
323
  http_statuses?: Record<string, Record<string, JsonValue>>;
323
324
  link_statuses?: Record<string, Record<string, JsonValue>>;
324
325
  route_inventory?: Record<string, JsonValue>;
package/dist/profile.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  resolveRiddleProofProfileTimeoutSec,
24
24
  slugifyRiddleProofProfileName,
25
25
  summarizeRiddleProofProfileResult
26
- } from "./chunk-QXQCG3WB.js";
26
+ } from "./chunk-QJJ3ISMK.js";
27
27
  export {
28
28
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
29
29
  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.149",
3
+ "version": "0.7.150",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",