From 872dd664ede82700c3223286de3ea99070871981 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Mon, 11 May 2026 19:38:56 -0400 Subject: [PATCH] revert(secrets): drop fork's usages-tracking + delete guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that upstream's #5429 provides provider-based secrets management with formal bindings (companySecretBindings table populated at config-save time) plus the /secrets/:id/usage endpoint backed by listBindingReferences(), the fork's parallel usages() scan is redundant for agent/routine bindings. The fork's scan did cover one path upstream doesn't track: skill metadata sourceAuthSecretId references. Dropping this means accidental deletion of a skill's auth secret is no longer rejected — accepted as a chase-upstream tradeoff. - server/src/services/secrets.ts: drop usages(), SecretUsage* types, in-use guard in remove(), and companySkills/agentService imports - server/src/routes/secrets.ts: drop GET /secrets/:id/usages route - ui/src/api/secrets.ts: drop usages() client method Typechecks clean on server and ui. --- server/src/routes/secrets.ts | 13 -------- server/src/services/secrets.ts | 60 ---------------------------------- ui/src/api/secrets.ts | 5 --- 3 files changed, 78 deletions(-) diff --git a/server/src/routes/secrets.ts b/server/src/routes/secrets.ts index 35c6a48b..be9d503f 100644 --- a/server/src/routes/secrets.ts +++ b/server/src/routes/secrets.ts @@ -419,19 +419,6 @@ export function secretRoutes(db: Db) { res.json(updated); }); - router.get("/secrets/:id/usages", async (req, res) => { - assertBoard(req); - const id = req.params.id as string; - const existing = await svc.getById(id); - if (!existing) { - res.status(404).json({ error: "Secret not found" }); - return; - } - assertCompanyAccess(req, existing.companyId); - const used = await svc.usages(existing.companyId, id); - res.json({ agents: used.agents, skills: used.skills }); - }); - router.get("/secrets/:id/usage", async (req, res) => { assertBoard(req); const id = req.params.id as string; diff --git a/server/src/services/secrets.ts b/server/src/services/secrets.ts index 1f6629a3..17f13cef 100644 --- a/server/src/services/secrets.ts +++ b/server/src/services/secrets.ts @@ -6,7 +6,6 @@ import { companySecretProviderConfigs, companySecrets, companySecretVersions, - companySkills, environments, heartbeatRuns, issues, @@ -53,7 +52,6 @@ import type { SecretProviderWriteContext, } from "../secrets/types.js"; import { isSecretProviderClientError } from "../secrets/types.js"; -import { agentService } from "./agents.js"; const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/; const SENSITIVE_ENV_KEY_RE = @@ -261,10 +259,6 @@ export function secretService(db: Db) { fieldPath?: string; }; - type SecretUsageAgent = { id: string; name: string; envKeys: string[] }; - type SecretUsageSkill = { id: string; name: string; slug: string }; - type SecretUsages = { agents: SecretUsageAgent[]; skills: SecretUsageSkill[] }; - async function getById(id: string) { return db .select() @@ -273,48 +267,6 @@ export function secretService(db: Db) { .then((rows) => rows[0] ?? null); } - async function usages(companyId: string, secretId: string): Promise { - const agents = agentService(db); - const allAgents = await agents.list(companyId); - const agentRefs: SecretUsageAgent[] = []; - for (const agent of allAgents) { - const config = asRecord(agent.adapterConfig); - if (!config) continue; - const env = asRecord(config.env); - if (!env) continue; - const matchingKeys: string[] = []; - for (const [key, rawBinding] of Object.entries(env)) { - const binding = asRecord(rawBinding); - if (!binding) continue; - if (binding.type === "secret_ref" && binding.secretId === secretId) { - matchingKeys.push(key); - } - } - if (matchingKeys.length > 0) { - agentRefs.push({ id: agent.id, name: agent.name, envKeys: matchingKeys }); - } - } - - const skillRows = await db - .select({ - id: companySkills.id, - name: companySkills.name, - slug: companySkills.slug, - }) - .from(companySkills) - .where(and( - eq(companySkills.companyId, companyId), - sql`${companySkills.metadata} ->> 'sourceAuthSecretId' = ${secretId}`, - )); - const skillRefs: SecretUsageSkill[] = skillRows.map((row) => ({ - id: row.id, - name: row.name, - slug: row.slug, - })); - - return { agents: agentRefs, skills: skillRefs }; - } - async function getByName(companyId: string, name: string) { return db .select() @@ -2083,16 +2035,6 @@ export function secretService(db: Db) { remove: async (secretId: string) => { const secret = await getById(secretId); if (!secret) return null; - const used = await usages(secret.companyId, secretId); - if (used.agents.length > 0 || used.skills.length > 0) { - const agentNames = used.agents.map((agent) => agent.name); - const skillNames = used.skills.map((skill) => skill.name); - const names = [...agentNames, ...skillNames].sort((left, right) => left.localeCompare(right)); - throw unprocessable( - `Cannot delete secret "${secret.name}" while it is still used by ${names.join(", ")}. Detach it from those references first.`, - { secretId, usedByAgents: used.agents, usedBySkills: used.skills }, - ); - } const versionRow = await getSecretVersion(secret.id, secret.latestVersion); const providerId = secret.provider as SecretProvider; const provider = getSecretProvider(providerId); @@ -2139,8 +2081,6 @@ export function secretService(db: Db) { return secret; }, - usages, - normalizeAdapterConfigForPersistence: async ( companyId: string, adapterConfig: Record, diff --git a/ui/src/api/secrets.ts b/ui/src/api/secrets.ts index f2fea34d..aedfa85d 100644 --- a/ui/src/api/secrets.ts +++ b/ui/src/api/secrets.ts @@ -126,11 +126,6 @@ export const secretsApi = { archive: (id: string) => api.patch(`/secrets/${id}`, { status: "archived" satisfies SecretStatus }), remove: (id: string) => api.delete<{ ok: true }>(`/secrets/${id}`), - usages: (id: string) => - api.get<{ - agents: { id: string; name: string; envKeys: string[] }[]; - skills: { id: string; name: string; slug: string }[]; - }>(`/secrets/${id}/usages`), usage: (id: string) => api.get(`/secrets/${id}/usage`), accessEvents: (id: string) => api.get(`/secrets/${id}/access-events`), remoteImportPreview: (companyId: string, data: RemoteImportPreviewInput) =>