01ca965c7f
Coordinate registered resource edits bottom-up into retained exact remote sources. Use one Nix-owned source graph for provisional checking, template publication, explicit baseline upgrades and host activation; retain independent runtime pins. Add scoped contract inspection, historical recovery, derived worklists, crash-safe locks, named dependency adoption and plain-QX structural editing. Repair TODO ownership and template instantiation, and document the supported agent workflow. Validated with protocol and command suites, real jj/Nix convergence and cache checks, TS/React installed-command acceptance, and fresh TODO first-edit acceptance. No live deployment or public publication performed. Props projection generation and a one-command rich feature generator remain explicitly outside this delivery.
34 lines
1.7 KiB
TypeScript
34 lines
1.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {mkdtemp, rm} from "node:fs/promises";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import {spawn} from "node:child_process";
|
|
import {once} from "node:events";
|
|
import {withFileLock} from "../src/capability-language/file-lock.js";
|
|
|
|
test("authoring lock excludes concurrent mutations and survives owner death", async context => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "qx-lock-test-"));
|
|
context.after(() => rm(root, {recursive: true, force: true}));
|
|
const filename = path.join(root, "lock");
|
|
await withFileLock(filename, async () => {
|
|
await assert.rejects(withFileLock(filename, async () => assert.fail("concurrent mutation")), /Another authoring command/);
|
|
});
|
|
const module = new URL("../src/capability-language/file-lock.js", import.meta.url).href;
|
|
const owner = spawn(process.execPath, ["--input-type=module", "-e",
|
|
`import {withFileLock} from ${JSON.stringify(module)}; await withFileLock(${JSON.stringify(filename)}, async () => {process.stdout.write('ready'); await new Promise(() => {});});`],
|
|
{stdio: ["ignore", "pipe", "pipe"]});
|
|
context.after(() => owner.kill("SIGKILL"));
|
|
await once(owner.stdout, "data");
|
|
const exited = once(owner, "exit");
|
|
owner.kill("SIGKILL");
|
|
await exited;
|
|
// EOF release happens in the helper; wait a bounded amount for scheduling.
|
|
let acquired = false;
|
|
for (let attempt = 0; attempt < 30 && !acquired; attempt++) {
|
|
try { await withFileLock(filename, async () => {acquired = true;}); }
|
|
catch (error) { if (!/Another authoring command/.test(String(error))) throw error; }
|
|
}
|
|
assert.equal(acquired, true);
|
|
});
|