@riddledc/riddle-proof 0.8.79 → 0.8.81

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
@@ -309,6 +309,15 @@ or as a stronger proof base before a change loop.
309
309
  }
310
310
  ```
311
311
 
312
+ Viewport entries may set `hasTouch` and `isMobile` when the behavior under
313
+ test depends on a touch-capable browser context rather than dimensions alone.
314
+ The snake-case aliases `has_touch` and `is_mobile` are accepted at profile
315
+ input boundaries. For example:
316
+
317
+ ```json
318
+ { "name": "ipad", "width": 820, "height": 1180, "hasTouch": true, "isMobile": true }
319
+ ```
320
+
312
321
  For early proof authoring, `profile-suggest` can turn a route plus changed file,
313
322
  selector, and changed-text hints into a conservative draft profile:
314
323
 
@@ -499,6 +508,7 @@ The package includes generic starter profiles:
499
508
  - `examples/profiles/handled-recovery-action-malformed-success.json` for action recovery profiles where the request succeeds at HTTP level but returns an unusable body.
500
509
  - `examples/profiles/terminal-result-partial-evidence.json` for API-console terminal error or timeout receipts that preserve partial screenshot, console, and HAR evidence.
501
510
  - `examples/profiles/gameplay-window-call-until.json` for gameplay profiles that wait on a runtime state contract instead of a fixed sleep.
511
+ - `examples/profiles/ordered-trace.json` for interaction profiles that require named runtime events to occur in strict temporal order.
502
512
  - `examples/profiles/spa-route-exit-state-hygiene.json` for SPA routes that must clean up short-lived proof helpers, receipts, timers, input state, or touch state after visible UI navigation exits the route.
503
513
 
504
514
  Copy one of those shapes into a repository profile directory and replace the
@@ -808,6 +818,44 @@ both first and last per-viewport receipts before filling remaining space, so
808
818
  late lifecycle phases such as terminal or restart remain visible in compact
809
819
  summaries.
810
820
 
821
+ Use `ordered_trace` when one point-in-time assertion cannot establish the
822
+ behavior, such as input -> response -> motion -> release -> idle. A labeled
823
+ `window_eval`, `window_call`, or `window_call_until` setup action must return a
824
+ JSON object containing the trace. The check declares predicates for each named
825
+ event and records the earliest strictly increasing witness indices plus compact
826
+ observed values:
827
+
828
+ ```json
829
+ {
830
+ "type": "ordered_trace",
831
+ "label": "input to release",
832
+ "setup_action_label": "capture interaction trace",
833
+ "trace_path": "trace",
834
+ "events": [
835
+ {
836
+ "label": "input",
837
+ "predicates": [{ "path": "target", "op": "abs_gte", "value": 0.8 }]
838
+ },
839
+ {
840
+ "label": "response",
841
+ "predicates": [{ "path": "applied", "op": "abs_gte", "value": 0.1 }]
842
+ },
843
+ {
844
+ "label": "released",
845
+ "predicates": [{ "path": "active", "op": "equals", "value": false }]
846
+ }
847
+ ]
848
+ }
849
+ ```
850
+
851
+ Supported predicate operators are `exists`, `equals`, `not_equals`, `truthy`,
852
+ `falsy`, `gt`, `gte`, `lt`, `lte`, `abs_gt`, `abs_gte`, `abs_lt`, and
853
+ `abs_lte`. Missing or empty traces and fields that never appear are
854
+ `proof_insufficient`; a complete trace that does not contain the sequence is a
855
+ `failed` product check. One sample cannot witness two successive events. This
856
+ keeps missing instrumentation distinct from a measured regression and lets an
857
+ ordinary Change Proof `check_status_transition` require `failed -> passed`.
858
+
811
859
  `target.timeout_sec` is optional. Use it for known-heavy profile targets so the
812
860
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
813
861
  still overrides the profile value for one-off runs.
@@ -12,9 +12,9 @@ import {
12
12
  parseRiddleProofHandoffReceipt,
13
13
  riddleProofChangeReceiptHtml,
14
14
  riddleProofChangeReceiptMarkdown
15
- } from "./chunk-6VFS2JFR.js";
15
+ } from "./chunk-LR4UYJDY.js";
16
16
  import "./chunk-MEVVL4TI.js";
17
- import "./chunk-FCSJZBC5.js";
17
+ import "./chunk-2K3DIK7A.js";
18
18
  import "./chunk-MLKGABMK.js";
19
19
  export {
20
20
  RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
@@ -26,6 +26,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
26
26
  "selector_text_visible",
27
27
  "selector_text_absent",
28
28
  "selector_text_order",
29
+ "ordered_trace",
29
30
  "observe_within",
30
31
  "frame_text_visible",
31
32
  "frame_url_equals",
@@ -89,6 +90,21 @@ var RIDDLE_PROOF_PROFILE_NETWORK_ABORT_ERROR_CODES = [
89
90
  "timedout",
90
91
  "failed"
91
92
  ];
93
+ var RIDDLE_PROOF_ORDERED_TRACE_OPERATORS = [
94
+ "exists",
95
+ "equals",
96
+ "not_equals",
97
+ "truthy",
98
+ "falsy",
99
+ "gt",
100
+ "gte",
101
+ "lt",
102
+ "lte",
103
+ "abs_gt",
104
+ "abs_gte",
105
+ "abs_lt",
106
+ "abs_lte"
107
+ ];
92
108
  function uniqueNonEmptyStrings(values) {
93
109
  const seen = /* @__PURE__ */ new Set();
94
110
  const result = [];
@@ -380,6 +396,185 @@ function resolveJsonPath(root, path) {
380
396
  }
381
397
  return { exists: true, value: current };
382
398
  }
399
+ function assessRiddleProofOrderedTrace(trace, events) {
400
+ const insufficient = (reason, traceLength = Array.isArray(trace) ? trace.length : 0, witnesses2 = [], missingEvent, missingPaths) => ({
401
+ version: "riddle-proof.ordered-trace-assessment.v1",
402
+ status: "proof_insufficient",
403
+ trace_length: traceLength,
404
+ witnesses: witnesses2,
405
+ missing_event: missingEvent,
406
+ missing_paths: missingPaths,
407
+ reason
408
+ });
409
+ const parsePath = (path) => {
410
+ const segments = [];
411
+ let token = "";
412
+ const pushToken = () => {
413
+ const value = token.trim();
414
+ if (value) segments.push(value);
415
+ token = "";
416
+ };
417
+ for (let index = 0; index < path.length; index += 1) {
418
+ const char = path[index];
419
+ if (char === ".") {
420
+ pushToken();
421
+ continue;
422
+ }
423
+ if (char !== "[") {
424
+ token += char;
425
+ continue;
426
+ }
427
+ pushToken();
428
+ const closeIndex = path.indexOf("]", index + 1);
429
+ if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
430
+ const bracket = path.slice(index + 1, closeIndex).trim();
431
+ if (!bracket) throw new Error(`empty bracket at ${index}`);
432
+ if (/^\d+$/.test(bracket)) {
433
+ segments.push(Number(bracket));
434
+ } else {
435
+ segments.push(bracket.replace(/^['"]|['"]$/g, ""));
436
+ }
437
+ index = closeIndex;
438
+ }
439
+ pushToken();
440
+ return segments;
441
+ };
442
+ const resolve = (root, path) => {
443
+ let segments;
444
+ try {
445
+ segments = parsePath(path);
446
+ } catch {
447
+ return { exists: false };
448
+ }
449
+ let current = root;
450
+ for (const segment of segments) {
451
+ if (Array.isArray(current)) {
452
+ const index = typeof segment === "number" ? segment : /^\d+$/.test(segment) ? Number(segment) : -1;
453
+ if (index < 0 || index >= current.length) return { exists: false };
454
+ current = current[index];
455
+ continue;
456
+ }
457
+ if (typeof segment !== "string" || current === null || typeof current !== "object") return { exists: false };
458
+ if (!Object.hasOwn(current, segment)) return { exists: false };
459
+ current = current[segment];
460
+ }
461
+ return { exists: true, value: current };
462
+ };
463
+ const valuesEqual = (left, right) => {
464
+ if (Object.is(left, right)) return true;
465
+ if (Array.isArray(left) || Array.isArray(right)) {
466
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => valuesEqual(value, right[index]));
467
+ }
468
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
469
+ const leftRecord = left;
470
+ const rightRecord = right;
471
+ const leftKeys = Object.keys(leftRecord).sort();
472
+ const rightKeys = Object.keys(rightRecord).sort();
473
+ return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && valuesEqual(leftRecord[key], rightRecord[key]));
474
+ };
475
+ const numericOperator = (op) => [
476
+ "gt",
477
+ "gte",
478
+ "lt",
479
+ "lte",
480
+ "abs_gt",
481
+ "abs_gte",
482
+ "abs_lt",
483
+ "abs_lte"
484
+ ].includes(op);
485
+ const matches = (value, predicate) => {
486
+ if (predicate.op === "exists") return true;
487
+ if (predicate.op === "equals") return valuesEqual(value, predicate.value);
488
+ if (predicate.op === "not_equals") return !valuesEqual(value, predicate.value);
489
+ if (predicate.op === "truthy") return Boolean(value);
490
+ if (predicate.op === "falsy") return !value;
491
+ const observed = typeof value === "number" ? value : Number.NaN;
492
+ const expected = typeof predicate.value === "number" ? predicate.value : Number.NaN;
493
+ if (!Number.isFinite(observed) || !Number.isFinite(expected)) return false;
494
+ const candidate = predicate.op.startsWith("abs_") ? Math.abs(observed) : observed;
495
+ if (predicate.op === "gt" || predicate.op === "abs_gt") return candidate > expected;
496
+ if (predicate.op === "gte" || predicate.op === "abs_gte") return candidate >= expected;
497
+ if (predicate.op === "lt" || predicate.op === "abs_lt") return candidate < expected;
498
+ return candidate <= expected;
499
+ };
500
+ if (!Array.isArray(trace) || trace.length === 0) return insufficient("trace_missing_or_empty");
501
+ if (!Array.isArray(events) || events.length === 0) return insufficient("events_missing", trace.length);
502
+ for (const event of events) {
503
+ const missingPaths = event.predicates.filter((predicate) => !trace.some((sample) => {
504
+ const resolved = resolve(sample, predicate.path);
505
+ return resolved.exists && (!numericOperator(predicate.op) || typeof resolved.value === "number" && Number.isFinite(resolved.value));
506
+ })).map((predicate) => predicate.path);
507
+ if (missingPaths.length) {
508
+ return insufficient("required_trace_field_missing", trace.length, [], event.label, Array.from(new Set(missingPaths)));
509
+ }
510
+ }
511
+ const witnesses = [];
512
+ let cursor = 0;
513
+ for (const event of events) {
514
+ let witnessIndex = -1;
515
+ for (let index = cursor; index < trace.length; index += 1) {
516
+ if (event.predicates.every((predicate) => {
517
+ const resolved = resolve(trace[index], predicate.path);
518
+ return resolved.exists && matches(resolved.value, predicate);
519
+ })) {
520
+ witnessIndex = index;
521
+ break;
522
+ }
523
+ }
524
+ if (witnessIndex < 0) {
525
+ return {
526
+ version: "riddle-proof.ordered-trace-assessment.v1",
527
+ status: "failed",
528
+ trace_length: trace.length,
529
+ witnesses,
530
+ missing_event: event.label,
531
+ reason: "ordered_event_not_observed"
532
+ };
533
+ }
534
+ witnesses.push({
535
+ label: event.label,
536
+ index: witnessIndex,
537
+ observations: event.predicates.map((predicate) => {
538
+ const observed = resolve(trace[witnessIndex], predicate.path).value;
539
+ return {
540
+ path: predicate.path,
541
+ op: predicate.op,
542
+ expected: predicate.value,
543
+ observed
544
+ };
545
+ })
546
+ });
547
+ cursor = witnessIndex + 1;
548
+ }
549
+ return {
550
+ version: "riddle-proof.ordered-trace-assessment.v1",
551
+ status: "passed",
552
+ trace_length: trace.length,
553
+ witnesses
554
+ };
555
+ }
556
+ function assessRiddleProofOrderedTraceSetupResults(results, setupActionLabel, tracePath, events) {
557
+ const insufficient = (reason) => ({
558
+ version: "riddle-proof.ordered-trace-assessment.v1",
559
+ status: "proof_insufficient",
560
+ trace_length: 0,
561
+ witnesses: [],
562
+ reason
563
+ });
564
+ if (!Array.isArray(results)) return insufficient("setup_results_missing");
565
+ const source = results.find((item) => item && typeof item === "object" && !Array.isArray(item) && item.label === setupActionLabel);
566
+ if (!source) return insufficient("setup_action_result_missing");
567
+ if (!Object.hasOwn(source, "returned")) return insufficient("setup_action_return_missing");
568
+ const segments = tracePath.split(".").map((segment) => segment.trim()).filter(Boolean);
569
+ let trace = source.returned;
570
+ for (const segment of segments) {
571
+ if (trace === null || typeof trace !== "object" || Array.isArray(trace) || !Object.hasOwn(trace, segment)) {
572
+ return insufficient("trace_path_missing");
573
+ }
574
+ trace = trace[segment];
575
+ }
576
+ return assessRiddleProofOrderedTrace(trace, events);
577
+ }
383
578
  function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
384
579
  const resolved = resolveJsonPath(root, assertion.path);
385
580
  const errors = [];
@@ -486,6 +681,7 @@ function profileSetupWindowCallReceipts(results) {
486
681
  return results.filter((result) => profileSetupResultAction(result) === "window_call").map((result) => {
487
682
  const receipt = {
488
683
  ordinal: result.ordinal ?? null,
684
+ label: result.label ?? null,
489
685
  ok: result.ok !== false,
490
686
  path: result.path ?? null,
491
687
  return_captured: result.return_captured ?? null,
@@ -504,6 +700,7 @@ function profileSetupWindowEvalReceipts(results) {
504
700
  return results.filter((result) => profileSetupResultAction(result) === "window_eval").map((result) => {
505
701
  const receipt = {
506
702
  ordinal: result.ordinal ?? null,
703
+ label: result.label ?? null,
507
704
  ok: result.ok !== false,
508
705
  script_length: result.script_length ?? null,
509
706
  return_captured: result.return_captured ?? null,
@@ -947,10 +1144,14 @@ function normalizeViewport(input, index) {
947
1144
  if (!width || !height || width < 100 || height < 100) {
948
1145
  throw new Error(`target.viewports[${index}] requires numeric width and height >= 100.`);
949
1146
  }
1147
+ const hasTouch = booleanValue(valueFromOwn(input, "hasTouch", "has_touch"));
1148
+ const isMobile = booleanValue(valueFromOwn(input, "isMobile", "is_mobile"));
950
1149
  return {
951
1150
  name: normalizeName(input.name || input.label, `viewport-${index + 1}`),
952
1151
  width: Math.round(width),
953
- height: Math.round(height)
1152
+ height: Math.round(height),
1153
+ ...hasTouch === void 0 ? {} : { hasTouch },
1154
+ ...isMobile === void 0 ? {} : { isMobile }
954
1155
  };
955
1156
  }
956
1157
  function normalizeViewports(value) {
@@ -1776,6 +1977,51 @@ function dialogCountFieldForCheckType(type) {
1776
1977
  if (type === "dialog_dismiss_count_equals") return "dialog_dismiss_count";
1777
1978
  return "dialog_count";
1778
1979
  }
1980
+ function normalizeOrderedTraceEvents(value, label) {
1981
+ if (value === void 0) return void 0;
1982
+ if (!Array.isArray(value) || !value.length) throw new Error(`${label} must be a non-empty array.`);
1983
+ const seenLabels = /* @__PURE__ */ new Set();
1984
+ return value.map((item, eventIndex) => {
1985
+ const eventLabel = `${label}[${eventIndex}]`;
1986
+ if (!isRecord(item)) throw new Error(`${eventLabel} must be an object.`);
1987
+ const name = stringFromOwn(item, "label", "name", "event");
1988
+ if (!name) throw new Error(`${eventLabel}.label is required.`);
1989
+ if (seenLabels.has(name)) throw new Error(`${eventLabel}.label must be unique.`);
1990
+ seenLabels.add(name);
1991
+ const predicatesInput = item.predicates ?? item.all ?? item.where;
1992
+ if (!Array.isArray(predicatesInput) || !predicatesInput.length) {
1993
+ throw new Error(`${eventLabel}.predicates must be a non-empty array.`);
1994
+ }
1995
+ const predicates = predicatesInput.map((predicateInput, predicateIndex) => {
1996
+ const predicateLabel = `${eventLabel}.predicates[${predicateIndex}]`;
1997
+ if (!isRecord(predicateInput)) throw new Error(`${predicateLabel} must be an object.`);
1998
+ const path = stringFromOwn(predicateInput, "path", "field", "key");
1999
+ if (!path) throw new Error(`${predicateLabel}.path is required.`);
2000
+ const op = stringFromOwn(predicateInput, "op", "operator");
2001
+ if (!op || !RIDDLE_PROOF_ORDERED_TRACE_OPERATORS.includes(op)) {
2002
+ throw new Error(`${predicateLabel}.op must be one of ${RIDDLE_PROOF_ORDERED_TRACE_OPERATORS.join(", ")}.`);
2003
+ }
2004
+ const requiresValue = !["exists", "truthy", "falsy"].includes(op);
2005
+ const hasValue = hasOwn(predicateInput, "value") || hasOwn(predicateInput, "expected");
2006
+ if (requiresValue && !hasValue) throw new Error(`${predicateLabel}.value is required for ${op}.`);
2007
+ const value2 = hasOwn(predicateInput, "value") ? predicateInput.value : predicateInput.expected;
2008
+ if (["gt", "gte", "lt", "lte", "abs_gt", "abs_gte", "abs_lt", "abs_lte"].includes(op)) {
2009
+ if (typeof value2 !== "number" || !Number.isFinite(value2)) {
2010
+ throw new Error(`${predicateLabel}.value must be a finite number for ${op}.`);
2011
+ }
2012
+ if (op.startsWith("abs_") && value2 < 0) {
2013
+ throw new Error(`${predicateLabel}.value must be non-negative for ${op}.`);
2014
+ }
2015
+ }
2016
+ return {
2017
+ path,
2018
+ op,
2019
+ value: requiresValue ? toJsonValue(value2) : void 0
2020
+ };
2021
+ });
2022
+ return { label: name, predicates };
2023
+ });
2024
+ }
1779
2025
  function normalizeCheck(input, index) {
1780
2026
  if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
1781
2027
  const type = stringValue(input.type);
@@ -1826,6 +2072,17 @@ function normalizeCheck(input, index) {
1826
2072
  if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
1827
2073
  if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
1828
2074
  }
2075
+ const setupActionLabel = stringFromOwn(input, "setup_action_label", "setupActionLabel", "source_action_label", "sourceActionLabel");
2076
+ const tracePath = stringFromOwn(input, "trace_path", "tracePath", "path");
2077
+ const orderedTraceEvents = normalizeOrderedTraceEvents(
2078
+ input.events ?? input.sequence ?? input.ordered_events ?? input.orderedEvents,
2079
+ `checks[${index}].events`
2080
+ );
2081
+ if (type === "ordered_trace") {
2082
+ if (!setupActionLabel) throw new Error(`checks[${index}] ordered_trace requires setup_action_label.`);
2083
+ if (!tracePath) throw new Error(`checks[${index}] ordered_trace requires trace_path.`);
2084
+ if (!orderedTraceEvents?.length) throw new Error(`checks[${index}] ordered_trace requires events.`);
2085
+ }
1829
2086
  const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
1830
2087
  if (type === "route_inventory" && !expectedRoutes?.length) {
1831
2088
  throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
@@ -1898,6 +2155,9 @@ function normalizeCheck(input, index) {
1898
2155
  body_not_patterns: bodyNotPatterns,
1899
2156
  body_json_assertions: bodyJsonAssertions,
1900
2157
  expected_texts: expectedTexts,
2158
+ setup_action_label: type === "ordered_trace" ? setupActionLabel : void 0,
2159
+ trace_path: type === "ordered_trace" ? tracePath : void 0,
2160
+ events: type === "ordered_trace" ? orderedTraceEvents : void 0,
1901
2161
  link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
1902
2162
  source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
1903
2163
  route_path_prefix: stringValue(input.route_path_prefix) || stringValue(input.routePathPrefix),
@@ -1964,6 +2224,20 @@ function normalizeRiddleProofProfile(input, options = {}) {
1964
2224
  const targetUrl = stringValue(options.url) || stringValue(targetInput.url);
1965
2225
  const route = stringValue(options.route) || stringValue(targetInput.route);
1966
2226
  if (!targetUrl && !route) throw new Error("profile.target requires url or route, or pass --url.");
2227
+ const setupActions = normalizeSetupActions(targetInput.setup_actions ?? targetInput.setupActions);
2228
+ for (const [index, check] of checks.entries()) {
2229
+ if (check.type !== "ordered_trace") continue;
2230
+ const matches = (setupActions || []).filter((action) => action.label === check.setup_action_label);
2231
+ if (matches.length !== 1) {
2232
+ throw new Error(`checks[${index}] ordered_trace setup_action_label must match exactly one target.setup_actions label.`);
2233
+ }
2234
+ if (!["window_eval", "window_call", "window_call_until"].includes(matches[0].type)) {
2235
+ throw new Error(`checks[${index}] ordered_trace source action must capture a window_eval, window_call, or window_call_until return.`);
2236
+ }
2237
+ if (matches[0].capture_return === false) {
2238
+ throw new Error(`checks[${index}] ordered_trace source action must not set capture_return to false.`);
2239
+ }
2240
+ }
1967
2241
  return {
1968
2242
  version: RIDDLE_PROOF_PROFILE_VERSION,
1969
2243
  name: normalizeName(input.name, "riddle-proof-profile"),
@@ -1976,7 +2250,7 @@ function normalizeRiddleProofProfile(input, options = {}) {
1976
2250
  wait_for_selector: stringValue(targetInput.wait_for_selector) || stringValue(targetInput.waitForSelector),
1977
2251
  wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs),
1978
2252
  screenshot_full_page: normalizeTargetScreenshotFullPage(targetInput),
1979
- setup_actions: normalizeSetupActions(targetInput.setup_actions ?? targetInput.setupActions),
2253
+ setup_actions: setupActions,
1980
2254
  network_mocks: normalizeNetworkMocks(targetInput.network_mocks ?? targetInput.networkMocks)
1981
2255
  },
1982
2256
  checks,
@@ -2989,6 +3263,32 @@ function assessCheckFromEvidence(check, evidence) {
2989
3263
  message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
2990
3264
  };
2991
3265
  }
3266
+ if (check.type === "ordered_trace") {
3267
+ const assessments = viewports.map((viewport) => ({
3268
+ viewport: viewport.name,
3269
+ assessment: assessRiddleProofOrderedTraceSetupResults(
3270
+ viewport.setup_action_results,
3271
+ check.setup_action_label || "",
3272
+ check.trace_path || "",
3273
+ check.events || []
3274
+ )
3275
+ }));
3276
+ const insufficient = assessments.filter((item) => item.assessment.status === "proof_insufficient");
3277
+ const failed = assessments.filter((item) => item.assessment.status === "failed");
3278
+ const status = insufficient.length ? "proof_insufficient" : failed.length ? "failed" : "passed";
3279
+ return {
3280
+ type: check.type,
3281
+ label: checkLabel(check),
3282
+ status,
3283
+ evidence: {
3284
+ setup_action_label: check.setup_action_label || "",
3285
+ trace_path: check.trace_path || "",
3286
+ events: toJsonValue((check.events || []).map((event) => event.label)),
3287
+ viewports: toJsonValue(assessments)
3288
+ },
3289
+ message: insufficient.length ? `Ordered trace evidence was insufficient in ${insufficient.length} viewport(s).` : failed.length ? `Ordered trace did not contain the required event sequence in ${failed.length} viewport(s).` : void 0
3290
+ };
3291
+ }
2992
3292
  if (check.type === "observe_within") {
2993
3293
  const key = observeWithinKey(check);
2994
3294
  const timeoutMs = observeWithinTimeoutMs(check);
@@ -3488,6 +3788,7 @@ function profileStatusFromEvidence(profile, evidence, checks) {
3488
3788
  if (!viewports.length || !checks.length) return "proof_insufficient";
3489
3789
  if (viewports.some((viewport) => viewport.navigation_error)) return "environment_blocked";
3490
3790
  if (expectedViewportCount && viewports.length < expectedViewportCount) return "proof_insufficient";
3791
+ if (checks.some((check) => check.status === "proof_insufficient")) return "proof_insufficient";
3491
3792
  if (checks.some((check) => check.status === "needs_human_review")) return "needs_human_review";
3492
3793
  if (checks.some((check) => check.status === "failed")) return "product_regression";
3493
3794
  return "passed";
@@ -3817,8 +4118,12 @@ function createRiddleProofProfileInsufficientResult(input) {
3817
4118
  error: message
3818
4119
  };
3819
4120
  }
3820
- function runtimeScriptAssessmentSource() {
4121
+ function runtimeScriptAssessmentSource(includeOrderedTrace = false) {
4122
+ const orderedTraceSource = includeOrderedTrace ? String.raw`
4123
+ const assessRiddleProofOrderedTrace = ${assessRiddleProofOrderedTrace.toString()};
4124
+ const assessRiddleProofOrderedTraceSetupResults = ${assessRiddleProofOrderedTraceSetupResults.toString()};` : "";
3821
4125
  return String.raw`
