Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
146 lines
5.1 KiB
JavaScript
146 lines
5.1 KiB
JavaScript
/**
|
|
* AgentVersionService integration tests — ADR-0007-S G1-data acceptance.
|
|
*
|
|
* Proves the three ADR-0007-S §7 acceptance criteria against a real Postgres:
|
|
* #1 mint one agent → exactly one active row
|
|
* #2 promote v2 → v1 retired, v2 active (atomic, never two active)
|
|
* #3 FK / service rejects an orphan version (no matching agent row)
|
|
*
|
|
* @requires migration 073 applied + `table_rows` present (godcrm_test is a
|
|
* migrated sync of prod, so both hold there — ADR-0009).
|
|
*
|
|
* Gated behind TEST_POSTGRES=true: on the prod-marked host the ADR-0009 boot
|
|
* guard hard-aborts the whole run (POSTGRES_DB=godcrm_prod), so this suite is
|
|
* meant for the DEV-local godcrm_test cycle / CI, never the prod box.
|
|
*/
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import {
|
|
AGENTS_TABLE_ID,
|
|
mintAgentVersion,
|
|
promoteAgentVersion,
|
|
retireAgentVersion,
|
|
listAgentVersions,
|
|
getActiveVersion,
|
|
} from '../AgentVersionService.js';
|
|
import { dbRun, dbGet, dbAll, destroyAdapter } from '../../database/connection.js';
|
|
|
|
const runPg = process.env.TEST_POSTGRES === 'true';
|
|
|
|
describe.skipIf(!runPg)('AgentVersionService (ADR-0007-S G1-data)', () => {
|
|
let agentA; // throwaway AI-Agents row id
|
|
let agentB;
|
|
let notAgent; // a non-agent row (wrong table_id)
|
|
|
|
beforeAll(async () => {
|
|
// Seed isolated fixtures in the universal-tables row store.
|
|
const a = await dbGet(
|
|
`INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, '{}'::jsonb) RETURNING id`,
|
|
[AGENTS_TABLE_ID, 'g1d-test-agentA']
|
|
);
|
|
const b = await dbGet(
|
|
`INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, '{}'::jsonb) RETURNING id`,
|
|
[AGENTS_TABLE_ID, 'g1d-test-agentB']
|
|
);
|
|
const n = await dbGet(
|
|
`INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, '{}'::jsonb) RETURNING id`,
|
|
[424242, 'g1d-test-not-agent']
|
|
);
|
|
agentA = a.id;
|
|
agentB = b.id;
|
|
notAgent = n.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// ON DELETE CASCADE on agent_versions.agent_row_id cleans the versions too.
|
|
for (const id of [agentA, agentB, notAgent]) {
|
|
if (id) await dbRun('DELETE FROM table_rows WHERE id = ?', [id]);
|
|
}
|
|
await destroyAdapter();
|
|
});
|
|
|
|
it('#1 mint one agent → exactly one active row', async () => {
|
|
const v1 = await mintAgentVersion({
|
|
agentRowId: agentA,
|
|
agentSlug: 'smith-researcher',
|
|
prompt: 'genesis prompt',
|
|
config: { model: 'claude-opus-4-8' },
|
|
createdBy: 1,
|
|
});
|
|
expect(v1.status).toBe('active'); // genesis is live by default
|
|
expect(v1.version_int).toBe(1);
|
|
|
|
const active = await dbAll(
|
|
`SELECT * FROM agent_versions WHERE agent_row_id = ? AND status = 'active'`,
|
|
[agentA]
|
|
);
|
|
expect(active).toHaveLength(1);
|
|
expect(active[0].id).toBe(v1.id);
|
|
});
|
|
|
|
it('#2 promote v2 → v1 retired, v2 active, never two active', async () => {
|
|
const v1 = await getActiveVersion(agentA);
|
|
const v2 = await mintAgentVersion({
|
|
agentRowId: agentA,
|
|
agentSlug: 'smith-researcher',
|
|
prompt: 'revised prompt',
|
|
activate: false,
|
|
});
|
|
expect(v2.status).toBe('draft');
|
|
expect(v2.version_int).toBe(2);
|
|
|
|
const promoted = await promoteAgentVersion(v2.id);
|
|
expect(promoted.status).toBe('active');
|
|
|
|
const v1After = await dbGet('SELECT * FROM agent_versions WHERE id = ?', [v1.id]);
|
|
expect(v1After.status).toBe('retired');
|
|
|
|
const active = await dbAll(
|
|
`SELECT * FROM agent_versions WHERE agent_row_id = ? AND status = 'active'`,
|
|
[agentA]
|
|
);
|
|
expect(active).toHaveLength(1);
|
|
expect(active[0].id).toBe(v2.id);
|
|
});
|
|
|
|
it('#2b DB partial-unique index blocks a second active row', async () => {
|
|
await expect(
|
|
dbRun(
|
|
`INSERT INTO agent_versions (agent_row_id, version_int, status) VALUES (?, 999, 'active')`,
|
|
[agentA]
|
|
)
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('#3 FK rejects an orphan version (non-existent agent row)', async () => {
|
|
await expect(
|
|
dbRun(`INSERT INTO agent_versions (agent_row_id, version_int) VALUES (?, 1)`, [999999999])
|
|
).rejects.toThrow();
|
|
});
|
|
|
|
it('#3b service rejects mint for a non-existent agent (AGENT_NOT_FOUND)', async () => {
|
|
await expect(mintAgentVersion({ agentRowId: 999999999 })).rejects.toMatchObject({
|
|
code: 'AGENT_NOT_FOUND',
|
|
});
|
|
});
|
|
|
|
it('#3c service rejects mint against a non-agent row (NOT_AN_AGENT)', async () => {
|
|
await expect(mintAgentVersion({ agentRowId: notAgent })).rejects.toMatchObject({
|
|
code: 'NOT_AN_AGENT',
|
|
});
|
|
});
|
|
|
|
it('retire + list + per-agent isolation', async () => {
|
|
const b1 = await mintAgentVersion({ agentRowId: agentB, agentSlug: 'smith-builder' });
|
|
expect(b1.status).toBe('active');
|
|
expect(b1.version_int).toBe(1);
|
|
|
|
const retired = await retireAgentVersion(b1.id);
|
|
expect(retired.status).toBe('retired');
|
|
expect(await getActiveVersion(agentB)).toBeNull();
|
|
|
|
// agentA still has its own 2 revisions, untouched by agentB activity.
|
|
const listA = await listAgentVersions(agentA);
|
|
expect(listA).toHaveLength(2);
|
|
expect(listA[0].version_int).toBe(2); // newest first
|
|
});
|
|
});
|