fix(ci): align FAT fixtures and consolidate duplicated workflow helpers

Declare the synthetic 4 KiB Mach-O slice alignment accurately while preserving the IDA thinning scenario. Share web cleanup aggregation and process-trace fixture facts instead of duplicating them across consumers and tests; keep the 3% duplication threshold unchanged.

Validation: npm run check:ci, test discovery, and 42 focused IDA, process-trace, web workflow and cleanup boundary tests. Duplication falls from 3.01% to 2.99%.
This commit is contained in:
morluto committed 2026-10-13 16:16:31 +08:00
1 parent 67c7a24e60
commit a0317cf732
7 files changed
+87 -158

No files matched your search

+1 -15
View File
@@ -1,3 +1,4 @@
import { closeOwnedWebResources } from "./closeOwnedWebResources.js";
import { isAbsolute } from "node:path";
import { z } from "zod";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
@@ -271,21 +272,6 @@ export class WebModuleTraceService {
}
}
const closeOwnedWebResources = async (
close: readonly ((() => Promise<void>) | undefined)[],
message: string,
): Promise<void> => {
const results = await Promise.allSettled(
close.flatMap((owner) =>
owner === undefined ? [] : [Promise.resolve().then(owner)],
),
);
const failures = results.flatMap((result) =>
result.status === "rejected" ? [result.reason] : [],
);
if (failures.length > 0) throw new AggregateError(failures, message);
};
const admitContext = (
value: string,
path: readonly string[],
+1 -14
View File
@@ -1,3 +1,4 @@
import { closeOwnedWebResources } from "./closeOwnedWebResources.js";
import { createHash } from "node:crypto";
import { isAbsolute } from "node:path";
import { z } from "zod";
@@ -181,20 +182,6 @@ export class WebSourceLocationService {
}
}
const closeOwnedWebResources = async (
close: readonly ((() => Promise<void>) | undefined)[],
message: string,
): Promise<void> => {
const results = await Promise.allSettled(
close.flatMap((owner) =>
owner === undefined ? [] : [Promise.resolve().then(owner)],
),
);
const failures = results.flatMap((result) =>
result.status === "rejected" ? [result.reason] : [],
);
if (failures.length > 0) throw new AggregateError(failures, message);
};
const invalidOutput = (message: string): Result<never, AnalysisError> =>
err(new AnalysisOutputError(OPERATION, message));
const invalid = (
+15
View File
@@ -0,0 +1,15 @@
/** Attempt every web workflow cleanup owner and report all failures. */
export const closeOwnedWebResources = async (
close: readonly ((() => Promise<void>) | undefined)[],
message: string,
): Promise<void> => {
const results = await Promise.allSettled(
close.flatMap((owner) =>
owner === undefined ? [] : [Promise.resolve().then(owner)],
),
);
const failures = results.flatMap((result) =>
result.status === "rejected" ? [result.reason] : [],
);
if (failures.length > 0) throw new AggregateError(failures, message);
};
@@ -1,3 +1,4 @@
import type { ProcessTraceSpecification } from "./processTraceSpecification.js";
import { compareProcessCaptures } from "./processComparison.js";
import { parseProcessCapture } from "./processCaptureParsing.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
@@ -112,3 +113,56 @@ export const compareUnverifiedProcessCaptures = (
parseProcessCapture(right),
options,
);
/** One journal and its matching declared trace, shared by domain and adapter checks. */
export const processTraceCaptureFixture = () => {
const capture = {
...EMPTY_PROCESS_CAPTURE_EXAMPLE,
event_journal: [
{ capture_order: 0, collection: "filesystem_checkpoints", index: 0 },
{ capture_order: 1, collection: "lifecycle", index: 0 },
{ capture_order: 2, collection: "lifecycle", index: 1 },
{ capture_order: 3, collection: "filesystem_checkpoints", index: 1 },
],
} as const;
const [before, after] = capture.filesystem_checkpoints;
if (before === undefined || after === undefined)
throw new Error(
"Trace fixture requires before and after filesystem checkpoints",
);
const traceSpecification: ProcessTraceSpecification = {
events: [
{
id: "before",
source: "filesystem",
exact: before,
cardinality: { kind: "required" },
},
{
id: "exit",
source: "lifecycle",
exact: { event: "exit", ...capture.exit },
cardinality: { kind: "required" },
},
{
id: "settlement",
source: "lifecycle",
exact: { event: "settlement", ...capture.settlement },
cardinality: { kind: "required" },
},
{
id: "after",
source: "filesystem",
exact: after,
cardinality: { kind: "required" },
},
],
language: {
kind: "finite_traces",
variants: [
{ id: "normal", trace: ["before", "exit", "settlement", "after"] },
],
},
};
return { capture, traceSpecification };
};
@@ -1,72 +1,13 @@
import { describe, expect, it } from "vitest";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "./processCaptureExample.js";
import { accountFullyObservedProcessCapture } from "./processCapture.fixture.js";
import {
processTraceCaptureFixture,
accountFullyObservedProcessCapture,
} from "./processCapture.fixture.js";
import { compareProcessCaptures } from "./processComparison.js";
import { parseProcessCapture } from "./processCaptureParsing.js";
import type { ProcessTraceSpecification } from "./processTraceSpecification.js";
const beforeCheckpoint = {
name: "before",
at_ms: 0,
files: [],
effects: [],
truncated: false,
};
const afterCheckpoint = {
name: "after_settlement",
at_ms: 50,
files: [],
effects: [],
truncated: false,
};
const capture = {
...EMPTY_PROCESS_CAPTURE_EXAMPLE,
event_journal: [
{ capture_order: 0, collection: "filesystem_checkpoints", index: 0 },
{ capture_order: 1, collection: "lifecycle", index: 0 },
{ capture_order: 2, collection: "lifecycle", index: 1 },
{ capture_order: 3, collection: "filesystem_checkpoints", index: 1 },
],
} as const;
const traceSpecification: ProcessTraceSpecification = {
events: [
{
id: "before",
source: "filesystem",
exact: beforeCheckpoint,
cardinality: { kind: "required" },
},
{
id: "exit",
source: "lifecycle",
exact: { event: "exit", ...capture.exit },
cardinality: { kind: "required" },
},
{
id: "settlement",
source: "lifecycle",
exact: { event: "settlement", ...capture.settlement },
cardinality: { kind: "required" },
},
{
id: "after",
source: "filesystem",
exact: afterCheckpoint,
cardinality: { kind: "required" },
},
],
language: {
kind: "finite_traces",
variants: [
{
id: "normal",
trace: ["before", "exit", "settlement", "after"],
},
],
},
};
const { capture, traceSpecification } = processTraceCaptureFixture();
describe("declared trace comparison nonconformance", () => {
it("does not call identical captures changed when both violate the language", () => {
+9 -3
View File
@@ -40,6 +40,7 @@ interface Layout {
readonly cpu: number;
readonly offset: number;
readonly size: number;
readonly alignmentExponent?: number;
}
/**
@@ -57,12 +58,12 @@ const largeUniversalTarget = async (
const bytes = Buffer.alloc(total);
bytes.writeUInt32BE(0xcafebabe, 0);
bytes.writeUInt32BE(layout.length, 4);
layout.forEach(({ cpu, offset, size }, index) => {
layout.forEach(({ cpu, offset, size, alignmentExponent = 14 }, index) => {
const entry = 8 + index * 20;
bytes.writeUInt32BE(cpu, entry);
bytes.writeUInt32BE(offset, entry + 8);
bytes.writeUInt32BE(size, entry + 12);
bytes.writeUInt32BE(14, entry + 16);
bytes.writeUInt32BE(alignmentExponent, entry + 16);
bytes.fill(0x11 * (index + 1), offset, offset + size);
thinMach(0xfeedfacf, cpu).copy(bytes, offset);
});
@@ -122,7 +123,12 @@ describe("IDA workspace input thinning", () => {
async () => {
const { bytes, target } = await largeUniversalTarget(
[
{ cpu: 0x01000007, offset: 4096, size: 8480 },
{
cpu: 0x01000007,
offset: 4096,
size: 8480,
alignmentExponent: 12,
},
{ cpu: 0x0100000c, offset: 16384, size: 33464 },
{ cpu: 0x00000007, offset: 65536, size: 33464 },
],
@@ -9,76 +9,16 @@ import { createTestBinarySession } from "../../fixtures/binarySession.js";
import type { AnalysisClient } from "../../../src/application/AnalysisProvider.js";
import { compareProcessEvidenceFiles } from "../../../src/application/process/ProcessCli.js";
import { PROCESS_PROVIDER } from "../../../src/domain/process/processEvidenceProvider.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../../../src/domain/process/processCaptureExample.js";
import { processTraceCaptureFixture } from "../../../src/domain/process/processCapture.fixture.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
import type { ProcessTraceSpecification } from "../../../src/domain/process/processTraceSpecification.js";
import { createServer } from "../../../src/server/createServer.js";
import { observed as ok } from "../../fixtures/analysisExecution.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const resources: Array<{ close(): Promise<void> }> = [];
const temporaryRoots: string[] = [];
const beforeCheckpoint = {
name: "before",
at_ms: 0,
files: [],
effects: [],
truncated: false,
};
const afterCheckpoint = {
name: "after_settlement",
at_ms: 50,
files: [],
effects: [],
truncated: false,
};
const capture = {
...EMPTY_PROCESS_CAPTURE_EXAMPLE,
event_journal: [
{ capture_order: 0, collection: "filesystem_checkpoints", index: 0 },
{ capture_order: 1, collection: "lifecycle", index: 0 },
{ capture_order: 2, collection: "lifecycle", index: 1 },
{ capture_order: 3, collection: "filesystem_checkpoints", index: 1 },
],
} as const;
const traceSpecification: ProcessTraceSpecification = {
events: [
{
id: "before",
source: "filesystem",
exact: beforeCheckpoint,
cardinality: { kind: "required" },
},
{
id: "exit",
source: "lifecycle",
exact: { event: "exit", ...capture.exit },
cardinality: { kind: "required" },
},
{
id: "settlement",
source: "lifecycle",
exact: { event: "settlement", ...capture.settlement },
cardinality: { kind: "required" },
},
{
id: "after",
source: "filesystem",
exact: afterCheckpoint,
cardinality: { kind: "required" },
},
],
language: {
kind: "finite_traces",
variants: [
{
id: "normal",
trace: ["before", "exit", "settlement", "after"],
},
],
},
};
const { capture, traceSpecification } = processTraceCaptureFixture();
const captureEvidence = (side: "left" | "right") =>
createEvidence(undefined, PROCESS_PROVIDER, {