fix(environment): return input permission refusals through Result

This commit is contained in:
morluto committed 2026-10-24 08:22:13 +08:00
1 parent 6323013d83
commit 11feab01da
4 files changed
+24 -17

No files matched your search

@@ -20,10 +20,7 @@ import {
} from "../../../tests/fixtures/environmentServe.js";
import { SafeOutputTree } from "../../artifacts/SafeOutputTree.js";
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
import {
AnalysisAccessDeniedError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
import {
exportEnvironmentBundle,
readEnvironmentExportInputs,
@@ -190,9 +187,14 @@ it.skipIf(process.platform === "win32")(
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisAccessDeniedError",
_tag: "AnalysisOutputError",
operation: "environment-export",
diagnostics: { output_directory: directory },
diagnostics: {
output_directory: directory,
path: join(directory, "README.md"),
system_code: "EACCES",
boundary: "filesystem-write",
},
},
});
expect(await readdir(directory)).toEqual([]);
@@ -232,12 +234,15 @@ it.skipIf(process.platform === "win32")(
});
if (result.ok) throw new Error("expected a permission refusal");
expect(result.error).toMatchObject({
_tag: "AnalysisAccessDeniedError",
_tag: "AnalysisOutputError",
diagnostics: { output_directory: directory },
});
if (!(result.error instanceof AnalysisAccessDeniedError))
throw result.error;
expect(result.error.path.startsWith(`${directory}/`)).toBe(true);
if (!(result.error instanceof AnalysisOutputError)) throw result.error;
expect(result.error.diagnostics).toMatchObject({
path: expect.stringContaining(`${directory}/`),
system_code: "EACCES",
boundary: "filesystem-write",
});
expect(await readdir(directory)).not.toEqual([]);
} finally {
writer.mockRestore();
@@ -268,6 +268,11 @@ const registerBundleDestinationAdmissionCases = () => {
files.capturePath,
);
if (readDenied.ok) throw new Error("Expected read denial");
expect(readDenied.error).toMatchObject({
_tag: "AnalysisAccessDeniedError",
path: files.manifestPath,
cause: { code: "EACCES", path: files.manifestPath },
});
expect(projectAnalysisError(readDenied.error)).toMatchObject({
code: "access_denied",
details: { boundary: "filesystem-read" },
@@ -193,11 +193,6 @@ const hostFailurePath = (cause: unknown): string | undefined => {
return undefined;
};
/** Host permission failures retain their typed cause; unexpected failures propagate. */
const rethrowUnlessDenied = (cause: unknown, path: string): never => {
throw accessDeniedError(cause, path) ?? cause;
};
/** A path that is not a readable regular file, including inline JSON text offered as one. */
const NOT_A_FILE_CODES = new Set([
"ENOENT",
@@ -300,7 +295,9 @@ const readInput = async (
`${path} could not be read as the ${role} file.`,
),
);
return rethrowUnlessDenied(cause, path);
const denied = accessDeniedError(cause, path);
if (denied !== undefined) return err(denied);
throw cause;
}
let parsed: ReturnType<typeof parseUtf8Json>;
try {
@@ -64,7 +64,7 @@ it("finds state entities using the recorded path's repeated separators", () => {
["GET", "/api//books/1", { id: 1, title: "Book" }],
]);
const tool = serving.source.manifest.tools.find(
(candidate) => candidate.path_template === "/api//books/{id}",
(candidate) => candidate.path_template === "/api//books/{numeric}",
);
if (tool === undefined) throw new Error("Missing double-separator item tool");
const result = answerFromState(serving, tool, { id: "1" });