Files
reverse-engineering-anything/src/artifacts/ArtifactReader.ts
T
morluto 0354759370 fix(artifacts): retain admitted ZIP ownership through extraction
Transfer the inventoried ZIP descriptor after inventory reader cleanup, then
borrow it for materialization instead of reopening the pathname. Release the
exact owner before publishing output; preserve failed owners for cleanup retry
and completed inventory facts through cancellation or later tagged failures.
This uses the existing admitted-descriptor reader API without a third carrier
hash pass. Remove the superseded materialization wrapper.

Cover identical-member carrier replacement, directory/missing replacements,
late cancellation at both handoff and final close, mixed cleanup outcomes and
retry. Root-owner test seams preserve real output-handle cleanup. Compiled
CLI/MCP ZIP, archive and report regressions also run; no benchmark is claimed.
2026-10-25 03:30:39 +08:00

335 lines
12 KiB
TypeScript

import type {
ArtifactAdmissionLimit,
ArtifactLimitBoundary,
} from "../domain/artifactOperationError.js";
import type {
ArtifactIntegrityClaim,
ArtifactIntegrityDetails,
} from "../domain/artifactIntegrity.js";
import type { ArtifactFormatMetadata } from "../domain/artifactFormatMetadata.js";
import { AnalysisError } from "../domain/analysisErrorBase.js";
import type { AnalysisCleanupLease } from "./AnalysisCleanupLease.js";
import type { Readable } from "node:stream";
import type {
ArtifactCommand,
ArtifactInventoryCoverage,
ArtifactTraversalOmissionKind,
} from "../domain/artifactGraph.js";
import type { ZipPackageFormat } from "../domain/zipPackageFormat.js";
import type {
AnalysisCleanupObservation,
AnalysisPartialObservation,
} from "../domain/analysisErrorBase.js";
export interface ArtifactReaderFailureOptions extends ErrorOptions {
readonly cancellation?: { readonly reason: unknown } | undefined;
readonly cleanup?: AnalysisCleanupObservation | undefined;
/** Exact authority for all reported cleanup; undefined leaves ownership unknown. */
readonly cleanupLeases?: readonly AnalysisCleanupLease[] | undefined;
readonly partialObservation?: AnalysisPartialObservation | undefined;
/** The constrained resource bound for a limit failure. */
readonly boundary?: ArtifactLimitBoundary;
/** Named admission whose recovery must not be inferred from other limit text. */
readonly admission?: ArtifactAdmissionLimit;
}
/** Archive-neutral entry metadata. Reader adapters never choose output paths. */
export interface ArtifactEntry {
readonly path: string;
/** Exact reader-source path or opaque key before logical path mapping. */
readonly sourcePath?: string;
readonly kind: "file" | "directory" | "symlink" | "slice" | "metadata";
readonly declaredSize: number | null;
readonly compressedSize: number | null;
readonly executable: boolean;
readonly encrypted: boolean;
readonly byteOffset: number | null;
readonly declaredIntegrity: ArtifactIntegrityClaim | null;
readonly formatMetadata?: ArtifactFormatMetadata;
readonly unpacked: boolean;
readonly limitations: readonly string[];
readonly adapterKey: string;
/** Filesystem identity captured during traversal, when supplied by an adapter. */
readonly sourceIdentity?: {
readonly device: bigint;
readonly inode: bigint;
readonly size: bigint;
readonly mode: bigint;
readonly mtimeNs: bigint;
readonly ctimeNs: bigint;
};
}
/** Compare captured filesystem identity and mutable metadata at a read boundary. */
export const sameArtifactSourceIdentity = (
expected: ArtifactEntry["sourceIdentity"],
observed: {
readonly dev: bigint;
readonly ino: bigint;
readonly size: bigint;
readonly mode: bigint;
readonly mtimeNs: bigint;
readonly ctimeNs: bigint;
},
): boolean =>
expected !== undefined &&
observed.dev === expected.device &&
observed.ino === expected.inode &&
observed.size === expected.size &&
observed.mode === expected.mode &&
observed.mtimeNs === expected.mtimeNs &&
observed.ctimeNs === expected.ctimeNs;
/** Snapshot mutable entry metadata before the producer later validates open(). */
export const copyArtifactEntry = (entry: ArtifactEntry): ArtifactEntry => ({
...entry,
limitations: [...entry.limitations],
declaredIntegrity:
entry.declaredIntegrity === null ? null : { ...entry.declaredIntegrity },
...(entry.formatMetadata === undefined
? {}
: { formatMetadata: { ...entry.formatMetadata } }),
...(entry.sourceIdentity === undefined
? {}
: { sourceIdentity: { ...entry.sourceIdentity } }),
});
/** Compare every portable and adapter-owned fact supplied with an entry. */
export const sameArtifactEntry = (
entry: ArtifactEntry,
produced: ArtifactEntry,
): boolean =>
entry.path === produced.path &&
entry.sourcePath === produced.sourcePath &&
entry.kind === produced.kind &&
entry.declaredSize === produced.declaredSize &&
entry.compressedSize === produced.compressedSize &&
entry.executable === produced.executable &&
entry.encrypted === produced.encrypted &&
entry.byteOffset === produced.byteOffset &&
sameDeclaredIntegrity(entry.declaredIntegrity, produced.declaredIntegrity) &&
sameFormatMetadata(entry.formatMetadata, produced.formatMetadata) &&
entry.unpacked === produced.unpacked &&
entry.adapterKey === produced.adapterKey &&
entry.limitations.length === produced.limitations.length &&
entry.limitations.every(
(limitation, index) => limitation === produced.limitations[index],
) &&
entry.sourceIdentity?.device === produced.sourceIdentity?.device &&
entry.sourceIdentity?.inode === produced.sourceIdentity?.inode &&
entry.sourceIdentity?.size === produced.sourceIdentity?.size &&
entry.sourceIdentity?.mode === produced.sourceIdentity?.mode &&
entry.sourceIdentity?.mtimeNs === produced.sourceIdentity?.mtimeNs &&
entry.sourceIdentity?.ctimeNs === produced.sourceIdentity?.ctimeNs;
const sameDeclaredIntegrity = (
left: ArtifactEntry["declaredIntegrity"],
right: ArtifactEntry["declaredIntegrity"],
): boolean =>
left === right ||
(left !== null &&
left !== undefined &&
right !== null &&
right !== undefined &&
left.algorithm === right.algorithm &&
left.digest === right.digest);
/** Format metadata contracts contain only primitive source fields. */
const sameFormatMetadata = (
left: ArtifactFormatMetadata | undefined,
right: ArtifactFormatMetadata | undefined,
): boolean =>
left === right ||
(left !== undefined &&
right !== undefined &&
Object.keys(left).length === Object.keys(right).length &&
Object.entries(left).every(
([key, value]) =>
Object.hasOwn(right, key) && Reflect.get(right, key) === value,
));
/** One entry or subtree deliberately excluded from a traversal. */
export interface ArtifactTraversalOmission {
readonly path: string;
readonly kind: ArtifactTraversalOmissionKind;
}
/** Facts from an exhausted traversal, separate from entry content certainty. */
export interface ArtifactReaderTraversal {
readonly status: ArtifactInventoryCoverage["status"];
readonly limitations: readonly string[];
/** Number of directory entries examined, including names omitted before decoding. */
readonly examinedEntries?: number;
}
/** Per-iterator observation sink; adapters report intentional omissions without shared state. */
export interface ArtifactTraversalOptions {
readonly onOmission?: (omission: ArtifactTraversalOmission) => void;
readonly onCoverage?: (coverage: ArtifactReaderTraversal) => void;
}
/** Read-only adapter over one directory, archive, or virtual container. */
export interface ArtifactReader {
readonly format:
| "directory"
| ZipPackageFormat
| "asar"
| "godot-pck"
| "pyinstaller"
| "node-sea"
| "file";
entries(
signal?: AbortSignal,
options?: ArtifactTraversalOptions,
): AsyncIterable<ArtifactEntry>;
open(entry: ArtifactEntry, signal?: AbortSignal): Promise<Readable>;
provenance(): readonly ArtifactCommand[];
formatMetadata?(): ArtifactFormatMetadata | undefined;
/** Bind materialization to the exact inventoried carrier before creating outputs. */
prepareContainer?(
expectedSha256: string,
signal?: AbortSignal,
): Promise<void>;
close(): Promise<void>;
}
/** Typed adapter failure translated at provider boundary. */
export class ArtifactReaderFailure extends Error {
readonly cancellation: { readonly reason: unknown } | undefined;
readonly cleanup: AnalysisCleanupObservation | undefined;
readonly cleanupLeases: readonly AnalysisCleanupLease[] | undefined;
partialObservation: AnalysisPartialObservation | undefined;
/** The constrained bound for a `limit` reason, when the reader knows it. */
readonly boundary: ArtifactLimitBoundary | undefined;
/** Named admission for a limit whose public recovery is owned by the producer. */
readonly admission: ArtifactAdmissionLimit | undefined;
constructor(
readonly reason:
| "cancelled"
| "format"
| "integrity"
| "io"
| "limit"
| "path"
| "unavailable",
message: string,
options?: ArtifactReaderFailureOptions,
readonly details?: ArtifactIntegrityDetails,
) {
super(message, options);
this.name = "ArtifactReaderFailure";
this.cancellation =
reason === "cancelled"
? (options?.cancellation ?? { reason: options?.cause })
: undefined;
this.cleanup = options?.cleanup;
this.cleanupLeases =
options?.cleanupLeases ?? (this.cleanup === undefined ? [] : undefined);
this.partialObservation = options?.partialObservation;
this.boundary = options?.boundary;
this.admission = options?.admission;
}
/** Retain completed facts when a later artifact step fails. */
retainPartialObservation(observation: AnalysisPartialObservation): this {
this.partialObservation ??= observation;
return this;
}
/** Preserve the primary failure and observation. Supplied leases must cover all merged cleanup. */
static withCleanup(
cause: unknown,
cleanup: AnalysisCleanupObservation,
partialObservation?: AnalysisPartialObservation,
cleanupLeases?: readonly AnalysisCleanupLease[],
): ArtifactReaderFailure {
const primary =
cause instanceof ArtifactReaderFailure
? cause
: new ArtifactReaderFailure("io", errorMessage(cause), {
cause,
...(cause instanceof AnalysisError
? {
partialObservation: cause.partialObservation,
...(cause.cleanupIncomplete
? {
cleanup: ArtifactReaderFailure.cleanupObservation(
cause,
[],
),
}
: {}),
}
: {}),
});
const mergedObservation = partialObservation ?? primary.partialObservation;
return new ArtifactReaderFailure(
primary.reason,
primary.message,
{
cause: primary,
cancellation: primary.cancellation,
cleanup: combineCleanup(primary.cleanup, cleanup),
cleanupLeases:
cleanupLeases ??
(cleanup === primary.cleanup ? primary.cleanupLeases : undefined),
// The constrained bound survives cleanup wrapping, or a file-size
// refusal would be reported as a memory constraint once a close fails.
...(primary.boundary === undefined
? {}
: { boundary: primary.boundary }),
...(primary.admission === undefined
? {}
: { admission: primary.admission }),
...(mergedObservation === undefined
? {}
: { partialObservation: mergedObservation }),
},
primary.details,
);
}
/** Give otherwise opaque cleanup failures a concrete owned resource. */
static cleanupObservation(
cause: unknown,
resource: string | readonly string[],
): AnalysisCleanupObservation {
if (cause instanceof ArtifactReaderFailure && cause.cleanup !== undefined)
return cause.cleanup;
if (cause instanceof AnalysisError && cause.cleanupIncomplete)
return {
reason: cause.cleanup?.reason ?? cause.message,
resources: [...cause.cleanupResources],
};
return {
reason: errorMessage(cause),
resources: typeof resource === "string" ? [resource] : [...resource],
};
}
}
const combineCleanup = (
primary: AnalysisCleanupObservation | undefined,
cleanup: AnalysisCleanupObservation,
): AnalysisCleanupObservation =>
primary === undefined || primary === cleanup
? cleanup
: {
reason: `${primary.reason}; ${cleanup.reason}`,
resources: [...new Set([...primary.resources, ...cleanup.resources])],
};
const errorMessage = (cause: unknown): string =>
cause instanceof Error ? cause.message : String(cause);
/** Read explicit source authority; unrelated domain cleanup remains unresolved. */
export const artifactPrimaryCleanupLeases = (
cause: unknown,
): readonly AnalysisCleanupLease[] | undefined =>
cause instanceof ArtifactReaderFailure
? cause.cleanupLeases
: cause instanceof AnalysisError && cause.cleanupIncomplete
? undefined
: [];