@riddledc/riddle-proof 0.7.200 → 0.7.202

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
@@ -202,6 +202,31 @@ generic inline-script warning threshold. Use `--strict=true` when you
202
202
  deliberately want Riddle's non-critical script-safety warnings to block the run.
203
203
  Critical script-safety violations remain blocked by Riddle either way.
204
204
 
205
+ Use `--viewport-name <name>` to run only one named viewport from a
206
+ multi-viewport profile while preserving viewport-scoped setup actions and
207
+ checks:
208
+
209
+ ```sh
210
+ riddle-proof-loop run-profile \
211
+ --profile .riddle-proof/profiles/pricing.json \
212
+ --url https://example.com \
213
+ --viewport-name ipad-mini \
214
+ --output artifacts/riddle-proof/pricing-ipad-mini
215
+ ```
216
+
217
+ When `--output` / `--output-dir` is set, hosted profile runs write
218
+ `riddle-job.json` as soon as Riddle returns a job id. If the local process is
219
+ interrupted or pruned, recover the profile artifacts from the hosted job:
220
+
221
+ ```sh
222
+ riddle-proof-loop run-profile recover \
223
+ --profile .riddle-proof/profiles/pricing.json \
224
+ --url https://example.com \
225
+ --job job_abc123 \
226
+ --viewport-name ipad-mini \
227
+ --output artifacts/riddle-proof/pricing-ipad-mini-recovered
228
+ ```
229
+
205
230
  When promoting proof artifacts into a durable public profile, avoid guessing
206
231
  which backend or runner tokens are preserved inside `proof.json`. Derive the
207
232
  `body_contains` fragments from the artifact body first:
@@ -441,11 +466,12 @@ until a browser-state predicate is satisfied. It accepts the same selector,
441
466
  coordinate, and pointer options as `tap`, plus `until_path`,
442
467
  `until_expected_value`, `max_taps` / `max_calls` from 1 to 100, and optional
443
468
  `interval_ms`. Set `tap_burst_size` from 1 to 100 when gameplay needs several
444
- fast taps before the next predicate check. The action stops early when the
445
- predicate matches and records one compact receipt with `tap_count`,
446
- `condition_check_count`, `elapsed_ms`, final `until_value`, and input dispatch
447
- details, so long canvas interaction loops do not need dozens of repeated setup
448
- actions.
469
+ fast taps before the next predicate check, and `settle_ms` when the app needs a
470
+ short post-burst frame/update delay before the predicate is trustworthy. The
471
+ action stops early when the predicate matches and records one compact receipt
472
+ with `tap_count`, `condition_check_count`, `settle_ms`, `elapsed_ms`, final
473
+ `until_value`, and input dispatch details, so long canvas interaction loops do
474
+ not need dozens of repeated setup actions.
449
475
  Use `set_range_value` for HTML range inputs and React-controlled sliders. It
450
476
  accepts aliases such as `set-slider-value`, requires `selector` plus `value`,
451
477
  uses the native input value setter, dispatches bubbling `input` and `change`
@@ -543,10 +569,11 @@ and `click_count_value_total`. Repeated selector runs such as long gameplay
543
569
  button loops are also grouped as compact `same-selector` click-sequence
544
570
  receipts with click totals and ordinals. `tap_until` actions are summarized as
545
571
  one compact receipt with total taps, optional burst size, predicate-check count,
546
- elapsed time, and the final predicate value, which is the preferred shape for
547
- long canvas gameplay loops. Setup receipt sampling favors both first and last
548
- per-viewport receipts before filling remaining space, so late lifecycle phases
549
- such as terminal or restart remain visible in compact summaries.
572
+ optional settle time, elapsed time, and the final predicate value, which is the
573
+ preferred shape for long canvas gameplay loops. Setup receipt sampling favors
574
+ both first and last per-viewport receipts before filling remaining space, so
575
+ late lifecycle phases such as terminal or restart remain visible in compact
576
+ summaries.
550
577
 
551
578
  `target.timeout_sec` is optional. Use it for known-heavy profile targets so the
