@riddledc/riddle-proof 0.7.41 → 0.7.42

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
@@ -145,6 +145,8 @@ or as a stronger proof base before a change loop.
145
145
  "checks": [
146
146
  { "type": "route_loaded", "expected_path": "/pricing" },
147
147
  { "type": "selector_visible", "selector": "[data-testid='pricing-cards']" },
148
+ { "type": "selector_absent", "selector": "[data-testid='loading-spinner']" },
149
+ { "type": "selector_count_equals", "selector": "[data-testid='pricing-card']", "expected_count": 3 },
148
150
  { "type": "text_visible", "text": "Start building" },
149
151
  { "type": "no_mobile_horizontal_overflow" },
150
152
  { "type": "no_fatal_console_errors" }
@@ -252,6 +254,21 @@ only unallowed `error` / `assert` console events and page errors fail the check.
252
254
  Use `allowed_page_error_patterns`, `allowed_console_texts`, or
253
255
  `allowed_page_error_texts` for narrower matching when needed.
254
256
 
257
+ Use `selector_absent` when a forbidden element must not render, and
258
+ `selector_count_equals` / `selector_count_equal` / `selector_count_eq` when a
259
+ profile needs an exact DOM count rather than a lower bound:
260
+
261
+ ```json
262
+ [
263
+ { "type": "selector_absent", "selector": ".game-player-root iframe" },
264
+ { "type": "selector_count_equals", "selector": ".pricing-card", "expected_count": 3 }
265
+ ]
266
+ ```
267
+
268
+ These checks are useful for audit/no-diff profiles where the product should
269
+ show a fallback state and avoid rendering stale loaders, duplicate rows, or
270
+ missing-resource iframes.
271
+
255
272
  Use `selector_text_order` when a table, list, or card group must show visible
256
273
  items in a specific order after setup actions such as sorting or filtering:
257
274
 
@@ -13,7 +13,11 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
13
13
  var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
14
14
  "route_loaded",
15
15
  "selector_visible",
16
+ "selector_absent",
16
17
  "selector_count_at_least",
18
+ "selector_count_equals",
19
+ "selector_count_equal",
20
+ "selector_count_eq",
17
21
  "selector_text_order",
18
22
  "frame_text_visible",
19
23
  "frame_no_horizontal_overflow",
@@ -353,7 +357,7 @@ function normalizeCheck(input, index) {
353
357
  if (!isSupportedCheckType(type)) {
354
358
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
355
359
  }
356
- if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
360
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue(input.selector)) {
357
361
  throw new Error(`checks[${index}] ${type} requires selector.`);
358
362
  }
359
363
  if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
@@ -368,6 +372,10 @@ function normalizeCheck(input, index) {
368
372
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
369
373
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
370
374
  }
375
+ const expectedCount = numberValue(input.expected_count) ?? numberValue(input.expectedCount) ?? numberValue(input.count);
376
+ if ((type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && expectedCount === void 0) {
377
+ throw new Error(`checks[${index}] ${type} requires expected_count.`);
378
+ }
371
379
  const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
372
380
  if (type === "selector_text_order") {
373
381
  if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
@@ -399,6 +407,7 @@ function normalizeCheck(input, index) {
399
407
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
400
408
  allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
401
409
  min_count: numberValue(input.min_count),
410
+ expected_count: expectedCount,
402
411
  max_overflow_px: numberValue(input.max_overflow_px),
403
412
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
404
413
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
@@ -666,6 +675,20 @@ function assessCheckFromEvidence(check, evidence) {
666
675
  message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
667
676
  };
668
677
  }
678
+ if (check.type === "selector_absent") {
679
+ const key = selectorKey(check);
680
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) > 0);
681
+ return {
682
+ type: check.type,
683
+ label: checkLabel(check),
684
+ status: failed.length ? "failed" : "passed",
685
+ evidence: {
686
+ selector: key,
687
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
688
+ },
689
+ message: failed.length ? `Selector ${key} was present in ${failed.length} viewport(s).` : void 0
690
+ };
691
+ }
669
692
  if (check.type === "selector_count_at_least") {
670
693
  const key = selectorKey(check);
671
694
  const minCount = check.min_count ?? 1;
@@ -682,6 +705,22 @@ function assessCheckFromEvidence(check, evidence) {
682
705
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
683
706
  };
684
707
  }
708
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
709
+ const key = selectorKey(check);
710
+ const expectedCount = check.expected_count ?? 0;
711
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) !== expectedCount);
712
+ return {
713
+ type: check.type,
714
+ label: checkLabel(check),
715
+ status: failed.length ? "failed" : "passed",
716
+ evidence: {
717
+ selector: key,
718
+ expected_count: expectedCount,
719
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
720
+ },
721
+ message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
722
+ };
723
+ }
685
724
  if (check.type === "selector_text_order") {
686
725
  const key = selectorKey(check);
687
726
  const expectedTexts = check.expected_texts || [];
@@ -1418,6 +1457,18 @@ function assessProfile(profile, evidence) {
1418
1457
  });
