@riddledc/riddle-proof 0.7.145 → 0.7.147

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 CHANGED
@@ -941,11 +941,14 @@ shape, and errors without rerunning blind.
941
941
 
942
942
  `riddle-proof-loop riddle-poll <job-id> --wait` keeps stdout as JSON and writes
943
943
  human progress lines to stderr while waiting. The JSON result includes
944
- `poll.timed_out`, `poll.elapsed_ms`, `poll.queue_elapsed_ms`, and
945
- `poll.running_without_submission` so delayed dispatch is distinguishable from a
946
- terminal proof failure. If `--wait` exhausts its attempts before a terminal job
947
- status, the command exits non-zero and the result explains the last observed
948
- status and `submitted_at` state.
944
+ `poll.timed_out`, `poll.elapsed_ms`, `poll.queue_elapsed_ms`,
945
+ `poll.pre_submission_elapsed_ms`, and `poll.running_without_submission` so
946
+ delayed dispatch is distinguishable from a terminal proof failure.
947
+ `queue_elapsed_ms` reflects Riddle's `created_at` to `submitted_at` timestamps;
948
+ `pre_submission_elapsed_ms` preserves how long the CLI actually observed the
949
+ job before `submitted_at` appeared. If `--wait` exhausts its attempts before a
950
+ terminal job status, the command exits non-zero and the result explains the
951
+ last observed status and `submitted_at` state.
949
952
 
950
953
  ## Base vs OpenClaw Wrapper Boundary
951
954
 
@@ -279,6 +279,7 @@ function buildPollSnapshot(jobId, job, input) {
279
279
  submitted_at: submittedAt,
280
280
  completed_at: completedAt,
281
281
  queue_elapsed_ms: queueElapsedMs,
282
+ pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
282
283
  running_without_submission: Boolean(status && !terminal && !submittedAt)
283
284
  };
284
285
  }
@@ -286,7 +287,8 @@ function pollMessage(snapshot, timedOut) {
286
287
  if (!timedOut) return void 0;
287
288
  const submitted = snapshot.submitted_at || "not submitted";
288
289
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
289
- return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
290
+ const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
291
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}${preSubmit}`;
290
292
  }
291
293
  async function pollRiddleJob(config, jobId, options = {}) {
292
294
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -298,15 +300,24 @@ async function pollRiddleJob(config, jobId, options = {}) {
298
300
  let lastSnapshot = null;
299
301
  let lastProgressAt = 0;
300
302
  let lastProgressKey = "";
303
+ let preSubmissionElapsedMs = 0;
301
304
  for (let index = 0; index < attempts; index += 1) {
302
305
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
303
306
  const observedAt = Date.now();
304
- lastSnapshot = buildPollSnapshot(jobId, job, {
307
+ const nextSnapshot = buildPollSnapshot(jobId, job, {
305
308
  attempt: index + 1,
306
309
  attempts,
307
310
  startedAt,
308
- observedAt
311
+ observedAt,
312
+ preSubmissionElapsedMs
309
313
  });
314
+ if (nextSnapshot.running_without_submission) {
315
+ preSubmissionElapsedMs = Math.max(preSubmissionElapsedMs, nextSnapshot.elapsed_ms);
316
+ }
317
+ lastSnapshot = {
318
+ ...nextSnapshot,
319
+ pre_submission_elapsed_ms: preSubmissionElapsedMs
320
+ };
310
321
  const progressKey = [
311
322
  lastSnapshot.status || "unknown",
312
323
  lastSnapshot.terminal ? "terminal" : "nonterminal",
package/dist/cli.cjs CHANGED
@@ -6840,6 +6840,7 @@ function buildPollSnapshot(jobId, job, input) {
6840
6840
  submitted_at: submittedAt,
6841
6841
  completed_at: completedAt,
6842
6842
  queue_elapsed_ms: queueElapsedMs,
6843
+ pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
6843
6844
  running_without_submission: Boolean(status && !terminal && !submittedAt)
6844
6845
  };
6845
6846
  }
@@ -6847,7 +6848,8 @@ function pollMessage(snapshot, timedOut) {
6847
6848
  if (!timedOut) return void 0;
6848
6849
  const submitted = snapshot.submitted_at || "not submitted";
6849
6850
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
6850
- return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
6851
+ const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
6852
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}${preSubmit}`;
6851
6853
  }
