@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.
Files changed (48) hide show
  1. package/README.md +65 -3
  2. package/dist/change-proof.cjs +744 -85
  3. package/dist/change-proof.d.cts +100 -8
  4. package/dist/change-proof.d.ts +100 -8
  5. package/dist/change-proof.js +15 -1
  6. package/dist/{chunk-B2DP2LET.js → chunk-5IFZSUPF.js} +52 -13
  7. package/dist/chunk-6VFS2JFR.js +886 -0
  8. package/dist/{chunk-IY4W6STC.js → chunk-7N6X54WG.js} +116 -17
  9. package/dist/{chunk-GG2D3MFZ.js → chunk-FCSJZBC5.js} +26 -1
  10. package/dist/chunk-MEVVL4TI.js +258 -0
  11. package/dist/{chunk-H25IDX76.js → chunk-NEXWITV4.js} +221 -35
  12. package/dist/{chunk-UE4I7RTI.js → chunk-RQPCKRKT.js} +1 -1
  13. package/dist/cli/index.js +7 -6
  14. package/dist/cli.cjs +2026 -1060
  15. package/dist/cli.js +7 -6
  16. package/dist/index.cjs +932 -122
  17. package/dist/index.d.cts +4 -3
  18. package/dist/index.d.ts +4 -3
  19. package/dist/index.js +42 -14
  20. package/dist/pr-comment.cjs +416 -17
  21. package/dist/pr-comment.d.cts +6 -1
  22. package/dist/pr-comment.d.ts +6 -1
  23. package/dist/pr-comment.js +6 -1
  24. package/dist/profile/index.cjs +26 -1
  25. package/dist/profile/index.js +1 -1
  26. package/dist/profile-suggestions.js +2 -2
  27. package/dist/profile.cjs +26 -1
  28. package/dist/profile.d.cts +7 -0
  29. package/dist/profile.d.ts +7 -0
  30. package/dist/profile.js +1 -1
  31. package/dist/receipts.cjs +286 -0
  32. package/dist/receipts.d.cts +115 -0
  33. package/dist/receipts.d.ts +115 -0
  34. package/dist/receipts.js +15 -0
  35. package/dist/riddle-client.cjs +98 -13
  36. package/dist/riddle-client.d.cts +18 -5
  37. package/dist/riddle-client.d.ts +18 -5
  38. package/dist/riddle-client.js +4 -1
  39. package/dist/runtime/index.cjs +98 -13
  40. package/dist/runtime/index.d.cts +5 -1
  41. package/dist/runtime/index.d.ts +5 -1
  42. package/dist/runtime/index.js +4 -1
  43. package/dist/runtime/riddle-client.cjs +98 -13
  44. package/dist/runtime/riddle-client.d.cts +5 -1
  45. package/dist/runtime/riddle-client.d.ts +5 -1
  46. package/dist/runtime/riddle-client.js +4 -1
  47. package/package.json +7 -2
  48. package/dist/chunk-BILL3UC2.js +0 -488
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var pr_comment_exports = {};
22
22
  __export(pr_comment_exports, {
23
23
  RIDDLE_PROOF_PR_COMMENT_MARKER: () => RIDDLE_PROOF_PR_COMMENT_MARKER,
24
+ buildRiddleProofHandoffPrCommentMarkdown: () => buildRiddleProofHandoffPrCommentMarkdown,
24
25
  buildRiddleProofPrCommentMarkdown: () => buildRiddleProofPrCommentMarkdown,
25
26
  summarizeRiddleProofPrComment: () => summarizeRiddleProofPrComment
26
27
  });
@@ -202,6 +203,313 @@ function summarizeRiddleProofPublicState(input) {
202
203
  };
203
204
  }
204
205
 
