mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
* refactor: remove redundant seams and preserve cleanup ownership * fix: validate PE resource extents before budget checks
221 lines
6.4 KiB
TypeScript
221 lines
6.4 KiB
TypeScript
import { PYTHON_PYC_PROVIDER_IDENTITY } from "./PythonPycProviderIdentity.js";
|
|
import {
|
|
projectSelectedArtifactFileFailure,
|
|
projectStableArtifactFailure,
|
|
stableArtifactInputRecovery,
|
|
projectArtifactCloseFailure,
|
|
} from "../artifacts/StableArtifactFailure.js";
|
|
import {
|
|
artifactFileCancellationCause,
|
|
artifactFilePrimaryFacts,
|
|
type ArtifactFilePrimaryFacts,
|
|
} from "../artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
|
import {
|
|
type StableArtifactSnapshot,
|
|
readStableArtifact,
|
|
} from "../artifacts/readStableArtifact.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import type { PythonPycPort } from "../application/python/PythonPycPort.js";
|
|
import type { ExecutionOptions } from "../application/AnalysisProvider.js";
|
|
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import {
|
|
AnalysisArtifactChangedError,
|
|
AnalysisCancelledError,
|
|
AnalysisInputError,
|
|
AnalysisResourceConstraintError,
|
|
AnalysisUnsupportedTargetError,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import {
|
|
PYTHON_PYC_LIMIT_MAXIMA,
|
|
type InspectPythonPycInput,
|
|
} from "../domain/python/pythonPyc.js";
|
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
import { err, ok } from "../domain/result.js";
|
|
import {
|
|
PythonPycFormatFailure,
|
|
PythonPycResourceFailure,
|
|
} from "./PythonPyc.errors.js";
|
|
import { parsePythonPyc } from "./PythonPycParser.js";
|
|
|
|
const OPERATION = "inspect_python_pyc";
|
|
|
|
const parserLimitRecovery = (
|
|
limits: PythonPycResourceFailure["reported_limits"],
|
|
) => {
|
|
const actions = (
|
|
Object.keys(
|
|
PYTHON_PYC_LIMIT_MAXIMA,
|
|
) as (keyof typeof PYTHON_PYC_LIMIT_MAXIMA)[]
|
|
).flatMap((field) => {
|
|
const selected = limits[field];
|
|
if (selected === undefined) return [];
|
|
const maximum = PYTHON_PYC_LIMIT_MAXIMA[field];
|
|
const option = `--${field.replaceAll("_", "-")}`;
|
|
return [
|
|
selected < maximum
|
|
? `Raise ${field} (${option}) above ${selected}, up to ${maximum}, on the next call.`
|
|
: `${field} (${option}) is at this operation's maximum of ${maximum}; select a complete artifact within that bound.`,
|
|
];
|
|
});
|
|
return {
|
|
retryable: false as const,
|
|
remediationAction: `${actions.join(" ")} Repeating the unchanged request fails the same way; waiting or adding host memory does not raise these parser limits.`,
|
|
};
|
|
};
|
|
|
|
const failureForPrimary = (
|
|
facts: ArtifactFilePrimaryFacts,
|
|
input: InspectPythonPycInput,
|
|
) => {
|
|
const { cause, origin } = facts;
|
|
if (origin === "owned-or-classified" && cause instanceof AnalysisError)
|
|
return cause;
|
|
if (
|
|
origin === "owned-or-classified" &&
|
|
cause instanceof ArtifactReaderFailure &&
|
|
cause.reason === "integrity"
|
|
)
|
|
return new AnalysisArtifactChangedError(
|
|
OPERATION,
|
|
input.path,
|
|
cause.message,
|
|
{ cause },
|
|
);
|
|
if (
|
|
origin === "owned-or-classified" &&
|
|
cause instanceof ArtifactReaderFailure &&
|
|
cause.reason === "cancelled"
|
|
)
|
|
return new AnalysisCancelledError(OPERATION, {
|
|
cause: artifactFileCancellationCause(cause),
|
|
});
|
|
if (cause instanceof PythonPycResourceFailure)
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
cause.boundary === "depth" ? "capacity" : "memory",
|
|
cause.message,
|
|
cause.reported_limits,
|
|
{ cause, ...parserLimitRecovery(cause.reported_limits) },
|
|
);
|
|
if (cause instanceof PythonPycFormatFailure)
|
|
return cause.kind === "unsupported"
|
|
? new AnalysisUnsupportedTargetError(
|
|
OPERATION,
|
|
input.path,
|
|
cause.message,
|
|
{ cause },
|
|
)
|
|
: new AnalysisInputError(OPERATION, { cause }, [
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_format",
|
|
message: `Selected pyc ${input.path}: ${cause.message}`,
|
|
},
|
|
]);
|
|
if (
|
|
origin === "owned-or-classified" &&
|
|
cause instanceof ArtifactReaderFailure
|
|
) {
|
|
if (cause.reason === "limit")
|
|
return new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"file-size",
|
|
cause.message,
|
|
{ max_file_bytes: input.max_file_bytes },
|
|
{
|
|
cause,
|
|
...parserLimitRecovery({ max_file_bytes: input.max_file_bytes }),
|
|
},
|
|
);
|
|
if (cause.reason === "path" || cause.reason === "format")
|
|
return new AnalysisInputError(
|
|
OPERATION,
|
|
{ cause, ...stableArtifactInputRecovery(cause) },
|
|
[
|
|
{
|
|
path: ["path"],
|
|
reason: "invalid_format",
|
|
message: cause.message,
|
|
},
|
|
],
|
|
);
|
|
}
|
|
const fileFailure = projectSelectedArtifactFileFailure(
|
|
cause,
|
|
OPERATION,
|
|
input.path,
|
|
"pyc",
|
|
);
|
|
if (fileFailure !== undefined) return fileFailure;
|
|
return new ProviderAdapterError(PYTHON_PYC_PROVIDER_IDENTITY.id, OPERATION, {
|
|
cause,
|
|
diagnostics: {
|
|
path: input.path,
|
|
reason: cause instanceof Error ? cause.message : String(cause),
|
|
},
|
|
});
|
|
};
|
|
|
|
const failureFor = (
|
|
cause: unknown,
|
|
input: InspectPythonPycInput,
|
|
snapshot?: StableArtifactSnapshot,
|
|
) => {
|
|
const primary = failureForPrimary(artifactFilePrimaryFacts(cause), input);
|
|
return projectStableArtifactFailure(
|
|
cause,
|
|
primary,
|
|
input.path,
|
|
PYTHON_PYC_PROVIDER_IDENTITY.id,
|
|
OPERATION,
|
|
snapshot,
|
|
);
|
|
};
|
|
|
|
/** Portable parser over one bounded, identity-checked regular file. */
|
|
export class PythonPycProvider implements PythonPycPort {
|
|
readonly identity = PYTHON_PYC_PROVIDER_IDENTITY;
|
|
readonly #resources = new ArtifactResourceScope();
|
|
|
|
async close(): Promise<void> {
|
|
try {
|
|
await this.#resources.close();
|
|
} catch (cause: unknown) {
|
|
throw projectArtifactCloseFailure(
|
|
cause,
|
|
PYTHON_PYC_PROVIDER_IDENTITY.id,
|
|
OPERATION,
|
|
);
|
|
}
|
|
}
|
|
|
|
async inspect(input: InspectPythonPycInput, options?: ExecutionOptions) {
|
|
let snapshot: StableArtifactSnapshot | undefined;
|
|
try {
|
|
snapshot = await this.#resources.run(() =>
|
|
readStableArtifact(
|
|
input.path,
|
|
input.max_file_bytes,
|
|
options?.signal,
|
|
undefined,
|
|
this.#resources,
|
|
),
|
|
);
|
|
return ok(
|
|
parsePythonPyc(
|
|
snapshot.bytes,
|
|
{
|
|
path: input.path,
|
|
sha256: snapshot.sha256,
|
|
bytes: snapshot.bytes.length,
|
|
},
|
|
input,
|
|
),
|
|
);
|
|
} catch (cause) {
|
|
return err(failureFor(cause, input, snapshot));
|
|
}
|
|
}
|
|
}
|