import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { execFile as callback } from "node:child_process"; import { promisify } from "node:util"; import { convergeAuthoring } from "../src/capability-language/authoring-converge.js"; import { loadQuixosLock } from "../src/resource-lock/index.js"; const execFile = promisify(callback); test("source convergence propagates nested edits and unchanged snapshots reach a fixed point", async context => { const temporary = await fs.mkdtemp(path.join(os.tmpdir(), "qx-converge-test-")); context.after(() => fs.rm(temporary, { recursive: true, force: true })); const workbench = path.join(temporary, "workbench"), remotes = path.join(temporary, "remotes"); await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true }); await fs.mkdir(remotes); const origin = "https://convergence.example.test/"; const previous = Object.fromEntries(["GIT_CONFIG_COUNT", "GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0"].map(key => [key, process.env[key]])); process.env.GIT_CONFIG_COUNT = "1"; process.env.GIT_CONFIG_KEY_0 = `url.file://${remotes}/.insteadOf`; process.env.GIT_CONFIG_VALUE_0 = origin; context.after(() => { for (const [key, value] of Object.entries(previous)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }); const resources = []; let dependency = ""; for (const [directory, kind, name] of [["resources/Base", "interface", "Base"], ["resources/Consumer", "package", "Consumer"], ["root", "workspace", "Root"]]) { const root = path.join(workbench, directory); await fs.mkdir(root, { recursive: true }); await execFile("jj", ["git", "init", "--colocate", root]); await execFile("git", ["init", "--bare", path.join(remotes, name)]); const repository = `${origin}${name}`; await execFile("git", ["-C", root, "remote", "add", "origin", repository]); await fs.writeFile(path.join(root, "quixos.lock"), `quixos-lock version 1 { quixos source { repository "https://example.test/quixos"; commit "${"a".repeat(40)}"; } ${dependency} }`); await fs.writeFile(path.join(root, `${kind}.qx`), "draft"); await execFile("jj", ["status"], { cwd: root }); const commit = (await execFile("jj", ["--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "commit_id"], { cwd: root })).stdout.trim(); if (kind !== "workspace") resources.push({ directory, kind, resourceId: `${kind}:${name}`, source: { resolver: "git", repository, commit } }); dependency = `${kind} ${name} source { repository "${repository}"; commit "${commit}"; }`; } await fs.writeFile(path.join(workbench, ".quixos/resource-graph.json"), JSON.stringify({ resources })); const first = await convergeAuthoring(workbench); assert.deepEqual(first.worklist, []); assert.equal(first.converged, true); assert.equal(first.verificationEvidence, false, "source retention never approves even syntactically invalid code"); await fs.writeFile(path.join(workbench, "resources/Base/interface.qx"), "edited draft"); const second = await convergeAuthoring(workbench); assert.deepEqual(second.worklist, []); assert.notEqual(second.candidate?.commit, first.candidate?.commit); const consumer = await loadQuixosLock(path.join(workbench, "resources/Consumer/quixos.lock")); assert.ok(consumer.ok); assert.equal(consumer.lock.resources[0].source.commit, second.retained.find(entry => entry.directory === "resources/Base")?.source.commit); const third = await convergeAuthoring(workbench); assert.deepEqual(third, second); const base = path.join(workbench, "resources/Base"); const consumerRoot = path.join(workbench, "resources/Consumer"); const goodLock = await fs.readFile(path.join(consumerRoot, "quixos.lock"), "utf8"); await fs.writeFile(path.join(consumerRoot, "quixos.lock"), "broken draft"); const scoped = await convergeAuthoring(workbench, "resources/Base"); assert.deepEqual(scoped.worklist, [], "an unrelated malformed consumer cannot block a provider check"); await fs.writeFile(path.join(base, "interface.qx"), "another edit"); const partial = await convergeAuthoring(workbench); assert.equal(partial.converged, false); const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8")); assert.equal(graph.resources[0].source.commit, partial.retained.find(entry => entry.directory === "resources/Base")?.source.commit); await fs.writeFile(path.join(consumerRoot, "quixos.lock"), goodLock); const resumed = await convergeAuthoring(workbench); assert.deepEqual(resumed.worklist, []); assert.deepEqual(await convergeAuthoring(workbench), resumed); await execFile("git", ["-C", base, "remote", "set-url", "origin", origin + "Wrong"]); const mismatch = await convergeAuthoring(workbench); assert.ok(mismatch.worklist.some(entry => entry.phase === "source" && /Origin differs/.test(entry.message))); await execFile("git", ["-C", base, "remote", "set-url", "origin", origin + "Base"]); await fs.rename(path.join(remotes, "Base"), path.join(remotes, "Base-offline")); const offline = await convergeAuthoring(workbench); assert.equal(offline.candidate, null); assert.ok(offline.worklist.some(entry => entry.phase === "publication")); await fs.rename(path.join(remotes, "Base-offline"), path.join(remotes, "Base")); assert.equal((await convergeAuthoring(workbench)).converged, true); const consumerSource = resumed.retained.find(entry => entry.directory === "resources/Consumer")!.source; await fs.writeFile(path.join(base, "quixos.lock"), `quixos-lock version 1 { quixos source { repository "https://example.test/quixos"; commit "${"a".repeat(40)}"; } package Consumer source { repository "${consumerSource.repository}"; commit "${consumerSource.commit}"; } }`); const cycle = await convergeAuthoring(workbench); assert.equal(cycle.candidate, null); assert.ok(cycle.worklist.some(entry => /Source dependency cycle/.test(entry.message))); });