fix(#356): DSAR-Sammlung auf Contract-Zugriff umgestellt — 4 Core→Plugin-Imports (mail/tasks/calendar/kommunikation) nutzen jetzt get_contract(); MailContract exponiert MailAccount; ImportError-Fallback-Semantik unverändert; Checker 4→0 Verstöße; DSAR-Suite 4/4 passed
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
fixes #356
This commit is contained in:
@@ -12,6 +12,15 @@
|
||||
|
||||
**Gates:** ruff exit=0 · tsc --noEmit exit=0 · pytest 11 passed · Vitest 2 passed
|
||||
|
||||
## Welle 2 — Cross-Plugin/DSAR-Fix (2026-08-27)
|
||||
|
||||
| Finding | Issue | Fix | Verifikation (Live-Messung) |
|
||||
|---|---|---|---|
|
||||
| 4 Core→Plugin-Imports in `core/jobs.py` DSAR-Sammlung (mail/tasks/calendar/kommunikation Models direkt importiert statt über Contracts) | [#356](https://forgejo.media-on.de/Leopoldadmin/leocrm/issues/356) | 4 Blöcke auf `get_contract()` umgestellt (bestehende Registry, ARCH-014-Schutz); MailContract exponiert `MailAccount`; ImportError-Fallback-Semantik unverändert | Checker vor Fix: 4 Verstöße → nach Fix: **0 Verstöße** (480 Dateien); DSAR-Suite `test_g1_dsar.py` 4/4 passed (Funktionserhalt); ruff: nur 2 Vorbestand-N811 |
|
||||
| P16 manuell klassifiziert: `app.models.contact`-Imports in core/jobs.py + worker.py sind KEINE Verstöße (Contact liegt im Core-Models-Layer) | — | Keine Aktion nötig, dokumentiert in #356 | Checker-Regex deckt nur `app.plugins.*` ab — korrekt so |
|
||||
|
||||
**Gates:** ruff modified-files grün (2 Vorbestand N811 ausgenommen) · Cross-Plugin-Checker 0 · pytest DSAR 4/4
|
||||
|
||||
## Welle 1 — Plugin-Lifecycle-Fix (2026-08-27)
|
||||
|
||||
| Finding | Issue | Fix | Verifikation (Live-Messung) |
|
||||
|
||||
+31
-18
@@ -172,6 +172,7 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
|
||||
from app.models.contact import Contact
|
||||
from app.models.notification import Notification
|
||||
from app.models.user import User
|
||||
from app.plugins.builtins.contracts import get_contract
|
||||
|
||||
uid = PyUUID(user_id)
|
||||
tid = PyUUID(tenant_id)
|
||||
@@ -256,13 +257,16 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
|
||||
# ── Categories promised by the dsgvo-export route docstring ──
|
||||
# (G1-b: mail accounts, tasks, calendar entries, comm messages)
|
||||
try:
|
||||
from app.plugins.builtins.mail.models import MailAccount
|
||||
mail_contract = get_contract("mail")
|
||||
if mail_contract is None:
|
||||
raise ImportError("mail plugin not active")
|
||||
mail_account_m = mail_contract.MailAccount
|
||||
|
||||
mail_accounts = (
|
||||
await db.execute(
|
||||
sa_select(MailAccount).where(
|
||||
MailAccount.tenant_id == tid,
|
||||
MailAccount.user_id == uid,
|
||||
sa_select(mail_account_m).where(
|
||||
mail_account_m.tenant_id == tid,
|
||||
mail_account_m.user_id == uid,
|
||||
).limit(500)
|
||||
)
|
||||
).scalars().all()
|
||||
@@ -280,14 +284,17 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
|
||||
pass
|
||||
|
||||
try:
|
||||
from app.plugins.builtins.tasks.models import Task as TaskModel
|
||||
tasks_contract = get_contract("tasks")
|
||||
if tasks_contract is None:
|
||||
raise ImportError("tasks plugin not active")
|
||||
task_m = tasks_contract.Task
|
||||
|
||||
tasks = (
|
||||
await db.execute(
|
||||
sa_select(TaskModel).where(
|
||||
sa_or_(TaskModel.owner_id == uid, TaskModel.assigned_to == uid),
|
||||
TaskModel.tenant_id == tid,
|
||||
TaskModel.deleted_at.is_(None),
|
||||
sa_select(task_m).where(
|
||||
sa_or_(task_m.owner_id == uid, task_m.assigned_to == uid),
|
||||
task_m.tenant_id == tid,
|
||||
task_m.deleted_at.is_(None),
|
||||
).limit(1000)
|
||||
)
|
||||
).scalars().all()
|
||||
@@ -305,14 +312,17 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
|
||||
pass
|
||||
|
||||
try:
|
||||
from app.plugins.builtins.calendar.models import CalendarEntry as CalEntry
|
||||
cal_contract = get_contract("calendar")
|
||||
if cal_contract is None:
|
||||
raise ImportError("calendar plugin not active")
|
||||
cal_entry_m = cal_contract.CalendarEntry
|
||||
|
||||
cal_entries = (
|
||||
await db.execute(
|
||||
sa_select(CalEntry).where(
|
||||
CalEntry.tenant_id == tid,
|
||||
CalEntry.owner_id == uid,
|
||||
CalEntry.deleted_at.is_(None),
|
||||
sa_select(cal_entry_m).where(
|
||||
cal_entry_m.tenant_id == tid,
|
||||
cal_entry_m.owner_id == uid,
|
||||
cal_entry_m.deleted_at.is_(None),
|
||||
).limit(1000)
|
||||
)
|
||||
).scalars().all()
|
||||
@@ -330,13 +340,16 @@ async def _dsar_collect_user_data(db: Any, tenant_id: str, user_id: str) -> dict
|
||||
pass
|
||||
|
||||
try:
|
||||
from app.plugins.builtins.kommunikation.models import CommMessage
|
||||
komm_contract = get_contract("kommunikation")
|
||||
if komm_contract is None:
|
||||
raise ImportError("kommunikation plugin not active")
|
||||
comm_msg_m = komm_contract.CommMessage
|
||||
|
||||
comm_messages = (
|
||||
await db.execute(
|
||||
sa_select(CommMessage).where(
|
||||
CommMessage.sender_id == uid,
|
||||
CommMessage.tenant_id == tid,
|
||||
sa_select(comm_msg_m).where(
|
||||
comm_msg_m.sender_id == uid,
|
||||
comm_msg_m.tenant_id == tid,
|
||||
).limit(1000)
|
||||
)
|
||||
).scalars().all()
|
||||
|
||||
@@ -17,20 +17,21 @@ instead of importing from internal modules directly.
|
||||
from __future__ import annotations
|
||||
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
from app.plugins.builtins.mail.models import Mail
|
||||
from app.plugins.builtins.mail.models import Mail, MailAccount
|
||||
|
||||
|
||||
class MailContract:
|
||||
"""Public API surface for the mail plugin.
|
||||
|
||||
Exposes the ``Mail`` ORM model so that other plugins can query the
|
||||
mails table without importing from ``mail.models`` directly.
|
||||
Exposes ORM models so that other plugins can query the mail tables
|
||||
without importing from ``mail.models`` directly.
|
||||
"""
|
||||
|
||||
contract_name = "mail"
|
||||
|
||||
# ─── models ───
|
||||
Mail = Mail
|
||||
MailAccount = MailAccount
|
||||
|
||||
@classmethod
|
||||
def get_function(cls, name: str):
|
||||
|
||||
Reference in New Issue
Block a user