Skip to content

Commit e5dfe05

Browse files
refactor(logs): drop per-workspace retention override; PII redaction stays org-scoped
- Remove the unused per-workspace data-retention-hours override (no UI; superseded by workspace-scoped PII rules). Reverts cleanup-dispatcher to org-only retention, drops resolveEffectiveRetentionHours, the workspace.dataRetentionSettings column + migration, and the workspace data-retention route/contract/hooks. Fixes Bugbot's null-as-unset finding by removing the buggy path entirely; org retention behavior is unchanged. - Stop re-checking isWorkspaceOnEnterprisePlan at persist time (it returns false on transient errors, which would fail-open and leak PII). Enabled rules already imply entitlement; redact whenever rules apply (fail-safe).
1 parent 1635ce1 commit e5dfe05

11 files changed

Lines changed: 13 additions & 17106 deletions

File tree

apps/sim/app/api/workspaces/[id]/data-retention/route.ts

Lines changed: 0 additions & 200 deletions
This file was deleted.

apps/sim/ee/data-retention/hooks/data-retention.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,13 @@ import {
99
type UpdateOrganizationDataRetentionBody,
1010
updateOrganizationDataRetentionContract,
1111
} from '@/lib/api/contracts/organization'
12-
import {
13-
getWorkspaceDataRetentionContract,
14-
type UpdateWorkspaceDataRetentionBody,
15-
updateWorkspaceDataRetentionContract,
16-
type WorkspaceDataRetention,
17-
} from '@/lib/api/contracts/workspaces'
1812

1913
export type RetentionValues = OrganizationRetentionValues
2014
export type DataRetentionResponse = OrganizationDataRetention
21-
export type WorkspaceDataRetentionResponse = WorkspaceDataRetention
2215

2316
export const dataRetentionKeys = {
2417
all: ['dataRetention'] as const,
2518
settings: (orgId: string) => [...dataRetentionKeys.all, 'settings', orgId] as const,
26-
workspaceSettings: (workspaceId: string) =>
27-
[...dataRetentionKeys.all, 'workspace', workspaceId] as const,
2819
}
2920

3021
async function fetchDataRetention(
@@ -66,45 +57,3 @@ export function useUpdateOrganizationRetention() {
6657
},
6758
})
6859
}
69-
70-
async function fetchWorkspaceDataRetention(
71-
workspaceId: string,
72-
signal?: AbortSignal
73-
): Promise<WorkspaceDataRetentionResponse> {
74-
const { data } = await requestJson(getWorkspaceDataRetentionContract, {
75-
params: { id: workspaceId },
76-
signal,
77-
})
78-
return data
79-
}
80-
81-
export function useWorkspaceRetention(workspaceId: string | undefined) {
82-
return useQuery({
83-
queryKey: dataRetentionKeys.workspaceSettings(workspaceId ?? ''),
84-
queryFn: ({ signal }) => fetchWorkspaceDataRetention(workspaceId as string, signal),
85-
enabled: Boolean(workspaceId),
86-
staleTime: 60 * 1000,
87-
})
88-
}
89-
90-
interface UpdateWorkspaceRetentionVariables {
91-
workspaceId: string
92-
settings: UpdateWorkspaceDataRetentionBody
93-
}
94-
95-
export function useUpdateWorkspaceRetention() {
96-
const queryClient = useQueryClient()
97-
98-
return useMutation({
99-
mutationFn: ({ workspaceId, settings }: UpdateWorkspaceRetentionVariables) =>
100-
requestJson(updateWorkspaceDataRetentionContract, {
101-
params: { id: workspaceId },
102-
body: settings,
103-
}),
104-
onSettled: (_data, _error, { workspaceId }) => {
105-
queryClient.invalidateQueries({
106-
queryKey: dataRetentionKeys.workspaceSettings(workspaceId),
107-
})
108-
},
109-
})
110-
}

apps/sim/lib/api/contracts/workspaces.ts

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -272,65 +272,3 @@ export const getWorkspaceMembersContract = defineRouteContract({
272272
})
273273