1419
1458
  continue;
1420
1459
  }
1460
+ if (check.type === "selector_absent") {
1461
+ const selector = check.selector || "";
1462
+ const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1463
+ checks.push({
1464
+ type: check.type,
1465
+ label: check.label || check.type,
1466
+ status: failed.length ? "failed" : "passed",
1467
+ evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1468
+ message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
1469
+ });
1470
+ continue;
1471
+ }
1421
1472
  if (check.type === "selector_count_at_least") {
1422
1473
  const selector = check.selector || "";
1423
1474
  const minCount = check.min_count == null ? 1 : check.min_count;
@@ -1431,6 +1482,19 @@ function assessProfile(profile, evidence) {
1431
1482
  });
1432
1483
  continue;
1433
1484
  }
1485
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
1486
+ const selector = check.selector || "";
1487
+ const expectedCount = check.expected_count == null ? 0 : check.expected_count;
1488
+ const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1489
+ checks.push({
1490
+ type: check.type,
1491
+ label: check.label || check.type,
1492
+ status: failed.length ? "failed" : "passed",
1493
+ evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1494
+ message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
1495
+ });
1496
+ continue;
1497
+ }
1434
1498
  if (check.type === "selector_text_order") {
1435
1499
  const selector = check.selector || "";
1436
1500
  const expectedTexts = check.expected_texts || [];
@@ -2447,7 +2511,16 @@ async function captureViewport(viewport) {
2447
2511
  const text_sequences = {};
2448
2512
  const text_matches = {};
2449
2513
  for (const check of profile.checks || []) {
2450
- if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
2514
+ if (
2515
+ (
2516
+ check.type === "selector_visible"
2517
+ || check.type === "selector_absent"
2518
+ || check.type === "selector_count_at_least"
2519
+ || check.type === "selector_count_equals"
2520
+ || check.type === "selector_count_equal"
2521
+ || check.type === "selector_count_eq"
2522
+ ) && check.selector
2523
+ ) {
2451
2524
  selectors[check.selector] = await selectorStats(check.selector);
2452
2525
  }
2453
2526
  if (check.type === "selector_text_order" && check.selector) {
package/dist/cli.cjs CHANGED
@@ -6886,7 +6886,11 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
6886
6886
  var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6887
6887
  "route_loaded",
6888
6888
  "selector_visible",
6889
+ "selector_absent",
6889
6890
  "selector_count_at_least",
6891
+ "selector_count_equals",
6892
+ "selector_count_equal",
6893
+ "selector_count_eq",
6890
6894
  "selector_text_order",
6891
6895
  "frame_text_visible",
6892
6896
  "frame_no_horizontal_overflow",
@@ -7226,7 +7230,7 @@ function normalizeCheck(input, index) {
7226
7230
  if (!isSupportedCheckType(type)) {
7227
7231
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
7228
7232
  }
7229
- if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue2(input.selector)) {
7233
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue2(input.selector)) {
7230
7234
  throw new Error(`checks[${index}] ${type} requires selector.`);
7231
7235
  }
7232
7236
  if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue2(input.selector)) {
@@ -7241,6 +7245,10 @@ function normalizeCheck(input, index) {
7241
7245
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
7242
7246
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
7243
7247
  }
7248
+ const expectedCount = numberValue(input.expected_count) ?? numberValue(input.expectedCount) ?? numberValue(input.count);
7249
+ if ((type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && expectedCount === void 0) {
7250
+ throw new Error(`checks[${index}] ${type} requires expected_count.`);
7251
+ }
7244
7252
  const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
7245
7253
  if (type === "selector_text_order") {
7246
7254
  if (!stringValue2(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
@@ -7272,6 +7280,7 @@ function normalizeCheck(input, index) {
7272
7280
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
7273
7281
  allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
7274
7282
  min_count: numberValue(input.min_count),
7283
+ expected_count: expectedCount,
7275
7284
  max_overflow_px: numberValue(input.max_overflow_px),
7276
7285
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
7277
7286
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
@@ -7539,6 +7548,20 @@ function assessCheckFromEvidence(check, evidence) {
7539
7548
  message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
7540
7549
  };
7541
7550
  }
7551
+ if (check.type === "selector_absent") {
7552
+ const key = selectorKey(check);
7553
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) > 0);
7554
+ return {
7555
+ type: check.type,
7556
+ label: checkLabel(check),
7557
+ status: failed.length ? "failed" : "passed",
7558
+ evidence: {
7559
+ selector: key,
7560
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
7561
+ },
7562
+ message: failed.length ? `Selector ${key} was present in ${failed.length} viewport(s).` : void 0
7563
+ };
7564
+ }
7542
7565
  if (check.type === "selector_count_at_least") {
7543
7566
  const key = selectorKey(check);
7544
7567
  const minCount = check.min_count ?? 1;
@@ -7555,6 +7578,22 @@ function assessCheckFromEvidence(check, evidence) {
7555
7578
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
7556
7579
  };
7557
7580
  }
7581
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
7582
+ const key = selectorKey(check);
7583
+ const expectedCount = check.expected_count ?? 0;
7584
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) !== expectedCount);
7585
+ return {
7586
+ type: check.type,
7587
+ label: checkLabel(check),
7588
+ status: failed.length ? "failed" : "passed",
7589
+ evidence: {
7590
+ selector: key,
7591
+ expected_count: expectedCount,
7592
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
7593
+ },
7594
+ message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
7595
+ };
7596
+ }
7558
7597
  if (check.type === "selector_text_order") {
7559
7598
  const key = selectorKey(check);
7560
7599
  const expectedTexts = check.expected_texts || [];
@@ -8275,6 +8314,18 @@ function assessProfile(profile, evidence) {
8275
8314
  });
8276
8315
  continue;
8277
8316
  }
8317
+ if (check.type === "selector_absent") {
8318
+ const selector = check.selector || "";
8319
+ const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
8320
+ checks.push({
8321
+ type: check.type,
8322
+ label: check.label || check.type,
8323
+ status: failed.length ? "failed" : "passed",
8324
+ evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8325
+ message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
8326
+ });
8327
+ continue;
8328
+ }
8278
8329
  if (check.type === "selector_count_at_least") {
8279
8330
  const selector = check.selector || "";
8280
8331
  const minCount = check.min_count == null ? 1 : check.min_count;
@@ -8288,6 +8339,19 @@ function assessProfile(profile, evidence) {
8288
8339
  });
8289
8340
  continue;
8290
8341
  }
8342
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
8343
+ const selector = check.selector || "";
8344
+ const expectedCount = check.expected_count == null ? 0 : check.expected_count;
8345
+ const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
8346
+ checks.push({
8347
+ type: check.type,
8348
+ label: check.label || check.type,
8349
+ status: failed.length ? "failed" : "passed",
8350
+ evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
8351
+ message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
8352
+ });
8353
+ continue;
8354
+ }
8291
8355
  if (check.type === "selector_text_order") {
8292
8356
  const selector = check.selector || "";
8293
8357
  const expectedTexts = check.expected_texts || [];
@@ -9304,7 +9368,16 @@ async function captureViewport(viewport) {
9304
9368
  const text_sequences = {};
9305
9369
  const text_matches = {};
9306
9370
  for (const check of profile.checks || []) {
9307
- if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
9371
+ if (
9372
+ (
9373
+ check.type === "selector_visible"
9374
+ || check.type === "selector_absent"
9375
+ || check.type === "selector_count_at_least"
9376
+ || check.type === "selector_count_equals"
9377
+ || check.type === "selector_count_equal"
9378
+ || check.type === "selector_count_eq"
9379
+ ) && check.selector
9380
+ ) {
9308
9381
  selectors[check.selector] = await selectorStats(check.selector);
9309
9382
  }
9310
9383
  if (check.type === "selector_text_order" && check.selector) {
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-6MVVU54I.js";
13
+ } from "./chunk-MT2OZCCP.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -8727,7 +8727,11 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
8727
8727
  var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
8728
8728
  "route_loaded",
8729
8729
  "selector_visible",
8730
+ "selector_absent",
8730
8731
  "selector_count_at_least",
8732
+ "selector_count_equals",
8733
+ "selector_count_equal",
8734
+ "selector_count_eq",
8731
8735
  "selector_text_order",
8732
8736
  "frame_text_visible",
8733
8737
  "frame_no_horizontal_overflow",
@@ -9067,7 +9071,7 @@ function normalizeCheck(input, index) {
9067
9071
  if (!isSupportedCheckType(type)) {
9068
9072
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
9069
9073
  }
9070
- if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue5(input.selector)) {
9074
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue5(input.selector)) {
9071
9075
  throw new Error(`checks[${index}] ${type} requires selector.`);
9072
9076
  }
9073
9077
  if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue5(input.selector)) {
@@ -9082,6 +9086,10 @@ function normalizeCheck(input, index) {
9082
9086
  if (type === "selector_count_at_least" && numberValue3(input.min_count) === void 0) {
9083
9087
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
9084
9088
  }
9089
+ const expectedCount = numberValue3(input.expected_count) ?? numberValue3(input.expectedCount) ?? numberValue3(input.count);
9090
+ if ((type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && expectedCount === void 0) {
9091
+ throw new Error(`checks[${index}] ${type} requires expected_count.`);
9092
+ }
9085
9093
  const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
9086
9094
  if (type === "selector_text_order") {
9087
9095
  if (!stringValue5(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
@@ -9113,6 +9121,7 @@ function normalizeCheck(input, index) {
9113
9121
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
9114
9122
  allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
9115
9123
  min_count: numberValue3(input.min_count),
9124
+ expected_count: expectedCount,
9116
9125
  max_overflow_px: numberValue3(input.max_overflow_px),
9117
9126
  timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
9118
9127
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
@@ -9380,6 +9389,20 @@ function assessCheckFromEvidence(check, evidence) {
9380
9389
  message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
9381
9390
  };
9382
9391
  }
9392
+ if (check.type === "selector_absent") {
9393
+ const key = selectorKey(check);
9394
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) > 0);
9395
+ return {
9396
+ type: check.type,
9397
+ label: checkLabel(check),
9398
+ status: failed.length ? "failed" : "passed",
9399
+ evidence: {
9400
+ selector: key,
9401
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
9402
+ },
9403
+ message: failed.length ? `Selector ${key} was present in ${failed.length} viewport(s).` : void 0
9404
+ };
9405
+ }
9383
9406
  if (check.type === "selector_count_at_least") {
9384
9407
  const key = selectorKey(check);
9385
9408
  const minCount = check.min_count ?? 1;
@@ -9396,6 +9419,22 @@ function assessCheckFromEvidence(check, evidence) {
9396
9419
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
9397
9420
  };
9398
9421
  }
9422
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
9423
+ const key = selectorKey(check);
9424
+ const expectedCount = check.expected_count ?? 0;
9425
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) !== expectedCount);
9426
+ return {
9427
+ type: check.type,
9428
+ label: checkLabel(check),
9429
+ status: failed.length ? "failed" : "passed",
9430
+ evidence: {
9431
+ selector: key,
9432
+ expected_count: expectedCount,
9433
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
9434
+ },
9435
+ message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
9436
+ };
9437
+ }
9399
9438
  if (check.type === "selector_text_order") {
9400
9439
  const key = selectorKey(check);
9401
9440
  const expectedTexts = check.expected_texts || [];
@@ -10132,6 +10171,18 @@ function assessProfile(profile, evidence) {
10132
10171
  });
10133
10172
  continue;
10134
10173
  }
10174
+ if (check.type === "selector_absent") {
10175
+ const selector = check.selector || "";
10176
+ const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
10177
+ checks.push({
10178
+ type: check.type,
10179
+ label: check.label || check.type,
10180
+ status: failed.length ? "failed" : "passed",
10181
+ evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10182
+ message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
10183
+ });
10184
+ continue;
10185
+ }
10135
10186
  if (check.type === "selector_count_at_least") {
10136
10187
  const selector = check.selector || "";
10137
10188
  const minCount = check.min_count == null ? 1 : check.min_count;
@@ -10145,6 +10196,19 @@ function assessProfile(profile, evidence) {
10145
10196
  });
10146
10197
  continue;
10147
10198
  }
10199
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
10200
+ const selector = check.selector || "";
10201
+ const expectedCount = check.expected_count == null ? 0 : check.expected_count;
10202
+ const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
10203
+ checks.push({
10204
+ type: check.type,
10205
+ label: check.label || check.type,
10206
+ status: failed.length ? "failed" : "passed",
10207
+ evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
10208
+ message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
10209
+ });
10210
+ continue;
10211
+ }
10148
10212
  if (check.type === "selector_text_order") {
10149
10213
  const selector = check.selector || "";
10150
10214
  const expectedTexts = check.expected_texts || [];
@@ -11161,7 +11225,16 @@ async function captureViewport(viewport) {
11161
11225
  const text_sequences = {};
11162
11226
  const text_matches = {};
11163
11227
  for (const check of profile.checks || []) {
11164
- if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
11228
+ if (
11229
+ (
11230
+ check.type === "selector_visible"
11231
+ || check.type === "selector_absent"
11232
+ || check.type === "selector_count_at_least"
11233
+ || check.type === "selector_count_equals"
11234
+ || check.type === "selector_count_equal"
11235
+ || check.type === "selector_count_eq"
11236
+ ) && check.selector
11237
+ ) {
11165
11238
  selectors[check.selector] = await selectorStats(check.selector);
11166
11239
  }
11167
11240
  if (check.type === "selector_text_order" && check.selector) {
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-6MVVU54I.js";
61
+ } from "./chunk-MT2OZCCP.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -56,7 +56,11 @@ var RIDDLE_PROOF_PROFILE_STATUSES = [
56
56
  var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
57
57
  "route_loaded",
58
58
  "selector_visible",
59
+ "selector_absent",
59
60
  "selector_count_at_least",
61
+ "selector_count_equals",
62
+ "selector_count_equal",
63
+ "selector_count_eq",
60
64
  "selector_text_order",
61
65
  "frame_text_visible",
62
66
  "frame_no_horizontal_overflow",
@@ -396,7 +400,7 @@ function normalizeCheck(input, index) {
396
400
  if (!isSupportedCheckType(type)) {
397
401
  throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
398
402
  }
399
- if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
403
+ if ((type === "selector_visible" || type === "selector_absent" || type === "selector_count_at_least" || type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && !stringValue(input.selector)) {
400
404
  throw new Error(`checks[${index}] ${type} requires selector.`);
401
405
  }
402
406
  if ((type === "frame_text_visible" || type === "frame_no_horizontal_overflow") && !stringValue(input.selector)) {
@@ -411,6 +415,10 @@ function normalizeCheck(input, index) {
411
415
  if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
412
416
  throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
413
417
  }
418
+ const expectedCount = numberValue(input.expected_count) ?? numberValue(input.expectedCount) ?? numberValue(input.count);
419
+ if ((type === "selector_count_equals" || type === "selector_count_equal" || type === "selector_count_eq") && expectedCount === void 0) {
420
+ throw new Error(`checks[${index}] ${type} requires expected_count.`);
421
+ }
414
422
  const expectedTexts = normalizeExpectedTexts(input.expected_texts ?? input.expectedTexts, index);
415
423
  if (type === "selector_text_order") {
416
424
  if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
@@ -442,6 +450,7 @@ function normalizeCheck(input, index) {
442
450
  allowed_page_error_texts: normalizeStringList(input.allowed_page_error_texts ?? input.allowedPageErrorTexts ?? input.allow_page_error_texts ?? input.allowPageErrorTexts, `checks[${index}] allowed_page_error_texts`),
443
451
  allowed_page_error_patterns: normalizeStringList(input.allowed_page_error_patterns ?? input.allowedPageErrorPatterns ?? input.allow_page_error_patterns ?? input.allowPageErrorPatterns, `checks[${index}] allowed_page_error_patterns`),
444
452
  min_count: numberValue(input.min_count),
453
+ expected_count: expectedCount,
445
454
  max_overflow_px: numberValue(input.max_overflow_px),
446
455
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
447
456
  run_direct_routes: input.run_direct_routes === false || input.runDirectRoutes === false ? false : true,
@@ -709,6 +718,20 @@ function assessCheckFromEvidence(check, evidence) {
709
718
  message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
710
719
  };
711
720
  }
721
+ if (check.type === "selector_absent") {
722
+ const key = selectorKey(check);
723
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) > 0);
724
+ return {
725
+ type: check.type,
726
+ label: checkLabel(check),
727
+ status: failed.length ? "failed" : "passed",
728
+ evidence: {
729
+ selector: key,
730
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
731
+ },
732
+ message: failed.length ? `Selector ${key} was present in ${failed.length} viewport(s).` : void 0
733
+ };
734
+ }
712
735
  if (check.type === "selector_count_at_least") {
713
736
  const key = selectorKey(check);
714
737
  const minCount = check.min_count ?? 1;
@@ -725,6 +748,22 @@ function assessCheckFromEvidence(check, evidence) {
725
748
  message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
726
749
  };
727
750
  }
751
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
752
+ const key = selectorKey(check);
753
+ const expectedCount = check.expected_count ?? 0;
754
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) !== expectedCount);
755
+ return {
756
+ type: check.type,
757
+ label: checkLabel(check),
758
+ status: failed.length ? "failed" : "passed",
759
+ evidence: {
760
+ selector: key,
761
+ expected_count: expectedCount,
762
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
763
+ },
764
+ message: failed.length ? `Selector ${key} count did not equal ${expectedCount} in ${failed.length} viewport(s).` : void 0
765
+ };
766
+ }
728
767
  if (check.type === "selector_text_order") {
729
768
  const key = selectorKey(check);
730
769
  const expectedTexts = check.expected_texts || [];
@@ -1461,6 +1500,18 @@ function assessProfile(profile, evidence) {
1461
1500
  });
1462
1501
  continue;
1463
1502
  }
1503
+ if (check.type === "selector_absent") {
1504
+ const selector = check.selector || "";
1505
+ const failed = viewports.filter((viewport) => viewport.selectors && viewport.selectors[selector] && viewport.selectors[selector].count > 0);
1506
+ checks.push({
1507
+ type: check.type,
1508
+ label: check.label || check.type,
1509
+ status: failed.length ? "failed" : "passed",
1510
+ evidence: { selector, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1511
+ message: failed.length ? "Selector " + selector + " was present in " + failed.length + " viewport(s)." : undefined,
1512
+ });
1513
+ continue;
1514
+ }
1464
1515
  if (check.type === "selector_count_at_least") {
1465
1516
  const selector = check.selector || "";
1466
1517
  const minCount = check.min_count == null ? 1 : check.min_count;
@@ -1474,6 +1525,19 @@ function assessProfile(profile, evidence) {
1474
1525
  });
1475
1526
  continue;
1476
1527
  }
1528
+ if (check.type === "selector_count_equals" || check.type === "selector_count_equal" || check.type === "selector_count_eq") {
1529
+ const selector = check.selector || "";
1530
+ const expectedCount = check.expected_count == null ? 0 : check.expected_count;
1531
+ const failed = viewports.filter((viewport) => (viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) !== expectedCount);
1532
+ checks.push({
1533
+ type: check.type,
1534
+ label: check.label || check.type,
1535
+ status: failed.length ? "failed" : "passed",
1536
+ evidence: { selector, expected_count: expectedCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
1537
+ message: failed.length ? "Selector " + selector + " count did not equal " + expectedCount + " in " + failed.length + " viewport(s)." : undefined,
1538
+ });
1539
+ continue;
1540
+ }
1477
1541
  if (check.type === "selector_text_order") {
1478
1542
  const selector = check.selector || "";
1479
1543
  const expectedTexts = check.expected_texts || [];
@@ -2490,7 +2554,16 @@ async function captureViewport(viewport) {
2490
2554
  const text_sequences = {};
2491
2555
  const text_matches = {};
2492
2556
  for (const check of profile.checks || []) {
2493
- if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
2557
+ if (
2558
+ (
2559
+ check.type === "selector_visible"
2560
+ || check.type === "selector_absent"
2561
+ || check.type === "selector_count_at_least"
2562
+ || check.type === "selector_count_equals"
2563
+ || check.type === "selector_count_equal"
2564
+ || check.type === "selector_count_eq"
2565
+ ) && check.selector
2566
+ ) {
2494
2567
  selectors[check.selector] = await selectorStats(check.selector);
2495
2568
  }
2496
2569
  if (check.type === "selector_text_order" && check.selector) {
@@ -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", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -88,6 +88,7 @@ interface RiddleProofProfileCheck {
88
88
  allowed_page_error_texts?: string[];
89
89
  allowed_page_error_patterns?: string[];
90
90
  min_count?: number;
91
+ expected_count?: number;
91
92
  max_overflow_px?: number;
92
93
  timeout_ms?: number;
93
94
  run_direct_routes?: boolean;
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", "selector_visible", "selector_count_at_least", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
7
+ declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
8
  declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
@@ -88,6 +88,7 @@ interface RiddleProofProfileCheck {
88
88
  allowed_page_error_texts?: string[];
89
89
  allowed_page_error_patterns?: string[];
90
90
  min_count?: number;
91
+ expected_count?: number;
91
92
  max_overflow_px?: number;
92
93
  timeout_ms?: number;
93
94
  run_direct_routes?: boolean;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-6MVVU54I.js";
22
+ } from "./chunk-MT2OZCCP.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.41",
3
+ "version": "0.7.42",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",