mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor: centralize input validation and schema interpretation
Migrate twelve application service files to the existing provider input owner, preserving schema causes, cancellation precedence, caller selection, and retained Evidence. Keep flows with distinct error policies separate. Replace the DeepSeek verifier's recursive object scan with schema-position traversal using the canonical keyword and local-reference owners. Literal pattern fields no longer cause false regex failures; patternProperties keys are checked alongside actual pattern constraints in native regex modes. Validation: 271 focused workflow/service tests and six verifier regressions passed; static checks, discovery, and actual Node source-loader smoke passed. The baseline scanner reproduced the new regressions. Installed DeepSeek client execution was not run.
This commit is contained in:
1 parent
8a65787507
commit
6c35d74a7d
15 files changed
+324
-240
No files matched your search
@@ -0,0 +1,48 @@
|
|||||||
|
import { JsonSchemaReferences } from "../../src/contracts/jsonSchemaReferences.js";
|
||||||
|
import {
|
||||||
|
schemaArrays,
|
||||||
|
schemaChildren,
|
||||||
|
schemaMaps,
|
||||||
|
} from "../../src/contracts/jsonSchemaKeywords.js";
|
||||||
|
|
||||||
|
const isSchemaObject = (
|
||||||
|
value: unknown,
|
||||||
|
): value is Readonly<Record<string, unknown>> =>
|
||||||
|
value !== null && typeof value === "object" && !Array.isArray(value);
|
||||||
|
|
||||||
|
/** Check forwarded regex constraints in native plain/Unicode/Unicode-sets modes. */
|
||||||
|
export const assertClientSchemaPatterns = (
|
||||||
|
schema: unknown,
|
||||||
|
): readonly string[] => {
|
||||||
|
if (!isSchemaObject(schema)) return [];
|
||||||
|
const patterns: string[] = [];
|
||||||
|
const seen = new Set<object>();
|
||||||
|
const checkPattern = (pattern: string): void => {
|
||||||
|
for (const flags of ["", "u", "v"]) new RegExp(pattern, flags);
|
||||||
|
patterns.push(pattern);
|
||||||
|
};
|
||||||
|
const visit = (value: unknown): void => {
|
||||||
|
if (!isSchemaObject(value) || seen.has(value)) return;
|
||||||
|
seen.add(value);
|
||||||
|
for (const [key, child] of Object.entries(value)) {
|
||||||
|
if (key === "pattern" && typeof child === "string") checkPattern(child);
|
||||||
|
else if (schemaMaps.has(key) && isSchemaObject(child)) {
|
||||||
|
for (const [name, nested] of Object.entries(child)) {
|
||||||
|
if (key === "patternProperties") checkPattern(name);
|
||||||
|
visit(nested);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(schemaArrays.has(key) || schemaChildren.has(key)) &&
|
||||||
|
Array.isArray(child)
|
||||||
|
) {
|
||||||
|
for (const nested of child) visit(nested);
|
||||||
|
} else if (schemaChildren.has(key)) visit(child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
visit(schema);
|
||||||
|
// Literal instance values stay opaque unless the reference owner resolves a
|
||||||
|
// local target into them. External/dynamic/ambiguous targets remain unknown.
|
||||||
|
for (const { target } of new JsonSchemaReferences(schema).references())
|
||||||
|
if (target.kind === "target") visit(target.value);
|
||||||
|
return patterns;
|
||||||
|
};
|
||||||
@@ -20,11 +20,17 @@ import {
|
|||||||
isClientJavaScriptEvidence,
|
isClientJavaScriptEvidence,
|
||||||
} from "./verify/clients/result-discovery.mjs";
|
} from "./verify/clients/result-discovery.mjs";
|
||||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||||
|
import { importVerifierSource } from "./lib/verifier-source-loader.mjs";
|
||||||
import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs";
|
import { createOpenAiModelFixture } from "./verify/clients/model-fixture.mjs";
|
||||||
|
|
||||||
const mode = process.argv[2] ?? "call";
|
const mode = process.argv[2] ?? "call";
|
||||||
assert(["chat", "call"].includes(mode), "Select chat or call.");
|
assert(["chat", "call"].includes(mode), "Select chat or call.");
|
||||||
assert(process.platform !== "win32", "Native Windows coverage is unverified.");
|
assert(process.platform !== "win32", "Native Windows coverage is unverified.");
|
||||||
|
const { assertClientSchemaPatterns } = await importVerifierSource(
|
||||||
|
new URL("./lib/client-schema-patterns.mts", import.meta.url).href,
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
|
||||||
const repo = fileURLToPath(new URL("..", import.meta.url));
|
const repo = fileURLToPath(new URL("..", import.meta.url));
|
||||||
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
|
const runtimeRoot = process.env.REA_VERIFY_RUNTIME_ROOT ?? repo;
|
||||||
const command = process.env.REA_VERIFY_DEEPSEEK_COMMAND ?? "dsh";
|
const command = process.env.REA_VERIFY_DEEPSEEK_COMMAND ?? "dsh";
|
||||||
@@ -123,17 +129,6 @@ await withVerifierCleanup(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
||||||
const validatePatterns = (value) => {
|
|
||||||
if (!value || typeof value !== "object") return;
|
|
||||||
for (const [key, child] of Object.entries(value)) {
|
|
||||||
if (key === "pattern" && typeof child === "string") {
|
|
||||||
// Check the representation the native client actually forwards to its model adapter.
|
|
||||||
for (const flags of ["", "u", "v"]) new RegExp(child, flags);
|
|
||||||
patterns.add(child);
|
|
||||||
patternCount++;
|
|
||||||
} else validatePatterns(child);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fixture = await createOpenAiModelFixture({
|
fixture = await createOpenAiModelFixture({
|
||||||
directory: lab,
|
directory: lab,
|
||||||
prefix: `deepseek-${mode}`,
|
prefix: `deepseek-${mode}`,
|
||||||
@@ -160,7 +155,11 @@ await withVerifierCleanup(async () => {
|
|||||||
ajv.validateSchema(tool.parameters),
|
ajv.validateSchema(tool.parameters),
|
||||||
`${tool.name}: ${ajv.errorsText(ajv.errors)}`,
|
`${tool.name}: ${ajv.errorsText(ajv.errors)}`,
|
||||||
);
|
);
|
||||||
validatePatterns(tool.parameters);
|
const forwardedPatterns = assertClientSchemaPatterns(
|
||||||
|
tool.parameters,
|
||||||
|
);
|
||||||
|
for (const pattern of forwardedPatterns) patterns.add(pattern);
|
||||||
|
patternCount += forwardedPatterns.length;
|
||||||
}
|
}
|
||||||
assert(
|
assert(
|
||||||
patternCount > 0,
|
patternCount > 0,
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import {
|
|||||||
type AnalysisViewRetention,
|
type AnalysisViewRetention,
|
||||||
} from "../analysisView/AnalysisViewService.js";
|
} from "../analysisView/AnalysisViewService.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
|
||||||
import { androidPackageInspectionInputSchema } from "../../domain/android/androidAnalysis.js";
|
import { androidPackageInspectionInputSchema } from "../../domain/android/androidAnalysis.js";
|
||||||
import type { Evidence } from "../../domain/evidence.js";
|
import type { Evidence } from "../../domain/evidence.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
import type { Result } from "../../domain/result.js";
|
||||||
import { err, type Result } from "../../domain/result.js";
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
/** Preserve the complete provider request; select retained delivery explicitly. */
|
/** Preserve the complete provider request; select retained delivery explicitly. */
|
||||||
export const inspectAndroidPackage = async (
|
export const inspectAndroidPackage = async (
|
||||||
@@ -20,26 +19,22 @@ export const inspectAndroidPackage = async (
|
|||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> => {
|
): Promise<Result<Evidence, AnalysisError>> => {
|
||||||
const operation = "inspect_android_package";
|
const operation = "inspect_android_package";
|
||||||
if (options?.signal?.aborted === true)
|
const parsed = parseProviderOperationInput(
|
||||||
return err(
|
operation,
|
||||||
new AnalysisCancelledError(operation, { cause: options.signal.reason }),
|
rawInput,
|
||||||
);
|
androidPackageInspectionInputSchema,
|
||||||
const parsed = androidPackageInspectionInputSchema.safeParse(rawInput);
|
options?.signal,
|
||||||
if (!parsed.success)
|
);
|
||||||
return err(
|
if (!parsed.ok) return parsed;
|
||||||
analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput, {
|
if (parsed.value.detail === "summary") {
|
||||||
cause: parsed.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (parsed.data.detail === "summary") {
|
|
||||||
const ready = requireAnalysisViewRetention(operation, retention);
|
const ready = requireAnalysisViewRetention(operation, retention);
|
||||||
if (!ready.ok) return ready;
|
if (!ready.ok) return ready;
|
||||||
}
|
}
|
||||||
const executed = await service.execute(
|
const executed = await service.execute(
|
||||||
operation,
|
operation,
|
||||||
{ path: parsed.data.path },
|
{ path: parsed.value.path },
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
if (!executed.ok || parsed.data.detail !== "summary") return executed;
|
if (!executed.ok || parsed.value.detail !== "summary") return executed;
|
||||||
return summarizeRetainedAnalysis(executed.value, retention);
|
return summarizeRetainedAnalysis(executed.value, retention);
|
||||||
};
|
};
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||||
import type { DesktopApplicationPort } from "./DesktopApplicationPort.js";
|
import type { DesktopApplicationPort } from "./DesktopApplicationPort.js";
|
||||||
import { ARTIFACT_GRAPH_PROVIDER } from "../../domain/investigationProviders.js";
|
import { ARTIFACT_GRAPH_PROVIDER } from "../../domain/investigationProviders.js";
|
||||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
|
||||||
import {
|
import {
|
||||||
AnalysisInputError,
|
completeProviderEvidence,
|
||||||
AnalysisOutputError,
|
parseProviderOperationInput,
|
||||||
AnalysisCancelledError,
|
} from "../ProviderOperationExecution.js";
|
||||||
} from "../../domain/analysisErrorCore.js";
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import type { Evidence } from "../../domain/evidence.js";
|
import type { Evidence } from "../../domain/evidence.js";
|
||||||
import { createDesktopApplicationEvidence } from "../../domain/desktopApplicationEvidence.js";
|
import { createDesktopApplicationEvidence } from "../../domain/desktopApplicationEvidence.js";
|
||||||
import {
|
import {
|
||||||
@@ -29,21 +27,17 @@ export class DesktopApplicationService {
|
|||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
const operation = "inspect_desktop_application";
|
const operation = "inspect_desktop_application";
|
||||||
if (options?.signal?.aborted)
|
const parsed = parseProviderOperationInput(
|
||||||
return err(
|
operation,
|
||||||
new AnalysisCancelledError(operation, { cause: options.signal.reason }),
|
rawInput,
|
||||||
);
|
inspectDesktopApplicationInputSchema,
|
||||||
const parsed = inspectDesktopApplicationInputSchema.safeParse(rawInput);
|
options?.signal,
|
||||||
if (!parsed.success)
|
);
|
||||||
return err(
|
if (!parsed.ok) return parsed;
|
||||||
analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput, {
|
const result = await this.reader.inspect(parsed.value, options);
|
||||||
cause: parsed.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const result = await this.reader.inspect(parsed.data, options);
|
|
||||||
if (!result.ok) return result;
|
if (!result.ok) return result;
|
||||||
const report = desktopApplicationSchema.safeParse(result.value);
|
const report = desktopApplicationSchema.safeParse(result.value);
|
||||||
if (!report.success || report.data.root.path !== parsed.data.path)
|
if (!report.success || report.data.root.path !== parsed.value.path)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisOutputError(
|
new AnalysisOutputError(
|
||||||
operation,
|
operation,
|
||||||
@@ -52,7 +46,7 @@ export class DesktopApplicationService {
|
|||||||
);
|
);
|
||||||
return completeProviderEvidence(
|
return completeProviderEvidence(
|
||||||
createDesktopApplicationEvidence(
|
createDesktopApplicationEvidence(
|
||||||
parsed.data,
|
parsed.value,
|
||||||
report.data,
|
report.data,
|
||||||
ARTIFACT_GRAPH_PROVIDER,
|
ARTIFACT_GRAPH_PROVIDER,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||||
import type { ElectronFusePort } from "./ElectronFusePort.js";
|
import type { ElectronFusePort } from "./ElectronFusePort.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import {
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||||
AnalysisCancelledError,
|
|
||||||
AnalysisOutputError,
|
|
||||||
} from "../../domain/analysisErrorCore.js";
|
|
||||||
import {
|
import {
|
||||||
electronFuseReportSchema,
|
electronFuseReportSchema,
|
||||||
inspectElectronFusesInputSchema,
|
inspectElectronFusesInputSchema,
|
||||||
} from "../../domain/electron/electronFuses.js";
|
} from "../../domain/electron/electronFuses.js";
|
||||||
import type { Evidence } from "../../domain/evidence.js";
|
import type { Evidence } from "../../domain/evidence.js";
|
||||||
import { createElectronFuseEvidence } from "../../domain/electron/electronFuseEvidence.js";
|
import { createElectronFuseEvidence } from "../../domain/electron/electronFuseEvidence.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import { err, ok, type Result } from "../../domain/result.js";
|
import { err, ok, type Result } from "../../domain/result.js";
|
||||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
import {
|
||||||
|
completeProviderEvidence,
|
||||||
|
parseProviderOperationInput,
|
||||||
|
} from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
const OPERATION = "inspect_electron_fuses";
|
const OPERATION = "inspect_electron_fuses";
|
||||||
|
|
||||||
@@ -31,23 +30,19 @@ export class ElectronFuseService {
|
|||||||
rawInput: unknown,
|
rawInput: unknown,
|
||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
if (options?.signal?.aborted)
|
const input = parseProviderOperationInput(
|
||||||
return err(
|
OPERATION,
|
||||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
rawInput,
|
||||||
);
|
inspectElectronFusesInputSchema,
|
||||||
const input = inspectElectronFusesInputSchema.safeParse(rawInput);
|
options?.signal,
|
||||||
if (!input.success)
|
);
|
||||||
return err(
|
if (!input.ok) return input;
|
||||||
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
|
const inspected = await this.provider.inspect(input.value, options);
|
||||||
cause: input.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const inspected = await this.provider.inspect(input.data, options);
|
|
||||||
if (!inspected.ok) return inspected;
|
if (!inspected.ok) return inspected;
|
||||||
const report = electronFuseReportSchema.safeParse(inspected.value);
|
const report = electronFuseReportSchema.safeParse(inspected.value);
|
||||||
if (
|
if (
|
||||||
!report.success ||
|
!report.success ||
|
||||||
report.data.artifact.selected_path !== input.data.path
|
report.data.artifact.selected_path !== input.value.path
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisOutputError(
|
new AnalysisOutputError(
|
||||||
@@ -57,7 +52,7 @@ export class ElectronFuseService {
|
|||||||
);
|
);
|
||||||
return completeProviderEvidence(
|
return completeProviderEvidence(
|
||||||
createElectronFuseEvidence(
|
createElectronFuseEvidence(
|
||||||
input.data,
|
input.value,
|
||||||
report.data,
|
report.data,
|
||||||
this.provider.identity,
|
this.provider.identity,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||||
import type { BeamPort } from "./BeamPort.js";
|
import type { BeamPort } from "./BeamPort.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import {
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||||
AnalysisCancelledError,
|
|
||||||
AnalysisOutputError,
|
|
||||||
} from "../../domain/analysisErrorCore.js";
|
|
||||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||||
import {
|
import {
|
||||||
inspectErlangBeamInputSchema,
|
inspectErlangBeamInputSchema,
|
||||||
erlangBeamSchema,
|
erlangBeamSchema,
|
||||||
type ErlangBeam,
|
type ErlangBeam,
|
||||||
} from "../../domain/erlang/beam.js";
|
} from "../../domain/erlang/beam.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import { err, type Result } from "../../domain/result.js";
|
import { err, type Result } from "../../domain/result.js";
|
||||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
import {
|
||||||
|
completeProviderEvidence,
|
||||||
|
parseProviderOperationInput,
|
||||||
|
} from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
const OPERATION = "inspect_erlang_beam";
|
const OPERATION = "inspect_erlang_beam";
|
||||||
|
|
||||||
@@ -29,30 +28,24 @@ export class BeamService {
|
|||||||
rawInput: unknown,
|
rawInput: unknown,
|
||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
if (options?.signal?.aborted)
|
const input = parseProviderOperationInput(
|
||||||
return err(
|
OPERATION,
|
||||||
new AnalysisCancelledError(OPERATION, {
|
rawInput,
|
||||||
cause: options.signal.reason,
|
inspectErlangBeamInputSchema,
|
||||||
}),
|
options?.signal,
|
||||||
);
|
);
|
||||||
const input = inspectErlangBeamInputSchema.safeParse(rawInput);
|
if (!input.ok) return input;
|
||||||
if (!input.success)
|
const result = await this.provider.inspect(input.value, options);
|
||||||
return err(
|
|
||||||
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
|
|
||||||
cause: input.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const result = await this.provider.inspect(input.data, options);
|
|
||||||
if (!result.ok) return result;
|
if (!result.ok) return result;
|
||||||
const report = erlangBeamSchema.safeParse(result.value);
|
const report = erlangBeamSchema.safeParse(result.value);
|
||||||
if (
|
if (
|
||||||
!report.success ||
|
!report.success ||
|
||||||
report.data.artifact.path !== input.data.path ||
|
report.data.artifact.path !== input.value.path ||
|
||||||
!validReport(
|
!validReport(
|
||||||
report.data,
|
report.data,
|
||||||
input.data.max_chunks,
|
input.value.max_chunks,
|
||||||
input.data.max_atoms,
|
input.value.max_atoms,
|
||||||
input.data.max_file_bytes,
|
input.value.max_file_bytes,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
@@ -72,7 +65,7 @@ export class BeamService {
|
|||||||
this.provider.identity,
|
this.provider.identity,
|
||||||
{
|
{
|
||||||
operation: OPERATION,
|
operation: OPERATION,
|
||||||
parameters: input.data,
|
parameters: input.value,
|
||||||
result: value,
|
result: value,
|
||||||
confidence: "observed",
|
confidence: "observed",
|
||||||
limitations: value.limitations,
|
limitations: value.limitations,
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import {
|
|||||||
type AnalysisViewRetention,
|
type AnalysisViewRetention,
|
||||||
} from "../analysisView/AnalysisViewService.js";
|
} from "../analysisView/AnalysisViewService.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
|
||||||
import { dartAotInspectionInputSchema } from "../../domain/flutter/flutterBuildAnalysis.js";
|
import { dartAotInspectionInputSchema } from "../../domain/flutter/flutterBuildAnalysis.js";
|
||||||
import type { Evidence } from "../../domain/evidence.js";
|
import type { Evidence } from "../../domain/evidence.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
import type { Result } from "../../domain/result.js";
|
||||||
import { err, type Result } from "../../domain/result.js";
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
/** Keep the caller's ABI choice and complete analysis; select only its delivery. */
|
/** Keep the caller's ABI choice and complete analysis; select only its delivery. */
|
||||||
export const inspectDartAot = async (
|
export const inspectDartAot = async (
|
||||||
@@ -20,24 +19,18 @@ export const inspectDartAot = async (
|
|||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> => {
|
): Promise<Result<Evidence, AnalysisError>> => {
|
||||||
const operation = "inspect_dart_aot";
|
const operation = "inspect_dart_aot";
|
||||||
if (options?.signal?.aborted === true)
|
const parsed = parseProviderOperationInput(
|
||||||
return err(
|
operation,
|
||||||
new AnalysisCancelledError(operation, {
|
rawInput,
|
||||||
cause: options.signal.reason,
|
dartAotInspectionInputSchema,
|
||||||
}),
|
options?.signal,
|
||||||
);
|
);
|
||||||
const parsed = dartAotInspectionInputSchema.safeParse(rawInput);
|
if (!parsed.ok) return parsed;
|
||||||
if (!parsed.success)
|
if (parsed.value.detail === "summary") {
|
||||||
return err(
|
|
||||||
analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput, {
|
|
||||||
cause: parsed.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (parsed.data.detail === "summary") {
|
|
||||||
const ready = requireAnalysisViewRetention(operation, retention);
|
const ready = requireAnalysisViewRetention(operation, retention);
|
||||||
if (!ready.ok) return ready;
|
if (!ready.ok) return ready;
|
||||||
}
|
}
|
||||||
const { detail, ...providerInput } = parsed.data;
|
const { detail, ...providerInput } = parsed.value;
|
||||||
const executed = await service.execute(operation, providerInput, options);
|
const executed = await service.execute(operation, providerInput, options);
|
||||||
if (!executed.ok || detail !== "summary") return executed;
|
if (!executed.ok || detail !== "summary") return executed;
|
||||||
return summarizeRetainedAnalysis(executed.value, retention);
|
return summarizeRetainedAnalysis(executed.value, retention);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "../../domain/analysisErrorCore.js";
|
} from "../../domain/analysisErrorCore.js";
|
||||||
import { err, ok, type Result } from "../../domain/result.js";
|
import { err, ok, type Result } from "../../domain/result.js";
|
||||||
import { fridaSessionStatusInputSchema } from "../../domain/fridaSessionStatus.js";
|
import { fridaSessionStatusInputSchema } from "../../domain/fridaSessionStatus.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
import {
|
import {
|
||||||
requireAnalysisViewRetention,
|
requireAnalysisViewRetention,
|
||||||
summarizeRetainedAnalysis,
|
summarizeRetainedAnalysis,
|
||||||
@@ -252,36 +252,32 @@ export class FridaInstrumentationService {
|
|||||||
evidenceById: undefined,
|
evidenceById: undefined,
|
||||||
},
|
},
|
||||||
): Result<Evidence, AnalysisError> {
|
): Result<Evidence, AnalysisError> {
|
||||||
const input = fridaSessionStatusInputSchema.safeParse(rawInput);
|
const input = parseProviderOperationInput(
|
||||||
if (!input.success)
|
"frida_session_status",
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(
|
fridaSessionStatusInputSchema,
|
||||||
"frida_session_status",
|
);
|
||||||
input.error.issues,
|
if (!input.ok) return input;
|
||||||
rawInput,
|
if (input.value.detail === "summary") {
|
||||||
{ cause: input.error },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (input.data.detail === "summary") {
|
|
||||||
const ready = requireAnalysisViewRetention(
|
const ready = requireAnalysisViewRetention(
|
||||||
"frida_session_status",
|
"frida_session_status",
|
||||||
retention,
|
retention,
|
||||||
);
|
);
|
||||||
if (!ready.ok) return ready;
|
if (!ready.ok) return ready;
|
||||||
}
|
}
|
||||||
const status = this.#provider.status(input.data.session_id);
|
const status = this.#provider.status(input.value.session_id);
|
||||||
if (status === undefined)
|
if (status === undefined)
|
||||||
return err(
|
return err(
|
||||||
unknownFridaSession("frida_session_status", input.data.session_id),
|
unknownFridaSession("frida_session_status", input.value.session_id),
|
||||||
);
|
);
|
||||||
const evidence = fridaEvidence(
|
const evidence = fridaEvidence(
|
||||||
"frida_session_status",
|
"frida_session_status",
|
||||||
sessionStatusOutput(status),
|
sessionStatusOutput(status),
|
||||||
{
|
{
|
||||||
session_id: input.data.session_id,
|
session_id: input.value.session_id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return input.data.detail === "summary"
|
return input.value.detail === "summary"
|
||||||
? summarizeRetainedAnalysis(evidence, retention)
|
? summarizeRetainedAnalysis(evidence, retention)
|
||||||
: ok(evidence);
|
: ok(evidence);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
||||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -25,7 +26,6 @@ import {
|
|||||||
type Il2cppMetadata,
|
type Il2cppMetadata,
|
||||||
type Il2cppMetadataProviderInput,
|
type Il2cppMetadataProviderInput,
|
||||||
} from "../../domain/il2cpp/il2cppMetadata.js";
|
} from "../../domain/il2cpp/il2cppMetadata.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import { err, ok, type Result } from "../../domain/result.js";
|
import { err, ok, type Result } from "../../domain/result.js";
|
||||||
|
|
||||||
const OPERATION = "inspect_il2cpp_metadata";
|
const OPERATION = "inspect_il2cpp_metadata";
|
||||||
@@ -99,20 +99,16 @@ export class Il2cppMetadataService {
|
|||||||
retention: AnalysisViewRetention,
|
retention: AnalysisViewRetention,
|
||||||
output?: Il2cppSeedOutput,
|
output?: Il2cppSeedOutput,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
if (options?.signal?.aborted)
|
const input = parseProviderOperationInput(
|
||||||
return err(
|
OPERATION,
|
||||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
rawInput,
|
||||||
);
|
inspectIl2cppMetadataInputSchema,
|
||||||
const input = inspectIl2cppMetadataInputSchema.safeParse(rawInput);
|
options?.signal,
|
||||||
if (!input.success)
|
);
|
||||||
return err(
|
if (!input.ok) return input;
|
||||||
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
|
|
||||||
cause: input.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
input.data.binary_architecture !== undefined &&
|
input.value.binary_architecture !== undefined &&
|
||||||
input.data.binary_path === undefined
|
input.value.binary_path === undefined
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisInputError(OPERATION, undefined, [
|
new AnalysisInputError(OPERATION, undefined, [
|
||||||
@@ -124,8 +120,8 @@ export class Il2cppMetadataService {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
input.data.generic_instances === true &&
|
input.value.generic_instances === true &&
|
||||||
input.data.binary_path === undefined
|
input.value.binary_path === undefined
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisInputError(OPERATION, undefined, [
|
new AnalysisInputError(OPERATION, undefined, [
|
||||||
@@ -137,7 +133,7 @@ export class Il2cppMetadataService {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
if (output !== undefined) {
|
if (output !== undefined) {
|
||||||
if (input.data.binary_path === undefined)
|
if (input.value.binary_path === undefined)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisInputError(OPERATION, undefined, [
|
new AnalysisInputError(OPERATION, undefined, [
|
||||||
{
|
{
|
||||||
@@ -158,7 +154,7 @@ export class Il2cppMetadataService {
|
|||||||
const ready = await this.seeds.check(output, options);
|
const ready = await this.seeds.check(output, options);
|
||||||
if (!ready.ok) return ready;
|
if (!ready.ok) return ready;
|
||||||
}
|
}
|
||||||
const { detail, ...providerInput } = input.data;
|
const { detail, ...providerInput } = input.value;
|
||||||
if (detail === "summary") {
|
if (detail === "summary") {
|
||||||
const ready = requireAnalysisViewRetention(OPERATION, retention);
|
const ready = requireAnalysisViewRetention(OPERATION, retention);
|
||||||
if (!ready.ok) return ready;
|
if (!ready.ok) return ready;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
type EvidenceLookup,
|
type EvidenceLookup,
|
||||||
} from "../EvidenceInputResolver.js";
|
} from "../EvidenceInputResolver.js";
|
||||||
import { retainedEvidenceReferenceSchema } from "../../domain/evidenceReference.js";
|
import { retainedEvidenceReferenceSchema } from "../../domain/evidenceReference.js";
|
||||||
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
import type { ResolvedSourceToBundleReference } from "./SourceToBundleReference.js";
|
import type { ResolvedSourceToBundleReference } from "./SourceToBundleReference.js";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -121,16 +122,15 @@ export const traceApplicationFeatureEvidence = (
|
|||||||
AnalysisError
|
AnalysisError
|
||||||
> => {
|
> => {
|
||||||
const operation = "trace_application_feature";
|
const operation = "trace_application_feature";
|
||||||
const request = featureTraceFrameSchema.safeParse(rawInput);
|
const request = parseProviderOperationInput(
|
||||||
if (!request.success)
|
operation,
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(operation, request.error.issues, rawInput, {
|
featureTraceFrameSchema,
|
||||||
cause: request.error,
|
);
|
||||||
}),
|
if (!request.ok) return request;
|
||||||
);
|
const selected = resolveWorkflowEvidence(request.value.application, lookup);
|
||||||
const selected = resolveWorkflowEvidence(request.data.application, lookup);
|
|
||||||
if (!selected.ok) return selected;
|
if (!selected.ok) return selected;
|
||||||
const input = { ...request.data, application: selected.value };
|
const input = { ...request.value, application: selected.value };
|
||||||
const sourceResult = parseApplicationGraphEvidence(input.application, [
|
const sourceResult = parseApplicationGraphEvidence(input.application, [
|
||||||
"application",
|
"application",
|
||||||
]);
|
]);
|
||||||
@@ -187,17 +187,16 @@ export const compareApplicationVersionsEvidence = (
|
|||||||
AnalysisError
|
AnalysisError
|
||||||
> => {
|
> => {
|
||||||
const operation = "compare_application_versions";
|
const operation = "compare_application_versions";
|
||||||
const request = versionComparisonFrameSchema.safeParse(rawInput);
|
const request = parseProviderOperationInput(
|
||||||
if (!request.success)
|
operation,
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(operation, request.error.issues, rawInput, {
|
versionComparisonFrameSchema,
|
||||||
cause: request.error,
|
);
|
||||||
}),
|
if (!request.ok) return request;
|
||||||
);
|
const pair = resolveApplicationGraphPair(operation, request.value, lookup);
|
||||||
const pair = resolveApplicationGraphPair(operation, request.data, lookup);
|
|
||||||
if (!pair.ok) return pair;
|
if (!pair.ok) return pair;
|
||||||
const { left, right } = pair.value;
|
const { left, right } = pair.value;
|
||||||
const input = { ...request.data, ...pair.value.selected };
|
const input = { ...request.value, ...pair.value.selected };
|
||||||
const leftNativeResult = parseNativeApplicationEvidence(
|
const leftNativeResult = parseNativeApplicationEvidence(
|
||||||
input.left_native_observations,
|
input.left_native_observations,
|
||||||
["left_native_observations"],
|
["left_native_observations"],
|
||||||
@@ -306,17 +305,16 @@ export const compareJavaScriptExportShapesEvidence = (
|
|||||||
AnalysisError
|
AnalysisError
|
||||||
> => {
|
> => {
|
||||||
const operation = "compare_javascript_export_shapes";
|
const operation = "compare_javascript_export_shapes";
|
||||||
const request = exportShapeFrameSchema.safeParse(rawInput);
|
const request = parseProviderOperationInput(
|
||||||
if (!request.success)
|
operation,
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(operation, request.error.issues, rawInput, {
|
exportShapeFrameSchema,
|
||||||
cause: request.error,
|
);
|
||||||
}),
|
if (!request.ok) return request;
|
||||||
);
|
const pair = resolveApplicationGraphPair(operation, request.value, lookup);
|
||||||
const pair = resolveApplicationGraphPair(operation, request.data, lookup);
|
|
||||||
if (!pair.ok) return pair;
|
if (!pair.ok) return pair;
|
||||||
const { left, right } = pair.value;
|
const { left, right } = pair.value;
|
||||||
const input = { ...request.data, ...pair.value.selected };
|
const input = { ...request.value, ...pair.value.selected };
|
||||||
const identities = javaScriptExportShapeIdentitiesSchema.safeParse({
|
const identities = javaScriptExportShapeIdentitiesSchema.safeParse({
|
||||||
left: left.evidence,
|
left: left.evidence,
|
||||||
right: right.evidence,
|
right: right.evidence,
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
type ManagedReconstructionImportResult,
|
type ManagedReconstructionImportResult,
|
||||||
type ResolvedManagedReconstructionImport,
|
type ResolvedManagedReconstructionImport,
|
||||||
} from "../../domain/managed/managedReconstruction.js";
|
} from "../../domain/managed/managedReconstruction.js";
|
||||||
import { err, ok, type Result } from "../../domain/result.js";
|
import { ok, type Result } from "../../domain/result.js";
|
||||||
import { MANAGED_WORKFLOW_PROVIDER } from "../../domain/investigationProviders.js";
|
import { MANAGED_WORKFLOW_PROVIDER } from "../../domain/investigationProviders.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
const OPERATION = "import_managed_reconstruction" as const;
|
const OPERATION = "import_managed_reconstruction" as const;
|
||||||
|
|
||||||
@@ -18,14 +18,13 @@ const OPERATION = "import_managed_reconstruction" as const;
|
|||||||
export const resolveManagedReconstructionEvidenceInput = (
|
export const resolveManagedReconstructionEvidenceInput = (
|
||||||
rawInput: unknown,
|
rawInput: unknown,
|
||||||
): Result<ResolvedManagedReconstructionImport, AnalysisError> => {
|
): Result<ResolvedManagedReconstructionImport, AnalysisError> => {
|
||||||
const parsed = managedReconstructionImportRequestSchema.safeParse(rawInput);
|
const parsed = parseProviderOperationInput(
|
||||||
if (!parsed.success)
|
OPERATION,
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput, {
|
managedReconstructionImportRequestSchema,
|
||||||
cause: parsed.error,
|
);
|
||||||
}),
|
if (!parsed.ok) return parsed;
|
||||||
);
|
return resolveManagedReconstructionImport(parsed.value);
|
||||||
return resolveManagedReconstructionImport(parsed.data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Execute from the source facts returned by the resolver. */
|
/** Execute from the source facts returned by the resolver. */
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
} from "../../process/OwnedCommand.js";
|
} from "../../process/OwnedCommand.js";
|
||||||
import type { ProviderProcessSupervisor } from "../../process/ProviderProcess.js";
|
import type { ProviderProcessSupervisor } from "../../process/ProviderProcess.js";
|
||||||
import { absoluteCallerPath } from "../../filesystem/callerPath.js";
|
import { absoluteCallerPath } from "../../filesystem/callerPath.js";
|
||||||
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
export type ReverseEngineeringOperation =
|
export type ReverseEngineeringOperation =
|
||||||
| "inspect_with_objdump"
|
| "inspect_with_objdump"
|
||||||
@@ -214,21 +215,17 @@ export class ReverseEngineeringService {
|
|||||||
recordEvidence: undefined,
|
recordEvidence: undefined,
|
||||||
},
|
},
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
const parsed = objdumpInputSchema.safeParse(rawInput);
|
const parsed = parseProviderOperationInput(
|
||||||
if (!parsed.success)
|
"inspect_with_objdump",
|
||||||
return err(
|
rawInput,
|
||||||
analysisInputErrorFromIssues(
|
objdumpInputSchema,
|
||||||
"inspect_with_objdump",
|
);
|
||||||
parsed.error.issues,
|
if (!parsed.ok) return parsed;
|
||||||
rawInput,
|
|
||||||
{ cause: parsed.error },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const {
|
const {
|
||||||
detail,
|
detail,
|
||||||
command_timeout_ms: commandTimeoutMs,
|
command_timeout_ms: commandTimeoutMs,
|
||||||
...input
|
...input
|
||||||
} = parsed.data;
|
} = parsed.value;
|
||||||
if (detail === "summary") {
|
if (detail === "summary") {
|
||||||
const ready = requireAnalysisViewRetention(
|
const ready = requireAnalysisViewRetention(
|
||||||
"inspect_with_objdump",
|
"inspect_with_objdump",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
|
import { parseProviderOperationInput } from "../ProviderOperationExecution.js";
|
||||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||||
import type { SqliteDatabasePort } from "./SqliteDatabasePort.js";
|
import type { SqliteDatabasePort } from "./SqliteDatabasePort.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import {
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||||
AnalysisCancelledError,
|
|
||||||
AnalysisOutputError,
|
|
||||||
} from "../../domain/analysisErrorCore.js";
|
|
||||||
import {
|
import {
|
||||||
createEvidence,
|
createEvidence,
|
||||||
type Evidence,
|
type Evidence,
|
||||||
@@ -21,7 +19,6 @@ import {
|
|||||||
sqliteSchemaScope,
|
sqliteSchemaScope,
|
||||||
sqliteInspectionParameters,
|
sqliteInspectionParameters,
|
||||||
} from "../../domain/sqlite/sqliteDatabase.js";
|
} from "../../domain/sqlite/sqliteDatabase.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import { err, ok, type Result } from "../../domain/result.js";
|
import { err, ok, type Result } from "../../domain/result.js";
|
||||||
|
|
||||||
const OPERATION = "inspect_sqlite_database";
|
const OPERATION = "inspect_sqlite_database";
|
||||||
@@ -82,27 +79,21 @@ export class SqliteDatabaseService {
|
|||||||
rawInput: unknown,
|
rawInput: unknown,
|
||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
if (options?.signal?.aborted)
|
const input = parseProviderOperationInput(
|
||||||
return err(
|
OPERATION,
|
||||||
new AnalysisCancelledError(OPERATION, {
|
rawInput,
|
||||||
cause: options.signal.reason,
|
inspectSqliteDatabaseInputSchema,
|
||||||
}),
|
options?.signal,
|
||||||
);
|
);
|
||||||
const input = inspectSqliteDatabaseInputSchema.safeParse(rawInput);
|
if (!input.ok) return input;
|
||||||
if (!input.success)
|
const inspected = await this.provider.inspect(input.value, options);
|
||||||
return err(
|
|
||||||
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
|
|
||||||
cause: input.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const inspected = await this.provider.inspect(input.data, options);
|
|
||||||
if (!inspected.ok) return inspected;
|
if (!inspected.ok) return inspected;
|
||||||
const execution = inspected.value;
|
const execution = inspected.value;
|
||||||
const report = sqliteDatabaseSchema.safeParse(execution.result);
|
const report = sqliteDatabaseSchema.safeParse(execution.result);
|
||||||
if (
|
if (
|
||||||
!report.success ||
|
!report.success ||
|
||||||
execution.subject == null ||
|
execution.subject == null ||
|
||||||
!matchesSelectedDatabase(report.data, input.data, execution.subject)
|
!matchesSelectedDatabase(report.data, input.value, execution.subject)
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
new AnalysisOutputError(
|
new AnalysisOutputError(
|
||||||
@@ -113,7 +104,7 @@ export class SqliteDatabaseService {
|
|||||||
return ok(
|
return ok(
|
||||||
createEvidence(execution.subject, execution.provider, {
|
createEvidence(execution.subject, execution.provider, {
|
||||||
operation: OPERATION,
|
operation: OPERATION,
|
||||||
parameters: sqliteInspectionParameters(input.data),
|
parameters: sqliteInspectionParameters(input.value),
|
||||||
result: report.data,
|
result: report.data,
|
||||||
...(execution.rawResult === null
|
...(execution.rawResult === null
|
||||||
? {}
|
? {}
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import type { ExecutionOptions } from "../AnalysisProvider.js";
|
|||||||
import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js";
|
import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js";
|
||||||
import type { WasmArtifactPort } from "./WasmArtifactPort.js";
|
import type { WasmArtifactPort } from "./WasmArtifactPort.js";
|
||||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||||
import {
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||||
AnalysisCancelledError,
|
|
||||||
AnalysisOutputError,
|
|
||||||
} from "../../domain/analysisErrorCore.js";
|
|
||||||
import type { Evidence } from "../../domain/evidence.js";
|
import type { Evidence } from "../../domain/evidence.js";
|
||||||
import {
|
import {
|
||||||
inspectWasmArtifactInputSchema,
|
inspectWasmArtifactInputSchema,
|
||||||
@@ -14,9 +11,11 @@ import {
|
|||||||
wasmArtifactMatchesSubject,
|
wasmArtifactMatchesSubject,
|
||||||
DEFAULT_WASM_COMMAND_TIMEOUT_MS,
|
DEFAULT_WASM_COMMAND_TIMEOUT_MS,
|
||||||
} from "../../domain/wasm/wasmArtifact.js";
|
} from "../../domain/wasm/wasmArtifact.js";
|
||||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
||||||
import { err, type Result } from "../../domain/result.js";
|
import { err, type Result } from "../../domain/result.js";
|
||||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
import {
|
||||||
|
completeProviderEvidence,
|
||||||
|
parseProviderOperationInput,
|
||||||
|
} from "../ProviderOperationExecution.js";
|
||||||
|
|
||||||
const OPERATION = "inspect_wasm_artifact";
|
const OPERATION = "inspect_wasm_artifact";
|
||||||
|
|
||||||
@@ -37,24 +36,18 @@ export class WasmArtifactService {
|
|||||||
rawInput: unknown,
|
rawInput: unknown,
|
||||||
options?: ExecutionOptions,
|
options?: ExecutionOptions,
|
||||||
): Promise<Result<Evidence, AnalysisError>> {
|
): Promise<Result<Evidence, AnalysisError>> {
|
||||||
if (options?.signal?.aborted)
|
const input = parseProviderOperationInput(
|
||||||
return err(
|
OPERATION,
|
||||||
new AnalysisCancelledError(OPERATION, {
|
rawInput,
|
||||||
cause: options.signal.reason,
|
inspectWasmArtifactInputSchema,
|
||||||
}),
|
options?.signal,
|
||||||
);
|
);
|
||||||
const input = inspectWasmArtifactInputSchema.safeParse(rawInput);
|
if (!input.ok) return input;
|
||||||
if (!input.success)
|
|
||||||
return err(
|
|
||||||
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
|
|
||||||
cause: input.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const timeout =
|
const timeout =
|
||||||
input.data.command_timeout_ms ??
|
input.value.command_timeout_ms ??
|
||||||
this.settings?.().overrides["wasm.command_timeout_ms"] ??
|
this.settings?.().overrides["wasm.command_timeout_ms"] ??
|
||||||
DEFAULT_WASM_COMMAND_TIMEOUT_MS;
|
DEFAULT_WASM_COMMAND_TIMEOUT_MS;
|
||||||
const inspected = await this.provider.inspect(input.data, {
|
const inspected = await this.provider.inspect(input.value, {
|
||||||
...options,
|
...options,
|
||||||
commandTimeoutMs: timeout,
|
commandTimeoutMs: timeout,
|
||||||
});
|
});
|
||||||
@@ -63,14 +56,14 @@ export class WasmArtifactService {
|
|||||||
const report = wasmArtifactSchema.safeParse(execution.result);
|
const report = wasmArtifactSchema.safeParse(execution.result);
|
||||||
if (
|
if (
|
||||||
!report.success ||
|
!report.success ||
|
||||||
report.data.artifact.path !== input.data.path ||
|
report.data.artifact.path !== input.value.path ||
|
||||||
report.data.tool_profile.timeout_ms !== timeout ||
|
report.data.tool_profile.timeout_ms !== timeout ||
|
||||||
!wasmArtifactMatchesSubject(report.data.artifact, execution.subject) ||
|
!wasmArtifactMatchesSubject(report.data.artifact, execution.subject) ||
|
||||||
JSON.stringify(report.data.glue.map((item) => item.artifact.path)) !==
|
JSON.stringify(report.data.glue.map((item) => item.artifact.path)) !==
|
||||||
JSON.stringify(input.data.glue_paths) ||
|
JSON.stringify(input.value.glue_paths) ||
|
||||||
JSON.stringify(report.data.candidates.map((item) => item.path)) !==
|
JSON.stringify(report.data.candidates.map((item) => item.path)) !==
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
...new Set([input.data.path, ...input.data.candidate_paths]),
|
...new Set([input.value.path, ...input.value.candidate_paths]),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
return err(
|
return err(
|
||||||
@@ -82,7 +75,7 @@ export class WasmArtifactService {
|
|||||||
return completeProviderEvidence(
|
return completeProviderEvidence(
|
||||||
createAnalysisExecutionEvidence(
|
createAnalysisExecutionEvidence(
|
||||||
OPERATION,
|
OPERATION,
|
||||||
input.data,
|
input.value,
|
||||||
{ ...execution, result: report.data },
|
{ ...execution, result: report.data },
|
||||||
"observed",
|
"observed",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { expect, it } from "vitest";
|
||||||
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||||
|
import { assertClientSchemaPatterns } from "../../../scripts/lib/client-schema-patterns.mjs";
|
||||||
|
|
||||||
|
it("keeps pattern-looking fields inside valid literal values opaque", () => {
|
||||||
|
const schema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
payload: {
|
||||||
|
const: { pattern: "[" },
|
||||||
|
enum: [{ pattern: "[" }],
|
||||||
|
default: { pattern: "[" },
|
||||||
|
examples: [{ pattern: "[" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
||||||
|
expect(ajv.validateSchema(schema)).toBe(true);
|
||||||
|
expect(assertClientSchemaPatterns(schema)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates regex keys in patternProperties", () => {
|
||||||
|
expect(() =>
|
||||||
|
assertClientSchemaPatterns({
|
||||||
|
patternProperties: { "[": { type: "string" } },
|
||||||
|
}),
|
||||||
|
).toThrow(SyntaxError);
|
||||||
|
expect(
|
||||||
|
assertClientSchemaPatterns({
|
||||||
|
patternProperties: { "^name": { pattern: "value$" } },
|
||||||
|
}),
|
||||||
|
).toEqual(["^name", "value$"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("visits nested schema positions owned by the schema keyword catalog", () => {
|
||||||
|
const schema = {
|
||||||
|
$defs: { named: { pattern: "^named" } },
|
||||||
|
properties: {
|
||||||
|
value: {
|
||||||
|
allOf: [
|
||||||
|
{ contains: { pattern: "^contained" } },
|
||||||
|
{
|
||||||
|
if: { propertyNames: { pattern: "^property" } },
|
||||||
|
then: { additionalProperties: { pattern: "^additional" } },
|
||||||
|
else: { not: { pattern: "^negated" } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prefixItems: [{ anyOf: [{ pattern: "^first" }, { pattern: "^second" }] }],
|
||||||
|
unevaluatedItems: { pattern: "^unevaluated" },
|
||||||
|
};
|
||||||
|
expect(assertClientSchemaPatterns(schema)).toEqual([
|
||||||
|
"^named",
|
||||||
|
"^contained",
|
||||||
|
"^property",
|
||||||
|
"^additional",
|
||||||
|
"^negated",
|
||||||
|
"^first",
|
||||||
|
"^second",
|
||||||
|
"^unevaluated",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats a literal object as a schema only when a local reference selects it", () => {
|
||||||
|
const schema = { const: { pattern: "[" } };
|
||||||
|
expect(assertClientSchemaPatterns(schema)).toEqual([]);
|
||||||
|
expect(() =>
|
||||||
|
assertClientSchemaPatterns({ ...schema, $ref: "#/const" }),
|
||||||
|
).toThrow(SyntaxError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses existing reference resolution without loading external or dynamic targets", () => {
|
||||||
|
expect(
|
||||||
|
assertClientSchemaPatterns({
|
||||||
|
$ref: "https://example.invalid/schema",
|
||||||
|
const: { pattern: "[" },
|
||||||
|
}),
|
||||||
|
).toEqual([]);
|
||||||
|
expect(
|
||||||
|
assertClientSchemaPatterns({
|
||||||
|
$dynamicRef: "#external",
|
||||||
|
examples: [{ pattern: "[" }],
|
||||||
|
}),
|
||||||
|
).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps native Unicode-mode failures and repeated pattern occurrences visible", () => {
|
||||||
|
expect(() => assertClientSchemaPatterns({ pattern: "\\a" })).toThrow(
|
||||||
|
SyntaxError,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
assertClientSchemaPatterns({
|
||||||
|
anyOf: [{ pattern: "^same" }, { pattern: "^same" }],
|
||||||
|
}),
|
||||||
|
).toEqual(["^same", "^same"]);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user