fix(arch-008,arch-009): canonical 2-segment permission schema enforced; fix dead role wildcard patterns
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
'''Fix role permission wildcard patterns to canonical 2-segment schema
|
||||
|
||||
Revision ID: 0141
|
||||
Revises: 0140
|
||||
Create Date: 2026-08-23
|
||||
|
||||
Migration 0019 seeded default roles with 3-segment permission patterns
|
||||
(core:*:read etc.). The runtime matcher (_matches_permission) compares
|
||||
segment counts strictly, so those patterns could never match any
|
||||
2-segment requirement - editor/viewer roles were silently dead.
|
||||
|
||||
Canonical schema is module:action (2 segments, * wildcards allowed).
|
||||
core:*:X means all modules with action X, so it converts to *:X.
|
||||
'''
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0141'
|
||||
down_revision = '0140'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
# Rebuild the permissions JSONB object, rewriting every key that starts
|
||||
# with the dead 'core:' prefix to its 2-segment equivalent ('*:X').
|
||||
_UPGRADE_SQL = '''
|
||||
UPDATE roles
|
||||
SET permissions = sub.new_perms,
|
||||
permission_version = permission_version + 1
|
||||
FROM (
|
||||
SELECT
|
||||
r.id AS role_id,
|
||||
jsonb_object_agg(
|
||||
CASE WHEN k LIKE 'core:%'
|
||||
THEN '*:' || split_part(k, ':', 3)
|
||||
ELSE k END,
|
||||
v
|
||||
) AS new_perms
|
||||
FROM roles r,
|
||||
jsonb_each(r.permissions) AS e(k, v)
|
||||
GROUP BY r.id
|
||||
) AS sub
|
||||
WHERE roles.id = sub.role_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM jsonb_object_keys(roles.permissions) k
|
||||
WHERE k LIKE 'core:%'
|
||||
)
|
||||
'''
|
||||
|
||||
# Reverse: map '*:X' back to 'core:*:X' only for keys that came from the
|
||||
# original seeding pattern. Roles that legitimately use '*:X' without a
|
||||
# matching 'core:*:X' history are left untouched (best-effort downgrade).
|
||||
_DOWNGRADE_SQL = '''
|
||||
UPDATE roles
|
||||
SET permissions = sub.new_perms,
|
||||
permission_version = permission_version + 1
|
||||
FROM (
|
||||
SELECT
|
||||
r.id AS role_id,
|
||||
jsonb_object_agg(
|
||||
CASE WHEN k = '*:' || split_part(k, ':', 2)
|
||||
AND k <> '*:*'
|
||||
THEN 'core:*:' || split_part(k, ':', 2)
|
||||
ELSE k END,
|
||||
v
|
||||
) AS new_perms
|
||||
FROM roles r,
|
||||
jsonb_each(r.permissions) AS e(k, v)
|
||||
GROUP BY r.id
|
||||
) AS sub
|
||||
WHERE roles.id = sub.role_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM jsonb_object_keys(roles.permissions) k
|
||||
WHERE k = '*:' || split_part(k, ':', 2) AND k <> '*:*'
|
||||
)
|
||||
'''
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(_UPGRADE_SQL)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(_DOWNGRADE_SQL)
|
||||
Reference in New Issue
Block a user