// E4/sage-3668: real flock contention must not turn a valid cite into an invalid-cite refusal. import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { ensureMirror, mirrorPath, SYNC_STATE_FILE, withLibraryLock } from '../../library/src/mirror.mjs'; import { guardReply } from '../../library/src/guard.mjs'; import { git } from '../../library/tests/helpers.mjs'; import { Client } from '../src/client.mjs'; import { serve } from '../src/server.mjs'; import { citationWorld, citedBody, fixtureCite } from './citation-helpers.mjs'; test('C2-06 held library lock gives the agent citation-unavailable and its retry reason', { timeout: 25000 }, async t => { const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 }); const context = { business: f.business, project: 'books', dataRoot }; await ensureMirror(context); const mirror = mirrorPath(context); git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served'); writeFileSync(join(mirror, SYNC_STATE_FILE), JSON.stringify({ version: 1, branch: 'main', served: f.commit, lastFetch: null, lastError: null, diverged: null, problems: [] }), { mode: 0o600 }); f.adapter.guard = (ctx, body, opts) => guardReply({ ...ctx, dataRoot }, body, opts); f.call(f.agent, 'role.claim'); const socket = join(f.root, 'availability.sock'), server = await serve({ broker: f.broker, path: socket }); t.after(() => server.close()); const client = new Client({ path: socket, cap: f.agent, timeout: 15000 }); const args = { to: 'human', body: citedBody([fixtureCite(f.commit)]) }; await client.call('message.send', args); // Establish that the cite and real mirror are valid. const before = f.store.get('SELECT count(*) n FROM messages').n; await withLibraryLock(mirror, async () => { await assert.rejects(client.call('message.send', args), e => { assert.equal(e.code, 'citation-unavailable'); assert.equal(e.reason, 'The library is busy or unavailable. Retry the answer when the library is available.'); return true; }); assert.equal(f.store.get('SELECT count(*) n FROM messages').n, before); // A fixed fallback carries no library cite and must not need the contested lock. await client.call('message.send', { to: 'human', body: '(no answer passed the citation check)' }); }); await client.call('message.send', args); // No lock mutation or lost valid cite after release. }); test('C2-06 malformed sync state is availability failure, never a fabricated invalid cite', async t => { const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 }); const context = { business: f.business, project: 'books', dataRoot }; await ensureMirror(context); const mirror = mirrorPath(context); writeFileSync(join(mirror, SYNC_STATE_FILE), '{broken', { mode: 0o600 }); await assert.rejects(guardReply(context, citedBody([fixtureCite(f.commit)]), { limits: f.limits(), definition: 'coder', pages: [], }), { code: 'citation-unavailable' }); }); test('C2-06 no mirror or freshly initialized empty mirror still means no served citation', async t => { const f = citationWorld(t), dataRoot = join(f.root, 'data'); mkdirSync(dataRoot, { mode: 0o700 }); const context = { business: f.business, project: 'books', dataRoot }; const check = () => guardReply(context, citedBody([fixtureCite(f.commit)]), { limits: f.limits(), definition: 'coder', pages: [] }); await assert.rejects(check(), { code: 'citation-invalid' }); await ensureMirror(context); await assert.rejects(check(), { code: 'citation-invalid' }); });