@riddledc/riddle-proof 0.7.149 → 0.7.151

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/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>;
@@ -390,6 +391,7 @@ interface RiddleProofProfileResult {
390
391
  attempt?: number;
391
392
  attempts?: number;
392
393
  timed_out?: boolean;
394
+ artifact_recovery?: boolean;
393
395
  split_jobs?: Array<{
394
396
  viewport: string;
395
397
  job_id?: string;
@@ -401,6 +403,7 @@ interface RiddleProofProfileResult {
401
403
  attempt?: number;
402
404
  attempts?: number;
403
405
  timed_out?: boolean;
406
+ artifact_recovery?: boolean;
404
407
  }>;
405
408
  };
406
409
  environment_blocker?: 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>;
@@ -390,6 +391,7 @@ interface RiddleProofProfileResult {
390
391
  attempt?: number;
391
392
  attempts?: number;
392
393
  timed_out?: boolean;
394
+ artifact_recovery?: boolean;
393
395
  split_jobs?: Array<{
394
396
  viewport: string;
395
397
  job_id?: string;
@@ -401,6 +403,7 @@ interface RiddleProofProfileResult {
401
403
  attempt?: number;
402
404
  attempts?: number;
403
405
  timed_out?: boolean;
406
+ artifact_recovery?: boolean;
404
407
  }>;
405
408
  };
406
409
  environment_blocker?: 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.151",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",