Retain completed status batches as routine observations

This commit is contained in:
2026-10-05 16:18:24 -04:00
parent ae703617af
commit 1d9e518feb
7 changed files with 60 additions and 9 deletions
@@ -63,6 +63,36 @@ describe('routine host observation retention', () => {
expect(api.pruneHostObservations()).toBe(0);
});
it('keeps per-server failures in routine completed batches and prunes them on schedule', async () => {
const api = await setup();
const results = [{ instanceId: 'one', generation: 1, reply: { ok: false, reason: 'timeout', text: '' } }];
const batches = [false, true].map(ok => api.add('rcon.status-batch', { result: { ok, results } }));
const failed = api.add('rcon.status-batch', { state: 'failed', error: 'Batch failed' });
const malformed = api.add('rcon.status-batch', { result: { ok: false } });
expect(api.listHostOperationHistory(1, 'important').operations.map(op => op.id).sort()).toEqual([failed, malformed].sort());
for (const id of batches) expect(api.getHostOperation(id)?.result).toMatchObject({ results });
expect(api.pruneHostObservations()).toBe(2);
for (const id of batches) expect(api.getHostOperationStatus(id)).toBeNull();
});
it('reclassifies existing completed batches once without hiding operation errors', async () => {
const api = await setup();
const result = { ok: false, results: [{ instanceId: 'one', generation: 1, reply: { ok: false, reason: 'timeout' } }] };
const batch = api.add('rcon.status-batch', { result });
const retained = [api.add('rcon.status-batch', { result, error: 'Incomplete' }),
api.add('rcon.status-batch', { result, state: 'failed' }),
api.add('rcon.status-batch', { result: { ...result, error: 'Incomplete' } })];
api.handle.exec("UPDATE ec_host_operations SET observation_success=0; DELETE FROM ec_host_schema_migrations WHERE name='completed-status-batches'");
api.closeDb();
expect(api.listHostOperationHistory(1, 'important').operations.map(op => op.id).sort()).toEqual(retained.sort());
expect(api.getHostOperation(batch)?.result).toEqual(result);
expect(api.pruneHostObservations()).toBe(1);
// The migration is not a recurring scan or reclassification of retained evidence.
api.db().prepare("UPDATE ec_host_operations SET state='completed',error=NULL,result=? WHERE id=?").run(JSON.stringify(result), retained[0]);
api.closeDb();
expect(api.pruneHostObservations()).toBe(0);
});
it('classifies full serialized RCON payloads and deletes their events and immutable bytes together', async () => {
const api = await setup();
const read = api.add('rcon.execute', { serialized: true, payload: { command: 'status' }, routing: { command: 'map_restart' } });