57 lines
3.6 KiB
JavaScript
57 lines
3.6 KiB
JavaScript
// 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' });
|
|
});
|