// #1873 Phase A slice A1: GET /api/agents/levels, the read behind the Agents grid's level and source // columns. Owner only, GET only; a bad root file, organization or project hides only itself, and // no body carries a host path. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { fileURLToPath } from 'node:url'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { startServer } from '../src/serve.mjs'; import { createDevOwner } from '../src/dev-owner.mjs'; import { agentLevelsHTTP } from '../src/agent-levels-read.mjs'; import { checkRootAgents, loadRootAgents } from '../../organization/src/index.mjs'; import { close } from './fixture.mjs'; const rolesDir = fileURLToPath(new URL('../../../roles', import.meta.url)); const CTX = { configDir: '/home/owner/.config/mosaic-test', dataRoot: '/home/owner/.mosaic-test', instance: 'test' }; const DOCS = { acme: { roles: { chief: { definition: 'chief-of-staff' }, writer: { definition: 'copywriter', vars: { model: 'sonnet-5.5' } } }, projects: { web: { root: '/srv/secret/web', agents: { checker: { definition: 'fact-checker' }, writer: { vars: { 'limits.network': 'none' } } } }, broken: { root: '/srv/secret/broken', agents: { ghost: { exclude: true } } } } }, bad: null, }; const members = async () => [{ id: 'acme', doc: DOCS.acme }, { id: 'bad', invalid: true, reason: 'organization file is not valid JSON' }]; const root = () => ({ state: 'ok', ...checkRootAgents({ agentsVersion: 1, agents: { assistant: { definition: 'assistant' }, reviewer: { definition: 'reviewer', excludeOrgs: ['bad'] } } }, { rolesDir }) }); function call(options, method = 'GET') { const res = { status: 0, body: null, writeHead(status) { this.status = status; }, end(text) { this.body = JSON.parse(text); } }; return agentLevelsHTTP({ method }, res, options).then(() => res); } test('the route refuses a write, a missing read, a non-owner and an unreadable registry', async () => { const levels = { loadRoot: root, rolesDir, ctx: CTX }; assert.equal((await call({ owner: true, session: {}, organizations: { members }, levels }, 'POST')).status, 405); assert.equal((await call({ owner: true, session: {}, organizations: null, levels })).status, 404); assert.equal((await call({ owner: true, session: {}, organizations: { members }, levels: null })).status, 404); const denied = await call({ owner: false, session: {}, organizations: { members }, levels }); assert.equal(denied.status, 403); assert.equal(denied.body.error, 'owner-only'); const down = await call({ owner: true, session: {}, organizations: { members: async () => { throw new Error('registry down'); } }, levels, log: () => {} }); assert.equal(down.status, 503); assert.doesNotMatch(JSON.stringify(down.body), /registry down/); }); test('the owner reads every level; a bad project or organization hides only itself; no host path', async t => { const levels = { loadRoot: root, rolesDir, ctx: CTX }; const server = await startServer({ port: 0, sso: createDevOwner(), organizations: { list: async () => [], resolve: async () => null, members }, agentLevels: levels, log: () => {} }); t.after(() => close(server)); const response = await fetch(`http://127.0.0.1:${server.address().port}/api/agents/levels`); assert.equal(response.status, 200); const text = await response.text(), body = JSON.parse(text); assert.equal(body.root.state, 'ok'); assert.equal(body.root.assistant, 'assistant'); assert.deepEqual(body.root.agents.map(a => [a.name, a.excludeOrgs]), [['assistant', []], ['reviewer', ['bad']]]); const acme = body.orgs.find(o => o.id === 'acme'); assert.equal(acme.state, 'ok'); assert.deepEqual(acme.base.agents.map(a => `${a.level}:${a.name}`), ['root:reviewer', 'org:chief', 'org:writer']); assert.deepEqual(acme.projects.web.agents.map(a => `${a.level}:${a.name}`), ['root:reviewer', 'org:chief', 'org:writer', 'project:checker']); assert.deepEqual(acme.projects.web.agents.find(a => a.name === 'writer'), { name: 'writer', definition: 'copywriter', level: 'org', model: 'sonnet-5.5', narrowed: ['limits.network'], overrides: [], scope: { kind: 'org', org: 'acme' } }); assert.equal(acme.projects.broken.state, 'invalid'); assert.match(acme.projects.broken.reason, /names no root or organization agent to exclude/); assert.deepEqual(acme.defined.web.map(e => e.form).sort(), ['define', 'narrow']); assert.deepEqual(body.orgs.find(o => o.id === 'bad'), { id: 'bad', state: 'invalid', reason: 'organization file is not valid JSON' }); // Only the model and the names of narrowed keys travel; never a root path or another var's value. assert.doesNotMatch(text, /\/srv\/secret|"none"|"vars"/); // Signed out: the hook fence refuses before the route runs. const anon = await startServer({ port: 0, sso: { ...createDevOwner(), session: () => null }, organizations: { list: async () => [], resolve: async () => null, members }, agentLevels: levels, log: () => {} }); t.after(() => close(anon)); assert.notEqual((await fetch(`http://127.0.0.1:${anon.address().port}/api/agents/levels`, { redirect: 'manual' })).status, 200); }); test('a refused root file hides the root level only, and says why without a host path', async () => { const levels = { loadRoot: () => loadRootAgents('/nonexistent-dir-for-test', { rolesDir }), rolesDir, ctx: CTX }; // A missing file is no root agents, not a refusal. const absent = await call({ owner: true, session: {}, organizations: { members }, levels }); assert.deepEqual(absent.body.root, { state: 'absent', agents: [], assistant: null }); const refusing = { ...levels, loadRoot: () => { const e = new Error(`root agent file must belong to uid 1000 (${CTX.configDir}/agents.json)`); e.name = 'BusinessError'; throw e; } }; const res = await call({ owner: true, session: {}, organizations: { members }, levels: refusing }); assert.equal(res.status, 200); assert.equal(res.body.root.state, 'invalid'); assert.match(res.body.root.reason, /must belong to uid 1000/); assert.doesNotMatch(res.body.root.reason, /\/home\/owner/); assert.equal(res.body.orgs.find(o => o.id === 'acme').state, 'ok'); const crash = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => { throw new Error('raw parser bytes {"x"'); } } }); assert.equal(crash.body.root.reason, 'could not be read'); // A malformed file read through the real loader: the reason names the file, never the parser's quote of its bytes. const dir = mkdtempSync(join(tmpdir(), 'a')); writeFileSync(join(dir, 'agents.json'), '{"agentsVersion": 1, "agents": {"x": secret-marker-7731}}', { mode: 0o600 }); const bad = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => loadRootAgents(dir, { rolesDir }) } }); assert.equal(bad.body.root.state, 'invalid'); assert.equal(bad.body.root.reason, 'root agent file is not valid JSON (agents.json)'); assert.doesNotMatch(JSON.stringify(bad.body), /secret-marker|Unexpected token|position/); // A var value the root file sets wrongly never reaches the body; the key does. writeFileSync(join(dir, 'agents.json'), JSON.stringify({ agentsVersion: 1, agents: { x: { definition: 'reviewer', vars: { thinking: 'sk-secret-marker-7731' } } } })); const badVar = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => loadRootAgents(dir, { rolesDir }) } }); assert.equal(badVar.body.root.state, 'invalid'); assert.match(badVar.body.root.reason, /variable thinking must be one of/); assert.doesNotMatch(JSON.stringify(badVar.body), /secret-marker/); });