fix(extraction): isolate explicit jobs and run cancellation

Use validated explicit archive paths without recursively discovering a shared package root. Route selective post-processing stops through the coordinator run owner so another generation of the same package remains active, while preserving unfiltered package cancellation behavior.
This commit is contained in:
Sucukdeluxe
2026-08-22 17:40:19 +02:00
parent 522f53d787
commit d4f8166c86
4 changed files with 134 additions and 18 deletions
+50
View File
@@ -15491,6 +15491,56 @@ describe("package priority ordering", () => {
});
});
describe("selective extraction cancellation", () => {
it("stops only the selected run when two generations share one package id", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-run-cancel-"));
tempDirs.push(root);
const manager = new DownloadManager(defaultSettings(), emptySession(), createStoragePaths(path.join(root, "state")));
const state = manager as any;
const coordinator = new ExtractionCoordinator(2);
state.extractionCoordinator = coordinator;
const packageId = "shared-package";
const operationA = await coordinator.beginOperation({
context: { operationId: "operation-a", packageId, generation: 1, runOwnerId: "run-a" },
targetPath: path.join(root, "out"),
members: []
});
const operationB = await coordinator.beginOperation({
context: { operationId: "operation-b", packageId, generation: 2, runOwnerId: "run-b" },
targetPath: path.join(root, "out"),
members: []
});
const started = new Set<string>();
let completeRunA = (): void => {};
const jobA = coordinator.scheduleArchive(operationA, "archive-a", (signal) => new Promise<void>((resolve, reject) => {
started.add("run-a");
completeRunA = resolve;
signal.addEventListener("abort", () => reject(new Error(String(signal.reason || "aborted"))), { once: true });
}));
const jobB = coordinator.scheduleArchive(operationB, "archive-b", (signal) => new Promise<void>((_resolve, reject) => {
started.add("run-b");
signal.addEventListener("abort", () => reject(new Error(String(signal.reason || "aborted"))), { once: true });
}));
const outcomeA = jobA.then(() => "completed", (error) => `cancelled:${String(error)}`);
const outcomeB = jobB.then(() => "completed", (error) => `cancelled:${String(error)}`);
await vi.waitFor(() => expect(started).toEqual(new Set(["run-a", "run-b"])));
const controllerA = new AbortController();
const controllerB = new AbortController();
state.packageHybridPostProcessControllers.set(packageId, new Set([controllerA, controllerB]));
state.packageHybridRunOwnerByController.set(controllerA, "run-a");
state.packageHybridRunOwnerByController.set(controllerB, "run-b");
state.abortPostProcessing("stop", "run-b");
await vi.waitFor(() => expect(controllerB.signal.aborted).toBe(true));
expect(controllerA.signal.aborted).toBe(false);
expect(await outcomeB).toContain("stop");
completeRunA();
expect(await outcomeA).toBe("completed");
await Promise.all([operationA.finalize(), operationB.finalize()]);
});
});
describe("package lifecycle telemetry boundaries", () => {
it("captures direct package output scopes concurrently without scanning a shared root", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-output-provenance-lock-"));
+44 -1
View File
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import AdmZip from "adm-zip";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
buildExternalExtractArgs,
buildExternalListArgs,
@@ -1427,6 +1427,49 @@ describe("extractor", () => {
expect(fs.existsSync(path.join(targetDir, "foreign.txt"))).toBe(false);
});
it("uses an explicit archive list without traversing a shared package root", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-explicit-archives-"));
tempDirs.push(root);
const packageDir = path.join(root, "shared");
const foreignDir = path.join(packageDir, "foreign-package");
const targetDir = path.join(root, "out");
fs.mkdirSync(foreignDir, { recursive: true });
const ownedArchive = path.join(packageDir, "owned.zip");
const foreignArchive = path.join(foreignDir, "foreign.zip");
const owned = new AdmZip();
owned.addFile("owned.txt", Buffer.from("owned"));
owned.writeZip(ownedArchive);
const foreign = new AdmZip();
foreign.addFile("foreign.txt", Buffer.from("foreign"));
foreign.writeZip(foreignArchive);
const readdirSpy = vi.spyOn(fs.promises, "readdir");
let sharedRootTraversals = 0;
try {
const result = await extractPackageArchives({
packageDir,
targetDir,
cleanupMode: "none",
conflictMode: "overwrite",
removeLinks: false,
removeSamples: false,
onlyArchives: new Set([path.resolve(ownedArchive).toLowerCase()])
});
const sharedRoot = path.resolve(packageDir);
sharedRootTraversals = readdirSpy.mock.calls.filter(([directory]) => {
const candidate = path.resolve(String(directory));
return candidate === sharedRoot || candidate.startsWith(`${sharedRoot}${path.sep}`);
}).length;
expect(result.extracted).toBe(1);
expect(fs.readFileSync(path.join(targetDir, "owned.txt"), "utf8")).toBe("owned");
expect(fs.existsSync(path.join(targetDir, "foreign.txt"))).toBe(false);
expect(sharedRootTraversals).toBe(0);
} finally {
readdirSpy.mockRestore();
}
});
it("delegates top-level and nested archive jobs through one scheduler", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-global-extract-scheduler-"));
tempDirs.push(root);