mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
refactor(javascript): share the reconstruction input contract
Remove the reconstruction-only request schema and consume the canonical parsed JavaScript application input. Production already owns parsing at the service boundary; direct test seams now use that same schema instead of a weaker path contract. Preserve format, filesystem, source selection and lifecycle validation. Add a NUL-path regression for canonical admission and typed input-error projection. Verified 63 focused cases plus static, build, docs, discovery and duplication checks; no target execution or real-provider claims.
This commit is contained in:
@@ -20,8 +20,11 @@ import { createJavaScriptArtifactReader as createReader } from "../../artifacts/
|
||||
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js";
|
||||
import type { JavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import type { JavaScriptSourceSelection } from "../../domain/javascript/javascriptSourceSelection.js";
|
||||
import type { ElectronBoundarySummary } from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { parseOwnedJavaScriptApplicationAnalysisSteps } from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import {
|
||||
parseOwnedJavaScriptApplicationAnalysisSteps,
|
||||
type AnalyzeJavaScriptApplicationInput,
|
||||
type ElectronBoundarySummary,
|
||||
} from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
||||
import { ArtifactOperationError } from "../../domain/artifactOperationError.js";
|
||||
@@ -38,10 +41,6 @@ import { JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS } from "../../domain/javascript/j
|
||||
import { createOwnedJavaScriptApplicationEvidenceCooperatively } from "./JavaScriptApplicationEvidence.js";
|
||||
import { readJavaScriptArtifactFiles } from "../../artifacts/javascript/JavaScriptArtifactFiles.js";
|
||||
import { buildImmutableJavaScriptArtifactGraphSteps } from "./JavaScriptArtifactGraphBuilder.js";
|
||||
import {
|
||||
javascriptArtifactReconstructionInputSchema,
|
||||
type JavaScriptArtifactReconstructionInput,
|
||||
} from "./JavaScriptArtifactReconstructionInput.js";
|
||||
import { scanCanonicalArtifactInventoryInScope } from "../../artifacts/inventory/scanCanonical.js";
|
||||
import { createJavaScriptNextAppContext } from "./JavaScriptNextAppContext.js";
|
||||
import { summarizeElectronBoundaries } from "./ElectronBoundaryAnalysis.js";
|
||||
@@ -80,9 +79,9 @@ export interface JavaScriptArtifactReconstructionResult {
|
||||
readonly limitations: readonly string[];
|
||||
}
|
||||
|
||||
/** Reconstruct one local ASAR or extracted directory without executing code. */
|
||||
/** Reconstruct a parsed local ASAR/directory request without executing code. */
|
||||
export const reconstructJavaScriptArtifact = async (
|
||||
rawInput: unknown,
|
||||
input: AnalyzeJavaScriptApplicationInput,
|
||||
scope: ArtifactResourceScope,
|
||||
signal?: AbortSignal,
|
||||
progress?: ProgressReporter,
|
||||
@@ -101,7 +100,6 @@ export const reconstructJavaScriptArtifact = async (
|
||||
});
|
||||
await checkpointJavaScriptAnalysis(signal);
|
||||
};
|
||||
const input = javascriptArtifactReconstructionInputSchema.parse(rawInput);
|
||||
abortIfNeeded(signal);
|
||||
const { path, metadata } = await resolveSelectedInput(input.input_path);
|
||||
const format = resolveFormat(path, metadata, input);
|
||||
@@ -487,7 +485,7 @@ const selectedInputResolutionMessage = (
|
||||
const resolveFormat = (
|
||||
path: string,
|
||||
metadata: Stats,
|
||||
input: JavaScriptArtifactReconstructionInput,
|
||||
input: AnalyzeJavaScriptApplicationInput,
|
||||
): "asar" | "directory" => {
|
||||
const observed = metadata.isDirectory()
|
||||
? "directory"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { artifactIntegrityPolicySchema } from "../../domain/artifactIntegrityPolicy.js";
|
||||
import { javaScriptAnalysisResourceControlsSchema } from "../../domain/javascript/javascriptAnalysisResourceControls.js";
|
||||
import { javaScriptSourcePathsSchema } from "../../domain/javascript/javascriptSourceSelection.js";
|
||||
|
||||
/** Local ASAR/directory reconstruction request. */
|
||||
export const javascriptArtifactReconstructionInputSchema = z.strictObject({
|
||||
input_path: z.string().min(1),
|
||||
format: z.enum(["auto", "asar", "directory"]).default("auto"),
|
||||
integrity_policy: artifactIntegrityPolicySchema,
|
||||
source_paths: javaScriptSourcePathsSchema.optional(),
|
||||
...javaScriptAnalysisResourceControlsSchema.shape,
|
||||
});
|
||||
|
||||
/** Parsed local reconstruction request. */
|
||||
export type JavaScriptArtifactReconstructionInput = z.infer<
|
||||
typeof javascriptArtifactReconstructionInputSchema
|
||||
>;
|
||||
@@ -8,6 +8,7 @@ import { expect, it } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import {
|
||||
analyzeJavaScriptApplication,
|
||||
reconstructJavaScriptArtifact,
|
||||
readJavaScriptArtifactFiles,
|
||||
} from "../../support/javascriptApplicationScope.js";
|
||||
@@ -384,6 +385,35 @@ it("retains every repeated content observation and containment path", async () =
|
||||
});
|
||||
}, 15_000);
|
||||
|
||||
it("rejects NUL paths through the public request contract before reconstruction", async () => {
|
||||
const input = { input_path: "invalid\u0000.asar" };
|
||||
await expect(reconstructJavaScriptArtifact(input)).rejects.toMatchObject({
|
||||
name: "ZodError",
|
||||
issues: [
|
||||
{
|
||||
code: "invalid_format",
|
||||
path: ["input_path"],
|
||||
message: "Local filesystem paths cannot contain NUL",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = await analyzeJavaScriptApplication(input);
|
||||
if (result.ok) throw new Error("Expected invalid input path");
|
||||
expect(result.error).toMatchObject({
|
||||
_tag: "AnalysisInputError",
|
||||
operation: "analyze_javascript_application",
|
||||
issues: [
|
||||
{
|
||||
path: ["input_path"],
|
||||
reason: "invalid_format",
|
||||
expected: "regex",
|
||||
message: "Local filesystem paths cannot contain NUL",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects traversal from a production reader seam and malformed ASAR containers", async () => {
|
||||
const root = await fixtureDirectory();
|
||||
const snapshot = await scanArtifactInventory(root);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js
|
||||
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
|
||||
import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
|
||||
import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js";
|
||||
import { analyzeJavaScriptApplicationInputSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
it("retains a failed nested reader through blocked work and scope cleanup", async () => {
|
||||
@@ -40,7 +41,9 @@ it("retains a failed nested reader through blocked work and scope cleanup", asyn
|
||||
},
|
||||
);
|
||||
|
||||
const input = { input_path: bundle };
|
||||
const input = analyzeJavaScriptApplicationInputSchema.parse({
|
||||
input_path: bundle,
|
||||
});
|
||||
const firstFailure = await reconstructJavaScriptArtifact(input, scope).catch(
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { withArtifactResourceScope } from "../fixtures/artifactInventory.js";
|
||||
import type { ExecutionOptions } from "../../src/application/AnalysisProvider.js";
|
||||
import {
|
||||
analyzeJavaScriptApplication as analyzeOwned,
|
||||
type JavaScriptApplicationOptions,
|
||||
} from "../../src/application/javascript/JavaScriptApplicationService.js";
|
||||
import { reconstructJavaScriptArtifact as reconstructOwned } from "../../src/application/javascript/JavaScriptArtifactReconstruction.js";
|
||||
import { analyzeJavaScriptApplicationInputSchema } from "../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import type { ProgressReporter } from "../../src/application/ProgressReporter.js";
|
||||
import { readJavaScriptArtifactFiles as readOwned } from "../../src/artifacts/javascript/JavaScriptArtifactFiles.js";
|
||||
import type { ArtifactReader } from "../../src/artifacts/ArtifactReader.js";
|
||||
@@ -34,5 +34,10 @@ export const reconstructJavaScriptArtifact = (
|
||||
progress?: ProgressReporter,
|
||||
) =>
|
||||
withArtifactResourceScope((scope) =>
|
||||
reconstructOwned(rawInput, scope, signal, progress),
|
||||
reconstructOwned(
|
||||
analyzeJavaScriptApplicationInputSchema.parse(rawInput),
|
||||
scope,
|
||||
signal,
|
||||
progress,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user