diff --git a/src/application/AnalysisContextQueries.test.ts b/src/application/AnalysisContextQueries.test.ts index 3f876ff260..cfc9dde921 100644 --- a/src/application/AnalysisContextQueries.test.ts +++ b/src/application/AnalysisContextQueries.test.ts @@ -18,7 +18,7 @@ import { err, ok } from "../domain/result.js"; const provider = { id: "fixture", name: "Fixture", version: "1" }; -describe("analysis context queries: navigation", () => { +const registerNavigationSelectionValidationTests = (): void => { it.each([ [null, "0x401000", "current_document"], ["fixture", null, "current_address"], @@ -47,6 +47,10 @@ describe("analysis context queries: navigation", () => { ); }, ); +}; + +describe("analysis context queries: navigation", () => { + registerNavigationSelectionValidationTests(); it("aggregates volatile navigation and treats a missing procedure as null", async () => { const calls: { diff --git a/src/contracts/toolInputReferences.test.ts b/src/contracts/toolInputReferences.test.ts index c4c40b9185..fd61c553e2 100644 --- a/src/contracts/toolInputReferences.test.ts +++ b/src/contracts/toolInputReferences.test.ts @@ -419,6 +419,76 @@ describe("root input presentation", () => { }); }); +const registerBooleanLiteralCompatibilityTests = (): void => { + it("keeps canonical boolean-literal inputs accepted after advertisement", () => { + const canonical = { + type: "object", + properties: { + enabled: { type: "boolean", const: false }, + }, + required: ["enabled"], + additionalProperties: false, + }; + const advertised = presentInputJsonSchema(canonical, () => undefined); + const canonicalValidate = new Ajv2020({ strict: false }).compile(canonical); + const advertisedValidate = new Ajv2020({ strict: false }).compile( + advertised, + ); + + expect(canonicalValidate({ enabled: false })).toBe(true); + expect(canonicalValidate({ enabled: true })).toBe(false); + expect(advertisedValidate({ enabled: false })).toBe(true); + expect(advertisedValidate({ enabled: true })).toBe(true); + expect(advertised.properties).toEqual({ enabled: { type: "boolean" } }); + }); + + it("drops affected negation and conditional groups without rejecting canonical inputs", () => { + const negated = { + type: "object", + properties: { + flag: { type: "boolean", not: { const: false } }, + }, + }; + const negatedAdvertisement = presentInputJsonSchema( + negated, + () => undefined, + ); + const canonicalNegated = new Ajv2020({ strict: false }).compile(negated); + const advertisedNegated = new Ajv2020({ strict: false }).compile( + negatedAdvertisement, + ); + expect(canonicalNegated({ flag: true })).toBe(true); + expect(canonicalNegated({ flag: false })).toBe(false); + expect(advertisedNegated({ flag: true })).toBe(true); + + const conditional = { + type: "object", + properties: { + choice: { + if: { const: false }, + then: { type: "string" }, + else: { type: "boolean" }, + }, + }, + }; + const conditionalAdvertisement = presentInputJsonSchema( + conditional, + () => undefined, + ); + const canonicalConditional = new Ajv2020({ strict: false }).compile( + conditional, + ); + const advertisedConditional = new Ajv2020({ strict: false }).compile( + conditionalAdvertisement, + ); + expect(canonicalConditional({ choice: true })).toBe(true); + expect(advertisedConditional({ choice: true })).toBe(true); + expect(conditionalAdvertisement.properties).toEqual({ + choice: {}, + }); + }); +}; + describe("advertised client-compatible literals", () => { it.each([{ const: "" }, { enum: ["", "named"] }])( "widens unsupported empty-string literals without changing canonical validation: %j", @@ -510,73 +580,7 @@ describe("advertised client-compatible literals", () => { expect(schema.safeParse(input).success).toBe(true); expect(input).not.toHaveProperty("only_left"); }); - it("keeps canonical boolean-literal inputs accepted after advertisement", () => { - const canonical = { - type: "object", - properties: { - enabled: { type: "boolean", const: false }, - }, - required: ["enabled"], - additionalProperties: false, - }; - const advertised = presentInputJsonSchema(canonical, () => undefined); - const canonicalValidate = new Ajv2020({ strict: false }).compile(canonical); - const advertisedValidate = new Ajv2020({ strict: false }).compile( - advertised, - ); - - expect(canonicalValidate({ enabled: false })).toBe(true); - expect(canonicalValidate({ enabled: true })).toBe(false); - expect(advertisedValidate({ enabled: false })).toBe(true); - expect(advertisedValidate({ enabled: true })).toBe(true); - expect(advertised.properties).toEqual({ enabled: { type: "boolean" } }); - }); - - it("drops affected negation and conditional groups without rejecting canonical inputs", () => { - const negated = { - type: "object", - properties: { - flag: { type: "boolean", not: { const: false } }, - }, - }; - const negatedAdvertisement = presentInputJsonSchema( - negated, - () => undefined, - ); - const canonicalNegated = new Ajv2020({ strict: false }).compile(negated); - const advertisedNegated = new Ajv2020({ strict: false }).compile( - negatedAdvertisement, - ); - expect(canonicalNegated({ flag: true })).toBe(true); - expect(canonicalNegated({ flag: false })).toBe(false); - expect(advertisedNegated({ flag: true })).toBe(true); - - const conditional = { - type: "object", - properties: { - choice: { - if: { const: false }, - then: { type: "string" }, - else: { type: "boolean" }, - }, - }, - }; - const conditionalAdvertisement = presentInputJsonSchema( - conditional, - () => undefined, - ); - const canonicalConditional = new Ajv2020({ strict: false }).compile( - conditional, - ); - const advertisedConditional = new Ajv2020({ strict: false }).compile( - conditionalAdvertisement, - ); - expect(canonicalConditional({ choice: true })).toBe(true); - expect(advertisedConditional({ choice: true })).toBe(true); - expect(conditionalAdvertisement.properties).toEqual({ - choice: {}, - }); - }); + registerBooleanLiteralCompatibilityTests(); }); describe("advertised Boolean schema composition", () => { diff --git a/src/domain/referenceSourceGraph.test.ts b/src/domain/referenceSourceGraph.test.ts index 70cdfb8f69..758a373ec7 100644 --- a/src/domain/referenceSourceGraph.test.ts +++ b/src/domain/referenceSourceGraph.test.ts @@ -103,53 +103,7 @@ const graphInput = (): HistoricalSourceGraphInput => ({ limitations: [], }); -describe("historical source graph", () => { - it("validates supplementary paths and derived indexes by code point", () => { - const paths = ["a", "aa", "\uE000", "\u{10000}"]; - const input: HistoricalSourceGraphInput = { - ...graphInput(), - entries: paths.map((path) => - fileEntry({ - path, - language: path, - classifications: ["manifest"], - }), - ), - relationships: [], - languages: paths, - manifests: paths, - }; - const graph = createHistoricalSourceGraph(input); - expect(parseHistoricalSourceGraph(graph)).toEqual(graph); - expect(() => - createHistoricalSourceGraph({ - ...input, - entries: [...input.entries].sort((left, right) => - left.path < right.path ? -1 : left.path > right.path ? 1 : 0, - ), - }), - ).toThrow(/Unicode code point/u); - }); - - it("omits absent local diagnostics and rejects explicitly undefined non-JSON metadata", () => { - const input = graphInput(); - const graph = createHistoricalSourceGraph(input); - expect( - graph.entries.every( - (entry) => !Object.hasOwn(entry, "local_diagnostics"), - ), - ).toBe(true); - expect(() => - createHistoricalSourceGraph({ - ...input, - entries: input.entries.map((entry) => ({ - ...entry, - local_diagnostics: undefined, - })), - }), - ).toThrow(); - }); - +const registerSourceCommitmentTests = (): void => { it("builds and verifies internal root and manifest commitments", () => { const graph = createHistoricalSourceGraph(graphInput()); const first = createHistoricalSourceManifest(graph); @@ -224,6 +178,56 @@ describe("historical source graph", () => { graphWith("*.env").root_sha256, ); }); +}; + +describe("historical source graph", () => { + it("validates supplementary paths and derived indexes by code point", () => { + const paths = ["a", "aa", "\uE000", "\u{10000}"]; + const input: HistoricalSourceGraphInput = { + ...graphInput(), + entries: paths.map((path) => + fileEntry({ + path, + language: path, + classifications: ["manifest"], + }), + ), + relationships: [], + languages: paths, + manifests: paths, + }; + const graph = createHistoricalSourceGraph(input); + expect(parseHistoricalSourceGraph(graph)).toEqual(graph); + expect(() => + createHistoricalSourceGraph({ + ...input, + entries: [...input.entries].sort((left, right) => + left.path < right.path ? -1 : left.path > right.path ? 1 : 0, + ), + }), + ).toThrow(/Unicode code point/u); + }); + + it("omits absent local diagnostics and rejects explicitly undefined non-JSON metadata", () => { + const input = graphInput(); + const graph = createHistoricalSourceGraph(input); + expect( + graph.entries.every( + (entry) => !Object.hasOwn(entry, "local_diagnostics"), + ), + ).toBe(true); + expect(() => + createHistoricalSourceGraph({ + ...input, + entries: input.entries.map((entry) => ({ + ...entry, + local_diagnostics: undefined, + })), + }), + ).toThrow(); + }); + + registerSourceCommitmentTests(); it("retains actual external targets and rejects placeholder targets", () => { const input = { ...graphInput(), inventory_state: "partial" as const }; @@ -277,7 +281,7 @@ describe("historical source graph", () => { }); }); -describe("partial historical source graphs", () => { +const registerLocalDiagnosticCommitmentTests = (): void => { it("keeps unknown-entry diagnostics outside the root commitment", () => { const base = graphInput(); base.inventory_state = "partial"; @@ -332,6 +336,10 @@ describe("partial historical source graphs", () => { computeHistoricalSourceGraphSha256(createHistoricalSourceGraph(moved)), ).not.toBe(computeHistoricalSourceGraphSha256(graph)); }); +}; + +describe("partial historical source graphs", () => { + registerLocalDiagnosticCommitmentTests(); it("keeps an unreadable symlink target unknown and out of root identity", () => { const base = graphInput(); diff --git a/tests/boundary/cli/cliInputSchemaRequired.test.ts b/tests/boundary/cli/cliInputSchemaRequired.test.ts index 33571d742b..e87c849ea8 100644 --- a/tests/boundary/cli/cliInputSchemaRequired.test.ts +++ b/tests/boundary/cli/cliInputSchemaRequired.test.ts @@ -75,7 +75,7 @@ const omissionRejected = (schema: z.ZodObject | undefined): string[] => const acceptsOmittedKey = (key: string, field: z.ZodType): boolean => z.object({ [key]: field }).safeParse({}).success; -describe("CLI advertised input schemas", () => { +const registerParserAdvertisementInventoryTests = (): void => { it("require exactly the inputs whose omission the parser rejects", async () => { const cli = createCli({}); const mismatches: string[] = []; @@ -111,6 +111,10 @@ describe("CLI advertised input schemas", () => { } expect(mismatches).toEqual([]); }); +}; + +describe("CLI advertised input schemas", () => { + registerParserAdvertisementInventoryTests(); it("preserves client choices, selected path bounds and required decoded steps", async () => { const cli = createCli({}); diff --git a/tests/boundary/cli/referenceSourceModuleSemantics.test.ts b/tests/boundary/cli/referenceSourceModuleSemantics.test.ts index 4970040de9..c055891f3a 100644 --- a/tests/boundary/cli/referenceSourceModuleSemantics.test.ts +++ b/tests/boundary/cli/referenceSourceModuleSemantics.test.ts @@ -10,50 +10,52 @@ import { historicalSourceGraphSchema } from "../../../src/domain/referenceSource import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; +const historicalModuleFixture = async () => { + const root = await createTestTempDirectory("rea-reference-module-semantics-"); + const files: Record = { + "package.json": '{"type":"module"}\n', + "main.js": 'require("./dep.cjs"); export {};\n', + "dep.cjs": 'throw new Error("must never load");\n', + "directory.cjs": 'require("./nested/");\n', + "nested/index.js": 'module.exports = "fallback";\n', + "optional.cjs": + 'require.resolve?.("./dep.cjs"); require?.resolve("./dep.cjs");\n', + "overwritten.cjs": + 'require.resolve = other; require.resolve?.("./dep.cjs");\n', + "shadowed.cjs": 'function f(require) { require?.resolve("./dep.cjs"); }\n', + "types.d.ts": + 'declare module "consumer" { import type { Marker } from "dependency"; export type A = import("./dep").Marker; export type B = typeof import("./dep"); }\n', + "dep.ts": "export interface Marker { id: string }\n", + "nested/package.json": '{"type":"commonjs","main":"main.js"}\n', + "nested/main.js": 'require("../dep.cjs");\n', + "untyped/package.json": "{}\n", + "untyped/main.js": 'require("../dep.cjs");\n', + "unknown/package.json": "{invalid", + "unknown/main.js": 'require("../dep.cjs");\n', + "url.mjs": + 'import "./dep%20name.mjs?v=1#first"; import "./dep%20name.mjs?v=2#second"; import "./dep%2520name.mjs"; import "./bad%2Fname.mjs"; import "./bad%.mjs";\n', + "dep name.mjs": "export const actual = 1;\n", + "dep%20name.mjs": "export const literalPercent = 2;\n", + "literal.cjs": + 'require.resolve("./dep%20name.mjs"); require.resolve("./dep?literal.cjs");\n', + "dep?literal.cjs": 'throw new Error("must never load");\n', + "oracle.mjs": + 'import { fileURLToPath } from "node:url"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); console.log(JSON.stringify([fileURLToPath(import.meta.resolve("./dep%20name.mjs?v=1#first")), fileURLToPath(import.meta.resolve("./dep%2520name.mjs")), require.resolve("./dep%20name.mjs"), require.resolve("./dep?literal.cjs")]));\n', + }; + for (const directory of ["nested", "unknown", "untyped"]) + await mkdir(join(root, directory)); + await Promise.all( + Object.entries(files).map(([path, source]) => + writeFile(join(root, path), source), + ), + ); + return { root, files }; +}; + cliTest( "preserves historical module syntax, loader scope and URL request identity", async ({ cli }) => { - const root = await createTestTempDirectory( - "rea-reference-module-semantics-", - ); - const files: Record = { - "package.json": '{"type":"module"}\n', - "main.js": 'require("./dep.cjs"); export {};\n', - "dep.cjs": 'throw new Error("must never load");\n', - "directory.cjs": 'require("./nested/");\n', - "nested/index.js": 'module.exports = "fallback";\n', - "optional.cjs": - 'require.resolve?.("./dep.cjs"); require?.resolve("./dep.cjs");\n', - "overwritten.cjs": - 'require.resolve = other; require.resolve?.("./dep.cjs");\n', - "shadowed.cjs": - 'function f(require) { require?.resolve("./dep.cjs"); }\n', - "types.d.ts": - 'declare module "consumer" { import type { Marker } from "dependency"; export type A = import("./dep").Marker; export type B = typeof import("./dep"); }\n', - "dep.ts": "export interface Marker { id: string }\n", - "nested/package.json": '{"type":"commonjs","main":"main.js"}\n', - "nested/main.js": 'require("../dep.cjs");\n', - "untyped/package.json": "{}\n", - "untyped/main.js": 'require("../dep.cjs");\n', - "unknown/package.json": "{invalid", - "unknown/main.js": 'require("../dep.cjs");\n', - "url.mjs": - 'import "./dep%20name.mjs?v=1#first"; import "./dep%20name.mjs?v=2#second"; import "./dep%2520name.mjs"; import "./bad%2Fname.mjs"; import "./bad%.mjs";\n', - "dep name.mjs": "export const actual = 1;\n", - "dep%20name.mjs": "export const literalPercent = 2;\n", - "literal.cjs": - 'require.resolve("./dep%20name.mjs"); require.resolve("./dep?literal.cjs");\n', - "dep?literal.cjs": 'throw new Error("must never load");\n', - "oracle.mjs": - 'import { fileURLToPath } from "node:url"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); console.log(JSON.stringify([fileURLToPath(import.meta.resolve("./dep%20name.mjs?v=1#first")), fileURLToPath(import.meta.resolve("./dep%2520name.mjs")), require.resolve("./dep%20name.mjs"), require.resolve("./dep?literal.cjs")]));\n', - }; - for (const directory of ["nested", "unknown", "untyped"]) - await mkdir(join(root, directory)); - await Promise.all( - Object.entries(files).map(([path, source]) => - writeFile(join(root, path), source), - ), - ); + const { root, files } = await historicalModuleFixture(); // This self-authored resolver control never loads a dependency. const oracle = await promisify(execFile)( process.execPath, diff --git a/tests/boundary/filesystem/javascriptApplicationInputSelection.test.ts b/tests/boundary/filesystem/javascriptApplicationInputSelection.test.ts index 3f8bd25b5b..2683f222c2 100644 --- a/tests/boundary/filesystem/javascriptApplicationInputSelection.test.ts +++ b/tests/boundary/filesystem/javascriptApplicationInputSelection.test.ts @@ -22,7 +22,7 @@ const application = async () => { return { root, directory }; }; -describe("JavaScript application input selection", () => { +const registerSelectedPathResolutionTests = (): void => { it("preserves the selected path and host reason for an overlong name", async () => { const { root } = await application(); const longPath = join(root, "x".repeat(300)); @@ -90,6 +90,10 @@ describe("JavaScript application input selection", () => { expect(loopError).not.toEqual(chainError); }, ); +}; + +describe("JavaScript application input selection", () => { + registerSelectedPathResolutionTests(); it("reports a missing input path as invalid input, not an artifact read failure", async () => { const { root } = await application(); diff --git a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts index 3df4079624..152998c6a4 100644 --- a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts +++ b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts @@ -18,6 +18,76 @@ import { createJavaScriptApplicationNode, } from "../../../src/domain/javascript/javascriptApplicationGraph.js"; +const registerImplicitInvocationComparisonTests = (): void => { + it("preserves accessor-effect uncertainty through analyzed Evidence and export comparison", async () => { + const [left, right] = await analyzeSources({ + left: `export default function observe() { + const box = { value: 1, get trigger() { this.value = 2; return 0; } }; + box.trigger; + return { type: "event", value: box.value, stable: "kept" }; + }`, + right: `export default function observe() { + return { type: "event", value: 2, stable: "kept" }; + }`, + }); + const execution = compareJavaScriptExportShapesEvidence({ + left: left.evidence, + right: right.evidence, + left_module_path: "parser.mjs", + left_export_name: "default", + right_module_path: "parser.mjs", + right_export_name: "default", + }); + if (!execution.ok) throw execution.error; + expect(execution.value.result.changes).toEqual([ + expect.objectContaining({ + status: "unknown", + path: "/value", + left: expect.objectContaining({ availability: "unknown" }), + right: { availability: "literal", value: 2 }, + }), + ]); + expect(execution.value.sources).toContain(left.evidence); + expect(execution.value.result.coverage.status).toBe("partial"); + expect(inventoryProperties(execution.value.result, "left", 0)).toEqual( + expect.arrayContaining(["/stable", "/value", "/type"]), + ); + }); + + it("preserves independent implicit invocation unknowns through retained export Evidence", async () => { + const [left, right] = await analyzeSources({ + left: `export default function observe() { + const getter = { value: 1, get trigger() { this.value = 2; return 0; } }; + const copy = { ...getter }; + const coercion = { [Symbol.toPrimitive]() { this.value = 2; return "key"; }, value: 1 }; + const keyed = { [coercion]: 0 }; + const disposal = { [Symbol.dispose]() { this.value = 2; }, value: 1 }; + { using owned = disposal; } + return { type: "event", getter: getter.value, coercion: coercion.value, disposal: disposal.value, stable: "kept" }; + }`, + right: `export default function observe() { + return { type: "event", getter: 2, coercion: 2, disposal: 2, stable: "kept" }; + }`, + }); + const result = compare(left, right); + expect(result.changes).toHaveLength(3); + expect(result.changes).toEqual( + expect.arrayContaining( + ["/getter", "/coercion", "/disposal"].map((path) => + expect.objectContaining({ + path, + status: "unknown", + left: expect.objectContaining({ availability: "unknown" }), + right: { availability: "literal", value: 2 }, + }), + ), + ), + ); + expect(result.coverage.status).toBe("partial"); + expect(inventoryProperties(result, "left", 0)).toContain("/stable"); + }); +}; + describe("JavaScript export return-shape comparison", () => { it("propagates proven ancestor absence to nested additions and removals", async () => { const [left, right] = await analyzeSources({ @@ -130,73 +200,7 @@ describe("JavaScript export return-shape comparison", () => { ); }); - it("preserves accessor-effect uncertainty through analyzed Evidence and export comparison", async () => { - const [left, right] = await analyzeSources({ - left: `export default function observe() { - const box = { value: 1, get trigger() { this.value = 2; return 0; } }; - box.trigger; - return { type: "event", value: box.value, stable: "kept" }; - }`, - right: `export default function observe() { - return { type: "event", value: 2, stable: "kept" }; - }`, - }); - const execution = compareJavaScriptExportShapesEvidence({ - left: left.evidence, - right: right.evidence, - left_module_path: "parser.mjs", - left_export_name: "default", - right_module_path: "parser.mjs", - right_export_name: "default", - }); - if (!execution.ok) throw execution.error; - expect(execution.value.result.changes).toEqual([ - expect.objectContaining({ - status: "unknown", - path: "/value", - left: expect.objectContaining({ availability: "unknown" }), - right: { availability: "literal", value: 2 }, - }), - ]); - expect(execution.value.sources).toContain(left.evidence); - expect(execution.value.result.coverage.status).toBe("partial"); - expect(inventoryProperties(execution.value.result, "left", 0)).toEqual( - expect.arrayContaining(["/stable", "/value", "/type"]), - ); - }); - - it("preserves independent implicit invocation unknowns through retained export Evidence", async () => { - const [left, right] = await analyzeSources({ - left: `export default function observe() { - const getter = { value: 1, get trigger() { this.value = 2; return 0; } }; - const copy = { ...getter }; - const coercion = { [Symbol.toPrimitive]() { this.value = 2; return "key"; }, value: 1 }; - const keyed = { [coercion]: 0 }; - const disposal = { [Symbol.dispose]() { this.value = 2; }, value: 1 }; - { using owned = disposal; } - return { type: "event", getter: getter.value, coercion: coercion.value, disposal: disposal.value, stable: "kept" }; - }`, - right: `export default function observe() { - return { type: "event", getter: 2, coercion: 2, disposal: 2, stable: "kept" }; - }`, - }); - const result = compare(left, right); - expect(result.changes).toHaveLength(3); - expect(result.changes).toEqual( - expect.arrayContaining( - ["/getter", "/coercion", "/disposal"].map((path) => - expect.objectContaining({ - path, - status: "unknown", - left: expect.objectContaining({ availability: "unknown" }), - right: { availability: "literal", value: 2 }, - }), - ), - ), - ); - expect(result.coverage.status).toBe("partial"); - expect(inventoryProperties(result, "left", 0)).toContain("/stable"); - }); + registerImplicitInvocationComparisonTests(); it("keeps additions unknown when spread coverage is incomplete", async () => { const [left, right] = await analyzeSources({ diff --git a/tests/boundary/filesystem/referenceSourceImport.test.ts b/tests/boundary/filesystem/referenceSourceImport.test.ts index cb34185470..6188f8f13f 100644 --- a/tests/boundary/filesystem/referenceSourceImport.test.ts +++ b/tests/boundary/filesystem/referenceSourceImport.test.ts @@ -602,6 +602,94 @@ describe("reference source manifest inventory", () => { }); }); +const registerReferenceRelocationTests = (): void => { + it("is relocation-stable, resolves imports, and excludes secrets before capture", async () => { + const parent = await createTestTempDirectory("rea-reference-import-"); + try { + const leftRoot = await fixture(parent, "left"); + const rightRoot = await fixture(parent, "right"); + const [left, right] = await Promise.all([ + importTree(leftRoot), + importTree(rightRoot), + ]); + if (!left.ok || !right.ok) throw new Error("expected imports to pass"); + expect(createHistoricalSourceManifest(left.value)).toEqual( + createHistoricalSourceManifest(right.value), + ); + expect(left.value.relationships).toContainEqual( + expect.objectContaining({ + from_path: "src/main.ts", + to: "src/dep.ts", + resolution: "internal", + }), + ); + expect(left.value.parse_failures).toHaveLength(1); + expect(left.value.exclusions).toContainEqual({ + path: ".env", + entry_kind: "file", + reason: "configured-secret", + pattern: ".env", + }); + expect(JSON.stringify(left.value)).not.toContain("SECRET_SENTINEL"); + expect(JSON.stringify(left.value)).not.toContain(leftRoot); + } finally { + await rm(parent, { recursive: true, force: true }); + } + }); + + it + .skipIf(process.platform === "win32" || process.getuid?.() === 0) + .each(["file", "directory"] as const)( + "commits equivalent unreadable %s inventories independently of relocation and retains exact local diagnostics", + async (kind) => { + const parent = await createTestTempDirectory( + "rea-reference-failure-relocation-", + ); + const roots = [join(parent, "left"), join(parent, "right")]; + const denied: string[] = []; + try { + for (const root of roots) { + await mkdir(root); + const path = join(root, kind === "file" ? "blocked.ts" : "blocked"); + if (kind === "file") + await writeFile(path, "export const answer = 42;\n"); + else await mkdir(path); + denied.push(path); + await chmod(path, 0); + } + const [left, right] = await Promise.all( + roots.map((root) => importTree(root)), + ); + if (left === undefined || right === undefined || !left.ok || !right.ok) + throw new Error("Expected partial imports"); + expect(left.value.inventory_state).toBe("partial"); + expect(createHistoricalSourceManifest(left.value)).toEqual( + createHistoricalSourceManifest(right.value), + ); + for (const [index, graph] of [left.value, right.value].entries()) { + const entry = graph.entries[0]; + const root = roots[index]; + if (entry === undefined || root === undefined) + throw new Error("Expected failed entry"); + expect(entry.limitations.join(" ")).toContain("EACCES"); + expect(entry.limitations.join(" ")).toMatch(/Check.*permissions/u); + expect(entry.limitations.join(" ")).not.toContain(root); + expect(entry.local_diagnostics).toEqual([ + expect.stringContaining(await realpath(root)), + ]); + expect( + createHistoricalSourceSummary(graph, 2).outline.entries[0], + ).toMatchObject({ local_diagnostics: entry.local_diagnostics }); + } + } finally { + for (const path of denied) + await chmod(path, kind === "file" ? 0o600 : 0o700); + await rm(parent, { recursive: true, force: true }); + } + }, + ); +}; + describe("reference source import behavior", () => { // The importer declares no byte ceiling and no entry-count ceiling. Scale // independence cannot be asserted as a relationship over a bounded fixture, @@ -697,91 +785,7 @@ describe("reference source import behavior", () => { } }); - it("is relocation-stable, resolves imports, and excludes secrets before capture", async () => { - const parent = await createTestTempDirectory("rea-reference-import-"); - try { - const leftRoot = await fixture(parent, "left"); - const rightRoot = await fixture(parent, "right"); - const [left, right] = await Promise.all([ - importTree(leftRoot), - importTree(rightRoot), - ]); - if (!left.ok || !right.ok) throw new Error("expected imports to pass"); - expect(createHistoricalSourceManifest(left.value)).toEqual( - createHistoricalSourceManifest(right.value), - ); - expect(left.value.relationships).toContainEqual( - expect.objectContaining({ - from_path: "src/main.ts", - to: "src/dep.ts", - resolution: "internal", - }), - ); - expect(left.value.parse_failures).toHaveLength(1); - expect(left.value.exclusions).toContainEqual({ - path: ".env", - entry_kind: "file", - reason: "configured-secret", - pattern: ".env", - }); - expect(JSON.stringify(left.value)).not.toContain("SECRET_SENTINEL"); - expect(JSON.stringify(left.value)).not.toContain(leftRoot); - } finally { - await rm(parent, { recursive: true, force: true }); - } - }); - - it - .skipIf(process.platform === "win32" || process.getuid?.() === 0) - .each(["file", "directory"] as const)( - "commits equivalent unreadable %s inventories independently of relocation and retains exact local diagnostics", - async (kind) => { - const parent = await createTestTempDirectory( - "rea-reference-failure-relocation-", - ); - const roots = [join(parent, "left"), join(parent, "right")]; - const denied: string[] = []; - try { - for (const root of roots) { - await mkdir(root); - const path = join(root, kind === "file" ? "blocked.ts" : "blocked"); - if (kind === "file") - await writeFile(path, "export const answer = 42;\n"); - else await mkdir(path); - denied.push(path); - await chmod(path, 0); - } - const [left, right] = await Promise.all( - roots.map((root) => importTree(root)), - ); - if (left === undefined || right === undefined || !left.ok || !right.ok) - throw new Error("Expected partial imports"); - expect(left.value.inventory_state).toBe("partial"); - expect(createHistoricalSourceManifest(left.value)).toEqual( - createHistoricalSourceManifest(right.value), - ); - for (const [index, graph] of [left.value, right.value].entries()) { - const entry = graph.entries[0]; - const root = roots[index]; - if (entry === undefined || root === undefined) - throw new Error("Expected failed entry"); - expect(entry.limitations.join(" ")).toContain("EACCES"); - expect(entry.limitations.join(" ")).toMatch(/Check.*permissions/u); - expect(entry.limitations.join(" ")).not.toContain(root); - expect(entry.local_diagnostics).toEqual([ - expect.stringContaining(await realpath(root)), - ]); - expect( - createHistoricalSourceSummary(graph, 2).outline.entries[0], - ).toMatchObject({ local_diagnostics: entry.local_diagnostics }); - } - } finally { - for (const path of denied) - await chmod(path, kind === "file" ? 0o600 : 0o700); - await rm(parent, { recursive: true, force: true }); - } - }, - ); + registerReferenceRelocationTests(); it("imports the caller-selected directory and honors cancellation", async () => { const outside = await createTestTempDirectory("rea-reference-outside-");