Aug 6 - Add external inspector role

This commit is contained in:
2026-08-06 10:40:33 -04:00
parent 0830585ff5
commit c988f8cabb
32 changed files with 329 additions and 99 deletions
@@ -0,0 +1,56 @@
"""phase49 — add 'external_inspector' role to users.role ENUM
Introduces the External Inspector role: an inspector employed by the customer
or a third party rather than by us. It has exactly the same capabilities as the
internal 'inspector' role and is scoped the same way — through
InspectorAssignment rows, resolved by get_inspector_scope().
Every capability/scoping check that used to test `role == 'inspector'` now
tests membership of User.INSPECTOR_ROLES, so the new role picks up inspector
behaviour everywhere without a per-route allowlist.
This is a pure ENUM expansion (adds a value, removes and migrates nothing), so
the 3-step ENUM protocol does not apply and re-running the same MODIFY is a
no-op — safe to re-run.
Notification matrix rows for the new 'external_inspector' column are NOT seeded
here: MATRIX_DEFAULTS mirrors the Inspector column at runtime and is_enabled()
falls back to that default when a row is absent, so an unseeded install behaves
exactly like the Inspector column until an admin saves the matrix page.
"""
revision = 'phase49_external_inspector'
down_revision = 'phase48_user_ui_theme'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
_ENUM_WITH_EXTERNAL = (
"ENUM('admin','director','inspector','project_manager','customer',"
"'auditor','external_inspector')"
)
_ENUM_WITHOUT_EXTERNAL = (
"ENUM('admin','director','inspector','project_manager','customer','auditor')"
)
def upgrade():
# Idempotent: MODIFY to the expanded set is harmless if already applied.
op.execute(sa.text(
f"ALTER TABLE users MODIFY COLUMN role {_ENUM_WITH_EXTERNAL} NOT NULL"
))
def downgrade():
# Reassign any external_inspector rows before contracting the ENUM so no
# account is orphaned. They become internal inspectors, which keeps their
# InspectorAssignment scoping intact — the same contracts still apply.
op.execute(sa.text(
"UPDATE users SET role = 'inspector' WHERE role = 'external_inspector'"
))
op.execute(sa.text(
f"ALTER TABLE users MODIFY COLUMN role {_ENUM_WITHOUT_EXTERNAL} NOT NULL"
))