The four share files (index.ts, module.json, guard.test.ts), so they land as one commit. GIT1: seat.ts serves pull, open-pulls, tags, trees, file, contains, statuses, status, comment, view, note, merge-pull as tools on the seat, each answering the contract's shape and an error of kind refused, not-found or failed; a comment, view or note whose act id is held is answered held (note matched by id). merge-pull merges the head the caller names (its from-G judgements are GIT6, GIT7, GIT9). GIT2: seatevents.ts says pull-updated, pull-merged, pull-closed, forge-pull, forge-status, forge-delete and repo-writable on mesh.seat.git.event.<event>; the module's emits drop them. GIT3: merge.ts, check.ts, the acting tools, the owed watch, the gate's source read and check-gates-merge are gone. GIT10: catalog.ts follows the seat mesh-catalog (revision, catalog-revision); no write is granted before the first view. Builds: design 47 G §310, §311, §312, §313, §314, §163, §317, §278, §321, §196 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
// **One writer of a head's mesh/merge-gate** (novox/hq issue 446): the announcement's pending words, a recheck's pending and
|
|
// a trunk's move, each write of a head in the order it was asked.
|
|
|
|
const SHA = "a6b08de8507c1703528277e5cefc80548dbc9bb3";
|
|
|
|
/** A forge that keeps the newest status per context, as gitea does, and whose writes take as long as a test says. */
|
|
function aForge() {
|
|
const kept: Record<string, { state: string; description: string; updated_at: string }> = {};
|
|
const written: string[] = [];
|
|
let failNext = 0;
|
|
let hold: Promise<void> | null = null;
|
|
let release = () => {};
|
|
return {
|
|
kept, written,
|
|
/** The next writes fail: a request the forge did not take. */
|
|
failWrites(n = 1) {
|
|
failNext = n;
|
|
},
|
|
/** The next writes wait until released: a request in flight. */
|
|
holdWrites() {
|
|
hold = new Promise<void>((r) => (release = r));
|
|
},
|
|
release() {
|
|
hold = null;
|
|
release();
|
|
},
|
|
async headChecks() {
|
|
return {};
|
|
},
|
|
async setCommitStatus(_o: string, _r: string, _sha: string, s: { state: string; context: string; description: string }) {
|
|
if (hold) await hold;
|
|
if (failNext > 0) {
|
|
failNext--;
|
|
throw new Error("the forge answered 502");
|
|
}
|
|
kept[s.context] = { state: s.state, description: s.description, updated_at: new Date().toISOString() };
|
|
written.push(`${s.state} ${s.description}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
// **A recheck's pending goes through the one writer** (novox/hq issue 456).
|
|
|
|
const recheckAt = new Date("2026-10-11T01:30:00Z");
|
|
const checkingAgain = { state: "pending" as const, context: "mesh/merge-gate", description: "checking again: main moved on" };
|
|
const gateOf = (forge: ReturnType<typeof aForge>) =>
|
|
`${forge.kept["mesh/merge-gate"].state} ${forge.kept["mesh/merge-gate"].description}`;
|
|
|
|
test("a repository as the mesh clones it is told from another", async () => {
|
|
const { clonedAs } = await import("../gate.ts");
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/hq.git", "novox", "hq"), true);
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-hq.git", "novox", "hq"), false);
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-controller.git", "novox", "hq"), false);
|
|
});
|
|
|
|
test("a recheck the forge does not take fails its caller, and the next write still takes its turn", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, () => recheckAt);
|
|
await gates.pending("novox", "hq", SHA);
|
|
forge.failWrites();
|
|
await assert.rejects(gates.status("novox", "hq", SHA, checkingAgain));
|
|
await gates.status("novox", "hq", SHA, checkingAgain);
|
|
assert.equal(gateOf(forge), "pending checking again: main moved on");
|
|
});
|
|
|