@riddledc/riddle-proof 0.5.57 → 0.7.0
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 +91 -0
- package/dist/basic-gameplay.cjs +437 -4
- package/dist/basic-gameplay.d.cts +121 -2
- package/dist/basic-gameplay.d.ts +121 -2
- package/dist/basic-gameplay.js +23 -3
- package/dist/chunk-7NMAU4DP.js +808 -0
- package/dist/chunk-AAIASO2C.js +699 -0
- package/dist/cli.cjs +950 -0
- package/dist/cli.js +192 -3
- package/dist/engine-harness.js +2 -2
- package/dist/index.cjs +1258 -2
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +60 -4
- package/dist/profile.cjs +848 -0
- package/dist/profile.d.cts +175 -0
- package/dist/profile.d.ts +175 -0
- package/dist/profile.js +38 -0
- package/examples/profiles/page-content-basic.json +26 -0
- package/package.json +8 -2
- package/dist/chunk-53DJMNQ6.js +0 -276
- package/dist/{chunk-2FBF2UDZ.js → chunk-D3M2FAYQ.js} +3 -3
package/README.md
CHANGED
|
@@ -92,6 +92,71 @@ file/object before resuming the run.
|
|
|
92
92
|
uses the local Codex CLI adapter underneath, but the loop contract and CLI
|
|
93
93
|
surface are intentionally not Codex-specific.
|
|
94
94
|
|
|
95
|
+
## CI / Profile Mode
|
|
96
|
+
|
|
97
|
+
Profile mode runs durable proof profiles against an existing site without an
|
|
98
|
+
implementation step. Use it for audits, regression checks, CI smoke profiles,
|
|
99
|
+
or as a stronger proof base before a change loop.
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"version": "riddle-proof.profile.v1",
|
|
104
|
+
"name": "pricing-page-basic",
|
|
105
|
+
"target": {
|
|
106
|
+
"route": "/pricing",
|
|
107
|
+
"viewports": [
|
|
108
|
+
{ "name": "mobile", "width": 390, "height": 844 },
|
|
109
|
+
{ "name": "desktop", "width": 1440, "height": 1000 }
|
|
110
|
+
],
|
|
111
|
+
"auth": "none"
|
|
112
|
+
},
|
|
113
|
+
"checks": [
|
|
114
|
+
{ "type": "route_loaded", "expected_path": "/pricing" },
|
|
115
|
+
{ "type": "selector_visible", "selector": "[data-testid='pricing-cards']" },
|
|
116
|
+
{ "type": "text_visible", "text": "Start building" },
|
|
117
|
+
{ "type": "no_mobile_horizontal_overflow" },
|
|
118
|
+
{ "type": "no_fatal_console_errors" }
|
|
119
|
+
],
|
|
120
|
+
"artifacts": ["screenshot", "console", "dom_summary", "proof_json"],
|
|
121
|
+
"failure_policy": {
|
|
122
|
+
"environment_blocked": "neutral",
|
|
123
|
+
"proof_insufficient": "fail",
|
|
124
|
+
"product_regression": "fail"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Run a profile with the hosted Riddle runner:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
riddle-proof-loop run-profile \
|
|
133
|
+
--profile .riddle-proof/profiles/pricing.json \
|
|
134
|
+
--url https://example.com \
|
|
135
|
+
--runner riddle \
|
|
136
|
+
--output artifacts/riddle-proof/pricing
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The package includes a generic starter profile at
|
|
140
|
+
`examples/profiles/page-content-basic.json`; copy that shape into a repository
|
|
141
|
+
profile directory and replace the selector/text checks with app-specific
|
|
142
|
+
invariants.
|
|
143
|
+
|
|
144
|
+
The result uses `riddle-proof.profile-result.v1` and separates product failures
|
|
145
|
+
from weak proof and environment blockers:
|
|
146
|
+
|
|
147
|
+
- `passed`: required evidence exists and checks passed.
|
|
148
|
+
- `product_regression`: the app loaded, but an invariant failed.
|
|
149
|
+
- `proof_insufficient`: capture did not produce enough evidence to decide.
|
|
150
|
+
- `environment_blocked`: browser, network, auth, or runner setup blocked proof.
|
|
151
|
+
- `configuration_error`: the profile or runner options are invalid.
|
|
152
|
+
- `needs_human_review`: artifacts were collected, but automation cannot safely decide.
|
|
153
|
+
|
|
154
|
+
`--output` writes `profile-result.json`, `summary.md`, and local copies of the
|
|
155
|
+
structured `proof.json`, `console.json`, and `dom-summary.json` when they are
|
|
156
|
+
available. Riddle screenshot URLs remain referenced in the result's artifact
|
|
157
|
+
list. The profile/result schema is runner-agnostic; Riddle is the first hosted
|
|
158
|
+
adapter.
|
|
159
|
+
|
|
95
160
|
## Runner Harness
|
|
96
161
|
|
|
97
162
|
`runRiddleProof` is the reusable idea-to-PR workflow driver. It does not ship
|
|
@@ -214,6 +279,32 @@ Expose that as `window.__riddleProofEvidence.playability` or
|
|
|
214
279
|
still frame, including a generated image plate, is supporting evidence only and
|
|
215
280
|
does not satisfy playable proof by itself.
|
|
216
281
|
|
|
282
|
+
### Basic Gameplay Helpers
|
|
283
|
+
|
|
284
|
+
`@riddledc/riddle-proof/basic-gameplay` exposes reusable proof primitives for
|
|
285
|
+
route-by-route game/site suites. The helpers assess the public
|
|
286
|
+
`riddle-proof.basic-gameplay.v1` evidence schema, attach Riddle screenshot
|
|
287
|
+
artifact hashes back to phase metrics, resolve visual false negatives when
|
|
288
|
+
artifact screenshots differ, and convert progression failures into catch
|
|
289
|
+
records.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import {
|
|
293
|
+
assessBasicGameplayEvidence,
|
|
294
|
+
attachBasicGameplayArtifactScreenshotHashes,
|
|
295
|
+
createBasicGameplayCatchRecords,
|
|
296
|
+
} from "@riddledc/riddle-proof/basic-gameplay";
|
|
297
|
+
|
|
298
|
+
attachBasicGameplayArtifactScreenshotHashes(evidence, { artifacts });
|
|
299
|
+
const assessment = assessBasicGameplayEvidence(evidence);
|
|
300
|
+
const catches = createBasicGameplayCatchRecords(assessment, evidence);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The package owns generic contracts such as `state_path`, `state_call`,
|
|
304
|
+
`property_path`, `number_unchanged`, held-key/window-call/evaluate action type
|
|
305
|
+
constants, and JSON-safe text compaction. Site-specific manifests, selectors,
|
|
306
|
+
and deterministic game scripts should stay in the caller.
|
|
307
|
+
|
|
217
308
|
### Server Preview Usage
|
|
218
309
|
|
|
219
310
|
Server preview proof is most useful when the request, capture, and PR comment
|
package/dist/basic-gameplay.cjs
CHANGED
|
@@ -20,18 +20,56 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/basic-gameplay.ts
|
|
21
21
|
var basic_gameplay_exports = {};
|
|
22
22
|
__export(basic_gameplay_exports, {
|
|
23
|
+
BASIC_GAMEPLAY_ACTION_TYPES: () => BASIC_GAMEPLAY_ACTION_TYPES,
|
|
24
|
+
BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES: () => BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
|
|
23
25
|
RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
|
|
24
26
|
RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
|
|
25
27
|
RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
|
|
26
28
|
assessBasicGameplayEvidence: () => assessBasicGameplayEvidence,
|
|
29
|
+
assessBasicGameplayProgressionCheck: () => assessBasicGameplayProgressionCheck,
|
|
30
|
+
assessBasicGameplayProgressionChecks: () => assessBasicGameplayProgressionChecks,
|
|
27
31
|
assessBasicGameplayRoute: () => assessBasicGameplayRoute,
|
|
32
|
+
attachBasicGameplayArtifactScreenshotHashes: () => attachBasicGameplayArtifactScreenshotHashes,
|
|
33
|
+
augmentBasicGameplayAssessmentWithProgressionChecks: () => augmentBasicGameplayAssessmentWithProgressionChecks,
|
|
34
|
+
compactBasicGameplayText: () => compactBasicGameplayText,
|
|
35
|
+
createBasicGameplayCatchRecords: () => createBasicGameplayCatchRecords,
|
|
28
36
|
createBasicGameplayCatchSummary: () => createBasicGameplayCatchSummary,
|
|
29
|
-
extractBasicGameplayEvidence: () => extractBasicGameplayEvidence
|
|
37
|
+
extractBasicGameplayEvidence: () => extractBasicGameplayEvidence,
|
|
38
|
+
resolveBasicGameplayProgressionCheckWithArtifactScreenshots: () => resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
|
|
39
|
+
sanitizeBasicGameplayJsonString: () => sanitizeBasicGameplayJsonString
|
|
30
40
|
});
|
|
31
41
|
module.exports = __toCommonJS(basic_gameplay_exports);
|
|
32
42
|
var RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION = "riddle-proof.basic-gameplay.v1";
|
|
33
43
|
var RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION = "riddle-proof.basic-gameplay.assessment.v1";
|
|
34
44
|
var RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION = "riddle-proof.basic-gameplay.catch.v1";
|
|
45
|
+
var BASIC_GAMEPLAY_ACTION_TYPES = [
|
|
46
|
+
"wait",
|
|
47
|
+
"key",
|
|
48
|
+
"key-down",
|
|
49
|
+
"key-up",
|
|
50
|
+
"hold-key",
|
|
51
|
+
"repeat",
|
|
52
|
+
"click",
|
|
53
|
+
"click-by-text",
|
|
54
|
+
"wait-for-text",
|
|
55
|
+
"window-call",
|
|
56
|
+
"evaluate"
|
|
57
|
+
];
|
|
58
|
+
var BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES = [
|
|
59
|
+
"selector_count_increases",
|
|
60
|
+
"selector_count_at_least",
|
|
61
|
+
"selector_absent",
|
|
62
|
+
"selector_text_matches",
|
|
63
|
+
"number_increases",
|
|
64
|
+
"number_decreases",
|
|
65
|
+
"number_unchanged",
|
|
66
|
+
"number_stays_equal",
|
|
67
|
+
"number_equals",
|
|
68
|
+
"canvas_hash_changes",
|
|
69
|
+
"screenshot_hash_changes",
|
|
70
|
+
"visual_hash_changes",
|
|
71
|
+
"state_changes"
|
|
72
|
+
];
|
|
35
73
|
var BASIC_GAMEPLAY_CONTAINER_KEYS = [
|
|
36
74
|
"basic_gameplay",
|
|
37
75
|
"basicGameplay",
|
|
@@ -40,6 +78,19 @@ var BASIC_GAMEPLAY_CONTAINER_KEYS = [
|
|
|
40
78
|
"gameplay_proof",
|
|
41
79
|
"gameplayProof"
|
|
42
80
|
];
|
|
81
|
+
var ARTIFACT_VISUAL_CHANGE_CHECKS = /* @__PURE__ */ new Set([
|
|
82
|
+
"canvas_hash_changes",
|
|
83
|
+
"screenshot_hash_changes",
|
|
84
|
+
"state_changes",
|
|
85
|
+
"visual_hash_changes"
|
|
86
|
+
]);
|
|
87
|
+
var PHASE_SCREENSHOT_SUFFIXES = {
|
|
88
|
+
initial: "before",
|
|
89
|
+
after_action: "after",
|
|
90
|
+
after_continue: "after-continue",
|
|
91
|
+
after_restart: "after-restart",
|
|
92
|
+
revisit: "revisit"
|
|
93
|
+
};
|
|
43
94
|
function assessBasicGameplayEvidence(evidence, options = {}) {
|
|
44
95
|
const run = extractBasicGameplayEvidence(evidence);
|
|
45
96
|
if (!run) {
|
|
@@ -55,12 +106,16 @@ function assessBasicGameplayEvidence(evidence, options = {}) {
|
|
|
55
106
|
route_results: []
|
|
56
107
|
};
|
|
57
108
|
}
|
|
58
|
-
const routeResults = (run.results || []).map((route) =>
|
|
109
|
+
const routeResults = (run.results || []).map((route) => augmentRouteAssessmentWithProgressionChecks(
|
|
110
|
+
assessBasicGameplayRoute(route, options),
|
|
111
|
+
route
|
|
112
|
+
));
|
|
59
113
|
const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
|
|
60
114
|
name: result.name,
|
|
61
115
|
path: result.path,
|
|
62
116
|
failures: result.failures,
|
|
63
|
-
warnings: result.warnings
|
|
117
|
+
warnings: result.warnings,
|
|
118
|
+
suite_failures: result.suite_failures
|
|
64
119
|
}));
|
|
65
120
|
return {
|
|
66
121
|
version: RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
|
|
@@ -141,6 +196,255 @@ function assessBasicGameplayRoute(route, options = {}) {
|
|
|
141
196
|
}
|
|
142
197
|
};
|
|
143
198
|
}
|
|
199
|
+
function sanitizeBasicGameplayJsonString(value) {
|
|
200
|
+
const text = String(value ?? "");
|
|
201
|
+
let output = "";
|
|
202
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
203
|
+
const code = text.charCodeAt(index);
|
|
204
|
+
if (code >= 55296 && code <= 56319) {
|
|
205
|
+
const next = text.charCodeAt(index + 1);
|
|
206
|
+
if (next >= 56320 && next <= 57343) {
|
|
207
|
+
output += text[index] + text[index + 1];
|
|
208
|
+
index += 1;
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (code >= 56320 && code <= 57343) continue;
|
|
213
|
+
output += text[index];
|
|
214
|
+
}
|
|
215
|
+
return output;
|
|
216
|
+
}
|
|
217
|
+
function compactBasicGameplayText(value, max = 160) {
|
|
218
|
+
const compacted = sanitizeBasicGameplayJsonString(value).replace(/\s+/g, " ").trim();
|
|
219
|
+
return Array.from(compacted).slice(0, max).join("");
|
|
220
|
+
}
|
|
221
|
+
function assessBasicGameplayProgressionCheck(check) {
|
|
222
|
+
const before = metricValue(check.before);
|
|
223
|
+
const after = metricValue(check.after);
|
|
224
|
+
const type = String(check.type || "");
|
|
225
|
+
const hasExplicitResult = typeof check.ok === "boolean";
|
|
226
|
+
let ok = hasExplicitResult ? check.ok === true : true;
|
|
227
|
+
let reason = check.reason ?? null;
|
|
228
|
+
if (type === "selector_count_increases") {
|
|
229
|
+
ok = numberValue(after?.count) > numberValue(before?.count);
|
|
230
|
+
reason = ok ? null : "selector_count_did_not_increase";
|
|
231
|
+
} else if (type === "selector_count_at_least") {
|
|
232
|
+
if (numericValue(check.min) === null && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
|
|
233
|
+
ok = numberValue(after?.count) >= numberValue(check.min);
|
|
234
|
+
reason = ok ? null : "selector_count_below_min";
|
|
235
|
+
} else if (type === "selector_absent") {
|
|
236
|
+
ok = !after?.present || numberValue(after?.count) === 0;
|
|
237
|
+
reason = ok ? null : "selector_still_present";
|
|
238
|
+
} else if (type === "selector_text_matches") {
|
|
239
|
+
if (!check.pattern && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
|
|
240
|
+
ok = textMatches(after?.text, check.pattern, check.flags);
|
|
241
|
+
reason = ok ? null : "selector_text_did_not_match";
|
|
242
|
+
} else if (type === "number_increases") {
|
|
243
|
+
ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) > numberValue(before?.number);
|
|
244
|
+
reason = ok ? null : "number_did_not_increase";
|
|
245
|
+
} else if (type === "number_decreases") {
|
|
246
|
+
ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) < numberValue(before?.number);
|
|
247
|
+
reason = ok ? null : "number_did_not_decrease";
|
|
248
|
+
} else if (type === "number_unchanged" || type === "number_stays_equal") {
|
|
249
|
+
ok = numericValue(before?.number) !== null && numericValue(after?.number) !== null && numberValue(after?.number) === numberValue(before?.number);
|
|
250
|
+
reason = ok ? null : "number_changed";
|
|
251
|
+
} else if (type === "number_equals") {
|
|
252
|
+
const expected = numericValue(check.expected ?? check.value);
|
|
253
|
+
if (expected === null && hasExplicitResult) return resolveBasicGameplayProgressionCheckWithArtifactScreenshots({ ...check, ok, reason });
|
|
254
|
+
ok = numericValue(after?.number) !== null && expected !== null && numberValue(after?.number) === expected;
|
|
255
|
+
reason = ok ? null : "number_did_not_equal_expected";
|
|
256
|
+
} else if (type === "canvas_hash_changes") {
|
|
257
|
+
ok = hashChanged(before, after, ["first_canvas_hash", "first_canvas_visual_sample_hash", "visual_sample_hash"]);
|
|
258
|
+
reason = ok ? null : "canvas_or_visual_sample_hash_did_not_change";
|
|
259
|
+
} else if (type === "screenshot_hash_changes") {
|
|
260
|
+
ok = hashChanged(before, after, ["screenshot_hash", "visual_sample_hash", "artifact_screenshot_hash"]);
|
|
261
|
+
reason = ok ? null : "screenshot_hash_did_not_change";
|
|
262
|
+
} else if (type === "visual_hash_changes") {
|
|
263
|
+
ok = hashChanged(before, after, ["visual_sample_hash", "screenshot_hash", "artifact_screenshot_hash"]);
|
|
264
|
+
reason = ok ? null : "visual_hash_did_not_change";
|
|
265
|
+
} else if (type === "state_changes") {
|
|
266
|
+
ok = hashChanged(before, after, [
|
|
267
|
+
"dom_signature_hash",
|
|
268
|
+
"body_text_hash",
|
|
269
|
+
"screenshot_hash",
|
|
270
|
+
"visual_sample_hash",
|
|
271
|
+
"artifact_screenshot_hash",
|
|
272
|
+
"first_canvas_hash",
|
|
273
|
+
"first_canvas_visual_sample_hash"
|
|
274
|
+
]);
|
|
275
|
+
reason = ok ? null : "state_hash_did_not_change";
|
|
276
|
+
}
|
|
277
|
+
const assessed = {
|
|
278
|
+
...check,
|
|
279
|
+
ok,
|
|
280
|
+
reason
|
|
281
|
+
};
|
|
282
|
+
return resolveBasicGameplayProgressionCheckWithArtifactScreenshots(assessed);
|
|
283
|
+
}
|
|
284
|
+
function assessBasicGameplayProgressionChecks(route) {
|
|
285
|
+
return progressionChecksForRoute(route).map((check) => assessBasicGameplayProgressionCheck(check));
|
|
286
|
+
}
|
|
287
|
+
function augmentBasicGameplayAssessmentWithProgressionChecks(assessment, evidence) {
|
|
288
|
+
const run = extractBasicGameplayEvidence(evidence);
|
|
289
|
+
if (!run?.results?.length) return assessment;
|
|
290
|
+
const routeResults = assessment.route_results.map(
|
|
291
|
+
(result, index) => augmentRouteAssessmentWithProgressionChecks(result, run.results?.[index] || {})
|
|
292
|
+
);
|
|
293
|
+
const failingRoutes = routeResults.filter((result) => !result.ok).map((result) => ({
|
|
294
|
+
name: result.name,
|
|
295
|
+
path: result.path,
|
|
296
|
+
failures: result.failures,
|
|
297
|
+
warnings: result.warnings,
|
|
298
|
+
suite_failures: result.suite_failures
|
|
299
|
+
}));
|
|
300
|
+
return {
|
|
301
|
+
...assessment,
|
|
302
|
+
passed: failingRoutes.length === 0,
|
|
303
|
+
passing_routes: routeResults.filter((result) => result.ok).length,
|
|
304
|
+
failing_routes: failingRoutes,
|
|
305
|
+
failure_counts: countCodes(routeResults.flatMap((result) => result.failures)),
|
|
306
|
+
warning_counts: countCodes(routeResults.flatMap((result) => result.warnings)),
|
|
307
|
+
route_results: routeResults
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function resolveBasicGameplayProgressionCheckWithArtifactScreenshots(check) {
|
|
311
|
+
if (check.ok !== false) return check;
|
|
312
|
+
if (!ARTIFACT_VISUAL_CHANGE_CHECKS.has(String(check.type || ""))) return check;
|
|
313
|
+
const beforeHash = stringValue(metricValue(check.before)?.artifact_screenshot_hash);
|
|
314
|
+
const afterHash = stringValue(metricValue(check.after)?.artifact_screenshot_hash);
|
|
315
|
+
if (!beforeHash || !afterHash || beforeHash === afterHash) return check;
|
|
316
|
+
return {
|
|
317
|
+
...check,
|
|
318
|
+
ok: true,
|
|
319
|
+
reason: null,
|
|
320
|
+
artifact_resolution: {
|
|
321
|
+
source: "riddle_screenshot_artifacts",
|
|
322
|
+
before_hash: beforeHash,
|
|
323
|
+
after_hash: afterHash
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function attachBasicGameplayArtifactScreenshotHashes(evidence, routesOrOptions = {}, maybeArtifacts = []) {
|
|
328
|
+
const routes = Array.isArray(routesOrOptions) ? routesOrOptions : routesOrOptions.routes || [];
|
|
329
|
+
const artifacts = Array.isArray(routesOrOptions) ? maybeArtifacts : routesOrOptions.artifacts || [];
|
|
330
|
+
if (!evidence?.results?.length || !artifacts.length) return evidence;
|
|
331
|
+
const artifactsByName = screenshotArtifactIndex(artifacts);
|
|
332
|
+
if (!artifactsByName.size) return evidence;
|
|
333
|
+
for (const [index, routeEvidence] of evidence.results.entries()) {
|
|
334
|
+
const routeContract = routes[index] || {};
|
|
335
|
+
for (const phase of Object.keys(PHASE_SCREENSHOT_SUFFIXES)) {
|
|
336
|
+
attachPhaseArtifactHash(routeEvidence, routeContract, phase, artifactsByName);
|
|
337
|
+
}
|
|
338
|
+
const checks = progressionChecksForRoute(routeEvidence);
|
|
339
|
+
const resolvedChecks = checks.map((check) => {
|
|
340
|
+
const fromPhase = check.from_phase || "initial";
|
|
341
|
+
const toPhase = check.to_phase || "after_action";
|
|
342
|
+
if (check.before) attachPhaseArtifactHash({ ...routeEvidence, [fromPhase]: check.before }, routeContract, fromPhase, artifactsByName);
|
|
343
|
+
if (check.after) attachPhaseArtifactHash({ ...routeEvidence, [toPhase]: check.after }, routeContract, toPhase, artifactsByName);
|
|
344
|
+
return resolveBasicGameplayProgressionCheckWithArtifactScreenshots(check);
|
|
345
|
+
});
|
|
346
|
+
if (routeEvidence.progression_checks) routeEvidence.progression_checks = resolvedChecks;
|
|
347
|
+
else if (routeEvidence.progressionChecks) routeEvidence.progressionChecks = resolvedChecks;
|
|
348
|
+
routeEvidence.progression_failure_count = resolvedChecks.filter((check) => check.ok === false).length;
|
|
349
|
+
}
|
|
350
|
+
return evidence;
|
|
351
|
+
}
|
|
352
|
+
function createBasicGameplayCatchRecords(assessment, evidence) {
|
|
353
|
+
const run = extractBasicGameplayEvidence(evidence);
|
|
354
|
+
if (!run?.results?.length) return [];
|
|
355
|
+
const catches = [];
|
|
356
|
+
for (const route of run.results) {
|
|
357
|
+
if (numberValue(route.page_error_count) > 0) {
|
|
358
|
+
catches.push({
|
|
359
|
+
site: run.site || null,
|
|
360
|
+
route: route.name,
|
|
361
|
+
path: route.path,
|
|
362
|
+
code: "fatal_page_error",
|
|
363
|
+
label: "page runtime error",
|
|
364
|
+
type: "page_error",
|
|
365
|
+
selector: null,
|
|
366
|
+
reason: stringValue(route.first_page_error) || "page_error",
|
|
367
|
+
page_errors: listValue(route.page_errors),
|
|
368
|
+
summary: `${route.name || route.path || "Route"}: page runtime error (${stringValue(route.first_page_error) || "page_error"})`
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (numberValue(route.console_error_count) > 0) {
|
|
372
|
+
catches.push({
|
|
373
|
+
site: run.site || null,
|
|
374
|
+
route: route.name,
|
|
375
|
+
path: route.path,
|
|
376
|
+
code: "critical_console_error",
|
|
377
|
+
label: "console error",
|
|
378
|
+
type: "console_error",
|
|
379
|
+
selector: null,
|
|
380
|
+
reason: stringValue(route.first_console_error) || "console_error",
|
|
381
|
+
console_errors: listValue(route.console_errors),
|
|
382
|
+
summary: `${route.name || route.path || "Route"}: console error (${stringValue(route.first_console_error) || "console_error"})`
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
for (const [group, phase] of [
|
|
386
|
+
["action_results", "after_action"],
|
|
387
|
+
["continued_action_results", "after_continue"],
|
|
388
|
+
["restart_action_results", "after_restart"]
|
|
389
|
+
]) {
|
|
390
|
+
for (const actionResult of listValue(route[group])) {
|
|
391
|
+
if (!actionResult || actionResult.ok !== false) continue;
|
|
392
|
+
const action = stringValue(actionResult.action) || group;
|
|
393
|
+
const reason = stringValue(actionResult.reason) || "action_failed";
|
|
394
|
+
catches.push({
|
|
395
|
+
site: run.site || null,
|
|
396
|
+
route: route.name,
|
|
397
|
+
path: route.path,
|
|
398
|
+
code: "action_failed",
|
|
399
|
+
label: action,
|
|
400
|
+
type: action,
|
|
401
|
+
selector: stringValue(actionResult.selector),
|
|
402
|
+
reason,
|
|
403
|
+
phase,
|
|
404
|
+
action_result: actionResult,
|
|
405
|
+
summary: `${route.name || route.path || "Route"}: ${action} failed (${reason})`
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const check of assessBasicGameplayProgressionChecks(route).filter((item) => item.ok === false)) {
|
|
410
|
+
catches.push({
|
|
411
|
+
site: run.site || null,
|
|
412
|
+
route: route.name,
|
|
413
|
+
path: route.path,
|
|
414
|
+
code: "progression_assertion_failed",
|
|
415
|
+
label: check.label,
|
|
416
|
+
type: String(check.type || ""),
|
|
417
|
+
selector: check.selector || null,
|
|
418
|
+
state_path: check.state_path || null,
|
|
419
|
+
state_call: check.state_call || null,
|
|
420
|
+
property_path: check.property_path || null,
|
|
421
|
+
reason: check.reason || "progression_assertion_failed",
|
|
422
|
+
from_phase: check.from_phase,
|
|
423
|
+
to_phase: check.to_phase,
|
|
424
|
+
before: compactCatchMetric(check.before),
|
|
425
|
+
after: compactCatchMetric(check.after),
|
|
426
|
+
summary: `${route.name || route.path || "Route"}: ${check.label || check.type || "progression check"} failed (${check.reason || "progression_assertion_failed"})`
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!catches.length && assessment.failing_routes.length) {
|
|
431
|
+
for (const route of assessment.failing_routes) {
|
|
432
|
+
for (const code of route.failures || []) {
|
|
433
|
+
catches.push({
|
|
434
|
+
site: run.site || null,
|
|
435
|
+
route: route.name,
|
|
436
|
+
path: route.path,
|
|
437
|
+
code,
|
|
438
|
+
label: code,
|
|
439
|
+
type: code,
|
|
440
|
+
reason: code,
|
|
441
|
+
summary: `${route.name || route.path || "Route"}: ${code}`
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return catches;
|
|
447
|
+
}
|
|
144
448
|
function createBasicGameplayCatchSummary(input, options = {}) {
|
|
145
449
|
const before = summarizeAssessment(assessBasicGameplayEvidence(input.before, options));
|
|
146
450
|
const after = input.after === void 0 ? void 0 : summarizeAssessment(assessBasicGameplayEvidence(input.after, options));
|
|
@@ -256,6 +560,122 @@ function changed(before, after) {
|
|
|
256
560
|
function canvasHashes(canvases) {
|
|
257
561
|
return (canvases || []).map((canvas) => canvas.hash).filter(Boolean).join("|");
|
|
258
562
|
}
|
|
563
|
+
function augmentRouteAssessmentWithProgressionChecks(result, route) {
|
|
564
|
+
const progressionFailures = assessBasicGameplayProgressionChecks(route).filter((check) => check.ok === false);
|
|
565
|
+
if (!progressionFailures.length) return result;
|
|
566
|
+
const failures = result.failures.includes("progression_assertion_failed") ? result.failures : [...result.failures, "progression_assertion_failed"];
|
|
567
|
+
return {
|
|
568
|
+
...result,
|
|
569
|
+
ok: false,
|
|
570
|
+
failures,
|
|
571
|
+
suite_failures: [
|
|
572
|
+
...result.suite_failures || [],
|
|
573
|
+
...progressionFailures.map((check) => ({
|
|
574
|
+
code: "progression_assertion_failed",
|
|
575
|
+
label: check.label,
|
|
576
|
+
reason: check.reason,
|
|
577
|
+
selector: check.selector || null,
|
|
578
|
+
state_path: check.state_path || null,
|
|
579
|
+
state_call: check.state_call || null,
|
|
580
|
+
property_path: check.property_path || null
|
|
581
|
+
}))
|
|
582
|
+
]
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function progressionChecksForRoute(route) {
|
|
586
|
+
return listValue(route.progression_checks || route.progressionChecks).filter((item) => Boolean(recordValue(item)));
|
|
587
|
+
}
|
|
588
|
+
function metricValue(value) {
|
|
589
|
+
return recordValue(value);
|
|
590
|
+
}
|
|
591
|
+
function textMatches(text, pattern, flags) {
|
|
592
|
+
if (!pattern) return Boolean(text);
|
|
593
|
+
try {
|
|
594
|
+
return new RegExp(String(pattern), typeof flags === "string" ? flags : "i").test(String(text ?? ""));
|
|
595
|
+
} catch {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function hashChanged(before, after, keys) {
|
|
600
|
+
if (!before || !after) return false;
|
|
601
|
+
return keys.some((key) => {
|
|
602
|
+
const beforeValue = stringValue(before[key]);
|
|
603
|
+
const afterValue = stringValue(after[key]);
|
|
604
|
+
return Boolean(beforeValue && afterValue && beforeValue !== afterValue);
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function screenshotArtifactIndex(artifacts) {
|
|
608
|
+
const index = /* @__PURE__ */ new Map();
|
|
609
|
+
for (const artifact of artifacts || []) {
|
|
610
|
+
const hash = stringValue(artifact.sha256 || artifact.hash);
|
|
611
|
+
if (!hash) continue;
|
|
612
|
+
if (String(artifact.kind || "").toLowerCase() !== "screenshot" && !/\.png($|\?)/i.test(`${artifact.name || ""} ${artifact.path || ""} ${artifact.url || ""}`)) continue;
|
|
613
|
+
const filename = artifactBasename(artifact.path || artifact.name || artifact.url);
|
|
614
|
+
if (!filename) continue;
|
|
615
|
+
index.set(filename.toLowerCase(), {
|
|
616
|
+
...artifact,
|
|
617
|
+
sha256: hash
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
return index;
|
|
621
|
+
}
|
|
622
|
+
function attachPhaseArtifactHash(routeEvidence, routeContract, phase, artifactsByName) {
|
|
623
|
+
const suffix = PHASE_SCREENSHOT_SUFFIXES[phase];
|
|
624
|
+
if (!suffix) return null;
|
|
625
|
+
const metric = metricValue(routeEvidence[phase]);
|
|
626
|
+
if (!metric) return null;
|
|
627
|
+
const filename = `${routeArtifactSlug(routeEvidence, routeContract)}-${suffix}.png`.toLowerCase();
|
|
628
|
+
const artifact = artifactsByName.get(filename);
|
|
629
|
+
if (!artifact?.sha256) return null;
|
|
630
|
+
metric.artifact_screenshot_hash = artifact.sha256;
|
|
631
|
+
metric.artifact_screenshot = {
|
|
632
|
+
name: artifact.name || artifactBasename(artifact.path || filename),
|
|
633
|
+
path: artifact.path,
|
|
634
|
+
url: artifact.url,
|
|
635
|
+
kind: artifact.kind,
|
|
636
|
+
sha256: artifact.sha256
|
|
637
|
+
};
|
|
638
|
+
return artifact.sha256;
|
|
639
|
+
}
|
|
640
|
+
function routeArtifactSlug(routeEvidence, routeContract) {
|
|
641
|
+
return slug(routeContract.name || routeEvidence.name || routeContract.path || routeEvidence.path || "route");
|
|
642
|
+
}
|
|
643
|
+
function artifactBasename(value) {
|
|
644
|
+
const raw = stringValue(value);
|
|
645
|
+
if (!raw) return "";
|
|
646
|
+
try {
|
|
647
|
+
const url = new URL(raw);
|
|
648
|
+
return url.pathname.split("/").filter(Boolean).pop() || "";
|
|
649
|
+
} catch {
|
|
650
|
+
return raw.split(/[\\/]/).filter(Boolean).pop() || raw;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function compactCatchMetric(metric) {
|
|
654
|
+
const value = metricValue(metric);
|
|
655
|
+
if (!value) return null;
|
|
656
|
+
return {
|
|
657
|
+
phase: value.phase,
|
|
658
|
+
text: value.text,
|
|
659
|
+
number: value.number,
|
|
660
|
+
count: value.count,
|
|
661
|
+
present: value.present,
|
|
662
|
+
state_path: value.state_path,
|
|
663
|
+
state_call: value.state_call,
|
|
664
|
+
property_path: value.property_path,
|
|
665
|
+
screenshot_hash: value.screenshot_hash,
|
|
666
|
+
visual_sample_hash: value.visual_sample_hash,
|
|
667
|
+
viewport_screenshot_hash: value.viewport_screenshot_hash,
|
|
668
|
+
artifact_screenshot_hash: value.artifact_screenshot_hash,
|
|
669
|
+
dom_signature_hash: value.dom_signature_hash,
|
|
670
|
+
body_text_hash: value.body_text_hash,
|
|
671
|
+
first_canvas_hash: value.first_canvas_hash,
|
|
672
|
+
first_canvas_visual_sample_hash: value.first_canvas_visual_sample_hash,
|
|
673
|
+
visible_canvas_count: value.visible_canvas_count
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function slug(value) {
|
|
677
|
+
return String(value || "artifact").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96) || "artifact";
|
|
678
|
+
}
|
|
259
679
|
function countCodes(codes) {
|
|
260
680
|
const counts = {};
|
|
261
681
|
for (const code of codes) counts[code] = (counts[code] || 0) + 1;
|
|
@@ -279,6 +699,9 @@ function numericValue(value) {
|
|
|
279
699
|
}
|
|
280
700
|
return null;
|
|
281
701
|
}
|
|
702
|
+
function stringValue(value) {
|
|
703
|
+
return typeof value === "string" && value.length ? value : null;
|
|
704
|
+
}
|
|
282
705
|
function recordValue(value) {
|
|
283
706
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
284
707
|
}
|
|
@@ -296,11 +719,21 @@ function parseJson(value) {
|
|
|
296
719
|
}
|
|
297
720
|
// Annotate the CommonJS export names for ESM import in node:
|
|
298
721
|
0 && (module.exports = {
|
|
722
|
+
BASIC_GAMEPLAY_ACTION_TYPES,
|
|
723
|
+
BASIC_GAMEPLAY_PROGRESS_CHECK_TYPES,
|
|
299
724
|
RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
|
|
300
725
|
RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
|
|
301
726
|
RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
|
|
302
727
|
assessBasicGameplayEvidence,
|
|
728
|
+
assessBasicGameplayProgressionCheck,
|
|
729
|
+
assessBasicGameplayProgressionChecks,
|
|
303
730
|
assessBasicGameplayRoute,
|
|
731
|
+
attachBasicGameplayArtifactScreenshotHashes,
|
|
732
|
+
augmentBasicGameplayAssessmentWithProgressionChecks,
|
|
733
|
+
compactBasicGameplayText,
|
|
734
|
+
createBasicGameplayCatchRecords,
|
|
304
735
|
createBasicGameplayCatchSummary,
|
|
305
|
-
extractBasicGameplayEvidence
|
|
736
|
+
extractBasicGameplayEvidence,
|
|
737
|
+
resolveBasicGameplayProgressionCheckWithArtifactScreenshots,
|
|
738
|
+
sanitizeBasicGameplayJsonString
|
|
306
739
|
});
|