274274
export type WorkspacesResponse = ContractJsonResponse<typeof listWorkspacesContract>
275-
276-
const workspaceRetentionHoursSchema = z.number().int().min(24).max(43800).nullable().optional()
277-
278-
/**
279-
* Per-workspace override of the org-level data retention settings. Any omitted
280-
* key falls back to the org default at resolution time, so a workspace need only
281-
* send the keys it wants to override.
282-
*/
283-
export const updateWorkspaceDataRetentionBodySchema = z.object({
284-
logRetentionHours: workspaceRetentionHoursSchema,
285-
softDeleteRetentionHours: workspaceRetentionHoursSchema,
286-
taskCleanupHours: workspaceRetentionHoursSchema,
287-
})
288-
289-
const workspaceRetentionValuesSchema = z.object({
290-
logRetentionHours: z.number().int().nullable(),
291-
softDeleteRetentionHours: z.number().int().nullable(),
292-
taskCleanupHours: z.number().int().nullable(),
293-
})
294-
295-
const workspaceDataRetentionDataSchema = z.object({
296-
isEnterprise: z.boolean(),
297-
defaults: workspaceRetentionValuesSchema,
298-
/** The org-level default this workspace inherits from when unset. */
299-
orgConfigured: workspaceRetentionValuesSchema,
300-
/** This workspace's own override (nulls where it defers to the org). */
301-
workspaceConfigured: workspaceRetentionValuesSchema,
302-
/** Resolved per key: workspace override ?? org default ?? plan default. */
303-
effective: workspaceRetentionValuesSchema,
304-
})
305-
306-
export type WorkspaceDataRetention = z.output<typeof workspaceDataRetentionDataSchema>
307-
308-
export const workspaceDataRetentionResponseSchema = z.object({
309-
success: z.boolean(),
310-
data: workspaceDataRetentionDataSchema,
311-
})
312-
313-
export const getWorkspaceDataRetentionContract = defineRouteContract({
314-
method: 'GET',
315-
path: '/api/workspaces/[id]/data-retention',
316-
params: workspaceParamsSchema,
317-
response: {
318-
mode: 'json',
319-
schema: workspaceDataRetentionResponseSchema,
320-
},
321-
})
322-
323-
export const updateWorkspaceDataRetentionContract = defineRouteContract({
324-
method: 'PUT',
325-
path: '/api/workspaces/[id]/data-retention',
326-
params: workspaceParamsSchema,
327-
body: updateWorkspaceDataRetentionBodySchema,
328-
response: {
329-
mode: 'json',
330-
schema: workspaceDataRetentionResponseSchema,
331-
},
332-
})
333-
334-
export type UpdateWorkspaceDataRetentionBody = z.input<
335-
typeof updateWorkspaceDataRetentionBodySchema
336-
>

apps/sim/lib/billing/cleanup-dispatcher.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { db } from '@sim/db'
2-
import type { DataRetentionSettings, WorkspaceMode } from '@sim/db/schema'
2+
import type { WorkspaceMode } from '@sim/db/schema'
33
import { organization, workspace } from '@sim/db/schema'
44
import { createLogger } from '@sim/logger'
55
import { tasks } from '@trigger.dev/sdk'
66
import { and, asc, eq, gt, isNull } from 'drizzle-orm'
77
import { getOrganizationSubscription } from '@/lib/billing/core/billing'
88
import { getHighestPriorityPersonalSubscription } from '@/lib/billing/core/subscription'
99
import { getPlanType, type PlanCategory } from '@/lib/billing/plan-helpers'
10-
import { resolveEffectiveRetentionHours } from '@/lib/billing/retention'
1110
import { chunkArray } from '@/lib/cleanup/batch-delete'
1211
import { getJobQueue } from '@/lib/core/async-jobs'
1312
import { shouldExecuteInline } from '@/lib/core/async-jobs/config'
@@ -59,7 +58,6 @@ interface WorkspaceCleanupScopeRow {
5958
organizationId: string | null
6059
workspaceMode: WorkspaceMode
6160
organizationSettings: OrganizationRetentionSettings | null
62-
workspaceSettings: DataRetentionSettings | null
6361
}
6462

6563
const DAY = 24
@@ -101,7 +99,6 @@ async function listActiveWorkspaceCleanupScopeRowsPage(
10199
organizationId: workspace.organizationId,
102100
workspaceMode: workspace.workspaceMode,
103101
organizationSettings: organization.dataRetentionSettings,
104-
workspaceSettings: workspace.dataRetentionSettings,
105102
})
106103
.from(workspace)
107104
.leftJoin(organization, eq(organization.id, workspace.organizationId))
@@ -117,7 +114,6 @@ async function listActiveWorkspaceCleanupScopeRowsPage(
117114
...row,
118115
organizationSettings:
119116
(row.organizationSettings as OrganizationRetentionSettings | null) ?? null,
120-
workspaceSettings: (row.workspaceSettings as DataRetentionSettings | null) ?? null,
121117
}))
122118
}
123119

@@ -269,12 +265,7 @@ async function forEachCleanupChunk(
269265

270266
for (const row of rows) {
271267
if (planByWorkspaceId.get(row.id) !== 'enterprise') continue
272-
const hours = resolveEffectiveRetentionHours({
273-
workspaceSettings: row.workspaceSettings,
274-
orgSettings: row.organizationSettings,
275-
key: config.key,
276-
fallback: config.defaults.enterprise,
277-
})
268+
const hours = row.organizationSettings?.[config.key]
278269
if (hours == null) continue
279270
workspaceCount++
280271
await emitChunk({

0 commit comments

Comments
 (0)