feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)

* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
This commit is contained in:
morluto authored and GitHub committed 2026-11-10 05:11:20 -05:00
1 parent 55b6d2ca20
commit cb4e5cfbff
515 files changed
+21066 -3637

No files matched your search

+34
View File
@@ -76,6 +76,23 @@ export const registerApktoolCommands = (
decodeTimeoutMs: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape.decode_timeout_ms,
),
stringOffset: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape.string_offset,
),
stringLimit: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape.string_limit,
),
maxTotalStrings: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape.max_total_strings,
),
maxStringTableBytes: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape
.max_string_table_bytes,
),
maxTotalStringTableBytes: cliDecimalField(
apktoolInputSchemas.decode_android_resources.shape
.max_total_string_table_bytes,
),
strings: z
.boolean()
.default(
@@ -104,6 +121,23 @@ export const registerApktoolCommands = (
...(options.decodeTimeoutMs === undefined
? {}
: { decode_timeout_ms: options.decodeTimeoutMs }),
...(options.stringOffset === undefined
? {}
: { string_offset: options.stringOffset }),
...(options.stringLimit === undefined
? {}
: { string_limit: options.stringLimit }),
...(options.maxTotalStrings === undefined
? {}
: { max_total_strings: options.maxTotalStrings }),
...(options.maxStringTableBytes === undefined
? {}
: { max_string_table_bytes: options.maxStringTableBytes }),
...(options.maxTotalStringTableBytes === undefined
? {}
: {
max_total_string_table_bytes: options.maxTotalStringTableBytes,
}),
},
args.path,
),
+20 -1
View File
@@ -16,6 +16,8 @@ import { keyedArchiveInputSchema } from "../domain/apple/keyedArchive.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { z } from "incur";
import { cliDecimalField } from "./options.js";
import { producerLimitsOption } from "./producerLimitsOption.js";
import { artifactReaderLimitsInputSchema } from "../domain/artifactReaderLimits.js";
import { artifactExtractionInputSchema } from "../contracts/artifactToolContracts.js";
@@ -32,6 +34,13 @@ const keyedArchiveInputBinding = {
fields: { path: "archive" },
} as const;
const artifactLimitsOption = producerLimitsOption(
artifactReaderLimitsInputSchema,
"artifact-limits",
).describe(
"JSON artifact producer admissions: bun, dotnet_bundle, node_sea, godot, and expanded_bytes. Omitted fields keep defaults; changes apply on the next call without restart. Independent of summary/paging and client delivery limits.",
);
export const registerArtifactCommands = (
cli: CliInstance,
logger: Logger,
@@ -254,6 +263,7 @@ const registerExtractionCommand = (
}),
options: z.object({
// Incur needs an ordinary optional; keep the owning contract's metadata.
artifactLimits: artifactLimitsOption,
outputRoot: artifactExtractionInputSchema.shape.output_root
.unwrap()
.optional()
@@ -277,6 +287,9 @@ const registerExtractionCommand = (
"extract_artifact",
{
integrity_policy: options.integrityPolicy,
...(options.artifactLimits === undefined
? {}
: { artifact_limits: options.artifactLimits }),
output_root: createArtifactExtractionDestination(
options.outputRoot,
),
@@ -306,6 +319,7 @@ const registerInspectionCommand = (
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
artifactLimits: artifactLimitsOption,
integrityPolicy: z
.enum(["fail", "record-and-continue"])
.default("fail")
@@ -327,7 +341,12 @@ const registerInspectionCommand = (
const complete = await runProviderAnalysis(
args.path,
"inspect_artifact",
{ integrity_policy: options.integrityPolicy },
{
integrity_policy: options.integrityPolicy,
...(options.artifactLimits === undefined
? {}
: { artifact_limits: options.artifactLimits }),
},
logger,
signal,
undefined,
+18
View File
@@ -5,6 +5,8 @@ import { inspectBinaryHardeningInputSchema } from "../domain/native/binaryHarden
import { inspectByteEntropyInputSchema } from "../domain/native/byteEntropy.js";
import { z } from "incur";
import { cliDecimalField } from "./options.js";
import { producerLimitsOption } from "./producerLimitsOption.js";
import { pwntoolsProducerLimitsSchema } from "../domain/native/pwntoolsResourceLimits.js";
import {
createByteEntropyService,
createRecordedCrashService,
@@ -272,6 +274,12 @@ export const registerBinaryDiagnosticsCommands = (
maxEntries: cliDecimalField(
inspectKernelModuleInputSchema.shape.max_entries,
),
maxModinfoKeyBytes: cliDecimalField(
inspectKernelModuleInputSchema.shape.max_modinfo_key_bytes,
),
maxModinfoValueBytes: cliDecimalField(
inspectKernelModuleInputSchema.shape.max_modinfo_value_bytes,
),
}),
run: ({ args, options }) =>
withCommandCancellation(async (signal) => {
@@ -288,6 +296,8 @@ export const registerBinaryDiagnosticsCommands = (
path: absoluteCallerPath(args.path),
max_file_bytes: options.maxFileBytes,
max_entries: options.maxEntries,
max_modinfo_key_bytes: options.maxModinfoKeyBytes,
max_modinfo_value_bytes: options.maxModinfoValueBytes,
},
{ signal },
),
@@ -745,6 +755,7 @@ export const registerBinaryDiagnosticsCommands = (
),
}),
options: z.object({
producerLimits: producerLimitsOption(pwntoolsProducerLimitsSchema),
pwntoolsTimeoutMs: cliDecimalField(
inspectBinaryLayoutInputSchema.shape.pwntools_timeout_ms,
),
@@ -755,6 +766,9 @@ export const registerBinaryDiagnosticsCommands = (
const result = await service.inspectAndClose(
{
path: absoluteCallerPath(args.path),
...(options.producerLimits === undefined
? {}
: { producer_limits: options.producerLimits }),
...(options.pwntoolsTimeoutMs === undefined
? {}
: { pwntools_timeout_ms: options.pwntoolsTimeoutMs }),
@@ -777,6 +791,7 @@ export const registerBinaryDiagnosticsCommands = (
),
}),
options: z.object({
producerLimits: producerLimitsOption(pwntoolsProducerLimitsSchema),
debuggerContext: z
.boolean()
.default(false)
@@ -792,6 +807,9 @@ export const registerBinaryDiagnosticsCommands = (
{
path: absoluteCallerPath(args.path),
include_debugger_context: options.debuggerContext,
...(options.producerLimits === undefined
? {}
: { producer_limits: options.producerLimits }),
...(options.pwntoolsTimeoutMs === undefined
? {}
: { pwntools_timeout_ms: options.pwntoolsTimeoutMs }),
+25
View File
@@ -1,3 +1,8 @@
import { parseCliJsonInput } from "../cliJsonInput.js";
import {
fetchedSourceMapLimitCliOptions,
sourceMapLimitsFromCli,
} from "./sourceMapLimits.js";
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { webBundleDeliveryOptions } from "./javascriptRouteDelivery.js";
import {
@@ -178,6 +183,12 @@ const registerBundleAnalysis = (
...passiveBrowserScopeOptions,
...webBundleDeliveryOptions,
observationMs: observationDuration(500),
semanticPrecision: z
.string()
.optional()
.describe(
"Inline JSON or JSON file of semantic_precision evaluator budgets for each inert script.",
),
fetchSourceMaps: z
.boolean()
.default(false)
@@ -189,6 +200,7 @@ const registerBundleAnalysis = (
totalAnalysisTimeoutMs: cliDecimalField(
analyzeWebBundleInputSchema.shape.total_analysis_timeout_ms,
),
...fetchedSourceMapLimitCliOptions,
sourceMapFetchTimeoutMs: cliDecimalField(
analyzeWebBundleInputSchema.shape.source_map_fetch_timeout_ms,
),
@@ -202,6 +214,15 @@ const registerBundleAnalysis = (
webRoutesDeliverySchema,
);
if (!delivery.ok) return projectAnalysisError(delivery.error);
const precision =
options.semanticPrecision === undefined
? undefined
: await parseCliJsonInput(
options.semanticPrecision,
"analyze_web_bundle",
signal,
);
if (precision !== undefined && !precision.ok) return precision.error;
const provider = createBrowserObservationProvider();
const input = {
cdp_endpoint: args.endpoint,
@@ -210,6 +231,10 @@ const registerBundleAnalysis = (
observation_ms: options.observationMs,
include_script_sources: true,
fetch_source_maps: options.fetchSourceMaps,
...(precision === undefined
? {}
: { semantic_precision: precision.value }),
...sourceMapLimitsFromCli(options),
...(options.maxHeapMb === undefined
? {}
: { max_heap_mb: options.maxHeapMb }),
+21 -5
View File
@@ -18,6 +18,7 @@ import { finishCliEvidenceCommand } from "./cliEvidenceCallerPath.js";
import { cliDecimalField, cliDecimalShape } from "./options.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { producerLimitsOption } from "./producerLimitsOption.js";
export const registerCaptureCommands = (
cli: CliInstance,
logger: Logger,
@@ -27,11 +28,16 @@ export const registerCaptureCommands = (
"inspect" | "inspectFields" | "close"
> = createPacketCaptureService(environment),
): void => {
const { producer_limits: discoveryLimits, ...discoveryShape } =
inspectPacketFieldsInputSchema.shape;
const { producer_limits: selectedLimits, ...selectedShape } =
packetCaptureOptionsSchema.shape;
cli.command(CLI_COMMANDS.inspectPacketFields, {
description: "Search installed TShark protocol and field definitions",
options: z
.object({
...cliDecimalShape(inspectPacketFieldsInputSchema.shape),
...cliDecimalShape(discoveryShape),
producerLimits: producerLimitsOption(discoveryLimits.unwrap()),
limit: cliDecimalField(
z
.number()
@@ -54,13 +60,19 @@ export const registerCaptureCommands = (
message: "all and limit are mutually exclusive",
});
}),
run: ({ options: { all, ...options } }) =>
run: ({ options: { all, producerLimits, ...options } }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.inspectPacketFields, async () =>
finishAnalysisCommand(
() =>
service.inspectFields(
{ ...options, ...(all ? { limit: null } : {}) },
{
...options,
...(producerLimits === undefined
? {}
: { producer_limits: producerLimits }),
...(all ? { limit: null } : {}),
},
{ signal },
),
service,
@@ -79,7 +91,8 @@ export const registerCaptureCommands = (
}),
options: z
.object({
...cliDecimalShape(packetCaptureOptionsSchema.shape),
...cliDecimalShape(selectedShape),
producerLimits: producerLimitsOption(selectedLimits.unwrap()),
match_limit: cliDecimalField(
z
.number()
@@ -107,7 +120,7 @@ export const registerCaptureCommands = (
withinPacketCaptureSelectorBudget,
packetCaptureSelectorBudgetIssue,
),
run: ({ args, options: { all_matches, ...options } }) =>
run: ({ args, options: { all_matches, producerLimits, ...options } }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.inspectPacketCapture, async () => {
return finishCliEvidenceCommand(
@@ -116,6 +129,9 @@ export const registerCaptureCommands = (
service.inspect(
{
...options,
...(producerLimits === undefined
? {}
: { producer_limits: producerLimits }),
...(all_matches ? { match_limit: null } : {}),
path: absoluteCallerPath(args.path),
},
+64 -4
View File
@@ -1,3 +1,6 @@
import { desktopLibraryPolicyInputSchema } from "../domain/desktop/libraryPolicy.js";
import { desktopViewPolicyInputSchema } from "../domain/desktop/desktopViewPolicy.js";
import { safeParseJson } from "../domain/safeJson.js";
import { createInterface } from "node:readline";
import { z } from "incur";
@@ -27,11 +30,52 @@ import { SessionRequestQueue } from "../desktop/DesktopSessionRequests.js";
import { withCommandCancellation } from "./commandCancellation.js";
import type { CliInstance } from "./types.js";
/** One coherent view policy; invalid JSON and unknown controls fail at admission. */
const viewPolicyOption = z
.string()
.transform((text, context) => {
const parsed = safeParseJson(text);
if (!parsed.ok) {
context.addIssue({
code: "custom",
message: "view-policy must be a JSON object",
});
return z.NEVER;
}
return parsed.value;
})
.pipe(desktopViewPolicyInputSchema)
.optional()
.describe(
"JSON budget overrides: file_bytes, sqlite_tables, sqlite_rows, sqlite_view_bytes, text_lines, xml_nodes, xml_depth, parsed_bytes, json_depth, zip_entries, zip_entry_bytes, zip_bytes, zip_ratio. Positive safe integers. Applies on this call and to exported readers; no restart. Does not recover bytes the capture never retained or raise Evidence/worker limits.",
);
/** Caller-selected inventory headroom; does not raise content-retention budgets. */
const libraryPolicyOption = z
.string()
.transform((text, context) => {
const parsed = safeParseJson(text);
if (!parsed.ok) {
context.addIssue({
code: "custom",
message: "library-policy must be a JSON object",
});
return z.NEVER;
}
return parsed.value;
})
.pipe(desktopLibraryPolicyInputSchema)
.optional()
.describe(
"JSON dependency inventory budgets: libraries (default 512, also per-image DT_NEEDED entries), search_path_entries (16 distinct offsets per DT_RUNPATH/DT_RPATH kind), string_bytes (4096 per string including NUL), table_bytes (1048576 per ELF program/dynamic table). Positive safe integers; applies to this capture without restart. Does not raise content or query-output budgets.",
);
const positive = (fallback: number) =>
cliDecimalField(z.int().positive().default(fallback));
/** Options of `capture-desktop-application`. */
const captureOptions = z.object({
libraryPolicy: libraryPolicyOption,
desktopEntry: z
.string()
.min(1)
@@ -150,8 +194,8 @@ const withStateReaders = async (
return finishAnalysisCommand(
async () => {
const result = await run({
sqlite: (database, readSignal) =>
readSqliteStateView(sqlite, database, readSignal),
sqlite: (database, readSignal, policy) =>
readSqliteStateView(sqlite, database, readSignal, policy),
dconf: (database, readSignal) =>
dumpDconfDatabase(database, environment, readSignal),
});
@@ -271,6 +315,7 @@ const registerDesktopExportCommand = (
),
}),
options: z.object({
viewPolicy: viewPolicyOption,
name: z
.string()
.optional()
@@ -351,6 +396,7 @@ const registerDesktopExportCommand = (
? undefined
: absoluteCallerPath(options.screens),
...withheldRequestOf(options),
viewPolicy: options.viewPolicy,
},
readers,
signal,
@@ -376,7 +422,7 @@ const registerDesktopTaskCommand = (
"Folder written by capture-desktop-application",
),
}),
options: z.object(checkpointOptions),
options: z.object({ ...checkpointOptions, viewPolicy: viewPolicyOption }),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(
@@ -399,6 +445,7 @@ const registerDesktopTaskCommand = (
end: options.endCheckpoint,
withhold: withholdOf(options),
},
options.viewPolicy,
),
);
},
@@ -421,7 +468,16 @@ const registerDesktopStateCommand = (
"Folder written by capture-desktop-application",
),
}),
run: ({ args }) =>
options: z.object({
viewPolicy: viewPolicyOption,
statePath: z
.array(z.string().min(1))
.optional()
.describe(
"Exact original captured path to inspect (repeatable); omission examines every path",
),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(
logger,
@@ -438,6 +494,7 @@ const registerDesktopStateCommand = (
absoluteCallerPath(args.capture),
readers,
signal,
{ viewPolicy: options.viewPolicy, paths: options.statePath },
),
);
},
@@ -477,6 +534,9 @@ export const registerDesktopCaptureCommands = (
output: options.output,
statePaths: options.statePath ?? [],
excludes: options.exclude ?? [],
...(options.libraryPolicy === undefined
? {}
: { libraryPolicy: options.libraryPolicy }),
maxDurationSeconds: options.maxDurationSeconds,
maxEntries: options.maxEntries,
maxFileBytes: options.maxFileBytes,
+13
View File
@@ -365,11 +365,24 @@ const registerJavaScriptApplicationCommand = (
signal,
);
if (!sourcePaths.ok) return sourcePaths.error;
const precision =
options.semanticPrecision === undefined
? undefined
: await parseCliJsonInput(
options.semanticPrecision,
"analyze_javascript_application",
signal,
);
if (precision !== undefined && !precision.ok)
return precision.error;
return runCliJavaScriptApplicationAnalysis(
{
// An empty path names no file; leave it for input validation.
input_path:
args.path === "" ? args.path : absoluteCallerPath(args.path),
...(precision === undefined
? {}
: { semantic_precision: precision.value }),
format: options.artifactFormat,
integrity_policy: options.integrityPolicy,
...(options.maxSemanticNodes === undefined
+2 -4
View File
@@ -335,7 +335,7 @@ const conflictingRules = (): JsonValue =>
/** Inspect a HAR as inspect-web-network-capture does and serialize the Evidence as it prints it; a refusal is the command's output. */
const inspectBuildHar = async (
capturePath: string,
callerCapture: string,
callerInputPath: string,
captureTimeoutMs: number | undefined,
environment: Readonly<NodeJS.ProcessEnv>,
signal: AbortSignal,
@@ -358,9 +358,7 @@ const inspectBuildHar = async (
: { capture_timeout_ms: captureTimeoutMs }),
},
{ signal },
// Provenance keeps the caller's spelling. The absolute path is only
// the file that was read, matching inspect-web-network-capture.
{ capture: callerCapture },
{ capture: callerInputPath },
),
service,
"web-network-capture",
+9 -1
View File
@@ -9,7 +9,11 @@ import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { finishCliEvidenceCommand } from "./cliEvidenceCallerPath.js";
import { cliDecimalField } from "./options.js";
import { inspectEvmInterfaceInputSchema } from "../domain/evm/evmInterface.js";
import {
inspectEvmInterfaceInputSchema,
evmProducerLimitsSchema,
} from "../domain/evm/evmInterface.js";
import { producerLimitsOption } from "./producerLimitsOption.js";
/** Resolve operator paths and preserve explicit encoding through the shared workflow. */
export const registerEvmCommands = (
@@ -27,6 +31,7 @@ export const registerEvmCommands = (
.describe("Selected carrier representation"),
}),
options: z.object({
producerLimits: producerLimitsOption(evmProducerLimitsSchema),
timeoutMs: cliDecimalField(
inspectEvmInterfaceInputSchema.shape.timeout_ms,
),
@@ -41,6 +46,9 @@ export const registerEvmCommands = (
{
path: absoluteCallerPath(args.path),
encoding: args.encoding,
...(options.producerLimits === undefined
? {}
: { producer_limits: options.producerLimits }),
...(options.timeoutMs === undefined
? {}
: { timeout_ms: options.timeoutMs }),
+23
View File
@@ -1,3 +1,4 @@
import { safeParseJson } from "../domain/safeJson.js";
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import {
evidenceSchema,
@@ -11,6 +12,7 @@ import { CLI_COMMANDS } from "../cliCommandNames.js";
import { logCliCommand } from "../cliLogging.js";
import {
flutterInputSchemas,
dartPoolPolicySchema,
type FlutterOperation,
} from "../domain/flutter/flutterBuildAnalysis.js";
import type { Logger } from "pino";
@@ -58,11 +60,32 @@ export const registerFlutterCommands = (
}),
optionSchema: z.object({
abi: flutterInputSchemas.inspect_dart_aot.shape.abi,
poolPolicy: z
.string()
.transform((text, context) => {
const parsed = safeParseJson(text);
if (!parsed.ok) {
context.addIssue({
code: "custom",
message: "pool-policy must be a JSON object",
});
return z.NEVER;
}
return parsed.value;
})
.pipe(dartPoolPolicySchema)
.optional()
.describe(
"JSON printable-run selection: contains, uri_limit (500), token_limit (2000), run_bytes (1024), token_bytes (128). Filters apply before bounded collection; URI/token counts cover matches. Positive safe integers; next call, no restart. Does not raise APK/library/ELF/reply limits.",
),
}),
operation: "inspect_dart_aot",
toInput: (arguments_, options) => ({
path: arguments_.path,
...(options.abi === undefined ? {} : { abi: options.abi }),
...(options.poolPolicy === undefined
? {}
: { pool_policy: options.poolPolicy }),
}),
},
];
+51
View File
@@ -1,3 +1,4 @@
import { fridaCapturePolicyInputSchema } from "../domain/fridaCapturePolicy.js";
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
@@ -176,6 +177,23 @@ export const registerFridaCommands = (
.array(z.string())
.optional()
.describe("Arguments passed to a spawned target"),
captureSessionBytes: cliDecimalField(
fridaCapturePolicyInputSchema.shape.session_bytes,
).describe(
"Session retention bytes including encoded payloads; select before starting the experiment",
),
captureMessageTextBytes: cliDecimalField(
fridaCapturePolicyInputSchema.shape.message_text_bytes,
).describe("Retained bytes per Frida error text field"),
captureMessageTypeBytes: cliDecimalField(
fridaCapturePolicyInputSchema.shape.message_type_bytes,
).describe("Retained bytes per Frida message type"),
captureJsonNodes: cliDecimalField(
fridaCapturePolicyInputSchema.shape.json_nodes,
).describe("Retained JSON nodes per Frida payload"),
captureJsonDepth: cliDecimalField(
fridaCapturePolicyInputSchema.shape.json_depth,
).describe("Retained JSON nesting depth per Frida payload"),
durationMs: cliDecimalField(
z
.number()
@@ -274,6 +292,39 @@ export const registerFridaCommands = (
const result = await service.instrument(
{
...targetInput,
...([
options.captureSessionBytes,
options.captureMessageTextBytes,
options.captureMessageTypeBytes,
options.captureJsonNodes,
options.captureJsonDepth,
].every((value) => value === undefined)
? {}
: {
captureLimits: {
...(options.captureSessionBytes === undefined
? {}
: { session_bytes: options.captureSessionBytes }),
...(options.captureMessageTextBytes === undefined
? {}
: {
message_text_bytes:
options.captureMessageTextBytes,
}),
...(options.captureMessageTypeBytes === undefined
? {}
: {
message_type_bytes:
options.captureMessageTypeBytes,
}),
...(options.captureJsonNodes === undefined
? {}
: { json_nodes: options.captureJsonNodes }),
...(options.captureJsonDepth === undefined
? {}
: { json_depth: options.captureJsonDepth }),
},
}),
source:
options.scriptFile === undefined
? { sourceKind: "inline", source: options.script ?? "" }
+52 -8
View File
@@ -12,11 +12,7 @@ import {
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { machoSliceArchitectureSchema } from "../domain/apple/machoTargetSelection.js";
import {
DEFAULT_MAX_GO_STRING_LITERALS,
MAX_GO_STRING_LITERALS_LIMIT,
goMaxStringLiteralsSchema,
} from "../domain/go/goBinary.js";
import { goResourceBudgetFields } from "../domain/go/goBinary.js";
import { cliDecimalField } from "./options.js";
/** Resolve caller paths and inspect static build metadata through the shared workflow. */
@@ -42,10 +38,26 @@ export const registerGoCommands = (cli: CliInstance, logger: Logger): void => {
.describe(
"Also recover bounded inferred Go UTF-8 string literals from static (ptr, len) headers",
),
maxInputBytes: cliDecimalField(goResourceBudgetFields.max_input_bytes),
maxReportBytes: cliDecimalField(goResourceBudgetFields.max_report_bytes),
maxBuildInfoBytes: cliDecimalField(
goResourceBudgetFields.max_build_info_bytes,
),
maxHeaderSearchBytes: cliDecimalField(
goResourceBudgetFields.max_header_search_bytes,
),
maxStringLiterals: cliDecimalField(
goMaxStringLiteralsSchema.optional(),
).describe(
`Maximum distinct recovered string literals (1-${String(MAX_GO_STRING_LITERALS_LIMIT)}; default ${String(DEFAULT_MAX_GO_STRING_LITERALS)}; requires --include-string-literals)`,
goResourceBudgetFields.max_string_literals,
),
maxStringLiteralBytes: cliDecimalField(
goResourceBudgetFields.max_string_literal_bytes,
),
maxStringBytes: cliDecimalField(goResourceBudgetFields.max_string_bytes),
maxStringHeaderScanBytes: cliDecimalField(
goResourceBudgetFields.max_string_header_scan_bytes,
),
maxStringCandidates: cliDecimalField(
goResourceBudgetFields.max_string_candidates,
),
}),
run: ({ args, options }) =>
@@ -70,9 +82,41 @@ export const registerGoCommands = (cli: CliInstance, logger: Logger): void => {
include_string_literals:
options.includeStringLiterals,
}),
...(options.maxInputBytes === undefined
? {}
: { max_input_bytes: options.maxInputBytes }),
...(options.maxReportBytes === undefined
? {}
: { max_report_bytes: options.maxReportBytes }),
...(options.maxBuildInfoBytes === undefined
? {}
: { max_build_info_bytes: options.maxBuildInfoBytes }),
...(options.maxHeaderSearchBytes === undefined
? {}
: {
max_header_search_bytes: options.maxHeaderSearchBytes,
}),
...(options.maxStringLiterals === undefined
? {}
: { max_string_literals: options.maxStringLiterals }),
...(options.maxStringLiteralBytes === undefined
? {}
: {
max_string_literal_bytes:
options.maxStringLiteralBytes,
}),
...(options.maxStringBytes === undefined
? {}
: { max_string_bytes: options.maxStringBytes }),
...(options.maxStringHeaderScanBytes === undefined
? {}
: {
max_string_header_scan_bytes:
options.maxStringHeaderScanBytes,
}),
...(options.maxStringCandidates === undefined
? {}
: { max_string_candidates: options.maxStringCandidates }),
},
{ signal },
),
+49 -5
View File
@@ -22,6 +22,32 @@ import { managedMembersInputSchema } from "../contracts/managed/managedToolContr
import { localPathStringSchema } from "../domain/localPath.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { managedInspectionBudgetShape } from "../domain/managed/managedInspectionLimits.js";
import { cliDecimalField } from "./options.js";
const managedBudgetOptions = {
maxSnapshotBytes: cliDecimalField(
managedInspectionBudgetShape.max_snapshot_bytes,
),
maxMetadataRepresentationBytes: cliDecimalField(
managedInspectionBudgetShape.max_metadata_representation_bytes,
),
};
const managedBudgetParameters = (options: {
readonly maxSnapshotBytes?: number | undefined;
readonly maxMetadataRepresentationBytes?: number | undefined;
}) => ({
...(options.maxSnapshotBytes === undefined
? {}
: { max_snapshot_bytes: options.maxSnapshotBytes }),
...(options.maxMetadataRepresentationBytes === undefined
? {}
: {
max_metadata_representation_bytes:
options.maxMetadataRepresentationBytes,
}),
});
export const registerManagedCommands = (
cli: CliInstance,
@@ -49,12 +75,13 @@ const registerInspectManagedArtifact = (
"Managed PE executable or assembly path",
),
}),
run: ({ args }) =>
options: z.object(managedBudgetOptions),
run: ({ args, options }) =>
logCliCommand(logger, "inspect-managed-artifact", () =>
runProviderAnalysis(
args.path,
"inspect_managed_artifact",
{},
managedBudgetParameters(options),
logger,
).then(projectCliAnalysisResult),
),
@@ -75,6 +102,7 @@ const registerInspectManagedMembers = (
),
}),
options: z.object({
...managedBudgetOptions,
type: managedMembersInputSchema.shape.type_names.describe(
"Inspect only members declared by this exact type full name, and the string literals they load, such as System.Threading.Monitor; a nested type is named by its bare name, which selects every nested type of that name (repeatable)",
),
@@ -93,7 +121,12 @@ const registerInspectManagedMembers = (
runProviderAnalysis(
args.path,
"inspect_managed_members",
options.type === undefined ? {} : { type_names: options.type },
{
...managedBudgetParameters(options),
...(options.type === undefined
? {}
: { type_names: options.type }),
},
logger,
signal,
{
@@ -123,12 +156,13 @@ const registerInspectManagedNativeBoundaries = (
"Managed PE executable or assembly path",
),
}),
run: ({ args }) =>
options: z.object(managedBudgetOptions),
run: ({ args, options }) =>
logCliCommand(logger, "inspect-managed-native-boundaries", () =>
runProviderAnalysis(
args.path,
"inspect_managed_native_boundaries",
{},
managedBudgetParameters(options),
logger,
).then(projectCliAnalysisResult),
),
@@ -153,6 +187,7 @@ const registerCompareManagedMembers = (
"Compare managed PE/CLI member inventories by exact declared type, name, and signature; names alone never form a match",
args: compareManagedMemberArgs,
options: z.object({
...managedBudgetOptions,
type: managedMembersInputSchema.shape.type_names.describe(
"Compare only members declared by this exact type full name on both sides, as selected by inspect-managed-members --type; use it when a whole-assembly comparison exceeds capacity (repeatable)",
),
@@ -167,6 +202,15 @@ const registerCompareManagedMembers = (
{
leftPath: args.leftPath,
rightPath: args.rightPath,
...(options.maxSnapshotBytes === undefined
? {}
: { maxSnapshotBytes: options.maxSnapshotBytes }),
...(options.maxMetadataRepresentationBytes === undefined
? {}
: {
maxRepresentationBytes:
options.maxMetadataRepresentationBytes,
}),
...(options.type === undefined
? {}
: { typeNames: options.type }),
+1 -1
View File
@@ -29,7 +29,7 @@ export const registerNativeCallCommands = (
inputJson: z
.string()
.describe(
"Inline JSON or a JSON file path: breakpoints, arguments, environment, duration_ms, max_events, argument_registers, backtrace_frames",
"Inline JSON or a JSON file path: breakpoints, arguments, environment, duration_ms, max_events, argument_registers, backtrace_frames, capture_limits",
),
}),
run: ({ args }) =>
+26
View File
@@ -0,0 +1,26 @@
import { z } from "incur";
import { safeParseJson } from "../domain/safeJson.js";
/** Incur passes object flags as text; validate the selected producer's exact policy. */
export const producerLimitsOption = <Schema extends z.ZodType>(
schema: Schema,
flagName = "producer-limits",
) =>
z
.string()
.transform((text, context) => {
const parsed = safeParseJson(text);
if (!parsed.ok) {
context.addIssue({
code: "custom",
message: `${flagName} must be a JSON object`,
});
return z.NEVER;
}
return parsed.value;
})
.pipe(schema)
.optional()
.describe(
"JSON producer budget overrides. Keys and units follow this operation's producer_limits contract; changes apply on this call without restart and preserve tighter inherited OS limits.",
);
+13
View File
@@ -17,6 +17,7 @@ import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
OBJDUMP_ARTIFACT_OPERATIONS,
reverseCommandTimeoutSchema,
reverseCommandOutputBytesSchema,
rizinInputSchema,
} from "../domain/reverseEngineering.js";
import { logCliCommand } from "../cliLogging.js";
@@ -173,6 +174,7 @@ export const registerReverseEngineeringCommands = (
"Follow local DWARF debug links. External debug-file identity is not bound to Evidence.",
),
commandTimeoutMs: cliDecimalField(reverseCommandTimeoutSchema),
maxOutputBytes: cliDecimalField(reverseCommandOutputBytesSchema),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
@@ -197,6 +199,9 @@ export const registerReverseEngineeringCommands = (
...(options.symbol === undefined
? {}
: { symbol: options.symbol }),
...(options.maxOutputBytes === undefined
? {}
: { max_output_bytes: options.maxOutputBytes }),
...(options.commandTimeoutMs === undefined
? {}
: { command_timeout_ms: options.commandTimeoutMs }),
@@ -220,6 +225,7 @@ export const registerReverseEngineeringCommands = (
options: z.object({
...analysisEvidenceDeliveryOptions,
commandTimeoutMs: cliDecimalField(reverseCommandTimeoutSchema),
maxOutputBytes: cliDecimalField(reverseCommandOutputBytesSchema),
}),
run: ({ options }) =>
withCommandCancellation((signal) =>
@@ -244,6 +250,9 @@ export const registerReverseEngineeringCommands = (
service.inspectWithObjdump(
{
operation: "architectures",
...(options.maxOutputBytes === undefined
? {}
: { max_output_bytes: options.maxOutputBytes }),
...(options.commandTimeoutMs === undefined
? {}
: { command_timeout_ms: options.commandTimeoutMs }),
@@ -277,6 +286,7 @@ export const registerReverseEngineeringCommands = (
asmCpu: rizinInputSchema.shape.asm_cpu,
baseAddress: rizinInputSchema.shape.base_address,
commandTimeoutMs: cliDecimalField(reverseCommandTimeoutSchema),
maxOutputBytes: cliDecimalField(reverseCommandOutputBytesSchema),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
@@ -300,6 +310,9 @@ export const registerReverseEngineeringCommands = (
...(options.baseAddress === undefined
? {}
: { base_address: options.baseAddress }),
...(options.maxOutputBytes === undefined
? {}
: { max_output_bytes: options.maxOutputBytes }),
...(options.commandTimeoutMs === undefined
? {}
: { command_timeout_ms: options.commandTimeoutMs }),
+20
View File
@@ -1,4 +1,8 @@
import { z } from "incur";
import { producerLimitsOption } from "./producerLimitsOption.js";
import { rustProducerLimitsSchema } from "../domain/rust/rustResourceLimits.js";
import { inspectRustBinaryInputSchema } from "../domain/rust/rustBinary.js";
import { cliDecimalField } from "./options.js";
import { createRustBinaryService } from "../composition/rust.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
import { logCliCommand } from "../cliLogging.js";
@@ -33,6 +37,10 @@ export const registerRustCommands = (
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
producerLimits: producerLimitsOption(rustProducerLimitsSchema),
maxRustcHashes: cliDecimalField(
inspectRustBinaryInputSchema.shape.max_rustc_hashes,
),
architecture: machoSliceArchitectureSchema
.optional()
.describe(
@@ -76,6 +84,12 @@ export const registerRustCommands = (
const view = decoded.value;
const input = inspectRustBinaryRequestSchema.safeParse({
path: absoluteCallerPath(args.path),
...(options.producerLimits === undefined
? {}
: { producer_limits: options.producerLimits }),
...(options.maxRustcHashes === undefined
? {}
: { max_rustc_hashes: options.maxRustcHashes }),
...(options.architecture === undefined
? {}
: { architecture: options.architecture }),
@@ -97,6 +111,12 @@ export const registerRustCommands = (
service.inspect(
{
path: input.data.path,
...(input.data.producer_limits === undefined
? {}
: { producer_limits: input.data.producer_limits }),
...(input.data.max_rustc_hashes === undefined
? {}
: { max_rustc_hashes: input.data.max_rustc_hashes }),
...(input.data.architecture === undefined
? {}
: { architecture: input.data.architecture }),
+21 -5
View File
@@ -18,6 +18,7 @@ import { finishCliEvidenceCommand } from "./cliEvidenceCallerPath.js";
import { cliDecimalField, cliDecimalShape } from "./options.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { producerLimitsOption } from "./producerLimitsOption.js";
export const registerSignalCommands = (
cli: CliInstance,
logger: Logger,
@@ -27,11 +28,16 @@ export const registerSignalCommands = (
"decode" | "inspectDecoders" | "close"
> = createSignalRecordingService(environment),
): void => {
const { producer_limits: discoveryLimits, ...discoveryShape } =
inspectSignalDecodersInputSchema.shape;
const { producer_limits: selectedLimits, ...selectedShape } =
signalRecordingOptionsSchema.shape;
cli.command(CLI_COMMANDS.inspectSignalDecoders, {
description: "Search installed sigrok decoders or selected decoder help",
options: z
.object({
...cliDecimalShape(inspectSignalDecodersInputSchema.shape),
...cliDecimalShape(discoveryShape),
producerLimits: producerLimitsOption(discoveryLimits.unwrap()),
limit: cliDecimalField(
z
.number()
@@ -56,13 +62,19 @@ export const registerSignalCommands = (
message: "all and limit are mutually exclusive",
});
}),
run: ({ options: { all, ...options } }) =>
run: ({ options: { all, producerLimits, ...options } }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.inspectSignalDecoders, async () =>
finishAnalysisCommand(
() =>
service.inspectDecoders(
{ ...options, ...(all ? { limit: null } : {}) },
{
...options,
...(producerLimits === undefined
? {}
: { producer_limits: producerLimits }),
...(all ? { limit: null } : {}),
},
{ signal },
),
service,
@@ -81,7 +93,8 @@ export const registerSignalCommands = (
}),
options: z
.object({
...cliDecimalShape(signalRecordingOptionsSchema.shape),
...cliDecimalShape(selectedShape),
producerLimits: producerLimitsOption(selectedLimits.unwrap()),
line_limit: cliDecimalField(
z
.number()
@@ -109,7 +122,7 @@ export const registerSignalCommands = (
withinSignalRecordingSelectorBudget,
signalRecordingSelectorBudgetIssue,
),
run: ({ args, options: { all_lines, ...options } }) =>
run: ({ args, options: { all_lines, producerLimits, ...options } }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.decodeSignalRecording, async () => {
return finishCliEvidenceCommand(
@@ -118,6 +131,9 @@ export const registerSignalCommands = (
service.decode(
{
...options,
...(producerLimits === undefined
? {}
: { producer_limits: producerLimits }),
...(all_lines ? { line_limit: null } : {}),
path: absoluteCallerPath(args.path),
},
+64
View File
@@ -0,0 +1,64 @@
import { cliDecimalField } from "./options.js";
import {
sourceMapLimitsSchema,
type SourceMapLimitSelection,
} from "../domain/sourceMapLimits.js";
const shape = sourceMapLimitsSchema.shape;
const sharedOptions = {
sourceMapOutputBytes: cliDecimalField(shape.output_bytes),
sourceMapDecodedRows: cliDecimalField(shape.decoded_rows),
sourceMapDecodedSegments: cliDecimalField(shape.decoded_segments),
sourceMapDecodedRecords: cliDecimalField(shape.decoded_records),
sourceMapSectionDepth: cliDecimalField(shape.section_depth),
};
export const localSourceMapLimitCliOptions = {
...sharedOptions,
sourceMapBytes: cliDecimalField(shape.map_bytes),
sourceMapCodecHeapMib: cliDecimalField(shape.codec_heap_mib),
};
export const fetchedSourceMapLimitCliOptions = {
...sharedOptions,
sourceMapFetchBytes: cliDecimalField(shape.fetch_bytes),
};
/** Keep omitted caller fields omitted in Evidence parameters. */
export const sourceMapLimitsFromCli = (options: {
sourceMapBytes?: number | undefined;
sourceMapOutputBytes?: number | undefined;
sourceMapDecodedRows?: number | undefined;
sourceMapDecodedSegments?: number | undefined;
sourceMapDecodedRecords?: number | undefined;
sourceMapSectionDepth?: number | undefined;
sourceMapCodecHeapMib?: number | undefined;
sourceMapFetchBytes?: number | undefined;
}): { source_map_limits?: SourceMapLimitSelection } => {
const selected: SourceMapLimitSelection = {
...(options.sourceMapBytes === undefined
? {}
: { map_bytes: options.sourceMapBytes }),
...(options.sourceMapOutputBytes === undefined
? {}
: { output_bytes: options.sourceMapOutputBytes }),
...(options.sourceMapDecodedRows === undefined
? {}
: { decoded_rows: options.sourceMapDecodedRows }),
...(options.sourceMapDecodedSegments === undefined
? {}
: { decoded_segments: options.sourceMapDecodedSegments }),
...(options.sourceMapDecodedRecords === undefined
? {}
: { decoded_records: options.sourceMapDecodedRecords }),
...(options.sourceMapSectionDepth === undefined
? {}
: { section_depth: options.sourceMapSectionDepth }),
...(options.sourceMapCodecHeapMib === undefined
? {}
: { codec_heap_mib: options.sourceMapCodecHeapMib }),
...(options.sourceMapFetchBytes === undefined
? {}
: { fetch_bytes: options.sourceMapFetchBytes }),
};
return Object.keys(selected).length === 0
? {}
: { source_map_limits: selected };
};
+30
View File
@@ -75,6 +75,21 @@ export const registerWasmCommands = (
commandTimeoutMs: cliDecimalField(
inspectWasmArtifactInputSchema.shape.command_timeout_ms,
),
maxInterfaceBytes: cliDecimalField(
inspectWasmArtifactInputSchema.shape.max_interface_bytes,
),
maxArtifactPaths: cliDecimalField(
inspectWasmArtifactInputSchema.shape.max_artifact_paths,
),
maxInputBytes: cliDecimalField(
inspectWasmArtifactInputSchema.shape.max_input_bytes,
),
maxToolBytes: cliDecimalField(
inspectWasmArtifactInputSchema.shape.max_tool_bytes,
),
maxOutputBytes: cliDecimalField(
inspectWasmArtifactInputSchema.shape.max_output_bytes,
),
glue_paths: wasmArtifactPathsSchema(localPathStringSchema)
.optional()
.describe("Selected local JavaScript glue paths"),
@@ -94,6 +109,21 @@ export const registerWasmCommands = (
...(options.commandTimeoutMs === undefined
? {}
: { command_timeout_ms: options.commandTimeoutMs }),
...(options.maxInterfaceBytes === undefined
? {}
: { max_interface_bytes: options.maxInterfaceBytes }),
...(options.maxArtifactPaths === undefined
? {}
: { max_artifact_paths: options.maxArtifactPaths }),
...(options.maxInputBytes === undefined
? {}
: { max_input_bytes: options.maxInputBytes }),
...(options.maxToolBytes === undefined
? {}
: { max_tool_bytes: options.maxToolBytes }),
...(options.maxOutputBytes === undefined
? {}
: { max_output_bytes: options.maxOutputBytes }),
glue_paths: options.glue_paths?.map((path) =>
absoluteCallerPath(path),
),
+18
View File
@@ -55,6 +55,15 @@ export const registerWebNetworkCaptureCommands = (
.optional()
.describe("Zero-based producer ordinals; repeat to select several"),
...explicitSensitiveValueOptions,
maxInputBytes: cliDecimalField(
inspectWebNetworkCaptureInputSchema.shape.max_input_bytes,
),
maxReplyBytes: cliDecimalField(
inspectWebNetworkCaptureInputSchema.shape.max_reply_bytes,
),
maxNestingDepth: cliDecimalField(
inspectWebNetworkCaptureInputSchema.shape.max_nesting_depth,
),
// Decimal tokens only: numeric coercion would read "0x1" as 1 ms.
captureTimeoutMs: cliDecimalField(
inspectWebNetworkCaptureInputSchema.shape.capture_timeout_ms,
@@ -74,6 +83,15 @@ export const registerWebNetworkCaptureCommands = (
capture_path: absoluteCallerPath(args.capturePath),
format: args.format,
sensitive_values: options.sensitiveValue,
...(options.maxInputBytes === undefined
? {}
: { max_input_bytes: options.maxInputBytes }),
...(options.maxReplyBytes === undefined
? {}
: { max_reply_bytes: options.maxReplyBytes }),
...(options.maxNestingDepth === undefined
? {}
: { max_nesting_depth: options.maxNestingDepth }),
...([
options.protobufFraming,
options.protobufBodyPointer,
+38 -9
View File
@@ -1,4 +1,14 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import {
localSourceMapLimitCliOptions,
sourceMapLimitsFromCli,
} from "./sourceMapLimits.js";
import {
analysisEvidenceDeliveryOptions,
parseCliAnalysisEvidenceDelivery,
deliverCliAnalysisEvidence,
} from "./analysisEvidenceDelivery.js";
import { webSourceLocationDeliverySchema } from "../domain/analysisView/webSourceLocationView.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { z } from "incur";
import { createWebSourceLocationService } from "../composition/webSourceLocations.js";
import { webSourceLocationInputSchema } from "../domain/webSourceLocation.js";
@@ -8,7 +18,6 @@ import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { cliDecimalField, cliIntegerArgument } from "./options.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
const sourceMapSelection = webSourceLocationInputSchema.shape.source_map;
@@ -35,15 +44,29 @@ export const registerWebSourceLocationCommands = (
),
}),
options: z.object({
...localSourceMapLimitCliOptions,
...analysisEvidenceDeliveryOptions,
view: z
.string()
.optional()
.describe(
"JSON source-location matches/source-text view; requires --detail summary and --evidence_output",
),
decodeTimeoutMs: cliDecimalField(
webSourceLocationInputSchema.shape.decode_timeout_ms,
),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.traceWebSourceLocation, () =>
finishAnalysisCommand(
() =>
logCliCommand(logger, CLI_COMMANDS.traceWebSourceLocation, async () => {
const delivery = parseCliAnalysisEvidenceDelivery(
"trace_web_source_location",
options,
webSourceLocationDeliverySchema,
);
if (!delivery.ok) return projectAnalysisError(delivery.error);
return deliverCliAnalysisEvidence({
run: () =>
service.trace(
{
manifest_path: args.manifestPath,
@@ -53,6 +76,7 @@ export const registerWebSourceLocationCommands = (
url: args.sourceMapUrl,
},
generated_position: { line: args.line, column: args.column },
...sourceMapLimitsFromCli(options),
...(options.decodeTimeoutMs === undefined
? {}
: { decode_timeout_ms: options.decodeTimeoutMs }),
@@ -60,11 +84,16 @@ export const registerWebSourceLocationCommands = (
{ signal },
),
service,
"web-source-location",
"trace_web_source_location",
providerId: "web-source-location",
operation: "trace_web_source_location",
signal,
).then(projectCliAnalysisResult),
),
detail: delivery.value.detail,
evidenceOutput: delivery.value.evidenceOutput,
...(delivery.value.view === undefined
? {}
: { view: delivery.value.view }),
});
}),
),
});
};