@riddledc/riddle-proof 0.8.75 → 0.8.77

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.
@@ -0,0 +1,488 @@
1
+ // src/change-proof.ts
2
+ var RIDDLE_PROOF_CHANGE_CONTRACT_VERSION = "riddle-proof.change-contract.v1";
3
+ var RIDDLE_PROOF_CHANGE_RESULT_VERSION = "riddle-proof.change-result.v1";
4
+ var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.v1";
5
+ var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
6
+ var DEFAULT_AFTER_STATUSES = ["passed"];
7
+ var DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE = ["product_regression"];
8
+ var DEFAULT_PROFILE_STATUS_TRANSITION_AFTER = ["passed"];
9
+ var DEFAULT_CHECK_STATUS_TRANSITION_BEFORE = ["failed"];
10
+ var DEFAULT_CHECK_STATUS_TRANSITION_AFTER = ["passed"];
11
+ function listValue(value, fallback) {
12
+ if (Array.isArray(value)) return value;
13
+ if (value === void 0) return fallback;
14
+ return [value];
15
+ }
16
+ function includesValue(allowed, observed) {
17
+ return observed !== void 0 && allowed.includes(observed);
18
+ }
19
+ function groupResult(side, contract, result) {
20
+ const fallback = side === "before" ? DEFAULT_BEFORE_STATUSES : DEFAULT_AFTER_STATUSES;
21
+ const requiredStatus = listValue(contract?.required_status, fallback);
22
+ const label = contract?.label || side;
23
+ if (!result) {
24
+ return {
25
+ side,
26
+ label,
27
+ ok: false,
28
+ status: "missing",
29
+ required_status: requiredStatus,
30
+ message: `${label} profile result is missing.`
31
+ };
32
+ }
33
+ const ok = includesValue(requiredStatus, result.status);
34
+ return {
35
+ side,
36
+ label,
37
+ ok,
38
+ profile_name: result.profile_name,
39
+ status: result.status,
40
+ required_status: requiredStatus,
41
+ summary: result.summary,
42
+ message: ok ? void 0 : `${label} profile status ${result.status} did not match required status ${requiredStatus.join(", ")}.`
43
+ };
44
+ }
45
+ function findCheck(result, delta) {
46
+ return result.checks.find((check) => {
47
+ if (delta.check_label && check.label !== delta.check_label) return false;
48
+ if (delta.check_type && check.type !== delta.check_type) return false;
49
+ return Boolean(delta.check_label || delta.check_type);
50
+ });
51
+ }
52
+ function profileStatusTransitionResult(delta, before, after) {
53
+ const label = delta.label || "profile-status-transition";
54
+ if (!before || !after) {
55
+ return {
56
+ type: delta.type,
57
+ label,
58
+ status: "proof_insufficient",
59
+ before_observed: before?.status,
60
+ after_observed: after?.status,
61
+ message: `${label} needs both before and after profile results.`
62
+ };
63
+ }
64
+ const beforeAllowed = listValue(delta.before_status, DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE);
65
+ const afterAllowed = listValue(delta.after_status, DEFAULT_PROFILE_STATUS_TRANSITION_AFTER);
66
+ const passed = includesValue(beforeAllowed, before.status) && includesValue(afterAllowed, after.status);
67
+ return {
68
+ type: delta.type,
69
+ label,
70
+ status: passed ? "passed" : "failed",
71
+ before_observed: before.status,
72
+ after_observed: after.status,
73
+ message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${before.status} -> ${after.status}.`
74
+ };
75
+ }
76
+ function checkStatusTransitionResult(delta, before, after) {
77
+ const label = delta.label || delta.check_label || delta.check_type || "check-status-transition";
78
+ if (!delta.check_label && !delta.check_type) {
79
+ return {
80
+ type: delta.type,
81
+ label,
82
+ status: "configuration_error",
83
+ message: `${label} needs check_label or check_type.`
84
+ };
85
+ }
86
+ if (!before || !after) {
87
+ return {
88
+ type: delta.type,
89
+ label,
90
+ status: "proof_insufficient",
91
+ before_observed: before?.status,
92
+ after_observed: after?.status,
93
+ message: `${label} needs both before and after profile results.`
94
+ };
95
+ }
96
+ const beforeCheck = findCheck(before, delta);
97
+ const afterCheck = findCheck(after, delta);
98
+ if (!beforeCheck || !afterCheck) {
99
+ return {
100
+ type: delta.type,
101
+ label,
102
+ status: "proof_insufficient",
103
+ before_observed: beforeCheck?.status,
104
+ after_observed: afterCheck?.status,
105
+ message: `${label} was not present in ${!beforeCheck && !afterCheck ? "before or after" : !beforeCheck ? "before" : "after"} profile checks.`
106
+ };
107
+ }
108
+ const beforeAllowed = listValue(delta.before_status, DEFAULT_CHECK_STATUS_TRANSITION_BEFORE);
109
+ const afterAllowed = listValue(delta.after_status, DEFAULT_CHECK_STATUS_TRANSITION_AFTER);
110
+ const passed = includesValue(beforeAllowed, beforeCheck.status) && includesValue(afterAllowed, afterCheck.status);
111
+ return {
112
+ type: delta.type,
113
+ label,
114
+ status: passed ? "passed" : "failed",
115
+ before_observed: beforeCheck.status,
116
+ after_observed: afterCheck.status,
117
+ message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${beforeCheck.status} -> ${afterCheck.status}.`
118
+ };
119
+ }
120
+ function assessDelta(delta, before, after) {
121
+ if (delta.type === "profile_status_transition") {
122
+ return profileStatusTransitionResult(delta, before, after);
123
+ }
124
+ return checkStatusTransitionResult(delta, before, after);
125
+ }
126
+ function collapsedChangeStatus(groups, deltas) {
127
+ const groupResults = [groups.before, groups.after];
128
+ if (groupResults.some((group) => group.status === "environment_blocked")) return "environment_blocked";
129
+ if (groupResults.some((group) => group.status === "configuration_error")) return "configuration_error";
130
+ if (deltas.some((delta) => delta.status === "configuration_error")) return "configuration_error";
131
+ if (groupResults.some((group) => group.status === "needs_human_review")) return "needs_human_review";
132
+ if (groupResults.some((group) => group.status === "missing" || group.status === "proof_insufficient")) return "proof_insufficient";
133
+ if (!deltas.length || deltas.some((delta) => delta.status === "proof_insufficient")) return "proof_insufficient";
134
+ if (groupResults.some((group) => !group.ok)) return "product_regression";
135
+ if (deltas.some((delta) => delta.status === "failed")) return "product_regression";
136
+ return "passed";
137
+ }
138
+ function changeSummary(name, status, deltas) {
139
+ if (status === "passed") return `${name} passed ${deltas.length} change delta(s).`;
140
+ if (status === "environment_blocked") return `${name} could not compare reliable evidence because an environment was blocked.`;
141
+ if (status === "configuration_error") return `${name} has an invalid change proof contract.`;
142
+ if (status === "needs_human_review") return `${name} needs human review before the change proof can pass.`;
143
+ if (status === "proof_insufficient") return `${name} did not produce enough before/after evidence for a change proof.`;
144
+ return `${name} failed ${deltas.filter((delta) => delta.status === "failed").length} change delta(s).`;
145
+ }
146
+ function assessRiddleProofChange(contract, input) {
147
+ const groups = {
148
+ before: groupResult("before", contract.before, input.before_result),
149
+ after: groupResult("after", contract.after, input.after_result)
150
+ };
151
+ const deltas = (contract.deltas || []).map((delta) => assessDelta(delta, input.before_result, input.after_result));
152
+ const status = collapsedChangeStatus(groups, deltas);
153
+ return {
154
+ version: RIDDLE_PROOF_CHANGE_RESULT_VERSION,
155
+ contract_name: contract.name,
156
+ status,
157
+ groups,
158
+ deltas,
159
+ summary: changeSummary(contract.name, status, deltas),
160
+ metadata: contract.metadata
161
+ };
162
+ }
163
+ function changeReceiptVerdict(status) {
164
+ if (status === "passed") return "mergeable";
165
+ if (status === "environment_blocked") return "environment_blocked";
166
+ if (status === "configuration_error") return "configuration_error";
167
+ if (status === "needs_human_review") return "needs_human_review";
168
+ if (status === "proof_insufficient") return "proof_insufficient";
169
+ return "not_mergeable";
170
+ }
171
+ function profileCheckCounts(result) {
172
+ const counts = {
173
+ total: result.checks.length,
174
+ passed: 0,
175
+ failed: 0,
176
+ skipped: 0,
177
+ needs_human_review: 0
178
+ };
179
+ for (const check of result.checks) {
180
+ if (check.status === "passed") counts.passed += 1;
181
+ if (check.status === "failed") counts.failed += 1;
182
+ if (check.status === "skipped") counts.skipped += 1;
183
+ if (check.status === "needs_human_review") counts.needs_human_review += 1;
184
+ }
185
+ return counts;
186
+ }
187
+ function artifactKind(ref) {
188
+ const text = [ref.name, ref.url, ref.path, ref.kind, ref.content_type].filter(Boolean).join(" ").toLowerCase();
189
+ if (/\bimage\b/.test(text) || /\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(text)) return "image";
190
+ if (/\bjson\b|\btext\b|\bmarkdown\b|\bhtml\b|\blog\b/.test(text) || /\.(json|md|txt|html|log|har)(\?|#|$)/.test(text)) return "data";
191
+ return "artifact";
192
+ }
193
+ function receiptArtifactFromRef(side, ref) {
194
+ return {
195
+ side,
196
+ name: ref.name,
197
+ kind: artifactKind(ref),
198
+ url: ref.url,
199
+ path: ref.path,
200
+ source: ref.source
201
+ };
202
+ }
203
+ function comparableArtifactName(value) {
204
+ return (value || "").split(/[/?#]/).pop()?.toLowerCase().replace(/\.(png|jpe?g|gif|webp|avif|svg)$/u, "").replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/g, "") || "";
205
+ }
206
+ function artifactMatchesScreenshotLabel(artifact, screenshot) {
207
+ const screenshotName = comparableArtifactName(screenshot);
208
+ if (!screenshotName) return false;
209
+ return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === screenshotName || name.endsWith(`-${screenshotName}`));
210
+ }
211
+ function collectReceiptArtifacts(side, result) {
212
+ const artifacts = [];
213
+ const seen = /* @__PURE__ */ new Set();
214
+ const add = (artifact) => {
215
+ const key = artifact.url || artifact.path || `${artifact.kind}:${artifact.name}`;
216
+ if (!artifact.name || seen.has(key)) return;
217
+ seen.add(key);
218
+ artifacts.push(artifact);
219
+ };
220
+ for (const ref of result.artifacts.riddle_artifacts || []) {
221
+ add(receiptArtifactFromRef(side, ref));
222
+ }
223
+ for (const screenshot of result.artifacts.screenshots || []) {
224
+ if (artifacts.some((artifact) => artifactMatchesScreenshotLabel(artifact, screenshot))) continue;
225
+ add({
226
+ side,
227
+ name: screenshot,
228
+ kind: "image"
229
+ });
230
+ }
231
+ return artifacts;
232
+ }
233
+ function artifactIsScreenshot(artifact) {
234
+ if (artifact.kind !== "image") return false;
235
+ return /screenshot|\.png|\.jpe?g|\.webp|\.gif|\.avif|\.svg/i.test([artifact.name, artifact.url, artifact.path].filter(Boolean).join(" "));
236
+ }
237
+ function receiptSide(side, source, result) {
238
+ const artifacts = collectReceiptArtifacts(side, result);
239
+ return {
240
+ side,
241
+ source,
242
+ profile_name: result.profile_name,
243
+ status: result.status,
244
+ summary: result.summary,
245
+ route: result.route,
246
+ captured_at: result.captured_at,
247
+ checks: profileCheckCounts(result),
248
+ screenshots: artifacts.filter(artifactIsScreenshot),
249
+ artifacts
250
+ };
251
+ }
252
+ function metadataStringList(metadata, key) {
253
+ const value = metadata?.[key];
254
+ if (!Array.isArray(value)) return [];
255
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
256
+ }
257
+ function createRiddleProofChangeReceipt(input) {
258
+ return {
259
+ version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
260
+ contract_name: input.result.contract_name,
261
+ profile_name: input.profile_name,
262
+ status: input.result.status,
263
+ verdict: changeReceiptVerdict(input.result.status),
264
+ summary: input.result.summary,
265
+ before: receiptSide("before", input.before_source, input.before_result),
266
+ after: receiptSide("after", input.after_source, input.after_result),
267
+ deltas: input.result.deltas.map((delta) => ({
268
+ type: delta.type,
269
+ label: delta.label,
270
+ status: delta.status,
271
+ before_observed: delta.before_observed,
272
+ after_observed: delta.after_observed,
273
+ message: delta.message
274
+ })),
275
+ proves: metadataStringList(input.contract.metadata, "required_receipts"),
276
+ does_not_prove: metadataStringList(input.contract.metadata, "does_not_prove"),
277
+ metadata: input.result.metadata
278
+ };
279
+ }
280
+ function markdownTableCell(value) {
281
+ return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
282
+ }
283
+ function artifactTarget(artifact) {
284
+ return artifact.url || artifact.path;
285
+ }
286
+ function markdownLink(label, target) {
287
+ return `[${label.replace(/\]/g, "\\]")}](${target})`;
288
+ }
289
+ function appendMarkdownArtifacts(lines, title, artifacts) {
290
+ lines.push(`### ${title}`, "");
291
+ if (!artifacts.length) {
292
+ lines.push("- No screenshot artifacts recorded.", "");
293
+ return;
294
+ }
295
+ for (const artifact of artifacts.slice(0, 6)) {
296
+ const target = artifactTarget(artifact);
297
+ if (target && artifact.kind === "image") {
298
+ lines.push(`![${artifact.name.replace(/\]/g, "\\]")}](${target})`);
299
+ } else if (target) {
300
+ lines.push(`- ${markdownLink(artifact.name, target)}`);
301
+ } else {
302
+ lines.push(`- ${artifact.name}`);
303
+ }
304
+ }
305
+ if (artifacts.length > 6) lines.push(`- ${artifacts.length - 6} more artifact(s) omitted`);
306
+ lines.push("");
307
+ }
308
+ function riddleProofChangeReceiptMarkdown(receipt) {
309
+ const lines = [
310
+ "# Riddle Proof Change Receipt",
311
+ "",
312
+ `**Verdict:** ${receipt.verdict}`,
313
+ `**Status:** ${receipt.status}`,
314
+ `**Contract:** ${receipt.contract_name}`,
315
+ receipt.profile_name ? `**Profile:** ${receipt.profile_name}` : "",
316
+ "",
317
+ receipt.summary,
318
+ "",
319
+ "## Evidence Pair",
320
+ "",
321
+ "| Side | Source | Status | Checks |",
322
+ "| --- | --- | --- | --- |",
323
+ `| Before | ${markdownTableCell(receipt.before.source)} | ${receipt.before.status} | ${receipt.before.checks.passed} passed / ${receipt.before.checks.failed} failed |`,
324
+ `| After | ${markdownTableCell(receipt.after.source)} | ${receipt.after.status} | ${receipt.after.checks.passed} passed / ${receipt.after.checks.failed} failed |`,
325
+ "",
326
+ "## Delta Checks",
327
+ "",
328
+ "| Delta | Status | Before | After |",
329
+ "| --- | --- | --- | --- |"
330
+ ].filter(Boolean);
331
+ for (const delta of receipt.deltas) {
332
+ lines.push(`| ${markdownTableCell(delta.label)} | ${delta.status} | ${markdownTableCell(delta.before_observed)} | ${markdownTableCell(delta.after_observed)} |`);
333
+ if (delta.message) lines.push(`| ${markdownTableCell(`${delta.label} message`)} | ${markdownTableCell(delta.message)} | | |`);
334
+ }
335
+ lines.push("");
336
+ appendMarkdownArtifacts(lines, "Before Screenshot", receipt.before.screenshots);
337
+ appendMarkdownArtifacts(lines, "After Screenshot", receipt.after.screenshots);
338
+ if (receipt.proves.length) {
339
+ lines.push("## What This Proves", "");
340
+ for (const claim of receipt.proves) lines.push(`- ${claim}`);
341
+ lines.push("");
342
+ }
343
+ if (receipt.does_not_prove.length) {
344
+ lines.push("## What This Does Not Prove", "");
345
+ for (const claim of receipt.does_not_prove) lines.push(`- ${claim}`);
346
+ lines.push("");
347
+ }
348
+ const linkedArtifacts = [...receipt.before.artifacts, ...receipt.after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 24);
349
+ if (linkedArtifacts.length) {
350
+ lines.push("## Artifacts", "");
351
+ for (const artifact of linkedArtifacts) {
352
+ lines.push(`- ${artifact.side}: ${markdownLink(artifact.name, artifactTarget(artifact) || "")}`);
353
+ }
354
+ lines.push("");
355
+ }
356
+ return `${lines.join("\n").trim()}
357
+ `;
358
+ }
359
+ function escapeHtml(value) {
360
+ return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
361
+ }
362
+ function htmlArtifactCard(artifact) {
363
+ const target = artifactTarget(artifact);
364
+ if (!target) return `<p>${escapeHtml(artifact.name)}</p>`;
365
+ if (artifact.kind === "image") {
366
+ return `<figure><img src="${escapeHtml(target)}" alt="${escapeHtml(artifact.name)}"><figcaption>${escapeHtml(artifact.name)}</figcaption></figure>`;
367
+ }
368
+ return `<p><a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a></p>`;
369
+ }
370
+ function htmlArtifactLink(artifact) {
371
+ const target = artifactTarget(artifact);
372
+ if (!target) return escapeHtml(artifact.name);
373
+ return `<a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a>`;
374
+ }
375
+ function htmlList(items, emptyText) {
376
+ if (!items.length) return `<p class="muted">${escapeHtml(emptyText)}</p>`;
377
+ return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
378
+ }
379
+ function riddleProofChangeReceiptHtml(receipt) {
380
+ const artifactLinks = [...receipt.before.artifacts, ...receipt.after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 32);
381
+ const deltaRows = receipt.deltas.map((delta) => `
382
+ <tr>
383
+ <td>${escapeHtml(delta.label)}</td>
384
+ <td><span class="status ${escapeHtml(delta.status)}">${escapeHtml(delta.status)}</span></td>
385
+ <td>${escapeHtml(delta.before_observed)}</td>
386
+ <td>${escapeHtml(delta.after_observed)}</td>
387
+ </tr>
388
+ ${delta.message ? `<tr><td class="muted">${escapeHtml(delta.label)} message</td><td colspan="3">${escapeHtml(delta.message)}</td></tr>` : ""}`).join("");
389
+ return `<!doctype html>
390
+ <html lang="en">
391
+ <head>
392
+ <meta charset="utf-8">
393
+ <meta name="viewport" content="width=device-width, initial-scale=1">
394
+ <title>${escapeHtml(receipt.contract_name)} - Riddle Proof Change Receipt</title>
395
+ <style>
396
+ :root { color-scheme: light dark; --bg: #0f141b; --panel: #171f29; --text: #eef4f8; --muted: #aebbc8; --border: #314152; --ok: #69d089; --bad: #ff7a7a; --warn: #ffd166; }
397
+ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
398
+ main { max-width: 1120px; margin: 0 auto; padding: 32px 20px 48px; }
399
+ header, section { border: 1px solid var(--border); background: var(--panel); border-radius: 8px; padding: 20px; margin: 0 0 18px; }
400
+ h1, h2, h3, p { margin-top: 0; }
401
+ h1 { font-size: clamp(1.6rem, 3vw, 2.4rem); }
402
+ h2 { font-size: 1.1rem; margin-bottom: 14px; }
403
+ .muted { color: var(--muted); }
404
+ .verdict { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); font-weight: 700; }
405
+ .mergeable, .passed { color: var(--ok); }
406
+ .not_mergeable, .failed, .product_regression { color: var(--bad); }
407
+ .proof_insufficient, .environment_blocked, .needs_human_review, .configuration_error { color: var(--warn); }
408
+ .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
409
+ .side { border: 1px solid var(--border); border-radius: 8px; padding: 14px; min-width: 0; }
410
+ code { overflow-wrap: anywhere; color: var(--muted); }
411
+ table { width: 100%; border-collapse: collapse; }
412
+ th, td { border-bottom: 1px solid var(--border); padding: 10px 8px; text-align: left; vertical-align: top; }
413
+ figure { margin: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: #0b1017; }
414
+ img { display: block; width: 100%; height: auto; }
415
+ figcaption { padding: 8px 10px; color: var(--muted); font-size: 0.9rem; }
416
+ ul { margin-bottom: 0; }
417
+ a { color: #8bc7ff; }
418
+ @media (max-width: 760px) { .grid { grid-template-columns: 1fr; } main { padding: 18px 12px 32px; } }
419
+ </style>
420
+ </head>
421
+ <body>
422
+ <main>
423
+ <header>
424
+ <p class="verdict ${escapeHtml(receipt.verdict)}">${escapeHtml(receipt.verdict)}</p>
425
+ <h1>${escapeHtml(receipt.contract_name)}</h1>
426
+ <p>${escapeHtml(receipt.summary)}</p>
427
+ <p class="muted">Status: ${escapeHtml(receipt.status)}${receipt.profile_name ? ` | Profile: ${escapeHtml(receipt.profile_name)}` : ""}</p>
428
+ </header>
429
+ <section>
430
+ <h2>Evidence Pair</h2>
431
+ <div class="grid">
432
+ <div class="side">
433
+ <h3>Before</h3>
434
+ <p><code>${escapeHtml(receipt.before.source)}</code></p>
435
+ <p>Status: <strong class="${escapeHtml(receipt.before.status)}">${escapeHtml(receipt.before.status)}</strong></p>
436
+ <p>${escapeHtml(receipt.before.summary)}</p>
437
+ <p class="muted">${receipt.before.checks.passed} passed / ${receipt.before.checks.failed} failed checks</p>
438
+ </div>
439
+ <div class="side">
440
+ <h3>After</h3>
441
+ <p><code>${escapeHtml(receipt.after.source)}</code></p>
442
+ <p>Status: <strong class="${escapeHtml(receipt.after.status)}">${escapeHtml(receipt.after.status)}</strong></p>
443
+ <p>${escapeHtml(receipt.after.summary)}</p>
444
+ <p class="muted">${receipt.after.checks.passed} passed / ${receipt.after.checks.failed} failed checks</p>
445
+ </div>
446
+ </div>
447
+ </section>
448
+ <section>
449
+ <h2>Delta Checks</h2>
450
+ <table>
451
+ <thead><tr><th>Delta</th><th>Status</th><th>Before</th><th>After</th></tr></thead>
452
+ <tbody>${deltaRows}</tbody>
453
+ </table>
454
+ </section>
455
+ <section>
456
+ <h2>Screenshots</h2>
457
+ <div class="grid">
458
+ <div>${receipt.before.screenshots.slice(0, 4).map(htmlArtifactCard).join("") || `<p class="muted">No before screenshots recorded.</p>`}</div>
459
+ <div>${receipt.after.screenshots.slice(0, 4).map(htmlArtifactCard).join("") || `<p class="muted">No after screenshots recorded.</p>`}</div>
460
+ </div>
461
+ </section>
462
+ <section>
463
+ <h2>What This Proves</h2>
464
+ ${htmlList(receipt.proves, "No explicit proof claims were recorded in contract metadata.")}
465
+ </section>
466
+ <section>
467
+ <h2>What This Does Not Prove</h2>
468
+ ${htmlList(receipt.does_not_prove, "No explicit limits were recorded in contract metadata.")}
469
+ </section>
470
+ <section>
471
+ <h2>Artifacts</h2>
472
+ ${artifactLinks.length ? `<ul>${artifactLinks.map((artifact) => `<li>${escapeHtml(artifact.side)}: ${htmlArtifactLink(artifact)}</li>`).join("")}</ul>` : `<p class="muted">No linked artifacts recorded.</p>`}
473
+ </section>
474
+ </main>
475
+ </body>
476
+ </html>
477
+ `;
478
+ }
479
+
480
+ export {
481
+ RIDDLE_PROOF_CHANGE_CONTRACT_VERSION,
482
+ RIDDLE_PROOF_CHANGE_RESULT_VERSION,
483
+ RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
484
+ assessRiddleProofChange,
485
+ createRiddleProofChangeReceipt,
486
+ riddleProofChangeReceiptMarkdown,
487
+ riddleProofChangeReceiptHtml
488
+ };
@@ -8,7 +8,7 @@ import {
8
8
  RIDDLE_PROOF_PR_COMMENT_MARKER,
9
9
  buildRiddleProofPrCommentMarkdown,
10
10
  summarizeRiddleProofPrComment
11
- } from "./chunk-CWRIXP5H.js";
11
+ } from "./chunk-IY4W6STC.js";
12
12
  import {
13
13
  suggestRiddleProofProfileChecks
14
14
  } from "./chunk-UE4I7RTI.js";
@@ -31,8 +31,11 @@ import {
31
31
  resolveRiddleProofProfileTimeoutSec
32
32
  } from "./chunk-GG2D3MFZ.js";
33
33
  import {
34
- assessRiddleProofChange
35
- } from "./chunk-6S7DZWVC.js";
34
+ assessRiddleProofChange,
35
+ createRiddleProofChangeReceipt,
36
+ riddleProofChangeReceiptHtml,
37
+ riddleProofChangeReceiptMarkdown
38
+ } from "./chunk-BILL3UC2.js";
36
39
  import {
37
40
  createCodexExecAgentAdapter,
38
41
  runCodexExecAgentDoctor
@@ -3986,6 +3989,9 @@ function compactChangeProofResult(run, options) {
3986
3989
  output_dir: outputDir,
3987
3990
  output_files: outputDir ? {
3988
3991
  change_proof_result: "change-proof-result.json",
3992
+ change_proof_receipt: "change-proof-receipt.json",
3993
+ change_proof_receipt_markdown: "change-proof-receipt.md",
3994
+ change_proof_receipt_html: "change-proof-receipt.html",
3989
3995
  summary: "summary.md",
3990
3996
  before_profile_result: "before/profile-result.json",
3991
3997
  after_profile_result: "after/profile-result.json"
@@ -3993,47 +3999,17 @@ function compactChangeProofResult(run, options) {
3993
3999
  };
3994
4000
  }
3995
4001
  function changeProofResultMarkdown(run) {
3996
- const lines = [
3997
- "# Riddle Proof Change Proof",
3998
- "",
3999
- `Contract: ${run.result.contract_name}`,
4000
- `Profile: ${run.profile.name}`,
4001
- `Status: ${run.result.status}`,
4002
- "",
4003
- run.result.summary,
4004
- "",
4005
- "## Before",
4006
- "",
4007
- `- source: ${markdownInlineCode(run.beforeSource, 160)}`,
4008
- `- profile: ${run.beforeResult.profile_name}`,
4009
- `- status: ${run.beforeResult.status}`,
4010
- `- summary: ${run.beforeResult.summary}`,
4011
- "",
4012
- "## After",
4013
- "",
4014
- `- source: ${markdownInlineCode(run.afterSource, 160)}`,
4015
- `- profile: ${run.afterResult.profile_name}`,
4016
- `- status: ${run.afterResult.status}`,
4017
- `- summary: ${run.afterResult.summary}`,
4018
- "",
4019
- "## Deltas",
4020
- ""
4021
- ];
4022
- for (const delta of run.result.deltas) {
4023
- const observed = [
4024
- delta.before_observed !== void 0 ? `before ${markdownInlineCode(String(delta.before_observed))}` : "",
4025
- delta.after_observed !== void 0 ? `after ${markdownInlineCode(String(delta.after_observed))}` : ""
4026
- ].filter(Boolean).join(" -> ");
4027
- lines.push(`- ${delta.status}: ${delta.label}${observed ? ` (${observed})` : ""}${delta.message ? ` - ${delta.message}` : ""}`);
4028
- }
4029
- return `${lines.join("\n")}
4030
- `;
4002
+ return riddleProofChangeReceiptMarkdown(run.receipt);
4031
4003
  }
4032
4004
  function writeChangeProofOutput(outputDir, run) {
4033
4005
  if (!outputDir) return;
4034
4006
  mkdirSync(outputDir, { recursive: true });
4035
4007
  writeFileSync(path.join(outputDir, "change-proof-result.json"), `${JSON.stringify(run.result, null, 2)}
4036
4008
  `);
4009
+ writeFileSync(path.join(outputDir, "change-proof-receipt.json"), `${JSON.stringify(run.receipt, null, 2)}
4010
+ `);
4011
+ writeFileSync(path.join(outputDir, "change-proof-receipt.md"), riddleProofChangeReceiptMarkdown(run.receipt));
4012
+ writeFileSync(path.join(outputDir, "change-proof-receipt.html"), riddleProofChangeReceiptHtml(run.receipt));
4037
4013
  writeFileSync(path.join(outputDir, "summary.md"), changeProofResultMarkdown(run));
4038
4014
  writeProfileOutput(changeProofSideOutputDir(outputDir, "before"), run.beforeResult);
4039
4015
  writeProfileOutput(changeProofSideOutputDir(outputDir, "after"), run.afterResult);
@@ -4948,7 +4924,16 @@ async function runChangeProofForCli(rawProfile, options) {
4948
4924
  before_result: beforeResult,
4949
4925
  after_result: afterResult
4950
4926
  });
4951
- return { profile, contract, beforeResult, afterResult, result, beforeSource, afterSource };
4927
+ const receipt = createRiddleProofChangeReceipt({
4928
+ contract,
4929
+ result,
4930
+ before_result: beforeResult,
4931
+ after_result: afterResult,
4932
+ before_source: beforeSource,
4933
+ after_source: afterSource,
4934
+ profile_name: profile.name
4935
+ });
4936
+ return { profile, contract, beforeResult, afterResult, result, receipt, beforeSource, afterSource };
4952
4937
  }
4953
4938
  function requestForRun(options) {
4954
4939
  const statePath = optionString(options, "statePath");
@@ -5032,6 +5017,8 @@ async function main() {
5032
5017
  const runResponsePath = explicitRunResponsePath || defaultProofDirJsonPath(proofDir, "riddle-run-response.json");
5033
5018
  const resultJsonPaths = explicitResultJsonPath ? [explicitResultJsonPath] : [
5034
5019
  defaultProofDirJsonPath(proofDir, "result.json"),
5020
+ defaultProofDirJsonPath(proofDir, "change-proof-receipt.json"),
5021
+ defaultProofDirJsonPath(proofDir, "change-proof-result.json"),
5035
5022
  defaultProofDirJsonPath(proofDir, "profile-result.json")
5036
5023
  ];
5037
5024
  const runResponse = readJsonFileIfExists(runResponsePath);