fix(reference)!: retain source and metadata cleanup owners

Require a caller-owned artifact resource scope for source traversal and Git metadata reads. Preserve cancellation, filesystem reasons, partial entries and bytes, and cleanup observations through import and the CLI. Transfer exact failed file owners at regular-file admission and close boundaries, including multiple failures encountered by Git reference resolution.

Validation: 169 focused tests passed across 26 files; two Windows-only cases skipped. Build, typecheck, lint, formatting, knip, architecture guards, discovery and docs checks passed.

BREAKING CHANGE: Reference source readers and imports require an ArtifactResourceScope. VCS metadata reads return Result, and CLI import failures include observed details and partial evidence.
This commit is contained in:
morluto committed 2026-10-11 06:10:10 +08:00
1 parent 215001a1ce
commit 53e7fadbd0
30 files changed
+1450 -222

No files matched your search

+44 -5
View File
@@ -4,8 +4,12 @@ import { dirname } from "node:path";
import {
NonRegularFileReadError,
RegularFileAdmissionFailure,
openRegularFile,
} from "../filesystem/RegularFile.js";
import { OwnedFileHandle } from "../filesystem/OwnedFileHandle.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import type { AnalysisCleanupObservation } from "../domain/analysisErrorBase.js";
import {
entryFailure,
@@ -195,9 +199,39 @@ const finalizeFileRead = async (
export const readStableFile = async (
request: StableFileRequest,
): Promise<{
readonly entry: ReferenceSourceEntry;
readonly cleanup?: AnalysisCleanupObservation;
}> => {
let owner: OwnedFileHandle | undefined;
let cleanup: AnalysisCleanupObservation | undefined;
let entry: ReferenceSourceEntry;
try {
entry = await readStableFileEntry(request, (admitted) => {
owner = admitted;
});
} finally {
if (owner !== undefined) {
const result = await request.resources.release({
kind: "file-handle",
handle: owner,
resource: request.absolute,
});
if (result.kind === "failed")
cleanup = ArtifactReaderFailure.cleanupObservation(
result.cause,
request.absolute,
);
}
}
return { entry, ...(cleanup === undefined ? {} : { cleanup }) };
};
const readStableFileEntry = async (
request: StableFileRequest,
retain: (owner: OwnedFileHandle) => void,
): Promise<ReferenceSourceEntry> => {
const { root, rootIdentity, absolute, path, expected, signal } = request;
let handle: FileHandle | undefined;
try {
const parentBefore = await validateDirectory(
root,
@@ -213,7 +247,11 @@ export const readStableFile = async (
parentBefore.message,
safeSize(expected.size),
);
handle = await openRegularFile(absolute, { symlinks: "reject", signal });
const handle = await openRegularFile(absolute, {
symlinks: "reject",
signal,
});
retain(new OwnedFileHandle(handle));
const prepared = await prepareFileRead(request, handle);
if (prepared.status === "failed") return prepared.entry;
const contents = await readFileContents({
@@ -236,6 +274,10 @@ export const readStableFile = async (
...(signal === undefined ? {} : { signal }),
});
} catch (cause: unknown) {
if (cause instanceof RegularFileAdmissionFailure) {
retain(cause.owner);
cause = cause.cause;
}
if (isAborted(signal))
return entryFailure(
path,
@@ -258,8 +300,5 @@ export const readStableFile = async (
);
if (message === undefined) throw cause;
return entryFailure(path, "file", "io", message, safeSize(expected.size));
} finally {
// best-effort cleanup: file-handle close must not mask the read result.
await handle?.close().catch(() => undefined);
}
};