@riddledc/riddle-proof 0.7.145 → 0.7.146

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
@@ -6938,6 +6938,7 @@ function createRiddleApiClient(config = {}) {
6938
6938
 
6939
6939
  // src/profile.ts
6940
6940
  var RIDDLE_PROOF_PROFILE_VERSION = "riddle-proof.profile.v1";
6941
+ var RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION = "riddle-proof.profile-evidence.v1";
6941
6942
  var RIDDLE_PROOF_PROFILE_RESULT_VERSION = "riddle-proof.profile-result.v1";
6942
6943
  var RIDDLE_PROOF_PROFILE_STATUSES = [
6943
6944
  "passed",
@@ -14380,7 +14381,7 @@ function usage() {
14380
14381
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
14381
14382
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
14382
14383
  " riddle-proof-loop status --state-path <path>",
14383
- " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
14384
+ " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
14384
14385
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
14385
14386
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
14386
14387
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
@@ -14437,6 +14438,9 @@ function optionBoolean(options, key) {
14437
14438
  function runProfileStrictOption(options) {
14438
14439
  return optionBoolean(options, "strict") ?? false;
14439
14440
  }
14441
+ function runProfileSplitViewportsOption(options) {
14442
+ return optionBoolean(options, "splitViewports") ?? false;
14443
+ }
14440
14444
  function optionNumber(options, ...keys) {
14441
14445
  for (const key of keys) {
14442
14446
  const value = optionString(options, key);
@@ -14749,6 +14753,8 @@ function profileRiddleJobMarkdown(result) {
14749
14753
  const riddle = cliRecord(result.riddle);
14750
14754
  if (!riddle) return [];
14751
14755
  const jobId = cliString(riddle.job_id);
14756
+ const mode = cliString(riddle.mode);
14757
+ const jobCount = cliFiniteNumber(riddle.job_count);
14752
14758
  const status = cliString(riddle.status);
14753
14759
  const terminal = typeof riddle.terminal === "boolean" ? riddle.terminal : void 0;
14754
14760
  const queueElapsedMs = cliFiniteNumber(riddle.queue_elapsed_ms);
@@ -14758,6 +14764,8 @@ function profileRiddleJobMarkdown(result) {
14758
14764
  const submittedAt = cliString(riddle.submitted_at);
14759
14765
  const completedAt = cliString(riddle.completed_at);
14760
14766
  const parts = [
14767
+ mode ? `mode ${markdownInlineCode(mode)}` : "",
14768
+ jobCount === void 0 ? "" : `jobs ${jobCount}`,
14761
14769
  jobId ? `job ${markdownInlineCode(jobId)}` : "",
14762
14770
  status ? `status ${markdownInlineCode(status)}` : "",
14763
14771
  terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
@@ -14771,6 +14779,21 @@ function profileRiddleJobMarkdown(result) {
14771
14779
  if (submittedAt || completedAt) {
14772
14780
  lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
14773
14781
  }
14782
+ const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
14783
+ for (const job of splitJobs.slice(0, 12)) {
14784
+ const viewport = cliString(job.viewport) || "viewport";
14785
+ const splitJobId = cliString(job.job_id);
14786
+ const splitStatus = cliString(job.status);
14787
+ const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
14788
+ lines.push(
14789
+ `- ${viewport}: ${[
14790
+ splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
14791
+ splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
14792
+ splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`
14793
+ ].filter(Boolean).join(", ") || "job metadata unavailable"}`
14794
+ );
14795
+ }
14796
+ if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
14774
14797
  return lines;
14775
14798
  }
14776
14799
  function markdownInlineCode(value, maxLength = 80) {
@@ -15396,13 +15419,146 @@ function riddleMetadataFromPoll(jobId, poll) {
15396
15419
  timed_out: poll.poll?.timed_out
15397
15420
  };
15398
15421
  }
15399
- async function runProfileForCli(profile, options) {
15400
- const runner = optionString(options, "runner") || "riddle";
15401
- if (runner !== "riddle") {
15402
- throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
15422
+ function profileForSplitViewport(profile, viewport) {
15423
+ return {
15424
+ ...profile,
15425
+ name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
15426
+ target: {
15427
+ ...profile.target,
15428
+ viewports: [viewport]
15429
+ },
15430
+ metadata: {
15431
+ ...profile.metadata || {},
15432
+ split_parent_profile: profile.name,
15433
+ split_viewport: viewport.name
15434
+ }
15435
+ };
15436
+ }
15437
+ function safeProfileOutputSegment(value) {
15438
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
15439
+ return safe || "viewport";
15440
+ }
15441
+ function splitViewportOutputDir(outputDir, viewportName, seen) {
15442
+ const base = safeProfileOutputSegment(viewportName);
15443
+ const count = seen.get(base) || 0;
15444
+ seen.set(base, count + 1);
15445
+ return import_node_path6.default.join(outputDir, count ? `${base}-${count + 1}` : base);
15446
+ }
15447
+ function splitViewportArtifactRefs(input) {
15448
+ return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
15449
+ ...artifact,
15450
+ name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
15451
+ }));
15452
+ }
15453
+ function sumDefinedNumbers(values) {
15454
+ const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
15455
+ return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
15456
+ }
15457
+ function splitViewportRiddleMetadata(childRuns) {
15458
+ const splitJobs = childRuns.map(({ viewport, result }) => ({
15459
+ viewport: viewport.name,
15460
+ job_id: result.riddle?.job_id,
15461
+ status: result.riddle?.status,
15462
+ terminal: result.riddle?.terminal,
15463
+ queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
15464
+ elapsed_ms: result.riddle?.elapsed_ms,
15465
+ attempt: result.riddle?.attempt,
15466
+ attempts: result.riddle?.attempts,
15467
+ timed_out: result.riddle?.timed_out
15468
+ }));
15469
+ return {
15470
+ mode: "split-viewports",
15471
+ job_count: childRuns.length,
15472
+ status: "split-viewports",
15473
+ terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
15474
+ queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
15475
+ elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
15476
+ split_jobs: splitJobs
15477
+ };
15478
+ }
15479
+ function latestCapturedAt(evidences) {
15480
+ let latest = "";
15481
+ let latestMs = Number.NEGATIVE_INFINITY;
15482
+ for (const evidence of evidences) {
15483
+ const parsed = Date.parse(evidence.captured_at);
15484
+ if (Number.isFinite(parsed) && parsed >= latestMs) {
15485
+ latest = evidence.captured_at;
15486
+ latestMs = parsed;
15487
+ }
15403
15488
  }
15489
+ return latest || (/* @__PURE__ */ new Date()).toISOString();
15490
+ }
15491
+ function splitViewportDomSummary(profile, childRuns, evidence) {
15492
+ return {
15493
+ split_viewports: true,
15494
+ expected_viewport_count: profile.target.viewports.length,
15495
+ viewport_count: evidence.viewports.length,
15496
+ child_result_count: childRuns.length,
15497
+ child_statuses: childRuns.map(({ viewport, result }) => ({
15498
+ viewport: viewport.name,
15499
+ profile_name: result.profile_name,
15500
+ status: result.status,
15501
+ job_id: result.riddle?.job_id || null
15502
+ })),
15503
+ routes: evidence.viewports.map((viewport) => ({
15504
+ viewport: viewport.name,
15505
+ requested: viewport.route.requested,
15506
+ observed: viewport.route.observed,
15507
+ matched: viewport.route.matched,
15508
+ http_status: viewport.route.http_status ?? null
15509
+ })),
15510
+ titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
15511
+ overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
15512
+ bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
15513
+ overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
15514
+ console_event_count: evidence.console.events.length,
15515
+ console_fatal_count: evidence.console.fatal_count,
15516
+ page_error_count: evidence.page_errors.length,
15517
+ network_mock_count: (profile.target.network_mocks || []).length,
15518
+ network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
15519
+ };
15520
+ }
15521
+ function aggregateSplitViewportEvidence(profile, childRuns) {
15522
+ const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
15523
+ const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
15524
+ const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
15525
+ const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
15526
+ const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
15527
+ const evidence = {
15528
+ version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
15529
+ profile_name: profile.name,
15530
+ target_url: resolveRiddleProofProfileTargetUrl(profile),
15531
+ baseline_policy: profile.baseline_policy,
15532
+ captured_at: latestCapturedAt(evidences),
15533
+ viewports,
15534
+ console: {
15535
+ events: consoleEvents,
15536
+ fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
15537
+ },
15538
+ page_errors: pageErrors,
15539
+ network_mocks: networkMocks.length ? networkMocks : void 0
15540
+ };
15541
+ evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
15542
+ return evidence;
15543
+ }
15544
+ function splitViewportBlockedMessage(childRuns) {
15545
+ const blocked = childRuns.filter(({ result }) => !result.evidence || result.status === "environment_blocked" || result.status === "configuration_error").map(({ viewport, result }) => `${viewport.name}: ${result.status}${result.error ? ` (${result.error})` : ""}`);
15546
+ return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
15547
+ }
15548
+ function withSplitViewportWarnings(profile, result) {
15549
+ const warnings = [];
15550
+ if (profile.target.network_mocks?.length) {
15551
+ warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
15552
+ }
15553
+ if (!warnings.length) return result;
15554
+ return {
15555
+ ...result,
15556
+ warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
15557
+ };
15558
+ }
15559
+ async function runSingleRiddleProfileForCli(profile, options, input) {
15560
+ const { client, runner } = input;
15404
15561
  const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
15405
- const client = createRiddleApiClient(riddleClientConfig(options));
15406
15562
  let created;
15407
15563
  try {
15408
15564
  created = await client.runScript({
@@ -15461,6 +15617,48 @@ async function runProfileForCli(profile, options) {
15461
15617
  artifacts
15462
15618
  });
15463
15619
  }
15620
+ async function runSplitViewportProfileForCli(profile, options, input) {
15621
+ const outputDir = profileOutputDirOption(options);
15622
+ const seenOutputNames = /* @__PURE__ */ new Map();
15623
+ const childRuns = [];
15624
+ for (const viewport of profile.target.viewports) {
15625
+ const childProfile = profileForSplitViewport(profile, viewport);
15626
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
15627
+ if (outputDir) {
15628
+ writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
15629
+ }
15630
+ childRuns.push({ viewport, profile: childProfile, result: result2 });
15631
+ }
15632
+ const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
15633
+ const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
15634
+ if (blocked.length) {
15635
+ return createRiddleProofProfileEnvironmentBlockedResult({
15636
+ profile,
15637
+ runner: input.runner,
15638
+ error: splitViewportBlockedMessage(childRuns),
15639
+ riddle: splitViewportRiddleMetadata(childRuns),
15640
+ artifacts
15641
+ });
15642
+ }
15643
+ const evidence = aggregateSplitViewportEvidence(profile, childRuns);
15644
+ const result = assessRiddleProofProfileEvidence(profile, evidence, {
15645
+ runner: input.runner,
15646
+ riddle: splitViewportRiddleMetadata(childRuns),
15647
+ artifacts
15648
+ });
15649
+ return withSplitViewportWarnings(profile, result);
15650
+ }
15651
+ async function runProfileForCli(profile, options) {
15652
+ const runner = optionString(options, "runner") || "riddle";
15653
+ if (runner !== "riddle") {
15654
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
15655
+ }
15656
+ const client = createRiddleApiClient(riddleClientConfig(options));
15657
+ if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
15658
+ return runSplitViewportProfileForCli(profile, options, { client, runner });
15659
+ }
15660
+ return runSingleRiddleProfileForCli(profile, options, { client, runner });
15661
+ }
15464
15662
  function requestForRun(options) {
15465
15663
  const statePath = optionString(options, "statePath");
15466
15664
  const withEngineModuleUrl = (request) => {
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
3
4
  assessRiddleProofProfileEvidence,
4
5
  buildRiddleProofProfileScript,
5
6
  collectRiddleProfileArtifactRefs,
@@ -46,7 +47,7 @@ function usage() {
46
47
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
47
48
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
48
49
  " riddle-proof-loop status --state-path <path>",
49
- " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
50
+ " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--quiet]",
50
51
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
51
52
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
52
53
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
@@ -103,6 +104,9 @@ function optionBoolean(options, key) {
103
104
  function runProfileStrictOption(options) {
104
105
  return optionBoolean(options, "strict") ?? false;
105
106
  }
107
+ function runProfileSplitViewportsOption(options) {
108
+ return optionBoolean(options, "splitViewports") ?? false;
109
+ }
106
110
  function optionNumber(options, ...keys) {
107
111
  for (const key of keys) {
108
112
  const value = optionString(options, key);
@@ -415,6 +419,8 @@ function profileRiddleJobMarkdown(result) {
415
419
  const riddle = cliRecord(result.riddle);
416
420
  if (!riddle) return [];
417
421
  const jobId = cliString(riddle.job_id);
422
+ const mode = cliString(riddle.mode);
423
+ const jobCount = cliFiniteNumber(riddle.job_count);
418
424
  const status = cliString(riddle.status);
419
425
  const terminal = typeof riddle.terminal === "boolean" ? riddle.terminal : void 0;
420
426
  const queueElapsedMs = cliFiniteNumber(riddle.queue_elapsed_ms);
@@ -424,6 +430,8 @@ function profileRiddleJobMarkdown(result) {
424
430
  const submittedAt = cliString(riddle.submitted_at);
425
431
  const completedAt = cliString(riddle.completed_at);
426
432
  const parts = [
433
+ mode ? `mode ${markdownInlineCode(mode)}` : "",
434
+ jobCount === void 0 ? "" : `jobs ${jobCount}`,
427
435
  jobId ? `job ${markdownInlineCode(jobId)}` : "",
428
436
  status ? `status ${markdownInlineCode(status)}` : "",
429
437
  terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
@@ -437,6 +445,21 @@ function profileRiddleJobMarkdown(result) {
437
445
  if (submittedAt || completedAt) {
438
446
  lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
439
447
  }
448
+ const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
449
+ for (const job of splitJobs.slice(0, 12)) {
450
+ const viewport = cliString(job.viewport) || "viewport";
451
+ const splitJobId = cliString(job.job_id);
452
+ const splitStatus = cliString(job.status);
453
+ const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
454
+ lines.push(
455
+ `- ${viewport}: ${[
456
+ splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
457
+ splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
458
+ splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`
459
+ ].filter(Boolean).join(", ") || "job metadata unavailable"}`
460
+ );
461
+ }
462
+ if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
440
463
  return lines;
441
464
  }
442
465
  function markdownInlineCode(value, maxLength = 80) {
@@ -1062,13 +1085,146 @@ function riddleMetadataFromPoll(jobId, poll) {
1062
1085
  timed_out: poll.poll?.timed_out
1063
1086
  };
1064
1087
  }
1065
- async function runProfileForCli(profile, options) {
1066
- const runner = optionString(options, "runner") || "riddle";
1067
- if (runner !== "riddle") {
1068
- throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
1088
+ function profileForSplitViewport(profile, viewport) {
1089
+ return {
1090
+ ...profile,
1091
+ name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
1092
+ target: {
1093
+ ...profile.target,
1094
+ viewports: [viewport]
1095
+ },
1096
+ metadata: {
1097
+ ...profile.metadata || {},
1098
+ split_parent_profile: profile.name,
1099
+ split_viewport: viewport.name
1100
+ }
1101
+ };
1102
+ }
1103
+ function safeProfileOutputSegment(value) {
1104
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
1105
+ return safe || "viewport";
1106
+ }
1107
+ function splitViewportOutputDir(outputDir, viewportName, seen) {
1108
+ const base = safeProfileOutputSegment(viewportName);
1109
+ const count = seen.get(base) || 0;
1110
+ seen.set(base, count + 1);
1111
+ return path.join(outputDir, count ? `${base}-${count + 1}` : base);
1112
+ }
1113
+ function splitViewportArtifactRefs(input) {
1114
+ return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
1115
+ ...artifact,
1116
+ name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
1117
+ }));
1118
+ }
1119
+ function sumDefinedNumbers(values) {
1120
+ const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
1121
+ return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
1122
+ }
1123
+ function splitViewportRiddleMetadata(childRuns) {
1124
+ const splitJobs = childRuns.map(({ viewport, result }) => ({
1125
+ viewport: viewport.name,
1126
+ job_id: result.riddle?.job_id,
1127
+ status: result.riddle?.status,
1128
+ terminal: result.riddle?.terminal,
1129
+ queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
1130
+ elapsed_ms: result.riddle?.elapsed_ms,
1131
+ attempt: result.riddle?.attempt,
1132
+ attempts: result.riddle?.attempts,
1133
+ timed_out: result.riddle?.timed_out
1134
+ }));
1135
+ return {
1136
+ mode: "split-viewports",
1137
+ job_count: childRuns.length,
1138
+ status: "split-viewports",
1139
+ terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
1140
+ queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
1141
+ elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
1142
+ split_jobs: splitJobs
1143
+ };
1144
+ }
1145
+ function latestCapturedAt(evidences) {
1146
+ let latest = "";
1147
+ let latestMs = Number.NEGATIVE_INFINITY;
1148
+ for (const evidence of evidences) {
1149
+ const parsed = Date.parse(evidence.captured_at);
1150
+ if (Number.isFinite(parsed) && parsed >= latestMs) {
1151
+ latest = evidence.captured_at;
1152
+ latestMs = parsed;
1153
+ }
1069
1154
  }
1155
+ return latest || (/* @__PURE__ */ new Date()).toISOString();
1156
+ }
1157
+ function splitViewportDomSummary(profile, childRuns, evidence) {
1158
+ return {
1159
+ split_viewports: true,
1160
+ expected_viewport_count: profile.target.viewports.length,
1161
+ viewport_count: evidence.viewports.length,
1162
+ child_result_count: childRuns.length,
1163
+ child_statuses: childRuns.map(({ viewport, result }) => ({
1164
+ viewport: viewport.name,
1165
+ profile_name: result.profile_name,
1166
+ status: result.status,
1167
+ job_id: result.riddle?.job_id || null
1168
+ })),
1169
+ routes: evidence.viewports.map((viewport) => ({
1170
+ viewport: viewport.name,
1171
+ requested: viewport.route.requested,
1172
+ observed: viewport.route.observed,
1173
+ matched: viewport.route.matched,
1174
+ http_status: viewport.route.http_status ?? null
1175
+ })),
1176
+ titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
1177
+ overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
1178
+ bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
1179
+ overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
1180
+ console_event_count: evidence.console.events.length,
1181
+ console_fatal_count: evidence.console.fatal_count,
1182
+ page_error_count: evidence.page_errors.length,
1183
+ network_mock_count: (profile.target.network_mocks || []).length,
1184
+ network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
1185
+ };
1186
+ }
1187
+ function aggregateSplitViewportEvidence(profile, childRuns) {
1188
+ const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
1189
+ const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
1190
+ const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
1191
+ const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
1192
+ const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
1193
+ const evidence = {
1194
+ version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
1195
+ profile_name: profile.name,
1196
+ target_url: resolveRiddleProofProfileTargetUrl(profile),
1197
+ baseline_policy: profile.baseline_policy,
1198
+ captured_at: latestCapturedAt(evidences),
1199
+ viewports,
1200
+ console: {
1201
+ events: consoleEvents,
1202
+ fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
1203
+ },
1204
+ page_errors: pageErrors,
1205
+ network_mocks: networkMocks.length ? networkMocks : void 0
1206
+ };
1207
+ evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
1208
+ return evidence;
1209
+ }
1210
+ function splitViewportBlockedMessage(childRuns) {
1211
+ const blocked = childRuns.filter(({ result }) => !result.evidence || result.status === "environment_blocked" || result.status === "configuration_error").map(({ viewport, result }) => `${viewport.name}: ${result.status}${result.error ? ` (${result.error})` : ""}`);
1212
+ return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
1213
+ }
1214
+ function withSplitViewportWarnings(profile, result) {
1215
+ const warnings = [];
1216
+ if (profile.target.network_mocks?.length) {
1217
+ warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
1218
+ }
1219
+ if (!warnings.length) return result;
1220
+ return {
1221
+ ...result,
1222
+ warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
1223
+ };
1224
+ }
1225
+ async function runSingleRiddleProfileForCli(profile, options, input) {
1226
+ const { client, runner } = input;
1070
1227
  const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
1071
- const client = createRiddleApiClient(riddleClientConfig(options));
1072
1228
  let created;
1073
1229
  try {
1074
1230
  created = await client.runScript({
@@ -1127,6 +1283,48 @@ async function runProfileForCli(profile, options) {
1127
1283
  artifacts
1128
1284
  });
1129
1285
  }
1286
+ async function runSplitViewportProfileForCli(profile, options, input) {
1287
+ const outputDir = profileOutputDirOption(options);
1288
+ const seenOutputNames = /* @__PURE__ */ new Map();
1289
+ const childRuns = [];
1290
+ for (const viewport of profile.target.viewports) {
1291
+ const childProfile = profileForSplitViewport(profile, viewport);
1292
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
1293
+ if (outputDir) {
1294
+ writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
1295
+ }
1296
+ childRuns.push({ viewport, profile: childProfile, result: result2 });
1297
+ }
1298
+ const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
1299
+ const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
1300
+ if (blocked.length) {
1301
+ return createRiddleProofProfileEnvironmentBlockedResult({
1302
+ profile,
1303
+ runner: input.runner,
1304
+ error: splitViewportBlockedMessage(childRuns),
1305
+ riddle: splitViewportRiddleMetadata(childRuns),
1306
+ artifacts
1307
+ });
1308
+ }
1309
+ const evidence = aggregateSplitViewportEvidence(profile, childRuns);
1310
+ const result = assessRiddleProofProfileEvidence(profile, evidence, {
1311
+ runner: input.runner,
1312
+ riddle: splitViewportRiddleMetadata(childRuns),
1313
+ artifacts
1314
+ });
1315
+ return withSplitViewportWarnings(profile, result);
1316
+ }
1317
+ async function runProfileForCli(profile, options) {
1318
+ const runner = optionString(options, "runner") || "riddle";
1319
+ if (runner !== "riddle") {
1320
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
1321
+ }
1322
+ const client = createRiddleApiClient(riddleClientConfig(options));
1323
+ if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
1324
+ return runSplitViewportProfileForCli(profile, options, { client, runner });
1325
+ }
1326
+ return runSingleRiddleProfileForCli(profile, options, { client, runner });
1327
+ }
1130
1328
  function requestForRun(options) {
1131
1329
  const statePath = optionString(options, "statePath");
1132
1330
  const withEngineModuleUrl = (request) => {
@@ -376,7 +376,9 @@ interface RiddleProofProfileResult {
376
376
  warnings?: string[];
377
377
  evidence?: RiddleProofProfileEvidence;
378
378
  riddle?: {
379
+ mode?: "split-viewports" | (string & {});
379
380
  job_id?: string;
381
+ job_count?: number;
380
382
  status?: string | null;
381
383
  terminal?: boolean;
382
384
  created_at?: string | null;
@@ -387,6 +389,17 @@ interface RiddleProofProfileResult {
387
389
  attempt?: number;
388
390
  attempts?: number;
389
391
  timed_out?: boolean;
392
+ split_jobs?: Array<{
393
+ viewport: string;
394
+ job_id?: string;
395
+ status?: string | null;
396
+ terminal?: boolean;
397
+ queue_elapsed_ms?: number | null;
398
+ elapsed_ms?: number;
399
+ attempt?: number;
400
+ attempts?: number;
401
+ timed_out?: boolean;
402
+ }>;
390
403
  };
391
404
  environment_blocker?: Record<string, JsonValue>;
392
405
  error?: string;
package/dist/profile.d.ts CHANGED
@@ -376,7 +376,9 @@ interface RiddleProofProfileResult {
376
376
  warnings?: string[];
377
377
  evidence?: RiddleProofProfileEvidence;
378
378
  riddle?: {
379
+ mode?: "split-viewports" | (string & {});
379
380
  job_id?: string;
381
+ job_count?: number;
380
382
  status?: string | null;
381
383
  terminal?: boolean;
382
384
  created_at?: string | null;
@@ -387,6 +389,17 @@ interface RiddleProofProfileResult {
387
389
  attempt?: number;
388
390
  attempts?: number;
389
391
  timed_out?: boolean;
392
+ split_jobs?: Array<{
393
+ viewport: string;
394
+ job_id?: string;
395
+ status?: string | null;
396
+ terminal?: boolean;
397
+ queue_elapsed_ms?: number | null;
398
+ elapsed_ms?: number;
399
+ attempt?: number;
400
+ attempts?: number;
401
+ timed_out?: boolean;
402
+ }>;
390
403
  };
391
404
  environment_blocker?: Record<string, JsonValue>;
392
405
  error?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.145",
3
+ "version": "0.7.146",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",