Read chat logs incrementally and drain retention (Refs #62)

This commit is contained in:
2026-10-08 17:28:49 -04:00
parent 0cc4498b53
commit c10f602e8e
18 changed files with 646 additions and 39 deletions
@@ -39,6 +39,37 @@ async function setup() {
}
describe('routine host observation retention', () => {
it('drains more than one batch while yielding and preserving recent or failed operations', async () => {
const api = await setup();
const { drainRetention } = await import('../../src/lib/server/retention.js');
const retained = [api.add('logs.read', { age: 0 }), api.add('logs.read', { state: 'failed' }), api.add('instance.restart')];
api.handle.transaction(() => {
const insert = api.handle.prepare(`INSERT INTO ec_host_operations
(id,host_id,kind,payload,state,created_at,updated_at,observation_success)
VALUES(?,?,'logs.read','{}','completed',?,?,1)`);
for (let i = 0; i < 1501; i++) insert.run(`read-${i}`, api.host.id, at(2 * day), at(2 * day));
})();
let yielded = false;
setImmediate(() => { yielded = true; });
expect(await drainRetention()).toBe(1501);
expect(yielded).toBe(true);
expect((api.handle.prepare('SELECT id FROM ec_host_operations').all() as { id: string }[]).map(row => row.id).sort()).toEqual(retained.sort());
expect(await drainRetention()).toBe(0);
});
it('strips existing log bodies in batches without deleting recent or protected history', async () => {
const api = await setup();
const { drainRetention, setRetentionControl } = await import('../../src/lib/server/retention.js');
const ids = Array.from({ length: 53 }, () => api.add('logs.read', { age: 0 }));
setRetentionControl({ category: 'observation', id: ids[0], protected: true });
api.handle.prepare("UPDATE ec_host_operations SET result=? WHERE kind='logs.read'")
.run(JSON.stringify({ lines: ['legacy log contents'], size: 20, truncated: false }));
expect(await drainRetention()).toBe(0);
for (const id of ids) expect(api.getHostOperation(id)?.result).toEqual({ retained: false, lineCount: 1, size: 20, truncated: false });
expect(api.handle.prepare("SELECT value FROM settings WHERE key='log_history_metadata_v1'").get()).toEqual({ value: '1' });
expect(api.handle.pragma('foreign_key_check')).toEqual([]);
});
it('removes aged successful reads, manifests, and read-only RCON while retaining failures, actions, and recent evidence', async () => {
const api = await setup();
const removed = ['host.inspect', 'instance.inspect', 'files.read', 'files.stat', 'files.list', 'files.manifest', 'logs.read']