@riddledc/riddle-proof 0.8.76 → 0.8.78

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 (56) hide show
  1. package/README.md +74 -5
  2. package/dist/advanced/index.d.cts +1 -1
  3. package/dist/advanced/index.d.ts +1 -1
  4. package/dist/advanced/proof-run-engine.d.cts +1 -1
  5. package/dist/advanced/proof-run-engine.d.ts +1 -1
  6. package/dist/change-proof.cjs +991 -7
  7. package/dist/change-proof.d.cts +157 -1
  8. package/dist/change-proof.d.ts +157 -1
  9. package/dist/change-proof.js +25 -3
  10. package/dist/{chunk-B2DP2LET.js → chunk-5IFZSUPF.js} +52 -13
  11. package/dist/chunk-6VFS2JFR.js +886 -0
  12. package/dist/{chunk-CWRIXP5H.js → chunk-7N6X54WG.js} +181 -17
  13. package/dist/{chunk-GG2D3MFZ.js → chunk-FCSJZBC5.js} +26 -1
  14. package/dist/{chunk-AK2EPEVO.js → chunk-HSGZV2NK.js} +221 -63
  15. package/dist/chunk-MEVVL4TI.js +258 -0
  16. package/dist/{chunk-UE4I7RTI.js → chunk-RQPCKRKT.js} +1 -1
  17. package/dist/cli/index.js +7 -6
  18. package/dist/cli.cjs +1678 -361
  19. package/dist/cli.js +7 -6
  20. package/dist/index.cjs +1242 -42
  21. package/dist/index.d.cts +4 -3
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +46 -10
  24. package/dist/pr-comment.cjs +481 -17
  25. package/dist/pr-comment.d.cts +6 -1
  26. package/dist/pr-comment.d.ts +6 -1
  27. package/dist/pr-comment.js +6 -1
  28. package/dist/profile/index.cjs +26 -1
  29. package/dist/profile/index.js +1 -1
  30. package/dist/profile-suggestions.js +2 -2
  31. package/dist/profile.cjs +26 -1
  32. package/dist/profile.d.cts +7 -0
  33. package/dist/profile.d.ts +7 -0
  34. package/dist/profile.js +1 -1
  35. package/dist/{proof-run-engine-MiKZt9oY.d.ts → proof-run-engine-CsQshTUX.d.ts} +3 -3
  36. package/dist/{proof-run-engine-Baiv6l3A.d.cts → proof-run-engine-DmUqh7Cw.d.cts} +3 -3
  37. package/dist/proof-run-engine.d.cts +1 -1
  38. package/dist/proof-run-engine.d.ts +1 -1
  39. package/dist/receipts.cjs +286 -0
  40. package/dist/receipts.d.cts +115 -0
  41. package/dist/receipts.d.ts +115 -0
  42. package/dist/receipts.js +15 -0
  43. package/dist/riddle-client.cjs +98 -13
  44. package/dist/riddle-client.d.cts +18 -5
  45. package/dist/riddle-client.d.ts +18 -5
  46. package/dist/riddle-client.js +4 -1
  47. package/dist/runtime/index.cjs +98 -13
  48. package/dist/runtime/index.d.cts +5 -1
  49. package/dist/runtime/index.d.ts +5 -1
  50. package/dist/runtime/index.js +4 -1
  51. package/dist/runtime/riddle-client.cjs +98 -13
  52. package/dist/runtime/riddle-client.d.cts +5 -1
  53. package/dist/runtime/riddle-client.d.ts +5 -1
  54. package/dist/runtime/riddle-client.js +4 -1
  55. package/package.json +7 -2
  56. package/dist/chunk-6S7DZWVC.js +0 -167
