refactor(artifacts): reuse admitted sources for extraction

This commit is contained in:
morluto committed 2026-10-27 08:18:04 +08:00
1 parent 3fcbabe593
commit cb0c73246d
6 files changed
+447 -74

No files matched your search

+39 -26
View File
@@ -14,6 +14,40 @@ import { hashReadable } from "./ArtifactHash.js";
import { ArtifactReaderFailure } from "./ArtifactReader.js";
import { ArtifactResourceScope } from "./ArtifactResourceScope.js";
/** Verify an admitted descriptor against the regular file still named by its path. */
export const assertArtifactFileSelectedSource = async (
path: string,
source: StableRegularFileDescriptor,
diagnosticLabel: string,
): Promise<void> => {
const [observed, selected] = await Promise.all([
source.handle.stat(),
lstat(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",
`${diagnosticLabel} source identity changed: ${path}`,
{ cause },
);
throw cause;
}),
]);
if (
!sameRegularFileState(source.initial, observed) ||
!sameRegularFileState(source.initial, selected)
)
throw new ArtifactReaderFailure(
"integrity",
`${diagnosticLabel} source identity changed: ${path}`,
);
};
/** One persistent descriptor and hash binding, borrowing caller-admitted sources. */
export class ArtifactFileSource {
readonly #scope = new ArtifactResourceScope();
@@ -140,32 +174,11 @@ export class ArtifactFileSource {
assertArtifactReadActive(signal);
this.#opening ??= this.#admit(signal);
const source = await this.#opening;
const [observed, selected] = await Promise.all([
source.handle.stat(),
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) ||
!sameRegularFileState(source.initial, selected)
)
throw new ArtifactReaderFailure(
"integrity",
`${this.diagnosticLabel} source identity changed: ${this.path}`,
);
await assertArtifactFileSelectedSource(
this.path,
source,
this.diagnosticLabel,
);
assertArtifactReadActive(signal);
return source;
}
+37 -30
View File
@@ -1,4 +1,5 @@
import { assertAdmittedRootDigest } from "../inventory/classify.js";
import { assertArtifactFileSelectedSource } from "../ArtifactFileSource.js";
import { assertArtifactSelectedSource } from "../ArtifactSelectedSource.js";
import { hashStableRootArtifactHandle } from "../inventory/hashStableRootArtifact.js";
import { absoluteCallerPath } from "../../filesystem/callerPath.js";
@@ -52,7 +53,7 @@ import { AnalysisUnsupportedTargetError } from "../../domain/analysisErrorCore.j
import type { BinaryTarget } from "../../domain/binaryTargetTypes.js";
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
import { scanArtifactInventoryForExtractionInScope } from "../inventory/ArtifactInventory.js";
import type { TransferredZipInventorySource } from "../inventory/scanCanonical.js";
import type { TransferredArtifactInventorySource } from "../inventory/scanCanonical.js";
import type { ArtifactIntegrityPolicyName } from "../../domain/artifactIntegrityPolicy.js";
/** Local extraction input with the output root chosen by the adapter. */
@@ -86,7 +87,7 @@ const extractArtifactInScope = async (
if (input.expectedRootSha256 !== undefined)
await assertArtifactSelectedSource(sourcePath, input.selectedPath);
const admission = await requireExtractableFormat(sourcePath, input, signal);
const { snapshot, zipSource } =
const { snapshot, transferredSource } =
await scanArtifactInventoryForExtractionInScope(
input.inputPath,
{
@@ -102,28 +103,28 @@ const extractArtifactInScope = async (
},
admission.probeFacts,
);
const activeZipSource =
zipSource === undefined
const activeSource =
transferredSource === undefined
? undefined
: { ...zipSource, releaseAttempted: false };
: { ...transferredSource, releaseAttempted: false };
try {
return await materializeArtifactInventoryInScope(
input,
sourcePath,
snapshot,
signal,
activeZipSource,
activeSource,
admission.probeFacts,
);
} catch (cause: unknown) {
if (activeZipSource !== undefined && cause instanceof ArtifactReaderFailure)
if (activeSource !== undefined && cause instanceof ArtifactReaderFailure)
cause.retainPartialObservation({
kind: "artifact-inventory",
inventory: snapshot,
});
const attempt = await releaseTransferredZipSource(
const attempt = await releaseTransferredArtifactSource(
input.resourceScope,
activeZipSource,
activeSource,
);
if (attempt?.kind === "failed")
throw input.resourceScope.cleanupFailure(
@@ -135,32 +136,33 @@ const extractArtifactInScope = async (
}
};
interface ActiveZipInventorySource extends TransferredZipInventorySource {
interface ActiveArtifactInventorySource extends TransferredArtifactInventorySource {
releaseAttempted: boolean;
}
const releaseTransferredZipSource = async (
const releaseTransferredArtifactSource = async (
resourceScope: ArtifactResourceScope,
zipSource: ActiveZipInventorySource | undefined,
transferredSource: ActiveArtifactInventorySource | undefined,
): Promise<ArtifactCleanupAttempt | undefined> => {
if (zipSource === undefined || zipSource.releaseAttempted) return undefined;
zipSource.releaseAttempted = true;
return resourceScope.release(zipSource.owner);
if (transferredSource === undefined || transferredSource.releaseAttempted)
return undefined;
transferredSource.releaseAttempted = true;
return resourceScope.release(transferredSource.owner);
};
/** Materialize a scanned inventory, borrowing its active ZIP source when supplied. */
/** Materialize a scanned inventory, borrowing its admitted source when supplied. */
export const materializeArtifactInventoryInScope = async (
input: ArtifactExtractionInput,
sourcePath: string,
snapshot: ArtifactInventorySnapshot,
signal?: AbortSignal,
zipSource?: ActiveZipInventorySource,
transferredSource?: ActiveArtifactInventorySource,
probeFacts?: ArtifactEmbeddedFormatProbeFacts,
): Promise<ArtifactExtractionResult> => {
abortIfNeeded(signal);
// A directory source must never acquire its own output as a new member on
// the verification traversal. Resolve the selected parent before any write.
if (zipSource === undefined) {
if (transferredSource === undefined) {
const canonicalSource = await realpath(sourcePath);
if ((await lstat(canonicalSource)).isDirectory()) {
const { canonicalOutput } = await SafeOutputTree.resolveDestination(
@@ -271,7 +273,7 @@ export const materializeArtifactInventoryInScope = async (
inventory: snapshot,
selected,
signal,
zipSource,
transferredSource,
probeFacts,
});
};
@@ -295,7 +297,7 @@ const materializeSelection = async ({
inventory,
selected,
signal,
zipSource,
transferredSource,
probeFacts,
}: {
readonly input: ArtifactExtractionInput;
@@ -303,7 +305,7 @@ const materializeSelection = async ({
readonly inventory: ArtifactInventorySnapshot;
readonly selected: readonly SelectedOccurrence[];
readonly signal: AbortSignal | undefined;
readonly zipSource: ActiveZipInventorySource | undefined;
readonly transferredSource: ActiveArtifactInventorySource | undefined;
readonly probeFacts: ArtifactEmbeddedFormatProbeFacts | undefined;
}): Promise<ArtifactExtractionResult> => {
const byPath = new Map<string, SelectedOccurrence>();
@@ -316,12 +318,18 @@ const materializeSelection = async ({
);
byPath.set(path, item);
}
if (transferredSource !== undefined && "kind" in transferredSource.admission)
await assertArtifactFileSelectedSource(
sourcePath,
transferredSource.admission.source,
"Artifact extraction",
);
const reader = await createReader(
sourcePath,
input,
inventory.manifest.root_format,
signal,
zipSource,
transferredSource,
probeFacts,
);
const readerOwner: ArtifactResourceOwner = {
@@ -362,9 +370,9 @@ const materializeSelection = async ({
[closeAttempt],
{ kind: "artifact-inventory", inventory },
);
const sourceClose = await releaseTransferredZipSource(
const sourceClose = await releaseTransferredArtifactSource(
input.resourceScope,
zipSource,
transferredSource,
);
if (sourceClose?.kind === "failed")
throw input.resourceScope.cleanupFailure(
@@ -372,7 +380,7 @@ const materializeSelection = async ({
[sourceClose],
{ kind: "artifact-inventory", inventory },
);
if (zipSource !== undefined) abortIfNeeded(signal);
if (transferredSource !== undefined) abortIfNeeded(signal);
extracted.sort((left, right) =>
compareUnicodeCodePoints(left.relative_path, right.relative_path),
);
@@ -644,13 +652,12 @@ const createReader = async (
>,
containerFormat: ArtifactGraphManifest["root_format"],
signal?: AbortSignal,
zipSource?: ActiveZipInventorySource,
transferredSource?: ActiveArtifactInventorySource,
probeFacts?: ArtifactEmbeddedFormatProbeFacts,
): Promise<ArtifactReader> => {
const transferredZip =
zipSource !== undefined && isZipPackageFormat(containerFormat);
let format: ArtifactReaderFormat;
if (transferredZip) format = containerFormat;
if (transferredSource !== undefined && isZipPackageFormat(containerFormat))
format = containerFormat;
else if (
containerFormat === "godot-pck" ||
containerFormat === "pyinstaller" ||
@@ -671,7 +678,7 @@ const createReader = async (
environment: input.environment,
unreadableDirectories: "fail",
selectedPath: absoluteCallerPath(input.selectedPath ?? input.inputPath),
admission: transferredZip ? zipSource.source : undefined,
admission: transferredSource?.admission,
asarHeaderAdmission:
format === "asar"
? (input.asarHeaderAdmission ??
@@ -0,0 +1,348 @@
import {
appendFile,
mkdir,
rename,
readFile,
stat,
symlink,
unlink,
writeFile,
} from "node:fs/promises";
import { createHash } from "node:crypto";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { buildGodotPck } from "../../../tests/fixtures/godotPck.js";
import { ArtifactResourceScope } from "../ArtifactResourceScope.js";
import { GodotPckArtifactReader } from "../GodotPckArtifactReader.js";
import { NodeSeaArtifactReader } from "../NodeSeaArtifactReader.js";
import { PyInstallerArtifactReader } from "../PyInstallerArtifactReader.js";
import { cArchiveExecutable } from "../pyinstaller/CArchive.fixture.js";
import { nodeSeaElf } from "../nodeSea/NodeSea.fixture.js";
import * as cArchive from "../pyinstaller/CArchiveLayout.js";
import * as nodeSea from "../nodeSea/NodeSeaBlob.js";
import * as godot from "../GodotPckLocation.js";
import { extractArtifact } from "./ArtifactExtraction.js";
const cases = [
{
format: "pyinstaller" as const,
inputFormat: "pe" as const,
reader: PyInstallerArtifactReader as {
prototype: { close(): Promise<void> };
},
bytes: () =>
cArchiveExecutable({
carrier: "pe",
layout: "2.1",
pythonVersion: 314,
members: [
{
name: String.raw`pkg\payload.txt`,
typeCode: "x",
payload: Buffer.from("fixture"),
compress: false,
},
],
}),
member: "pkg/payload.txt",
observe: () => vi.spyOn(cArchive, "findPyInstallerCookie"),
},
{
format: "node-sea" as const,
inputFormat: "elf" as const,
reader: NodeSeaArtifactReader as { prototype: { close(): Promise<void> } },
bytes: () => nodeSeaElf({ codePath: "payload.js", main: "fixture" }),
member: "payload.js",
observe: () => vi.spyOn(nodeSea, "decodeNodeSeaBlob"),
},
{
format: "godot-pck" as const,
inputFormat: "godot-pck" as const,
reader: GodotPckArtifactReader as { prototype: { close(): Promise<void> } },
bytes: () => buildGodotPck(3, [{ path: "payload.txt", data: "fixture" }]),
member: "payload.txt",
observe: () => vi.spyOn(godot, "locateGodotPck"),
},
];
it.each(cases)(
"extracts $format using the inventory admission once",
async ({ format, inputFormat, reader, bytes, member, observe }) => {
const directory = await createTestTempDirectory("rea-embedded-extraction-");
const path = join(directory, "source");
const output = join(directory, "out");
const carrier = bytes();
await writeFile(path, carrier);
const scope = new ArtifactResourceScope();
const observation = observe();
const close = vi.spyOn(reader.prototype, "close");
const release = vi.spyOn(scope, "release");
try {
const result = await extractArtifact({
inputPath: path,
inputFormat,
outputRoot: output,
environment: {},
integrityPolicy: "fail",
resourceScope: scope,
expectedRootSha256: createHash("sha256").update(carrier).digest("hex"),
selectedPath: path,
});
expect(result.manifest.root_format).toBe(format);
expect(await readFile(join(output, member), "utf8")).toBe("fixture");
expect(observation).toHaveBeenCalledTimes(1);
expect(close).toHaveBeenCalledTimes(2);
const rootRelease = release.mock.calls.findIndex(
([owner]) =>
owner.kind === "file-handle" &&
owner.resource === `root artifact descriptor for ${path}`,
);
expect(rootRelease).toBeGreaterThanOrEqual(0);
expect(
release.mock.calls
.slice(0, rootRelease)
.filter(([owner]) => owner.kind === "reader"),
).toHaveLength(2);
expect(scope.hasPending).toBe(false);
} finally {
await scope.close();
observation.mockRestore();
close.mockRestore();
release.mockRestore();
}
},
);
it.each(cases)(
"rejects changed $format bytes before materializing admitted facts",
async ({ inputFormat, reader, bytes }) => {
const directory = await createTestTempDirectory(
"rea-embedded-extraction-stale-",
);
const path = join(directory, "source");
const output = join(directory, "out");
await writeFile(path, bytes());
const scope = new ArtifactResourceScope();
const original = reader.prototype.close;
let closes = 0;
const close = vi
.spyOn(reader.prototype, "close")
.mockImplementation(async function (this: { close(): Promise<void> }) {
await original.call(this);
if (++closes === 1) await appendFile(path, "changed");
});
try {
await expect(
extractArtifact({
inputPath: path,
inputFormat,
outputRoot: output,
environment: {},
integrityPolicy: "fail",
resourceScope: scope,
}),
).rejects.toMatchObject({ reason: "integrity" });
await expect(stat(output)).rejects.toMatchObject({ code: "ENOENT" });
expect(scope.hasPending).toBe(false);
} finally {
await scope.close();
close.mockRestore();
}
},
);
it.each(
cases.flatMap((fixture) =>
["cancel", "retry", "mixed"].map((mode) => ({ ...fixture, mode })),
),
)(
"retains $format finalization semantics during $mode",
async ({ inputFormat, reader, bytes, mode }) => {
const directory = await createTestTempDirectory(
"rea-embedded-extraction-cleanup-",
);
const path = join(directory, "source");
const output = join(directory, "out");
await writeFile(path, bytes());
const scope = new ArtifactResourceScope();
const controller = new AbortController();
const failure = new Error("root close denied");
const originalReaderClose = reader.prototype.close;
let readerAttempts = 0;
const readerClose = vi
.spyOn(reader.prototype, "close")
.mockImplementation(async function (this: { close(): Promise<void> }) {
if (++readerAttempts === 2 && mode === "mixed")
throw new Error("reader close denied");
await originalReaderClose.call(this);
});
const originalRelease = scope.release.bind(scope);
let rootAttempts = 0;
let rootClose: { mockRestore(): void } | undefined;
const release = vi
.spyOn(scope, "release")
.mockImplementation(async (owner) => {
if (
owner.kind === "file-handle" &&
owner.resource === `root artifact descriptor for ${path}`
) {
const originalClose = owner.handle.close.bind(owner.handle);
rootClose = vi
.spyOn(owner.handle, "close")
.mockImplementation(async () => {
rootAttempts++;
expect(readerAttempts).toBeGreaterThanOrEqual(2);
if (rootAttempts === 1) {
if (mode === "cancel") controller.abort(failure);
else throw failure;
}
await originalClose();
});
}
return originalRelease(owner);
});
try {
const execution = extractArtifact(
{
inputPath: path,
inputFormat,
outputRoot: output,
environment: {},
integrityPolicy: "fail",
resourceScope: scope,
},
controller.signal,
);
if (mode === "cancel")
await expect(execution).rejects.toMatchObject({
reason: "cancelled",
cause: failure,
});
else
await expect(execution).rejects.toMatchObject({
cleanup: {
resources: expect.arrayContaining([
`root artifact descriptor for ${path}`,
]),
},
});
await expect(stat(output)).rejects.toMatchObject({ code: "ENOENT" });
if (mode === "mixed")
expect(scope.cleanupResources).toEqual([
`artifact reader for ${path}`,
`root artifact descriptor for ${path}`,
]);
else if (mode === "retry")
expect(scope.cleanupResources).toEqual([
`root artifact descriptor for ${path}`,
]);
await scope.close();
expect(rootAttempts).toBe(mode === "cancel" ? 1 : 2);
expect(readerAttempts).toBe(mode === "mixed" ? 3 : 2);
expect(scope.hasPending).toBe(false);
} finally {
await scope.close().catch(() => undefined);
rootClose?.mockRestore();
release.mockRestore();
readerClose.mockRestore();
}
},
);
it.each(cases)(
"rejects a retargeted selected path before publishing $format extraction",
async ({ inputFormat, reader, bytes }) => {
const directory = await createTestTempDirectory(
"rea-embedded-extraction-selection-",
);
const path = join(directory, "source");
const selectedPath = join(directory, "selected");
const replacement = join(directory, "replacement");
const output = join(directory, "out");
const carrier = bytes();
await writeFile(path, carrier);
await writeFile(replacement, carrier);
await symlink(path, selectedPath);
const scope = new ArtifactResourceScope();
const originalClose = reader.prototype.close;
let closes = 0;
const close = vi
.spyOn(reader.prototype, "close")
.mockImplementation(async function (this: { close(): Promise<void> }) {
await originalClose.call(this);
if (++closes === 2) {
await unlink(selectedPath);
await symlink(replacement, selectedPath);
}
});
try {
await expect(
extractArtifact({
inputPath: path,
selectedPath,
expectedRootSha256: createHash("sha256")
.update(carrier)
.digest("hex"),
inputFormat,
outputRoot: output,
environment: {},
integrityPolicy: "fail",
resourceScope: scope,
}),
).rejects.toMatchObject({ reason: "integrity" });
await expect(stat(output)).rejects.toMatchObject({ code: "ENOENT" });
expect(closes).toBe(2);
expect(scope.hasPending).toBe(false);
} finally {
await scope.close();
close.mockRestore();
}
},
);
it.each(cases)(
"rejects a replacement at the admitted $format pathname",
async ({ inputFormat, reader, bytes }) => {
const directory = await createTestTempDirectory(
"rea-embedded-extraction-replacement-",
);
const selectedDirectory = join(directory, "selected");
await mkdir(selectedDirectory);
const path = join(selectedDirectory, "source");
const output = join(directory, "out");
await writeFile(path, bytes());
const scope = new ArtifactResourceScope();
const originalClose = reader.prototype.close;
let closes = 0;
const close = vi
.spyOn(reader.prototype, "close")
.mockImplementation(async function (this: { close(): Promise<void> }) {
await originalClose.call(this);
if (++closes === 1) {
// Moving the parent leaves the admitted regular file's metadata intact.
await rename(selectedDirectory, join(directory, "admitted"));
await mkdir(selectedDirectory);
await writeFile(path, Buffer.from("replacement"));
}
});
try {
await expect(
extractArtifact({
inputPath: path,
inputFormat,
outputRoot: output,
environment: {},
integrityPolicy: "fail",
resourceScope: scope,
}),
).rejects.toMatchObject({ reason: "integrity" });
await expect(stat(output)).rejects.toMatchObject({ code: "ENOENT" });
expect(closes).toBe(1);
expect(scope.hasPending).toBe(false);
} finally {
await scope.close();
close.mockRestore();
}
},
);
+2 -2
View File
@@ -9,7 +9,7 @@ import { abortIfNeeded } from "../ArtifactHash.js";
import {
scanCanonicalArtifactInventoryInScope,
scanCanonicalArtifactInventoryForExtractionInScope,
type TransferredZipInventorySource,
type TransferredArtifactInventorySource,
} from "./scanCanonical.js";
import type { ArtifactInventoryOptions } from "./types.js";
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
@@ -46,7 +46,7 @@ export const scanArtifactInventoryForExtractionInScope = async (
probeFacts?: ArtifactEmbeddedFormatProbeFacts,
): Promise<{
readonly snapshot: ArtifactInventorySnapshot;
readonly zipSource: TransferredZipInventorySource | undefined;
readonly transferredSource: TransferredArtifactInventorySource | undefined;
}> => {
abortIfNeeded(options.signal);
const path = await realpath(inputPath);
+18 -13
View File
@@ -52,9 +52,9 @@ import {
type ArtifactInventoryOptions,
} from "./types.js";
export interface TransferredZipInventorySource {
readonly source: RootInventorySource;
export interface TransferredArtifactInventorySource {
readonly owner: ArtifactResourceOwner;
readonly admission: NonNullable<Parameters<typeof createReader>[3]>;
}
export const scanCanonicalArtifactInventoryInScope = async (
@@ -74,7 +74,7 @@ export const scanCanonicalArtifactInventoryInScope = async (
)
).snapshot;
/** Keep the admitted ZIP descriptor alive for extraction of this inventory. */
/** Keep the admitted source and parsed format facts alive for extraction of this inventory. */
export const scanCanonicalArtifactInventoryForExtractionInScope = async (
path: string,
options: ArtifactInventoryOptions,
@@ -82,13 +82,13 @@ export const scanCanonicalArtifactInventoryForExtractionInScope = async (
selectedPath: string = path,
): Promise<{
readonly snapshot: ArtifactInventorySnapshot;
readonly zipSource: TransferredZipInventorySource | undefined;
readonly transferredSource: TransferredArtifactInventorySource | undefined;
}> =>
scanCanonical(
path,
options,
createReader,
"transfer-zip",
"transfer-source",
probeFacts,
selectedPath,
);
@@ -97,12 +97,12 @@ const scanCanonical = async (
path: string,
options: ArtifactInventoryOptions,
readerFactory: typeof createReader,
rootDisposition: "release" | "transfer-zip",
rootDisposition: "release" | "transfer-source",
probeFacts?: ArtifactEmbeddedFormatProbeFacts,
selectedPath: string = path,
): Promise<{
readonly snapshot: ArtifactInventorySnapshot;
readonly zipSource: TransferredZipInventorySource | undefined;
readonly transferredSource: TransferredArtifactInventorySource | undefined;
}> => {
const integrity = options.integrity ?? STRICT_INTEGRITY_POLICY;
const metadata = await lstat(path);
@@ -247,18 +247,20 @@ const scanCanonical = async (
options,
rootSource,
ownedReaders,
readerAdmission,
outcome,
rootDisposition:
rootDisposition === "transfer-zip" &&
rootDisposition === "transfer-source" &&
outcome.kind === "completed" &&
rootSource !== undefined &&
isZipPackageFormat(rootFormat)
(isZipPackageFormat(rootFormat) ||
(readerAdmission !== undefined && "kind" in readerAdmission))
? "transfer"
: "release",
});
if (cleanup.failure !== undefined) throw cleanup.failure;
if (outcome.kind === "failed") throw outcome.cause;
return { snapshot: outcome.snapshot, zipSource: cleanup.transferred };
return { snapshot: outcome.snapshot, transferredSource: cleanup.transferred };
};
type InventoryScanOutcome =
@@ -273,6 +275,7 @@ const cleanupInventoryOwners = async ({
options,
rootSource,
ownedReaders,
readerAdmission,
outcome,
rootDisposition,
}: {
@@ -280,11 +283,12 @@ const cleanupInventoryOwners = async ({
readonly options: ArtifactInventoryOptions;
readonly rootSource: RootInventorySource | undefined;
readonly ownedReaders: readonly ArtifactReader[];
readonly readerAdmission: Parameters<typeof createReader>[3];
readonly outcome: InventoryScanOutcome;
readonly rootDisposition: "release" | "transfer";
}): Promise<{
readonly failure: ArtifactReaderFailure | undefined;
readonly transferred: TransferredZipInventorySource | undefined;
readonly transferred: TransferredArtifactInventorySource | undefined;
}> => {
let cleanupFailure: ArtifactReaderFailure | undefined;
const readerOwners: ArtifactResourceOwner[] = [...ownedReaders]
@@ -319,11 +323,12 @@ const cleanupInventoryOwners = async ({
rootDisposition === "transfer" &&
cleanupFailure === undefined &&
rootSource !== undefined &&
rootOwner !== undefined
rootOwner !== undefined &&
readerAdmission !== undefined
)
return {
failure: undefined,
transferred: { source: rootSource, owner: rootOwner },
transferred: { owner: rootOwner, admission: readerAdmission },
};
if (rootOwner !== undefined) {
const attempt = await options.resourceScope.release(rootOwner);
@@ -307,10 +307,10 @@ it.each([
expect(extracted.manifest.root_format).toBe(format);
expect(extracted.artifacts.length).toBeGreaterThan(0);
expect(probesBeforeHash).toEqual(probes);
// Preflight's cookie also supplies inventory. Extraction opens its
// own reader and establishes that reader's cookie independently.
// Preflight's cookie supplies inventory and the transferred extraction
// admission; descriptor/path and digest checks still validate freshness.
if (format === "pyinstaller")
expect(pyInstaller).toHaveBeenCalledTimes(2);
expect(pyInstaller).toHaveBeenCalledTimes(1);
},
);
} finally {