Prune seeded no-lockout language memberships#87
Merged
Conversation
Migration 20260608_0001 seeded an as_language_members row for every existing annotation-studio user across every active language to avoid a deploy lockout, which made per-language scoping ineffective (everyone could edit everything). Delete those seeded grants (granted_by IS NULL) so only explicitly-invited members keep access. Members-panel invites set granted_by, so they're preserved; admins/platform admins bypass membership and are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Summary
Per-language access control is enforced correctly, but migration
20260608_0001seeded anas_language_membersrow for every existing annotation-studio user across every active language (to avoid a deploy lockout). That made scoping ineffective in practice — everyone can currently edit every language. This migration removes those seeded grants so only explicitly-invited members keep access.How it identifies seeded rows
The seed insert omitted
granted_by(→NULL); Members-panel invites always setgranted_byto the admin's id (member_service.add_member). SoDELETE … WHERE granted_by IS NULLremoves exactly the seeded rows and preserves real invites. Admins and platform admins bypass membership entirely, so they're unaffected.Changes
alembic/versions/20260609_0001_prune_seeded_language_members.py(new):upgrade()deletesas_language_members WHERE granted_by IS NULL;downgrade()is a no-op (the seed set can't be reconstructed). Auto-applies on deploy (alembic upgrade head).Type of Change
Testing
uv run alembic heads→ single head20260609_0001;uv run pytest -q→ 677 passed / 2 skipped (existing AS access + HTTP tests already prove non-members get 403). The DELETE semantics were validated on a fixture: onlygranted_by IS NULLrows are removed, invited rows remain.🤖 Generated with Claude Code