@riddledc/riddle-proof 0.7.87 → 0.7.89

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
@@ -103,7 +103,9 @@ targeted copy or UI changes, the visual gate can use a lower targeted threshold
103
103
  only when route/text semantics match the requested change. When before/after
104
104
  image artifacts can be compared directly, the runtime also records the changed
105
105
  region bounding box and refuses the targeted threshold if that exact region is
106
- too broad for a localized UI change. Missing or unmeasured visual deltas
106
+ too broad for a localized UI change. External visual-delta payloads can provide
107
+ that region with common field names such as `changed_region`, `bounds`, `bbox`,
108
+ `boundingBox`, `xMin`/`xMax`, or `x1`/`x2`. Missing or unmeasured visual deltas
107
109
  continue through evidence recovery instead of being treated as a passing proof.
108
110
 
109
111
  ## CI / Profile Mode
package/dist/cli.cjs CHANGED
@@ -11721,6 +11721,28 @@ function profileEnvironmentBlockerMarkdown(result) {
11721
11721
  if (minimumPurchaseDollars !== void 0) lines.push(`- minimum purchase: $${minimumPurchaseDollars}`);
11722
11722
  return lines;
11723
11723
  }
11724
+ function profileCliDiagnosticLine(result) {
11725
+ if (result.status !== "environment_blocked") return void 0;
11726
+ const blocker = cliRecord(result.environment_blocker);
11727
+ if (blocker?.reason === "insufficient_balance") {
11728
+ const requiredSeconds = cliFiniteNumber(blocker.required_seconds);
11729
+ const availableSeconds = cliFiniteNumber(blocker.available_seconds);
11730
+ const deficitSeconds = cliFiniteNumber(blocker.deficit_seconds);
11731
+ const parts = [
11732
+ requiredSeconds === void 0 ? "" : `required=${requiredSeconds}s`,
11733
+ availableSeconds === void 0 ? "" : `available=${availableSeconds}s`,
11734
+ deficitSeconds === void 0 ? "" : `deficit=${deficitSeconds}s`
11735
+ ].filter(Boolean);
11736
+ return `[riddle-profile] environment_blocked insufficient_balance${parts.length ? ` ${parts.join(" ")}` : ""}`;
11737
+ }
11738
+ const source = cliString(blocker?.source);
11739
+ const endpoint = cliString(blocker?.endpoint);
11740
+ const httpStatus = cliFiniteNumber(blocker?.http_status);
11741
+ if (source || endpoint || httpStatus !== void 0) {
11742
+ return `[riddle-profile] environment_blocked${source ? ` source=${source}` : ""}${endpoint ? ` endpoint=${endpoint}` : ""}${httpStatus === void 0 ? "" : ` http_status=${httpStatus}`}`;
11743
+ }
11744
+ return `[riddle-profile] environment_blocked ${result.summary}`;
11745
+ }
11724
11746
  function profileSetupSummaryMarkdown(result) {
11725
11747
  const setupCheck = result.checks.find((check) => check.type === "setup_actions_succeeded");
11726
11748
  const setupSummary = cliRecord(setupCheck?.evidence?.setup_summary);
@@ -12020,6 +12042,11 @@ async function main() {
12020
12042
  const profile = normalizeProfileForCli(options);
12021
12043
  const result = await runProfileForCli(profile, options);
12022
12044
  writeProfileOutput(profileOutputDirOption(options), result);
12045
+ const diagnosticLine = profileCliDiagnosticLine(result);
12046
+ if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
12047
+ process.stderr.write(`${diagnosticLine}
12048
+ `);
12049
+ }
12023
12050
  process.stdout.write(`${JSON.stringify(result, null, 2)}
12024
12051
  `);
12025
12052
  process.exitCode = profileStatusExitCode(profile, result.status);
package/dist/cli.js CHANGED
@@ -456,6 +456,28 @@ function profileEnvironmentBlockerMarkdown(result) {
456
456
  if (minimumPurchaseDollars !== void 0) lines.push(`- minimum purchase: $${minimumPurchaseDollars}`);
457
457
  return lines;
458
458
  }
459
+ function profileCliDiagnosticLine(result) {
460
+ if (result.status !== "environment_blocked") return void 0;
461
+ const blocker = cliRecord(result.environment_blocker);
462
+ if (blocker?.reason === "insufficient_balance") {
463
+ const requiredSeconds = cliFiniteNumber(blocker.required_seconds);
464
+ const availableSeconds = cliFiniteNumber(blocker.available_seconds);
465
+ const deficitSeconds = cliFiniteNumber(blocker.deficit_seconds);
466
+ const parts = [
467
+ requiredSeconds === void 0 ? "" : `required=${requiredSeconds}s`,
468
+ availableSeconds === void 0 ? "" : `available=${availableSeconds}s`,
469
+ deficitSeconds === void 0 ? "" : `deficit=${deficitSeconds}s`
470
+ ].filter(Boolean);
471
+ return `[riddle-profile] environment_blocked insufficient_balance${parts.length ? ` ${parts.join(" ")}` : ""}`;
472
+ }
473
+ const source = cliString(blocker?.source);
474
+ const endpoint = cliString(blocker?.endpoint);
475
+ const httpStatus = cliFiniteNumber(blocker?.http_status);
476
+ if (source || endpoint || httpStatus !== void 0) {
477
+ return `[riddle-profile] environment_blocked${source ? ` source=${source}` : ""}${endpoint ? ` endpoint=${endpoint}` : ""}${httpStatus === void 0 ? "" : ` http_status=${httpStatus}`}`;
478
+ }
479
+ return `[riddle-profile] environment_blocked ${result.summary}`;
480
+ }
459
481
  function profileSetupSummaryMarkdown(result) {
460
482
  const setupCheck = result.checks.find((check) => check.type === "setup_actions_succeeded");
461
483
  const setupSummary = cliRecord(setupCheck?.evidence?.setup_summary);
@@ -755,6 +777,11 @@ async function main() {
755
777
  const profile = normalizeProfileForCli(options);
756
778
  const result = await runProfileForCli(profile, options);
757
779
  writeProfileOutput(profileOutputDirOption(options), result);
780
+ const diagnosticLine = profileCliDiagnosticLine(result);
781
+ if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
782
+ process.stderr.write(`${diagnosticLine}
783
+ `);
784
+ }
758
785
  process.stdout.write(`${JSON.stringify(result, null, 2)}
759
786
  `);
760
787
  process.exitCode = profileStatusExitCode(profile, result.status);
@@ -120,7 +120,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
120
120
  target_image_hash: string;
121
121
  viewport_matrix_json: string;
122
122
  deterministic_setup_json: string;
123
- reference: "before" | "prod" | "both";
123
+ reference: "prod" | "before" | "both";
124
124
  base_branch: string;
125
125
  before_ref: string;
126
126
  allow_static_preview_fallback: string;
@@ -120,7 +120,7 @@ declare function buildSetupArgs(params: WorkflowParams, config: ReturnType<typeo
120
120
  target_image_hash: string;
121
121
  viewport_matrix_json: string;
122
122
  deterministic_setup_json: string;
123
- reference: "before" | "prod" | "both";
123
+ reference: "prod" | "before" | "both";
124
124
  base_branch: string;
125
125
  before_ref: string;
126
126
  allow_static_preview_fallback: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.87",
3
+ "version": "0.7.89",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -100,14 +100,40 @@ VISUAL_CHANGED_REGION_KEYS = {
100
100
  'changed_region', 'changedregion', 'diff_region', 'diffregion',
101
101
  'difference_region', 'differenceregion', 'changed_bounds',
102
102
  'changedbounds', 'diff_bounds', 'diffbounds', 'difference_bounds',
103
- 'differencebounds', 'bounding_box', 'boundingbox', 'bbox', 'bounds',
103
+ 'differencebounds', 'changed_bounding_box', 'changedboundingbox',
104
+ 'changed_bbox', 'changedbbox', 'diff_bounding_box', 'diffboundingbox',
105
+ 'diff_bbox', 'diffbbox', 'difference_bounding_box',
106
+ 'differenceboundingbox', 'difference_bbox', 'differencebbox',
107
+ 'mismatch_region', 'mismatchregion', 'mismatch_bounds',
108
+ 'mismatchbounds', 'mismatch_bounding_box', 'mismatchboundingbox',
109
+ 'visual_diff_region', 'visualdiffregion', 'visual_diff_bounds',
110
+ 'visualdiffbounds', 'visual_diff_bounding_box',
111
+ 'visualdiffboundingbox', 'bounding_box', 'boundingbox',
112
+ 'bounding_rect', 'boundingrect', 'bbox', 'bounds', 'box', 'rect',
113
+ 'rectangle', 'region',
114
+ }
115
+ VISUAL_REGION_X_KEYS = {
116
+ 'x', 'x1', 'left', 'min_x', 'minx', 'x_min', 'xmin',
117
+ 'start_x', 'startx', 'from_x', 'fromx',
118
+ }
119
+ VISUAL_REGION_Y_KEYS = {
120
+ 'y', 'y1', 'top', 'min_y', 'miny', 'y_min', 'ymin',
121
+ 'start_y', 'starty', 'from_y', 'fromy',
122
+ }
123
+ VISUAL_REGION_WIDTH_KEYS = {
124
+ 'width', 'w', 'region_width', 'regionwidth', 'bbox_width', 'bboxwidth',
125
+ }
126
+ VISUAL_REGION_HEIGHT_KEYS = {
127
+ 'height', 'h', 'region_height', 'regionheight', 'bbox_height', 'bboxheight',
128
+ }
129
+ VISUAL_REGION_RIGHT_KEYS = {
130
+ 'right', 'x2', 'max_x', 'maxx', 'x_max', 'xmax',
131
+ 'end_x', 'endx', 'to_x', 'tox',
132
+ }
133
+ VISUAL_REGION_BOTTOM_KEYS = {
134
+ 'bottom', 'y2', 'max_y', 'maxy', 'y_max', 'ymax',
135
+ 'end_y', 'endy', 'to_y', 'toy',
104
136
  }
105
- VISUAL_REGION_X_KEYS = {'x', 'left', 'min_x', 'minx'}
106
- VISUAL_REGION_Y_KEYS = {'y', 'top', 'min_y', 'miny'}
107
- VISUAL_REGION_WIDTH_KEYS = {'width', 'w'}
108
- VISUAL_REGION_HEIGHT_KEYS = {'height', 'h'}
109
- VISUAL_REGION_RIGHT_KEYS = {'right', 'max_x', 'maxx'}
110
- VISUAL_REGION_BOTTOM_KEYS = {'bottom', 'max_y', 'maxy'}
111
137
 
112
138
 
113
139
  def capture_script_saves_screenshot(script):
@@ -1022,16 +1048,27 @@ def visual_delta_semantic_support(state=None, semantic_context=None):
1022
1048
  }
1023
1049
 
1024
1050
 
1051
+ def find_direct_metric_value(value, key_names):
1052
+ if not isinstance(value, dict):
1053
+ return None
1054
+ for raw_key, raw_value in value.items():
1055
+ if normalize_metric_key(raw_key) in key_names:
1056
+ number = metric_number(raw_value)
1057
+ if number is not None:
1058
+ return number
1059
+ return None
1060
+
1061
+
1025
1062
  def normalize_changed_region(value, total_pixels=None, changed_pixels=None, image_width=None, image_height=None, dimension_mismatch=False):
1026
1063
  if not isinstance(value, dict):
1027
1064
  return None
1028
1065
 
1029
- x = find_metric_value(value, VISUAL_REGION_X_KEYS)
1030
- y = find_metric_value(value, VISUAL_REGION_Y_KEYS)
1031
- width = find_metric_value(value, VISUAL_REGION_WIDTH_KEYS)
1032
- height = find_metric_value(value, VISUAL_REGION_HEIGHT_KEYS)
1033
- right = find_metric_value(value, VISUAL_REGION_RIGHT_KEYS)
1034
- bottom = find_metric_value(value, VISUAL_REGION_BOTTOM_KEYS)
1066
+ x = find_direct_metric_value(value, VISUAL_REGION_X_KEYS)
1067
+ y = find_direct_metric_value(value, VISUAL_REGION_Y_KEYS)
1068
+ width = find_direct_metric_value(value, VISUAL_REGION_WIDTH_KEYS)
1069
+ height = find_direct_metric_value(value, VISUAL_REGION_HEIGHT_KEYS)
1070
+ right = find_direct_metric_value(value, VISUAL_REGION_RIGHT_KEYS)
1071
+ bottom = find_direct_metric_value(value, VISUAL_REGION_BOTTOM_KEYS)
1035
1072
 
1036
1073
  if width is None and x is not None and right is not None and right > x:
1037
1074
  width = right - x
@@ -1091,10 +1128,31 @@ def normalize_changed_region(value, total_pixels=None, changed_pixels=None, imag
1091
1128
  }
1092
1129
 
1093
1130
 
1131
+ def looks_like_changed_region(value):
1132
+ if not isinstance(value, dict):
1133
+ return False
1134
+ keys = {normalize_metric_key(key) for key in value.keys()}
1135
+ has_x = bool(keys & VISUAL_REGION_X_KEYS)
1136
+ has_y = bool(keys & VISUAL_REGION_Y_KEYS)
1137
+ has_size = bool(keys & VISUAL_REGION_WIDTH_KEYS) and bool(keys & VISUAL_REGION_HEIGHT_KEYS)
1138
+ has_edges = bool(keys & VISUAL_REGION_RIGHT_KEYS) and bool(keys & VISUAL_REGION_BOTTOM_KEYS)
1139
+ return has_x and has_y and (has_size or has_edges)
1140
+
1141
+
1094
1142
  def find_changed_region(value, total_pixels=None, changed_pixels=None, image_width=None, image_height=None, depth=0):
1095
1143
  if depth > 7:
1096
1144
  return None
1097
1145
  if isinstance(value, dict):
1146
+ if looks_like_changed_region(value):
1147
+ region = normalize_changed_region(
1148
+ value,
1149
+ total_pixels=total_pixels,
1150
+ changed_pixels=changed_pixels,
1151
+ image_width=image_width,
1152
+ image_height=image_height,
1153
+ )
1154
+ if region is not None:
1155
+ return region
1098
1156
  for raw_key, raw_value in value.items():
1099
1157
  if normalize_metric_key(raw_key) in VISUAL_CHANGED_REGION_KEYS:
1100
1158
  region = normalize_changed_region(
@@ -707,6 +707,7 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
707
707
  'changePercent': '1.45',
708
708
  'diffPixelCount': 14094,
709
709
  'totalPixels': 972000,
710
+ 'diffBoundingBox': {'xMin': 10, 'yMin': 20, 'xMax': 110, 'yMax': 70},
710
711
  },
711
712
  },
712
713
  })