4126
+ ${orderedTraceSource}
3822
4127
  function normalizeRoutePath(path) {
3823
4128
  const value = path || "/";
3824
4129
  if (value === "/") return "/";
@@ -4648,6 +4953,7 @@ function profileSetupWindowCallReceipts(results) {
4648
4953
  .map((result) => {
4649
4954
  const receipt = {
4650
4955
  ordinal: result.ordinal ?? null,
4956
+ label: result.label ?? null,
4651
4957
  ok: result.ok !== false,
4652
4958
  path: result.path ?? null,
4653
4959
  return_captured: result.return_captured ?? null,
@@ -4668,6 +4974,7 @@ function profileSetupWindowEvalReceipts(results) {
4668
4974
  .map((result) => {
4669
4975
  const receipt = {
4670
4976
  ordinal: result.ordinal ?? null,
4977
+ label: result.label ?? null,
4671
4978
  ok: result.ok !== false,
4672
4979
  script_length: result.script_length ?? null,
4673
4980
  return_captured: result.return_captured ?? null,
@@ -5483,6 +5790,36 @@ function assessProfile(profile, evidence) {
5483
5790
  });
5484
5791
  continue;
5485
5792
  }
5793
+ if (check.type === "ordered_trace") {
5794
+ const assessments = checkViewports.map((viewport) => ({
5795
+ viewport: viewport.name,
5796
+ assessment: assessRiddleProofOrderedTraceSetupResults(
5797
+ viewport.setup_action_results,
5798
+ check.setup_action_label || "",
5799
+ check.trace_path || "",
5800
+ check.events || [],
5801
+ ),
5802
+ }));
5803
+ const insufficient = assessments.filter((item) => item.assessment.status === "proof_insufficient");
5804
+ const failed = assessments.filter((item) => item.assessment.status === "failed");
5805
+ checks.push({
5806
+ type: check.type,
5807
+ label: check.label || check.type,
5808
+ status: insufficient.length ? "proof_insufficient" : failed.length ? "failed" : "passed",
5809
+ evidence: {
5810
+ setup_action_label: check.setup_action_label || "",
5811
+ trace_path: check.trace_path || "",
5812
+ events: (check.events || []).map((event) => event.label),
5813
+ viewports: assessments,
5814
+ },
5815
+ message: insufficient.length
5816
+ ? "Ordered trace evidence was insufficient in " + insufficient.length + " viewport(s)."
5817
+ : failed.length
5818
+ ? "Ordered trace did not contain the required event sequence in " + failed.length + " viewport(s)."
5819
+ : undefined,
5820
+ });
5821
+ continue;
5822
+ }
5486
5823
  if (check.type === "observe_within") {
5487
5824
  const key = observeWithinKey(check);
5488
5825
  const timeoutMs = observeWithinTimeoutMs(check);
@@ -5839,6 +6176,7 @@ function assessProfile(profile, evidence) {
5839
6176
  if (!viewports.length || !checks.length) status = "proof_insufficient";
5840
6177
  else if (viewports.some((viewport) => viewport.navigation_error)) status = "environment_blocked";
5841
6178
  else if (expectedViewportCount && viewports.length < expectedViewportCount) status = "proof_insufficient";
6179
+ else if (checks.some((check) => check.status === "proof_insufficient")) status = "proof_insufficient";
5842
6180
  else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
5843
6181
  else if (checks.some((check) => check.status === "failed")) status = "product_regression";
5844
6182
  const screenshotLabels = profileScreenshotLabels(viewports);
@@ -6770,7 +7108,7 @@ let activeViewportName = null;
6770
7108
  async function executeSetupAction(action, ordinal, viewport) {
6771
7109
  const type = setupActionType(action);
6772
7110
  const frameSelector = setupFrameSelector(action);
6773
- const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null, frame_selector: frameSelector || null, optional: action.optional === true };
7111
+ const base = { ok: false, action: type || "unknown", ordinal, label: action.label || null, selector: action.selector || null, frame_selector: frameSelector || null, optional: action.optional === true };
6774
7112
  const timeout = setupNumber(action.timeout_ms, 5000);
6775
7113
  try {
6776
7114
  if (type === "wait") {
@@ -9422,7 +9760,7 @@ async function captureViewport(viewport) {
9422
9760
  wait_error: waitError,
9423
9761
  };
9424
9762
  }
9425
- ${runtimeScriptAssessmentSource()}
9763
+ ${runtimeScriptAssessmentSource(profile.checks.some((check) => check.type === "ordered_trace"))}
9426
9764
  const viewports = [];
9427
9765
  function buildProfileEvidence(currentViewports) {
9428
9766
  const expectedViewportCount = (profile.target.viewports || []).length;
@@ -9633,7 +9971,10 @@ export {
9633
9971
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
9634
9972
  RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES,
9635
9973
  RIDDLE_PROOF_PROFILE_NETWORK_ABORT_ERROR_CODES,
9974
+ RIDDLE_PROOF_ORDERED_TRACE_OPERATORS,
9636
9975
  deriveRiddleProofArtifactBodyAssertions,
9976
+ assessRiddleProofOrderedTrace,
9977
+ assessRiddleProofOrderedTraceSetupResults,
9637
9978
  slugifyRiddleProofProfileName,
9638
9979
  collectRiddleProofProfileWarnings,
9639
9980
  normalizeRiddleProofProfile,
@@ -5,7 +5,7 @@ import {
5
5
  createRiddleProofHandoffReceipt,
6
6
  parseRiddleProofChangeReceipt,
7
7
  parseRiddleProofHandoffReceipt
8
- } from "./chunk-6VFS2JFR.js";
8
+ } from "./chunk-LR4UYJDY.js";
9
9
  import {
10
10
  riddleProofPublicStateAllowsMergeRecommendation,
11
11
  riddleProofPublicStateMergeRecommendation,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RIDDLE_PROOF_PROFILE_VERSION
3
- } from "./chunk-FCSJZBC5.js";
3
+ } from "./chunk-2K3DIK7A.js";
4
4
 
5
5
  // src/profile-suggestions.ts
6
6
  var RIDDLE_PROOF_PROFILE_SUGGESTIONS_VERSION = "riddle-proof.profile-suggestions.v1";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-MEVVL4TI.js";
5
5
  import {
6
6
  RIDDLE_PROOF_PROFILE_STATUSES
7
- } from "./chunk-FCSJZBC5.js";
7
+ } from "./chunk-2K3DIK7A.js";
8
8
 
9
9
  // src/change-proof.ts
10
10
  var RIDDLE_PROOF_CHANGE_CONTRACT_VERSION = "riddle-proof.change-contract.v1";
@@ -8,17 +8,17 @@ import {
8
8
  RIDDLE_PROOF_PR_COMMENT_MARKER,
9
9
  buildRiddleProofPrCommentMarkdown,
10
10
  summarizeRiddleProofPrComment
11
- } from "./chunk-7N6X54WG.js";
11
+ } from "./chunk-7OAETQU3.js";
12
12
  import {
13
13
  suggestRiddleProofProfileChecks
14
- } from "./chunk-RQPCKRKT.js";
14
+ } from "./chunk-FW7CKARF.js";
15
15
  import {
16
16
  assessRiddleProofChange,
17
17
  createRiddleProofChangeReceipt,
18
18
  createRiddleProofHandoffReceipt,
19
19
  riddleProofChangeReceiptHtml,
20
20
  riddleProofChangeReceiptMarkdown
21
- } from "./chunk-6VFS2JFR.js";
21
+ } from "./chunk-LR4UYJDY.js";
22
22
  import {
23
23
  createRiddleProofObservationReceipt,
24
24
  parseRiddlePreviewReceipt,
@@ -41,7 +41,7 @@ import {
41
41
  profileStatusExitCode,
42
42
  resolveRiddleProofProfileTargetUrl,
43
43
  resolveRiddleProofProfileTimeoutSec
44
- } from "./chunk-FCSJZBC5.js";
44
+ } from "./chunk-2K3DIK7A.js";
45
45
  import {
46
46
  createCodexExecAgentAdapter,
47
47
  runCodexExecAgentDoctor
package/dist/cli/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import "../chunk-NEXWITV4.js";
1
+ import "../chunk-QYZ3GKCP.js";
2
2
  import "../chunk-5IFZSUPF.js";
3
3
  import "../chunk-JFQXAJH2.js";
4
- import "../chunk-7N6X54WG.js";
5
- import "../chunk-RQPCKRKT.js";
6
- import "../chunk-6VFS2JFR.js";
4
+ import "../chunk-7OAETQU3.js";
5
+ import "../chunk-FW7CKARF.js";
6
+ import "../chunk-LR4UYJDY.js";
7
7
  import "../chunk-MEVVL4TI.js";
8
- import "../chunk-FCSJZBC5.js";
8
+ import "../chunk-2K3DIK7A.js";
9
9
  import "../chunk-KXLEN4SA.js";
10
10
  import "../chunk-WLUMLHII.js";
11
11
  import "../chunk-CGJX7LJO.js";