@riddledc/riddle-proof 0.8.77 → 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 +65 -3
  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 +744 -85
  7. package/dist/change-proof.d.cts +100 -8
  8. package/dist/change-proof.d.ts +100 -8
  9. package/dist/change-proof.js +15 -1
  10. package/dist/{chunk-B2DP2LET.js → chunk-5IFZSUPF.js} +52 -13
  11. package/dist/chunk-6VFS2JFR.js +886 -0
  12. package/dist/{chunk-IY4W6STC.js → chunk-7N6X54WG.js} +116 -17
  13. package/dist/{chunk-GG2D3MFZ.js → chunk-FCSJZBC5.js} +26 -1
  14. package/dist/{chunk-H25IDX76.js → chunk-HSGZV2NK.js} +206 -35
  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 +2009 -1058
  19. package/dist/cli.js +7 -6
  20. package/dist/index.cjs +932 -122
  21. package/dist/index.d.cts +4 -3
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +42 -14
  24. package/dist/pr-comment.cjs +416 -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-BILL3UC2.js +0 -488
package/dist/index.cjs CHANGED
@@ -3414,16 +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,
3422
3424
  RIDDLE_PROOF_CHANGE_RECEIPT_VERSION: () => RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
3423
3425
  RIDDLE_PROOF_CHANGE_RESULT_VERSION: () => RIDDLE_PROOF_CHANGE_RESULT_VERSION,
3424
3426
  RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION: () => RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
3425
3427
  RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION: () => RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
3428
+ RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION: () => RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
3426
3429
  RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS: () => RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS,
3430
+ RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION: () => RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
3427
3431
  RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION: () => RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
3428
3432
  RIDDLE_PROOF_PLAYABILITY_VERSION: () => RIDDLE_PROOF_PLAYABILITY_VERSION,
3429
3433
  RIDDLE_PROOF_PROFILE_CHECK_TYPES: () => RIDDLE_PROOF_PROFILE_CHECK_TYPES,
