Skip to content

refactor: added new logic for soft-delete#265

Merged
Polliog merged 8 commits into
logtide-dev:developfrom
Justy116:feat/soft-delete-projects
Jun 27, 2026
Merged

refactor: added new logic for soft-delete#265
Polliog merged 8 commits into
logtide-dev:developfrom
Justy116:feat/soft-delete-projects

Conversation

@Justy116

@Justy116 Justy116 commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Tenant safety

LogTide is multi-tenant. Confirm the following for any new/changed query, endpoint,
or background job (see docs/security/tenant-isolation-audit.md):

  • Tenant tables are filtered by organization_id (and project_id where relevant).
  • Joins enforce scoping at every level, not just the outer query.
  • Updates/deletes verify scope before executing, not just trusting the filter to match.
  • Cache keys include the organization id.
  • Background jobs carry the org id and the consumer re-validates it.
  • Ids from a URL parameter or request body are verified to belong to the requesting tenant before use.
  • New data-access paths are added to the audit doc.
  • npm run check:tenant-scoping passes (run from packages/backend).

Testing

@Polliog Polliog changed the base branch from main to develop June 26, 2026 14:23
@Polliog Polliog linked an issue Jun 26, 2026 that may be closed by this pull request
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

@Polliog Polliog left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: soft-delete projects

Solid approach overall. The service queries are consistently re-scoped with deleted_at IS NULL, the soft-delete/restore updates are atomic and guarded, and dropping ON DELETE CASCADE in favor of the controlled purge path is the right call. No cross-tenant scoping issues found. A few things should be addressed before merge.

Changes required

1. verifyApiKey does not filter deleted_at — a "deleted" project keeps accepting ingestion for 30 days
packages/backend/src/modules/api-keys/service.ts (~L118): the api_keys -> projects join in the hot auth path has no deleted_at IS NULL filter. An API key for a soft-deleted project stays fully valid, so logs/spans/metrics keep flowing for the whole grace window, accrue against storage/metering, and then get purged. Deleting a project no longer stops writes.

  • Add .where('projects.deleted_at', 'is', null) to the verify query.
  • Invalidate the CacheManager.apiKeyKey(...) entries on soft-delete. The cached verification survives until TTL, so even after hard-delete a cached key resolves to a now-removed project_id and ingestion inserts hit an FK violation for a few seconds.

2. Control-plane callers don't check deletedAt even though getProjectById now returns soft-deleted rows
getProjectById intentionally returns deleted projects (per the doc comment), but the api-keys routes (packages/backend/src/modules/api-keys/routes.ts L41/L68/L125) only check if (!project). As a result you can list/create/revoke API keys on a trashed project. Add an explicit deletedAt guard at these call sites (or expose a getActiveProjectById helper for callers that want active-only).

3. Purge worker can leave a project stuck un-purged on TimescaleDB
packages/backend/src/worker.ts (~L643) + migration 051: with CASCADE removed, the FK becomes RESTRICT. The worker runs reservoir.purgeProject() and then db.deleteFrom('projects'). If deleteByTimeRange doesn't remove every row (compressed TimescaleDB chunks that a plain DELETE can't touch, or rows outside the [1970, 2100) window), the projects delete fails the FK constraint, gets swallowed by the catch, and the project stays soft-deleted forever with only a console.error. Please confirm deleteByTimeRange decompresses / uses the same chunk strategy as retention cleanup, and consider surfacing an alert when a project repeatedly fails to purge past its grace window.

Minor (non-blocking)

  • packages/backend/src/modules/projects/service.ts (~L368): orderBy('deleted_at', 'asc') with the comment "active (null) first" is wrong. Postgres orders NULLS LAST for ASC, so active projects sort last. No practical impact (the frontend re-splits by deletedAt), but either use asc nulls first or fix the comment.
  • The deletedAt check in the /restore route (routes.ts ~L165) is duplicated inside restoreProject — harmless redundancy.

@Justy116 Justy116 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check polliog

@CLAassistant

CLAassistant commented Jun 27, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Polliog
❌ Giustino Gragnaniello


Giustino Gragnaniello seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@Polliog Polliog merged commit c699a7c into logtide-dev:develop Jun 27, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Soft-delete projects to preserve historical logs

3 participants