@riddledc/riddle-proof 0.7.201 → 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 +25 -0
- package/dist/cli.cjs +230 -9
- package/dist/cli.js +230 -9
- package/dist/proof-run-engine.d.cts +3 -3
- package/dist/proof-run-engine.d.ts +3 -3
- package/package.json +1 -1
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:
|
package/dist/cli.cjs
CHANGED
|
@@ -16223,7 +16223,8 @@ function usage() {
|
|
|
16223
16223
|
" riddle-proof-loop respond --state-path <path> --response-json <file|json|->",
|
|
16224
16224
|
" riddle-proof-loop respond --state-path <path> --decision <decision> --summary <text> [--payload-json <file|json|->]",
|
|
16225
16225
|
" riddle-proof-loop status --state-path <path>",
|
|
16226
|
-
" 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]",
|
|
16227
16228
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
16228
16229
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
16229
16230
|
" riddle-proof-loop riddle-preview-deploy <build-dir> <label> [--framework spa|static]",
|
|
@@ -16283,6 +16284,11 @@ function runProfileStrictOption(options) {
|
|
|
16283
16284
|
function runProfileSplitViewportsOption(options) {
|
|
16284
16285
|
return optionBoolean(options, "splitViewports") ?? false;
|
|
16285
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
|
+
}
|
|
16286
16292
|
var DEFAULT_PROFILE_UNSUBMITTED_RETRY_TIMEOUT_MS = 9e4;
|
|
16287
16293
|
var DEFAULT_PROFILE_UNSUBMITTED_RETRIES = 2;
|
|
16288
16294
|
function optionNumber(options, ...keys) {
|
|
@@ -16883,6 +16889,61 @@ function profileHasRouteExitAffordanceReceipt(receipts) {
|
|
|
16883
16889
|
return routeFields.some((name) => setupReturnSummaryValue(receipt, [name]) !== void 0) || haystack.includes("route=") || haystack.includes("browserpath=");
|
|
16884
16890
|
});
|
|
16885
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
|
+
}
|
|
16886
16947
|
function profileHasOfflineAudioMetricsReceipt(receipts) {
|
|
16887
16948
|
const metricFields = [
|
|
16888
16949
|
"mixPeak",
|
|
@@ -16984,7 +17045,7 @@ function profileHasRecoveredStateReceipt(receipts) {
|
|
|
16984
17045
|
const path7 = cliString(receipt.path) || cliString(receipt.function_name) || "";
|
|
16985
17046
|
const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
|
|
16986
17047
|
const haystack = `${storedTo} ${label} ${path7} ${summary}`.toLowerCase();
|
|
16987
|
-
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");
|
|
16988
17049
|
if (!labelsRecovery) return false;
|
|
16989
17050
|
const status = profileLowerSummaryValue(receipt, ["status", "state", "phase"]);
|
|
16990
17051
|
const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result"]);
|
|
@@ -16995,6 +17056,38 @@ function profileHasRecoveredStateReceipt(receipts) {
|
|
|
16995
17056
|
return hasRecoveredState || success || hasValid && hasInvalid === false;
|
|
16996
17057
|
});
|
|
16997
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
|
+
}
|
|
16998
17091
|
function profilePackReceiptStatus(result, metadata, receipt) {
|
|
16999
17092
|
const text = receipt.toLowerCase();
|
|
17000
17093
|
const setupSummary = profileSetupSummaryRecord(result);
|
|
@@ -17025,6 +17118,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17025
17118
|
const clickFallbackTapCount = clickFallbackTapKeys.size;
|
|
17026
17119
|
const tapUntilCount = profileSetupReceiptTotal(setupViewports, "tap_until");
|
|
17027
17120
|
const visibleUiActionCount = clickCount + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount;
|
|
17121
|
+
const visibleCleanupActionCount = profileVisibleCleanupActionCount(setupViewports);
|
|
17028
17122
|
const setupFailureCount = profileSetupFailureCount(setupViewports);
|
|
17029
17123
|
const setupObstructionCount = profileSetupObstructionCount(setupViewports);
|
|
17030
17124
|
const inputDispatchCount = profileSetupReceiptTotal(setupViewports, "drag") + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount + profileSetupReceiptTotal(setupViewports, "press") + profileSetupReceiptTotal(setupViewports, "keyboard_sequence");
|
|
@@ -17060,6 +17154,7 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17060
17154
|
const hasTextAbsence = profileHasPassedCheck(result, ["text_absent", "selector_text_absent"]);
|
|
17061
17155
|
const hasMeasuredStateChange = hasNaturalInput || hasCanvasChange || valueReceipts.some((item) => setupReturnSummaryValue(item, ["changed"]) === true || setupReturnSummaryValue(item, ["nonWhiteDelta", "darkDelta", "pixelDelta", "movementDelta"]) !== void 0);
|
|
17062
17156
|
const hasRouteExitAffordanceReceipt = profileHasRouteExitAffordanceReceipt(valueReceipts);
|
|
17157
|
+
const hasCleanupBoundaryAffordanceReceipt = profileHasCleanupBoundaryAffordanceReceipt(valueReceipts);
|
|
17063
17158
|
const hasOfflineAudioMetricsReceipt = profileHasOfflineAudioMetricsReceipt(valueReceipts);
|
|
17064
17159
|
const hasActiveRouteLocalProofReceipt = profileHasActiveRouteLocalProofReceipt(valueReceipts);
|
|
17065
17160
|
const hasTerminalLossReceipt = profileHasTerminalLossReceipt(valueReceipts);
|
|
@@ -17068,6 +17163,8 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17068
17163
|
const hasControlledSuccessLaunchReceipt = profileHasControlledLaunchReceipt(valueReceipts, "success");
|
|
17069
17164
|
const hasRouteContinuationReceipt = profileHasRouteContinuationReceipt(valueReceipts);
|
|
17070
17165
|
const hasRecoveredStateReceipt = profileHasRecoveredStateReceipt(valueReceipts);
|
|
17166
|
+
const hasGeneratedOutputContract = profileMetadataHasGeneratedOutputContract(metadata);
|
|
17167
|
+
const hasGeneratedOutputReceipt = profileHasGeneratedOutputReceipt(valueReceipts);
|
|
17071
17168
|
const failedCleanupInventoryReason = profileFailedCleanupInventoryReason(setupViewports);
|
|
17072
17169
|
const passedCleanupInventoryReason = profilePassedCleanupInventoryReason(setupViewports);
|
|
17073
17170
|
if (text.includes("artifact link") || text.includes("artifact path")) {
|
|
@@ -17121,6 +17218,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17121
17218
|
}
|
|
17122
17219
|
return profileReceiptSignalStatus(hasTextAbsence, "absence check passed", "absence check missing");
|
|
17123
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
|
+
}
|
|
17124
17228
|
if (text.includes("recovered") || text.includes("final state")) {
|
|
17125
17229
|
return profileReceiptSignalStatus(hasStateContract || hasTextVisibility, "final state receipt present", "final state receipt missing");
|
|
17126
17230
|
}
|
|
@@ -17177,6 +17281,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17177
17281
|
"route continuation receipt missing"
|
|
17178
17282
|
);
|
|
17179
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
|
+
}
|
|
17180
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")) {
|
|
17181
17292
|
return profileReceiptSignalStatus(
|
|
17182
17293
|
visibleUiActionCount > 0,
|
|
@@ -17191,6 +17302,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
|
|
|
17191
17302
|
"affordance receipt missing"
|
|
17192
17303
|
);
|
|
17193
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
|
+
}
|
|
17194
17312
|
if (text.includes("retry") || text.includes("repair") || text.includes("reset") || text.includes("affordance")) {
|
|
17195
17313
|
return profileReceiptSignalStatus(hasStateContract || clickCount > 0, "affordance or transition receipt present", "affordance receipt missing");
|
|
17196
17314
|
}
|
|
@@ -18528,6 +18646,21 @@ function writeProfileOutput(outputDir, result) {
|
|
|
18528
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)}
|
|
18529
18647
|
`);
|
|
18530
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
|
+
}
|
|
18531
18664
|
async function readArtifactJson(artifact) {
|
|
18532
18665
|
const target = artifact.url || artifact.path;
|
|
18533
18666
|
if (!target) return void 0;
|
|
@@ -18731,6 +18864,43 @@ function profileForSplitViewport(profile, viewport) {
|
|
|
18731
18864
|
}
|
|
18732
18865
|
};
|
|
18733
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
|
+
}
|
|
18734
18904
|
function safeProfileOutputSegment(value) {
|
|
18735
18905
|
const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
18736
18906
|
return safe || "viewport";
|
|
@@ -18919,6 +19089,13 @@ async function runSingleRiddleProfileForCli(profile, options, input) {
|
|
|
18919
19089
|
const directResult = extractRiddleProofProfileResult(created);
|
|
18920
19090
|
return directResult ? withRiddleMetadata(withProfileMetadata(profile, directResult), { artifacts: collectRiddleProfileArtifactRefs(created) }) : createRiddleProofProfileInsufficientResult({ profile, runner, error: "Riddle run response was missing job_id.", artifacts: collectRiddleProfileArtifactRefs(created) });
|
|
18921
19091
|
}
|
|
19092
|
+
writeRiddleJobReceipt(input.outputDir, {
|
|
19093
|
+
profile,
|
|
19094
|
+
jobId,
|
|
19095
|
+
targetUrl,
|
|
19096
|
+
viewport: profile.target.viewports[0],
|
|
19097
|
+
created
|
|
19098
|
+
});
|
|
18922
19099
|
poll = await client.pollJob(jobId, pollOptions);
|
|
18923
19100
|
if (attempt < retryLimit && shouldRetryUnsubmittedRiddleJob(poll)) {
|
|
18924
19101
|
const recoveredResult = await recoverProfileResultFromRiddleArtifacts(profile, {
|
|
@@ -18995,10 +19172,9 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
18995
19172
|
const childRuns = [];
|
|
18996
19173
|
for (const viewport of profile.target.viewports) {
|
|
18997
19174
|
const childProfile = profileForSplitViewport(profile, viewport);
|
|
18998
|
-
const
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
}
|
|
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);
|
|
19002
19178
|
childRuns.push({ viewport, profile: childProfile, result: result2 });
|
|
19003
19179
|
}
|
|
19004
19180
|
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
@@ -19020,6 +19196,51 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
19020
19196
|
});
|
|
19021
19197
|
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
19022
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
|
+
}
|
|
19023
19244
|
async function runProfileForCli(profile, options) {
|
|
19024
19245
|
const runner = optionString(options, "runner") || "riddle";
|
|
19025
19246
|
if (runner !== "riddle") {
|
|
@@ -19029,7 +19250,7 @@ async function runProfileForCli(profile, options) {
|
|
|
19029
19250
|
if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
|
|
19030
19251
|
return runSplitViewportProfileForCli(profile, options, { client, runner });
|
|
19031
19252
|
}
|
|
19032
|
-
return runSingleRiddleProfileForCli(profile, options, { client, runner });
|
|
19253
|
+
return runSingleRiddleProfileForCli(profile, options, { client, runner, outputDir: profileOutputDirOption(options) });
|
|
19033
19254
|
}
|
|
19034
19255
|
function requestForRun(options) {
|
|
19035
19256
|
const statePath = optionString(options, "statePath");
|
|
@@ -19078,8 +19299,8 @@ async function main() {
|
|
|
19078
19299
|
return;
|
|
19079
19300
|
}
|
|
19080
19301
|
if (command === "run-profile") {
|
|
19081
|
-
const profile = normalizeProfileForCli(options);
|
|
19082
|
-
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);
|
|
19083
19304
|
writeProfileOutput(profileOutputDirOption(options), result);
|
|
19084
19305
|
const diagnosticLine = profileCliDiagnosticLine(result);
|
|
19085
19306
|
if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
|
package/dist/cli.js
CHANGED
|
@@ -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
|
}
|
|
@@ -2353,6 +2471,21 @@ function writeProfileOutput(outputDir, result) {
|
|
|
2353
2471
|
if (result.evidence?.dom_summary) writeFileSync(path.join(outputDir, "dom-summary.json"), `${JSON.stringify(result.evidence.dom_summary, null, 2)}
|
|
2354
2472
|
`);
|
|
2355
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
|
+
}
|
|
2356
2489
|
async function readArtifactJson(artifact) {
|
|
2357
2490
|
const target = artifact.url || artifact.path;
|
|
2358
2491
|
if (!target) return void 0;
|
|
@@ -2556,6 +2689,43 @@ function profileForSplitViewport(profile, viewport) {
|
|
|
2556
2689
|
}
|
|
2557
2690
|
};
|
|
2558
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
|
+
}
|
|
2559
2729
|
function safeProfileOutputSegment(value) {
|
|
2560
2730
|
const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2561
2731
|
return safe || "viewport";
|
|
@@ -2744,6 +2914,13 @@ async function runSingleRiddleProfileForCli(profile, options, input) {
|
|
|
2744
2914
|
const directResult = extractRiddleProofProfileResult(created);
|
|
2745
2915
|
return directResult ? withRiddleMetadata(withProfileMetadata(profile, directResult), { artifacts: collectRiddleProfileArtifactRefs(created) }) : createRiddleProofProfileInsufficientResult({ profile, runner, error: "Riddle run response was missing job_id.", artifacts: collectRiddleProfileArtifactRefs(created) });
|
|
2746
2916
|
}
|
|
2917
|
+
writeRiddleJobReceipt(input.outputDir, {
|
|
2918
|
+
profile,
|
|
2919
|
+
jobId,
|
|
2920
|
+
targetUrl,
|
|
2921
|
+
viewport: profile.target.viewports[0],
|
|
2922
|
+
created
|
|
2923
|
+
});
|
|
2747
2924
|
poll = await client.pollJob(jobId, pollOptions);
|
|
2748
2925
|
if (attempt < retryLimit && shouldRetryUnsubmittedRiddleJob(poll)) {
|
|
2749
2926
|
const recoveredResult = await recoverProfileResultFromRiddleArtifacts(profile, {
|
|
@@ -2820,10 +2997,9 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
2820
2997
|
const childRuns = [];
|
|
2821
2998
|
for (const viewport of profile.target.viewports) {
|
|
2822
2999
|
const childProfile = profileForSplitViewport(profile, viewport);
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
}
|
|
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);
|
|
2827
3003
|
childRuns.push({ viewport, profile: childProfile, result: result2 });
|
|
2828
3004
|
}
|
|
2829
3005
|
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
@@ -2845,6 +3021,51 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
2845
3021
|
});
|
|
2846
3022
|
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
2847
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
|
+
}
|
|
2848
3069
|
async function runProfileForCli(profile, options) {
|
|
2849
3070
|
const runner = optionString(options, "runner") || "riddle";
|
|
2850
3071
|
if (runner !== "riddle") {
|
|
@@ -2854,7 +3075,7 @@ async function runProfileForCli(profile, options) {
|
|
|
2854
3075
|
if (runProfileSplitViewportsOption(options) && profile.target.viewports.length > 1) {
|
|
2855
3076
|
return runSplitViewportProfileForCli(profile, options, { client, runner });
|
|
2856
3077
|
}
|
|
2857
|
-
return runSingleRiddleProfileForCli(profile, options, { client, runner });
|
|
3078
|
+
return runSingleRiddleProfileForCli(profile, options, { client, runner, outputDir: profileOutputDirOption(options) });
|
|
2858
3079
|
}
|
|
2859
3080
|
function requestForRun(options) {
|
|
2860
3081
|
const statePath = optionString(options, "statePath");
|
|
@@ -2903,8 +3124,8 @@ async function main() {
|
|
|
2903
3124
|
return;
|
|
2904
3125
|
}
|
|
2905
3126
|
if (command === "run-profile") {
|
|
2906
|
-
const profile = normalizeProfileForCli(options);
|
|
2907
|
-
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);
|
|
2908
3129
|
writeProfileOutput(profileOutputDirOption(options), result);
|
|
2909
3130
|
const diagnosticLine = profileCliDiagnosticLine(result);
|
|
2910
3131
|
if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
|
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
662
|
+
action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|