6852
6854
  async function pollRiddleJob(config, jobId, options = {}) {
6853
6855
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -6859,15 +6861,24 @@ async function pollRiddleJob(config, jobId, options = {}) {
6859
6861
  let lastSnapshot = null;
6860
6862
  let lastProgressAt = 0;
6861
6863
  let lastProgressKey = "";
6864
+ let preSubmissionElapsedMs = 0;
6862
6865
  for (let index = 0; index < attempts; index += 1) {
6863
6866
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
6864
6867
  const observedAt = Date.now();
6865
- lastSnapshot = buildPollSnapshot(jobId, job, {
6868
+ const nextSnapshot = buildPollSnapshot(jobId, job, {
6866
6869
  attempt: index + 1,
6867
6870
  attempts,
6868
6871
  startedAt,
6869
- observedAt
6872
+ observedAt,
6873
+ preSubmissionElapsedMs
6870
6874
  });
6875
+ if (nextSnapshot.running_without_submission) {
6876
+ preSubmissionElapsedMs = Math.max(preSubmissionElapsedMs, nextSnapshot.elapsed_ms);
6877
+ }
6878
+ lastSnapshot = {
6879
+ ...nextSnapshot,
6880
+ pre_submission_elapsed_ms: preSubmissionElapsedMs
6881
+ };
6871
6882
  const progressKey = [
6872
6883
  lastSnapshot.status || "unknown",
6873
6884
  lastSnapshot.terminal ? "terminal" : "nonterminal",
@@ -6938,6 +6949,7 @@ function createRiddleApiClient(config = {}) {
6938
6949
 
6939
6950
  // src/profile.ts
6940
6951
  var RIDDLE_PROOF_PROFILE_VERSION = "riddle-proof.profile.v1";
6952
+ var RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION = "riddle-proof.profile-evidence.v1";
6941
6953
  var RIDDLE_PROOF_PROFILE_RESULT_VERSION = "riddle-proof.profile-result.v1";
6942
6954
  var RIDDLE_PROOF_PROFILE_STATUSES = [
6943
6955
  "passed",
@@ -14380,7 +14392,7 @@ function usage() {
14380
14392
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
14381
14393
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
14382
14394
  " 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]",
14395
+ " 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
14396
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
14385
14397
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
14386
14398
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
@@ -14437,6 +14449,9 @@ function optionBoolean(options, key) {
14437
14449
  function runProfileStrictOption(options) {
14438
14450
  return optionBoolean(options, "strict") ?? false;
14439
14451
  }
14452
+ function runProfileSplitViewportsOption(options) {
14453
+ return optionBoolean(options, "splitViewports") ?? false;
14454
+ }
14440
14455
  function optionNumber(options, ...keys) {
14441
14456
  for (const key of keys) {
14442
14457
  const value = optionString(options, key);
@@ -14476,7 +14491,7 @@ function formatPollDuration(ms) {
14476
14491
  }
14477
14492
  function riddlePollProgressLine(snapshot) {
14478
14493
  const submittedAt = snapshot.submitted_at || "not-submitted";
14479
- const queuePart = snapshot.running_without_submission ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
14494
+ const queuePart = snapshot.running_without_submission ? ` waiting_for_submit=${formatPollDuration(snapshot.pre_submission_elapsed_ms)}${snapshot.queue_elapsed_ms !== null ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : ""}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
14480
14495
  const terminalPart = snapshot.terminal ? " terminal=true" : "";
14481
14496
  return [
14482
14497
  "[riddle-poll]",
@@ -14749,15 +14764,20 @@ function profileRiddleJobMarkdown(result) {
14749
14764
  const riddle = cliRecord(result.riddle);
14750
14765
  if (!riddle) return [];
14751
14766
  const jobId = cliString(riddle.job_id);
14767
+ const mode = cliString(riddle.mode);
14768
+ const jobCount = cliFiniteNumber(riddle.job_count);
14752
14769
  const status = cliString(riddle.status);
14753
14770
  const terminal = typeof riddle.terminal === "boolean" ? riddle.terminal : void 0;
14754
14771
  const queueElapsedMs = cliFiniteNumber(riddle.queue_elapsed_ms);
14755
14772
  const elapsedMs3 = cliFiniteNumber(riddle.elapsed_ms);
14773
+ const preSubmissionElapsedMs = cliFiniteNumber(riddle.pre_submission_elapsed_ms);
14756
14774
  const attempt = cliFiniteNumber(riddle.attempt);
14757
14775
  const attempts = cliFiniteNumber(riddle.attempts);
14758
14776
  const submittedAt = cliString(riddle.submitted_at);
14759
14777
  const completedAt = cliString(riddle.completed_at);
14760
14778
  const parts = [
14779
+ mode ? `mode ${markdownInlineCode(mode)}` : "",
14780
+ jobCount === void 0 ? "" : `jobs ${jobCount}`,
14761
14781
  jobId ? `job ${markdownInlineCode(jobId)}` : "",
14762
14782
  status ? `status ${markdownInlineCode(status)}` : "",
14763
14783
  terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
@@ -14765,12 +14785,31 @@ function profileRiddleJobMarkdown(result) {
14765
14785
  const lines = parts.length ? [`- ${parts.join(", ")}`] : [];
14766
14786
  if (queueElapsedMs !== void 0 || elapsedMs3 !== void 0 || attempt !== void 0 || attempts !== void 0) {
14767
14787
  lines.push(
14768
- `- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs3)}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
14788
+ `- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs3)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
14769
14789
  );
14770
14790
  }
14771
14791
  if (submittedAt || completedAt) {
14772
14792
  lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
14773
14793
  }
14794
+ const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
14795
+ for (const job of splitJobs.slice(0, 12)) {
14796
+ const viewport = cliString(job.viewport) || "viewport";
14797
+ const splitJobId = cliString(job.job_id);
14798
+ const splitStatus = cliString(job.status);
14799
+ const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
14800
+ const splitElapsedMs = cliFiniteNumber(job.elapsed_ms);
14801
+ const splitPreSubmissionElapsedMs = cliFiniteNumber(job.pre_submission_elapsed_ms);
14802
+ lines.push(
14803
+ `- ${viewport}: ${[
14804
+ splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
14805
+ splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
14806
+ splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`,
14807
+ splitElapsedMs === void 0 ? "" : `elapsed ${formatPollDuration(splitElapsedMs)}`,
14808
+ splitPreSubmissionElapsedMs === void 0 || splitPreSubmissionElapsedMs < 1e3 ? "" : `pre-submit ${formatPollDuration(splitPreSubmissionElapsedMs)}`
14809
+ ].filter(Boolean).join(", ") || "job metadata unavailable"}`
14810
+ );
14811
+ }
14812
+ if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
14774
14813
  return lines;
14775
14814
  }
14776
14815
  function markdownInlineCode(value, maxLength = 80) {
@@ -15370,6 +15409,7 @@ function withRiddleMetadata(result, input) {
15370
15409
  submitted_at: poll?.submitted_at ?? result.riddle?.submitted_at,
15371
15410
  completed_at: poll?.completed_at ?? result.riddle?.completed_at,
15372
15411
  queue_elapsed_ms: poll?.queue_elapsed_ms ?? result.riddle?.queue_elapsed_ms,
15412
+ pre_submission_elapsed_ms: poll?.pre_submission_elapsed_ms ?? result.riddle?.pre_submission_elapsed_ms,
15373
15413
  elapsed_ms: poll?.elapsed_ms ?? result.riddle?.elapsed_ms,
15374
15414
  attempt: poll?.attempt ?? result.riddle?.attempt,
15375
15415
  attempts: poll?.attempts ?? result.riddle?.attempts,
@@ -15390,19 +15430,155 @@ function riddleMetadataFromPoll(jobId, poll) {
15390
15430
  submitted_at: poll.poll?.submitted_at,
15391
15431
  completed_at: poll.poll?.completed_at,
15392
15432
  queue_elapsed_ms: poll.poll?.queue_elapsed_ms,
15433
+ pre_submission_elapsed_ms: poll.poll?.pre_submission_elapsed_ms,
15393
15434
  elapsed_ms: poll.poll?.elapsed_ms,
15394
15435
  attempt: poll.poll?.attempt,
15395
15436
  attempts: poll.poll?.attempts,
15396
15437
  timed_out: poll.poll?.timed_out
15397
15438
  };
15398
15439
  }
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.`);
15440
+ function profileForSplitViewport(profile, viewport) {
15441
+ return {
15442
+ ...profile,
15443
+ name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
15444
+ target: {
15445
+ ...profile.target,
15446
+ viewports: [viewport]
15447
+ },
15448
+ metadata: {
15449
+ ...profile.metadata || {},
15450
+ split_parent_profile: profile.name,
15451
+ split_viewport: viewport.name
15452
+ }
15453
+ };
15454
+ }
15455
+ function safeProfileOutputSegment(value) {
15456
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
15457
+ return safe || "viewport";
15458
+ }
15459
+ function splitViewportOutputDir(outputDir, viewportName, seen) {
15460
+ const base = safeProfileOutputSegment(viewportName);
15461
+ const count = seen.get(base) || 0;
15462
+ seen.set(base, count + 1);
15463
+ return import_node_path6.default.join(outputDir, count ? `${base}-${count + 1}` : base);
15464
+ }
15465
+ function splitViewportArtifactRefs(input) {
15466
+ return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
15467
+ ...artifact,
15468
+ name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
15469
+ }));
15470
+ }
15471
+ function sumDefinedNumbers(values) {
15472
+ const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
15473
+ return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
15474
+ }
15475
+ function splitViewportRiddleMetadata(childRuns) {
15476
+ const splitJobs = childRuns.map(({ viewport, result }) => ({
15477
+ viewport: viewport.name,
15478
+ job_id: result.riddle?.job_id,
15479
+ status: result.riddle?.status,
15480
+ terminal: result.riddle?.terminal,
15481
+ queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
15482
+ pre_submission_elapsed_ms: result.riddle?.pre_submission_elapsed_ms,
15483
+ elapsed_ms: result.riddle?.elapsed_ms,
15484
+ attempt: result.riddle?.attempt,
15485
+ attempts: result.riddle?.attempts,
15486
+ timed_out: result.riddle?.timed_out
15487
+ }));
15488
+ return {
15489
+ mode: "split-viewports",
15490
+ job_count: childRuns.length,
15491
+ status: "split-viewports",
15492
+ terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
15493
+ queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
15494
+ pre_submission_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.pre_submission_elapsed_ms)),
15495
+ elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
15496
+ split_jobs: splitJobs
15497
+ };
15498
+ }
15499
+ function latestCapturedAt(evidences) {
15500
+ let latest = "";
15501
+ let latestMs = Number.NEGATIVE_INFINITY;
15502
+ for (const evidence of evidences) {
15503
+ const parsed = Date.parse(evidence.captured_at);
15504
+ if (Number.isFinite(parsed) && parsed >= latestMs) {
15505
+ latest = evidence.captured_at;
15506
+ latestMs = parsed;
15507
+ }
15403
15508
  }
15509
+ return latest || (/* @__PURE__ */ new Date()).toISOString();
15510
+ }
15511
+ function splitViewportDomSummary(profile, childRuns, evidence) {
15512
+ return {
15513
+ split_viewports: true,
15514
+ expected_viewport_count: profile.target.viewports.length,
15515
+ viewport_count: evidence.viewports.length,
15516
+ child_result_count: childRuns.length,
15517
+ child_statuses: childRuns.map(({ viewport, result }) => ({
15518
+ viewport: viewport.name,
15519
+ profile_name: result.profile_name,
15520
+ status: result.status,
15521
+ job_id: result.riddle?.job_id || null
15522
+ })),
15523
+ routes: evidence.viewports.map((viewport) => ({
15524
+ viewport: viewport.name,
15525
+ requested: viewport.route.requested,
15526
+ observed: viewport.route.observed,
15527
+ matched: viewport.route.matched,
15528
+ http_status: viewport.route.http_status ?? null
15529
+ })),
15530
+ titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
15531
+ overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
15532
+ bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
15533
+ overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
15534
+ console_event_count: evidence.console.events.length,
15535
+ console_fatal_count: evidence.console.fatal_count,
15536
+ page_error_count: evidence.page_errors.length,
15537
+ network_mock_count: (profile.target.network_mocks || []).length,
15538
+ network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
15539
+ };
15540
+ }
15541
+ function aggregateSplitViewportEvidence(profile, childRuns) {
15542
+ const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
15543
+ const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
15544
+ const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
15545
+ const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
15546
+ const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
15547
+ const evidence = {
15548
+ version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
15549
+ profile_name: profile.name,
15550
+ target_url: resolveRiddleProofProfileTargetUrl(profile),
15551
+ baseline_policy: profile.baseline_policy,
15552
+ captured_at: latestCapturedAt(evidences),
15553
+ viewports,
15554
+ console: {
15555
+ events: consoleEvents,
15556
+ fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
15557
+ },
15558
+ page_errors: pageErrors,
15559
+ network_mocks: networkMocks.length ? networkMocks : void 0
15560
+ };
15561
+ evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
15562
+ return evidence;
15563
+ }
15564
+ function splitViewportBlockedMessage(childRuns) {
15565
+ 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})` : ""}`);
15566
+ return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
15567
+ }
15568
+ function withSplitViewportWarnings(profile, result) {
15569
+ const warnings = [];
15570
+ if (profile.target.network_mocks?.length) {
15571
+ warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
15572
+ }
15573
+ if (!warnings.length) return result;
15574
+ return {
15575
+ ...result,
15576
+ warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
15577
+ };
15578
+ }
15579
+ async function runSingleRiddleProfileForCli(profile, options, input) {
15580
+ const { client, runner } = input;
15404
15581
  const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
15405
- const client = createRiddleApiClient(riddleClientConfig(options));
15406
15582
  let created;
15407
15583
  try {
15408
15584
  created = await client.runScript({
@@ -15461,6 +15637,48 @@ async function runProfileForCli(profile, options) {
15461
15637
  artifacts
15462
15638
  });
15463
15639
  }
15640
+ async function runSplitViewportProfileForCli(profile, options, input) {
15641
+ const outputDir = profileOutputDirOption(options);
15642
+ const seenOutputNames = /* @__PURE__ */ new Map();
15643
+ const childRuns = [];
15644
+ for (const viewport of profile.target.viewports) {
15645
+ const childProfile = profileForSplitViewport(profile, viewport);
15646
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
15647
+ if (outputDir) {
15648
+ writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
15649
+ }
15650
+ childRuns.push({ viewport, profile: childProfile, result: result2 });
15651
+ }
15652
+ const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
15653
+ const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
15654
+ if (blocked.length) {
15655
+ return createRiddleProofProfileEnvironmentBlockedResult({
15656
+ profile,
15657
+ runner: input.runner,
15658
+ error: splitViewportBlockedMessage(childRuns),
15659
+ riddle: splitViewportRiddleMetadata(childRuns),
15660
+ artifacts
15661
+ });
15662
+ }
15663
+ const evidence = aggregateSplitViewportEvidence(profile, childRuns);
15664
+ const result = assessRiddleProofProfileEvidence(profile, evidence, {
15665
+ runner: input.runner,
15666
+ riddle: splitViewportRiddleMetadata(childRuns),
15667
+ artifacts
15668
+ });
15669
+ return withSplitViewportWarnings(profile, result);
15670
+ }
15671
+ async function runProfileForCli(profile, options) {
15672
+ const runner = optionString(options, "runner") || "riddle";
15673
+ if (runner !== "riddle") {
15674
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
15675
+ }
15676
+ const client = createRiddleApiClient(riddleClientConfig(options));
15677
+ if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
15678
+ return runSplitViewportProfileForCli(profile, options, { client, runner });
15679
+ }
15680
+ return runSingleRiddleProfileForCli(profile, options, { client, runner });
15681
+ }
15464
15682
  function requestForRun(options) {
15465
15683
  const statePath = optionString(options, "statePath");
15466
15684
  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,
@@ -16,7 +17,7 @@ import {
16
17
  import {
17
18
  createRiddleApiClient,
18
19
  parseRiddleViewport
19
- } from "./chunk-7GHBRZHQ.js";
20
+ } from "./chunk-M3ZTY6PQ.js";
20
21
  import {
21
22
  createDisabledRiddleProofAgentAdapter,
22
23
  readRiddleProofRunStatus,
@@ -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);
@@ -142,7 +146,7 @@ function formatPollDuration(ms) {
142
146
  }
143
147
  function riddlePollProgressLine(snapshot) {
144
148
  const submittedAt = snapshot.submitted_at || "not-submitted";
145
- const queuePart = snapshot.running_without_submission ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
149
+ const queuePart = snapshot.running_without_submission ? ` waiting_for_submit=${formatPollDuration(snapshot.pre_submission_elapsed_ms)}${snapshot.queue_elapsed_ms !== null ? ` queued_for=${formatPollDuration(snapshot.queue_elapsed_ms)}` : ""}` : snapshot.queue_elapsed_ms !== null ? ` queue=${formatPollDuration(snapshot.queue_elapsed_ms)}` : "";
146
150
  const terminalPart = snapshot.terminal ? " terminal=true" : "";
147
151
  return [
148
152
  "[riddle-poll]",
@@ -415,15 +419,20 @@ 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);
421
427
  const elapsedMs = cliFiniteNumber(riddle.elapsed_ms);
428
+ const preSubmissionElapsedMs = cliFiniteNumber(riddle.pre_submission_elapsed_ms);
422
429
  const attempt = cliFiniteNumber(riddle.attempt);
423
430
  const attempts = cliFiniteNumber(riddle.attempts);
424
431
  const submittedAt = cliString(riddle.submitted_at);
425
432
  const completedAt = cliString(riddle.completed_at);
426
433
  const parts = [
434
+ mode ? `mode ${markdownInlineCode(mode)}` : "",
435
+ jobCount === void 0 ? "" : `jobs ${jobCount}`,
427
436
  jobId ? `job ${markdownInlineCode(jobId)}` : "",
428
437
  status ? `status ${markdownInlineCode(status)}` : "",
429
438
  terminal === void 0 ? "" : `terminal ${terminal ? "true" : "false"}`
@@ -431,12 +440,31 @@ function profileRiddleJobMarkdown(result) {
431
440
  const lines = parts.length ? [`- ${parts.join(", ")}`] : [];
432
441
  if (queueElapsedMs !== void 0 || elapsedMs !== void 0 || attempt !== void 0 || attempts !== void 0) {
433
442
  lines.push(
434
- `- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs)}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
443
+ `- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
435
444
  );
436
445
  }
437
446
  if (submittedAt || completedAt) {
438
447
  lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
439
448
  }
449
+ const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
450
+ for (const job of splitJobs.slice(0, 12)) {
451
+ const viewport = cliString(job.viewport) || "viewport";
452
+ const splitJobId = cliString(job.job_id);
453
+ const splitStatus = cliString(job.status);
454
+ const splitTerminal = typeof job.terminal === "boolean" ? job.terminal : void 0;
455
+ const splitElapsedMs = cliFiniteNumber(job.elapsed_ms);
456
+ const splitPreSubmissionElapsedMs = cliFiniteNumber(job.pre_submission_elapsed_ms);
457
+ lines.push(
458
+ `- ${viewport}: ${[
459
+ splitJobId ? `job ${markdownInlineCode(splitJobId)}` : "",
460
+ splitStatus ? `status ${markdownInlineCode(splitStatus)}` : "",
461
+ splitTerminal === void 0 ? "" : `terminal ${splitTerminal ? "true" : "false"}`,
462
+ splitElapsedMs === void 0 ? "" : `elapsed ${formatPollDuration(splitElapsedMs)}`,
463
+ splitPreSubmissionElapsedMs === void 0 || splitPreSubmissionElapsedMs < 1e3 ? "" : `pre-submit ${formatPollDuration(splitPreSubmissionElapsedMs)}`
464
+ ].filter(Boolean).join(", ") || "job metadata unavailable"}`
465
+ );
466
+ }
467
+ if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
440
468
  return lines;
441
469
  }
442
470
  function markdownInlineCode(value, maxLength = 80) {
@@ -1036,6 +1064,7 @@ function withRiddleMetadata(result, input) {
1036
1064
  submitted_at: poll?.submitted_at ?? result.riddle?.submitted_at,
1037
1065
  completed_at: poll?.completed_at ?? result.riddle?.completed_at,
1038
1066
  queue_elapsed_ms: poll?.queue_elapsed_ms ?? result.riddle?.queue_elapsed_ms,
1067
+ pre_submission_elapsed_ms: poll?.pre_submission_elapsed_ms ?? result.riddle?.pre_submission_elapsed_ms,
1039
1068
  elapsed_ms: poll?.elapsed_ms ?? result.riddle?.elapsed_ms,
1040
1069
  attempt: poll?.attempt ?? result.riddle?.attempt,
1041
1070
  attempts: poll?.attempts ?? result.riddle?.attempts,
@@ -1056,19 +1085,155 @@ function riddleMetadataFromPoll(jobId, poll) {
1056
1085
  submitted_at: poll.poll?.submitted_at,
1057
1086
  completed_at: poll.poll?.completed_at,
1058
1087
  queue_elapsed_ms: poll.poll?.queue_elapsed_ms,
1088
+ pre_submission_elapsed_ms: poll.poll?.pre_submission_elapsed_ms,
1059
1089
  elapsed_ms: poll.poll?.elapsed_ms,
1060
1090
  attempt: poll.poll?.attempt,
1061
1091
  attempts: poll.poll?.attempts,
1062
1092
  timed_out: poll.poll?.timed_out
1063
1093
  };
1064
1094
  }
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.`);
1095
+ function profileForSplitViewport(profile, viewport) {
1096
+ return {
1097
+ ...profile,
1098
+ name: `${profile.name}-${viewport.name || `${viewport.width}x${viewport.height}`}`,
1099
+ target: {
1100
+ ...profile.target,
1101
+ viewports: [viewport]
1102
+ },
1103
+ metadata: {
1104
+ ...profile.metadata || {},
1105
+ split_parent_profile: profile.name,
1106
+ split_viewport: viewport.name
1107
+ }
1108
+ };
1109
+ }
1110
+ function safeProfileOutputSegment(value) {
1111
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
1112
+ return safe || "viewport";
1113
+ }
1114
+ function splitViewportOutputDir(outputDir, viewportName, seen) {
1115
+ const base = safeProfileOutputSegment(viewportName);
1116
+ const count = seen.get(base) || 0;
1117
+ seen.set(base, count + 1);
1118
+ return path.join(outputDir, count ? `${base}-${count + 1}` : base);
1119
+ }
1120
+ function splitViewportArtifactRefs(input) {
1121
+ return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
1122
+ ...artifact,
1123
+ name: `${safeProfileOutputSegment(input.viewport.name)}/${artifact.name || artifact.kind || "artifact"}`
1124
+ }));
1125
+ }
1126
+ function sumDefinedNumbers(values) {
1127
+ const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
1128
+ return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
1129
+ }
1130
+ function splitViewportRiddleMetadata(childRuns) {
1131
+ const splitJobs = childRuns.map(({ viewport, result }) => ({
1132
+ viewport: viewport.name,
1133
+ job_id: result.riddle?.job_id,
1134
+ status: result.riddle?.status,
1135
+ terminal: result.riddle?.terminal,
1136
+ queue_elapsed_ms: result.riddle?.queue_elapsed_ms,
1137
+ pre_submission_elapsed_ms: result.riddle?.pre_submission_elapsed_ms,
1138
+ elapsed_ms: result.riddle?.elapsed_ms,
1139
+ attempt: result.riddle?.attempt,
1140
+ attempts: result.riddle?.attempts,
1141
+ timed_out: result.riddle?.timed_out
1142
+ }));
1143
+ return {
1144
+ mode: "split-viewports",
1145
+ job_count: childRuns.length,
1146
+ status: "split-viewports",
1147
+ terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
1148
+ queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
1149
+ pre_submission_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.pre_submission_elapsed_ms)),
1150
+ elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.elapsed_ms)),
1151
+ split_jobs: splitJobs
1152
+ };
1153
+ }
1154
+ function latestCapturedAt(evidences) {
1155
+ let latest = "";
1156
+ let latestMs = Number.NEGATIVE_INFINITY;
1157
+ for (const evidence of evidences) {
1158
+ const parsed = Date.parse(evidence.captured_at);
1159
+ if (Number.isFinite(parsed) && parsed >= latestMs) {
1160
+ latest = evidence.captured_at;
1161
+ latestMs = parsed;
1162
+ }
1069
1163
  }
1164
+ return latest || (/* @__PURE__ */ new Date()).toISOString();
1165
+ }
1166
+ function splitViewportDomSummary(profile, childRuns, evidence) {
1167
+ return {
1168
+ split_viewports: true,
1169
+ expected_viewport_count: profile.target.viewports.length,
1170
+ viewport_count: evidence.viewports.length,
1171
+ child_result_count: childRuns.length,
1172
+ child_statuses: childRuns.map(({ viewport, result }) => ({
1173
+ viewport: viewport.name,
1174
+ profile_name: result.profile_name,
1175
+ status: result.status,
1176
+ job_id: result.riddle?.job_id || null
1177
+ })),
1178
+ routes: evidence.viewports.map((viewport) => ({
1179
+ viewport: viewport.name,
1180
+ requested: viewport.route.requested,
1181
+ observed: viewport.route.observed,
1182
+ matched: viewport.route.matched,
1183
+ http_status: viewport.route.http_status ?? null
1184
+ })),
1185
+ titles: evidence.viewports.map((viewport) => viewport.title).filter((title) => Boolean(title)),
1186
+ overflow_px: evidence.viewports.map((viewport) => viewport.overflow_px ?? null),
1187
+ bounds_overflow_px: evidence.viewports.map((viewport) => viewport.bounds_overflow_px ?? null),
1188
+ overflow_offender_counts: evidence.viewports.map((viewport) => (viewport.overflow_offenders || []).length),
1189
+ console_event_count: evidence.console.events.length,
1190
+ console_fatal_count: evidence.console.fatal_count,
1191
+ page_error_count: evidence.page_errors.length,
1192
+ network_mock_count: (profile.target.network_mocks || []).length,
1193
+ network_mock_hit_count: (evidence.network_mocks || []).filter((event) => event.ok !== false).length
1194
+ };
1195
+ }
1196
+ function aggregateSplitViewportEvidence(profile, childRuns) {
1197
+ const evidences = childRuns.map(({ result }) => result.evidence).filter((evidence2) => Boolean(evidence2));
1198
+ const viewports = evidences.flatMap((evidence2) => evidence2.viewports || []);
1199
+ const consoleEvents = evidences.flatMap((evidence2) => evidence2.console?.events || []);
1200
+ const pageErrors = evidences.flatMap((evidence2) => evidence2.page_errors || []);
1201
+ const networkMocks = evidences.flatMap((evidence2) => evidence2.network_mocks || []);
1202
+ const evidence = {
1203
+ version: RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
1204
+ profile_name: profile.name,
1205
+ target_url: resolveRiddleProofProfileTargetUrl(profile),
1206
+ baseline_policy: profile.baseline_policy,
1207
+ captured_at: latestCapturedAt(evidences),
1208
+ viewports,
1209
+ console: {
1210
+ events: consoleEvents,
1211
+ fatal_count: evidences.reduce((sum, item) => sum + (item.console?.fatal_count || 0), 0)
1212
+ },
1213
+ page_errors: pageErrors,
1214
+ network_mocks: networkMocks.length ? networkMocks : void 0
1215
+ };
1216
+ evidence.dom_summary = splitViewportDomSummary(profile, childRuns, evidence);
1217
+ return evidence;
1218
+ }
1219
+ function splitViewportBlockedMessage(childRuns) {
1220
+ 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})` : ""}`);
1221
+ return `Split viewport run did not produce reliable evidence for ${blocked.join("; ")}.`;
1222
+ }
1223
+ function withSplitViewportWarnings(profile, result) {
1224
+ const warnings = [];
1225
+ if (profile.target.network_mocks?.length) {
1226
+ warnings.push("Split viewport mode runs each viewport in a separate Riddle job; global network mock sequencing is assessed from aggregated events.");
1227
+ }
1228
+ if (!warnings.length) return result;
1229
+ return {
1230
+ ...result,
1231
+ warnings: [.../* @__PURE__ */ new Set([...result.warnings || [], ...warnings])]
1232
+ };
1233
+ }
1234
+ async function runSingleRiddleProfileForCli(profile, options, input) {
1235
+ const { client, runner } = input;
1070
1236
  const targetUrl = resolveRiddleProofProfileTargetUrl(profile);
1071
- const client = createRiddleApiClient(riddleClientConfig(options));
1072
1237
  let created;
1073
1238
  try {
1074
1239
  created = await client.runScript({
@@ -1127,6 +1292,48 @@ async function runProfileForCli(profile, options) {
1127
1292
  artifacts
1128
1293
  });
1129
1294
  }
1295
+ async function runSplitViewportProfileForCli(profile, options, input) {
1296
+ const outputDir = profileOutputDirOption(options);
1297
+ const seenOutputNames = /* @__PURE__ */ new Map();
1298
+ const childRuns = [];
1299
+ for (const viewport of profile.target.viewports) {
1300
+ const childProfile = profileForSplitViewport(profile, viewport);
1301
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
1302
+ if (outputDir) {
1303
+ writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
1304
+ }
1305
+ childRuns.push({ viewport, profile: childProfile, result: result2 });
1306
+ }
1307
+ const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
1308
+ const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
1309
+ if (blocked.length) {
1310
+ return createRiddleProofProfileEnvironmentBlockedResult({
1311
+ profile,
1312
+ runner: input.runner,
1313
+ error: splitViewportBlockedMessage(childRuns),
1314
+ riddle: splitViewportRiddleMetadata(childRuns),
1315
+ artifacts
1316
+ });
1317
+ }
1318
+ const evidence = aggregateSplitViewportEvidence(profile, childRuns);
1319
+ const result = assessRiddleProofProfileEvidence(profile, evidence, {
1320
+ runner: input.runner,
1321
+ riddle: splitViewportRiddleMetadata(childRuns),
1322
+ artifacts
1323
+ });
1324
+ return withSplitViewportWarnings(profile, result);
1325
+ }
1326
+ async function runProfileForCli(profile, options) {
1327
+ const runner = optionString(options, "runner") || "riddle";
1328
+ if (runner !== "riddle") {
1329
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
1330
+ }
1331
+ const client = createRiddleApiClient(riddleClientConfig(options));
1332
+ if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
1333
+ return runSplitViewportProfileForCli(profile, options, { client, runner });
1334
+ }
1335
+ return runSingleRiddleProfileForCli(profile, options, { client, runner });
1336
+ }
1130
1337
  function requestForRun(options) {
1131
1338
  const statePath = optionString(options, "statePath");
1132
1339
  const withEngineModuleUrl = (request) => {
package/dist/index.cjs CHANGED
@@ -16464,6 +16464,7 @@ function buildPollSnapshot(jobId, job, input) {
16464
16464
  submitted_at: submittedAt,
16465
16465
  completed_at: completedAt,
16466
16466
  queue_elapsed_ms: queueElapsedMs,
16467
+ pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
16467
16468
  running_without_submission: Boolean(status && !terminal && !submittedAt)
16468
16469
  };
16469
16470
  }
@@ -16471,7 +16472,8 @@ function pollMessage(snapshot, timedOut) {
16471
16472
  if (!timedOut) return void 0;
16472
16473
  const submitted = snapshot.submitted_at || "not submitted";
16473
16474
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
16474
- return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
16475
+ const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
16476
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}${preSubmit}`;
16475
16477
  }
16476
16478
  async function pollRiddleJob(config, jobId, options = {}) {
16477
16479
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -16483,15 +16485,24 @@ async function pollRiddleJob(config, jobId, options = {}) {
16483
16485
  let lastSnapshot = null;
16484
16486
  let lastProgressAt = 0;
16485
16487
  let lastProgressKey = "";
16488
+ let preSubmissionElapsedMs = 0;
16486
16489
  for (let index = 0; index < attempts; index += 1) {
16487
16490
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
16488
16491
  const observedAt = Date.now();
16489
- lastSnapshot = buildPollSnapshot(jobId, job, {
16492
+ const nextSnapshot = buildPollSnapshot(jobId, job, {
16490
16493
  attempt: index + 1,
16491
16494
  attempts,
16492
16495
  startedAt,
16493
- observedAt
16496
+ observedAt,
16497
+ preSubmissionElapsedMs
16494
16498
  });
16499
+ if (nextSnapshot.running_without_submission) {
16500
+ preSubmissionElapsedMs = Math.max(preSubmissionElapsedMs, nextSnapshot.elapsed_ms);
16501
+ }
16502
+ lastSnapshot = {
16503
+ ...nextSnapshot,
16504
+ pre_submission_elapsed_ms: preSubmissionElapsedMs
16505
+ };
16495
16506
  const progressKey = [
16496
16507
  lastSnapshot.status || "unknown",
16497
16508
  lastSnapshot.terminal ? "terminal" : "nonterminal",
package/dist/index.js CHANGED
@@ -77,7 +77,7 @@ import {
77
77
  riddleRequestJson,
78
78
  runRiddleScript,
79
79
  runRiddleServerPreview
80
- } from "./chunk-7GHBRZHQ.js";
80
+ } from "./chunk-M3ZTY6PQ.js";
81
81
  import {
82
82
  DEFAULT_DIAGNOSTIC_ARRAY_LIMIT,
83
83
  DEFAULT_DIAGNOSTIC_HISTORY_LIMIT,
@@ -376,17 +376,32 @@ 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;
383
385
  submitted_at?: string | null;
384
386
  completed_at?: string | null;
385
387
  queue_elapsed_ms?: number | null;
388
+ pre_submission_elapsed_ms?: number;
386
389
  elapsed_ms?: number;
387
390
  attempt?: number;
388
391
  attempts?: number;
389
392
  timed_out?: boolean;
393
+ split_jobs?: Array<{
394
+ viewport: string;
395
+ job_id?: string;
396
+ status?: string | null;
397
+ terminal?: boolean;
398
+ queue_elapsed_ms?: number | null;
399
+ pre_submission_elapsed_ms?: number;
400
+ elapsed_ms?: number;
401
+ attempt?: number;
402
+ attempts?: number;
403
+ timed_out?: boolean;
404
+ }>;
390
405
  };
391
406
  environment_blocker?: Record<string, JsonValue>;
392
407
  error?: string;
package/dist/profile.d.ts CHANGED
@@ -376,17 +376,32 @@ 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;
383
385
  submitted_at?: string | null;
384
386
  completed_at?: string | null;
385
387
  queue_elapsed_ms?: number | null;
388
+ pre_submission_elapsed_ms?: number;
386
389
  elapsed_ms?: number;
387
390
  attempt?: number;
388
391
  attempts?: number;
389
392
  timed_out?: boolean;
393
+ split_jobs?: Array<{
394
+ viewport: string;
395
+ job_id?: string;
396
+ status?: string | null;
397
+ terminal?: boolean;
398
+ queue_elapsed_ms?: number | null;
399
+ pre_submission_elapsed_ms?: number;
400
+ elapsed_ms?: number;
401
+ attempt?: number;
402
+ attempts?: number;
403
+ timed_out?: boolean;
404
+ }>;
390
405
  };
391
406
  environment_blocker?: Record<string, JsonValue>;
392
407
  error?: string;
@@ -325,6 +325,7 @@ function buildPollSnapshot(jobId, job, input) {
325
325
  submitted_at: submittedAt,
326
326
  completed_at: completedAt,
327
327
  queue_elapsed_ms: queueElapsedMs,
328
+ pre_submission_elapsed_ms: Math.max(0, Math.floor(input.preSubmissionElapsedMs ?? 0)),
328
329
  running_without_submission: Boolean(status && !terminal && !submittedAt)
329
330
  };
330
331
  }
@@ -332,7 +333,8 @@ function pollMessage(snapshot, timedOut) {
332
333
  if (!timedOut) return void 0;
333
334
  const submitted = snapshot.submitted_at || "not submitted";
334
335
  const queue = snapshot.queue_elapsed_ms !== null ? ` queue_elapsed_ms=${snapshot.queue_elapsed_ms}` : "";
335
- return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}`;
336
+ const preSubmit = snapshot.pre_submission_elapsed_ms > 0 ? ` pre_submission_elapsed_ms=${snapshot.pre_submission_elapsed_ms}` : "";
337
+ return `Riddle job ${snapshot.job_id} did not reach a terminal status after ${snapshot.attempt} poll attempts; status=${snapshot.status || "unknown"} submitted_at=${submitted}.${queue}${preSubmit}`;
336
338
  }
337
339
  async function pollRiddleJob(config, jobId, options = {}) {
338
340
  if (!jobId?.trim()) throw new Error("jobId is required");
@@ -344,15 +346,24 @@ async function pollRiddleJob(config, jobId, options = {}) {
344
346
  let lastSnapshot = null;
345
347
  let lastProgressAt = 0;
346
348
  let lastProgressKey = "";
349
+ let preSubmissionElapsedMs = 0;
347
350
  for (let index = 0; index < attempts; index += 1) {
348
351
  job = await riddleRequestJson(config, `/v1/jobs/${jobId}`);
349
352
  const observedAt = Date.now();
350
- lastSnapshot = buildPollSnapshot(jobId, job, {
353
+ const nextSnapshot = buildPollSnapshot(jobId, job, {
351
354
  attempt: index + 1,
352
355
  attempts,
353
356
  startedAt,
354
- observedAt
357
+ observedAt,
358
+ preSubmissionElapsedMs
355
359
  });
360
+ if (nextSnapshot.running_without_submission) {
361
+ preSubmissionElapsedMs = Math.max(preSubmissionElapsedMs, nextSnapshot.elapsed_ms);
362
+ }
363
+ lastSnapshot = {
364
+ ...nextSnapshot,
365
+ pre_submission_elapsed_ms: preSubmissionElapsedMs
366
+ };
356
367
  const progressKey = [
357
368
  lastSnapshot.status || "unknown",
358
369
  lastSnapshot.terminal ? "terminal" : "nonterminal",
@@ -18,6 +18,7 @@ interface RiddlePollProgressSnapshot {
18
18
  submitted_at: string | null;
19
19
  completed_at: string | null;
20
20
  queue_elapsed_ms: number | null;
21
+ pre_submission_elapsed_ms: number;
21
22
  running_without_submission: boolean;
22
23
  }
23
24
  interface RiddlePollSummary extends RiddlePollProgressSnapshot {
@@ -18,6 +18,7 @@ interface RiddlePollProgressSnapshot {
18
18
  submitted_at: string | null;
19
19
  completed_at: string | null;
20
20
  queue_elapsed_ms: number | null;
21
+ pre_submission_elapsed_ms: number;
21
22
  running_without_submission: boolean;
22
23
  }
23
24
  interface RiddlePollSummary extends RiddlePollProgressSnapshot {
@@ -12,7 +12,7 @@ import {
12
12
  riddleRequestJson,
13
13
  runRiddleScript,
14
14
  runRiddleServerPreview
15
- } from "./chunk-7GHBRZHQ.js";
15
+ } from "./chunk-M3ZTY6PQ.js";
16
16
  export {
17
17
  DEFAULT_RIDDLE_API_BASE_URL,
18
18
  DEFAULT_RIDDLE_API_KEY_FILE,
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.147",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",