@riddledc/riddle-proof 0.5.57 → 0.7.0

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/dist/cli.cjs CHANGED
@@ -2772,6 +2772,7 @@ var init_proof_run_engine = __esm({
2772
2772
 
2773
2773
  // src/cli.ts
2774
2774
  var import_node_fs6 = require("fs");
2775
+ var import_node_path6 = __toESM(require("path"), 1);
2775
2776
 
2776
2777
  // src/engine-harness.ts
2777
2778
  var import_node_child_process2 = require("child_process");
@@ -6631,6 +6632,778 @@ function createRiddleApiClient(config = {}) {
6631
6632
  };
6632
6633
  }
6633
6634
 
6635
+ // src/profile.ts
6636
+ var RIDDLE_PROOF_PROFILE_VERSION = "riddle-proof.profile.v1";
6637
+ var RIDDLE_PROOF_PROFILE_RESULT_VERSION = "riddle-proof.profile-result.v1";
6638
+ var RIDDLE_PROOF_PROFILE_STATUSES = [
6639
+ "passed",
6640
+ "product_regression",
6641
+ "proof_insufficient",
6642
+ "environment_blocked",
6643
+ "configuration_error",
6644
+ "needs_human_review"
6645
+ ];
6646
+ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
6647
+ "route_loaded",
6648
+ "selector_visible",
6649
+ "selector_count_at_least",
6650
+ "text_visible",
6651
+ "text_absent",
6652
+ "no_horizontal_overflow",
6653
+ "no_mobile_horizontal_overflow",
6654
+ "no_fatal_console_errors"
6655
+ ];
6656
+ var DEFAULT_VIEWPORTS = [
6657
+ { name: "desktop", width: 1280, height: 800 }
6658
+ ];
6659
+ function isRecord(value) {
6660
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6661
+ }
6662
+ function stringValue2(value) {
6663
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
6664
+ }
6665
+ function numberValue(value) {
6666
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
6667
+ }
6668
+ function jsonRecord(value) {
6669
+ if (!isRecord(value)) return void 0;
6670
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
6671
+ }
6672
+ function toJsonValue(value) {
6673
+ if (value === null || value === void 0) return null;
6674
+ if (typeof value === "string" || typeof value === "boolean") return value;
6675
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
6676
+ if (Array.isArray(value)) return value.map(toJsonValue);
6677
+ if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
6678
+ return String(value);
6679
+ }
6680
+ function normalizeName(value, fallback) {
6681
+ const name = stringValue2(value) || fallback;
6682
+ return name.replace(/\s+/g, " ").trim();
6683
+ }
6684
+ function slugifyRiddleProofProfileName(value) {
6685
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "profile";
6686
+ }
6687
+ function normalizeViewport(input, index) {
6688
+ if (!isRecord(input)) throw new Error(`target.viewports[${index}] must be an object.`);
6689
+ const width = numberValue(input.width);
6690
+ const height = numberValue(input.height);
6691
+ if (!width || !height || width < 100 || height < 100) {
6692
+ throw new Error(`target.viewports[${index}] requires numeric width and height >= 100.`);
6693
+ }
6694
+ return {
6695
+ name: normalizeName(input.name || input.label, `viewport-${index + 1}`),
6696
+ width: Math.round(width),
6697
+ height: Math.round(height)
6698
+ };
6699
+ }
6700
+ function normalizeViewports(value) {
6701
+ if (value === void 0) return [...DEFAULT_VIEWPORTS];
6702
+ if (!Array.isArray(value) || value.length === 0) throw new Error("target.viewports must be a non-empty array.");
6703
+ return value.map(normalizeViewport);
6704
+ }
6705
+ function isSupportedCheckType(value) {
6706
+ return RIDDLE_PROOF_PROFILE_CHECK_TYPES.includes(value);
6707
+ }
6708
+ function normalizeCheck(input, index) {
6709
+ if (!isRecord(input)) throw new Error(`checks[${index}] must be an object.`);
6710
+ const type = stringValue2(input.type);
6711
+ if (!type) throw new Error(`checks[${index}].type is required.`);
6712
+ if (!isSupportedCheckType(type)) {
6713
+ throw new Error(`checks[${index}].type ${type} is not supported. Supported checks: ${RIDDLE_PROOF_PROFILE_CHECK_TYPES.join(", ")}`);
6714
+ }
6715
+ if ((type === "selector_visible" || type === "selector_count_at_least") && !stringValue2(input.selector)) {
6716
+ throw new Error(`checks[${index}] ${type} requires selector.`);
6717
+ }
6718
+ if ((type === "text_visible" || type === "text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
6719
+ throw new Error(`checks[${index}] ${type} requires text or pattern.`);
6720
+ }
6721
+ if (type === "selector_count_at_least" && numberValue(input.min_count) === void 0) {
6722
+ throw new Error(`checks[${index}] selector_count_at_least requires min_count.`);
6723
+ }
6724
+ return {
6725
+ type,
6726
+ label: stringValue2(input.label),
6727
+ expected_path: stringValue2(input.expected_path),
6728
+ selector: stringValue2(input.selector),
6729
+ text: stringValue2(input.text),
6730
+ pattern: stringValue2(input.pattern),
6731
+ flags: stringValue2(input.flags),
6732
+ min_count: numberValue(input.min_count),
6733
+ max_overflow_px: numberValue(input.max_overflow_px)
6734
+ };
6735
+ }
6736
+ function normalizeFailurePolicy(input) {
6737
+ const defaults = {
6738
+ product_regression: "fail",
6739
+ proof_insufficient: "fail",
6740
+ environment_blocked: "neutral",
6741
+ configuration_error: "fail",
6742
+ needs_human_review: "fail"
6743
+ };
6744
+ if (!isRecord(input)) return defaults;
6745
+ const next = { ...defaults };
6746
+ for (const [key, value] of Object.entries(input)) {
6747
+ if (!RIDDLE_PROOF_PROFILE_STATUSES.includes(key)) continue;
6748
+ if (value === "fail" || value === "neutral" || value === "review") {
6749
+ next[key] = value;
6750
+ }
6751
+ }
6752
+ return next;
6753
+ }
6754
+ function normalizeRiddleProofProfile(input, options = {}) {
6755
+ if (!isRecord(input)) throw new Error("profile must be a JSON object.");
6756
+ const version = stringValue2(input.version) || RIDDLE_PROOF_PROFILE_VERSION;
6757
+ if (version !== RIDDLE_PROOF_PROFILE_VERSION) {
6758
+ throw new Error(`Unsupported profile version ${version}. Expected ${RIDDLE_PROOF_PROFILE_VERSION}.`);
6759
+ }
6760
+ const targetInput = isRecord(input.target) ? input.target : {};
6761
+ const checks = Array.isArray(input.checks) ? input.checks.map(normalizeCheck) : [];
6762
+ if (!checks.length) throw new Error("profile.checks must contain at least one check.");
6763
+ const targetUrl = stringValue2(options.url) || stringValue2(targetInput.url);
6764
+ const route = stringValue2(options.route) || stringValue2(targetInput.route);
6765
+ if (!targetUrl && !route) throw new Error("profile.target requires url or route, or pass --url.");
6766
+ return {
6767
+ version: RIDDLE_PROOF_PROFILE_VERSION,
6768
+ name: normalizeName(input.name, "riddle-proof-profile"),
6769
+ target: {
6770
+ url: targetUrl,
6771
+ route,
6772
+ viewports: options.viewports?.length ? options.viewports : normalizeViewports(targetInput.viewports),
6773
+ auth: stringValue2(targetInput.auth) || "none",
6774
+ wait_for_selector: stringValue2(targetInput.wait_for_selector) || stringValue2(targetInput.waitForSelector),
6775
+ wait_ms: numberValue(targetInput.wait_ms) ?? numberValue(targetInput.waitMs)
6776
+ },
6777
+ checks,
6778
+ artifacts: Array.isArray(input.artifacts) ? input.artifacts.map((item) => String(item)).filter(Boolean) : ["screenshot", "console", "dom_summary", "proof_json"],
6779
+ baseline_policy: stringValue2(input.baseline_policy) || stringValue2(input.baselinePolicy) || "invariant_only",
6780
+ failure_policy: normalizeFailurePolicy(input.failure_policy || input.failurePolicy),
6781
+ metadata: jsonRecord(input.metadata)
6782
+ };
6783
+ }
6784
+ function resolveRiddleProofProfileTargetUrl(profile) {
6785
+ const route = profile.target.route || "";
6786
+ const targetUrl = profile.target.url || "";
6787
+ if (/^https?:\/\//i.test(route)) return route;
6788
+ if (targetUrl && route) return new URL(route, targetUrl).href;
6789
+ if (targetUrl) return targetUrl;
6790
+ throw new Error("profile target URL could not be resolved.");
6791
+ }
6792
+ function routeForViewport(viewport) {
6793
+ return viewport?.route || {
6794
+ requested: "",
6795
+ observed: "",
6796
+ matched: false,
6797
+ error: "missing viewport evidence"
6798
+ };
6799
+ }
6800
+ function checkLabel(check) {
6801
+ return check.label || check.type;
6802
+ }
6803
+ function selectorKey(check) {
6804
+ return check.selector || "";
6805
+ }
6806
+ function textKey(check) {
6807
+ return check.pattern ? `pattern:${check.pattern}/${check.flags || ""}` : `text:${check.text || ""}`;
6808
+ }
6809
+ function matchText(sample, check) {
6810
+ if (check.pattern) {
6811
+ try {
6812
+ return new RegExp(check.pattern, check.flags || "").test(sample);
6813
+ } catch {
6814
+ return false;
6815
+ }
6816
+ }
6817
+ return sample.includes(check.text || "");
6818
+ }
6819
+ function successfulRoute(route) {
6820
+ return route.matched && !route.error && (route.http_status === null || route.http_status === void 0 || route.http_status < 400);
6821
+ }
6822
+ function assessCheckFromEvidence(check, evidence) {
6823
+ const viewports = evidence.viewports || [];
6824
+ if (!viewports.length) {
6825
+ return {
6826
+ type: check.type,
6827
+ label: checkLabel(check),
6828
+ status: "failed",
6829
+ evidence: {},
6830
+ message: "No viewport evidence was captured."
6831
+ };
6832
+ }
6833
+ if (check.type === "route_loaded") {
6834
+ const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
6835
+ const failed = viewports.filter((viewport) => !successfulRoute({
6836
+ ...viewport.route,
6837
+ expected_path: expectedPath,
6838
+ matched: viewport.route.observed === expectedPath || viewport.route.matched
6839
+ }));
6840
+ return {
6841
+ type: check.type,
6842
+ label: checkLabel(check),
6843
+ status: failed.length ? "failed" : "passed",
6844
+ evidence: {
6845
+ expected_path: expectedPath,
6846
+ observed_paths: viewports.map((viewport) => viewport.route.observed),
6847
+ http_statuses: viewports.map((viewport) => viewport.route.http_status ?? null)
6848
+ },
6849
+ message: failed.length ? `Route did not load as ${expectedPath} in ${failed.length} viewport(s).` : void 0
6850
+ };
6851
+ }
6852
+ if (check.type === "selector_visible") {
6853
+ const key = selectorKey(check);
6854
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.visible_count || 0) < 1);
6855
+ return {
6856
+ type: check.type,
6857
+ label: checkLabel(check),
6858
+ status: failed.length ? "failed" : "passed",
6859
+ evidence: {
6860
+ selector: key,
6861
+ visible_counts: viewports.map((viewport) => viewport.selectors?.[key]?.visible_count || 0)
6862
+ },
6863
+ message: failed.length ? `Selector ${key} was not visible in ${failed.length} viewport(s).` : void 0
6864
+ };
6865
+ }
6866
+ if (check.type === "selector_count_at_least") {
6867
+ const key = selectorKey(check);
6868
+ const minCount = check.min_count ?? 1;
6869
+ const failed = viewports.filter((viewport) => (viewport.selectors?.[key]?.count || 0) < minCount);
6870
+ return {
6871
+ type: check.type,
6872
+ label: checkLabel(check),
6873
+ status: failed.length ? "failed" : "passed",
6874
+ evidence: {
6875
+ selector: key,
6876
+ min_count: minCount,
6877
+ counts: viewports.map((viewport) => viewport.selectors?.[key]?.count || 0)
6878
+ },
6879
+ message: failed.length ? `Selector ${key} count was below ${minCount} in ${failed.length} viewport(s).` : void 0
6880
+ };
6881
+ }
6882
+ if (check.type === "text_visible" || check.type === "text_absent") {
6883
+ const key = textKey(check);
6884
+ const expectedVisible = check.type === "text_visible";
6885
+ const matches = viewports.map((viewport) => {
6886
+ const fromEvidence = viewport.text_matches?.[key];
6887
+ return typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
6888
+ });
6889
+ const failed = matches.filter((matched) => matched !== expectedVisible).length;
6890
+ return {
6891
+ type: check.type,
6892
+ label: checkLabel(check),
6893
+ status: failed ? "failed" : "passed",
6894
+ evidence: {
6895
+ text: check.text || null,
6896
+ pattern: check.pattern || null,
6897
+ matches
6898
+ },
6899
+ message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
6900
+ };
6901
+ }
6902
+ if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
6903
+ const maxOverflow = check.max_overflow_px ?? 4;
6904
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
6905
+ if (!applicable.length) {
6906
+ return {
6907
+ type: check.type,
6908
+ label: checkLabel(check),
6909
+ status: "failed",
6910
+ evidence: { max_overflow_px: maxOverflow },
6911
+ message: "No applicable viewport evidence was captured for overflow check."
6912
+ };
6913
+ }
6914
+ const failed = applicable.filter((viewport) => (viewport.overflow_px ?? 0) > maxOverflow);
6915
+ return {
6916
+ type: check.type,
6917
+ label: checkLabel(check),
6918
+ status: failed.length ? "failed" : "passed",
6919
+ evidence: {
6920
+ max_overflow_px: maxOverflow,
6921
+ overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null),
6922
+ viewports: applicable.map((viewport) => viewport.name)
6923
+ },
6924
+ message: failed.length ? `Horizontal overflow exceeded ${maxOverflow}px in ${failed.length} viewport(s).` : void 0
6925
+ };
6926
+ }
6927
+ if (check.type === "no_fatal_console_errors") {
6928
+ const fatalCount = (evidence.console?.fatal_count || 0) + (evidence.page_errors?.length || 0);
6929
+ return {
6930
+ type: check.type,
6931
+ label: checkLabel(check),
6932
+ status: fatalCount ? "failed" : "passed",
6933
+ evidence: {
6934
+ console_fatal_count: evidence.console?.fatal_count || 0,
6935
+ page_error_count: evidence.page_errors?.length || 0
6936
+ },
6937
+ message: fatalCount ? `${fatalCount} fatal browser error(s) were captured.` : void 0
6938
+ };
6939
+ }
6940
+ return {
6941
+ type: check.type,
6942
+ label: checkLabel(check),
6943
+ status: "needs_human_review",
6944
+ evidence: {},
6945
+ message: "Unsupported check type."
6946
+ };
6947
+ }
6948
+ function profileStatusFromEvidence(evidence, checks) {
6949
+ if (!evidence) return "proof_insufficient";
6950
+ const viewports = evidence.viewports || [];
6951
+ if (!viewports.length || !checks.length) return "proof_insufficient";
6952
+ if (viewports.some((viewport) => viewport.navigation_error)) return "environment_blocked";
6953
+ if (checks.some((check) => check.status === "needs_human_review")) return "needs_human_review";
6954
+ if (checks.some((check) => check.status === "failed")) return "product_regression";
6955
+ return "passed";
6956
+ }
6957
+ function assessRiddleProofProfileEvidence(profile, evidence, options = {}) {
6958
+ const capturedAt = evidence?.captured_at || (/* @__PURE__ */ new Date()).toISOString();
6959
+ const checks = evidence ? profile.checks.map((check) => assessCheckFromEvidence(check, evidence)) : [];
6960
+ const status = profileStatusFromEvidence(evidence, checks);
6961
+ const firstViewport = evidence?.viewports?.[0];
6962
+ const screenshots = (evidence?.viewports || []).map((viewport) => viewport.screenshot_label).filter((label) => Boolean(label));
6963
+ return {
6964
+ version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
6965
+ profile_name: profile.name,
6966
+ runner: options.runner || "riddle",
6967
+ status,
6968
+ baseline_policy: profile.baseline_policy,
6969
+ route: routeForViewport(firstViewport),
6970
+ artifacts: {
6971
+ screenshots,
6972
+ console: "console.json",
6973
+ proof_json: "proof.json",
6974
+ dom_summary: "dom-summary.json",
6975
+ riddle_artifacts: options.artifacts
6976
+ },
6977
+ checks,
6978
+ summary: summarizeRiddleProofProfileResult({ profile_name: profile.name, status, checks, viewports: evidence?.viewports || [] }),
6979
+ captured_at: capturedAt,
6980
+ evidence,
6981
+ riddle: options.riddle
6982
+ };
6983
+ }
6984
+ function summarizeRiddleProofProfileResult(input) {
6985
+ const passedChecks = input.checks.filter((check) => check.status === "passed").length;
6986
+ const failedChecks = input.checks.filter((check) => check.status === "failed").length;
6987
+ const viewportNames = input.viewports.map((viewport) => viewport.name).join(", ");
6988
+ if (input.status === "passed") {
6989
+ return `${input.profile_name} passed ${passedChecks} check(s) across ${input.viewports.length} viewport(s)${viewportNames ? ` (${viewportNames})` : ""}.`;
6990
+ }
6991
+ if (input.status === "product_regression") {
6992
+ return `${input.profile_name} failed ${failedChecks} product invariant(s) across ${input.viewports.length} viewport(s).`;
6993
+ }
6994
+ if (input.status === "environment_blocked") {
6995
+ return `${input.profile_name} could not collect reliable evidence because navigation or the browser environment was blocked.`;
6996
+ }
6997
+ if (input.status === "proof_insufficient") {
6998
+ return `${input.profile_name} did not produce enough evidence for a profile judgment.`;
6999
+ }
7000
+ if (input.status === "configuration_error") {
7001
+ return `${input.profile_name} has a profile configuration error.`;
7002
+ }
7003
+ return `${input.profile_name} collected artifacts but needs human review.`;
7004
+ }
7005
+ function profileStatusExitCode(profile, status) {
7006
+ if (status === "passed") return 0;
7007
+ return profile.failure_policy[status] === "neutral" ? 0 : 1;
7008
+ }
7009
+ function createRiddleProofProfileEnvironmentBlockedResult(input) {
7010
+ const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "Riddle runner did not complete successfully.";
7011
+ return {
7012
+ version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
7013
+ profile_name: input.profile.name,
7014
+ runner: input.runner || "riddle",
7015
+ status: "environment_blocked",
7016
+ baseline_policy: input.profile.baseline_policy,
7017
+ route: { requested: resolveRiddleProofProfileTargetUrl(input.profile), observed: "", matched: false, error: message },
7018
+ artifacts: { screenshots: [], proof_json: "proof.json", riddle_artifacts: input.artifacts },
7019
+ checks: [],
7020
+ summary: `${input.profile.name} could not collect reliable evidence because the runner was blocked.`,
7021
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
7022
+ riddle: input.riddle,
7023
+ error: message
7024
+ };
7025
+ }
7026
+ function createRiddleProofProfileInsufficientResult(input) {
7027
+ const message = input.error instanceof Error ? input.error.message : input.error ? String(input.error) : "No proof.json profile result artifact was found.";
7028
+ return {
7029
+ version: RIDDLE_PROOF_PROFILE_RESULT_VERSION,
7030
+ profile_name: input.profile.name,
7031
+ runner: input.runner || "riddle",
7032
+ status: "proof_insufficient",
7033
+ baseline_policy: input.profile.baseline_policy,
7034
+ route: { requested: resolveRiddleProofProfileTargetUrl(input.profile), observed: "", matched: false, error: message },
7035
+ artifacts: { screenshots: [], proof_json: "proof.json", riddle_artifacts: input.artifacts },
7036
+ checks: [],
7037
+ summary: `${input.profile.name} did not produce enough evidence for a profile judgment.`,
7038
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
7039
+ riddle: input.riddle,
7040
+ error: message
7041
+ };
7042
+ }
7043
+ function runtimeScriptAssessmentSource() {
7044
+ return String.raw`
7045
+ function routeOk(route) {
7046
+ return Boolean(route && route.matched && !route.error && (route.http_status == null || route.http_status < 400));
7047
+ }
7048
+ function textMatches(sample, check) {
7049
+ if (check.pattern) {
7050
+ try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
7051
+ }
7052
+ return String(sample || "").includes(check.text || "");
7053
+ }
7054
+ function assessProfile(profile, evidence) {
7055
+ const checks = [];
7056
+ const viewports = evidence.viewports || [];
7057
+ for (const check of profile.checks || []) {
7058
+ if (check.type === "route_loaded") {
7059
+ const expectedPath = check.expected_path || new URL(evidence.target_url).pathname || "/";
7060
+ const failed = viewports.filter((viewport) => {
7061
+ const route = { ...(viewport.route || {}), expected_path: expectedPath };
7062
+ route.matched = route.observed === expectedPath || route.matched;
7063
+ return !routeOk(route);
7064
+ });
7065
+ checks.push({
7066
+ type: check.type,
7067
+ label: check.label || check.type,
7068
+ status: failed.length ? "failed" : "passed",
7069
+ evidence: {
7070
+ expected_path: expectedPath,
7071
+ observed_paths: viewports.map((viewport) => viewport.route && viewport.route.observed),
7072
+ http_statuses: viewports.map((viewport) => viewport.route ? viewport.route.http_status ?? null : null),
7073
+ },
7074
+ message: failed.length ? "Route did not load as " + expectedPath + " in " + failed.length + " viewport(s)." : undefined,
7075
+ });
7076
+ continue;
7077
+ }
7078
+ if (check.type === "selector_visible") {
7079
+ const selector = check.selector || "";
7080
+ const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].visible_count < 1);
7081
+ checks.push({
7082
+ type: check.type,
7083
+ label: check.label || check.type,
7084
+ status: failed.length ? "failed" : "passed",
7085
+ evidence: { selector, visible_counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].visible_count : 0) },
7086
+ message: failed.length ? "Selector " + selector + " was not visible in " + failed.length + " viewport(s)." : undefined,
7087
+ });
7088
+ continue;
7089
+ }
7090
+ if (check.type === "selector_count_at_least") {
7091
+ const selector = check.selector || "";
7092
+ const minCount = check.min_count == null ? 1 : check.min_count;
7093
+ const failed = viewports.filter((viewport) => !viewport.selectors || !viewport.selectors[selector] || viewport.selectors[selector].count < minCount);
7094
+ checks.push({
7095
+ type: check.type,
7096
+ label: check.label || check.type,
7097
+ status: failed.length ? "failed" : "passed",
7098
+ evidence: { selector, min_count: minCount, counts: viewports.map((viewport) => viewport.selectors && viewport.selectors[selector] ? viewport.selectors[selector].count : 0) },
7099
+ message: failed.length ? "Selector " + selector + " count was below " + minCount + " in " + failed.length + " viewport(s)." : undefined,
7100
+ });
7101
+ continue;
7102
+ }
7103
+ if (check.type === "text_visible" || check.type === "text_absent") {
7104
+ const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
7105
+ const expectedVisible = check.type === "text_visible";
7106
+ const matches = viewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
7107
+ const failed = matches.filter((matched) => matched !== expectedVisible).length;
7108
+ checks.push({
7109
+ type: check.type,
7110
+ label: check.label || check.type,
7111
+ status: failed ? "failed" : "passed",
7112
+ evidence: { text: check.text, pattern: check.pattern, matches },
7113
+ message: failed ? "Text assertion failed in " + failed + " viewport(s)." : undefined,
7114
+ });
7115
+ continue;
7116
+ }
7117
+ if (check.type === "no_horizontal_overflow" || check.type === "no_mobile_horizontal_overflow") {
7118
+ const maxOverflow = check.max_overflow_px == null ? 4 : check.max_overflow_px;
7119
+ const applicable = check.type === "no_mobile_horizontal_overflow" ? viewports.filter((viewport) => viewport.width <= 820) : viewports;
7120
+ if (!applicable.length) {
7121
+ checks.push({
7122
+ type: check.type,
7123
+ label: check.label || check.type,
7124
+ status: "failed",
7125
+ evidence: { max_overflow_px: maxOverflow },
7126
+ message: "No applicable viewport evidence was captured for overflow check.",
7127
+ });
7128
+ continue;
7129
+ }
7130
+ const failed = applicable.filter((viewport) => (viewport.overflow_px || 0) > maxOverflow);
7131
+ checks.push({
7132
+ type: check.type,
7133
+ label: check.label || check.type,
7134
+ status: failed.length ? "failed" : "passed",
7135
+ evidence: { max_overflow_px: maxOverflow, overflow_px: applicable.map((viewport) => viewport.overflow_px ?? null), viewports: applicable.map((viewport) => viewport.name) },
7136
+ message: failed.length ? "Horizontal overflow exceeded " + maxOverflow + "px in " + failed.length + " viewport(s)." : undefined,
7137
+ });
7138
+ continue;
7139
+ }
7140
+ if (check.type === "no_fatal_console_errors") {
7141
+ const fatalCount = ((evidence.console && evidence.console.fatal_count) || 0) + ((evidence.page_errors || []).length);
7142
+ checks.push({
7143
+ type: check.type,
7144
+ label: check.label || check.type,
7145
+ status: fatalCount ? "failed" : "passed",
7146
+ evidence: { console_fatal_count: (evidence.console && evidence.console.fatal_count) || 0, page_error_count: (evidence.page_errors || []).length },
7147
+ message: fatalCount ? String(fatalCount) + " fatal browser error(s) were captured." : undefined,
7148
+ });
7149
+ continue;
7150
+ }
7151
+ checks.push({ type: check.type, label: check.label || check.type, status: "needs_human_review", evidence: {}, message: "Unsupported check type." });
7152
+ }
7153
+ let status = "passed";
7154
+ if (!viewports.length || !checks.length) status = "proof_insufficient";
7155
+ else if (viewports.some((viewport) => viewport.navigation_error)) status = "environment_blocked";
7156
+ else if (checks.some((check) => check.status === "needs_human_review")) status = "needs_human_review";
7157
+ else if (checks.some((check) => check.status === "failed")) status = "product_regression";
7158
+ const screenshotLabels = viewports.map((viewport) => viewport.screenshot_label).filter(Boolean);
7159
+ const route = viewports[0] && viewports[0].route ? viewports[0].route : { requested: evidence.target_url, observed: "", matched: false, error: "missing viewport evidence" };
7160
+ const passedChecks = checks.filter((check) => check.status === "passed").length;
7161
+ const failedChecks = checks.filter((check) => check.status === "failed").length;
7162
+ const viewportNames = viewports.map((viewport) => viewport.name).join(", ");
7163
+ let summary = profile.name + " collected artifacts but needs human review.";
7164
+ if (status === "passed") summary = profile.name + " passed " + passedChecks + " check(s) across " + viewports.length + " viewport(s)" + (viewportNames ? " (" + viewportNames + ")." : ".");
7165
+ if (status === "product_regression") summary = profile.name + " failed " + failedChecks + " product invariant(s) across " + viewports.length + " viewport(s).";
7166
+ if (status === "environment_blocked") summary = profile.name + " could not collect reliable evidence because navigation or the browser environment was blocked.";
7167
+ if (status === "proof_insufficient") summary = profile.name + " did not produce enough evidence for a profile judgment.";
7168
+ return {
7169
+ version: "riddle-proof.profile-result.v1",
7170
+ profile_name: profile.name,
7171
+ runner: "riddle",
7172
+ status,
7173
+ baseline_policy: profile.baseline_policy || "invariant_only",
7174
+ route,
7175
+ artifacts: { screenshots: screenshotLabels, console: "console.json", proof_json: "proof.json", dom_summary: "dom-summary.json" },
7176
+ checks,
7177
+ summary,
7178
+ captured_at: evidence.captured_at,
7179
+ evidence,
7180
+ };
7181
+ }
7182
+ `;
7183
+ }
7184
+ function buildRiddleProofProfileScript(profile) {
7185
+ const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
7186
+ const slug = slugifyRiddleProofProfileName(profile.name);
7187
+ const serializableProfile = JSON.stringify(profile);
7188
+ const serializableTargetUrl = JSON.stringify(targetUrl);
7189
+ const serializableSlug = JSON.stringify(slug);
7190
+ return String.raw`
7191
+ const profile = ${serializableProfile};
7192
+ const targetUrl = ${serializableTargetUrl};
7193
+ const profileSlug = ${serializableSlug};
7194
+ const capturedAt = new Date().toISOString();
7195
+ const consoleEvents = [];
7196
+ const pageErrors = [];
7197
+ page.on("console", (message) => {
7198
+ const type = message.type();
7199
+ if (type === "error" || type === "warning" || type === "assert") {
7200
+ consoleEvents.push({
7201
+ type,
7202
+ text: message.text().slice(0, 1000),
7203
+ location: message.location ? message.location() : undefined,
7204
+ });
7205
+ }
7206
+ });
7207
+ page.on("pageerror", (error) => {
7208
+ pageErrors.push({ message: String(error && error.message ? error.message : error).slice(0, 1000) });
7209
+ });
7210
+ function textKey(check) {
7211
+ return check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
7212
+ }
7213
+ function textMatches(sample, check) {
7214
+ if (check.pattern) {
7215
+ try { return new RegExp(check.pattern, check.flags || "").test(sample || ""); } catch { return false; }
7216
+ }
7217
+ return String(sample || "").includes(check.text || "");
7218
+ }
7219
+ function expectedPathFor(check) {
7220
+ return check.expected_path || new URL(targetUrl).pathname || "/";
7221
+ }
7222
+ async function selectorStats(selector) {
7223
+ return page.locator(selector).evaluateAll((elements) => {
7224
+ const isVisible = (element) => {
7225
+ const style = window.getComputedStyle(element);
7226
+ const rect = element.getBoundingClientRect();
7227
+ return style && style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
7228
+ };
7229
+ return { count: elements.length, visible_count: elements.filter(isVisible).length };
7230
+ }).catch((error) => ({ count: 0, visible_count: 0, error: String(error && error.message ? error.message : error).slice(0, 500) }));
7231
+ }
7232
+ async function captureViewport(viewport) {
7233
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
7234
+ let httpStatus = null;
7235
+ let navigationError;
7236
+ let waitError;
7237
+ try {
7238
+ const response = await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 45000 });
7239
+ httpStatus = response ? response.status() : null;
7240
+ } catch (error) {
7241
+ navigationError = String(error && error.message ? error.message : error).slice(0, 1000);
7242
+ }
7243
+ if (!navigationError && profile.target.wait_for_selector) {
7244
+ try {
7245
+ await page.waitForSelector(profile.target.wait_for_selector, { state: "visible", timeout: 15000 });
7246
+ } catch (error) {
7247
+ waitError = String(error && error.message ? error.message : error).slice(0, 1000);
7248
+ }
7249
+ }
7250
+ if (!navigationError && profile.target.wait_ms) {
7251
+ await page.waitForTimeout(profile.target.wait_ms);
7252
+ }
7253
+ const dom = await page.evaluate(() => {
7254
+ const body = document.body;
7255
+ const documentElement = document.documentElement;
7256
+ const text = (body ? body.innerText : "").replace(/\s+/g, " ").trim();
7257
+ return {
7258
+ url: location.href,
7259
+ pathname: location.pathname,
7260
+ title: document.title,
7261
+ body_text_length: text.length,
7262
+ body_text_sample: text.slice(0, 8000),
7263
+ scroll_width: documentElement ? documentElement.scrollWidth : 0,
7264
+ client_width: documentElement ? documentElement.clientWidth : window.innerWidth,
7265
+ };
7266
+ }).catch((error) => ({
7267
+ url: page.url(),
7268
+ pathname: "",
7269
+ title: "",
7270
+ body_text_length: 0,
7271
+ body_text_sample: "",
7272
+ scroll_width: 0,
7273
+ client_width: viewport.width,
7274
+ evaluation_error: String(error && error.message ? error.message : error).slice(0, 1000),
7275
+ }));
7276
+ const selectors = {};
7277
+ const text_matches = {};
7278
+ for (const check of profile.checks || []) {
7279
+ if ((check.type === "selector_visible" || check.type === "selector_count_at_least") && check.selector) {
7280
+ selectors[check.selector] = await selectorStats(check.selector);
7281
+ }
7282
+ if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
7283
+ text_matches[textKey(check)] = textMatches(dom.body_text_sample || "", check);
7284
+ }
7285
+ }
7286
+ const screenshotLabel = profileSlug + "-" + viewport.name;
7287
+ try {
7288
+ if (typeof saveScreenshot === "function") await saveScreenshot(screenshotLabel);
7289
+ } catch (error) {
7290
+ pageErrors.push({ message: "saveScreenshot failed: " + String(error && error.message ? error.message : error).slice(0, 500) });
7291
+ }
7292
+ const expectedPath = profile.checks.find((check) => check.type === "route_loaded" && check.expected_path)?.expected_path || new URL(targetUrl).pathname || "/";
7293
+ return {
7294
+ name: viewport.name,
7295
+ width: viewport.width,
7296
+ height: viewport.height,
7297
+ url: dom.url,
7298
+ route: {
7299
+ requested: targetUrl,
7300
+ observed: dom.pathname,
7301
+ expected_path: expectedPath,
7302
+ matched: dom.pathname === expectedPath,
7303
+ http_status: httpStatus,
7304
+ error: navigationError || waitError || undefined,
7305
+ },
7306
+ title: dom.title,
7307
+ body_text_length: dom.body_text_length,
7308
+ body_text_sample: dom.body_text_sample,
7309
+ scroll_width: dom.scroll_width,
7310
+ client_width: dom.client_width,
7311
+ overflow_px: Math.max(0, (dom.scroll_width || 0) - (dom.client_width || viewport.width)),
7312
+ selectors,
7313
+ text_matches,
7314
+ screenshot_label: screenshotLabel,
7315
+ navigation_error: navigationError,
7316
+ wait_error: waitError,
7317
+ };
7318
+ }
7319
+ ${runtimeScriptAssessmentSource()}
7320
+ const viewports = [];
7321
+ for (const viewport of profile.target.viewports || []) {
7322
+ viewports.push(await captureViewport(viewport));
7323
+ }
7324
+ const evidence = {
7325
+ version: "riddle-proof.profile-evidence.v1",
7326
+ profile_name: profile.name,
7327
+ target_url: targetUrl,
7328
+ baseline_policy: profile.baseline_policy || "invariant_only",
7329
+ captured_at: capturedAt,
7330
+ viewports,
7331
+ console: {
7332
+ events: consoleEvents,
7333
+ fatal_count: consoleEvents.filter((event) => event.type === "error" || event.type === "assert").length,
7334
+ },
7335
+ page_errors: pageErrors,
7336
+ dom_summary: {
7337
+ viewport_count: viewports.length,
7338
+ routes: viewports.map((viewport) => viewport.route),
7339
+ titles: viewports.map((viewport) => viewport.title),
7340
+ overflow_px: viewports.map((viewport) => viewport.overflow_px),
7341
+ },
7342
+ };
7343
+ const result = assessProfile(profile, evidence);
7344
+ if (typeof saveJson === "function") {
7345
+ await saveJson("proof.json", result);
7346
+ await saveJson("console.json", { events: consoleEvents, page_errors: pageErrors });
7347
+ await saveJson("dom-summary.json", evidence.dom_summary);
7348
+ }
7349
+ return result;
7350
+ `.trim();
7351
+ }
7352
+ function collectRiddleProfileArtifactRefs(input) {
7353
+ const refs = [];
7354
+ const seen = /* @__PURE__ */ new Set();
7355
+ function add(item, source) {
7356
+ if (!isRecord(item)) return;
7357
+ const name = stringValue2(item.name) || stringValue2(item.filename) || "";
7358
+ const url = stringValue2(item.url);
7359
+ const path7 = stringValue2(item.path);
7360
+ if (!name && !url && !path7) return;
7361
+ const key = `${name}:${url || path7 || ""}`;
7362
+ if (seen.has(key)) return;
7363
+ seen.add(key);
7364
+ refs.push({
7365
+ name,
7366
+ url,
7367
+ path: path7,
7368
+ kind: stringValue2(item.kind) || stringValue2(item.type),
7369
+ content_type: stringValue2(item.content_type) || stringValue2(item.contentType),
7370
+ source
7371
+ });
7372
+ }
7373
+ function visit(value, source) {
7374
+ if (Array.isArray(value)) {
7375
+ for (const item of value) add(item, source);
7376
+ return;
7377
+ }
7378
+ if (!isRecord(value)) return;
7379
+ for (const key of ["artifacts", "outputs", "screenshots", "files"]) {
7380
+ if (Array.isArray(value[key])) visit(value[key], key);
7381
+ }
7382
+ }
7383
+ visit(input, "artifacts");
7384
+ return refs;
7385
+ }
7386
+ function extractRiddleProofProfileResult(input) {
7387
+ if (!isRecord(input)) return void 0;
7388
+ if (input.version === RIDDLE_PROOF_PROFILE_RESULT_VERSION) return input;
7389
+ const candidates = [
7390
+ input.result,
7391
+ input.return_value,
7392
+ input.value,
7393
+ input.profile_result,
7394
+ isRecord(input["proof.json"]) ? input["proof.json"] : void 0,
7395
+ isRecord(input._proof_json) ? input._proof_json : void 0
7396
+ ];
7397
+ for (const candidate of candidates) {
7398
+ const result = extractRiddleProofProfileResult(candidate);
7399
+ if (result) return result;
7400
+ }
7401
+ if (isRecord(input._artifact_json)) {
7402
+ return extractRiddleProofProfileResult(input._artifact_json["proof.json"]);
7403
+ }
7404
+ return void 0;
7405
+ }
7406
+
6634
7407
  // src/cli.ts