@@ -3462,6 +3466,7 @@ __export(index_exports, {
3462
3466
  buildAuthorCheckpointPacket: () => buildAuthorCheckpointPacket,
3463
3467
  buildCheckpointPacketForEngineResult: () => buildCheckpointPacketForEngineResult,
3464
3468
  buildProofAssessmentCheckpointPacket: () => buildProofAssessmentCheckpointPacket,
3469
+ buildRiddleProofHandoffPrCommentMarkdown: () => buildRiddleProofHandoffPrCommentMarkdown,
3465
3470
  buildRiddleProofPrCommentMarkdown: () => buildRiddleProofPrCommentMarkdown,
3466
3471
  buildRiddleProofProfileScript: () => buildRiddleProofProfileScript,
3467
3472
  buildStageCheckpointPacket: () => buildStageCheckpointPacket,
@@ -3486,6 +3491,8 @@ __export(index_exports, {
3486
3491
  createLocalAgentJsonRunner: () => createCodexExecJsonRunner,
3487
3492
  createRiddleApiClient: () => createRiddleApiClient,
3488
3493
  createRiddleProofChangeReceipt: () => createRiddleProofChangeReceipt,
3494
+ createRiddleProofHandoffReceipt: () => createRiddleProofHandoffReceipt,
3495
+ createRiddleProofObservationReceipt: () => createRiddleProofObservationReceipt,
3489
3496
  createRiddleProofProfileConfigurationError: () => createRiddleProofProfileConfigurationError,
3490
3497
  createRiddleProofProfileEnvironmentBlockedResult: () => createRiddleProofProfileEnvironmentBlockedResult,
3491
3498
  createRiddleProofProfileInsufficientResult: () => createRiddleProofProfileInsufficientResult,
@@ -3496,6 +3503,7 @@ __export(index_exports, {
3496
3503
  deployRiddlePreview: () => deployRiddlePreview,
3497
3504
  deployRiddleStaticPreview: () => deployRiddleStaticPreview,
3498
3505
  deriveRiddleProofArtifactBodyAssertions: () => deriveRiddleProofArtifactBodyAssertions,
3506
+ detectRiddlePreviewSource: () => detectRiddlePreviewSource,
3499
3507
  extractBasicGameplayEvidence: () => extractBasicGameplayEvidence,
3500
3508
  extractPlayabilityEvidence: () => extractPlayabilityEvidence,
3501
3509
  extractRiddleProofProfileResult: () => extractRiddleProofProfileResult,
@@ -3506,6 +3514,7 @@ __export(index_exports, {
3506
3514
  isSuccessfulStatus: () => isSuccessfulStatus,
3507
3515
  isTerminalRiddleJobStatus: () => isTerminalRiddleJobStatus,
3508
3516
  isTerminalStatus: () => isTerminalStatus,
3517
+ migrateRiddleProofChangeReceipt: () => migrateRiddleProofChangeReceipt,
3509
3518
  nonEmptyString: () => nonEmptyString,
3510
3519
  normalizeCheckpointResponse: () => normalizeCheckpointResponse,
3511
3520
  normalizeIntegrationContext: () => normalizeIntegrationContext,
@@ -3513,6 +3522,10 @@ __export(index_exports, {
3513
3522
  normalizeRiddleProofProfile: () => normalizeRiddleProofProfile,
3514
3523
  normalizeRunParams: () => normalizeRunParams,
3515
3524
  normalizeTerminalMetadata: () => normalizeTerminalMetadata,
3525
+ parseRiddlePreviewReceipt: () => parseRiddlePreviewReceipt,
3526
+ parseRiddleProofChangeReceipt: () => parseRiddleProofChangeReceipt,
3527
+ parseRiddleProofHandoffReceipt: () => parseRiddleProofHandoffReceipt,
3528
+ parseRiddleProofObservationReceipt: () => parseRiddleProofObservationReceipt,
3516
3529
  parseRiddleViewport: () => parseRiddleViewport,
3517
3530
  parseVisualProofSession: () => parseVisualProofSession,
3518
3531
  pollRiddleJob: () => pollRiddleJob,
@@ -10947,6 +10960,12 @@ function profileScreenshotLabels(viewports) {
10947
10960
  }
10948
10961
  return labels;
10949
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
+ }
10950
10969
  function profileSetupSummary(viewports, actionCount, expectedActionCountByViewport, finalScreenshotFullPage) {
10951
10970
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === void 0 ? void 0 : finalScreenshotFullPage !== false;
10952
10971
  const finalScreenshotCount = viewports.filter((viewport) => typeof viewport.screenshot_label === "string" && viewport.screenshot_label.trim()).length;
@@ -13797,6 +13816,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
13797
13816
  const status = profileStatusFromEvidence(profile, evidence, checks);
13798
13817
  const firstViewport = evidence?.viewports?.[0];
13799
13818
  const screenshots = profileScreenshotLabels(evidence?.viewports);
13819
+ const canonicalScreenshots = profileFinalScreenshotLabels(evidence?.viewports);
13820
+ const setupScreenshots = profileAllSetupScreenshotLabels(evidence?.viewports);
13800
13821
  const result = {
13801
13822
  version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
13802
13823
  profile_name: profile.name,
@@ -13806,6 +13827,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
13806
13827
  route: routeForViewport(firstViewport, evidence?.target_url),
13807
13828
  artifacts: {
13808
13829
  screenshots,
13830
+ canonical_screenshots: canonicalScreenshots,
13831
+ setup_screenshots: setupScreenshots,
13809
13832
  console: "console.json",
13810
13833
  proof_json: "proof.json",
13811
13834
  dom_summary: "dom-summary.json",
@@ -15124,6 +15147,12 @@ function profileScreenshotLabels(viewports) {
15124
15147
  }
15125
15148
  return labels;
15126
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
+ }
15127
15156
  function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewport, finalScreenshotFullPage) {
15128
15157
  const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === undefined
15129
15158
  ? undefined
@@ -15992,6 +16021,8 @@ function assessProfile(profile, evidence) {
15992
16021
  else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
15993
16022
  else if (checks.some((check) => check.status === "failed")) status = "product_regression";
15994
16023
  const screenshotLabels = profileScreenshotLabels(viewports);
16024
+ const canonicalScreenshotLabels = profileFinalScreenshotLabels(viewports);
16025
+ const setupScreenshotLabels = profileAllSetupScreenshotLabels(viewports);
15995
16026
  const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
15996
16027
  const passedChecks = checks.filter((check) => check.status === "passed").length;
15997
16028
  const failedChecks = checks.filter((check) => check.status === "failed").length;
@@ -16008,7 +16039,14 @@ function assessProfile(profile, evidence) {
16008
16039
  status,
16009
16040
  baseline_policy: profile.baseline_policy || "invariant_only",
16010
16041
  route,
16011
- 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
+ },
16012
16050
  checks,
16013
16051
  summary,
16014
16052
  captured_at: evidence.captured_at,
@@ -19936,10 +19974,263 @@ function suggestRiddleProofProfileChecks(input) {
19936
19974
  };
19937
19975
  }
19938
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
+
19939
20228
  // src/change-proof.ts
19940
20229
  var RIDDLE_PROOF_CHANGE_CONTRACT_VERSION = "riddle-proof.change-contract.v1";
19941
20230
  var RIDDLE_PROOF_CHANGE_RESULT_VERSION = "riddle-proof.change-result.v1";
19942
- var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.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";
19943
20234
  var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
19944
20235
  var DEFAULT_AFTER_STATUSES = ["passed"];
19945
20236
  var DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE = ["product_regression"];
@@ -20061,13 +20352,163 @@ function assessDelta(delta, before, after) {
20061
20352
  }
20062
20353
  return checkStatusTransitionResult(delta, before, after);
20063
20354
  }
20064
- 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) {
20065
20505
  const groupResults = [groups.before, groups.after];
20066
20506
  if (groupResults.some((group) => group.status === "environment_blocked")) return "environment_blocked";
20067
20507
  if (groupResults.some((group) => group.status === "configuration_error")) return "configuration_error";
20068
20508
  if (deltas.some((delta) => delta.status === "configuration_error")) return "configuration_error";
20069
20509
  if (groupResults.some((group) => group.status === "needs_human_review")) return "needs_human_review";
20070
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";
20071
20512
  if (!deltas.length || deltas.some((delta) => delta.status === "proof_insufficient")) return "proof_insufficient";
20072
20513
  if (groupResults.some((group) => !group.ok)) return "product_regression";
20073
20514
  if (deltas.some((delta) => delta.status === "failed")) return "product_regression";
@@ -20082,18 +20523,38 @@ function changeSummary(name, status, deltas) {
20082
20523
  return `${name} failed ${deltas.filter((delta) => delta.status === "failed").length} change delta(s).`;
20083
20524
  }
20084
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);
20085
20528
  const groups = {
20086
- before: groupResult("before", contract.before, input.before_result),
20087
- after: groupResult("after", contract.after, input.after_result)
20529
+ before: groupResult("before", contract.before, beforeResult),
20530
+ after: groupResult("after", contract.after, afterResult)
20088
20531
  };
20089
- const deltas = (contract.deltas || []).map((delta) => assessDelta(delta, input.before_result, input.after_result));
20090
- 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);
20091
20551
  return {
20092
20552
  version: RIDDLE_PROOF_CHANGE_RESULT_VERSION,
20093
20553
  contract_name: contract.name,
20094
20554
  status,
20095
20555
  groups,
20096
20556
  deltas,
20557
+ source_bindings: sourceBindings,
20097
20558
  summary: changeSummary(contract.name, status, deltas),
20098
20559
  metadata: contract.metadata
20099
20560
  };
@@ -20106,7 +20567,7 @@ function changeReceiptVerdict(status) {
20106
20567
  if (status === "proof_insufficient") return "proof_insufficient";
20107
20568
  return "not_mergeable";
20108
20569
  }
20109
- function profileCheckCounts(result) {
20570
+ function profileCheckCounts2(result) {
20110
20571
  const counts = {
20111
20572
  total: result.checks.length,
20112
20573
  passed: 0,
@@ -20122,86 +20583,90 @@ function profileCheckCounts(result) {
20122
20583
  }
20123
20584
  return counts;
20124
20585
  }
20125
- function artifactKind(ref) {
20126
- const text = [ref.name, ref.url, ref.path, ref.kind, ref.content_type].filter(Boolean).join(" ").toLowerCase();
20127
- if (/\bimage\b/.test(text) || /\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(text)) return "image";
20128
- if (/\bjson\b|\btext\b|\bmarkdown\b|\bhtml\b|\blog\b/.test(text) || /\.(json|md|txt|html|log|har)(\?|#|$)/.test(text)) return "data";
20129
- return "artifact";
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(" "));
20130
20589
  }
20131
- function receiptArtifactFromRef(side, ref) {
20132
- return {
20133
- side,
20134
- name: ref.name,
20135
- kind: artifactKind(ref),
20136
- url: ref.url,
20137
- path: ref.path,
20138
- source: ref.source
20139
- };
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());
20140
20594
  }
20141
- function comparableArtifactName(value) {
20142
- return (value || "").split(/[/?#]/).pop()?.toLowerCase().replace(/\.(png|jpe?g|gif|webp|avif|svg)$/u, "").replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/g, "") || "";
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 };
20143
20606
  }
20144
- function artifactMatchesScreenshotLabel(artifact, screenshot) {
20145
- const screenshotName = comparableArtifactName(screenshot);
20146
- if (!screenshotName) return false;
20147
- return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === screenshotName || name.endsWith(`-${screenshotName}`));
20607
+ function noShippingAuthorization() {
20608
+ return { status: "not_granted", authorized: false, source: "none" };
20148
20609
  }
20149
- function collectReceiptArtifacts(side, result) {
20150
- const artifacts = [];
20151
- const seen = /* @__PURE__ */ new Set();
20152
- const add = (artifact) => {
20153
- const key = artifact.url || artifact.path || `${artifact.kind}:${artifact.name}`;
20154
- if (!artifact.name || seen.has(key)) return;
20155
- seen.add(key);
20156
- artifacts.push(artifact);
20157
- };
20158
- for (const ref of result.artifacts.riddle_artifacts || []) {
20159
- add(receiptArtifactFromRef(side, ref));
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.`);
20160
20617
  }
20161
- for (const screenshot of result.artifacts.screenshots || []) {
20162
- if (artifacts.some((artifact) => artifactMatchesScreenshotLabel(artifact, screenshot))) continue;
20163
- add({
20164
- side,
20165
- name: screenshot,
20166
- kind: "image"
20167
- });
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.`);
20168
20627
  }
20169
- return artifacts;
20170
20628
  }
20171
- function artifactIsScreenshot(artifact) {
20172
- if (artifact.kind !== "image") return false;
20173
- return /screenshot|\.png|\.jpe?g|\.webp|\.gif|\.avif|\.svg/i.test([artifact.name, artifact.url, artifact.path].filter(Boolean).join(" "));
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;
20174
20632
  }
20175
- function receiptSide(side, source, result) {
20176
- const artifacts = collectReceiptArtifacts(side, result);
20177
- return {
20178
- side,
20179
- source,
20180
- profile_name: result.profile_name,
20181
- status: result.status,
20182
- summary: result.summary,
20183
- route: result.route,
20184
- captured_at: result.captured_at,
20185
- checks: profileCheckCounts(result),
20186
- screenshots: artifacts.filter(artifactIsScreenshot),
20187
- artifacts
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
20188
20638
  };
20189
20639
  }
20190
- function metadataStringList(metadata, key) {
20191
- const value = metadata?.[key];
20192
- if (!Array.isArray(value)) return [];
20193
- return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
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
+ });
20194
20652
  }
20195
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");
20196
20659
  return {
20197
20660
  version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
20198
20661
  contract_name: input.result.contract_name,
20199
20662
  profile_name: input.profile_name,
20200
20663
  status: input.result.status,
20201
- verdict: changeReceiptVerdict(input.result.status),
20664
+ verdict,
20202
20665
  summary: input.result.summary,
20203
- before: receiptSide("before", input.before_source, input.before_result),
20204
- after: receiptSide("after", input.after_source, input.after_result),
20666
+ before,
20667
+ after,
20668
+ groups: input.result.groups,
20669
+ source_bindings: input.result.source_bindings,
20205
20670
  deltas: input.result.deltas.map((delta) => ({
20206
20671
  type: delta.type,
20207
20672
  label: delta.label,
@@ -20210,17 +20675,201 @@ function createRiddleProofChangeReceipt(input) {
20210
20675
  after_observed: delta.after_observed,
20211
20676
  message: delta.message
20212
20677
  })),
20678
+ recommendation: changeRecommendation(verdict),
20679
+ shipping_authorization: shippingAuthorization,
20213
20680
  proves: metadataStringList(input.contract.metadata, "required_receipts"),
20214
20681
  does_not_prove: metadataStringList(input.contract.metadata, "does_not_prove"),
20215
20682
  metadata: input.result.metadata
20216
20683
  };
20217
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
+ }
20218
20823
  function markdownTableCell(value) {
20219
20824
  return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
20220
20825
  }
20221
20826
  function artifactTarget(artifact) {
20222
20827
  return artifact.url || artifact.path;
20223
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
+ }
20224
20873
  function markdownLink(label, target) {
20225
20874
  return `[${label.replace(/\]/g, "\\]")}](${target})`;
20226
20875
  }
@@ -20244,6 +20893,8 @@ function appendMarkdownArtifacts(lines, title, artifacts) {
20244
20893
  lines.push("");
20245
20894
  }
20246
20895
  function riddleProofChangeReceiptMarkdown(receipt) {
20896
+ const before = receiptSideView("before", receipt.before);
20897
+ const after = receiptSideView("after", receipt.after);
20247
20898
  const lines = [
20248
20899
  "# Riddle Proof Change Receipt",
20249
20900
  "",
@@ -20251,6 +20902,8 @@ function riddleProofChangeReceiptMarkdown(receipt) {
20251
20902
  `**Status:** ${receipt.status}`,
20252
20903
  `**Contract:** ${receipt.contract_name}`,
20253
20904
  receipt.profile_name ? `**Profile:** ${receipt.profile_name}` : "",
20905
+ `**Recommendation:** ${receipt.recommendation.label}`,
20906
+ `**Shipping authorization:** ${receipt.shipping_authorization.status}`,
20254
20907
  "",
20255
20908
  receipt.summary,
20256
20909
  "",
@@ -20258,8 +20911,15 @@ function riddleProofChangeReceiptMarkdown(receipt) {
20258
20911
  "",
20259
20912
  "| Side | Source | Status | Checks |",
20260
20913
  "| --- | --- | --- | --- |",
20261
- `| Before | ${markdownTableCell(receipt.before.source)} | ${receipt.before.status} | ${receipt.before.checks.passed} passed / ${receipt.before.checks.failed} failed |`,
20262
- `| After | ${markdownTableCell(receipt.after.source)} | ${receipt.after.status} | ${receipt.after.checks.passed} passed / ${receipt.after.checks.failed} failed |`,
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")} |`,
20263
20923
  "",
20264
20924
  "## Delta Checks",
20265
20925
  "",
@@ -20271,8 +20931,8 @@ function riddleProofChangeReceiptMarkdown(receipt) {
20271
20931
  if (delta.message) lines.push(`| ${markdownTableCell(`${delta.label} message`)} | ${markdownTableCell(delta.message)} | | |`);
20272
20932
  }
20273
20933
  lines.push("");
20274
- appendMarkdownArtifacts(lines, "Before Screenshot", receipt.before.screenshots);
20275
- appendMarkdownArtifacts(lines, "After Screenshot", receipt.after.screenshots);
20934
+ appendMarkdownArtifacts(lines, "Before Canonical Screenshot", before.screenshots.slice(0, 1));
20935
+ appendMarkdownArtifacts(lines, "After Canonical Screenshot", after.screenshots.slice(0, 1));
20276
20936
  if (receipt.proves.length) {
20277
20937
  lines.push("## What This Proves", "");
20278
20938
  for (const claim of receipt.proves) lines.push(`- ${claim}`);
@@ -20283,7 +20943,7 @@ function riddleProofChangeReceiptMarkdown(receipt) {
20283
20943
  for (const claim of receipt.does_not_prove) lines.push(`- ${claim}`);
20284
20944
  lines.push("");
20285
20945
  }
20286
- const linkedArtifacts = [...receipt.before.artifacts, ...receipt.after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 24);
20946
+ const linkedArtifacts = [...before.artifacts, ...after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 24);
20287
20947
  if (linkedArtifacts.length) {
20288
20948
  lines.push("## Artifacts", "");
20289
20949
  for (const artifact of linkedArtifacts) {
@@ -20315,7 +20975,9 @@ function htmlList(items, emptyText) {
20315
20975
  return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
20316
20976
  }
20317
20977
  function riddleProofChangeReceiptHtml(receipt) {
20318
- const artifactLinks = [...receipt.before.artifacts, ...receipt.after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 32);
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);
20319
20981
  const deltaRows = receipt.deltas.map((delta) => `
20320
20982
  <tr>
20321
20983
  <td>${escapeHtml(delta.label)}</td>
@@ -20363,26 +21025,37 @@ function riddleProofChangeReceiptHtml(receipt) {
20363
21025
  <h1>${escapeHtml(receipt.contract_name)}</h1>
20364
21026
  <p>${escapeHtml(receipt.summary)}</p>
20365
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>
20366
21029
  </header>
20367
21030
  <section>
20368
21031
  <h2>Evidence Pair</h2>
20369
21032
  <div class="grid">
20370
21033
  <div class="side">
20371
21034
  <h3>Before</h3>
20372
- <p><code>${escapeHtml(receipt.before.source)}</code></p>
20373
- <p>Status: <strong class="${escapeHtml(receipt.before.status)}">${escapeHtml(receipt.before.status)}</strong></p>
20374
- <p>${escapeHtml(receipt.before.summary)}</p>
20375
- <p class="muted">${receipt.before.checks.passed} passed / ${receipt.before.checks.failed} failed checks</p>
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>
20376
21039
  </div>
20377
21040
  <div class="side">
20378
21041
  <h3>After</h3>
20379
- <p><code>${escapeHtml(receipt.after.source)}</code></p>
20380
- <p>Status: <strong class="${escapeHtml(receipt.after.status)}">${escapeHtml(receipt.after.status)}</strong></p>
20381
- <p>${escapeHtml(receipt.after.summary)}</p>
20382
- <p class="muted">${receipt.after.checks.passed} passed / ${receipt.after.checks.failed} failed checks</p>
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>
20383
21046
  </div>
20384
21047
  </div>
20385
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>
20386
21059
  <section>
20387
21060
  <h2>Delta Checks</h2>
20388
21061
  <table>
@@ -20393,8 +21066,8 @@ function riddleProofChangeReceiptHtml(receipt) {
20393
21066
  <section>
20394
21067
  <h2>Screenshots</h2>
20395
21068
  <div class="grid">
20396
- <div>${receipt.before.screenshots.slice(0, 4).map(htmlArtifactCard).join("") || `<p class="muted">No before screenshots recorded.</p>`}</div>
20397
- <div>${receipt.after.screenshots.slice(0, 4).map(htmlArtifactCard).join("") || `<p class="muted">No after screenshots recorded.</p>`}</div>
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>
20398
21071
  </div>
20399
21072
  </section>
20400
21073
  <section>
@@ -20481,6 +21154,7 @@ async function getRiddleBalance(config = {}) {
20481
21154
  }
20482
21155
  function previewDeployResultFromRecord(input) {
20483
21156
  const { record, id, label, framework, expiresAt, publishRecovered, publishError } = input;
21157
+ const receipt = record.receipt && typeof record.receipt === "object" ? parseRiddlePreviewReceipt(record.receipt) : void 0;
20484
21158
  return {
20485
21159
  ok: true,
20486
21160
  id: String(record.id || record.preview_id || id),
@@ -20489,13 +21163,32 @@ function previewDeployResultFromRecord(input) {
20489
21163
  preview_url: String(record.preview_url || ""),
20490
21164
  file_count: typeof record.file_count === "number" ? record.file_count : void 0,
20491
21165
  total_bytes: typeof record.total_bytes === "number" ? record.total_bytes : void 0,
20492
- expires_at: expiresAt,
21166
+ expires_at: receipt?.expires_at || expiresAt,
21167
+ receipt,
20493
21168
  publish_recovered: publishRecovered || void 0,
20494
21169
  publish_error: publishError,
20495
21170
  warnings: input.warnings?.length ? input.warnings : void 0,
20496
21171
  raw: record
20497
21172
  };
20498
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
+ }
20499
21192
  function canRecoverPreviewPublish(error) {
20500
21193
  return error instanceof RiddleApiError && PREVIEW_PUBLISH_RECOVERY_STATUSES.has(error.status);
20501
21194
  }
@@ -20578,12 +21271,13 @@ async function waitForPublishedPreview(config, input) {
20578
21271
  }
20579
21272
  throw input.publishError;
20580
21273
  }
20581
- async function deployRiddlePreview(config, directory, label, framework = "static") {
21274
+ async function deployRiddlePreview(config, directory, label, framework = "static", options = {}) {
20582
21275
  if (!directory?.trim()) throw new Error("directory is required");
20583
21276
  if (!label?.trim()) throw new Error("label is required");
20584
21277
  if (framework !== "spa" && framework !== "static") throw new Error("framework must be spa or static");
20585
21278
  const startedAt = Date.now();
20586
21279
  const warnings = collectRiddlePreviewDeployWarnings(directory, framework);
21280
+ const source = options.source || detectRiddlePreviewSource(directory);
20587
21281
  const emitProgress = previewProgressEmitter(config, { label, framework, directory, startedAt, warnings });
20588
21282
  await emitProgress({ stage: "validating", message: "checking preview input directory" });
20589
21283
  const localSummary = summarizePreviewDirectory(directory);
@@ -20595,7 +21289,7 @@ async function deployRiddlePreview(config, directory, label, framework = "static
20595
21289
  });
20596
21290
  const created = await riddleRequestJson(config, "/v1/preview", {
20597
21291
  method: "POST",
20598
- body: JSON.stringify({ framework, label })
21292
+ body: JSON.stringify({ framework, label, source })
20599
21293
  });
20600
21294
  const id = String(created.id || "");
20601
21295
  const uploadUrl = String(created.upload_url || "");
@@ -20737,8 +21431,8 @@ function collectRiddlePreviewDeployWarnings(directory, framework = "static") {
20737
21431
  return [];
20738
21432
  }
20739
21433
  }
20740
- async function deployRiddleStaticPreview(config, directory, label) {
20741
- return deployRiddlePreview(config, directory, label, "static");
21434
+ async function deployRiddleStaticPreview(config, directory, label, options = {}) {
21435
+ return deployRiddlePreview(config, directory, label, "static", options);
20742
21436
  }
20743
21437
  function createTarball(directory, label, exclude = []) {
20744
21438
  const scratch = (0, import_node_fs5.mkdtempSync)(import_node_path5.default.join((0, import_node_os2.tmpdir)(), "riddle-upload-"));
@@ -20860,21 +21554,31 @@ function parseTimestampMs(value) {
20860
21554
  }
20861
21555
  function buildPollSnapshot(jobId, job, input) {
20862
21556
  const status = job?.status ? String(job.status) : null;
21557
+ const phase = job?.phase ? String(job.phase) : null;
20863
21558
  const terminal = isTerminalRiddleJobStatus(status);
20864
21559
  const createdAt = stringField(job, "created_at");
20865
21560
  const submittedAt = stringField(job, "submitted_at");
20866
21561
  const completedAt = stringField(job, "completed_at");
20867
21562
  const createdMs = parseTimestampMs(createdAt);
20868
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);
20869
21570
  let queueElapsedMs = null;
20870
- if (createdMs !== null && submittedMs !== null) {
20871
- queueElapsedMs = Math.max(0, submittedMs - createdMs);
20872
- } else if (createdMs !== null && !submittedAt && !terminal) {
20873
- 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);
20874
21577
  }
20875
21578
  return {
20876
21579
  job_id: jobId,
20877
21580
  status,
21581
+ phase,
20878
21582
  terminal,
20879
21583
  attempt: input.attempt,
20880
21584
  attempts: input.attempts,
@@ -20884,7 +21588,9 @@ function buildPollSnapshot(jobId, job, input) {
20884
21588
  completed_at: completedAt,
20885
21589
  queue_elapsed_ms: queueElapsedMs,
20886
21590
  pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
20887
- 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
20888
21594
  };
20889
21595
  }
20890
21596
  function pollMessage(snapshot, timedOut) {
@@ -20893,7 +21599,7 @@ function pollMessage(snapshot, timedOut) {
20893
21599
  const wakeHint = snapshot.running_without_submission && !snapshot.created_at && !snapshot.submitted_at ? ` ${RIDDLE_UNSUBMITTED_WAKE_HINT}.` : "";
20894
21600
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
20895
21601
  const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
20896
- 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}`;
20897
21603
  }
20898
21604
  async function pollRiddleJob(config, jobId, options = {}) {
20899
21605
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -20927,6 +21633,7 @@ async function pollRiddleJob(config, jobId, options = {}) {
20927
21633
  };
20928
21634
  const progressKey = [
20929
21635
  lastSnapshot.status || "unknown",
21636
+ lastSnapshot.phase || "unknown",
20930
21637
  lastSnapshot.terminal ? "terminal" : "nonterminal",
20931
21638
  lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
20932
21639
  ].join(":");
@@ -20993,8 +21700,8 @@ function createRiddleApiClient(config = {}) {
20993
21700
  apiKeySource: () => resolveRiddleApiKeySource(config),
20994
21701
  requestJson: (pathname, init) => riddleRequestJson(config, pathname, init),
20995
21702
  getBalance: () => getRiddleBalance(config),
20996
- deployPreview: (directory, label, framework = "static") => deployRiddlePreview(config, directory, label, framework),
20997
- 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),
20998
21705
  runScript: (input) => runRiddleScript(config, input),
20999
21706
  runServerPreview: (input) => runRiddleServerPreview(config, input),
21000
21707
  pollJob: (jobId, options) => pollRiddleJob(config, jobId, options)
@@ -21025,7 +21732,7 @@ function firstStringValue2(...values) {
21025
21732
  }
21026
21733
  return void 0;
21027
21734
  }
21028
- function artifactKind2(name, url) {
21735
+ function artifactKind(name, url) {
21029
21736
  const target = `${name} ${url}`.toLowerCase();
21030
21737
  if (/\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(target)) return "image";
21031
21738
  if (/\.(json|har|txt|md|html|log)(\?|#|$)/.test(target)) return "data";
@@ -21050,7 +21757,7 @@ function collectArtifacts(runResponse) {
21050
21757
  artifacts.push({
21051
21758
  name,
21052
21759
  url,
21053
- kind: artifactKind2(name, url),
21760
+ kind: artifactKind(name, url),
21054
21761
  size_bytes: numberValue5(artifact.size)
21055
21762
  });
21056
21763
  }
@@ -21070,7 +21777,7 @@ function collectProfileArtifacts(result) {
21070
21777
  artifacts.push({
21071
21778
  name,
21072
21779
  url,
21073
- kind: artifactKind2(name, url),
21780
+ kind: artifactKind(name, url),
21074
21781
  size_bytes: numberValue5(artifact.size_bytes) ?? numberValue5(artifact.size)
21075
21782
  });
21076
21783
  }
@@ -21081,9 +21788,10 @@ function collectChangeReceiptArtifacts(result) {
21081
21788
  const seen = /* @__PURE__ */ new Set();
21082
21789
  const collectSide = (sideName, side) => {
21083
21790
  const candidates = [
21791
+ side.canonical_screenshot,
21084
21792
  ...asArray(side.screenshots),
21085
21793
  ...asArray(side.artifacts)
21086
- ];
21794
+ ].filter(Boolean);
21087
21795
  for (const [index, item] of candidates.entries()) {
21088
21796
  const artifact = asRecord2(item);
21089
21797
  const url = stringValue7(artifact.url) || stringValue7(artifact.path);
@@ -21094,7 +21802,7 @@ function collectChangeReceiptArtifacts(result) {
21094
21802
  artifacts.push({
21095
21803
  name,
21096
21804
  url,
21097
- kind: artifactKind2(name, url),
21805
+ kind: artifactKind(name, url),
21098
21806
  size_bytes: numberValue5(artifact.size_bytes) ?? numberValue5(artifact.size)
21099
21807
  });
21100
21808
  }
@@ -21129,7 +21837,7 @@ function pageSummaries(result) {
21129
21837
  }
21130
21838
  return pages;
21131
21839
  }
21132
- function profileCheckCounts2(result) {
21840
+ function profileCheckCounts3(result) {
21133
21841
  const checks = asArray(result.checks);
21134
21842
  if (checks.length) {
21135
21843
  let passed2 = 0;
@@ -21175,9 +21883,10 @@ function changeReceiptPageSummaries(result) {
21175
21883
  for (const sideName of ["before", "after"]) {
21176
21884
  const side = asRecord2(result[sideName]);
21177
21885
  if (!Object.keys(side).length) continue;
21178
- const checks = asRecord2(side.checks);
21886
+ const profileSummary = asRecord2(side.profile_summary);
21887
+ const checks = Object.keys(asRecord2(side.checks)).length ? asRecord2(side.checks) : asRecord2(profileSummary.checks);
21179
21888
  pages.push({
21180
- route: firstStringValue2(side.source, side.profile_name) || sideName,
21889
+ route: firstStringValue2(asRecord2(side.target).url, side.source, profileSummary.profile_name, side.profile_name) || sideName,
21181
21890
  passed: numberValue5(checks.passed) ?? 0,
21182
21891
  failed: numberValue5(checks.failed) ?? 0
21183
21892
  });
@@ -21240,7 +21949,11 @@ function isProfileResult(result) {
21240
21949
  return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
21241
21950
  }
21242
21951
  function isChangeReceiptResult(result) {
21243
- return stringValue7(result.version) === "riddle-proof.change-receipt.v1";
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;
21244
21957
  }
21245
21958
  function isChangeResult(result) {
21246
21959
  const version = stringValue7(result.version);
@@ -21248,10 +21961,13 @@ function isChangeResult(result) {
21248
21961
  }
21249
21962
  function summarizeRiddleProofPrComment(input) {
21250
21963
  const runResponse = asRecord2(input.runResponse);
21251
- 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;
21252
21968
  const proofResult = asRecord2(runResponse.proofResult);
21253
21969
  const profileResult = isProfileResult(result);
21254
- const changeReceiptResult = isChangeReceiptResult(result);
21970
+ const changeReceiptResult = Boolean(parsedChangeReceipt);
21255
21971
  const changeResult = changeReceiptResult || isChangeResult(result);
21256
21972
  const profileRiddle = asRecord2(result.riddle);
21257
21973
  const preview = asRecord2(runResponse.preview);
@@ -21260,7 +21976,7 @@ function summarizeRiddleProofPrComment(input) {
21260
21976
  const resultDetails = asRecord2(result.details);
21261
21977
  const resultRaw = asRecord2(result.raw);
21262
21978
  const rawDetails = asRecord2(resultRaw.details);
21263
- const profileChecks = profileResult ? profileCheckCounts2(result) : void 0;
21979
+ const profileChecks = profileResult ? profileCheckCounts3(result) : void 0;
21264
21980
  const changeChecks = changeResult ? changeReceiptCheckCounts(result) : void 0;
21265
21981
  const artifacts = mergeArtifacts(
21266
21982
  collectArtifacts(runResponse),
@@ -21272,7 +21988,7 @@ function summarizeRiddleProofPrComment(input) {
21272
21988
  delete checkSource.ok;
21273
21989
  const nestedChecks = summarizeExplicitChecks(checkSource);
21274
21990
  const resultStatus = firstStringValue2(result.status, stopCondition.status);
21275
- const ok = changeResult ? resultStatus === "passed" : booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
21991
+ const ok = changeReceiptResult ? parsedChangeReceipt?.recommendation.merge_recommended ?? false : changeResult ? resultStatus === "passed" : booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
21276
21992
  const checkpointSummary = checkpointSummaryFrom2(
21277
21993
  result.checkpoint_summary,
21278
21994
  stopCondition.checkpoint_summary,
@@ -21280,19 +21996,19 @@ function summarizeRiddleProofPrComment(input) {
21280
21996
  rawDetails.checkpoint_summary,
21281
21997
  proofResult.checkpoint_summary
21282
21998
  );
21283
- const publicState = summarizeRiddleProofPublicState({
21999
+ const publicState = changeReceiptResult ? void 0 : summarizeRiddleProofPublicState({
21284
22000
  ...result,
21285
22001
  status: resultStatus,
21286
22002
  checkpoint_summary: checkpointSummary || result.checkpoint_summary
21287
22003
  });
21288
- const mergeRecommendation = riddleProofPublicStateMergeRecommendation(
22004
+ const mergeRecommendation = publicState ? riddleProofPublicStateMergeRecommendation(
21289
22005
  publicState,
21290
22006
  firstStringValue2(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
21291
- );
22007
+ ) : void 0;
21292
22008
  return {
21293
22009
  ok,
21294
22010
  status: firstStringValue2(proofResult.status, profileRiddle.status),
21295
- result_status: changeResult ? resultStatus : publicState.status,
22011
+ result_status: changeResult ? resultStatus : publicState?.status,
21296
22012
  job_id: firstStringValue2(proofResult.job_id, profileRiddle.job_id),
21297
22013
  duration_ms: numberValue5(proofResult.duration_ms) ?? numberValue5(profileRiddle.elapsed_ms),
21298
22014
  proof_url: stringValue7(runResponse.proofUrl),
@@ -21300,13 +22016,13 @@ function summarizeRiddleProofPrComment(input) {
21300
22016
  preview_url: stringValue7(preview.preview_url) || stringValue7(preview.url),
21301
22017
  preview_publish_recovered: booleanValue3(preview.publish_recovered),
21302
22018
  preview_publish_error: stringValue7(preview.publish_error),
21303
- ship_held: publicState.ship_held,
21304
- shipping_disabled: publicState.shipping_disabled,
21305
- ship_authorized: publicState.ship_authorized,
21306
- merge_ready: publicState.merge_ready,
21307
- 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,
21308
22024
  proof_decision: firstStringValue2(result.proof_decision, stopCondition.proof_decision, resultRaw.proof_decision),
21309
- merge_recommendation: changeReceiptResult && resultStatus ? stringValue7(result.verdict) : mergeRecommendation,
22025
+ merge_recommendation: changeReceiptResult ? parsedChangeReceipt?.recommendation.label : mergeRecommendation,
21310
22026
  checkpoint_summary: checkpointSummary,
21311
22027
  public_state: publicState,
21312
22028
  passed_checks: changeChecks?.passed ?? profileChecks?.passed ?? nestedChecks.passed,
@@ -21374,7 +22090,88 @@ function checkpointSummaryLine(summary) {
21374
22090
  summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
21375
22091
  ].filter(Boolean).join("; ");
21376
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
+ }
21377
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
+ }
21378
22175
  const summary = summarizeRiddleProofPrComment(input);
21379
22176
  const title = input.title?.trim() || "Riddle Proof Evidence";
21380
22177
  const lines = [
@@ -21450,16 +22247,20 @@ function buildRiddleProofPrCommentMarkdown(input) {
21450
22247
  DEFAULT_DIAGNOSTIC_STRING_LIMIT,
21451
22248
  DEFAULT_RIDDLE_API_BASE_URL,
21452
22249
  DEFAULT_RIDDLE_API_KEY_FILE,
22250
+ RIDDLE_PREVIEW_RECEIPT_VERSION,
21453
22251
  RIDDLE_PROOF_BASIC_GAMEPLAY_ASSESSMENT_VERSION,
21454
22252
  RIDDLE_PROOF_BASIC_GAMEPLAY_CATCH_VERSION,
21455
22253
  RIDDLE_PROOF_BASIC_GAMEPLAY_VERSION,
21456
22254
  RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION,
21457
22255
  RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
22256
+ RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION,
21458
22257
  RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
21459
22258
  RIDDLE_PROOF_CHANGE_RESULT_VERSION,
21460
22259
  RIDDLE_PROOF_CHECKPOINT_PACKET_VERSION,
21461
22260
  RIDDLE_PROOF_CHECKPOINT_RESPONSE_VERSION,
22261
+ RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
21462
22262
  RIDDLE_PROOF_HOSTED_PROFILE_ARTIFACTS,
22263
+ RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
21463
22264
  RIDDLE_PROOF_PLAYABILITY_ASSESSMENT_VERSION,
21464
22265
  RIDDLE_PROOF_PLAYABILITY_VERSION,
21465
22266
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
@@ -21498,6 +22299,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21498
22299
  buildAuthorCheckpointPacket,
21499
22300
  buildCheckpointPacketForEngineResult,
21500
22301
  buildProofAssessmentCheckpointPacket,
22302
+ buildRiddleProofHandoffPrCommentMarkdown,
21501
22303
  buildRiddleProofPrCommentMarkdown,
21502
22304
  buildRiddleProofProfileScript,
21503
22305
  buildStageCheckpointPacket,
@@ -21522,6 +22324,8 @@ function buildRiddleProofPrCommentMarkdown(input) {
21522
22324
  createLocalAgentJsonRunner,
21523
22325
  createRiddleApiClient,
21524
22326
  createRiddleProofChangeReceipt,
22327
+ createRiddleProofHandoffReceipt,
22328
+ createRiddleProofObservationReceipt,
21525
22329
  createRiddleProofProfileConfigurationError,
21526
22330
  createRiddleProofProfileEnvironmentBlockedResult,
21527
22331
  createRiddleProofProfileInsufficientResult,
@@ -21532,6 +22336,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21532
22336
  deployRiddlePreview,
21533
22337
  deployRiddleStaticPreview,
21534
22338
  deriveRiddleProofArtifactBodyAssertions,
22339
+ detectRiddlePreviewSource,
21535
22340
  extractBasicGameplayEvidence,
21536
22341
  extractPlayabilityEvidence,
21537
22342
  extractRiddleProofProfileResult,
@@ -21542,6 +22347,7 @@ function buildRiddleProofPrCommentMarkdown(input) {
21542
22347
  isSuccessfulStatus,
21543
22348
  isTerminalRiddleJobStatus,
21544
22349
  isTerminalStatus,
22350
+ migrateRiddleProofChangeReceipt,
21545
22351
  nonEmptyString,
21546
22352
  normalizeCheckpointResponse,
21547
22353
  normalizeIntegrationContext,
@@ -21549,6 +22355,10 @@ function buildRiddleProofPrCommentMarkdown(input) {
21549
22355
  normalizeRiddleProofProfile,
21550
22356
  normalizeRunParams,
21551
22357
  normalizeTerminalMetadata,
22358
+ parseRiddlePreviewReceipt,
22359
+ parseRiddleProofChangeReceipt,
22360
+ parseRiddleProofHandoffReceipt,
22361
+ parseRiddleProofObservationReceipt,
21552
22362
  parseRiddleViewport,
21553
22363
  parseVisualProofSession,
21554
22364
  pollRiddleJob,