Drop stale jail and agent migration paths (Codex)
Remove completed controlplane agent-id migration, simplify jail-name resolution to current canonical names, and drop SUDO_UID ownership fallback from service setup. --- Build: pass | Tests: pass — 2370 passed (704 files)
This commit is contained in:
parent
c136418c5a
commit
f1dc7ea6df
16 changed files with 37 additions and 236 deletions
|
|
@ -198,11 +198,7 @@ export async function run(args: string[]): Promise<void> {
|
|||
}
|
||||
|
||||
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}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<ProvisionResult> {
|
||||
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);
|
||||
|
|
|
|||
13
setup/cms.ts
13
setup/cms.ts
|
|
@ -916,25 +916,14 @@ export async function run(_args: string[]): Promise<void> {
|
|||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
''
|
||||
).trim();
|
||||
const jailName = resolveJailName({
|
||||
agentName: TENANT_ID,
|
||||
role: 'db',
|
||||
envOverride: explicitJailName || undefined,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
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 ||
|
||||
|
|
|
|||
|
|
@ -55,14 +55,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,14 +55,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
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<void> {
|
|||
runScriptPath,
|
||||
generateRunScript(runtime, nodePath, projectRoot),
|
||||
);
|
||||
chownIfSudoContext(runScriptPath);
|
||||
logger.info({ runScriptPath }, 'Wrote run wrapper');
|
||||
|
||||
writeWrapper(rcdPath, generateRcdService(runtime, projectRoot, logPath));
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -322,11 +322,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -357,11 +357,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||
|
||||
// 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<void> {
|
|||
});
|
||||
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)) {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -231,7 +231,6 @@ CREATE TABLE IF NOT EXISTS chat_spend (
|
|||
|
||||
export async function runSchemaMigration(pool: pg.Pool): Promise<void> {
|
||||
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<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function migrateLegacyAgentIds(pool: pg.Pool): Promise<void> {
|
||||
// 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<void> {
|
||||
await pool.query(
|
||||
'ALTER TABLE agents DROP CONSTRAINT IF EXISTS agents_role_check',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue