fix(db): free-account deletion and fresh Cloud installs work on Postgres — CI now tests on Postgres - #185
Draft
MrChengLen wants to merge 1 commit into
Draft
fix(db): free-account deletion and fresh Cloud installs work on Postgres — CI now tests on Postgres#185MrChengLen wants to merge 1 commit into
MrChengLen wants to merge 1 commit into
Conversation
…on fails there
The suite only ever ran on in-memory SQLite, where migration 005's
append-only trigger on audit_events does not exist. On Postgres the
trigger also refuses the ON DELETE SET NULL cascade from users, so
DELETE /api/v1/auth/account aborts for every account without a Stripe
customer id and deletes nothing.
lint-and-test and the weekly deps-latest run now start a PostgreSQL 16
service (pinned by digest; trust auth on a throwaway database), and
tests/test_audit_postgres.py migrates it with Alembic, deletes an
account through the API and checks that every other change to an audit
event is still refused. A guard fails if either workflow stops
providing the database, so these tests cannot skip unnoticed in CI.
This commit adds the tests only, and they are expected to fail: the
first run on an empty Postgres stops in migration 001 ("type tier_enum
already exists"), and once that is fixed,
test_deleting_a_free_account_nulls_its_audit_actor fails with "audit_events
is append-only; UPDATE is not permitted". Both fixes follow.
Local suite 1490 passed, 79 skipped (the Postgres tests skip without a
database); ruff clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two PostgreSQL-only bugs that the SQLite test suite could not see, and a CI job that runs the migrations and the audit trigger on a real PostgreSQL from now on.
1. Deleting a free account failed on PostgreSQL (GDPR Art. 17).
DELETE /api/v1/auth/accountreturned 500, and nothing was deleted.audit_events.actor_user_idreferencesuserswithON DELETE SET NULL. Postgres carries out that SET NULL as an UPDATE of the audit rows, and the append-only trigger from migration 005 refused every UPDATE, so the wholeDELETE FROM usersrolled back. Every account has at least one audit event naming it (at the latest the deletion request itself), so every free-path deletion failed. Accounts with a Stripe customer id take the tax-retained path, which keeps theusersrow, so nothing cascades there.Migration
012_audit_actor_erasurereplaces the trigger function so that exactly that change passes:actor_user_idgoes from a value to NULL;to_jsonb(NEW) - 'actor_user_id' = to_jsonb(OLD) - 'actor_user_id', so a column added or dropped later cannot break it);public.users. The lookup is schema-qualified because a temporary table nameduserswould otherwise shadow the real one.A direct UPDATE that nulls a live account's id, every other UPDATE and every DELETE are still refused.
actor_user_idis not an input torecord_hash, soverify_chainstill passes. The downgrade restores migration 005's function.2. A fresh Cloud Edition database could not be migrated. Migration 001 created
tier_enumandjob_status_enumitself and then again throughop.create_table, which creates a column's enum type withoutcheckfirst. On an empty Postgres,alembic upgrade headtherefore stopped withtype "tier_enum" already exists. The container entrypoint runs that command on every Cloud Edition start, so a fresh install never came up. The two explicitcreate()calls are gone. A database that already has the schema is not affected, since Alembic does not run 001 again.CI
lint-and-testand the weeklydeps-latestrun now start a PostgreSQL 16 service (digest-pinned; trust auth on a throwaway database).tests/test_audit_postgres.py:userstable standing in, and a cascade that changes anything besides the actor.A guard test fails if either workflow stops providing the database, so the Postgres tests cannot skip unnoticed.
Reproduction, commit by commit
type "tier_enum" already exists).audit_events is append-only; UPDATE is not permitted).Docs
The TOM annex and the vendor security questionnaire name migration 012 next to 005. The RoPA, DPA and questionnaire already say that a hard delete nulls the actor id; with this PR that is what happens. Two CHANGELOG entries.
Rejected alternatives
NO ACTION: the deleted person's id would stay in the log.pg_trigger_depth() > 1: admits an UPDATE from any trigger, not just the deletion cascade.🤖 Generated with Claude Code