@riddledc/riddle-proof 0.7.202 → 0.7.204
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 +19 -0
- package/dist/cli.cjs +186 -29
- package/dist/cli.js +187 -30
- 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
|
@@ -227,6 +227,25 @@ riddle-proof-loop run-profile recover \
|
|
|
227
227
|
--output artifacts/riddle-proof/pricing-ipad-mini-recovered
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
+
When a matrix run is executed as separate named-viewport jobs, aggregate the
|
|
231
|
+
saved child outputs back into one profile judgment:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
riddle-proof-loop run-profile aggregate \
|
|
235
|
+
--profile .riddle-proof/profiles/pricing.json \
|
|
236
|
+
--url https://example.com \
|
|
237
|
+
--input-dir artifacts/riddle-proof/pricing-matrix \
|
|
238
|
+
--output artifacts/riddle-proof/pricing-matrix
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`--input-dir` reads child `*/profile-result.json` files such as
|
|
242
|
+
`desktop/profile-result.json` and `ipad-mini/profile-result.json`, then writes
|
|
243
|
+
the combined `profile-result.json` and `summary.md`. Use `--inputs` with a
|
|
244
|
+
comma-separated list of files or directories when the child outputs do not
|
|
245
|
+
share one parent directory. Aggregate summaries label summed hosted timing as
|
|
246
|
+
child poll totals and also show the maximum child latency, so a matrix report
|
|
247
|
+
does not make sequential child wait time look like a single job duration.
|
|
248
|
+
|
|
230
249
|
When promoting proof artifacts into a durable public profile, avoid guessing
|
|
231
250
|
which backend or runner tokens are preserved inside `proof.json`. Derive the
|
|
232
251
|
`body_contains` fragments from the artifact body first:
|
package/dist/cli.cjs
CHANGED
|
@@ -16224,6 +16224,7 @@ function usage() {
|
|
|
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
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 aggregate --profile <file|json|-> --url <base-url> --input-dir <dir>|--inputs <path[,path...]> [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
16227
16228
|
" 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]",
|
|
16228
16229
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
16229
16230
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
@@ -16703,6 +16704,7 @@ function profileRiddleJobMarkdown(result) {
|
|
|
16703
16704
|
const artifactRecovery = riddle.artifact_recovery === true;
|
|
16704
16705
|
const retryCount = cliFiniteNumber(riddle.retry_count);
|
|
16705
16706
|
const staleJobIds = Array.isArray(riddle.stale_job_ids) ? riddle.stale_job_ids.map((value) => cliString(value)).filter((value) => Boolean(value)) : [];
|
|
16707
|
+
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
16706
16708
|
const parts = [
|
|
16707
16709
|
mode ? `mode ${markdownInlineCode(mode)}` : "",
|
|
16708
16710
|
jobCount === void 0 ? "" : `jobs ${jobCount}`,
|
|
@@ -16712,9 +16714,18 @@ function profileRiddleJobMarkdown(result) {
|
|
|
16712
16714
|
].filter(Boolean);
|
|
16713
16715
|
const lines = parts.length ? [`- ${parts.join(", ")}`] : [];
|
|
16714
16716
|
if (queueElapsedMs !== void 0 || elapsedMs3 !== void 0 || attempt !== void 0 || attempts !== void 0) {
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16717
|
+
if (splitJobs.length) {
|
|
16718
|
+
const maxChildQueueElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.queue_elapsed_ms)));
|
|
16719
|
+
const maxChildElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.elapsed_ms)));
|
|
16720
|
+
const maxChildPreSubmissionElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.pre_submission_elapsed_ms)));
|
|
16721
|
+
lines.push(
|
|
16722
|
+
`- child poll totals: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs3)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}; max child queue ${formatPollDuration(maxChildQueueElapsedMs)}, max child elapsed ${formatPollDuration(maxChildElapsedMs)}${maxChildPreSubmissionElapsedMs === void 0 || maxChildPreSubmissionElapsedMs < 1e3 ? "" : `, max child pre-submit ${formatPollDuration(maxChildPreSubmissionElapsedMs)}`}`
|
|
16723
|
+
);
|
|
16724
|
+
} else {
|
|
16725
|
+
lines.push(
|
|
16726
|
+
`- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs3)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
|
|
16727
|
+
);
|
|
16728
|
+
}
|
|
16718
16729
|
}
|
|
16719
16730
|
if (submittedAt || completedAt) {
|
|
16720
16731
|
lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
|
|
@@ -16725,7 +16736,6 @@ function profileRiddleJobMarkdown(result) {
|
|
|
16725
16736
|
if (retryCount !== void 0 && retryCount > 0) {
|
|
16726
16737
|
lines.push(`- retry recovery: replaced ${retryCount} unsubmitted job${retryCount === 1 ? "" : "s"}${staleJobIds.length ? ` (${staleJobIds.map((value) => markdownInlineCode(value)).join(", ")})` : ""}`);
|
|
16727
16738
|
}
|
|
16728
|
-
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
16729
16739
|
for (const job of splitJobs.slice(0, 12)) {
|
|
16730
16740
|
const viewport = cliString(job.viewport) || "viewport";
|
|
16731
16741
|
const splitJobId = cliString(job.job_id);
|
|
@@ -16750,6 +16760,10 @@ function profileRiddleJobMarkdown(result) {
|
|
|
16750
16760
|
if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
|
|
16751
16761
|
return lines;
|
|
16752
16762
|
}
|
|
16763
|
+
function maxDefinedNumbers(values) {
|
|
16764
|
+
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
16765
|
+
return numbers.length ? Math.max(...numbers) : void 0;
|
|
16766
|
+
}
|
|
16753
16767
|
function profileMetadataStringArray(value) {
|
|
16754
16768
|
return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => Boolean(item)) : [];
|
|
16755
16769
|
}
|
|
@@ -16838,32 +16852,64 @@ function profileIsCleanupInventoryReceipt(receipt) {
|
|
|
16838
16852
|
const haystack = profileCleanupInventoryHaystack(receipt);
|
|
16839
16853
|
return haystack.includes("cleanup") || haystack.includes("post-cleanup") || haystack.includes("stale") || haystack.includes("statehygiene") || haystack.includes("state hygiene") || haystack.includes("remained after") || haystack.includes("still present");
|
|
16840
16854
|
}
|
|
16841
|
-
function
|
|
16842
|
-
const
|
|
16855
|
+
function profileIsCleanupPhaseInventoryReceipt(receipt) {
|
|
16856
|
+
const storedTo = (cliString(receipt.return_stored_to) || "").toLowerCase();
|
|
16857
|
+
const storedSegments = storedTo.split(/[.[\]/]/).filter(Boolean);
|
|
16858
|
+
const storedSegment = storedSegments[storedSegments.length - 1] || storedTo;
|
|
16859
|
+
const state = (cliString(setupReturnSummaryValue(receipt, ["state", "phase"])) || "").toLowerCase();
|
|
16860
|
+
const summary = (cliReturnSummaryLabel(receipt.return_summary) || "").toLowerCase();
|
|
16861
|
+
const markers = [storedSegment, state, summary];
|
|
16862
|
+
return markers.some((marker) => marker === "cleanup" || marker === "postcleanup" || marker === "post-cleanup" || marker === "aftercleanup" || marker === "after-cleanup" || marker.includes("post-cleanup") || marker.includes("after-cleanup") || marker.includes("after-clear") || marker.includes("after-reset") || marker.includes("after-undo") || marker.includes("after-discard") || marker.includes("after-new"));
|
|
16863
|
+
}
|
|
16864
|
+
function profileCleanupPhaseInventoryReceipts(setupViewports) {
|
|
16865
|
+
return setupViewports.flatMap((viewport) => [
|
|
16843
16866
|
...setupReceiptArray(viewport, "window_eval"),
|
|
16844
16867
|
...setupReceiptArray(viewport, "window_call")
|
|
16845
|
-
]);
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16868
|
+
]).filter((receipt) => profileIsCleanupInventoryReceipt(receipt) && profileIsCleanupPhaseInventoryReceipt(receipt));
|
|
16869
|
+
}
|
|
16870
|
+
function profileFailedCleanupInventoryReceiptReason(receipt) {
|
|
16871
|
+
if (receipt.ok === false) {
|
|
16849
16872
|
const error = cliString(receipt.error);
|
|
16850
16873
|
const reason = cliString(receipt.reason);
|
|
16851
16874
|
return compactProfileReceiptReason(error) || compactProfileReceiptReason(reason) || "cleanup inventory failed";
|
|
16852
16875
|
}
|
|
16876
|
+
const parts = [];
|
|
16877
|
+
if (setupReturnedSummaryValue(receipt, ["ok"]) === false) parts.push("ok=false");
|
|
16878
|
+
if (setupReturnedSummaryValue(receipt, ["success"]) === false) parts.push("success=false");
|
|
16879
|
+
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
16880
|
+
if (staleCount !== void 0 && staleCount > 0) parts.push(`staleCount=${staleCount}`);
|
|
16881
|
+
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
16882
|
+
if (Array.isArray(staleNames) && staleNames.length > 0) {
|
|
16883
|
+
const staleNamesLabel = cliValueLabel(staleNames);
|
|
16884
|
+
if (staleNamesLabel) parts.push(`staleNames=${compactProfileReceiptReason(staleNamesLabel, 120) ?? staleNamesLabel}`);
|
|
16885
|
+
}
|
|
16886
|
+
const productIssue = setupReturnedSummaryValue(receipt, ["productIssue", "issue"]);
|
|
16887
|
+
const productIssueLabel = typeof productIssue === "string" ? compactProfileReceiptReason(productIssue, 120) : void 0;
|
|
16888
|
+
if (parts.length && productIssueLabel) parts.push(productIssueLabel);
|
|
16889
|
+
return parts.length ? parts.join(", ") : void 0;
|
|
16890
|
+
}
|
|
16891
|
+
function profileFailedCleanupInventoryReason(setupViewports) {
|
|
16892
|
+
const receipts = profileCleanupPhaseInventoryReceipts(setupViewports);
|
|
16893
|
+
for (const receipt of [...receipts].reverse()) {
|
|
16894
|
+
const reason = profileFailedCleanupInventoryReceiptReason(receipt);
|
|
16895
|
+
if (reason) return reason;
|
|
16896
|
+
}
|
|
16853
16897
|
return void 0;
|
|
16854
16898
|
}
|
|
16899
|
+
function profilePassedCleanupInventoryReceiptReason(receipt) {
|
|
16900
|
+
if (receipt.ok === false) return void 0;
|
|
16901
|
+
if (setupReturnedSummaryValue(receipt, ["ok"]) === false) return void 0;
|
|
16902
|
+
if (setupReturnedSummaryValue(receipt, ["success"]) === false) return void 0;
|
|
16903
|
+
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
16904
|
+
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
16905
|
+
if (staleCount !== 0 || !Array.isArray(staleNames) || staleNames.length !== 0) return void 0;
|
|
16906
|
+
return "staleCount=0, staleNames=[]";
|
|
16907
|
+
}
|
|
16855
16908
|
function profilePassedCleanupInventoryReason(setupViewports) {
|
|
16856
|
-
const receipts = setupViewports
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
for (const receipt of receipts) {
|
|
16861
|
-
if (receipt.ok === false || !profileIsCleanupInventoryReceipt(receipt)) continue;
|
|
16862
|
-
if (setupReturnSummaryValue(receipt, ["ok"]) === false) continue;
|
|
16863
|
-
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
16864
|
-
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
16865
|
-
if (staleCount !== 0 || !Array.isArray(staleNames) || staleNames.length !== 0) continue;
|
|
16866
|
-
return "staleCount=0, staleNames=[]";
|
|
16909
|
+
const receipts = profileCleanupPhaseInventoryReceipts(setupViewports);
|
|
16910
|
+
for (const receipt of [...receipts].reverse()) {
|
|
16911
|
+
const reason = profilePassedCleanupInventoryReceiptReason(receipt);
|
|
16912
|
+
if (reason) return reason;
|
|
16867
16913
|
}
|
|
16868
16914
|
return void 0;
|
|
16869
16915
|
}
|
|
@@ -17045,15 +17091,17 @@ function profileHasRecoveredStateReceipt(receipts) {
|
|
|
17045
17091
|
const path7 = cliString(receipt.path) || cliString(receipt.function_name) || "";
|
|
17046
17092
|
const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
|
|
17047
17093
|
const haystack = `${storedTo} ${label} ${path7} ${summary}`.toLowerCase();
|
|
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");
|
|
17094
|
+
const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("retry") || 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");
|
|
17049
17095
|
if (!labelsRecovery) return false;
|
|
17050
17096
|
const status = profileLowerSummaryValue(receipt, ["status", "state", "phase"]);
|
|
17051
|
-
const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result"]);
|
|
17052
|
-
const hasRecoveredState = ["valid", "success", "recovered", "fixed", "ready"].includes(status) || ["valid", "success", "recovered", "fixed", "ready"].includes(outcome);
|
|
17097
|
+
const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result", "retryOutcome", "retry_outcome"]);
|
|
17098
|
+
const hasRecoveredState = ["valid", "success", "recovered", "fixed", "ready"].includes(status) || ["valid", "success", "recovered", "fixed", "ready", "running_after_retry", "ready_after_retry"].includes(outcome);
|
|
17053
17099
|
const hasValid = setupReturnSummaryValue(receipt, ["hasValid", "valid", "isValid"]) === true;
|
|
17054
17100
|
const hasInvalid = setupReturnSummaryValue(receipt, ["hasInvalid", "invalid", "isInvalid"]);
|
|
17055
17101
|
const success = setupReturnSummaryValue(receipt, ["success", "recovered", "fixed"]) === true;
|
|
17056
|
-
|
|
17102
|
+
const leftTerminalState = setupReturnSummaryValue(receipt, ["leftTerminalState", "left_terminal_state"]) === true;
|
|
17103
|
+
const retrySurfaceReady = setupReturnSummaryValue(receipt, ["retrySurfaceReady", "retry_surface_ready"]) === true;
|
|
17104
|
+
return hasRecoveredState || success || hasValid && hasInvalid === false || leftTerminalState && retrySurfaceReady;
|
|
17057
17105
|
});
|
|
17058
17106
|
}
|
|
17059
17107
|
function profileMetadataHasGeneratedOutputContract(metadata) {
|
|
@@ -17606,6 +17654,9 @@ function setupReturnSummaryValue(receipt, names) {
|
|
|
17606
17654
|
for (const name of names) {
|
|
17607
17655
|
if (receipt[name] !== void 0) return receipt[name];
|
|
17608
17656
|
}
|
|
17657
|
+
return setupReturnedSummaryValue(receipt, names);
|
|
17658
|
+
}
|
|
17659
|
+
function setupReturnedSummaryValue(receipt, names) {
|
|
17609
17660
|
const returned = cliRecord(receipt.returned);
|
|
17610
17661
|
for (const name of names) {
|
|
17611
17662
|
if (returned?.[name] !== void 0) return returned[name];
|
|
@@ -18911,6 +18962,78 @@ function splitViewportOutputDir(outputDir, viewportName, seen) {
|
|
|
18911
18962
|
seen.set(base, count + 1);
|
|
18912
18963
|
return import_node_path6.default.join(outputDir, count ? `${base}-${count + 1}` : base);
|
|
18913
18964
|
}
|
|
18965
|
+
function profileResultPathFromInput(inputPath) {
|
|
18966
|
+
if (!(0, import_node_fs6.existsSync)(inputPath)) throw new Error(`Profile aggregate input path does not exist: ${inputPath}`);
|
|
18967
|
+
const stat = (0, import_node_fs6.statSync)(inputPath);
|
|
18968
|
+
if (stat.isFile()) return [inputPath];
|
|
18969
|
+
if (!stat.isDirectory()) throw new Error(`Profile aggregate input path must be a file or directory: ${inputPath}`);
|
|
18970
|
+
const childProfileResults = (0, import_node_fs6.readdirSync)(inputPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => import_node_path6.default.join(inputPath, entry.name, "profile-result.json")).filter((candidate) => (0, import_node_fs6.existsSync)(candidate));
|
|
18971
|
+
if (childProfileResults.length) return childProfileResults;
|
|
18972
|
+
const directProfileResult = import_node_path6.default.join(inputPath, "profile-result.json");
|
|
18973
|
+
if ((0, import_node_fs6.existsSync)(directProfileResult)) return [directProfileResult];
|
|
18974
|
+
throw new Error(`Profile aggregate input directory has no child profile-result.json files: ${inputPath}`);
|
|
18975
|
+
}
|
|
18976
|
+
function runProfileAggregateInputPathsOption(options) {
|
|
18977
|
+
const rawInputs = [
|
|
18978
|
+
optionString(options, "input"),
|
|
18979
|
+
optionString(options, "inputs"),
|
|
18980
|
+
optionString(options, "inputFile"),
|
|
18981
|
+
optionString(options, "inputFiles")
|
|
18982
|
+
].filter((value) => Boolean(value));
|
|
18983
|
+
const explicitInputs = rawInputs.flatMap((raw) => raw.split(",").map((part) => part.trim()).filter(Boolean));
|
|
18984
|
+
const inputDir = optionString(options, "inputDir") ?? optionString(options, "resultsDir") ?? optionString(options, "runDir");
|
|
18985
|
+
const discoveredInputs = inputDir ? profileResultPathFromInput(inputDir) : [];
|
|
18986
|
+
const paths = [...explicitInputs.flatMap(profileResultPathFromInput), ...discoveredInputs];
|
|
18987
|
+
const uniquePaths = [...new Set(paths.map((inputPath) => import_node_path6.default.resolve(inputPath)))];
|
|
18988
|
+
if (!uniquePaths.length) {
|
|
18989
|
+
throw new Error("run-profile aggregate requires --input-dir <dir> or --inputs <path[,path...]>.");
|
|
18990
|
+
}
|
|
18991
|
+
return uniquePaths;
|
|
18992
|
+
}
|
|
18993
|
+
function readProfileResultForAggregate(resultPath) {
|
|
18994
|
+
const parsed = readJsonValue(resultPath, resultPath);
|
|
18995
|
+
if (parsed.version !== "riddle-proof.profile-result.v1" || !Array.isArray(parsed.checks)) {
|
|
18996
|
+
throw new Error(`Profile aggregate input is not a riddle-proof.profile-result.v1 result: ${resultPath}`);
|
|
18997
|
+
}
|
|
18998
|
+
return parsed;
|
|
18999
|
+
}
|
|
19000
|
+
function profileResultIsAggregateParent(result) {
|
|
19001
|
+
const mode = cliString(cliRecord(result.riddle)?.mode);
|
|
19002
|
+
return (mode === "split-viewports" || mode === "named-viewport-aggregate") && (result.evidence?.viewports?.length || 0) > 1;
|
|
19003
|
+
}
|
|
19004
|
+
function aggregateProfileResultViewportName(result) {
|
|
19005
|
+
const evidenceViewports = result.evidence?.viewports || [];
|
|
19006
|
+
if (evidenceViewports.length === 1) return evidenceViewports[0].name;
|
|
19007
|
+
const metadata = cliRecord(result.metadata);
|
|
19008
|
+
const splitViewport = cliString(metadata?.split_viewport);
|
|
19009
|
+
if (splitViewport) return splitViewport;
|
|
19010
|
+
const selectedViewports = Array.isArray(metadata?.selected_viewports) ? metadata.selected_viewports.map(cliString).filter((name) => Boolean(name)) : [];
|
|
19011
|
+
if (selectedViewports.length === 1) return selectedViewports[0];
|
|
19012
|
+
return void 0;
|
|
19013
|
+
}
|
|
19014
|
+
function aggregateProfileResultViewport(profile, result, resultPath) {
|
|
19015
|
+
const viewportName = aggregateProfileResultViewportName(result);
|
|
19016
|
+
const parentViewport = viewportName ? profile.target.viewports.find((viewport) => viewport.name === viewportName) : void 0;
|
|
19017
|
+
if (parentViewport) return parentViewport;
|
|
19018
|
+
const evidenceViewport = result.evidence?.viewports?.length === 1 ? result.evidence.viewports[0] : void 0;
|
|
19019
|
+
if (evidenceViewport && typeof evidenceViewport.name === "string" && typeof evidenceViewport.width === "number" && Number.isFinite(evidenceViewport.width) && typeof evidenceViewport.height === "number" && Number.isFinite(evidenceViewport.height)) {
|
|
19020
|
+
return {
|
|
19021
|
+
name: evidenceViewport.name,
|
|
19022
|
+
width: evidenceViewport.width,
|
|
19023
|
+
height: evidenceViewport.height
|
|
19024
|
+
};
|
|
19025
|
+
}
|
|
19026
|
+
throw new Error(`Profile aggregate input must be a single named viewport result or include selected viewport metadata: ${resultPath}`);
|
|
19027
|
+
}
|
|
19028
|
+
function sortAggregateChildRuns(profile, childRuns) {
|
|
19029
|
+
const viewportOrder = new Map(profile.target.viewports.map((viewport, index) => [viewport.name, index]));
|
|
19030
|
+
return [...childRuns].sort((a, b) => {
|
|
19031
|
+
const aIndex = viewportOrder.get(a.viewport.name) ?? Number.MAX_SAFE_INTEGER;
|
|
19032
|
+
const bIndex = viewportOrder.get(b.viewport.name) ?? Number.MAX_SAFE_INTEGER;
|
|
19033
|
+
if (aIndex !== bIndex) return aIndex - bIndex;
|
|
19034
|
+
return a.viewport.name.localeCompare(b.viewport.name);
|
|
19035
|
+
});
|
|
19036
|
+
}
|
|
18914
19037
|
function splitViewportArtifactRefs(input) {
|
|
18915
19038
|
return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
|
|
18916
19039
|
...artifact,
|
|
@@ -18921,7 +19044,7 @@ function sumDefinedNumbers(values) {
|
|
|
18921
19044
|
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
18922
19045
|
return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
|
|
18923
19046
|
}
|
|
18924
|
-
function splitViewportRiddleMetadata(childRuns) {
|
|
19047
|
+
function splitViewportRiddleMetadata(childRuns, mode = "split-viewports") {
|
|
18925
19048
|
const splitJobs = childRuns.map(({ viewport, result }) => ({
|
|
18926
19049
|
viewport: viewport.name,
|
|
18927
19050
|
job_id: result.riddle?.job_id,
|
|
@@ -18938,9 +19061,9 @@ function splitViewportRiddleMetadata(childRuns) {
|
|
|
18938
19061
|
artifact_recovery: result.riddle?.artifact_recovery
|
|
18939
19062
|
}));
|
|
18940
19063
|
return {
|
|
18941
|
-
mode
|
|
19064
|
+
mode,
|
|
18942
19065
|
job_count: childRuns.length,
|
|
18943
|
-
status:
|
|
19066
|
+
status: mode,
|
|
18944
19067
|
terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
|
|
18945
19068
|
artifact_recovery: childRuns.some(({ result }) => result.riddle?.artifact_recovery === true),
|
|
18946
19069
|
queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
|
|
@@ -19196,6 +19319,40 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
19196
19319
|
});
|
|
19197
19320
|
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
19198
19321
|
}
|
|
19322
|
+
async function aggregateProfileResultsForCli(profile, options) {
|
|
19323
|
+
const resultPaths = runProfileAggregateInputPathsOption(options);
|
|
19324
|
+
const seenViewports = /* @__PURE__ */ new Set();
|
|
19325
|
+
const childInputs = resultPaths.map((resultPath) => ({ resultPath, result: readProfileResultForAggregate(resultPath) })).filter(({ result: result2 }) => !profileResultIsAggregateParent(result2));
|
|
19326
|
+
if (!childInputs.length) {
|
|
19327
|
+
throw new Error("run-profile aggregate found no single-viewport child profile results.");
|
|
19328
|
+
}
|
|
19329
|
+
const childRuns = sortAggregateChildRuns(profile, childInputs.map(({ resultPath, result: result2 }) => {
|
|
19330
|
+
const viewport = aggregateProfileResultViewport(profile, result2, resultPath);
|
|
19331
|
+
if (seenViewports.has(viewport.name)) {
|
|
19332
|
+
throw new Error(`Profile aggregate received more than one result for viewport ${viewport.name}.`);
|
|
19333
|
+
}
|
|
19334
|
+
seenViewports.add(viewport.name);
|
|
19335
|
+
return { viewport, profile: profileForSplitViewport(profile, viewport), result: result2 };
|
|
19336
|
+
}));
|
|
19337
|
+
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
19338
|
+
const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
|
|
19339
|
+
if (blocked.length) {
|
|
19340
|
+
return createRiddleProofProfileEnvironmentBlockedResult({
|
|
19341
|
+
profile,
|
|
19342
|
+
runner: "riddle",
|
|
19343
|
+
error: splitViewportBlockedMessage(childRuns),
|
|
19344
|
+
riddle: splitViewportRiddleMetadata(childRuns, "named-viewport-aggregate"),
|
|
19345
|
+
artifacts
|
|
19346
|
+
});
|
|
19347
|
+
}
|
|
19348
|
+
const evidence = aggregateSplitViewportEvidence(profile, childRuns);
|
|
19349
|
+
const result = assessRiddleProofProfileEvidence(profile, evidence, {
|
|
19350
|
+
runner: "riddle",
|
|
19351
|
+
riddle: splitViewportRiddleMetadata(childRuns, "named-viewport-aggregate"),
|
|
19352
|
+
artifacts
|
|
19353
|
+
});
|
|
19354
|
+
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
19355
|
+
}
|
|
19199
19356
|
async function recoverProfileForCli(profile, options) {
|
|
19200
19357
|
const runner = optionString(options, "runner") || "riddle";
|
|
19201
19358
|
if (runner !== "riddle") {
|
|
@@ -19300,7 +19457,7 @@ async function main() {
|
|
|
19300
19457
|
}
|
|
19301
19458
|
if (command === "run-profile") {
|
|
19302
19459
|
const profile = profileWithSelectedViewportNamesForCli(normalizeProfileForCli(options), options);
|
|
19303
|
-
const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : await runProfileForCli(profile, options);
|
|
19460
|
+
const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : positional[1] === "aggregate" ? await aggregateProfileResultsForCli(profile, options) : await runProfileForCli(profile, options);
|
|
19304
19461
|
writeProfileOutput(profileOutputDirOption(options), result);
|
|
19305
19462
|
const diagnosticLine = profileCliDiagnosticLine(result);
|
|
19306
19463
|
if (diagnosticLine && optionBoolean(options, "quiet") !== true) {
|
package/dist/cli.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
import "./chunk-VY4Y5U57.js";
|
|
39
39
|
|
|
40
40
|
// src/cli.ts
|
|
41
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
41
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
42
42
|
import path from "path";
|
|
43
43
|
function usage() {
|
|
44
44
|
return [
|
|
@@ -49,6 +49,7 @@ function usage() {
|
|
|
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
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 aggregate --profile <file|json|-> --url <base-url> --input-dir <dir>|--inputs <path[,path...]> [--output <dir>|--output-dir <dir>] [--result-format json|compact-json|summary|none; default json]",
|
|
52
53
|
" 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]",
|
|
53
54
|
" riddle-proof-loop profile-body-assertions --artifact <file|url|-> --candidates-json <file|json|-> [--required-json <file|json|->] [--format json|body-contains]",
|
|
54
55
|
" riddle-proof-loop profile-http-status-preflight --profile <file|json|-> --url <base-url> [--format json|summary]",
|
|
@@ -528,6 +529,7 @@ function profileRiddleJobMarkdown(result) {
|
|
|
528
529
|
const artifactRecovery = riddle.artifact_recovery === true;
|
|
529
530
|
const retryCount = cliFiniteNumber(riddle.retry_count);
|
|
530
531
|
const staleJobIds = Array.isArray(riddle.stale_job_ids) ? riddle.stale_job_ids.map((value) => cliString(value)).filter((value) => Boolean(value)) : [];
|
|
532
|
+
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
531
533
|
const parts = [
|
|
532
534
|
mode ? `mode ${markdownInlineCode(mode)}` : "",
|
|
533
535
|
jobCount === void 0 ? "" : `jobs ${jobCount}`,
|
|
@@ -537,9 +539,18 @@ function profileRiddleJobMarkdown(result) {
|
|
|
537
539
|
].filter(Boolean);
|
|
538
540
|
const lines = parts.length ? [`- ${parts.join(", ")}`] : [];
|
|
539
541
|
if (queueElapsedMs !== void 0 || elapsedMs !== void 0 || attempt !== void 0 || attempts !== void 0) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
542
|
+
if (splitJobs.length) {
|
|
543
|
+
const maxChildQueueElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.queue_elapsed_ms)));
|
|
544
|
+
const maxChildElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.elapsed_ms)));
|
|
545
|
+
const maxChildPreSubmissionElapsedMs = maxDefinedNumbers(splitJobs.map((job) => cliFiniteNumber(job.pre_submission_elapsed_ms)));
|
|
546
|
+
lines.push(
|
|
547
|
+
`- child poll totals: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}; max child queue ${formatPollDuration(maxChildQueueElapsedMs)}, max child elapsed ${formatPollDuration(maxChildElapsedMs)}${maxChildPreSubmissionElapsedMs === void 0 || maxChildPreSubmissionElapsedMs < 1e3 ? "" : `, max child pre-submit ${formatPollDuration(maxChildPreSubmissionElapsedMs)}`}`
|
|
548
|
+
);
|
|
549
|
+
} else {
|
|
550
|
+
lines.push(
|
|
551
|
+
`- poll: queue ${formatPollDuration(queueElapsedMs)}, elapsed ${formatPollDuration(elapsedMs)}${preSubmissionElapsedMs === void 0 || preSubmissionElapsedMs < 1e3 ? "" : `, pre-submit ${formatPollDuration(preSubmissionElapsedMs)}`}${attempt === void 0 ? "" : `, attempt ${attempt}${attempts === void 0 ? "" : `/${attempts}`}`}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
543
554
|
}
|
|
544
555
|
if (submittedAt || completedAt) {
|
|
545
556
|
lines.push(`- timing:${submittedAt ? ` submitted ${markdownInlineCode(submittedAt)}` : ""}${completedAt ? ` completed ${markdownInlineCode(completedAt)}` : ""}`);
|
|
@@ -550,7 +561,6 @@ function profileRiddleJobMarkdown(result) {
|
|
|
550
561
|
if (retryCount !== void 0 && retryCount > 0) {
|
|
551
562
|
lines.push(`- retry recovery: replaced ${retryCount} unsubmitted job${retryCount === 1 ? "" : "s"}${staleJobIds.length ? ` (${staleJobIds.map((value) => markdownInlineCode(value)).join(", ")})` : ""}`);
|
|
552
563
|
}
|
|
553
|
-
const splitJobs = Array.isArray(riddle.split_jobs) ? riddle.split_jobs.map(cliRecord).filter((job) => Boolean(job)) : [];
|
|
554
564
|
for (const job of splitJobs.slice(0, 12)) {
|
|
555
565
|
const viewport = cliString(job.viewport) || "viewport";
|
|
556
566
|
const splitJobId = cliString(job.job_id);
|
|
@@ -575,6 +585,10 @@ function profileRiddleJobMarkdown(result) {
|
|
|
575
585
|
if (splitJobs.length > 12) lines.push(`- ${splitJobs.length - 12} additional split job(s) omitted.`);
|
|
576
586
|
return lines;
|
|
577
587
|
}
|
|
588
|
+
function maxDefinedNumbers(values) {
|
|
589
|
+
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
590
|
+
return numbers.length ? Math.max(...numbers) : void 0;
|
|
591
|
+
}
|
|
578
592
|
function profileMetadataStringArray(value) {
|
|
579
593
|
return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => Boolean(item)) : [];
|
|
580
594
|
}
|
|
@@ -663,32 +677,64 @@ function profileIsCleanupInventoryReceipt(receipt) {
|
|
|
663
677
|
const haystack = profileCleanupInventoryHaystack(receipt);
|
|
664
678
|
return haystack.includes("cleanup") || haystack.includes("post-cleanup") || haystack.includes("stale") || haystack.includes("statehygiene") || haystack.includes("state hygiene") || haystack.includes("remained after") || haystack.includes("still present");
|
|
665
679
|
}
|
|
666
|
-
function
|
|
667
|
-
const
|
|
680
|
+
function profileIsCleanupPhaseInventoryReceipt(receipt) {
|
|
681
|
+
const storedTo = (cliString(receipt.return_stored_to) || "").toLowerCase();
|
|
682
|
+
const storedSegments = storedTo.split(/[.[\]/]/).filter(Boolean);
|
|
683
|
+
const storedSegment = storedSegments[storedSegments.length - 1] || storedTo;
|
|
684
|
+
const state = (cliString(setupReturnSummaryValue(receipt, ["state", "phase"])) || "").toLowerCase();
|
|
685
|
+
const summary = (cliReturnSummaryLabel(receipt.return_summary) || "").toLowerCase();
|
|
686
|
+
const markers = [storedSegment, state, summary];
|
|
687
|
+
return markers.some((marker) => marker === "cleanup" || marker === "postcleanup" || marker === "post-cleanup" || marker === "aftercleanup" || marker === "after-cleanup" || marker.includes("post-cleanup") || marker.includes("after-cleanup") || marker.includes("after-clear") || marker.includes("after-reset") || marker.includes("after-undo") || marker.includes("after-discard") || marker.includes("after-new"));
|
|
688
|
+
}
|
|
689
|
+
function profileCleanupPhaseInventoryReceipts(setupViewports) {
|
|
690
|
+
return setupViewports.flatMap((viewport) => [
|
|
668
691
|
...setupReceiptArray(viewport, "window_eval"),
|
|
669
692
|
...setupReceiptArray(viewport, "window_call")
|
|
670
|
-
]);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
693
|
+
]).filter((receipt) => profileIsCleanupInventoryReceipt(receipt) && profileIsCleanupPhaseInventoryReceipt(receipt));
|
|
694
|
+
}
|
|
695
|
+
function profileFailedCleanupInventoryReceiptReason(receipt) {
|
|
696
|
+
if (receipt.ok === false) {
|
|
674
697
|
const error = cliString(receipt.error);
|
|
675
698
|
const reason = cliString(receipt.reason);
|
|
676
699
|
return compactProfileReceiptReason(error) || compactProfileReceiptReason(reason) || "cleanup inventory failed";
|
|
677
700
|
}
|
|
701
|
+
const parts = [];
|
|
702
|
+
if (setupReturnedSummaryValue(receipt, ["ok"]) === false) parts.push("ok=false");
|
|
703
|
+
if (setupReturnedSummaryValue(receipt, ["success"]) === false) parts.push("success=false");
|
|
704
|
+
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
705
|
+
if (staleCount !== void 0 && staleCount > 0) parts.push(`staleCount=${staleCount}`);
|
|
706
|
+
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
707
|
+
if (Array.isArray(staleNames) && staleNames.length > 0) {
|
|
708
|
+
const staleNamesLabel = cliValueLabel(staleNames);
|
|
709
|
+
if (staleNamesLabel) parts.push(`staleNames=${compactProfileReceiptReason(staleNamesLabel, 120) ?? staleNamesLabel}`);
|
|
710
|
+
}
|
|
711
|
+
const productIssue = setupReturnedSummaryValue(receipt, ["productIssue", "issue"]);
|
|
712
|
+
const productIssueLabel = typeof productIssue === "string" ? compactProfileReceiptReason(productIssue, 120) : void 0;
|
|
713
|
+
if (parts.length && productIssueLabel) parts.push(productIssueLabel);
|
|
714
|
+
return parts.length ? parts.join(", ") : void 0;
|
|
715
|
+
}
|
|
716
|
+
function profileFailedCleanupInventoryReason(setupViewports) {
|
|
717
|
+
const receipts = profileCleanupPhaseInventoryReceipts(setupViewports);
|
|
718
|
+
for (const receipt of [...receipts].reverse()) {
|
|
719
|
+
const reason = profileFailedCleanupInventoryReceiptReason(receipt);
|
|
720
|
+
if (reason) return reason;
|
|
721
|
+
}
|
|
678
722
|
return void 0;
|
|
679
723
|
}
|
|
724
|
+
function profilePassedCleanupInventoryReceiptReason(receipt) {
|
|
725
|
+
if (receipt.ok === false) return void 0;
|
|
726
|
+
if (setupReturnedSummaryValue(receipt, ["ok"]) === false) return void 0;
|
|
727
|
+
if (setupReturnedSummaryValue(receipt, ["success"]) === false) return void 0;
|
|
728
|
+
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
729
|
+
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
730
|
+
if (staleCount !== 0 || !Array.isArray(staleNames) || staleNames.length !== 0) return void 0;
|
|
731
|
+
return "staleCount=0, staleNames=[]";
|
|
732
|
+
}
|
|
680
733
|
function profilePassedCleanupInventoryReason(setupViewports) {
|
|
681
|
-
const receipts = setupViewports
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
for (const receipt of receipts) {
|
|
686
|
-
if (receipt.ok === false || !profileIsCleanupInventoryReceipt(receipt)) continue;
|
|
687
|
-
if (setupReturnSummaryValue(receipt, ["ok"]) === false) continue;
|
|
688
|
-
const staleCount = cliFiniteNumber(setupReturnSummaryValue(receipt, ["staleCount"]));
|
|
689
|
-
const staleNames = setupReturnSummaryValue(receipt, ["staleNames"]);
|
|
690
|
-
if (staleCount !== 0 || !Array.isArray(staleNames) || staleNames.length !== 0) continue;
|
|
691
|
-
return "staleCount=0, staleNames=[]";
|
|
734
|
+
const receipts = profileCleanupPhaseInventoryReceipts(setupViewports);
|
|
735
|
+
for (const receipt of [...receipts].reverse()) {
|
|
736
|
+
const reason = profilePassedCleanupInventoryReceiptReason(receipt);
|
|
737
|
+
if (reason) return reason;
|
|
692
738
|
}
|
|
693
739
|
return void 0;
|
|
694
740
|
}
|
|
@@ -870,15 +916,17 @@ function profileHasRecoveredStateReceipt(receipts) {
|
|
|
870
916
|
const path2 = cliString(receipt.path) || cliString(receipt.function_name) || "";
|
|
871
917
|
const summary = cliReturnSummaryLabel(receipt.return_summary) || "";
|
|
872
918
|
const haystack = `${storedTo} ${label} ${path2} ${summary}`.toLowerCase();
|
|
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");
|
|
919
|
+
const labelsRecovery = haystack.includes("recover") || haystack.includes("repaired") || haystack.includes("repair") || haystack.includes("retry") || 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");
|
|
874
920
|
if (!labelsRecovery) return false;
|
|
875
921
|
const status = profileLowerSummaryValue(receipt, ["status", "state", "phase"]);
|
|
876
|
-
const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result"]);
|
|
877
|
-
const hasRecoveredState = ["valid", "success", "recovered", "fixed", "ready"].includes(status) || ["valid", "success", "recovered", "fixed", "ready"].includes(outcome);
|
|
922
|
+
const outcome = profileLowerSummaryValue(receipt, ["lastOutcome", "outcome", "result", "retryOutcome", "retry_outcome"]);
|
|
923
|
+
const hasRecoveredState = ["valid", "success", "recovered", "fixed", "ready"].includes(status) || ["valid", "success", "recovered", "fixed", "ready", "running_after_retry", "ready_after_retry"].includes(outcome);
|
|
878
924
|
const hasValid = setupReturnSummaryValue(receipt, ["hasValid", "valid", "isValid"]) === true;
|
|
879
925
|
const hasInvalid = setupReturnSummaryValue(receipt, ["hasInvalid", "invalid", "isInvalid"]);
|
|
880
926
|
const success = setupReturnSummaryValue(receipt, ["success", "recovered", "fixed"]) === true;
|
|
881
|
-
|
|
927
|
+
const leftTerminalState = setupReturnSummaryValue(receipt, ["leftTerminalState", "left_terminal_state"]) === true;
|
|
928
|
+
const retrySurfaceReady = setupReturnSummaryValue(receipt, ["retrySurfaceReady", "retry_surface_ready"]) === true;
|
|
929
|
+
return hasRecoveredState || success || hasValid && hasInvalid === false || leftTerminalState && retrySurfaceReady;
|
|
882
930
|
});
|
|
883
931
|
}
|
|
884
932
|
function profileMetadataHasGeneratedOutputContract(metadata) {
|
|
@@ -1431,6 +1479,9 @@ function setupReturnSummaryValue(receipt, names) {
|
|
|
1431
1479
|
for (const name of names) {
|
|
1432
1480
|
if (receipt[name] !== void 0) return receipt[name];
|
|
1433
1481
|
}
|
|
1482
|
+
return setupReturnedSummaryValue(receipt, names);
|
|
1483
|
+
}
|
|
1484
|
+
function setupReturnedSummaryValue(receipt, names) {
|
|
1434
1485
|
const returned = cliRecord(receipt.returned);
|
|
1435
1486
|
for (const name of names) {
|
|
1436
1487
|
if (returned?.[name] !== void 0) return returned[name];
|
|
@@ -2736,6 +2787,78 @@ function splitViewportOutputDir(outputDir, viewportName, seen) {
|
|
|
2736
2787
|
seen.set(base, count + 1);
|
|
2737
2788
|
return path.join(outputDir, count ? `${base}-${count + 1}` : base);
|
|
2738
2789
|
}
|
|
2790
|
+
function profileResultPathFromInput(inputPath) {
|
|
2791
|
+
if (!existsSync(inputPath)) throw new Error(`Profile aggregate input path does not exist: ${inputPath}`);
|
|
2792
|
+
const stat = statSync(inputPath);
|
|
2793
|
+
if (stat.isFile()) return [inputPath];
|
|
2794
|
+
if (!stat.isDirectory()) throw new Error(`Profile aggregate input path must be a file or directory: ${inputPath}`);
|
|
2795
|
+
const childProfileResults = readdirSync(inputPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(inputPath, entry.name, "profile-result.json")).filter((candidate) => existsSync(candidate));
|
|
2796
|
+
if (childProfileResults.length) return childProfileResults;
|
|
2797
|
+
const directProfileResult = path.join(inputPath, "profile-result.json");
|
|
2798
|
+
if (existsSync(directProfileResult)) return [directProfileResult];
|
|
2799
|
+
throw new Error(`Profile aggregate input directory has no child profile-result.json files: ${inputPath}`);
|
|
2800
|
+
}
|
|
2801
|
+
function runProfileAggregateInputPathsOption(options) {
|
|
2802
|
+
const rawInputs = [
|
|
2803
|
+
optionString(options, "input"),
|
|
2804
|
+
optionString(options, "inputs"),
|
|
2805
|
+
optionString(options, "inputFile"),
|
|
2806
|
+
optionString(options, "inputFiles")
|
|
2807
|
+
].filter((value) => Boolean(value));
|
|
2808
|
+
const explicitInputs = rawInputs.flatMap((raw) => raw.split(",").map((part) => part.trim()).filter(Boolean));
|
|
2809
|
+
const inputDir = optionString(options, "inputDir") ?? optionString(options, "resultsDir") ?? optionString(options, "runDir");
|
|
2810
|
+
const discoveredInputs = inputDir ? profileResultPathFromInput(inputDir) : [];
|
|
2811
|
+
const paths = [...explicitInputs.flatMap(profileResultPathFromInput), ...discoveredInputs];
|
|
2812
|
+
const uniquePaths = [...new Set(paths.map((inputPath) => path.resolve(inputPath)))];
|
|
2813
|
+
if (!uniquePaths.length) {
|
|
2814
|
+
throw new Error("run-profile aggregate requires --input-dir <dir> or --inputs <path[,path...]>.");
|
|
2815
|
+
}
|
|
2816
|
+
return uniquePaths;
|
|
2817
|
+
}
|
|
2818
|
+
function readProfileResultForAggregate(resultPath) {
|
|
2819
|
+
const parsed = readJsonValue(resultPath, resultPath);
|
|
2820
|
+
if (parsed.version !== "riddle-proof.profile-result.v1" || !Array.isArray(parsed.checks)) {
|
|
2821
|
+
throw new Error(`Profile aggregate input is not a riddle-proof.profile-result.v1 result: ${resultPath}`);
|
|
2822
|
+
}
|
|
2823
|
+
return parsed;
|
|
2824
|
+
}
|
|
2825
|
+
function profileResultIsAggregateParent(result) {
|
|
2826
|
+
const mode = cliString(cliRecord(result.riddle)?.mode);
|
|
2827
|
+
return (mode === "split-viewports" || mode === "named-viewport-aggregate") && (result.evidence?.viewports?.length || 0) > 1;
|
|
2828
|
+
}
|
|
2829
|
+
function aggregateProfileResultViewportName(result) {
|
|
2830
|
+
const evidenceViewports = result.evidence?.viewports || [];
|
|
2831
|
+
if (evidenceViewports.length === 1) return evidenceViewports[0].name;
|
|
2832
|
+
const metadata = cliRecord(result.metadata);
|
|
2833
|
+
const splitViewport = cliString(metadata?.split_viewport);
|
|
2834
|
+
if (splitViewport) return splitViewport;
|
|
2835
|
+
const selectedViewports = Array.isArray(metadata?.selected_viewports) ? metadata.selected_viewports.map(cliString).filter((name) => Boolean(name)) : [];
|
|
2836
|
+
if (selectedViewports.length === 1) return selectedViewports[0];
|
|
2837
|
+
return void 0;
|
|
2838
|
+
}
|
|
2839
|
+
function aggregateProfileResultViewport(profile, result, resultPath) {
|
|
2840
|
+
const viewportName = aggregateProfileResultViewportName(result);
|
|
2841
|
+
const parentViewport = viewportName ? profile.target.viewports.find((viewport) => viewport.name === viewportName) : void 0;
|
|
2842
|
+
if (parentViewport) return parentViewport;
|
|
2843
|
+
const evidenceViewport = result.evidence?.viewports?.length === 1 ? result.evidence.viewports[0] : void 0;
|
|
2844
|
+
if (evidenceViewport && typeof evidenceViewport.name === "string" && typeof evidenceViewport.width === "number" && Number.isFinite(evidenceViewport.width) && typeof evidenceViewport.height === "number" && Number.isFinite(evidenceViewport.height)) {
|
|
2845
|
+
return {
|
|
2846
|
+
name: evidenceViewport.name,
|
|
2847
|
+
width: evidenceViewport.width,
|
|
2848
|
+
height: evidenceViewport.height
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
throw new Error(`Profile aggregate input must be a single named viewport result or include selected viewport metadata: ${resultPath}`);
|
|
2852
|
+
}
|
|
2853
|
+
function sortAggregateChildRuns(profile, childRuns) {
|
|
2854
|
+
const viewportOrder = new Map(profile.target.viewports.map((viewport, index) => [viewport.name, index]));
|
|
2855
|
+
return [...childRuns].sort((a, b) => {
|
|
2856
|
+
const aIndex = viewportOrder.get(a.viewport.name) ?? Number.MAX_SAFE_INTEGER;
|
|
2857
|
+
const bIndex = viewportOrder.get(b.viewport.name) ?? Number.MAX_SAFE_INTEGER;
|
|
2858
|
+
if (aIndex !== bIndex) return aIndex - bIndex;
|
|
2859
|
+
return a.viewport.name.localeCompare(b.viewport.name);
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2739
2862
|
function splitViewportArtifactRefs(input) {
|
|
2740
2863
|
return (input.result.artifacts.riddle_artifacts || []).map((artifact) => ({
|
|
2741
2864
|
...artifact,
|
|
@@ -2746,7 +2869,7 @@ function sumDefinedNumbers(values) {
|
|
|
2746
2869
|
const numbers = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
2747
2870
|
return numbers.length ? numbers.reduce((sum, value) => sum + value, 0) : void 0;
|
|
2748
2871
|
}
|
|
2749
|
-
function splitViewportRiddleMetadata(childRuns) {
|
|
2872
|
+
function splitViewportRiddleMetadata(childRuns, mode = "split-viewports") {
|
|
2750
2873
|
const splitJobs = childRuns.map(({ viewport, result }) => ({
|
|
2751
2874
|
viewport: viewport.name,
|
|
2752
2875
|
job_id: result.riddle?.job_id,
|
|
@@ -2763,9 +2886,9 @@ function splitViewportRiddleMetadata(childRuns) {
|
|
|
2763
2886
|
artifact_recovery: result.riddle?.artifact_recovery
|
|
2764
2887
|
}));
|
|
2765
2888
|
return {
|
|
2766
|
-
mode
|
|
2889
|
+
mode,
|
|
2767
2890
|
job_count: childRuns.length,
|
|
2768
|
-
status:
|
|
2891
|
+
status: mode,
|
|
2769
2892
|
terminal: childRuns.every(({ result }) => result.riddle?.terminal !== false),
|
|
2770
2893
|
artifact_recovery: childRuns.some(({ result }) => result.riddle?.artifact_recovery === true),
|
|
2771
2894
|
queue_elapsed_ms: sumDefinedNumbers(splitJobs.map((job) => job.queue_elapsed_ms)),
|
|
@@ -3021,6 +3144,40 @@ async function runSplitViewportProfileForCli(profile, options, input) {
|
|
|
3021
3144
|
});
|
|
3022
3145
|
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
3023
3146
|
}
|
|
3147
|
+
async function aggregateProfileResultsForCli(profile, options) {
|
|
3148
|
+
const resultPaths = runProfileAggregateInputPathsOption(options);
|
|
3149
|
+
const seenViewports = /* @__PURE__ */ new Set();
|
|
3150
|
+
const childInputs = resultPaths.map((resultPath) => ({ resultPath, result: readProfileResultForAggregate(resultPath) })).filter(({ result: result2 }) => !profileResultIsAggregateParent(result2));
|
|
3151
|
+
if (!childInputs.length) {
|
|
3152
|
+
throw new Error("run-profile aggregate found no single-viewport child profile results.");
|
|
3153
|
+
}
|
|
3154
|
+
const childRuns = sortAggregateChildRuns(profile, childInputs.map(({ resultPath, result: result2 }) => {
|
|
3155
|
+
const viewport = aggregateProfileResultViewport(profile, result2, resultPath);
|
|
3156
|
+
if (seenViewports.has(viewport.name)) {
|
|
3157
|
+
throw new Error(`Profile aggregate received more than one result for viewport ${viewport.name}.`);
|
|
3158
|
+
}
|
|
3159
|
+
seenViewports.add(viewport.name);
|
|
3160
|
+
return { viewport, profile: profileForSplitViewport(profile, viewport), result: result2 };
|
|
3161
|
+
}));
|
|
3162
|
+
const artifacts = childRuns.flatMap(splitViewportArtifactRefs);
|
|
3163
|
+
const blocked = childRuns.filter(({ result: result2 }) => !result2.evidence || result2.status === "environment_blocked" || result2.status === "configuration_error");
|
|
3164
|
+
if (blocked.length) {
|
|
3165
|
+
return createRiddleProofProfileEnvironmentBlockedResult({
|
|
3166
|
+
profile,
|
|
3167
|
+
runner: "riddle",
|
|
3168
|
+
error: splitViewportBlockedMessage(childRuns),
|
|
3169
|
+
riddle: splitViewportRiddleMetadata(childRuns, "named-viewport-aggregate"),
|
|
3170
|
+
artifacts
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
const evidence = aggregateSplitViewportEvidence(profile, childRuns);
|
|
3174
|
+
const result = assessRiddleProofProfileEvidence(profile, evidence, {
|
|
3175
|
+
runner: "riddle",
|
|
3176
|
+
riddle: splitViewportRiddleMetadata(childRuns, "named-viewport-aggregate"),
|
|
3177
|
+
artifacts
|
|
3178
|
+
});
|
|
3179
|
+
return withSplitViewportWarnings(profile, withSplitViewportChildStatusCheck(profile, result, childRuns));
|
|
3180
|
+
}
|
|
3024
3181
|
async function recoverProfileForCli(profile, options) {
|
|
3025
3182
|
const runner = optionString(options, "runner") || "riddle";
|
|
3026
3183
|
if (runner !== "riddle") {
|
|
@@ -3125,7 +3282,7 @@ async function main() {
|
|
|
3125
3282
|
}
|
|
3126
3283
|
if (command === "run-profile") {
|
|
3127
3284
|
const profile = profileWithSelectedViewportNamesForCli(normalizeProfileForCli(options), options);
|
|
3128
|
-
const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : await runProfileForCli(profile, options);
|
|
3285
|
+
const result = positional[1] === "recover" ? await recoverProfileForCli(profile, options) : positional[1] === "aggregate" ? await aggregateProfileResultsForCli(profile, options) : await runProfileForCli(profile, options);
|
|
3129
3286
|
writeProfileOutput(profileOutputDirOption(options), result);
|
|
3130
3287
|
const diagnosticLine = profileCliDiagnosticLine(result);
|
|
3131
3288
|
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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "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: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
|
|
663
663
|
state_path: string;
|
|
664
664
|
stage: any;
|
|
665
665
|
summary: string;
|