Skip to content

Commit 704e1b6

Browse files
scotty595claude
andcommitted
Add deleteAgent to GovernanceStorage and bump to 0.7.2
Adds deleteAgent(id) to the storage interface, with implementations for memory and PostgreSQL adapters. Audit events are preserved. Tests cover successful delete, isolation between agents, audit preservation, and not-found errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5fc1096 commit 704e1b6

5 files changed

Lines changed: 91 additions & 3 deletions

File tree

packages/governance/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "governance-sdk",
3-
"version": "0.7.1",
3+
"version": "0.7.2",
44
"description": "AI Agent Governance for TypeScript — policy enforcement, scoring, compliance, and audit for AI agents",
55
"type": "module",
66
"main": "./dist/index.js",

packages/governance/src/storage-postgres.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ function createMockPool(): PgPoolLike & { queries: string[] } {
2121
return { rows: [], rowCount: 0 };
2222
}
2323

24-
// Parse table name from INSERT/SELECT/UPDATE
24+
// Parse table name from INSERT/SELECT/UPDATE/DELETE
2525
const insertMatch = text.match(/INSERT INTO (\S+)/);
2626
const selectMatch = text.match(/SELECT .* FROM (\S+)/);
2727
const updateMatch = text.match(/UPDATE (\S+)/);
28+
const deleteMatch = text.match(/DELETE FROM (\S+)/);
2829

2930
if (insertMatch) {
3031
const table = insertMatch[1];
@@ -105,6 +106,19 @@ function createMockPool(): PgPoolLike & { queries: string[] } {
105106
return { rows: [row], rowCount: 1 };
106107
}
107108

109+
if (deleteMatch) {
110+
const table = deleteMatch[1];
111+
const rows = tables.get(table) ?? [];
112+
const before = rows.length;
113+
const filtered = applyWhere(rows, text, values ?? []);
114+
// Remove matched rows in place
115+
for (const row of filtered) {
116+
const idx = rows.indexOf(row);
117+
if (idx >= 0) rows.splice(idx, 1);
118+
}
119+
return { rows: [], rowCount: before - rows.length };
120+
}
121+
108122
return { rows: [], rowCount: 0 };
109123
},
110124
async end() {
@@ -258,6 +272,34 @@ describe("PostgreSQL Storage Adapter", () => {
258272
assert.ok(updated);
259273
});
260274

275+
test("deleteAgent removes agent and throws if not found", async () => {
276+
const pool = createMockPool();
277+
const storage = await createPostgresStorage({ pool });
278+
279+
await storage.createAgent({
280+
id: "del-1",
281+
name: "doomed",
282+
framework: "mastra",
283+
owner: "team",
284+
version: "1.0.0",
285+
channels: [],
286+
tools: [],
287+
compositeScore: 50,
288+
governanceLevel: 2,
289+
status: "registered",
290+
registeredAt: new Date().toISOString(),
291+
updatedAt: new Date().toISOString(),
292+
});
293+
294+
await storage.deleteAgent("del-1");
295+
assert.equal(await storage.getAgent("del-1"), null);
296+
297+
await assert.rejects(
298+
() => storage.deleteAgent("del-1"),
299+
{ message: "Agent del-1 not found" },
300+
);
301+
});
302+
261303
test("createAuditEvent and queryAuditEvents work", async () => {
262304
const pool = createMockPool();
263305
const storage = await createPostgresStorage({ pool });

packages/governance/src/storage-postgres.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ export async function createPostgresStorage(
154154
return rowToAgent(result.rows[0]);
155155
}
156156

157+
async function deleteAgent(id: string): Promise<void> {
158+
await ensureMigrated();
159+
const result = await pool.query(`DELETE FROM ${prefix}_agents WHERE id = $1`, [id]);
160+
if (result.rowCount === 0) throw new Error(`Agent ${id} not found`);
161+
}
162+
157163
async function createAuditEvent(event: AuditEvent): Promise<AuditEvent> {
158164
await ensureMigrated();
159165
await pool.query(
@@ -188,7 +194,7 @@ export async function createPostgresStorage(
188194

189195
if (autoMigrate) await migrate();
190196

191-
return { createAgent, getAgent, getAgentByName, listAgents, updateAgent, createAuditEvent, queryAuditEvents, countAuditEvents, migrate, close: () => pool.end() };
197+
return { createAgent, getAgent, getAgentByName, listAgents, updateAgent, deleteAgent, createAuditEvent, queryAuditEvents, countAuditEvents, migrate, close: () => pool.end() };
192198
}
193199

194200
function buildAuditWhere(filters: AuditQueryFilters): { clauses: string[]; values: unknown[]; paramIdx: number } {

packages/governance/src/storage.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,40 @@ describe("createMemoryStorage", () => {
9191
);
9292
});
9393

94+
test("deletes an agent", async () => {
95+
const storage = createMemoryStorage();
96+
await storage.createAgent(makeAgent({ id: "del-1" }));
97+
await storage.deleteAgent("del-1");
98+
assert.equal(await storage.getAgent("del-1"), null);
99+
});
100+
101+
test("delete preserves other agents", async () => {
102+
const storage = createMemoryStorage();
103+
await storage.createAgent(makeAgent({ id: "keep" }));
104+
await storage.createAgent(makeAgent({ id: "drop" }));
105+
await storage.deleteAgent("drop");
106+
const remaining = await storage.listAgents();
107+
assert.equal(remaining.length, 1);
108+
assert.equal(remaining[0].id, "keep");
109+
});
110+
111+
test("delete preserves audit events", async () => {
112+
const storage = createMemoryStorage();
113+
await storage.createAgent(makeAgent({ id: "a-with-events" }));
114+
await storage.createAuditEvent(makeEvent({ agentId: "a-with-events" }));
115+
await storage.deleteAgent("a-with-events");
116+
const events = await storage.queryAuditEvents({ agentId: "a-with-events" });
117+
assert.equal(events.length, 1);
118+
});
119+
120+
test("throws when deleting nonexistent agent", async () => {
121+
const storage = createMemoryStorage();
122+
await assert.rejects(
123+
() => storage.deleteAgent("nope"),
124+
{ message: "Agent nope not found" },
125+
);
126+
});
127+
94128
test("creates and counts audit events", async () => {
95129
const storage = createMemoryStorage();
96130
await storage.createAuditEvent(makeEvent());

packages/governance/src/storage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface GovernanceStorage {
1414
getAgentByName(name: string, owner: string): Promise<StoredAgent | null>;
1515
listAgents(organizationId?: string): Promise<StoredAgent[]>;
1616
updateAgent(id: string, data: Partial<StoredAgent>): Promise<StoredAgent>;
17+
/** Delete an agent by id. Audit events are preserved. Throws if not found. */
18+
deleteAgent(id: string): Promise<void>;
1719
createAuditEvent(event: AuditEvent): Promise<AuditEvent>;
1820
queryAuditEvents(filters: AuditQueryFilters): Promise<AuditEvent[]>;
1921
countAuditEvents(filters?: AuditQueryFilters): Promise<number>;
@@ -112,6 +114,10 @@ export function createMemoryStorage(): GovernanceStorage {
112114
agents.set(id, updated);
113115
return updated;
114116
},
117+
async deleteAgent(id) {
118+
if (!agents.has(id)) throw new Error(`Agent ${id} not found`);
119+
agents.delete(id);
120+
},
115121
async createAuditEvent(event) {
116122
events.push(event);
117123
// Evict oldest events when exceeding capacity

0 commit comments

Comments
 (0)