mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
test: consolidate service and process fixtures
This commit is contained in:
@@ -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],
|
||||
});
|
||||
@@ -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,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 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user