Core migration 0026/0110 tried to add password_salt to mail_accounts
but the table did not exist during core migration run. Added
password_salt directly to the mail plugin initial migration.
All built-in plugins should be auto-activated. The is_core flag was
only set on some plugins, leaving Mail, DMS, Calendar, Automation,
Unified Search etc. inactive by default.
Instead of a static list, find ALL tables with tenant_id but without
owner_id and add the column. This catches all plugin tables that were
created after core migration 0054 ran.
crm_migration role is created by migration 0085, so the first
alembic run on a fresh DB must use crm_user (POSTGRES_USER).
Fixes initial deployment failure on fresh databases.
- Integrate ABAC policies into apply_visibility_filter() (allow/deny with priority)
- Add field whitelist (ABAC_ALLOWED_FIELDS) for build_sql_condition() security
- Add request-level ContextVar for user principals (group_ids, role_id)
- Set principals in deps.py (session + bearer auth)
- Use ContextVar in visibility.py and permission_resolver.py (N+1 fix)
- Add version validation to get_cached_visible_ids() (cache strategy unification)
- Deactivate delegation route (parked — not integrated into resolve_permissions)
- Add 7 ABAC integration tests
All 70 tests pass (7 ABAC + 63 existing). No regressions.
- Add Role records for editor, viewer, guest in seed_full_data
- Link UserTenant.role_id to Role records (legacy string roles no longer grant permissions)
- Fixes 10 test failures caused by removal of Legacy Role Bypass
- All 33 tests now pass
- Remove import from app.plugins.builtins.test_sample (deleted in Phase 4)
- Define SamplePlugin inline in test_plugins.py with same lifecycle behavior
- Replace all test_sample/TestSamplePlugin references with sample_plugin/SamplePlugin
- Create migration SQL files: 0001_sample_plugin.sql, 0001_bad_migration.sql
- Update discover_builtins test to check for tags plugin instead
P34: Remove test_sample plugin from production code
P35: Remove CompanyContact=None dead code from contact.py
P36: Change Plugin.config from Text to JSONB (model + migration 0117 + service)
P37: Add AI comment about workspace overengineering in workspace.py
P38: Add container resource limits to docker-compose.yaml
P39: Guest TTL 1800 not found — already migrated to regular users
P40: Add AI comment about missing IP/Device binding in session.py
P41: Fix Redis healthcheck to use auth password
P42: RLS migration history comment already present in alembic/env.py
Phase 2 cleanup: contact_folder_permission.py model was removed from
models/__init__.py and is no longer imported anywhere. The service,
schema, and routes remain as they delegate to EntityPermission.
Deleted:
- app/models/contact_folder_permission.py (dead model class)
Kept (still actively used):
- app/services/contact_folder_permission_service.py (delegates to EntityPermission)
- app/schemas/contact_folder_permission.py (pure Pydantic schemas)
- app/routes/contact_folder_permissions.py (registered in main.py)
Problem 1: Remove legacy role bypass
- Remove role="admin" string bypass in permissions.py resolve_permissions()
- Remove role="admin"/"editor" bypass in auth.py check_permission()
- Remove legacy role string fallback in deps.py require_admin/require_write
- Add migration 0112: Create Role records for built-in roles and link role_id
- KI-Kommentar: Legacy Role Bypass entfernt — alle Admins müssen echte role_id haben
Problem 2: Enforce API token scopes
- Add _token_scopes check in require_permission() in deps.py
- When _token_scopes is set (API token auth), required permission must be in scopes
- When _token_scopes not set (session auth), normal permission check applies
Problem 3: Migration chain verification
- Chain is already linear: 0027→0028_rls_force→0028_user_preferences→0029
- user_preferences table confirmed exists in DB
- No duplicate revision IDs found
Problem 4: RLS for remaining tenant tables
- Add migration 0111: Dynamic RLS activation for any remaining tables with tenant_id
- Login tables and global tables explicitly excluded
- DB check shows 0 tables currently missing RLS (safety net migration)
Problem 5: Permission cache invalidation on tenant switch
- Add invalidate_permission_cache() call in switch_tenant() for old tenant
- Stale cached permissions from old tenant no longer leak
Problem 6+7: Guest system removal
- Remove get_current_guest() from deps.py
- Remove guest_auth.py router from main.py and routes/__init__.py
- Rewrite guests.py to use regular User/UserTenant with role=guest
- Remove GuestUser/GuestInvitation from models/__init__.py
- Add migration 0113: Migrate guest_users to regular users, drop guest tables
- Update frontend GuestLogin/GuestContacts to redirect to normal pages
- KI-Kommentar: Guest-System umgebaut — Guests sind jetzt reguläre User mit role=guest
- Add tests/test_permission_system_live.py: 33 live tests against real PostgreSQL
testing RBAC, ABAC, RLS, cross-tenant isolation, guest access, entity sharing,
field-level permissions, role invalidation, group permissions, membership suspension
- fix(contacts): delete route uses contacts:delete instead of contacts:write
The delete_contact and delete_contact_person routes were checking contacts:write
permission instead of contacts:delete, allowing users without delete permission
to delete contacts.
- fix(contacts): DeleteContactCommand passes is_system_admin to service
DeleteContactCommand.run() was not passing is_system_admin from the session
to contact_service.delete_contact(), causing system admins to be blocked
by the row-level admin access check.
- fix(contacts): allow deletion of tenant-owned contacts
contact_service.delete_contact() required admin-level entity access for ALL
contacts, including tenant-owned ones (owner_id=None). Tenant-owned contacts
can now be deleted by any user with contacts:delete permission (already
verified by the route via require_permission).
Plugin route registration in main.py was mutating module-level router
singletons by appending require_active_plugin dependencies directly to
router.routes. This persisted across app instances, causing test routes
to inherit require_active_plugin checks and return 403 "plugin inactive"
when tests created their own FastAPI apps with those routers.
Fix: use app.include_router(router, dependencies=[plugin_dep]) which
adds dependencies at the app level without modifying the shared router.
Fixes 35 test failures across 4 test files:
- test_agent_memory.py (6 failures)
- test_external_agent_api.py (15 failures)
- test_graph_rag.py (7 failures)
- test_marketplace.py (7 failures)
- Use migration engine (crm_migration, BYPASSRLS) for default data seeding
in app/main.py instead of crm_api role which is RLS-enforced
- Skip domains PATCH for dockercompose apps in deploy_api() to avoid 422
- Regenerate migration_hashes.txt for 0085_restore_tenant_rls.py
- Add migration 0110: password_salt column to mail_accounts
LOGIN_TABLES (users, user_tenants, tenants, sessions, password_reset_tokens)
added to skip list. RLS on these tables blocked crm_auth from reading users
during login → 401 Invalid email or password.
crm_auth grants applied directly (no RLS) matching 0085 AUTH_TABLES.
⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert crm_auth beim Login.
Siehe 0085 AUTH_TABLES für die korrekten Grants.
Plugin migrations run after core migration 0085 which sets up RLS for
all known core tables. Plugin-created tables were left without RLS,
creating a critical multi-tenant isolation gap (84 tables affected).
The migration runner now automatically enables RLS on all newly created
tenant tables after validation:
- ENABLE + FORCE ROW LEVEL SECURITY
- Idempotent DROP IF EXISTS + CREATE fail-closed tenant isolation policy
- GRANT CRUD to crm_api and crm_worker
- ALTER TABLE OWNER TO crm_migration
Global tables (-- GLOBAL TABLE comment) are skipped.
Plugin activation was broken for ALL inactive plugins because
sync_notification_types() tried to INSERT search_reindex_complete (22 chars)
into type_key VARCHAR(20), causing StringDataRightTruncationError.
Alembic head: 0106 → 0107
unified_search plugin activation calls sync_notification_types() which
DELETEs stale rows from notification_types. App DB user (crm_api) lacked
DELETE permission, causing plugin activation to fail with
InsufficientPrivilegeError.
Alembic head: 0105 → 0106
Fixes 3 issues found by API integration tests:
1. migration_runner.py: Add GLOBAL TABLE exemption for tables without tenant_id
- New _extract_global_table_names() method parses -- GLOBAL TABLE: comments
- marketplace_listings is intentionally global (no tenant_id)
2. unified_search/migrations/0002_embeddings.sql: Remove companies table (does not exist),
add DO $$ BEGIN END $$ blocks to check table existence before ALTER
3. marketplace/migrations/0001_initial.sql: Add -- GLOBAL TABLE: marketplace_listings comment