From f8a53a8c065f1ccbdaa051a4e1ef267a2047d759 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 8 Oct 2026 03:59:24 +0800 Subject: [PATCH] test: consolidate service and process fixtures --- .../ArtifactGraphConstruction.test.ts | 53 +--- .../ArtifactInventoryPolicy.test.ts | 14 - .../CapabilityInventory.contract.test.ts | 28 -- src/application/CapabilityInventory.test.ts | 18 ++ ...ReconstructionObligationCandidates.test.ts | 21 +- ...tructionObligationLedger.authority.test.ts | 145 +---------- ...nstructionObligationLedger.closure.test.ts | 143 +--------- .../ReconstructionObligationLedger.fixture.ts | 143 ++++++++++ src/application/Setup.planning.test.ts | 9 - src/application/SetupInstallFailure.test.ts | 31 --- .../SetupRegistrationEnvironment.test.ts | 40 --- .../binary/AnalysisProviderRegistry.test.ts | 196 +++++--------- src/domain/comparisonSemantics.test.ts | 8 +- src/domain/digests.test.ts | 6 - src/domain/emptyArraySchema.test.ts | 21 -- .../process/processCapture.comparison.test.ts | 244 ++++++++---------- .../processCapture.traceProjection.test.ts | 62 ----- .../process/processEvidenceProvider.test.ts | 32 --- src/domain/process/processObservation.test.ts | 25 +- src/domain/unicodeCodePointOrder.test.ts | 22 -- src/process/ProcessOwnership.part2.test.ts | 31 +-- src/process/ProcessOwnership.test.ts | 31 +-- .../capture/ProcessCaptureObservation.test.ts | 84 ------ .../capture/ProcessNormalization.test.ts | 220 ++++++++-------- tests/process-global/verifierRun.test.ts | 20 -- 25 files changed, 471 insertions(+), 1176 deletions(-) delete mode 100644 src/application/ArtifactInventoryPolicy.test.ts delete mode 100644 src/application/CapabilityInventory.contract.test.ts create mode 100644 src/application/ReconstructionObligationLedger.fixture.ts delete mode 100644 src/application/SetupInstallFailure.test.ts delete mode 100644 src/application/SetupRegistrationEnvironment.test.ts delete mode 100644 src/domain/emptyArraySchema.test.ts delete mode 100644 src/domain/process/processCapture.traceProjection.test.ts delete mode 100644 src/domain/process/processEvidenceProvider.test.ts delete mode 100644 src/domain/unicodeCodePointOrder.test.ts diff --git a/src/application/ArtifactGraphConstruction.test.ts b/src/application/ArtifactGraphConstruction.test.ts index 1454a504c..93afadcb4 100644 --- a/src/application/ArtifactGraphConstruction.test.ts +++ b/src/application/ArtifactGraphConstruction.test.ts @@ -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", + ]); }); diff --git a/src/application/ArtifactInventoryPolicy.test.ts b/src/application/ArtifactInventoryPolicy.test.ts deleted file mode 100644 index 66c89c0e4..000000000 --- a/src/application/ArtifactInventoryPolicy.test.ts +++ /dev/null @@ -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" }); - }); -}); diff --git a/src/application/CapabilityInventory.contract.test.ts b/src/application/CapabilityInventory.contract.test.ts deleted file mode 100644 index c9200f405..000000000 --- a/src/application/CapabilityInventory.contract.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/application/CapabilityInventory.test.ts b/src/application/CapabilityInventory.test.ts index 2f960f15f..8ddf4f724 100644 --- a/src/application/CapabilityInventory.test.ts +++ b/src/application/CapabilityInventory.test.ts @@ -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(), diff --git a/src/application/ReconstructionObligationCandidates.test.ts b/src/application/ReconstructionObligationCandidates.test.ts index d3ddc3224..e1f1f2986 100644 --- a/src/application/ReconstructionObligationCandidates.test.ts +++ b/src/application/ReconstructionObligationCandidates.test.ts @@ -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)) diff --git a/src/application/ReconstructionObligationLedger.authority.test.ts b/src/application/ReconstructionObligationLedger.authority.test.ts index 650dd75fa..401088add 100644 --- a/src/application/ReconstructionObligationLedger.authority.test.ts +++ b/src/application/ReconstructionObligationLedger.authority.test.ts @@ -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 => ({ - 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], -}); diff --git a/src/application/ReconstructionObligationLedger.closure.test.ts b/src/application/ReconstructionObligationLedger.closure.test.ts index 80838c194..e11bffadd 100644 --- a/src/application/ReconstructionObligationLedger.closure.test.ts +++ b/src/application/ReconstructionObligationLedger.closure.test.ts @@ -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 => ({ - 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", () => { diff --git a/src/application/ReconstructionObligationLedger.fixture.ts b/src/application/ReconstructionObligationLedger.fixture.ts new file mode 100644 index 000000000..1892a4442 --- /dev/null +++ b/src/application/ReconstructionObligationLedger.fixture.ts @@ -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 => ({ + 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], +}); diff --git a/src/application/Setup.planning.test.ts b/src/application/Setup.planning.test.ts index 3654c2dcf..dbcd96f6f 100644 --- a/src/application/Setup.planning.test.ts +++ b/src/application/Setup.planning.test.ts @@ -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) }, diff --git a/src/application/SetupInstallFailure.test.ts b/src/application/SetupInstallFailure.test.ts deleted file mode 100644 index 088fc4b91..000000000 --- a/src/application/SetupInstallFailure.test.ts +++ /dev/null @@ -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) }); - }); -}); diff --git a/src/application/SetupRegistrationEnvironment.test.ts b/src/application/SetupRegistrationEnvironment.test.ts deleted file mode 100644 index 295794c76..000000000 --- a/src/application/SetupRegistrationEnvironment.test.ts +++ /dev/null @@ -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 => ({ - 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" }); - }); -}); diff --git a/src/application/binary/AnalysisProviderRegistry.test.ts b/src/application/binary/AnalysisProviderRegistry.test.ts index acbbae5fc..8c0b2ed4e 100644 --- a/src/application/binary/AnalysisProviderRegistry.test.ts +++ b/src/application/binary/AnalysisProviderRegistry.test.ts @@ -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, -}); diff --git a/src/domain/comparisonSemantics.test.ts b/src/domain/comparisonSemantics.test.ts index 9e86c0254..a2c569f6a 100644 --- a/src/domain/comparisonSemantics.test.ts +++ b/src/domain/comparisonSemantics.test.ts @@ -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 }), - ); - }); }); diff --git a/src/domain/digests.test.ts b/src/domain/digests.test.ts index 9593cef6f..7beb70881 100644 --- a/src/domain/digests.test.ts +++ b/src/domain/digests.test.ts @@ -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); - }); }); diff --git a/src/domain/emptyArraySchema.test.ts b/src/domain/emptyArraySchema.test.ts deleted file mode 100644 index c2cf8a3da..000000000 --- a/src/domain/emptyArraySchema.test.ts +++ /dev/null @@ -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); - } - }); -}); diff --git a/src/domain/process/processCapture.comparison.test.ts b/src/domain/process/processCapture.comparison.test.ts index 431fef24e..93d724627 100644 --- a/src/domain/process/processCapture.comparison.test.ts +++ b/src/domain/process/processCapture.comparison.test.ts @@ -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" }); }); diff --git a/src/domain/process/processCapture.traceProjection.test.ts b/src/domain/process/processCapture.traceProjection.test.ts deleted file mode 100644 index b6b77917a..000000000 --- a/src/domain/process/processCapture.traceProjection.test.ts +++ /dev/null @@ -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"); -}); diff --git a/src/domain/process/processEvidenceProvider.test.ts b/src/domain/process/processEvidenceProvider.test.ts deleted file mode 100644 index 637dd36e6..000000000 --- a/src/domain/process/processEvidenceProvider.test.ts +++ /dev/null @@ -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, - ); -}); diff --git a/src/domain/process/processObservation.test.ts b/src/domain/process/processObservation.test.ts index 7ebf55a25..134938572 100644 --- a/src/domain/process/processObservation.test.ts +++ b/src/domain/process/processObservation.test.ts @@ -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", diff --git a/src/domain/unicodeCodePointOrder.test.ts b/src/domain/unicodeCodePointOrder.test.ts deleted file mode 100644 index 9ac4acf68..000000000 --- a/src/domain/unicodeCodePointOrder.test.ts +++ /dev/null @@ -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), - ); - }); -}); diff --git a/src/process/ProcessOwnership.part2.test.ts b/src/process/ProcessOwnership.part2.test.ts index 7e6e81b4c..6ee89b19e 100644 --- a/src/process/ProcessOwnership.part2.test.ts +++ b/src/process/ProcessOwnership.part2.test.ts @@ -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>>>, -): { - readonly adapter: ProcessOwnershipHost; - readonly signalGroup: ReturnType; -} => { - 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(); diff --git a/src/process/ProcessOwnership.test.ts b/src/process/ProcessOwnership.test.ts index d565f0494..ccbbd8c24 100644 --- a/src/process/ProcessOwnership.test.ts +++ b/src/process/ProcessOwnership.test.ts @@ -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>>>, -): { - readonly adapter: ProcessOwnershipHost; - readonly signalGroup: ReturnType; -} => { - 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( diff --git a/src/process/capture/ProcessCaptureObservation.test.ts b/src/process/capture/ProcessCaptureObservation.test.ts index 3cc3d0b2c..9f476bf26 100644 --- a/src/process/capture/ProcessCaptureObservation.test.ts +++ b/src/process/capture/ProcessCaptureObservation.test.ts @@ -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 peer= 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/); -}); diff --git a/src/process/capture/ProcessNormalization.test.ts b/src/process/capture/ProcessNormalization.test.ts index d7d0101c4..7a15b8b96 100644 --- a/src/process/capture/ProcessNormalization.test.ts +++ b/src/process/capture/ProcessNormalization.test.ts @@ -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 ", - "child parent group session ", - "peer unrelated 145050", - "unknown ", - "unavailable ", - ]; +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 ? "" : "/workspace"; - const identifiers = pids - ? "child parent group session " - : "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 ? "" : "8080"} selected`, - }, - ]); - }, - ); - } -} + expect(normalized).toEqual([ + { + at_ms: 10, + pid: 1, + parent_pid: 2, + process_group_id: 1, + session_id: 1, + command: "root ", + }, + { + at_ms: 20, + pid: 3, + parent_pid: 1, + process_group_id: 4, + session_id: 5, + command: + "/program child parent group session listen: selected", + }, + { + at_ms: 30, + pid: 6, + parent_pid: 3, + process_group_id: 4, + session_id: 5, + command: "peer unrelated 145050", + }, + { + at_ms: 40, + pid: 7, + parent_pid: 0, + process_group_id: null, + session_id: 0, + command: "unknown ", + }, + { + at_ms: 50, + pid: 8, + parent_pid: 0, + process_group_id: 0, + session_id: null, + command: "unavailable ", + }, + ]); + 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 parent group session 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( + "/program child 42020 parent 41010 group 43030 session 44040 listen: selected", + ); +}); diff --git a/tests/process-global/verifierRun.test.ts b/tests/process-global/verifierRun.test.ts index 3b42e282d..10cb80f64 100644 --- a/tests/process-global/verifierRun.test.ts +++ b/tests/process-global/verifierRun.test.ts @@ -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 =>