Skip to content

ADR-0069: Organisation und Projekt als getrennte Rollen-Ebenen

  • Status: accepted
  • Datum: 2026-10-09
  • Phase: Multi-Tenant / Prozessmanagement
  • Bezug: ADR-0032, ADR-0052, ADR-0053, ADR-0060, ADR-0061, ADR-0068
  • Ersetzt: die Projektrollen aus ADR-0052 (owner / editor / reader, Übernahme von Altprojekten) sowie project_contacts und die org-weite Kommunikationsrolle aus ADR-0060 / ADR-0061

Kontext

Rechte und Kommunikation hingen an drei unabhängigen Stellen: Projektmitgliedschaft mit owner / editor / reader für Web-Konten (ADR-0052), Feldkontakte in project_contacts für Personen ohne Konto (ADR-0060) und eine org-weite Kommunikationsrolle persons.role_name (field / office / leadership). Wer „Leitung“ war, bekam Alarme aller Projekte; ein Feldmitarbeiter in zwei Projekten schrieb in die Unterhaltung, in die zuletzt geschrieben wurde. Mitarbeiter ohne Web-Login (nur Telegram/WhatsApp) konnten nicht Projektmitglied sein, Web-Konten nicht Feldkontakt. Die Frage „wer darf in diesem Projekt was, und wer bekommt welche Nachricht?“ hatte keine einzige Antwort.

Entscheidung

  1. Zwei Ebenen.
    • Organisation (= Mandant): Mitarbeiter sind app_users, mit oder ohne Web-Login. Ohne Login ist z. B. der Feldmitarbeiter, der nur über Telegram oder WhatsApp erreichbar ist. Die Org-Rollen bleiben: Admin (org_admin), Staff (staff), Gast (customer) (ADR-0053).
    • Projekt: Mitglieder sind Mitarbeiter der Organisation mit der Projektrolle Leitung (lead), Büro (office) oder Feld (field).
  2. Tabelle project_members(project_id, user_id, person_id, role), user_id und person_id beide NOT NULL, eindeutig je (project_id, person_id) und (project_id, user_id). Mehrere Leitungen sind erlaubt. Wer ein Projekt anlegt, wird Leitung. Die letzte Leitung kann nur ein Org-Admin entfernen oder herabstufen.
  3. Rechte = Schnittmenge aus Org-Recht und Projektrolle (app/projects/access.py):

    Recht Leitung Büro Feld
    Projekt sehen ✓ ✓ ✓
    Mitglieder verwalten, Projekteinstellungen (Briefing-Zeit, assistant_policy), Projekt löschen ✓ – –
    GIS/Datensätze bearbeiten (QGIS edit), Freigaben bearbeiten, Aufgaben zuweisen, Projekt-Dokumente ✓ ✓ –
    Leitungs-Alarm bei Stillstand ✓ – –
    Statusabfrage per Messenger ✓ ✓ –
    Briefings und Aufgaben per Messenger, Rückmeldungen – – ✓

    Ein Gast (ohne Org-Recht edit_records) bleibt unabhängig von der Projektrolle nur lesend. Der Org-Admin sieht und verwaltet jedes Projekt ohne Mitgliedschaft (er handelt als Leitung), ist aber nur Empfänger von Nachrichten, wenn er Mitglied ist. Staff und Gast sehen nur Projekte, in denen sie Mitglied sind. Das Übernehmen von Projekten ohne Mitglieder („unclaimed“) und der Kundenfilter über die Stammdaten-Organisation entfallen. 4. Stammdaten bleiben Stammdaten. Die Stammdaten-Organisationen (Unternehmen, Gemeinden in organizations), projects.project_manager und metadata_json.actor_assignments beschreiben externe Beteiligte und vergeben keine Rechte (ADR-0032). 5. Entfernt: Tabelle project_contacts und die Endpunkte /projects/{id}/contacts; die org-weite Kommunikationsrolle (persons.role_name = field / office / leadership, communication_role an /users). Die Mitarbeiterliste zeigt stattdessen project_count. 6. Kommunikation je Projekt (app/messaging/context.py, app/assistant/inbound.py). Ein Telegram-Bot bzw. eine WhatsApp-Nummer je Mandant, Identitäten je Person. Eine eingehende Nachricht wird zuerst vorläufig abgelegt (WhatsApp jetzt nach Person gefiltert), dann wird das Projekt in dieser Reihenfolge bestimmt: 1. Antwort auf eine offene Frage „Zu welchem Projekt gehört Deine Nachricht?“ (Nummer, Name oder Projektcode); 2. Projektname oder Projektcode im Text; 3. das einzige aktive Projekt des Absenders; 4. das Projekt, in das Linetra innerhalb von 12 Stunden geschrieben hat (REPLY_WINDOW, z. B. ein Briefing); 5. sonst schickt Linetra eine nummerierte Liste; die Nachricht wartet in einer Unterhaltung ohne Projekt (Zustand awaiting_project) und wird verarbeitet, sobald der Absender gewählt hat.

    Ist der Absender nur Leitung oder Büro, bekommt er die Statusantwort seiner Projekte. Feld-Nachrichten laufen im aufgelösten Projekt durch den Feld-Assistenten (ADR-0061); ist der Absender im aufgelösten Projekt Leitung oder Büro, gibt es eine Statusantwort. Absender ohne Mitgliedschaft bekommen „Du bist noch in keinem Projekt eingetragen …“. Unterhaltungen ohne Projekt gibt es nur noch für die Projektfrage. 7. Briefings gehen je Projekt an die Mitglieder mit Rolle Feld und nennen das Projekt. Der Leitungs-Alarm geht an die Leitungs-Mitglieder des betroffenen Projekts, in die Unterhaltung dieses Projekts. Folgeaufgaben (Chef/Büro) bleiben ohne Zuständigen. 8. Betrieb arbeitet je Projekt. GET /api/v1/operations/inbox, /inbox/messages, /review, /agent-runs und /reports verlangen project_id und prüfen den Zugriff; /operations/status, /tasks/summary und /alerts/summary (project_id optional) umfassen nur zugängliche Projekte. Die Aufgabenliste liefert assignee_name und assignee_is_member. 9. Migration (Major-Release): Tenant 0038_project_roles (owner → lead, editor → office, reader → field; project_contacts → Mitglieder; Personen mit role_name = 'leadership' → Leitung in allen aktiven Projekten; leert role_name für field / office / leadership), danach python -m app.projects.backfill_members (verknüpft jede Mitgliedschaft mit Mitarbeiter und Person, legt für Kontakte Staff-Mitarbeiter ohne Login an, verwirft Mitgliedschaften mandantenfremder Konten wie Systemadmins; idempotent), danach 0039_project_members_not_null (NOT NULL, löscht project_contacts; bricht laut ab, wenn unverknüpfte Zeilen übrig sind). Control 0050_project_member_roles spiegelt das für die gemeinsame pytest-DB. ./scripts/alembic_upgrade_all.sh führt alle drei Schritte aus.

