@riddledc/riddle-proof 0.8.77 → 0.8.79
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 +65 -3
- package/dist/change-proof.cjs +744 -85
- package/dist/change-proof.d.cts +100 -8
- package/dist/change-proof.d.ts +100 -8
- package/dist/change-proof.js +15 -1
- package/dist/{chunk-B2DP2LET.js → chunk-5IFZSUPF.js} +52 -13
- package/dist/chunk-6VFS2JFR.js +886 -0
- package/dist/{chunk-IY4W6STC.js → chunk-7N6X54WG.js} +116 -17
- package/dist/{chunk-GG2D3MFZ.js → chunk-FCSJZBC5.js} +26 -1
- package/dist/chunk-MEVVL4TI.js +258 -0
- package/dist/{chunk-H25IDX76.js → chunk-NEXWITV4.js} +221 -35
- package/dist/{chunk-UE4I7RTI.js → chunk-RQPCKRKT.js} +1 -1
- package/dist/cli/index.js +7 -6
- package/dist/cli.cjs +2026 -1060
- package/dist/cli.js +7 -6
- package/dist/index.cjs +932 -122
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +42 -14
- package/dist/pr-comment.cjs +416 -17
- package/dist/pr-comment.d.cts +6 -1
- package/dist/pr-comment.d.ts +6 -1
- package/dist/pr-comment.js +6 -1
- package/dist/profile/index.cjs +26 -1
- package/dist/profile/index.js +1 -1
- package/dist/profile-suggestions.js +2 -2
- package/dist/profile.cjs +26 -1
- package/dist/profile.d.cts +7 -0
- package/dist/profile.d.ts +7 -0
- package/dist/profile.js +1 -1
- package/dist/receipts.cjs +286 -0
- package/dist/receipts.d.cts +115 -0
- package/dist/receipts.d.ts +115 -0
- package/dist/receipts.js +15 -0
- package/dist/riddle-client.cjs +98 -13
- package/dist/riddle-client.d.cts +18 -5
- package/dist/riddle-client.d.ts +18 -5
- package/dist/riddle-client.js +4 -1
- package/dist/runtime/index.cjs +98 -13
- package/dist/runtime/index.d.cts +5 -1
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/riddle-client.cjs +98 -13
- package/dist/runtime/riddle-client.d.cts +5 -1
- package/dist/runtime/riddle-client.d.ts +5 -1
- package/dist/runtime/riddle-client.js +4 -1
- package/package.json +7 -2
- package/dist/chunk-BILL3UC2.js +0 -488
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION,
|
|
3
|
+
RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
|
|
4
|
+
RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
|
|
5
|
+
createRiddleProofHandoffReceipt,
|
|
6
|
+
parseRiddleProofChangeReceipt,
|
|
7
|
+
parseRiddleProofHandoffReceipt
|
|
8
|
+
} from "./chunk-6VFS2JFR.js";
|
|
1
9
|
import {
|
|
2
10
|
riddleProofPublicStateAllowsMergeRecommendation,
|
|
3
11
|
riddleProofPublicStateMergeRecommendation,
|
|
@@ -84,9 +92,10 @@ function collectChangeReceiptArtifacts(result) {
|
|
|
84
92
|
const seen = /* @__PURE__ */ new Set();
|
|
85
93
|
const collectSide = (sideName, side) => {
|
|
86
94
|
const candidates = [
|
|
95
|
+
side.canonical_screenshot,
|
|
87
96
|
...asArray(side.screenshots),
|
|
88
97
|
...asArray(side.artifacts)
|
|
89
|
-
];
|
|
98
|
+
].filter(Boolean);
|
|
90
99
|
for (const [index, item] of candidates.entries()) {
|
|
91
100
|
const artifact = asRecord(item);
|
|
92
101
|
const url = stringValue(artifact.url) || stringValue(artifact.path);
|
|
@@ -178,9 +187,10 @@ function changeReceiptPageSummaries(result) {
|
|
|
178
187
|
for (const sideName of ["before", "after"]) {
|
|
179
188
|
const side = asRecord(result[sideName]);
|
|
180
189
|
if (!Object.keys(side).length) continue;
|
|
181
|
-
const
|
|
190
|
+
const profileSummary = asRecord(side.profile_summary);
|
|
191
|
+
const checks = Object.keys(asRecord(side.checks)).length ? asRecord(side.checks) : asRecord(profileSummary.checks);
|
|
182
192
|
pages.push({
|
|
183
|
-
route: firstStringValue(side.source, side.profile_name) || sideName,
|
|
193
|
+
route: firstStringValue(asRecord(side.target).url, side.source, profileSummary.profile_name, side.profile_name) || sideName,
|
|
184
194
|
passed: numberValue(checks.passed) ?? 0,
|
|
185
195
|
failed: numberValue(checks.failed) ?? 0
|
|
186
196
|
});
|
|
@@ -243,7 +253,11 @@ function isProfileResult(result) {
|
|
|
243
253
|
return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
|
|
244
254
|
}
|
|
245
255
|
function isChangeReceiptResult(result) {
|
|
246
|
-
|
|
256
|
+
const version = stringValue(result.version);
|
|
257
|
+
return version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION || version === RIDDLE_PROOF_CHANGE_RECEIPT_VERSION;
|
|
258
|
+
}
|
|
259
|
+
function isHandoffReceiptResult(result) {
|
|
260
|
+
return stringValue(result.version) === RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION;
|
|
247
261
|
}
|
|
248
262
|
function isChangeResult(result) {
|
|
249
263
|
const version = stringValue(result.version);
|
|
@@ -251,10 +265,13 @@ function isChangeResult(result) {
|
|
|
251
265
|
}
|
|
252
266
|
function summarizeRiddleProofPrComment(input) {
|
|
253
267
|
const runResponse = asRecord(input.runResponse);
|
|
254
|
-
const
|
|
268
|
+
const inputResult = asRecord(input.result);
|
|
269
|
+
const handoffReceipt = isHandoffReceiptResult(inputResult) ? parseRiddleProofHandoffReceipt(inputResult) : void 0;
|
|
270
|
+
const parsedChangeReceipt = handoffReceipt?.change || (isChangeReceiptResult(inputResult) ? parseRiddleProofChangeReceipt(inputResult) : void 0);
|
|
271
|
+
const result = parsedChangeReceipt ? asRecord(parsedChangeReceipt) : inputResult;
|
|
255
272
|
const proofResult = asRecord(runResponse.proofResult);
|
|
256
273
|
const profileResult = isProfileResult(result);
|
|
257
|
-
const changeReceiptResult =
|
|
274
|
+
const changeReceiptResult = Boolean(parsedChangeReceipt);
|
|
258
275
|
const changeResult = changeReceiptResult || isChangeResult(result);
|
|
259
276
|
const profileRiddle = asRecord(result.riddle);
|
|
260
277
|
const preview = asRecord(runResponse.preview);
|
|
@@ -275,7 +292,7 @@ function summarizeRiddleProofPrComment(input) {
|
|
|
275
292
|
delete checkSource.ok;
|
|
276
293
|
const nestedChecks = summarizeExplicitChecks(checkSource);
|
|
277
294
|
const resultStatus = firstStringValue(result.status, stopCondition.status);
|
|
278
|
-
const ok = changeResult ? resultStatus === "passed" : booleanValue(result.ok) ?? booleanValue(runResponse.ok) ?? null;
|
|
295
|
+
const ok = changeReceiptResult ? parsedChangeReceipt?.recommendation.merge_recommended ?? false : changeResult ? resultStatus === "passed" : booleanValue(result.ok) ?? booleanValue(runResponse.ok) ?? null;
|
|
279
296
|
const checkpointSummary = checkpointSummaryFrom(
|
|
280
297
|
result.checkpoint_summary,
|
|
281
298
|
stopCondition.checkpoint_summary,
|
|
@@ -283,19 +300,19 @@ function summarizeRiddleProofPrComment(input) {
|
|
|
283
300
|
rawDetails.checkpoint_summary,
|
|
284
301
|
proofResult.checkpoint_summary
|
|
285
302
|
);
|
|
286
|
-
const publicState = summarizeRiddleProofPublicState({
|
|
303
|
+
const publicState = changeReceiptResult ? void 0 : summarizeRiddleProofPublicState({
|
|
287
304
|
...result,
|
|
288
305
|
status: resultStatus,
|
|
289
306
|
checkpoint_summary: checkpointSummary || result.checkpoint_summary
|
|
290
307
|
});
|
|
291
|
-
const mergeRecommendation = riddleProofPublicStateMergeRecommendation(
|
|
308
|
+
const mergeRecommendation = publicState ? riddleProofPublicStateMergeRecommendation(
|
|
292
309
|
publicState,
|
|
293
310
|
firstStringValue(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
|
|
294
|
-
);
|
|
311
|
+
) : void 0;
|
|
295
312
|
return {
|
|
296
313
|
ok,
|
|
297
314
|
status: firstStringValue(proofResult.status, profileRiddle.status),
|
|
298
|
-
result_status: changeResult ? resultStatus : publicState
|
|
315
|
+
result_status: changeResult ? resultStatus : publicState?.status,
|
|
299
316
|
job_id: firstStringValue(proofResult.job_id, profileRiddle.job_id),
|
|
300
317
|
duration_ms: numberValue(proofResult.duration_ms) ?? numberValue(profileRiddle.elapsed_ms),
|
|
301
318
|
proof_url: stringValue(runResponse.proofUrl),
|
|
@@ -303,13 +320,13 @@ function summarizeRiddleProofPrComment(input) {
|
|
|
303
320
|
preview_url: stringValue(preview.preview_url) || stringValue(preview.url),
|
|
304
321
|
preview_publish_recovered: booleanValue(preview.publish_recovered),
|
|
305
322
|
preview_publish_error: stringValue(preview.publish_error),
|
|
306
|
-
ship_held: publicState
|
|
307
|
-
shipping_disabled: publicState
|
|
308
|
-
ship_authorized: publicState
|
|
309
|
-
merge_ready: publicState
|
|
310
|
-
sync_allowed: publicState
|
|
323
|
+
ship_held: publicState?.ship_held,
|
|
324
|
+
shipping_disabled: publicState?.shipping_disabled,
|
|
325
|
+
ship_authorized: changeReceiptResult ? parsedChangeReceipt?.shipping_authorization.authorized : publicState?.ship_authorized,
|
|
326
|
+
merge_ready: publicState?.merge_ready,
|
|
327
|
+
sync_allowed: publicState?.sync_allowed,
|
|
311
328
|
proof_decision: firstStringValue(result.proof_decision, stopCondition.proof_decision, resultRaw.proof_decision),
|
|
312
|
-
merge_recommendation: changeReceiptResult
|
|
329
|
+
merge_recommendation: changeReceiptResult ? parsedChangeReceipt?.recommendation.label : mergeRecommendation,
|
|
313
330
|
checkpoint_summary: checkpointSummary,
|
|
314
331
|
public_state: publicState,
|
|
315
332
|
passed_checks: changeChecks?.passed ?? profileChecks?.passed ?? nestedChecks.passed,
|
|
@@ -377,7 +394,88 @@ function checkpointSummaryLine(summary) {
|
|
|
377
394
|
summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
|
|
378
395
|
].filter(Boolean).join("; ");
|
|
379
396
|
}
|
|
397
|
+
function markdownTableValue(value) {
|
|
398
|
+
return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
399
|
+
}
|
|
400
|
+
function observationArtifactTarget(artifact) {
|
|
401
|
+
return artifact?.url || artifact?.path;
|
|
402
|
+
}
|
|
403
|
+
function canonicalScreenshotCell(artifact, fallback) {
|
|
404
|
+
const target = observationArtifactTarget(artifact);
|
|
405
|
+
if (!artifact || !target) return fallback;
|
|
406
|
+
if (artifact.url) return `![${artifact.name.replace(/\]/g, "\\]")}](${artifact.url})`;
|
|
407
|
+
return markdownTableValue(artifact.path || artifact.name);
|
|
408
|
+
}
|
|
409
|
+
function shortRevision(value) {
|
|
410
|
+
return value ? value.slice(0, 12) : "not recorded";
|
|
411
|
+
}
|
|
412
|
+
function buildRiddleProofHandoffPrCommentMarkdown(handoff, input = {}) {
|
|
413
|
+
const receipt = parseRiddleProofHandoffReceipt(handoff);
|
|
414
|
+
const change = receipt.change;
|
|
415
|
+
const beforeStatus = change.before.profile_summary?.status || "not recorded";
|
|
416
|
+
const afterStatus = change.after.profile_summary?.status || "not recorded";
|
|
417
|
+
const beforeBaseline = change.groups.before.ok ? "matched baseline" : "baseline mismatch";
|
|
418
|
+
const lines = [
|
|
419
|
+
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
420
|
+
`## ${input.title?.trim() || "Riddle Change Proof"}`,
|
|
421
|
+
"",
|
|
422
|
+
`**Result:** ${receipt.recommendation.label}`,
|
|
423
|
+
`**Evidence verdict:** \`${receipt.verdict}\``,
|
|
424
|
+
`**Shipping authorization:** ${receipt.shipping_authorization.status}`,
|
|
425
|
+
`**Contract:** ${change.contract_name}`
|
|
426
|
+
];
|
|
427
|
+
if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
|
|
428
|
+
if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
|
|
429
|
+
lines.push("", change.summary, "", "### Canonical Before / After", "");
|
|
430
|
+
lines.push("| Before | After |");
|
|
431
|
+
lines.push("| --- | --- |");
|
|
432
|
+
lines.push(`| ${beforeBaseline}: \`${markdownTableValue(beforeStatus)}\` | \`${markdownTableValue(afterStatus)}\` |`);
|
|
433
|
+
lines.push(`| ${canonicalScreenshotCell(receipt.canonical_pair.before, "No canonical before screenshot")} | ${canonicalScreenshotCell(receipt.canonical_pair.after, "No canonical after screenshot")} |`);
|
|
434
|
+
lines.push("", "### Source Binding", "");
|
|
435
|
+
lines.push("| Side | Target | Git revision | Binding | Preview | Digest |");
|
|
436
|
+
lines.push("| --- | --- | --- | --- | --- | --- |");
|
|
437
|
+
for (const side of ["before", "after"]) {
|
|
438
|
+
const observation = change[side];
|
|
439
|
+
const binding = change.source_bindings[side];
|
|
440
|
+
lines.push(`| ${side} | ${markdownTableValue(observation.target.url)} | \`${shortRevision(binding.observed_git_revision || observation.source.git_revision)}\` | ${binding.status} | ${markdownTableValue(binding.preview_id || observation.target.preview?.preview_id || "not required")} | \`${markdownTableValue(binding.content_digest || observation.target.preview?.content_digest || "not required")}\` |`);
|
|
441
|
+
}
|
|
442
|
+
lines.push("", "### Declared Delta", "");
|
|
443
|
+
lines.push("| Measurement | Status | Before | After |");
|
|
444
|
+
lines.push("| --- | --- | --- | --- |");
|
|
445
|
+
for (const delta of change.deltas) {
|
|
446
|
+
lines.push(`| ${markdownTableValue(delta.label)} | ${delta.status} | ${markdownTableValue(delta.before_observed)} | ${markdownTableValue(delta.after_observed)} |`);
|
|
447
|
+
}
|
|
448
|
+
if (change.proves.length) {
|
|
449
|
+
lines.push("", "### What This Proves", "");
|
|
450
|
+
for (const claim of change.proves) lines.push(`- ${claim}`);
|
|
451
|
+
}
|
|
452
|
+
if (change.does_not_prove.length) {
|
|
453
|
+
lines.push("", "### Limits", "");
|
|
454
|
+
for (const claim of change.does_not_prove) lines.push(`- ${claim}`);
|
|
455
|
+
}
|
|
456
|
+
const linkedArtifacts = [
|
|
457
|
+
...change.before.artifacts.map((artifact) => ({ side: "before", artifact })),
|
|
458
|
+
...change.after.artifacts.map((artifact) => ({ side: "after", artifact }))
|
|
459
|
+
].filter(({ artifact }) => Boolean(artifact.url));
|
|
460
|
+
if (linkedArtifacts.length) {
|
|
461
|
+
lines.push("", "### Artifacts", "");
|
|
462
|
+
for (const { side, artifact } of linkedArtifacts.slice(0, 24)) {
|
|
463
|
+
lines.push(`- ${side}: ${markdownLink(artifact.name, artifact.url || "")}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
lines.push("", input.source?.trim() ? `_Source: ${input.source.trim()}_` : "_Rendered from the Handoff receipt without recomputing its verdict._");
|
|
467
|
+
return `${lines.join("\n").trim()}
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
380
470
|
function buildRiddleProofPrCommentMarkdown(input) {
|
|
471
|
+
const result = asRecord(input.result);
|
|
472
|
+
if (isHandoffReceiptResult(result)) {
|
|
473
|
+
return buildRiddleProofHandoffPrCommentMarkdown(parseRiddleProofHandoffReceipt(result), input);
|
|
474
|
+
}
|
|
475
|
+
if (isChangeReceiptResult(result)) {
|
|
476
|
+
const handoff = createRiddleProofHandoffReceipt(parseRiddleProofChangeReceipt(result));
|
|
477
|
+
return buildRiddleProofHandoffPrCommentMarkdown(handoff, input);
|
|
478
|
+
}
|
|
381
479
|
const summary = summarizeRiddleProofPrComment(input);
|
|
382
480
|
const title = input.title?.trim() || "Riddle Proof Evidence";
|
|
383
481
|
const lines = [
|
|
@@ -448,5 +546,6 @@ function buildRiddleProofPrCommentMarkdown(input) {
|
|
|
448
546
|
export {
|
|
449
547
|
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
450
548
|
summarizeRiddleProofPrComment,
|
|
549
|
+
buildRiddleProofHandoffPrCommentMarkdown,
|
|
451
550
|
buildRiddleProofPrCommentMarkdown
|
|
452
551
|
};
|
|
@@ -781,6 +781,12 @@ function profileScreenshotLabels(viewports) {
|
|
|
781
781
|
}
|
|
782
782
|
return labels;
|
|
783
783
|
}
|
|
784
|
+
function profileFinalScreenshotLabels(viewports) {
|
|
785
|
+
return (viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
|
|
786
|
+
}
|
|
787
|
+
function profileAllSetupScreenshotLabels(viewports) {
|
|
788
|
+
return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport.setup_action_results || []));
|
|
789
|
+
}
|
|
784
790
|
function profileSetupSummary(viewports, actionCount, expectedActionCountByViewport, finalScreenshotFullPage) {
|
|
785
791
|
const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === void 0 ? void 0 : finalScreenshotFullPage !== false;
|
|
786
792
|
const finalScreenshotCount = viewports.filter((viewport) => typeof viewport.screenshot_label === "string" && viewport.screenshot_label.trim()).length;
|
|
@@ -3631,6 +3637,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
|
|
|
3631
3637
|
const status = profileStatusFromEvidence(profile, evidence, checks);
|
|
3632
3638
|
const firstViewport = evidence?.viewports?.[0];
|
|
3633
3639
|
const screenshots = profileScreenshotLabels(evidence?.viewports);
|
|
3640
|
+
const canonicalScreenshots = profileFinalScreenshotLabels(evidence?.viewports);
|
|
3641
|
+
const setupScreenshots = profileAllSetupScreenshotLabels(evidence?.viewports);
|
|
3634
3642
|
const result = {
|
|
3635
3643
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
3636
3644
|
profile_name: profile.name,
|
|
@@ -3640,6 +3648,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
|
|
|
3640
3648
|
route: routeForViewport(firstViewport, evidence?.target_url),
|
|
3641
3649
|
artifacts: {
|
|
3642
3650
|
screenshots,
|
|
3651
|
+
canonical_screenshots: canonicalScreenshots,
|
|
3652
|
+
setup_screenshots: setupScreenshots,
|
|
3643
3653
|
console: "console.json",
|
|
3644
3654
|
proof_json: "proof.json",
|
|
3645
3655
|
dom_summary: "dom-summary.json",
|
|
@@ -4958,6 +4968,12 @@ function profileScreenshotLabels(viewports) {
|
|
|
4958
4968
|
}
|
|
4959
4969
|
return labels;
|
|
4960
4970
|
}
|
|
4971
|
+
function profileFinalScreenshotLabels(viewports) {
|
|
4972
|
+
return (viewports || []).map((viewport) => viewport && viewport.screenshot_label).filter(Boolean);
|
|
4973
|
+
}
|
|
4974
|
+
function profileAllSetupScreenshotLabels(viewports) {
|
|
4975
|
+
return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport && viewport.setup_action_results || []));
|
|
4976
|
+
}
|
|
4961
4977
|
function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewport, finalScreenshotFullPage) {
|
|
4962
4978
|
const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === undefined
|
|
4963
4979
|
? undefined
|
|
@@ -5826,6 +5842,8 @@ function assessProfile(profile, evidence) {
|
|
|
5826
5842
|
else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
|
|
5827
5843
|
else if (checks.some((check) => check.status === "failed")) status = "product_regression";
|
|
5828
5844
|
const screenshotLabels = profileScreenshotLabels(viewports);
|
|
5845
|
+
const canonicalScreenshotLabels = profileFinalScreenshotLabels(viewports);
|
|
5846
|
+
const setupScreenshotLabels = profileAllSetupScreenshotLabels(viewports);
|
|
5829
5847
|
const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
|
|
5830
5848
|
const passedChecks = checks.filter((check) => check.status === "passed").length;
|
|
5831
5849
|
const failedChecks = checks.filter((check) => check.status === "failed").length;
|
|
@@ -5842,7 +5860,14 @@ function assessProfile(profile, evidence) {
|
|
|
5842
5860
|
status,
|
|
5843
5861
|
baseline_policy: profile.baseline_policy || "invariant_only",
|
|
5844
5862
|
route,
|
|
5845
|
-
artifacts: {
|
|
5863
|
+
artifacts: {
|
|
5864
|
+
screenshots: screenshotLabels,
|
|
5865
|
+
canonical_screenshots: canonicalScreenshotLabels,
|
|
5866
|
+
setup_screenshots: setupScreenshotLabels,
|
|
5867
|
+
console: "console.json",
|
|
5868
|
+
proof_json: "proof.json",
|
|
5869
|
+
dom_summary: "dom-summary.json"
|
|
5870
|
+
},
|
|
5846
5871
|
checks,
|
|
5847
5872
|
summary,
|
|
5848
5873
|
captured_at: evidence.captured_at,
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// src/receipts.ts
|
|
2
|
+
var RIDDLE_PREVIEW_RECEIPT_VERSION = "riddle.preview-receipt.v1";
|
|
3
|
+
var RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION = "riddle-proof.observation-receipt.v1";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function requiredString(record, key, context) {
|
|
8
|
+
const value = record[key];
|
|
9
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
10
|
+
throw new Error(`${context}.${key} must be a non-empty string.`);
|
|
11
|
+
}
|
|
12
|
+
return value.trim();
|
|
13
|
+
}
|
|
14
|
+
function normalizedSourceIdentity(value) {
|
|
15
|
+
if (!isRecord(value)) return {};
|
|
16
|
+
return {
|
|
17
|
+
git_revision: typeof value.git_revision === "string" && value.git_revision.trim() ? value.git_revision.trim() : void 0,
|
|
18
|
+
repository: typeof value.repository === "string" && value.repository.trim() ? value.repository.trim() : void 0,
|
|
19
|
+
dirty: typeof value.dirty === "boolean" ? value.dirty : void 0,
|
|
20
|
+
label: typeof value.label === "string" && value.label.trim() ? value.label.trim() : void 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseRiddlePreviewReceipt(value) {
|
|
24
|
+
if (!isRecord(value)) throw new Error("Preview receipt must be an object.");
|
|
25
|
+
if (value.version !== RIDDLE_PREVIEW_RECEIPT_VERSION) {
|
|
26
|
+
throw new Error(`Unsupported Preview receipt version ${String(value.version || "missing")}.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isRecord(value.source)) throw new Error("Preview receipt source must be an object.");
|
|
29
|
+
const expiresAt = requiredString(value, "expires_at", "preview receipt");
|
|
30
|
+
const publishedAt = requiredString(value, "published_at", "preview receipt");
|
|
31
|
+
const contentDigest = requiredString(value, "content_digest", "preview receipt");
|
|
32
|
+
if (!contentDigest.startsWith("sha256:") || contentDigest.length <= "sha256:".length) {
|
|
33
|
+
throw new Error("preview receipt.content_digest must be a sha256 digest.");
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isFinite(Date.parse(expiresAt)) || !Number.isFinite(Date.parse(publishedAt))) {
|
|
36
|
+
throw new Error("Preview receipt timestamps must be valid ISO date strings.");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
version: RIDDLE_PREVIEW_RECEIPT_VERSION,
|
|
40
|
+
preview_id: requiredString(value, "preview_id", "preview receipt"),
|
|
41
|
+
url: requiredString(value, "url", "preview receipt"),
|
|
42
|
+
expires_at: expiresAt,
|
|
43
|
+
content_digest: contentDigest,
|
|
44
|
+
source: normalizedSourceIdentity(value.source),
|
|
45
|
+
published_at: publishedAt
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function comparableArtifactName(value) {
|
|
49
|
+
return (value || "").split(/[/?#]/).pop()?.toLowerCase().replace(/\.(png|jpe?g|gif|webp|avif|svg)$/u, "").replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/g, "") || "";
|
|
50
|
+
}
|
|
51
|
+
function artifactMatchesLabel(artifact, label) {
|
|
52
|
+
const expected = comparableArtifactName(label);
|
|
53
|
+
if (!expected) return false;
|
|
54
|
+
return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === expected || name.endsWith(`-${expected}`));
|
|
55
|
+
}
|
|
56
|
+
function artifactLooksLikeScreenshot(ref) {
|
|
57
|
+
const text = [ref.name, ref.url, ref.path, ref.kind, ref.content_type].filter(Boolean).join(" ").toLowerCase();
|
|
58
|
+
return /screenshot|\bimage\b/.test(text) || /\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(text);
|
|
59
|
+
}
|
|
60
|
+
function finalScreenshotLabels(result) {
|
|
61
|
+
const explicit = result.artifacts.canonical_screenshots || [];
|
|
62
|
+
if (explicit.length) return explicit;
|
|
63
|
+
const fromEvidence = (result.evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
|
|
64
|
+
if (fromEvidence.length) return fromEvidence;
|
|
65
|
+
return (result.artifacts.screenshots || []).slice(0, 1);
|
|
66
|
+
}
|
|
67
|
+
function setupScreenshotLabels(result) {
|
|
68
|
+
const explicit = result.artifacts.setup_screenshots || [];
|
|
69
|
+
if (explicit.length) return explicit;
|
|
70
|
+
return (result.evidence?.viewports || []).flatMap(
|
|
71
|
+
(viewport) => (viewport.setup_action_results || []).filter((action) => action.action === "screenshot" && action.ok !== false && typeof action.screenshot_label === "string").map((action) => String(action.screenshot_label))
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
function observationArtifactFromRef(ref, canonicalLabels, setupLabels) {
|
|
75
|
+
const base = {
|
|
76
|
+
name: ref.name,
|
|
77
|
+
role: ref.role || "artifact",
|
|
78
|
+
url: ref.url,
|
|
79
|
+
path: ref.path,
|
|
80
|
+
kind: ref.kind,
|
|
81
|
+
content_type: ref.content_type,
|
|
82
|
+
source: ref.source
|
|
83
|
+
};
|
|
84
|
+
if (!artifactLooksLikeScreenshot(ref)) return base;
|
|
85
|
+
if (canonicalLabels.some((label) => artifactMatchesLabel(base, label))) {
|
|
86
|
+
return { ...base, role: "canonical_screenshot" };
|
|
87
|
+
}
|
|
88
|
+
if (setupLabels.some((label) => artifactMatchesLabel(base, label))) {
|
|
89
|
+
return { ...base, role: "setup_screenshot" };
|
|
90
|
+
}
|
|
91
|
+
return { ...base, role: "diagnostic" };
|
|
92
|
+
}
|
|
93
|
+
function profileObservationArtifacts(result) {
|
|
94
|
+
const canonicalLabels = finalScreenshotLabels(result);
|
|
95
|
+
const setupLabels = setupScreenshotLabels(result);
|
|
96
|
+
const artifacts = [];
|
|
97
|
+
const seen = /* @__PURE__ */ new Set();
|
|
98
|
+
const add = (artifact) => {
|
|
99
|
+
const key = artifact.url || artifact.path || `${artifact.role}:${artifact.name}`;
|
|
100
|
+
if (!artifact.name || seen.has(key)) return;
|
|
101
|
+
seen.add(key);
|
|
102
|
+
artifacts.push(artifact);
|
|
103
|
+
};
|
|
104
|
+
for (const ref of result.artifacts.riddle_artifacts || []) {
|
|
105
|
+
add(observationArtifactFromRef(ref, canonicalLabels, setupLabels));
|
|
106
|
+
}
|
|
107
|
+
for (const label of canonicalLabels) {
|
|
108
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
109
|
+
add({ name: label, path: label, role: "canonical_screenshot", kind: "image" });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const label of setupLabels) {
|
|
113
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
114
|
+
add({ name: label, path: label, role: "setup_screenshot", kind: "image" });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const label of result.artifacts.screenshots || []) {
|
|
118
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
119
|
+
add({ name: label, path: label, role: "diagnostic", kind: "image" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return artifacts;
|
|
123
|
+
}
|
|
124
|
+
function mergeObservationArtifacts(profileArtifacts, suppliedArtifacts) {
|
|
125
|
+
const artifacts = [...profileArtifacts];
|
|
126
|
+
for (const supplied of suppliedArtifacts) {
|
|
127
|
+
const suppliedNames = [supplied.name, supplied.url, supplied.path].map(comparableArtifactName).filter(Boolean);
|
|
128
|
+
const existingIndex = artifacts.findIndex((artifact) => {
|
|
129
|
+
if (supplied.url && artifact.url === supplied.url) return true;
|
|
130
|
+
if (supplied.path && artifact.path === supplied.path) return true;
|
|
131
|
+
return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name && suppliedNames.includes(name));
|
|
132
|
+
});
|
|
133
|
+
if (existingIndex < 0) {
|
|
134
|
+
artifacts.push(supplied);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const existing = artifacts[existingIndex];
|
|
138
|
+
const role = supplied.role === "canonical_screenshot" || supplied.role === "setup_screenshot" ? supplied.role : existing.role === "canonical_screenshot" || existing.role === "setup_screenshot" ? existing.role : supplied.role === "artifact" ? existing.role : supplied.role;
|
|
139
|
+
artifacts[existingIndex] = {
|
|
140
|
+
...existing,
|
|
141
|
+
...supplied,
|
|
142
|
+
role
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return artifacts;
|
|
146
|
+
}
|
|
147
|
+
function defaultObservationId(role, capturedAt) {
|
|
148
|
+
return `obs_${role}_${capturedAt.replace(/[^0-9]/g, "").slice(0, 17) || "unknown"}`;
|
|
149
|
+
}
|
|
150
|
+
function profileCheckCounts(result) {
|
|
151
|
+
const counts = { total: result.checks.length, passed: 0, failed: 0, skipped: 0, needs_human_review: 0 };
|
|
152
|
+
for (const check of result.checks) {
|
|
153
|
+
if (check.status === "passed") counts.passed += 1;
|
|
154
|
+
if (check.status === "failed") counts.failed += 1;
|
|
155
|
+
if (check.status === "skipped") counts.skipped += 1;
|
|
156
|
+
if (check.status === "needs_human_review") counts.needs_human_review += 1;
|
|
157
|
+
}
|
|
158
|
+
return counts;
|
|
159
|
+
}
|
|
160
|
+
function createRiddleProofObservationReceipt(input) {
|
|
161
|
+
const capturedAt = input.captured_at || input.profile_result?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
162
|
+
const profileArtifacts = input.profile_result ? profileObservationArtifacts(input.profile_result) : [];
|
|
163
|
+
const requestedCanonical = input.canonical_screenshot ? { ...input.canonical_screenshot, role: "canonical_screenshot" } : void 0;
|
|
164
|
+
const artifacts = mergeObservationArtifacts(
|
|
165
|
+
profileArtifacts,
|
|
166
|
+
[...input.artifacts || [], ...requestedCanonical ? [requestedCanonical] : []]
|
|
167
|
+
);
|
|
168
|
+
const canonicalScreenshot = requestedCanonical ? artifacts.find((artifact) => artifact.role === "canonical_screenshot" && artifactMatchesLabel(artifact, requestedCanonical.name)) || requestedCanonical : artifacts.find((artifact) => artifact.role === "canonical_screenshot");
|
|
169
|
+
const target = input.target.kind === "preview" && input.target.preview ? { ...input.target, preview: parseRiddlePreviewReceipt(input.target.preview) } : input.target;
|
|
170
|
+
return {
|
|
171
|
+
version: RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
|
|
172
|
+
observation_id: input.observation_id || defaultObservationId(input.comparison_role, capturedAt),
|
|
173
|
+
comparison_role: input.comparison_role,
|
|
174
|
+
executor: input.executor,
|
|
175
|
+
target,
|
|
176
|
+
source: normalizedSourceIdentity(input.source || input.target.preview?.source),
|
|
177
|
+
captured_at: capturedAt,
|
|
178
|
+
artifacts,
|
|
179
|
+
canonical_screenshot: canonicalScreenshot,
|
|
180
|
+
profile_summary: input.profile_result ? {
|
|
181
|
+
profile_name: input.profile_result.profile_name,
|
|
182
|
+
status: input.profile_result.status,
|
|
183
|
+
summary: input.profile_result.summary,
|
|
184
|
+
route: input.profile_result.route,
|
|
185
|
+
checks: profileCheckCounts(input.profile_result)
|
|
186
|
+
} : void 0,
|
|
187
|
+
proof: input.profile_result ? {
|
|
188
|
+
profile_name: input.profile_result.profile_name,
|
|
189
|
+
result: input.profile_result
|
|
190
|
+
} : void 0,
|
|
191
|
+
publication: input.publication,
|
|
192
|
+
execution: input.execution,
|
|
193
|
+
metadata: input.metadata
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function parseRiddleProofObservationReceipt(value) {
|
|
197
|
+
if (!isRecord(value)) throw new Error("Observation receipt must be an object.");
|
|
198
|
+
if (value.version !== RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION) {
|
|
199
|
+
throw new Error(`Unsupported Observation receipt version ${String(value.version || "missing")}.`);
|
|
200
|
+
}
|
|
201
|
+
if (!isRecord(value.executor)) throw new Error("Observation receipt executor must be an object.");
|
|
202
|
+
if (!isRecord(value.target)) throw new Error("Observation receipt target must be an object.");
|
|
203
|
+
if (!isRecord(value.source)) throw new Error("Observation receipt source must be an object.");
|
|
204
|
+
requiredString(value, "observation_id", "observation receipt");
|
|
205
|
+
requiredString(value, "captured_at", "observation receipt");
|
|
206
|
+
const executorKind = requiredString(value.executor, "kind", "observation receipt executor");
|
|
207
|
+
if (!"local_playwright,riddle_hosted,browser_api,other".split(",").includes(executorKind)) {
|
|
208
|
+
throw new Error(`Unsupported observation executor kind ${executorKind}.`);
|
|
209
|
+
}
|
|
210
|
+
if (value.executor.runner !== void 0) {
|
|
211
|
+
requiredString(value.executor, "runner", "observation receipt executor");
|
|
212
|
+
}
|
|
213
|
+
const role = requiredString(value, "comparison_role", "observation receipt");
|
|
214
|
+
if (!["before", "after", "standalone"].includes(role)) {
|
|
215
|
+
throw new Error(`Unsupported observation comparison role ${role}.`);
|
|
216
|
+
}
|
|
217
|
+
const targetKind = requiredString(value.target, "kind", "observation receipt target");
|
|
218
|
+
if (targetKind !== "url" && targetKind !== "preview") {
|
|
219
|
+
throw new Error(`Unsupported observation target kind ${targetKind}.`);
|
|
220
|
+
}
|
|
221
|
+
requiredString(value.target, "url", "observation receipt target");
|
|
222
|
+
if (targetKind === "preview") {
|
|
223
|
+
if (value.target.preview === void 0) {
|
|
224
|
+
throw new Error("Preview Observation target must include its Preview receipt.");
|
|
225
|
+
}
|
|
226
|
+
parseRiddlePreviewReceipt(value.target.preview);
|
|
227
|
+
}
|
|
228
|
+
if (!Array.isArray(value.artifacts)) throw new Error("Observation receipt artifacts must be an array.");
|
|
229
|
+
const artifactRoles = /* @__PURE__ */ new Set(["canonical_screenshot", "setup_screenshot", "data", "diagnostic", "artifact"]);
|
|
230
|
+
const artifacts = value.artifacts.map((artifact, index) => {
|
|
231
|
+
if (!isRecord(artifact)) throw new Error(`Observation receipt artifact ${index} must be an object.`);
|
|
232
|
+
requiredString(artifact, "name", `observation receipt artifact ${index}`);
|
|
233
|
+
const artifactRole = requiredString(artifact, "role", `observation receipt artifact ${index}`);
|
|
234
|
+
if (!artifactRoles.has(artifactRole)) {
|
|
235
|
+
throw new Error(`Unsupported observation artifact role ${artifactRole}.`);
|
|
236
|
+
}
|
|
237
|
+
return artifact;
|
|
238
|
+
});
|
|
239
|
+
if (value.canonical_screenshot !== void 0) {
|
|
240
|
+
if (!isRecord(value.canonical_screenshot) || value.canonical_screenshot.role !== "canonical_screenshot") {
|
|
241
|
+
throw new Error("Observation canonical_screenshot must have the canonical_screenshot role.");
|
|
242
|
+
}
|
|
243
|
+
const canonical = value.canonical_screenshot;
|
|
244
|
+
const included = artifacts.some((artifact) => artifact.role === "canonical_screenshot" && artifact.name === canonical.name && artifact.url === canonical.url && artifact.path === canonical.path);
|
|
245
|
+
if (!included) {
|
|
246
|
+
throw new Error("Observation canonical_screenshot must reference an artifact in the receipt.");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export {
|
|
253
|
+
RIDDLE_PREVIEW_RECEIPT_VERSION,
|
|
254
|
+
RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
|
|
255
|
+
parseRiddlePreviewReceipt,
|
|
256
|
+
createRiddleProofObservationReceipt,
|
|
257
|
+
parseRiddleProofObservationReceipt
|
|
258
|
+
};
|