open-design/apps/daemon/tests/critique-boot-reconcile.test.ts
marco 5dd70b5016
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m3s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 11s
Initial import: open-design source for helix-mind.ai distribution
This repository contains the open-design daemon CLI source code, built
and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use
by the HelixMind /design slash command.

Licenses: Apache-2.0 (root) + MIT (skills/*)
2026-05-06 20:50:24 +02:00

137 lines
4.5 KiB
TypeScript

/**
* Boot-reconcile tests for Critique Theater (Defect 6).
*
* Verifies that reconcileStaleRuns is called on daemon boot (simulated here
* by calling it directly as the server would) and that it flips old 'running'
* rows to 'interrupted' with recoveryReason='daemon_restart'.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync } from 'node:fs';
import { rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import Database from 'better-sqlite3';
import {
migrateCritique,
insertCritiqueRun,
getCritiqueRun,
reconcileStaleRuns,
} from '../src/critique/persistence.js';
import { defaultCritiqueConfig } from '@open-design/contracts/critique';
function freshDb(): Database.Database {
const db = new Database(':memory:');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
db.exec(`
CREATE TABLE projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE conversations (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
);
INSERT INTO projects (id, name, created_at, updated_at) VALUES ('p1', 'p1', 0, 0);
`);
migrateCritique(db);
return db;
}
let tmpDir: string;
let db: Database.Database;
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'od-boot-reconcile-test-'));
db = freshDb();
});
afterEach(async () => {
db.close();
await rm(tmpDir, { recursive: true, force: true });
});
describe('boot reconcile (Defect 6)', () => {
it('seeds an old running row then flips it to interrupted on simulated boot', () => {
const cfg = defaultCritiqueConfig();
const staleAfterMs = cfg.totalTimeoutMs;
// Insert a 'running' row whose updated_at is older than staleAfterMs.
const oldTs = Date.now() - staleAfterMs - 10_000;
insertCritiqueRun(db, {
id: 'stale-run-1',
projectId: 'p1',
conversationId: null,
status: 'running',
protocolVersion: 1,
createdAt: oldTs,
updatedAt: oldTs,
});
// Simulate what the daemon boot path does after openDatabase.
const flipped = reconcileStaleRuns(db, { staleAfterMs });
expect(flipped).toBe(1);
// The row should now be 'interrupted' with recoveryReason='daemon_restart'.
const row = getCritiqueRun(db, 'stale-run-1');
expect(row?.status).toBe('interrupted');
// rounds_json is accessible via row.rounds; the recoveryReason is an internal
// field not exposed on CritiqueRunRow. Access the raw value via the DB directly.
const raw = db
.prepare(`SELECT rounds_json FROM critique_runs WHERE id = ?`)
.get('stale-run-1') as { rounds_json: string } | undefined;
const payload = raw ? (JSON.parse(raw.rounds_json) as { recoveryReason?: string }) : {};
expect(payload.recoveryReason).toBe('daemon_restart');
});
it('does not flip a recently-running row (within staleAfterMs)', () => {
const cfg = defaultCritiqueConfig();
const staleAfterMs = cfg.totalTimeoutMs;
// Insert a 'running' row whose updated_at is recent (not stale).
const recentTs = Date.now() - 100;
insertCritiqueRun(db, {
id: 'fresh-run-1',
projectId: 'p1',
conversationId: null,
status: 'running',
protocolVersion: 1,
createdAt: recentTs,
updatedAt: recentTs,
});
const flipped = reconcileStaleRuns(db, { staleAfterMs });
expect(flipped).toBe(0);
const row = getCritiqueRun(db, 'fresh-run-1');
expect(row?.status).toBe('running');
});
it('is idempotent: a second call on the same db flips 0 rows', () => {
const cfg = defaultCritiqueConfig();
const staleAfterMs = cfg.totalTimeoutMs;
const oldTs = Date.now() - staleAfterMs - 10_000;
insertCritiqueRun(db, {
id: 'stale-run-2',
projectId: 'p1',
conversationId: null,
status: 'running',
protocolVersion: 1,
createdAt: oldTs,
updatedAt: oldTs,
});
const first = reconcileStaleRuns(db, { staleAfterMs });
expect(first).toBe(1);
// Second call: the row is now 'interrupted', not 'running', so nothing more to flip.
const second = reconcileStaleRuns(db, { staleAfterMs });
expect(second).toBe(0);
});
});