test: consolidate service and process fixtures

This commit is contained in:
morluto
2026-10-08 04:03:24 +08:00
parent d88d6c1897
commit f8a53a8c06
25 changed files with 471 additions and 1176 deletions
@@ -1,47 +1,14 @@
import { describe, expect, it } from "vitest";
import { expect, it } from "vitest";
import { compareDirectoryChildNames } from "./ArtifactGraphConstruction.js";
const tiedNames = ["a.txt", "a\u200b.txt", "a\u200c.txt", "a\u200d.txt"];
describe("directory child-name ordering", () => {
it("orders the tied subset lexically with reflexivity and antisymmetry", () => {
for (const left of tiedNames) {
expect(compareDirectoryChildNames(left, left)).toBe(0);
for (const right of tiedNames) {
expect(left.localeCompare(right)).toBe(0);
const compared = compareDirectoryChildNames(left, right);
const lexical = left < right ? -1 : left > right ? 1 : 0;
expect(compared).toBe(lexical);
expect(compared + compareDirectoryChildNames(right, left)).toBe(0);
}
}
});
it("is transitive across the tied subset", () => {
for (const first of tiedNames)
for (const second of tiedNames)
for (const third of tiedNames)
if (
compareDirectoryChildNames(first, second) <= 0 &&
compareDirectoryChildNames(second, third) <= 0
)
expect(
compareDirectoryChildNames(first, third),
).toBeLessThanOrEqual(0);
});
it.each([
["a.txt", "b.txt"],
["z.txt", "ä.txt"],
["Z", "a"],
["a/data.txt", "b/data.txt"],
])("preserves the current non-tied collation for %s / %s", (left, right) => {
const collated = left.localeCompare(right);
expect(collated).not.toBe(0);
expect(compareDirectoryChildNames(left, right)).toBe(collated);
expect(compareDirectoryChildNames(right, left)).toBe(
right.localeCompare(left),
);
});
it("breaks locale-collation ties deterministically by code unit", () => {
const names = ["a\u200d.txt", "a.txt", "a\u200b.txt", "a\u200c.txt"];
expect("a\u200d.txt".localeCompare("a.txt")).toBe(0);
expect(names.sort(compareDirectoryChildNames)).toEqual([
"a.txt",
"a\u200b.txt",
"a\u200c.txt",
"a\u200d.txt",
]);
});
@@ -1,14 +0,0 @@
import { describe, expect, it } from "vitest";
import { resolveArtifactIntegrityPolicy } from "./ArtifactInventory/policy.js";
describe("artifact inventory policy resolution", () => {
it("preserves the selected integrity behavior without disabling verification", () => {
expect(resolveArtifactIntegrityPolicy({ mode: "fail" })).toEqual({
mode: "fail",
});
expect(
resolveArtifactIntegrityPolicy({ mode: "record-and-continue" }),
).toEqual({ mode: "record-and-continue" });
});
});
@@ -1,28 +0,0 @@
import { describe, expect, it } from "vitest";
import { toolAvailability } from "../contracts/toolOutputSchemaPrimitives.js";
import { buildCapabilityInventory } from "./CapabilityInventory.js";
describe("capability inventory contract", () => {
it("produces valid availability and rejects contradictory states", () => {
const inventory = buildCapabilityInventory(
{ open: false, capabilities: [] },
{
processCaptureEnabled: false,
},
);
const unavailable = inventory.find(({ available }) => !available);
expect(unavailable).toBeDefined();
expect(toolAvailability.safeParse(unavailable).success).toBe(true);
expect(
toolAvailability.safeParse({ ...unavailable, available: true }).success,
).toBe(false);
expect(
toolAvailability.safeParse({
...unavailable,
available: false,
reason: "available",
}).success,
).toBe(false);
});
});
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { toolAvailability } from "../contracts/toolOutputSchemaPrimitives.js";
import { buildCapabilityInventory } from "./CapabilityInventory.js";
it("reports module syntax and native resolution availability separately", () => {
@@ -249,6 +250,23 @@ describe("capability inventory: caller guidance", () => {
}
});
it("keeps the public availability schema consistent with generated states", () => {
const inventory = buildCapabilityInventory(status(), enabledPolicy);
const unavailable = inventory.find(({ available }) => !available);
expect(unavailable).toBeDefined();
expect(toolAvailability.safeParse(unavailable).success).toBe(true);
expect(
toolAvailability.safeParse({ ...unavailable, available: true }).success,
).toBe(false);
expect(
toolAvailability.safeParse({
...unavailable,
available: false,
reason: "available",
}).success,
).toBe(false);
});
it("does not require interactive elicitation for process capture", () => {
const withoutElicitation = buildCapabilityInventory(
status(),
@@ -105,30 +105,13 @@ const unknownLimitation = (graphId: string, nodeId: string): string =>
`Application graph ${graphId} contains unresolved node ${nodeId}; reconstruction obligations remain unknown.`;
describe("reconstruction obligation candidates", () => {
it("preserves admitted unknown application nodes as limitations", () => {
const { graph, unknownNodes, generated } = applicationWithUnknowns([
"unclassified-boundary",
]);
const unknownNode = unknownNodes[0];
if (unknownNode === undefined) throw new Error("Expected one unknown node");
expect(generated.candidates.length).toBeGreaterThan(0);
expect(
generated.candidates.some(
({ target }) => target.application_node_id === unknownNode.node_id,
),
).toBe(false);
expect(generated.limitations).toEqual([
unknownLimitation(graph.graph_id, unknownNode.node_id),
]);
});
it("emits one deterministic limitation per unknown node", () => {
it("preserves unresolved application nodes as deterministic limitations", () => {
const { graph, unknownNodes, generated } = applicationWithUnknowns([
"unclassified-first",
"unclassified-second",
]);
expect(generated.candidates.length).toBeGreaterThan(0);
expect([...generated.limitations].sort()).toEqual(
unknownNodes
.map((node) => unknownLimitation(graph.graph_id, node.node_id))
@@ -1,112 +1,16 @@
import { describe, expect, it } from "vitest";
import { PROCESS_PROVIDER } from "./process/ProcessEvidence.js";
import {
buildReconstructionObligationLedgerEvidenceValidated,
resolveReconstructionObligationLedgerRequest,
} from "./ReconstructionObligationLedgerService.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/process/processCapture.fixture.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { processCaptureSchema } from "../domain/process/processCapture.js";
import {
reconstructionObligationLedgerSchema,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedger,
type ReviewedReconstructionObligation,
} from "../domain/reconstructionObligationLedgerSchemas.js";
const proofEvidence = (id: string) =>
createEvidence(
undefined,
{ id: `fixture-${id}`, name: "Fixture verifier", version: "1" },
{
predicateType: "rea.fixture-verification",
operation: "run_fixture_verifier",
parameters: { id },
result: { passed: true },
confidence: "observed",
authority: "controlled-replay",
},
);
const processEvidence = () =>
createEvidence(undefined, PROCESS_PROVIDER, {
predicateType: "rea.process-capture",
operation: "capture_process_scenario",
parameters: {},
result: jsonValueSchema.parse(
processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE),
),
confidence: "observed",
authority: "controlled-replay",
});
const boundProofEvidence = (
id: string,
result: {
readonly obligation_ids: readonly string[];
readonly fixture_ids: readonly string[];
readonly case_kinds: readonly string[];
readonly verifier_ids: readonly string[];
readonly claim_ids: readonly string[];
},
authority: "shipped-artifact" | "controlled-replay" = "controlled-replay",
) =>
createEvidence(
undefined,
{ id: `proof-${id}`, name: "Reconstruction proof", version: "1" },
{
predicateType: "rea.reconstruction-proof",
operation: "verify_reconstruction_obligations",
parameters: {},
result: {
passed: true,
obligation_ids: [...result.obligation_ids],
fixture_ids: [...result.fixture_ids],
case_kinds: [...result.case_kinds],
verifier_ids: [...result.verifier_ids],
claim_ids: [...result.claim_ids],
},
confidence: "observed",
authority,
},
);
const request = (
records: readonly Evidence[],
overrides: Partial<ReconstructionObligationLedgerInput> = {},
): ReconstructionObligationLedgerInput => ({
evidence_bundle: createEvidenceBundle(records),
reviewed_obligations: [],
manifest: { bindings: [], contradictions: [] },
...overrides,
});
const build = (
input: ReconstructionObligationLedgerInput,
): ReconstructionObligationLedger => {
const parsed = resolveReconstructionObligationLedgerRequest(input);
if (!parsed.ok) throw parsed.error;
const result = buildReconstructionObligationLedgerEvidenceValidated(
parsed.value,
);
if (!result.ok) throw result.error;
return reconstructionObligationLedgerSchema.parse(
result.value.normalized_result,
);
};
const originalCases = (
obligation: ReconstructionObligationLedger["obligations"][number],
evidenceId: string,
) =>
obligation.required_case_kinds.map((caseKind) => ({
kind: caseKind,
evidence_id: evidenceId,
location: `/normalized_result/cases/${caseKind}`,
}));
boundProofEvidence,
build,
originalCases,
processEvidence,
proofEvidence,
request,
reviewedObligation,
} from "./ReconstructionObligationLedger.fixture.js";
import { createEvidence } from "../domain/evidence.js";
import type { ReviewedReconstructionObligation } from "../domain/reconstructionObligationLedgerSchemas.js";
describe("reconstruction obligation ledger", () => {
it("does not authenticate packaged-process proof with generic replay evidence", () => {
@@ -315,32 +219,3 @@ describe("reconstruction obligation ledger fail-closed behavior", () => {
]);
});
});
const reviewedObligation = (
obligationId: string,
evidenceId: string,
): ReviewedReconstructionObligation => ({
obligation_id: obligationId,
obligation_version: 1,
title: `Reviewed obligation ${obligationId}`,
application_layer: "other",
family: "reviewed",
target: {
artifact_sha256: null,
application_node_id: null,
semantic_node_id: null,
location: `/reviewed/${obligationId}`,
},
required: true,
required_case_kinds: ["positive"],
required_original_authority: "static",
required_fixture_authority: "unit",
required_verifier_authority: "unit",
requires_parser_type: false,
dependency_obligation_ids: [],
residual_unknown_ids: [],
unavailable_authority: [],
required_next_evidence: ["Bind an owner and verifier."],
disposition: "active",
review_evidence_ids: [evidenceId],
});
@@ -1,141 +1,14 @@
import { describe, expect, it } from "vitest";
import { PROCESS_PROVIDER } from "./process/ProcessEvidence.js";
import {
buildReconstructionObligationLedgerEvidenceValidated,
resolveReconstructionObligationLedgerRequest,
} from "./ReconstructionObligationLedgerService.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/process/processCapture.fixture.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { processCaptureSchema } from "../domain/process/processCapture.js";
import {
reconstructionObligationLedgerSchema,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedger,
type ReviewedReconstructionObligation,
} from "../domain/reconstructionObligationLedgerSchemas.js";
const proofEvidence = (id: string) =>
createEvidence(
undefined,
{ id: `fixture-${id}`, name: "Fixture verifier", version: "1" },
{
predicateType: "rea.fixture-verification",
operation: "run_fixture_verifier",
parameters: { id },
result: { passed: true },
confidence: "observed",
authority: "controlled-replay",
},
);
const processEvidence = () =>
createEvidence(undefined, PROCESS_PROVIDER, {
predicateType: "rea.process-capture",
operation: "capture_process_scenario",
parameters: {},
result: jsonValueSchema.parse(
processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE),
),
confidence: "observed",
authority: "controlled-replay",
});
const boundProofEvidence = (
id: string,
result: {
readonly obligation_ids: readonly string[];
readonly fixture_ids: readonly string[];
readonly case_kinds: readonly string[];
readonly verifier_ids: readonly string[];
readonly claim_ids: readonly string[];
},
authority: "shipped-artifact" | "controlled-replay" = "controlled-replay",
) =>
createEvidence(
undefined,
{ id: `proof-${id}`, name: "Reconstruction proof", version: "1" },
{
predicateType: "rea.reconstruction-proof",
operation: "verify_reconstruction_obligations",
parameters: {},
result: {
passed: true,
obligation_ids: [...result.obligation_ids],
fixture_ids: [...result.fixture_ids],
case_kinds: [...result.case_kinds],
verifier_ids: [...result.verifier_ids],
claim_ids: [...result.claim_ids],
},
confidence: "observed",
authority,
},
);
const request = (
records: readonly Evidence[],
overrides: Partial<ReconstructionObligationLedgerInput> = {},
): ReconstructionObligationLedgerInput => ({
evidence_bundle: createEvidenceBundle(records),
reviewed_obligations: [],
manifest: { bindings: [], contradictions: [] },
...overrides,
});
const build = (
input: ReconstructionObligationLedgerInput,
): ReconstructionObligationLedger => {
const parsed = resolveReconstructionObligationLedgerRequest(input);
if (!parsed.ok) throw parsed.error;
const result = buildReconstructionObligationLedgerEvidenceValidated(
parsed.value,
);
if (!result.ok) throw result.error;
return reconstructionObligationLedgerSchema.parse(
result.value.normalized_result,
);
};
const originalCases = (
obligation: ReconstructionObligationLedger["obligations"][number],
evidenceId: string,
) =>
obligation.required_case_kinds.map((caseKind) => ({
kind: caseKind,
evidence_id: evidenceId,
location: `/normalized_result/cases/${caseKind}`,
}));
const reviewedObligation = (
obligationId: string,
evidenceId: string,
): ReviewedReconstructionObligation => ({
obligation_id: obligationId,
obligation_version: 1,
title: `Reviewed obligation ${obligationId}`,
application_layer: "other",
family: "reviewed",
target: {
artifact_sha256: null,
application_node_id: null,
semantic_node_id: null,
location: `/reviewed/${obligationId}`,
},
required: true,
required_case_kinds: ["positive"],
required_original_authority: "static",
required_fixture_authority: "unit",
required_verifier_authority: "unit",
requires_parser_type: false,
dependency_obligation_ids: [],
residual_unknown_ids: [],
unavailable_authority: [],
required_next_evidence: ["Bind an owner and verifier."],
disposition: "active",
review_evidence_ids: [evidenceId],
});
boundProofEvidence,
build,
originalCases,
processEvidence,
proofEvidence,
request,
reviewedObligation,
} from "./ReconstructionObligationLedger.fixture.js";
describe("reconstruction obligation ledger", () => {
it("emits the complete deterministic ledger and closure identity", () => {
@@ -0,0 +1,143 @@
import { PROCESS_PROVIDER } from "./process/ProcessEvidence.js";
import {
buildReconstructionObligationLedgerEvidenceValidated,
resolveReconstructionObligationLedgerRequest,
} from "./ReconstructionObligationLedgerService.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/process/processCapture.fixture.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
import { processCaptureSchema } from "../domain/process/processCapture.js";
import {
reconstructionObligationLedgerSchema,
type ReconstructionObligationLedgerInput,
type ReconstructionObligationLedger,
type ReviewedReconstructionObligation,
} from "../domain/reconstructionObligationLedgerSchemas.js";
/** Build controlled fixture Evidence used by reconstruction-ledger scenarios. */
export const proofEvidence = (id: string) =>
createEvidence(
undefined,
{ id: `fixture-${id}`, name: "Fixture verifier", version: "1" },
{
predicateType: "rea.fixture-verification",
operation: "run_fixture_verifier",
parameters: { id },
result: { passed: true },
confidence: "observed",
authority: "controlled-replay",
},
);
/** Use the canonical empty process capture as a controlled Evidence example. */
export const processEvidence = () =>
createEvidence(undefined, PROCESS_PROVIDER, {
predicateType: "rea.process-capture",
operation: "capture_process_scenario",
parameters: {},
result: jsonValueSchema.parse(
processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE),
),
confidence: "observed",
authority: "controlled-replay",
});
/** Build verifier Evidence bound to enumerated fixture and obligation identities. */
export const boundProofEvidence = (
id: string,
result: {
readonly obligation_ids: readonly string[];
readonly fixture_ids: readonly string[];
readonly case_kinds: readonly string[];
readonly verifier_ids: readonly string[];
readonly claim_ids: readonly string[];
},
authority: "shipped-artifact" | "controlled-replay" = "controlled-replay",
) =>
createEvidence(
undefined,
{ id: `proof-${id}`, name: "Reconstruction proof", version: "1" },
{
predicateType: "rea.reconstruction-proof",
operation: "verify_reconstruction_obligations",
parameters: {},
result: {
passed: true,
obligation_ids: [...result.obligation_ids],
fixture_ids: [...result.fixture_ids],
case_kinds: [...result.case_kinds],
verifier_ids: [...result.verifier_ids],
claim_ids: [...result.claim_ids],
},
confidence: "observed",
authority,
},
);
/** Build a ledger request with empty reviewed input and manifest defaults. */
export const request = (
records: readonly Evidence[],
overrides: Partial<ReconstructionObligationLedgerInput> = {},
): ReconstructionObligationLedgerInput => ({
evidence_bundle: createEvidenceBundle(records),
reviewed_obligations: [],
manifest: { bindings: [], contradictions: [] },
...overrides,
});
/** Run request resolution, Evidence validation, and output parsing for a fixture. */
export const build = (
input: ReconstructionObligationLedgerInput,
): ReconstructionObligationLedger => {
const parsed = resolveReconstructionObligationLedgerRequest(input);
if (!parsed.ok) throw parsed.error;
const result = buildReconstructionObligationLedgerEvidenceValidated(
parsed.value,
);
if (!result.ok) throw result.error;
return reconstructionObligationLedgerSchema.parse(
result.value.normalized_result,
);
};
/** Create provenance for each required case from one source Evidence record. */
export const originalCases = (
obligation: ReconstructionObligationLedger["obligations"][number],
evidenceId: string,
) =>
obligation.required_case_kinds.map((caseKind) => ({
kind: caseKind,
evidence_id: evidenceId,
location: `/normalized_result/cases/${caseKind}`,
}));
/** Create a minimal reviewed obligation for ledger closure scenarios. */
export const reviewedObligation = (
obligationId: string,
evidenceId: string,
): ReviewedReconstructionObligation => ({
obligation_id: obligationId,
obligation_version: 1,
title: `Reviewed obligation ${obligationId}`,
application_layer: "other",
family: "reviewed",
target: {
artifact_sha256: null,
application_node_id: null,
semantic_node_id: null,
location: `/reviewed/${obligationId}`,
},
required: true,
required_case_kinds: ["positive"],
required_original_authority: "static",
required_fixture_authority: "unit",
required_verifier_authority: "unit",
requires_parser_type: false,
dependency_obligation_ids: [],
residual_unknown_ids: [],
unavailable_authority: [],
required_next_evidence: ["Bind an owner and verifier."],
disposition: "active",
review_evidence_ids: [evidenceId],
});
-9
View File
@@ -165,15 +165,6 @@ describe("setup workflow lifecycle and provider planning", () => {
remediation: null,
},
];
host.productRegistrations = [
{
client: "codex",
config_path: "/codex.toml",
command: ["rea", "mcp"],
state: "aligned",
remediation: null,
},
];
const progress: SetupProgressEvent[] = [];
await runSetup(
{ ...options(true), onProgress: (event) => progress.push(event) },
@@ -1,31 +0,0 @@
import { describe, expect, it } from "vitest";
import {
setupInstallFailure,
type HopperInstallFailureReason,
} from "./SetupInstallFailure.js";
describe("setup installer failure projection", () => {
it.each([
["unsupported_host", "unsupported_host"],
["download", "download_failed"],
["release_metadata", "download_failed"],
["integrity", "integrity_mismatch"],
[
"authorization_or_package_manager",
"authorization_or_package_manager_failed",
],
["launcher_missing", "launcher_missing"],
["runtime_dependencies", "runtime_dependency_unavailable"],
["unsupported_hopper_build", "unsupported_hopper_build"],
["cancelled", "setup_cancelled"],
["destination_exists", "destination_exists"],
["mount", "mount_failed"],
["bundle", "bundle_invalid"],
["copy", "copy_failed"],
] as const)("maps %s to %s", (reason, code) => {
const result = setupInstallFailure(reason as HopperInstallFailureReason);
expect(result).toMatchObject({ status: "failed", code });
expect(result).toMatchObject({ remediation: expect.stringMatching(/\S/u) });
});
});
@@ -1,40 +0,0 @@
import { describe, expect, it } from "vitest";
import type { DoctorProviderInspection } from "./Doctor.js";
import { providerRegistrationEnvironment } from "./SetupRegistrationEnvironment.js";
const inspection = (
overrides: Partial<DoctorProviderInspection> = {},
): DoctorProviderInspection => ({
id: "fixture",
configured: true,
available: false,
providerVersion: "1",
registrationEnvironment: {
REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider",
},
checks: [],
...overrides,
});
describe("provider registration settings", () => {
it("retains valid published paths when runtime capabilities are unavailable", () => {
expect(providerRegistrationEnvironment([inspection()])).toEqual({
REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider",
});
});
it("does not invent settings when the adapter cannot publish an installation", () => {
expect(
providerRegistrationEnvironment([
inspection({ configured: false, registrationEnvironment: {} }),
]),
).toEqual({});
});
it("keeps available-provider settings unchanged", () => {
expect(
providerRegistrationEnvironment([inspection({ available: true })]),
).toEqual({ REA_FIXTURE_PROVIDER_ROOT: "C:\\Tools\\Provider" });
});
});
@@ -1,9 +1,6 @@
import { describe, expect, it } from "vitest";
import {
AnalysisProviderRegistry,
type AnalysisProviderBinding,
} from "./AnalysisProviderRegistry.js";
import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
import {
createAnalysisExecution,
type AnalysisClient,
@@ -33,132 +30,77 @@ const ARTIFACT_TARGET: BinaryTarget = {
format: "asar",
};
describe("analysis provider registry: discovery and selection", () => {
it("admits overlapping operations, sorts discovery, and rejects duplicate IDs", () => {
const alpha = candidate("alpha");
const beta = candidate("beta");
const registry = new AnalysisProviderRegistry([
beta.provider,
alpha.provider,
]);
it("rejects malformed provider capability declarations", () => {
const alpha = candidate("alpha");
expect(
() => new AnalysisProviderRegistry([alpha.provider, alpha.provider]),
).toThrow(/Duplicate analysis provider ID: alpha/u);
expect(
() =>
new AnalysisProviderRegistry([
{
...alpha.provider,
capabilities: () => {
const descriptor = capability(alpha.provider.identity());
return [descriptor, descriptor];
},
},
]),
).toThrow(/declares operation address_name more than once/u);
expect(
() =>
new AnalysisProviderRegistry([
{
...alpha.provider,
capabilities: () => [
capability({ ...alpha.provider.identity(), name: "forged" }),
],
},
]),
).toThrow(/published mismatched capability provenance/u);
expect(
() => new AnalysisProviderRegistry([candidate("auto").provider]),
).toThrow(/Invalid analysis provider ID: auto/u);
});
expect(registry.identities().map(({ id }) => id)).toEqual([
"alpha",
"beta",
]);
expect(
registry.candidates().map(({ provider, targetSupport }) => ({
id: provider.id,
support: targetSupport.status,
})),
).toEqual([
{ id: "alpha", support: "unknown" },
{ id: "beta", support: "unknown" },
]);
expect(alpha.created).toEqual([]);
expect(beta.created).toEqual([]);
expect(
() => new AnalysisProviderRegistry([alpha.provider, alpha.provider]),
).toThrow(/Duplicate analysis provider ID: alpha/u);
expect(
() =>
new AnalysisProviderRegistry([
{
...alpha.provider,
capabilities: () => {
const descriptor = capability(alpha.provider.identity());
return [descriptor, descriptor];
},
},
]),
).toThrow(/declares operation address_name more than once/u);
expect(
() =>
new AnalysisProviderRegistry([
{
...alpha.provider,
capabilities: () => [
capability({
...alpha.provider.identity(),
name: "mismatched provider",
}),
],
},
]),
).toThrow(/published mismatched capability provenance/u);
expect(
() => new AnalysisProviderRegistry([candidate("auto").provider]),
).toThrow(/Invalid analysis provider ID: auto/u);
it("selects the sole available provider and honors explicit selection precedence", async () => {
const alpha = candidate("alpha");
const beta = candidate("beta", { available: false });
const sole = await new AnalysisProviderRegistry([
beta.provider,
alpha.provider,
]).select(DATABASE_TARGET);
expect(sole).toMatchObject({
ok: true,
value: {
binding: {
identity: { id: "alpha" },
selectionSource: "auto-single-candidate",
},
},
});
it("selects the sole usable candidate without creating a client", async () => {
const alpha = candidate("alpha");
const beta = candidate("beta", { available: false });
const registry = new AnalysisProviderRegistry([
beta.provider,
alpha.provider,
]);
const selected = await registry.select(DATABASE_TARGET);
expect(selected.ok).toBe(true);
if (!selected.ok) throw new Error("provider selection failed");
if (selected.value.binding === null)
throw new Error("expected a bound provider");
expect(bindingProjection(selected.value.binding)).toEqual({
id: "alpha",
source: "auto-single-candidate",
version: "1",
});
expect(selected.value.candidates).toMatchObject([
{ provider: { id: "alpha", version: "1" }, selected: true },
{
provider: { id: "beta" },
selected: false,
availability: { status: "unavailable", code: "runtime_missing" },
const selected = new AnalysisProviderRegistry(
[alpha.provider, candidate("beta").provider],
"beta",
);
expect(await selected.select(DATABASE_TARGET)).toMatchObject({
ok: true,
value: {
binding: {
identity: { id: "beta" },
selectionSource: "environment",
},
]);
expect(alpha.created).toEqual([]);
expect(beta.created).toEqual([]);
},
});
it("makes auto ambiguity explicit and honors request over environment", async () => {
const alpha = candidate("alpha");
const beta = candidate("beta");
const registry = new AnalysisProviderRegistry(
[alpha.provider, beta.provider],
"beta",
);
const environment = await registry.select(DATABASE_TARGET);
expect(environment.ok).toBe(true);
if (environment.ok)
expect(environment.value.binding?.selectionSource).toBe("environment");
expect(environment.ok && environment.value.binding?.identity.id).toBe(
"beta",
);
const request = await registry.select(DATABASE_TARGET, "alpha");
expect(request.ok && request.value.binding?.identity.id).toBe("alpha");
if (request.ok)
expect(request.value.binding?.selectionSource).toBe("request");
const automatic = await registry.select(DATABASE_TARGET, "auto");
expect(automatic.ok).toBe(false);
if (automatic.ok) return;
expect(automatic.error).toMatchObject({
reason: "ambiguous",
requestedProviderId: "auto",
candidateIds: ["alpha", "beta"],
});
expect(projectAnalysisError(automatic.error)).toMatchObject({
code: "capability_unavailable",
details: {
selection_reason: "ambiguous",
candidate_ids: ["alpha", "beta"],
expect(await selected.select(DATABASE_TARGET, "alpha")).toMatchObject({
ok: true,
value: {
binding: {
identity: { id: "alpha" },
selectionSource: "request",
},
});
expect(alpha.created).toEqual([]);
expect(beta.created).toEqual([]);
},
});
});
@@ -414,9 +356,3 @@ const capability = (provider: ProviderIdentity): CapabilityDescriptor => ({
},
limitations: [],
});
const bindingProjection = (binding: AnalysisProviderBinding) => ({
id: binding.identity.id,
source: binding.selectionSource,
version: binding.identity.version,
});
+1 -7
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { absenceClaimable, canonicalDigest } from "./comparisonSemantics.js";
import { absenceClaimable } from "./comparisonSemantics.js";
describe("shared comparison semantics", () => {
it("permits absence claims only for complete, non-truncated inventories", () => {
@@ -27,10 +27,4 @@ describe("shared comparison semantics", () => {
).toBe(false);
expect(absenceClaimable({ status: "partial" })).toBe(false);
});
it("produces one stable SHA-256 for equivalent canonical JSON values", () => {
expect(canonicalDigest({ b: 2, a: 1 })).toBe(
canonicalDigest({ a: 1, b: 2 }),
);
});
});
-6
View File
@@ -5,7 +5,6 @@ import {
isDigest,
isPrefixedDigest,
prefixedDigestSchema,
SHA256_PATTERN,
} from "./digests.js";
const DIGEST = "a".repeat(64);
@@ -59,9 +58,4 @@ describe("digest shapes", () => {
`jag_${OTHER_DIGEST}`,
);
});
it("exposes the pattern for callers that cannot use zod", () => {
expect(SHA256_PATTERN.test(DIGEST)).toBe(true);
expect(SHA256_PATTERN.test(DIGEST.toUpperCase())).toBe(false);
});
});
-21
View File
@@ -1,21 +0,0 @@
import { describe, expect, it } from "vitest";
import { emptyArraySchema } from "./emptyArraySchema.js";
describe("empty array schema", () => {
it("accepts only an empty array", () => {
expect(emptyArraySchema.parse([])).toEqual([]);
for (const value of [
[null],
["candidate"],
[0],
[false],
[{}],
[[]],
null,
{},
]) {
expect(emptyArraySchema.safeParse(value).success).toBe(false);
}
});
});
@@ -5,36 +5,10 @@ import {
emptyProcessCapture as emptyCapture,
} from "./processCapture.fixture.js";
it("classifies missing observations as unknown and one-sided evidence as added", () => {
const base = {
manifest: emptyCapture().manifest,
settlement: emptyCapture().settlement,
normalization: {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
},
frames: [],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" as const },
process_samples: [],
filesystem_checkpoints: emptyCapture().filesystem_checkpoints,
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [],
cleanup: {
owned_process_group: "verified" as const,
temporary_root: "removed" as const,
},
};
const added = compareProcessCaptures(base, {
...base,
it("distinguishes added evidence from unknown observations", () => {
const capture = emptyCapture();
const added = compareProcessCaptures(capture, {
...capture,
frames: [{ sequence: 0, at_ms: 0, data: "new" }],
rendered_frames: [
{
@@ -50,19 +24,30 @@ it("classifies missing observations as unknown and one-sided evidence as added",
},
],
});
expect(added.terminal).toBe("added");
expect(added.status).toBe("changed");
const unknown = compareProcessCaptures(
{ ...base, residual_unknowns: [{ scope: "process", reason: "sampled" }] },
base,
);
expect(unknown.process).toBe("unknown");
expect(unknown.status).toBe("unknown");
expect(added).toMatchObject({ terminal: "added", status: "changed" });
for (const [scope, dimension] of [
["process", "process"],
["interaction", "interaction"],
] as const) {
expect(
compareProcessCaptures(
{
...capture,
residual_unknowns: [{ scope, reason: "observation was partial" }],
},
capture,
),
).toMatchObject({
status: "unknown",
[dimension]: "unknown",
terminal: "unchanged",
});
}
});
it("treats equivalent normalization records as equal regardless of member order", () => {
it("compares raw terminal and process observations while honoring normalization shape", () => {
const capture = emptyCapture();
expect(
compareProcessCaptures(capture, {
...capture,
@@ -78,84 +63,61 @@ it("treats equivalent normalization records as equal regardless of member order"
status: "unchanged",
first_divergence: { status: "none" },
});
const terminal = compareProcessCaptures(
{ ...capture, frames: [{ sequence: 0, at_ms: 0, data: "bar" }] },
{ ...capture, frames: [{ sequence: 0, at_ms: 0, data: "foo\rbar" }] },
);
expect(terminal).toMatchObject({
terminal: "changed",
first_divergence: { status: "found", dimension: "terminal" },
});
expect(
compareProcessCaptures(
{
...capture,
process_samples: [
{
at_ms: 10,
pid: 1,
parent_pid: 0,
process_group_id: 1,
session_id: 1,
command: "worker",
},
],
},
{
...capture,
process_samples: [
{
at_ms: 20,
pid: 1,
parent_pid: 2,
process_group_id: 1,
session_id: 1,
command: "worker",
},
],
},
),
).toMatchObject({ process: "changed", status: "changed" });
});
it("compares raw terminal chunks even when rendered states agree", () => {
const capture = {
manifest: emptyCapture().manifest,
settlement: emptyCapture().settlement,
normalization: {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
},
frames: [{ sequence: 0, at_ms: 0, data: "bar" }],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" as const },
process_samples: [],
filesystem_checkpoints: emptyCapture().filesystem_checkpoints,
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [],
cleanup: {
owned_process_group: "verified" as const,
temporary_root: "removed" as const,
},
};
const comparison = compareProcessCaptures(capture, {
it("keeps filesystem comparison unknown without coverage and reports observed changes", () => {
const capture = emptyCapture();
const incomplete = {
...capture,
frames: [{ sequence: 0, at_ms: 0, data: "foo\rbar" }],
});
expect(comparison.terminal).toBe("changed");
expect(comparison.first_divergence).toMatchObject({
status: "found",
dimension: "terminal",
});
});
it("keeps filesystem evidence unknown when stable snapshots match", () => {
const capture = {
manifest: emptyCapture().manifest,
settlement: emptyCapture().settlement,
normalization: {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
},
frames: [],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" as const },
process_samples: [],
filesystem_checkpoints: emptyCapture().filesystem_checkpoints,
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [
{ scope: "filesystem" as const, reason: "watcher unavailable" },
],
cleanup: {
owned_process_group: "verified" as const,
temporary_root: "removed" as const,
},
};
expect(compareProcessCaptures(incomplete, incomplete)).toMatchObject({
filesystem: "unknown",
status: "unknown",
});
const comparison = compareProcessCaptures(capture, capture);
expect(comparison.filesystem).toBe("unknown");
expect(comparison.status).toBe("unknown");
const complete = { ...capture, residual_unknowns: [] };
const finalFile = {
path: "root_0:created.txt",
type: "file" as const,
@@ -164,35 +126,35 @@ it("keeps filesystem evidence unknown when stable snapshots match", () => {
sha256: "a".repeat(64),
symlink_target: null,
};
const changed = compareProcessCaptures(complete, {
...complete,
filesystem_checkpoints: [
{ name: "before", at_ms: 0, files: [], effects: [], truncated: false },
{
name: "after_settlement",
at_ms: 10,
files: [finalFile],
effects: [
{
path: finalFile.path,
status: "created",
before: null,
after: finalFile,
},
],
truncated: false,
},
],
files_after: [finalFile],
filesystem_effects: [
{
path: finalFile.path,
status: "created",
before: null,
after: finalFile,
},
],
});
expect(changed.filesystem).toBe("changed");
expect(changed.status).toBe("changed");
expect(
compareProcessCaptures(capture, {
...capture,
filesystem_checkpoints: [
{ name: "before", at_ms: 0, files: [], effects: [], truncated: false },
{
name: "after_settlement",
at_ms: 10,
files: [finalFile],
effects: [
{
path: finalFile.path,
status: "created",
before: null,
after: finalFile,
},
],
truncated: false,
},
],
files_after: [finalFile],
filesystem_effects: [
{
path: finalFile.path,
status: "created",
before: null,
after: finalFile,
},
],
}),
).toMatchObject({ filesystem: "changed", status: "changed" });
});
@@ -1,62 +0,0 @@
import { expect, it } from "vitest";
import {
compareUnverifiedProcessCaptures as compareProcessCaptures,
emptyProcessCapture as emptyCapture,
} from "./processCapture.fixture.js";
it("detects changes in normalized process sample metadata", () => {
const capture = {
manifest: emptyCapture().manifest,
settlement: emptyCapture().settlement,
normalization: {
paths: true,
pids: true,
ports: true,
time_bucket_ms: 10,
patterns: [],
},
frames: [],
rendered_frames: [],
interaction_events: [],
exit: { code: 0, signal: null, reason: "exited" as const },
process_samples: [
{
at_ms: 10,
pid: 1,
parent_pid: 0,
process_group_id: 1,
session_id: 1,
command: "worker",
},
],
filesystem_checkpoints: emptyCapture().filesystem_checkpoints,
files_before: [],
files_after: [],
filesystem_effects: [],
truncated: false,
limitations: [],
residual_unknowns: [],
cleanup: {
owned_process_group: "verified" as const,
temporary_root: "removed" as const,
},
};
const changed = {
...capture,
process_samples: [
{
at_ms: 20,
pid: 1,
parent_pid: 2,
process_group_id: 1,
session_id: 1,
command: "worker",
},
],
};
const comparison = compareProcessCaptures(capture, changed);
expect(comparison.process).toBe("changed");
expect(comparison.status).toBe("changed");
});
@@ -1,32 +0,0 @@
import { expect, it } from "vitest";
import {
PROCESS_PROVIDER,
isProcessEvidenceProvider,
} from "./processEvidenceProvider.js";
it.each(["REA process capture", "REA deterministic process harness"])(
"accepts the exact v3 process provider name %s",
(name) => {
expect(isProcessEvidenceProvider({ ...PROCESS_PROVIDER, name })).toBe(true);
},
);
it.each([
{ name: "REA process capture extra" },
{ name: "REA deterministic process harness extra" },
{ name: "rea process capture" },
{ name: "REA Process Capture" },
{ name: " REA process capture" },
{ name: "REA process capture " },
{ name: "REA deterministic process harness " },
{ name: "Fixture process provider" },
{ id: "fixture-process" },
{ version: "2" },
{ version: "4" },
{ version: null },
])("rejects other process identities: %j", (change) => {
expect(isProcessEvidenceProvider({ ...PROCESS_PROVIDER, ...change })).toBe(
false,
);
});
+2 -23
View File
@@ -5,10 +5,7 @@ import {
processCaptureSchema,
type UnverifiedProcessCapture,
} from "./processCapture.js";
import {
createProcessObservation,
projectProcessObservation,
} from "./processObservation.js";
import { projectProcessObservation } from "./processObservation.js";
const emptyCapture = processCaptureSchema.parse(EMPTY_PROCESS_CAPTURE_EXAMPLE);
@@ -86,25 +83,7 @@ describe("process observation vocabulary", () => {
expect(observations[4]?.captured_at_ms).toBeNull();
});
it("uses the same constructor for live and post-hoc observations", () => {
const location = {
collection: "frames" as const,
index: 0,
capture_order: 4,
};
const projected = projectProcessObservation(capture, location);
const live = createProcessObservation({
source: "terminal_raw",
source_sequence: 0,
captured_at_ms: 1,
subject_id: null,
location,
payload: capture.frames[0],
});
expect(live).toEqual(projected);
});
it("returns no observation for a missing raw record", () => {
it("returns no observation for a missing collection record", () => {
expect(
projectProcessObservation(capture, {
collection: "frames",
-22
View File
@@ -1,22 +0,0 @@
import { describe, expect, it } from "vitest";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
describe("Unicode code-point ordering", () => {
it("orders supplementary characters by scalar value rather than UTF-16 units", () => {
expect(compareUnicodeCodePoints("\uE000", "\u{10000}")).toBeLessThan(0);
expect(compareUnicodeCodePoints("\u{10000}", "\uE000")).toBeGreaterThan(0);
});
it.each([
["", "a", -1],
["same", "same", 0],
["a", "aa", -1],
["e\u0301", "\u00e9", -132],
["\uD800", "\uD801", -1],
])("orders %j and %j by scalar value", (left, right, expectedSign) => {
expect(Math.sign(compareUnicodeCodePoints(left, right))).toBe(
Math.sign(expectedSign),
);
});
});
+1 -30
View File
@@ -5,36 +5,7 @@ import {
type ProcessOwnershipHost,
} from "./ProcessOwnership.js";
import { observeOwnedProcessLineage } from "./ProcessOwnershipObservation.js";
const ownership = {
runId: "run-token",
leaderPid: 100,
processGroupId: 100,
};
const host = (
environments: Readonly<Record<number, Readonly<Record<string, string>>>>,
): {
readonly adapter: ProcessOwnershipHost;
readonly signalGroup: ReturnType<typeof vi.fn>;
} => {
const signalGroup = vi.fn();
return {
adapter: {
listProcesses: () =>
Promise.resolve(
Object.keys(environments).map((pid) => ({
pid: Number(pid),
parentPid: Number(pid) === 100 ? 1 : 100,
processGroupId: 100,
state: "S",
command: "fixture",
})),
),
environment: (pid) => Promise.resolve(environments[pid] ?? {}),
signalGroup,
},
signalGroup,
};
};
import { host, ownership } from "./ProcessOwnership.fixture.js";
describe("owned process-group cleanup discovery", () => {
it("signals token-owned groups that were reparented outside the launcher tree", async () => {
const signalGroup = vi.fn();
+1 -30
View File
@@ -6,36 +6,7 @@ import {
import { parseProcessEnvironment } from "./ProcessOwnershipObservation.js";
import { selectCapturedProcessGroupIds } from "./ProcessOwnershipProcessTree.js";
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
const ownership = {
runId: "run-token",
leaderPid: 100,
processGroupId: 100,
};
const host = (
environments: Readonly<Record<number, Readonly<Record<string, string>>>>,
): {
readonly adapter: ProcessOwnershipHost;
readonly signalGroup: ReturnType<typeof vi.fn>;
} => {
const signalGroup = vi.fn();
return {
adapter: {
listProcesses: () =>
Promise.resolve(
Object.keys(environments).map((pid) => ({
pid: Number(pid),
parentPid: Number(pid) === 100 ? 1 : 100,
processGroupId: 100,
state: "S",
command: "fixture",
})),
),
environment: (pid) => Promise.resolve(environments[pid] ?? {}),
signalGroup,
},
signalGroup,
};
};
import { host, ownership } from "./ProcessOwnership.fixture.js";
describe("owned process-group cleanup discovery", () => {
it("excludes sampled groups whose leader was outside the captured tree", () => {
expect(
@@ -2,12 +2,9 @@ import { expect, it } from "vitest";
import { snapshotRoots } from "./FilesystemSnapshot.js";
import { buildCaptureResult } from "./ProcessCaptureLifecycle.js";
import { normalizeProcessSamples } from "./ProcessNormalization.js";
import { isInitializedPtyRoot, readLinuxChildren } from "./ProcessSampling.js";
import { TerminalRenderer } from "./TerminalRenderer.js";
import {
compareProcessCaptures,
parseProcessCapture,
parseProcessScenario,
type ProcessCapture,
} from "../../domain/process/processCapture.js";
@@ -139,33 +136,6 @@ it("marks redacted scripted input as an interaction unknown", () => {
});
});
it("normalizes every sampled process identifier in command text", () => {
const samples = normalizeProcessSamples(
[
{
at_ms: 0,
pid: 101,
parent_pid: 0,
process_group_id: 101,
session_id: 101,
command: "root 101",
},
{
at_ms: 10,
pid: 202,
parent_pid: 101,
process_group_id: 101,
session_id: 101,
command: "child 202 peer=101 unrelated 1202",
},
],
parseProcessScenario(base),
101,
);
expect(samples[1]?.command).toBe("child <pid> peer=<pid> unrelated 1202");
});
it("collects and deduplicates children from every Linux thread", async () => {
const signal = new AbortController().signal;
expect(
@@ -225,35 +195,6 @@ it("admits PTY samples only after stable session and token setup", () => {
).toBe(true);
});
it("keeps interaction and process residual uncertainty in separate scopes", () => {
const baseCapture = emptyCapture();
const interaction = parseProcessCapture({
...baseCapture,
residual_unknowns: [
{ scope: "interaction", reason: "Interaction capture was partial." },
],
});
const process = parseProcessCapture({
...baseCapture,
residual_unknowns: [
{ scope: "process", reason: "Process sampling was partial." },
],
});
expect(compareProcessCaptures(interaction, baseCapture)).toMatchObject({
status: "unknown",
terminal: "unchanged",
interaction: "unknown",
process: "unchanged",
});
expect(compareProcessCaptures(process, baseCapture)).toMatchObject({
status: "unknown",
terminal: "unchanged",
interaction: "unchanged",
process: "unknown",
});
});
it("cancels filesystem snapshots before traversing declared roots", async () => {
const controller = new AbortController();
controller.abort();
@@ -267,28 +208,3 @@ it("cancels filesystem snapshots before traversing declared roots", async () =>
),
).rejects.toMatchObject({ name: "AbortError" });
});
it("parses bounded scenarios and rejects unordered events", () => {
const timeoutMs = parseProcessScenario(base).timeout_ms;
expect(() =>
parseProcessScenario({
...base,
events: [
{ type: "input", at_ms: 2, data: "a" },
{ type: "input", at_ms: 1, data: "b" },
],
}),
).toThrow(/ordered/);
expect(
parseProcessScenario({
...base,
environment: { HOME: "/caller-selected-home" },
}).environment.HOME,
).toBe("/caller-selected-home");
expect(() =>
parseProcessScenario({
...base,
events: [{ type: "input", at_ms: timeoutMs + 1, data: "late" }],
}),
).toThrow(/after the scenario timeout/);
});
+106 -114
View File
@@ -1,10 +1,7 @@
import { expect, it } from "vitest";
import { normalizeProcessSamples } from "./ProcessNormalization.js";
import {
parseProcessScenario,
type ProcessSample,
} from "../../domain/process/processCapture.js";
import { parseProcessScenario } from "../../domain/process/processCapture.js";
const rootPid = 41_010;
const samples = Object.freeze(
@@ -23,7 +20,8 @@ const samples = Object.freeze(
parent_pid: rootPid,
process_group_id: 43_030,
session_id: 44_040,
command: "child 42020 parent 41010 group 43030 session 44040",
command:
" /workspace/program child 42020 parent 41010 group 43030 session 44040 listen:8080 marker ",
},
{
at_ms: 39,
@@ -54,114 +52,108 @@ const samples = Object.freeze(
),
);
it.each([false, true, undefined])(
"projects only declared sample fields without mutating inputs when pids is %s",
(pids) => {
const scenario = parseProcessScenario({
executable: "/usr/bin/node",
...(pids === undefined ? {} : { normalization: { pids } }),
});
const before = JSON.stringify(samples);
const normalized = normalizeProcessSamples(samples, scenario, rootPid);
const identities =
pids === false
? [
[rootPid, 40_000, rootPid, rootPid],
[42_020, rootPid, 43_030, 44_040],
[45_050, 42_020, 43_030, 44_040],
[46_060, 0, null, 0],
[47_070, 0, 0, null],
]
: [
[1, 2, 1, 1],
[3, 1, 4, 5],
[6, 3, 4, 5],
[7, 0, null, 0],
[8, 0, 0, null],
];
const commands =
pids === false
? samples.map(({ command }) => command.trim())
: [
"root <pid>",
"child <pid> parent <pid> group <pid> session <pid>",
"peer <pid> unrelated 145050",
"unknown <pid>",
"unavailable <pid>",
];
const scenario = (normalization: {
readonly pids?: boolean;
readonly paths?: boolean;
readonly ports?: boolean;
}) =>
parseProcessScenario({
executable: "/usr/bin/node",
working_directory: "/workspace",
normalization: {
paths: true,
...normalization,
time_bucket_ms: 10,
patterns: [{ pattern: "marker", replacement: "selected" }],
},
});
expect(scenario.normalization.pids).toBe(pids ?? true);
expect(normalized).toEqual(
identities.map(
([pid, parent_pid, process_group_id, session_id], index) => ({
at_ms: (index + 1) * 10,
pid,
parent_pid,
process_group_id,
session_id,
command: commands[index],
}),
),
);
expect(normalized).not.toBe(samples);
for (const [index, sample] of normalized.entries()) {
expect(sample).not.toBe(samples[index]);
expect(Object.keys(sample).sort()).toEqual(
[
"at_ms",
"pid",
"parent_pid",
"process_group_id",
"session_id",
"command",
].sort(),
);
}
expect(JSON.stringify(samples)).toBe(before);
},
);
it("normalizes representative process identities and preserves null, zero, and unrelated numbers", () => {
const before = JSON.stringify(samples);
const normalized = normalizeProcessSamples(samples, scenario({}), rootPid);
for (const pids of [false, true]) {
for (const paths of [false, true]) {
it.each([false, true])(
`keeps time, patterns, paths=${String(paths)}, and ports=%s independent of pids=${String(pids)}`,
(ports) => {
const sample: ProcessSample = Object.freeze({
at_ms: 29,
pid: 42_020,
parent_pid: rootPid,
process_group_id: 43_030,
session_id: 44_040,
command:
" /workspace/program child 42020 parent 41010 group 43030 session 44040 listen:8080 marker ",
});
const scenario = parseProcessScenario({
executable: "/usr/bin/node",
working_directory: "/workspace",
normalization: {
pids,
paths,
ports,
time_bucket_ms: 25,
patterns: [{ pattern: "marker", replacement: "selected" }],
},
});
const normalized = normalizeProcessSamples([sample], scenario, rootPid);
const directory = paths ? "<working-directory>" : "/workspace";
const identifiers = pids
? "child <pid> parent <pid> group <pid> session <pid>"
: "child 42020 parent 41010 group 43030 session 44040";
expect(normalized).toEqual([
{
at_ms: 25,
pid: pids ? 2 : 42_020,
parent_pid: pids ? 1 : rootPid,
process_group_id: pids ? 3 : 43_030,
session_id: pids ? 4 : 44_040,
command: `${directory}/program ${identifiers} listen:${ports ? "<port>" : "8080"} selected`,
},
]);
},
);
}
}
expect(normalized).toEqual([
{
at_ms: 10,
pid: 1,
parent_pid: 2,
process_group_id: 1,
session_id: 1,
command: "root <pid>",
},
{
at_ms: 20,
pid: 3,
parent_pid: 1,
process_group_id: 4,
session_id: 5,
command:
"<working-directory>/program child <pid> parent <pid> group <pid> session <pid> listen:<port> selected",
},
{
at_ms: 30,
pid: 6,
parent_pid: 3,
process_group_id: 4,
session_id: 5,
command: "peer <pid> unrelated 145050",
},
{
at_ms: 40,
pid: 7,
parent_pid: 0,
process_group_id: null,
session_id: 0,
command: "unknown <pid>",
},
{
at_ms: 50,
pid: 8,
parent_pid: 0,
process_group_id: 0,
session_id: null,
command: "unavailable <pid>",
},
]);
expect(JSON.stringify(samples)).toBe(before);
expect(normalized.every((sample) => !("runtime_metadata" in sample))).toBe(
true,
);
const pathAndPortPreserved = normalizeProcessSamples(
samples,
scenario({ paths: false, ports: false }),
rootPid,
);
expect(pathAndPortPreserved[1]).toMatchObject({
pid: 3,
parent_pid: 1,
command:
"/workspace/program child <pid> parent <pid> group <pid> session <pid> listen:8080 selected",
});
const identifiersPreserved = normalizeProcessSamples(
samples,
scenario({ pids: false }),
rootPid,
);
expect(
identifiersPreserved.map(
({ pid, parent_pid, process_group_id, session_id }) => [
pid,
parent_pid,
process_group_id,
session_id,
],
),
).toEqual([
[rootPid, 40_000, rootPid, rootPid],
[42_020, rootPid, 43_030, 44_040],
[45_050, 42_020, 43_030, 44_040],
[46_060, 0, null, 0],
[47_070, 0, 0, null],
]);
expect(identifiersPreserved[1]?.command).toBe(
"<working-directory>/program child 42020 parent 41010 group 43030 session 44040 listen:<port> selected",
);
});
-20
View File
@@ -104,26 +104,6 @@ describe("verifier run identity", { concurrent: false }, () => {
}
},
);
it.runIf(process.platform !== "win32")(
"bounds cleanup when a verifier child ignores SIGTERM",
async () => {
const child = spawn(
process.execPath,
[
"-e",
"process.on('SIGTERM', () => {}); process.stdout.write('ready'); setInterval(() => {}, 1000)",
],
{ stdio: ["ignore", "pipe", "ignore"] },
);
try {
await childReady(child);
} finally {
await stopChild(child);
}
expect(child.signalCode).toBe("SIGKILL");
},
);
});
const spawnReadyChild = (env: NodeJS.ProcessEnv): ChildProcess =>