6635
7408
  function usage() {
6636
7409
  return [
@@ -6640,6 +7413,7 @@ function usage() {
6640
7413
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
6641
7414
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
6642
7415
  " riddle-proof-loop status --state-path <path>",
7416
+ " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--output <dir>]",
6643
7417
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label>",
6644
7418
  " riddle-proof-loop riddle-server-preview <directory> --script-file <file> [--path /route] [--wait-for-selector selector]",
6645
7419
  " riddle-proof-loop riddle-run-script --url <url> --script-file <file> [--viewport 1280x720]",
@@ -6831,6 +7605,173 @@ function riddleClientConfig(options) {
6831
7605
  apiBaseUrl: optionString(options, "apiBaseUrl")
6832
7606
  };
6833
7607
  }
7608
+ function parseProfileViewports(value) {
7609
+ if (!value) return void 0;
7610
+ return value.split(",").map((part, index) => {
7611
+ const trimmed = part.trim();
7612
+ const named = /^([a-zA-Z0-9_-]+)=(\d+x\d+)$/.exec(trimmed);
7613
+ const viewport = parseRiddleViewport(named ? named[2] : trimmed);
7614
+ if (!viewport) throw new Error(`Invalid viewport ${trimmed}.`);
7615
+ return {
7616
+ name: named ? named[1] : `viewport-${index + 1}`,
7617
+ width: viewport.width,
7618
+ height: viewport.height
7619
+ };
7620
+ });
7621
+ }
7622
+ function normalizeProfileForCli(options) {
7623
+ const rawProfile = readJsonValue(optionString(options, "profile"), "--profile");
7624
+ return normalizeRiddleProofProfile(rawProfile, {
7625
+ url: optionString(options, "url"),
7626
+ route: optionString(options, "route"),
7627
+ viewports: parseProfileViewports(optionString(options, "viewports") || optionString(options, "viewport"))
7628
+ });
7629
+ }
7630
+ function profileResultMarkdown(result) {
7631
+ const lines = [
7632
+ `# Riddle Proof Profile: ${result.profile_name}`,
7633
+ "",
7634
+ `Status: ${result.status}`,
7635
+ `Runner: ${result.runner}`,
7636
+ `Captured: ${result.captured_at}`,
7637
+ "",
7638
+ result.summary,
7639
+ "",
7640
+ "## Checks",
7641
+ ""
7642
+ ];
7643
+ for (const check of result.checks) {
7644
+ lines.push(`- ${check.status}: ${check.label || check.type}`);
7645
+ if (check.message) lines.push(` ${check.message}`);
7646
+ }
7647
+ if (result.artifacts.riddle_artifacts?.length) {
7648
+ lines.push("", "## Riddle Artifacts", "");
7649
+ for (const artifact of result.artifacts.riddle_artifacts.slice(0, 40)) {
7650
+ lines.push(`- ${artifact.name || artifact.kind || "artifact"}${artifact.url ? `: ${artifact.url}` : ""}`);
7651
+ }
7652
+ }
7653
+ return `${lines.join("\n")}
7654
+ `;
7655
+ }
7656
+ function writeProfileOutput(outputDir, result) {
7657
+ if (!outputDir) return;
7658
+ (0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
7659
+ (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "profile-result.json"), `${JSON.stringify(result, null, 2)}
7660
+ `);
7661
+ (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "summary.md"), profileResultMarkdown(result));
7662
+ if (result.evidence) (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "proof.json"), `${JSON.stringify(result, null, 2)}
7663
+ `);
7664
+ if (result.evidence?.console) (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "console.json"), `${JSON.stringify(result.evidence.console, null, 2)}
7665
+ `);
7666
+ if (result.evidence?.dom_summary) (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "dom-summary.json"), `${JSON.stringify(result.evidence.dom_summary, null, 2)}
7667
+ `);
7668
+ }
7669
+ async function readArtifactJson(artifact) {
7670
+ const target = artifact.url || artifact.path;
7671
+ if (!target) return void 0;
7672
+ try {
7673
+ const raw = artifact.url ? await (await fetch(artifact.url)).text() : (0, import_node_fs6.existsSync)(target) ? (0, import_node_fs6.readFileSync)(target, "utf-8") : "";
7674
+ if (!raw.trim()) return void 0;
7675
+ const parsed = JSON.parse(raw);
7676
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
7677
+ } catch {
7678
+ return void 0;
7679
+ }
7680
+ }
7681
+ async function profileResultFromRiddleArtifacts(profile, artifacts, fallbackInputs) {
7682
+ for (const input of fallbackInputs) {
7683
+ const result = extractRiddleProofProfileResult(input);
7684
+ if (result) return result;
7685
+ }
7686
+ const proofArtifacts = artifacts.filter((artifact) => /(^|\/)proof\.json(?:\.json)?$/i.test(artifact.name || artifact.url || artifact.path || "")).sort((left, right) => {
7687
+ const leftName = left.name || left.url || left.path || "";
7688
+ const rightName = right.name || right.url || right.path || "";
7689
+ return Number(/proof\.json\.json$/i.test(rightName)) - Number(/proof\.json\.json$/i.test(leftName));
7690
+ });
7691
+ for (const artifact of proofArtifacts) {
7692
+ const parsed = await readArtifactJson(artifact);
7693
+ const result = extractRiddleProofProfileResult(parsed);
7694
+ if (result) return result;
7695
+ }
7696
+ const evidenceArtifacts = artifacts.filter((artifact) => /profile-evidence|evidence\.json/i.test(artifact.name || artifact.url || artifact.path || ""));
7697
+ for (const artifact of evidenceArtifacts) {
7698
+ const parsed = await readArtifactJson(artifact);
7699
+ if (parsed?.version === "riddle-proof.profile-evidence.v1") {
7700
+ return assessRiddleProofProfileEvidence(profile, parsed, { artifacts });
7701
+ }
7702
+ }
7703
+ return void 0;
7704
+ }
7705
+ function withRiddleMetadata(result, input) {
7706
+ return {
7707
+ ...result,
7708
+ riddle: {
7709
+ ...result.riddle || {},
7710
+ job_id: input.job_id || result.riddle?.job_id,
7711
+ status: input.status ?? result.riddle?.status,
7712
+ terminal: input.terminal ?? result.riddle?.terminal
7713
+ },
7714
+ artifacts: {
7715
+ ...result.artifacts,
7716
+ riddle_artifacts: input.artifacts || result.artifacts.riddle_artifacts
7717
+ }
7718
+ };
7719
+ }
7720
+ async function runProfileForCli(profile, options) {
7721
+ const runner = optionString(options, "runner") || "riddle";
7722
+ if (runner !== "riddle") {
7723
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
7724
+ }
7725
+ const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
7726
+ const client = createRiddleApiClient(riddleClientConfig(options));
7727
+ let created;
7728
+ try {
7729
+ created = await client.runScript({
7730
+ url: targetUrl,
7731
+ script: buildRiddleProofProfileScript(profile),
7732
+ viewport: profile.target.viewports[0],
7733
+ timeoutSec: optionString(options, "timeout") ? Number(optionString(options, "timeout")) : void 0,
7734
+ sync: options.sync === true ? true : void 0
7735
+ });
7736
+ } catch (error) {
7737
+ return createRiddleProofProfileEnvironmentBlockedResult({ profile, runner, error });
7738
+ }
7739
+ const jobId = typeof created.job_id === "string" ? created.job_id : typeof created.id === "string" ? created.id : "";
7740
+ if (!jobId) {
7741
+ const directResult = extractRiddleProofProfileResult(created);
7742
+ return directResult ? withRiddleMetadata(directResult, { artifacts: collectRiddleProfileArtifactRefs(created) }) : createRiddleProofProfileInsufficientResult({ profile, runner, error: "Riddle run response was missing job_id.", artifacts: collectRiddleProfileArtifactRefs(created) });
7743
+ }
7744
+ const poll = await client.pollJob(jobId, {
7745
+ wait: true,
7746
+ attempts: optionString(options, "attempts") ? Number(optionString(options, "attempts")) : void 0,
7747
+ intervalMs: optionString(options, "intervalMs") ? Number(optionString(options, "intervalMs")) : void 0
7748
+ });
7749
+ const artifacts = collectRiddleProfileArtifactRefs(poll.artifacts);
7750
+ if (!poll.ok || !poll.terminal) {
7751
+ return createRiddleProofProfileEnvironmentBlockedResult({
7752
+ profile,
7753
+ runner,
7754
+ error: `Riddle job ${jobId} ended with status ${poll.status || "unknown"}.`,
7755
+ riddle: { job_id: jobId, status: poll.status, terminal: poll.terminal },
7756
+ artifacts
7757
+ });
7758
+ }
7759
+ const artifactResult = await profileResultFromRiddleArtifacts(profile, artifacts, [poll.job, poll.artifacts, created]);
7760
+ if (!artifactResult) {
7761
+ return createRiddleProofProfileInsufficientResult({
7762
+ profile,
7763
+ runner,
7764
+ riddle: { job_id: jobId, status: poll.status, terminal: poll.terminal },
7765
+ artifacts
7766
+ });
7767
+ }
7768
+ return withRiddleMetadata(artifactResult, {
7769
+ job_id: jobId,
7770
+ status: poll.status,
7771
+ terminal: poll.terminal,
7772
+ artifacts
7773
+ });
7774
+ }
6834
7775
  function requestForRun(options) {
6835
7776
  const statePath = optionString(options, "statePath");
6836
7777
  const withEngineModuleUrl = (request) => {
@@ -6877,6 +7818,15 @@ async function main() {
6877
7818
  `);
6878
7819
  return;
6879
7820
  }
7821
+ if (command === "run-profile") {
7822
+ const profile = normalizeProfileForCli(options);
7823
+ const result = await runProfileForCli(profile, options);
7824
+ writeProfileOutput(optionString(options, "output"), result);
7825
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
7826
+ `);
7827
+ process.exitCode = profileStatusExitCode(profile, result.status);
7828
+ return;
7829
+ }
6880
7830
  if (command === "riddle-preview-deploy") {
6881
7831
  const buildDir = positional[1];
6882
7832
  const label = positional[2];