mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(artifacts): retain source identity failures after relocation
This commit is contained in:
@@ -135,7 +135,21 @@ export class ArtifactFileSource {
|
||||
const source = await this.#opening;
|
||||
const [observed, selected] = await Promise.all([
|
||||
source.handle.stat(),
|
||||
lstat(this.path),
|
||||
lstat(this.path).catch((cause: unknown) => {
|
||||
// Admission already established this selected name. Its disappearance
|
||||
// or a replaced ancestor is a source change, not an unrelated I/O fault.
|
||||
if (
|
||||
cause instanceof Error &&
|
||||
"code" in cause &&
|
||||
(cause.code === "ENOENT" || cause.code === "ENOTDIR")
|
||||
)
|
||||
throw new ArtifactReaderFailure(
|
||||
"integrity",
|
||||
`${this.diagnosticLabel} source identity changed: ${this.path}`,
|
||||
{ cause },
|
||||
);
|
||||
throw cause;
|
||||
}),
|
||||
]);
|
||||
if (
|
||||
!sameRegularFileState(source.initial, observed) ||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { withPyInstallerReader } from "../../../tests/fixtures/pyinstallerReader.js";
|
||||
import { join } from "node:path";
|
||||
import { dirname, join } from "node:path";
|
||||
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { writeFile, type FileHandle } from "node:fs/promises";
|
||||
import { rename, unlink, writeFile, type FileHandle } from "node:fs/promises";
|
||||
import { buffer } from "node:stream/consumers";
|
||||
import { deflateSync } from "node:zlib";
|
||||
import { setImmediate } from "node:timers/promises";
|
||||
@@ -10,6 +10,8 @@ import { expect, it, vi } from "vitest";
|
||||
import { PyInstallerArtifactReader } from "../PyInstallerArtifactReader.js";
|
||||
import { OwnedFileHandle } from "../../filesystem/OwnedFileHandle.js";
|
||||
import { hashReadable } from "../ArtifactHash.js";
|
||||
import { translateArtifactFailure } from "../ArtifactOperationFailure.js";
|
||||
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
||||
import { marshalCode } from "../../python/PythonPyc.fixture.js";
|
||||
import { PyInstallerPyzStore } from "./PyInstallerPyzStore.js";
|
||||
import {
|
||||
@@ -279,6 +281,59 @@ it("binds materialization to the inventoried digest and checks cached carrier fr
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["ENOENT", "ENOTDIR"] as const)(
|
||||
"retains changed-source evidence when an admitted carrier becomes unaddressable (%s)",
|
||||
async (code) => {
|
||||
const body = Buffer.from("observed before the source moved");
|
||||
const bytes = archive(body);
|
||||
await withPyInstallerReader(bytes, async (reader, handle, path) => {
|
||||
await reader.prepareContainer(hash(bytes));
|
||||
const [entry] = await members(reader);
|
||||
if (entry === undefined) throw new Error("Missing original member");
|
||||
const observed = await hashReadable(await reader.open(entry));
|
||||
expect(observed).toMatchObject({
|
||||
sha256: hash(body),
|
||||
bytes: body.length,
|
||||
});
|
||||
const moved = code === "ENOENT" ? path : dirname(path);
|
||||
await rename(moved, `${moved}.moved`);
|
||||
if (code === "ENOTDIR") await writeFile(moved, "replaced ancestor");
|
||||
try {
|
||||
const attempts = [
|
||||
() => reader.prepareContainer(hash(bytes)),
|
||||
() => members(reader),
|
||||
() => reader.open(entry),
|
||||
];
|
||||
for (const attempt of attempts) {
|
||||
const cause = await attempt().then(
|
||||
() => undefined,
|
||||
(failure: unknown) => failure,
|
||||
);
|
||||
expect(cause).toMatchObject({
|
||||
reason: "integrity",
|
||||
message: `PyInstaller source identity changed: ${path}`,
|
||||
});
|
||||
expect(
|
||||
projectAnalysisError(
|
||||
translateArtifactFailure("extract_artifact", cause),
|
||||
),
|
||||
).toMatchObject({
|
||||
category: "integrity_mismatch",
|
||||
details: {
|
||||
reason: "integrity",
|
||||
detail: `PyInstaller source identity changed: ${path}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
expect(handle.fd).toBeGreaterThanOrEqual(0);
|
||||
} finally {
|
||||
if (code === "ENOTDIR") await unlink(moved);
|
||||
await rename(`${moved}.moved`, moved);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("bounds descriptor reads while hashing a selected stored member", async () => {
|
||||
const payload = Buffer.alloc(2 * 1024 * 1024, 0x61);
|
||||
await withPyInstallerReader(archive(payload), async (reader, handle) => {
|
||||
|
||||
Reference in New Issue
Block a user