revert(secrets): drop fork's usages-tracking + delete guard

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.
This commit is contained in:
2026-05-11 19:38:56 -04:00
parent d9d9bbcf06
commit 872dd664ed
3 changed files with 0 additions and 78 deletions
-13
View File
@@ -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;
-60
View File
@@ -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<SecretUsages> {
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<string, unknown>,
-5
View File
@@ -126,11 +126,6 @@ export const secretsApi = {
archive: (id: string) =>
api.patch<CompanySecret>(`/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<SecretUsageResponse>(`/secrets/${id}/usage`),
accessEvents: (id: string) => api.get<SecretAccessEvent[]>(`/secrets/${id}/access-events`),
remoteImportPreview: (companyId: string, data: RemoteImportPreviewInput) =>