Begründung

Eine Organisation hat Mitarbeiter, ein Projekt hat Mitglieder mit einer Aufgabe darin. Wer in Projekt A Leitung und in Projekt B Feld ist, lässt sich mit einer org-weiten Rolle nicht abbilden. Die Projektrolle beschreibt die Aufgabe im Projekt, die Org-Rolle bleibt die Obergrenze; die Schnittmenge verhindert, dass eine Projektrolle einem Gast Schreibrechte gibt. Weil jedes Mitglied ein Mitarbeiter ist, gelten Rechte, Telegram-Verknüpfung und Briefings für Personen mit und ohne Login gleich. Die Projektauflösung je Nachricht ersetzt das Raten über „die letzte Unterhaltung“: Eindeutige Fälle laufen ohne Rückfrage, mehrdeutige werden gefragt statt falsch zugeordnet.

Verworfene Alternativen

Alternative Warum verworfen
Kommunikationsrolle org-weit behalten, Projektrollen nur für Rechte Zwei Rollenmodelle für dieselbe Frage; Leitung bekäme weiter Alarme fremder Projekte
project_contacts neben project_members behalten Zwei Listen je Projekt, Konten ohne Login nicht als Mitglied abbildbar
Bot bzw. Nummer je Projekt Kosten und Verwaltung; der Mitarbeiter müsste wissen, wem er schreibt (ADR-0060)
Projekt immer per LLM aus der Nachricht bestimmen Nicht deterministisch; die Projektauflösung bleibt Code (ADR-0061)
Stammdaten-Projektleiter (project_manager) als Leitung Personen in Stammdaten sind externe Beteiligte, keine Mitarbeiter (ADR-0032)

Folgen

  • Breaking: Projektrollen heißen lead / office / field; /projects/{id}/contacts und communication_role sind entfernt; die Betrieb-Listen verlangen project_id. my_role an /projects liefert die neuen Werte.
  • Die Oberfläche für Mitglieder liegt unter Stammdaten → Projekte → Projekt und Kontomenü → Projekte → Projekt: jeder Mitarbeiter (auch ohne Web-Login) wählbar, Rolle je Mitglied, Spalten Telefon und Messenger.
  • Betrieb (Inbox, Freigaben, Berichte, Agent-Log) zeigt das in der Kopfzeile gewählte Projekt; ohne Auswahl erscheint ein Hinweis.
  • Ein Absender in mehreren Projekten bekommt gelegentlich die Projektfrage; eine Antwort mit Nummer, Name oder Code genügt.
  • Org-Admins ohne Mitgliedschaft bekommen keine Alarme und keine Briefings; wer benachrichtigt werden soll, muss Mitglied sein.
  • Postgres-Grants bleiben tenant-weit wie in ADR-0052; die Projektgrenze setzen API und Plugin.