206
+ // src/profile.ts
207
+ var RIDDLE_PROOF_PROFILE_STATUSES = [
208
+ "passed",
209
+ "product_regression",
210
+ "proof_insufficient",
211
+ "environment_blocked",
212
+ "configuration_error",
213
+ "needs_human_review"
214
+ ];
215
+
216
+ // src/receipts.ts
217
+ var RIDDLE_PREVIEW_RECEIPT_VERSION = "riddle.preview-receipt.v1";
218
+ var RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION = "riddle-proof.observation-receipt.v1";
219
+ function isRecord(value) {
220
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
221
+ }
222
+ function requiredString(record, key, context) {
223
+ const value = record[key];
224
+ if (typeof value !== "string" || !value.trim()) {
225
+ throw new Error(`${context}.${key} must be a non-empty string.`);
226
+ }
227
+ return value.trim();
228
+ }
229
+ function normalizedSourceIdentity(value) {
230
+ if (!isRecord(value)) return {};
231
+ return {
232
+ git_revision: typeof value.git_revision === "string" && value.git_revision.trim() ? value.git_revision.trim() : void 0,
233
+ repository: typeof value.repository === "string" && value.repository.trim() ? value.repository.trim() : void 0,
234
+ dirty: typeof value.dirty === "boolean" ? value.dirty : void 0,
235
+ label: typeof value.label === "string" && value.label.trim() ? value.label.trim() : void 0
236
+ };
237
+ }
238
+ function parseRiddlePreviewReceipt(value) {
239
+ if (!isRecord(value)) throw new Error("Preview receipt must be an object.");
240
+ if (value.version !== RIDDLE_PREVIEW_RECEIPT_VERSION) {
241
+ throw new Error(`Unsupported Preview receipt version ${String(value.version || "missing")}.`);
242
+ }
243
+ if (!isRecord(value.source)) throw new Error("Preview receipt source must be an object.");
244
+ const expiresAt = requiredString(value, "expires_at", "preview receipt");
245
+ const publishedAt = requiredString(value, "published_at", "preview receipt");
246
+ const contentDigest = requiredString(value, "content_digest", "preview receipt");
247
+ if (!contentDigest.startsWith("sha256:") || contentDigest.length <= "sha256:".length) {
248
+ throw new Error("preview receipt.content_digest must be a sha256 digest.");
249
+ }
250
+ if (!Number.isFinite(Date.parse(expiresAt)) || !Number.isFinite(Date.parse(publishedAt))) {
251
+ throw new Error("Preview receipt timestamps must be valid ISO date strings.");
252
+ }
253
+ return {
254
+ version: RIDDLE_PREVIEW_RECEIPT_VERSION,
255
+ preview_id: requiredString(value, "preview_id", "preview receipt"),
256
+ url: requiredString(value, "url", "preview receipt"),
257
+ expires_at: expiresAt,
258
+ content_digest: contentDigest,
259
+ source: normalizedSourceIdentity(value.source),
260
+ published_at: publishedAt
261
+ };
262
+ }
263
+ function parseRiddleProofObservationReceipt(value) {
264
+ if (!isRecord(value)) throw new Error("Observation receipt must be an object.");
265
+ if (value.version !== RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION) {
266
+ throw new Error(`Unsupported Observation receipt version ${String(value.version || "missing")}.`);
267
+ }
268
+ if (!isRecord(value.executor)) throw new Error("Observation receipt executor must be an object.");
269
+ if (!isRecord(value.target)) throw new Error("Observation receipt target must be an object.");
270
+ if (!isRecord(value.source)) throw new Error("Observation receipt source must be an object.");
271
+ requiredString(value, "observation_id", "observation receipt");
272
+ requiredString(value, "captured_at", "observation receipt");
273
+ const executorKind = requiredString(value.executor, "kind", "observation receipt executor");
274
+ if (!"local_playwright,riddle_hosted,browser_api,other".split(",").includes(executorKind)) {
275
+ throw new Error(`Unsupported observation executor kind ${executorKind}.`);
276
+ }
277
+ if (value.executor.runner !== void 0) {
278
+ requiredString(value.executor, "runner", "observation receipt executor");
279
+ }
280
+ const role = requiredString(value, "comparison_role", "observation receipt");
281
+ if (!["before", "after", "standalone"].includes(role)) {
282
+ throw new Error(`Unsupported observation comparison role ${role}.`);
283
+ }
284
+ const targetKind = requiredString(value.target, "kind", "observation receipt target");
285
+ if (targetKind !== "url" && targetKind !== "preview") {
286
+ throw new Error(`Unsupported observation target kind ${targetKind}.`);
287
+ }
288
+ requiredString(value.target, "url", "observation receipt target");
289
+ if (targetKind === "preview") {
290
+ if (value.target.preview === void 0) {
291
+ throw new Error("Preview Observation target must include its Preview receipt.");
292
+ }
293
+ parseRiddlePreviewReceipt(value.target.preview);
294
+ }
295
+ if (!Array.isArray(value.artifacts)) throw new Error("Observation receipt artifacts must be an array.");
296
+ const artifactRoles = /* @__PURE__ */ new Set(["canonical_screenshot", "setup_screenshot", "data", "diagnostic", "artifact"]);
297
+ const artifacts = value.artifacts.map((artifact, index) => {
298
+ if (!isRecord(artifact)) throw new Error(`Observation receipt artifact ${index} must be an object.`);
299
+ requiredString(artifact, "name", `observation receipt artifact ${index}`);
300
+ const artifactRole = requiredString(artifact, "role", `observation receipt artifact ${index}`);
301
+ if (!artifactRoles.has(artifactRole)) {
302
+ throw new Error(`Unsupported observation artifact role ${artifactRole}.`);
303
+ }
304
+ return artifact;
305
+ });
306
+ if (value.canonical_screenshot !== void 0) {
307
+ if (!isRecord(value.canonical_screenshot) || value.canonical_screenshot.role !== "canonical_screenshot") {
308
+ throw new Error("Observation canonical_screenshot must have the canonical_screenshot role.");
309
+ }
310
+ const canonical = value.canonical_screenshot;
311
+ const included = artifacts.some((artifact) => artifact.role === "canonical_screenshot" && artifact.name === canonical.name && artifact.url === canonical.url && artifact.path === canonical.path);
312
+ if (!included) {
313
+ throw new Error("Observation canonical_screenshot must reference an artifact in the receipt.");
314
+ }
315
+ }
316
+ return value;
317
+ }
318
+
319
+ // src/change-proof.ts
320
+ var RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION = "riddle-proof.change-receipt.v1";
321
+ var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.v2";
322
+ var RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION = "riddle-proof.handoff-receipt.v1";
323
+ var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
324
+ var DEFAULT_AFTER_STATUSES = ["passed"];
325
+ function changeReceiptVerdict(status) {
326
+ if (status === "passed") return "mergeable";
327
+ if (status === "environment_blocked") return "environment_blocked";
328
+ if (status === "configuration_error") return "configuration_error";
329
+ if (status === "needs_human_review") return "needs_human_review";
330
+ if (status === "proof_insufficient") return "proof_insufficient";
331
+ return "not_mergeable";
332
+ }
333
+ function changeRecommendation(verdict) {
334
+ if (verdict === "mergeable") {
335
+ return {
336
+ merge_recommended: true,
337
+ verdict,
338
+ label: "Merge recommended",
339
+ reason: "The declared before/after delta contract passed with sufficient bound evidence."
340
+ };
341
+ }
342
+ const reason = verdict === "not_mergeable" ? "The declared change delta did not pass." : verdict === "environment_blocked" ? "The environment blocked reliable evidence collection." : verdict === "needs_human_review" ? "The evidence requires human review." : verdict === "configuration_error" ? "The change contract is invalid." : "The change proof did not produce sufficient bound evidence.";
343
+ return { merge_recommended: false, verdict, label: "Merge not recommended", reason };
344
+ }
345
+ function noShippingAuthorization() {
346
+ return { status: "not_granted", authorized: false, source: "none" };
347
+ }
348
+ function recommendationMatches(actual, expected) {
349
+ return actual?.merge_recommended === expected.merge_recommended && actual.verdict === expected.verdict && actual.label === expected.label && actual.reason === expected.reason;
350
+ }
351
+ function assertShippingAuthorizationConsistent(authorization, receiptName) {
352
+ if (!authorization) throw new Error(`${receiptName} shipping authorization is required.`);
353
+ if (authorization.status !== "not_granted" && authorization.status !== "granted") {
354
+ throw new Error(`${receiptName} shipping authorization status is invalid.`);
355
+ }
356
+ if (authorization.source !== "none" && authorization.source !== "human" && authorization.source !== "automation") {
357
+ throw new Error(`${receiptName} shipping authorization source is invalid.`);
358
+ }
359
+ const granted = authorization.status === "granted";
360
+ if (authorization.authorized !== granted) {
361
+ throw new Error(`${receiptName} shipping authorization status and authorized flag must agree.`);
362
+ }
363
+ if (authorization.source === "none" === granted) {
364
+ throw new Error(`${receiptName} shipping authorization source must identify an authorizer only when granted.`);
365
+ }
366
+ }
367
+ function observationArtifactMatches(actual, expected) {
368
+ if (!actual || !expected) return actual === expected;
369
+ return actual.name === expected.name && actual.role === expected.role && actual.url === expected.url && actual.path === expected.path && actual.kind === expected.kind && actual.content_type === expected.content_type && actual.source === expected.source;
370
+ }
371
+ function isRecord2(value) {
372
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
373
+ }
374
+ function migratedObservationFromLegacySide(side, legacy) {
375
+ const legacyArtifacts = legacy.artifacts.map((artifact) => ({
376
+ name: artifact.name,
377
+ url: artifact.url,
378
+ path: artifact.path,
379
+ source: artifact.source,
380
+ kind: artifact.kind,
381
+ role: legacy.screenshots[0] && artifactTarget(artifact) === artifactTarget(legacy.screenshots[0]) ? "canonical_screenshot" : artifact.kind === "image" ? "diagnostic" : artifact.kind === "data" ? "data" : "artifact"
382
+ }));
383
+ const canonicalScreenshot = legacyArtifacts.find((artifact) => artifact.role === "canonical_screenshot");
384
+ return {
385
+ version: "riddle-proof.observation-receipt.v1",
386
+ observation_id: `obs_${side}_migrated`,
387
+ comparison_role: side,
388
+ executor: { kind: "other", runner: "legacy-change-receipt" },
389
+ target: { kind: "url", url: legacy.source },
390
+ source: {},
391
+ captured_at: legacy.captured_at || (/* @__PURE__ */ new Date(0)).toISOString(),
392
+ artifacts: legacyArtifacts,
393
+ canonical_screenshot: canonicalScreenshot,
394
+ profile_summary: {
395
+ profile_name: legacy.profile_name,
396
+ status: legacy.status,
397
+ summary: legacy.summary,
398
+ route: legacy.route,
399
+ checks: legacy.checks
400
+ },
401
+ metadata: { migrated_from: RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION }
402
+ };
403
+ }
404
+ function migrateRiddleProofChangeReceipt(legacy) {
405
+ const recommendation = changeRecommendation(legacy.verdict);
406
+ return {
407
+ version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
408
+ contract_name: legacy.contract_name,
409
+ profile_name: legacy.profile_name,
410
+ status: legacy.status,
411
+ verdict: legacy.verdict,
412
+ summary: legacy.summary,
413
+ before: migratedObservationFromLegacySide("before", legacy.before),
414
+ after: migratedObservationFromLegacySide("after", legacy.after),
415
+ groups: {
416
+ before: {
417
+ side: "before",
418
+ label: "before",
419
+ ok: legacy.before.status === "passed" || legacy.before.status === "product_regression",
420
+ profile_name: legacy.before.profile_name,
421
+ status: legacy.before.status,
422
+ required_status: DEFAULT_BEFORE_STATUSES,
423
+ summary: legacy.before.summary
424
+ },
425
+ after: {
426
+ side: "after",
427
+ label: "after",
428
+ ok: legacy.after.status === "passed",
429
+ profile_name: legacy.after.profile_name,
430
+ status: legacy.after.status,
431
+ required_status: DEFAULT_AFTER_STATUSES,
432
+ summary: legacy.after.summary
433
+ }
434
+ },
435
+ source_bindings: {
436
+ before: { side: "before", required: false, ok: true, status: "not_required" },
437
+ after: { side: "after", required: false, ok: true, status: "not_required" }
438
+ },
439
+ deltas: legacy.deltas,
440
+ recommendation,
441
+ shipping_authorization: noShippingAuthorization(),
442
+ proves: legacy.proves,
443
+ does_not_prove: legacy.does_not_prove,
444
+ metadata: legacy.metadata
445
+ };
446
+ }
447
+ function parseRiddleProofChangeReceipt(value) {
448
+ if (!isRecord2(value)) throw new Error("Change receipt must be an object.");
449
+ if (value.version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION) {
450
+ return migrateRiddleProofChangeReceipt(value);
451
+ }
452
+ if (value.version !== RIDDLE_PROOF_CHANGE_RECEIPT_VERSION) {
453
+ throw new Error(`Unsupported Change receipt version ${String(value.version || "missing")}.`);
454
+ }
455
+ const receipt = value;
456
+ if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(receipt.status)) {
457
+ throw new Error(`Unsupported Change receipt status ${String(receipt.status || "missing")}.`);
458
+ }
459
+ parseRiddleProofObservationReceipt(receipt.before);
460
+ parseRiddleProofObservationReceipt(receipt.after);
461
+ if (receipt.before.comparison_role !== "before" || receipt.after.comparison_role !== "after") {
462
+ throw new Error("Change receipt observations must preserve their before and after comparison roles.");
463
+ }
464
+ const expectedVerdict = changeReceiptVerdict(receipt.status);
465
+ if (receipt.verdict !== expectedVerdict) {
466
+ throw new Error("Change receipt verdict must match its evaluated status.");
467
+ }
468
+ if (!recommendationMatches(receipt.recommendation, changeRecommendation(expectedVerdict))) {
469
+ throw new Error("Change receipt recommendation must be derived from the Change receipt verdict.");
470
+ }
471
+ assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Change receipt");
472
+ return receipt;
473
+ }
474
+ function createRiddleProofHandoffReceipt(changeReceipt, options = {}) {
475
+ const change = parseRiddleProofChangeReceipt(changeReceipt);
476
+ const authorization = options.shipping_authorization || change.shipping_authorization;
477
+ assertShippingAuthorizationConsistent(authorization, "Handoff receipt");
478
+ return {
479
+ version: RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
480
+ change,
481
+ verdict: change.verdict,
482
+ recommendation: change.recommendation,
483
+ shipping_authorization: authorization,
484
+ canonical_pair: {
485
+ before: change.before.canonical_screenshot,
486
+ after: change.after.canonical_screenshot
487
+ },
488
+ created_at: options.created_at || (/* @__PURE__ */ new Date()).toISOString()
489
+ };
490
+ }
491
+ function parseRiddleProofHandoffReceipt(value) {
492
+ if (!isRecord2(value) || value.version !== RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION) {
493
+ throw new Error(`Unsupported Handoff receipt version ${String(isRecord2(value) ? value.version || "missing" : "missing")}.`);
494
+ }
495
+ const receipt = value;
496
+ if (!Number.isFinite(Date.parse(receipt.created_at))) {
497
+ throw new Error("Handoff receipt created_at must be a valid timestamp.");
498
+ }
499
+ const change = parseRiddleProofChangeReceipt(receipt.change);
500
+ if (receipt.verdict !== change.verdict || !recommendationMatches(receipt.recommendation, change.recommendation)) {
501
+ throw new Error("Handoff receipt must preserve the Change receipt verdict and recommendation.");
502
+ }
503
+ if (!observationArtifactMatches(receipt.canonical_pair?.before, change.before.canonical_screenshot) || !observationArtifactMatches(receipt.canonical_pair?.after, change.after.canonical_screenshot)) {
504
+ throw new Error("Handoff receipt canonical pair must project the Change receipt observations.");
505
+ }
506
+ assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Handoff receipt");
507
+ return receipt;
508
+ }
509
+ function artifactTarget(artifact) {
510
+ return artifact.url || artifact.path;
511
+ }
512
+
205
513
  // src/pr-comment.ts
