diff --git a/setup/agent-jails.ts b/setup/agent-jails.ts index 87f80ec..aa0b311 100644 --- a/setup/agent-jails.ts +++ b/setup/agent-jails.ts @@ -198,11 +198,7 @@ export async function run(args: string[]): Promise { } const { role, def } = entry; - const jailName = resolveJailName({ - agentName: TENANT_ID, - role, - legacyNames: [role], - }); + const jailName = resolveJailName({ role }); const ip = resolveJailIp(registry, role); const hostname = `${role}.${internalDomain}`; diff --git a/setup/bastille-helpers.test.ts b/setup/bastille-helpers.test.ts index 41c1b7f..fe26dcc 100644 --- a/setup/bastille-helpers.test.ts +++ b/setup/bastille-helpers.test.ts @@ -147,7 +147,6 @@ describe('resolveJailName()', () => { it('returns envOverride immediately without calling bastille', () => { const result = resolveJailName({ - agentName: 'clawdie', role: 'db', envOverride: 'custom-db-jail', }); @@ -155,39 +154,23 @@ describe('resolveJailName()', () => { expect(spawnSyncMock).not.toHaveBeenCalled(); }); - it('returns preferred name when it exists', () => { - // preferred = agentName (strip [-_]) + '_' + role (hyphens→_) = "clawdie_db" - const list = BASTILLE_LIST_RUNNING + '\n 3 clawdie_db on 99 Up thick 10.0.0.100 - 15.0-RELEASE'; + it('returns the role name when it exists', () => { + const list = BASTILLE_LIST_RUNNING + '\n 3 db on 99 Up thick 10.0.0.100 - 15.0-RELEASE'; spawnSyncMock.mockReturnValue(makeSpawnResult(list, 0)); - const result = resolveJailName({ agentName: 'clawdie', role: 'db' }); - expect(result).toBe('clawdie_db'); + const result = resolveJailName({ role: 'db' }); + expect(result).toBe('db'); }); - it('falls back to legacy hyphen name when preferred does not exist', () => { - // preferred = "clawdie_db" not found, legacyHyphen = "clawdie-db" exists - const list = BASTILLE_LIST_RUNNING + '\n 3 clawdie-db on 99 Up thick 10.0.0.100 - 15.0-RELEASE'; - spawnSyncMock.mockReturnValue(makeSpawnResult(list, 0)); - const result = resolveJailName({ agentName: 'clawdie', role: 'db' }); - expect(result).toBe('clawdie-db'); - }); - - it('returns preferred name when nothing is found (default)', () => { + it('returns the role name when nothing is found', () => { spawnSyncMock.mockReturnValue(makeSpawnResult(BASTILLE_LIST_RUNNING, 0)); - const result = resolveJailName({ agentName: 'clawdie', role: 'newrole' }); - expect(result).toBe('clawdie_newrole'); + const result = resolveJailName({ role: 'newrole' }); + expect(result).toBe('newrole'); }); - it('strips hyphens and underscores from agentName for preferred', () => { - spawnSyncMock.mockReturnValue(makeSpawnResult('', 0)); - const result = resolveJailName({ agentName: 'my-agent_bot', role: 'db' }); - // safeAgentName = 'myagentbot', separated by _ - expect(result).toBe('myagentbot_db'); - }); - - it('converts hyphens in role to underscores for preferred', () => { - spawnSyncMock.mockReturnValue(makeSpawnResult('', 0)); - const result = resolveJailName({ agentName: 'clawdie', role: 'db-worker' }); - // safeRole = 'db_worker' (hyphens converted to _ for readability) - expect(result).toBe('clawdie_db_worker'); + it('uses explicit candidate names when supplied', () => { + const list = BASTILLE_LIST_RUNNING + '\n 3 cms on 99 Up thick 10.0.0.100 - 15.0-RELEASE'; + spawnSyncMock.mockReturnValue(makeSpawnResult(list, 0)); + const result = resolveJailName({ role: 'web', names: ['web', 'cms'] }); + expect(result).toBe('cms'); }); }); diff --git a/setup/bastille-helpers.ts b/setup/bastille-helpers.ts index d65baf4..68ba021 100644 --- a/setup/bastille-helpers.ts +++ b/setup/bastille-helpers.ts @@ -74,29 +74,19 @@ export function jailRoot(jailName: string): string { } export interface ResolveJailNameOptions { - agentName: string; role: string; - legacyNames?: string[]; + names?: string[]; envOverride?: string; } export function resolveJailName(opts: ResolveJailNameOptions): string { if (opts.envOverride) return opts.envOverride; - const safeAgentName = opts.agentName.replace(/[-_]/g, ''); - const safeRole = opts.role.replace(/-/g, '_'); - const preferred = `${safeAgentName}_${safeRole}`; - const legacyHyphen = `${opts.agentName}-${opts.role}`; - - const candidates = [ - preferred, - legacyHyphen, - ...(opts.legacyNames || [opts.role]), - ]; + const candidates = opts.names && opts.names.length > 0 ? opts.names : [opts.role]; for (const candidate of candidates) { if (jailExists(candidate)) return candidate; } - return preferred; + return candidates[0]; } export interface ProvisionOpts { @@ -201,11 +191,7 @@ export async function provisionJail( opts: ProvisionOpts, ): Promise { const safeAgentName = opts.agentName.replace(/[-_]/g, ''); - const jailName = resolveJailName({ - agentName: opts.agentName, - role, - legacyNames: [role], - }); + const jailName = resolveJailName({ role }); const registry = loadJailRegistry(); const ip = resolveJailIp(registry, role); diff --git a/setup/cms.ts b/setup/cms.ts index 6bc037e..1ebed4c 100644 --- a/setup/cms.ts +++ b/setup/cms.ts @@ -916,25 +916,14 @@ export async function run(_args: string[]): Promise { process.exit(1); } - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); const defaultJailName = 'cms'; - const preferredJailName = `${safeAgentName}cms`; - const legacyHyphenName = `${TENANT_ID}-cms`; const astroSitePathReal = jailPathNoHomeSymlink(CMS_DOCS_SITE_PATH); const landingSitePathReal = jailPathNoHomeSymlink(PLATFORM_LANDING_SITE_PATH); const landingPublicDomain = publicRootDomain(); const landingEnabled = landingPublicDomain.length > 0; let jailName = explicitJailName; if (!jailName) { - if (jailExists(defaultJailName)) { - jailName = defaultJailName; - } else if (jailExists(preferredJailName)) { - jailName = preferredJailName; - } else if (jailExists(legacyHyphenName)) { - jailName = legacyHyphenName; - } else { - jailName = defaultJailName; - } + jailName = defaultJailName; } const runBastille = (args: string[]) => bastille(...args); diff --git a/setup/db.ts b/setup/db.ts index 5314ec4..cabbd92 100644 --- a/setup/db.ts +++ b/setup/db.ts @@ -4,8 +4,8 @@ * Supports two modes: * - `DB_RUNTIME=host` (default): provision PostgreSQL directly on the host; * jails connect via warden0 at `${AGENT_SUBNET_BASE}.1:5432` - * - `DB_RUNTIME=jail`: provision a Bastille db jail (legacy/optional — - * pulls a fat jail and duplicates pkg upgrade paths) + * - `DB_RUNTIME=jail`: provision PostgreSQL in a Bastille db jail when an + * operator intentionally wants database isolation from the host service) */ import { execSync, spawnSync } from 'child_process'; import fs from 'fs'; @@ -563,7 +563,6 @@ export async function run(_args: string[]): Promise { '' ).trim(); const jailName = resolveJailName({ - agentName: TENANT_ID, role: 'db', envOverride: explicitJailName || undefined, }); diff --git a/setup/jails.ts b/setup/jails.ts index 2c51fd4..440a098 100644 --- a/setup/jails.ts +++ b/setup/jails.ts @@ -7,7 +7,7 @@ */ import { execSync, spawnSync } from 'child_process'; -import { AGENT_INTERNAL_DOMAIN, SUBNET_BASE, TENANT_ID } from '../src/config.js'; +import { AGENT_INTERNAL_DOMAIN, SUBNET_BASE } from '../src/config.js'; import { logger } from '../src/logger.js'; import { loadPackageList, mountPkgCacheInJail } from './packages.js'; import { commandExists, getPlatform, isRoot } from './platform.js'; @@ -59,16 +59,7 @@ export async function run(args: string[]): Promise { return; } - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); - const preferredJailName = `${safeAgentName}worker`; - const legacyHyphenName = `${TENANT_ID}-worker`; - const legacyPlainName = 'worker'; - let workerJail = preferredJailName; - if (jailExists(legacyHyphenName) && !jailExists(preferredJailName)) { - workerJail = legacyHyphenName; - } else if (jailExists(legacyPlainName) && !jailExists(preferredJailName)) { - workerJail = legacyPlainName; - } + const workerJail = 'worker'; const workerIp = process.env.WORKER_JAIL_IP_START || process.env.WORKER_JAIL_IP || diff --git a/setup/llama-cpp.ts b/setup/llama-cpp.ts index edd41ca..6db5dd5 100644 --- a/setup/llama-cpp.ts +++ b/setup/llama-cpp.ts @@ -55,14 +55,7 @@ export async function run(_args: string[]): Promise { } try { - const preferredJailName = `${RUNTIME_ID}-llamacpp`; - const legacyJailName = 'llamacpp'; - let jailName = explicitJailName || preferredJailName; - if (!explicitJailName) { - if (jailExists(legacyJailName) && !jailExists(preferredJailName)) { - jailName = legacyJailName; - } - } + const jailName = explicitJailName || `${RUNTIME_ID}-llamacpp`; const exists = jailExists(jailName); diff --git a/setup/ollama.ts b/setup/ollama.ts index 6082324..5d4c36c 100644 --- a/setup/ollama.ts +++ b/setup/ollama.ts @@ -55,14 +55,7 @@ export async function run(_args: string[]): Promise { } try { - const preferredJailName = `${RUNTIME_ID}-ollama`; - const legacyJailName = 'ollama'; - let jailName = explicitJailName || preferredJailName; - if (!explicitJailName) { - if (jailExists(legacyJailName) && !jailExists(preferredJailName)) { - jailName = legacyJailName; - } - } + const jailName = explicitJailName || `${RUNTIME_ID}-ollama`; const exists = jailExists(jailName); diff --git a/setup/preflight.ts b/setup/preflight.ts index 1c7668b..90e5def 100644 --- a/setup/preflight.ts +++ b/setup/preflight.ts @@ -471,11 +471,6 @@ function resolveCmsJailName(projectRoot: string): string { .map((line) => line.trim()) .filter(Boolean); if (names.includes('cms')) return 'cms'; - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); - const preferred = `${safeAgentName}cms`; - const legacyHyphen = `${TENANT_ID}-cms`; - if (names.includes(preferred)) return preferred; - if (names.includes(legacyHyphen)) return legacyHyphen; } catch { // Default to cms when jail discovery is unavailable. } diff --git a/setup/service.ts b/setup/service.ts index 6d0e1d6..6cec6cc 100644 --- a/setup/service.ts +++ b/setup/service.ts @@ -71,16 +71,6 @@ function buildProject(projectRoot: string): void { }); } -function chownIfSudoContext(filePath: string): void { - const sudo = getSudoContext(); - if (!isRoot() || sudo.uid === null || sudo.gid === null) return; - try { - fs.chownSync(filePath, sudo.uid, sudo.gid); - } catch { - // best-effort only - } -} - // Recursively chown a directory to the agent user. // Used after root creates runtime dirs so the agent process (which runs as the // named user via daemon -u) can write to them without EACCES on first startup. @@ -397,8 +387,6 @@ export async function run(_args: string[]): Promise { fs.mkdirSync(dirPath, { recursive: true }); chownRuntimeDir(dirPath, runtime.runtimeUser); } - // Legacy: also chown via SUDO_UID/SUDO_GID for the logs file itself (best-effort) - chownIfSudoContext(path.join(projectRoot, 'logs')); const pidFile = path.join(projectRoot, `${runtime.serviceName}.pid`); const logPath = path.join(projectRoot, 'logs', `${runtime.serviceName}.log`); @@ -415,7 +403,6 @@ export async function run(_args: string[]): Promise { runScriptPath, generateRunScript(runtime, nodePath, projectRoot), ); - chownIfSudoContext(runScriptPath); logger.info({ runScriptPath }, 'Wrote run wrapper'); writeWrapper(rcdPath, generateRcdService(runtime, projectRoot, logPath)); diff --git a/setup/skills-memory.ts b/setup/skills-memory.ts index 39674a9..6574384 100644 --- a/setup/skills-memory.ts +++ b/setup/skills-memory.ts @@ -14,7 +14,6 @@ import { SKILLS_DB_NAME, SKILLS_DB_URL, SKILLS_DB_USER, - TENANT_ID, } from '../src/config.js'; import { BUILTIN_KNOWLEDGE_ARTIFACT_SQL as ARTIFACT_SQL, @@ -175,15 +174,7 @@ function detectDbJailName(): string { ).trim(); if (explicit) return explicit; - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); - const preferred = `${safeAgentName}db`; - const legacyHyphen = `${TENANT_ID}-db`; - const legacy = 'db'; - - if (jailExists(preferred)) return preferred; - if (jailExists(legacyHyphen)) return legacyHyphen; - if (jailExists(legacy)) return legacy; - return preferred; + return 'db'; } function importArtifactViaBastille(jailName: string, dbName: string): void { diff --git a/setup/verify-agent-jails.ts b/setup/verify-agent-jails.ts index 68e2b62..d14754f 100644 --- a/setup/verify-agent-jails.ts +++ b/setup/verify-agent-jails.ts @@ -322,11 +322,7 @@ export async function run(_args: string[]): Promise { continue; } - const jailName = resolveJailName({ - agentName: TENANT_ID, - role: entry.role, - legacyNames: [entry.role], - }); + const jailName = resolveJailName({ role: entry.role }); logger.info({ specialist, jailName }, 'Verifying agent jail'); const result = verifyJail(specialist, jailName); diff --git a/setup/verify.ts b/setup/verify.ts index 7e38cfd..4f33920 100644 --- a/setup/verify.ts +++ b/setup/verify.ts @@ -357,11 +357,7 @@ export async function run(_args: string[]): Promise { // 7. Check host/jail package baseline and CMS runtime const rsyncTool = commandExists('rsync') ? 'available' : 'missing'; - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); - const preferredWorkerJailName = `${safeAgentName}worker`; - const legacyWorkerJailName = `${TENANT_ID}-worker`; - const legacyPlainWorkerJailName = 'worker'; - let workerJailName = preferredWorkerJailName; + let workerJailName = 'worker'; let workerJailPackages = 'unknown'; if (platform === 'freebsd') { try { @@ -371,26 +367,7 @@ export async function run(_args: string[]): Promise { }); const jailList = jails.split('\n').map((line) => line.trim()); if (/^(YES|yes|true|TRUE|1)$/u.test(cmsEnabled)) { - const safeAgentName = TENANT_ID.replace(/[-_]/g, ''); - const preferred = `${safeAgentName}cms`; - const legacyHyphen = `${TENANT_ID}-cms`; - const legacy = 'cms'; - if (!jailList.some((line) => line === cmsJailName)) { - if (jailList.some((line) => line === preferred)) { - cmsJailName = preferred; - } else if (jailList.some((line) => line === legacyHyphen)) { - cmsJailName = legacyHyphen; - } else if (jailList.some((line) => line === legacy)) { - cmsJailName = legacy; - } - } - } - if (jailList.some((line) => line === preferredWorkerJailName)) { - workerJailName = preferredWorkerJailName; - } else if (jailList.some((line) => line === legacyWorkerJailName)) { - workerJailName = legacyWorkerJailName; - } else if (jailList.some((line) => line === legacyPlainWorkerJailName)) { - workerJailName = legacyPlainWorkerJailName; + cmsJailName = 'cms'; } if (jailList.some((line) => line === workerJailName)) { diff --git a/src/agent-capabilities.test.ts b/src/agent-capabilities.test.ts index 07de83a..7772c9f 100644 --- a/src/agent-capabilities.test.ts +++ b/src/agent-capabilities.test.ts @@ -22,10 +22,10 @@ describe('checkAgentTaskCapability', () => { expect(result.ok).toBe(true); }); - it('normalizes prefixed jail names', () => { - expect(__TEST_ONLY.normalizeJailName('alpha_git_worker')).toBe('git-worker'); - expect(__TEST_ONLY.normalizeJailName('alpha_db_worker')).toBe('db-worker'); - expect(__TEST_ONLY.normalizeJailName('alpha_ctrl_worker')).toBe('ctrl-worker'); + it('maps tenant worker jail names to capability keys', () => { + expect(__TEST_ONLY.capabilityKeyForJail('alpha_git_worker')).toBe('git-worker'); + expect(__TEST_ONLY.capabilityKeyForJail('alpha_db_worker')).toBe('db-worker'); + expect(__TEST_ONLY.capabilityKeyForJail('alpha_ctrl_worker')).toBe('ctrl-worker'); }); it('refuses git-worker for git-push-upstream', () => { diff --git a/src/agent-capabilities.ts b/src/agent-capabilities.ts index 37e2c3f..49cb6ca 100644 --- a/src/agent-capabilities.ts +++ b/src/agent-capabilities.ts @@ -18,9 +18,7 @@ export interface CapabilityCheck { const OK: CapabilityCheck = { ok: true, missing: [], errorCode: null }; -// Runtime jail names are prefixed (for example "clawdie_git_worker"). Normalize -// them to stable role keys before capability lookup. -function normalizeJailName(jailName: string): string { +function capabilityKeyForJail(jailName: string): string { if (jailName.endsWith('_git_worker')) return 'git-worker'; if (jailName.endsWith('_db_worker')) return 'db-worker'; if (jailName.endsWith('_ctrl_worker')) return 'ctrl-worker'; @@ -46,19 +44,19 @@ export function checkAgentTaskCapability( if (!jailName || !skillName) return OK; const required = SKILL_REQUIRES[skillName]; if (!required || required.length === 0) return OK; - const normalizedJail = normalizeJailName(jailName); - const granted = JAIL_CAPABILITIES[normalizedJail] ?? []; + const capabilityKey = capabilityKeyForJail(jailName); + const granted = JAIL_CAPABILITIES[capabilityKey] ?? []; const missing = required.filter((capability) => !granted.includes(capability)); if (missing.length === 0) return OK; return { ok: false, missing: [...missing], - errorCode: `task_requires_${[...missing].sort().join('_')}_jail_${normalizedJail}_lacks`, + errorCode: `task_requires_${[...missing].sort().join('_')}_jail_${capabilityKey}_lacks`, }; } export const __TEST_ONLY = { JAIL_CAPABILITIES, SKILL_REQUIRES, - normalizeJailName, + capabilityKeyForJail, }; diff --git a/src/controlplane-db.ts b/src/controlplane-db.ts index e518eb5..0de4e9b 100644 --- a/src/controlplane-db.ts +++ b/src/controlplane-db.ts @@ -231,7 +231,6 @@ CREATE TABLE IF NOT EXISTS chat_spend ( export async function runSchemaMigration(pool: pg.Pool): Promise { await pool.query(CONTROLPLANE_SCHEMA_SQL); - await migrateLegacyAgentIds(pool); await ensureRoleConstraint(pool); await ensureApprovalDecisionColumn(pool); await ensureParentTaskId(pool); @@ -256,78 +255,6 @@ export async function runSchemaMigration(pool: pg.Pool): Promise { } } -async function migrateLegacyAgentIds(pool: pg.Pool): Promise { - // Aggressive dev policy: canonical agent IDs are: - // sysadmin, db-admin, git-admin, coordinator, plus orchestrator = TENANT_ID. - // Migrate old *_agent IDs in-place once (idempotent). - const legacyToCanonical: Array<[string, string]> = [ - ['sysadmin_agent', 'sysadmin'], - ['db_admin_agent', 'db-admin'], - ['git_admin_agent', 'git-admin'], - ]; - - const legacyIds = legacyToCanonical.map(([legacy]) => legacy); - const { rows } = await pool.query( - `SELECT id FROM agents WHERE id = ANY($1)`, - [legacyIds], - ); - if (rows.length === 0) return; - - await pool.query('BEGIN'); - try { - await pool.query( - 'ALTER TABLE agents DROP CONSTRAINT IF EXISTS agents_role_check', - ); - for (const table of [ - 'tasks', - 'agent_activity', - 'agent_budgets', - 'approvals', - ]) { - await pool.query( - `ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_${table === 'tasks' ? 'assigned_to' : 'agent_id'}_fkey`, - ); - } - - for (const [legacy, canonical] of legacyToCanonical) { - await pool.query( - 'UPDATE tasks SET assigned_to = $1 WHERE assigned_to = $2', - [canonical, legacy], - ); - await pool.query( - 'UPDATE agent_activity SET agent_id = $1 WHERE agent_id = $2', - [canonical, legacy], - ); - await pool.query( - 'UPDATE agent_budgets SET agent_id = $1 WHERE agent_id = $2', - [canonical, legacy], - ); - await pool.query( - 'UPDATE approvals SET agent_id = $1 WHERE agent_id = $2', - [canonical, legacy], - ); - - const canonicalExists = await pool.query( - 'SELECT 1 FROM agents WHERE id = $1 LIMIT 1', - [canonical], - ); - if (canonicalExists.rows.length > 0) { - await pool.query('DELETE FROM agents WHERE id = $1', [legacy]); - } else { - await pool.query('UPDATE agents SET id = $1, role = $1 WHERE id = $2', [ - canonical, - legacy, - ]); - } - } - - await pool.query('COMMIT'); - } catch (err) { - await pool.query('ROLLBACK'); - throw err; - } -} - async function ensureRoleConstraint(pool: pg.Pool): Promise { await pool.query( 'ALTER TABLE agents DROP CONSTRAINT IF EXISTS agents_role_check',