// C2-04: local made-up repositories only. No remote credential or live config. import test from 'node:test'; import assert from 'node:assert/strict'; import { chmodSync, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { spawn } from 'node:child_process'; import { once } from 'node:events'; import { mappingEntry, repo, git, BASE, MARKER } from './helpers.mjs'; import * as api from '../src/mirror.mjs'; const NOW = '2026-10-21T00:00:00.000Z'; const MOD = new URL('../src/mirror.mjs', import.meta.url).href; const state = (served = null) => ({ version: 1, branch: 'main', served, lastFetch: null, lastError: null, diverged: null, problems: [] }); const stateFile = mirror => join(mirror, api.SYNC_STATE_FILE); async function fixture(t, files = BASE) { const f = repo(t, files), dataRoot = join(f.root, 'data'); const context = { business: { id: 'acme', projects: { books: mappingEntry(), code: { root: f.repo } } }, project: 'books', dataRoot }; const mirror = await api.ensureMirror(context); const writeState = value => writeFileSync(stateFile(mirror), JSON.stringify(value), { mode: 0o600 }); const seed = (commit = f.commit) => { git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, `${commit}:refs/heads/served`); writeState(state(commit)); }; return { ...f, dataRoot, context, mirror, writeState, seed }; } test('C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization', async t => { const f = await fixture(t); assert.equal(api.SYNC_STATE_FILE, 'mosaic-library-sync.json'); assert.equal(api.mirrorPath(f.context), join(f.dataRoot, 'projects/acme/books')); assert.equal(f.mirror, api.mirrorPath(f.context)); assert.equal(git(f.mirror, 'rev-parse', '--is-bare-repository'), 'true'); assert.equal(git(f.mirror, 'remote'), ''); assert.equal(existsSync(join(f.mirror, '.git')), false); const lock = join(f.mirror, 'mosaic-library-sync.lock'), st = lstatSync(lock); assert.equal(st.mode & 0o777, 0o600); assert.equal(st.nlink, 1); assert.equal(await api.ensureMirror(f.context), f.mirror); assert.equal(lstatSync(lock).ino, st.ino, 'the lock inode is stable'); assert.equal(await api.servedCommit(f.mirror), null); assert.deepEqual(await api.libraryStatus(f.context), { project: 'books', ...state() }); }); test('C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths', async t => { const f = repo(t), dataRoot = join(f.root, 'data'); const c = { business: { id: 'acme', projects: { books: mappingEntry() } }, project: 'books', dataRoot }; const results = await Promise.all(Array.from({ length: 4 }, () => api.ensureMirror(c))); assert.deepEqual([...new Set(results)], [join(dataRoot, 'projects/acme/books')]); assert.deepEqual(readdirSync(join(dataRoot, 'projects/acme')), ['books']); assert.equal(git(results[0], 'rev-parse', '--is-bare-repository'), 'true'); }); test('C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner', { timeout: 15000 }, async t => { for (const status of [0, 1]) { const f = repo(t), dataRoot = join(f.root, 'data'); const context = { business: { id: 'acme', projects: { books: mappingEntry() } }, project: 'books', dataRoot }; const creator = child(t, `import cp from 'node:child_process';import {cpSync,chmodSync,lstatSync,readdirSync} from 'node:fs';import {dirname} from 'node:path';import {syncBuiltinESMExports} from 'node:module'; const original=cp.execFile;let winner; cp.execFile=(command,args,options,callback)=>{if(command!=='/usr/bin/mv')return original(command,args,options,callback); cpSync(args[2],args[3],{recursive:true});chmodSync(args[3],0o700);winner=lstatSync(args[3]).ino; const error=${status} ? Object.assign(Error('fixture no-clobber diagnostic'),{code:${status}}) : null; queueMicrotask(()=>callback(error));};syncBuiltinESMExports(); const {ensureMirror}=await import(${JSON.stringify(MOD)});const mirror=await ensureMirror(${JSON.stringify(context)}); process.send({same:lstatSync(mirror).ino===winner,entries:readdirSync(dirname(mirror))});process.disconnect();`); const [result] = await Promise.race([once(creator, 'message'), creator.finished.then(result => { throw Error(JSON.stringify(result)); })]); assert.deepEqual(result, { same: true, entries: ['books'] }); assert.deepEqual(await creator.finished, { code: 0, stderr: '' }); } }); test('C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps', async t => { const f = repo(t), dataRoot = join(f.root, 'data'); const c = { business: { id: 'acme', projects: { books: mappingEntry() } }, project: 'books', dataRoot }; const path = join(dataRoot, 'projects/acme/books'); mkdirSync(path, { recursive: true }); writeFileSync(join(path, 'keep'), 'made-up human file'); await assert.rejects(api.ensureMirror(c)); assert.equal(readFileSync(join(path, 'keep'), 'utf8'), 'made-up human file'); rmSync(dataRoot, { recursive: true }); symlinkSync(f.repo, dataRoot); assert.throws(() => api.mirrorPath(c), { code: 'project-path' }); unlinkSync(dataRoot); c.business.projects.code = { root: dataRoot }; assert.throws(() => api.mirrorPath(c), { code: 'project-path' }); }); test('C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock', async t => { const f = await fixture(t); writeFileSync(join(f.mirror, 'unknown-note'), 'do not silently allow this'); await assert.rejects(api.ensureMirror(f.context), { code: 'library-git' }); assert.equal(readFileSync(join(f.mirror, 'unknown-note'), 'utf8'), 'do not silently allow this'); unlinkSync(join(f.mirror, 'unknown-note')); git(f.mirror, 'config', 'remote.origin.url', 'https://example.invalid/fixture.git'); await assert.rejects(api.ensureMirror(f.context), { code: 'library-git' }); git(f.mirror, 'config', '--remove-section', 'remote.origin'); unlinkSync(join(f.mirror, 'mosaic-library-sync.lock')); await assert.rejects(api.ensureMirror(f.context), { code: 'library-state-invalid' }); assert.equal(existsSync(join(f.mirror, 'mosaic-library-sync.lock')), false); }); test('C2-04 state: missing state is empty only while the served ref is absent', async t => { const f = await fixture(t); assert.deepEqual(await api.readSyncState(f.mirror, { branch: 'main' }), state()); f.seed(); unlinkSync(stateFile(f.mirror)); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); }); test('C2-04 state: valid problems and sync metadata return no content and change no state bytes', async t => { const f = await fixture(t); f.seed(); const value = { ...state(f.commit), lastFetch: NOW, lastError: { code: 'library-git', at: NOW }, diverged: { commit: 'a'.repeat(40), at: NOW }, problems: [{ code: 'R4', path: 'Book One/Chapters/A.md', line: 2, message: 'link resolves to no file or to more than one' }] }; f.writeState(value); const before = readFileSync(stateFile(f.mirror)); assert.deepEqual(await api.readSyncState(f.mirror, { branch: 'main' }), value); assert.equal(JSON.stringify(await api.libraryStatus(f.context)).includes(MARKER), false); assert.deepEqual(readFileSync(stateFile(f.mirror)), before); }); test('C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed', async t => { const f = await fixture(t); f.seed(); const mutations = [ s => { s.extra = true; }, s => { s.version = 2; }, s => { s.branch = 'other'; }, s => { s.served = 'a'.repeat(40); }, s => { s.served = null; }, s => { s.served = 'ABC'; }, s => { s.lastFetch = 'yesterday'; }, s => { s.lastFetch = 1; }, s => { s.lastError = { code: 'bad code', at: NOW }; }, s => { s.lastError = { code: 'library-git', at: NOW, stderr: MARKER }; }, s => { s.diverged = { commit: 'not-a-sha', at: NOW }; }, s => { s.problems = [{ code: 'R1', path: 'Book One/A.md', message: MARKER }]; }, s => { s.problems = [{ code: 'R4', path: 'Book One/A.md', line: 0, message: 'link resolves to no file or to more than one' }]; }, s => { s.problems = null; }, ]; for (const mutate of mutations) { const s = state(f.commit); mutate(s); f.writeState(s); const before = readFileSync(stateFile(f.mirror)); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); assert.deepEqual(readFileSync(stateFile(f.mirror)), before); } writeFileSync(stateFile(f.mirror), '{'); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); }); test('C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement', async t => { const f = await fixture(t); f.seed(); const outside = join(f.root, 'outside.json'); writeFileSync(outside, JSON.stringify(state(f.commit)), { mode: 0o600 }); unlinkSync(stateFile(f.mirror)); symlinkSync(outside, stateFile(f.mirror)); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); unlinkSync(stateFile(f.mirror)); linkSync(outside, stateFile(f.mirror)); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); unlinkSync(stateFile(f.mirror)); f.writeState(state(f.commit)); chmodSync(stateFile(f.mirror), 0o644); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); chmodSync(stateFile(f.mirror), 0o600); unlinkSync(join(f.mirror, 'mosaic-library-sync.lock')); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-state-invalid' }); assert.equal(existsSync(join(f.mirror, 'mosaic-library-sync.lock')), false); }); test('C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text', async t => { const f = await fixture(t); f.seed(); const path = 'Book One/Characters/Tomas.md'; writeFileSync(join(f.repo, path), 'newer text\n'); git(f.repo, 'commit', '-qam', 'newer'); git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', f.mirror, 'HEAD:refs/heads/unserved'); git(f.mirror, 'symbolic-ref', 'HEAD', 'refs/heads/unserved'); assert.deepEqual(await api.readLibraryFile({ ...f.context, path }), { project: 'books', path, commit: f.commit, content: BASE[path] }); await assert.rejects(api.readLibraryFile({ ...f.context, path, commit: git(f.repo, 'rev-parse', 'HEAD') }), { code: 'project-read-path' }); for (const path of ['../escape.md', '/absolute.md', 'Book One/Board.canvas', 'Inbox Note.md']) await assert.rejects(api.readLibraryFile({ ...f.context, path }), { code: 'project-read-path' }); await assert.rejects(api.readLibraryFile({ ...f.context, path: 'Book One/Missing.md' }), { code: 'project-read-missing' }); }); test('C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies', async t => { const f = await fixture(t, { ...BASE, 'Book One/Bad.md': { link: 'Characters/Tomas.md' }, 'Book One/Executable.md': { mode: 0o755, text: MARKER }, 'Book One/Twin.md': MARKER, 'Book One/twin.md': MARKER }); await assert.rejects(api.readLibraryFile({ ...f.context, path: 'Book One/Characters/Tomas.md' }), { code: 'library-commit' }); f.seed(); for (const path of ['Book One/Bad.md', 'Book One/Executable.md', 'Book One/Twin.md', 'Book One/twin.md']) await assert.rejects(api.readLibraryFile({ ...f.context, path }), { code: 'project-read-missing' }); }); function child(t, source) { const env = { ...process.env }; delete env.NODE_TEST_CONTEXT; const p = spawn(process.execPath, ['--input-type=module', '-e', source], { env, stdio: ['ignore', 'pipe', 'pipe', 'ipc'] }); let stderr = ''; p.stderr.on('data', b => { stderr += b; }); p.stdout.resume(); p.finished = new Promise(resolve => { p.once('error', e => resolve({ error: e })); p.once('close', code => resolve({ code, stderr })); }); t.after(async () => { if (p.exitCode === null) p.kill('SIGKILL'); await p.finished; }); return p; } test('C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes', { timeout: 15000 }, async t => { const f = await fixture(t); // Transfer objects without serving them. No fetch or remote is added by the API. git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', f.mirror, 'HEAD:refs/heads/candidate'); for (const first of [true, false]) { if (!first) { f.seed(); writeFileSync(join(f.repo, 'Book One/New.md'), '# New\n'); git(f.repo, 'add', '.'); git(f.repo, 'commit', '-qm', 'new'); git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', f.mirror, 'HEAD:refs/heads/candidate'); } const commit = git(f.repo, 'rev-parse', 'HEAD'); const writer = child(t, `import {withLibraryLock} from ${JSON.stringify(MOD)};import {writeFileSync} from 'node:fs';import {git} from ${JSON.stringify(new URL('../../project/src/git.mjs', import.meta.url).href)}; await withLibraryLock(${JSON.stringify(f.mirror)},async()=>{await git(${JSON.stringify(f.mirror)},['update-ref','refs/heads/served',${JSON.stringify(commit)}]);process.send('moved');await new Promise(r=>process.once('message',r));writeFileSync(${JSON.stringify(stateFile(f.mirror))},${JSON.stringify(JSON.stringify(state(commit)))},{mode:0o600});});process.disconnect();`); const [message] = await once(writer, 'message'); assert.equal(message, 'moved'); let complete = false; const read = api.readSyncState(f.mirror, { branch: 'main' }).then(value => { complete = true; return value; }); await new Promise(r => setTimeout(r, 50)); assert.equal(complete, false, 'the inconsistent pair is not observed'); writer.send('publish'); assert.deepEqual(await read, state(commit)); assert.deepEqual(await writer.finished, { code: 0, stderr: '' }); } }); test('C2-04 lock: same-process waits, timeout code, callback expiry and throw release', { timeout: 15000 }, async t => { const f = await fixture(t); let release, scoped; const held = api.withLibraryLock(f.mirror, async lock => { scoped = lock.readSyncState; await new Promise(r => { release = r; }); }); const deadline = Date.now() + 5000; while (!release && Date.now() < deadline) await new Promise(r => setTimeout(r, 5)); assert.equal(typeof release, 'function', 'first lock acquired before deadline'); await assert.rejects(api.readSyncState(f.mirror, { branch: 'main' }), { code: 'library-sync-busy' }); release(); await held; await assert.rejects(scoped({ branch: 'main' }), { code: 'library-state-invalid' }); await assert.rejects(api.withLibraryLock(f.mirror, async () => { throw Error('fixture throw'); }), /fixture throw/); assert.deepEqual(await api.readSyncState(f.mirror, { branch: 'main' }), state()); }); test('C2-04 lock: owner death releases the kernel lock without replacing its file', { timeout: 15000 }, async t => { const f = await fixture(t), lock = join(f.mirror, 'mosaic-library-sync.lock'), ino = lstatSync(lock).ino; const owner = child(t, `import {withLibraryLock} from ${JSON.stringify(MOD)};await withLibraryLock(${JSON.stringify(f.mirror)},async()=>{process.send('held');await new Promise(r=>process.once('message',r));});`); const [message] = await once(owner, 'message'); assert.equal(message, 'held'); owner.kill('SIGKILL'); await owner.finished; assert.deepEqual(await api.readSyncState(f.mirror, { branch: 'main' }), state()); assert.equal(lstatSync(lock).ino, ino); }); test('C2-04 lock: a missing flock binary becomes a handled refusal', { timeout: 10000 }, async t => { const f = await fixture(t); const missing = child(t, `import cp from 'node:child_process';import {syncBuiltinESMExports} from 'node:module';import {EventEmitter} from 'node:events'; const original=cp.spawn;cp.spawn=(command,...args)=>{if(command!=='/usr/bin/flock')return original(command,...args);const p=new EventEmitter();process.nextTick(()=>p.emit('error',Object.assign(Error('fixture missing flock'),{code:'ENOENT'})));return p;};syncBuiltinESMExports(); const {readSyncState}=await import(${JSON.stringify(MOD)});try{await readSyncState(${JSON.stringify(f.mirror)},{branch:'main'});process.send('unexpected success');}catch(e){process.send(e.code);}process.disconnect();`); const [code] = await once(missing, 'message'); assert.equal(code, 'library-state-invalid'); assert.deepEqual(await missing.finished, { code: 0, stderr: '' }); }); test('C2-04 lock: an unawaited scoped read drains before the lock is released', { timeout: 10000 }, async t => { const f = await fixture(t); let scoped, pending, complete = false; await api.withLibraryLock(f.mirror, lock => { scoped = lock.readSyncState; pending = scoped({ branch: 'main' }).then(value => { complete = true; return value; }); }); assert.equal(complete, true); assert.deepEqual(await pending, state()); await assert.rejects(scoped({ branch: 'main' }), { code: 'library-state-invalid' }); });