@@ -714,6 +715,28 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
714
715
  assert visual_diff_delta['passed'] is True
715
716
  assert visual_diff_delta['change_percent'] == 1.45
716
717
  assert visual_diff_delta['changed_pixels'] == 14094
718
+ assert visual_diff_delta['changed_region']['x'] == 10
719
+ assert visual_diff_delta['changed_region']['width'] == 100
720
+ assert visual_diff_delta['changed_region']['height'] == 50
721
+
722
+ root_region_delta = namespace['extract_visual_delta']({
723
+ 'ok': True,
724
+ 'visual_diff': {
725
+ 'diffPercentage': 0.04,
726
+ 'differentPixels': 320,
727
+ 'totalPixels': 972000,
728
+ 'dimensions': {'width': 1080, 'height': 900},
729
+ 'x1': 24,
730
+ 'y1': 36,
731
+ 'x2': 64,
732
+ 'y2': 56,
733
+ },
734
+ })
735
+ assert root_region_delta['status'] == 'measured'
736
+ assert root_region_delta['changed_region']['x'] == 24
737
+ assert root_region_delta['changed_region']['y'] == 36
738
+ assert root_region_delta['changed_region']['width'] == 40
739
+ assert root_region_delta['changed_region']['height'] == 20
717
740
 
718
741
  viewport_status = namespace['capture_viewport_matrix_status'](
719
742
  {