206
514
  var RIDDLE_PROOF_PR_COMMENT_MARKER = "<!-- riddle-proof:pr-comment:v1 -->";
207
515
  function asRecord2(value) {
@@ -282,9 +590,10 @@ function collectChangeReceiptArtifacts(result) {
282
590
  const seen = /* @__PURE__ */ new Set();
283
591
  const collectSide = (sideName, side) => {
284
592
  const candidates = [
593
+ side.canonical_screenshot,
285
594
  ...asArray(side.screenshots),
286
595
  ...asArray(side.artifacts)
287
- ];
596
+ ].filter(Boolean);
288
597
  for (const [index, item] of candidates.entries()) {
289
598
  const artifact = asRecord2(item);
290
599
  const url = stringValue2(artifact.url) || stringValue2(artifact.path);
@@ -376,9 +685,10 @@ function changeReceiptPageSummaries(result) {
376
685
  for (const sideName of ["before", "after"]) {
377
686
  const side = asRecord2(result[sideName]);
378
687
  if (!Object.keys(side).length) continue;
379
- const checks = asRecord2(side.checks);
688
+ const profileSummary = asRecord2(side.profile_summary);
689
+ const checks = Object.keys(asRecord2(side.checks)).length ? asRecord2(side.checks) : asRecord2(profileSummary.checks);
380
690
  pages.push({
381
- route: firstStringValue2(side.source, side.profile_name) || sideName,
691
+ route: firstStringValue2(asRecord2(side.target).url, side.source, profileSummary.profile_name, side.profile_name) || sideName,
382
692
  passed: numberValue2(checks.passed) ?? 0,
383
693
  failed: numberValue2(checks.failed) ?? 0
384
694
  });
@@ -441,7 +751,11 @@ function isProfileResult(result) {
441
751
  return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
442
752
  }
443
753
  function isChangeReceiptResult(result) {
444
- return stringValue2(result.version) === "riddle-proof.change-receipt.v1";
754
+ const version = stringValue2(result.version);
755
+ return version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION || version === RIDDLE_PROOF_CHANGE_RECEIPT_VERSION;
756
+ }
757
+ function isHandoffReceiptResult(result) {
758
+ return stringValue2(result.version) === RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION;
445
759
  }
446
760
  function isChangeResult(result) {
447
761
  const version = stringValue2(result.version);
@@ -449,10 +763,13 @@ function isChangeResult(result) {
449
763
  }
450
764
  function summarizeRiddleProofPrComment(input) {
451
765
  const runResponse = asRecord2(input.runResponse);
452
- const result = asRecord2(input.result);
766
+ const inputResult = asRecord2(input.result);
767
+ const handoffReceipt = isHandoffReceiptResult(inputResult) ? parseRiddleProofHandoffReceipt(inputResult) : void 0;
768
+ const parsedChangeReceipt = handoffReceipt?.change || (isChangeReceiptResult(inputResult) ? parseRiddleProofChangeReceipt(inputResult) : void 0);
769
+ const result = parsedChangeReceipt ? asRecord2(parsedChangeReceipt) : inputResult;
453
770
  const proofResult = asRecord2(runResponse.proofResult);
454
771
  const profileResult = isProfileResult(result);
455
- const changeReceiptResult = isChangeReceiptResult(result);
772
+ const changeReceiptResult = Boolean(parsedChangeReceipt);
456
773
  const changeResult = changeReceiptResult || isChangeResult(result);
457
774
  const profileRiddle = asRecord2(result.riddle);
458
775
  const preview = asRecord2(runResponse.preview);
@@ -473,7 +790,7 @@ function summarizeRiddleProofPrComment(input) {
473
790
  delete checkSource.ok;
474
791
  const nestedChecks = summarizeExplicitChecks(checkSource);
475
792
  const resultStatus = firstStringValue2(result.status, stopCondition.status);
476
- const ok = changeResult ? resultStatus === "passed" : booleanValue2(result.ok) ?? booleanValue2(runResponse.ok) ?? null;
793
+ const ok = changeReceiptResult ? parsedChangeReceipt?.recommendation.merge_recommended ?? false : changeResult ? resultStatus === "passed" : booleanValue2(result.ok) ?? booleanValue2(runResponse.ok) ?? null;
477
794
  const checkpointSummary = checkpointSummaryFrom2(
478
795
  result.checkpoint_summary,
479
796
  stopCondition.checkpoint_summary,
@@ -481,19 +798,19 @@ function summarizeRiddleProofPrComment(input) {
481
798
  rawDetails.checkpoint_summary,
482
799
  proofResult.checkpoint_summary
483
800
  );
484
- const publicState = summarizeRiddleProofPublicState({
801
+ const publicState = changeReceiptResult ? void 0 : summarizeRiddleProofPublicState({
485
802
  ...result,
486
803
  status: resultStatus,
487
804
  checkpoint_summary: checkpointSummary || result.checkpoint_summary
488
805
  });
489
- const mergeRecommendation = riddleProofPublicStateMergeRecommendation(
806
+ const mergeRecommendation = publicState ? riddleProofPublicStateMergeRecommendation(
490
807
  publicState,
491
808
  firstStringValue2(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
492
- );
809
+ ) : void 0;
493
810
  return {
494
811
  ok,
495
812
  status: firstStringValue2(proofResult.status, profileRiddle.status),
496
- result_status: changeResult ? resultStatus : publicState.status,
813
+ result_status: changeResult ? resultStatus : publicState?.status,
497
814
  job_id: firstStringValue2(proofResult.job_id, profileRiddle.job_id),
498
815
  duration_ms: numberValue2(proofResult.duration_ms) ?? numberValue2(profileRiddle.elapsed_ms),
499
816
  proof_url: stringValue2(runResponse.proofUrl),
@@ -501,13 +818,13 @@ function summarizeRiddleProofPrComment(input) {
501
818
  preview_url: stringValue2(preview.preview_url) || stringValue2(preview.url),
502
819
  preview_publish_recovered: booleanValue2(preview.publish_recovered),
503
820
  preview_publish_error: stringValue2(preview.publish_error),
504
- ship_held: publicState.ship_held,
505
- shipping_disabled: publicState.shipping_disabled,
506
- ship_authorized: publicState.ship_authorized,
507
- merge_ready: publicState.merge_ready,
508
- sync_allowed: publicState.sync_allowed,
821
+ ship_held: publicState?.ship_held,
822
+ shipping_disabled: publicState?.shipping_disabled,
823
+ ship_authorized: changeReceiptResult ? parsedChangeReceipt?.shipping_authorization.authorized : publicState?.ship_authorized,
824
+ merge_ready: publicState?.merge_ready,
825
+ sync_allowed: publicState?.sync_allowed,
509
826
  proof_decision: firstStringValue2(result.proof_decision, stopCondition.proof_decision, resultRaw.proof_decision),
510
- merge_recommendation: changeReceiptResult && resultStatus ? stringValue2(result.verdict) : mergeRecommendation,
827
+ merge_recommendation: changeReceiptResult ? parsedChangeReceipt?.recommendation.label : mergeRecommendation,
511
828
  checkpoint_summary: checkpointSummary,
512
829
  public_state: publicState,
513
830
  passed_checks: changeChecks?.passed ?? profileChecks?.passed ?? nestedChecks.passed,
@@ -575,7 +892,88 @@ function checkpointSummaryLine(summary) {
575
892
  summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
576
893
  ].filter(Boolean).join("; ");
577
894
  }
895
+ function markdownTableValue(value) {
896
+ return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
897
+ }
898
+ function observationArtifactTarget(artifact) {
899
+ return artifact?.url || artifact?.path;
900
+ }
901
+ function canonicalScreenshotCell(artifact, fallback) {
902
+ const target = observationArtifactTarget(artifact);
903
+ if (!artifact || !target) return fallback;
904
+ if (artifact.url) return `![${artifact.name.replace(/\]/g, "\\]")}](${artifact.url})`;
905
+ return markdownTableValue(artifact.path || artifact.name);
906
+ }
907
+ function shortRevision(value) {
908
+ return value ? value.slice(0, 12) : "not recorded";
909
+ }
910
+ function buildRiddleProofHandoffPrCommentMarkdown(handoff, input = {}) {
911
+ const receipt = parseRiddleProofHandoffReceipt(handoff);
912
+ const change = receipt.change;
913
+ const beforeStatus = change.before.profile_summary?.status || "not recorded";
914
+ const afterStatus = change.after.profile_summary?.status || "not recorded";
915
+ const beforeBaseline = change.groups.before.ok ? "matched baseline" : "baseline mismatch";
916
+ const lines = [
917
+ RIDDLE_PROOF_PR_COMMENT_MARKER,
918
+ `## ${input.title?.trim() || "Riddle Change Proof"}`,
919
+ "",
920
+ `**Result:** ${receipt.recommendation.label}`,
921
+ `**Evidence verdict:** \`${receipt.verdict}\``,
922
+ `**Shipping authorization:** ${receipt.shipping_authorization.status}`,
923
+ `**Contract:** ${change.contract_name}`
924
+ ];
925
+ if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
926
+ if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
927
+ lines.push("", change.summary, "", "### Canonical Before / After", "");
928
+ lines.push("| Before | After |");
929
+ lines.push("| --- | --- |");
930
+ lines.push(`| ${beforeBaseline}: \`${markdownTableValue(beforeStatus)}\` | \`${markdownTableValue(afterStatus)}\` |`);
931
+ lines.push(`| ${canonicalScreenshotCell(receipt.canonical_pair.before, "No canonical before screenshot")} | ${canonicalScreenshotCell(receipt.canonical_pair.after, "No canonical after screenshot")} |`);
932
+ lines.push("", "### Source Binding", "");
933
+ lines.push("| Side | Target | Git revision | Binding | Preview | Digest |");
934
+ lines.push("| --- | --- | --- | --- | --- | --- |");
935
+ for (const side of ["before", "after"]) {
936
+ const observation = change[side];
937
+ const binding = change.source_bindings[side];
938
+ 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")}\` |`);
939
+ }
940
+ lines.push("", "### Declared Delta", "");
941
+ lines.push("| Measurement | Status | Before | After |");
942
+ lines.push("| --- | --- | --- | --- |");
943
+ for (const delta of change.deltas) {
944
+ lines.push(`| ${markdownTableValue(delta.label)} | ${delta.status} | ${markdownTableValue(delta.before_observed)} | ${markdownTableValue(delta.after_observed)} |`);
945
+ }
946
+ if (change.proves.length) {
947
+ lines.push("", "### What This Proves", "");
948
+ for (const claim of change.proves) lines.push(`- ${claim}`);
949
+ }
950
+ if (change.does_not_prove.length) {
951
+ lines.push("", "### Limits", "");
952
+ for (const claim of change.does_not_prove) lines.push(`- ${claim}`);
953
+ }
954
+ const linkedArtifacts = [
955
+ ...change.before.artifacts.map((artifact) => ({ side: "before", artifact })),
956
+ ...change.after.artifacts.map((artifact) => ({ side: "after", artifact }))
957
+ ].filter(({ artifact }) => Boolean(artifact.url));
958
+ if (linkedArtifacts.length) {
959
+ lines.push("", "### Artifacts", "");
960
+ for (const { side, artifact } of linkedArtifacts.slice(0, 24)) {
961
+ lines.push(`- ${side}: ${markdownLink(artifact.name, artifact.url || "")}`);
962
+ }
963
+ }
964
+ lines.push("", input.source?.trim() ? `_Source: ${input.source.trim()}_` : "_Rendered from the Handoff receipt without recomputing its verdict._");
965
+ return `${lines.join("\n").trim()}
966
+ `;
967
+ }
578
968
  function buildRiddleProofPrCommentMarkdown(input) {
969
+ const result = asRecord2(input.result);
970
+ if (isHandoffReceiptResult(result)) {
971
+ return buildRiddleProofHandoffPrCommentMarkdown(parseRiddleProofHandoffReceipt(result), input);
972
+ }
973
+ if (isChangeReceiptResult(result)) {
974
+ const handoff = createRiddleProofHandoffReceipt(parseRiddleProofChangeReceipt(result));
975
+ return buildRiddleProofHandoffPrCommentMarkdown(handoff, input);
976
+ }
579
977
  const summary = summarizeRiddleProofPrComment(input);
580
978
  const title = input.title?.trim() || "Riddle Proof Evidence";
581
979
  const lines = [
@@ -645,6 +1043,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
645
1043
  // Annotate the CommonJS export names for ESM import in node:
646
1044
  0 && (module.exports = {
647
1045
  RIDDLE_PROOF_PR_COMMENT_MARKER,
1046
+ buildRiddleProofHandoffPrCommentMarkdown,
648
1047
  buildRiddleProofPrCommentMarkdown,
649
1048
  summarizeRiddleProofPrComment
650
1049
  });
@@ -1,4 +1,8 @@
1
1
  import { RiddleProofPublicStateSummary } from './public-state.cjs';
2
+ import { RiddleProofHandoffReceipt } from './change-proof.cjs';
3
+ import './profile.cjs';
4
+ import './types.cjs';
5
+ import './receipts.cjs';
2
6
 
3
7
  declare const RIDDLE_PROOF_PR_COMMENT_MARKER = "<!-- riddle-proof:pr-comment:v1 -->";
4
8
  type RiddleProofPrCommentArtifactKind = "image" | "data" | "artifact";
@@ -58,6 +62,7 @@ interface RiddleProofPrCommentInput {
58
62
  source?: string;
59
63
  }
60
64
  declare function summarizeRiddleProofPrComment(input: RiddleProofPrCommentInput): RiddleProofPrCommentSummary;
65
+ declare function buildRiddleProofHandoffPrCommentMarkdown(handoff: RiddleProofHandoffReceipt, input?: Omit<RiddleProofPrCommentInput, "result">): string;
61
66
  declare function buildRiddleProofPrCommentMarkdown(input: RiddleProofPrCommentInput): string;
62
67
 
63
- export { RIDDLE_PROOF_PR_COMMENT_MARKER, type RiddleProofPrCommentArtifact, type RiddleProofPrCommentArtifactKind, type RiddleProofPrCommentCheckpointSummary, type RiddleProofPrCommentInput, type RiddleProofPrCommentPageSummary, type RiddleProofPrCommentSummary, buildRiddleProofPrCommentMarkdown, summarizeRiddleProofPrComment };
68
+ export { RIDDLE_PROOF_PR_COMMENT_MARKER, type RiddleProofPrCommentArtifact, type RiddleProofPrCommentArtifactKind, type RiddleProofPrCommentCheckpointSummary, type RiddleProofPrCommentInput, type RiddleProofPrCommentPageSummary, type RiddleProofPrCommentSummary, buildRiddleProofHandoffPrCommentMarkdown, buildRiddleProofPrCommentMarkdown, summarizeRiddleProofPrComment };
@@ -1,4 +1,8 @@
1
1
  import { RiddleProofPublicStateSummary } from './public-state.js';
2
+ import { RiddleProofHandoffReceipt } from './change-proof.js';
3
+ import './profile.js';
4
+ import './types.js';
5
+ import './receipts.js';
2
6
 
3
7
  declare const RIDDLE_PROOF_PR_COMMENT_MARKER = "<!-- riddle-proof:pr-comment:v1 -->";
4
8
  type RiddleProofPrCommentArtifactKind = "image" | "data" | "artifact";
@@ -58,6 +62,7 @@ interface RiddleProofPrCommentInput {
58
62
  source?: string;
59
63
  }
60
64
  declare function summarizeRiddleProofPrComment(input: RiddleProofPrCommentInput): RiddleProofPrCommentSummary;
65
+ declare function buildRiddleProofHandoffPrCommentMarkdown(handoff: RiddleProofHandoffReceipt, input?: Omit<RiddleProofPrCommentInput, "result">): string;
61
66
  declare function buildRiddleProofPrCommentMarkdown(input: RiddleProofPrCommentInput): string;
62
67
 
63
- export { RIDDLE_PROOF_PR_COMMENT_MARKER, type RiddleProofPrCommentArtifact, type RiddleProofPrCommentArtifactKind, type RiddleProofPrCommentCheckpointSummary, type RiddleProofPrCommentInput, type RiddleProofPrCommentPageSummary, type RiddleProofPrCommentSummary, buildRiddleProofPrCommentMarkdown, summarizeRiddleProofPrComment };
68
+ export { RIDDLE_PROOF_PR_COMMENT_MARKER, type RiddleProofPrCommentArtifact, type RiddleProofPrCommentArtifactKind, type RiddleProofPrCommentCheckpointSummary, type RiddleProofPrCommentInput, type RiddleProofPrCommentPageSummary, type RiddleProofPrCommentSummary, buildRiddleProofHandoffPrCommentMarkdown, buildRiddleProofPrCommentMarkdown, summarizeRiddleProofPrComment };
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  RIDDLE_PROOF_PR_COMMENT_MARKER,
3
+ buildRiddleProofHandoffPrCommentMarkdown,
3
4
  buildRiddleProofPrCommentMarkdown,
4
5
  summarizeRiddleProofPrComment
5
- } from "./chunk-IY4W6STC.js";
6
+ } from "./chunk-7N6X54WG.js";
7
+ import "./chunk-6VFS2JFR.js";
8
+ import "./chunk-MEVVL4TI.js";
9
+ import "./chunk-FCSJZBC5.js";
6
10
  import "./chunk-ZAR7BWMN.js";
7
11
  import "./chunk-MLKGABMK.js";
8
12
  export {
9
13
  RIDDLE_PROOF_PR_COMMENT_MARKER,
14
+ buildRiddleProofHandoffPrCommentMarkdown,
10
15
  buildRiddleProofPrCommentMarkdown,
11
16
  summarizeRiddleProofPrComment
12
17
  };
@@ -834,6 +834,12 @@ function profileScreenshotLabels(viewports) {
834
834
  }
835
835
  return labels;
836
836
  }
837
+ function profileFinalScreenshotLabels(viewports) {
838
+ return (viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
839
+ }
840
+ function profileAllSetupScreenshotLabels(viewports) {
841
+ return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport.setup_action_results || []));
842
+ }
837
843
  function profileSetupSummary(viewports, actionCount, expectedActionCountByViewport, finalScreenshotFullPage) {
838
844
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === void 0 ? void 0 : finalScreenshotFullPage !== false;
839
845
  const finalScreenshotCount = viewports.filter((viewport) => typeof viewport.screenshot_label === "string" && viewport.screenshot_label.trim()).length;
@@ -3684,6 +3690,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
3684
3690
  const status = profileStatusFromEvidence(profile, evidence, checks);
3685
3691
  const firstViewport = evidence?.viewports?.[0];
3686
3692
  const screenshots = profileScreenshotLabels(evidence?.viewports);
3693
+ const canonicalScreenshots = profileFinalScreenshotLabels(evidence?.viewports);
3694
+ const setupScreenshots = profileAllSetupScreenshotLabels(evidence?.viewports);
3687
3695
  const result = {
3688
3696
  version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
3689
3697
  profile_name: profile.name,
@@ -3693,6 +3701,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
3693
3701
  route: routeForViewport(firstViewport, evidence?.target_url),
3694
3702
  artifacts: {
3695
3703
  screenshots,
3704
+ canonical_screenshots: canonicalScreenshots,
3705
+ setup_screenshots: setupScreenshots,
3696
3706
  console: "console.json",
3697
3707
  proof_json: "proof.json",
3698
3708
  dom_summary: "dom-summary.json",
@@ -5011,6 +5021,12 @@ function profileScreenshotLabels(viewports) {
5011
5021
  }
5012
5022
  return labels;
5013
5023
  }
5024
+ function profileFinalScreenshotLabels(viewports) {
5025
+ return (viewports || []).map((viewport) => viewport && viewport.screenshot_label).filter(Boolean);
5026
+ }
5027
+ function profileAllSetupScreenshotLabels(viewports) {
5028
+ return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport && viewport.setup_action_results || []));
5029
+ }
5014
5030
  function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewport, finalScreenshotFullPage) {
5015
5031
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === undefined
5016
5032
  ? undefined
@@ -5879,6 +5895,8 @@ function assessProfile(profile, evidence) {
5879
5895
  else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
5880
5896
  else if (checks.some((check) => check.status === "failed")) status = "product_regression";
5881
5897
  const screenshotLabels = profileScreenshotLabels(viewports);
5898
+ const canonicalScreenshotLabels = profileFinalScreenshotLabels(viewports);
5899
+ const setupScreenshotLabels = profileAllSetupScreenshotLabels(viewports);
5882
5900
  const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
5883
5901
  const passedChecks = checks.filter((check) => check.status === "passed").length;
5884
5902
  const failedChecks = checks.filter((check) => check.status === "failed").length;
@@ -5895,7 +5913,14 @@ function assessProfile(profile, evidence) {
5895
5913
  status,
5896
5914
  baseline_policy: profile.baseline_policy || "invariant_only",
5897
5915
  route,
5898
- artifacts: { screenshots: screenshotLabels, console: "console.json", proof_json: "proof.json", dom_summary: "dom-summary.json" },
5916
+ artifacts: {
5917
+ screenshots: screenshotLabels,
5918
+ canonical_screenshots: canonicalScreenshotLabels,
5919
+ setup_screenshots: setupScreenshotLabels,
5920
+ console: "console.json",
5921
+ proof_json: "proof.json",
5922
+ dom_summary: "dom-summary.json"
5923
+ },
5899
5924
  checks,
5900
5925
  summary,
5901
5926
  captured_at: evidence.captured_at,
@@ -27,7 +27,7 @@ import {
27
27
  resolveRiddleProofProfileTimeoutSec,
28
28
  slugifyRiddleProofProfileName,
29
29
  summarizeRiddleProofProfileResult
30
- } from "../chunk-GG2D3MFZ.js";
30
+ } from "../chunk-FCSJZBC5.js";
31
31
  import "../chunk-MLKGABMK.js";
32
32
  export {
33
33
  RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS,