godcrm/backend/services/__tests__/agent-users-handle-first.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

110 lines
3.9 KiB
JavaScript

// ADR-164 Phase 1 — Pointer-first (handle-first) agent resolution.
//
// Proves the D3 resolver contract on the canonical service:
// 1. Global slug resolves passport-first:
// handle -> users -> (managed_by_agent_table_id, managed_by_agent_row_id) -> config.
// When the handle matches, the slug-by-name scan (dbAll) is NEVER run.
// 2. Fuzzy fallback is removed: a slug that only contains/prefixes an agent
// name no longer resolves.
// 3. Slug-by-name survives as a deprecated fallback (exact match only) and
// emits a WARN carrying event 'deprecated_slug_resolve'.
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { resolveAgentUser } from '../agent-users.js';
const mockDbAll = vi.fn();
const mockDbGet = vi.fn();
const mockDbRun = vi.fn();
const mockWarn = vi.fn();
vi.mock('../../database/connection.js', () => ({
dbGet: (...a) => mockDbGet(...a),
dbAll: (...a) => mockDbAll(...a),
dbRun: (...a) => mockDbRun(...a),
safeJsonParse: (raw, fallback) => {
if (raw && typeof raw === 'object') return raw;
try { return JSON.parse(raw); } catch { return fallback; }
},
}));
vi.mock('../../utils/logger.js', () => ({
apiLogger: { debug: vi.fn(), info: vi.fn(), warn: (...a) => mockWarn(...a), error: vi.fn() },
}));
beforeEach(() => {
mockDbAll.mockReset();
mockDbGet.mockReset();
mockDbRun.mockReset();
mockWarn.mockReset();
});
describe('ADR-164 handle-first resolution', () => {
it('resolves by passport handle without ever scanning agent rows by name', async () => {
const passport = {
id: 24,
name: 'Architect',
handle: 'architect',
user_type: 'agent',
managed_by_agent_table_id: 1784,
managed_by_agent_row_id: 987,
};
const residence = {
row_id: 987,
table_id: 1784,
data: JSON.stringify({ name: 'Architect', status: 'active', model: 'opus' }),
};
mockDbGet.mockImplementation((sql) => {
if (/lower\(handle\)/i.test(sql)) return Promise.resolve(passport);
if (/FROM table_rows tr\s+WHERE tr\.id/i.test(sql)) return Promise.resolve(residence);
return Promise.resolve(null);
});
const res = await resolveAgentUser('@architect');
expect(res).not.toBeNull();
expect(res.userId).toBe(24);
expect(res.agentRowId).toBe(987);
expect(res.user.handle).toBe('architect');
expect(res.user._agentConfig.model).toBe('opus');
// pointer-first: the slug-by-name scan must not run at all.
expect(mockDbAll).not.toHaveBeenCalled();
// pointer-first must never mint a new passport.
expect(mockDbRun).not.toHaveBeenCalled();
});
it('does NOT fuzzy-match a partial slug against an agent name', async () => {
mockDbGet.mockResolvedValue(null);
mockDbAll.mockResolvedValue([
{ row_id: 5, data: JSON.stringify({ name: 'Frontend Developer', status: 'active' }), table_id: 1784 },
]);
const res = await resolveAgentUser('@frontend');
expect(res).toBeNull();
// fuzzy is gone: a partial slug must never mint a passport for the wrong agent.
expect(mockDbRun).not.toHaveBeenCalled();
});
it('falls back to exact slug-by-name and warns deprecated_slug_resolve', async () => {
mockDbGet.mockImplementation((sql) => {
if (/lower\(handle\)/i.test(sql)) return Promise.resolve(null);
if (/managed_by_agent_row_id/i.test(sql)) return Promise.resolve({
id: 900, name: 'Cryptoralph', user_type: 'agent', managed_by_agent_row_id: 42,
});
return Promise.resolve(null);
});
mockDbAll.mockResolvedValue([
{ row_id: 42, data: JSON.stringify({ name: 'Cryptoralph', status: 'active' }), table_id: 1784 },
]);
const res = await resolveAgentUser('@cryptoralph');
expect(res).not.toBeNull();
expect(res.agentRowId).toBe(42);
const warned = mockWarn.mock.calls.some(
([meta]) => meta && meta.event === 'deprecated_slug_resolve'
);
expect(warned).toBe(true);
});
});