@riddledc/riddle-proof 0.8.79 → 0.8.80
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 +39 -0
- package/dist/change-proof.js +2 -2
- package/dist/{chunk-7N6X54WG.js → chunk-6T6ZAK77.js} +1 -1
- package/dist/{chunk-RQPCKRKT.js → chunk-A4EF4M3B.js} +1 -1
- package/dist/{chunk-6VFS2JFR.js → chunk-IL7MLYB5.js} +1 -1
- package/dist/{chunk-NEXWITV4.js → chunk-N7EETHYG.js} +4 -4
- package/dist/{chunk-FCSJZBC5.js → chunk-ZNVYJFR7.js} +341 -4
- package/dist/cli/index.js +5 -5
- package/dist/cli.cjs +338 -4
- package/dist/cli.js +5 -5
- package/dist/index.cjs +344 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -4
- package/dist/pr-comment.js +3 -3
- package/dist/profile/index.cjs +344 -4
- package/dist/profile/index.d.cts +1 -1
- package/dist/profile/index.d.ts +1 -1
- package/dist/profile/index.js +7 -1
- package/dist/profile-suggestions.js +2 -2
- package/dist/profile.cjs +344 -4
- package/dist/profile.d.cts +38 -3
- package/dist/profile.d.ts +38 -3
- package/dist/profile.js +7 -1
- package/examples/profiles/ordered-trace.json +58 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -499,6 +499,7 @@ The package includes generic starter profiles:
|
|
|
499
499
|
- `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
500
|
- `examples/profiles/terminal-result-partial-evidence.json` for API-console terminal error or timeout receipts that preserve partial screenshot, console, and HAR evidence.
|
|
501
501
|
- `examples/profiles/gameplay-window-call-until.json` for gameplay profiles that wait on a runtime state contract instead of a fixed sleep.
|
|
502
|
+
- `examples/profiles/ordered-trace.json` for interaction profiles that require named runtime events to occur in strict temporal order.
|
|
502
503
|
- `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
504
|
|
|
504
505
|
Copy one of those shapes into a repository profile directory and replace the
|
|
@@ -808,6 +809,44 @@ both first and last per-viewport receipts before filling remaining space, so
|
|
|
808
809
|
late lifecycle phases such as terminal or restart remain visible in compact
|
|
809
810
|
summaries.
|
|
810
811
|
|
|
812
|
+
Use `ordered_trace` when one point-in-time assertion cannot establish the
|
|
813
|
+
behavior, such as input -> response -> motion -> release -> idle. A labeled
|
|
814
|
+
`window_eval`, `window_call`, or `window_call_until` setup action must return a
|
|
815
|
+
JSON object containing the trace. The check declares predicates for each named
|
|
816
|
+
event and records the earliest strictly increasing witness indices plus compact
|
|
817
|
+
observed values:
|
|
818
|
+
|
|
819
|
+
```json
|
|
820
|
+
{
|
|
821
|
+
"type": "ordered_trace",
|
|
822
|
+
"label": "input to release",
|
|
823
|
+
"setup_action_label": "capture interaction trace",
|
|
824
|
+
"trace_path": "trace",
|
|
825
|
+
"events": [
|
|
826
|
+
{
|
|
827
|
+
"label": "input",
|
|
828
|
+
"predicates": [{ "path": "target", "op": "abs_gte", "value": 0.8 }]
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
"label": "response",
|
|
832
|
+
"predicates": [{ "path": "applied", "op": "abs_gte", "value": 0.1 }]
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
"label": "released",
|
|
836
|
+
"predicates": [{ "path": "active", "op": "equals", "value": false }]
|
|
837
|
+
}
|
|
838
|
+
]
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Supported predicate operators are `exists`, `equals`, `not_equals`, `truthy`,
|
|
843
|
+
`falsy`, `gt`, `gte`, `lt`, `lte`, `abs_gt`, `abs_gte`, `abs_lt`, and
|
|
844
|
+
`abs_lte`. Missing or empty traces and fields that never appear are
|
|
845
|
+
`proof_insufficient`; a complete trace that does not contain the sequence is a
|
|
846
|
+
`failed` product check. One sample cannot witness two successive events. This
|
|
847
|
+
keeps missing instrumentation distinct from a measured regression and lets an
|
|
848
|
+
ordinary Change Proof `check_status_transition` require `failed -> passed`.
|
|
849
|
+
|
|
811
850
|
`target.timeout_sec` is optional. Use it for known-heavy profile targets so the
|
|
812
851
|
profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
|
|
813
852
|
still overrides the profile value for one-off runs.
|
package/dist/change-proof.js
CHANGED
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
parseRiddleProofHandoffReceipt,
|
|
13
13
|
riddleProofChangeReceiptHtml,
|
|
14
14
|
riddleProofChangeReceiptMarkdown
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IL7MLYB5.js";
|
|
16
16
|
import "./chunk-MEVVL4TI.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-ZNVYJFR7.js";
|
|
18
18
|
import "./chunk-MLKGABMK.js";
|
|
19
19
|
export {
|
|
20
20
|
RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createRiddleProofHandoffReceipt,
|
|
6
6
|
parseRiddleProofChangeReceipt,
|
|
7
7
|
parseRiddleProofHandoffReceipt
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IL7MLYB5.js";
|
|
9
9
|
import {
|
|
10
10
|
riddleProofPublicStateAllowsMergeRecommendation,
|
|
11
11
|
riddleProofPublicStateMergeRecommendation,
|
|
@@ -8,17 +8,17 @@ import {
|
|
|
8
8
|
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
9
9
|
buildRiddleProofPrCommentMarkdown,
|
|
10
10
|
summarizeRiddleProofPrComment
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6T6ZAK77.js";
|
|
12
12
|
import {
|
|
13
13
|
suggestRiddleProofProfileChecks
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-A4EF4M3B.js";
|
|
15
15
|
import {
|
|
16
16
|
assessRiddleProofChange,
|
|
17
17
|
createRiddleProofChangeReceipt,
|
|
18
18
|
createRiddleProofHandoffReceipt,
|
|
19
19
|
riddleProofChangeReceiptHtml,
|
|
20
20
|
riddleProofChangeReceiptMarkdown
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-IL7MLYB5.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-
|
|
44
|
+
} from "./chunk-ZNVYJFR7.js";
|
|
45
45
|
import {
|
|
46
46
|
createCodexExecAgentAdapter,
|
|
47
47
|
runCodexExecAgentDoctor
|
|
@@ -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,
|
|
@@ -1776,6 +1973,51 @@ function dialogCountFieldForCheckType(type) {
|
|
|
1776
1973
|
if (type === "dialog_dismiss_count_equals") return "dialog_dismiss_count";
|
|
1777
1974
|
return "dialog_count";
|
|
1778
1975
|
}
|
|
1976
|
+
function normalizeOrderedTraceEvents(value, label) {
|
|
1977
|
+
if (value === void 0) return void 0;
|
|
1978
|
+
if (!Array.isArray(value) || !value.length) throw new Error(`${label} must be a non-empty array.`);
|
|
1979
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
1980
|
+
return value.map((item, eventIndex) => {
|
|
1981
|
+
const eventLabel = `${label}[${eventIndex}]`;
|
|
1982
|
+
if (!isRecord(item)) throw new Error(`${eventLabel} must be an object.`);
|
|
1983
|
+
const name = stringFromOwn(item, "label", "name", "event");
|
|
1984
|
+
if (!name) throw new Error(`${eventLabel}.label is required.`);
|
|
1985
|
+
if (seenLabels.has(name)) throw new Error(`${eventLabel}.label must be unique.`);
|
|
1986
|
+
seenLabels.add(name);
|
|
1987
|
+
const predicatesInput = item.predicates ?? item.all ?? item.where;
|
|
1988
|
+
if (!Array.isArray(predicatesInput) || !predicatesInput.length) {
|
|
1989
|
+
throw new Error(`${eventLabel}.predicates must be a non-empty array.`);
|
|
1990
|
+
}
|
|
1991
|
+
const predicates = predicatesInput.map((predicateInput, predicateIndex) => {
|
|
1992
|
+
const predicateLabel = `${eventLabel}.predicates[${predicateIndex}]`;
|
|
1993
|
+
if (!isRecord(predicateInput)) throw new Error(`${predicateLabel} must be an object.`);
|
|
1994
|
+
const path = stringFromOwn(predicateInput, "path", "field", "key");
|
|
1995
|
+
if (!path) throw new Error(`${predicateLabel}.path is required.`);
|
|
1996
|
+
const op = stringFromOwn(predicateInput, "op", "operator");
|
|
1997
|
+
if (!op || !RIDDLE_PROOF_ORDERED_TRACE_OPERATORS.includes(op)) {
|
|
1998
|
+
throw new Error(`${predicateLabel}.op must be one of ${RIDDLE_PROOF_ORDERED_TRACE_OPERATORS.join(", ")}.`);
|
|
1999
|
+
}
|
|
2000
|
+
const requiresValue = !["exists", "truthy", "falsy"].includes(op);
|
|
2001
|
+
const hasValue = hasOwn(predicateInput, "value") || hasOwn(predicateInput, "expected");
|
|
2002
|
+
if (requiresValue && !hasValue) throw new Error(`${predicateLabel}.value is required for ${op}.`);
|
|
2003
|
+
const value2 = hasOwn(predicateInput, "value") ? predicateInput.value : predicateInput.expected;
|
|
2004
|
+
if (["gt", "gte", "lt", "lte", "abs_gt", "abs_gte", "abs_lt", "abs_lte"].includes(op)) {
|
|
2005
|
+
if (typeof value2 !== "number" || !Number.isFinite(value2)) {
|
|
2006
|
+
throw new Error(`${predicateLabel}.value must be a finite number for ${op}.`);
|
|
2007
|
+
}
|
|
2008
|
+
if (op.startsWith("abs_") && value2 < 0) {
|
|
2009
|
+
throw new Error(`${predicateLabel}.value must be non-negative for ${op}.`);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return {
|
|
2013
|
+
path,
|
|
2014
|
+
op,
|
|
2015
|
+
value: requiresValue ? toJsonValue(value2) : void 0
|
|
2016
|
+
};
|
|
2017
|
+
});
|
|
2018
|
+
return { label: name, predicates };
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
1779
2021
|
function normalizeCheck(input, index) {
|
|
1780
2022
|
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
1781
2023
|
const type = stringValue(input.type);
|
|
@@ -1826,6 +2068,17 @@ function normalizeCheck(input, index) {
|
|
|
1826
2068
|
if (!stringValue(input.selector)) throw new Error(`checks[${index}] selector_text_order requires selector.`);
|
|
1827
2069
|
if (!expectedTexts?.length) throw new Error(`checks[${index}] selector_text_order requires expected_texts.`);
|
|
1828
2070
|
}
|
|
2071
|
+
const setupActionLabel = stringFromOwn(input, "setup_action_label", "setupActionLabel", "source_action_label", "sourceActionLabel");
|
|
2072
|
+
const tracePath = stringFromOwn(input, "trace_path", "tracePath", "path");
|
|
2073
|
+
const orderedTraceEvents = normalizeOrderedTraceEvents(
|
|
2074
|
+
input.events ?? input.sequence ?? input.ordered_events ?? input.orderedEvents,
|
|
2075
|
+
`checks[${index}].events`
|
|
2076
|
+
);
|
|
2077
|
+
if (type === "ordered_trace") {
|
|
2078
|
+
if (!setupActionLabel) throw new Error(`checks[${index}] ordered_trace requires setup_action_label.`);
|
|
2079
|
+
if (!tracePath) throw new Error(`checks[${index}] ordered_trace requires trace_path.`);
|
|
2080
|
+
if (!orderedTraceEvents?.length) throw new Error(`checks[${index}] ordered_trace requires events.`);
|
|
2081
|
+
}
|
|
1829
2082
|
const expectedRoutes = normalizeRouteInventoryRoutes(input.expected_routes ?? input.expectedRoutes, index);
|
|
1830
2083
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
1831
2084
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
@@ -1898,6 +2151,9 @@ function normalizeCheck(input, index) {
|
|
|
1898
2151
|
body_not_patterns: bodyNotPatterns,
|
|
1899
2152
|
body_json_assertions: bodyJsonAssertions,
|
|
1900
2153
|
expected_texts: expectedTexts,
|
|
2154
|
+
setup_action_label: type === "ordered_trace" ? setupActionLabel : void 0,
|
|
2155
|
+
trace_path: type === "ordered_trace" ? tracePath : void 0,
|
|
2156
|
+
events: type === "ordered_trace" ? orderedTraceEvents : void 0,
|
|
1901
2157
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
1902
2158
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
1903
2159
|
route_path_prefix: stringValue(input.route_path_prefix) || stringValue(input.routePathPrefix),
|
|
@@ -1964,6 +2220,20 @@ function normalizeRiddleProofProfile(input, options = {}) {
|
|
|
1964
2220
|
const targetUrl = stringValue(options.url) || stringValue(targetInput.url);
|
|
1965
2221
|
const route = stringValue(options.route) || stringValue(targetInput.route);
|
|
1966
2222
|
if (!targetUrl && !route) throw new Error("profile.target requires url or route, or pass --url.");
|
|
2223
|
+
const setupActions = normalizeSetupActions(targetInput.setup_actions ?? targetInput.setupActions);
|
|
2224
|
+
for (const [index, check] of checks.entries()) {
|
|
2225
|
+
if (check.type !== "ordered_trace") continue;
|
|
2226
|
+
const matches = (setupActions || []).filter((action) => action.label === check.setup_action_label);
|
|
2227
|
+
if (matches.length !== 1) {
|
|
2228
|
+
throw new Error(`checks[${index}] ordered_trace setup_action_label must match exactly one target.setup_actions label.`);
|
|
2229
|
+
}
|
|
2230
|
+
if (!["window_eval", "window_call", "window_call_until"].includes(matches[0].type)) {
|
|
2231
|
+
throw new Error(`checks[${index}] ordered_trace source action must capture a window_eval, window_call, or window_call_until return.`);
|
|
2232
|
+
}
|
|
2233
|
+
if (matches[0].capture_return === false) {
|
|
2234
|
+
throw new Error(`checks[${index}] ordered_trace source action must not set capture_return to false.`);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
1967
2237
|
return {
|
|
1968
2238
|
version: RIDDLE_PROOF_PROFILE_VERSION,
|
|
1969
2239
|
name: normalizeName(input.name, "riddle-proof-profile"),
|
|
@@ -1976,7 +2246,7 @@ function normalizeRiddleProofProfile(input, options = {}) {
|
|
|
1976
2246
|
wait_for_selector: stringValue(targetInput.wait_for_selector) || stringValue(targetInput.waitForSelector),
|
|
1977
2247
|
wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs),
|
|
1978
2248
|
screenshot_full_page: normalizeTargetScreenshotFullPage(targetInput),
|
|
1979
|
-
setup_actions:
|
|
2249
|
+
setup_actions: setupActions,
|
|
1980
2250
|
network_mocks: normalizeNetworkMocks(targetInput.network_mocks ?? targetInput.networkMocks)
|
|
1981
2251
|
},
|
|
1982
2252
|
checks,
|
|
@@ -2989,6 +3259,32 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2989
3259
|
message: failed ? `Selector ${key} text order failed in ${failed} viewport(s).` : void 0
|
|
2990
3260
|
};
|
|
2991
3261
|
}
|
|
3262
|
+
if (check.type === "ordered_trace") {
|
|
3263
|
+
const assessments = viewports.map((viewport) => ({
|
|
3264
|
+
viewport: viewport.name,
|
|
3265
|
+
assessment: assessRiddleProofOrderedTraceSetupResults(
|
|
3266
|
+
viewport.setup_action_results,
|
|
3267
|
+
check.setup_action_label || "",
|
|
3268
|
+
check.trace_path || "",
|
|
3269
|
+
check.events || []
|
|
3270
|
+
)
|
|
3271
|
+
}));
|
|
3272
|
+
const insufficient = assessments.filter((item) => item.assessment.status === "proof_insufficient");
|
|
3273
|
+
const failed = assessments.filter((item) => item.assessment.status === "failed");
|
|
3274
|
+
const status = insufficient.length ? "proof_insufficient" : failed.length ? "failed" : "passed";
|
|
3275
|
+
return {
|
|
3276
|
+
type: check.type,
|
|
3277
|
+
label: checkLabel(check),
|
|
3278
|
+
status,
|
|
3279
|
+
evidence: {
|
|
3280
|
+
setup_action_label: check.setup_action_label || "",
|
|
3281
|
+
trace_path: check.trace_path || "",
|
|
3282
|
+
events: toJsonValue((check.events || []).map((event) => event.label)),
|
|
3283
|
+
viewports: toJsonValue(assessments)
|
|
3284
|
+
},
|
|
3285
|
+
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
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
2992
3288
|
if (check.type === "observe_within") {
|
|
2993
3289
|
const key = observeWithinKey(check);
|
|
2994
3290
|
const timeoutMs = observeWithinTimeoutMs(check);
|
|
@@ -3488,6 +3784,7 @@ function profileStatusFromEvidence(profile, evidence, checks) {
|
|
|
3488
3784
|
if (!viewports.length || !checks.length) return "proof_insufficient";
|
|
3489
3785
|
if (viewports.some((viewport) => viewport.navigation_error)) return "environment_blocked";
|
|
3490
3786
|
if (expectedViewportCount && viewports.length < expectedViewportCount) return "proof_insufficient";
|
|
3787
|
+
if (checks.some((check) => check.status === "proof_insufficient")) return "proof_insufficient";
|
|
3491
3788
|
if (checks.some((check) => check.status === "needs_human_review")) return "needs_human_review";
|
|
3492
3789
|
if (checks.some((check) => check.status === "failed")) return "product_regression";
|
|
3493
3790
|
return "passed";
|
|
@@ -3817,8 +4114,12 @@ function createRiddleProofProfileInsufficientResult(input) {
|
|
|
3817
4114
|
error: message
|
|
3818
4115
|
};
|
|
3819
4116
|
}
|
|
3820
|
-
function runtimeScriptAssessmentSource() {
|
|
4117
|
+
function runtimeScriptAssessmentSource(includeOrderedTrace = false) {
|
|
4118
|
+
const orderedTraceSource = includeOrderedTrace ? String.raw`
|
|
4119
|
+
const assessRiddleProofOrderedTrace = ${assessRiddleProofOrderedTrace.toString()};
|
|
4120
|
+
const assessRiddleProofOrderedTraceSetupResults = ${assessRiddleProofOrderedTraceSetupResults.toString()};` : "";
|
|
3821
4121
|
return String.raw`
|
|
4122
|
+
${orderedTraceSource}
|
|
3822
4123
|
function normalizeRoutePath(path) {
|
|
3823
4124
|
const value = path || "/";
|
|
3824
4125
|
if (value === "/") return "/";
|
|
@@ -4648,6 +4949,7 @@ function profileSetupWindowCallReceipts(results) {
|
|
|
4648
4949
|
.map((result) => {
|
|
4649
4950
|
const receipt = {
|
|
4650
4951
|
ordinal: result.ordinal ?? null,
|
|
4952
|
+
label: result.label ?? null,
|
|
4651
4953
|
ok: result.ok !== false,
|
|
4652
4954
|
path: result.path ?? null,
|
|
4653
4955
|
return_captured: result.return_captured ?? null,
|
|
@@ -4668,6 +4970,7 @@ function profileSetupWindowEvalReceipts(results) {
|
|
|
4668
4970
|
.map((result) => {
|
|
4669
4971
|
const receipt = {
|
|
4670
4972
|
ordinal: result.ordinal ?? null,
|
|
4973
|
+
label: result.label ?? null,
|
|
4671
4974
|
ok: result.ok !== false,
|
|
4672
4975
|
script_length: result.script_length ?? null,
|
|
4673
4976
|
return_captured: result.return_captured ?? null,
|
|
@@ -5483,6 +5786,36 @@ function assessProfile(profile, evidence) {
|
|
|
5483
5786
|
});
|
|
5484
5787
|
continue;
|
|
5485
5788
|
}
|
|
5789
|
+
if (check.type === "ordered_trace") {
|
|
5790
|
+
const assessments = checkViewports.map((viewport) => ({
|
|
5791
|
+
viewport: viewport.name,
|
|
5792
|
+
assessment: assessRiddleProofOrderedTraceSetupResults(
|
|
5793
|
+
viewport.setup_action_results,
|
|
5794
|
+
check.setup_action_label || "",
|
|
5795
|
+
check.trace_path || "",
|
|
5796
|
+
check.events || [],
|
|
5797
|
+
),
|
|
5798
|
+
}));
|
|
5799
|
+
const insufficient = assessments.filter((item) => item.assessment.status === "proof_insufficient");
|
|
5800
|
+
const failed = assessments.filter((item) => item.assessment.status === "failed");
|
|
5801
|
+
checks.push({
|
|
5802
|
+
type: check.type,
|
|
5803
|
+
label: check.label || check.type,
|
|
5804
|
+
status: insufficient.length ? "proof_insufficient" : failed.length ? "failed" : "passed",
|
|
5805
|
+
evidence: {
|
|
5806
|
+
setup_action_label: check.setup_action_label || "",
|
|
5807
|
+
trace_path: check.trace_path || "",
|
|
5808
|
+
events: (check.events || []).map((event) => event.label),
|
|
5809
|
+
viewports: assessments,
|
|
5810
|
+
},
|
|
5811
|
+
message: insufficient.length
|
|
5812
|
+
? "Ordered trace evidence was insufficient in " + insufficient.length + " viewport(s)."
|
|
5813
|
+
: failed.length
|
|
5814
|
+
? "Ordered trace did not contain the required event sequence in " + failed.length + " viewport(s)."
|
|
5815
|
+
: undefined,
|
|
5816
|
+
});
|
|
5817
|
+
continue;
|
|
5818
|
+
}
|
|
5486
5819
|
if (check.type === "observe_within") {
|
|
5487
5820
|
const key = observeWithinKey(check);
|
|
5488
5821
|
const timeoutMs = observeWithinTimeoutMs(check);
|
|
@@ -5839,6 +6172,7 @@ function assessProfile(profile, evidence) {
|
|
|
5839
6172
|
if (!viewports.length || !checks.length) status = "proof_insufficient";
|
|
5840
6173
|
else if (viewports.some((viewport) => viewport.navigation_error)) status = "environment_blocked";
|
|
5841
6174
|
else if (expectedViewportCount && viewports.length < expectedViewportCount) status = "proof_insufficient";
|
|
6175
|
+
else if (checks.some((check) => check.status === "proof_insufficient")) status = "proof_insufficient";
|
|
5842
6176
|
else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
|
|
5843
6177
|
else if (checks.some((check) => check.status === "failed")) status = "product_regression";
|
|
5844
6178
|
const screenshotLabels = profileScreenshotLabels(viewports);
|
|
@@ -6770,7 +7104,7 @@ let activeViewportName = null;
|
|
|
6770
7104
|
async function executeSetupAction(action, ordinal, viewport) {
|
|
6771
7105
|
const type = setupActionType(action);
|
|
6772
7106
|
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 };
|
|
7107
|
+
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
7108
|
const timeout = setupNumber(action.timeout_ms, 5000);
|
|
6775
7109
|
try {
|
|
6776
7110
|
if (type === "wait") {
|
|
@@ -9422,7 +9756,7 @@ async function captureViewport(viewport) {
|
|
|
9422
9756
|
wait_error: waitError,
|
|
9423
9757
|
};
|
|
9424
9758
|
}
|
|
9425
|
-
${runtimeScriptAssessmentSource()}
|
|
9759
|
+
${runtimeScriptAssessmentSource(profile.checks.some((check) => check.type === "ordered_trace"))}
|
|
9426
9760
|
const viewports = [];
|
|
9427
9761
|
function buildProfileEvidence(currentViewports) {
|
|
9428
9762
|
const expectedViewportCount = (profile.target.viewports || []).length;
|
|
@@ -9633,7 +9967,10 @@ export {
|
|
|
9633
9967
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
9634
9968
|
RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES,
|
|
9635
9969
|
RIDDLE_PROOF_PROFILE_NETWORK_ABORT_ERROR_CODES,
|
|
9970
|
+
RIDDLE_PROOF_ORDERED_TRACE_OPERATORS,
|
|
9636
9971
|
deriveRiddleProofArtifactBodyAssertions,
|
|
9972
|
+
assessRiddleProofOrderedTrace,
|
|
9973
|
+
assessRiddleProofOrderedTraceSetupResults,
|
|
9637
9974
|
slugifyRiddleProofProfileName,
|
|
9638
9975
|
collectRiddleProofProfileWarnings,
|
|
9639
9976
|
normalizeRiddleProofProfile,
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import "../chunk-
|
|
1
|
+
import "../chunk-N7EETHYG.js";
|
|
2
2
|
import "../chunk-5IFZSUPF.js";
|
|
3
3
|
import "../chunk-JFQXAJH2.js";
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
import "../chunk-6T6ZAK77.js";
|
|
5
|
+
import "../chunk-A4EF4M3B.js";
|
|
6
|
+
import "../chunk-IL7MLYB5.js";
|
|
7
7
|
import "../chunk-MEVVL4TI.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-ZNVYJFR7.js";
|
|
9
9
|
import "../chunk-KXLEN4SA.js";
|
|
10
10
|
import "../chunk-WLUMLHII.js";
|
|
11
11
|
import "../chunk-CGJX7LJO.js";
|