Skip to content

fix(account-erasure): track ClickHouse part lineage - #2366

Merged
Asherlc merged 31 commits into
mainfrom
codex/issue-1994
Aug 3, 2026
Merged

Asherlc merged 31 commits into
mainfrom
codex/issue-1994

Conversation

@Asherlc

@Asherlc Asherlc commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • complete the approved durable account-erasure implementation and close its transactional authorization type gap
  • track only predicate-matching ClickHouse parts plus MergeParts and MutatePart lineage instead of every inactive part
  • add unit coverage and document the physical-erasure proof invariant

Validation

  • focused account-erasure unit suites: 15 passed
  • root, server, web, and mobile TypeScript checks passed
  • pnpm lint:sandbox passed
  • full unit/mobile run reached 14,842 passing tests but reported one existing Vitest worker timeout in the Compose-environment test while the local Docker daemon was unresponsive
  • the ClickHouse integration regression was previously reproduced with completed mutations and unrelated inactive parts; the post-fix rerun is blocked locally because docker info and the repository Compose wrapper do not get a Docker daemon response

Fixes #1994


Summary by cubic

Implements durable account erasure end to end with instant session revocation, write fences, a public bearer‑status page, and an immutable R2 ledger, and tightens the ClickHouse proof to predicate‑matching parts and lineage. Adds a device erasure cutoff on mobile with full on‑device purge and guards deleted Slack rows; closes #1994.

  • New Features

    • Server/CI: Outbox‑driven erasure with public bearer status; deploy env validation and least‑privilege per‑service dotenvs; external apple_private_key; e2e MinIO ledger; Cypress accept/revoke/status flow; workflow_dispatch diff sharding; CI coverage for ClickHouse proof and malformed restore‑ledger metadata.
    • ClickHouse: Proof limits erased data to predicate‑matching parts and MergeParts/MutatePart lineage to avoid unrelated inactive parts.
    • Mobile: Adds AccountErasurePanel and AccountDeletionStatus screens; enforces a device erasure cutoff that gates and purges BLE HR, Core Motion, HealthKit, Watch, and WHOOP data; disables Watch sync until a new account enables it; introduces an export cache with unique filenames and purges it, scheduled notifications, and pending billing checkouts on erasure.
    • Misc: Guard deleted Slack rows during cleanup.
  • Migration

    • Apply drizzle/0062_account_erasure.sql.
    • Provision Cloudflare R2 dofek-account-erasure-ledger with an indefinite lock.
    • deploy/stack.yml reads per‑service env files (e.g., WEB_ENV_FILE) and an external apple_private_key Docker secret; CI validates the control‑plane export and renders service‑scoped env files.

Written for commit 83a99d3. Summary will update on new commits.

Review in cubic

Asherlc added 5 commits July 27, 2026 10:17
Require worker stream prerequisites, refresh erasure fences after migrations, fail loudly when provider revocation cannot be verified, and keep R2 as the sole immutable completion ledger.\n\nRefs #1994
Avoid treating unrelated inactive parts as erased account data when proving MergeTree cleanup through mutation and merge history.

Fixes #1994
Copilot AI review requested due to automatic review settings August 1, 2026 18:03
@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 300 files, and this pull request has 462

@greptile-apps

greptile-apps Bot commented Aug 1, 2026

Copy link
Copy Markdown

