Files
1201c25542 fix(ci): align static checks and regression fixtures with current contracts (#7349)
* fix(ci): retire unused internal analysis declarations

* test: use an admitted app bundle for provider routing

(cherry picked from commit 00aa17f12a)

* test: keep bundle routing coverage outside oversized group

(cherry picked from commit 8658353b0d)

* fix(ci): align regression fixtures with current admission contracts

* test: bind extraction close fixtures to their ZIP digest

(cherry picked from commit 626d0250db)

* test(cli): match typed analysis outcomes in function detail fixture

* test(ghidra): preserve empty Java property separator framing

* test(electron): retain unknown IPC ownership for native module gaps

* fix(electron): share source-scoped call path identity

* test(captures): assert safe source identity on SAZ refusal

* test(apple): match root occurrence kind to inventory manifest

* fix(examples): align directory root occurrence identity

* test(electron): align equivalent fixture assertions with upstream

* test(process): assert retained capture root identity

* test(windows): declare the PE fixture optional header

* test(ci): refresh owned registration and verifier fixtures

* test(setup): use owned registrations for refresh fixtures

* test(process): control unavailable ownership in CLI cleanup fixture

* test(process): guard controlled compiler output arguments

* test(setup): align equivalent entrypoint guards with upstream

* test(process): release the cleanup target through an owned gate

* test(ci): align semantic identities and owned fixture facts

* test(pcap): retain the upstream DNS wire fixture branch

* test(pcap): use the canonical wire ownership fixture

* test(pcap): assert exact DNS wire query labels

* test(ci): preserve admission and ownership fixture prerequisites

* test(ci): align authenticated fixture and cleanup boundaries

* test(windows): align native artifact fixtures with upstream

* test(android): request complete evidence for inline MCP contracts

* test: avoid pinning diagnostic wording

* test: verify cleanup diagnostics at their source

* test: strengthen evidence and cleanup checks

* test: avoid pinning registration guidance

---------

Co-authored-by: Plastic-time <Plastic-time@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-27 21:24:30 +08:00

343 lines
12 KiB
TypeScript

import { mkdtemp, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { setImmediate } from "node:timers/promises";
import { expect, it } from "vitest";
import { createDeferred } from "../../fixtures/binarySession.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { ProcessCaptureError } from "../../../src/process/capture/ProcessCaptureError.js";
import {
ProcessCaptureResourceScope,
resolveProcessResult,
type ProcessCaptureCleanupHost,
type ProcessCaptureResourceCleanupOptions,
} from "../../../src/process/capture/ProcessCaptureLifecycle.js";
const cleanupOptions = (
temporaryRoot: string,
removeTemporaryRoot: (path: string) => Promise<void>,
): ProcessCaptureResourceCleanupOptions => {
const host: ProcessCaptureCleanupHost = {
platform: process.platform,
cleanupProcessGroup: async () => ({ cleaned: true, signaled: false }),
removeTemporaryRoot,
};
return {
timers: new Set(),
terminal: undefined,
renderer: undefined,
runId: "cleanup-retry-boundary",
temporaryRoot,
host,
};
};
const createRoot = (): Promise<string> =>
mkdtemp(join(tmpdir(), "rea-process-cleanup-retry-"));
const expectCaptureError = (operation: () => unknown): ProcessCaptureError => {
try {
operation();
} catch (cause: unknown) {
if (cause instanceof ProcessCaptureError) return cause;
throw cause;
}
throw new Error("Expected process capture failure");
};
it("resolves the origin and finalization copy after the exact root retry", async () => {
const scope = new ProcessCaptureResourceScope();
const root = await createRoot();
const primaryCause = new Error("capture failed before root cleanup");
const primary = new ProcessCaptureError("capture failed", {
cause: primaryCause,
reason: "cleanup_incomplete",
cleanup: {
reason: "temporary root removal failed",
resources: ["temporary_root"],
},
});
let denyRemoval = true;
const release = await scope.release(
cleanupOptions(root, async (path) => {
if (denyRemoval) throw new Error("controlled root removal denial");
await rm(path, { recursive: true });
}),
);
try {
if (release.cleanupLease === undefined)
throw new Error("Expected the failed root's exact cleanup lease");
release.cleanupLease.bindCleanupError(primary);
const reported = expectCaptureError(() =>
resolveProcessResult(undefined, primary, release),
);
const historicalReport = reported.cleanupReport;
expect(reported).not.toBe(primary);
expect(primary.cleanupIncomplete).toBe(true);
expect(reported.cleanupIncomplete).toBe(true);
expect(projectAnalysisError(reported).code).toBe("cleanup_incomplete");
expect(reported.cause).toBe(primaryCause);
expect(historicalReport?.temporary_root).toEqual({
state: "failed",
reason: "controlled root removal denial",
});
denyRemoval = false;
await scope.retryCleanup();
await scope.close();
expect(primary.cleanupIncomplete).toBe(false);
expect(reported.cleanupIncomplete).toBe(false);
expect(projectAnalysisError(reported).code).not.toBe("cleanup_incomplete");
expect(reported.cause).toBe(primaryCause);
expect(reported.cleanupReport).toBe(historicalReport);
expect(reported.cleanupReport?.temporary_root).toEqual({
state: "failed",
reason: "controlled root removal denial",
});
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
denyRemoval = false;
await scope.close();
await rm(root, { recursive: true, force: true });
}
});
it("keeps mixed root errors and retry aggregates bound to only unresolved roots", async () => {
const scope = new ProcessCaptureResourceScope();
const roots = [await createRoot(), await createRoot()] as const;
const denied = new Set(roots);
const outcomes = new Map<string, Awaited<ReturnType<typeof scope.release>>>();
try {
for (const root of roots) {
outcomes.set(
root,
await scope.release(
cleanupOptions(root, async (path) => {
if (denied.has(path))
throw new Error(`controlled removal denial: ${path}`);
await rm(path, { recursive: true });
}),
),
);
}
const firstOutcome = outcomes.get(roots[0]);
const secondOutcome = outcomes.get(roots[1]);
if (firstOutcome === undefined || secondOutcome === undefined)
throw new Error("Expected both retained cleanup outcomes");
const first = expectCaptureError(() =>
resolveProcessResult(
undefined,
new ProcessCaptureError("first primary failure"),
firstOutcome,
),
);
const second = expectCaptureError(() =>
resolveProcessResult(
undefined,
new ProcessCaptureError("second primary failure"),
secondOutcome,
),
);
expect(first.cleanupIncomplete).toBe(true);
expect(second.cleanupIncomplete).toBe(true);
const bothUnresolved: unknown = await scope
.retryCleanup()
.catch((cause: unknown) => cause);
expect(bothUnresolved).toBeInstanceOf(ProcessCaptureError);
if (!(bothUnresolved instanceof ProcessCaptureError))
throw new Error("Expected both owners in a typed retry aggregate");
expect(bothUnresolved.cleanupIncomplete).toBe(true);
expect(bothUnresolved.cause).toBeInstanceOf(AggregateError);
if (!(bothUnresolved.cause instanceof AggregateError))
throw new Error("Expected retained child retry failures");
expect(bothUnresolved.cause.errors).toHaveLength(2);
denied.delete(roots[0]);
const retryFailure: unknown = await scope
.retryCleanup()
.catch((cause: unknown) => cause);
expect(retryFailure).toBeInstanceOf(ProcessCaptureError);
if (!(retryFailure instanceof ProcessCaptureError))
throw new Error("Expected a typed retry aggregate");
expect(first.cleanupIncomplete).toBe(false);
expect(second.cleanupIncomplete).toBe(true);
expect(bothUnresolved.cleanupIncomplete).toBe(true);
expect(retryFailure.cleanupIncomplete).toBe(true);
expect(retryFailure.cleanupResources).toEqual([roots[1]]);
denied.clear();
await scope.retryCleanup();
await scope.close();
expect(second.cleanupIncomplete).toBe(false);
expect(retryFailure.cleanupIncomplete).toBe(false);
expect(bothUnresolved.cleanupIncomplete).toBe(false);
expect(first.cleanupReport?.temporary_root.state).toBe("failed");
expect(second.cleanupReport?.temporary_root.state).toBe("failed");
for (const root of roots)
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
denied.clear();
await scope.close();
await Promise.all(
roots.map((root) => rm(root, { recursive: true, force: true })),
);
}
});
it("joins an already-started release when shutdown begins", async () => {
const scope = new ProcessCaptureResourceScope();
const root = await createRoot();
const entered = createDeferred<void>();
const unblock = createDeferred<void>();
let attempts = 0;
const releasing = scope.release(
cleanupOptions(root, async (path) => {
attempts += 1;
entered.resolve();
await unblock.promise;
await rm(path, { recursive: true });
}),
);
await entered.promise;
let closed = false;
const closing = scope.close().then(() => {
closed = true;
});
try {
await setImmediate();
expect(closed).toBe(false);
await expect(stat(root)).resolves.toBeDefined();
} finally {
unblock.resolve();
await Promise.all([releasing, closing]);
await rm(root, { recursive: true, force: true });
}
expect(attempts).toBe(1);
expect(closed).toBe(true);
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
});
it("binds a historical failed report after its owner already retried", async () => {
const scope = new ProcessCaptureResourceScope();
const root = await createRoot();
let denyRemoval = true;
const release = await scope.release(
cleanupOptions(root, async (path) => {
if (denyRemoval) throw new Error("controlled root removal denial");
await rm(path, { recursive: true });
}),
);
try {
denyRemoval = false;
await scope.retryCleanup();
await scope.close();
const reported = expectCaptureError(() =>
resolveProcessResult(
undefined,
new ProcessCaptureError("primary failure after release"),
release,
),
);
expect(reported.cleanupIncomplete).toBe(false);
expect(projectAnalysisError(reported).code).not.toBe("cleanup_incomplete");
expect(reported.cleanupReport?.temporary_root).toMatchObject({
state: "failed",
});
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
denyRemoval = false;
await scope.close();
await rm(root, { recursive: true, force: true });
}
});
it("blocks scope admission on owner-bound retry failure and resumes after release", async () => {
const scope = new ProcessCaptureResourceScope();
const root = await createRoot();
let denyRemoval = true;
const release = await scope.release(
cleanupOptions(root, async (path) => {
if (denyRemoval) throw new Error("controlled root removal denial");
await rm(path, { recursive: true });
}),
);
const origin = expectCaptureError(() =>
resolveProcessResult(
undefined,
new ProcessCaptureError("primary failure"),
release,
),
);
try {
let admitted = false;
const retryFailure: unknown = await scope
.run(async () => {
admitted = true;
})
.catch((cause: unknown) => cause);
expect(admitted).toBe(false);
expect(retryFailure).toBeInstanceOf(ProcessCaptureError);
if (!(retryFailure instanceof ProcessCaptureError))
throw new Error("Expected a typed owner-bound retry failure");
expect(retryFailure.cleanupIncomplete).toBe(true);
expect(retryFailure.cleanupResources).toEqual([root]);
expect(origin.cleanupIncomplete).toBe(true);
denyRemoval = false;
await expect(
scope.run(async () => {
admitted = true;
return "admitted after cleanup";
}),
).resolves.toBe("admitted after cleanup");
await scope.close();
expect(admitted).toBe(true);
expect(origin.cleanupIncomplete).toBe(false);
expect(retryFailure.cleanupIncomplete).toBe(false);
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
denyRemoval = false;
await scope.close();
await rm(root, { recursive: true, force: true });
}
});
it("keeps historical report edits separate from cleanup authority", async () => {
const scope = new ProcessCaptureResourceScope();
const root = await createRoot();
let denied = true;
let attempts = 0;
try {
const outcome = await scope.release(
cleanupOptions(root, async (path) => {
attempts += 1;
if (denied) throw new Error("controlled root removal denial");
await rm(path, { recursive: true });
}),
);
Reflect.set(outcome.report.temporary_root, "state", "cleaned");
await expect(scope.retryCleanup()).rejects.toBeInstanceOf(
ProcessCaptureError,
);
expect(attempts).toBe(2);
expect(outcome.cleanupLease?.released).toBe(false);
await expect(stat(root)).resolves.toBeDefined();
denied = false;
await scope.close();
expect(attempts).toBe(3);
await expect(stat(root)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
denied = false;
await scope.close();
await rm(root, { recursive: true, force: true });
}
});