@riddledc/riddle-proof 0.8.77 → 0.8.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -3
- package/dist/change-proof.cjs +744 -85
- package/dist/change-proof.d.cts +100 -8
- package/dist/change-proof.d.ts +100 -8
- package/dist/change-proof.js +15 -1
- package/dist/{chunk-B2DP2LET.js → chunk-5IFZSUPF.js} +52 -13
- package/dist/chunk-6VFS2JFR.js +886 -0
- package/dist/{chunk-IY4W6STC.js → chunk-7N6X54WG.js} +116 -17
- package/dist/{chunk-GG2D3MFZ.js → chunk-FCSJZBC5.js} +26 -1
- package/dist/chunk-MEVVL4TI.js +258 -0
- package/dist/{chunk-H25IDX76.js → chunk-NEXWITV4.js} +221 -35
- package/dist/{chunk-UE4I7RTI.js → chunk-RQPCKRKT.js} +1 -1
- package/dist/cli/index.js +7 -6
- package/dist/cli.cjs +2026 -1060
- package/dist/cli.js +7 -6
- package/dist/index.cjs +932 -122
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +42 -14
- package/dist/pr-comment.cjs +416 -17
- package/dist/pr-comment.d.cts +6 -1
- package/dist/pr-comment.d.ts +6 -1
- package/dist/pr-comment.js +6 -1
- package/dist/profile/index.cjs +26 -1
- package/dist/profile/index.js +1 -1
- package/dist/profile-suggestions.js +2 -2
- package/dist/profile.cjs +26 -1
- package/dist/profile.d.cts +7 -0
- package/dist/profile.d.ts +7 -0
- package/dist/profile.js +1 -1
- package/dist/receipts.cjs +286 -0
- package/dist/receipts.d.cts +115 -0
- package/dist/receipts.d.ts +115 -0
- package/dist/receipts.js +15 -0
- package/dist/riddle-client.cjs +98 -13
- package/dist/riddle-client.d.cts +18 -5
- package/dist/riddle-client.d.ts +18 -5
- package/dist/riddle-client.js +4 -1
- package/dist/runtime/index.cjs +98 -13
- package/dist/runtime/index.d.cts +5 -1
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/riddle-client.cjs +98 -13
- package/dist/runtime/riddle-client.d.cts +5 -1
- package/dist/runtime/riddle-client.d.ts +5 -1
- package/dist/runtime/riddle-client.js +4 -1
- package/package.json +7 -2
- package/dist/chunk-BILL3UC2.js +0 -488
package/dist/cli.cjs
CHANGED
|
@@ -7879,6 +7879,259 @@ var import_node_child_process4 = require("child_process");
|
|
|
7879
7879
|
var import_node_fs5 = require("fs");
|
|
7880
7880
|
var import_node_os2 = require("os");
|
|
7881
7881
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
7882
|
+
|
|
7883
|
+
// src/receipts.ts
|
|
7884
|
+
var RIDDLE_PREVIEW_RECEIPT_VERSION = "riddle.preview-receipt.v1";
|
|
7885
|
+
var RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION = "riddle-proof.observation-receipt.v1";
|
|
7886
|
+
function isRecord(value) {
|
|
7887
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7888
|
+
}
|
|
7889
|
+
function requiredString(record, key, context) {
|
|
7890
|
+
const value = record[key];
|
|
7891
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7892
|
+
throw new Error(`${context}.${key} must be a non-empty string.`);
|
|
7893
|
+
}
|
|
7894
|
+
return value.trim();
|
|
7895
|
+
}
|
|
7896
|
+
function normalizedSourceIdentity(value) {
|
|
7897
|
+
if (!isRecord(value)) return {};
|
|
7898
|
+
return {
|
|
7899
|
+
git_revision: typeof value.git_revision === "string" && value.git_revision.trim() ? value.git_revision.trim() : void 0,
|
|
7900
|
+
repository: typeof value.repository === "string" && value.repository.trim() ? value.repository.trim() : void 0,
|
|
7901
|
+
dirty: typeof value.dirty === "boolean" ? value.dirty : void 0,
|
|
7902
|
+
label: typeof value.label === "string" && value.label.trim() ? value.label.trim() : void 0
|
|
7903
|
+
};
|
|
7904
|
+
}
|
|
7905
|
+
function parseRiddlePreviewReceipt(value) {
|
|
7906
|
+
if (!isRecord(value)) throw new Error("Preview receipt must be an object.");
|
|
7907
|
+
if (value.version !== RIDDLE_PREVIEW_RECEIPT_VERSION) {
|
|
7908
|
+
throw new Error(`Unsupported Preview receipt version ${String(value.version || "missing")}.`);
|
|
7909
|
+
}
|
|
7910
|
+
if (!isRecord(value.source)) throw new Error("Preview receipt source must be an object.");
|
|
7911
|
+
const expiresAt = requiredString(value, "expires_at", "preview receipt");
|
|
7912
|
+
const publishedAt = requiredString(value, "published_at", "preview receipt");
|
|
7913
|
+
const contentDigest = requiredString(value, "content_digest", "preview receipt");
|
|
7914
|
+
if (!contentDigest.startsWith("sha256:") || contentDigest.length <= "sha256:".length) {
|
|
7915
|
+
throw new Error("preview receipt.content_digest must be a sha256 digest.");
|
|
7916
|
+
}
|
|
7917
|
+
if (!Number.isFinite(Date.parse(expiresAt)) || !Number.isFinite(Date.parse(publishedAt))) {
|
|
7918
|
+
throw new Error("Preview receipt timestamps must be valid ISO date strings.");
|
|
7919
|
+
}
|
|
7920
|
+
return {
|
|
7921
|
+
version: RIDDLE_PREVIEW_RECEIPT_VERSION,
|
|
7922
|
+
preview_id: requiredString(value, "preview_id", "preview receipt"),
|
|
7923
|
+
url: requiredString(value, "url", "preview receipt"),
|
|
7924
|
+
expires_at: expiresAt,
|
|
7925
|
+
content_digest: contentDigest,
|
|
7926
|
+
source: normalizedSourceIdentity(value.source),
|
|
7927
|
+
published_at: publishedAt
|
|
7928
|
+
};
|
|
7929
|
+
}
|
|
7930
|
+
function comparableArtifactName(value) {
|
|
7931
|
+
return (value || "").split(/[/?#]/).pop()?.toLowerCase().replace(/\.(png|jpe?g|gif|webp|avif|svg)$/u, "").replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/g, "") || "";
|
|
7932
|
+
}
|
|
7933
|
+
function artifactMatchesLabel(artifact, label) {
|
|
7934
|
+
const expected = comparableArtifactName(label);
|
|
7935
|
+
if (!expected) return false;
|
|
7936
|
+
return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === expected || name.endsWith(`-${expected}`));
|
|
7937
|
+
}
|
|
7938
|
+
function artifactLooksLikeScreenshot(ref) {
|
|
7939
|
+
const text = [ref.name, ref.url, ref.path, ref.kind, ref.content_type].filter(Boolean).join(" ").toLowerCase();
|
|
7940
|
+
return /screenshot|\bimage\b/.test(text) || /\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(text);
|
|
7941
|
+
}
|
|
7942
|
+
function finalScreenshotLabels(result) {
|
|
7943
|
+
const explicit = result.artifacts.canonical_screenshots || [];
|
|
7944
|
+
if (explicit.length) return explicit;
|
|
7945
|
+
const fromEvidence = (result.evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
|
|
7946
|
+
if (fromEvidence.length) return fromEvidence;
|
|
7947
|
+
return (result.artifacts.screenshots || []).slice(0, 1);
|
|
7948
|
+
}
|
|
7949
|
+
function setupScreenshotLabels(result) {
|
|
7950
|
+
const explicit = result.artifacts.setup_screenshots || [];
|
|
7951
|
+
if (explicit.length) return explicit;
|
|
7952
|
+
return (result.evidence?.viewports || []).flatMap(
|
|
7953
|
+
(viewport) => (viewport.setup_action_results || []).filter((action) => action.action === "screenshot" && action.ok !== false && typeof action.screenshot_label === "string").map((action) => String(action.screenshot_label))
|
|
7954
|
+
);
|
|
7955
|
+
}
|
|
7956
|
+
function observationArtifactFromRef(ref, canonicalLabels, setupLabels) {
|
|
7957
|
+
const base = {
|
|
7958
|
+
name: ref.name,
|
|
7959
|
+
role: ref.role || "artifact",
|
|
7960
|
+
url: ref.url,
|
|
7961
|
+
path: ref.path,
|
|
7962
|
+
kind: ref.kind,
|
|
7963
|
+
content_type: ref.content_type,
|
|
7964
|
+
source: ref.source
|
|
7965
|
+
};
|
|
7966
|
+
if (!artifactLooksLikeScreenshot(ref)) return base;
|
|
7967
|
+
if (canonicalLabels.some((label) => artifactMatchesLabel(base, label))) {
|
|
7968
|
+
return { ...base, role: "canonical_screenshot" };
|
|
7969
|
+
}
|
|
7970
|
+
if (setupLabels.some((label) => artifactMatchesLabel(base, label))) {
|
|
7971
|
+
return { ...base, role: "setup_screenshot" };
|
|
7972
|
+
}
|
|
7973
|
+
return { ...base, role: "diagnostic" };
|
|
7974
|
+
}
|
|
7975
|
+
function profileObservationArtifacts(result) {
|
|
7976
|
+
const canonicalLabels = finalScreenshotLabels(result);
|
|
7977
|
+
const setupLabels = setupScreenshotLabels(result);
|
|
7978
|
+
const artifacts = [];
|
|
7979
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7980
|
+
const add = (artifact) => {
|
|
7981
|
+
const key = artifact.url || artifact.path || `${artifact.role}:${artifact.name}`;
|
|
7982
|
+
if (!artifact.name || seen.has(key)) return;
|
|
7983
|
+
seen.add(key);
|
|
7984
|
+
artifacts.push(artifact);
|
|
7985
|
+
};
|
|
7986
|
+
for (const ref of result.artifacts.riddle_artifacts || []) {
|
|
7987
|
+
add(observationArtifactFromRef(ref, canonicalLabels, setupLabels));
|
|
7988
|
+
}
|
|
7989
|
+
for (const label of canonicalLabels) {
|
|
7990
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
7991
|
+
add({ name: label, path: label, role: "canonical_screenshot", kind: "image" });
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
for (const label of setupLabels) {
|
|
7995
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
7996
|
+
add({ name: label, path: label, role: "setup_screenshot", kind: "image" });
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
for (const label of result.artifacts.screenshots || []) {
|
|
8000
|
+
if (!artifacts.some((artifact) => artifactMatchesLabel(artifact, label))) {
|
|
8001
|
+
add({ name: label, path: label, role: "diagnostic", kind: "image" });
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
return artifacts;
|
|
8005
|
+
}
|
|
8006
|
+
function mergeObservationArtifacts(profileArtifacts, suppliedArtifacts) {
|
|
8007
|
+
const artifacts = [...profileArtifacts];
|
|
8008
|
+
for (const supplied of suppliedArtifacts) {
|
|
8009
|
+
const suppliedNames = [supplied.name, supplied.url, supplied.path].map(comparableArtifactName).filter(Boolean);
|
|
8010
|
+
const existingIndex = artifacts.findIndex((artifact) => {
|
|
8011
|
+
if (supplied.url && artifact.url === supplied.url) return true;
|
|
8012
|
+
if (supplied.path && artifact.path === supplied.path) return true;
|
|
8013
|
+
return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name && suppliedNames.includes(name));
|
|
8014
|
+
});
|
|
8015
|
+
if (existingIndex < 0) {
|
|
8016
|
+
artifacts.push(supplied);
|
|
8017
|
+
continue;
|
|
8018
|
+
}
|
|
8019
|
+
const existing = artifacts[existingIndex];
|
|
8020
|
+
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;
|
|
8021
|
+
artifacts[existingIndex] = {
|
|
8022
|
+
...existing,
|
|
8023
|
+
...supplied,
|
|
8024
|
+
role
|
|
8025
|
+
};
|
|
8026
|
+
}
|
|
8027
|
+
return artifacts;
|
|
8028
|
+
}
|
|
8029
|
+
function defaultObservationId(role, capturedAt) {
|
|
8030
|
+
return `obs_${role}_${capturedAt.replace(/[^0-9]/g, "").slice(0, 17) || "unknown"}`;
|
|
8031
|
+
}
|
|
8032
|
+
function profileCheckCounts(result) {
|
|
8033
|
+
const counts = { total: result.checks.length, passed: 0, failed: 0, skipped: 0, needs_human_review: 0 };
|
|
8034
|
+
for (const check of result.checks) {
|
|
8035
|
+
if (check.status === "passed") counts.passed += 1;
|
|
8036
|
+
if (check.status === "failed") counts.failed += 1;
|
|
8037
|
+
if (check.status === "skipped") counts.skipped += 1;
|
|
8038
|
+
if (check.status === "needs_human_review") counts.needs_human_review += 1;
|
|
8039
|
+
}
|
|
8040
|
+
return counts;
|
|
8041
|
+
}
|
|
8042
|
+
function createRiddleProofObservationReceipt(input) {
|
|
8043
|
+
const capturedAt = input.captured_at || input.profile_result?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
8044
|
+
const profileArtifacts = input.profile_result ? profileObservationArtifacts(input.profile_result) : [];
|
|
8045
|
+
const requestedCanonical = input.canonical_screenshot ? { ...input.canonical_screenshot, role: "canonical_screenshot" } : void 0;
|
|
8046
|
+
const artifacts = mergeObservationArtifacts(
|
|
8047
|
+
profileArtifacts,
|
|
8048
|
+
[...input.artifacts || [], ...requestedCanonical ? [requestedCanonical] : []]
|
|
8049
|
+
);
|
|
8050
|
+
const canonicalScreenshot = requestedCanonical ? artifacts.find((artifact) => artifact.role === "canonical_screenshot" && artifactMatchesLabel(artifact, requestedCanonical.name)) || requestedCanonical : artifacts.find((artifact) => artifact.role === "canonical_screenshot");
|
|
8051
|
+
const target = input.target.kind === "preview" && input.target.preview ? { ...input.target, preview: parseRiddlePreviewReceipt(input.target.preview) } : input.target;
|
|
8052
|
+
return {
|
|
8053
|
+
version: RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION,
|
|
8054
|
+
observation_id: input.observation_id || defaultObservationId(input.comparison_role, capturedAt),
|
|
8055
|
+
comparison_role: input.comparison_role,
|
|
8056
|
+
executor: input.executor,
|
|
8057
|
+
target,
|
|
8058
|
+
source: normalizedSourceIdentity(input.source || input.target.preview?.source),
|
|
8059
|
+
captured_at: capturedAt,
|
|
8060
|
+
artifacts,
|
|
8061
|
+
canonical_screenshot: canonicalScreenshot,
|
|
8062
|
+
profile_summary: input.profile_result ? {
|
|
8063
|
+
profile_name: input.profile_result.profile_name,
|
|
8064
|
+
status: input.profile_result.status,
|
|
8065
|
+
summary: input.profile_result.summary,
|
|
8066
|
+
route: input.profile_result.route,
|
|
8067
|
+
checks: profileCheckCounts(input.profile_result)
|
|
8068
|
+
} : void 0,
|
|
8069
|
+
proof: input.profile_result ? {
|
|
8070
|
+
profile_name: input.profile_result.profile_name,
|
|
8071
|
+
result: input.profile_result
|
|
8072
|
+
} : void 0,
|
|
8073
|
+
publication: input.publication,
|
|
8074
|
+
execution: input.execution,
|
|
8075
|
+
metadata: input.metadata
|
|
8076
|
+
};
|
|
8077
|
+
}
|
|
8078
|
+
function parseRiddleProofObservationReceipt(value) {
|
|
8079
|
+
if (!isRecord(value)) throw new Error("Observation receipt must be an object.");
|
|
8080
|
+
if (value.version !== RIDDLE_PROOF_OBSERVATION_RECEIPT_VERSION) {
|
|
8081
|
+
throw new Error(`Unsupported Observation receipt version ${String(value.version || "missing")}.`);
|
|
8082
|
+
}
|
|
8083
|
+
if (!isRecord(value.executor)) throw new Error("Observation receipt executor must be an object.");
|
|
8084
|
+
if (!isRecord(value.target)) throw new Error("Observation receipt target must be an object.");
|
|
8085
|
+
if (!isRecord(value.source)) throw new Error("Observation receipt source must be an object.");
|
|
8086
|
+
requiredString(value, "observation_id", "observation receipt");
|
|
8087
|
+
requiredString(value, "captured_at", "observation receipt");
|
|
8088
|
+
const executorKind = requiredString(value.executor, "kind", "observation receipt executor");
|
|
8089
|
+
if (!"local_playwright,riddle_hosted,browser_api,other".split(",").includes(executorKind)) {
|
|
8090
|
+
throw new Error(`Unsupported observation executor kind ${executorKind}.`);
|
|
8091
|
+
}
|
|
8092
|
+
if (value.executor.runner !== void 0) {
|
|
8093
|
+
requiredString(value.executor, "runner", "observation receipt executor");
|
|
8094
|
+
}
|
|
8095
|
+
const role = requiredString(value, "comparison_role", "observation receipt");
|
|
8096
|
+
if (!["before", "after", "standalone"].includes(role)) {
|
|
8097
|
+
throw new Error(`Unsupported observation comparison role ${role}.`);
|
|
8098
|
+
}
|
|
8099
|
+
const targetKind = requiredString(value.target, "kind", "observation receipt target");
|
|
8100
|
+
if (targetKind !== "url" && targetKind !== "preview") {
|
|
8101
|
+
throw new Error(`Unsupported observation target kind ${targetKind}.`);
|
|
8102
|
+
}
|
|
8103
|
+
requiredString(value.target, "url", "observation receipt target");
|
|
8104
|
+
if (targetKind === "preview") {
|
|
8105
|
+
if (value.target.preview === void 0) {
|
|
8106
|
+
throw new Error("Preview Observation target must include its Preview receipt.");
|
|
8107
|
+
}
|
|
8108
|
+
parseRiddlePreviewReceipt(value.target.preview);
|
|
8109
|
+
}
|
|
8110
|
+
if (!Array.isArray(value.artifacts)) throw new Error("Observation receipt artifacts must be an array.");
|
|
8111
|
+
const artifactRoles = /* @__PURE__ */ new Set(["canonical_screenshot", "setup_screenshot", "data", "diagnostic", "artifact"]);
|
|
8112
|
+
const artifacts = value.artifacts.map((artifact, index) => {
|
|
8113
|
+
if (!isRecord(artifact)) throw new Error(`Observation receipt artifact ${index} must be an object.`);
|
|
8114
|
+
requiredString(artifact, "name", `observation receipt artifact ${index}`);
|
|
8115
|
+
const artifactRole = requiredString(artifact, "role", `observation receipt artifact ${index}`);
|
|
8116
|
+
if (!artifactRoles.has(artifactRole)) {
|
|
8117
|
+
throw new Error(`Unsupported observation artifact role ${artifactRole}.`);
|
|
8118
|
+
}
|
|
8119
|
+
return artifact;
|
|
8120
|
+
});
|
|
8121
|
+
if (value.canonical_screenshot !== void 0) {
|
|
8122
|
+
if (!isRecord(value.canonical_screenshot) || value.canonical_screenshot.role !== "canonical_screenshot") {
|
|
8123
|
+
throw new Error("Observation canonical_screenshot must have the canonical_screenshot role.");
|
|
8124
|
+
}
|
|
8125
|
+
const canonical = value.canonical_screenshot;
|
|
8126
|
+
const included = artifacts.some((artifact) => artifact.role === "canonical_screenshot" && artifact.name === canonical.name && artifact.url === canonical.url && artifact.path === canonical.path);
|
|
8127
|
+
if (!included) {
|
|
8128
|
+
throw new Error("Observation canonical_screenshot must reference an artifact in the receipt.");
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
return value;
|
|
8132
|
+
}
|
|
8133
|
+
|
|
8134
|
+
// src/riddle-client.ts
|
|
7882
8135
|
var DEFAULT_RIDDLE_API_BASE_URL = "https://api.riddledc.com";
|
|
7883
8136
|
var DEFAULT_RIDDLE_API_KEY_FILE = "/tmp/riddle-api-key";
|
|
7884
8137
|
var RIDDLE_UNSUBMITTED_WAKE_HINT = "hosted worker may still be waking or waiting for a lease";
|
|
@@ -7940,6 +8193,7 @@ async function getRiddleBalance(config = {}) {
|
|
|
7940
8193
|
}
|
|
7941
8194
|
function previewDeployResultFromRecord(input) {
|
|
7942
8195
|
const { record, id, label, framework, expiresAt, publishRecovered, publishError } = input;
|
|
8196
|
+
const receipt = record.receipt && typeof record.receipt === "object" ? parseRiddlePreviewReceipt(record.receipt) : void 0;
|
|
7943
8197
|
return {
|
|
7944
8198
|
ok: true,
|
|
7945
8199
|
id: String(record.id || record.preview_id || id),
|
|
@@ -7948,13 +8202,32 @@ function previewDeployResultFromRecord(input) {
|
|
|
7948
8202
|
preview_url: String(record.preview_url || ""),
|
|
7949
8203
|
file_count: typeof record.file_count === "number" ? record.file_count : void 0,
|
|
7950
8204
|
total_bytes: typeof record.total_bytes === "number" ? record.total_bytes : void 0,
|
|
7951
|
-
expires_at: expiresAt,
|
|
8205
|
+
expires_at: receipt?.expires_at || expiresAt,
|
|
8206
|
+
receipt,
|
|
7952
8207
|
publish_recovered: publishRecovered || void 0,
|
|
7953
8208
|
publish_error: publishError,
|
|
7954
8209
|
warnings: input.warnings?.length ? input.warnings : void 0,
|
|
7955
8210
|
raw: record
|
|
7956
8211
|
};
|
|
7957
8212
|
}
|
|
8213
|
+
function gitOutput(directory, args) {
|
|
8214
|
+
try {
|
|
8215
|
+
return (0, import_node_child_process4.execFileSync)("git", ["-C", directory, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
8216
|
+
} catch {
|
|
8217
|
+
return "";
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
function detectRiddlePreviewSource(directory) {
|
|
8221
|
+
const gitRevision = gitOutput(directory, ["rev-parse", "HEAD"]);
|
|
8222
|
+
if (!gitRevision) return {};
|
|
8223
|
+
const repository = gitOutput(directory, ["config", "--get", "remote.origin.url"]);
|
|
8224
|
+
const status = gitOutput(directory, ["status", "--porcelain", "--untracked-files=normal"]);
|
|
8225
|
+
return {
|
|
8226
|
+
git_revision: gitRevision,
|
|
8227
|
+
repository: repository || void 0,
|
|
8228
|
+
dirty: Boolean(status)
|
|
8229
|
+
};
|
|
8230
|
+
}
|
|
7958
8231
|
function canRecoverPreviewPublish(error) {
|
|
7959
8232
|
return error instanceof RiddleApiError && PREVIEW_PUBLISH_RECOVERY_STATUSES.has(error.status);
|
|
7960
8233
|
}
|
|
@@ -8037,12 +8310,13 @@ async function waitForPublishedPreview(config, input) {
|
|
|
8037
8310
|
}
|
|
8038
8311
|
throw input.publishError;
|
|
8039
8312
|
}
|
|
8040
|
-
async function deployRiddlePreview(config, directory, label, framework = "static") {
|
|
8313
|
+
async function deployRiddlePreview(config, directory, label, framework = "static", options = {}) {
|
|
8041
8314
|
if (!directory?.trim()) throw new Error("directory is required");
|
|
8042
8315
|
if (!label?.trim()) throw new Error("label is required");
|
|
8043
8316
|
if (framework !== "spa" && framework !== "static") throw new Error("framework must be spa or static");
|
|
8044
8317
|
const startedAt = Date.now();
|
|
8045
8318
|
const warnings = collectRiddlePreviewDeployWarnings(directory, framework);
|
|
8319
|
+
const source = options.source || detectRiddlePreviewSource(directory);
|
|
8046
8320
|
const emitProgress = previewProgressEmitter(config, { label, framework, directory, startedAt, warnings });
|
|
8047
8321
|
await emitProgress({ stage: "validating", message: "checking preview input directory" });
|
|
8048
8322
|
const localSummary = summarizePreviewDirectory(directory);
|
|
@@ -8054,7 +8328,7 @@ async function deployRiddlePreview(config, directory, label, framework = "static
|
|
|
8054
8328
|
});
|
|
8055
8329
|
const created = await riddleRequestJson(config, "/v1/preview", {
|
|
8056
8330
|
method: "POST",
|
|
8057
|
-
body: JSON.stringify({ framework, label })
|
|
8331
|
+
body: JSON.stringify({ framework, label, source })
|
|
8058
8332
|
});
|
|
8059
8333
|
const id = String(created.id || "");
|
|
8060
8334
|
const uploadUrl = String(created.upload_url || "");
|
|
@@ -8196,8 +8470,8 @@ function collectRiddlePreviewDeployWarnings(directory, framework = "static") {
|
|
|
8196
8470
|
return [];
|
|
8197
8471
|
}
|
|
8198
8472
|
}
|
|
8199
|
-
async function deployRiddleStaticPreview(config, directory, label) {
|
|
8200
|
-
return deployRiddlePreview(config, directory, label, "static");
|
|
8473
|
+
async function deployRiddleStaticPreview(config, directory, label, options = {}) {
|
|
8474
|
+
return deployRiddlePreview(config, directory, label, "static", options);
|
|
8201
8475
|
}
|
|
8202
8476
|
function createTarball(directory, label, exclude = []) {
|
|
8203
8477
|
const scratch = (0, import_node_fs5.mkdtempSync)(import_node_path5.default.join((0, import_node_os2.tmpdir)(), "riddle-upload-"));
|
|
@@ -8319,21 +8593,31 @@ function parseTimestampMs(value) {
|
|
|
8319
8593
|
}
|
|
8320
8594
|
function buildPollSnapshot(jobId, job, input) {
|
|
8321
8595
|
const status = job?.status ? String(job.status) : null;
|
|
8596
|
+
const phase = job?.phase ? String(job.phase) : null;
|
|
8322
8597
|
const terminal = isTerminalRiddleJobStatus(status);
|
|
8323
8598
|
const createdAt = stringField(job, "created_at");
|
|
8324
8599
|
const submittedAt = stringField(job, "submitted_at");
|
|
8325
8600
|
const completedAt = stringField(job, "completed_at");
|
|
8326
8601
|
const createdMs = parseTimestampMs(createdAt);
|
|
8327
8602
|
const submittedMs = parseTimestampMs(submittedAt);
|
|
8603
|
+
const rawExecution = job?.execution && typeof job.execution === "object" && !Array.isArray(job.execution) ? job.execution : null;
|
|
8604
|
+
const enqueuedAt = rawExecution ? stringField(rawExecution, "enqueued_at") : null;
|
|
8605
|
+
const enqueuedMs = parseTimestampMs(enqueuedAt);
|
|
8606
|
+
const queueStartedMs = enqueuedMs ?? createdMs;
|
|
8607
|
+
const claimedAt = rawExecution ? stringField(rawExecution, "claimed_at") : null;
|
|
8608
|
+
const claimedMs = parseTimestampMs(claimedAt);
|
|
8328
8609
|
let queueElapsedMs = null;
|
|
8329
|
-
if (
|
|
8330
|
-
queueElapsedMs = Math.max(0,
|
|
8331
|
-
} else if (
|
|
8332
|
-
queueElapsedMs = Math.max(0,
|
|
8610
|
+
if (queueStartedMs !== null && claimedMs !== null) {
|
|
8611
|
+
queueElapsedMs = Math.max(0, claimedMs - queueStartedMs);
|
|
8612
|
+
} else if (queueStartedMs !== null && submittedMs !== null) {
|
|
8613
|
+
queueElapsedMs = Math.max(0, submittedMs - queueStartedMs);
|
|
8614
|
+
} else if (queueStartedMs !== null && !submittedAt && !terminal) {
|
|
8615
|
+
queueElapsedMs = Math.max(0, input.observedAt - queueStartedMs);
|
|
8333
8616
|
}
|
|
8334
8617
|
return {
|
|
8335
8618
|
job_id: jobId,
|
|
8336
8619
|
status,
|
|
8620
|
+
phase,
|
|
8337
8621
|
terminal,
|
|
8338
8622
|
attempt: input.attempt,
|
|
8339
8623
|
attempts: input.attempts,
|
|
@@ -8343,7 +8627,9 @@ function buildPollSnapshot(jobId, job, input) {
|
|
|
8343
8627
|
completed_at: completedAt,
|
|
8344
8628
|
queue_elapsed_ms: queueElapsedMs,
|
|
8345
8629
|
pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
|
|
8346
|
-
running_without_submission: Boolean(status && !terminal && !submittedAt)
|
|
8630
|
+
running_without_submission: Boolean(status && !terminal && !submittedAt),
|
|
8631
|
+
active_execution: Boolean(status === "running" && phase && phase !== "queued"),
|
|
8632
|
+
execution: rawExecution ? rawExecution : void 0
|
|
8347
8633
|
};
|
|
8348
8634
|
}
|
|
8349
8635
|
function pollMessage(snapshot, timedOut) {
|
|
@@ -8352,7 +8638,7 @@ function pollMessage(snapshot, timedOut) {
|
|
|
8352
8638
|
const wakeHint = snapshot.running_without_submission && !snapshot.created_at && !snapshot.submitted_at ? ` ${RIDDLE_UNSUBMITTED_WAKE_HINT}.` : "";
|
|
8353
8639
|
const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
|
|
8354
8640
|
const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
|
|
8355
|
-
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}`;
|
|
8641
|
+
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}`;
|
|
8356
8642
|
}
|
|
8357
8643
|
async function pollRiddleJob(config, jobId, options = {}) {
|
|
8358
8644
|
if (!jobId?.trim()) throw new Error("jobId is required");
|
|
@@ -8386,6 +8672,7 @@ async function pollRiddleJob(config, jobId, options = {}) {
|
|
|
8386
8672
|
};
|
|
8387
8673
|
const progressKey = [
|
|
8388
8674
|
lastSnapshot.status || "unknown",
|
|
8675
|
+
lastSnapshot.phase || "unknown",
|
|
8389
8676
|
lastSnapshot.terminal ? "terminal" : "nonterminal",
|
|
8390
8677
|
lastSnapshot.submitted_at ? "submitted" : "unsubmitted"
|
|
8391
8678
|
].join(":");
|
|
@@ -8452,8 +8739,8 @@ function createRiddleApiClient(config = {}) {
|
|
|
8452
8739
|
apiKeySource: () => resolveRiddleApiKeySource(config),
|
|
8453
8740
|
requestJson: (pathname, init) => riddleRequestJson(config, pathname, init),
|
|
8454
8741
|
getBalance: () => getRiddleBalance(config),
|
|
8455
|
-
deployPreview: (directory, label, framework = "static") => deployRiddlePreview(config, directory, label, framework),
|
|
8456
|
-
deployStaticPreview: (directory, label) => deployRiddleStaticPreview(config, directory, label),
|
|
8742
|
+
deployPreview: (directory, label, framework = "static", options) => deployRiddlePreview(config, directory, label, framework, options),
|
|
8743
|
+
deployStaticPreview: (directory, label, options) => deployRiddleStaticPreview(config, directory, label, options),
|
|
8457
8744
|
runScript: (input) => runRiddleScript(config, input),
|
|
8458
8745
|
runServerPreview: (input) => runRiddleServerPreview(config, input),
|
|
8459
8746
|
pollJob: (jobId, options) => pollRiddleJob(config, jobId, options)
|
|
@@ -8584,7 +8871,7 @@ function deriveRiddleProofArtifactBodyAssertions(input) {
|
|
|
8584
8871
|
var DEFAULT_VIEWPORTS = [
|
|
8585
8872
|
{ name: "desktop", width: 1280, height: 800 }
|
|
8586
8873
|
];
|
|
8587
|
-
function
|
|
8874
|
+
function isRecord2(value) {
|
|
8588
8875
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8589
8876
|
}
|
|
8590
8877
|
function stringValue3(value) {
|
|
@@ -8615,7 +8902,7 @@ function booleanValue2(value) {
|
|
|
8615
8902
|
return typeof value === "boolean" ? value : void 0;
|
|
8616
8903
|
}
|
|
8617
8904
|
function horizontalBoundsOverflowPx(value) {
|
|
8618
|
-
if (!
|
|
8905
|
+
if (!isRecord2(value)) return 0;
|
|
8619
8906
|
let max = maxPositiveNumber(
|
|
8620
8907
|
value.overflow_px,
|
|
8621
8908
|
value.overflow,
|
|
@@ -8630,7 +8917,7 @@ function horizontalBoundsOverflowPx(value) {
|
|
|
8630
8917
|
return roundPixels(max);
|
|
8631
8918
|
}
|
|
8632
8919
|
function horizontalOffenderOverflowPx(value) {
|
|
8633
|
-
if (!
|
|
8920
|
+
if (!isRecord2(value)) return 0;
|
|
8634
8921
|
let max = maxPositiveNumber(
|
|
8635
8922
|
value.overflow,
|
|
8636
8923
|
value.overflow_px,
|
|
@@ -8641,8 +8928,8 @@ function horizontalOffenderOverflowPx(value) {
|
|
|
8641
8928
|
value.leftOverflowPx,
|
|
8642
8929
|
value.rightOverflowPx
|
|
8643
8930
|
);
|
|
8644
|
-
const clipped =
|
|
8645
|
-
if (
|
|
8931
|
+
const clipped = isRecord2(value.clipped || value.clip || value.clipping) ? value.clipped || value.clip || value.clipping : void 0;
|
|
8932
|
+
if (isRecord2(clipped)) {
|
|
8646
8933
|
max = Math.max(max, maxPositiveNumber(
|
|
8647
8934
|
clipped.left,
|
|
8648
8935
|
clipped.right,
|
|
@@ -8652,9 +8939,9 @@ function horizontalOffenderOverflowPx(value) {
|
|
|
8652
8939
|
clipped.rightPx
|
|
8653
8940
|
));
|
|
8654
8941
|
}
|
|
8655
|
-
const rect =
|
|
8942
|
+
const rect = isRecord2(value.rect || value.bounds || value.bounding_rect || value.boundingRect) ? value.rect || value.bounds || value.bounding_rect || value.boundingRect : void 0;
|
|
8656
8943
|
const viewportWidth = numberValue2(value.viewport_width ?? value.viewportWidth);
|
|
8657
|
-
if (
|
|
8944
|
+
if (isRecord2(rect) && viewportWidth !== void 0) {
|
|
8658
8945
|
const left = numberValue2(rect.left);
|
|
8659
8946
|
const right = numberValue2(rect.right);
|
|
8660
8947
|
if (left !== void 0 && left < 0) max = Math.max(max, Math.abs(left));
|
|
@@ -8663,7 +8950,7 @@ function horizontalOffenderOverflowPx(value) {
|
|
|
8663
8950
|
return roundPixels(max);
|
|
8664
8951
|
}
|
|
8665
8952
|
function boundsOffendersForEvidence(value) {
|
|
8666
|
-
if (!
|
|
8953
|
+
if (!isRecord2(value)) return [];
|
|
8667
8954
|
const offenders = [
|
|
8668
8955
|
...Array.isArray(value.overflow_offenders) ? value.overflow_offenders : [],
|
|
8669
8956
|
...Array.isArray(value.overflowOffenders) ? value.overflowOffenders : [],
|
|
@@ -8674,7 +8961,7 @@ function boundsOffendersForEvidence(value) {
|
|
|
8674
8961
|
...Array.isArray(value.clipping_offenders) ? value.clipping_offenders : [],
|
|
8675
8962
|
...Array.isArray(value.clippingOffenders) ? value.clippingOffenders : []
|
|
8676
8963
|
];
|
|
8677
|
-
return offenders.filter((item) =>
|
|
8964
|
+
return offenders.filter((item) => isRecord2(item)).sort((a, b) => horizontalOffenderOverflowPx(b) - horizontalOffenderOverflowPx(a));
|
|
8678
8965
|
}
|
|
8679
8966
|
function maxPositiveNumber(...values) {
|
|
8680
8967
|
let max = 0;
|
|
@@ -8692,11 +8979,11 @@ function timeoutSecValue(value) {
|
|
|
8692
8979
|
return number && number > 0 ? Math.ceil(number) : void 0;
|
|
8693
8980
|
}
|
|
8694
8981
|
function jsonRecord(value) {
|
|
8695
|
-
if (!
|
|
8982
|
+
if (!isRecord2(value)) return void 0;
|
|
8696
8983
|
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
8697
8984
|
}
|
|
8698
8985
|
function stringRecord(value) {
|
|
8699
|
-
if (!
|
|
8986
|
+
if (!isRecord2(value)) return void 0;
|
|
8700
8987
|
const entries = Object.entries(value).map(([key, child]) => [key, String(child)]).filter(([key]) => key.trim());
|
|
8701
8988
|
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
8702
8989
|
}
|
|
@@ -8705,7 +8992,7 @@ function toJsonValue(value) {
|
|
|
8705
8992
|
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
8706
8993
|
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
8707
8994
|
if (Array.isArray(value)) return value.map(toJsonValue);
|
|
8708
|
-
if (
|
|
8995
|
+
if (isRecord2(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
8709
8996
|
return String(value);
|
|
8710
8997
|
}
|
|
8711
8998
|
function jsonValueType(value) {
|
|
@@ -8723,7 +9010,7 @@ function compactJsonAssertionSample(value, depth = 0) {
|
|
|
8723
9010
|
if (depth >= 2) return `[array:${value.length}]`;
|
|
8724
9011
|
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
8725
9012
|
}
|
|
8726
|
-
if (
|
|
9013
|
+
if (isRecord2(value)) {
|
|
8727
9014
|
const entries = Object.entries(value).slice(0, 8);
|
|
8728
9015
|
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
8729
9016
|
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
@@ -8738,7 +9025,7 @@ function attachJsonAssertionObservedValue(result, value) {
|
|
|
8738
9025
|
result.observed_sample = compactJsonAssertionSample(value);
|
|
8739
9026
|
return;
|
|
8740
9027
|
}
|
|
8741
|
-
if (type === "object" &&
|
|
9028
|
+
if (type === "object" && isRecord2(value)) {
|
|
8742
9029
|
const keyCount = Object.keys(value).length;
|
|
8743
9030
|
result.observed_key_count = keyCount;
|
|
8744
9031
|
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
@@ -8756,7 +9043,7 @@ function deepJsonEqual(left, right) {
|
|
|
8756
9043
|
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
8757
9044
|
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
8758
9045
|
}
|
|
8759
|
-
if (!
|
|
9046
|
+
if (!isRecord2(left) || !isRecord2(right)) return false;
|
|
8760
9047
|
const leftKeys = Object.keys(left).sort();
|
|
8761
9048
|
const rightKeys = Object.keys(right).sort();
|
|
8762
9049
|
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
@@ -8769,7 +9056,7 @@ function jsonContains(observed, expected) {
|
|
|
8769
9056
|
if (Array.isArray(observed)) {
|
|
8770
9057
|
return observed.some((item) => deepJsonEqual(item, expected));
|
|
8771
9058
|
}
|
|
8772
|
-
if (
|
|
9059
|
+
if (isRecord2(observed) && isRecord2(expected)) {
|
|
8773
9060
|
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
8774
9061
|
}
|
|
8775
9062
|
return false;
|
|
@@ -8837,7 +9124,7 @@ function resolveJsonPath(root, path7) {
|
|
|
8837
9124
|
if (typeof segment === "number") {
|
|
8838
9125
|
return { exists: false };
|
|
8839
9126
|
}
|
|
8840
|
-
if (!
|
|
9127
|
+
if (!isRecord2(current) || !hasOwn(current, segment)) return { exists: false };
|
|
8841
9128
|
current = current[segment];
|
|
8842
9129
|
}
|
|
8843
9130
|
return { exists: true, value: current };
|
|
@@ -8911,9 +9198,9 @@ function profileSetupFrameUrls(viewport) {
|
|
|
8911
9198
|
const urls = [];
|
|
8912
9199
|
const frames = viewport.frames || {};
|
|
8913
9200
|
for (const container of Object.values(frames)) {
|
|
8914
|
-
if (!
|
|
9201
|
+
if (!isRecord2(container) || !Array.isArray(container.frames)) continue;
|
|
8915
9202
|
for (const frame of container.frames) {
|
|
8916
|
-
if (!
|
|
9203
|
+
if (!isRecord2(frame)) continue;
|
|
8917
9204
|
const url = typeof frame.url === "string" ? frame.url : void 0;
|
|
8918
9205
|
if (url && !urls.includes(url)) urls.push(url);
|
|
8919
9206
|
}
|
|
@@ -9007,7 +9294,7 @@ function profileSetupReturnSummaryFields(result) {
|
|
|
9007
9294
|
if (path8) fields.push({ path: path8 });
|
|
9008
9295
|
continue;
|
|
9009
9296
|
}
|
|
9010
|
-
if (!
|
|
9297
|
+
if (!isRecord2(item)) continue;
|
|
9011
9298
|
const path7 = stringValue3(item.path) ?? stringValue3(item.key) ?? stringValue3(item.json_path) ?? stringValue3(item.jsonPath);
|
|
9012
9299
|
if (!path7) continue;
|
|
9013
9300
|
const label = stringValue3(item.label) ?? stringValue3(item.name) ?? stringValue3(item.title);
|
|
@@ -9243,6 +9530,12 @@ function profileScreenshotLabels(viewports) {
|
|
|
9243
9530
|
}
|
|
9244
9531
|
return labels;
|
|
9245
9532
|
}
|
|
9533
|
+
function profileFinalScreenshotLabels(viewports) {
|
|
9534
|
+
return (viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => typeof label === "string" && Boolean(label.trim()));
|
|
9535
|
+
}
|
|
9536
|
+
function profileAllSetupScreenshotLabels(viewports) {
|
|
9537
|
+
return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport.setup_action_results || []));
|
|
9538
|
+
}
|
|
9246
9539
|
function profileSetupSummary(viewports, actionCount, expectedActionCountByViewport, finalScreenshotFullPage) {
|
|
9247
9540
|
const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === void 0 ? void 0 : finalScreenshotFullPage !== false;
|
|
9248
9541
|
const finalScreenshotCount = viewports.filter((viewport) => typeof viewport.screenshot_label === "string" && viewport.screenshot_label.trim()).length;
|
|
@@ -9397,7 +9690,7 @@ function slugifyRiddleProofProfileName(value) {
|
|
|
9397
9690
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "profile";
|
|
9398
9691
|
}
|
|
9399
9692
|
function normalizeViewport(input, index) {
|
|
9400
|
-
if (!
|
|
9693
|
+
if (!isRecord2(input)) throw new Error(`target.viewports[${index}] must be an object.`);
|
|
9401
9694
|
const width = numberValue2(input.width);
|
|
9402
9695
|
const height = numberValue2(input.height);
|
|
9403
9696
|
if (!width || !height || width < 100 || height < 100) {
|
|
@@ -9463,7 +9756,7 @@ function normalizeReturnSummaryFields(input, index) {
|
|
|
9463
9756
|
if (!path8) throw new Error(`target.setup_actions[${index}].return_summary_fields[${fieldIndex}] requires path.`);
|
|
9464
9757
|
return { path: path8 };
|
|
9465
9758
|
}
|
|
9466
|
-
if (!
|
|
9759
|
+
if (!isRecord2(field)) {
|
|
9467
9760
|
throw new Error(`target.setup_actions[${index}].return_summary_fields[${fieldIndex}] must be a string path or object.`);
|
|
9468
9761
|
}
|
|
9469
9762
|
const path7 = stringFromOwn(field, "path", "key", "json_path", "jsonPath");
|
|
@@ -9572,7 +9865,7 @@ function normalizeSetupActionNonNegativeNumber(input, index, outputKey, ...keys)
|
|
|
9572
9865
|
return value;
|
|
9573
9866
|
}
|
|
9574
9867
|
function normalizeSetupAction(input, index) {
|
|
9575
|
-
if (!
|
|
9868
|
+
if (!isRecord2(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
|
|
9576
9869
|
const type = normalizeSetupActionType(stringValue3(input.type), index);
|
|
9577
9870
|
const rawType = String(input.type || "").trim().replace(/-/g, "_").toLowerCase();
|
|
9578
9871
|
const selector = stringValue3(input.selector);
|
|
@@ -9864,7 +10157,7 @@ function normalizeTargetScreenshotFullPage(input) {
|
|
|
9864
10157
|
return values[0];
|
|
9865
10158
|
}
|
|
9866
10159
|
function normalizeNetworkMock(input, index) {
|
|
9867
|
-
if (!
|
|
10160
|
+
if (!isRecord2(input)) throw new Error(`target.network_mocks[${index}] must be an object.`);
|
|
9868
10161
|
const url = stringValue3(input.url) || stringValue3(input.glob) || stringValue3(input.pattern);
|
|
9869
10162
|
if (!url) throw new Error(`target.network_mocks[${index}] requires url.`);
|
|
9870
10163
|
const payload = normalizeNetworkMockResponsePayload(input, `target.network_mocks[${index}]`);
|
|
@@ -10019,7 +10312,7 @@ function normalizeNetworkMockResponses(value, mockIndex, defaults) {
|
|
|
10019
10312
|
abort_error_code: defaults.abort_error_code
|
|
10020
10313
|
};
|
|
10021
10314
|
return value.map((response, responseIndex) => {
|
|
10022
|
-
if (!
|
|
10315
|
+
if (!isRecord2(response)) {
|
|
10023
10316
|
throw new Error(`target.network_mocks[${mockIndex}].responses[${responseIndex}] must be an object.`);
|
|
10024
10317
|
}
|
|
10025
10318
|
return normalizeNetworkMockResponsePayload(
|
|
@@ -10116,7 +10409,7 @@ function normalizeRouteInventoryPath(value, label) {
|
|
|
10116
10409
|
}
|
|
10117
10410
|
function normalizeRouteInventoryRoute(input, index) {
|
|
10118
10411
|
if (typeof input === "string") return { path: normalizeRouteInventoryPath(input, `checks route_inventory expected_routes[${index}]`) };
|
|
10119
|
-
if (!
|
|
10412
|
+
if (!isRecord2(input)) throw new Error(`checks route_inventory expected_routes[${index}] must be a string or object.`);
|
|
10120
10413
|
return {
|
|
10121
10414
|
name: stringValue3(input.name),
|
|
10122
10415
|
path: normalizeRouteInventoryPath(input.path, `checks route_inventory expected_routes[${index}]`)
|
|
@@ -10195,7 +10488,7 @@ function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
|
10195
10488
|
if (!path8) throw new Error(`${itemLabel} path must not be empty.`);
|
|
10196
10489
|
return { path: path8, exists: true };
|
|
10197
10490
|
}
|
|
10198
|
-
if (!
|
|
10491
|
+
if (!isRecord2(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
10199
10492
|
const path7 = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
10200
10493
|
if (!path7) throw new Error(`${itemLabel}.path is required.`);
|
|
10201
10494
|
const assertion = {
|
|
@@ -10233,7 +10526,7 @@ function dialogCountFieldForCheckType(type) {
|
|
|
10233
10526
|
return "dialog_count";
|
|
10234
10527
|
}
|
|
10235
10528
|
function normalizeCheck(input, index) {
|
|
10236
|
-
if (!
|
|
10529
|
+
if (!isRecord2(input)) throw new Error(`checks[${index}] must be an object.`);
|
|
10237
10530
|
const type = stringValue3(input.type);
|
|
10238
10531
|
if (!type) throw new Error(`checks[${index}].type is required.`);
|
|
10239
10532
|
if (!isSupportedCheckType(type)) {
|
|
@@ -10398,7 +10691,7 @@ function normalizeFailurePolicy(input) {
|
|
|
10398
10691
|
configuration_error: "fail",
|
|
10399
10692
|
needs_human_review: "fail"
|
|
10400
10693
|
};
|
|
10401
|
-
if (!
|
|
10694
|
+
if (!isRecord2(input)) return defaults;
|
|
10402
10695
|
const next = { ...defaults };
|
|
10403
10696
|
for (const [key, value] of Object.entries(input)) {
|
|
10404
10697
|
if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(key)) continue;
|
|
@@ -10409,12 +10702,12 @@ function normalizeFailurePolicy(input) {
|
|
|
10409
10702
|
return next;
|
|
10410
10703
|
}
|
|
10411
10704
|
function normalizeRiddleProofProfile(input, options = {}) {
|
|
10412
|
-
if (!
|
|
10705
|
+
if (!isRecord2(input)) throw new Error("profile must be a JSON object.");
|
|
10413
10706
|
const version = stringValue3(input.version) || RIDDLE_PROOF_PROFILE_VERSION;
|
|
10414
10707
|
if (version !== RIDDLE_PROOF_PROFILE_VERSION) {
|
|
10415
10708
|
throw new Error(`Unsupported profile version ${version}. Expected ${RIDDLE_PROOF_PROFILE_VERSION}.`);
|
|
10416
10709
|
}
|
|
10417
|
-
const targetInput =
|
|
10710
|
+
const targetInput = isRecord2(input.target) ? input.target : {};
|
|
10418
10711
|
const checks = Array.isArray(input.checks) ? input.checks.map(normalizeCheck) : [];
|
|
10419
10712
|
if (!checks.length) throw new Error("profile.checks must contain at least one check.");
|
|
10420
10713
|
const targetUrl = stringValue3(options.url) || stringValue3(targetInput.url);
|
|
@@ -10545,19 +10838,19 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
10545
10838
|
function httpStatusBodyContainsFailures(result, check) {
|
|
10546
10839
|
const expected = check.body_contains?.filter(Boolean) ?? [];
|
|
10547
10840
|
if (!expected.length) return [];
|
|
10548
|
-
const observed =
|
|
10841
|
+
const observed = isRecord2(result.body_contains) ? result.body_contains : {};
|
|
10549
10842
|
return expected.filter((text) => observed[text] !== true);
|
|
10550
10843
|
}
|
|
10551
10844
|
function httpStatusBodyNotContainsFailures(result, check) {
|
|
10552
10845
|
const forbidden = check.body_not_contains?.filter(Boolean) ?? [];
|
|
10553
10846
|
if (!forbidden.length) return [];
|
|
10554
|
-
const observed =
|
|
10847
|
+
const observed = isRecord2(result.body_not_contains) ? result.body_not_contains : {};
|
|
10555
10848
|
return forbidden.filter((text) => observed[text] !== false);
|
|
10556
10849
|
}
|
|
10557
10850
|
function httpStatusBodyNotPatternFailures(result, check) {
|
|
10558
10851
|
const forbidden = check.body_not_patterns?.filter(Boolean) ?? [];
|
|
10559
10852
|
if (!forbidden.length) return [];
|
|
10560
|
-
const observed =
|
|
10853
|
+
const observed = isRecord2(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
10561
10854
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
10562
10855
|
}
|
|
10563
10856
|
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
@@ -10573,7 +10866,7 @@ function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
|
10573
10866
|
errors: ["body_json_assertions evidence missing"]
|
|
10574
10867
|
}));
|
|
10575
10868
|
}
|
|
10576
|
-
return result.body_json_assertions.filter((assertion) =>
|
|
10869
|
+
return result.body_json_assertions.filter((assertion) => isRecord2(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
10577
10870
|
label: stringValue3(assertion.label) || stringValue3(assertion.path) || "json assertion",
|
|
10578
10871
|
path: stringValue3(assertion.path) || "",
|
|
10579
10872
|
ok: false,
|
|
@@ -10639,7 +10932,7 @@ async function responseBodyText(response) {
|
|
|
10639
10932
|
return { text: "", bytes: null };
|
|
10640
10933
|
}
|
|
10641
10934
|
function httpStatusRequestBody(check) {
|
|
10642
|
-
const headers =
|
|
10935
|
+
const headers = isRecord2(check.headers) ? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim())) : {};
|
|
10643
10936
|
if (check.body_json !== void 0) {
|
|
10644
10937
|
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) {
|
|
10645
10938
|
headers["content-type"] = "application/json";
|
|
@@ -10716,11 +11009,11 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10716
11009
|
content_type: stringValue3(result.content_type) ?? null,
|
|
10717
11010
|
content_length: numberValue2(result.content_length) ?? null,
|
|
10718
11011
|
bytes: numberValue2(result.bytes) ?? null,
|
|
10719
|
-
body_contains:
|
|
11012
|
+
body_contains: isRecord2(result.body_contains) ? Object.fromEntries(Object.entries(result.body_contains).map(([key, value]) => [key, value === true])) : null,
|
|
10720
11013
|
body_contains_missing: bodyContainsMissing,
|
|
10721
|
-
body_not_contains:
|
|
11014
|
+
body_not_contains: isRecord2(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
10722
11015
|
body_not_contains_found: bodyNotContainsFound,
|
|
10723
|
-
body_not_patterns:
|
|
11016
|
+
body_not_patterns: isRecord2(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
10724
11017
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10725
11018
|
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
10726
11019
|
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
@@ -10748,7 +11041,7 @@ async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}
|
|
|
10748
11041
|
}
|
|
10749
11042
|
function httpStatusEvidenceForCheck(viewport, check) {
|
|
10750
11043
|
const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
|
|
10751
|
-
return
|
|
11044
|
+
return isRecord2(evidence) ? evidence : void 0;
|
|
10752
11045
|
}
|
|
10753
11046
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
10754
11047
|
const key = httpStatusKey(check, viewport.url);
|
|
@@ -10804,11 +11097,11 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10804
11097
|
content_type: stringValue3(statusEvidence.content_type) ?? null,
|
|
10805
11098
|
content_length: numberValue2(statusEvidence.content_length) ?? null,
|
|
10806
11099
|
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
10807
|
-
body_contains:
|
|
11100
|
+
body_contains: isRecord2(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
|
|
10808
11101
|
body_contains_missing: bodyContainsMissing,
|
|
10809
|
-
body_not_contains:
|
|
11102
|
+
body_not_contains: isRecord2(statusEvidence.body_not_contains) ? toJsonValue(statusEvidence.body_not_contains) : null,
|
|
10810
11103
|
body_not_contains_found: bodyNotContainsFound,
|
|
10811
|
-
body_not_patterns:
|
|
11104
|
+
body_not_patterns: isRecord2(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
10812
11105
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10813
11106
|
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
10814
11107
|
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
@@ -10818,7 +11111,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10818
11111
|
}
|
|
10819
11112
|
function linkStatusEvidenceForCheck(viewport, check) {
|
|
10820
11113
|
const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
|
|
10821
|
-
return
|
|
11114
|
+
return isRecord2(evidence) ? evidence : void 0;
|
|
10822
11115
|
}
|
|
10823
11116
|
function summarizeLinkStatusEvidence(viewport, check) {
|
|
10824
11117
|
const linkEvidence = linkStatusEvidenceForCheck(viewport, check);
|
|
@@ -10832,7 +11125,7 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
10832
11125
|
failures: [{ code: "link_status_evidence_missing" }]
|
|
10833
11126
|
};
|
|
10834
11127
|
}
|
|
10835
|
-
const results = Array.isArray(linkEvidence.results) ? linkEvidence.results.filter(
|
|
11128
|
+
const results = Array.isArray(linkEvidence.results) ? linkEvidence.results.filter(isRecord2) : [];
|
|
10836
11129
|
const totalCount = numberValue2(linkEvidence.total_count) ?? results.length;
|
|
10837
11130
|
const resultCount = numberValue2(linkEvidence.result_count) ?? totalCount;
|
|
10838
11131
|
const storedResultCount = numberValue2(linkEvidence.stored_result_count) ?? results.length;
|
|
@@ -10875,7 +11168,7 @@ function summarizeLinkStatusEvidence(viewport, check) {
|
|
|
10875
11168
|
if (check.min_count !== void 0 && totalCount < check.min_count) {
|
|
10876
11169
|
failures.push({ code: "link_status_count_below_minimum", min_count: check.min_count, actual: totalCount });
|
|
10877
11170
|
}
|
|
10878
|
-
const statusCounts =
|
|
11171
|
+
const statusCounts = isRecord2(linkEvidence.status_counts) ? linkEvidence.status_counts : {};
|
|
10879
11172
|
return {
|
|
10880
11173
|
viewport: viewport.name,
|
|
10881
11174
|
selector: linkStatusSelector(check),
|
|
@@ -10912,7 +11205,7 @@ function observeWithinKey(check) {
|
|
|
10912
11205
|
function textSequenceForCheck(viewport, check) {
|
|
10913
11206
|
const key = selectorKey(check);
|
|
10914
11207
|
const sequence = viewport.text_sequences?.[key];
|
|
10915
|
-
if (
|
|
11208
|
+
if (isRecord2(sequence)) {
|
|
10916
11209
|
const visibleMatchTexts = Array.isArray(sequence.visible_match_texts) ? sequence.visible_match_texts : [];
|
|
10917
11210
|
const matchTexts = Array.isArray(sequence.match_texts) ? sequence.match_texts : [];
|
|
10918
11211
|
const visibleTexts = Array.isArray(sequence.visible_texts) ? sequence.visible_texts : [];
|
|
@@ -10942,9 +11235,9 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
10942
11235
|
}
|
|
10943
11236
|
function frameEvidenceForSelector(viewport, selector) {
|
|
10944
11237
|
const container = viewport.frames?.[selector];
|
|
10945
|
-
if (!
|
|
11238
|
+
if (!isRecord2(container)) return [];
|
|
10946
11239
|
const frames = Array.isArray(container.frames) ? container.frames : [];
|
|
10947
|
-
return frames.filter((frame) =>
|
|
11240
|
+
return frames.filter((frame) => isRecord2(frame));
|
|
10948
11241
|
}
|
|
10949
11242
|
function frameTextSample(frame) {
|
|
10950
11243
|
const parts = [
|
|
@@ -10988,7 +11281,7 @@ function routeInventoryExpectedRouteSummaries(value, fallback) {
|
|
|
10988
11281
|
const path8 = route.trim();
|
|
10989
11282
|
return path8 ? { path: path8 } : void 0;
|
|
10990
11283
|
}
|
|
10991
|
-
if (!
|
|
11284
|
+
if (!isRecord2(route)) return void 0;
|
|
10992
11285
|
const path7 = stringValue3(route.path);
|
|
10993
11286
|
if (!path7) return void 0;
|
|
10994
11287
|
const name = stringValue3(route.name);
|
|
@@ -11070,11 +11363,11 @@ function textCheckFailureSamples(viewport, check) {
|
|
|
11070
11363
|
return fallback ? [fallback] : [];
|
|
11071
11364
|
}
|
|
11072
11365
|
function allowedMessageSample(input) {
|
|
11073
|
-
if (!
|
|
11366
|
+
if (!isRecord2(input)) return String(input || "");
|
|
11074
11367
|
const parts = [
|
|
11075
11368
|
input.text,
|
|
11076
11369
|
input.message,
|
|
11077
|
-
|
|
11370
|
+
isRecord2(input.location) ? input.location.url : void 0
|
|
11078
11371
|
];
|
|
11079
11372
|
return parts.map((part) => String(part || "")).filter(Boolean).join(" ");
|
|
11080
11373
|
}
|
|
@@ -11091,12 +11384,12 @@ function matchesAllowedMessage(input, texts, patterns) {
|
|
|
11091
11384
|
return false;
|
|
11092
11385
|
}
|
|
11093
11386
|
function consoleEventLocationUrl(input) {
|
|
11094
|
-
if (!
|
|
11387
|
+
if (!isRecord2(input) || !isRecord2(input.location)) return void 0;
|
|
11095
11388
|
return stringValue3(input.location.url);
|
|
11096
11389
|
}
|
|
11097
11390
|
function expectedFailedNetworkMockEvents(evidence) {
|
|
11098
11391
|
return (evidence.network_mocks || []).filter((event) => {
|
|
11099
|
-
if (!
|
|
11392
|
+
if (!isRecord2(event) || event.ok === false) return false;
|
|
11100
11393
|
const status = numberValue2(event.status);
|
|
11101
11394
|
const isHttpFailure = status !== void 0 && status >= 400;
|
|
11102
11395
|
const isAbortedMock = event.abort === true && Boolean(stringValue3(event.abort_error_code));
|
|
@@ -11126,7 +11419,7 @@ function expectedFailedNetworkMockConsoleEventSummary(event, evidence) {
|
|
|
11126
11419
|
abort_error_code: match ? stringValue3(match.abort_error_code) ?? null : null,
|
|
11127
11420
|
label: match ? stringValue3(match.label) ?? null : null,
|
|
11128
11421
|
response_label: match ? stringValue3(match.response_label) ?? null : null,
|
|
11129
|
-
text:
|
|
11422
|
+
text: isRecord2(event) && typeof event.text === "string" ? event.text.slice(0, 300) : sample.slice(0, 300)
|
|
11130
11423
|
};
|
|
11131
11424
|
}
|
|
11132
11425
|
function normalizeRoutePath2(path7) {
|
|
@@ -11647,7 +11940,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
11647
11940
|
};
|
|
11648
11941
|
}
|
|
11649
11942
|
if (check.type === "route_inventory") {
|
|
11650
|
-
const inventories = viewports.map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory })).filter((item) =>
|
|
11943
|
+
const inventories = viewports.map((viewport) => ({ viewport: viewport.name, inventory: viewport.route_inventory })).filter((item) => isRecord2(item.inventory));
|
|
11651
11944
|
if (!inventories.length) {
|
|
11652
11945
|
return {
|
|
11653
11946
|
type: check.type,
|
|
@@ -12093,6 +12386,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
|
|
|
12093
12386
|
const status = profileStatusFromEvidence(profile, evidence, checks);
|
|
12094
12387
|
const firstViewport = evidence?.viewports?.[0];
|
|
12095
12388
|
const screenshots = profileScreenshotLabels(evidence?.viewports);
|
|
12389
|
+
const canonicalScreenshots = profileFinalScreenshotLabels(evidence?.viewports);
|
|
12390
|
+
const setupScreenshots = profileAllSetupScreenshotLabels(evidence?.viewports);
|
|
12096
12391
|
const result = {
|
|
12097
12392
|
version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
|
|
12098
12393
|
profile_name: profile.name,
|
|
@@ -12102,6 +12397,8 @@ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
|
|
|
12102
12397
|
route: routeForViewport(firstViewport, evidence?.target_url),
|
|
12103
12398
|
artifacts: {
|
|
12104
12399
|
screenshots,
|
|
12400
|
+
canonical_screenshots: canonicalScreenshots,
|
|
12401
|
+
setup_screenshots: setupScreenshots,
|
|
12105
12402
|
console: "console.json",
|
|
12106
12403
|
proof_json: "proof.json",
|
|
12107
12404
|
dom_summary: "dom-summary.json",
|
|
@@ -13420,6 +13717,12 @@ function profileScreenshotLabels(viewports) {
|
|
|
13420
13717
|
}
|
|
13421
13718
|
return labels;
|
|
13422
13719
|
}
|
|
13720
|
+
function profileFinalScreenshotLabels(viewports) {
|
|
13721
|
+
return (viewports || []).map((viewport) => viewport && viewport.screenshot_label).filter(Boolean);
|
|
13722
|
+
}
|
|
13723
|
+
function profileAllSetupScreenshotLabels(viewports) {
|
|
13724
|
+
return (viewports || []).flatMap((viewport) => profileSetupScreenshotLabels(viewport && viewport.setup_action_results || []));
|
|
13725
|
+
}
|
|
13423
13726
|
function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewport, finalScreenshotFullPage) {
|
|
13424
13727
|
const normalizedFinalScreenshotFullPage = finalScreenshotFullPage === undefined
|
|
13425
13728
|
? undefined
|
|
@@ -14288,6 +14591,8 @@ function assessProfile(profile, evidence) {
|
|
|
14288
14591
|
else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
|
|
14289
14592
|
else if (checks.some((check) => check.status === "failed")) status = "product_regression";
|
|
14290
14593
|
const screenshotLabels = profileScreenshotLabels(viewports);
|
|
14594
|
+
const canonicalScreenshotLabels = profileFinalScreenshotLabels(viewports);
|
|
14595
|
+
const setupScreenshotLabels = profileAllSetupScreenshotLabels(viewports);
|
|
14291
14596
|
const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
|
|
14292
14597
|
const passedChecks = checks.filter((check) => check.status === "passed").length;
|
|
14293
14598
|
const failedChecks = checks.filter((check) => check.status === "failed").length;
|
|
@@ -14304,7 +14609,14 @@ function assessProfile(profile, evidence) {
|
|
|
14304
14609
|
status,
|
|
14305
14610
|
baseline_policy: profile.baseline_policy || "invariant_only",
|
|
14306
14611
|
route,
|
|
14307
|
-
artifacts: {
|
|
14612
|
+
artifacts: {
|
|
14613
|
+
screenshots: screenshotLabels,
|
|
14614
|
+
canonical_screenshots: canonicalScreenshotLabels,
|
|
14615
|
+
setup_screenshots: setupScreenshotLabels,
|
|
14616
|
+
console: "console.json",
|
|
14617
|
+
proof_json: "proof.json",
|
|
14618
|
+
dom_summary: "dom-summary.json"
|
|
14619
|
+
},
|
|
14308
14620
|
checks,
|
|
14309
14621
|
summary,
|
|
14310
14622
|
captured_at: evidence.captured_at,
|
|
@@ -17975,7 +18287,7 @@ function collectRiddleProfileArtifactRefs(input) {
|
|
|
17975
18287
|
const indexes = /* @__PURE__ */ new Map();
|
|
17976
18288
|
const priorities = /* @__PURE__ */ new Map();
|
|
17977
18289
|
function add(item, source) {
|
|
17978
|
-
if (!
|
|
18290
|
+
if (!isRecord2(item)) return;
|
|
17979
18291
|
const url = stringValue3(item.url);
|
|
17980
18292
|
const path7 = stringValue3(item.path);
|
|
17981
18293
|
const rawName = stringValue3(item.name) || stringValue3(item.filename) || artifactNameFromPath(url || path7) || "";
|
|
@@ -18008,7 +18320,7 @@ function collectRiddleProfileArtifactRefs(input) {
|
|
|
18008
18320
|
for (const item of value) add(item, source);
|
|
18009
18321
|
return;
|
|
18010
18322
|
}
|
|
18011
|
-
if (!
|
|
18323
|
+
if (!isRecord2(value)) return;
|
|
18012
18324
|
for (const key of ["artifacts", "outputs", "screenshots", "files"]) {
|
|
18013
18325
|
if (Array.isArray(value[key])) visit(value[key], key);
|
|
18014
18326
|
}
|
|
@@ -18042,1113 +18354,1587 @@ var PROFILE_SINGLETON_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
|
18042
18354
|
"dom-summary.json"
|
|
18043
18355
|
]);
|
|
18044
18356
|
function extractRiddleProofProfileResult(input) {
|
|
18045
|
-
if (!
|
|
18357
|
+
if (!isRecord2(input)) return void 0;
|
|
18046
18358
|
if (input.version === RIDDLE_PROOF_PROFILE_RESULT_VERSION) return input;
|
|
18047
18359
|
const candidates = [
|
|
18048
18360
|
input.result,
|
|
18049
18361
|
input.return_value,
|
|
18050
18362
|
input.value,
|
|
18051
18363
|
input.profile_result,
|
|
18052
|
-
|
|
18053
|
-
|
|
18364
|
+
isRecord2(input["proof.json"]) ? input["proof.json"] : void 0,
|
|
18365
|
+
isRecord2(input._proof_json) ? input._proof_json : void 0
|
|
18054
18366
|
];
|
|
18055
18367
|
for (const candidate of candidates) {
|
|
18056
18368
|
const result = extractRiddleProofProfileResult(candidate);
|
|
18057
18369
|
if (result) return result;
|
|
18058
18370
|
}
|
|
18059
|
-
if (
|
|
18371
|
+
if (isRecord2(input._artifact_json)) {
|
|
18060
18372
|
return extractRiddleProofProfileResult(input._artifact_json["proof.json"]);
|
|
18061
18373
|
}
|
|
18062
18374
|
return void 0;
|
|
18063
18375
|
}
|
|
18064
18376
|
|
|
18065
|
-
// src/
|
|
18066
|
-
var
|
|
18067
|
-
|
|
18068
|
-
|
|
18377
|
+
// src/change-proof.ts
|
|
18378
|
+
var RIDDLE_PROOF_CHANGE_RESULT_VERSION = "riddle-proof.change-result.v1";
|
|
18379
|
+
var RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION = "riddle-proof.change-receipt.v1";
|
|
18380
|
+
var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.v2";
|
|
18381
|
+
var RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION = "riddle-proof.handoff-receipt.v1";
|
|
18382
|
+
var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
|
|
18383
|
+
var DEFAULT_AFTER_STATUSES = ["passed"];
|
|
18384
|
+
var DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE = ["product_regression"];
|
|
18385
|
+
var DEFAULT_PROFILE_STATUS_TRANSITION_AFTER = ["passed"];
|
|
18386
|
+
var DEFAULT_CHECK_STATUS_TRANSITION_BEFORE = ["failed"];
|
|
18387
|
+
var DEFAULT_CHECK_STATUS_TRANSITION_AFTER = ["passed"];
|
|
18388
|
+
function listValue(value, fallback) {
|
|
18389
|
+
if (Array.isArray(value)) return value;
|
|
18390
|
+
if (value === void 0) return fallback;
|
|
18391
|
+
return [value];
|
|
18069
18392
|
}
|
|
18070
|
-
function
|
|
18071
|
-
return
|
|
18393
|
+
function includesValue(allowed, observed) {
|
|
18394
|
+
return observed !== void 0 && allowed.includes(observed);
|
|
18072
18395
|
}
|
|
18073
|
-
function
|
|
18074
|
-
|
|
18396
|
+
function groupResult(side, contract, result) {
|
|
18397
|
+
const fallback = side === "before" ? DEFAULT_BEFORE_STATUSES : DEFAULT_AFTER_STATUSES;
|
|
18398
|
+
const requiredStatus = listValue(contract?.required_status, fallback);
|
|
18399
|
+
const label = contract?.label || side;
|
|
18400
|
+
if (!result) {
|
|
18401
|
+
return {
|
|
18402
|
+
side,
|
|
18403
|
+
label,
|
|
18404
|
+
ok: false,
|
|
18405
|
+
status: "missing",
|
|
18406
|
+
required_status: requiredStatus,
|
|
18407
|
+
message: `${label} profile result is missing.`
|
|
18408
|
+
};
|
|
18409
|
+
}
|
|
18410
|
+
const ok = includesValue(requiredStatus, result.status);
|
|
18411
|
+
return {
|
|
18412
|
+
side,
|
|
18413
|
+
label,
|
|
18414
|
+
ok,
|
|
18415
|
+
profile_name: result.profile_name,
|
|
18416
|
+
status: result.status,
|
|
18417
|
+
required_status: requiredStatus,
|
|
18418
|
+
summary: result.summary,
|
|
18419
|
+
message: ok ? void 0 : `${label} profile status ${result.status} did not match required status ${requiredStatus.join(", ")}.`
|
|
18420
|
+
};
|
|
18075
18421
|
}
|
|
18076
|
-
function
|
|
18077
|
-
return
|
|
18422
|
+
function findCheck(result, delta) {
|
|
18423
|
+
return result.checks.find((check) => {
|
|
18424
|
+
if (delta.check_label && check.label !== delta.check_label) return false;
|
|
18425
|
+
if (delta.check_type && check.type !== delta.check_type) return false;
|
|
18426
|
+
return Boolean(delta.check_label || delta.check_type);
|
|
18427
|
+
});
|
|
18078
18428
|
}
|
|
18079
|
-
function
|
|
18080
|
-
|
|
18429
|
+
function profileStatusTransitionResult(delta, before, after) {
|
|
18430
|
+
const label = delta.label || "profile-status-transition";
|
|
18431
|
+
if (!before || !after) {
|
|
18432
|
+
return {
|
|
18433
|
+
type: delta.type,
|
|
18434
|
+
label,
|
|
18435
|
+
status: "proof_insufficient",
|
|
18436
|
+
before_observed: before?.status,
|
|
18437
|
+
after_observed: after?.status,
|
|
18438
|
+
message: `${label} needs both before and after profile results.`
|
|
18439
|
+
};
|
|
18440
|
+
}
|
|
18441
|
+
const beforeAllowed = listValue(delta.before_status, DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE);
|
|
18442
|
+
const afterAllowed = listValue(delta.after_status, DEFAULT_PROFILE_STATUS_TRANSITION_AFTER);
|
|
18443
|
+
const passed = includesValue(beforeAllowed, before.status) && includesValue(afterAllowed, after.status);
|
|
18444
|
+
return {
|
|
18445
|
+
type: delta.type,
|
|
18446
|
+
label,
|
|
18447
|
+
status: passed ? "passed" : "failed",
|
|
18448
|
+
before_observed: before.status,
|
|
18449
|
+
after_observed: after.status,
|
|
18450
|
+
message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${before.status} -> ${after.status}.`
|
|
18451
|
+
};
|
|
18081
18452
|
}
|
|
18082
|
-
function
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18453
|
+
function checkStatusTransitionResult(delta, before, after) {
|
|
18454
|
+
const label = delta.label || delta.check_label || delta.check_type || "check-status-transition";
|
|
18455
|
+
if (!delta.check_label && !delta.check_type) {
|
|
18456
|
+
return {
|
|
18457
|
+
type: delta.type,
|
|
18458
|
+
label,
|
|
18459
|
+
status: "configuration_error",
|
|
18460
|
+
message: `${label} needs check_label or check_type.`
|
|
18461
|
+
};
|
|
18086
18462
|
}
|
|
18087
|
-
|
|
18463
|
+
if (!before || !after) {
|
|
18464
|
+
return {
|
|
18465
|
+
type: delta.type,
|
|
18466
|
+
label,
|
|
18467
|
+
status: "proof_insufficient",
|
|
18468
|
+
before_observed: before?.status,
|
|
18469
|
+
after_observed: after?.status,
|
|
18470
|
+
message: `${label} needs both before and after profile results.`
|
|
18471
|
+
};
|
|
18472
|
+
}
|
|
18473
|
+
const beforeCheck = findCheck(before, delta);
|
|
18474
|
+
const afterCheck = findCheck(after, delta);
|
|
18475
|
+
if (!beforeCheck || !afterCheck) {
|
|
18476
|
+
return {
|
|
18477
|
+
type: delta.type,
|
|
18478
|
+
label,
|
|
18479
|
+
status: "proof_insufficient",
|
|
18480
|
+
before_observed: beforeCheck?.status,
|
|
18481
|
+
after_observed: afterCheck?.status,
|
|
18482
|
+
message: `${label} was not present in ${!beforeCheck && !afterCheck ? "before or after" : !beforeCheck ? "before" : "after"} profile checks.`
|
|
18483
|
+
};
|
|
18484
|
+
}
|
|
18485
|
+
const beforeAllowed = listValue(delta.before_status, DEFAULT_CHECK_STATUS_TRANSITION_BEFORE);
|
|
18486
|
+
const afterAllowed = listValue(delta.after_status, DEFAULT_CHECK_STATUS_TRANSITION_AFTER);
|
|
18487
|
+
const passed = includesValue(beforeAllowed, beforeCheck.status) && includesValue(afterAllowed, afterCheck.status);
|
|
18488
|
+
return {
|
|
18489
|
+
type: delta.type,
|
|
18490
|
+
label,
|
|
18491
|
+
status: passed ? "passed" : "failed",
|
|
18492
|
+
before_observed: beforeCheck.status,
|
|
18493
|
+
after_observed: afterCheck.status,
|
|
18494
|
+
message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${beforeCheck.status} -> ${afterCheck.status}.`
|
|
18495
|
+
};
|
|
18088
18496
|
}
|
|
18089
|
-
function
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
return
|
|
18497
|
+
function assessDelta(delta, before, after) {
|
|
18498
|
+
if (delta.type === "profile_status_transition") {
|
|
18499
|
+
return profileStatusTransitionResult(delta, before, after);
|
|
18500
|
+
}
|
|
18501
|
+
return checkStatusTransitionResult(delta, before, after);
|
|
18094
18502
|
}
|
|
18095
|
-
function
|
|
18096
|
-
|
|
18097
|
-
if (raw) return raw;
|
|
18098
|
-
return fallback;
|
|
18503
|
+
function profileResultFromObservation(observation) {
|
|
18504
|
+
return observation?.proof?.result;
|
|
18099
18505
|
}
|
|
18100
|
-
function
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
if (!url || seen.has(url)) continue;
|
|
18109
|
-
seen.add(url);
|
|
18110
|
-
const name = artifactDisplayName(artifact.name, `artifact-${index + 1}`);
|
|
18111
|
-
artifacts.push({
|
|
18112
|
-
name,
|
|
18113
|
-
url,
|
|
18114
|
-
kind: artifactKind(name, url),
|
|
18115
|
-
size_bytes: numberValue3(artifact.size)
|
|
18116
|
-
});
|
|
18506
|
+
function previewReceiptCoversTarget(previewUrl, targetUrl) {
|
|
18507
|
+
try {
|
|
18508
|
+
const preview = new URL(previewUrl);
|
|
18509
|
+
const target = new URL(targetUrl);
|
|
18510
|
+
const previewPath = preview.pathname.replace(/\/+$/u, "");
|
|
18511
|
+
return preview.origin === target.origin && (target.pathname === previewPath || target.pathname.startsWith(`${previewPath}/`));
|
|
18512
|
+
} catch {
|
|
18513
|
+
return false;
|
|
18117
18514
|
}
|
|
18118
|
-
return artifacts;
|
|
18119
18515
|
}
|
|
18120
|
-
function
|
|
18121
|
-
const
|
|
18122
|
-
const
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
size_bytes: numberValue3(artifact.size_bytes) ?? numberValue3(artifact.size)
|
|
18136
|
-
});
|
|
18516
|
+
function sourceBindingResult(side, requirement, observation, expectedRevision, evaluatedAt) {
|
|
18517
|
+
const expected = expectedRevision || requirement?.expected_git_revision;
|
|
18518
|
+
const required = Boolean(
|
|
18519
|
+
requirement?.preview_receipt_required || requirement?.require_clean_source || requirement?.require_content_digest || requirement?.expected_git_revision || expected
|
|
18520
|
+
);
|
|
18521
|
+
if (!required) return { side, required: false, ok: true, status: "not_required" };
|
|
18522
|
+
if (!observation) {
|
|
18523
|
+
return {
|
|
18524
|
+
side,
|
|
18525
|
+
required: true,
|
|
18526
|
+
ok: false,
|
|
18527
|
+
status: "missing",
|
|
18528
|
+
expected_git_revision: expected,
|
|
18529
|
+
message: `${side} source binding requires an Observation receipt.`
|
|
18530
|
+
};
|
|
18137
18531
|
}
|
|
18138
|
-
|
|
18532
|
+
const preview = observation.target.preview;
|
|
18533
|
+
if (requirement?.preview_receipt_required && !preview) {
|
|
18534
|
+
return {
|
|
18535
|
+
side,
|
|
18536
|
+
required: true,
|
|
18537
|
+
ok: false,
|
|
18538
|
+
status: "missing",
|
|
18539
|
+
expected_git_revision: expected,
|
|
18540
|
+
message: `${side} source binding requires a Preview receipt.`
|
|
18541
|
+
};
|
|
18542
|
+
}
|
|
18543
|
+
if (preview && !previewReceiptCoversTarget(preview.url, observation.target.url)) {
|
|
18544
|
+
return {
|
|
18545
|
+
side,
|
|
18546
|
+
required: true,
|
|
18547
|
+
ok: false,
|
|
18548
|
+
status: "mismatched",
|
|
18549
|
+
expected_git_revision: expected,
|
|
18550
|
+
observed_git_revision: preview.source.git_revision || observation.source.git_revision,
|
|
18551
|
+
content_digest: preview.content_digest,
|
|
18552
|
+
preview_id: preview.preview_id,
|
|
18553
|
+
preview_url: preview.url,
|
|
18554
|
+
target_url: observation.target.url,
|
|
18555
|
+
message: `${side} observation target is not contained by Preview ${preview.preview_id}.`
|
|
18556
|
+
};
|
|
18557
|
+
}
|
|
18558
|
+
const observed = preview?.source.git_revision || observation.source.git_revision;
|
|
18559
|
+
const digestRequired = requirement?.require_content_digest === true || requirement?.preview_receipt_required === true;
|
|
18560
|
+
if (digestRequired && !preview?.content_digest) {
|
|
18561
|
+
return {
|
|
18562
|
+
side,
|
|
18563
|
+
required: true,
|
|
18564
|
+
ok: false,
|
|
18565
|
+
status: "missing",
|
|
18566
|
+
expected_git_revision: expected,
|
|
18567
|
+
observed_git_revision: observed,
|
|
18568
|
+
preview_id: preview?.preview_id,
|
|
18569
|
+
preview_url: preview?.url,
|
|
18570
|
+
target_url: observation.target.url,
|
|
18571
|
+
message: `${side} Preview receipt is missing a content digest.`
|
|
18572
|
+
};
|
|
18573
|
+
}
|
|
18574
|
+
if (expected && !observed) {
|
|
18575
|
+
return {
|
|
18576
|
+
side,
|
|
18577
|
+
required: true,
|
|
18578
|
+
ok: false,
|
|
18579
|
+
status: "missing",
|
|
18580
|
+
expected_git_revision: expected,
|
|
18581
|
+
content_digest: preview?.content_digest,
|
|
18582
|
+
preview_id: preview?.preview_id,
|
|
18583
|
+
preview_url: preview?.url,
|
|
18584
|
+
target_url: observation.target.url,
|
|
18585
|
+
message: `${side} evidence is missing its source Git revision.`
|
|
18586
|
+
};
|
|
18587
|
+
}
|
|
18588
|
+
if (expected && observed !== expected) {
|
|
18589
|
+
return {
|
|
18590
|
+
side,
|
|
18591
|
+
required: true,
|
|
18592
|
+
ok: false,
|
|
18593
|
+
status: "mismatched",
|
|
18594
|
+
expected_git_revision: expected,
|
|
18595
|
+
observed_git_revision: observed,
|
|
18596
|
+
content_digest: preview?.content_digest,
|
|
18597
|
+
preview_id: preview?.preview_id,
|
|
18598
|
+
preview_url: preview?.url,
|
|
18599
|
+
target_url: observation.target.url,
|
|
18600
|
+
message: `${side} evidence came from Git revision ${observed}, not expected revision ${expected}.`
|
|
18601
|
+
};
|
|
18602
|
+
}
|
|
18603
|
+
const cleanRequired = requirement?.require_clean_source === true || requirement?.preview_receipt_required === true;
|
|
18604
|
+
const dirty = preview?.source.dirty ?? observation.source.dirty;
|
|
18605
|
+
if (cleanRequired && dirty !== false) {
|
|
18606
|
+
return {
|
|
18607
|
+
side,
|
|
18608
|
+
required: true,
|
|
18609
|
+
ok: false,
|
|
18610
|
+
status: dirty === true ? "mismatched" : "missing",
|
|
18611
|
+
expected_git_revision: expected,
|
|
18612
|
+
observed_git_revision: observed,
|
|
18613
|
+
content_digest: preview?.content_digest,
|
|
18614
|
+
preview_id: preview?.preview_id,
|
|
18615
|
+
preview_url: preview?.url,
|
|
18616
|
+
target_url: observation.target.url,
|
|
18617
|
+
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.`
|
|
18618
|
+
};
|
|
18619
|
+
}
|
|
18620
|
+
const evaluatedMs = Date.parse(evaluatedAt);
|
|
18621
|
+
const expiresMs = preview?.expires_at ? Date.parse(preview.expires_at) : Number.NaN;
|
|
18622
|
+
if (preview && Number.isFinite(evaluatedMs) && Number.isFinite(expiresMs) && expiresMs <= evaluatedMs) {
|
|
18623
|
+
return {
|
|
18624
|
+
side,
|
|
18625
|
+
required: true,
|
|
18626
|
+
ok: false,
|
|
18627
|
+
status: "stale",
|
|
18628
|
+
expected_git_revision: expected,
|
|
18629
|
+
observed_git_revision: observed,
|
|
18630
|
+
content_digest: preview.content_digest,
|
|
18631
|
+
preview_id: preview.preview_id,
|
|
18632
|
+
preview_url: preview.url,
|
|
18633
|
+
target_url: observation.target.url,
|
|
18634
|
+
expires_at: preview.expires_at,
|
|
18635
|
+
message: `${side} Preview receipt expired at ${preview.expires_at}.`
|
|
18636
|
+
};
|
|
18637
|
+
}
|
|
18638
|
+
return {
|
|
18639
|
+
side,
|
|
18640
|
+
required: true,
|
|
18641
|
+
ok: true,
|
|
18642
|
+
status: "matched",
|
|
18643
|
+
expected_git_revision: expected,
|
|
18644
|
+
observed_git_revision: observed,
|
|
18645
|
+
content_digest: preview?.content_digest,
|
|
18646
|
+
preview_id: preview?.preview_id,
|
|
18647
|
+
preview_url: preview?.url,
|
|
18648
|
+
target_url: observation.target.url,
|
|
18649
|
+
expires_at: preview?.expires_at
|
|
18650
|
+
};
|
|
18139
18651
|
}
|
|
18140
|
-
function
|
|
18141
|
-
const
|
|
18142
|
-
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
|
|
18652
|
+
function collapsedChangeStatus(groups, deltas, sourceBindings) {
|
|
18653
|
+
const groupResults = [groups.before, groups.after];
|
|
18654
|
+
if (groupResults.some((group) => group.status === "environment_blocked")) return "environment_blocked";
|
|
18655
|
+
if (groupResults.some((group) => group.status === "configuration_error")) return "configuration_error";
|
|
18656
|
+
if (deltas.some((delta) => delta.status === "configuration_error")) return "configuration_error";
|
|
18657
|
+
if (groupResults.some((group) => group.status === "needs_human_review")) return "needs_human_review";
|
|
18658
|
+
if (groupResults.some((group) => group.status === "missing" || group.status === "proof_insufficient")) return "proof_insufficient";
|
|
18659
|
+
if ([sourceBindings.before, sourceBindings.after].some((binding) => binding.required && !binding.ok)) return "proof_insufficient";
|
|
18660
|
+
if (!deltas.length || deltas.some((delta) => delta.status === "proof_insufficient")) return "proof_insufficient";
|
|
18661
|
+
if (groupResults.some((group) => !group.ok)) return "product_regression";
|
|
18662
|
+
if (deltas.some((delta) => delta.status === "failed")) return "product_regression";
|
|
18663
|
+
return "passed";
|
|
18664
|
+
}
|
|
18665
|
+
function changeSummary(name, status, deltas) {
|
|
18666
|
+
if (status === "passed") return `${name} passed ${deltas.length} change delta(s).`;
|
|
18667
|
+
if (status === "environment_blocked") return `${name} could not compare reliable evidence because an environment was blocked.`;
|
|
18668
|
+
if (status === "configuration_error") return `${name} has an invalid change proof contract.`;
|
|
18669
|
+
if (status === "needs_human_review") return `${name} needs human review before the change proof can pass.`;
|
|
18670
|
+
if (status === "proof_insufficient") return `${name} did not produce enough before/after evidence for a change proof.`;
|
|
18671
|
+
return `${name} failed ${deltas.filter((delta) => delta.status === "failed").length} change delta(s).`;
|
|
18672
|
+
}
|
|
18673
|
+
function assessRiddleProofChange(contract, input) {
|
|
18674
|
+
const beforeResult = input.before_result || profileResultFromObservation(input.before_observation);
|
|
18675
|
+
const afterResult = input.after_result || profileResultFromObservation(input.after_observation);
|
|
18676
|
+
const groups = {
|
|
18677
|
+
before: groupResult("before", contract.before, beforeResult),
|
|
18678
|
+
after: groupResult("after", contract.after, afterResult)
|
|
18679
|
+
};
|
|
18680
|
+
const deltas = (contract.deltas || []).map((delta) => assessDelta(delta, beforeResult, afterResult));
|
|
18681
|
+
const evaluatedAt = input.evaluated_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
18682
|
+
const sourceBindings = {
|
|
18683
|
+
before: sourceBindingResult(
|
|
18684
|
+
"before",
|
|
18685
|
+
contract.source_binding?.before,
|
|
18686
|
+
input.before_observation,
|
|
18687
|
+
input.expected_source_revisions?.before,
|
|
18688
|
+
evaluatedAt
|
|
18689
|
+
),
|
|
18690
|
+
after: sourceBindingResult(
|
|
18691
|
+
"after",
|
|
18692
|
+
contract.source_binding?.after,
|
|
18693
|
+
input.after_observation,
|
|
18694
|
+
input.expected_source_revisions?.after,
|
|
18695
|
+
evaluatedAt
|
|
18696
|
+
)
|
|
18697
|
+
};
|
|
18698
|
+
const status = collapsedChangeStatus(groups, deltas, sourceBindings);
|
|
18699
|
+
return {
|
|
18700
|
+
version: RIDDLE_PROOF_CHANGE_RESULT_VERSION,
|
|
18701
|
+
contract_name: contract.name,
|
|
18702
|
+
status,
|
|
18703
|
+
groups,
|
|
18704
|
+
deltas,
|
|
18705
|
+
source_bindings: sourceBindings,
|
|
18706
|
+
summary: changeSummary(contract.name, status, deltas),
|
|
18707
|
+
metadata: contract.metadata
|
|
18162
18708
|
};
|
|
18163
|
-
collectSide("before", asRecord2(result.before));
|
|
18164
|
-
collectSide("after", asRecord2(result.after));
|
|
18165
|
-
return artifacts;
|
|
18166
18709
|
}
|
|
18167
|
-
function
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
18710
|
+
function changeReceiptVerdict(status) {
|
|
18711
|
+
if (status === "passed") return "mergeable";
|
|
18712
|
+
if (status === "environment_blocked") return "environment_blocked";
|
|
18713
|
+
if (status === "configuration_error") return "configuration_error";
|
|
18714
|
+
if (status === "needs_human_review") return "needs_human_review";
|
|
18715
|
+
if (status === "proof_insufficient") return "proof_insufficient";
|
|
18716
|
+
return "not_mergeable";
|
|
18717
|
+
}
|
|
18718
|
+
function profileCheckCounts2(result) {
|
|
18719
|
+
const counts = {
|
|
18720
|
+
total: result.checks.length,
|
|
18721
|
+
passed: 0,
|
|
18722
|
+
failed: 0,
|
|
18723
|
+
skipped: 0,
|
|
18724
|
+
needs_human_review: 0
|
|
18725
|
+
};
|
|
18726
|
+
for (const check of result.checks) {
|
|
18727
|
+
if (check.status === "passed") counts.passed += 1;
|
|
18728
|
+
if (check.status === "failed") counts.failed += 1;
|
|
18729
|
+
if (check.status === "skipped") counts.skipped += 1;
|
|
18730
|
+
if (check.status === "needs_human_review") counts.needs_human_review += 1;
|
|
18174
18731
|
}
|
|
18175
|
-
return
|
|
18732
|
+
return counts;
|
|
18176
18733
|
}
|
|
18177
|
-
function
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18734
|
+
function artifactIsScreenshot(artifact) {
|
|
18735
|
+
if (artifact.kind !== "image") return false;
|
|
18736
|
+
return /screenshot|\.png|\.jpe?g|\.webp|\.gif|\.avif|\.svg/i.test([artifact.name, artifact.url, artifact.path].filter(Boolean).join(" "));
|
|
18737
|
+
}
|
|
18738
|
+
function metadataStringList(metadata, key) {
|
|
18739
|
+
const value = metadata?.[key];
|
|
18740
|
+
if (!Array.isArray(value)) return [];
|
|
18741
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
18742
|
+
}
|
|
18743
|
+
function changeRecommendation(verdict) {
|
|
18744
|
+
if (verdict === "mergeable") {
|
|
18745
|
+
return {
|
|
18746
|
+
merge_recommended: true,
|
|
18747
|
+
verdict,
|
|
18748
|
+
label: "Merge recommended",
|
|
18749
|
+
reason: "The declared before/after delta contract passed with sufficient bound evidence."
|
|
18750
|
+
};
|
|
18190
18751
|
}
|
|
18191
|
-
|
|
18752
|
+
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.";
|
|
18753
|
+
return { merge_recommended: false, verdict, label: "Merge not recommended", reason };
|
|
18192
18754
|
}
|
|
18193
|
-
function
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
}
|
|
18203
|
-
return { passed: passed2, failed: failed2 };
|
|
18755
|
+
function noShippingAuthorization() {
|
|
18756
|
+
return { status: "not_granted", authorized: false, source: "none" };
|
|
18757
|
+
}
|
|
18758
|
+
function recommendationMatches(actual, expected) {
|
|
18759
|
+
return actual?.merge_recommended === expected.merge_recommended && actual.verdict === expected.verdict && actual.label === expected.label && actual.reason === expected.reason;
|
|
18760
|
+
}
|
|
18761
|
+
function assertShippingAuthorizationConsistent(authorization, receiptName) {
|
|
18762
|
+
if (!authorization) throw new Error(`${receiptName} shipping authorization is required.`);
|
|
18763
|
+
if (authorization.status !== "not_granted" && authorization.status !== "granted") {
|
|
18764
|
+
throw new Error(`${receiptName} shipping authorization status is invalid.`);
|
|
18204
18765
|
}
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18766
|
+
if (authorization.source !== "none" && authorization.source !== "human" && authorization.source !== "automation") {
|
|
18767
|
+
throw new Error(`${receiptName} shipping authorization source is invalid.`);
|
|
18768
|
+
}
|
|
18769
|
+
const granted = authorization.status === "granted";
|
|
18770
|
+
if (authorization.authorized !== granted) {
|
|
18771
|
+
throw new Error(`${receiptName} shipping authorization status and authorized flag must agree.`);
|
|
18772
|
+
}
|
|
18773
|
+
if (authorization.source === "none" === granted) {
|
|
18774
|
+
throw new Error(`${receiptName} shipping authorization source must identify an authorizer only when granted.`);
|
|
18210
18775
|
}
|
|
18211
|
-
return void 0;
|
|
18212
18776
|
}
|
|
18213
|
-
function
|
|
18214
|
-
if (!
|
|
18215
|
-
|
|
18216
|
-
return [{
|
|
18217
|
-
route: firstStringValue2(route.observed, route.expected_path, route.requested, result.profile_name) || "profile",
|
|
18218
|
-
passed: counts.passed,
|
|
18219
|
-
failed: counts.failed
|
|
18220
|
-
}];
|
|
18777
|
+
function observationArtifactMatches(actual, expected) {
|
|
18778
|
+
if (!actual || !expected) return actual === expected;
|
|
18779
|
+
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;
|
|
18221
18780
|
}
|
|
18222
|
-
function
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
const status = stringValue4(asRecord2(item).status);
|
|
18229
|
-
if (status === "passed") passed += 1;
|
|
18230
|
-
if (status === "failed" || status === "proof_insufficient" || status === "configuration_error") failed += 1;
|
|
18231
|
-
}
|
|
18232
|
-
return { passed, failed };
|
|
18781
|
+
function observationExecutor(result) {
|
|
18782
|
+
return result.runner === "local-playwright" ? { kind: "local_playwright", runner: result.runner } : {
|
|
18783
|
+
kind: "riddle_hosted",
|
|
18784
|
+
runner: result.runner,
|
|
18785
|
+
job_id: result.riddle?.job_id
|
|
18786
|
+
};
|
|
18233
18787
|
}
|
|
18234
|
-
function
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
}
|
|
18245
|
-
}
|
|
18246
|
-
return pages;
|
|
18788
|
+
function observationForReceiptSide(side, result, source, sourceIdentity) {
|
|
18789
|
+
if (!result) throw new Error(`${side}_result or ${side}_observation is required.`);
|
|
18790
|
+
if (!source) throw new Error(`${side}_source or ${side}_observation is required.`);
|
|
18791
|
+
return createRiddleProofObservationReceipt({
|
|
18792
|
+
comparison_role: side,
|
|
18793
|
+
executor: observationExecutor(result),
|
|
18794
|
+
target: { kind: "url", url: source },
|
|
18795
|
+
source: sourceIdentity,
|
|
18796
|
+
profile_result: result,
|
|
18797
|
+
execution: result.riddle?.execution,
|
|
18798
|
+
publication: source.startsWith("http") ? { kind: result.runner === "local-playwright" ? "other" : "riddle_cdn", url: source } : { kind: "local", path: source }
|
|
18799
|
+
});
|
|
18247
18800
|
}
|
|
18248
|
-
function
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
const
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
|
|
18801
|
+
function createRiddleProofChangeReceipt(input) {
|
|
18802
|
+
const before = input.before_observation ? parseRiddleProofObservationReceipt(input.before_observation) : observationForReceiptSide("before", input.before_result, input.before_source, input.before_source_identity);
|
|
18803
|
+
const after = input.after_observation ? parseRiddleProofObservationReceipt(input.after_observation) : observationForReceiptSide("after", input.after_result, input.after_source, input.after_source_identity);
|
|
18804
|
+
const verdict = changeReceiptVerdict(input.result.status);
|
|
18805
|
+
const shippingAuthorization = input.shipping_authorization || noShippingAuthorization();
|
|
18806
|
+
assertShippingAuthorizationConsistent(shippingAuthorization, "Change receipt");
|
|
18807
|
+
return {
|
|
18808
|
+
version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
|
|
18809
|
+
contract_name: input.result.contract_name,
|
|
18810
|
+
profile_name: input.profile_name,
|
|
18811
|
+
status: input.result.status,
|
|
18812
|
+
verdict,
|
|
18813
|
+
summary: input.result.summary,
|
|
18814
|
+
before,
|
|
18815
|
+
after,
|
|
18816
|
+
groups: input.result.groups,
|
|
18817
|
+
source_bindings: input.result.source_bindings,
|
|
18818
|
+
deltas: input.result.deltas.map((delta) => ({
|
|
18819
|
+
type: delta.type,
|
|
18820
|
+
label: delta.label,
|
|
18821
|
+
status: delta.status,
|
|
18822
|
+
before_observed: delta.before_observed,
|
|
18823
|
+
after_observed: delta.after_observed,
|
|
18824
|
+
message: delta.message
|
|
18825
|
+
})),
|
|
18826
|
+
recommendation: changeRecommendation(verdict),
|
|
18827
|
+
shipping_authorization: shippingAuthorization,
|
|
18828
|
+
proves: metadataStringList(input.contract.metadata, "required_receipts"),
|
|
18829
|
+
does_not_prove: metadataStringList(input.contract.metadata, "does_not_prove"),
|
|
18830
|
+
metadata: input.result.metadata
|
|
18269
18831
|
};
|
|
18270
|
-
visit(value);
|
|
18271
|
-
return { passed, failed };
|
|
18272
|
-
}
|
|
18273
|
-
function selectPrimaryImage(artifacts) {
|
|
18274
|
-
const images = artifacts.filter((artifact) => artifact.kind === "image");
|
|
18275
|
-
return images.find((artifact) => /after|proof|screenshot/i.test(artifact.name)) || images[0];
|
|
18276
18832
|
}
|
|
18277
|
-
function
|
|
18278
|
-
|
|
18279
|
-
const record = asRecord2(value);
|
|
18280
|
-
if (Object.keys(record).length) return record;
|
|
18281
|
-
}
|
|
18282
|
-
return void 0;
|
|
18833
|
+
function isRecord3(value) {
|
|
18834
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18283
18835
|
}
|
|
18284
|
-
function
|
|
18285
|
-
const
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18836
|
+
function migratedObservationFromLegacySide(side, legacy) {
|
|
18837
|
+
const legacyArtifacts = legacy.artifacts.map((artifact) => ({
|
|
18838
|
+
name: artifact.name,
|
|
18839
|
+
url: artifact.url,
|
|
18840
|
+
path: artifact.path,
|
|
18841
|
+
source: artifact.source,
|
|
18842
|
+
kind: artifact.kind,
|
|
18843
|
+
role: legacy.screenshots[0] && artifactTarget(artifact) === artifactTarget(legacy.screenshots[0]) ? "canonical_screenshot" : artifact.kind === "image" ? "diagnostic" : artifact.kind === "data" ? "data" : "artifact"
|
|
18844
|
+
}));
|
|
18845
|
+
const canonicalScreenshot = legacyArtifacts.find((artifact) => artifact.role === "canonical_screenshot");
|
|
18846
|
+
return {
|
|
18847
|
+
version: "riddle-proof.observation-receipt.v1",
|
|
18848
|
+
observation_id: `obs_${side}_migrated`,
|
|
18849
|
+
comparison_role: side,
|
|
18850
|
+
executor: { kind: "other", runner: "legacy-change-receipt" },
|
|
18851
|
+
target: { kind: "url", url: legacy.source },
|
|
18852
|
+
source: {},
|
|
18853
|
+
captured_at: legacy.captured_at || (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
18854
|
+
artifacts: legacyArtifacts,
|
|
18855
|
+
canonical_screenshot: canonicalScreenshot,
|
|
18856
|
+
profile_summary: {
|
|
18857
|
+
profile_name: legacy.profile_name,
|
|
18858
|
+
status: legacy.status,
|
|
18859
|
+
summary: legacy.summary,
|
|
18860
|
+
route: legacy.route,
|
|
18861
|
+
checks: legacy.checks
|
|
18862
|
+
},
|
|
18863
|
+
metadata: { migrated_from: RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION }
|
|
18296
18864
|
};
|
|
18297
|
-
return Object.values(summary).some((value) => typeof value !== "undefined") ? summary : void 0;
|
|
18298
|
-
}
|
|
18299
|
-
function isProfileResult(result) {
|
|
18300
|
-
const version = stringValue4(result.version);
|
|
18301
|
-
return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
|
|
18302
|
-
}
|
|
18303
|
-
function isChangeReceiptResult(result) {
|
|
18304
|
-
return stringValue4(result.version) === "riddle-proof.change-receipt.v1";
|
|
18305
|
-
}
|
|
18306
|
-
function isChangeResult(result) {
|
|
18307
|
-
const version = stringValue4(result.version);
|
|
18308
|
-
return version === "riddle-proof.change-result.v1" || version === "riddle-proof.change-compact-result.v1";
|
|
18309
18865
|
}
|
|
18310
|
-
function
|
|
18311
|
-
const
|
|
18312
|
-
const result = asRecord2(input.result);
|
|
18313
|
-
const proofResult = asRecord2(runResponse.proofResult);
|
|
18314
|
-
const profileResult = isProfileResult(result);
|
|
18315
|
-
const changeReceiptResult = isChangeReceiptResult(result);
|
|
18316
|
-
const changeResult = changeReceiptResult || isChangeResult(result);
|
|
18317
|
-
const profileRiddle = asRecord2(result.riddle);
|
|
18318
|
-
const preview = asRecord2(runResponse.preview);
|
|
18319
|
-
const resultRunCard = asRecord2(result.run_card);
|
|
18320
|
-
const stopCondition = asRecord2(resultRunCard.stop_condition);
|
|
18321
|
-
const resultDetails = asRecord2(result.details);
|
|
18322
|
-
const resultRaw = asRecord2(result.raw);
|
|
18323
|
-
const rawDetails = asRecord2(resultRaw.details);
|
|
18324
|
-
const profileChecks = profileResult ? profileCheckCounts(result) : void 0;
|
|
18325
|
-
const changeChecks = changeResult ? changeReceiptCheckCounts(result) : void 0;
|
|
18326
|
-
const artifacts = mergeArtifacts(
|
|
18327
|
-
collectArtifacts(runResponse),
|
|
18328
|
-
profileResult ? collectProfileArtifacts(result) : [],
|
|
18329
|
-
changeReceiptResult ? collectChangeReceiptArtifacts(result) : []
|
|
18330
|
-
);
|
|
18331
|
-
const pages = changeReceiptResult ? changeReceiptPageSummaries(result) : profileResult ? profilePageSummaries(result, profileChecks) : pageSummaries(result);
|
|
18332
|
-
const checkSource = { ...result };
|
|
18333
|
-
delete checkSource.ok;
|
|
18334
|
-
const nestedChecks = summarizeExplicitChecks(checkSource);
|
|
18335
|
-
const resultStatus = firstStringValue2(result.status, stopCondition.status);
|
|
18336
|
-
const ok = changeResult ? resultStatus === "passed" : booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
|
|
18337
|
-
const checkpointSummary = checkpointSummaryFrom2(
|
|
18338
|
-
result.checkpoint_summary,
|
|
18339
|
-
stopCondition.checkpoint_summary,
|
|
18340
|
-
resultDetails.checkpoint_summary,
|
|
18341
|
-
rawDetails.checkpoint_summary,
|
|
18342
|
-
proofResult.checkpoint_summary
|
|
18343
|
-
);
|
|
18344
|
-
const publicState = summarizeRiddleProofPublicState({
|
|
18345
|
-
...result,
|
|
18346
|
-
status: resultStatus,
|
|
18347
|
-
checkpoint_summary: checkpointSummary || result.checkpoint_summary
|
|
18348
|
-
});
|
|
18349
|
-
const mergeRecommendation = riddleProofPublicStateMergeRecommendation(
|
|
18350
|
-
publicState,
|
|
18351
|
-
firstStringValue2(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
|
|
18352
|
-
);
|
|
18866
|
+
function migrateRiddleProofChangeReceipt(legacy) {
|
|
18867
|
+
const recommendation = changeRecommendation(legacy.verdict);
|
|
18353
18868
|
return {
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18363
|
-
|
|
18364
|
-
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18869
|
+
version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
|
|
18870
|
+
contract_name: legacy.contract_name,
|
|
18871
|
+
profile_name: legacy.profile_name,
|
|
18872
|
+
status: legacy.status,
|
|
18873
|
+
verdict: legacy.verdict,
|
|
18874
|
+
summary: legacy.summary,
|
|
18875
|
+
before: migratedObservationFromLegacySide("before", legacy.before),
|
|
18876
|
+
after: migratedObservationFromLegacySide("after", legacy.after),
|
|
18877
|
+
groups: {
|
|
18878
|
+
before: {
|
|
18879
|
+
side: "before",
|
|
18880
|
+
label: "before",
|
|
18881
|
+
ok: legacy.before.status === "passed" || legacy.before.status === "product_regression",
|
|
18882
|
+
profile_name: legacy.before.profile_name,
|
|
18883
|
+
status: legacy.before.status,
|
|
18884
|
+
required_status: DEFAULT_BEFORE_STATUSES,
|
|
18885
|
+
summary: legacy.before.summary
|
|
18886
|
+
},
|
|
18887
|
+
after: {
|
|
18888
|
+
side: "after",
|
|
18889
|
+
label: "after",
|
|
18890
|
+
ok: legacy.after.status === "passed",
|
|
18891
|
+
profile_name: legacy.after.profile_name,
|
|
18892
|
+
status: legacy.after.status,
|
|
18893
|
+
required_status: DEFAULT_AFTER_STATUSES,
|
|
18894
|
+
summary: legacy.after.summary
|
|
18895
|
+
}
|
|
18896
|
+
},
|
|
18897
|
+
source_bindings: {
|
|
18898
|
+
before: { side: "before", required: false, ok: true, status: "not_required" },
|
|
18899
|
+
after: { side: "after", required: false, ok: true, status: "not_required" }
|
|
18900
|
+
},
|
|
18901
|
+
deltas: legacy.deltas,
|
|
18902
|
+
recommendation,
|
|
18903
|
+
shipping_authorization: noShippingAuthorization(),
|
|
18904
|
+
proves: legacy.proves,
|
|
18905
|
+
does_not_prove: legacy.does_not_prove,
|
|
18906
|
+
metadata: legacy.metadata
|
|
18378
18907
|
};
|
|
18379
18908
|
}
|
|
18380
|
-
function
|
|
18381
|
-
if (
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18909
|
+
function parseRiddleProofChangeReceipt(value) {
|
|
18910
|
+
if (!isRecord3(value)) throw new Error("Change receipt must be an object.");
|
|
18911
|
+
if (value.version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION) {
|
|
18912
|
+
return migrateRiddleProofChangeReceipt(value);
|
|
18913
|
+
}
|
|
18914
|
+
if (value.version !== RIDDLE_PROOF_CHANGE_RECEIPT_VERSION) {
|
|
18915
|
+
throw new Error(`Unsupported Change receipt version ${String(value.version || "missing")}.`);
|
|
18916
|
+
}
|
|
18917
|
+
const receipt = value;
|
|
18918
|
+
if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(receipt.status)) {
|
|
18919
|
+
throw new Error(`Unsupported Change receipt status ${String(receipt.status || "missing")}.`);
|
|
18920
|
+
}
|
|
18921
|
+
parseRiddleProofObservationReceipt(receipt.before);
|
|
18922
|
+
parseRiddleProofObservationReceipt(receipt.after);
|
|
18923
|
+
if (receipt.before.comparison_role !== "before" || receipt.after.comparison_role !== "after") {
|
|
18924
|
+
throw new Error("Change receipt observations must preserve their before and after comparison roles.");
|
|
18925
|
+
}
|
|
18926
|
+
const expectedVerdict = changeReceiptVerdict(receipt.status);
|
|
18927
|
+
if (receipt.verdict !== expectedVerdict) {
|
|
18928
|
+
throw new Error("Change receipt verdict must match its evaluated status.");
|
|
18929
|
+
}
|
|
18930
|
+
if (!recommendationMatches(receipt.recommendation, changeRecommendation(expectedVerdict))) {
|
|
18931
|
+
throw new Error("Change receipt recommendation must be derived from the Change receipt verdict.");
|
|
18932
|
+
}
|
|
18933
|
+
assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Change receipt");
|
|
18934
|
+
return receipt;
|
|
18386
18935
|
}
|
|
18387
|
-
function
|
|
18388
|
-
|
|
18936
|
+
function createRiddleProofHandoffReceipt(changeReceipt, options = {}) {
|
|
18937
|
+
const change = parseRiddleProofChangeReceipt(changeReceipt);
|
|
18938
|
+
const authorization = options.shipping_authorization || change.shipping_authorization;
|
|
18939
|
+
assertShippingAuthorizationConsistent(authorization, "Handoff receipt");
|
|
18940
|
+
return {
|
|
18941
|
+
version: RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION,
|
|
18942
|
+
change,
|
|
18943
|
+
verdict: change.verdict,
|
|
18944
|
+
recommendation: change.recommendation,
|
|
18945
|
+
shipping_authorization: authorization,
|
|
18946
|
+
canonical_pair: {
|
|
18947
|
+
before: change.before.canonical_screenshot,
|
|
18948
|
+
after: change.after.canonical_screenshot
|
|
18949
|
+
},
|
|
18950
|
+
created_at: options.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
18951
|
+
};
|
|
18389
18952
|
}
|
|
18390
|
-
function
|
|
18391
|
-
if (
|
|
18392
|
-
|
|
18393
|
-
if (summary.result_status === "shipped") return "shipped";
|
|
18394
|
-
if (summary.result_status === "completed") return "completed";
|
|
18395
|
-
if (summary.ship_held === true) return "proof passed; ship held";
|
|
18396
|
-
if (summary.ship_authorized === true) return "passed; ship authorized";
|
|
18397
|
-
return "passed";
|
|
18953
|
+
function parseRiddleProofHandoffReceipt(value) {
|
|
18954
|
+
if (!isRecord3(value) || value.version !== RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION) {
|
|
18955
|
+
throw new Error(`Unsupported Handoff receipt version ${String(isRecord3(value) ? value.version || "missing" : "missing")}.`);
|
|
18398
18956
|
}
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
const
|
|
18404
|
-
if (
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
if (
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18957
|
+
const receipt = value;
|
|
18958
|
+
if (!Number.isFinite(Date.parse(receipt.created_at))) {
|
|
18959
|
+
throw new Error("Handoff receipt created_at must be a valid timestamp.");
|
|
18960
|
+
}
|
|
18961
|
+
const change = parseRiddleProofChangeReceipt(receipt.change);
|
|
18962
|
+
if (receipt.verdict !== change.verdict || !recommendationMatches(receipt.recommendation, change.recommendation)) {
|
|
18963
|
+
throw new Error("Handoff receipt must preserve the Change receipt verdict and recommendation.");
|
|
18964
|
+
}
|
|
18965
|
+
if (!observationArtifactMatches(receipt.canonical_pair?.before, change.before.canonical_screenshot) || !observationArtifactMatches(receipt.canonical_pair?.after, change.after.canonical_screenshot)) {
|
|
18966
|
+
throw new Error("Handoff receipt canonical pair must project the Change receipt observations.");
|
|
18967
|
+
}
|
|
18968
|
+
assertShippingAuthorizationConsistent(receipt.shipping_authorization, "Handoff receipt");
|
|
18969
|
+
return receipt;
|
|
18411
18970
|
}
|
|
18412
|
-
function
|
|
18413
|
-
return
|
|
18971
|
+
function markdownTableCell(value) {
|
|
18972
|
+
return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
18414
18973
|
}
|
|
18415
|
-
function
|
|
18416
|
-
return
|
|
18974
|
+
function artifactTarget(artifact) {
|
|
18975
|
+
return artifact.url || artifact.path;
|
|
18417
18976
|
}
|
|
18418
|
-
function
|
|
18419
|
-
|
|
18977
|
+
function observationArtifactKind(artifact) {
|
|
18978
|
+
if (artifact.role === "canonical_screenshot" || artifact.role === "setup_screenshot" || artifact.kind === "image") return "image";
|
|
18979
|
+
if (artifact.role === "data" || /\.(json|md|txt|html|log|har)(\?|#|$)/i.test(artifact.name)) return "data";
|
|
18980
|
+
return "artifact";
|
|
18420
18981
|
}
|
|
18421
|
-
function
|
|
18422
|
-
|
|
18423
|
-
|
|
18982
|
+
function receiptSideView(side, observation) {
|
|
18983
|
+
const artifacts = observation.artifacts.map((artifact) => ({
|
|
18984
|
+
side,
|
|
18985
|
+
name: artifact.name,
|
|
18986
|
+
kind: observationArtifactKind(artifact),
|
|
18987
|
+
url: artifact.url,
|
|
18988
|
+
path: artifact.path,
|
|
18989
|
+
source: artifact.source
|
|
18990
|
+
}));
|
|
18991
|
+
const summary = observation.profile_summary;
|
|
18992
|
+
const canonical = observation.canonical_screenshot;
|
|
18993
|
+
const screenshots = artifacts.filter(artifactIsScreenshot);
|
|
18994
|
+
if (canonical) {
|
|
18995
|
+
const canonicalTarget = canonical.url || canonical.path;
|
|
18996
|
+
screenshots.sort((left, right) => {
|
|
18997
|
+
const leftCanonical = (left.url || left.path) === canonicalTarget || left.name === canonical.name;
|
|
18998
|
+
const rightCanonical = (right.url || right.path) === canonicalTarget || right.name === canonical.name;
|
|
18999
|
+
return Number(rightCanonical) - Number(leftCanonical);
|
|
19000
|
+
});
|
|
19001
|
+
}
|
|
19002
|
+
return {
|
|
19003
|
+
side,
|
|
19004
|
+
source: observation.target.url,
|
|
19005
|
+
profile_name: summary?.profile_name || observation.proof?.profile_name || "profile",
|
|
19006
|
+
status: summary?.status || observation.proof?.result.status || "proof_insufficient",
|
|
19007
|
+
summary: summary?.summary || observation.proof?.result.summary || "No profile summary recorded.",
|
|
19008
|
+
route: summary?.route,
|
|
19009
|
+
captured_at: observation.captured_at,
|
|
19010
|
+
checks: summary?.checks || (observation.proof ? profileCheckCounts2(observation.proof.result) : {
|
|
19011
|
+
total: 0,
|
|
19012
|
+
passed: 0,
|
|
19013
|
+
failed: 0,
|
|
19014
|
+
skipped: 0,
|
|
19015
|
+
needs_human_review: 0
|
|
19016
|
+
}),
|
|
19017
|
+
screenshots,
|
|
19018
|
+
artifacts
|
|
19019
|
+
};
|
|
18424
19020
|
}
|
|
18425
|
-
function
|
|
18426
|
-
|
|
18427
|
-
const rejected = summary.rejected_response_count ?? 0;
|
|
18428
|
-
const ignored = summary.ignored_response_count ?? 0;
|
|
18429
|
-
const parts = [`${accepted} accepted`, `${rejected} rejected`, `${ignored} ignored`];
|
|
18430
|
-
if ((summary.duplicate_response_count ?? 0) > 0) parts.push(`${summary.duplicate_response_count} duplicate`);
|
|
18431
|
-
const state = summary.pending === true ? "pending" : summary.pending === false ? "complete" : "";
|
|
18432
|
-
return [
|
|
18433
|
-
parts.join(" / "),
|
|
18434
|
-
state,
|
|
18435
|
-
summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
|
|
18436
|
-
].filter(Boolean).join("; ");
|
|
19021
|
+
function markdownLink(label, target) {
|
|
19022
|
+
return `[${label.replace(/\]/g, "\\]")}](${target})`;
|
|
18437
19023
|
}
|
|
18438
|
-
function
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
`## ${title}`,
|
|
18444
|
-
"",
|
|
18445
|
-
`**Result:** ${resultLabel(summary)}`
|
|
18446
|
-
];
|
|
18447
|
-
if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
|
|
18448
|
-
if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
|
|
18449
|
-
if (summary.result_status) lines.push(`**Evidence status:** ${summary.result_status}`);
|
|
18450
|
-
if (summary.status) lines.push(`**Riddle job status:** ${summary.status}`);
|
|
18451
|
-
if (summary.job_id) lines.push(`**Riddle job:** \`${summary.job_id}\``);
|
|
18452
|
-
if (summary.duration_ms) lines.push(`**Duration:** ${formatDuration(summary.duration_ms)}`);
|
|
18453
|
-
if (hasShipControl(summary)) {
|
|
18454
|
-
lines.push(`**Ship control:** held=${formatBool(summary.ship_held)}, shipping_disabled=${formatBool(summary.shipping_disabled)}, authorized=${formatBool(summary.ship_authorized)}`);
|
|
18455
|
-
}
|
|
18456
|
-
if (hasHandoffControl(summary)) {
|
|
18457
|
-
lines.push(`**Handoff:** merge_ready=${formatBool(summary.merge_ready)}, sync_allowed=${formatBool(summary.sync_allowed)}`);
|
|
18458
|
-
}
|
|
18459
|
-
if (summary.proof_decision) lines.push(`**Proof decision:** \`${summary.proof_decision}\``);
|
|
18460
|
-
if (shouldRenderMergeRecommendation(summary)) lines.push(`**Merge recommendation:** ${summary.merge_recommendation}`);
|
|
18461
|
-
if (summary.checkpoint_summary) lines.push(`**Checkpoints:** ${checkpointSummaryLine(summary.checkpoint_summary)}`);
|
|
18462
|
-
if (summary.proof_url) lines.push(`**Proof URL:** ${markdownLink(summary.proof_url, summary.proof_url)}`);
|
|
18463
|
-
if (summary.preview_id || summary.preview_url) {
|
|
18464
|
-
const previewLabel = summary.preview_id ? `\`${summary.preview_id}\`` : "preview";
|
|
18465
|
-
lines.push(`**Preview:** ${summary.preview_url ? markdownLink(previewLabel, summary.preview_url) : previewLabel}`);
|
|
19024
|
+
function appendMarkdownArtifacts(lines, title, artifacts) {
|
|
19025
|
+
lines.push(`### ${title}`, "");
|
|
19026
|
+
if (!artifacts.length) {
|
|
19027
|
+
lines.push("- No screenshot artifacts recorded.", "");
|
|
19028
|
+
return;
|
|
18466
19029
|
}
|
|
18467
|
-
|
|
18468
|
-
const
|
|
18469
|
-
|
|
19030
|
+
for (const artifact of artifacts.slice(0, 6)) {
|
|
19031
|
+
const target = artifactTarget(artifact);
|
|
19032
|
+
if (target && artifact.kind === "image") {
|
|
19033
|
+
lines.push(`![${artifact.name.replace(/\]/g, "\\]")}](${target})`);
|
|
19034
|
+
} else if (target) {
|
|
19035
|
+
lines.push(`- ${markdownLink(artifact.name, target)}`);
|
|
19036
|
+
} else {
|
|
19037
|
+
lines.push(`- ${artifact.name}`);
|
|
19038
|
+
}
|
|
18470
19039
|
}
|
|
18471
|
-
lines.push(
|
|
19040
|
+
if (artifacts.length > 6) lines.push(`- ${artifacts.length - 6} more artifact(s) omitted`);
|
|
18472
19041
|
lines.push("");
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
|
|
18481
|
-
|
|
18482
|
-
}
|
|
18483
|
-
|
|
19042
|
+
}
|
|
19043
|
+
function riddleProofChangeReceiptMarkdown(receipt) {
|
|
19044
|
+
const before = receiptSideView("before", receipt.before);
|
|
19045
|
+
const after = receiptSideView("after", receipt.after);
|
|
19046
|
+
const lines = [
|
|
19047
|
+
"# Riddle Proof Change Receipt",
|
|
19048
|
+
"",
|
|
19049
|
+
`**Verdict:** ${receipt.verdict}`,
|
|
19050
|
+
`**Status:** ${receipt.status}`,
|
|
19051
|
+
`**Contract:** ${receipt.contract_name}`,
|
|
19052
|
+
receipt.profile_name ? `**Profile:** ${receipt.profile_name}` : "",
|
|
19053
|
+
`**Recommendation:** ${receipt.recommendation.label}`,
|
|
19054
|
+
`**Shipping authorization:** ${receipt.shipping_authorization.status}`,
|
|
19055
|
+
"",
|
|
19056
|
+
receipt.summary,
|
|
19057
|
+
"",
|
|
19058
|
+
"## Evidence Pair",
|
|
19059
|
+
"",
|
|
19060
|
+
"| Side | Source | Status | Checks |",
|
|
19061
|
+
"| --- | --- | --- | --- |",
|
|
19062
|
+
`| Before | ${markdownTableCell(before.source)} | ${before.status}${receipt.groups.before.ok ? " (matched baseline)" : " (baseline mismatch)"} | ${before.checks.passed} passed / ${before.checks.failed} failed |`,
|
|
19063
|
+
`| After | ${markdownTableCell(after.source)} | ${after.status} | ${after.checks.passed} passed / ${after.checks.failed} failed |`,
|
|
19064
|
+
"",
|
|
19065
|
+
"## Source Binding",
|
|
19066
|
+
"",
|
|
19067
|
+
"| Side | Status | Git revision | Preview | Digest |",
|
|
19068
|
+
"| --- | --- | --- | --- | --- |",
|
|
19069
|
+
`| 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")} |`,
|
|
19070
|
+
`| 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")} |`,
|
|
19071
|
+
"",
|
|
19072
|
+
"## Delta Checks",
|
|
19073
|
+
"",
|
|
19074
|
+
"| Delta | Status | Before | After |",
|
|
19075
|
+
"| --- | --- | --- | --- |"
|
|
19076
|
+
].filter(Boolean);
|
|
19077
|
+
for (const delta of receipt.deltas) {
|
|
19078
|
+
lines.push(`| ${markdownTableCell(delta.label)} | ${delta.status} | ${markdownTableCell(delta.before_observed)} | ${markdownTableCell(delta.after_observed)} |`);
|
|
19079
|
+
if (delta.message) lines.push(`| ${markdownTableCell(`${delta.label} message`)} | ${markdownTableCell(delta.message)} | | |`);
|
|
19080
|
+
}
|
|
19081
|
+
lines.push("");
|
|
19082
|
+
appendMarkdownArtifacts(lines, "Before Canonical Screenshot", before.screenshots.slice(0, 1));
|
|
19083
|
+
appendMarkdownArtifacts(lines, "After Canonical Screenshot", after.screenshots.slice(0, 1));
|
|
19084
|
+
if (receipt.proves.length) {
|
|
19085
|
+
lines.push("## What This Proves", "");
|
|
19086
|
+
for (const claim of receipt.proves) lines.push(`- ${claim}`);
|
|
18484
19087
|
lines.push("");
|
|
18485
19088
|
}
|
|
18486
|
-
|
|
19089
|
+
if (receipt.does_not_prove.length) {
|
|
19090
|
+
lines.push("## What This Does Not Prove", "");
|
|
19091
|
+
for (const claim of receipt.does_not_prove) lines.push(`- ${claim}`);
|
|
19092
|
+
lines.push("");
|
|
19093
|
+
}
|
|
19094
|
+
const linkedArtifacts = [...before.artifacts, ...after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 24);
|
|
18487
19095
|
if (linkedArtifacts.length) {
|
|
18488
|
-
lines.push("
|
|
19096
|
+
lines.push("## Artifacts", "");
|
|
18489
19097
|
for (const artifact of linkedArtifacts) {
|
|
18490
|
-
lines.push(`- ${markdownLink(artifact.name, artifact
|
|
18491
|
-
}
|
|
18492
|
-
if (summary.artifacts.length - (summary.primary_image ? 1 : 0) > linkedArtifacts.length) {
|
|
18493
|
-
lines.push(`- ${summary.artifacts.length - (summary.primary_image ? 1 : 0) - linkedArtifacts.length} more artifact(s) omitted`);
|
|
19098
|
+
lines.push(`- ${artifact.side}: ${markdownLink(artifact.name, artifactTarget(artifact) || "")}`);
|
|
18494
19099
|
}
|
|
18495
19100
|
lines.push("");
|
|
18496
19101
|
}
|
|
18497
|
-
if (input.source?.trim()) {
|
|
18498
|
-
lines.push(`_Source: ${input.source.trim()}_`);
|
|
18499
|
-
} else {
|
|
18500
|
-
lines.push("_Updated by `riddle-proof-loop pr-comment`._");
|
|
18501
|
-
}
|
|
18502
19102
|
return `${lines.join("\n").trim()}
|
|
18503
19103
|
`;
|
|
18504
19104
|
}
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
var RIDDLE_PROOF_PROFILE_SUGGESTIONS_VERSION = "riddle-proof.profile-suggestions.v1";
|
|
18508
|
-
var UI_FILE_PATTERN = /\.(css|html?|jsx?|tsx?|vue|svelte|mdx)$/i;
|
|
18509
|
-
var VISUAL_ASSET_PATTERN = /\.(avif|gif|jpe?g|png|svg|webp)$/i;
|
|
18510
|
-
function nonEmptyStrings(values) {
|
|
18511
|
-
return Array.from(new Set(
|
|
18512
|
-
(values || []).filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
|
|
18513
|
-
));
|
|
18514
|
-
}
|
|
18515
|
-
function normalizeRoute(value) {
|
|
18516
|
-
if (!value?.trim()) return void 0;
|
|
18517
|
-
const trimmed = value.trim();
|
|
18518
|
-
if (/^https?:\/\//i.test(trimmed)) return void 0;
|
|
18519
|
-
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
19105
|
+
function escapeHtml(value) {
|
|
19106
|
+
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
18520
19107
|
}
|
|
18521
|
-
function
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
return
|
|
18526
|
-
} catch {
|
|
18527
|
-
return void 0;
|
|
19108
|
+
function htmlArtifactCard(artifact) {
|
|
19109
|
+
const target = artifactTarget(artifact);
|
|
19110
|
+
if (!target) return `<p>${escapeHtml(artifact.name)}</p>`;
|
|
19111
|
+
if (artifact.kind === "image") {
|
|
19112
|
+
return `<figure><img src="${escapeHtml(target)}" alt="${escapeHtml(artifact.name)}"><figcaption>${escapeHtml(artifact.name)}</figcaption></figure>`;
|
|
18528
19113
|
}
|
|
19114
|
+
return `<p><a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a></p>`;
|
|
18529
19115
|
}
|
|
18530
|
-
function
|
|
18531
|
-
|
|
19116
|
+
function htmlArtifactLink(artifact) {
|
|
19117
|
+
const target = artifactTarget(artifact);
|
|
19118
|
+
if (!target) return escapeHtml(artifact.name);
|
|
19119
|
+
return `<a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a>`;
|
|
18532
19120
|
}
|
|
18533
|
-
function
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
if (!routeMatch) return void 0;
|
|
18537
|
-
const route = routeMatch[1].replace(/\/index$/i, "").replace(/\/page$/i, "").replace(/\[[^/\]]+\]/g, ":param");
|
|
18538
|
-
return normalizeRoute(route || "/");
|
|
19121
|
+
function htmlList(items, emptyText) {
|
|
19122
|
+
if (!items.length) return `<p class="muted">${escapeHtml(emptyText)}</p>`;
|
|
19123
|
+
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
|
|
18539
19124
|
}
|
|
18540
|
-
function
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
19125
|
+
function riddleProofChangeReceiptHtml(receipt) {
|
|
19126
|
+
const before = receiptSideView("before", receipt.before);
|
|
19127
|
+
const after = receiptSideView("after", receipt.after);
|
|
19128
|
+
const artifactLinks = [...before.artifacts, ...after.artifacts].filter((artifact) => artifactTarget(artifact)).slice(0, 32);
|
|
19129
|
+
const deltaRows = receipt.deltas.map((delta) => `
|
|
19130
|
+
<tr>
|
|
19131
|
+
<td>${escapeHtml(delta.label)}</td>
|
|
19132
|
+
<td><span class="status ${escapeHtml(delta.status)}">${escapeHtml(delta.status)}</span></td>
|
|
19133
|
+
<td>${escapeHtml(delta.before_observed)}</td>
|
|
19134
|
+
<td>${escapeHtml(delta.after_observed)}</td>
|
|
19135
|
+
</tr>
|
|
19136
|
+
${delta.message ? `<tr><td class="muted">${escapeHtml(delta.label)} message</td><td colspan="3">${escapeHtml(delta.message)}</td></tr>` : ""}`).join("");
|
|
19137
|
+
return `<!doctype html>
|
|
19138
|
+
<html lang="en">
|
|
19139
|
+
<head>
|
|
19140
|
+
<meta charset="utf-8">
|
|
19141
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
19142
|
+
<title>${escapeHtml(receipt.contract_name)} - Riddle Proof Change Receipt</title>
|
|
19143
|
+
<style>
|
|
19144
|
+
:root { color-scheme: light dark; --bg: #0f141b; --panel: #171f29; --text: #eef4f8; --muted: #aebbc8; --border: #314152; --ok: #69d089; --bad: #ff7a7a; --warn: #ffd166; }
|
|
19145
|
+
body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); }
|
|
19146
|
+
main { max-width: 1120px; margin: 0 auto; padding: 32px 20px 48px; }
|
|
19147
|
+
header, section { border: 1px solid var(--border); background: var(--panel); border-radius: 8px; padding: 20px; margin: 0 0 18px; }
|
|
19148
|
+
h1, h2, h3, p { margin-top: 0; }
|
|
19149
|
+
h1 { font-size: clamp(1.6rem, 3vw, 2.4rem); }
|
|
19150
|
+
h2 { font-size: 1.1rem; margin-bottom: 14px; }
|
|
19151
|
+
.muted { color: var(--muted); }
|
|
19152
|
+
.verdict { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); font-weight: 700; }
|
|
19153
|
+
.mergeable, .passed { color: var(--ok); }
|
|
19154
|
+
.not_mergeable, .failed, .product_regression { color: var(--bad); }
|
|
19155
|
+
.proof_insufficient, .environment_blocked, .needs_human_review, .configuration_error { color: var(--warn); }
|
|
19156
|
+
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
|
19157
|
+
.side { border: 1px solid var(--border); border-radius: 8px; padding: 14px; min-width: 0; }
|
|
19158
|
+
code { overflow-wrap: anywhere; color: var(--muted); }
|
|
19159
|
+
table { width: 100%; border-collapse: collapse; }
|
|
19160
|
+
th, td { border-bottom: 1px solid var(--border); padding: 10px 8px; text-align: left; vertical-align: top; }
|
|
19161
|
+
figure { margin: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: #0b1017; }
|
|
19162
|
+
img { display: block; width: 100%; height: auto; }
|
|
19163
|
+
figcaption { padding: 8px 10px; color: var(--muted); font-size: 0.9rem; }
|
|
19164
|
+
ul { margin-bottom: 0; }
|
|
19165
|
+
a { color: #8bc7ff; }
|
|
19166
|
+
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } main { padding: 18px 12px 32px; } }
|
|
19167
|
+
</style>
|
|
19168
|
+
</head>
|
|
19169
|
+
<body>
|
|
19170
|
+
<main>
|
|
19171
|
+
<header>
|
|
19172
|
+
<p class="verdict ${escapeHtml(receipt.verdict)}">${escapeHtml(receipt.verdict)}</p>
|
|
19173
|
+
<h1>${escapeHtml(receipt.contract_name)}</h1>
|
|
19174
|
+
<p>${escapeHtml(receipt.summary)}</p>
|
|
19175
|
+
<p class="muted">Status: ${escapeHtml(receipt.status)}${receipt.profile_name ? ` | Profile: ${escapeHtml(receipt.profile_name)}` : ""}</p>
|
|
19176
|
+
<p>${escapeHtml(receipt.recommendation.label)}. Shipping authorization: ${escapeHtml(receipt.shipping_authorization.status)}.</p>
|
|
19177
|
+
</header>
|
|
19178
|
+
<section>
|
|
19179
|
+
<h2>Evidence Pair</h2>
|
|
19180
|
+
<div class="grid">
|
|
19181
|
+
<div class="side">
|
|
19182
|
+
<h3>Before</h3>
|
|
19183
|
+
<p><code>${escapeHtml(before.source)}</code></p>
|
|
19184
|
+
<p>Status: <strong class="${escapeHtml(before.status)}">${escapeHtml(before.status)}</strong>${receipt.groups.before.ok ? " (matched baseline)" : " (baseline mismatch)"}</p>
|
|
19185
|
+
<p>${escapeHtml(before.summary)}</p>
|
|
19186
|
+
<p class="muted">${before.checks.passed} passed / ${before.checks.failed} failed checks</p>
|
|
19187
|
+
</div>
|
|
19188
|
+
<div class="side">
|
|
19189
|
+
<h3>After</h3>
|
|
19190
|
+
<p><code>${escapeHtml(after.source)}</code></p>
|
|
19191
|
+
<p>Status: <strong class="${escapeHtml(after.status)}">${escapeHtml(after.status)}</strong></p>
|
|
19192
|
+
<p>${escapeHtml(after.summary)}</p>
|
|
19193
|
+
<p class="muted">${after.checks.passed} passed / ${after.checks.failed} failed checks</p>
|
|
19194
|
+
</div>
|
|
19195
|
+
</div>
|
|
19196
|
+
</section>
|
|
19197
|
+
<section>
|
|
19198
|
+
<h2>Source Binding</h2>
|
|
19199
|
+
<table>
|
|
19200
|
+
<thead><tr><th>Side</th><th>Status</th><th>Git revision</th><th>Preview</th><th>Digest</th></tr></thead>
|
|
19201
|
+
<tbody>
|
|
19202
|
+
<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>
|
|
19203
|
+
<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>
|
|
19204
|
+
</tbody>
|
|
19205
|
+
</table>
|
|
19206
|
+
</section>
|
|
19207
|
+
<section>
|
|
19208
|
+
<h2>Delta Checks</h2>
|
|
19209
|
+
<table>
|
|
19210
|
+
<thead><tr><th>Delta</th><th>Status</th><th>Before</th><th>After</th></tr></thead>
|
|
19211
|
+
<tbody>${deltaRows}</tbody>
|
|
19212
|
+
</table>
|
|
19213
|
+
</section>
|
|
19214
|
+
<section>
|
|
19215
|
+
<h2>Screenshots</h2>
|
|
19216
|
+
<div class="grid">
|
|
19217
|
+
<div>${before.screenshots.slice(0, 1).map(htmlArtifactCard).join("") || `<p class="muted">No canonical before screenshot recorded.</p>`}</div>
|
|
19218
|
+
<div>${after.screenshots.slice(0, 1).map(htmlArtifactCard).join("") || `<p class="muted">No canonical after screenshot recorded.</p>`}</div>
|
|
19219
|
+
</div>
|
|
19220
|
+
</section>
|
|
19221
|
+
<section>
|
|
19222
|
+
<h2>What This Proves</h2>
|
|
19223
|
+
${htmlList(receipt.proves, "No explicit proof claims were recorded in contract metadata.")}
|
|
19224
|
+
</section>
|
|
19225
|
+
<section>
|
|
19226
|
+
<h2>What This Does Not Prove</h2>
|
|
19227
|
+
${htmlList(receipt.does_not_prove, "No explicit limits were recorded in contract metadata.")}
|
|
19228
|
+
</section>
|
|
19229
|
+
<section>
|
|
19230
|
+
<h2>Artifacts</h2>
|
|
19231
|
+
${artifactLinks.length ? `<ul>${artifactLinks.map((artifact) => `<li>${escapeHtml(artifact.side)}: ${htmlArtifactLink(artifact)}</li>`).join("")}</ul>` : `<p class="muted">No linked artifacts recorded.</p>`}
|
|
19232
|
+
</section>
|
|
19233
|
+
</main>
|
|
19234
|
+
</body>
|
|
19235
|
+
</html>
|
|
19236
|
+
`;
|
|
18546
19237
|
}
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
seen.add(key);
|
|
18553
|
-
return true;
|
|
18554
|
-
});
|
|
19238
|
+
|
|
19239
|
+
// src/pr-comment.ts
|
|
19240
|
+
var RIDDLE_PROOF_PR_COMMENT_MARKER = "<!-- riddle-proof:pr-comment:v1 -->";
|
|
19241
|
+
function asRecord2(value) {
|
|
19242
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
18555
19243
|
}
|
|
18556
|
-
function
|
|
18557
|
-
return
|
|
19244
|
+
function asArray(value) {
|
|
19245
|
+
return Array.isArray(value) ? value : [];
|
|
18558
19246
|
}
|
|
18559
|
-
function
|
|
18560
|
-
return typeof
|
|
19247
|
+
function stringValue4(value) {
|
|
19248
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
18561
19249
|
}
|
|
18562
|
-
function
|
|
18563
|
-
return typeof
|
|
19250
|
+
function numberValue3(value) {
|
|
19251
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
18564
19252
|
}
|
|
18565
|
-
function
|
|
18566
|
-
|
|
18567
|
-
return `suggested-${slugify(route || files[0] || "profile")}`;
|
|
19253
|
+
function booleanValue3(value) {
|
|
19254
|
+
return typeof value === "boolean" ? value : void 0;
|
|
18568
19255
|
}
|
|
18569
|
-
function
|
|
18570
|
-
const
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
if (includeMobile !== false) {
|
|
18574
|
-
viewports.push({ name: "mobile", width: 390, height: 844 });
|
|
19256
|
+
function firstStringValue2(...values) {
|
|
19257
|
+
for (const value of values) {
|
|
19258
|
+
const text = stringValue4(value);
|
|
19259
|
+
if (text) return text;
|
|
18575
19260
|
}
|
|
18576
|
-
return
|
|
19261
|
+
return void 0;
|
|
18577
19262
|
}
|
|
18578
|
-
function
|
|
18579
|
-
const
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
const
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
const
|
|
18595
|
-
|
|
18596
|
-
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
18604
|
-
|
|
18605
|
-
{ type: "no_mobile_horizontal_overflow" }
|
|
18606
|
-
];
|
|
18607
|
-
suggestions.push({
|
|
18608
|
-
id: "runtime-hygiene",
|
|
18609
|
-
reason: "UI changes should not introduce fatal console errors or mobile overflow.",
|
|
18610
|
-
checks: hygieneChecks
|
|
18611
|
-
});
|
|
18612
|
-
for (const selector of selectors) {
|
|
18613
|
-
suggestions.push({
|
|
18614
|
-
id: `selector-${slugify(selector)}`,
|
|
18615
|
-
reason: `Changed selector ${selector} should remain visible.`,
|
|
18616
|
-
checks: [{ type: "selector_visible", selector }]
|
|
18617
|
-
});
|
|
18618
|
-
}
|
|
18619
|
-
for (const entry of changedText) {
|
|
18620
|
-
const text = changedTextEntryText(entry);
|
|
18621
|
-
if (!text) continue;
|
|
18622
|
-
const selector = changedTextEntrySelector(entry);
|
|
18623
|
-
const label = changedTextEntryLabel(entry);
|
|
18624
|
-
const check = selector ? { type: "selector_text_visible", selector, text, label } : { type: "text_visible", text, label };
|
|
18625
|
-
suggestions.push({
|
|
18626
|
-
id: `text-${slugify(`${selector || "page"}-${text}`)}`,
|
|
18627
|
-
reason: selector ? `Expected text should be visible inside ${selector}.` : "Expected text should be visible on the route.",
|
|
18628
|
-
checks: [check]
|
|
19263
|
+
function artifactKind(name, url) {
|
|
19264
|
+
const target = `${name} ${url}`.toLowerCase();
|
|
19265
|
+
if (/\.(png|jpe?g|gif|webp|avif|svg)(\?|#|$)/.test(target)) return "image";
|
|
19266
|
+
if (/\.(json|har|txt|md|html|log)(\?|#|$)/.test(target)) return "data";
|
|
19267
|
+
return "artifact";
|
|
19268
|
+
}
|
|
19269
|
+
function artifactDisplayName(value, fallback) {
|
|
19270
|
+
const raw = stringValue4(value);
|
|
19271
|
+
if (raw) return raw;
|
|
19272
|
+
return fallback;
|
|
19273
|
+
}
|
|
19274
|
+
function collectArtifacts(runResponse) {
|
|
19275
|
+
const proofResult = asRecord2(runResponse.proofResult);
|
|
19276
|
+
const outputs = asArray(proofResult.outputs);
|
|
19277
|
+
const artifacts = [];
|
|
19278
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19279
|
+
for (const [index, item] of outputs.entries()) {
|
|
19280
|
+
const artifact = asRecord2(item);
|
|
19281
|
+
const url = stringValue4(artifact.url);
|
|
19282
|
+
if (!url || seen.has(url)) continue;
|
|
19283
|
+
seen.add(url);
|
|
19284
|
+
const name = artifactDisplayName(artifact.name, `artifact-${index + 1}`);
|
|
19285
|
+
artifacts.push({
|
|
19286
|
+
name,
|
|
19287
|
+
url,
|
|
19288
|
+
kind: artifactKind(name, url),
|
|
19289
|
+
size_bytes: numberValue3(artifact.size)
|
|
18629
19290
|
});
|
|
18630
19291
|
}
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18634
|
-
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
19292
|
+
return artifacts;
|
|
19293
|
+
}
|
|
19294
|
+
function collectProfileArtifacts(result) {
|
|
19295
|
+
const resultArtifacts = asRecord2(result.artifacts);
|
|
19296
|
+
const artifacts = [];
|
|
19297
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19298
|
+
for (const [index, item] of asArray(resultArtifacts.riddle_artifacts).entries()) {
|
|
19299
|
+
const artifact = asRecord2(item);
|
|
19300
|
+
const url = stringValue4(artifact.url);
|
|
19301
|
+
if (!url || seen.has(url)) continue;
|
|
19302
|
+
seen.add(url);
|
|
19303
|
+
const fallbackName = stringValue4(artifact.kind) || `artifact-${index + 1}`;
|
|
19304
|
+
const name = artifactDisplayName(artifact.name, fallbackName);
|
|
19305
|
+
artifacts.push({
|
|
19306
|
+
name,
|
|
19307
|
+
url,
|
|
19308
|
+
kind: artifactKind(name, url),
|
|
19309
|
+
size_bytes: numberValue3(artifact.size_bytes) ?? numberValue3(artifact.size)
|
|
18638
19310
|
});
|
|
18639
19311
|
}
|
|
18640
|
-
|
|
18641
|
-
const target = {
|
|
18642
|
-
...input.url?.trim() ? { url: input.url.trim() } : { route: routeOrDefault },
|
|
18643
|
-
viewports: defaultViewports(input.include_mobile),
|
|
18644
|
-
setup_actions: setupActions.length ? setupActions : void 0
|
|
18645
|
-
};
|
|
18646
|
-
const profile = {
|
|
18647
|
-
version: RIDDLE_PROOF_PROFILE_VERSION,
|
|
18648
|
-
name: suggestionProfileName(input, routeOrDefault, changedFiles),
|
|
18649
|
-
target,
|
|
18650
|
-
checks,
|
|
18651
|
-
artifacts: ["screenshot", "console", "proof_json"],
|
|
18652
|
-
baseline_policy: "invariant_only",
|
|
18653
|
-
failure_policy: {
|
|
18654
|
-
product_regression: "fail",
|
|
18655
|
-
proof_insufficient: "review",
|
|
18656
|
-
environment_blocked: "neutral",
|
|
18657
|
-
configuration_error: "fail",
|
|
18658
|
-
needs_human_review: "review"
|
|
18659
|
-
},
|
|
18660
|
-
metadata: {
|
|
18661
|
-
suggestion_input: {
|
|
18662
|
-
changed_files: changedFiles,
|
|
18663
|
-
selectors,
|
|
18664
|
-
changed_text_count: changedText.length
|
|
18665
|
-
}
|
|
18666
|
-
}
|
|
18667
|
-
};
|
|
18668
|
-
return {
|
|
18669
|
-
version: RIDDLE_PROOF_PROFILE_SUGGESTIONS_VERSION,
|
|
18670
|
-
profile,
|
|
18671
|
-
suggestions,
|
|
18672
|
-
warnings
|
|
18673
|
-
};
|
|
18674
|
-
}
|
|
18675
|
-
|
|
18676
|
-
// src/change-proof.ts
|
|
18677
|
-
var RIDDLE_PROOF_CHANGE_RESULT_VERSION = "riddle-proof.change-result.v1";
|
|
18678
|
-
var RIDDLE_PROOF_CHANGE_RECEIPT_VERSION = "riddle-proof.change-receipt.v1";
|
|
18679
|
-
var DEFAULT_BEFORE_STATUSES = ["passed", "product_regression"];
|
|
18680
|
-
var DEFAULT_AFTER_STATUSES = ["passed"];
|
|
18681
|
-
var DEFAULT_PROFILE_STATUS_TRANSITION_BEFORE = ["product_regression"];
|
|
18682
|
-
var DEFAULT_PROFILE_STATUS_TRANSITION_AFTER = ["passed"];
|
|
18683
|
-
var DEFAULT_CHECK_STATUS_TRANSITION_BEFORE = ["failed"];
|
|
18684
|
-
var DEFAULT_CHECK_STATUS_TRANSITION_AFTER = ["passed"];
|
|
18685
|
-
function listValue(value, fallback) {
|
|
18686
|
-
if (Array.isArray(value)) return value;
|
|
18687
|
-
if (value === void 0) return fallback;
|
|
18688
|
-
return [value];
|
|
18689
|
-
}
|
|
18690
|
-
function includesValue(allowed, observed) {
|
|
18691
|
-
return observed !== void 0 && allowed.includes(observed);
|
|
19312
|
+
return artifacts;
|
|
18692
19313
|
}
|
|
18693
|
-
function
|
|
18694
|
-
const
|
|
18695
|
-
const
|
|
18696
|
-
const
|
|
18697
|
-
|
|
18698
|
-
|
|
18699
|
-
side,
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
message: ok ? void 0 : `${label} profile status ${result.status} did not match required status ${requiredStatus.join(", ")}.`
|
|
19314
|
+
function collectChangeReceiptArtifacts(result) {
|
|
19315
|
+
const artifacts = [];
|
|
19316
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19317
|
+
const collectSide = (sideName, side) => {
|
|
19318
|
+
const candidates = [
|
|
19319
|
+
side.canonical_screenshot,
|
|
19320
|
+
...asArray(side.screenshots),
|
|
19321
|
+
...asArray(side.artifacts)
|
|
19322
|
+
].filter(Boolean);
|
|
19323
|
+
for (const [index, item] of candidates.entries()) {
|
|
19324
|
+
const artifact = asRecord2(item);
|
|
19325
|
+
const url = stringValue4(artifact.url) || stringValue4(artifact.path);
|
|
19326
|
+
if (!url || seen.has(url)) continue;
|
|
19327
|
+
seen.add(url);
|
|
19328
|
+
const fallbackName = `${sideName}-artifact-${index + 1}`;
|
|
19329
|
+
const name = `${sideName}/${artifactDisplayName(artifact.name, fallbackName)}`;
|
|
19330
|
+
artifacts.push({
|
|
19331
|
+
name,
|
|
19332
|
+
url,
|
|
19333
|
+
kind: artifactKind(name, url),
|
|
19334
|
+
size_bytes: numberValue3(artifact.size_bytes) ?? numberValue3(artifact.size)
|
|
19335
|
+
});
|
|
19336
|
+
}
|
|
18717
19337
|
};
|
|
19338
|
+
collectSide("before", asRecord2(result.before));
|
|
19339
|
+
collectSide("after", asRecord2(result.after));
|
|
19340
|
+
return artifacts;
|
|
18718
19341
|
}
|
|
18719
|
-
function
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
function profileStatusTransitionResult(delta, before, after) {
|
|
18727
|
-
const label = delta.label || "profile-status-transition";
|
|
18728
|
-
if (!before || !after) {
|
|
18729
|
-
return {
|
|
18730
|
-
type: delta.type,
|
|
18731
|
-
label,
|
|
18732
|
-
status: "proof_insufficient",
|
|
18733
|
-
before_observed: before?.status,
|
|
18734
|
-
after_observed: after?.status,
|
|
18735
|
-
message: `${label} needs both before and after profile results.`
|
|
18736
|
-
};
|
|
19342
|
+
function mergeArtifacts(...artifactLists) {
|
|
19343
|
+
const artifacts = [];
|
|
19344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19345
|
+
for (const artifact of artifactLists.flat()) {
|
|
19346
|
+
if (seen.has(artifact.url)) continue;
|
|
19347
|
+
seen.add(artifact.url);
|
|
19348
|
+
artifacts.push(artifact);
|
|
18737
19349
|
}
|
|
18738
|
-
|
|
18739
|
-
const afterAllowed = listValue(delta.after_status, DEFAULT_PROFILE_STATUS_TRANSITION_AFTER);
|
|
18740
|
-
const passed = includesValue(beforeAllowed, before.status) && includesValue(afterAllowed, after.status);
|
|
18741
|
-
return {
|
|
18742
|
-
type: delta.type,
|
|
18743
|
-
label,
|
|
18744
|
-
status: passed ? "passed" : "failed",
|
|
18745
|
-
before_observed: before.status,
|
|
18746
|
-
after_observed: after.status,
|
|
18747
|
-
message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${before.status} -> ${after.status}.`
|
|
18748
|
-
};
|
|
19350
|
+
return artifacts;
|
|
18749
19351
|
}
|
|
18750
|
-
function
|
|
18751
|
-
const
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
19352
|
+
function pageSummaries(result) {
|
|
19353
|
+
const pages = [];
|
|
19354
|
+
for (const page of asArray(result.pages)) {
|
|
19355
|
+
const record = asRecord2(page);
|
|
19356
|
+
const route = stringValue4(record.route) || stringValue4(record.url) || "page";
|
|
19357
|
+
const checks = asRecord2(record.checks);
|
|
19358
|
+
let passed = 0;
|
|
19359
|
+
let failed = 0;
|
|
19360
|
+
for (const value of Object.values(checks)) {
|
|
19361
|
+
if (value === true) passed += 1;
|
|
19362
|
+
if (value === false) failed += 1;
|
|
19363
|
+
}
|
|
19364
|
+
pages.push({ route, passed, failed });
|
|
18759
19365
|
}
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
19366
|
+
return pages;
|
|
19367
|
+
}
|
|
19368
|
+
function profileCheckCounts3(result) {
|
|
19369
|
+
const checks = asArray(result.checks);
|
|
19370
|
+
if (checks.length) {
|
|
19371
|
+
let passed2 = 0;
|
|
19372
|
+
let failed2 = 0;
|
|
19373
|
+
for (const item of checks) {
|
|
19374
|
+
const status = stringValue4(asRecord2(item).status);
|
|
19375
|
+
if (status === "passed") passed2 += 1;
|
|
19376
|
+
if (status === "failed") failed2 += 1;
|
|
19377
|
+
}
|
|
19378
|
+
return { passed: passed2, failed: failed2 };
|
|
18769
19379
|
}
|
|
18770
|
-
const
|
|
18771
|
-
const
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
label,
|
|
18776
|
-
status: "proof_insufficient",
|
|
18777
|
-
before_observed: beforeCheck?.status,
|
|
18778
|
-
after_observed: afterCheck?.status,
|
|
18779
|
-
message: `${label} was not present in ${!beforeCheck && !afterCheck ? "before or after" : !beforeCheck ? "before" : "after"} profile checks.`
|
|
18780
|
-
};
|
|
19380
|
+
const checkCounts = asRecord2(result.check_counts);
|
|
19381
|
+
const passed = numberValue3(checkCounts.passed);
|
|
19382
|
+
const failed = numberValue3(checkCounts.failed);
|
|
19383
|
+
if (typeof passed === "number" || typeof failed === "number") {
|
|
19384
|
+
return { passed: passed ?? 0, failed: failed ?? 0 };
|
|
18781
19385
|
}
|
|
18782
|
-
|
|
18783
|
-
const afterAllowed = listValue(delta.after_status, DEFAULT_CHECK_STATUS_TRANSITION_AFTER);
|
|
18784
|
-
const passed = includesValue(beforeAllowed, beforeCheck.status) && includesValue(afterAllowed, afterCheck.status);
|
|
18785
|
-
return {
|
|
18786
|
-
type: delta.type,
|
|
18787
|
-
label,
|
|
18788
|
-
status: passed ? "passed" : "failed",
|
|
18789
|
-
before_observed: beforeCheck.status,
|
|
18790
|
-
after_observed: afterCheck.status,
|
|
18791
|
-
message: passed ? void 0 : `${label} expected before ${beforeAllowed.join(", ")} and after ${afterAllowed.join(", ")}, got ${beforeCheck.status} -> ${afterCheck.status}.`
|
|
18792
|
-
};
|
|
19386
|
+
return void 0;
|
|
18793
19387
|
}
|
|
18794
|
-
function
|
|
18795
|
-
if (
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
19388
|
+
function profilePageSummaries(result, counts) {
|
|
19389
|
+
if (!counts) return [];
|
|
19390
|
+
const route = asRecord2(result.route);
|
|
19391
|
+
return [{
|
|
19392
|
+
route: firstStringValue2(route.observed, route.expected_path, route.requested, result.profile_name) || "profile",
|
|
19393
|
+
passed: counts.passed,
|
|
19394
|
+
failed: counts.failed
|
|
19395
|
+
}];
|
|
18799
19396
|
}
|
|
18800
|
-
function
|
|
18801
|
-
const
|
|
18802
|
-
if (
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
return
|
|
19397
|
+
function changeReceiptCheckCounts(result) {
|
|
19398
|
+
const deltas = asArray(result.deltas);
|
|
19399
|
+
if (!deltas.length) return void 0;
|
|
19400
|
+
let passed = 0;
|
|
19401
|
+
let failed = 0;
|
|
19402
|
+
for (const item of deltas) {
|
|
19403
|
+
const status = stringValue4(asRecord2(item).status);
|
|
19404
|
+
if (status === "passed") passed += 1;
|
|
19405
|
+
if (status === "failed" || status === "proof_insufficient" || status === "configuration_error") failed += 1;
|
|
19406
|
+
}
|
|
19407
|
+
return { passed, failed };
|
|
18811
19408
|
}
|
|
18812
|
-
function
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
19409
|
+
function changeReceiptPageSummaries(result) {
|
|
19410
|
+
const pages = [];
|
|
19411
|
+
for (const sideName of ["before", "after"]) {
|
|
19412
|
+
const side = asRecord2(result[sideName]);
|
|
19413
|
+
if (!Object.keys(side).length) continue;
|
|
19414
|
+
const profileSummary = asRecord2(side.profile_summary);
|
|
19415
|
+
const checks = Object.keys(asRecord2(side.checks)).length ? asRecord2(side.checks) : asRecord2(profileSummary.checks);
|
|
19416
|
+
pages.push({
|
|
19417
|
+
route: firstStringValue2(asRecord2(side.target).url, side.source, profileSummary.profile_name, side.profile_name) || sideName,
|
|
19418
|
+
passed: numberValue3(checks.passed) ?? 0,
|
|
19419
|
+
failed: numberValue3(checks.failed) ?? 0
|
|
19420
|
+
});
|
|
19421
|
+
}
|
|
19422
|
+
return pages;
|
|
18819
19423
|
}
|
|
18820
|
-
function
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18833
|
-
|
|
18834
|
-
|
|
19424
|
+
function summarizeExplicitChecks(value) {
|
|
19425
|
+
let passed = 0;
|
|
19426
|
+
let failed = 0;
|
|
19427
|
+
const visit = (current, inChecks = false) => {
|
|
19428
|
+
if (current === true && inChecks) {
|
|
19429
|
+
passed += 1;
|
|
19430
|
+
return;
|
|
19431
|
+
}
|
|
19432
|
+
if (current === false && inChecks) {
|
|
19433
|
+
failed += 1;
|
|
19434
|
+
return;
|
|
19435
|
+
}
|
|
19436
|
+
if (Array.isArray(current)) {
|
|
19437
|
+
for (const item of current) visit(item, inChecks);
|
|
19438
|
+
return;
|
|
19439
|
+
}
|
|
19440
|
+
if (current && typeof current === "object") {
|
|
19441
|
+
for (const [key, item] of Object.entries(current)) {
|
|
19442
|
+
visit(item, inChecks || key === "checks");
|
|
19443
|
+
}
|
|
19444
|
+
}
|
|
18835
19445
|
};
|
|
19446
|
+
visit(value);
|
|
19447
|
+
return { passed, failed };
|
|
18836
19448
|
}
|
|
18837
|
-
function
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
if (status === "configuration_error") return "configuration_error";
|
|
18841
|
-
if (status === "needs_human_review") return "needs_human_review";
|
|
18842
|
-
if (status === "proof_insufficient") return "proof_insufficient";
|
|
18843
|
-
return "not_mergeable";
|
|
19449
|
+
function selectPrimaryImage(artifacts) {
|
|
19450
|
+
const images = artifacts.filter((artifact) => artifact.kind === "image");
|
|
19451
|
+
return images.find((artifact) => /after|proof|screenshot/i.test(artifact.name)) || images[0];
|
|
18844
19452
|
}
|
|
18845
|
-
function
|
|
18846
|
-
const
|
|
18847
|
-
|
|
18848
|
-
|
|
18849
|
-
failed: 0,
|
|
18850
|
-
skipped: 0,
|
|
18851
|
-
needs_human_review: 0
|
|
18852
|
-
};
|
|
18853
|
-
for (const check of result.checks) {
|
|
18854
|
-
if (check.status === "passed") counts.passed += 1;
|
|
18855
|
-
if (check.status === "failed") counts.failed += 1;
|
|
18856
|
-
if (check.status === "skipped") counts.skipped += 1;
|
|
18857
|
-
if (check.status === "needs_human_review") counts.needs_human_review += 1;
|
|
19453
|
+
function firstRecordValue2(...values) {
|
|
19454
|
+
for (const value of values) {
|
|
19455
|
+
const record = asRecord2(value);
|
|
19456
|
+
if (Object.keys(record).length) return record;
|
|
18858
19457
|
}
|
|
18859
|
-
return
|
|
19458
|
+
return void 0;
|
|
18860
19459
|
}
|
|
18861
|
-
function
|
|
18862
|
-
const
|
|
18863
|
-
if (
|
|
18864
|
-
|
|
18865
|
-
|
|
19460
|
+
function checkpointSummaryFrom2(...values) {
|
|
19461
|
+
const record = firstRecordValue2(...values);
|
|
19462
|
+
if (!record) return void 0;
|
|
19463
|
+
const summary = {
|
|
19464
|
+
pending: booleanValue3(record.pending),
|
|
19465
|
+
response_count: numberValue3(record.response_count),
|
|
19466
|
+
rejected_response_count: numberValue3(record.rejected_response_count),
|
|
19467
|
+
ignored_response_count: numberValue3(record.ignored_response_count),
|
|
19468
|
+
duplicate_response_count: numberValue3(record.duplicate_response_count),
|
|
19469
|
+
latest_decision: stringValue4(record.latest_decision),
|
|
19470
|
+
latest_packet_id: stringValue4(record.latest_packet_id),
|
|
19471
|
+
latest_resume_token: stringValue4(record.latest_resume_token)
|
|
19472
|
+
};
|
|
19473
|
+
return Object.values(summary).some((value) => typeof value !== "undefined") ? summary : void 0;
|
|
19474
|
+
}
|
|
19475
|
+
function isProfileResult(result) {
|
|
19476
|
+
const version = stringValue4(result.version);
|
|
19477
|
+
return version === "riddle-proof.profile-result.v1" || version === "riddle-proof.profile-compact-result.v1";
|
|
19478
|
+
}
|
|
19479
|
+
function isChangeReceiptResult(result) {
|
|
19480
|
+
const version = stringValue4(result.version);
|
|
19481
|
+
return version === RIDDLE_PROOF_CHANGE_RECEIPT_V1_VERSION || version === RIDDLE_PROOF_CHANGE_RECEIPT_VERSION;
|
|
19482
|
+
}
|
|
19483
|
+
function isHandoffReceiptResult(result) {
|
|
19484
|
+
return stringValue4(result.version) === RIDDLE_PROOF_HANDOFF_RECEIPT_VERSION;
|
|
19485
|
+
}
|
|
19486
|
+
function isChangeResult(result) {
|
|
19487
|
+
const version = stringValue4(result.version);
|
|
19488
|
+
return version === "riddle-proof.change-result.v1" || version === "riddle-proof.change-compact-result.v1";
|
|
18866
19489
|
}
|
|
18867
|
-
function
|
|
19490
|
+
function summarizeRiddleProofPrComment(input) {
|
|
19491
|
+
const runResponse = asRecord2(input.runResponse);
|
|
19492
|
+
const inputResult = asRecord2(input.result);
|
|
19493
|
+
const handoffReceipt = isHandoffReceiptResult(inputResult) ? parseRiddleProofHandoffReceipt(inputResult) : void 0;
|
|
19494
|
+
const parsedChangeReceipt = handoffReceipt?.change || (isChangeReceiptResult(inputResult) ? parseRiddleProofChangeReceipt(inputResult) : void 0);
|
|
19495
|
+
const result = parsedChangeReceipt ? asRecord2(parsedChangeReceipt) : inputResult;
|
|
19496
|
+
const proofResult = asRecord2(runResponse.proofResult);
|
|
19497
|
+
const profileResult = isProfileResult(result);
|
|
19498
|
+
const changeReceiptResult = Boolean(parsedChangeReceipt);
|
|
19499
|
+
const changeResult = changeReceiptResult || isChangeResult(result);
|
|
19500
|
+
const profileRiddle = asRecord2(result.riddle);
|
|
19501
|
+
const preview = asRecord2(runResponse.preview);
|
|
19502
|
+
const resultRunCard = asRecord2(result.run_card);
|
|
19503
|
+
const stopCondition = asRecord2(resultRunCard.stop_condition);
|
|
19504
|
+
const resultDetails = asRecord2(result.details);
|
|
19505
|
+
const resultRaw = asRecord2(result.raw);
|
|
19506
|
+
const rawDetails = asRecord2(resultRaw.details);
|
|
19507
|
+
const profileChecks = profileResult ? profileCheckCounts3(result) : void 0;
|
|
19508
|
+
const changeChecks = changeResult ? changeReceiptCheckCounts(result) : void 0;
|
|
19509
|
+
const artifacts = mergeArtifacts(
|
|
19510
|
+
collectArtifacts(runResponse),
|
|
19511
|
+
profileResult ? collectProfileArtifacts(result) : [],
|
|
19512
|
+
changeReceiptResult ? collectChangeReceiptArtifacts(result) : []
|
|
19513
|
+
);
|
|
19514
|
+
const pages = changeReceiptResult ? changeReceiptPageSummaries(result) : profileResult ? profilePageSummaries(result, profileChecks) : pageSummaries(result);
|
|
19515
|
+
const checkSource = { ...result };
|
|
19516
|
+
delete checkSource.ok;
|
|
19517
|
+
const nestedChecks = summarizeExplicitChecks(checkSource);
|
|
19518
|
+
const resultStatus = firstStringValue2(result.status, stopCondition.status);
|
|
19519
|
+
const ok = changeReceiptResult ? parsedChangeReceipt?.recommendation.merge_recommended ?? false : changeResult ? resultStatus === "passed" : booleanValue3(result.ok) ?? booleanValue3(runResponse.ok) ?? null;
|
|
19520
|
+
const checkpointSummary = checkpointSummaryFrom2(
|
|
19521
|
+
result.checkpoint_summary,
|
|
19522
|
+
stopCondition.checkpoint_summary,
|
|
19523
|
+
resultDetails.checkpoint_summary,
|
|
19524
|
+
rawDetails.checkpoint_summary,
|
|
19525
|
+
proofResult.checkpoint_summary
|
|
19526
|
+
);
|
|
19527
|
+
const publicState = changeReceiptResult ? void 0 : summarizeRiddleProofPublicState({
|
|
19528
|
+
...result,
|
|
19529
|
+
status: resultStatus,
|
|
19530
|
+
checkpoint_summary: checkpointSummary || result.checkpoint_summary
|
|
19531
|
+
});
|
|
19532
|
+
const mergeRecommendation = publicState ? riddleProofPublicStateMergeRecommendation(
|
|
19533
|
+
publicState,
|
|
19534
|
+
firstStringValue2(result.merge_recommendation, stopCondition.merge_recommendation, resultRaw.merge_recommendation)
|
|
19535
|
+
) : void 0;
|
|
18868
19536
|
return {
|
|
18869
|
-
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
|
|
19537
|
+
ok,
|
|
19538
|
+
status: firstStringValue2(proofResult.status, profileRiddle.status),
|
|
19539
|
+
result_status: changeResult ? resultStatus : publicState?.status,
|
|
19540
|
+
job_id: firstStringValue2(proofResult.job_id, profileRiddle.job_id),
|
|
19541
|
+
duration_ms: numberValue3(proofResult.duration_ms) ?? numberValue3(profileRiddle.elapsed_ms),
|
|
19542
|
+
proof_url: stringValue4(runResponse.proofUrl),
|
|
19543
|
+
preview_id: stringValue4(preview.id),
|
|
19544
|
+
preview_url: stringValue4(preview.preview_url) || stringValue4(preview.url),
|
|
19545
|
+
preview_publish_recovered: booleanValue3(preview.publish_recovered),
|
|
19546
|
+
preview_publish_error: stringValue4(preview.publish_error),
|
|
19547
|
+
ship_held: publicState?.ship_held,
|
|
19548
|
+
shipping_disabled: publicState?.shipping_disabled,
|
|
19549
|
+
ship_authorized: changeReceiptResult ? parsedChangeReceipt?.shipping_authorization.authorized : publicState?.ship_authorized,
|
|
19550
|
+
merge_ready: publicState?.merge_ready,
|
|
19551
|
+
sync_allowed: publicState?.sync_allowed,
|
|
19552
|
+
proof_decision: firstStringValue2(result.proof_decision, stopCondition.proof_decision, resultRaw.proof_decision),
|
|
19553
|
+
merge_recommendation: changeReceiptResult ? parsedChangeReceipt?.recommendation.label : mergeRecommendation,
|
|
19554
|
+
checkpoint_summary: checkpointSummary,
|
|
19555
|
+
public_state: publicState,
|
|
19556
|
+
passed_checks: changeChecks?.passed ?? profileChecks?.passed ?? nestedChecks.passed,
|
|
19557
|
+
failed_checks: changeChecks?.failed ?? profileChecks?.failed ?? nestedChecks.failed,
|
|
19558
|
+
pages,
|
|
19559
|
+
artifacts,
|
|
19560
|
+
primary_image: selectPrimaryImage(artifacts)
|
|
18875
19561
|
};
|
|
18876
19562
|
}
|
|
18877
|
-
function
|
|
18878
|
-
|
|
19563
|
+
function formatDuration(ms) {
|
|
19564
|
+
if (typeof ms !== "number" || !Number.isFinite(ms)) return "";
|
|
19565
|
+
const seconds = Math.max(0, Math.round(ms / 1e3));
|
|
19566
|
+
const minutes = Math.floor(seconds / 60);
|
|
19567
|
+
const remainder = seconds % 60;
|
|
19568
|
+
return minutes > 0 ? `${minutes}m${String(remainder).padStart(2, "0")}s` : `${seconds}s`;
|
|
18879
19569
|
}
|
|
18880
|
-
function
|
|
18881
|
-
|
|
18882
|
-
if (!screenshotName) return false;
|
|
18883
|
-
return [artifact.name, artifact.url, artifact.path].map(comparableArtifactName).some((name) => name === screenshotName || name.endsWith(`-${screenshotName}`));
|
|
19570
|
+
function markdownLink2(label, url) {
|
|
19571
|
+
return `[${label.replace(/\]/g, "\\]")}](${url})`;
|
|
18884
19572
|
}
|
|
18885
|
-
function
|
|
18886
|
-
|
|
18887
|
-
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
if (
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
};
|
|
18894
|
-
for (const ref of result.artifacts.riddle_artifacts || []) {
|
|
18895
|
-
add(receiptArtifactFromRef(side, ref));
|
|
18896
|
-
}
|
|
18897
|
-
for (const screenshot of result.artifacts.screenshots || []) {
|
|
18898
|
-
if (artifacts.some((artifact) => artifactMatchesScreenshotLabel(artifact, screenshot))) continue;
|
|
18899
|
-
add({
|
|
18900
|
-
side,
|
|
18901
|
-
name: screenshot,
|
|
18902
|
-
kind: "image"
|
|
18903
|
-
});
|
|
19573
|
+
function resultLabel(summary) {
|
|
19574
|
+
if (summary.public_state?.result_label) return summary.public_state.result_label;
|
|
19575
|
+
if (summary.ok === true) {
|
|
19576
|
+
if (summary.result_status === "shipped") return "shipped";
|
|
19577
|
+
if (summary.result_status === "completed") return "completed";
|
|
19578
|
+
if (summary.ship_held === true) return "proof passed; ship held";
|
|
19579
|
+
if (summary.ship_authorized === true) return "passed; ship authorized";
|
|
19580
|
+
return "passed";
|
|
18904
19581
|
}
|
|
18905
|
-
return
|
|
19582
|
+
if (summary.ok === false) return "failed";
|
|
19583
|
+
return summary.result_status || summary.status || "recorded";
|
|
18906
19584
|
}
|
|
18907
|
-
function
|
|
18908
|
-
|
|
18909
|
-
|
|
19585
|
+
function artifactRank(artifact) {
|
|
19586
|
+
const name = artifact.name.toLowerCase();
|
|
19587
|
+
if (name === "proof.json") return 0;
|
|
19588
|
+
if (name === "result.json") return 1;
|
|
19589
|
+
if (name.includes("proof") && name.endsWith(".json") && !name.includes("layout")) return 2;
|
|
19590
|
+
if (name === "console.json") return 3;
|
|
19591
|
+
if (artifact.kind === "data") return 10;
|
|
19592
|
+
if (artifact.kind === "image") return 20;
|
|
19593
|
+
return 30;
|
|
18910
19594
|
}
|
|
18911
|
-
function
|
|
18912
|
-
|
|
18913
|
-
return {
|
|
18914
|
-
side,
|
|
18915
|
-
source,
|
|
18916
|
-
profile_name: result.profile_name,
|
|
18917
|
-
status: result.status,
|
|
18918
|
-
summary: result.summary,
|
|
18919
|
-
route: result.route,
|
|
18920
|
-
captured_at: result.captured_at,
|
|
18921
|
-
checks: profileCheckCounts2(result),
|
|
18922
|
-
screenshots: artifacts.filter(artifactIsScreenshot),
|
|
18923
|
-
artifacts
|
|
18924
|
-
};
|
|
19595
|
+
function formatBool(value) {
|
|
19596
|
+
return typeof value === "boolean" ? String(value) : "unknown";
|
|
18925
19597
|
}
|
|
18926
|
-
function
|
|
18927
|
-
|
|
18928
|
-
if (!Array.isArray(value)) return [];
|
|
18929
|
-
return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
19598
|
+
function hasShipControl(summary) {
|
|
19599
|
+
return typeof summary.ship_held === "boolean" || typeof summary.shipping_disabled === "boolean" || typeof summary.ship_authorized === "boolean";
|
|
18930
19600
|
}
|
|
18931
|
-
function
|
|
18932
|
-
return
|
|
18933
|
-
version: RIDDLE_PROOF_CHANGE_RECEIPT_VERSION,
|
|
18934
|
-
contract_name: input.result.contract_name,
|
|
18935
|
-
profile_name: input.profile_name,
|
|
18936
|
-
status: input.result.status,
|
|
18937
|
-
verdict: changeReceiptVerdict(input.result.status),
|
|
18938
|
-
summary: input.result.summary,
|
|
18939
|
-
before: receiptSide("before", input.before_source, input.before_result),
|
|
18940
|
-
after: receiptSide("after", input.after_source, input.after_result),
|
|
18941
|
-
deltas: input.result.deltas.map((delta) => ({
|
|
18942
|
-
type: delta.type,
|
|
18943
|
-
label: delta.label,
|
|
18944
|
-
status: delta.status,
|
|
18945
|
-
before_observed: delta.before_observed,
|
|
18946
|
-
after_observed: delta.after_observed,
|
|
18947
|
-
message: delta.message
|
|
18948
|
-
})),
|
|
18949
|
-
proves: metadataStringList(input.contract.metadata, "required_receipts"),
|
|
18950
|
-
does_not_prove: metadataStringList(input.contract.metadata, "does_not_prove"),
|
|
18951
|
-
metadata: input.result.metadata
|
|
18952
|
-
};
|
|
19601
|
+
function hasHandoffControl(summary) {
|
|
19602
|
+
return typeof summary.merge_ready === "boolean" || typeof summary.sync_allowed === "boolean";
|
|
18953
19603
|
}
|
|
18954
|
-
function
|
|
19604
|
+
function shouldRenderMergeRecommendation(summary) {
|
|
19605
|
+
if (!summary.merge_recommendation) return false;
|
|
19606
|
+
return riddleProofPublicStateAllowsMergeRecommendation(summary.public_state);
|
|
19607
|
+
}
|
|
19608
|
+
function checkpointSummaryLine(summary) {
|
|
19609
|
+
const accepted = summary.response_count ?? 0;
|
|
19610
|
+
const rejected = summary.rejected_response_count ?? 0;
|
|
19611
|
+
const ignored = summary.ignored_response_count ?? 0;
|
|
19612
|
+
const parts = [`${accepted} accepted`, `${rejected} rejected`, `${ignored} ignored`];
|
|
19613
|
+
if ((summary.duplicate_response_count ?? 0) > 0) parts.push(`${summary.duplicate_response_count} duplicate`);
|
|
19614
|
+
const state = summary.pending === true ? "pending" : summary.pending === false ? "complete" : "";
|
|
19615
|
+
return [
|
|
19616
|
+
parts.join(" / "),
|
|
19617
|
+
state,
|
|
19618
|
+
summary.latest_decision ? `latest decision \`${summary.latest_decision}\`` : ""
|
|
19619
|
+
].filter(Boolean).join("; ");
|
|
19620
|
+
}
|
|
19621
|
+
function markdownTableValue(value) {
|
|
18955
19622
|
return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
18956
19623
|
}
|
|
18957
|
-
function
|
|
18958
|
-
return artifact
|
|
19624
|
+
function observationArtifactTarget(artifact) {
|
|
19625
|
+
return artifact?.url || artifact?.path;
|
|
18959
19626
|
}
|
|
18960
|
-
function
|
|
18961
|
-
|
|
19627
|
+
function canonicalScreenshotCell(artifact, fallback) {
|
|
19628
|
+
const target = observationArtifactTarget(artifact);
|
|
19629
|
+
if (!artifact || !target) return fallback;
|
|
19630
|
+
if (artifact.url) return `![${artifact.name.replace(/\]/g, "\\]")}](${artifact.url})`;
|
|
19631
|
+
return markdownTableValue(artifact.path || artifact.name);
|
|
18962
19632
|
}
|
|
18963
|
-
function
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
19633
|
+
function shortRevision(value) {
|
|
19634
|
+
return value ? value.slice(0, 12) : "not recorded";
|
|
19635
|
+
}
|
|
19636
|
+
function buildRiddleProofHandoffPrCommentMarkdown(handoff, input = {}) {
|
|
19637
|
+
const receipt = parseRiddleProofHandoffReceipt(handoff);
|
|
19638
|
+
const change = receipt.change;
|
|
19639
|
+
const beforeStatus = change.before.profile_summary?.status || "not recorded";
|
|
19640
|
+
const afterStatus = change.after.profile_summary?.status || "not recorded";
|
|
19641
|
+
const beforeBaseline = change.groups.before.ok ? "matched baseline" : "baseline mismatch";
|
|
19642
|
+
const lines = [
|
|
19643
|
+
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
19644
|
+
`## ${input.title?.trim() || "Riddle Change Proof"}`,
|
|
19645
|
+
"",
|
|
19646
|
+
`**Result:** ${receipt.recommendation.label}`,
|
|
19647
|
+
`**Evidence verdict:** \`${receipt.verdict}\``,
|
|
19648
|
+
`**Shipping authorization:** ${receipt.shipping_authorization.status}`,
|
|
19649
|
+
`**Contract:** ${change.contract_name}`
|
|
19650
|
+
];
|
|
19651
|
+
if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
|
|
19652
|
+
if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
|
|
19653
|
+
lines.push("", change.summary, "", "### Canonical Before / After", "");
|
|
19654
|
+
lines.push("| Before | After |");
|
|
19655
|
+
lines.push("| --- | --- |");
|
|
19656
|
+
lines.push(`| ${beforeBaseline}: \`${markdownTableValue(beforeStatus)}\` | \`${markdownTableValue(afterStatus)}\` |`);
|
|
19657
|
+
lines.push(`| ${canonicalScreenshotCell(receipt.canonical_pair.before, "No canonical before screenshot")} | ${canonicalScreenshotCell(receipt.canonical_pair.after, "No canonical after screenshot")} |`);
|
|
19658
|
+
lines.push("", "### Source Binding", "");
|
|
19659
|
+
lines.push("| Side | Target | Git revision | Binding | Preview | Digest |");
|
|
19660
|
+
lines.push("| --- | --- | --- | --- | --- | --- |");
|
|
19661
|
+
for (const side of ["before", "after"]) {
|
|
19662
|
+
const observation = change[side];
|
|
19663
|
+
const binding = change.source_bindings[side];
|
|
19664
|
+
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")}\` |`);
|
|
19665
|
+
}
|
|
19666
|
+
lines.push("", "### Declared Delta", "");
|
|
19667
|
+
lines.push("| Measurement | Status | Before | After |");
|
|
19668
|
+
lines.push("| --- | --- | --- | --- |");
|
|
19669
|
+
for (const delta of change.deltas) {
|
|
19670
|
+
lines.push(`| ${markdownTableValue(delta.label)} | ${delta.status} | ${markdownTableValue(delta.before_observed)} | ${markdownTableValue(delta.after_observed)} |`);
|
|
19671
|
+
}
|
|
19672
|
+
if (change.proves.length) {
|
|
19673
|
+
lines.push("", "### What This Proves", "");
|
|
19674
|
+
for (const claim of change.proves) lines.push(`- ${claim}`);
|
|
19675
|
+
}
|
|
19676
|
+
if (change.does_not_prove.length) {
|
|
19677
|
+
lines.push("", "### Limits", "");
|
|
19678
|
+
for (const claim of change.does_not_prove) lines.push(`- ${claim}`);
|
|
19679
|
+
}
|
|
19680
|
+
const linkedArtifacts = [
|
|
19681
|
+
...change.before.artifacts.map((artifact) => ({ side: "before", artifact })),
|
|
19682
|
+
...change.after.artifacts.map((artifact) => ({ side: "after", artifact }))
|
|
19683
|
+
].filter(({ artifact }) => Boolean(artifact.url));
|
|
19684
|
+
if (linkedArtifacts.length) {
|
|
19685
|
+
lines.push("", "### Artifacts", "");
|
|
19686
|
+
for (const { side, artifact } of linkedArtifacts.slice(0, 24)) {
|
|
19687
|
+
lines.push(`- ${side}: ${markdownLink2(artifact.name, artifact.url || "")}`);
|
|
18977
19688
|
}
|
|
18978
19689
|
}
|
|
18979
|
-
|
|
18980
|
-
lines.
|
|
19690
|
+
lines.push("", input.source?.trim() ? `_Source: ${input.source.trim()}_` : "_Rendered from the Handoff receipt without recomputing its verdict._");
|
|
19691
|
+
return `${lines.join("\n").trim()}
|
|
19692
|
+
`;
|
|
18981
19693
|
}
|
|
18982
|
-
function
|
|
19694
|
+
function buildRiddleProofPrCommentMarkdown(input) {
|
|
19695
|
+
const result = asRecord2(input.result);
|
|
19696
|
+
if (isHandoffReceiptResult(result)) {
|
|
19697
|
+
return buildRiddleProofHandoffPrCommentMarkdown(parseRiddleProofHandoffReceipt(result), input);
|
|
19698
|
+
}
|
|
19699
|
+
if (isChangeReceiptResult(result)) {
|
|
19700
|
+
const handoff = createRiddleProofHandoffReceipt(parseRiddleProofChangeReceipt(result));
|
|
19701
|
+
return buildRiddleProofHandoffPrCommentMarkdown(handoff, input);
|
|
19702
|
+
}
|
|
19703
|
+
const summary = summarizeRiddleProofPrComment(input);
|
|
19704
|
+
const title = input.title?.trim() || "Riddle Proof Evidence";
|
|
18983
19705
|
const lines = [
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
`**Verdict:** ${receipt.verdict}`,
|
|
18987
|
-
`**Status:** ${receipt.status}`,
|
|
18988
|
-
`**Contract:** ${receipt.contract_name}`,
|
|
18989
|
-
receipt.profile_name ? `**Profile:** ${receipt.profile_name}` : "",
|
|
18990
|
-
"",
|
|
18991
|
-
receipt.summary,
|
|
18992
|
-
"",
|
|
18993
|
-
"## Evidence Pair",
|
|
18994
|
-
"",
|
|
18995
|
-
"| Side | Source | Status | Checks |",
|
|
18996
|
-
"| --- | --- | --- | --- |",
|
|
18997
|
-
`| Before | ${markdownTableCell(receipt.before.source)} | ${receipt.before.status} | ${receipt.before.checks.passed} passed / ${receipt.before.checks.failed} failed |`,
|
|
18998
|
-
`| After | ${markdownTableCell(receipt.after.source)} | ${receipt.after.status} | ${receipt.after.checks.passed} passed / ${receipt.after.checks.failed} failed |`,
|
|
18999
|
-
"",
|
|
19000
|
-
"## Delta Checks",
|
|
19706
|
+
RIDDLE_PROOF_PR_COMMENT_MARKER,
|
|
19707
|
+
`## ${title}`,
|
|
19001
19708
|
"",
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19709
|
+
`**Result:** ${resultLabel(summary)}`
|
|
19710
|
+
];
|
|
19711
|
+
if (input.goal?.trim()) lines.push(`**Goal:** ${input.goal.trim()}`);
|
|
19712
|
+
if (input.successCriteria?.trim()) lines.push(`**Success criteria:** ${input.successCriteria.trim()}`);
|
|
19713
|
+
if (summary.result_status) lines.push(`**Evidence status:** ${summary.result_status}`);
|
|
19714
|
+
if (summary.status) lines.push(`**Riddle job status:** ${summary.status}`);
|
|
19715
|
+
if (summary.job_id) lines.push(`**Riddle job:** \`${summary.job_id}\``);
|
|
19716
|
+
if (summary.duration_ms) lines.push(`**Duration:** ${formatDuration(summary.duration_ms)}`);
|
|
19717
|
+
if (hasShipControl(summary)) {
|
|
19718
|
+
lines.push(`**Ship control:** held=${formatBool(summary.ship_held)}, shipping_disabled=${formatBool(summary.shipping_disabled)}, authorized=${formatBool(summary.ship_authorized)}`);
|
|
19719
|
+
}
|
|
19720
|
+
if (hasHandoffControl(summary)) {
|
|
19721
|
+
lines.push(`**Handoff:** merge_ready=${formatBool(summary.merge_ready)}, sync_allowed=${formatBool(summary.sync_allowed)}`);
|
|
19722
|
+
}
|
|
19723
|
+
if (summary.proof_decision) lines.push(`**Proof decision:** \`${summary.proof_decision}\``);
|
|
19724
|
+
if (shouldRenderMergeRecommendation(summary)) lines.push(`**Merge recommendation:** ${summary.merge_recommendation}`);
|
|
19725
|
+
if (summary.checkpoint_summary) lines.push(`**Checkpoints:** ${checkpointSummaryLine(summary.checkpoint_summary)}`);
|
|
19726
|
+
if (summary.proof_url) lines.push(`**Proof URL:** ${markdownLink2(summary.proof_url, summary.proof_url)}`);
|
|
19727
|
+
if (summary.preview_id || summary.preview_url) {
|
|
19728
|
+
const previewLabel = summary.preview_id ? `\`${summary.preview_id}\`` : "preview";
|
|
19729
|
+
lines.push(`**Preview:** ${summary.preview_url ? markdownLink2(previewLabel, summary.preview_url) : previewLabel}`);
|
|
19730
|
+
}
|
|
19731
|
+
if (summary.preview_publish_recovered) {
|
|
19732
|
+
const detail = summary.preview_publish_error ? `: ${summary.preview_publish_error}` : "";
|
|
19733
|
+
lines.push(`**Preview publish recovery:** recovered after publish error${detail}`);
|
|
19008
19734
|
}
|
|
19735
|
+
lines.push(`**Checks:** ${summary.passed_checks} passed / ${summary.failed_checks} failed`);
|
|
19009
19736
|
lines.push("");
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
19013
|
-
lines.push("## What This Proves", "");
|
|
19014
|
-
for (const claim of receipt.proves) lines.push(`- ${claim}`);
|
|
19737
|
+
if (summary.primary_image) {
|
|
19738
|
+
lines.push("### Screenshot");
|
|
19739
|
+
lines.push(``);
|
|
19015
19740
|
lines.push("");
|
|
19016
19741
|
}
|
|
19017
|
-
if (
|
|
19018
|
-
lines.push("
|
|
19019
|
-
for (const
|
|
19742
|
+
if (summary.pages.length) {
|
|
19743
|
+
lines.push("### Page Checks");
|
|
19744
|
+
for (const page of summary.pages.slice(0, 12)) {
|
|
19745
|
+
lines.push(`- \`${page.route}\`: ${page.passed} passed / ${page.failed} failed`);
|
|
19746
|
+
}
|
|
19747
|
+
if (summary.pages.length > 12) lines.push(`- ${summary.pages.length - 12} more page(s) omitted`);
|
|
19020
19748
|
lines.push("");
|
|
19021
19749
|
}
|
|
19022
|
-
const linkedArtifacts =
|
|
19750
|
+
const linkedArtifacts = summary.artifacts.filter((artifact) => artifact.url !== summary.primary_image?.url).sort((left, right) => artifactRank(left) - artifactRank(right) || left.name.localeCompare(right.name)).slice(0, 20);
|
|
19023
19751
|
if (linkedArtifacts.length) {
|
|
19024
|
-
lines.push("
|
|
19752
|
+
lines.push("### Artifacts");
|
|
19025
19753
|
for (const artifact of linkedArtifacts) {
|
|
19026
|
-
lines.push(`- ${
|
|
19754
|
+
lines.push(`- ${markdownLink2(artifact.name, artifact.url)}`);
|
|
19755
|
+
}
|
|
19756
|
+
if (summary.artifacts.length - (summary.primary_image ? 1 : 0) > linkedArtifacts.length) {
|
|
19757
|
+
lines.push(`- ${summary.artifacts.length - (summary.primary_image ? 1 : 0) - linkedArtifacts.length} more artifact(s) omitted`);
|
|
19027
19758
|
}
|
|
19028
19759
|
lines.push("");
|
|
19029
19760
|
}
|
|
19761
|
+
if (input.source?.trim()) {
|
|
19762
|
+
lines.push(`_Source: ${input.source.trim()}_`);
|
|
19763
|
+
} else {
|
|
19764
|
+
lines.push("_Updated by `riddle-proof-loop pr-comment`._");
|
|
19765
|
+
}
|
|
19030
19766
|
return `${lines.join("\n").trim()}
|
|
19031
19767
|
`;
|
|
19032
19768
|
}
|
|
19033
|
-
|
|
19034
|
-
|
|
19769
|
+
|
|
19770
|
+
// src/profile-suggestions.ts
|
|
19771
|
+
var RIDDLE_PROOF_PROFILE_SUGGESTIONS_VERSION = "riddle-proof.profile-suggestions.v1";
|
|
19772
|
+
var UI_FILE_PATTERN = /\.(css|html?|jsx?|tsx?|vue|svelte|mdx)$/i;
|
|
19773
|
+
var VISUAL_ASSET_PATTERN = /\.(avif|gif|jpe?g|png|svg|webp)$/i;
|
|
19774
|
+
function nonEmptyStrings(values) {
|
|
19775
|
+
return Array.from(new Set(
|
|
19776
|
+
(values || []).filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
|
|
19777
|
+
));
|
|
19778
|
+
}
|
|
19779
|
+
function normalizeRoute(value) {
|
|
19780
|
+
if (!value?.trim()) return void 0;
|
|
19781
|
+
const trimmed = value.trim();
|
|
19782
|
+
if (/^https?:\/\//i.test(trimmed)) return void 0;
|
|
19783
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
19784
|
+
}
|
|
19785
|
+
function routeFromUrl(value) {
|
|
19786
|
+
if (!value?.trim()) return void 0;
|
|
19787
|
+
try {
|
|
19788
|
+
const url = new URL(value);
|
|
19789
|
+
return normalizeRoute(`${url.pathname}${url.search || ""}`);
|
|
19790
|
+
} catch {
|
|
19791
|
+
return void 0;
|
|
19792
|
+
}
|
|
19793
|
+
}
|
|
19794
|
+
function slugify(value) {
|
|
19795
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "suggested-profile";
|
|
19796
|
+
}
|
|
19797
|
+
function inferRouteFromFile(file) {
|
|
19798
|
+
const normalized = file.replace(/\\/g, "/");
|
|
19799
|
+
const routeMatch = normalized.match(/(?:^|\/)(?:pages|routes|app)\/(.+?)(?:\.[^.\/]+)?$/);
|
|
19800
|
+
if (!routeMatch) return void 0;
|
|
19801
|
+
const route = routeMatch[1].replace(/\/index$/i, "").replace(/\/page$/i, "").replace(/\[[^/\]]+\]/g, ":param");
|
|
19802
|
+
return normalizeRoute(route || "/");
|
|
19803
|
+
}
|
|
19804
|
+
function inferRouteFromFiles(files) {
|
|
19805
|
+
for (const file of files) {
|
|
19806
|
+
const route = inferRouteFromFile(file);
|
|
19807
|
+
if (route) return route;
|
|
19808
|
+
}
|
|
19809
|
+
return void 0;
|
|
19810
|
+
}
|
|
19811
|
+
function uniqueChecks(checks) {
|
|
19812
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19813
|
+
return checks.filter((check) => {
|
|
19814
|
+
const key = JSON.stringify(check);
|
|
19815
|
+
if (seen.has(key)) return false;
|
|
19816
|
+
seen.add(key);
|
|
19817
|
+
return true;
|
|
19818
|
+
});
|
|
19819
|
+
}
|
|
19820
|
+
function changedTextEntryText(entry) {
|
|
19821
|
+
return typeof entry === "string" ? entry.trim() : (entry.expected_text || entry.text || "").trim();
|
|
19822
|
+
}
|
|
19823
|
+
function changedTextEntrySelector(entry) {
|
|
19824
|
+
return typeof entry === "string" ? void 0 : entry.selector?.trim() || void 0;
|
|
19035
19825
|
}
|
|
19036
|
-
function
|
|
19037
|
-
|
|
19038
|
-
if (!target) return `<p>${escapeHtml(artifact.name)}</p>`;
|
|
19039
|
-
if (artifact.kind === "image") {
|
|
19040
|
-
return `<figure><img src="${escapeHtml(target)}" alt="${escapeHtml(artifact.name)}"><figcaption>${escapeHtml(artifact.name)}</figcaption></figure>`;
|
|
19041
|
-
}
|
|
19042
|
-
return `<p><a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a></p>`;
|
|
19826
|
+
function changedTextEntryLabel(entry) {
|
|
19827
|
+
return typeof entry === "string" ? void 0 : entry.label?.trim() || void 0;
|
|
19043
19828
|
}
|
|
19044
|
-
function
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
return `<a href="${escapeHtml(target)}">${escapeHtml(artifact.name)}</a>`;
|
|
19829
|
+
function suggestionProfileName(input, route, files) {
|
|
19830
|
+
if (input.name?.trim()) return input.name.trim();
|
|
19831
|
+
return `suggested-${slugify(route || files[0] || "profile")}`;
|
|
19048
19832
|
}
|
|
19049
|
-
function
|
|
19050
|
-
|
|
19051
|
-
|
|
19833
|
+
function defaultViewports(includeMobile) {
|
|
19834
|
+
const viewports = [
|
|
19835
|
+
{ name: "desktop", width: 1280, height: 800 }
|
|
19836
|
+
];
|
|
19837
|
+
if (includeMobile !== false) {
|
|
19838
|
+
viewports.push({ name: "mobile", width: 390, height: 844 });
|
|
19839
|
+
}
|
|
19840
|
+
return viewports;
|
|
19052
19841
|
}
|
|
19053
|
-
function
|
|
19054
|
-
const
|
|
19055
|
-
const
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
</body>
|
|
19150
|
-
</html>
|
|
19151
|
-
`;
|
|
19842
|
+
function suggestRiddleProofProfileChecks(input) {
|
|
19843
|
+
const changedFiles = nonEmptyStrings(input.changed_files);
|
|
19844
|
+
const selectors = nonEmptyStrings(input.selectors);
|
|
19845
|
+
const changedText = input.changed_text || [];
|
|
19846
|
+
const route = normalizeRoute(input.route) || inferRouteFromFiles(changedFiles) || routeFromUrl(input.url);
|
|
19847
|
+
const uiFiles = changedFiles.filter((file) => UI_FILE_PATTERN.test(file));
|
|
19848
|
+
const visualAssets = changedFiles.filter((file) => VISUAL_ASSET_PATTERN.test(file));
|
|
19849
|
+
const warnings = [];
|
|
19850
|
+
const suggestions = [];
|
|
19851
|
+
const setupActions = [];
|
|
19852
|
+
if (!route && !input.url?.trim()) {
|
|
19853
|
+
warnings.push("No route or URL was supplied; the draft profile defaults to route /.");
|
|
19854
|
+
}
|
|
19855
|
+
if ((uiFiles.length || visualAssets.length) && !changedText.length && !selectors.length) {
|
|
19856
|
+
warnings.push("UI or visual files changed, but no changed text or selectors were supplied; add focused checks before trusting this profile.");
|
|
19857
|
+
}
|
|
19858
|
+
const routeOrDefault = route || "/";
|
|
19859
|
+
const routeChecks = [
|
|
19860
|
+
{ type: "route_loaded", expected_path: routeOrDefault }
|
|
19861
|
+
];
|
|
19862
|
+
suggestions.push({
|
|
19863
|
+
id: "route-loaded",
|
|
19864
|
+
reason: "Every browser proof should first prove that the intended route loaded.",
|
|
19865
|
+
checks: routeChecks
|
|
19866
|
+
});
|
|
19867
|
+
const hygieneChecks = [
|
|
19868
|
+
{ type: "no_fatal_console_errors" },
|
|
19869
|
+
{ type: "no_mobile_horizontal_overflow" }
|
|
19870
|
+
];
|
|
19871
|
+
suggestions.push({
|
|
19872
|
+
id: "runtime-hygiene",
|
|
19873
|
+
reason: "UI changes should not introduce fatal console errors or mobile overflow.",
|
|
19874
|
+
checks: hygieneChecks
|
|
19875
|
+
});
|
|
19876
|
+
for (const selector of selectors) {
|
|
19877
|
+
suggestions.push({
|
|
19878
|
+
id: `selector-${slugify(selector)}`,
|
|
19879
|
+
reason: `Changed selector ${selector} should remain visible.`,
|
|
19880
|
+
checks: [{ type: "selector_visible", selector }]
|
|
19881
|
+
});
|
|
19882
|
+
}
|
|
19883
|
+
for (const entry of changedText) {
|
|
19884
|
+
const text = changedTextEntryText(entry);
|
|
19885
|
+
if (!text) continue;
|
|
19886
|
+
const selector = changedTextEntrySelector(entry);
|
|
19887
|
+
const label = changedTextEntryLabel(entry);
|
|
19888
|
+
const check = selector ? { type: "selector_text_visible", selector, text, label } : { type: "text_visible", text, label };
|
|
19889
|
+
suggestions.push({
|
|
19890
|
+
id: `text-${slugify(`${selector || "page"}-${text}`)}`,
|
|
19891
|
+
reason: selector ? `Expected text should be visible inside ${selector}.` : "Expected text should be visible on the route.",
|
|
19892
|
+
checks: [check]
|
|
19893
|
+
});
|
|
19894
|
+
}
|
|
19895
|
+
if (input.include_screenshot !== false && (uiFiles.length || visualAssets.length || selectors.length || changedText.length)) {
|
|
19896
|
+
setupActions.push({ type: "screenshot", label: "suggested-proof-screenshot", full_page: true });
|
|
19897
|
+
suggestions.push({
|
|
19898
|
+
id: "screenshot-artifact",
|
|
19899
|
+
reason: "A screenshot artifact keeps the proof packet inspectable by humans and hosted renderers.",
|
|
19900
|
+
checks: [],
|
|
19901
|
+
setup_actions: setupActions
|
|
19902
|
+
});
|
|
19903
|
+
}
|
|
19904
|
+
const checks = uniqueChecks(suggestions.flatMap((suggestion) => suggestion.checks));
|
|
19905
|
+
const target = {
|
|
19906
|
+
...input.url?.trim() ? { url: input.url.trim() } : { route: routeOrDefault },
|
|
19907
|
+
viewports: defaultViewports(input.include_mobile),
|
|
19908
|
+
setup_actions: setupActions.length ? setupActions : void 0
|
|
19909
|
+
};
|
|
19910
|
+
const profile = {
|
|
19911
|
+
version: RIDDLE_PROOF_PROFILE_VERSION,
|
|
19912
|
+
name: suggestionProfileName(input, routeOrDefault, changedFiles),
|
|
19913
|
+
target,
|
|
19914
|
+
checks,
|
|
19915
|
+
artifacts: ["screenshot", "console", "proof_json"],
|
|
19916
|
+
baseline_policy: "invariant_only",
|
|
19917
|
+
failure_policy: {
|
|
19918
|
+
product_regression: "fail",
|
|
19919
|
+
proof_insufficient: "review",
|
|
19920
|
+
environment_blocked: "neutral",
|
|
19921
|
+
configuration_error: "fail",
|
|
19922
|
+
needs_human_review: "review"
|
|
19923
|
+
},
|
|
19924
|
+
metadata: {
|
|
19925
|
+
suggestion_input: {
|
|
19926
|
+
changed_files: changedFiles,
|
|
19927
|
+
selectors,
|
|
19928
|
+
changed_text_count: changedText.length
|
|
19929
|
+
}
|
|
19930
|
+
}
|
|
19931
|
+
};
|
|
19932
|
+
return {
|
|
19933
|
+
version: RIDDLE_PROOF_PROFILE_SUGGESTIONS_VERSION,
|
|
19934
|
+
profile,
|
|
19935
|
+
suggestions,
|
|
19936
|
+
warnings
|
|
19937
|
+
};
|
|
19152
19938
|
}
|
|
19153
19939
|
|
|
19154
19940
|
// src/cli.ts
|
|
@@ -19163,8 +19949,14 @@ var KNOWN_CLI_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
19163
19949
|
"balancePreflight",
|
|
19164
19950
|
"baseUrl",
|
|
19165
19951
|
"afterResult",
|
|
19952
|
+
"afterObservation",
|
|
19953
|
+
"afterPreviewReceipt",
|
|
19954
|
+
"afterSourceRevision",
|
|
19166
19955
|
"afterUrl",
|
|
19167
19956
|
"beforeResult",
|
|
19957
|
+
"beforeObservation",
|
|
19958
|
+
"beforePreviewReceipt",
|
|
19959
|
+
"beforeSourceRevision",
|
|
19168
19960
|
"beforeUrl",
|
|
19169
19961
|
"candidateJson",
|
|
19170
19962
|
"candidatesJson",
|
|
@@ -19218,6 +20010,7 @@ var KNOWN_CLI_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
19218
20010
|
"pack",
|
|
19219
20011
|
"packFile",
|
|
19220
20012
|
"profile",
|
|
20013
|
+
"previewReceipt",
|
|
19221
20014
|
"proofDir",
|
|
19222
20015
|
"pr",
|
|
19223
20016
|
"progressEveryMs",
|
|
@@ -19241,6 +20034,9 @@ var KNOWN_CLI_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
19241
20034
|
"scriptFile",
|
|
19242
20035
|
"selectors",
|
|
19243
20036
|
"sourceKind",
|
|
20037
|
+
"sourceDirty",
|
|
20038
|
+
"sourceRepository",
|
|
20039
|
+
"sourceRevision",
|
|
19244
20040
|
"splitViewports",
|
|
19245
20041
|
"stateDir",
|
|
19246
20042
|
"statePath",
|
|
@@ -19272,16 +20068,16 @@ function usage() {
|
|
|
19272
20068
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
19273
20069
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
19274
20070
|
" riddle-proof-loop status --state-path <path>",
|
|
19275
|
-
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--
|
|
20071
|
+
" riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--preview-receipt <file|json>] [--source-revision <sha>] [--runner riddle] [--viewport-name <name[,name...]>] [--strict true|false; default false] [--split-viewports true|false; default false] [--balance-preflight true|false; default true] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json] [--quiet]",
|
|
19276
20072
|
" riddle-proof-loop run-profile aggregate --profile <file|json|-> --url <base-url> [--base-url <base-url>] --input-dir <dir>|--inputs <path[,path...]> [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
19277
20073
|
" riddle-proof-loop run-profile recover --profile <file|json|-> --url <base-url> [--base-url <base-url>] --job <job-id> [--viewport-name <name[,name...]>] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
19278
|
-
" riddle-proof-loop run-change-proof --profile <file|json|-> --change-contract <file|json|-> (--before-url <url> --after-url <url> | --before-result <file> --after-result <file>) [--viewport-name <name[,name...]>] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
20074
|
+
" riddle-proof-loop run-change-proof --profile <file|json|-> --change-contract <file|json|-> (--before-url <url> --after-url <url> | --before-result <file> --after-result <file> | --before-observation <file> --after-observation <file>) [--before-preview-receipt <file|json>] [--after-preview-receipt <file|json>] [--before-source-revision <sha>] [--after-source-revision <sha>] [--viewport-name <name[,name...]>] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
19279
20075
|
" riddle-proof-loop profile-suggest --route /path|--url <url> [--changed-files a,b] [--selectors .a,.b] [--changed-text-json <file|json|->] [--format json|profile]",
|
|
19280
20076
|
" riddle-proof-loop regression-pack run [--pack oc-flow-regression|--pack-file <file>] [--local-core true|false; default true] [--hosted-riddle true|false; default false] [--format json|markdown|compact-json; default json] [--output <dir>|--output-dir <dir>]",
|
|
19281
20077
|
" riddle-proof-loop pr-comment --proof-dir <dir>|--run-response <file> [--result-json <file>] --pr <number|url> [--repo owner/name] [--dry-run] [--body-file <file>] [--comment-mode update|append]",
|
|
19282
20078
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
19283
20079
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
19284
|
-
" riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static] [--quiet]",
|
|
20080
|
+
" riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static] [--output <dir>] [--quiet]",
|
|
19285
20081
|
" riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
|
|
19286
20082
|
" riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720] [--strict true|false]",
|
|
19287
20083
|
" riddle-proof-loop riddle-poll <job-id> [--wait] [--attempts n] [--quiet]",
|
|
@@ -19441,6 +20237,7 @@ function compactRunProfileResult(result, options) {
|
|
|
19441
20237
|
output_dir: outputDir,
|
|
19442
20238
|
output_files: outputDir ? {
|
|
19443
20239
|
profile_result: "profile-result.json",
|
|
20240
|
+
observation_receipt: "observation-receipt.json",
|
|
19444
20241
|
summary: "summary.md",
|
|
19445
20242
|
proof_json: result.evidence ? "proof.json" : void 0,
|
|
19446
20243
|
console: result.evidence?.console ? "console.json" : void 0,
|
|
@@ -20069,12 +20866,13 @@ function formatByteCount(bytes) {
|
|
|
20069
20866
|
}
|
|
20070
20867
|
function riddlePollProgressLine(snapshot) {
|
|
20071
20868
|
const submittedAt = snapshot.submitted_at || "not-submitted";
|
|
20072
|
-
const queuePart = snapshot.running_without_submission ? `
|
|
20869
|
+
const queuePart = snapshot.active_execution ? `${snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : ""} active_execution=${formatPollDuration(snapshot.elapsed_ms)}` : snapshot.running_without_submission ? ` waiting_for_worker_claim=${formatPollDuration(snapshot.pre_submission_elapsed_ms)} worker_wake=possible${snapshot.queue_elapsed_ms !== null ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : ""}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
|
|
20073
20870
|
const terminalPart = snapshot.terminal ? " terminal=true" : "";
|
|
20074
20871
|
return [
|
|
20075
20872
|
"[riddle-poll]",
|
|
20076
20873
|
snapshot.job_id,
|
|
20077
20874
|
`status=${snapshot.status || "unknown"}`,
|
|
20875
|
+
`phase=${snapshot.phase || "unknown"}`,
|
|
20078
20876
|
`attempt=${snapshot.attempt}/${snapshot.attempts}`,
|
|
20079
20877
|
`elapsed=${formatPollDuration(snapshot.elapsed_ms)}`,
|
|
20080
20878
|
`submitted_at=${submittedAt}${queuePart}${terminalPart}`
|
|
@@ -23012,10 +23810,63 @@ function profileHttpStatusSummaryMarkdown(result) {
|
|
|
23012
23810
|
}
|
|
23013
23811
|
return lines;
|
|
23014
23812
|
}
|
|
23015
|
-
function
|
|
23813
|
+
function profileResultTargetUrl(result) {
|
|
23814
|
+
const legacyRoute = result.route;
|
|
23815
|
+
const observed = result.route.observed;
|
|
23816
|
+
const requested = result.route.requested || legacyRoute.url || result.evidence?.target_url;
|
|
23817
|
+
if (observed) {
|
|
23818
|
+
try {
|
|
23819
|
+
return new URL(observed, requested).href;
|
|
23820
|
+
} catch {
|
|
23821
|
+
}
|
|
23822
|
+
}
|
|
23823
|
+
if (requested) {
|
|
23824
|
+
try {
|
|
23825
|
+
return new URL(requested).href;
|
|
23826
|
+
} catch {
|
|
23827
|
+
return requested;
|
|
23828
|
+
}
|
|
23829
|
+
}
|
|
23830
|
+
return observed || "unknown";
|
|
23831
|
+
}
|
|
23832
|
+
function profileObservationForOutput(result, outputDir, options) {
|
|
23833
|
+
if (options.observation) return options.observation;
|
|
23834
|
+
const preview = options.previewReceipt;
|
|
23835
|
+
const targetUrl = profileResultTargetUrl(result);
|
|
23836
|
+
return createRiddleProofObservationReceipt({
|
|
23837
|
+
comparison_role: options.comparisonRole || "standalone",
|
|
23838
|
+
executor: result.runner === "local-playwright" ? { kind: "local_playwright", runner: result.runner } : {
|
|
23839
|
+
kind: "riddle_hosted",
|
|
23840
|
+
runner: result.runner,
|
|
23841
|
+
job_id: result.riddle?.job_id,
|
|
23842
|
+
worker_id: typeof result.riddle?.execution?.worker_id === "string" ? result.riddle.execution.worker_id : void 0
|
|
23843
|
+
},
|
|
23844
|
+
target: preview ? { kind: "preview", url: targetUrl, preview } : { kind: "url", url: targetUrl },
|
|
23845
|
+
source: options.source || preview?.source,
|
|
23846
|
+
profile_result: result,
|
|
23847
|
+
publication: { kind: "local", path: outputDir },
|
|
23848
|
+
execution: result.riddle?.execution
|
|
23849
|
+
});
|
|
23850
|
+
}
|
|
23851
|
+
function profileOutputOptionsForCli(options) {
|
|
23852
|
+
const previewInput = optionString(options, "previewReceipt");
|
|
23853
|
+
const previewReceipt = previewInput ? parseRiddlePreviewReceipt(readJsonValue(previewInput, "--preview-receipt")) : void 0;
|
|
23854
|
+
const sourceDirty = optionBoolean(options, "sourceDirty");
|
|
23855
|
+
const source = {
|
|
23856
|
+
...previewReceipt?.source || {},
|
|
23857
|
+
...optionString(options, "sourceRevision") ? { git_revision: optionString(options, "sourceRevision") } : {},
|
|
23858
|
+
...optionString(options, "sourceRepository") ? { repository: optionString(options, "sourceRepository") } : {},
|
|
23859
|
+
...typeof sourceDirty === "boolean" ? { dirty: sourceDirty } : {}
|
|
23860
|
+
};
|
|
23861
|
+
return { previewReceipt, source };
|
|
23862
|
+
}
|
|
23863
|
+
function writeProfileOutput(outputDir, result, options = {}) {
|
|
23016
23864
|
if (!outputDir) return;
|
|
23017
23865
|
(0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
23018
23866
|
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "profile-result.json"), `${JSON.stringify(result, null, 2)}
|
|
23867
|
+
`);
|
|
23868
|
+
const observation = profileObservationForOutput(result, outputDir, options);
|
|
23869
|
+
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "observation-receipt.json"), `${JSON.stringify(observation, null, 2)}
|
|
23019
23870
|
`);
|
|
23020
23871
|
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "summary.md"), profileResultMarkdown(result));
|
|
23021
23872
|
if (result.evidence) (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "proof.json"), `${JSON.stringify(result, null, 2)}
|
|
@@ -23056,6 +23907,12 @@ function profileOptionsForChangeProofSide(options, url, outputDir) {
|
|
|
23056
23907
|
}
|
|
23057
23908
|
return next;
|
|
23058
23909
|
}
|
|
23910
|
+
function observationForChangeSide(observation, side) {
|
|
23911
|
+
if (observation.comparison_role !== "standalone" && observation.comparison_role !== side) {
|
|
23912
|
+
throw new Error(`${side} observation has incompatible comparison_role ${observation.comparison_role}.`);
|
|
23913
|
+
}
|
|
23914
|
+
return observation.comparison_role === side ? observation : { ...observation, comparison_role: side };
|
|
23915
|
+
}
|
|
23059
23916
|
function compactChangeProofResult(run, options) {
|
|
23060
23917
|
const outputDir = profileOutputDirOption(options);
|
|
23061
23918
|
return {
|
|
@@ -23091,9 +23948,13 @@ function compactChangeProofResult(run, options) {
|
|
|
23091
23948
|
change_proof_receipt: "change-proof-receipt.json",
|
|
23092
23949
|
change_proof_receipt_markdown: "change-proof-receipt.md",
|
|
23093
23950
|
change_proof_receipt_html: "change-proof-receipt.html",
|
|
23951
|
+
handoff_receipt: "handoff-receipt.json",
|
|
23952
|
+
handoff_receipt_markdown: "handoff-receipt.md",
|
|
23094
23953
|
summary: "summary.md",
|
|
23095
23954
|
before_profile_result: "before/profile-result.json",
|
|
23096
|
-
after_profile_result: "after/profile-result.json"
|
|
23955
|
+
after_profile_result: "after/profile-result.json",
|
|
23956
|
+
before_observation_receipt: "before/observation-receipt.json",
|
|
23957
|
+
after_observation_receipt: "after/observation-receipt.json"
|
|
23097
23958
|
} : void 0
|
|
23098
23959
|
};
|
|
23099
23960
|
}
|
|
@@ -23109,9 +23970,12 @@ function writeChangeProofOutput(outputDir, run) {
|
|
|
23109
23970
|
`);
|
|
23110
23971
|
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "change-proof-receipt.md"), riddleProofChangeReceiptMarkdown(run.receipt));
|
|
23111
23972
|
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "change-proof-receipt.html"), riddleProofChangeReceiptHtml(run.receipt));
|
|
23973
|
+
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "handoff-receipt.json"), `${JSON.stringify(run.handoff, null, 2)}
|
|
23974
|
+
`);
|
|
23975
|
+
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "handoff-receipt.md"), buildRiddleProofPrCommentMarkdown({ result: run.handoff }));
|
|
23112
23976
|
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "summary.md"), changeProofResultMarkdown(run));
|
|
23113
|
-
writeProfileOutput(changeProofSideOutputDir(outputDir, "before"), run.beforeResult);
|
|
23114
|
-
writeProfileOutput(changeProofSideOutputDir(outputDir, "after"), run.afterResult);
|
|
23977
|
+
writeProfileOutput(changeProofSideOutputDir(outputDir, "before"), run.beforeResult, { observation: run.beforeObservation });
|
|
23978
|
+
writeProfileOutput(changeProofSideOutputDir(outputDir, "after"), run.afterResult, { observation: run.afterObservation });
|
|
23115
23979
|
}
|
|
23116
23980
|
function writeRunChangeProofResult(run, options) {
|
|
23117
23981
|
const format = runProfileResultFormatOption(options);
|
|
@@ -23210,6 +24074,7 @@ function withRiddleMetadata(result, input) {
|
|
|
23210
24074
|
...result.riddle || {},
|
|
23211
24075
|
job_id: input.job_id || result.riddle?.job_id,
|
|
23212
24076
|
status: input.status ?? result.riddle?.status,
|
|
24077
|
+
phase: poll?.phase ?? result.riddle?.phase,
|
|
23213
24078
|
terminal: input.terminal ?? result.riddle?.terminal,
|
|
23214
24079
|
created_at: poll?.created_at ?? result.riddle?.created_at,
|
|
23215
24080
|
submitted_at: poll?.submitted_at ?? result.riddle?.submitted_at,
|
|
@@ -23222,7 +24087,8 @@ function withRiddleMetadata(result, input) {
|
|
|
23222
24087
|
timed_out: poll?.timed_out ?? result.riddle?.timed_out,
|
|
23223
24088
|
retry_count: input.retryCount ?? result.riddle?.retry_count,
|
|
23224
24089
|
stale_job_ids: staleJobIds?.length ? staleJobIds : result.riddle?.stale_job_ids,
|
|
23225
|
-
artifact_recovery: input.artifactRecovery ?? result.riddle?.artifact_recovery
|
|
24090
|
+
artifact_recovery: input.artifactRecovery ?? result.riddle?.artifact_recovery,
|
|
24091
|
+
execution: poll?.execution ?? result.riddle?.execution
|
|
23226
24092
|
},
|
|
23227
24093
|
artifacts: {
|
|
23228
24094
|
...result.artifacts,
|
|
@@ -23290,6 +24156,7 @@ function riddleMetadataFromPoll(jobId, poll) {
|
|
|
23290
24156
|
return {
|
|
23291
24157
|
job_id: jobId,
|
|
23292
24158
|
status: poll.status,
|
|
24159
|
+
phase: poll.poll?.phase,
|
|
23293
24160
|
terminal: poll.terminal,
|
|
23294
24161
|
created_at: poll.poll?.created_at,
|
|
23295
24162
|
submitted_at: poll.poll?.submitted_at,
|
|
@@ -23299,7 +24166,8 @@ function riddleMetadataFromPoll(jobId, poll) {
|
|
|
23299
24166
|
elapsed_ms: poll.poll?.elapsed_ms,
|
|
23300
24167
|
attempt: poll.poll?.attempt,
|
|
23301
24168
|
attempts: poll.poll?.attempts,
|
|
23302
|
-
timed_out: poll.poll?.timed_out
|
|
24169
|
+
timed_out: poll.poll?.timed_out,
|
|
24170
|
+
execution: poll.poll?.execution
|
|
23303
24171
|
};
|
|
23304
24172
|
}
|
|
23305
24173
|
function profileUnsubmittedRetryTimeoutMs(options) {
|
|
@@ -23845,7 +24713,7 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
23845
24713
|
const childProfile = profileForSplitViewport(profile, viewport);
|
|
23846
24714
|
const childOutputDir = outputDir ? splitViewportOutputDir(outputDir, viewport.name, seenOutputNames) : void 0;
|
|
23847
24715
|
const result2 = await runSingleRiddleProfileForCli(childProfile, options, { ...input, outputDir: childOutputDir });
|
|
23848
|
-
if (childOutputDir) writeProfileOutput(childOutputDir, result2);
|
|
24716
|
+
if (childOutputDir) writeProfileOutput(childOutputDir, result2, profileOutputOptionsForCli(options));
|
|
23849
24717
|
childRuns.push({ viewport, profile: childProfile, result: result2 });
|
|
23850
24718
|
}
|
|
23851
24719
|
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
@@ -23977,18 +24845,34 @@ async function runChangeProofForCli(rawProfile, options) {
|
|
|
23977
24845
|
const contract = readChangeContractForCli(options);
|
|
23978
24846
|
const beforeResultPath = optionString(options, "beforeResult");
|
|
23979
24847
|
const afterResultPath = optionString(options, "afterResult");
|
|
24848
|
+
const beforeObservationPath = optionString(options, "beforeObservation");
|
|
24849
|
+
const afterObservationPath = optionString(options, "afterObservation");
|
|
23980
24850
|
const beforeUrl = optionString(options, "beforeUrl");
|
|
23981
24851
|
const afterUrl = optionString(options, "afterUrl");
|
|
24852
|
+
const beforePreviewInput = optionString(options, "beforePreviewReceipt");
|
|
24853
|
+
const afterPreviewInput = optionString(options, "afterPreviewReceipt");
|
|
24854
|
+
const beforePreview = beforePreviewInput ? parseRiddlePreviewReceipt(readJsonValue(beforePreviewInput, "--before-preview-receipt")) : void 0;
|
|
24855
|
+
const afterPreview = afterPreviewInput ? parseRiddlePreviewReceipt(readJsonValue(afterPreviewInput, "--after-preview-receipt")) : void 0;
|
|
23982
24856
|
const hasResultInputs = Boolean(beforeResultPath || afterResultPath);
|
|
24857
|
+
const hasObservationInputs = Boolean(beforeObservationPath || afterObservationPath);
|
|
23983
24858
|
const hasUrlInputs = Boolean(beforeUrl || afterUrl);
|
|
23984
|
-
if (hasResultInputs
|
|
23985
|
-
throw new Error("run-change-proof accepts
|
|
24859
|
+
if ([hasResultInputs, hasObservationInputs, hasUrlInputs].filter(Boolean).length > 1) {
|
|
24860
|
+
throw new Error("run-change-proof accepts one before/after input pair: result, observation, or URL.");
|
|
23986
24861
|
}
|
|
23987
24862
|
if (hasResultInputs && (!beforeResultPath || !afterResultPath)) {
|
|
23988
24863
|
throw new Error("run-change-proof requires both --before-result and --after-result.");
|
|
23989
24864
|
}
|
|
23990
|
-
if (
|
|
23991
|
-
throw new Error("run-change-proof requires both --before-
|
|
24865
|
+
if (hasObservationInputs && (!beforeObservationPath || !afterObservationPath)) {
|
|
24866
|
+
throw new Error("run-change-proof requires both --before-observation and --after-observation.");
|
|
24867
|
+
}
|
|
24868
|
+
if (hasUrlInputs && (!beforeUrl || !afterUrl)) {
|
|
24869
|
+
throw new Error("run-change-proof requires both --before-url and --after-url.");
|
|
24870
|
+
}
|
|
24871
|
+
if (!hasResultInputs && !hasObservationInputs && !hasUrlInputs) {
|
|
24872
|
+
throw new Error("run-change-proof requires a before/after result, observation, or URL pair.");
|
|
24873
|
+
}
|
|
24874
|
+
if ((beforePreview || afterPreview) && !hasUrlInputs) {
|
|
24875
|
+
throw new Error("--before-preview-receipt and --after-preview-receipt require the URL input mode.");
|
|
23992
24876
|
}
|
|
23993
24877
|
const outputDir = profileOutputDirOption(options);
|
|
23994
24878
|
let profile;
|
|
@@ -23996,15 +24880,53 @@ async function runChangeProofForCli(rawProfile, options) {
|
|
|
23996
24880
|
let afterResult;
|
|
23997
24881
|
let beforeSource;
|
|
23998
24882
|
let afterSource;
|
|
23999
|
-
|
|
24883
|
+
let beforeObservation;
|
|
24884
|
+
let afterObservation;
|
|
24885
|
+
if (hasObservationInputs) {
|
|
24886
|
+
if (!beforeObservationPath || !afterObservationPath) {
|
|
24887
|
+
throw new Error("run-change-proof requires both --before-observation and --after-observation.");
|
|
24888
|
+
}
|
|
24889
|
+
profile = profileWithSelectedViewportNamesForCli(normalizeProfileRecordForCli(rawProfile, options), options);
|
|
24890
|
+
beforeObservation = observationForChangeSide(
|
|
24891
|
+
parseRiddleProofObservationReceipt(readJsonValue(beforeObservationPath, "--before-observation")),
|
|
24892
|
+
"before"
|
|
24893
|
+
);
|
|
24894
|
+
afterObservation = observationForChangeSide(
|
|
24895
|
+
parseRiddleProofObservationReceipt(readJsonValue(afterObservationPath, "--after-observation")),
|
|
24896
|
+
"after"
|
|
24897
|
+
);
|
|
24898
|
+
if (!beforeObservation.proof?.result || !afterObservation.proof?.result) {
|
|
24899
|
+
throw new Error("Change Proof observations must include their profile results.");
|
|
24900
|
+
}
|
|
24901
|
+
beforeResult = beforeObservation.proof.result;
|
|
24902
|
+
afterResult = afterObservation.proof.result;
|
|
24903
|
+
beforeSource = beforeObservation.target.url;
|
|
24904
|
+
afterSource = afterObservation.target.url;
|
|
24905
|
+
} else if (hasResultInputs) {
|
|
24000
24906
|
if (!beforeResultPath || !afterResultPath) {
|
|
24001
24907
|
throw new Error("run-change-proof requires both --before-result and --after-result.");
|
|
24002
24908
|
}
|
|
24003
24909
|
profile = profileWithSelectedViewportNamesForCli(normalizeProfileRecordForCli(rawProfile, options), options);
|
|
24004
24910
|
beforeResult = readProfileResultForCli(beforeResultPath, "--before-result");
|
|
24005
24911
|
afterResult = readProfileResultForCli(afterResultPath, "--after-result");
|
|
24006
|
-
beforeSource =
|
|
24007
|
-
afterSource =
|
|
24912
|
+
beforeSource = profileResultTargetUrl(beforeResult);
|
|
24913
|
+
afterSource = profileResultTargetUrl(afterResult);
|
|
24914
|
+
beforeObservation = createRiddleProofObservationReceipt({
|
|
24915
|
+
comparison_role: "before",
|
|
24916
|
+
executor: { kind: beforeResult.runner === "local-playwright" ? "local_playwright" : "riddle_hosted", runner: beforeResult.runner, job_id: beforeResult.riddle?.job_id },
|
|
24917
|
+
target: { kind: "url", url: beforeSource },
|
|
24918
|
+
profile_result: beforeResult,
|
|
24919
|
+
execution: beforeResult.riddle?.execution,
|
|
24920
|
+
publication: { kind: "local", path: beforeResultPath }
|
|
24921
|
+
});
|
|
24922
|
+
afterObservation = createRiddleProofObservationReceipt({
|
|
24923
|
+
comparison_role: "after",
|
|
24924
|
+
executor: { kind: afterResult.runner === "local-playwright" ? "local_playwright" : "riddle_hosted", runner: afterResult.runner, job_id: afterResult.riddle?.job_id },
|
|
24925
|
+
target: { kind: "url", url: afterSource },
|
|
24926
|
+
profile_result: afterResult,
|
|
24927
|
+
execution: afterResult.riddle?.execution,
|
|
24928
|
+
publication: { kind: "local", path: afterResultPath }
|
|
24929
|
+
});
|
|
24008
24930
|
} else {
|
|
24009
24931
|
if (!beforeUrl || !afterUrl) {
|
|
24010
24932
|
throw new Error("run-change-proof requires both --before-url and --after-url unless saved results are provided.");
|
|
@@ -24018,21 +24940,54 @@ async function runChangeProofForCli(rawProfile, options) {
|
|
|
24018
24940
|
afterResult = await runProfileForCli(afterProfile, afterOptions);
|
|
24019
24941
|
beforeSource = beforeUrl;
|
|
24020
24942
|
afterSource = afterUrl;
|
|
24943
|
+
beforeObservation = createRiddleProofObservationReceipt({
|
|
24944
|
+
comparison_role: "before",
|
|
24945
|
+
executor: { kind: "riddle_hosted", runner: beforeResult.runner, job_id: beforeResult.riddle?.job_id },
|
|
24946
|
+
target: beforePreview ? { kind: "preview", url: beforeSource, preview: beforePreview } : { kind: "url", url: beforeSource },
|
|
24947
|
+
source: beforePreview?.source,
|
|
24948
|
+
profile_result: beforeResult,
|
|
24949
|
+
execution: beforeResult.riddle?.execution
|
|
24950
|
+
});
|
|
24951
|
+
afterObservation = createRiddleProofObservationReceipt({
|
|
24952
|
+
comparison_role: "after",
|
|
24953
|
+
executor: { kind: "riddle_hosted", runner: afterResult.runner, job_id: afterResult.riddle?.job_id },
|
|
24954
|
+
target: afterPreview ? { kind: "preview", url: afterSource, preview: afterPreview } : { kind: "url", url: afterSource },
|
|
24955
|
+
source: afterPreview?.source,
|
|
24956
|
+
profile_result: afterResult,
|
|
24957
|
+
execution: afterResult.riddle?.execution
|
|
24958
|
+
});
|
|
24021
24959
|
}
|
|
24022
24960
|
const result = assessRiddleProofChange(contract, {
|
|
24023
24961
|
before_result: beforeResult,
|
|
24024
|
-
after_result: afterResult
|
|
24962
|
+
after_result: afterResult,
|
|
24963
|
+
before_observation: beforeObservation,
|
|
24964
|
+
after_observation: afterObservation,
|
|
24965
|
+
expected_source_revisions: {
|
|
24966
|
+
before: optionString(options, "beforeSourceRevision"),
|
|
24967
|
+
after: optionString(options, "afterSourceRevision")
|
|
24968
|
+
}
|
|
24025
24969
|
});
|
|
24026
24970
|
const receipt = createRiddleProofChangeReceipt({
|
|
24027
24971
|
contract,
|
|
24028
24972
|
result,
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
before_source: beforeSource,
|
|
24032
|
-
after_source: afterSource,
|
|
24973
|
+
before_observation: beforeObservation,
|
|
24974
|
+
after_observation: afterObservation,
|
|
24033
24975
|
profile_name: profile.name
|
|
24034
24976
|
});
|
|
24035
|
-
|
|
24977
|
+
const handoff = createRiddleProofHandoffReceipt(receipt);
|
|
24978
|
+
return {
|
|
24979
|
+
profile,
|
|
24980
|
+
contract,
|
|
24981
|
+
beforeResult,
|
|
24982
|
+
afterResult,
|
|
24983
|
+
result,
|
|
24984
|
+
receipt,
|
|
24985
|
+
handoff,
|
|
24986
|
+
beforeObservation,
|
|
24987
|
+
afterObservation,
|
|
24988
|
+
beforeSource,
|
|
24989
|
+
afterSource
|
|
24990
|
+
};
|
|
24036
24991
|
}
|
|
24037
24992
|
function requestForRun(options) {
|
|
24038
24993
|
const statePath = optionString(options, "statePath");
|
|
@@ -24084,7 +25039,7 @@ async function main() {
|
|
|
24084
25039
|
if (command === "run-profile") {
|
|
24085
25040
|
const profile = profileWithSelectedViewportNamesForCli(normalizeProfileForCli(options), options);
|
|
24086
25041
|
const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : positional[1] === "aggregate" ? await aggregateProfileResultsForCli(profile, options) : await runProfileForCli(profile, options);
|
|
24087
|
-
writeProfileOutput(profileOutputDirOption(options), result);
|
|
25042
|
+
writeProfileOutput(profileOutputDirOption(options), result, profileOutputOptionsForCli(options));
|
|
24088
25043
|
const diagnosticLine = profileCliDiagnosticLine(result);
|
|
24089
25044
|
if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
|
|
24090
25045
|
process.stderr.write(`${diagnosticLine}
|
|
@@ -24115,6 +25070,7 @@ async function main() {
|
|
|
24115
25070
|
const explicitResultJsonPath = optionString(options, "resultJson");
|
|
24116
25071
|
const runResponsePath = explicitRunResponsePath || defaultProofDirJsonPath(proofDir, "riddle-run-response.json");
|
|
24117
25072
|
const resultJsonPaths = explicitResultJsonPath ? [explicitResultJsonPath] : [
|
|
25073
|
+
defaultProofDirJsonPath(proofDir, "handoff-receipt.json"),
|
|
24118
25074
|
defaultProofDirJsonPath(proofDir, "result.json"),
|
|
24119
25075
|
defaultProofDirJsonPath(proofDir, "change-proof-receipt.json"),
|
|
24120
25076
|
defaultProofDirJsonPath(proofDir, "change-proof-result.json"),
|
|
@@ -24236,6 +25192,16 @@ async function main() {
|
|
|
24236
25192
|
};
|
|
24237
25193
|
}
|
|
24238
25194
|
const result = await createRiddleApiClient(clientConfig).deployPreview(buildDir, label, previewFrameworkOption(options));
|
|
25195
|
+
const outputDir = profileOutputDirOption(options);
|
|
25196
|
+
if (outputDir) {
|
|
25197
|
+
(0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
25198
|
+
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "preview-deploy-result.json"), `${JSON.stringify(result, null, 2)}
|
|
25199
|
+
`);
|
|
25200
|
+
if (result.receipt) {
|
|
25201
|
+
(0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "preview-receipt.json"), `${JSON.stringify(result.receipt, null, 2)}
|
|
25202
|
+
`);
|
|
25203
|
+
}
|
|
25204
|
+
}
|
|
24239
25205
|
for (const warning of result.warnings ?? []) {
|
|
24240
25206
|
process.stderr.write(`Warning: ${warning}
|
|
24241
25207
|
`);
|