package/dist/index.cjs CHANGED
@@ -3414,15 +3414,20 @@ __export(index_exports, {
3414
3414
  DEFAULT_DIAGNOSTIC_STRING_LIMIT: () => DEFAULT_DIAGNOSTIC_STRING_LIMIT,
3415
3415
  DEFAULT_RIDDLE_API_BASE_URL: () => DEFAULT_RIDDLE_API_BASE_URL,
3416
3416
  DEFAULT_RIDDLE_API_KEY_FILE: () => DEFAULT_RIDDLE_API_KEY_FILE,
3417
+ RIDDLE_PREVIEW_RECEIPT_VERSION: () => RIDDLE_PREVIEW_RECEIPT_VERSION,
3417
3418
  RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
3418
3419
  RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
3419
3420
  RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION: () => RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
3420
3421
  RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION: () => RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION,
3421
3422
  RIDDLE_PROOF_CHANGE_CONTRACT_VERSION: () => RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
3423
+ RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION: () => RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION,
3424
+ RIDDLE_PROOF_CHANGE_RECEIPT_VERSION: () => RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
3422
3425
  RIDDLE_PROOF_CHANGE_RESULT_VERSION: () => RIDDLE_PROOF_CHANGE_RESULT_VERSION,
3423
3426
  RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION: () => RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
3424
3427
  RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION: () => RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
3428
+ RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION: () => RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
3425
3429
  RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS: () => RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS,
3430
+ RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION: () => RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
3426
3431
  RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION: () => RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
3427
3432
  RIDDLE_PROOF_PLAYABILITY_VERSION: () => RIDDLE_PROOF_PLAYABILITY_VERSION,
3428
3433
  RIDDLE_PROOF_PROFILE_CHECK_TYPES: () => RIDDLE_PROOF_PROFILE_CHECK_TYPES,
@@ -3461,6 +3466,7 @@ __export(index_exports, {
3461
3466
  buildAuthorCheckpointPacket: () => buildAuthorCheckpointPacket,
3462
3467
  buildCheckpointPacketForEngineResult: () => buildCheckpointPacketForEngineResult,
3463
3468
  buildProofAssessmentCheckpointPacket: () => buildProofAssessmentCheckpointPacket,
3469
+ buildRiddleProofHandoffPrCommentMarkdown: () => buildRiddleProofHandoffPrCommentMarkdown,
3464
3470
  buildRiddleProofPrCommentMarkdown: () => buildRiddleProofPrCommentMarkdown,
3465
3471
  buildRiddleProofProfileScript: () => buildRiddleProofProfileScript,
3466
3472
  buildStageCheckpointPacket: () => buildStageCheckpointPacket,
@@ -3484,6 +3490,9 @@ __export(index_exports, {
3484
3490
  createLocalAgentAdapter: () => createCodexExecAgentAdapter,
3485
3491
  createLocalAgentJsonRunner: () => createCodexExecJsonRunner,
3486
3492
  createRiddleApiClient: () => createRiddleApiClient,
3493
+ createRiddleProofChangeReceipt: () => createRiddleProofChangeReceipt,
3494
+ createRiddleProofHandoffReceipt: () => createRiddleProofHandoffReceipt,
3495
+ createRiddleProofObservationReceipt: () => createRiddleProofObservationReceipt,
3487
3496
  createRiddleProofProfileConfigurationError: () => createRiddleProofProfileConfigurationError,
3488
3497
  createRiddleProofProfileEnvironmentBlockedResult: () => createRiddleProofProfileEnvironmentBlockedResult,
3489
3498
  createRiddleProofProfileInsufficientResult: () => createRiddleProofProfileInsufficientResult,
@@ -3494,6 +3503,7 @@ __export(index_exports, {
3494
3503
  deployRiddlePreview: () => deployRiddlePreview,
3495
3504
  deployRiddleStaticPreview: () => deployRiddleStaticPreview,
3496
3505
  deriveRiddleProofArtifactBodyAssertions: () => deriveRiddleProofArtifactBodyAssertions,
3506
+ detectRiddlePreviewSource: () => detectRiddlePreviewSource,
3497
3507
  extractBasicGameplayEvidence: () => extractBasicGameplayEvidence,
3498
3508
  extractPlayabilityEvidence: () => extractPlayabilityEvidence,
3499
3509
  extractRiddleProofProfileResult: () => extractRiddleProofProfileResult,
@@ -3504,6 +3514,7 @@ __export(index_exports, {
3504
3514
  isSuccessfulStatus: () => isSuccessfulStatus,
3505
3515
  isTerminalRiddleJobStatus: () => isTerminalRiddleJobStatus,
3506
3516
  isTerminalStatus: () => isTerminalStatus,
3517
+ migrateRiddleProofChangeReceipt: () => migrateRiddleProofChangeReceipt,
3507
3518
  nonEmptyString: () => nonEmptyString,
3508
3519
  normalizeCheckpointResponse: () => normalizeCheckpointResponse,
3509
3520
  normalizeIntegrationContext: () => normalizeIntegrationContext,
@@ -3511,6 +3522,10 @@ __export(index_exports, {
3511
3522
  normalizeRiddleProofProfile: () => normalizeRiddleProofProfile,
3512
3523
  normalizeRunParams: () => normalizeRunParams,
3513
3524
  normalizeTerminalMetadata: () => normalizeTerminalMetadata,
3525
+ parseRiddlePreviewReceipt: () => parseRiddlePreviewReceipt,
3526
+ parseRiddleProofChangeReceipt: () => parseRiddleProofChangeReceipt,
3527
+ parseRiddleProofHandoffReceipt: () => parseRiddleProofHandoffReceipt,
3528
+ parseRiddleProofObservationReceipt: () => parseRiddleProofObservationReceipt,
3514
3529
  parseRiddleViewport: () => parseRiddleViewport,
3515
3530
  parseVisualProofSession: () => parseVisualProofSession,
3516
3531
  pollRiddleJob: () => pollRiddleJob,
@@ -3529,6 +3544,8 @@ __export(index_exports, {
3529
3544
  resolveRiddleProofProfileRouteUrl: () => resolveRiddleProofProfileRouteUrl,
3530
3545
  resolveRiddleProofProfileTargetUrl: () => resolveRiddleProofProfileTargetUrl,
3531
3546
  resolveRiddleProofProfileTimeoutSec: () => resolveRiddleProofProfileTimeoutSec,
3547
+ riddleProofChangeReceiptHtml: () => riddleProofChangeReceiptHtml,
3548
+ riddleProofChangeReceiptMarkdown: () => riddleProofChangeReceiptMarkdown,
3532
3549
  riddleProofPublicStateAllowsClaim: () => riddleProofPublicStateAllowsClaim,
3533
3550
  riddleProofPublicStateAllowsMergeRecommendation: () => riddleProofPublicStateAllowsMergeRecommendation,
3534
3551
  riddleProofPublicStateMergeRecommendation: () => riddleProofPublicStateMergeRecommendation,
@@ -10943,6 +10960,12 @@ function profileScreenshotLabels(viewports) {
10943
10960
  }
10944
10961
  return labels;
10945
10962
  }
10963
+ function profileFinalScreenshotLabels(viewports) {
10964
+ return (viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
10965
+ }
10966
+ function profileAllSetupScreenshotLabels(viewports) {
10967
+ return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport.setup_action_results || []));
10968
+ }
10946
10969
  function profileSetupSummary(viewports, actionCount, expectedActionCountByViewport, finalScreenshotFullPage) {
10947
10970
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === void 0 ? void 0 : finalScreenshotFullPage !== false;
10948
10971
  const finalScreenshotCount = viewports.filter((viewport) => typeof viewport.screenshot_label === "string" && viewport.screenshot_label.trim()).length;
@@ -13793,6 +13816,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
13793
13816
  const status = profileStatusFromEvidence(profile, evidence, checks);
13794
13817
  const firstViewport = evidence?.viewports?.[0];
13795
13818
  const screenshots = profileScreenshotLabels(evidence?.viewports);
13819
+ const canonicalScreenshots = profileFinalScreenshotLabels(evidence?.viewports);
13820
+ const setupScreenshots = profileAllSetupScreenshotLabels(evidence?.viewports);
13796
13821
  const result = {
13797
13822
  version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
13798
13823
  profile_name: profile.name,
@@ -13802,6 +13827,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
13802
13827
  route: routeForViewport(firstViewport, evidence?.target_url),
13803
13828
  artifacts: {
13804
13829
  screenshots,
13830
+ canonical_screenshots: canonicalScreenshots,
13831
+ setup_screenshots: setupScreenshots,
13805
13832
  console: "console.json",
13806
13833
  proof_json: "proof.json",
13807
13834
  dom_summary: "dom-summary.json",
@@ -15120,6 +15147,12 @@ function profileScreenshotLabels(viewports) {
15120
15147
  }
15121
15148
  return labels;
15122
15149
  }
15150
+ function profileFinalScreenshotLabels(viewports) {
15151
+ return (viewports || []).map((viewport) => viewport && viewport.screenshot_label).filter(Boolean);
15152
+ }
15153
+ function profileAllSetupScreenshotLabels(viewports) {
15154
+ return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport && viewport.setup_action_results || []));
15155
+ }
15123
15156
  function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewport, finalScreenshotFullPage) {
15124
15157
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === undefined
15125
15158
  ? undefined
@@ -15988,6 +16021,8 @@ function assessProfile(profile, evidence) {
15988
16021
  else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
15989
16022
  else if (checks.some((check) => check.status === "failed")) status = "product_regression";
15990
16023
  const screenshotLabels = profileScreenshotLabels(viewports);
16024
+ const canonicalScreenshotLabels = profileFinalScreenshotLabels(viewports);
16025
+ const setupScreenshotLabels = profileAllSetupScreenshotLabels(viewports);
15991
16026
  const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
15992
16027
  const passedChecks = checks.filter((check) => check.status === "passed").length;
15993
16028
  const failedChecks = checks.filter((check) => check.status === "failed").length;
@@ -16004,7 +16039,14 @@ function assessProfile(profile, evidence) {
16004
16039
  status,
16005
16040
  baseline_policy: profile.baseline_policy || "invariant_only",
16006
16041
  route,
16007
- artifacts: { screenshots: screenshotLabels, console: "console.json", proof_json: "proof.json", dom_summary: "dom-summary.json" },
16042
+ artifacts: {
16043
+ screenshots: screenshotLabels,
16044
+ canonical_screenshots: canonicalScreenshotLabels,
16045
+ setup_screenshots: setupScreenshotLabels,
16046
+ console: "console.json",
16047
+ proof_json: "proof.json",
16048
+ dom_summary: "dom-summary.json"
16049
+ },
16008
16050
  checks,
16009
16051
  summary,
16010
16052
  captured_at: evidence.captured_at,
@@ -19932,9 +19974,263 @@ function suggestRiddleProofProfileChecks(input) {
19932
19974
  };
19933
19975
  }
19934
19976
 
19977
+ // src/receipts.ts
19978
+ var RIDDLE_PREVIEW_RECEIPT_VERSION = "riddle.preview-receipt.v1";
19979
+ var RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION = "riddle-proof.observation-receipt.v1";
19980
+ function isRecord3(value) {
19981
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19982
+ }
19983
+ function requiredString(record, key, context) {
19984
+ const value = record[key];
19985
+ if (typeof value !== "string" || !value.trim()) {
19986
+ throw new Error(`${context}.${key} must be a non-empty string.`);
19987
+ }
19988
+ return value.trim();
19989
+ }
19990
+ function normalizedSourceIdentity(value) {
19991
+ if (!isRecord3(value)) return {};
19992
+ return {
19993
+ git_revision: typeof value.git_revision === "string" && value.git_revision.trim() ? value.git_revision.trim() : void 0,
19994
+ repository: typeof value.repository === "string" && value.repository.trim() ? value.repository.trim() : void 0,
19995
+ dirty: typeof value.dirty === "boolean" ? value.dirty : void 0,
19996
+ label: typeof value.label === "string" && value.label.trim() ? value.label.trim() : void 0
19997
+ };
19998
+ }
19999
+ function parseRiddlePreviewReceipt(value) {
20000
+ if (!isRecord3(value)) throw new Error("Preview receipt must be an object.");
20001
+ if (value.version !== RIDDLE_PREVIEW_RECEIPT_VERSION) {
20002
+ throw new Error(`Unsupported Preview receipt version ${String(value.version || "missing")}.`);
20003
+ }
20004
+ if (!isRecord3(value.source)) throw new Error("Preview receipt source must be an object.");
20005
+ const expiresAt = requiredString(value, "expires_at", "preview receipt");
20006
+ const publishedAt = requiredString(value, "published_at", "preview receipt");
20007
+ const contentDigest = requiredString(value, "content_digest", "preview receipt");
20008
+ if (!contentDigest.startsWith("sha256:") || contentDigest.length <= "sha256:".length) {
20009
+ throw new Error("preview receipt.content_digest must be a sha256 digest.");
20010
+ }
20011
+ if (!Number.isFinite(Date.parse(expiresAt)) || !Number.isFinite(Date.parse(publishedAt))) {
20012
+ throw new Error("Preview receipt timestamps must be valid ISO date strings.");
20013
+ }
20014
+ return {
20015
+ version: RIDDLE_PREVIEW_RECEIPT_VERSION,
20016
+ preview_id: requiredString(value, "preview_id", "preview receipt"),
20017
+ url: requiredString(value, "url", "preview receipt"),
20018
+ expires_at: expiresAt,
20019
+ content_digest: contentDigest,
20020
+ source: normalizedSourceIdentity(value.source),
20021
+ published_at: publishedAt
20022
+ };
20023
+ }
20024
+ function comparableArtifactName(value) {
20025
+ return (value || "").split(/[/?#]/).pop()?.toLowerCase().replace(/\.(png|jpe?g|gif|webp|avif|svg)$/u, "").replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/g, "") || "";
20026
+ }
20027
+ function artifactMatchesLabel(artifact, label) {
20028
+ const expected = comparableArtifactName(label);
20029
+ if (!expected) return false;
20030
+ return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === expected || name.endsWith(`-${expected}`));
20031
+ }
20032
+ function artifactLooksLikeScreenshot(ref) {
20033
+ const text = [ref.name, ref.url, ref.path, ref.kind, ref.content_type].filter(Boolean).join(" ").toLowerCase();
20034
+ return /screenshot|\bimage\b/.test(text) || /\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(text);
20035
+ }
20036
+ function finalScreenshotLabels(result) {
20037
+ const explicit = result.artifacts.canonical_screenshots || [];
20038
+ if (explicit.length) return explicit;
20039
+ const fromEvidence = (result.evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
20040
+ if (fromEvidence.length) return fromEvidence;
20041
+ return (result.artifacts.screenshots || []).slice(0, 1);
20042
+ }
20043
+ function setupScreenshotLabels(result) {
20044
+ const explicit = result.artifacts.setup_screenshots || [];
20045
+ if (explicit.length) return explicit;
20046
+ return (result.evidence?.viewports || []).flatMap(
20047
+ (viewport) => (viewport.setup_action_results || []).filter((action) => action.action === "screenshot" && action.ok !== false && typeof action.screenshot_label === "string").map((action) => String(action.screenshot_label))
20048
+ );
20049
+ }
20050
+ function observationArtifactFromRef(ref, canonicalLabels, setupLabels) {
20051
+ const base = {
20052
+ name: ref.name,
20053
+ role: ref.role || "artifact",
20054
+ url: ref.url,
20055
+ path: ref.path,
20056
+ kind: ref.kind,
20057
+ content_type: ref.content_type,
20058
+ source: ref.source
20059
+ };
20060
+ if (!artifactLooksLikeScreenshot(ref)) return base;
20061
+ if (canonicalLabels.some((label) => artifactMatchesLabel(base, label))) {
20062
+ return { ...base, role: "canonical_screenshot" };
20063
+ }
20064
+ if (setupLabels.some((label) => artifactMatchesLabel(base, label))) {
20065
+ return { ...base, role: "setup_screenshot" };
20066
+ }
20067
+ return { ...base, role: "diagnostic" };
20068
+ }
20069
+ function profileObservationArtifacts(result) {
20070
+ const canonicalLabels = finalScreenshotLabels(result);
20071
+ const setupLabels = setupScreenshotLabels(result);
20072
+ const artifacts = [];
20073
+ const seen = /* @__PURE__ */ new Set();
20074
+ const add = (artifact) => {
20075
+ const key = artifact.url || artifact.path || `${artifact.role}:${artifact.name}`;
20076
+ if (!artifact.name || seen.has(key)) return;
20077
+ seen.add(key);
20078
+ artifacts.push(artifact);
20079
+ };
20080
+ for (const ref of result.artifacts.riddle_artifacts || []) {
20081
+ add(observationArtifactFromRef(ref, canonicalLabels, setupLabels));
20082
+ }
20083
+ for (const label of canonicalLabels) {
20084
+ if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
20085
+ add({ name: label, path: label, role: "canonical_screenshot", kind: "image" });
20086
+ }
20087
+ }
20088
+ for (const label of setupLabels) {
20089
+ if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
20090
+ add({ name: label, path: label, role: "setup_screenshot", kind: "image" });
20091
+ }
20092
+ }
20093
+ for (const label of result.artifacts.screenshots || []) {
20094
+ if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
20095
+ add({ name: label, path: label, role: "diagnostic", kind: "image" });
20096
+ }
20097
+ }
20098
+ return artifacts;
20099
+ }
20100
+ function mergeObservationArtifacts(profileArtifacts, suppliedArtifacts) {
20101
+ const artifacts = [...profileArtifacts];
20102
+ for (const supplied of suppliedArtifacts) {
20103
+ const suppliedNames = [supplied.name, supplied.url, supplied.path].map(comparableArtifactName).filter(Boolean);
20104
+ const existingIndex = artifacts.findIndex((artifact) => {
20105
+ if (supplied.url && artifact.url === supplied.url) return true;
20106
+ if (supplied.path && artifact.path === supplied.path) return true;
20107
+ return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name && suppliedNames.includes(name));
20108
+ });
20109
+ if (existingIndex < 0) {
20110
+ artifacts.push(supplied);
20111
+ continue;
20112
+ }
20113
+ const existing = artifacts[existingIndex];
20114
+ 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;
20115
+ artifacts[existingIndex] = {
20116
+ ...existing,
20117
+ ...supplied,
20118
+ role
20119
+ };
20120
+ }
20121
+ return artifacts;
20122
+ }
20123
+ function defaultObservationId(role, capturedAt) {
20124
+ return `obs_${role}_${capturedAt.replace(/[^0-9]/g, "").slice(0, 17) || "unknown"}`;
20125
+ }
20126
+ function profileCheckCounts(result) {
20127
+ const counts = { total: result.checks.length, passed: 0, failed: 0, skipped: 0, needs_human_review: 0 };
20128
+ for (const check of result.checks) {
20129
+ if (check.status === "passed") counts.passed += 1;
20130
+ if (check.status === "failed") counts.failed += 1;
20131
+ if (check.status === "skipped") counts.skipped += 1;
20132
+ if (check.status === "needs_human_review") counts.needs_human_review += 1;
20133
+ }
20134
+ return counts;
20135
+ }
20136
+ function createRiddleProofObservationReceipt(input) {
20137
+ const capturedAt = input.captured_at || input.profile_result?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
20138
+ const profileArtifacts = input.profile_result ? profileObservationArtifacts(input.profile_result) : [];
20139
+ const requestedCanonical = input.canonical_screenshot ? { ...input.canonical_screenshot, role: "canonical_screenshot" } : void 0;
20140
+ const artifacts = mergeObservationArtifacts(
20141
+ profileArtifacts,
20142
+ [...input.artifacts || [], ...requestedCanonical ? [requestedCanonical] : []]
20143
+ );
20144
+ const canonicalScreenshot = requestedCanonical ? artifacts.find((artifact) => artifact.role === "canonical_screenshot" && artifactMatchesLabel(artifact, requestedCanonical.name)) || requestedCanonical : artifacts.find((artifact) => artifact.role === "canonical_screenshot");
20145
+ const target = input.target.kind === "preview" && input.target.preview ? { ...input.target, preview: parseRiddlePreviewReceipt(input.target.preview) } : input.target;
20146
+ return {
20147
+ version: RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
20148
+ observation_id: input.observation_id || defaultObservationId(input.comparison_role, capturedAt),
20149
+ comparison_role: input.comparison_role,
20150
+ executor: input.executor,
20151
+ target,
20152
+ source: normalizedSourceIdentity(input.source || input.target.preview?.source),
20153
+ captured_at: capturedAt,
20154
+ artifacts,
20155
+ canonical_screenshot: canonicalScreenshot,
20156
+ profile_summary: input.profile_result ? {
20157
+ profile_name: input.profile_result.profile_name,
20158
+ status: input.profile_result.status,
20159
+ summary: input.profile_result.summary,
20160
+ route: input.profile_result.route,
20161
+ checks: profileCheckCounts(input.profile_result)
20162
+ } : void 0,
20163
+ proof: input.profile_result ? {
20164
+ profile_name: input.profile_result.profile_name,
20165
+ result: input.profile_result
20166
+ } : void 0,
20167
+ publication: input.publication,
20168
+ execution: input.execution,
20169
+ metadata: input.metadata
20170
+ };
20171
+ }
20172
+ function parseRiddleProofObservationReceipt(value) {
20173
+ if (!isRecord3(value)) throw new Error("Observation receipt must be an object.");
20174
+ if (value.version !== RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION) {
20175
+ throw new Error(`Unsupported Observation receipt version ${String(value.version || "missing")}.`);
20176
+ }
20177
+ if (!isRecord3(value.executor)) throw new Error("Observation receipt executor must be an object.");
20178
+ if (!isRecord3(value.target)) throw new Error("Observation receipt target must be an object.");
20179
+ if (!isRecord3(value.source)) throw new Error("Observation receipt source must be an object.");
20180
+ requiredString(value, "observation_id", "observation receipt");
20181
+ requiredString(value, "captured_at", "observation receipt");
20182
+ const executorKind = requiredString(value.executor, "kind", "observation receipt executor");
20183
+ if (!"local_playwright,riddle_hosted,browser_api,other".split(",").includes(executorKind)) {
20184
+ throw new Error(`Unsupported observation executor kind ${executorKind}.`);
20185
+ }
20186
+ if (value.executor.runner !== void 0) {
20187
+ requiredString(value.executor, "runner", "observation receipt executor");
20188
+ }
20189
+ const role = requiredString(value, "comparison_role", "observation receipt");
20190
+ if (!["before", "after", "standalone"].includes(role)) {
20191
+ throw new Error(`Unsupported observation comparison role ${role}.`);
20192
+ }
20193
+ const targetKind = requiredString(value.target, "kind", "observation receipt target");
20194
+ if (targetKind !== "url" && targetKind !== "preview") {
20195
+ throw new Error(`Unsupported observation target kind ${targetKind}.`);
20196
+ }
20197
+ requiredString(value.target, "url", "observation receipt target");
20198
+ if (targetKind === "preview") {
20199
+ if (value.target.preview === void 0) {
20200
+ throw new Error("Preview Observation target must include its Preview receipt.");
20201
+ }
20202
+ parseRiddlePreviewReceipt(value.target.preview);
20203
+ }
20204
+ if (!Array.isArray(value.artifacts)) throw new Error("Observation receipt artifacts must be an array.");
20205
+ const artifactRoles = /* @__PURE__ */ new Set(["canonical_screenshot", "setup_screenshot", "data", "diagnostic", "artifact"]);
20206
+ const artifacts = value.artifacts.map((artifact, index) => {
20207
+ if (!isRecord3(artifact)) throw new Error(`Observation receipt artifact ${index} must be an object.`);
20208
+ requiredString(artifact, "name", `observation receipt artifact ${index}`);
20209
+ const artifactRole = requiredString(artifact, "role", `observation receipt artifact ${index}`);
20210
+ if (!artifactRoles.has(artifactRole)) {
20211
+ throw new Error(`Unsupported observation artifact role ${artifactRole}.`);
20212
+ }
20213
+ return artifact;
20214
+ });
20215
+ if (value.canonical_screenshot !== void 0) {
20216
+ if (!isRecord3(value.canonical_screenshot) || value.canonical_screenshot.role !== "canonical_screenshot") {
20217
+ throw new Error("Observation canonical_screenshot must have the canonical_screenshot role.");
20218
+ }
20219
+ const canonical = value.canonical_screenshot;
20220
+ const included = artifacts.some((artifact) => artifact.role === "canonical_screenshot" && artifact.name === canonical.name && artifact.url === canonical.url && artifact.path === canonical.path);
20221
+ if (!included) {
20222
+ throw new Error("Observation canonical_screenshot must reference an artifact in the receipt.");
20223
+ }
20224
+ }
20225
+ return value;
20226
+ }
20227
+
19935
20228
  // src/change-proof.ts
19936
20229
  var RIDDLE_PROOF_CHANGE_CONTRACT_VERSION = "riddle-proof.change-contract.v1";
19937
20230
  var RIDDLE_PROOF_CHANGE_RESULT_VERSION = "riddle-proof.change-result.v1";
20231
+ var RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION = "riddle-proof.change-receipt.v1";
20232
+ var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.v2";
20233
+ var RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION = "riddle-proof.handoff-receipt.v1";
19938
20234
  var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
19939
20235
  var DEFAULT_AFTER_STATUSES = ["passed"];
19940
20236
  var DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE = ["product_regression"];
@@ -20056,13 +20352,163 @@ function assessDelta(delta, before, after) {
20056
20352
  }
20057
20353
  return checkStatusTransitionResult(delta, before, after);
20058
20354
  }
20059
- function collapsedChangeStatus(groups, deltas) {
20355
+ function profileResultFromObservation(observation) {
20356
+ return observation?.proof?.result;
20357
+ }
20358
+ function previewReceiptCoversTarget(previewUrl, targetUrl) {
20359
+ try {
20360
+ const preview = new URL(previewUrl);
20361
+ const target = new URL(targetUrl);
20362
+ const previewPath = preview.pathname.replace(/\/+$/u, "");
20363
+ return preview.origin === target.origin && (target.pathname === previewPath || target.pathname.startsWith(`${previewPath}/`));
20364
+ } catch {
20365
+ return false;
20366
+ }
20367
+ }
20368
+ function sourceBindingResult(side, requirement, observation, expectedRevision, evaluatedAt) {
20369
+ const expected = expectedRevision || requirement?.expected_git_revision;
20370
+ const required = Boolean(
20371
+ requirement?.preview_receipt_required || requirement?.require_clean_source || requirement?.require_content_digest || requirement?.expected_git_revision || expected
20372
+ );
20373
+ if (!required) return { side, required: false, ok: true, status: "not_required" };
20374
+ if (!observation) {
20375
+ return {
20376
+ side,
20377
+ required: true,
20378
+ ok: false,
20379
+ status: "missing",
20380
+ expected_git_revision: expected,
20381
+ message: `${side} source binding requires an Observation receipt.`
20382
+ };
20383
+ }
20384
+ const preview = observation.target.preview;
20385
+ if (requirement?.preview_receipt_required && !preview) {
20386
+ return {
20387
+ side,
20388
+ required: true,
20389
+ ok: false,
20390
+ status: "missing",
20391
+ expected_git_revision: expected,
20392
+ message: `${side} source binding requires a Preview receipt.`
20393
+ };
20394
+ }
20395
+ if (preview && !previewReceiptCoversTarget(preview.url, observation.target.url)) {
20396
+ return {
20397
+ side,
20398
+ required: true,
20399
+ ok: false,
20400
+ status: "mismatched",
20401
+ expected_git_revision: expected,
20402
+ observed_git_revision: preview.source.git_revision || observation.source.git_revision,
20403
+ content_digest: preview.content_digest,
20404
+ preview_id: preview.preview_id,
20405
+ preview_url: preview.url,
20406
+ target_url: observation.target.url,
20407
+ message: `${side} observation target is not contained by Preview ${preview.preview_id}.`
20408
+ };
20409
+ }
20410
+ const observed = preview?.source.git_revision || observation.source.git_revision;
20411
+ const digestRequired = requirement?.require_content_digest === true || requirement?.preview_receipt_required === true;
20412
+ if (digestRequired && !preview?.content_digest) {
20413
+ return {
20414
+ side,
20415
+ required: true,
20416
+ ok: false,
20417
+ status: "missing",
20418
+ expected_git_revision: expected,
20419
+ observed_git_revision: observed,
20420
+ preview_id: preview?.preview_id,
20421
+ preview_url: preview?.url,
20422
+ target_url: observation.target.url,
20423
+ message: `${side} Preview receipt is missing a content digest.`
20424
+ };
20425
+ }
20426
+ if (expected && !observed) {
20427
+ return {
20428
+ side,
20429
+ required: true,
20430
+ ok: false,
20431
+ status: "missing",
20432
+ expected_git_revision: expected,
20433
+ content_digest: preview?.content_digest,
20434
+ preview_id: preview?.preview_id,
20435
+ preview_url: preview?.url,
20436
+ target_url: observation.target.url,
20437
+ message: `${side} evidence is missing its source Git revision.`
20438
+ };
20439
+ }
20440
+ if (expected && observed !== expected) {
20441
+ return {
20442
+ side,
20443
+ required: true,
20444
+ ok: false,
20445
+ status: "mismatched",
20446
+ expected_git_revision: expected,
20447
+ observed_git_revision: observed,
20448
+ content_digest: preview?.content_digest,
20449
+ preview_id: preview?.preview_id,
20450
+ preview_url: preview?.url,
20451
+ target_url: observation.target.url,
20452
+ message: `${side} evidence came from Git revision ${observed}, not expected revision ${expected}.`
20453
+ };
20454
+ }
20455
+ const cleanRequired = requirement?.require_clean_source === true || requirement?.preview_receipt_required === true;
20456
+ const dirty = preview?.source.dirty ?? observation.source.dirty;
20457
+ if (cleanRequired && dirty !== false) {
20458
+ return {
20459
+ side,
20460
+ required: true,
20461
+ ok: false,
20462
+ status: dirty === true ? "mismatched" : "missing",
20463
+ expected_git_revision: expected,
20464
+ observed_git_revision: observed,
20465
+ content_digest: preview?.content_digest,
20466
+ preview_id: preview?.preview_id,
20467
+ preview_url: preview?.url,
20468
+ target_url: observation.target.url,
20469
+ message: dirty === true ? `${side} evidence was built from a dirty worktree and is not bound only to its Git revision.` : `${side} evidence does not record whether its source worktree was clean.`
20470
+ };
20471
+ }
20472
+ const evaluatedMs = Date.parse(evaluatedAt);
20473
+ const expiresMs = preview?.expires_at ? Date.parse(preview.expires_at) : Number.NaN;
20474
+ if (preview && Number.isFinite(evaluatedMs) && Number.isFinite(expiresMs) && expiresMs <= evaluatedMs) {
20475
+ return {
20476
+ side,
20477
+ required: true,
20478
+ ok: false,
20479
+ status: "stale",
20480
+ expected_git_revision: expected,
20481
+ observed_git_revision: observed,
20482
+ content_digest: preview.content_digest,
20483
+ preview_id: preview.preview_id,
20484
+ preview_url: preview.url,
20485
+ target_url: observation.target.url,
20486
+ expires_at: preview.expires_at,
20487
+ message: `${side} Preview receipt expired at ${preview.expires_at}.`
20488
+ };
20489
+ }
20490
+ return {
20491
+ side,
20492
+ required: true,
20493
+ ok: true,
20494
+ status: "matched",
20495
+ expected_git_revision: expected,
20496
+ observed_git_revision: observed,
20497
+ content_digest: preview?.content_digest,
20498
+ preview_id: preview?.preview_id,
20499
+ preview_url: preview?.url,
20500
+ target_url: observation.target.url,
20501
+ expires_at: preview?.expires_at
20502
+ };
20503
+ }
20504
+ function collapsedChangeStatus(groups, deltas, sourceBindings) {
20060
20505
  const groupResults = [groups.before, groups.after];
20061
20506
  if (groupResults.some((group) => group.status === "environment_blocked")) return "environment_blocked";
20062
20507
  if (groupResults.some((group) => group.status === "configuration_error")) return "configuration_error";
20063
20508
  if (deltas.some((delta) => delta.status === "configuration_error")) return "configuration_error";
20064
20509
  if (groupResults.some((group) => group.status === "needs_human_review")) return "needs_human_review";
20065
20510
  if (groupResults.some((group) => group.status === "missing" || group.status === "proof_insufficient")) return "proof_insufficient";
20511
+ if ([sourceBindings.before, sourceBindings.after].some((binding) => binding.required && !binding.ok)) return "proof_insufficient";
20066
20512
  if (!deltas.length || deltas.some((delta) => delta.status === "proof_insufficient")) return "proof_insufficient";
20067
20513
  if (groupResults.some((group) => !group.ok)) return "product_regression";
20068
20514
  if (deltas.some((delta) => delta.status === "failed")) return "product_regression";
@@ -20077,22 +20523,570 @@ function changeSummary(name, status, deltas) {
20077
20523
  return `${name} failed ${deltas.filter((delta) => delta.status === "failed").length} change delta(s).`;
20078
20524
  }
20079
20525
  function assessRiddleProofChange(contract, input) {
20526
+ const beforeResult = input.before_result || profileResultFromObservation(input.before_observation);
20527
+ const afterResult = input.after_result || profileResultFromObservation(input.after_observation);
20080
20528
  const groups = {
20081
- before: groupResult("before", contract.before, input.before_result),
20082
- after: groupResult("after", contract.after, input.after_result)
20529
+ before: groupResult("before", contract.before, beforeResult),
20530
+ after: groupResult("after", contract.after, afterResult)
20083
20531
  };
20084
- const deltas = (contract.deltas || []).map((delta) => assessDelta(delta, input.before_result, input.after_result));
20085
- const status = collapsedChangeStatus(groups, deltas);
20532
+ const deltas = (contract.deltas || []).map((delta) => assessDelta(delta, beforeResult, afterResult));
20533
+ const evaluatedAt = input.evaluated_at || (/* @__PURE__ */ new Date()).toISOString();
20534
+ const sourceBindings = {
20535
+ before: sourceBindingResult(
20536
+ "before",
20537
+ contract.source_binding?.before,
20538
+ input.before_observation,
20539
+ input.expected_source_revisions?.before,
20540
+ evaluatedAt
20541
+ ),
20542
+ after: sourceBindingResult(
20543
+ "after",
20544
+ contract.source_binding?.after,
20545
+ input.after_observation,
20546
+ input.expected_source_revisions?.after,
20547
+ evaluatedAt
20548
+ )
20549
+ };
20550
+ const status = collapsedChangeStatus(groups, deltas, sourceBindings);
20086
20551
  return {
20087
20552
  version: RIDDLE_PROOF_CHANGE_RESULT_VERSION,
20088
20553
  contract_name: contract.name,
20089
20554
  status,
20090
20555
  groups,
20091
20556
  deltas,
20557
+ source_bindings: sourceBindings,
20092
20558
  summary: changeSummary(contract.name, status, deltas),
20093
20559
  metadata: contract.metadata
20094
20560
  };
20095
20561
  }
20562
+ function changeReceiptVerdict(status) {
20563
+ if (status === "passed") return "mergeable";
20564
+ if (status === "environment_blocked") return "environment_blocked";
20565
+ if (status === "configuration_error") return "configuration_error";
20566
+ if (status === "needs_human_review") return "needs_human_review";
20567
+ if (status === "proof_insufficient") return "proof_insufficient";
20568
+ return "not_mergeable";
20569
+ }
20570
+ function profileCheckCounts2(result) {
20571
+ const counts = {
20572
+ total: result.checks.length,
20573
+ passed: 0,
20574
+ failed: 0,
20575
+ skipped: 0,
20576
+ needs_human_review: 0
20577
+ };
20578
+ for (const check of result.checks) {
20579
+ if (check.status === "passed") counts.passed += 1;
20580
+ if (check.status === "failed") counts.failed += 1;
20581
+ if (check.status === "skipped") counts.skipped += 1;
20582
+ if (check.status === "needs_human_review") counts.needs_human_review += 1;
20583
+ }
20584
+ return counts;
20585
+ }
20586
+ function artifactIsScreenshot(artifact) {
20587
+ if (artifact.kind !== "image") return false;
20588
+ return /screenshot|\.png|\.jpe?g|\.webp|\.gif|\.avif|\.svg/i.test([artifact.name, artifact.url, artifact.path].filter(Boolean).join(" "));
20589
+ }
20590
+ function metadataStringList(metadata, key) {
20591
+ const value = metadata?.[key];
20592
+ if (!Array.isArray(value)) return [];
20593
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
20594
+ }
20595
+ function changeRecommendation(verdict) {
20596
+ if (verdict === "mergeable") {
20597
+ return {
20598
+ merge_recommended: true,
20599
+ verdict,
20600
+ label: "Merge recommended",
20601
+ reason: "The declared before/after delta contract passed with sufficient bound evidence."
20602
+ };
20603
+ }
20604
+ 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.";
20605
+ return { merge_recommended: false, verdict, label: "Merge not recommended", reason };
20606
+ }
20607
+ function noShippingAuthorization() {
20608
+ return { status: "not_granted", authorized: false, source: "none" };
20609
+ }
20610
+ function recommendationMatches(actual, expected) {
20611
+ return actual?.merge_recommended === expected.merge_recommended && actual.verdict === expected.verdict && actual.label === expected.label && actual.reason === expected.reason;
20612
+ }
20613
+ function assertShippingAuthorizationConsistent(authorization, receiptName) {
20614
+ if (!authorization) throw new Error(`${receiptName} shipping authorization is required.`);
20615
+ if (authorization.status !== "not_granted" && authorization.status !== "granted") {
20616
+ throw new Error(`${receiptName} shipping authorization status is invalid.`);
20617
+ }
20618
+ if (authorization.source !== "none" && authorization.source !== "human" && authorization.source !== "automation") {
20619
+ throw new Error(`${receiptName} shipping authorization source is invalid.`);
20620
+ }
20621
+ const granted = authorization.status === "granted";
20622
+ if (authorization.authorized !== granted) {
20623
+ throw new Error(`${receiptName} shipping authorization status and authorized flag must agree.`);
20624
+ }
20625
+ if (authorization.source === "none" === granted) {
20626
+ throw new Error(`${receiptName} shipping authorization source must identify an authorizer only when granted.`);
20627
+ }
20628
+ }
20629
+ function observationArtifactMatches(actual, expected) {
20630
+ if (!actual || !expected) return actual === expected;
20631
+ 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;
20632
+ }
20633
+ function observationExecutor(result) {
20634
+ return result.runner === "local-playwright" ? { kind: "local_playwright", runner: result.runner } : {
20635
+ kind: "riddle_hosted",
20636
+ runner: result.runner,
20637
+ job_id: result.riddle?.job_id
20638
+ };
20639
+ }
20640
+ function observationForReceiptSide(side, result, source, sourceIdentity) {
20641
+ if (!result) throw new Error(`${side}_result or ${side}_observation is required.`);
20642
+ if (!source) throw new Error(`${side}_source or ${side}_observation is required.`);
20643
+ return createRiddleProofObservationReceipt({
20644
+ comparison_role: side,
20645
+ executor: observationExecutor(result),
20646
+ target: { kind: "url", url: source },
20647
+ source: sourceIdentity,
20648
+ profile_result: result,
20649
+ execution: result.riddle?.execution,
20650
+ publication: source.startsWith("http") ? { kind: result.runner === "local-playwright" ? "other" : "riddle_cdn", url: source } : { kind: "local", path: source }
20651
+ });
20652
+ }
20653
+ function createRiddleProofChangeReceipt(input) {
20654
+ const before = input.before_observation ? parseRiddleProofObservationReceipt(input.before_observation) : observationForReceiptSide("before", input.before_result, input.before_source, input.before_source_identity);
20655
+ const after = input.after_observation ? parseRiddleProofObservationReceipt(input.after_observation) : observationForReceiptSide("after", input.after_result, input.after_source, input.after_source_identity);
20656
+ const verdict = changeReceiptVerdict(input.result.status);
20657
+ const shippingAuthorization = input.shipping_authorization || noShippingAuthorization();
20658
+ assertShippingAuthorizationConsistent(shippingAuthorization, "Change receipt");
20659
+ return {
20660
+ version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
20661
+ contract_name: input.result.contract_name,
20662
+ profile_name: input.profile_name,
20663
+ status: input.result.status,
20664
+ verdict,
20665
+ summary: input.result.summary,
20666
+ before,
20667
+ after,
20668
+ groups: input.result.groups,
20669
+ source_bindings: input.result.source_bindings,
20670
+ deltas: input.result.deltas.map((delta) => ({
20671
+ type: delta.type,
20672
+ label: delta.label,
20673
+ status: delta.status,
20674
+ before_observed: delta.before_observed,
20675
+ after_observed: delta.after_observed,
20676
+ message: delta.message
20677
+ })),
20678
+ recommendation: changeRecommendation(verdict),
20679
+ shipping_authorization: shippingAuthorization,
20680
+ proves: metadataStringList(input.contract.metadata, "required_receipts"),
20681
+ does_not_prove: metadataStringList(input.contract.metadata, "does_not_prove"),
20682
+ metadata: input.result.metadata
20683
+ };
20684
+ }
20685
+ function isRecord4(value) {
20686
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20687
+ }
20688
+ function migratedObservationFromLegacySide(side, legacy) {
20689
+ const legacyArtifacts = legacy.artifacts.map((artifact) => ({
20690
+ name: artifact.name,
20691
+ url: artifact.url,
20692
+ path: artifact.path,
20693
+ source: artifact.source,
20694
+ kind: artifact.kind,
20695
+ role: legacy.screenshots[0] && artifactTarget(artifact) === artifactTarget(legacy.screenshots[0]) ? "canonical_screenshot" : artifact.kind === "image" ? "diagnostic" : artifact.kind === "data" ? "data" : "artifact"
20696
+ }));
20697
+ const canonicalScreenshot = legacyArtifacts.find((artifact) => artifact.role === "canonical_screenshot");
20698
+ return {
20699
+ version: "riddle-proof.observation-receipt.v1",
20700
+ observation_id: `obs_${side}_migrated`,
20701
+ comparison_role: side,
20702
+ executor: { kind: "other", runner: "legacy-change-receipt" },
20703
+ target: { kind: "url", url: legacy.source },
20704
+ source: {},
20705
+ captured_at: legacy.captured_at || (/* @__PURE__ */ new Date(0)).toISOString(),
20706
+ artifacts: legacyArtifacts,
20707
+ canonical_screenshot: canonicalScreenshot,
20708
+ profile_summary: {
20709
+ profile_name: legacy.profile_name,
20710
+ status: legacy.status,
20711
+ summary: legacy.summary,
20712
+ route: legacy.route,
20713
+ checks: legacy.checks
20714
+ },
20715
+ metadata: { migrated_from: RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION }
20716
+ };
20717
+ }
20718
+ function migrateRiddleProofChangeReceipt(legacy) {
20719
+ const recommendation = changeRecommendation(legacy.verdict);
20720
+ return {
20721
+ version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
20722
+ contract_name: legacy.contract_name,
20723
+ profile_name: legacy.profile_name,
20724
+ status: legacy.status,
20725
+ verdict: legacy.verdict,
20726
+ summary: legacy.summary,
20727
+ before: migratedObservationFromLegacySide("before", legacy.before),
20728
+ after: migratedObservationFromLegacySide("after", legacy.after),
20729
+ groups: {
20730
+ before: {
20731
+ side: "before",
20732
+ label: "before",
20733
+ ok: legacy.before.status === "passed" || legacy.before.status === "product_regression",
20734
+ profile_name: legacy.before.profile_name,
20735
+ status: legacy.before.status,
20736
+ required_status: DEFAULT_BEFORE_STATUSES,
20737
+ summary: legacy.before.summary
20738
+ },
20739
+ after: {
20740
+ side: "after",
20741
+ label: "after",
20742
+ ok: legacy.after.status === "passed",
20743
+ profile_name: legacy.after.profile_name,
20744
+ status: legacy.after.status,
20745
+ required_status: DEFAULT_AFTER_STATUSES,
20746
+ summary: legacy.after.summary
20747
+ }
20748
+ },
20749
+ source_bindings: {
20750
+ before: { side: "before", required: false, ok: true, status: "not_required" },
20751
+ after: { side: "after", required: false, ok: true, status: "not_required" }
20752
+ },
20753
+ deltas: legacy.deltas,
20754
+ recommendation,
20755
+ shipping_authorization: noShippingAuthorization(),
20756
+ proves: legacy.proves,
20757
+ does_not_prove: legacy.does_not_prove,
20758
+ metadata: legacy.metadata
20759
+ };
20760
+ }
20761
+ function parseRiddleProofChangeReceipt(value) {
20762
+ if (!isRecord4(value)) throw new Error("Change receipt must be an object.");
20763
+ if (value.version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION) {
20764
+ return migrateRiddleProofChangeReceipt(value);
20765
+ }
20766
+ if (value.version !== RIDDLE_PROOF_CHANGE_RECEIPT_VERSION) {
20767
+ throw new Error(`Unsupported Change receipt version ${String(value.version || "missing")}.`);
20768
+ }
20769
+ const receipt = value;
20770
+ if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(receipt.status)) {
20771
+ throw new Error(`Unsupported Change receipt status ${String(receipt.status || "missing")}.`);
20772
+ }
20773
+ parseRiddleProofObservationReceipt(receipt.before);
20774
+ parseRiddleProofObservationReceipt(receipt.after);
20775
+ if (receipt.before.comparison_role !== "before" || receipt.after.comparison_role !== "after") {
20776
+ throw new Error("Change receipt observations must preserve their before and after comparison roles.");
20777
+ }
20778
+ const expectedVerdict = changeReceiptVerdict(receipt.status);
20779
+ if (receipt.verdict !== expectedVerdict) {
20780
+ throw new Error("Change receipt verdict must match its evaluated status.");
20781
+ }
20782
+ if (!recommendationMatches(receipt.recommendation, changeRecommendation(expectedVerdict))) {
20783
+ throw new Error("Change receipt recommendation must be derived from the Change receipt verdict.");
20784
+ }
20785
+ assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Change receipt");
20786
+ return receipt;
20787
+ }
20788
+ function createRiddleProofHandoffReceipt(changeReceipt, options = {}) {
20789
+ const change = parseRiddleProofChangeReceipt(changeReceipt);
20790
+ const authorization = options.shipping_authorization || change.shipping_authorization;
20791
+ assertShippingAuthorizationConsistent(authorization, "Handoff receipt");
20792
+ return {
20793
+ version: RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
20794
+ change,
20795
+ verdict: change.verdict,
20796
+ recommendation: change.recommendation,
20797
+ shipping_authorization: authorization,
20798
+ canonical_pair: {
20799
+ before: change.before.canonical_screenshot,
20800
+ after: change.after.canonical_screenshot
20801
+ },
20802
+ created_at: options.created_at || (/* @__PURE__ */ new Date()).toISOString()
20803
+ };
20804
+ }
20805
+ function parseRiddleProofHandoffReceipt(value) {
20806
+ if (!isRecord4(value) || value.version !== RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION) {
20807
+ throw new Error(`Unsupported Handoff receipt version ${String(isRecord4(value) ? value.version || "missing" : "missing")}.`);
20808
+ }
20809
+ const receipt = value;
20810
+ if (!Number.isFinite(Date.parse(receipt.created_at))) {
20811
+ throw new Error("Handoff receipt created_at must be a valid timestamp.");
20812
+ }
20813
+ const change = parseRiddleProofChangeReceipt(receipt.change);
20814
+ if (receipt.verdict !== change.verdict || !recommendationMatches(receipt.recommendation, change.recommendation)) {
20815
+ throw new Error("Handoff receipt must preserve the Change receipt verdict and recommendation.");
20816
+ }
20817
+ if (!observationArtifactMatches(receipt.canonical_pair?.before, change.before.canonical_screenshot) || !observationArtifactMatches(receipt.canonical_pair?.after, change.after.canonical_screenshot)) {
20818
+ throw new Error("Handoff receipt canonical pair must project the Change receipt observations.");
20819
+ }
20820
+ assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Handoff receipt");
20821
+ return receipt;
20822
+ }
20823
+ function markdownTableCell(value) {
20824
+ return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
20825
+ }
20826
+ function artifactTarget(artifact) {
20827
+ return artifact.url || artifact.path;
20828
+ }
20829
+ function observationArtifactKind(artifact) {
20830
+ if (artifact.role === "canonical_screenshot" || artifact.role === "setup_screenshot" || artifact.kind === "image") return "image";
20831
+ if (artifact.role === "data" || /\.(json|md|txt|html|log|har)(\?|#|$)/i.test(artifact.name)) return "data";
20832
+ return "artifact";
20833
+ }
20834
+ function receiptSideView(side, observation) {
20835
+ const artifacts = observation.artifacts.map((artifact) => ({
20836
+ side,
20837
+ name: artifact.name,
20838
+ kind: observationArtifactKind(artifact),
20839
+ url: artifact.url,
20840
+ path: artifact.path,
20841
+ source: artifact.source
20842
+ }));
20843
+ const summary = observation.profile_summary;
20844
+ const canonical = observation.canonical_screenshot;
20845
+ const screenshots = artifacts.filter(artifactIsScreenshot);
20846
+ if (canonical) {
20847
+ const canonicalTarget = canonical.url || canonical.path;
20848
+ screenshots.sort((left, right) => {
20849
+ const leftCanonical = (left.url || left.path) === canonicalTarget || left.name === canonical.name;
20850
+ const rightCanonical = (right.url || right.path) === canonicalTarget || right.name === canonical.name;
20851
+ return Number(rightCanonical) - Number(leftCanonical);
20852
+ });
20853
+ }
20854
+ return {
20855
+ side,
20856
+ source: observation.target.url,
20857
+ profile_name: summary?.profile_name || observation.proof?.profile_name || "profile",
20858
+ status: summary?.status || observation.proof?.result.status || "proof_insufficient",
20859
+ summary: summary?.summary || observation.proof?.result.summary || "No profile summary recorded.",
20860
+ route: summary?.route,
20861
+ captured_at: observation.captured_at,
20862
+ checks: summary?.checks || (observation.proof ? profileCheckCounts2(observation.proof.result) : {
20863
+ total: 0,
20864
+ passed: 0,
20865
+ failed: 0,
20866
+ skipped: 0,
20867
+ needs_human_review: 0
20868
+ }),
20869
+ screenshots,
20870
+ artifacts
20871
+ };
20872
+ }
20873
+ function markdownLink(label, target) {
20874
+ return `[${label.replace(/\]/g, "\\]")}](${target})`;
20875
+ }
20876
+ function appendMarkdownArtifacts(lines, title, artifacts) {
20877
+ lines.push(`### ${title}`, "");
20878
+ if (!artifacts.length) {
20879
+ lines.push("- No screenshot artifacts recorded.", "");
20880
+ return;
20881
+ }
20882
+ for (const artifact of artifacts.slice(0, 6)) {
20883
+ const target = artifactTarget(artifact);
20884
+ if (target && artifact.kind === "image") {
20885
+ lines.push(`![${artifact.name.replace(/\]/g, "\\]")}](${target})`);
20886
+ } else if (target) {
20887
+ lines.push(`- ${markdownLink(artifact.name, target)}`);
20888
+ } else {
20889
+ lines.push(`- ${artifact.name}`);
20890
+ }
20891
+ }
20892
+ if (artifacts.length > 6) lines.push(`- ${artifacts.length - 6} more artifact(s) omitted`);
20893
+ lines.push("");
20894
+ }
20895
+ function riddleProofChangeReceiptMarkdown(receipt) {
20896
+ const before = receiptSideView("before", receipt.before);
20897
+ const after = receiptSideView("after", receipt.after);
20898
+ const lines = [
20899
+ "# Riddle Proof Change Receipt",
20900
+ "",
20901
+ `**Verdict:** ${receipt.verdict}`,
20902
+ `**Status:** ${receipt.status}`,
20903
+ `**Contract:** ${receipt.contract_name}`,
20904
+ receipt.profile_name ? `**Profile:** ${receipt.profile_name}` : "",
20905
+ `**Recommendation:** ${receipt.recommendation.label}`,
20906
+ `**Shipping authorization:** ${receipt.shipping_authorization.status}`,
20907
+ "",
20908
+ receipt.summary,
20909
+ "",
20910
+ "## Evidence Pair",
20911
+ "",
20912
+ "| Side | Source | Status | Checks |",
20913
+ "| --- | --- | --- | --- |",
20914
+ `| Before | ${markdownTableCell(before.source)} | ${before.status}${receipt.groups.before.ok ? " (matched baseline)" : " (baseline mismatch)"} | ${before.checks.passed} passed / ${before.checks.failed} failed |`,
20915
+ `| After | ${markdownTableCell(after.source)} | ${after.status} | ${after.checks.passed} passed / ${after.checks.failed} failed |`,
20916
+ "",
20917
+ "## Source Binding",
20918
+ "",
20919
+ "| Side | Status | Git revision | Preview | Digest |",
20920
+ "| --- | --- | --- | --- | --- |",
20921
+ `| Before | ${receipt.source_bindings.before.status} | ${markdownTableCell(receipt.source_bindings.before.observed_git_revision || "not recorded")} | ${markdownTableCell(receipt.source_bindings.before.preview_id || "not required")} | ${markdownTableCell(receipt.source_bindings.before.content_digest || "not required")} |`,
20922
+ `| After | ${receipt.source_bindings.after.status} | ${markdownTableCell(receipt.source_bindings.after.observed_git_revision || "not recorded")} | ${markdownTableCell(receipt.source_bindings.after.preview_id || "not required")} | ${markdownTableCell(receipt.source_bindings.after.content_digest || "not required")} |`,
20923
+ "",
20924
+ "## Delta Checks",
20925
+ "",
20926
+ "| Delta | Status | Before | After |",
20927
+ "| --- | --- | --- | --- |"
20928
+ ].filter(Boolean);
20929
+ for (const delta of receipt.deltas) {
20930
+ lines.push(`| ${markdownTableCell(delta.label)} | ${delta.status} | ${markdownTableCell(delta.before_observed)} | ${markdownTableCell(delta.after_observed)} |`);
20931
+ if (delta.message) lines.push(`| ${markdownTableCell(`${delta.label} message`)} | ${markdownTableCell(delta.message)} | | |`);
20932
+ }
20933
+ lines.push("");
20934
+ appendMarkdownArtifacts(lines, "Before Canonical Screenshot", before.screenshots.slice(0, 1));
20935
+ appendMarkdownArtifacts(lines, "After Canonical Screenshot", after.screenshots.slice(0, 1));
20936
+ if (receipt.proves.length) {
20937
+ lines.push("## What This Proves", "");
20938
+ for (const claim of receipt.proves) lines.push(`- ${claim}`);
20939
+ lines.push("");
20940
+ }
20941
+ if (receipt.does_not_prove.length) {
20942
+ lines.push("## What This Does Not Prove", "");
20943
+ for (const claim of receipt.does_not_prove) lines.push(`- ${claim}`);
20944
+ lines.push("");
20945
+ }
20946
+ const linkedArtifacts = [...before.artifacts, ...after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 24);
20947
+ if (linkedArtifacts.length) {
20948
+ lines.push("## Artifacts", "");
20949
+ for (const artifact of linkedArtifacts) {
20950
+ lines.push(`- ${artifact.side}: ${markdownLink(artifact.name, artifactTarget(artifact) || "")}`);
20951
+ }
20952
+ lines.push("");
20953
+ }
20954
+ return `${lines.join("\n").trim()}
20955
+ `;
20956
+ }
20957
+ function escapeHtml(value) {
20958
+ return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
20959
+ }
20960
+ function htmlArtifactCard(artifact) {
20961
+ const target = artifactTarget(artifact);
20962
+ if (!target) return `<p>${escapeHtml(artifact.name)}</p>`;
20963
+ if (artifact.kind === "image") {
20964
+ return `<figure><img src="${escapeHtml(target)}" alt="${escapeHtml(artifact.name)}"><figcaption>${escapeHtml(artifact.name)}</figcaption></figure>`;
20965
+ }
20966
+ return `<p><a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a></p>`;
20967
+ }
20968
+ function htmlArtifactLink(artifact) {
20969
+ const target = artifactTarget(artifact);
20970
+ if (!target) return escapeHtml(artifact.name);
20971
+ return `<a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a>`;
20972
+ }
20973
+ function htmlList(items, emptyText) {
20974
+ if (!items.length) return `<p class="muted">${escapeHtml(emptyText)}</p>`;
20975
+ return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
20976
+ }
20977
+ function riddleProofChangeReceiptHtml(receipt) {
20978
+ const before = receiptSideView("before", receipt.before);
20979
+ const after = receiptSideView("after", receipt.after);
20980
+ const artifactLinks = [...before.artifacts, ...after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 32);
20981
+ const deltaRows = receipt.deltas.map((delta) => `
20982
+ <tr>
20983
+ <td>${escapeHtml(delta.label)}</td>
20984
+ <td><span class="status ${escapeHtml(delta.status)}">${escapeHtml(delta.status)}</span></td>
20985
+ <td>${escapeHtml(delta.before_observed)}</td>
20986
+ <td>${escapeHtml(delta.after_observed)}</td>
20987
+ </tr>
20988
+ ${delta.message ? `<tr><td class="muted">${escapeHtml(delta.label)} message</td><td colspan="3">${escapeHtml(delta.message)}</td></tr>` : ""}`).join("");
20989
+ return `<!doctype html>
20990
+ <html lang="en">
20991
+ <head>
20992
+ <meta charset="utf-8">
20993
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20994
+ <title>${escapeHtml(receipt.contract_name)} - Riddle Proof Change Receipt</title>
20995
+ <style>
20996
+ :root { color-scheme: light dark; --bg: #0f141b; --panel: #171f29; --text: #eef4f8; --muted: #aebbc8; --border: #314152; --ok: #69d089; --bad: #ff7a7a; --warn: #ffd166; }
20997
+ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
20998
+ main { max-width: 1120px; margin: 0 auto; padding: 32px 20px 48px; }
20999
+ header, section { border: 1px solid var(--border); background: var(--panel); border-radius: 8px; padding: 20px; margin: 0 0 18px; }
21000
+ h1, h2, h3, p { margin-top: 0; }
21001
+ h1 { font-size: clamp(1.6rem, 3vw, 2.4rem); }
21002
+ h2 { font-size: 1.1rem; margin-bottom: 14px; }
21003
+ .muted { color: var(--muted); }
21004
+ .verdict { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); font-weight: 700; }
21005
+ .mergeable, .passed { color: var(--ok); }
21006
+ .not_mergeable, .failed, .product_regression { color: var(--bad); }
21007
+ .proof_insufficient, .environment_blocked, .needs_human_review, .configuration_error { color: var(--warn); }
21008
+ .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
21009
+ .side { border: 1px solid var(--border); border-radius: 8px; padding: 14px; min-width: 0; }
21010
+ code { overflow-wrap: anywhere; color: var(--muted); }
21011
+ table { width: 100%; border-collapse: collapse; }
21012
+ th, td { border-bottom: 1px solid var(--border); padding: 10px 8px; text-align: left; vertical-align: top; }
21013
+ figure { margin: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: #0b1017; }
21014
+ img { display: block; width: 100%; height: auto; }
21015
+ figcaption { padding: 8px 10px; color: var(--muted); font-size: 0.9rem; }
21016
+ ul { margin-bottom: 0; }
21017
+ a { color: #8bc7ff; }
21018
+ @media (max-width: 760px) { .grid { grid-template-columns: 1fr; } main { padding: 18px 12px 32px; } }
21019
+ </style>
21020
+ </head>
21021
+ <body>
21022
+ <main>
21023
+ <header>
21024
+ <p class="verdict ${escapeHtml(receipt.verdict)}">${escapeHtml(receipt.verdict)}</p>
21025
+ <h1>${escapeHtml(receipt.contract_name)}</h1>
21026
+ <p>${escapeHtml(receipt.summary)}</p>
21027
+ <p class="muted">Status: ${escapeHtml(receipt.status)}${receipt.profile_name ? ` | Profile: ${escapeHtml(receipt.profile_name)}` : ""}</p>
21028
+ <p>${escapeHtml(receipt.recommendation.label)}. Shipping authorization: ${escapeHtml(receipt.shipping_authorization.status)}.</p>
21029
+ </header>
21030
+ <section>
21031
+ <h2>Evidence Pair</h2>
21032
+ <div class="grid">
21033
+ <div class="side">
21034
+ <h3>Before</h3>
21035
+ <p><code>${escapeHtml(before.source)}</code></p>
21036
+ <p>Status: <strong class="${escapeHtml(before.status)}">${escapeHtml(before.status)}</strong>${receipt.groups.before.ok ? " (matched baseline)" : " (baseline mismatch)"}</p>
21037
+ <p>${escapeHtml(before.summary)}</p>
21038
+ <p class="muted">${before.checks.passed} passed / ${before.checks.failed} failed checks</p>
21039
+ </div>
21040
+ <div class="side">
21041
+ <h3>After</h3>
21042
+ <p><code>${escapeHtml(after.source)}</code></p>
21043
+ <p>Status: <strong class="${escapeHtml(after.status)}">${escapeHtml(after.status)}</strong></p>
21044
+ <p>${escapeHtml(after.summary)}</p>
21045
+ <p class="muted">${after.checks.passed} passed / ${after.checks.failed} failed checks</p>
21046
+ </div>
21047
+ </div>
21048
+ </section>
21049
+ <section>
21050
+ <h2>Source Binding</h2>
21051
+ <table>
21052
+ <thead><tr><th>Side</th><th>Status</th><th>Git revision</th><th>Preview</th><th>Digest</th></tr></thead>
21053
+ <tbody>
21054
+ <tr><td>Before</td><td>${escapeHtml(receipt.source_bindings.before.status)}</td><td>${escapeHtml(receipt.source_bindings.before.observed_git_revision || "not recorded")}</td><td>${escapeHtml(receipt.source_bindings.before.preview_id || "not required")}</td><td>${escapeHtml(receipt.source_bindings.before.content_digest || "not required")}</td></tr>
21055
+ <tr><td>After</td><td>${escapeHtml(receipt.source_bindings.after.status)}</td><td>${escapeHtml(receipt.source_bindings.after.observed_git_revision || "not recorded")}</td><td>${escapeHtml(receipt.source_bindings.after.preview_id || "not required")}</td><td>${escapeHtml(receipt.source_bindings.after.content_digest || "not required")}</td></tr>
21056
+ </tbody>
21057
+ </table>
21058
+ </section>
21059
+ <section>
21060
+ <h2>Delta Checks</h2>
21061
+ <table>
21062
+ <thead><tr><th>Delta</th><th>Status</th><th>Before</th><th>After</th></tr></thead>
21063
+ <tbody>${deltaRows}</tbody>
21064
+ </table>
21065
+ </section>
21066
+ <section>
21067
+ <h2>Screenshots</h2>
21068
+ <div class="grid">
21069
+ <div>${before.screenshots.slice(0, 1).map(htmlArtifactCard).join("") || `<p class="muted">No canonical before screenshot recorded.</p>`}</div>
21070
+ <div>${after.screenshots.slice(0, 1).map(htmlArtifactCard).join("") || `<p class="muted">No canonical after screenshot recorded.</p>`}</div>
21071
+ </div>
21072
+ </section>
21073
+ <section>
21074
+ <h2>What This Proves</h2>
21075
+ ${htmlList(receipt.proves, "No explicit proof claims were recorded in contract metadata.")}
21076
+ </section>
21077
+ <section>
21078
+ <h2>What This Does Not Prove</h2>
21079
+ ${htmlList(receipt.does_not_prove, "No explicit limits were recorded in contract metadata.")}
21080
+ </section>
21081
+ <section>
21082
+ <h2>Artifacts</h2>
21083
+ ${artifactLinks.length ? `<ul>${artifactLinks.map((artifact) => `<li>${escapeHtml(artifact.side)}: ${htmlArtifactLink(artifact)}</li>`).join("")}</ul>` : `<p class="muted">No linked artifacts recorded.</p>`}
21084
+ </section>
21085
+ </main>
21086
+ </body>
21087
+ </html>
21088
+ `;
21089
+ }
20096
21090
 
20097
21091
  // src/riddle-client.ts
20098
21092
  var import_node_child_process4 = require("child_process");
@@ -20160,6 +21154,7 @@ async function getRiddleBalance(config = {}) {
20160
21154
  }
20161
21155
  function previewDeployResultFromRecord(input) {
20162
21156
  const { record, id, label, framework, expiresAt, publishRecovered, publishError } = input;
21157
+ const receipt = record.receipt && typeof record.receipt === "object" ? parseRiddlePreviewReceipt(record.receipt) : void 0;
20163
21158
  return {
20164
21159
  ok: true,
20165
21160
  id: String(record.id || record.preview_id || id),
@@ -20168,13 +21163,32 @@ function previewDeployResultFromRecord(input) {
20168
21163
  preview_url: String(record.preview_url || ""),
20169
21164
  file_count: typeof record.file_count === "number" ? record.file_count : void 0,
20170
21165
  total_bytes: typeof record.total_bytes === "number" ? record.total_bytes : void 0,
20171
- expires_at: expiresAt,
21166
+ expires_at: receipt?.expires_at || expiresAt,
21167
+ receipt,
20172
21168
  publish_recovered: publishRecovered || void 0,
20173
21169
  publish_error: publishError,
20174
21170
  warnings: input.warnings?.length ? input.warnings : void 0,
20175
21171
  raw: record
20176
21172
  };
20177
21173
  }
21174
+ function gitOutput(directory, args) {
21175
+ try {
21176
+ return (0, import_node_child_process4.execFileSync)("git", ["-C", directory, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
21177
+ } catch {
21178
+ return "";
21179
+ }
21180
+ }
21181
+ function detectRiddlePreviewSource(directory) {
21182
+ const gitRevision = gitOutput(directory, ["rev-parse", "HEAD"]);
21183
+ if (!gitRevision) return {};
21184
+ const repository = gitOutput(directory, ["config", "--get", "remote.origin.url"]);
21185
+ const status = gitOutput(directory, ["status", "--porcelain", "--untracked-files=normal"]);
21186
+ return {
21187
+ git_revision: gitRevision,
21188
+ repository: repository || void 0,
21189
+ dirty: Boolean(status)
21190
+ };
21191
+ }
20178
21192
  function canRecoverPreviewPublish(error) {
20179
21193
  return error instanceof RiddleApiError && PREVIEW_PUBLISH_RECOVERY_STATUSES.has(error.status);
20180
21194
  }
@@ -20257,12 +21271,13 @@ async function waitForPublishedPreview(config, input) {
20257
21271
  }
20258
21272
  throw input.publishError;
20259
21273
  }
20260
- async function deployRiddlePreview(config, directory, label, framework = "static") {
21274
+ async function deployRiddlePreview(config, directory, label, framework = "static", options = {}) {
20261
21275
  if (!directory?.trim()) throw new Error("directory is required");
20262
21276
  if (!label?.trim()) throw new Error("label is required");
20263
21277
  if (framework !== "spa" && framework !== "static") throw new Error("framework must be spa or static");
20264
21278
  const startedAt = Date.now();
20265
21279
  const warnings = collectRiddlePreviewDeployWarnings(directory, framework);
21280
+ const source = options.source || detectRiddlePreviewSource(directory);
20266
21281
  const emitProgress = previewProgressEmitter(config, { label, framework, directory, startedAt, warnings });
20267
21282
  await emitProgress({ stage: "validating", message: "checking preview input directory" });
20268
21283
  const localSummary = summarizePreviewDirectory(directory);
@@ -20274,7 +21289,7 @@ async function deployRiddlePreview(config, directory, label, framework = "static
20274
21289
  });
20275
21290
  const created = await riddleRequestJson(config, "/v1/preview", {
20276
21291
  method: "POST",
20277
- body: JSON.stringify({ framework, label })
21292
+ body: JSON.stringify({ framework, label, source })
20278
21293
  });
20279
21294
  const id = String(created.id || "");
20280
21295
  const uploadUrl = String(created.upload_url || "");
@@ -20416,8 +21431,8 @@ function collectRiddlePreviewDeployWarnings(directory, framework = "static") {
20416
21431
  return [];
20417
21432
  }
20418
21433
  }
20419
- async function deployRiddleStaticPreview(config, directory, label) {
20420
- return deployRiddlePreview(config, directory, label, "static");
21434
+ async function deployRiddleStaticPreview(config, directory, label, options = {}) {
21435
+ return deployRiddlePreview(config, directory, label, "static", options);
20421
21436
  }
20422
21437
  function createTarball(directory, label, exclude = []) {
20423
21438
  const scratch = (0, import_node_fs5.mkdtempSync)(import_node_path5.default.join((0, import_node_os2.tmpdir)(), "riddle-upload-"));
@@ -20539,21 +21554,31 @@ function parseTimestampMs(value) {
20539
21554
  }
20540
21555
  function buildPollSnapshot(jobId, job, input) {
20541
21556
  const status = job?.status ? String(job.status) : null;
21557
+ const phase = job?.phase ? String(job.phase) : null;
20542
21558
  const terminal = isTerminalRiddleJobStatus(status);
20543
21559
  const createdAt = stringField(job, "created_at");
20544
21560
  const submittedAt = stringField(job, "submitted_at");
20545
21561
  const completedAt = stringField(job, "completed_at");
20546
21562
  const createdMs = parseTimestampMs(createdAt);
20547
21563
  const submittedMs = parseTimestampMs(submittedAt);
21564
+ const rawExecution = job?.execution && typeof job.execution === "object" && !Array.isArray(job.execution) ? job.execution : null;
21565
+ const enqueuedAt = rawExecution ? stringField(rawExecution, "enqueued_at") : null;
21566
+ const enqueuedMs = parseTimestampMs(enqueuedAt);
21567
+ const queueStartedMs = enqueuedMs ?? createdMs;
21568
+ const claimedAt = rawExecution ? stringField(rawExecution, "claimed_at") : null;
21569
+ const claimedMs = parseTimestampMs(claimedAt);
20548
21570
  let queueElapsedMs = null;
20549
- if (createdMs !== null && submittedMs !== null) {
20550
- queueElapsedMs = Math.max(0, submittedMs - createdMs);
20551
- } else if (createdMs !== null && !submittedAt && !terminal) {
20552
- queueElapsedMs = Math.max(0, input.observedAt - createdMs);
21571
+ if (queueStartedMs !== null && claimedMs !== null) {
21572
+ queueElapsedMs = Math.max(0, claimedMs - queueStartedMs);
21573
+ } else if (queueStartedMs !== null && submittedMs !== null) {
21574
+ queueElapsedMs = Math.max(0, submittedMs - queueStartedMs);
21575
+ } else if (queueStartedMs !== null && !submittedAt && !terminal) {
21576
+ queueElapsedMs = Math.max(0, input.observedAt - queueStartedMs);
20553
21577
  }
20554
21578
  return {
20555
21579
  job_id: jobId,
20556
21580
  status,
21581
+ phase,
20557
21582
  terminal,
20558
21583
  attempt: input.attempt,
20559
21584
  attempts: input.attempts,
@@ -20563,7 +21588,9 @@ function buildPollSnapshot(jobId, job, input) {
20563
21588
  completed_at: completedAt,
20564
21589
  queue_elapsed_ms: queueElapsedMs,
20565
21590
  pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
20566
- running_without_submission: Boolean(status && !terminal && !submittedAt)
21591
+ running_without_submission: Boolean(status && !terminal && !submittedAt),
21592
+ active_execution: Boolean(status === "running" && phase && phase !== "queued"),
21593
+ execution: rawExecution ? rawExecution : void 0
20567
21594
  };
20568
21595
  }
20569
21596
  function pollMessage(snapshot, timedOut) {
@@ -20572,7 +21599,7 @@ function pollMessage(snapshot, timedOut) {
20572
21599
  const wakeHint = snapshot.running_without_submission && !snapshot.created_at && !snapshot.submitted_at ? ` ${RIDDLE_UNSUBMITTED_WAKE_HINT}.` : "";
20573
21600
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
20574
21601
  const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
20575
- return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${wakeHint}${queue}${preSubmit}`;
21602
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} phase=${snapshot.phase || "unknown"} submitted_at=${submitted}.${wakeHint}${queue}${preSubmit}`;
20576
21603
  }
20577
21604
  async function pollRiddleJob(config, jobId, options = {}) {
20578
21605
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -20606,6 +21633,7 @@ async function pollRiddleJob(config, jobId, options = {}) {
20606
21633
  };
20607
21634
  const progressKey = [
20608
21635
  lastSnapshot.status || "unknown",
21636
+ lastSnapshot.phase || "unknown",
20609
21637
  lastSnapshot.terminal ? "terminal" : "nonterminal",
20610
21638
  lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
20611
21639
  ].join(":");
@@ -20672,8 +21700,8 @@ function createRiddleApiClient(config = {}) {
20672
21700
  apiKeySource: () => resolveRiddleApiKeySource(config),
20673
21701
  requestJson: (pathname, init) => riddleRequestJson(config, pathname, init),
20674
21702
  getBalance: () => getRiddleBalance(config),
20675
- deployPreview: (directory, label, framework = "static") => deployRiddlePreview(config, directory, label, framework),
20676
- deployStaticPreview: (directory, label) => deployRiddleStaticPreview(config, directory, label),
21703
+ deployPreview: (directory, label, framework = "static", options) => deployRiddlePreview(config, directory, label, framework, options),
21704
+ deployStaticPreview: (directory, label, options) => deployRiddleStaticPreview(config, directory, label, options),
20677
21705
  runScript: (input) => runRiddleScript(config, input),
20678
21706
  runServerPreview: (input) => runRiddleServerPreview(config, input),
20679
21707
  pollJob: (jobId, options) => pollRiddleJob(config, jobId, options)
@@ -20755,6 +21783,34 @@ function collectProfileArtifacts(result) {
20755
21783
  }
20756
21784
  return artifacts;
20757
21785
  }
21786
+ function collectChangeReceiptArtifacts(result) {
21787
+ const artifacts = [];
21788
+ const seen = /* @__PURE__ */ new Set();
21789
+ const collectSide = (sideName, side) => {
21790
+ const candidates = [
21791
+ side.canonical_screenshot,
21792
+ ...asArray(side.screenshots),
21793
+ ...asArray(side.artifacts)
21794
+ ].filter(Boolean);
21795
+ for (const [index, item] of candidates.entries()) {
21796
+ const artifact = asRecord2(item);
21797
+ const url = stringValue7(artifact.url) || stringValue7(artifact.path);
21798
+ if (!url || seen.has(url)) continue;
21799
+ seen.add(url);
21800
+ const fallbackName = `${sideName}-artifact-${index + 1}`;
21801
+ const name = `${sideName}/${artifactDisplayName(artifact.name, fallbackName)}`;
21802
+ artifacts.push({
21803
+ name,
21804
+ url,
21805
+ kind: artifactKind(name, url),
21806
+ size_bytes: numberValue5(artifact.size_bytes) ?? numberValue5(artifact.size)
21807
+ });
21808
+ }
21809
+ };
21810
+ collectSide("before", asRecord2(result.before));
21811
+ collectSide("after", asRecord2(result.after));
21812
+ return artifacts;
21813
+ }
20758
21814
  function mergeArtifacts(...artifactLists) {
20759
21815
  const artifacts = [];
20760
21816
  const seen = /* @__PURE__ */ new Set();
@@ -20781,7 +21837,7 @@ function pageSummaries(result) {
20781
21837
  }
20782
21838
  return pages;
20783
21839
  }
20784
- function profileCheckCounts(result) {
21840
+ function profileCheckCounts3(result) {
20785
21841
  const checks = asArray(result.checks);
20786
21842
  if (checks.length) {
20787
21843
  let passed2 = 0;
@@ -20810,6 +21866,33 @@ function profilePageSummaries(result, counts) {
20810
21866
  failed: counts.failed
20811
21867
  }];
20812
21868
  }
21869
+ function changeReceiptCheckCounts(result) {
21870
+ const deltas = asArray(result.deltas);
21871
+ if (!deltas.length) return void 0;
21872
+ let passed = 0;
21873
+ let failed = 0;
21874
+ for (const item of deltas) {
21875
+ const status = stringValue7(asRecord2(item).status);
21876
+ if (status === "passed") passed += 1;
21877
+ if (status === "failed" || status === "proof_insufficient" || status === "configuration_error") failed += 1;
21878
+ }
21879
+ return { passed, failed };
21880
+ }
21881
+ function changeReceiptPageSummaries(result) {
21882
+ const pages = [];
21883
+ for (const sideName of ["before", "after"]) {
21884
+ const side = asRecord2(result[sideName]);
21885
+ if (!Object.keys(side).length) continue;
21886
+ const profileSummary = asRecord2(side.profile_summary);
21887
+ const checks = Object.keys(asRecord2(side.checks)).length ? asRecord2(side.checks) : asRecord2(profileSummary.checks);
21888
+ pages.push({
21889
+ route: firstStringValue2(asRecord2(side.target).url, side.source, profileSummary.profile_name, side.profile_name) || sideName,
21890
+ passed: numberValue5(checks.passed) ?? 0,
21891
+ failed: numberValue5(checks.failed) ?? 0
21892
+ });
21893
+ }
21894
+ return pages;
21895
+ }
20813
21896
  function summarizeExplicitChecks(value) {
20814
21897
  let passed = 0;
20815
21898
  let failed = 0;
@@ -20865,11 +21948,27 @@ function isProfileResult(result) {
20865
21948
  const version = stringValue7(result.version);
20866
21949
  return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
20867
21950
  }
21951
+ function isChangeReceiptResult(result) {
21952
+ const version = stringValue7(result.version);
21953
+ return version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION || version === RIDDLE_PROOF_CHANGE_RECEIPT_VERSION;
21954
+ }
21955
+ function isHandoffReceiptResult(result) {
21956
+ return stringValue7(result.version) === RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION;
21957
+ }
21958
+ function isChangeResult(result) {
21959
+ const version = stringValue7(result.version);
21960
+ return version === "riddle-proof.change-result.v1" || version === "riddle-proof.change-compact-result.v1";
21961
+ }
20868
21962
  function summarizeRiddleProofPrComment(input) {
20869
21963
  const runResponse = asRecord2(input.runResponse);
20870
- const result = asRecord2(input.result);
21964
+ const inputResult = asRecord2(input.result);
21965
+ const handoffReceipt = isHandoffReceiptResult(inputResult) ? parseRiddleProofHandoffReceipt(inputResult) : void 0;
21966
+ const parsedChangeReceipt = handoffReceipt?.change || (isChangeReceiptResult(inputResult) ? parseRiddleProofChangeReceipt(inputResult) : void 0);
21967
+ const result = parsedChangeReceipt ? asRecord2(parsedChangeReceipt) : inputResult;
20871
21968
  const proofResult = asRecord2(runResponse.proofResult);
20872
21969
  const profileResult = isProfileResult(result);
21970
+ const changeReceiptResult = Boolean(parsedChangeReceipt);
21971
+ const changeResult = changeReceiptResult || isChangeResult(result);
20873
21972
  const profileRiddle = asRecord2(result.riddle);
20874
21973
  const preview = asRecord2(runResponse.preview);
20875
21974
  const resultRunCard = asRecord2(result.run_card);
@@ -20877,16 +21976,19 @@ function summarizeRiddleProofPrComment(input) {
20877
21976
  const resultDetails = asRecord2(result.details);
20878
21977
  const resultRaw = asRecord2(result.raw);
20879
21978
  const rawDetails = asRecord2(resultRaw.details);
20880
- const profileChecks = profileResult ? profileCheckCounts(result) : void 0;
21979
+ const profileChecks = profileResult ? profileCheckCounts3(result) : void 0;
21980
+ const changeChecks = changeResult ? changeReceiptCheckCounts(result) : void 0;
20881
21981
  const artifacts = mergeArtifacts(
20882
21982
  collectArtifacts(runResponse),
20883
- profileResult ? collectProfileArtifacts(result) : []
21983
+ profileResult ? collectProfileArtifacts(result) : [],
21984
+ changeReceiptResult ? collectChangeReceiptArtifacts(result) : []
20884
21985
  );
20885
- const pages = profileResult ? profilePageSummaries(result, profileChecks) : pageSummaries(result);
21986
+ const pages = changeReceiptResult ? changeReceiptPageSummaries(result) : profileResult ? profilePageSummaries(result, profileChecks) : pageSummaries(result);
20886
21987
  const checkSource = { ...result };
20887
21988
  delete checkSource.ok;
20888
21989
  const nestedChecks = summarizeExplicitChecks(checkSource);
20889
- const ok = booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
21990
+ const resultStatus = firstStringValue2(result.status, stopCondition.status);
21991
+ const ok = changeReceiptResult ? parsedChangeReceipt?.recommendation.merge_recommended ?? false : changeResult ? resultStatus === "passed" : booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
20890
21992
  const checkpointSummary = checkpointSummaryFrom2(
20891
21993
  result.checkpoint_summary,
20892
21994
  stopCondition.checkpoint_summary,
@@ -20894,19 +21996,19 @@ function summarizeRiddleProofPrComment(input) {
20894
21996
  rawDetails.checkpoint_summary,
20895
21997
  proofResult.checkpoint_summary
20896
21998
  );
20897
- const publicState = summarizeRiddleProofPublicState({
21999
+ const publicState = changeReceiptResult ? void 0 : summarizeRiddleProofPublicState({
20898
22000
  ...result,
20899
- status: firstStringValue2(result.status, stopCondition.status),
22001
+ status: resultStatus,
20900
22002
  checkpoint_summary: checkpointSummary || result.checkpoint_summary
20901
22003
  });
20902
- const mergeRecommendation = riddleProofPublicStateMergeRecommendation(
22004
+ const mergeRecommendation = publicState ? riddleProofPublicStateMergeRecommendation(
20903
22005
  publicState,
20904
22006
  firstStringValue2(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
20905
- );
22007
+ ) : void 0;
20906
22008
  return {
20907
22009
  ok,
20908
22010
  status: firstStringValue2(proofResult.status, profileRiddle.status),
20909
- result_status: publicState.status,
22011
+ result_status: changeResult ? resultStatus : publicState?.status,
20910
22012
  job_id: firstStringValue2(proofResult.job_id, profileRiddle.job_id),
20911
22013
  duration_ms: numberValue5(proofResult.duration_ms) ?? numberValue5(profileRiddle.elapsed_ms),
20912
22014
  proof_url: stringValue7(runResponse.proofUrl),
@@ -20914,17 +22016,17 @@ function summarizeRiddleProofPrComment(input) {
20914
22016
  preview_url: stringValue7(preview.preview_url) || stringValue7(preview.url),
20915
22017
  preview_publish_recovered: booleanValue3(preview.publish_recovered),
20916
22018
  preview_publish_error: stringValue7(preview.publish_error),
20917
- ship_held: publicState.ship_held,
20918
- shipping_disabled: publicState.shipping_disabled,
20919
- ship_authorized: publicState.ship_authorized,
20920
- merge_ready: publicState.merge_ready,
20921
- sync_allowed: publicState.sync_allowed,
22019
+ ship_held: publicState?.ship_held,
22020
+ shipping_disabled: publicState?.shipping_disabled,
22021
+ ship_authorized: changeReceiptResult ? parsedChangeReceipt?.shipping_authorization.authorized : publicState?.ship_authorized,
22022
+ merge_ready: publicState?.merge_ready,
22023
+ sync_allowed: publicState?.sync_allowed,
20922
22024
  proof_decision: firstStringValue2(result.proof_decision, stopCondition.proof_decision, resultRaw.proof_decision),
20923
- merge_recommendation: mergeRecommendation,
22025
+ merge_recommendation: changeReceiptResult ? parsedChangeReceipt?.recommendation.label : mergeRecommendation,
20924
22026
  checkpoint_summary: checkpointSummary,
20925
22027
  public_state: publicState,
20926
- passed_checks: profileChecks?.passed ?? nestedChecks.passed,
20927
- failed_checks: profileChecks?.failed ?? nestedChecks.failed,
22028
+ passed_checks: changeChecks?.passed ?? profileChecks?.passed ?? nestedChecks.passed,
22029
+ failed_checks: changeChecks?.failed ?? profileChecks?.failed ?? nestedChecks.failed,
20928
22030
  pages,
20929
22031
  artifacts,
20930
22032
  primary_image: selectPrimaryImage(artifacts)
@@ -20937,7 +22039,7 @@ function formatDuration(ms) {
20937
22039
  const remainder = seconds % 60;
20938
22040
  return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
20939
22041
  }
20940
- function markdownLink(label, url) {
22042
+ function markdownLink2(label, url) {
20941
22043
  return `[${label.replace(/\]/g, "\\]")}](${url})`;
20942
22044
  }
20943
22045
  function resultLabel(summary) {
@@ -20988,7 +22090,88 @@ function checkpointSummaryLine(summary) {
20988
22090
  summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
20989
22091
  ].filter(Boolean).join("; ");
20990
22092
  }
22093
+ function markdownTableValue(value) {
22094
+ return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
22095
+ }
22096
+ function observationArtifactTarget(artifact) {
22097
+ return artifact?.url || artifact?.path;
22098
+ }
22099
+ function canonicalScreenshotCell(artifact, fallback) {
22100
+ const target = observationArtifactTarget(artifact);
22101
+ if (!artifact || !target) return fallback;
22102
+ if (artifact.url) return `![${artifact.name.replace(/\]/g, "\\]")}](${artifact.url})`;
22103
+ return markdownTableValue(artifact.path || artifact.name);
22104
+ }
22105
+ function shortRevision(value) {
22106
+ return value ? value.slice(0, 12) : "not recorded";
22107
+ }
22108
+ function buildRiddleProofHandoffPrCommentMarkdown(handoff, input = {}) {
22109
+ const receipt = parseRiddleProofHandoffReceipt(handoff);
22110
+ const change = receipt.change;
22111
+ const beforeStatus = change.before.profile_summary?.status || "not recorded";
22112
+ const afterStatus = change.after.profile_summary?.status || "not recorded";
22113
+ const beforeBaseline = change.groups.before.ok ? "matched baseline" : "baseline mismatch";
22114
+ const lines = [
22115
+ RIDDLE_PROOF_PR_COMMENT_MARKER,
22116
+ `## ${input.title?.trim() || "Riddle Change Proof"}`,
22117
+ "",
22118
+ `**Result:** ${receipt.recommendation.label}`,
22119
+ `**Evidence verdict:** \`${receipt.verdict}\``,
22120
+ `**Shipping authorization:** ${receipt.shipping_authorization.status}`,
22121
+ `**Contract:** ${change.contract_name}`
22122
+ ];
22123
+ if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
22124
+ if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
22125
+ lines.push("", change.summary, "", "### Canonical Before / After", "");
22126
+ lines.push("| Before | After |");
22127
+ lines.push("| --- | --- |");
22128
+ lines.push(`| ${beforeBaseline}: \`${markdownTableValue(beforeStatus)}\` | \`${markdownTableValue(afterStatus)}\` |`);
22129
+ lines.push(`| ${canonicalScreenshotCell(receipt.canonical_pair.before, "No canonical before screenshot")} | ${canonicalScreenshotCell(receipt.canonical_pair.after, "No canonical after screenshot")} |`);
22130
+ lines.push("", "### Source Binding", "");
22131
+ lines.push("| Side | Target | Git revision | Binding | Preview | Digest |");
22132
+ lines.push("| --- | --- | --- | --- | --- | --- |");
22133
+ for (const side of ["before", "after"]) {
22134
+ const observation = change[side];
22135
+ const binding = change.source_bindings[side];
22136
+ 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")}\` |`);
22137
+ }
22138
+ lines.push("", "### Declared Delta", "");
22139
+ lines.push("| Measurement | Status | Before | After |");
22140
+ lines.push("| --- | --- | --- | --- |");
22141
+ for (const delta of change.deltas) {
22142
+ lines.push(`| ${markdownTableValue(delta.label)} | ${delta.status} | ${markdownTableValue(delta.before_observed)} | ${markdownTableValue(delta.after_observed)} |`);
22143
+ }
22144
+ if (change.proves.length) {
22145
+ lines.push("", "### What This Proves", "");
22146
+ for (const claim of change.proves) lines.push(`- ${claim}`);
22147
+ }
22148
+ if (change.does_not_prove.length) {
22149
+ lines.push("", "### Limits", "");
22150
+ for (const claim of change.does_not_prove) lines.push(`- ${claim}`);
22151
+ }
22152
+ const linkedArtifacts = [
22153
+ ...change.before.artifacts.map((artifact) => ({ side: "before", artifact })),
22154
+ ...change.after.artifacts.map((artifact) => ({ side: "after", artifact }))
22155
+ ].filter(({ artifact }) => Boolean(artifact.url));
22156
+ if (linkedArtifacts.length) {
22157
+ lines.push("", "### Artifacts", "");
22158
+ for (const { side, artifact } of linkedArtifacts.slice(0, 24)) {
22159
+ lines.push(`- ${side}: ${markdownLink2(artifact.name, artifact.url || "")}`);
22160
+ }
22161
+ }
22162
+ lines.push("", input.source?.trim() ? `_Source: ${input.source.trim()}_` : "_Rendered from the Handoff receipt without recomputing its verdict._");
22163
+ return `${lines.join("\n").trim()}
22164
+ `;
22165
+ }
20991
22166
  function buildRiddleProofPrCommentMarkdown(input) {
22167
+ const result = asRecord2(input.result);
22168
+ if (isHandoffReceiptResult(result)) {
22169
+ return buildRiddleProofHandoffPrCommentMarkdown(parseRiddleProofHandoffReceipt(result), input);
22170
+ }
22171
+ if (isChangeReceiptResult(result)) {
22172
+ const handoff = createRiddleProofHandoffReceipt(parseRiddleProofChangeReceipt(result));
22173
+ return buildRiddleProofHandoffPrCommentMarkdown(handoff, input);
22174
+ }
20992
22175
  const summary = summarizeRiddleProofPrComment(input);
20993
22176
  const title = input.title?.trim() || "Riddle Proof Evidence";
20994
22177
  const lines = [
@@ -21012,10 +22195,10 @@ function buildRiddleProofPrCommentMarkdown(input) {
21012
22195
  if (summary.proof_decision) lines.push(`**Proof decision:** \`${summary.proof_decision}\``);
21013
22196
  if (shouldRenderMergeRecommendation(summary)) lines.push(`**Merge recommendation:** ${summary.merge_recommendation}`);
21014
22197
  if (summary.checkpoint_summary) lines.push(`**Checkpoints:** ${checkpointSummaryLine(summary.checkpoint_summary)}`);
21015
- if (summary.proof_url) lines.push(`**Proof URL:** ${markdownLink(summary.proof_url, summary.proof_url)}`);
22198
+ if (summary.proof_url) lines.push(`**Proof URL:** ${markdownLink2(summary.proof_url, summary.proof_url)}`);
21016
22199
  if (summary.preview_id || summary.preview_url) {
21017
22200
  const previewLabel = summary.preview_id ? `\`${summary.preview_id}\`` : "preview";
21018
- lines.push(`**Preview:** ${summary.preview_url ? markdownLink(previewLabel, summary.preview_url) : previewLabel}`);
22201
+ lines.push(`**Preview:** ${summary.preview_url ? markdownLink2(previewLabel, summary.preview_url) : previewLabel}`);
21019
22202
  }
21020
22203
  if (summary.preview_publish_recovered) {
21021
22204
  const detail = summary.preview_publish_error ? `: ${summary.preview_publish_error}` : "";
@@ -21040,7 +22223,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21040
22223
  if (linkedArtifacts.length) {
21041
22224
  lines.push("### Artifacts");
21042
22225
  for (const artifact of linkedArtifacts) {
21043
- lines.push(`- ${markdownLink(artifact.name, artifact.url)}`);
22226
+ lines.push(`- ${markdownLink2(artifact.name, artifact.url)}`);
21044
22227
  }
21045
22228
  if (summary.artifacts.length - (summary.primary_image ? 1 : 0) > linkedArtifacts.length) {
21046
22229
  lines.push(`- ${summary.artifacts.length - (summary.primary_image ? 1 : 0) - linkedArtifacts.length} more artifact(s) omitted`);
@@ -21064,15 +22247,20 @@ function buildRiddleProofPrCommentMarkdown(input) {
21064
22247
  DEFAULT_DIAGNOSTIC_STRING_LIMIT,
21065
22248
  DEFAULT_RIDDLE_API_BASE_URL,
21066
22249
  DEFAULT_RIDDLE_API_KEY_FILE,
22250
+ RIDDLE_PREVIEW_RECEIPT_VERSION,
21067
22251
  RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
21068
22252
  RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
21069
22253
  RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
21070
22254
  RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION,
21071
22255
  RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
22256
+ RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION,
22257
+ RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
21072
22258
  RIDDLE_PROOF_CHANGE_RESULT_VERSION,
21073
22259
  RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
21074
22260
  RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
22261
+ RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
21075
22262
  RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS,
22263
+ RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
21076
22264
  RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
21077
22265
  RIDDLE_PROOF_PLAYABILITY_VERSION,
21078
22266
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
@@ -21111,6 +22299,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21111
22299
  buildAuthorCheckpointPacket,
21112
22300
  buildCheckpointPacketForEngineResult,
21113
22301
  buildProofAssessmentCheckpointPacket,
22302
+ buildRiddleProofHandoffPrCommentMarkdown,
21114
22303
  buildRiddleProofPrCommentMarkdown,
21115
22304
  buildRiddleProofProfileScript,
21116
22305
  buildStageCheckpointPacket,
@@ -21134,6 +22323,9 @@ function buildRiddleProofPrCommentMarkdown(input) {
21134
22323
  createLocalAgentAdapter,
21135
22324
  createLocalAgentJsonRunner,
21136
22325
  createRiddleApiClient,
22326
+ createRiddleProofChangeReceipt,
22327
+ createRiddleProofHandoffReceipt,
22328
+ createRiddleProofObservationReceipt,
21137
22329
  createRiddleProofProfileConfigurationError,
21138
22330
  createRiddleProofProfileEnvironmentBlockedResult,
21139
22331
  createRiddleProofProfileInsufficientResult,
@@ -21144,6 +22336,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21144
22336
  deployRiddlePreview,
21145
22337
  deployRiddleStaticPreview,
21146
22338
  deriveRiddleProofArtifactBodyAssertions,
22339
+ detectRiddlePreviewSource,
21147
22340
  extractBasicGameplayEvidence,
21148
22341
  extractPlayabilityEvidence,
21149
22342
  extractRiddleProofProfileResult,
@@ -21154,6 +22347,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21154
22347
  isSuccessfulStatus,
21155
22348
  isTerminalRiddleJobStatus,
21156
22349
  isTerminalStatus,
22350
+ migrateRiddleProofChangeReceipt,
21157
22351
  nonEmptyString,
21158
22352
  normalizeCheckpointResponse,
21159
22353
  normalizeIntegrationContext,
@@ -21161,6 +22355,10 @@ function buildRiddleProofPrCommentMarkdown(input) {
21161
22355
  normalizeRiddleProofProfile,
21162
22356
  normalizeRunParams,
21163
22357
  normalizeTerminalMetadata,
22358
+ parseRiddlePreviewReceipt,
22359
+ parseRiddleProofChangeReceipt,
22360
+ parseRiddleProofHandoffReceipt,
22361
+ parseRiddleProofObservationReceipt,
21164
22362
  parseRiddleViewport,
21165
22363
  parseVisualProofSession,
21166
22364
  pollRiddleJob,
@@ -21179,6 +22377,8 @@ function buildRiddleProofPrCommentMarkdown(input) {
21179
22377
  resolveRiddleProofProfileRouteUrl,
21180
22378
  resolveRiddleProofProfileTargetUrl,
21181
22379
  resolveRiddleProofProfileTimeoutSec,
22380
+ riddleProofChangeReceiptHtml,
22381
+ riddleProofChangeReceiptMarkdown,
21182
22382
  riddleProofPublicStateAllowsClaim,
21183
22383
  riddleProofPublicStateAllowsMergeRecommendation,
21184
22384
  riddleProofPublicStateMergeRecommendation,