Bifrost/tests/persona-matrix-mouth-send-gate.test.mjs
Eureka 7adc7315a3 send gate: re-check the brake after the gate wait, before the PUT
A send queued at the room gate passed the brake check at entry only, so a
brake placed while it waited still let the PUT through. The check now also
runs inside the gate, just before the PUT, and answers a typed Brake refusal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 10:37:22 +00:00

225 lines
14 KiB
JavaScript

// The mouth's send gate: a Persona's v2 send is refused, with no PUT, when the room moved past what she held,
// and the check and the PUT are one serial step per room across every Persona. The homeserver here
// answers /messages as Synapse does (start, chunk newest first, end; the types filter and limit honoured)
// and serves the chunk captured from #swarm on 2026-11-03, not a hand-made shape.
import test from "node:test";
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { createMouth, createRoomGate, movesRoom } from "../tools/persona-matrix-mouth.mjs";
const HS = "gamecult.org";
const ROOM = "!0pZn8mTZ0FdY5-G99iOuD7JWWlF3yA_5p20yO1C8WeA";
const ALIAS = `#swarm:${HS}`;
const captured = JSON.parse(readFileSync(resolve(import.meta.dirname, "fixtures", "persona-chat-v2", "messages-swarm-2026-11-03.json"), "utf8"));
const swarm = captured.chunk.slice().reverse(); // oldest first, 11 real events
const [question] = swarm;
const EUREKA = `@eureka:${HS}`;
const idsBy = (events, pred) => events.filter(pred).map(e => e.event_id);
// A synthetic event, marked as such by its id: the capture holds no threaded reply, edit or notice.
const synthetic = (kind, ts, sender, content) => ({ type: "m.room.message", event_id: `$synthetic-${kind}`, sender, origin_server_ts: ts, content });
const t0 = swarm.at(-1).origin_server_ts;
const threaded = synthetic("thread", t0 + 10, `@nibu:${HS}`, { msgtype: "m.text", body: "in a thread", "m.relates_to": { rel_type: "m.thread", event_id: question.event_id } });
const edit = synthetic("replace", t0 + 20, `@aqua:${HS}`, { msgtype: "m.text", body: "* fixed", "m.new_content": { msgtype: "m.text", body: "fixed" }, "m.relates_to": { rel_type: "m.replace", event_id: swarm[1].event_id } });
const notice = synthetic("notice", t0 + 30, `@root-watch:${HS}`, { msgtype: "m.notice", body: "sudo audit line" });
// users: bearer token -> user id. events: the room's history, oldest first. hooks.delay(kind) -> ms to hold a GET /messages or a PUT.
async function homeserver({ events = swarm, users, headStatus = 200, headHangs = false, delay = () => 0 } = {}) {
const room = [...events];
const seen = [];
const sent = new Map();
const server = createServer((req, res) => {
let raw = "";
req.on("data", c => (raw += c));
req.on("end", async () => {
const url = new URL(req.url, "http://x");
const path = decodeURIComponent(url.pathname).replace("/_matrix/client/v3", "");
const bearer = (req.headers.authorization ?? "").replace("Bearer ", "");
const reply = (status, body) => { res.writeHead(status, { "content-type": "application/json" }); res.end(JSON.stringify(body)); };
const record = { method: req.method, path, body: raw ? JSON.parse(raw) : null, who: users[bearer], query: url.searchParams };
seen.push(record);
if (path === "/account/whoami") return reply(200, { user_id: users[bearer] });
if (path === "/joined_rooms") return reply(200, { joined_rooms: [ROOM] });
if (path.includes("/account_data/m.direct")) return reply(404, { errcode: "M_NOT_FOUND", error: "Account data not found" });
if (path.startsWith("/directory/room/")) return reply(200, { room_id: ROOM, servers: [HS] });
if (req.method === "GET" && path === `/rooms/${ROOM}/messages`) {
if (headHangs) return; // never answers
await new Promise(r => setTimeout(r, delay("get")));
if (headStatus !== 200) return reply(headStatus, { errcode: "M_UNKNOWN", error: "Internal server error" });
const types = JSON.parse(url.searchParams.get("filter") ?? "{}").types;
const page = room.filter(e => !types || types.includes(e.type)).reverse().slice(0, Number(url.searchParams.get("limit") ?? 10));
return reply(200, { start: "s1", chunk: page, end: "e1" });
}
const put = path.match(/^\/rooms\/(.+)\/send\/m\.room\.message\/(.+)$/);
if (req.method === "PUT" && put) {
await new Promise(r => setTimeout(r, delay("put")));
if (!sent.has(put[2])) {
sent.set(put[2], `$sent-${sent.size + 1}`);
room.push({ type: "m.room.message", event_id: sent.get(put[2]), sender: users[bearer], origin_server_ts: t0 + 1000 + sent.size, content: record.body });
}
return reply(200, { event_id: sent.get(put[2]) });
}
reply(404, { errcode: "M_NOT_FOUND" });
});
});
await new Promise(ok => server.listen(0, "127.0.0.1", ok));
return {
url: `http://127.0.0.1:${server.address().port}`, room,
puts: () => seen.filter(r => r.method === "PUT" && r.path.includes("/send/")),
heads: () => seen.filter(r => r.method === "GET" && r.path.endsWith("/messages")),
close: () => { server.closeAllConnections(); return new Promise(ok => server.close(ok)); },
};
}
const memoryState = () => { const box = { value: null }; return { load: async () => box.value, save: async v => { box.value = v; } }; };
const mouth = (hs, persona, token, extra = {}) => createMouth({
persona, homeserver: hs.url, serverName: HS, rooms: [ALIAS], token, state: memoryState(), brake: async () => true, syncTimeoutMs: 1, ...extra,
});
const send = (m, requestId, basis, persona = "eureka") => m.send({ requestId, persona, roomAlias: ALIAS, text: `line ${requestId}`, basis });
const basisOn = (event, sinceUnixMs = 0) => ({ seenEventId: event.event_id, sinceUnixMs });
test("a stale send makes no PUT and answers RoomMoved with the newer ids, oldest first", async () => {
const hs = await homeserver({ users: { tok: EUREKA } });
try {
const receipt = await send(mouth(hs, "eureka", "tok"), "r1", basisOn(question));
assert.equal(receipt.refusal, "RoomMoved");
assert.equal(receipt.eventId, null);
// Every message after the question except her own three lines.
assert.deepEqual(receipt.newer, idsBy(swarm.slice(1), e => e.sender !== EUREKA));
assert.equal(receipt.newer.length, 7);
assert.equal(hs.puts().length, 0, "a refused send reaches the room never");
const [head] = hs.heads();
assert.deepEqual([head.query.get("dir"), head.query.get("limit"), JSON.parse(head.query.get("filter"))], ["b", "50", { types: ["m.room.message"] }], "the head read asks for messages only, newest first");
} finally { await hs.close(); }
});
test("a send whose room moved only by m.notice, m.replace or her own account goes out", async () => {
const quiet = [question, notice, edit, { ...swarm[6], event_id: "$her-own" }];
const hs = await homeserver({ events: quiet, users: { tok: EUREKA } });
try {
const receipt = await send(mouth(hs, "eureka", "tok"), "r2", basisOn(question));
assert.deepEqual([receipt.refusal, receipt.newer], [null, []]);
assert.match(receipt.eventId, /^\$sent-/);
assert.equal(hs.puts().length, 1);
} finally { await hs.close(); }
});
test("a threaded reply moves the room", async () => {
const hs = await homeserver({ events: [question, threaded], users: { tok: EUREKA } });
try {
const receipt = await send(mouth(hs, "eureka", "tok"), "r3", basisOn(question));
assert.deepEqual([receipt.refusal, receipt.newer], ["RoomMoved", [threaded.event_id]]);
} finally { await hs.close(); }
});
test("movesRoom over the captured #swarm chunk and the synthetic kinds", () => {
const moved = swarm.filter(e => movesRoom(e, EUREKA)).map(e => e.event_id);
assert.deepEqual(moved, idsBy(swarm, e => e.sender !== EUREKA), "the eleven real events count except her three lines");
assert.equal(moved.length, 8);
assert.equal(movesRoom(threaded, EUREKA), true, "a threaded reply counts");
assert.equal(movesRoom(edit, EUREKA), false, "an m.replace edit does not");
assert.equal(movesRoom(notice, EUREKA), false, "an m.notice does not");
assert.equal(movesRoom({ type: "m.room.member", sender: `@x:${HS}`, content: { membership: "join" } }, EUREKA), false, "only messages move a room");
});
test("no seen event: the since floor decides; a seen event outside the page: the whole page counts", async () => {
const hs = await homeserver({ users: { tok: EUREKA } });
try {
const m = mouth(hs, "eureka", "tok");
const libby = swarm[5];
const floor = await send(m, "r4", { seenEventId: null, sinceUnixMs: libby.origin_server_ts });
assert.deepEqual(floor.newer, idsBy(swarm.slice(6), e => e.sender !== EUREKA), "only what came after the since time; the equal-time event is her turn's own start");
const lost = await send(m, "r5", { seenEventId: "$never-in-the-page", sinceUnixMs: t0 + 1e6 });
assert.deepEqual(lost.newer, idsBy(swarm, e => e.sender !== EUREKA), "the unreachable seen event leaves the whole page, whatever the since time says");
const seenWins = await send(m, "r6", { seenEventId: swarm[9].event_id, sinceUnixMs: 0 });
assert.deepEqual(seenWins.newer, [swarm[10].event_id], "with a seen event, the since time does not matter");
} finally { await hs.close(); }
});
test("a failed head read makes no PUT and answers SendFailed; so does a head read that hangs", async () => {
for (const mode of [{ headStatus: 500 }, { headHangs: true }]) {
const hs = await homeserver({ users: { tok: EUREKA }, ...mode });
const logs = [];
try {
const receipt = await send(mouth(hs, "eureka", "tok", { sendTimeoutMs: 80, log: l => logs.push(l) }), "r7", basisOn(question));
assert.deepEqual([receipt.eventId, receipt.refusal, receipt.newer], [null, "SendFailed", []], JSON.stringify(mode));
assert.equal(hs.puts().length, 0, "the guarantee does not fail open");
} finally { await hs.close(); }
}
});
test("a failed send does not stop the next one in that room", async () => {
const gate = createRoomGate();
await assert.rejects(gate.run(ROOM, async () => { throw new Error("boom"); }), /boom/);
assert.equal(await gate.run(ROOM, async () => "next"), "next");
});
test("eight Minds answering one message: one PUT, seven RoomMoved naming the first", async () => {
const names = Array.from({ length: 8 }, (_, i) => `mind${i}`);
const users = Object.fromEntries(names.map(n => [`tok-${n}`, `@${n}:${HS}`]));
const hs = await homeserver({ events: [question], users, delay: () => 25 });
try {
const gate = createRoomGate();
const receipts = await Promise.all(names.map(n => send(mouth(hs, n, `tok-${n}`, { gate }), `req-${n}`, basisOn(question), n)));
assert.equal(hs.puts().length, 1, "exactly one Mind spoke");
const winner = receipts.filter(r => r.refusal === null);
assert.equal(winner.length, 1);
const refused = receipts.filter(r => r.refusal !== null);
assert.equal(refused.length, 7);
for (const r of refused) assert.deepEqual([r.refusal, r.newer], ["RoomMoved", [winner[0].eventId]]);
} finally { await hs.close(); }
});
test("revisions with a nil basis all go out, in the order they reached the gate, and read no head", async () => {
const hs = await homeserver({ events: [question], users: { tok: EUREKA }, delay: kind => (kind === "put" ? 15 : 0) });
try {
const inner = createRoomGate();
let arrivals = 0;
const started = [];
const gate = { run: (room, fn) => { const n = arrivals++; return inner.run(room, () => { started.push(n); return fn(); }); } };
const m = mouth(hs, "eureka", "tok", { gate });
const ids = Array.from({ length: 7 }, (_, i) => `rev-${i}`);
const receipts = await Promise.all(ids.map(id => send(m, id, null)));
assert.ok(receipts.every(r => r.refusal === null));
assert.deepEqual(started, [0, 1, 2, 3, 4, 5, 6], "the gate runs them in the order they reached it");
assert.deepEqual(hs.puts().map(p => p.body.body).sort(), ids.map(id => `line ${id}`), "all seven went out");
assert.equal(hs.heads().length, 0, "her choice stands: no check");
} finally { await hs.close(); }
});
test("a v1 send (no basis field at all) is served unchecked through the gate", async () => {
const hs = await homeserver({ users: { tok: EUREKA } });
try {
const receipt = await mouth(hs, "eureka", "tok").send({ requestId: "v1", persona: "eureka", roomAlias: ALIAS, text: "old organ" });
assert.equal(receipt.refusal, null);
assert.equal(hs.heads().length, 0);
assert.equal(hs.puts().length, 1);
} finally { await hs.close(); }
});
test("a gated send is serial with a gated send in the same room and leaves other rooms alone", async () => {
const gate = createRoomGate();
const order = [];
const hold = (room, name, ms) => gate.run(room, async () => { order.push(`${name}:start`); await new Promise(r => setTimeout(r, ms)); order.push(`${name}:end`); });
await Promise.all([hold("!a", "a1", 30), hold("!a", "a2", 1), hold("!b", "b1", 1)]);
assert.deepEqual(order, ["a1:start", "b1:start", "b1:end", "a1:end", "a2:start", "a2:end"]);
});
test("a send queued at the gate re-checks the brake before its PUT: braked during the wait, no PUT and a typed Brake refusal", async () => {
const hs = await homeserver({ users: { tok: EUREKA }, delay: kind => (kind === "put" ? 150 : 0) });
try {
let released = true;
const m = mouth(hs, "eureka", "tok", { brake: async () => released });
const first = send(m, "g1"); // holds the room gate for the length of its PUT
await new Promise(r => setTimeout(r, 50));
const queued = send(m, "g2"); // passes the entry brake check, then waits at the gate
await new Promise(r => setTimeout(r, 20));
released = false;
const [a, b] = await Promise.all([first, queued]);
assert.equal(a.refusal, null);
assert.deepEqual([b.refusal, b.eventId, b.newer], ["Brake", null, []]);
assert.deepEqual(hs.puts().map(p => p.body.body), ["line g1"], "the queued send never reaches the room");
} finally { await hs.close(); }
});