import { join } from 'path' /** * A board is saved over only by someone who read its last save (ADR-0098, architect ruling 2026-09-27). * * The editor sends the updatedAt it read; the save is claimed in one conditional statement, compared to the * millisecond. Here on sqlite through the real entity; the same claim is run on postgres, with two saves at * once, in board-claim-postgres.test.ts. */ const { createSqliteTestDataSource } = require( join(__dirname, '..', '..', 'ops-master', 'test', 'support', 'database.cjs') ) const DIST = join(__dirname, '..', 'dist-server') const { Board } = require(join(DIST, 'service', 'board', 'board.js')) const { claimUnchangedSince } = require(join(DIST, 'service', 'board', 'board-claim.js')) const shell = require('@things-factory/shell') let db: any let here: any let elsewhere: any beforeAll(async () => { db = createSqliteTestDataSource([Board]) await db.initialize() shell.addDataSource('default', db) here = await db.getRepository(shell.Domain).save({ name: 'here', subdomain: 'here' }) elsewhere = await db.getRepository(shell.Domain).save({ name: 'elsewhere', subdomain: 'elsewhere' }) }) afterAll(async () => { await db?.destroy() }) const aBoard = async (name: string) => { const saved = await db.getRepository(Board).save({ domain: here, name, model: '{"width":1,"height":1}' }) return db.getRepository(Board).findOneBy({ id: saved.id }) } test('★ the save that read the last save is claimed, and moves updatedAt on', async () => { const board = await aBoard('read-last') expect(await claimUnchangedSince(undefined, here.id, board.id, new Date(board.updatedAt))).toBe(true) const after = await db.getRepository(Board).findOneBy({ id: board.id }) expect(new Date(after.updatedAt).getTime()).not.toBe(new Date(board.updatedAt).getTime()) }) test('★ a save that read an older one is refused', async () => { const board = await aBoard('read-older') const read = new Date(board.updatedAt) await new Promise(resolve => setTimeout(resolve, 5)) expect(await claimUnchangedSince(undefined, here.id, board.id, read)).toBe(true) expect(await claimUnchangedSince(undefined, here.id, board.id, read)).toBe(false) }) test('the millisecond it read is enough - not the one before or after', async () => { const board = await aBoard('window') const at = new Date(board.updatedAt).getTime() expect(await claimUnchangedSince(undefined, here.id, board.id, new Date(at - 1))).toBe(false) expect(await claimUnchangedSince(undefined, here.id, board.id, new Date(at + 1))).toBe(false) expect(await claimUnchangedSince(undefined, here.id, board.id, new Date(at))).toBe(true) }) test('another site\'s board is never claimed', async () => { const board = await aBoard('other-site') expect(await claimUnchangedSince(undefined, elsewhere.id, board.id, new Date(board.updatedAt))).toBe(false) }) test('★ updateBoard claims before it writes, refuses by name, and never stores the expectation', () => { const { readFileSync } = require('fs') const source = readFileSync(join(__dirname, '..', 'server', 'service', 'board', 'board-mutation.ts'), 'utf-8') const update = source.slice(source.indexOf(' async updateBoard('), source.indexOf(' async releaseBoard(')) const claim = update.indexOf('claimUnchangedSince(tx, domain.id, id, new Date(patch.expectedUpdatedAt))') expect(claim).toBeGreaterThan(0) expect(claim).toBeLessThan(update.indexOf('repository.save(')) expect(update).toMatch(/throw new Refusal\(\s*'board-changed-since'/) expect(update).toMatch(/const \{ groupId, expectedUpdatedAt, \.\.\.patched \} = patch/) expect(source.slice(0, source.indexOf(' async updateBoard(')).slice(-400)).toMatch(/@UseMiddleware\(SpeakRefusals\)/) })