@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
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
// src/profile.ts
|
|
2
|
+
var RIDDLE_PROOF_PROFILE_VERSION = "riddle-proof.profile.v1";
|
|
3
|
+
var RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION = "riddle-proof.profile-evidence.v1";
|
|
4
|
+
var RIDDLE_PROOF_PROFILE_RESULT_VERSION = "riddle-proof.profile-result.v1";
|
|
5
|
+
var RIDDLE_PROOF_PROFILE_STATUSES = [
|
|
6
|
+
"passed",
|
|
7
|
+
"product_regression",
|
|
8
|
+
"proof_insufficient",
|
|
9
|
+
"environment_blocked",
|
|
10
|
+
"configuration_error",
|
|
11
|
+
"needs_human_review"
|
|
12
|
+
];
|
|
13
|
+
var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
14
|
+
"route_loaded",
|
|
15
|
+
"selector_visible",
|
|
16
|
+
"selector_count_at_least",
|
|
17
|
+
"text_visible",
|
|
18
|
+
"text_absent",
|
|
19
|
+
"no_horizontal_overflow",
|
|
20
|
+
"no_mobile_horizontal_overflow",
|
|
21
|
+
"no_fatal_console_errors"
|
|
22
|
+
];
|
|
23
|
+
var DEFAULT_VIEWPORTS = [
|
|
24
|
+
{ name: "desktop", width: 1280, height: 800 }
|
|
25
|
+
];
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function stringValue(value) {
|
|
30
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
31
|
+
}
|
|
32
|
+
function numberValue(value) {
|
|
33
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
34
|
+
}
|
|
35
|
+
function jsonRecord(value) {
|
|
36
|
+
if (!isRecord(value)) return void 0;
|
|
37
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
38
|
+
}
|
|
39
|
+
function toJsonValue(value) {
|
|
40
|
+
if (value === null || value === void 0) return null;
|
|
41
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
42
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
43
|
+
if (Array.isArray(value)) return value.map(toJsonValue);
|
|
44
|
+
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
47
|
+
function normalizeName(value, fallback) {
|
|
48
|
+
const name = stringValue(value) || fallback;
|
|
49
|
+
return name.replace(/\s+/g, " ").trim();
|
|
50
|
+
}
|
|
51
|
+
function slugifyRiddleProofProfileName(value) {
|
|
52
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "profile";
|
|
53
|
+
}
|
|
54
|
+
function normalizeViewport(input, index) {
|
|
55
|
+
if (!isRecord(input)) throw new Error(`target.viewports[${index}] must be an object.`);
|
|
56
|
+
const width = numberValue(input.width);
|
|
57
|
+
const height = numberValue(input.height);
|
|
58
|
+
if (!width || !height || width < 100 || height < 100) {
|
|
59
|
+
throw new Error(`target.viewports[${index}] requires numeric width and height >= 100.`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
name: normalizeName(input.name || input.label, `viewport-${index + 1}`),
|
|
63
|
+
width: Math.round(width),
|
|
64
|
+
height: Math.round(height)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function normalizeViewports(value) {
|
|
68
|
+
if (value === void 0) return [...DEFAULT_VIEWPORTS];
|
|
69
|
+
if (!Array.isArray(value) || value.length === 0) throw new Error("target.viewports must be a non-empty array.");
|
|
70
|
+
return value.map(normalizeViewport);
|
|
71
|
+
}
|
|
72
|
+
function isSupportedCheckType(value) {
|
|
73
|
+
return RIDDLE_PROOF_PROFILE_CHECK_TYPES.includes(value);
|
|
74
|
+
}
|
|
75
|
+
function normalizeCheck(input, index) {
|
|
76
|
+
if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
77
|
+
const type = stringValue(input.type);
|
|
78
|
+
if (!type) throw new Error(`checks[${index}].type is required.`);
|
|
79
|
+
if (!isSupportedCheckType(type)) {
|
|
80
|
+
throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue(input.selector)) {
|
|
83
|
+
throw new Error(`checks[${index}] ${type} requires selector.`);
|
|
84
|
+
}
|
|
85
|
+
if ((type === "text_visible" || type === "text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
|
|
86
|
+
throw new Error(`checks[${index}] ${type} requires text or pattern.`);
|
|
87
|
+
}
|
|
88
|
+
if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
|
|
89
|
+
throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
type,
|
|
93
|
+
label: stringValue(input.label),
|
|
94
|
+
expected_path: stringValue(input.expected_path),
|
|
95
|
+
selector: stringValue(input.selector),
|
|
96
|
+
text: stringValue(input.text),
|
|
97
|
+
pattern: stringValue(input.pattern),
|
|
98
|
+
flags: stringValue(input.flags),
|
|
99
|
+
min_count: numberValue(input.min_count),
|
|
100
|
+
max_overflow_px: numberValue(input.max_overflow_px)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function normalizeFailurePolicy(input) {
|
|
104
|
+
const defaults = {
|
|
105
|
+
product_regression: "fail",
|
|
106
|
+
proof_insufficient: "fail",
|
|
107
|
+
environment_blocked: "neutral",
|
|
108
|
+
configuration_error: "fail",
|
|
109
|
+
needs_human_review: "fail"
|
|
110
|
+
};
|
|
111
|
+
if (!isRecord(input)) return defaults;
|
|
112
|
+
const next = { ...defaults };
|
|
113
|
+
for (const [key, value] of Object.entries(input)) {
|
|
114
|
+
if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(key)) continue;
|
|
115
|
+
if (value === "fail" || value === "neutral" || value === "review") {
|
|
116
|
+
next[key] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return next;
|
|
120
|
+
}
|
|
121
|
+
function normalizeRiddleProofProfile(input, options = {}) {
|
|
122
|
+
if (!isRecord(input)) throw new Error("profile must be a JSON object.");
|
|
123
|
+
const version = stringValue(input.version) || RIDDLE_PROOF_PROFILE_VERSION;
|
|
124
|
+
if (version !== RIDDLE_PROOF_PROFILE_VERSION) {
|
|
125
|
+
throw new Error(`Unsupported profile version ${version}. Expected ${RIDDLE_PROOF_PROFILE_VERSION}.`);
|
|
126
|
+
}
|
|
127
|
+
const targetInput = isRecord(input.target) ? input.target : {};
|
|
128
|
+
const checks = Array.isArray(input.checks) ? input.checks.map(normalizeCheck) : [];
|
|
129
|
+
if (!checks.length) throw new Error("profile.checks must contain at least one check.");
|
|
130
|
+
const targetUrl = stringValue(options.url) || stringValue(targetInput.url);
|
|
131
|
+
const route = stringValue(options.route) || stringValue(targetInput.route);
|
|
132
|
+
if (!targetUrl && !route) throw new Error("profile.target requires url or route, or pass --url.");
|
|
133
|
+
return {
|
|
134
|
+
version: RIDDLE_PROOF_PROFILE_VERSION,
|
|
135
|
+
name: normalizeName(input.name, "riddle-proof-profile"),
|
|
136
|
+
target: {
|
|
137
|
+
url: targetUrl,
|
|
138
|
+
route,
|
|
139
|
+
viewports: options.viewports?.length ? options.viewports : normalizeViewports(targetInput.viewports),
|
|
140
|
+
auth: stringValue(targetInput.auth) || "none",
|
|
141
|
+
wait_for_selector: stringValue(targetInput.wait_for_selector) || stringValue(targetInput.waitForSelector),
|
|
142
|
+
wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs)
|
|
143
|
+
},
|
|
144
|
+
checks,
|
|
145
|
+
artifacts: Array.isArray(input.artifacts) ? input.artifacts.map((item) => String(item)).filter(Boolean) : ["screenshot", "console", "dom_summary", "proof_json"],
|
|
146
|
+
baseline_policy: stringValue(input.baseline_policy) || stringValue(input.baselinePolicy) || "invariant_only",
|
|
147
|
+
failure_policy: normalizeFailurePolicy(input.failure_policy || input.failurePolicy),
|
|
148
|
+
metadata: jsonRecord(input.metadata)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function resolveRiddleProofProfileTargetUrl(profile) {
|
|
152
|
+
const route = profile.target.route || "";
|
|
153
|
+
const targetUrl = profile.target.url || "";
|
|
154
|
+
if (/^https?:\/\//i.test(route)) return route;
|
|
155
|
+
if (targetUrl && route) return new URL(route, targetUrl).href;
|
|
156
|
+
if (targetUrl) return targetUrl;
|
|
157
|
+
throw new Error("profile target URL could not be resolved.");
|
|
158
|
+
}
|
|
159
|
+
function routeForViewport(viewport) {
|
|
160
|
+
return viewport?.route || {
|
|
161
|
+
requested: "",
|
|
162
|
+
observed: "",
|
|
163
|
+
matched: false,
|
|
164
|
+
error: "missing viewport evidence"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function checkLabel(check) {
|
|
168
|
+
return check.label || check.type;
|
|
169
|
+
}
|
|
170
|
+
function selectorKey(check) {
|
|
171
|
+
return check.selector || "";
|
|
172
|
+
}
|
|
173
|
+
function textKey(check) {
|
|
174
|
+
return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
|
|
175
|
+
}
|
|
176
|
+
function matchText(sample, check) {
|
|
177
|
+
if (check.pattern) {
|
|
178
|
+
try {
|
|
179
|
+
return new RegExp(check.pattern, check.flags || "").test(sample);
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return sample.includes(check.text || "");
|
|
185
|
+
}
|
|
186
|
+
function successfulRoute(route) {
|
|
187
|
+
return route.matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
|
|
188
|
+
}
|
|
189
|
+
function assessCheckFromEvidence(check, evidence) {
|
|
190
|
+
const viewports = evidence.viewports || [];
|
|
191
|
+
if (!viewports.length) {
|
|
192
|
+
return {
|
|
193
|
+
type: check.type,
|
|
194
|
+
label: checkLabel(check),
|
|
195
|
+
status: "failed",
|
|
196
|
+
evidence: {},
|
|
197
|
+
message: "No viewport evidence was captured."
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (check.type === "route_loaded") {
|
|
201
|
+
const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
|
|
202
|
+
const failed = viewports.filter((viewport) => !successfulRoute({
|
|
203
|
+
...viewport.route,
|
|
204
|
+
expected_path: expectedPath,
|
|
205
|
+
matched: viewport.route.observed === expectedPath || viewport.route.matched
|
|
206
|
+
}));
|
|
207
|
+
return {
|
|
208
|
+
type: check.type,
|
|
209
|
+
label: checkLabel(check),
|
|
210
|
+
status: failed.length ? "failed" : "passed",
|
|
211
|
+
evidence: {
|
|
212
|
+
expected_path: expectedPath,
|
|
213
|
+
observed_paths: viewports.map((viewport) => viewport.route.observed),
|
|
214
|
+
http_statuses: viewports.map((viewport) => viewport.route.http_status ?? null)
|
|
215
|
+
},
|
|
216
|
+
message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (check.type === "selector_visible") {
|
|
220
|
+
const key = selectorKey(check);
|
|
221
|
+
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
|
|
222
|
+
return {
|
|
223
|
+
type: check.type,
|
|
224
|
+
label: checkLabel(check),
|
|
225
|
+
status: failed.length ? "failed" : "passed",
|
|
226
|
+
evidence: {
|
|
227
|
+
selector: key,
|
|
228
|
+
visible_counts: viewports.map((viewport) => viewport.selectors?.[key]?.visible_count || 0)
|
|
229
|
+
},
|
|
230
|
+
message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (check.type === "selector_count_at_least") {
|
|
234
|
+
const key = selectorKey(check);
|
|
235
|
+
const minCount = check.min_count ?? 1;
|
|
236
|
+
const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) < minCount);
|
|
237
|
+
return {
|
|
238
|
+
type: check.type,
|
|
239
|
+
label: checkLabel(check),
|
|
240
|
+
status: failed.length ? "failed" : "passed",
|
|
241
|
+
evidence: {
|
|
242
|
+
selector: key,
|
|
243
|
+
min_count: minCount,
|
|
244
|
+
counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
|
|
245
|
+
},
|
|
246
|
+
message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
250
|
+
const key = textKey(check);
|
|
251
|
+
const expectedVisible = check.type === "text_visible";
|
|
252
|
+
const matches = viewports.map((viewport) => {
|
|
253
|
+
const fromEvidence = viewport.text_matches?.[key];
|
|
254
|
+
return typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
|
|
255
|
+
});
|
|
256
|
+
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
257
|
+
return {
|
|
258
|
+
type: check.type,
|
|
259
|
+
label: checkLabel(check),
|
|
260
|
+
status: failed ? "failed" : "passed",
|
|
261
|
+
evidence: {
|
|
262
|
+
text: check.text || null,
|
|
263
|
+
pattern: check.pattern || null,
|
|
264
|
+
matches
|
|
265
|
+
},
|
|
266
|
+
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
|
|
270
|
+
const maxOverflow = check.max_overflow_px ?? 4;
|
|
271
|
+
const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
|
|
272
|
+
if (!applicable.length) {
|
|
273
|
+
return {
|
|
274
|
+
type: check.type,
|
|
275
|
+
label: checkLabel(check),
|
|
276
|
+
status: "failed",
|
|
277
|
+
evidence: { max_overflow_px: maxOverflow },
|
|
278
|
+
message: "No applicable viewport evidence was captured for overflow check."
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const failed = applicable.filter((viewport) => (viewport.overflow_px ?? 0) > maxOverflow);
|
|
282
|
+
return {
|
|
283
|
+
type: check.type,
|
|
284
|
+
label: checkLabel(check),
|
|
285
|
+
status: failed.length ? "failed" : "passed",
|
|
286
|
+
evidence: {
|
|
287
|
+
max_overflow_px: maxOverflow,
|
|
288
|
+
overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null),
|
|
289
|
+
viewports: applicable.map((viewport) => viewport.name)
|
|
290
|
+
},
|
|
291
|
+
message: failed.length ? `Horizontal overflow exceeded ${maxOverflow}px in ${failed.length} viewport(s).` : void 0
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (check.type === "no_fatal_console_errors") {
|
|
295
|
+
const fatalCount = (evidence.console?.fatal_count || 0) + (evidence.page_errors?.length || 0);
|
|
296
|
+
return {
|
|
297
|
+
type: check.type,
|
|
298
|
+
label: checkLabel(check),
|
|
299
|
+
status: fatalCount ? "failed" : "passed",
|
|
300
|
+
evidence: {
|
|
301
|
+
console_fatal_count: evidence.console?.fatal_count || 0,
|
|
302
|
+
page_error_count: evidence.page_errors?.length || 0
|
|
303
|
+
},
|
|
304
|
+
message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
type: check.type,
|
|
309
|
+
label: checkLabel(check),
|
|
310
|
+
status: "needs_human_review",
|
|
311
|
+
evidence: {},
|
|
312
|
+
message: "Unsupported check type."
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function profileStatusFromEvidence(evidence, checks) {
|
|
316
|
+
if (!evidence) return "proof_insufficient";
|
|
317
|
+
const viewports = evidence.viewports || [];
|
|
318
|
+
if (!viewports.length || !checks.length) return "proof_insufficient";
|
|
319
|
+
if (viewports.some((viewport) => viewport.navigation_error)) return "environment_blocked";
|
|
320
|
+
if (checks.some((check) => check.status === "needs_human_review")) return "needs_human_review";
|
|
321
|
+
if (checks.some((check) => check.status === "failed")) return "product_regression";
|
|
322
|
+
return "passed";
|
|
323
|
+
}
|
|
324
|
+
function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
|
|
325
|
+
const capturedAt = evidence?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
326
|
+
const checks = evidence ? profile.checks.map((check) => assessCheckFromEvidence(check, evidence)) : [];
|
|
327
|
+
const status = profileStatusFromEvidence(evidence, checks);
|
|
328
|
+
const firstViewport = evidence?.viewports?.[0];
|
|
329
|
+
const screenshots = (evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => Boolean(label));
|
|
330
|
+
return {
|
|
331
|
+
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
332
|
+
profile_name: profile.name,
|
|
333
|
+
runner: options.runner || "riddle",
|
|
334
|
+
status,
|
|
335
|
+
baseline_policy: profile.baseline_policy,
|
|
336
|
+
route: routeForViewport(firstViewport),
|
|
337
|
+
artifacts: {
|
|
338
|
+
screenshots,
|
|
339
|
+
console: "console.json",
|
|
340
|
+
proof_json: "proof.json",
|
|
341
|
+
dom_summary: "dom-summary.json",
|
|
342
|
+
riddle_artifacts: options.artifacts
|
|
343
|
+
},
|
|
344
|
+
checks,
|
|
345
|
+
summary: summarizeRiddleProofProfileResult({ profile_name: profile.name, status, checks, viewports: evidence?.viewports || [] }),
|
|
346
|
+
captured_at: capturedAt,
|
|
347
|
+
evidence,
|
|
348
|
+
riddle: options.riddle
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function summarizeRiddleProofProfileResult(input) {
|
|
352
|
+
const passedChecks = input.checks.filter((check) => check.status === "passed").length;
|
|
353
|
+
const failedChecks = input.checks.filter((check) => check.status === "failed").length;
|
|
354
|
+
const viewportNames = input.viewports.map((viewport) => viewport.name).join(", ");
|
|
355
|
+
if (input.status === "passed") {
|
|
356
|
+
return `${input.profile_name} passed ${passedChecks} check(s) across ${input.viewports.length} viewport(s)${viewportNames ? ` (${viewportNames})` : ""}.`;
|
|
357
|
+
}
|
|
358
|
+
if (input.status === "product_regression") {
|
|
359
|
+
return `${input.profile_name} failed ${failedChecks} product invariant(s) across ${input.viewports.length} viewport(s).`;
|
|
360
|
+
}
|
|
361
|
+
if (input.status === "environment_blocked") {
|
|
362
|
+
return `${input.profile_name} could not collect reliable evidence because navigation or the browser environment was blocked.`;
|
|
363
|
+
}
|
|
364
|
+
if (input.status === "proof_insufficient") {
|
|
365
|
+
return `${input.profile_name} did not produce enough evidence for a profile judgment.`;
|
|
366
|
+
}
|
|
367
|
+
if (input.status === "configuration_error") {
|
|
368
|
+
return `${input.profile_name} has a profile configuration error.`;
|
|
369
|
+
}
|
|
370
|
+
return `${input.profile_name} collected artifacts but needs human review.`;
|
|
371
|
+
}
|
|
372
|
+
function profileStatusExitCode(profile, status) {
|
|
373
|
+
if (status === "passed") return 0;
|
|
374
|
+
return profile.failure_policy[status] === "neutral" ? 0 : 1;
|
|
375
|
+
}
|
|
376
|
+
function createRiddleProofProfileConfigurationError(name, error, runner = "riddle") {
|
|
377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
378
|
+
return {
|
|
379
|
+
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
380
|
+
profile_name: name,
|
|
381
|
+
runner,
|
|
382
|
+
status: "configuration_error",
|
|
383
|
+
baseline_policy: "invariant_only",
|
|
384
|
+
route: { requested: "", observed: "", matched: false, error: message },
|
|
385
|
+
artifacts: { screenshots: [], proof_json: "proof.json" },
|
|
386
|
+
checks: [],
|
|
387
|
+
summary: `${name} has a profile configuration error.`,
|
|
388
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
389
|
+
error: message
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function createRiddleProofProfileEnvironmentBlockedResult(input) {
|
|
393
|
+
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
|
|
394
|
+
return {
|
|
395
|
+
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
396
|
+
profile_name: input.profile.name,
|
|
397
|
+
runner: input.runner || "riddle",
|
|
398
|
+
status: "environment_blocked",
|
|
399
|
+
baseline_policy: input.profile.baseline_policy,
|
|
400
|
+
route: { requested: resolveRiddleProofProfileTargetUrl(input.profile), observed: "", matched: false, error: message },
|
|
401
|
+
artifacts: { screenshots: [], proof_json: "proof.json", riddle_artifacts: input.artifacts },
|
|
402
|
+
checks: [],
|
|
403
|
+
summary: `${input.profile.name} could not collect reliable evidence because the runner was blocked.`,
|
|
404
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
405
|
+
riddle: input.riddle,
|
|
406
|
+
error: message
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function createRiddleProofProfileInsufficientResult(input) {
|
|
410
|
+
const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "No proof.json profile result artifact was found.";
|
|
411
|
+
return {
|
|
412
|
+
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
413
|
+
profile_name: input.profile.name,
|
|
414
|
+
runner: input.runner || "riddle",
|
|
415
|
+
status: "proof_insufficient",
|
|
416
|
+
baseline_policy: input.profile.baseline_policy,
|
|
417
|
+
route: { requested: resolveRiddleProofProfileTargetUrl(input.profile), observed: "", matched: false, error: message },
|
|
418
|
+
artifacts: { screenshots: [], proof_json: "proof.json", riddle_artifacts: input.artifacts },
|
|
419
|
+
checks: [],
|
|
420
|
+
summary: `${input.profile.name} did not produce enough evidence for a profile judgment.`,
|
|
421
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
422
|
+
riddle: input.riddle,
|
|
423
|
+
error: message
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function runtimeScriptAssessmentSource() {
|
|
427
|
+
return String.raw`
|
|
428
|
+
function routeOk(route) {
|
|
429
|
+
return Boolean(route && route.matched && !route.error && (route.http_status == null || route.http_status < 400));
|
|
430
|
+
}
|
|
431
|
+
function textMatches(sample, check) {
|
|
432
|
+
if (check.pattern) {
|
|
433
|
+
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
434
|
+
}
|
|
435
|
+
return String(sample || "").includes(check.text || "");
|
|
436
|
+
}
|
|
437
|
+
function assessProfile(profile, evidence) {
|
|
438
|
+
const checks = [];
|
|
439
|
+
const viewports = evidence.viewports || [];
|
|
440
|
+
for (const check of profile.checks || []) {
|
|
441
|
+
if (check.type === "route_loaded") {
|
|
442
|
+
const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
|
|
443
|
+
const failed = viewports.filter((viewport) => {
|
|
444
|
+
const route = { ...(viewport.route || {}), expected_path: expectedPath };
|
|
445
|
+
route.matched = route.observed === expectedPath || route.matched;
|
|
446
|
+
return !routeOk(route);
|
|
447
|
+
});
|
|
448
|
+
checks.push({
|
|
449
|
+
type: check.type,
|
|
450
|
+
label: check.label || check.type,
|
|
451
|
+
status: failed.length ? "failed" : "passed",
|
|
452
|
+
evidence: {
|
|
453
|
+
expected_path: expectedPath,
|
|
454
|
+
observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
|
|
455
|
+
http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
|
|
456
|
+
},
|
|
457
|
+
message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (check.type === "selector_visible") {
|
|
462
|
+
const selector = check.selector || "";
|
|
463
|
+
const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
|
|
464
|
+
checks.push({
|
|
465
|
+
type: check.type,
|
|
466
|
+
label: check.label || check.type,
|
|
467
|
+
status: failed.length ? "failed" : "passed",
|
|
468
|
+
evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
|
|
469
|
+
message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
|
|
470
|
+
});
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (check.type === "selector_count_at_least") {
|
|
474
|
+
const selector = check.selector || "";
|
|
475
|
+
const minCount = check.min_count == null ? 1 : check.min_count;
|
|
476
|
+
const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
|
|
477
|
+
checks.push({
|
|
478
|
+
type: check.type,
|
|
479
|
+
label: check.label || check.type,
|
|
480
|
+
status: failed.length ? "failed" : "passed",
|
|
481
|
+
evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
|
|
482
|
+
message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
|
|
483
|
+
});
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (check.type === "text_visible" || check.type === "text_absent") {
|
|
487
|
+
const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
488
|
+
const expectedVisible = check.type === "text_visible";
|
|
489
|
+
const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
|
|
490
|
+
const failed = matches.filter((matched) => matched !== expectedVisible).length;
|
|
491
|
+
checks.push({
|
|
492
|
+
type: check.type,
|
|
493
|
+
label: check.label || check.type,
|
|
494
|
+
status: failed ? "failed" : "passed",
|
|
495
|
+
evidence: { text: check.text, pattern: check.pattern, matches },
|
|
496
|
+
message: failed ? "Text assertion failed in " + failed + " viewport(s)." : undefined,
|
|
497
|
+
});
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
|
|
501
|
+
const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
|
|
502
|
+
const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
|
|
503
|
+
if (!applicable.length) {
|
|
504
|
+
checks.push({
|
|
505
|
+
type: check.type,
|
|
506
|
+
label: check.label || check.type,
|
|
507
|
+
status: "failed",
|
|
508
|
+
evidence: { max_overflow_px: maxOverflow },
|
|
509
|
+
message: "No applicable viewport evidence was captured for overflow check.",
|
|
510
|
+
});
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const failed = applicable.filter((viewport) => (viewport.overflow_px || 0) > maxOverflow);
|
|
514
|
+
checks.push({
|
|
515
|
+
type: check.type,
|
|
516
|
+
label: check.label || check.type,
|
|
517
|
+
status: failed.length ? "failed" : "passed",
|
|
518
|
+
evidence: { max_overflow_px: maxOverflow, overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null), viewports: applicable.map((viewport) => viewport.name) },
|
|
519
|
+
message: failed.length ? "Horizontal overflow exceeded " + maxOverflow + "px in " + failed.length + " viewport(s)." : undefined,
|
|
520
|
+
});
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (check.type === "no_fatal_console_errors") {
|
|
524
|
+
const fatalCount = ((evidence.console && evidence.console.fatal_count) || 0) + ((evidence.page_errors || []).length);
|
|
525
|
+
checks.push({
|
|
526
|
+
type: check.type,
|
|
527
|
+
label: check.label || check.type,
|
|
528
|
+
status: fatalCount ? "failed" : "passed",
|
|
529
|
+
evidence: { console_fatal_count: (evidence.console && evidence.console.fatal_count) || 0, page_error_count: (evidence.page_errors || []).length },
|
|
530
|
+
message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
|
|
531
|
+
});
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
checks.push({ type: check.type, label: check.label || check.type, status: "needs_human_review", evidence: {}, message: "Unsupported check type." });
|
|
535
|
+
}
|
|
536
|
+
let status = "passed";
|
|
537
|
+
if (!viewports.length || !checks.length) status = "proof_insufficient";
|
|
538
|
+
else if (viewports.some((viewport) => viewport.navigation_error)) status = "environment_blocked";
|
|
539
|
+
else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
|
|
540
|
+
else if (checks.some((check) => check.status === "failed")) status = "product_regression";
|
|
541
|
+
const screenshotLabels = viewports.map((viewport) => viewport.screenshot_label).filter(Boolean);
|
|
542
|
+
const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
|
|
543
|
+
const passedChecks = checks.filter((check) => check.status === "passed").length;
|
|
544
|
+
const failedChecks = checks.filter((check) => check.status === "failed").length;
|
|
545
|
+
const viewportNames = viewports.map((viewport) => viewport.name).join(", ");
|
|
546
|
+
let summary = profile.name + " collected artifacts but needs human review.";
|
|
547
|
+
if (status === "passed") summary = profile.name + " passed " + passedChecks + " check(s) across " + viewports.length + " viewport(s)" + (viewportNames ? " (" + viewportNames + ")." : ".");
|
|
548
|
+
if (status === "product_regression") summary = profile.name + " failed " + failedChecks + " product invariant(s) across " + viewports.length + " viewport(s).";
|
|
549
|
+
if (status === "environment_blocked") summary = profile.name + " could not collect reliable evidence because navigation or the browser environment was blocked.";
|
|
550
|
+
if (status === "proof_insufficient") summary = profile.name + " did not produce enough evidence for a profile judgment.";
|
|
551
|
+
return {
|
|
552
|
+
version: "riddle-proof.profile-result.v1",
|
|
553
|
+
profile_name: profile.name,
|
|
554
|
+
runner: "riddle",
|
|
555
|
+
status,
|
|
556
|
+
baseline_policy: profile.baseline_policy || "invariant_only",
|
|
557
|
+
route,
|
|
558
|
+
artifacts: { screenshots: screenshotLabels, console: "console.json", proof_json: "proof.json", dom_summary: "dom-summary.json" },
|
|
559
|
+
checks,
|
|
560
|
+
summary,
|
|
561
|
+
captured_at: evidence.captured_at,
|
|
562
|
+
evidence,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
`;
|
|
566
|
+
}
|
|
567
|
+
function buildRiddleProofProfileScript(profile) {
|
|
568
|
+
const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
|
|
569
|
+
const slug = slugifyRiddleProofProfileName(profile.name);
|
|
570
|
+
const serializableProfile = JSON.stringify(profile);
|
|
571
|
+
const serializableTargetUrl = JSON.stringify(targetUrl);
|
|
572
|
+
const serializableSlug = JSON.stringify(slug);
|
|
573
|
+
return String.raw`
|
|
574
|
+
const profile = ${serializableProfile};
|
|
575
|
+
const targetUrl = ${serializableTargetUrl};
|
|
576
|
+
const profileSlug = ${serializableSlug};
|
|
577
|
+
const capturedAt = new Date().toISOString();
|
|
578
|
+
const consoleEvents = [];
|
|
579
|
+
const pageErrors = [];
|
|
580
|
+
page.on("console", (message) => {
|
|
581
|
+
const type = message.type();
|
|
582
|
+
if (type === "error" || type === "warning" || type === "assert") {
|
|
583
|
+
consoleEvents.push({
|
|
584
|
+
type,
|
|
585
|
+
text: message.text().slice(0, 1000),
|
|
586
|
+
location: message.location ? message.location() : undefined,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
page.on("pageerror", (error) => {
|
|
591
|
+
pageErrors.push({ message: String(error && error.message ? error.message : error).slice(0, 1000) });
|
|
592
|
+
});
|
|
593
|
+
function textKey(check) {
|
|
594
|
+
return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
|
|
595
|
+
}
|
|
596
|
+
function textMatches(sample, check) {
|
|
597
|
+
if (check.pattern) {
|
|
598
|
+
try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
|
|
599
|
+
}
|
|
600
|
+
return String(sample || "").includes(check.text || "");
|
|
601
|
+
}
|
|
602
|
+
function expectedPathFor(check) {
|
|
603
|
+
return check.expected_path || new URL(targetUrl).pathname || "/";
|
|
604
|
+
}
|
|
605
|
+
async function selectorStats(selector) {
|
|
606
|
+
return page.locator(selector).evaluateAll((elements) => {
|
|
607
|
+
const isVisible = (element) => {
|
|
608
|
+
const style = window.getComputedStyle(element);
|
|
609
|
+
const rect = element.getBoundingClientRect();
|
|
610
|
+
return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
611
|
+
};
|
|
612
|
+
return { count: elements.length, visible_count: elements.filter(isVisible).length };
|
|
613
|
+
}).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
|
|
614
|
+
}
|
|
615
|
+
async function captureViewport(viewport) {
|
|
616
|
+
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
617
|
+
let httpStatus = null;
|
|
618
|
+
let navigationError;
|
|
619
|
+
let waitError;
|
|
620
|
+
try {
|
|
621
|
+
const response = await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 45000 });
|
|
622
|
+
httpStatus = response ? response.status() : null;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
navigationError = String(error && error.message ? error.message : error).slice(0, 1000);
|
|
625
|
+
}
|
|
626
|
+
if (!navigationError && profile.target.wait_for_selector) {
|
|
627
|
+
try {
|
|
628
|
+
await page.waitForSelector(profile.target.wait_for_selector, { state: "visible", timeout: 15000 });
|
|
629
|
+
} catch (error) {
|
|
630
|
+
waitError = String(error && error.message ? error.message : error).slice(0, 1000);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (!navigationError && profile.target.wait_ms) {
|
|
634
|
+
await page.waitForTimeout(profile.target.wait_ms);
|
|
635
|
+
}
|
|
636
|
+
const dom = await page.evaluate(() => {
|
|
637
|
+
const body = document.body;
|
|
638
|
+
const documentElement = document.documentElement;
|
|
639
|
+
const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
|
|
640
|
+
return {
|
|
641
|
+
url: location.href,
|
|
642
|
+
pathname: location.pathname,
|
|
643
|
+
title: document.title,
|
|
644
|
+
body_text_length: text.length,
|
|
645
|
+
body_text_sample: text.slice(0, 8000),
|
|
646
|
+
scroll_width: documentElement ? documentElement.scrollWidth : 0,
|
|
647
|
+
client_width: documentElement ? documentElement.clientWidth : window.innerWidth,
|
|
648
|
+
};
|
|
649
|
+
}).catch((error) => ({
|
|
650
|
+
url: page.url(),
|
|
651
|
+
pathname: "",
|
|
652
|
+
title: "",
|
|
653
|
+
body_text_length: 0,
|
|
654
|
+
body_text_sample: "",
|
|
655
|
+
scroll_width: 0,
|
|
656
|
+
client_width: viewport.width,
|
|
657
|
+
evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
|
|
658
|
+
}));
|
|
659
|
+
const selectors = {};
|
|
660
|
+
const text_matches = {};
|
|
661
|
+
for (const check of profile.checks || []) {
|
|
662
|
+
if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
|
|
663
|
+
selectors[check.selector] = await selectorStats(check.selector);
|
|
664
|
+
}
|
|
665
|
+
if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
|
|
666
|
+
text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const screenshotLabel = profileSlug + "-" + viewport.name;
|
|
670
|
+
try {
|
|
671
|
+
if (typeof saveScreenshot === "function") await saveScreenshot(screenshotLabel);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
pageErrors.push({ message: "saveScreenshot failed: " + String(error && error.message ? error.message : error).slice(0, 500) });
|
|
674
|
+
}
|
|
675
|
+
const expectedPath = profile.checks.find((check) => check.type === "route_loaded" && check.expected_path)?.expected_path || new URL(targetUrl).pathname || "/";
|
|
676
|
+
return {
|
|
677
|
+
name: viewport.name,
|
|
678
|
+
width: viewport.width,
|
|
679
|
+
height: viewport.height,
|
|
680
|
+
url: dom.url,
|
|
681
|
+
route: {
|
|
682
|
+
requested: targetUrl,
|
|
683
|
+
observed: dom.pathname,
|
|
684
|
+
expected_path: expectedPath,
|
|
685
|
+
matched: dom.pathname === expectedPath,
|
|
686
|
+
http_status: httpStatus,
|
|
687
|
+
error: navigationError || waitError || undefined,
|
|
688
|
+
},
|
|
689
|
+
title: dom.title,
|
|
690
|
+
body_text_length: dom.body_text_length,
|
|
691
|
+
body_text_sample: dom.body_text_sample,
|
|
692
|
+
scroll_width: dom.scroll_width,
|
|
693
|
+
client_width: dom.client_width,
|
|
694
|
+
overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
|
|
695
|
+
selectors,
|
|
696
|
+
text_matches,
|
|
697
|
+
screenshot_label: screenshotLabel,
|
|
698
|
+
navigation_error: navigationError,
|
|
699
|
+
wait_error: waitError,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
${runtimeScriptAssessmentSource()}
|
|
703
|
+
const viewports = [];
|
|
704
|
+
for (const viewport of profile.target.viewports || []) {
|
|
705
|
+
viewports.push(await captureViewport(viewport));
|
|
706
|
+
}
|
|
707
|
+
const evidence = {
|
|
708
|
+
version: "riddle-proof.profile-evidence.v1",
|
|
709
|
+
profile_name: profile.name,
|
|
710
|
+
target_url: targetUrl,
|
|
711
|
+
baseline_policy: profile.baseline_policy || "invariant_only",
|
|
712
|
+
captured_at: capturedAt,
|
|
713
|
+
viewports,
|
|
714
|
+
console: {
|
|
715
|
+
events: consoleEvents,
|
|
716
|
+
fatal_count: consoleEvents.filter((event) => event.type === "error" || event.type === "assert").length,
|
|
717
|
+
},
|
|
718
|
+
page_errors: pageErrors,
|
|
719
|
+
dom_summary: {
|
|
720
|
+
viewport_count: viewports.length,
|
|
721
|
+
routes: viewports.map((viewport) => viewport.route),
|
|
722
|
+
titles: viewports.map((viewport) => viewport.title),
|
|
723
|
+
overflow_px: viewports.map((viewport) => viewport.overflow_px),
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
const result = assessProfile(profile, evidence);
|
|
727
|
+
if (typeof saveJson === "function") {
|
|
728
|
+
await saveJson("proof.json", result);
|
|
729
|
+
await saveJson("console.json", { events: consoleEvents, page_errors: pageErrors });
|
|
730
|
+
await saveJson("dom-summary.json", evidence.dom_summary);
|
|
731
|
+
}
|
|
732
|
+
return result;
|
|
733
|
+
`.trim();
|
|
734
|
+
}
|
|
735
|
+
function collectRiddleProfileArtifactRefs(input) {
|
|
736
|
+
const refs = [];
|
|
737
|
+
const seen = /* @__PURE__ */ new Set();
|
|
738
|
+
function add(item, source) {
|
|
739
|
+
if (!isRecord(item)) return;
|
|
740
|
+
const name = stringValue(item.name) || stringValue(item.filename) || "";
|
|
741
|
+
const url = stringValue(item.url);
|
|
742
|
+
const path = stringValue(item.path);
|
|
743
|
+
if (!name && !url && !path) return;
|
|
744
|
+
const key = `${name}:${url || path || ""}`;
|
|
745
|
+
if (seen.has(key)) return;
|
|
746
|
+
seen.add(key);
|
|
747
|
+
refs.push({
|
|
748
|
+
name,
|
|
749
|
+
url,
|
|
750
|
+
path,
|
|
751
|
+
kind: stringValue(item.kind) || stringValue(item.type),
|
|
752
|
+
content_type: stringValue(item.content_type) || stringValue(item.contentType),
|
|
753
|
+
source
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
function visit(value, source) {
|
|
757
|
+
if (Array.isArray(value)) {
|
|
758
|
+
for (const item of value) add(item, source);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (!isRecord(value)) return;
|
|
762
|
+
for (const key of ["artifacts", "outputs", "screenshots", "files"]) {
|
|
763
|
+
if (Array.isArray(value[key])) visit(value[key], key);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
visit(input, "artifacts");
|
|
767
|
+
return refs;
|
|
768
|
+
}
|
|
769
|
+
function extractRiddleProofProfileResult(input) {
|
|
770
|
+
if (!isRecord(input)) return void 0;
|
|
771
|
+
if (input.version === RIDDLE_PROOF_PROFILE_RESULT_VERSION) return input;
|
|
772
|
+
const candidates = [
|
|
773
|
+
input.result,
|
|
774
|
+
input.return_value,
|
|
775
|
+
input.value,
|
|
776
|
+
input.profile_result,
|
|
777
|
+
isRecord(input["proof.json"]) ? input["proof.json"] : void 0,
|
|
778
|
+
isRecord(input._proof_json) ? input._proof_json : void 0
|
|
779
|
+
];
|
|
780
|
+
for (const candidate of candidates) {
|
|
781
|
+
const result = extractRiddleProofProfileResult(candidate);
|
|
782
|
+
if (result) return result;
|
|
783
|
+
}
|
|
784
|
+
if (isRecord(input._artifact_json)) {
|
|
785
|
+
return extractRiddleProofProfileResult(input._artifact_json["proof.json"]);
|
|
786
|
+
}
|
|
787
|
+
return void 0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export {
|
|
791
|
+
RIDDLE_PROOF_PROFILE_VERSION,
|
|
792
|
+
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|
|
793
|
+
RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
794
|
+
RIDDLE_PROOF_PROFILE_STATUSES,
|
|
795
|
+
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
796
|
+
slugifyRiddleProofProfileName,
|
|
797
|
+
normalizeRiddleProofProfile,
|
|
798
|
+
resolveRiddleProofProfileTargetUrl,
|
|
799
|
+
assessRiddleProofProfileEvidence,
|
|
800
|
+
summarizeRiddleProofProfileResult,
|
|
801
|
+
profileStatusExitCode,
|
|
802
|
+
createRiddleProofProfileConfigurationError,
|
|
803
|
+
createRiddleProofProfileEnvironmentBlockedResult,
|
|
804
|
+
createRiddleProofProfileInsufficientResult,
|
|
805
|
+
buildRiddleProofProfileScript,
|
|
806
|
+
collectRiddleProfileArtifactRefs,
|
|
807
|
+
extractRiddleProofProfileResult
|
|
808
|
+
};
|