Too many files changed for review. (469 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 468 files, which is 368 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dae5a57d-4fa8-4ec0-b538-b1e1eeb7f739

📥 Commits

Reviewing files that changed from the base of the PR and between 2b7ad5a and 83a99d3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (468)
  • .github/workflows/deploy-web-stack.yml
  • .github/workflows/test.yml
  • cspell.json
  • cypress.config.ts
  • cypress/e2e/account-erasure.cy.ts
  • deploy/README.md
  • deploy/stack.yml
  • deploy/storage.tf
  • docker-compose.e2e.yml
  • docker-compose.peerdb.yml
  • docs/README.md
  • docs/account-erasure-runbook.md
  • docs/production-incident-baseline.md
  • docs/schema.dbml
  • docs/schema.puml
  • docs/superpowers/plans/2026-07-26-durable-account-erasure.md
  • docs/testing.md
  • drizzle/0062_account_erasure.sql
  • drizzle/meta/_journal.json
  • package.json
  • packages/auth/package.json
  • packages/auth/src/account-erasure.test.ts
  • packages/auth/src/account-erasure.ts
  • packages/mobile/app/_layout.cleanup.test.tsx
  • packages/mobile/app/_layout.telemetry-guard.test.ts
  • packages/mobile/app/_layout.tsx
  • packages/mobile/app/account-deletion.tsx
  • packages/mobile/app/settings.test.tsx
  • packages/mobile/app/settings.tsx
  • packages/mobile/components/AccountDeletionStatusScreen.stories.tsx
  • packages/mobile/components/AccountDeletionStatusScreen.test.tsx
  • packages/mobile/components/AccountDeletionStatusScreen.tsx
  • packages/mobile/components/AccountErasurePanel.test.tsx
  • packages/mobile/components/AccountErasurePanel.tsx
  • packages/mobile/components/DataExportSection.test.tsx
  • packages/mobile/components/DataExportSection.tsx
  • packages/mobile/lib/account-erasure-storage.test.ts
  • packages/mobile/lib/account-erasure-storage.ts
  • packages/mobile/lib/activity-export.test.ts
  • packages/mobile/lib/activity-export.ts
  • packages/mobile/lib/apple-health-provider.test.ts
  • packages/mobile/lib/apple-health-provider.ts
  • packages/mobile/lib/auth-context.test.ts
  • packages/mobile/lib/auth-context.tsx
  • packages/mobile/lib/auth.test.ts
  • packages/mobile/lib/auth.ts
  • packages/mobile/lib/background-accelerometer-sync.test.ts
  • packages/mobile/lib/background-accelerometer-sync.ts
  • packages/mobile/lib/background-health-kit-sync.test.ts
  • packages/mobile/lib/background-watch-inertial-measurement-unit-sync.test.ts
  • packages/mobile/lib/background-watch-inertial-measurement-unit-sync.ts
  • packages/mobile/lib/background-whoop-ble-sync.test.ts
  • packages/mobile/lib/background-whoop-ble-sync.ts
  • packages/mobile/lib/billing-checkout-operation.test.ts
  • packages/mobile/lib/billing-checkout-operation.ts
  • packages/mobile/lib/device-erasure-cutoff.test.ts
  • packages/mobile/lib/device-erasure-cutoff.ts
  • packages/mobile/lib/health-kit-food-writeback.test.ts
  • packages/mobile/lib/health-kit-food-writeback.ts
  • packages/mobile/lib/health-kit-sync.test.ts
  • packages/mobile/lib/health-kit-sync.ts
  • packages/mobile/lib/heart-rate-recording-service.test.ts
  • packages/mobile/lib/heart-rate-recording-service.ts
  • packages/mobile/lib/inertial-measurement-unit-sync.test.ts
  • packages/mobile/lib/inertial-measurement-unit-sync.ts
  • packages/mobile/lib/medication-reminder-notifications.ts
  • packages/mobile/lib/mobile-account-purge.test.ts
  • packages/mobile/lib/mobile-account-purge.ts
  • packages/mobile/lib/mobile-export-cache.test.ts
  • packages/mobile/lib/mobile-export-cache.ts
  • packages/mobile/lib/mobile-query-persistence.test.tsx
  • packages/mobile/lib/mobile-query-persistence.ts
  • packages/mobile/lib/watch-altitude-file-sync.test.ts
  • packages/mobile/lib/watch-altitude-file-sync.ts
  • packages/mobile/lib/watch-file-sync.test.ts
  • packages/mobile/lib/watch-file-sync.ts
  • packages/mobile/modules/ble-heart-rate/Tests/BleHeartRateSampleBufferTests.swift
  • packages/mobile/modules/ble-heart-rate/index.ts
  • packages/mobile/modules/ble-heart-rate/ios/BleHeartRateModule.swift
  • packages/mobile/modules/ble-heart-rate/ios/BleHeartRateSampleBuffer.swift
  • packages/mobile/modules/core-motion/Tests/CoreMotionAccountStateStoreTests.swift
  • packages/mobile/modules/core-motion/index.ts
  • packages/mobile/modules/core-motion/ios/CoreMotionAccountStateStore.swift
  • packages/mobile/modules/core-motion/ios/CoreMotionModule.swift
  • packages/mobile/modules/health-kit/Tests/HealthKitAccountStateStoreTests.swift
  • packages/mobile/modules/health-kit/Tests/HealthKitAnchorStoreTests.swift
  • packages/mobile/modules/health-kit/index.ts
  • packages/mobile/modules/health-kit/ios/HealthKitAccountStateStore.swift
  • packages/mobile/modules/health-kit/ios/HealthKitAnchorStore.swift
  • packages/mobile/modules/health-kit/ios/HealthKitModule.swift
  • packages/mobile/modules/watch-motion/Package.swift
  • packages/mobile/modules/watch-motion/Tests/WatchFileReceiverTests.swift
  • packages/mobile/modules/watch-motion/Tests/WatchMotionAccountStateStoreTests.swift
  • packages/mobile/modules/watch-motion/index.ts
  • packages/mobile/modules/watch-motion/ios/WatchFileInbox.swift
  • packages/mobile/modules/watch-motion/ios/WatchMotionAccountStateStore.swift
  • packages/mobile/modules/watch-motion/ios/WatchMotionModule.swift
  • packages/mobile/modules/whoop-ble/Tests/WhoopBleSampleBufferTests.swift
  • packages/mobile/modules/whoop-ble/Tests/WhoopBleSampleBufferTimestampTests.swift
  • packages/mobile/modules/whoop-ble/index.ts
  • packages/mobile/modules/whoop-ble/ios/WhoopBleModule.swift
  • packages/mobile/modules/whoop-ble/ios/WhoopBleSampleBuffer.swift
  • packages/mobile/package.json
  • packages/mobile/targets/DofekWatch/AccelerometerRecorder.swift
  • packages/mobile/targets/DofekWatch/AccelerometerTransferCursor.swift
  • packages/mobile/targets/DofekWatch/AltimeterRecorder.swift
  • packages/mobile/targets/DofekWatch/GyroscopeRecorder.swift
  • packages/mobile/targets/DofekWatch/GyroscopeSampleBuffer.swift
  • packages/mobile/targets/DofekWatch/Package.swift
  • packages/mobile/targets/DofekWatch/Tests/AccelerometerTransferCursor.test.swift
  • packages/mobile/targets/DofekWatch/Tests/GyroscopeSampleBuffer.test.swift
  • packages/mobile/targets/DofekWatch/Tests/WatchAccountStateStore.test.swift
  • packages/mobile/targets/DofekWatch/TransferManager+Compression.swift
  • packages/mobile/targets/DofekWatch/TransferManager.swift
  • packages/mobile/targets/DofekWatch/WatchAccountStateStore.swift
  • packages/mobile/targets/DofekWatch/WatchSessionDelegate.swift
  • packages/mobile/test-setup.ts
  • packages/mobile/vitest.config.ts
  • packages/server/package.json
  • packages/server/src/auth/account-linking.integration.test.ts
  • packages/server/src/auth/account-linking.test.ts
  • packages/server/src/auth/account-linking.ts
  • packages/server/src/auth/identity-credential-revocation.integration.test.ts
  • packages/server/src/auth/identity-credential-revocation.ts
  • packages/server/src/auth/password-credential.ts
  • packages/server/src/auth/password-reset.integration.test.ts
  • packages/server/src/auth/password-reset.ts
  • packages/server/src/auth/provider-credential-revocation.test.ts
  • packages/server/src/auth/provider-credential-revocation.ts
  • packages/server/src/auth/providers.integration.test.ts
  • packages/server/src/auth/providers.ts
  • packages/server/src/auth/session.test.ts
  • packages/server/src/auth/session.ts
  • packages/server/src/billing/stripe-client.test.ts
  • packages/server/src/billing/stripe-client.ts
  • packages/server/src/companion/token-repository.ts
  • packages/server/src/export.integration.test.ts
  • packages/server/src/index.test.ts
  • packages/server/src/index.ts
  • packages/server/src/jobs/process-provider-data-deletion-job.integration.test.ts
  • packages/server/src/lib/companion-pairing-store.ts
  • packages/server/src/mcp/route-lifecycle.test.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/route.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/repositories/activity-recording-repository.test.ts
  • packages/server/src/repositories/health-kit-sync-repository.test.ts
  • packages/server/src/repositories/settings-repository.test.ts
  • packages/server/src/repositories/slack-installation-repository.integration.test.ts
  • packages/server/src/repositories/slack-installation-repository.ts
  • packages/server/src/router.test.ts
  • packages/server/src/router.ts
  • packages/server/src/routers/account-erasure.integration.test.ts
  • packages/server/src/routers/account-erasure.ts
  • packages/server/src/routers/activity-recording.test.ts
  • packages/server/src/routers/activity.test.ts
  • packages/server/src/routers/activity.ts
  • packages/server/src/routers/billing.test.ts
  • packages/server/src/routers/billing.ts
  • packages/server/src/routers/ble-heart-rate-sync.test.ts
  • packages/server/src/routers/companion-pairing.test.ts
  • packages/server/src/routers/companion-pairing.ts
  • packages/server/src/routers/companion-token.test.ts
  • packages/server/src/routers/companion-token.ts
  • packages/server/src/routers/file-upload.test.ts
  • packages/server/src/routers/file-upload.ts
  • packages/server/src/routers/health-kit-sync-processors.test.ts
  • packages/server/src/routers/health-kit-sync.test.ts
  • packages/server/src/routers/inertial-measurement-unit-sync.test.ts
  • packages/server/src/routers/session.test.ts
  • packages/server/src/routers/support.integration.test.ts
  • packages/server/src/routers/support.test.ts
  • packages/server/src/routers/support.ts
  • packages/server/src/routers/sync.test.ts
  • packages/server/src/routers/sync.ts
  • packages/server/src/routers/test-helpers.ts
  • packages/server/src/routers/trpc.test.ts
  • packages/server/src/routers/watch-altitude-sync.test.ts
  • packages/server/src/routers/whoop-ble-sync.test.ts
  • packages/server/src/routes/auth.test.ts
  • packages/server/src/routes/auth/apple-native.ts
  • packages/server/src/routes/auth/complete-signup.ts
  • packages/server/src/routes/auth/data-provider-callback.test.ts
  • packages/server/src/routes/auth/data-provider-callback.ts
  • packages/server/src/routes/auth/identity-callback.ts
  • packages/server/src/routes/auth/index.ts
  • packages/server/src/routes/auth/password-auth.test.ts
  • packages/server/src/routes/auth/password-auth.ts
  • packages/server/src/routes/auth/shared.ts
  • packages/server/src/routes/auth/slack-oauth.test.ts
  • packages/server/src/routes/auth/slack-oauth.ts
  • packages/server/src/routes/companion-token.test.ts
  • packages/server/src/routes/companion-token.ts
  • packages/server/src/routes/export.test.ts
  • packages/server/src/routes/export.ts
  • packages/server/src/routes/webhooks.test.ts
  • packages/server/src/routes/webhooks.ts
  • packages/server/src/slack/bot-unit.test.ts
  • packages/server/src/slack/food-entry-repository.integration.test.ts
  • packages/server/src/slack/food-entry-repository.test.ts
  • packages/server/src/slack/food-entry-repository.ts
  • packages/server/src/slack/slack-handlers.ts
  • packages/server/src/trpc.ts
  • packages/web/src/components/AccountErasurePanel.stories.tsx
  • packages/web/src/components/AccountErasurePanel.test.tsx
  • packages/web/src/components/AccountErasurePanel.tsx
  • packages/web/src/lib/account-erasure-purge.test.ts
  • packages/web/src/lib/account-erasure-purge.ts
  • packages/web/src/lib/account-erasure-storage.test.ts
  • packages/web/src/lib/account-erasure-storage.ts
  • packages/web/src/lib/auth-context.test.tsx
  • packages/web/src/lib/auth-context.tsx
  • packages/web/src/lib/auth.test.ts
  • packages/web/src/lib/auth.ts
  • packages/web/src/lib/billing-checkout-operation.test.ts
  • packages/web/src/lib/billing-checkout-operation.ts
  • packages/web/src/lib/posthog.test.ts
  • packages/web/src/lib/posthog.ts
  • packages/web/src/lib/resumable-file-upload.ts
  • packages/web/src/lib/web-account-state-lock.test-helpers.ts
  • packages/web/src/lib/web-account-state-lock.test.ts
  • packages/web/src/lib/web-account-state-lock.ts
  • packages/web/src/pages/AccountDeletionStatusPage.stories.tsx
  • packages/web/src/pages/AccountDeletionStatusPage.test.tsx
  • packages/web/src/pages/AccountDeletionStatusPage.tsx
  • packages/web/src/pages/LandingPage.test.tsx
  • packages/web/src/pages/LandingPage.tsx
  • packages/web/src/pages/SettingsPage.test.tsx
  • packages/web/src/pages/SettingsPage.tsx
  • packages/web/src/routeTree.gen.ts
  • packages/web/src/routes/__root.test.tsx
  • packages/web/src/routes/__root.tsx
  • packages/web/src/routes/account-deletion.tsx
  • packages/web/src/routes/privacy.test.tsx
  • packages/web/src/routes/privacy.tsx
  • packages/web/src/routes/training/cycling.test.tsx
  • packages/web/test-setup.ts
  • scripts/README.md
  • scripts/backfill-exercise-provenance.integration.test.ts
  • scripts/backfill-exercise-provenance.ts
  • scripts/backfill-ride-with-gps-track-points.test.ts
  • scripts/backfill-ride-with-gps-track-points.ts
  • scripts/backfill-slack-team-memberships.integration.test.ts
  • scripts/backfill-slack-team-memberships.ts
  • scripts/deploy-service-environment.test.ts
  • scripts/deploy-service-environment.ts
  • scripts/e2e-web-reuse.test.ts
  • scripts/e2e-web-reuse.ts
  • scripts/generate-schema-diagram.test.ts
  • scripts/generate-schema-diagram.ts
  • scripts/no-suppressions.test.ts
  • scripts/no-suppressions.ts
  • scripts/render-deploy-service-env.ts
  • scripts/seed/training.ts
  • scripts/sweep-expired-r2-backups.test.ts
  • scripts/sweep-expired-r2-backups.ts
  • scripts/validate-deploy-env.test.ts
  • scripts/validate-deploy-env.ts
  • src/account-erasure/archive-erasure.integration.test.ts
  • src/account-erasure/archive-erasure.test.ts
  • src/account-erasure/archive-erasure.ts
  • src/account-erasure/backup-retention.test.ts
  • src/account-erasure/backup-retention.ts
  • src/account-erasure/clickhouse-erasure.integration.test.ts
  • src/account-erasure/clickhouse-erasure.test.ts
  • src/account-erasure/clickhouse-erasure.ts
  • src/account-erasure/clickhouse-physical-erasure.test.ts
  • src/account-erasure/clickhouse-physical-erasure.ts
  • src/account-erasure/database-backup-storage.integration.test.ts
  • src/account-erasure/database-backup-storage.ts
  • src/account-erasure/identity.test.ts
  • src/account-erasure/identity.ts
  • src/account-erasure/peerdb-erasure.test.ts
  • src/account-erasure/peerdb-erasure.ts
  • src/account-erasure/peerdb-staging-retention.integration.test.ts
  • src/account-erasure/peerdb-staging-retention.test.ts
  • src/account-erasure/peerdb-staging-retention.ts
  • src/account-erasure/peerdb-staging-writer-barrier.test.ts
  • src/account-erasure/peerdb-staging-writer-barrier.ts
  • src/account-erasure/postgres-erasure.integration.test.ts
  • src/account-erasure/postgres-erasure.ts
  • src/account-erasure/processor-erasure.integration.test.ts
  • src/account-erasure/processor-erasure.test.ts
  • src/account-erasure/processor-erasure.ts
  • src/account-erasure/processor-retention.integration.test.ts
  • src/account-erasure/processor-retention.test.ts
  • src/account-erasure/processor-retention.ts
  • src/account-erasure/redis-erasure-runtime.test.ts
  • src/account-erasure/redis-erasure-runtime.ts
  • src/account-erasure/redis-erasure.integration.test.ts
  • src/account-erasure/redis-erasure.test.ts
  • src/account-erasure/redis-erasure.ts
  • src/account-erasure/redpanda-drain.integration.test.ts
  • src/account-erasure/redpanda-drain.test.ts
  • src/account-erasure/redpanda-drain.ts
  • src/account-erasure/remote-revocation.integration.test.ts
  • src/account-erasure/remote-revocation.test.ts
  • src/account-erasure/remote-revocation.ts
  • src/account-erasure/remote-snapshot.integration.test.ts
  • src/account-erasure/remote-snapshot.ts
  • src/account-erasure/restore-intent-codec.test.ts
  • src/account-erasure/restore-intent-codec.ts
  • src/account-erasure/restore-ledger.test.ts
  • src/account-erasure/restore-ledger.ts
  • src/account-erasure/restore-reconciliation.integration.test.ts
  • src/account-erasure/restore-reconciliation.ts
  • src/account-erasure/stripe-acceptance.integration.test.ts
  • src/account-erasure/stripe-acceptance.ts
  • src/account-erasure/tracing.test.ts
  • src/account-erasure/tracing.ts
  • src/account-erasure/work-erasure.test.ts
  • src/account-erasure/work-erasure.ts
  • src/auth/apple-client-secret.ts
  • src/auth/apple-credential-revocation.ts
  • src/auth/apple-private-key.ts
  • src/auth/oauth.ts
  • src/billing/stripe-client.ts
  • src/bullmq-redis-client.ts
  • src/db/account-erasure-locks.ts
  • src/db/account-erasure-migration.integration.test.ts
  • src/db/account-erasure-processing.ts
  • src/db/account-erasure.integration.test.ts
  • src/db/account-erasure.ts
  • src/db/clickhouse-cdc-health.ts
  • src/db/clickhouse-cdc.test.ts
  • src/db/clickhouse-cdc.ts
  • src/db/clickhouse-metric-stream-bootstrap.ts
  • src/db/clickhouse-migrations/0070_account_erasure_fence.ts
  • src/db/clickhouse-migrations/registry.test.ts
  • src/db/clickhouse-migrations/registry.ts
  • src/db/clickhouse.ts
  • src/db/data-export.integration.test.ts
  • src/db/data-export.test.ts
  • src/db/data-export.ts
  • src/db/exercise-provenance.ts
  • src/db/file-upload.integration.test.ts
  • src/db/file-upload.ts
  • src/db/index.integration.test.ts
  • src/db/index.test.ts
  • src/db/index.ts
  • src/db/metric-stream-writer.test.ts
  • src/db/metric-stream-writer.ts
  • src/db/migrate.integration.test.ts
  • src/db/migrate.test.ts
  • src/db/migrate.ts
  • src/db/provider-data-deletion.integration.test.ts
  • src/db/provider-data-deletion.ts
  • src/db/schema/account.ts
  • src/db/schema/reference.ts
  • src/db/test-helpers.ts
  • src/db/tokens.test.ts
  • src/db/tokens.ts
  • src/db/user-external-effect.integration.test.ts
  • src/db/user-external-effect.ts
  • src/index.test.ts
  • src/index.ts
  • src/jobs/account-erasure-outbox.test.ts
  • src/jobs/account-erasure-outbox.ts
  • src/jobs/account-erasure-phase-runner.test.ts
  • src/jobs/account-erasure-phase-runner.ts
  • src/jobs/account-erasure-runtime.test.ts
  • src/jobs/account-erasure-runtime.ts
  • src/jobs/account-erasure-startup.test.ts
  • src/jobs/account-erasure-startup.ts
  • src/jobs/account-erasure-work-guard.integration.test.ts
  • src/jobs/account-erasure-work-guard.test.ts
  • src/jobs/account-erasure-work-guard.ts
  • src/jobs/account-erasure-work-purger.test.ts
  • src/jobs/account-erasure-work-purger.ts
  • src/jobs/account-erasure-worker.integration.test.ts
  • src/jobs/account-erasure-worker.ts
  • src/jobs/data-export-outbox.test.ts
  • src/jobs/data-export-outbox.ts
  • src/jobs/file-upload-outbox.test.ts
  • src/jobs/file-upload-outbox.ts
  • src/jobs/file-upload-reconciliation.test.ts
  • src/jobs/file-upload-reconciliation.ts
  • src/jobs/garmin-dump-flow.test.ts
  • src/jobs/garmin-dump-flow.ts
  • src/jobs/process-account-erasure-job.test.ts
  • src/jobs/process-account-erasure-job.ts
  • src/jobs/process-account-erasure-request.integration.test.ts
  • src/jobs/process-account-erasure-request.test.ts
  • src/jobs/process-account-erasure-request.ts
  • src/jobs/process-activity-delete-analytics-job.test.ts
  • src/jobs/process-activity-delete-analytics-job.ts
  • src/jobs/process-export-job.test.ts
  • src/jobs/process-export-job.ts
  • src/jobs/process-file-upload-import-job.test.ts
  • src/jobs/process-file-upload-import-job.ts
  • src/jobs/process-fit-file-import-job.test.ts
  • src/jobs/process-fit-file-import-job.ts
  • src/jobs/process-garmin-dump-import-job.test.ts
  • src/jobs/process-garmin-dump-import-job.ts
  • src/jobs/process-import-job.test.ts
  • src/jobs/process-import-job.ts
  • src/jobs/process-post-sync-job.test.ts
  • src/jobs/process-post-sync-job.ts
  • src/jobs/process-provider-data-deletion-job.test.ts
  • src/jobs/process-provider-data-deletion-job.ts
  • src/jobs/process-scheduled-sync-job.test.ts
  • src/jobs/process-scheduled-sync-job.ts
  • src/jobs/process-sync-job.test.ts
  • src/jobs/process-sync-job.ts
  • src/jobs/process-zip-entry-extract-job.test.ts
  • src/jobs/provider-data-deletion-dependencies.test.ts
  • src/jobs/provider-data-deletion-dependencies.ts
  • src/jobs/provider-data-deletion-outbox.test.ts
  • src/jobs/provider-data-deletion-outbox.ts
  • src/jobs/queues.test.ts
  • src/jobs/queues.ts
  • src/jobs/worker.test.ts
  • src/jobs/worker.ts
  • src/lib/sentry.test.ts
  • src/lib/sentry.ts
  • src/metric-stream/clickhouse-sink.test.ts
  • src/metric-stream/clickhouse-sink.ts
  • src/metric-stream/clickhouse-table.ts
  • src/metric-stream/events.test.ts
  • src/metric-stream/events.ts
  • src/metric-stream/redpanda-quarantine.ts
  • src/metric-stream/write-fence-context.ts
  • src/metric-stream/write-metric-stream.test.ts
  • src/metric-stream/write-metric-stream.ts
  • src/processing/processing-event-store.ts
  • src/providers/README.md
  • src/providers/apple-health/db-insertion.test.ts
  • src/providers/apple-health/import.test.ts
  • src/providers/coros-sync.integration.test.ts
  • src/providers/fitbit-sync.integration.test.ts
  • src/providers/fitbit/provider.test.ts
  • src/providers/fitbit/provider.ts
  • src/providers/garmin.test.ts
  • src/providers/komoot.test.ts
  • src/providers/komoot.ts
  • src/providers/mapmyfitness.test.ts
  • src/providers/mapmyfitness.ts
  • src/providers/oura.test.ts
  • src/providers/peloton.test.ts
  • src/providers/polar/client.test.ts
  • src/providers/polar/client.ts
  • src/providers/polar/provider.test.ts
  • src/providers/polar/provider.ts
  • src/providers/ride-with-gps-ext.test.ts
  • src/providers/ride-with-gps-sync.integration.test.ts
  • src/providers/ride-with-gps.ts
  • src/providers/strava.test.ts
  • src/providers/strava.ts
  • src/providers/strong-csv-sync.integration.test.ts
  • src/providers/strong-csv.test.ts
  • src/providers/strong-csv.ts
  • src/providers/suunto-sync.integration.test.ts
  • src/providers/suunto.test.ts
  • src/providers/suunto.ts
  • src/providers/test-helpers.ts
  • src/providers/types.ts
  • src/providers/whoop.test.ts
  • src/providers/whoop/sync-workouts.test.ts
  • src/providers/whoop/sync-workouts.ts
  • src/providers/withings.test.ts
  • src/providers/withings.ts
  • src/r2-storage.test.ts
  • src/r2-storage.ts
  • src/security/slack-credential-context.ts
  • src/zoho-desk.integration.test.ts
  • src/zoho-desk.ts
  • vitest.config.mutation.ts
  • vitest.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

fix(account-erasure): track ClickHouse part lineage for physical-erasure proof

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Fix ClickHouse erasure proof by tracking only predicate-matching parts and MergeTree lineage.
• Harden durable account-erasure flow with durable fencing, tokens, and fail-closed verification.
• Add focused unit/e2e coverage and an operator runbook for erasure invariants.
Diagram

graph TD
  U(("User")) --> C["Client apps"] --> API["Account-erasure API"] --> DB[("Postgres")]
  DB --> W["Erasure worker"] --> CH[("ClickHouse")]
  CH --> CHLOG[("system.part_log")]
  W --> R2[("R2 ledger")]
  W --> EXT{{"Processors"}}

  subgraph Legend
    direction LR
    _actor(("Actor")) ~~~ _comp["Component"] ~~~ _db[("Database")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Track all inactive parts in system.parts
  • ➕ Simpler implementation: no part_log traversal
  • ➖ Can mis-attribute unrelated inactive parts as user data (false positives)
  • ➖ Doesn’t model MergeTree merge/mutation history, weakening the proof invariant
2. Prove only via row-count=0 after DELETE mutation
  • ➕ Cheaper queries; avoids system.part_log reliance
  • ➖ Row absence doesn’t prove physical part removal from disk
  • ➖ Fails the documented physical-erasure proof requirement
3. Persist and poll only mutation_id(s) from system.mutations
  • ➕ Directly ties to the applied DELETE mutation
  • ➖ Mutation completion alone doesn’t capture merges; still needs lineage to prove part disappearance
  • ➖ Can miss the full ancestry required for a robust proof

Recommendation: Proceed with the PR’s lineage-based approach: seed the proof set from predicate-matching active parts and expand through system.part_log MergeParts/MutatePart (plus completed-mutation lineage). This is the most robust fail-closed proof while avoiding false positives from unrelated inactive parts.

Files changed (15) +5048 / -32

Enhancement (7) +3151 / -0
clickhouse-erasure.tsIntegrate physical-erasure proof into ClickHouse account erasure +836/-0

Integrate physical-erasure proof into ClickHouse account erasure

• Builds per-table physical erasure targets, applies mutations, and blocks completion until all relevant parts are physically removed. Ensures ClickHouse queries are PII-safe by disabling query logging.

src/account-erasure/clickhouse-erasure.ts

account-erasure.tsDurable account-erasure prepare/confirm/status primitives +853/-0

Durable account-erasure prepare/confirm/status primitives

• Adds DB-layer implementation for preparation tokens, confirmation, status tokens, and public status projection. Enforces fail-closed invariants around hashing, fencing, and durable request creation.

src/db/account-erasure.ts

account-erasure-processing.tsOutbox/lease/checkpoint orchestration for erasure worker +669/-0

Outbox/lease/checkpoint orchestration for erasure worker

• Adds query helpers to list/claim pending requests, manage leases and retries, prune expired preparations, and load/store checkpoint metadata used by phase execution.

src/db/account-erasure-processing.ts

process-account-erasure-job.tsDefine phase model and retention timing helpers +205/-0

Define phase model and retention timing helpers

• Defines the canonical phase sets (critical/initial/verification/retention) and waiting semantics (replay vs retention), including the day-29 retention verification point.

src/jobs/process-account-erasure-job.ts

account-erasure-phase-runner.tsCentralize per-phase checkpoint schemas and dependencies +324/-0

Centralize per-phase checkpoint schemas and dependencies

• Introduces typed checkpoint parsing for high-watermarks, peerdb drain, archive sweep progress, and processor progress. Enforces that identifying phases cannot run after request PII is scrubbed.

src/jobs/account-erasure-phase-runner.ts

account-erasure.tsExpose account erasure prepare/confirm/status endpoints +99/-0

Expose account erasure prepare/confirm/status endpoints

• Adds tRPC router endpoints with recent-auth gating for prepare, capability-token confirmation, and public status lookup via opaque status token after session revocation. Maps prerequisite failures to PRECONDITION_FAILED/NOT_FOUND appropriately.

packages/server/src/routers/account-erasure.ts

account-erasure.tsShared capability schemas + status presentation helper +165/-0

Shared capability schemas + status presentation helper

• Defines Zod schemas for preparation/status capabilities and a consistent client-facing status description function (pending/running/waiting/failed/completed).

packages/auth/src/account-erasure.ts

Bug fix (1) +334 / -0
clickhouse-physical-erasure.tsTrack MergeTree part lineage for physical-erasure proof +334/-0

Track MergeTree part lineage for physical-erasure proof

• Implements physical-erasure targeting and proof: find predicate-matching active parts, expand lineage via system.part_log (MergeParts/MutatePart), add completed mutation lineage, and wait until referenced parts vanish from system.parts. Fails closed on detached parts, non-zero remaining rows, and timeouts.

src/account-erasure/clickhouse-physical-erasure.ts

Tests (2) +142 / -0
clickhouse-physical-erasure.test.tsUnit tests for lineage-based part targeting +93/-0

Unit tests for lineage-based part targeting

• Verifies the target set includes only predicate-matching parts (not unrelated inactive parts) and that mutation lineage expands the proof set correctly.

src/account-erasure/clickhouse-physical-erasure.test.ts

account-erasure.cy.tsE2E coverage for account deletion flow +49/-0

E2E coverage for account deletion flow

• Adds Cypress coverage for prepare/confirm and status display paths to guard the end-to-end user journey.

cypress/e2e/account-erasure.cy.ts

Documentation (1) +512 / -0
account-erasure-runbook.mdOperator runbook and proof invariants for durable erasure +512/-0

Operator runbook and proof invariants for durable erasure

• Documents safety invariants, user flow, phase map, required evidence, and deployment prerequisites. Explicitly captures the physical-erasure proof expectations (including ClickHouse part lineage constraints).

docs/account-erasure-runbook.md

Other (4) +909 / -32
0062_account_erasure.sqlAdd durable account-erasure tables and constraints +762/-0

Add durable account-erasure tables and constraints

• Introduces account erasure preparation/request/checkpoint/outbox/identity-fence tables plus indexes and CHECK constraints to support durable orchestration and token uniqueness.

drizzle/0062_account_erasure.sql

package.jsonExport new account-erasure modules and add dependencies +18/-0

Export new account-erasure modules and add dependencies

• Adds package exports for account-erasure modules and DB entrypoints, and introduces dependency updates (notably Stripe and @opentelemetry/core) required by the workflow.

package.json

deploy-web-stack.ymlDeploy workflow validation for erasure prerequisites +59/-32

Deploy workflow validation for erasure prerequisites

• Updates CI/CD workflow steps to validate required configuration/secrets before deploying erasure-capable services.

.github/workflows/deploy-web-stack.yml

storage.tfProvision storage for erasure ledgers/backups +70/-0

Provision storage for erasure ledgers/backups

• Updates Terraform-managed storage to support immutable ledger and retention/backup artifacts used by the erasure workflow.

deploy/storage.tf

@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 300 rules

Grey Divider


Action required

1. Mutation lineage lookup broken ✓ Resolved 🐞 Bug ≡ Correctness
Description
applyClickHousePhysicalErasureMutation searches system.mutations.command for the random marker
value, but the ALTER TABLE ... DELETE embeds the marker only via query parameters, so ClickHouse
stores placeholders (not substituted values) and the lookup returns no mutation_id. As a result,
the code never adds MutatePart lineage to target.partNames, making the physical-part deletion
proof potentially incomplete.
Code

src/account-erasure/clickhouse-physical-erasure.ts[R218-256]

+  const mutationMarker = randomUUID();
+  await client.command({
+    query: `ALTER TABLE ${qualifiedTable(target.table)}
+      DELETE WHERE (${target.predicate.sql})
+        AND {account_erasure_mutation_marker:String}
+          = {account_erasure_mutation_marker:String}`,
+    query_params: {
+      ...target.predicate.queryParameters,
+      account_erasure_mutation_marker: mutationMarker,
+    },
+    clickhouse_settings: {
+      log_queries: 0,
+      mutations_sync: 2,
+    },
+  });
+  const rowsRemaining = await countRowsMatchingPredicate(client, target.table, target.predicate);
+  if (rowsRemaining !== 0) {
+    throw new Error(
+      `ClickHouse account erasure left ${rowsRemaining} row(s) in ${target.table.database}.${target.table.name}`,
+    );
+  }
+  const mutationResult = await client.query({
+    query: `SELECT mutation_id
+      FROM system.mutations
+      WHERE database = {database:String}
+        AND table = {table:String}
+        AND is_done = 1
+        AND position(command, {mutation_marker:String}) > 0`,
+    query_params: {
+      database: target.table.database,
+      mutation_marker: mutationMarker,
+      table: target.table.name,
+    },
+    format: "JSONEachRow",
+    clickhouse_settings: { log_queries: 0 },
+  });
+  const mutationIds = mutationRowsSchema
+    .parse(await mutationResult.json())
+    .map((row) => row.mutation_id);
Relevance

●● Moderate

Finding references paths not present in this repo snapshot; no historical evidence for this specific
ClickHouse mutation-marker pattern.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The physical-erasure code correlates mutations by searching for the marker value in
system.mutations.command, but an integration test asserts that parameterized mutation commands do
not contain parameter values in system.mutations.command (so the marker value will not be present
either), making the lookup fail.

src/account-erasure/clickhouse-physical-erasure.ts[213-263]
src/account-erasure/clickhouse-erasure.integration.test.ts[922-938]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`applyClickHousePhysicalErasureMutation` tries to find the completed mutation by searching `system.mutations.command` for the **marker value** (`mutationMarker`). However, the mutation query uses ClickHouse query parameters (`{account_erasure_mutation_marker:String}`), and ClickHouse records the command text with placeholders rather than substituted parameter values. This causes `mutationIds` to be empty, so the code never collects mutation part lineage.

### Issue Context
This breaks the intended behavior validated by unit tests (mutation lineage should be appended to `target.partNames`) and undermines the “track MergeParts/MutatePart lineage” guarantee.

### Fix Focus Areas
- src/account-erasure/clickhouse-physical-erasure.ts[213-256]

### Suggested fix
Update the correlation strategy so the mutation can be reliably identified:
- Option A (simplest): inline the marker as a **literal** in the ALTER statement (the marker is random/non-PII), e.g. `AND '${mutationMarker}' = '${mutationMarker}'` (or a SQL comment containing the marker). Then keep the existing `position(command, ...)` lookup.
- Option B: search `system.mutations.command` for the **placeholder name** (e.g. `account_erasure_mutation_marker`) and further disambiguate by `database`, `table`, and a time window around execution (or an explicit query_id if you can set one).

After the change, ensure `mutationIds` is non-empty for a completed mutation so `listMutationPartLineage()` runs and `target.partNames` is expanded as intended.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. client.query() bypasses executeWithSchema ✓ Resolved 📘 Rule violation ≡ Correctness
Description
New code paths execute raw SQL via client.query(...) (including backfill scripts, advisory lock
statements, and migrate/coverage-hook checks) instead of routing through the required
executeWithSchema wrapper. This violates the typed SQL execution requirement and increases the
risk of unvalidated or shape-drifting results and inconsistent DB access patterns.
Code

scripts/backfill-exercise-provenance.ts[R33-56]

+  const missingSystemSources = await client.query(
+    `SELECT count(*)::integer AS count
+     FROM jsonb_to_recordset($1::jsonb) AS definition(
+       equipment text,
+       "exerciseType" text,
+       movement text,
+       "muscleGroup" text,
+       "muscleGroups" text[],
+       name text,
+       "sourceKind" text
+     )
+     JOIN fitness.exercise AS exercise
+       ON exercise.name = definition.name
+       AND exercise.muscle_group IS NOT DISTINCT FROM definition."muscleGroup"
+       AND exercise.muscle_groups IS NOT DISTINCT FROM definition."muscleGroups"
+       AND exercise.equipment IS NOT DISTINCT FROM definition.equipment
+       AND exercise.exercise_type IS NOT DISTINCT FROM definition."exerciseType"
+       AND exercise.movement IS NOT DISTINCT FROM definition.movement
+     LEFT JOIN fitness.exercise_source AS source
+       ON source.exercise_id = exercise.id
+       AND source.source_kind = definition."sourceKind"
+     WHERE source.id IS NULL`,
+    [JSON.stringify(SYSTEM_SEEDED_EXERCISE_DEFINITIONS)],
+  );
Relevance

●●● Strong

Strong precedent: reviewers repeatedly required executeWithSchema/Zod validation instead of direct
DB query execution.

PR-#409
PR-#2302
PR-#1860

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 722086 requires raw SQL execution to go through executeWithSchema (or an approved
wrapper that ultimately calls it) to enforce runtime row-shape validation (e.g., via a Zod schema)
and maintain consistent access patterns. The cited additions show direct client.query(...) usage
with raw SQL text in scripts/backfill-exercise-provenance.ts and
scripts/backfill-slack-team-memberships.ts, direct advisory lock acquire/release SQL executed via
client.query(...) in src/jobs/account-erasure-work-guard.ts, and a new raw SQL execution for a
coverage-hook check in src/db/migrate.ts, all of which bypass the required wrapper.

Rule 722086: Execute raw SQL via typed executeWithSchema wrapper
scripts/backfill-exercise-provenance.ts[33-56]
scripts/backfill-slack-team-memberships.ts[111-126]
src/jobs/account-erasure-work-guard.ts[42-71]
src/db/migrate.ts[84-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Multiple newly added code paths execute raw SQL via `client.query(...)` instead of the required `executeWithSchema(...)` wrapper, violating the typed SQL execution requirement.

## Issue Context
PR Compliance ID 722086 requires raw SQL execution to be performed via the typed `executeWithSchema` wrapper (or an approved wrapper that ultimately calls it) to enforce consistent runtime result validation (e.g., with a Zod schema), reduce ad-hoc DB access patterns, and avoid unvalidated/shape-drifting results. The affected areas include backfill scripts, advisory-lock acquisition/release logic, and a migrate-time/coverage-hook check.

## Fix Focus Areas
- scripts/backfill-exercise-provenance.ts[33-56]
- scripts/backfill-slack-team-memberships.ts[111-126]
- src/jobs/account-erasure-work-guard.ts[42-71]
- src/db/migrate.ts[84-90]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Stripe cleanup catch hides error ✓ Resolved 📘 Rule violation ☼ Reliability
Description
Cleanup-related catch {} blocks in createCheckoutSession and removeMobileQueryCache() discard
the real exception and instead capture/report only a generic Error, losing the original error and
stack trace and effectively swallowing failures. This violates the requirement to log the actual
caught exception and/or propagate it, unless an intentional ignore is explicitly documented and
narrowly scoped.
Code

packages/server/src/routers/billing.ts[R131-148]

+      } catch (error: unknown) {
+        if (createdCheckoutSessionId) {
+          try {
+            await stripe.checkout.sessions.expire(createdCheckoutSessionId);
+          } catch {
+            Sentry.captureException(new Error("Stripe checkout session cleanup failed"), {
+              tags: { source: "billing", operation: "expire-orphan-checkout-session" },
+            });
+          }
+        }
+        if (createdCustomerId) {
+          try {
+            await stripe.customers.del(createdCustomerId);
+          } catch {
+            Sentry.captureException(new Error("Stripe customer cleanup failed"), {
+              tags: { source: "billing", operation: "delete-orphan-stripe-customer" },
+            });
+          }
Relevance

●●● Strong

Team previously accepted preserving caught errors in cleanup catches (captureException(releaseError)
without masking original).

PR-#1815
PR-#794
PR-#2308

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 721934 flags catch blocks as violations when they neither log the real caught
exception (including its stack) nor propagate the error to the caller. In
packages/server/src/routers/billing.ts, the new cleanup catch {} blocks do not bind the thrown
error object and therefore cannot log/capture the real exception, and they also do not document an
intentional ignore; similarly, in packages/mobile/lib/mobile-query-persistence.ts, the `catch {
... } in removeMobileQueryCache` omits the caught error object and does not rethrow or otherwise
return an error result, making failures opaque and hard to diagnose.

Rule 721934: Do not silently swallow errors in catch/except blocks
packages/server/src/routers/billing.ts[131-148]
packages/mobile/lib/mobile-query-persistence.ts[38-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`catch { ... }` blocks in server and mobile cleanup code are swallowing failures by not capturing/logging the actual thrown error object (and stack) and by not propagating the failure (or explicitly documenting a narrowly-scoped intentional ignore). In some cases they report only a newly-created generic `Error`, which drops the original exception context.

## Issue Context
Compliance rule 721934 requires modified catch blocks to either (a) log the exception including the original error object/stack trace and/or (b) propagate the error (rethrow/return an error result), and only allows intentional ignores when explicitly documented and narrowly scoped. The current patterns in `createCheckoutSession` cleanup and `removeMobileQueryCache()` use `catch { ... }` without binding the error, resulting in loss of underlying exception details and potentially hiding cleanup failures (e.g., AsyncStorage issues) that can leave stale state.

## Fix Focus Areas
- packages/server/src/routers/billing.ts[131-148]
- packages/mobile/lib/mobile-query-persistence.ts[38-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Backfill scripts use with-env.ts ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The newly added backfill commands are documented for use via pnpm backfill:*, but these scripts
run through scripts/with-env.ts, which the checklist forbids for VM-targeted shell commands. This
can break VM/automation execution paths that must source environment files directly instead of
relying on Infisical export.
Code

scripts/README.md[R18-27]

+- `backfill-exercise-provenance.ts`: Idempotently reconstructs user/provider
+  ownership for exercises and provider aliases from historical strength sets,
+  in bounded batches, then verifies that no attributable rows were missed.
+  - Usage: `pnpm backfill:exercise-provenance`
+- `backfill-slack-team-memberships.ts`: Verifies every stored Slack bot token
+  against its recorded workspace, uses team-qualified Slack API responses to
+  reconstruct legacy Dofek memberships, and fails before writing on missing
+  scopes or ambiguous identities. It defaults to a dry run.
+  - Dry run: `pnpm backfill:slack-team-memberships`
+  - Execute: `pnpm backfill:slack-team-memberships -- --execute`
Relevance

●● Moderate

No clear historical enforcement that VM/automation commands must avoid scripts/with-env.ts;
with-env.ts widely adopted recently.

PR-#1879
PR-#1827

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2237918 forbids VM-targeted command lines from invoking scripts/with-env.ts. The
PR adds and documents backfill commands (pnpm backfill:*) that are implemented in package.json
using tsx scripts/with-env.ts, which would violate the VM-command requirement if followed in
VM/automation contexts.

Rule 2237918: Do not invoke scripts/with-env.ts in VM shell commands; source .env.local instead
scripts/README.md[18-27]
package.json[165-169]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new backfill commands are documented as `pnpm backfill:exercise-provenance` / `pnpm backfill:slack-team-memberships`, but those package.json scripts invoke `tsx scripts/with-env.ts`, which is disallowed for VM-targeted commands.

## Issue Context
Per the compliance rule, VM-facing run commands should source `.env.local` (or use explicit env files) and execute the underlying script directly, rather than relying on `scripts/with-env.ts` (Infisical-dependent).

## Fix Focus Areas
- package.json[165-169]
- scripts/README.md[18-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
5. APPLE_PRIVATE_KEY stored in env_file ✓ Resolved 📘 Rule violation ⛨ Security
Description
scripts/deploy-service-environment.ts allows APPLE_PRIVATE_KEY to be written into per-service
dotenv files that are then consumed via env_file in deploy/stack.yml. Since the Apple key is
handled as a PEM private key, this violates the requirement to inject multiline secrets via Docker
Swarm secrets instead of env files.
Code

scripts/deploy-service-environment.ts[R29-35]

+const APPLICATION_ENVIRONMENT_KEYS = [
+  "ACCOUNT_ERASURE_LEDGER_KEYRING_JSON",
+  "APPLE_BUNDLE_ID",
+  "APPLE_CLIENT_ID",
+  "APPLE_KEY_ID",
+  "APPLE_PRIVATE_KEY",
+  "APPLE_REDIRECT_URI",
Relevance

●● Moderate

Repo enforces some deploy secret hygiene, but no clear past decisions about PEM/multiline secrets
via env_file vs Swarm secrets.

PR-#837
PR-#1075
PR-#761

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 722081 prohibits providing PEM-style multiline secrets via env_file. The PR adds
APPLE_PRIVATE_KEY to the keys written into rendered service env files, and deploy/stack.yml
consumes those via env_file; additionally, src/auth/apple-client-secret.ts processes PEM headers
and \n escapes, confirming the secret is a PEM private key typically stored multiline.

Rule 722081: Inject multiline secrets as Docker Swarm secrets instead of env_file
scripts/deploy-service-environment.ts[29-35]
deploy/stack.yml[40-44]
src/auth/apple-client-secret.ts[1-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`APPLE_PRIVATE_KEY` (a PEM private key) is being distributed via `env_file`-backed dotenv files rendered by `scripts/deploy-service-environment.ts`. The compliance rule requires multiline secrets (PEM keys/certs) to be injected via Docker Swarm secrets and read from `/run/secrets/...` paths instead of environment variables.

## Issue Context
- `deploy/stack.yml` uses `env_file` for `web`/`worker`.
- The deploy env rendering script includes `APPLE_PRIVATE_KEY` among allowed keys for service env files.
- Apple client-secret generation explicitly parses PEM headers/footers and newline escapes, indicating this value is a PEM-formatted private key.

## Fix Focus Areas
- scripts/deploy-service-environment.ts[29-35]
- deploy/stack.yml[40-44]
- src/auth/apple-client-secret.ts[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread scripts/deploy-service-environment.ts
Comment thread packages/server/src/routers/billing.ts
Comment thread scripts/backfill-exercise-provenance.ts Outdated
Comment thread scripts/README.md Outdated
Comment thread src/account-erasure/clickhouse-physical-erasure.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 462 files

Note: This PR contains a large number of files. cubic only reviews up to 200 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.

Re-trigger cubic

Comment thread src/providers/strava.ts
Comment thread packages/mobile/modules/watch-motion/ios/WatchFileInbox.swift
Comment thread packages/web/src/lib/posthog.ts
Comment thread packages/server/src/routers/billing.ts
Comment thread packages/mobile/targets/DofekWatch/AltimeterRecorder.swift
Comment thread packages/web/src/lib/account-erasure-storage.ts Outdated
Comment thread packages/server/src/routes/auth/slack-oauth.ts Outdated
Comment thread packages/server/src/routes/auth/identity-callback.ts Outdated
Comment thread packages/server/src/routes/auth/identity-callback.ts Outdated
Comment thread .github/workflows/deploy-web-stack.yml Outdated
@Asherlc

Asherlc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Addressed and replied to all 45 actionable review threads in commit b7dbdeb. The fixes cover account-erasure fencing and cleanup races, OAuth rollback/response ordering, Stripe retry safety, mobile/watch/HealthKit state purges, typed SQL backfills, ClickHouse mutation lineage, Swarm secret handling, and one-time deployment backfills. Validation: Biome 2,631 files; TypeScript checks for root/server/mobile/web; 348 focused unit tests; 13 database integration tests; Swift package tests 81/119/14.

Asherlc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Re-audit result: this PR is not merge-ready. It is 209 commits behind main, has 67 merge conflicts, and has 45 unresolved review threads (15 P1). The remaining correctness blockers include the placeholder ClickHouse mutation proof at src/account-erasure/clickhouse-physical-erasure.ts:220, PostHog opt-out reset mismatch, one-shot cross-tab cleanup, native purge races, mobile recovery nonce/spinner failures, OAuth success before transaction commit, and unsafe historical-backfill behavior in the deploy workflow.

I have not made a speculative patch on top of this stale/conflicted branch. The implementation needs to be ported onto current main and repaired with executable coverage before this PR can close #1994.

@codereviewbot-ai

codereviewbot-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Pull Request Review Summary

The PR introduces mobile UI components, capability storage, and status recovery for account erasure, alongside database write fence triggers and background sync gating. The frontend state management, error handling, and recovery flows are robust and thoroughly covered with unit/integration tests.

Performance Recommendation

  • Database Trigger Index Scans (drizzle/0062_account_erasure.sql): In resolve_account_erasure_owner_ids, dynamic SQL predicates construct WHERE to_jsonb(account_erasure_parent) ->> %L = %L. This prevents PostgreSQL B-tree index usage on foreign key parent columns, resulting in full table scans on every write to transitively owned tables. Updating the predicate to compare column identifiers directly (e.g. %I::text = %L) resolves the issue.

🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

Comment thread drizzle/0062_account_erasure.sql
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Asherlc added 2 commits August 2, 2026 18:26
# Conflicts:
#	docs/production-incident-baseline.md
#	docs/schema.dbml
#	docs/zoho-desk.md
#	drizzle/meta/_journal.json
#	package.json
#	packages/mobile/app/_layout.cleanup.test.tsx
#	packages/mobile/app/_layout.tsx
#	packages/mobile/app/settings.tsx
#	packages/mobile/components/DataExportSection.test.tsx
#	packages/mobile/components/DataExportSection.tsx
#	packages/mobile/lib/auth-context.tsx
#	packages/mobile/lib/health-kit-sync.ts
#	packages/mobile/lib/mobile-query-persistence.test.tsx
#	packages/mobile/lib/mobile-query-persistence.ts
#	packages/mobile/modules/health-kit/ios/HealthKitModule.swift
#	packages/mobile/package.json
#	packages/mobile/test-setup.ts
#	packages/server/src/companion/token-repository.ts
#	packages/server/src/index.ts
#	packages/server/src/repositories/settings-repository.test.ts
#	packages/server/src/repositories/settings-repository.ts
#	packages/server/src/router.ts
#	packages/server/src/routers/companion-pairing.test.ts
#	packages/server/src/routers/companion-pairing.ts
#	packages/server/src/routers/companion-token.ts
#	packages/server/src/routers/file-upload.ts
#	packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
#	packages/server/src/routers/settings.integration.test.ts
#	packages/server/src/routers/settings.ts
#	packages/server/src/routers/support.integration.test.ts
#	packages/server/src/routers/support.test.ts
#	packages/server/src/routers/support.ts
#	packages/server/src/routers/sync.ts
#	packages/server/src/routes/auth/apple-native.ts
#	packages/server/src/routes/auth/complete-signup.ts
#	packages/server/src/routes/auth/data-provider-callback.test.ts
#	packages/server/src/routes/auth/data-provider-callback.ts
#	packages/server/src/routes/auth/identity-callback.ts
#	packages/server/src/routes/auth/password-auth.ts
#	packages/server/src/routes/companion-token.test.ts
#	packages/server/src/routes/companion-token.ts
#	packages/server/src/routes/export.ts
#	packages/server/src/routes/webhooks.ts
#	packages/server/src/slack/bot-unit.test.ts
#	packages/server/src/slack/slack-handlers.ts
#	packages/server/src/trpc.ts
#	packages/web/src/lib/auth-context.test.tsx
#	packages/web/src/lib/auth-context.tsx
#	packages/web/src/lib/posthog.test.ts
#	packages/web/src/pages/LandingPage.test.tsx
#	packages/web/src/pages/SettingsPage.tsx
#	packages/web/src/routeTree.gen.ts
#	packages/web/src/routes/__root.tsx
#	packages/web/src/routes/privacy.test.tsx
#	packages/web/src/routes/privacy.tsx
#	packages/web/test-setup.ts
#	pnpm-lock.yaml
#	src/db/clickhouse-migrations/registry.test.ts
#	src/db/clickhouse-migrations/registry.ts
#	src/jobs/data-export-outbox.ts
#	src/jobs/file-upload-outbox.ts
#	src/jobs/process-garmin-dump-import-job.ts
#	src/jobs/process-import-job.ts
#	src/jobs/process-post-sync-job.ts
#	src/jobs/process-sync-job.ts
#	src/jobs/provider-data-deletion-outbox.ts
#	src/jobs/worker.ts
#	src/lib/sentry.test.ts
#	src/zoho-desk.test.ts
#	vitest.config.ts
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

QR code for dofek://preview/pr-2366

Channel pr-2366
Deep Link dofek://preview/pr-2366
Commit a700536

To test on device:

  1. Build and install the preview client: PREVIEW_CHANNEL=pr-2366 pnpm expo prebuild --clean -p ios
  2. Or tap deep link on an existing preview build: dofek://preview/pr-2366

Each PR gets its own channel. Build a preview client with PREVIEW_CHANNEL=pr-{N} to test.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

codereviewbot-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Code Review Summary

overall look at the account erasure implementation across database migrations, mobile status/erasure screens, storage capabilities, and test suites.

Key Observation / Action Required:

  1. Unassigned NEW Record in fitness.reject_slack_team_erasure_write() (drizzle/0062_account_erasure.sql):
    • Evaluating to_jsonb(NEW) unconditionally inside unnest(ARRAY[...]) causes PostgreSQL to throw ERROR: record "new" is not assigned yet when executing DELETE statements on fitness.slack_installation or fitness.slack_team_membership. Added an inline comment with a suggested fix.

Other changes in mobile layout gating, capability persistence, cache cleanup, and Cypress E2E test setup are clean and correctly handled.


🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

Comment thread drizzle/0062_account_erasure.sql Outdated
@codereviewbot-ai

codereviewbot-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

LGTM! The changes for durable account erasure, database write fences, capability storage, and mobile cleanup/status recovery flows are well structured and thoroughly tested.


🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@Asherlc
Asherlc merged commit b3dd49b into main Aug 3, 2026
221 checks passed
@Asherlc
Asherlc deleted the codex/issue-1994 branch August 3, 2026 10:27
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.

Account-deletion promises do not match the controls or privacy instructions

3 participants