@riddledc/riddle-proof 0.7.115 → 0.7.117
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 +21 -4
- package/dist/{chunk-QOOPPRYK.js → chunk-SBOGXOV5.js} +38 -1
- package/dist/cli.cjs +38 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +38 -1
- package/dist/index.js +1 -1
- package/dist/profile.cjs +38 -1
- package/dist/profile.js +1 -1
- package/examples/profiles/handled-recovery-list-load.json +95 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -198,10 +198,27 @@ generic inline-script warning threshold. Use `--strict=true` when you
|
|
|
198
198
|
deliberately want Riddle's non-critical script-safety warnings to block the run.
|
|
199
199
|
Critical script-safety violations remain blocked by Riddle either way.
|
|
200
200
|
|
|
201
|
-
The package includes
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
The package includes generic starter profiles:
|
|
202
|
+
|
|
203
|
+
- `examples/profiles/page-content-basic.json` for route/content/layout smoke profiles.
|
|
204
|
+
- `examples/profiles/route-inventory-basic.json` for source-link and direct-route audits.
|
|
205
|
+
- `examples/profiles/handled-recovery-list-load.json` for malformed list-load recovery profiles.
|
|
206
|
+
|
|
207
|
+
Copy one of those shapes into a repository profile directory and replace the
|
|
208
|
+
routes, selectors, mock URLs, and text checks with app-specific invariants.
|
|
209
|
+
|
|
210
|
+
For handled recovery profiles, prefer proving the whole boundary instead of
|
|
211
|
+
only checking that an error message appears. Mock one dependent endpoint into a
|
|
212
|
+
malformed or failed response, keep independent endpoints healthy, and assert
|
|
213
|
+
that the page still renders the independent evidence. Capture a setup
|
|
214
|
+
screenshot immediately after the recovery state appears, before high-risk
|
|
215
|
+
absence assertions, so failing runs keep durable visual evidence. Then reject
|
|
216
|
+
raw parser text such as `SyntaxError`, `Expected property name`, and
|
|
217
|
+
`[object Object]`; reject contradictory empty-state copy such as `No items yet`
|
|
218
|
+
when the list failed to load; and keep `no_fatal_console_errors` plus
|
|
219
|
+
`no_console_warnings` in the final checks. This pattern catches both visible
|
|
220
|
+
recovery-quality bugs and hidden browser-health debt without requiring a
|
|
221
|
+
separate CI or wrapper-specific path.
|
|
205
222
|
|
|
206
223
|
Checks normally apply to every captured viewport. Add `viewports` (or
|
|
207
224
|
`viewport_names`) to a check when responsive UI intentionally exposes an
|
|
@@ -699,6 +699,38 @@ function collectRiddleProofProfileWarnings(profile) {
|
|
|
699
699
|
}
|
|
700
700
|
return warnings;
|
|
701
701
|
}
|
|
702
|
+
function profileStatusProbeCounts(profile) {
|
|
703
|
+
let httpStatus = 0;
|
|
704
|
+
let linkStatus = 0;
|
|
705
|
+
for (const check of profile.checks || []) {
|
|
706
|
+
if (check.type === "http_status") httpStatus += 1;
|
|
707
|
+
if (check.type === "link_status" || check.type === "artifact_link_status") linkStatus += 1;
|
|
708
|
+
}
|
|
709
|
+
return { httpStatus, linkStatus };
|
|
710
|
+
}
|
|
711
|
+
function riddleApiStrictSafetyWarnings(blocker) {
|
|
712
|
+
const rawWarnings = Array.isArray(blocker?.warnings) ? blocker.warnings.filter((warning) => typeof warning === "string") : [];
|
|
713
|
+
const errorText = typeof blocker?.error === "string" ? blocker.error.toLowerCase() : "";
|
|
714
|
+
const hasSafetyError = errorText.includes("potentially unsafe operations");
|
|
715
|
+
const hasDirectNetworkWarning = rawWarnings.some((warning) => warning.toLowerCase().includes("direct network operations"));
|
|
716
|
+
return hasSafetyError || hasDirectNetworkWarning ? rawWarnings : [];
|
|
717
|
+
}
|
|
718
|
+
function collectRiddleProofProfileEnvironmentBlockedWarnings(profile, blocker) {
|
|
719
|
+
const warnings = collectRiddleProofProfileWarnings(profile);
|
|
720
|
+
const safetyWarnings = riddleApiStrictSafetyWarnings(blocker);
|
|
721
|
+
if (!safetyWarnings.length) return warnings;
|
|
722
|
+
const counts = profileStatusProbeCounts(profile);
|
|
723
|
+
const statusProbeCount = counts.httpStatus + counts.linkStatus;
|
|
724
|
+
if (!statusProbeCount) return warnings;
|
|
725
|
+
const parts = [
|
|
726
|
+
counts.httpStatus ? `${counts.httpStatus} http_status` : "",
|
|
727
|
+
counts.linkStatus ? `${counts.linkStatus} link_status/artifact_link_status` : ""
|
|
728
|
+
].filter(Boolean);
|
|
729
|
+
warnings.push(
|
|
730
|
+
`Riddle API strict script validation blocked a hosted profile runner with ${parts.join(" and ")} check(s). These checks intentionally collect endpoint/link evidence from inside the generated browser runner; hosted run-profile defaults to --strict=false, so omit --strict=true or rerun with --strict=false for trusted generated profile runs.`
|
|
731
|
+
);
|
|
732
|
+
return warnings;
|
|
733
|
+
}
|
|
702
734
|
function normalizeRouteInventoryPath(value, label) {
|
|
703
735
|
const path = stringValue(value);
|
|
704
736
|
if (!path) throw new Error(`${label} requires path.`);
|
|
@@ -2223,7 +2255,7 @@ function createRiddleProofProfileConfigurationError(name, error, runner = "riddl
|
|
|
2223
2255
|
function createRiddleProofProfileEnvironmentBlockedResult(input) {
|
|
2224
2256
|
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
|
|
2225
2257
|
const environmentBlocker = extractRiddleRunnerBlocker(message);
|
|
2226
|
-
const warnings =
|
|
2258
|
+
const warnings = collectRiddleProofProfileEnvironmentBlockedWarnings(input.profile, environmentBlocker);
|
|
2227
2259
|
return {
|
|
2228
2260
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
2229
2261
|
profile_name: input.profile.name,
|
|
@@ -2266,6 +2298,11 @@ function extractRiddleRunnerBlocker(message) {
|
|
|
2266
2298
|
for (const key of ["error", "required_seconds", "available_seconds", "deficit_seconds", "minimum_purchase_dollars"]) {
|
|
2267
2299
|
copyScalar(key);
|
|
2268
2300
|
}
|
|
2301
|
+
const warnings = payload?.warnings;
|
|
2302
|
+
if (Array.isArray(warnings)) {
|
|
2303
|
+
const warningStrings = warnings.filter((warning) => typeof warning === "string");
|
|
2304
|
+
if (warningStrings.length) details.warnings = warningStrings;
|
|
2305
|
+
}
|
|
2269
2306
|
const httpStatus = typeof details.http_status === "number" ? details.http_status : void 0;
|
|
2270
2307
|
const errorText = typeof details.error === "string" ? details.error.toLowerCase() : "";
|
|
2271
2308
|
if (httpStatus === 402 && errorText.includes("balance")) {
|
package/dist/cli.cjs
CHANGED
|
@@ -7636,6 +7636,38 @@ function collectRiddleProofProfileWarnings(profile) {
|
|
|
7636
7636
|
}
|
|
7637
7637
|
return warnings;
|
|
7638
7638
|
}
|
|
7639
|
+
function profileStatusProbeCounts(profile) {
|
|
7640
|
+
let httpStatus = 0;
|
|
7641
|
+
let linkStatus = 0;
|
|
7642
|
+
for (const check of profile.checks || []) {
|
|
7643
|
+
if (check.type === "http_status") httpStatus += 1;
|
|
7644
|
+
if (check.type === "link_status" || check.type === "artifact_link_status") linkStatus += 1;
|
|
7645
|
+
}
|
|
7646
|
+
return { httpStatus, linkStatus };
|
|
7647
|
+
}
|
|
7648
|
+
function riddleApiStrictSafetyWarnings(blocker) {
|
|
7649
|
+
const rawWarnings = Array.isArray(blocker?.warnings) ? blocker.warnings.filter((warning) => typeof warning === "string") : [];
|
|
7650
|
+
const errorText = typeof blocker?.error === "string" ? blocker.error.toLowerCase() : "";
|
|
7651
|
+
const hasSafetyError = errorText.includes("potentially unsafe operations");
|
|
7652
|
+
const hasDirectNetworkWarning = rawWarnings.some((warning) => warning.toLowerCase().includes("direct network operations"));
|
|
7653
|
+
return hasSafetyError || hasDirectNetworkWarning ? rawWarnings : [];
|
|
7654
|
+
}
|
|
7655
|
+
function collectRiddleProofProfileEnvironmentBlockedWarnings(profile, blocker) {
|
|
7656
|
+
const warnings = collectRiddleProofProfileWarnings(profile);
|
|
7657
|
+
const safetyWarnings = riddleApiStrictSafetyWarnings(blocker);
|
|
7658
|
+
if (!safetyWarnings.length) return warnings;
|
|
7659
|
+
const counts = profileStatusProbeCounts(profile);
|
|
7660
|
+
const statusProbeCount = counts.httpStatus + counts.linkStatus;
|
|
7661
|
+
if (!statusProbeCount) return warnings;
|
|
7662
|
+
const parts = [
|
|
7663
|
+
counts.httpStatus ? `${counts.httpStatus} http_status` : "",
|
|
7664
|
+
counts.linkStatus ? `${counts.linkStatus} link_status/artifact_link_status` : ""
|
|
7665
|
+
].filter(Boolean);
|
|
7666
|
+
warnings.push(
|
|
7667
|
+
`Riddle API strict script validation blocked a hosted profile runner with ${parts.join(" and ")} check(s). These checks intentionally collect endpoint/link evidence from inside the generated browser runner; hosted run-profile defaults to --strict=false, so omit --strict=true or rerun with --strict=false for trusted generated profile runs.`
|
|
7668
|
+
);
|
|
7669
|
+
return warnings;
|
|
7670
|
+
}
|
|
7639
7671
|
function normalizeRouteInventoryPath(value, label) {
|
|
7640
7672
|
const path7 = stringValue2(value);
|
|
7641
7673
|
if (!path7) throw new Error(`${label} requires path.`);
|
|
@@ -9144,7 +9176,7 @@ function profileStatusExitCode(profile, status) {
|
|
|
9144
9176
|
function createRiddleProofProfileEnvironmentBlockedResult(input) {
|
|
9145
9177
|
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
|
|
9146
9178
|
const environmentBlocker = extractRiddleRunnerBlocker(message);
|
|
9147
|
-
const warnings =
|
|
9179
|
+
const warnings = collectRiddleProofProfileEnvironmentBlockedWarnings(input.profile, environmentBlocker);
|
|
9148
9180
|
return {
|
|
9149
9181
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
9150
9182
|
profile_name: input.profile.name,
|
|
@@ -9187,6 +9219,11 @@ function extractRiddleRunnerBlocker(message) {
|
|
|
9187
9219
|
for (const key of ["error", "required_seconds", "available_seconds", "deficit_seconds", "minimum_purchase_dollars"]) {
|
|
9188
9220
|
copyScalar(key);
|
|
9189
9221
|
}
|
|
9222
|
+
const warnings = payload?.warnings;
|
|
9223
|
+
if (Array.isArray(warnings)) {
|
|
9224
|
+
const warningStrings = warnings.filter((warning) => typeof warning === "string");
|
|
9225
|
+
if (warningStrings.length) details.warnings = warningStrings;
|
|
9226
|
+
}
|
|
9190
9227
|
const httpStatus = typeof details.http_status === "number" ? details.http_status : void 0;
|
|
9191
9228
|
const errorText = typeof details.error === "string" ? details.error.toLowerCase() : "";
|
|
9192
9229
|
if (httpStatus === 402 && errorText.includes("balance")) {
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -9429,6 +9429,38 @@ function collectRiddleProofProfileWarnings(profile) {
|
|
|
9429
9429
|
}
|
|
9430
9430
|
return warnings;
|
|
9431
9431
|
}
|
|
9432
|
+
function profileStatusProbeCounts(profile) {
|
|
9433
|
+
let httpStatus = 0;
|
|
9434
|
+
let linkStatus = 0;
|
|
9435
|
+
for (const check of profile.checks || []) {
|
|
9436
|
+
if (check.type === "http_status") httpStatus += 1;
|
|
9437
|
+
if (check.type === "link_status" || check.type === "artifact_link_status") linkStatus += 1;
|
|
9438
|
+
}
|
|
9439
|
+
return { httpStatus, linkStatus };
|
|
9440
|
+
}
|
|
9441
|
+
function riddleApiStrictSafetyWarnings(blocker) {
|
|
9442
|
+
const rawWarnings = Array.isArray(blocker?.warnings) ? blocker.warnings.filter((warning) => typeof warning === "string") : [];
|
|
9443
|
+
const errorText = typeof blocker?.error === "string" ? blocker.error.toLowerCase() : "";
|
|
9444
|
+
const hasSafetyError = errorText.includes("potentially unsafe operations");
|
|
9445
|
+
const hasDirectNetworkWarning = rawWarnings.some((warning) => warning.toLowerCase().includes("direct network operations"));
|
|
9446
|
+
return hasSafetyError || hasDirectNetworkWarning ? rawWarnings : [];
|
|
9447
|
+
}
|
|
9448
|
+
function collectRiddleProofProfileEnvironmentBlockedWarnings(profile, blocker) {
|
|
9449
|
+
const warnings = collectRiddleProofProfileWarnings(profile);
|
|
9450
|
+
const safetyWarnings = riddleApiStrictSafetyWarnings(blocker);
|
|
9451
|
+
if (!safetyWarnings.length) return warnings;
|
|
9452
|
+
const counts = profileStatusProbeCounts(profile);
|
|
9453
|
+
const statusProbeCount = counts.httpStatus + counts.linkStatus;
|
|
9454
|
+
if (!statusProbeCount) return warnings;
|
|
9455
|
+
const parts = [
|
|
9456
|
+
counts.httpStatus ? `${counts.httpStatus} http_status` : "",
|
|
9457
|
+
counts.linkStatus ? `${counts.linkStatus} link_status/artifact_link_status` : ""
|
|
9458
|
+
].filter(Boolean);
|
|
9459
|
+
warnings.push(
|
|
9460
|
+
`Riddle API strict script validation blocked a hosted profile runner with ${parts.join(" and ")} check(s). These checks intentionally collect endpoint/link evidence from inside the generated browser runner; hosted run-profile defaults to --strict=false, so omit --strict=true or rerun with --strict=false for trusted generated profile runs.`
|
|
9461
|
+
);
|
|
9462
|
+
return warnings;
|
|
9463
|
+
}
|
|
9432
9464
|
function normalizeRouteInventoryPath(value, label) {
|
|
9433
9465
|
const path6 = stringValue5(value);
|
|
9434
9466
|
if (!path6) throw new Error(`${label} requires path.`);
|
|
@@ -10953,7 +10985,7 @@ function createRiddleProofProfileConfigurationError(name, error, runner = "riddl
|
|
|
10953
10985
|
function createRiddleProofProfileEnvironmentBlockedResult(input) {
|
|
10954
10986
|
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
|
|
10955
10987
|
const environmentBlocker = extractRiddleRunnerBlocker(message);
|
|
10956
|
-
const warnings =
|
|
10988
|
+
const warnings = collectRiddleProofProfileEnvironmentBlockedWarnings(input.profile, environmentBlocker);
|
|
10957
10989
|
return {
|
|
10958
10990
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
10959
10991
|
profile_name: input.profile.name,
|
|
@@ -10996,6 +11028,11 @@ function extractRiddleRunnerBlocker(message) {
|
|
|
10996
11028
|
for (const key of ["error", "required_seconds", "available_seconds", "deficit_seconds", "minimum_purchase_dollars"]) {
|
|
10997
11029
|
copyScalar(key);
|
|
10998
11030
|
}
|
|
11031
|
+
const warnings = payload?.warnings;
|
|
11032
|
+
if (Array.isArray(warnings)) {
|
|
11033
|
+
const warningStrings = warnings.filter((warning) => typeof warning === "string");
|
|
11034
|
+
if (warningStrings.length) details.warnings = warningStrings;
|
|
11035
|
+
}
|
|
10999
11036
|
const httpStatus = typeof details.http_status === "number" ? details.http_status : void 0;
|
|
11000
11037
|
const errorText = typeof details.error === "string" ? details.error.toLowerCase() : "";
|
|
11001
11038
|
if (httpStatus === 402 && errorText.includes("balance")) {
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
resolveRiddleProofProfileTimeoutSec,
|
|
60
60
|
slugifyRiddleProofProfileName,
|
|
61
61
|
summarizeRiddleProofProfileResult
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-SBOGXOV5.js";
|
|
63
63
|
import {
|
|
64
64
|
DEFAULT_RIDDLE_API_BASE_URL,
|
|
65
65
|
DEFAULT_RIDDLE_API_KEY_FILE,
|
package/dist/profile.cjs
CHANGED
|
@@ -743,6 +743,38 @@ function collectRiddleProofProfileWarnings(profile) {
|
|
|
743
743
|
}
|
|
744
744
|
return warnings;
|
|
745
745
|
}
|
|
746
|
+
function profileStatusProbeCounts(profile) {
|
|
747
|
+
let httpStatus = 0;
|
|
748
|
+
let linkStatus = 0;
|
|
749
|
+
for (const check of profile.checks || []) {
|
|
750
|
+
if (check.type === "http_status") httpStatus += 1;
|
|
751
|
+
if (check.type === "link_status" || check.type === "artifact_link_status") linkStatus += 1;
|
|
752
|
+
}
|
|
753
|
+
return { httpStatus, linkStatus };
|
|
754
|
+
}
|
|
755
|
+
function riddleApiStrictSafetyWarnings(blocker) {
|
|
756
|
+
const rawWarnings = Array.isArray(blocker?.warnings) ? blocker.warnings.filter((warning) => typeof warning === "string") : [];
|
|
757
|
+
const errorText = typeof blocker?.error === "string" ? blocker.error.toLowerCase() : "";
|
|
758
|
+
const hasSafetyError = errorText.includes("potentially unsafe operations");
|
|
759
|
+
const hasDirectNetworkWarning = rawWarnings.some((warning) => warning.toLowerCase().includes("direct network operations"));
|
|
760
|
+
return hasSafetyError || hasDirectNetworkWarning ? rawWarnings : [];
|
|
761
|
+
}
|
|
762
|
+
function collectRiddleProofProfileEnvironmentBlockedWarnings(profile, blocker) {
|
|
763
|
+
const warnings = collectRiddleProofProfileWarnings(profile);
|
|
764
|
+
const safetyWarnings = riddleApiStrictSafetyWarnings(blocker);
|
|
765
|
+
if (!safetyWarnings.length) return warnings;
|
|
766
|
+
const counts = profileStatusProbeCounts(profile);
|
|
767
|
+
const statusProbeCount = counts.httpStatus + counts.linkStatus;
|
|
768
|
+
if (!statusProbeCount) return warnings;
|
|
769
|
+
const parts = [
|
|
770
|
+
counts.httpStatus ? `${counts.httpStatus} http_status` : "",
|
|
771
|
+
counts.linkStatus ? `${counts.linkStatus} link_status/artifact_link_status` : ""
|
|
772
|
+
].filter(Boolean);
|
|
773
|
+
warnings.push(
|
|
774
|
+
`Riddle API strict script validation blocked a hosted profile runner with ${parts.join(" and ")} check(s). These checks intentionally collect endpoint/link evidence from inside the generated browser runner; hosted run-profile defaults to --strict=false, so omit --strict=true or rerun with --strict=false for trusted generated profile runs.`
|
|
775
|
+
);
|
|
776
|
+
return warnings;
|
|
777
|
+
}
|
|
746
778
|
function normalizeRouteInventoryPath(value, label) {
|
|
747
779
|
const path = stringValue(value);
|
|
748
780
|
if (!path) throw new Error(`${label} requires path.`);
|
|
@@ -2267,7 +2299,7 @@ function createRiddleProofProfileConfigurationError(name, error, runner = "riddl
|
|
|
2267
2299
|
function createRiddleProofProfileEnvironmentBlockedResult(input) {
|
|
2268
2300
|
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
|
|
2269
2301
|
const environmentBlocker = extractRiddleRunnerBlocker(message);
|
|
2270
|
-
const warnings =
|
|
2302
|
+
const warnings = collectRiddleProofProfileEnvironmentBlockedWarnings(input.profile, environmentBlocker);
|
|
2271
2303
|
return {
|
|
2272
2304
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
2273
2305
|
profile_name: input.profile.name,
|
|
@@ -2310,6 +2342,11 @@ function extractRiddleRunnerBlocker(message) {
|
|
|
2310
2342
|
for (const key of ["error", "required_seconds", "available_seconds", "deficit_seconds", "minimum_purchase_dollars"]) {
|
|
2311
2343
|
copyScalar(key);
|
|
2312
2344
|
}
|
|
2345
|
+
const warnings = payload?.warnings;
|
|
2346
|
+
if (Array.isArray(warnings)) {
|
|
2347
|
+
const warningStrings = warnings.filter((warning) => typeof warning === "string");
|
|
2348
|
+
if (warningStrings.length) details.warnings = warningStrings;
|
|
2349
|
+
}
|
|
2313
2350
|
const httpStatus = typeof details.http_status === "number" ? details.http_status : void 0;
|
|
2314
2351
|
const errorText = typeof details.error === "string" ? details.error.toLowerCase() : "";
|
|
2315
2352
|
if (httpStatus === 402 && errorText.includes("balance")) {
|
package/dist/profile.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveRiddleProofProfileTimeoutSec,
|
|
21
21
|
slugifyRiddleProofProfileName,
|
|
22
22
|
summarizeRiddleProofProfileResult
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SBOGXOV5.js";
|
|
24
24
|
export {
|
|
25
25
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
26
26
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "riddle-proof.profile.v1",
|
|
3
|
+
"name": "handled-recovery-list-load",
|
|
4
|
+
"target": {
|
|
5
|
+
"route": "/account",
|
|
6
|
+
"viewports": [
|
|
7
|
+
{ "name": "mobile", "width": 390, "height": 844 },
|
|
8
|
+
{ "name": "tablet", "width": 820, "height": 1180 },
|
|
9
|
+
{ "name": "desktop", "width": 1440, "height": 1000 }
|
|
10
|
+
],
|
|
11
|
+
"timeout_sec": 300,
|
|
12
|
+
"wait_ms": 800,
|
|
13
|
+
"network_mocks": [
|
|
14
|
+
{
|
|
15
|
+
"label": "account-summary",
|
|
16
|
+
"url": "**/api/account/summary",
|
|
17
|
+
"method": "GET",
|
|
18
|
+
"status": 200,
|
|
19
|
+
"content_type": "application/json",
|
|
20
|
+
"required_hit_count": 3,
|
|
21
|
+
"json": {
|
|
22
|
+
"available_time": "4h 0m",
|
|
23
|
+
"active_jobs": 2
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"label": "recent-jobs",
|
|
28
|
+
"url": "**/api/jobs?limit=10",
|
|
29
|
+
"method": "GET",
|
|
30
|
+
"status": 200,
|
|
31
|
+
"content_type": "application/json",
|
|
32
|
+
"required_hit_count": 3,
|
|
33
|
+
"json": {
|
|
34
|
+
"jobs": [
|
|
35
|
+
{
|
|
36
|
+
"id": "job_recovery_template_survives",
|
|
37
|
+
"status": "completed"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"label": "saved-items-malformed-load",
|
|
44
|
+
"url": "**/api/saved-items",
|
|
45
|
+
"method": "GET",
|
|
46
|
+
"status": 200,
|
|
47
|
+
"content_type": "application/json",
|
|
48
|
+
"required_hit_count": 3,
|
|
49
|
+
"body": "{not valid saved items json"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"setup_actions": [
|
|
53
|
+
{ "type": "clear_storage", "storage": "both", "reload": true },
|
|
54
|
+
{ "type": "wait_for_selector", "selector": "[data-testid='account-page']", "timeout_ms": 30000 },
|
|
55
|
+
{ "type": "wait_for_text", "selector": "body", "text": "4h 0m", "timeout_ms": 30000 },
|
|
56
|
+
{ "type": "wait_for_text", "selector": "body", "text": "job_recovery_template_survives", "timeout_ms": 30000 },
|
|
57
|
+
{ "type": "wait_for_text", "selector": "body", "text": "Failed to load saved items", "timeout_ms": 30000 },
|
|
58
|
+
{ "type": "screenshot", "label": "saved-items-malformed-load-visible-recovery" },
|
|
59
|
+
{ "type": "assert_text_absent", "selector": "body", "text": "No saved items yet", "timeout_ms": 1000 },
|
|
60
|
+
{ "type": "assert_text_absent", "selector": "body", "text": "Expected property name", "timeout_ms": 1000 },
|
|
61
|
+
{ "type": "assert_text_absent", "selector": "body", "text": "SyntaxError", "timeout_ms": 1000 },
|
|
62
|
+
{ "type": "assert_text_absent", "selector": "body", "text": "[object Object]", "timeout_ms": 1000 }
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"checks": [
|
|
66
|
+
{ "type": "route_loaded", "expected_path": "/account" },
|
|
67
|
+
{ "type": "selector_visible", "selector": "[data-testid='account-page']" },
|
|
68
|
+
{ "type": "selector_visible", "selector": "[data-testid='saved-items-error']" },
|
|
69
|
+
{ "type": "text_visible", "text": "4h 0m" },
|
|
70
|
+
{ "type": "text_visible", "text": "2 active" },
|
|
71
|
+
{ "type": "text_visible", "text": "job_recovery_template_survives" },
|
|
72
|
+
{ "type": "text_visible", "text": "Failed to load saved items" },
|
|
73
|
+
{ "type": "text_absent", "text": "No saved items yet" },
|
|
74
|
+
{ "type": "text_absent", "text": "Expected property name" },
|
|
75
|
+
{ "type": "text_absent", "text": "SyntaxError" },
|
|
76
|
+
{ "type": "text_absent", "text": "[object Object]" },
|
|
77
|
+
{ "type": "text_absent", "text": "Application error" },
|
|
78
|
+
{ "type": "selector_count_equals", "selector": "[data-testid='saved-items-error']", "expected_count": 1 },
|
|
79
|
+
{ "type": "selector_count_equals", "selector": "[data-testid='recent-job-row']", "expected_count": 1 },
|
|
80
|
+
{ "type": "selector_count_equals", "selector": "[data-testid='saved-item-row']", "expected_count": 0 },
|
|
81
|
+
{ "type": "no_horizontal_overflow", "max_overflow_px": 1 },
|
|
82
|
+
{ "type": "no_fatal_console_errors" },
|
|
83
|
+
{ "type": "no_console_warnings" }
|
|
84
|
+
],
|
|
85
|
+
"artifacts": ["screenshot", "console", "dom_summary", "proof_json"],
|
|
86
|
+
"baseline_policy": "invariant_only",
|
|
87
|
+
"failure_policy": {
|
|
88
|
+
"environment_blocked": "neutral",
|
|
89
|
+
"proof_insufficient": "fail",
|
|
90
|
+
"product_regression": "fail"
|
|
91
|
+
},
|
|
92
|
+
"metadata": {
|
|
93
|
+
"purpose": "Template for handled malformed list-load recovery: prove independent page data still renders, the failed list shows one recovery message, contradictory empty-state copy and raw parser text stay absent, and browser console evidence remains clean."
|
|
94
|
+
}
|
|
95
|
+
}
|