552
579
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
@@ -635,6 +635,7 @@ function profileSetupTapUntilReceipts(results) {
635
635
  max_taps: result.max_taps ?? result.max_calls ?? null,
636
636
  tap_burst_size: result.tap_burst_size ?? null,
637
637
  condition_check_count: result.condition_check_count ?? null,
638
+ settle_ms: result.settle_ms ?? null,
638
639
  elapsed_ms: result.elapsed_ms ?? null,
639
640
  interval_ms: result.interval_ms ?? null,
640
641
  timeout_ms: result.timeout_ms ?? null,
@@ -1280,6 +1281,10 @@ function normalizeSetupAction(input, index) {
1280
1281
  if (type === "tap_until" && tapBurstSize !== void 0 && (!Number.isInteger(tapBurstSize) || tapBurstSize < 1 || tapBurstSize > 100)) {
1281
1282
  throw new Error(`target.setup_actions[${index}].tap_burst_size must be an integer from 1 to 100.`);
1282
1283
  }
1284
+ const settleMs = type === "tap_until" ? numberValue(valueFromOwn(input, "settle_ms", "settleMs", "predicate_settle_ms", "predicateSettleMs", "post_burst_wait_ms", "postBurstWaitMs", "after_burst_ms", "afterBurstMs", "settle_after_tap_ms", "settleAfterTapMs")) : void 0;
1285
+ if (type === "tap_until" && settleMs !== void 0 && (!Number.isInteger(settleMs) || settleMs < 0 || settleMs > 1e4)) {
1286
+ throw new Error(`target.setup_actions[${index}].settle_ms must be an integer from 0 to 10000.`);
1287
+ }
1283
1288
  const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
1284
1289
  if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
1285
1290
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
@@ -1328,6 +1333,7 @@ function normalizeSetupAction(input, index) {
1328
1333
  until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
1329
1334
  max_calls: maxCalls,
1330
1335
  tap_burst_size: tapBurstSize,
1336
+ settle_ms: settleMs,
1331
1337
  interval_ms: intervalMs,
1332
1338
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
1333
1339
  min_value: minValue,
@@ -4569,6 +4575,7 @@ function profileSetupTapUntilReceipts(results) {
4569
4575
  max_taps: result.max_taps ?? result.max_calls ?? null,
4570
4576
  tap_burst_size: result.tap_burst_size ?? null,
4571
4577
  condition_check_count: result.condition_check_count ?? null,
4578
+ settle_ms: result.settle_ms ?? null,
4572
4579
  elapsed_ms: result.elapsed_ms ?? null,
4573
4580
  interval_ms: result.interval_ms ?? null,
4574
4581
  timeout_ms: result.timeout_ms ?? null,
@@ -6560,6 +6567,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6560
6567
  if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
6561
6568
  const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
6562
6569
  const tapBurstSize = Math.min(maxTaps, Math.min(100, Math.max(1, Math.floor(setupNumber(action.tap_burst_size ?? action.tapBurstSize ?? action.burst_size ?? action.burstSize ?? action.check_every_taps ?? action.checkEveryTaps ?? action.predicate_interval_taps ?? action.predicateIntervalTaps, 1) || 1))));
6570
+ const settleMs = Math.min(10000, Math.max(0, Math.floor(setupNumber(action.settle_ms ?? action.settleMs ?? action.predicate_settle_ms ?? action.predicateSettleMs ?? action.post_burst_wait_ms ?? action.postBurstWaitMs ?? action.after_burst_ms ?? action.afterBurstMs ?? action.settle_after_tap_ms ?? action.settleAfterTapMs, 0) || 0)));
6563
6571
  const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
6564
6572
  const scope = await setupActionScope(action, timeout);
6565
6573
  if (!scope.ok) return setupScopeFailure(base, scope);
@@ -6585,6 +6593,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6585
6593
  max_calls: maxTaps,
6586
6594
  tap_burst_size: tapBurstSize,
6587
6595
  condition_check_count: conditionCheckCount,
6596
+ settle_ms: settleMs,
6588
6597
  elapsed_ms: elapsedMs,
6589
6598
  interval_ms: intervalMs,
6590
6599
  timeout_ms: timeout,
@@ -6597,6 +6606,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6597
6606
  tapCount += 1;
6598
6607
  if (tapCount < maxTaps && burstIndex < burstCount - 1 && intervalMs) await page.waitForTimeout(intervalMs);
6599
6608
  }
6609
+ if (settleMs) await page.waitForTimeout(settleMs);
6600
6610
  lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
6601
6611
  conditionCheckCount += 1;
6602
6612
  if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
@@ -6614,6 +6624,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6614
6624
  max_calls: maxTaps,
6615
6625
  tap_burst_size: tapBurstSize,
6616
6626
  condition_check_count: conditionCheckCount,
6627
+ settle_ms: settleMs,
6617
6628
  elapsed_ms: elapsedMs,
6618
6629
  interval_ms: intervalMs,
6619
6630
  timeout_ms: timeout,
@@ -6634,6 +6645,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6634
6645
  max_calls: maxTaps,
6635
6646
  tap_burst_size: tapBurstSize,
6636
6647
  condition_check_count: conditionCheckCount,
6648
+ settle_ms: settleMs,
6637
6649
  elapsed_ms: elapsedMs,
6638
6650
  interval_ms: intervalMs,
6639
6651
  timeout_ms: timeout,
package/dist/cli.cjs CHANGED
@@ -7592,6 +7592,7 @@ function profileSetupTapUntilReceipts(results) {
7592
7592
  max_taps: result.max_taps ?? result.max_calls ?? null,
7593
7593
  tap_burst_size: result.tap_burst_size ?? null,
7594
7594
  condition_check_count: result.condition_check_count ?? null,
7595
+ settle_ms: result.settle_ms ?? null,
7595
7596
  elapsed_ms: result.elapsed_ms ?? null,
7596
7597
  interval_ms: result.interval_ms ?? null,
7597
7598
  timeout_ms: result.timeout_ms ?? null,
@@ -8237,6 +8238,10 @@ function normalizeSetupAction(input, index) {
8237
8238
  if (type === "tap_until" && tapBurstSize !== void 0 && (!Number.isInteger(tapBurstSize) || tapBurstSize < 1 || tapBurstSize > 100)) {
8238
8239
  throw new Error(`target.setup_actions[${index}].tap_burst_size must be an integer from 1 to 100.`);
8239
8240
  }
8241
+ const settleMs = type === "tap_until" ? numberValue(valueFromOwn(input, "settle_ms", "settleMs", "predicate_settle_ms", "predicateSettleMs", "post_burst_wait_ms", "postBurstWaitMs", "after_burst_ms", "afterBurstMs", "settle_after_tap_ms", "settleAfterTapMs")) : void 0;
8242
+ if (type === "tap_until" && settleMs !== void 0 && (!Number.isInteger(settleMs) || settleMs < 0 || settleMs > 1e4)) {
8243
+ throw new Error(`target.setup_actions[${index}].settle_ms must be an integer from 0 to 10000.`);
8244
+ }
8240
8245
  const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
8241
8246
  if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
8242
8247
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
@@ -8285,6 +8290,7 @@ function normalizeSetupAction(input, index) {
8285
8290
  until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
8286
8291
  max_calls: maxCalls,
8287
8292
  tap_burst_size: tapBurstSize,
8293
+ settle_ms: settleMs,
8288
8294
  interval_ms: intervalMs,
8289
8295
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
8290
8296
  min_value: minValue,
@@ -11510,6 +11516,7 @@ function profileSetupTapUntilReceipts(results) {
11510
11516
  max_taps: result.max_taps ?? result.max_calls ?? null,
11511
11517
  tap_burst_size: result.tap_burst_size ?? null,
11512
11518
  condition_check_count: result.condition_check_count ?? null,
11519
+ settle_ms: result.settle_ms ?? null,
11513
11520
  elapsed_ms: result.elapsed_ms ?? null,
11514
11521
  interval_ms: result.interval_ms ?? null,
11515
11522
  timeout_ms: result.timeout_ms ?? null,
@@ -13501,6 +13508,7 @@ async function executeSetupAction(action, ordinal, viewport) {
13501
13508
  if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
13502
13509
  const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
13503
13510
  const tapBurstSize = Math.min(maxTaps, Math.min(100, Math.max(1, Math.floor(setupNumber(action.tap_burst_size ?? action.tapBurstSize ?? action.burst_size ?? action.burstSize ?? action.check_every_taps ?? action.checkEveryTaps ?? action.predicate_interval_taps ?? action.predicateIntervalTaps, 1) || 1))));
13511
+ const settleMs = Math.min(10000, Math.max(0, Math.floor(setupNumber(action.settle_ms ?? action.settleMs ?? action.predicate_settle_ms ?? action.predicateSettleMs ?? action.post_burst_wait_ms ?? action.postBurstWaitMs ?? action.after_burst_ms ?? action.afterBurstMs ?? action.settle_after_tap_ms ?? action.settleAfterTapMs, 0) || 0)));
13504
13512
  const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
13505
13513
  const scope = await setupActionScope(action, timeout);
13506
13514
  if (!scope.ok) return setupScopeFailure(base, scope);
@@ -13526,6 +13534,7 @@ async function executeSetupAction(action, ordinal, viewport) {
13526
13534
  max_calls: maxTaps,
13527
13535
  tap_burst_size: tapBurstSize,
13528
13536
  condition_check_count: conditionCheckCount,
13537
+ settle_ms: settleMs,
13529
13538
  elapsed_ms: elapsedMs,
13530
13539
  interval_ms: intervalMs,
13531
13540
  timeout_ms: timeout,
@@ -13538,6 +13547,7 @@ async function executeSetupAction(action, ordinal, viewport) {
13538
13547
  tapCount += 1;
13539
13548
  if (tapCount < maxTaps && burstIndex < burstCount - 1 && intervalMs) await page.waitForTimeout(intervalMs);
13540
13549
  }
13550
+ if (settleMs) await page.waitForTimeout(settleMs);
13541
13551
  lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
13542
13552
  conditionCheckCount += 1;
13543
13553
  if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
@@ -13555,6 +13565,7 @@ async function executeSetupAction(action, ordinal, viewport) {
13555
13565
  max_calls: maxTaps,
13556
13566
  tap_burst_size: tapBurstSize,
13557
13567
  condition_check_count: conditionCheckCount,
13568
+ settle_ms: settleMs,
13558
13569
  elapsed_ms: elapsedMs,
13559
13570
  interval_ms: intervalMs,
13560
13571
  timeout_ms: timeout,
@@ -13575,6 +13586,7 @@ async function executeSetupAction(action, ordinal, viewport) {
13575
13586
  max_calls: maxTaps,
13576
13587
  tap_burst_size: tapBurstSize,
13577
13588
  condition_check_count: conditionCheckCount,
13589
+ settle_ms: settleMs,
13578
13590
  elapsed_ms: elapsedMs,
13579
13591
  interval_ms: intervalMs,
13580
13592
  timeout_ms: timeout,
@@ -16211,7 +16223,8 @@ function usage() {
16211
16223
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
16212
16224
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
16213
16225
  " riddle-proof-loop status --state-path <path>",
16214
- " 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>] [--result-format json|compact-json|summary|none; default json] [--quiet]",
16226
+ " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--viewport-name <name[,name...]>] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json] [--quiet]",
16227
+ " riddle-proof-loop run-profile recover --profile <file|json|-> --url <base-url> --job <job-id> [--viewport-name <name[,name...]>] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
16215
16228
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
16216
16229
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
16217
16230
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
@@ -16271,6 +16284,11 @@ function runProfileStrictOption(options) {
16271
16284
  function runProfileSplitViewportsOption(options) {
16272
16285
  return optionBoolean(options, "splitViewports") ?? false;
16273
16286
  }
16287
+ function runProfileViewportNamesOption(options) {
16288
+ const raw = optionString(options, "viewportName") ?? optionString(options, "viewportNames");
16289
+ if (!raw) return [];
16290
+ return raw.split(",").map((part) => part.trim()).filter(Boolean);
16291
+ }
16274
16292
  var DEFAULT_PROFILE_UNSUBMITTED_RETRY_TIMEOUT_MS = 9e4;
16275
16293
  var DEFAULT_PROFILE_UNSUBMITTED_RETRIES = 2;
16276
16294
  function optionNumber(options, ...keys) {
@@ -16871,6 +16889,61 @@ function profileHasRouteExitAffordanceReceipt(receipts) {
16871
16889
  return routeFields.some((name) => setupReturnSummaryValue(receipt, [name]) !== void 0) || haystack.includes("route=") || haystack.includes("browserpath=");
16872
16890
  });
16873
16891
  }
16892
+ function profileCleanupLabelMatches(value) {
16893
+ if (!value) return false;
16894
+ return /\b(cleanup|clean|clear|reset|undo|discard|new)\b/i.test(value);
16895
+ }
16896
+ function profileHasCleanupBoundaryAffordanceReceipt(receipts) {
16897
+ const visibleFields = [
16898
+ "cleanupControlVisible",
16899
+ "cleanupVisible",
16900
+ "clearControlVisible",
16901
+ "resetControlVisible",
16902
+ "undoVisible",
16903
+ "discardVisible",
16904
+ "newControlVisible",
16905
+ "exitControlVisible"
16906
+ ];
16907
+ const textFields = [
16908
+ "cleanupControlText",
16909
+ "cleanupText",
16910
+ "clearControlText",
16911
+ "resetControlText",
16912
+ "undoText",
16913
+ "discardText",
16914
+ "newControlText",
16915
+ "exitControlText",
16916
+ "controlText",
16917
+ "affordanceText"
16918
+ ];
16919
+ return receipts.some((receipt) => {
16920
+ const storedTo = cliString(receipt.return_stored_to) || "";
16921
+ const label = cliString(receipt.label) || "";
16922
+ const path7 = cliString(receipt.path) || cliString(receipt.function_name) || "";
16923
+ const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
16924
+ const haystack = `${storedTo} ${label} ${path7} ${summary}`.toLowerCase();
16925
+ const mentionsCleanupBoundary = haystack.includes("cleanup") || haystack.includes("precleanup") || haystack.includes("pre-cleanup") || haystack.includes("boundary") || haystack.includes("undo") || haystack.includes("clear") || haystack.includes("reset") || haystack.includes("discard");
16926
+ const visibleControl = visibleFields.some((name) => setupReturnSummaryValue(receipt, [name]) === true);
16927
+ const controlText = textFields.map((name) => cliString(setupReturnSummaryValue(receipt, [name]))).find((value) => profileCleanupLabelMatches(value));
16928
+ return mentionsCleanupBoundary && (visibleControl || Boolean(controlText));
16929
+ });
16930
+ }
16931
+ function profileVisibleCleanupActionCount(setupViewports) {
16932
+ const keys = /* @__PURE__ */ new Set();
16933
+ const clickedReceipts = setupViewports.flatMap((viewport) => [
16934
+ ...setupReceiptArray(viewport, "clicked"),
16935
+ ...setupReceiptArray(viewport, "tap"),
16936
+ ...setupReceiptArray(viewport, "tap_until")
16937
+ ]);
16938
+ clickedReceipts.forEach((receipt, index) => {
16939
+ if (receipt.ok === false) return;
16940
+ const text = cliString(receipt.text) || cliString(receipt.label) || cliString(receipt.target) || cliString(receipt.selector);
16941
+ if (!profileCleanupLabelMatches(text)) return;
16942
+ const ordinal = cliFiniteNumber(receipt.ordinal);
16943
+ keys.add(ordinal === void 0 ? `idx:${index}:${text}` : `ord:${ordinal}:${text}`);
16944
+ });
16945
+ return keys.size;
16946
+ }
16874
16947
  function profileHasOfflineAudioMetricsReceipt(receipts) {
16875
16948
  const metricFields = [
16876
16949
  "mixPeak",
@@ -16972,7 +17045,7 @@ function profileHasRecoveredStateReceipt(receipts) {
16972
17045
  const path7 = cliString(receipt.path) || cliString(receipt.function_name) || "";
16973
17046
  const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
16974
17047
  const haystack = `${storedTo} ${label} ${path7} ${summary}`.toLowerCase();
16975
- const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("try fix") || haystack.includes("tryfix") || haystack.includes("after-fix") || haystack.includes("fixed");
17048
+ const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("restart") || haystack.includes("play again") || haystack.includes("playagain") || haystack.includes("play-again") || haystack.includes("try fix") || haystack.includes("tryfix") || haystack.includes("after-fix") || haystack.includes("fixed");
16976
17049
  if (!labelsRecovery) return false;
16977
17050
  const status = profileLowerSummaryValue(receipt, ["status", "state", "phase"]);
16978
17051
  const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result"]);
@@ -16983,6 +17056,38 @@ function profileHasRecoveredStateReceipt(receipts) {
16983
17056
  return hasRecoveredState || success || hasValid && hasInvalid === false;
16984
17057
  });
16985
17058
  }
17059
+ function profileMetadataHasGeneratedOutputContract(metadata) {
17060
+ const contract = cliRecord(metadata.declared_state_contract);
17061
+ if (!contract) return false;
17062
+ const keys = Object.keys(contract).join(" ").toLowerCase();
17063
+ const values = Object.values(contract).map((value) => cliString(value)?.toLowerCase() || "").join(" ");
17064
+ const haystack = `${keys} ${values}`;
17065
+ return haystack.includes("generated_output") || haystack.includes("generated output") || haystack.includes("output-size") || haystack.includes("output size") || haystack.includes("output result") || haystack.includes("optimizer size");
17066
+ }
17067
+ function profileHasGeneratedOutputReceipt(receipts) {
17068
+ let outputReady = false;
17069
+ let outputChanged = false;
17070
+ for (const receipt of receipts) {
17071
+ const storedTo = cliString(receipt.return_stored_to) || "";
17072
+ const label = cliString(receipt.label) || "";
17073
+ const path7 = cliString(receipt.path) || cliString(receipt.function_name) || "";
17074
+ const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
17075
+ const haystack = `${storedTo} ${label} ${path7} ${summary}`.toLowerCase();
17076
+ const readySignal = setupReturnSummaryValue(receipt, ["outputReady", "outputStillReady"]) === true || cliFiniteNumber(setupReturnSummaryValue(receipt, ["surfaceCount", "before.surfaceCount", "after.surfaceCount"])) !== void 0 || setupReturnSummaryValue(receipt, ["size", "before.size", "after.size", "size.text", "before.size.text", "after.size.text"]) !== void 0;
17077
+ if (readySignal && (haystack.includes("output") || haystack.includes("size") || haystack.includes("result"))) {
17078
+ outputReady = true;
17079
+ }
17080
+ const beforeBytes = cliFiniteNumber(setupReturnSummaryValue(receipt, ["before.size.outputBytes", "before.outputBytes", "beforeBytes"]));
17081
+ const afterBytes = cliFiniteNumber(setupReturnSummaryValue(receipt, ["after.size.outputBytes", "after.outputBytes", "afterBytes"]));
17082
+ const beforeText = cliString(setupReturnSummaryValue(receipt, ["before.size.text", "before.outputText", "beforeText"]));
17083
+ const afterText = cliString(setupReturnSummaryValue(receipt, ["after.size.text", "after.outputText", "afterText"]));
17084
+ const explicitChange = setupReturnSummaryValue(receipt, ["sizeChanged", "outputChanged", "resultChanged"]) === true;
17085
+ const byteChange = beforeBytes !== void 0 && afterBytes !== void 0 && beforeBytes !== afterBytes;
17086
+ const textChange = Boolean(beforeText && afterText && beforeText !== afterText);
17087
+ if (explicitChange || byteChange || textChange) outputChanged = true;
17088
+ }
17089
+ return outputReady && outputChanged;
17090
+ }
16986
17091
  function profilePackReceiptStatus(result, metadata, receipt) {
16987
17092
  const text = receipt.toLowerCase();
16988
17093
  const setupSummary = profileSetupSummaryRecord(result);
@@ -17013,6 +17118,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17013
17118
  const clickFallbackTapCount = clickFallbackTapKeys.size;
17014
17119
  const tapUntilCount = profileSetupReceiptTotal(setupViewports, "tap_until");
17015
17120
  const visibleUiActionCount = clickCount + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount;
17121
+ const visibleCleanupActionCount = profileVisibleCleanupActionCount(setupViewports);
17016
17122
  const setupFailureCount = profileSetupFailureCount(setupViewports);
17017
17123
  const setupObstructionCount = profileSetupObstructionCount(setupViewports);
17018
17124
  const inputDispatchCount = profileSetupReceiptTotal(setupViewports, "drag") + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount + profileSetupReceiptTotal(setupViewports, "press") + profileSetupReceiptTotal(setupViewports, "keyboard_sequence");
@@ -17048,6 +17154,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17048
17154
  const hasTextAbsence = profileHasPassedCheck(result, ["text_absent", "selector_text_absent"]);
17049
17155
  const hasMeasuredStateChange = hasNaturalInput || hasCanvasChange || valueReceipts.some((item) => setupReturnSummaryValue(item, ["changed"]) === true || setupReturnSummaryValue(item, ["nonWhiteDelta", "darkDelta", "pixelDelta", "movementDelta"]) !== void 0);
17050
17156
  const hasRouteExitAffordanceReceipt = profileHasRouteExitAffordanceReceipt(valueReceipts);
17157
+ const hasCleanupBoundaryAffordanceReceipt = profileHasCleanupBoundaryAffordanceReceipt(valueReceipts);
17051
17158
  const hasOfflineAudioMetricsReceipt = profileHasOfflineAudioMetricsReceipt(valueReceipts);
17052
17159
  const hasActiveRouteLocalProofReceipt = profileHasActiveRouteLocalProofReceipt(valueReceipts);
17053
17160
  const hasTerminalLossReceipt = profileHasTerminalLossReceipt(valueReceipts);
@@ -17056,6 +17163,8 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17056
17163
  const hasControlledSuccessLaunchReceipt = profileHasControlledLaunchReceipt(valueReceipts, "success");
17057
17164
  const hasRouteContinuationReceipt = profileHasRouteContinuationReceipt(valueReceipts);
17058
17165
  const hasRecoveredStateReceipt = profileHasRecoveredStateReceipt(valueReceipts);
17166
+ const hasGeneratedOutputContract = profileMetadataHasGeneratedOutputContract(metadata);
17167
+ const hasGeneratedOutputReceipt = profileHasGeneratedOutputReceipt(valueReceipts);
17059
17168
  const failedCleanupInventoryReason = profileFailedCleanupInventoryReason(setupViewports);
17060
17169
  const passedCleanupInventoryReason = profilePassedCleanupInventoryReason(setupViewports);
17061
17170
  if (text.includes("artifact link") || text.includes("artifact path")) {
@@ -17109,6 +17218,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17109
17218
  }
17110
17219
  return profileReceiptSignalStatus(hasTextAbsence, "absence check passed", "absence check missing");
17111
17220
  }
17221
+ if (text.includes("generated-output") || text.includes("generated output") || text.includes("output-size") || text.includes("output size") || (text.includes("output") || text.includes("result")) && (text.includes("mutation") || text.includes("final"))) {
17222
+ return profileReceiptSignalStatus(
17223
+ hasGeneratedOutputContract && hasGeneratedOutputReceipt,
17224
+ "generated-output mutation receipt present",
17225
+ "generated-output mutation receipt missing"
17226
+ );
17227
+ }
17112
17228
  if (text.includes("recovered") || text.includes("final state")) {
17113
17229
  return profileReceiptSignalStatus(hasStateContract || hasTextVisibility, "final state receipt present", "final state receipt missing");
17114
17230
  }
@@ -17165,6 +17281,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17165
17281
  "route continuation receipt missing"
17166
17282
  );
17167
17283
  }
17284
+ if (text.includes("cleanup") && text.includes("action") && (text.includes("visible ui") || text.includes("visible"))) {
17285
+ return profileReceiptSignalStatus(
17286
+ visibleCleanupActionCount > 0,
17287
+ `visible cleanup action receipt present (${visibleCleanupActionCount})`,
17288
+ "visible cleanup action receipt missing"
17289
+ );
17290
+ }
17168
17291
  if (text.includes("through visible ui") || text.includes("visible ui action") || text.includes("ui-routed") || text.includes("ui routed") || text.includes("visible") && text.includes("route") && text.includes("exit") && text.includes("action") || text.includes("visible") && text.includes("mode") && text.includes("exit") && text.includes("action")) {
17169
17292
  return profileReceiptSignalStatus(
17170
17293
  visibleUiActionCount > 0,
@@ -17179,6 +17302,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
17179
17302
  "affordance receipt missing"
17180
17303
  );
17181
17304
  }
17305
+ if (text.includes("cleanup") && (text.includes("affordance") || text.includes("control") || text.includes("boundary") || text.includes("inventory"))) {
17306
+ return profileReceiptSignalStatus(
17307
+ hasCleanupBoundaryAffordanceReceipt,
17308
+ "visible cleanup affordance receipt present",
17309
+ "visible cleanup affordance receipt missing"
17310
+ );
17311
+ }
17182
17312
  if (text.includes("retry") || text.includes("repair") || text.includes("reset") || text.includes("affordance")) {
17183
17313
  return profileReceiptSignalStatus(hasStateContract || clickCount > 0, "affordance or transition receipt present", "affordance receipt missing");
17184
17314
  }
@@ -18084,6 +18214,7 @@ function profileSetupSummaryMarkdown(result) {
18084
18214
  const maxTaps = cliFiniteNumber(receipt.max_taps) ?? cliFiniteNumber(receipt.max_calls);
18085
18215
  const tapBurstSize = cliFiniteNumber(receipt.tap_burst_size);
18086
18216
  const conditionCheckCount = cliFiniteNumber(receipt.condition_check_count);
18217
+ const settleMs = cliFiniteNumber(receipt.settle_ms);
18087
18218
  const elapsedMs3 = cliFiniteNumber(receipt.elapsed_ms);
18088
18219
  const ok = receipt.ok === false ? "failed" : "ok";
18089
18220
  const reason = cliString(receipt.reason);
@@ -18091,8 +18222,9 @@ function profileSetupSummaryMarkdown(result) {
18091
18222
  const tapText = tapCount === void 0 ? "" : ` in ${tapCount}${maxTaps === void 0 ? "" : `/${maxTaps}`} tap(s)`;
18092
18223
  const burstText = tapBurstSize === void 0 || tapBurstSize <= 1 ? "" : `, burst ${tapBurstSize}`;
18093
18224
  const conditionCheckText = conditionCheckCount === void 0 ? "" : `, ${conditionCheckCount} check(s)`;
18225
+ const settleText = settleMs === void 0 || settleMs <= 0 ? "" : `, settle ${settleMs}ms`;
18094
18226
  const elapsedText = elapsedMs3 === void 0 ? "" : `, elapsed ${elapsedMs3}ms`;
18095
- lines.push(`- ${name} tap_until: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`} until ${markdownInlineCode(untilPath)}${expected === void 0 ? "" : ` == ${markdownInlineCode(expected, 80)}`}${tapText}${burstText}${conditionCheckText}${elapsedText}${actual === void 0 ? "" : `, observed ${markdownInlineCode(actual, 80)}`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
18227
+ lines.push(`- ${name} tap_until: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`} until ${markdownInlineCode(untilPath)}${expected === void 0 ? "" : ` == ${markdownInlineCode(expected, 80)}`}${tapText}${burstText}${conditionCheckText}${settleText}${elapsedText}${actual === void 0 ? "" : `, observed ${markdownInlineCode(actual, 80)}`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
18096
18228
  }
18097
18229
  if (tapUntilDetails.length > sampledTapUntilDetails.length) lines.push(`- ${tapUntilDetails.length - sampledTapUntilDetails.length} additional tap_until receipt(s) omitted.`);
18098
18230
  const keyboardGroups = viewports.map((viewport) => {
@@ -18514,6 +18646,21 @@ function writeProfileOutput(outputDir, result) {
18514
18646
  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)}
18515
18647
  `);
18516
18648
  }
18649
+ function writeRiddleJobReceipt(outputDir, input) {
18650
+ if (!outputDir) return;
18651
+ (0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
18652
+ (0, import_node_fs6.writeFileSync)(import_node_path6.default.join(outputDir, "riddle-job.json"), `${JSON.stringify({
18653
+ version: "riddle-proof.riddle-job-receipt.v1",
18654
+ profile_name: input.profile.name,
18655
+ job_id: input.jobId,
18656
+ target_url: input.targetUrl,
18657
+ viewport: input.viewport || null,
18658
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
18659
+ created: input.created || null,
18660
+ recovery_command: `riddle-proof-loop run-profile recover --profile <profile> --job ${input.jobId} --output-dir ${outputDir}`
18661
+ }, null, 2)}
18662
+ `);
18663
+ }
18517
18664
  async function readArtifactJson(artifact) {
18518
18665
  const target = artifact.url || artifact.path;
18519
18666
  if (!target) return void 0;
@@ -18717,6 +18864,43 @@ function profileForSplitViewport(profile, viewport) {
18717
18864
  }
18718
18865
  };
18719
18866
  }
18867
+ function profileItemAppliesToAnySelectedViewport(item, viewports) {
18868
+ if (!item.viewports?.length) return true;
18869
+ const names = new Set(viewports.map((viewport) => viewport.name).filter(Boolean));
18870
+ return item.viewports.some((name) => names.has(name));
18871
+ }
18872
+ function profileForSelectedViewports(profile, viewports) {
18873
+ const suffix = viewports.map((viewport) => viewport.name || `${viewport.width}x${viewport.height}`).join("-");
18874
+ const setupActions = profile.target.setup_actions?.filter((action) => profileItemAppliesToAnySelectedViewport(action, viewports));
18875
+ return {
18876
+ ...profile,
18877
+ name: `${profile.name}-${suffix}`,
18878
+ checks: profile.checks.filter((check) => profileItemAppliesToAnySelectedViewport(check, viewports)),
18879
+ target: {
18880
+ ...profile.target,
18881
+ viewports,
18882
+ ...setupActions ? { setup_actions: setupActions } : {}
18883
+ },
18884
+ metadata: {
18885
+ ...profile.metadata || {},
18886
+ selected_parent_profile: profile.name,
18887
+ selected_viewports: viewports.map((viewport) => viewport.name || `${viewport.width}x${viewport.height}`)
18888
+ }
18889
+ };
18890
+ }
18891
+ function profileWithSelectedViewportNamesForCli(profile, options) {
18892
+ const names = runProfileViewportNamesOption(options);
18893
+ if (!names.length) return profile;
18894
+ const requested = new Set(names);
18895
+ const viewports = profile.target.viewports.filter((viewport) => viewport.name && requested.has(viewport.name));
18896
+ const matched = new Set(viewports.map((viewport) => viewport.name).filter(Boolean));
18897
+ const missing = names.filter((name) => !matched.has(name));
18898
+ if (missing.length) {
18899
+ const available = profile.target.viewports.map((viewport) => viewport.name).filter(Boolean).join(", ") || "none";
18900
+ throw new Error(`Unknown --viewport-name ${missing.join(", ")}. Available viewport names: ${available}.`);
18901
+ }
18902
+ return profileForSelectedViewports(profile, viewports);
18903
+ }
18720
18904
  function safeProfileOutputSegment(value) {
18721
18905
  const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
18722
18906
  return safe || "viewport";
@@ -18905,6 +19089,13 @@ async function runSingleRiddleProfileForCli(profile, options, input) {
18905
19089
  const directResult = extractRiddleProofProfileResult(created);
18906
19090
  return directResult ? withRiddleMetadata(withProfileMetadata(profile, directResult), { artifacts: collectRiddleProfileArtifactRefs(created) }) : createRiddleProofProfileInsufficientResult({ profile, runner, error: "Riddle run response was missing job_id.", artifacts: collectRiddleProfileArtifactRefs(created) });
18907
19091
  }
19092
+ writeRiddleJobReceipt(input.outputDir, {
19093
+ profile,
19094
+ jobId,
19095
+ targetUrl,
19096
+ viewport: profile.target.viewports[0],
19097
+ created
19098
+ });
18908
19099
  poll = await client.pollJob(jobId, pollOptions);
18909
19100
  if (attempt < retryLimit && shouldRetryUnsubmittedRiddleJob(poll)) {
18910
19101
  const recoveredResult = await recoverProfileResultFromRiddleArtifacts(profile, {
@@ -18981,10 +19172,9 @@ async function runSplitViewportProfileForCli(profile, options, input) {
18981
19172
  const childRuns = [];
18982
19173
  for (const viewport of profile.target.viewports) {
18983
19174
  const childProfile = profileForSplitViewport(profile, viewport);
18984
- const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
18985
- if (outputDir) {
18986
- writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
18987
- }
19175
+ const childOutputDir = outputDir ? splitViewportOutputDir(outputDir, viewport.name, seenOutputNames) : void 0;
19176
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, { ...input, outputDir: childOutputDir });
19177
+ if (childOutputDir) writeProfileOutput(childOutputDir, result2);
18988
19178
  childRuns.push({ viewport, profile: childProfile, result: result2 });
18989
19179
  }
18990
19180
  const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
@@ -19006,6 +19196,51 @@ async function runSplitViewportProfileForCli(profile, options, input) {
19006
19196
  });
19007
19197
  return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
19008
19198
  }
19199
+ async function recoverProfileForCli(profile, options) {
19200
+ const runner = optionString(options, "runner") || "riddle";
19201
+ if (runner !== "riddle") {
19202
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
19203
+ }
19204
+ const jobId = optionString(options, "job") ?? optionString(options, "jobId");
19205
+ if (!jobId) throw new Error("run-profile recover requires --job <job-id>.");
19206
+ const client = createRiddleApiClient(riddleClientConfig(options));
19207
+ let artifactPayload;
19208
+ try {
19209
+ artifactPayload = await client.requestJson(`/v1/jobs/${jobId}/artifacts`);
19210
+ } catch (error) {
19211
+ return createRiddleProofProfileEnvironmentBlockedResult({
19212
+ profile,
19213
+ runner,
19214
+ error,
19215
+ riddle: { job_id: jobId, terminal: false }
19216
+ });
19217
+ }
19218
+ const artifacts = collectRiddleProfileArtifactRefs(artifactPayload);
19219
+ const artifactStatus = riddleArtifactsPayloadStatus(artifactPayload);
19220
+ const terminal = artifactStatus ? isTerminalRiddleJobStatus(artifactStatus) : artifacts.length > 0;
19221
+ const recovered = await profileResultFromRiddleArtifacts(profile, artifacts, [artifactPayload]);
19222
+ if (recovered) {
19223
+ return withRiddleMetadata(recovered, {
19224
+ job_id: jobId,
19225
+ status: artifactStatus,
19226
+ terminal,
19227
+ artifacts,
19228
+ artifactRecovery: true
19229
+ });
19230
+ }
19231
+ return createRiddleProofProfileInsufficientResult({
19232
+ profile,
19233
+ runner,
19234
+ error: artifacts.length ? `Riddle job ${jobId} artifacts were recovered without a proof result.` : `Riddle job ${jobId} had no recoverable artifacts.`,
19235
+ riddle: {
19236
+ job_id: jobId,
19237
+ status: artifactStatus,
19238
+ terminal,
19239
+ artifact_recovery: artifacts.length > 0
19240
+ },
19241
+ artifacts
19242
+ });
19243
+ }
19009
19244
  async function runProfileForCli(profile, options) {
19010
19245
  const runner = optionString(options, "runner") || "riddle";
19011
19246
  if (runner !== "riddle") {
@@ -19015,7 +19250,7 @@ async function runProfileForCli(profile, options) {
19015
19250
  if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
19016
19251
  return runSplitViewportProfileForCli(profile, options, { client, runner });
19017
19252
  }
19018
- return runSingleRiddleProfileForCli(profile, options, { client, runner });
19253
+ return runSingleRiddleProfileForCli(profile, options, { client, runner, outputDir: profileOutputDirOption(options) });
19019
19254
  }
19020
19255
  function requestForRun(options) {
19021
19256
  const statePath = optionString(options, "statePath");
@@ -19064,8 +19299,8 @@ async function main() {
19064
19299
  return;
19065
19300
  }
19066
19301
  if (command === "run-profile") {
19067
- const profile = normalizeProfileForCli(options);
19068
- const result = await runProfileForCli(profile, options);
19302
+ const profile = profileWithSelectedViewportNamesForCli(normalizeProfileForCli(options), options);
19303
+ const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : await runProfileForCli(profile, options);
19069
19304
  writeProfileOutput(profileOutputDirOption(options), result);
19070
19305
  const diagnosticLine = profileCliDiagnosticLine(result);
19071
19306
  if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  profileStatusExitCode,
14
14
  resolveRiddleProofProfileTargetUrl,
15
15
  resolveRiddleProofProfileTimeoutSec
16
- } from "./chunk-FNVDZCVZ.js";
16
+ } from "./chunk-OUZKZ5U4.js";
17
17
  import {
18
18
  createRiddleApiClient,
19
19
  isTerminalRiddleJobStatus,
@@ -48,7 +48,8 @@ function usage() {
48
48
  " riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
49
49
  " riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
50
50
  " riddle-proof-loop status --state-path <path>",
51
- " 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>] [--result-format json|compact-json|summary|none; default json] [--quiet]",
51
+ " riddle-proof-loop run-profile --profile <file|json|-> --url <base-url> [--runner riddle] [--viewport-name <name[,name...]>] [--strict true|false; default false] [--split-viewports true|false; default false] [--poll-attempts n] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json] [--quiet]",
52
+ " riddle-proof-loop run-profile recover --profile <file|json|-> --url <base-url> --job <job-id> [--viewport-name <name[,name...]>] [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
52
53
  " riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
53
54
  " riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
54
55
  " riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
@@ -108,6 +109,11 @@ function runProfileStrictOption(options) {
108
109
  function runProfileSplitViewportsOption(options) {
109
110
  return optionBoolean(options, "splitViewports") ?? false;
110
111
  }
112
+ function runProfileViewportNamesOption(options) {
113
+ const raw = optionString(options, "viewportName") ?? optionString(options, "viewportNames");
114
+ if (!raw) return [];
115
+ return raw.split(",").map((part) => part.trim()).filter(Boolean);
116
+ }
111
117
  var DEFAULT_PROFILE_UNSUBMITTED_RETRY_TIMEOUT_MS = 9e4;
112
118
  var DEFAULT_PROFILE_UNSUBMITTED_RETRIES = 2;
113
119
  function optionNumber(options, ...keys) {
@@ -708,6 +714,61 @@ function profileHasRouteExitAffordanceReceipt(receipts) {
708
714
  return routeFields.some((name) => setupReturnSummaryValue(receipt, [name]) !== void 0) || haystack.includes("route=") || haystack.includes("browserpath=");
709
715
  });
710
716
  }
717
+ function profileCleanupLabelMatches(value) {
718
+ if (!value) return false;
719
+ return /\b(cleanup|clean|clear|reset|undo|discard|new)\b/i.test(value);
720
+ }
721
+ function profileHasCleanupBoundaryAffordanceReceipt(receipts) {
722
+ const visibleFields = [
723
+ "cleanupControlVisible",
724
+ "cleanupVisible",
725
+ "clearControlVisible",
726
+ "resetControlVisible",
727
+ "undoVisible",
728
+ "discardVisible",
729
+ "newControlVisible",
730
+ "exitControlVisible"
731
+ ];
732
+ const textFields = [
733
+ "cleanupControlText",
734
+ "cleanupText",
735
+ "clearControlText",
736
+ "resetControlText",
737
+ "undoText",
738
+ "discardText",
739
+ "newControlText",
740
+ "exitControlText",
741
+ "controlText",
742
+ "affordanceText"
743
+ ];
744
+ return receipts.some((receipt) => {
745
+ const storedTo = cliString(receipt.return_stored_to) || "";
746
+ const label = cliString(receipt.label) || "";
747
+ const path2 = cliString(receipt.path) || cliString(receipt.function_name) || "";
748
+ const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
749
+ const haystack = `${storedTo} ${label} ${path2} ${summary}`.toLowerCase();
750
+ const mentionsCleanupBoundary = haystack.includes("cleanup") || haystack.includes("precleanup") || haystack.includes("pre-cleanup") || haystack.includes("boundary") || haystack.includes("undo") || haystack.includes("clear") || haystack.includes("reset") || haystack.includes("discard");
751
+ const visibleControl = visibleFields.some((name) => setupReturnSummaryValue(receipt, [name]) === true);
752
+ const controlText = textFields.map((name) => cliString(setupReturnSummaryValue(receipt, [name]))).find((value) => profileCleanupLabelMatches(value));
753
+ return mentionsCleanupBoundary && (visibleControl || Boolean(controlText));
754
+ });
755
+ }
756
+ function profileVisibleCleanupActionCount(setupViewports) {
757
+ const keys = /* @__PURE__ */ new Set();
758
+ const clickedReceipts = setupViewports.flatMap((viewport) => [
759
+ ...setupReceiptArray(viewport, "clicked"),
760
+ ...setupReceiptArray(viewport, "tap"),
761
+ ...setupReceiptArray(viewport, "tap_until")
762
+ ]);
763
+ clickedReceipts.forEach((receipt, index) => {
764
+ if (receipt.ok === false) return;
765
+ const text = cliString(receipt.text) || cliString(receipt.label) || cliString(receipt.target) || cliString(receipt.selector);
766
+ if (!profileCleanupLabelMatches(text)) return;
767
+ const ordinal = cliFiniteNumber(receipt.ordinal);
768
+ keys.add(ordinal === void 0 ? `idx:${index}:${text}` : `ord:${ordinal}:${text}`);
769
+ });
770
+ return keys.size;
771
+ }
711
772
  function profileHasOfflineAudioMetricsReceipt(receipts) {
712
773
  const metricFields = [
713
774
  "mixPeak",
@@ -809,7 +870,7 @@ function profileHasRecoveredStateReceipt(receipts) {
809
870
  const path2 = cliString(receipt.path) || cliString(receipt.function_name) || "";
810
871
  const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
811
872
  const haystack = `${storedTo} ${label} ${path2} ${summary}`.toLowerCase();
812
- const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("try fix") || haystack.includes("tryfix") || haystack.includes("after-fix") || haystack.includes("fixed");
873
+ const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("restart") || haystack.includes("play again") || haystack.includes("playagain") || haystack.includes("play-again") || haystack.includes("try fix") || haystack.includes("tryfix") || haystack.includes("after-fix") || haystack.includes("fixed");
813
874
  if (!labelsRecovery) return false;
814
875
  const status = profileLowerSummaryValue(receipt, ["status", "state", "phase"]);
815
876
  const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result"]);
@@ -820,6 +881,38 @@ function profileHasRecoveredStateReceipt(receipts) {
820
881
  return hasRecoveredState || success || hasValid && hasInvalid === false;
821
882
  });
822
883
  }
884
+ function profileMetadataHasGeneratedOutputContract(metadata) {
885
+ const contract = cliRecord(metadata.declared_state_contract);
886
+ if (!contract) return false;
887
+ const keys = Object.keys(contract).join(" ").toLowerCase();
888
+ const values = Object.values(contract).map((value) => cliString(value)?.toLowerCase() || "").join(" ");
889
+ const haystack = `${keys} ${values}`;
890
+ return haystack.includes("generated_output") || haystack.includes("generated output") || haystack.includes("output-size") || haystack.includes("output size") || haystack.includes("output result") || haystack.includes("optimizer size");
891
+ }
892
+ function profileHasGeneratedOutputReceipt(receipts) {
893
+ let outputReady = false;
894
+ let outputChanged = false;
895
+ for (const receipt of receipts) {
896
+ const storedTo = cliString(receipt.return_stored_to) || "";
897
+ const label = cliString(receipt.label) || "";
898
+ const path2 = cliString(receipt.path) || cliString(receipt.function_name) || "";
899
+ const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
900
+ const haystack = `${storedTo} ${label} ${path2} ${summary}`.toLowerCase();
901
+ const readySignal = setupReturnSummaryValue(receipt, ["outputReady", "outputStillReady"]) === true || cliFiniteNumber(setupReturnSummaryValue(receipt, ["surfaceCount", "before.surfaceCount", "after.surfaceCount"])) !== void 0 || setupReturnSummaryValue(receipt, ["size", "before.size", "after.size", "size.text", "before.size.text", "after.size.text"]) !== void 0;
902
+ if (readySignal && (haystack.includes("output") || haystack.includes("size") || haystack.includes("result"))) {
903
+ outputReady = true;
904
+ }
905
+ const beforeBytes = cliFiniteNumber(setupReturnSummaryValue(receipt, ["before.size.outputBytes", "before.outputBytes", "beforeBytes"]));
906
+ const afterBytes = cliFiniteNumber(setupReturnSummaryValue(receipt, ["after.size.outputBytes", "after.outputBytes", "afterBytes"]));
907
+ const beforeText = cliString(setupReturnSummaryValue(receipt, ["before.size.text", "before.outputText", "beforeText"]));
908
+ const afterText = cliString(setupReturnSummaryValue(receipt, ["after.size.text", "after.outputText", "afterText"]));
909
+ const explicitChange = setupReturnSummaryValue(receipt, ["sizeChanged", "outputChanged", "resultChanged"]) === true;
910
+ const byteChange = beforeBytes !== void 0 && afterBytes !== void 0 && beforeBytes !== afterBytes;
911
+ const textChange = Boolean(beforeText && afterText && beforeText !== afterText);
912
+ if (explicitChange || byteChange || textChange) outputChanged = true;
913
+ }
914
+ return outputReady && outputChanged;
915
+ }
823
916
  function profilePackReceiptStatus(result, metadata, receipt) {
824
917
  const text = receipt.toLowerCase();
825
918
  const setupSummary = profileSetupSummaryRecord(result);
@@ -850,6 +943,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
850
943
  const clickFallbackTapCount = clickFallbackTapKeys.size;
851
944
  const tapUntilCount = profileSetupReceiptTotal(setupViewports, "tap_until");
852
945
  const visibleUiActionCount = clickCount + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount;
946
+ const visibleCleanupActionCount = profileVisibleCleanupActionCount(setupViewports);
853
947
  const setupFailureCount = profileSetupFailureCount(setupViewports);
854
948
  const setupObstructionCount = profileSetupObstructionCount(setupViewports);
855
949
  const inputDispatchCount = profileSetupReceiptTotal(setupViewports, "drag") + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount + profileSetupReceiptTotal(setupViewports, "press") + profileSetupReceiptTotal(setupViewports, "keyboard_sequence");
@@ -885,6 +979,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
885
979
  const hasTextAbsence = profileHasPassedCheck(result, ["text_absent", "selector_text_absent"]);
886
980
  const hasMeasuredStateChange = hasNaturalInput || hasCanvasChange || valueReceipts.some((item) => setupReturnSummaryValue(item, ["changed"]) === true || setupReturnSummaryValue(item, ["nonWhiteDelta", "darkDelta", "pixelDelta", "movementDelta"]) !== void 0);
887
981
  const hasRouteExitAffordanceReceipt = profileHasRouteExitAffordanceReceipt(valueReceipts);
982
+ const hasCleanupBoundaryAffordanceReceipt = profileHasCleanupBoundaryAffordanceReceipt(valueReceipts);
888
983
  const hasOfflineAudioMetricsReceipt = profileHasOfflineAudioMetricsReceipt(valueReceipts);
889
984
  const hasActiveRouteLocalProofReceipt = profileHasActiveRouteLocalProofReceipt(valueReceipts);
890
985
  const hasTerminalLossReceipt = profileHasTerminalLossReceipt(valueReceipts);
@@ -893,6 +988,8 @@ function profilePackReceiptStatus(result, metadata, receipt) {
893
988
  const hasControlledSuccessLaunchReceipt = profileHasControlledLaunchReceipt(valueReceipts, "success");
894
989
  const hasRouteContinuationReceipt = profileHasRouteContinuationReceipt(valueReceipts);
895
990
  const hasRecoveredStateReceipt = profileHasRecoveredStateReceipt(valueReceipts);
991
+ const hasGeneratedOutputContract = profileMetadataHasGeneratedOutputContract(metadata);
992
+ const hasGeneratedOutputReceipt = profileHasGeneratedOutputReceipt(valueReceipts);
896
993
  const failedCleanupInventoryReason = profileFailedCleanupInventoryReason(setupViewports);
897
994
  const passedCleanupInventoryReason = profilePassedCleanupInventoryReason(setupViewports);
898
995
  if (text.includes("artifact link") || text.includes("artifact path")) {
@@ -946,6 +1043,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
946
1043
  }
947
1044
  return profileReceiptSignalStatus(hasTextAbsence, "absence check passed", "absence check missing");
948
1045
  }
1046
+ if (text.includes("generated-output") || text.includes("generated output") || text.includes("output-size") || text.includes("output size") || (text.includes("output") || text.includes("result")) && (text.includes("mutation") || text.includes("final"))) {
1047
+ return profileReceiptSignalStatus(
1048
+ hasGeneratedOutputContract && hasGeneratedOutputReceipt,
1049
+ "generated-output mutation receipt present",
1050
+ "generated-output mutation receipt missing"
1051
+ );
1052
+ }
949
1053
  if (text.includes("recovered") || text.includes("final state")) {
950
1054
  return profileReceiptSignalStatus(hasStateContract || hasTextVisibility, "final state receipt present", "final state receipt missing");
951
1055
  }
@@ -1002,6 +1106,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
1002
1106
  "route continuation receipt missing"
1003
1107
  );
1004
1108
  }
1109
+ if (text.includes("cleanup") && text.includes("action") && (text.includes("visible ui") || text.includes("visible"))) {
1110
+ return profileReceiptSignalStatus(
1111
+ visibleCleanupActionCount > 0,
1112
+ `visible cleanup action receipt present (${visibleCleanupActionCount})`,
1113
+ "visible cleanup action receipt missing"
1114
+ );
1115
+ }
1005
1116
  if (text.includes("through visible ui") || text.includes("visible ui action") || text.includes("ui-routed") || text.includes("ui routed") || text.includes("visible") && text.includes("route") && text.includes("exit") && text.includes("action") || text.includes("visible") && text.includes("mode") && text.includes("exit") && text.includes("action")) {
1006
1117
  return profileReceiptSignalStatus(
1007
1118
  visibleUiActionCount > 0,
@@ -1016,6 +1127,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
1016
1127
  "affordance receipt missing"
1017
1128
  );
1018
1129
  }
1130
+ if (text.includes("cleanup") && (text.includes("affordance") || text.includes("control") || text.includes("boundary") || text.includes("inventory"))) {
1131
+ return profileReceiptSignalStatus(
1132
+ hasCleanupBoundaryAffordanceReceipt,
1133
+ "visible cleanup affordance receipt present",
1134
+ "visible cleanup affordance receipt missing"
1135
+ );
1136
+ }
1019
1137
  if (text.includes("retry") || text.includes("repair") || text.includes("reset") || text.includes("affordance")) {
1020
1138
  return profileReceiptSignalStatus(hasStateContract || clickCount > 0, "affordance or transition receipt present", "affordance receipt missing");
1021
1139
  }
@@ -1921,6 +2039,7 @@ function profileSetupSummaryMarkdown(result) {
1921
2039
  const maxTaps = cliFiniteNumber(receipt.max_taps) ?? cliFiniteNumber(receipt.max_calls);
1922
2040
  const tapBurstSize = cliFiniteNumber(receipt.tap_burst_size);
1923
2041
  const conditionCheckCount = cliFiniteNumber(receipt.condition_check_count);
2042
+ const settleMs = cliFiniteNumber(receipt.settle_ms);
1924
2043
  const elapsedMs = cliFiniteNumber(receipt.elapsed_ms);
1925
2044
  const ok = receipt.ok === false ? "failed" : "ok";
1926
2045
  const reason = cliString(receipt.reason);
@@ -1928,8 +2047,9 @@ function profileSetupSummaryMarkdown(result) {
1928
2047
  const tapText = tapCount === void 0 ? "" : ` in ${tapCount}${maxTaps === void 0 ? "" : `/${maxTaps}`} tap(s)`;
1929
2048
  const burstText = tapBurstSize === void 0 || tapBurstSize <= 1 ? "" : `, burst ${tapBurstSize}`;
1930
2049
  const conditionCheckText = conditionCheckCount === void 0 ? "" : `, ${conditionCheckCount} check(s)`;
2050
+ const settleText = settleMs === void 0 || settleMs <= 0 ? "" : `, settle ${settleMs}ms`;
1931
2051
  const elapsedText = elapsedMs === void 0 ? "" : `, elapsed ${elapsedMs}ms`;
1932
- lines.push(`- ${name} tap_until: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`} until ${markdownInlineCode(untilPath)}${expected === void 0 ? "" : ` == ${markdownInlineCode(expected, 80)}`}${tapText}${burstText}${conditionCheckText}${elapsedText}${actual === void 0 ? "" : `, observed ${markdownInlineCode(actual, 80)}`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
2052
+ lines.push(`- ${name} tap_until: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`} until ${markdownInlineCode(untilPath)}${expected === void 0 ? "" : ` == ${markdownInlineCode(expected, 80)}`}${tapText}${burstText}${conditionCheckText}${settleText}${elapsedText}${actual === void 0 ? "" : `, observed ${markdownInlineCode(actual, 80)}`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
1933
2053
  }
1934
2054
  if (tapUntilDetails.length > sampledTapUntilDetails.length) lines.push(`- ${tapUntilDetails.length - sampledTapUntilDetails.length} additional tap_until receipt(s) omitted.`);
1935
2055
  const keyboardGroups = viewports.map((viewport) => {
@@ -2351,6 +2471,21 @@ function writeProfileOutput(outputDir, result) {
2351
2471
  if (result.evidence?.dom_summary) writeFileSync(path.join(outputDir, "dom-summary.json"), `${JSON.stringify(result.evidence.dom_summary, null, 2)}
2352
2472
  `);
2353
2473
  }
2474
+ function writeRiddleJobReceipt(outputDir, input) {
2475
+ if (!outputDir) return;
2476
+ mkdirSync(outputDir, { recursive: true });
2477
+ writeFileSync(path.join(outputDir, "riddle-job.json"), `${JSON.stringify({
2478
+ version: "riddle-proof.riddle-job-receipt.v1",
2479
+ profile_name: input.profile.name,
2480
+ job_id: input.jobId,
2481
+ target_url: input.targetUrl,
2482
+ viewport: input.viewport || null,
2483
+ captured_at: (/* @__PURE__ */ new Date()).toISOString(),
2484
+ created: input.created || null,
2485
+ recovery_command: `riddle-proof-loop run-profile recover --profile <profile> --job ${input.jobId} --output-dir ${outputDir}`
2486
+ }, null, 2)}
2487
+ `);
2488
+ }
2354
2489
  async function readArtifactJson(artifact) {
2355
2490
  const target = artifact.url || artifact.path;
2356
2491
  if (!target) return void 0;
@@ -2554,6 +2689,43 @@ function profileForSplitViewport(profile, viewport) {
2554
2689
  }
2555
2690
  };
2556
2691
  }
2692
+ function profileItemAppliesToAnySelectedViewport(item, viewports) {
2693
+ if (!item.viewports?.length) return true;
2694
+ const names = new Set(viewports.map((viewport) => viewport.name).filter(Boolean));
2695
+ return item.viewports.some((name) => names.has(name));
2696
+ }
2697
+ function profileForSelectedViewports(profile, viewports) {
2698
+ const suffix = viewports.map((viewport) => viewport.name || `${viewport.width}x${viewport.height}`).join("-");
2699
+ const setupActions = profile.target.setup_actions?.filter((action) => profileItemAppliesToAnySelectedViewport(action, viewports));
2700
+ return {
2701
+ ...profile,
2702
+ name: `${profile.name}-${suffix}`,
2703
+ checks: profile.checks.filter((check) => profileItemAppliesToAnySelectedViewport(check, viewports)),
2704
+ target: {
2705
+ ...profile.target,
2706
+ viewports,
2707
+ ...setupActions ? { setup_actions: setupActions } : {}
2708
+ },
2709
+ metadata: {
2710
+ ...profile.metadata || {},
2711
+ selected_parent_profile: profile.name,
2712
+ selected_viewports: viewports.map((viewport) => viewport.name || `${viewport.width}x${viewport.height}`)
2713
+ }
2714
+ };
2715
+ }
2716
+ function profileWithSelectedViewportNamesForCli(profile, options) {
2717
+ const names = runProfileViewportNamesOption(options);
2718
+ if (!names.length) return profile;
2719
+ const requested = new Set(names);
2720
+ const viewports = profile.target.viewports.filter((viewport) => viewport.name && requested.has(viewport.name));
2721
+ const matched = new Set(viewports.map((viewport) => viewport.name).filter(Boolean));
2722
+ const missing = names.filter((name) => !matched.has(name));
2723
+ if (missing.length) {
2724
+ const available = profile.target.viewports.map((viewport) => viewport.name).filter(Boolean).join(", ") || "none";
2725
+ throw new Error(`Unknown --viewport-name ${missing.join(", ")}. Available viewport names: ${available}.`);
2726
+ }
2727
+ return profileForSelectedViewports(profile, viewports);
2728
+ }
2557
2729
  function safeProfileOutputSegment(value) {
2558
2730
  const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
2559
2731
  return safe || "viewport";
@@ -2742,6 +2914,13 @@ async function runSingleRiddleProfileForCli(profile, options, input) {
2742
2914
  const directResult = extractRiddleProofProfileResult(created);
2743
2915
  return directResult ? withRiddleMetadata(withProfileMetadata(profile, directResult), { artifacts: collectRiddleProfileArtifactRefs(created) }) : createRiddleProofProfileInsufficientResult({ profile, runner, error: "Riddle run response was missing job_id.", artifacts: collectRiddleProfileArtifactRefs(created) });
2744
2916
  }
2917
+ writeRiddleJobReceipt(input.outputDir, {
2918
+ profile,
2919
+ jobId,
2920
+ targetUrl,
2921
+ viewport: profile.target.viewports[0],
2922
+ created
2923
+ });
2745
2924
  poll = await client.pollJob(jobId, pollOptions);
2746
2925
  if (attempt < retryLimit && shouldRetryUnsubmittedRiddleJob(poll)) {
2747
2926
  const recoveredResult = await recoverProfileResultFromRiddleArtifacts(profile, {
@@ -2818,10 +2997,9 @@ async function runSplitViewportProfileForCli(profile, options, input) {
2818
2997
  const childRuns = [];
2819
2998
  for (const viewport of profile.target.viewports) {
2820
2999
  const childProfile = profileForSplitViewport(profile, viewport);
2821
- const result2 = await runSingleRiddleProfileForCli(childProfile, options, input);
2822
- if (outputDir) {
2823
- writeProfileOutput(splitViewportOutputDir(outputDir, viewport.name, seenOutputNames), result2);
2824
- }
3000
+ const childOutputDir = outputDir ? splitViewportOutputDir(outputDir, viewport.name, seenOutputNames) : void 0;
3001
+ const result2 = await runSingleRiddleProfileForCli(childProfile, options, { ...input, outputDir: childOutputDir });
3002
+ if (childOutputDir) writeProfileOutput(childOutputDir, result2);
2825
3003
  childRuns.push({ viewport, profile: childProfile, result: result2 });
2826
3004
  }
2827
3005
  const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
@@ -2843,6 +3021,51 @@ async function runSplitViewportProfileForCli(profile, options, input) {
2843
3021
  });
2844
3022
  return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
2845
3023
  }
3024
+ async function recoverProfileForCli(profile, options) {
3025
+ const runner = optionString(options, "runner") || "riddle";
3026
+ if (runner !== "riddle") {
3027
+ throw new Error(`Unsupported --runner ${runner}. The current CLI supports --runner riddle.`);
3028
+ }
3029
+ const jobId = optionString(options, "job") ?? optionString(options, "jobId");
3030
+ if (!jobId) throw new Error("run-profile recover requires --job <job-id>.");
3031
+ const client = createRiddleApiClient(riddleClientConfig(options));
3032
+ let artifactPayload;
3033
+ try {
3034
+ artifactPayload = await client.requestJson(`/v1/jobs/${jobId}/artifacts`);
3035
+ } catch (error) {
3036
+ return createRiddleProofProfileEnvironmentBlockedResult({
3037
+ profile,
3038
+ runner,
3039
+ error,
3040
+ riddle: { job_id: jobId, terminal: false }
3041
+ });
3042
+ }
3043
+ const artifacts = collectRiddleProfileArtifactRefs(artifactPayload);
3044
+ const artifactStatus = riddleArtifactsPayloadStatus(artifactPayload);
3045
+ const terminal = artifactStatus ? isTerminalRiddleJobStatus(artifactStatus) : artifacts.length > 0;
3046
+ const recovered = await profileResultFromRiddleArtifacts(profile, artifacts, [artifactPayload]);
3047
+ if (recovered) {
3048
+ return withRiddleMetadata(recovered, {
3049
+ job_id: jobId,
3050
+ status: artifactStatus,
3051
+ terminal,
3052
+ artifacts,
3053
+ artifactRecovery: true
3054
+ });
3055
+ }
3056
+ return createRiddleProofProfileInsufficientResult({
3057
+ profile,
3058
+ runner,
3059
+ error: artifacts.length ? `Riddle job ${jobId} artifacts were recovered without a proof result.` : `Riddle job ${jobId} had no recoverable artifacts.`,
3060
+ riddle: {
3061
+ job_id: jobId,
3062
+ status: artifactStatus,
3063
+ terminal,
3064
+ artifact_recovery: artifacts.length > 0
3065
+ },
3066
+ artifacts
3067
+ });
3068
+ }
2846
3069
  async function runProfileForCli(profile, options) {
2847
3070
  const runner = optionString(options, "runner") || "riddle";
2848
3071
  if (runner !== "riddle") {
@@ -2852,7 +3075,7 @@ async function runProfileForCli(profile, options) {
2852
3075
  if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
2853
3076
  return runSplitViewportProfileForCli(profile, options, { client, runner });
2854
3077
  }
2855
- return runSingleRiddleProfileForCli(profile, options, { client, runner });
3078
+ return runSingleRiddleProfileForCli(profile, options, { client, runner, outputDir: profileOutputDirOption(options) });
2856
3079
  }
2857
3080
  function requestForRun(options) {
2858
3081
  const statePath = optionString(options, "statePath");
@@ -2901,8 +3124,8 @@ async function main() {
2901
3124
  return;
2902
3125
  }
2903
3126
  if (command === "run-profile") {
2904
- const profile = normalizeProfileForCli(options);
2905
- const result = await runProfileForCli(profile, options);
3127
+ const profile = profileWithSelectedViewportNamesForCli(normalizeProfileForCli(options), options);
3128
+ const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : await runProfileForCli(profile, options);
2906
3129
  writeProfileOutput(profileOutputDirOption(options), result);
2907
3130
  const diagnosticLine = profileCliDiagnosticLine(result);
2908
3131
  if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
package/dist/index.cjs CHANGED
@@ -9368,6 +9368,7 @@ function profileSetupTapUntilReceipts(results) {
9368
9368
  max_taps: result.max_taps ?? result.max_calls ?? null,
9369
9369
  tap_burst_size: result.tap_burst_size ?? null,
9370
9370
  condition_check_count: result.condition_check_count ?? null,
9371
+ settle_ms: result.settle_ms ?? null,
9371
9372
  elapsed_ms: result.elapsed_ms ?? null,
9372
9373
  interval_ms: result.interval_ms ?? null,
9373
9374
  timeout_ms: result.timeout_ms ?? null,
@@ -10013,6 +10014,10 @@ function normalizeSetupAction(input, index) {
10013
10014
  if (type === "tap_until" && tapBurstSize !== void 0 && (!Number.isInteger(tapBurstSize) || tapBurstSize < 1 || tapBurstSize > 100)) {
10014
10015
  throw new Error(`target.setup_actions[${index}].tap_burst_size must be an integer from 1 to 100.`);
10015
10016
  }
10017
+ const settleMs = type === "tap_until" ? numberValue3(valueFromOwn(input, "settle_ms", "settleMs", "predicate_settle_ms", "predicateSettleMs", "post_burst_wait_ms", "postBurstWaitMs", "after_burst_ms", "afterBurstMs", "settle_after_tap_ms", "settleAfterTapMs")) : void 0;
10018
+ if (type === "tap_until" && settleMs !== void 0 && (!Number.isInteger(settleMs) || settleMs < 0 || settleMs > 1e4)) {
10019
+ throw new Error(`target.setup_actions[${index}].settle_ms must be an integer from 0 to 10000.`);
10020
+ }
10016
10021
  const intervalMs = numberValue3(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
10017
10022
  if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
10018
10023
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
@@ -10061,6 +10066,7 @@ function normalizeSetupAction(input, index) {
10061
10066
  until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
10062
10067
  max_calls: maxCalls,
10063
10068
  tap_burst_size: tapBurstSize,
10069
+ settle_ms: settleMs,
10064
10070
  interval_ms: intervalMs,
10065
10071
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
10066
10072
  min_value: minValue,
@@ -13302,6 +13308,7 @@ function profileSetupTapUntilReceipts(results) {
13302
13308
  max_taps: result.max_taps ?? result.max_calls ?? null,
13303
13309
  tap_burst_size: result.tap_burst_size ?? null,
13304
13310
  condition_check_count: result.condition_check_count ?? null,
13311
+ settle_ms: result.settle_ms ?? null,
13305
13312
  elapsed_ms: result.elapsed_ms ?? null,
13306
13313
  interval_ms: result.interval_ms ?? null,
13307
13314
  timeout_ms: result.timeout_ms ?? null,
@@ -15293,6 +15300,7 @@ async function executeSetupAction(action, ordinal, viewport) {
15293
15300
  if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
15294
15301
  const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
15295
15302
  const tapBurstSize = Math.min(maxTaps, Math.min(100, Math.max(1, Math.floor(setupNumber(action.tap_burst_size ?? action.tapBurstSize ?? action.burst_size ?? action.burstSize ?? action.check_every_taps ?? action.checkEveryTaps ?? action.predicate_interval_taps ?? action.predicateIntervalTaps, 1) || 1))));
15303
+ const settleMs = Math.min(10000, Math.max(0, Math.floor(setupNumber(action.settle_ms ?? action.settleMs ?? action.predicate_settle_ms ?? action.predicateSettleMs ?? action.post_burst_wait_ms ?? action.postBurstWaitMs ?? action.after_burst_ms ?? action.afterBurstMs ?? action.settle_after_tap_ms ?? action.settleAfterTapMs, 0) || 0)));
15296
15304
  const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
15297
15305
  const scope = await setupActionScope(action, timeout);
15298
15306
  if (!scope.ok) return setupScopeFailure(base, scope);
@@ -15318,6 +15326,7 @@ async function executeSetupAction(action, ordinal, viewport) {
15318
15326
  max_calls: maxTaps,
15319
15327
  tap_burst_size: tapBurstSize,
15320
15328
  condition_check_count: conditionCheckCount,
15329
+ settle_ms: settleMs,
15321
15330
  elapsed_ms: elapsedMs,
15322
15331
  interval_ms: intervalMs,
15323
15332
  timeout_ms: timeout,
@@ -15330,6 +15339,7 @@ async function executeSetupAction(action, ordinal, viewport) {
15330
15339
  tapCount += 1;
15331
15340
  if (tapCount < maxTaps && burstIndex < burstCount - 1 && intervalMs) await page.waitForTimeout(intervalMs);
15332
15341
  }
15342
+ if (settleMs) await page.waitForTimeout(settleMs);
15333
15343
  lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
15334
15344
  conditionCheckCount += 1;
15335
15345
  if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
@@ -15347,6 +15357,7 @@ async function executeSetupAction(action, ordinal, viewport) {
15347
15357
  max_calls: maxTaps,
15348
15358
  tap_burst_size: tapBurstSize,
15349
15359
  condition_check_count: conditionCheckCount,
15360
+ settle_ms: settleMs,
15350
15361
  elapsed_ms: elapsedMs,
15351
15362
  interval_ms: intervalMs,
15352
15363
  timeout_ms: timeout,
@@ -15367,6 +15378,7 @@ async function executeSetupAction(action, ordinal, viewport) {
15367
15378
  max_calls: maxTaps,
15368
15379
  tap_burst_size: tapBurstSize,
15369
15380
  condition_check_count: conditionCheckCount,
15381
+ settle_ms: settleMs,
15370
15382
  elapsed_ms: elapsedMs,
15371
15383
  interval_ms: intervalMs,
15372
15384
  timeout_ms: timeout,
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  resolveRiddleProofProfileTimeoutSec,
63
63
  slugifyRiddleProofProfileName,
64
64
  summarizeRiddleProofProfileResult
65
- } from "./chunk-FNVDZCVZ.js";
65
+ } from "./chunk-OUZKZ5U4.js";
66
66
  import {
67
67
  DEFAULT_RIDDLE_API_BASE_URL,
68
68
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -682,6 +682,7 @@ function profileSetupTapUntilReceipts(results) {
682
682
  max_taps: result.max_taps ?? result.max_calls ?? null,
683
683
  tap_burst_size: result.tap_burst_size ?? null,
684
684
  condition_check_count: result.condition_check_count ?? null,
685
+ settle_ms: result.settle_ms ?? null,
685
686
  elapsed_ms: result.elapsed_ms ?? null,
686
687
  interval_ms: result.interval_ms ?? null,
687
688
  timeout_ms: result.timeout_ms ?? null,
@@ -1327,6 +1328,10 @@ function normalizeSetupAction(input, index) {
1327
1328
  if (type === "tap_until" && tapBurstSize !== void 0 && (!Number.isInteger(tapBurstSize) || tapBurstSize < 1 || tapBurstSize > 100)) {
1328
1329
  throw new Error(`target.setup_actions[${index}].tap_burst_size must be an integer from 1 to 100.`);
1329
1330
  }
1331
+ const settleMs = type === "tap_until" ? numberValue(valueFromOwn(input, "settle_ms", "settleMs", "predicate_settle_ms", "predicateSettleMs", "post_burst_wait_ms", "postBurstWaitMs", "after_burst_ms", "afterBurstMs", "settle_after_tap_ms", "settleAfterTapMs")) : void 0;
1332
+ if (type === "tap_until" && settleMs !== void 0 && (!Number.isInteger(settleMs) || settleMs < 0 || settleMs > 1e4)) {
1333
+ throw new Error(`target.setup_actions[${index}].settle_ms must be an integer from 0 to 10000.`);
1334
+ }
1330
1335
  const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
1331
1336
  if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
1332
1337
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
@@ -1375,6 +1380,7 @@ function normalizeSetupAction(input, index) {
1375
1380
  until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
1376
1381
  max_calls: maxCalls,
1377
1382
  tap_burst_size: tapBurstSize,
1383
+ settle_ms: settleMs,
1378
1384
  interval_ms: intervalMs,
1379
1385
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
1380
1386
  min_value: minValue,
@@ -4616,6 +4622,7 @@ function profileSetupTapUntilReceipts(results) {
4616
4622
  max_taps: result.max_taps ?? result.max_calls ?? null,
4617
4623
  tap_burst_size: result.tap_burst_size ?? null,
4618
4624
  condition_check_count: result.condition_check_count ?? null,
4625
+ settle_ms: result.settle_ms ?? null,
4619
4626
  elapsed_ms: result.elapsed_ms ?? null,
4620
4627
  interval_ms: result.interval_ms ?? null,
4621
4628
  timeout_ms: result.timeout_ms ?? null,
@@ -6607,6 +6614,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6607
6614
  if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
6608
6615
  const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
6609
6616
  const tapBurstSize = Math.min(maxTaps, Math.min(100, Math.max(1, Math.floor(setupNumber(action.tap_burst_size ?? action.tapBurstSize ?? action.burst_size ?? action.burstSize ?? action.check_every_taps ?? action.checkEveryTaps ?? action.predicate_interval_taps ?? action.predicateIntervalTaps, 1) || 1))));
6617
+ const settleMs = Math.min(10000, Math.max(0, Math.floor(setupNumber(action.settle_ms ?? action.settleMs ?? action.predicate_settle_ms ?? action.predicateSettleMs ?? action.post_burst_wait_ms ?? action.postBurstWaitMs ?? action.after_burst_ms ?? action.afterBurstMs ?? action.settle_after_tap_ms ?? action.settleAfterTapMs, 0) || 0)));
6610
6618
  const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
6611
6619
  const scope = await setupActionScope(action, timeout);
6612
6620
  if (!scope.ok) return setupScopeFailure(base, scope);
@@ -6632,6 +6640,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6632
6640
  max_calls: maxTaps,
6633
6641
  tap_burst_size: tapBurstSize,
6634
6642
  condition_check_count: conditionCheckCount,
6643
+ settle_ms: settleMs,
6635
6644
  elapsed_ms: elapsedMs,
6636
6645
  interval_ms: intervalMs,
6637
6646
  timeout_ms: timeout,
@@ -6644,6 +6653,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6644
6653
  tapCount += 1;
6645
6654
  if (tapCount < maxTaps && burstIndex < burstCount - 1 && intervalMs) await page.waitForTimeout(intervalMs);
6646
6655
  }
6656
+ if (settleMs) await page.waitForTimeout(settleMs);
6647
6657
  lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
6648
6658
  conditionCheckCount += 1;
6649
6659
  if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
@@ -6661,6 +6671,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6661
6671
  max_calls: maxTaps,
6662
6672
  tap_burst_size: tapBurstSize,
6663
6673
  condition_check_count: conditionCheckCount,
6674
+ settle_ms: settleMs,
6664
6675
  elapsed_ms: elapsedMs,
6665
6676
  interval_ms: intervalMs,
6666
6677
  timeout_ms: timeout,
@@ -6681,6 +6692,7 @@ async function executeSetupAction(action, ordinal, viewport) {
6681
6692
  max_calls: maxTaps,
6682
6693
  tap_burst_size: tapBurstSize,
6683
6694
  condition_check_count: conditionCheckCount,
6695
+ settle_ms: settleMs,
6684
6696
  elapsed_ms: elapsedMs,
6685
6697
  interval_ms: intervalMs,
6686
6698
  timeout_ms: timeout,
@@ -147,6 +147,7 @@ interface RiddleProofProfileSetupAction {
147
147
  until_expected_value?: JsonValue;
148
148
  max_calls?: number;
149
149
  tap_burst_size?: number;
150
+ settle_ms?: number;
150
151
  interval_ms?: number;
151
152
  expected_value?: JsonValue;
152
153
  min_value?: number;
package/dist/profile.d.ts CHANGED
@@ -147,6 +147,7 @@ interface RiddleProofProfileSetupAction {
147
147
  until_expected_value?: JsonValue;
148
148
  max_calls?: number;
149
149
  tap_burst_size?: number;
150
+ settle_ms?: number;
150
151
  interval_ms?: number;
151
152
  expected_value?: JsonValue;
152
153
  min_value?: number;
package/dist/profile.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  resolveRiddleProofProfileTimeoutSec,
24
24
  slugifyRiddleProofProfileName,
25
25
  summarizeRiddleProofProfileResult
26
- } from "./chunk-FNVDZCVZ.js";
26
+ } from "./chunk-OUZKZ5U4.js";
27
27
  export {
28
28
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
29
29
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
295
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
385
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
662
+ action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.200",
3
+ "version": "0.7.202",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",