# Gateway -- Architektur ## Überblick Das Gateway ist das Python-Backend der PowerOn-Plattform (FastAPI, PostgreSQL). Es kapselt Mandanten- und Feature-Logik hinter einer REST-API und folgt einer klaren Schichtenfolge: **Connectors** sprechen externe Systeme und Datenbanken an, **Interfaces** normalisieren Zugriffe und DTOs, **Services** bündeln fachliche Operationen darauf, und das **ServiceCenter** löst diese Services pro Request mit einheitlichem Kontext auf. Routen in `modules/routes/` sind der HTTP-Einstieg; Security-Middleware und gemeinsame Utilities liegen in `modules/security/` bzw. `modules/shared/`. Feature-spezifische Domänen leben in `modules/features/` als weitgehend autonome Module. ## Modulstruktur Unter `platform-core/modules/` (Kontext-Audit): | Modul | Zweck | |-------|-------| | `aicore/` | Model-Registry, Model-Selector, Provider-Plugins (Anthropic, OpenAI, Mistral, Perplexity, Tavily, PrivateLLM) | | `auth/` | Authentifizierung, CSRF, Token-Refresh-Middleware, JWT | | `connectors/` | DB-Connector (PostgreSQL), Provider-Subpakete (Microsoft, Google, ClickUp, FTP, Infomaniak), Ticket/Messaging/Geo-Konnektoren. Pro Provider registrieren ServiceAdapter (`OutlookAdapter`, `OneDriveAdapter`, `SharepointAdapter`, `TeamsAdapter`, `CalendarAdapter`, `ContactsAdapter`, `GmailAdapter`, `DriveAdapter`, `KdriveAdapter`, …) die UDB-Services. Adapter-Registry pro Connector ist `_SERVICE_MAP` | | `datamodels/` | Pydantic-Datenmodelle (u. a. Ai, Billing, Chat, Content, Files, Knowledge, Rbac, Subscription, UiLanguage, Workflow) | | `features/` | Feature-Module (autonome Domänen): workspace, graphicalEditor, commcoach, neutralization, realEstate, trustee, teamsbot | | `interfaces/` | DB-Interfaces (App, Billing, Chat, Knowledge, Management, Subscription), AI-Objects, RBAC, Features, Messaging | | `dbHelpers/` | DB-Hilfsfunktionen (Registry, Audit-Logger, FK-Resolver, Pagination, Multi-Tenant-Optimierungen) | | `nodeCatalog/` | Neutraler Container (L2): nodeDefinitions, portTypes, nodeAdapter, entryPoints | | `routes/` | REST-API-Routen (u. a. Admin, Billing, DataFiles, DataSources, i18n, Security, Store, System) | | `security/` | RBAC (`rbac.py`, `rbacCatalog.py`), Root-Access | | `serviceCenter/` | Zentrale Service-Orchestrierung (Registry, Resolver, Kontext, Haupt-Services, **ServicesBag**) | | `shared/` | Gemeinsame Utilities (attributeUtils, i18nRegistry, Konfiguration, Logging, …) | | `system/` | System-Konfiguration, Feature-Discovery (`registry.py`: `loadFeatureMainModules`, `loadFeatureRouters`) | | `workflows/` | Workflow-Engine mit Methoden, Aktionen und konsolidiertem Scheduler | | `workflowAutomation/` | System-Komponente (L5b Orchestrator): Graph-Editor, Execution Engine, Run-Scheduler | ## ServiceCenter (Kern-Orchestrierung) Das ServiceCenter ist die zentrale Schicht, die Kern- und importierbare Services verbindet. Pro Request/Session wird ein **`ServiceCenterContext`** (`serviceCenter/context.py`) mitgeführt und propagiert u. a.: - `user: User` (gesamtes User-Objekt), `mandateId`, `featureInstanceId` - `workflow_id`, `workflow`, `feature_code` - `requireNeutralization` (optional) — Neutralisierungsflag auf Chat-Ebene Die öffentliche API umfasst u. a. `getService(key, context)` zur Auflösung einer Service-Instanz; registrierte Services sind in `registry.py` als **CORE_SERVICES** und **IMPORTABLE_SERVICES** hinterlegt, mit feature-spezifischer Auflösung und optionaler Vorabladung (`preWarm`). ### Services (`serviceCenter/services/`) | Service | Hauptmodul (Auszug) | Zweck | |---------|---------------------|-------| | `serviceAgent` | `mainServiceAgent.py` | AI-Agent mit vielen Tools (u. a. Dateien, Suche, Container, Web, Mail) | | `serviceAi` | `mainServiceAi.py` | AI-Call-Gateway: Neutralisierung, Billing-Preflight, Provider-Auswahl, Streaming, Extraktion, Generierung | | `serviceKnowledge` | `mainServiceKnowledge.py` | Knowledge Store: Indexierung, semantische Suche, RAG-Kontext (`buildAgentContext`) | | `serviceBilling` | `mainServiceBilling.py` | Billing-Checks, Transaktionen | | `serviceChat` | `mainServiceChat.py` | Chat-Persistenz | | `serviceExtraction` | `mainServiceExtraction.py` | Dokument-Extraktion (PDF, DOCX, XLSX, …) | | `serviceGeneration` | `mainServiceGeneration.py` | Dokument-Generierung | | `serviceMessaging` | `mainServiceMessaging.py` | E-Mail, Notifications | | `serviceSubscription` | `mainServiceSubscription.py` | Subscription-Verwaltung | | `serviceWeb` | `mainServiceWeb.py` | Web-Scraping | **Hinweis (Agent):** Agent-Tools liefern Rohdaten; Neutralisierung vor dem Versand an ein Sprachmodell erfolgt zentral im **serviceAi** (`mainServiceAi.py`), nicht in den Tools selbst. ### Einordnung (Framework) Daten- und Steuerfluss: **Client oder Workflow → Service Center → Service → Interface → Connector → externes System**. Das Service Center bündelt dabei Erkennbarkeit (Registry), Konfiguration und querschnittliche Aspekte wie Logging, RBAC-Objekt-Registrierung (`registerServiceObjects`) und konsistente Service-Lebenszyklen. ### ServicesBag (kanonischer Service-Zugriff) > **ARCHITEKTUR-REGEL: Es gibt genau EINEN Service-Bag -- `ServicesBag` in `serviceCenter/services/serviceAgent/mainServiceAgent.py`, exportiert via `serviceCenter/__init__.py`. KEINE Parallelstrukturen erstellen!** Der `ServicesBag` ist das einzige Objekt, ueber das Code auf Services zugreift. Er wird als `self.services` in Workflows, Agent-Tools, und Processing-Modulen propagiert. | Attribut | Typ | Zugriff | |----------|-----|---------| | `user` | `User` | Direkt | | `mandateId` | `str` | Direkt | | `featureInstanceId` | `str` | Direkt | | `chat` | `ChatService` | Property (lazy) | | `extraction` | `ExtractionService` | Property (lazy) | | `rbac` | `RbacClass` | Property (lazy) | | `getService(key)` | Service | Methode (dynamisch) | | `canAccessService(key)` | `bool` | Methode | **Verbotene Patterns (NICHT verwenden):** 1. **KEIN `ServiceHub`** -- Die alte `ServiceHub`-Klasse (`serviceCenter/serviceHub.py`) wurde eliminiert (2026-06-08). Sie exponierte rohe DB-Interfaces (`interfaceDbApp`, `interfaceDbComponent`, `interfaceDbChat`) als blanke Attribute -- unkontrollierter Zugriff ohne Service-Kapselung. 2. **KEIN `_WorkflowAutomationServiceHub`** -- Die alte parallele Service-Bag-Klasse in `workflowAutomation/` wurde eliminiert und durch `ServicesBag` ersetzt. 3. **KEIN `_ServicesAdapter`** -- Der alte Name fuer `ServicesBag`. Umbenannt 2026-06-08. 4. **KEINE blanken Interface-Properties** auf Service-Bags (z.B. `services.interfaceDbComponent`). Zugriff auf DB-Operationen erfolgt AUSSCHLIESSLICH ueber Service-Methoden (z.B. `services.chat.getFile()`, `services.chat.createFile()`). 5. **KEIN defensiver `getattr`** fuer garantierte Attribute (z.B. `getattr(services, "chat", None)` ist verboten wenn `chat` garantiert existiert). 6. **KEINE manuellen `ServiceCenterContext`-Konstruktionen** in Consumer-Code. Der Kontext wird vom ServicesBag propagiert. **ChatService als Kapselungsschicht:** Der `ChatService` (`serviceCenter/services/serviceChat/mainServiceChat.py`) kapselt alle File/Folder-CRUD-Operationen und Workflow-Listing. Consumer greifen NIE direkt auf `interfaceDbComponent` oder `interfaceDbChat` zu, sondern immer ueber `services.chat.*`: | Methode | Kapselt | |---------|---------| | `getFile(fileId)` | `interfaceDbComponent.getFile` | | `getFileData(fileId)` | `interfaceDbComponent.getFileData` | | `createFile(name, mime, content)` | `interfaceDbComponent.createFile` | | `createFileData(fileId, data)` | `interfaceDbComponent.createFileData` | | `saveUploadedFile(content, name)` | `interfaceDbComponent.saveUploadedFile` | | `deleteFile(fileId)` | `interfaceDbComponent.deleteFile` | | `copyFile(sourceId, newName)` | `interfaceDbComponent.copyFile` | | `createFolder(name, parentId)` | `interfaceDbComponent.createFolder` | | `getWorkflows(pagination)` | `interfaceDbChat.getWorkflows` | | `getMessages(workflowId, pagination)` | `interfaceDbChat.getMessages` | | `createActionItem(actionData)` | `interfaceDbChat._separateObjectFields` + `db.recordCreate` | ## Interfaces (DB-Schicht) Die genannten Module kapseln die Datenbankzugriffe bzw. die zugehörigen Fachverträge (Audit-Liste): | Interface | Verantwortlich für | |-----------|-------------------| | `interfaceDbApp.py` | User, Mandate, FeatureAccess, UserConnections, Preferences | | `interfaceDbBilling.py` | BillingAccount, BillingTransaction, Subscriptions | | `interfaceDbChat.py` | ChatWorkflow, ChatMessage, ChatDocument | | `interfaceDbKnowledge.py` | FileContentIndex, ContentChunk, RoundMemory (RAG/Knowledge Store) | | `interfaceDbManagement.py` | FileItem, Folder, Prompt, DataSource (mandantenweite Stammdaten), UiLanguageSet-Seeding | | `interfaceDbSubscription.py` | Subscription-Verwaltung | | `interfaceAiObjects.py` | AI-Call-Abstraktion (Text, Embedding, Vision, Streaming) | | `interfaceFeatures.py` | Feature-Instanz-Lifecycle, Template-Rollen-Kopie | | `interfaceRbac.py` | RBAC-Regelauswertung | Weitere Interface-Dateien im Ordner (z. B. Voice, Tickets, Messaging, Bootstrap) erfüllen dieselbe Normalisierungsrolle gegenüber Connectors bzw. externen APIs; die Tabelle oben entspricht der expliziten DB-/Kern-Zuordnung aus dem Kontext-Audit. ## Feature Lifecycle Hooks Features sind autonome Module unter `modules/features//`. Jedes Feature hat ein `main.py` mit Registrierungs- und Lifecycle-Funktionen. Die Core-Schicht ruft diese **dynamisch** via `system/registry.py` → `loadFeatureMainModules()` auf — es gibt **keine hardcodierten Feature-Imports** in den Interfaces. ### Pflicht-Funktionen (Feature-Registrierung) | Funktion | Beschreibung | |----------|-------------| | `getFeatureDefinition() -> Dict` | Feature-Metadaten: `code`, `label`, `icon`, `autoCreateInstance` | | `registerFeature(catalogService) -> bool` | RBAC-Objekte (UI, Resource, Data) im Katalog registrieren | | `getUiObjects() -> List[Dict]` | UI-Objekte fuer RBAC-Sichtbarkeit | | `getResourceObjects() -> List[Dict]` | Resource-Objekte fuer RBAC | | `getTemplateRoles() -> List[Dict]` | Template-Rollen mit AccessRules | ### Optionale Lifecycle Hooks Jedes Feature kann diese Funktionen in seinem `main.py` definieren. Die Core-Schicht ruft sie automatisch auf, wenn die entsprechende Situation eintritt: | Hook | Signatur | Wann aufgerufen | Wo aufgerufen | |------|----------|-----------------|---------------| | `onMandateDelete` | `(mandateId: str, instances: list) -> None` | Mandate wird geloescht (hard-delete) | `interfaceDbApp.py` (Cascade-Delete-Loop) | | `onBootstrap` | `() -> None` | App-Start (nach DB-Initialisierung) | `interfaceBootstrap.py` (Boot-Sequenz) | | `onInstanceCreate` | `(mandateId: str, instanceId: str, featureCode: str, templateWorkflows: list) -> int` | Feature-Instanz wird erstellt und das Feature hat Template-Workflows | `interfaceFeatures.py` (via graphicalEditor) | | `onStart` | `async (eventUser) -> None` | App-Start (Feature-spezifische Hintergrund-Prozesse) | `app.py` (Startup-Event) | | `onStop` | `async (eventUser) -> None` | App-Shutdown | `app.py` (Shutdown-Event) | | `getTemplateWorkflows` | `() -> List[Dict]` | Bootstrap / Instance-Create (Workflow-Templates bereitstellen) | `mainGraphicalEditor.onBootstrap()` / `onInstanceCreate()` | ### Aufruf-Pattern (Core-Seite) ```python from modules.system.registry import loadFeatureMainModules for featureCode, mod in loadFeatureMainModules().items(): hook = getattr(mod, "onMandateDelete", None) if hook: try: hook(mandateId, instances) except Exception as e: logger.warning(f"onMandateDelete hook for '{featureCode}' failed: {e}") ``` ### Implementierungs-Status Alle Features haben `onMandateDelete` implementiert: | Feature | DB | `onMandateDelete` | `onBootstrap` | `onInstanceCreate` | Besonderheiten | |---------|----|----|----|----|-----| | graphicalEditor | poweron_graphicaleditor | Yes | Yes | Yes | Vollstaendige Hooks | | trustee | poweron_trustee | Yes | — | — | 13 Models | | commcoach | poweron_commcoach | Yes | — | — | Nutzt `instanceId` statt `featureInstanceId` | | teamsbot | poweron_teamsbot | Yes | — | — | Nutzt `instanceId`; Child-Records via Session-FK | | neutralization | poweron_neutralization | Yes | — | — | 2 Models | | workspace | poweron_workspace | Yes | — | — | 1 Model | | realEstate | poweron_realestate | Yes | — | — | Fallback auf `mandateId` | | redmine | poweron_redmine | Yes | — | — | 3 Models inkl. Ticket-Mirrors | ### Beispiel: Neues Feature mit Lifecycle ```python # modules/features/myFeature/mainMyFeature.py FEATURE_CODE = "myFeature" def getFeatureDefinition() -> Dict[str, Any]: return {"code": FEATURE_CODE, "label": "My Feature", "icon": "mdi-star", "autoCreateInstance": False} def onMandateDelete(mandateId: str, instances: list) -> None: """Eigene Daten aufraumen wenn ein Mandate geloescht wird.""" # Hier: Verbindung zur eigenen DB, Records loeschen pass def onBootstrap() -> None: """Eigene System-Daten seeden beim App-Start.""" pass ``` ### Architektur-Prinzipien 1. **Kein Aufwaerts-Import:** Die Core-Schicht (interfaces) importiert NIEMALS direkt aus `features/`. Alle Feature-spezifische Logik wird ueber Hooks und die Registry aufgerufen. 2. **Feature raeumt selbst auf:** Jedes Feature ist verantwortlich fuer seine eigenen Daten (eigene DB, eigene Models). Die Core-Schicht delegiert Lifecycle-Events. 3. **Fail-safe:** Hook-Fehler werden geloggt, brechen aber nicht den Boot oder die Mandate-Deletion ab. 4. **Dynamisch erweiterbar:** Neue Features muessen nur die Hook-Funktion definieren — kein Code in der Core-Schicht muss angepasst werden. ### Workflow-Automation Models Die Workflow-Engine-Models leben kanonisch in `datamodels/datamodelWorkflowAutomation.py`: | Model | Beschreibung | DB | |-------|-------------|-----| | `AutoWorkflow` | Workflow-Definition (Graph, Triggers, Templates) | poweron_graphicaleditor | | `AutoVersion` | Versionierter Graph-Snapshot | poweron_graphicaleditor | | `AutoRun` | Laufende/abgeschlossene Ausfuehrung | poweron_graphicaleditor | | `AutoStepLog` | Protokoll pro Knoten-Ausfuehrung | poweron_graphicaleditor | | `AutoTask` | Human Tasks (Formulare, Approvals) | poweron_graphicaleditor | Die DB-Konstante `WORKFLOW_AUTOMATION_DATABASE = "poweron_graphicaleditor"` ist ebenfalls dort definiert (DB-Name ist historisch, Konstante wurde umbenannt 2026-06-08). Feature-interne Re-Exports in `features/graphicalEditor/datamodelFeatureGraphicalEditor.py` existieren fuer Backward-Kompatibilitaet. ## Schlüssel-Dateien | Datei / Pfad | Rolle | |--------------|-------| | `platform-core/app.py` | FastAPI-Anwendung (generischer Entry-Point), Routen, Middleware, EventManager, Scheduler-MainLoop | | `platform-core/modules/serviceCenter/__init__.py` | Service Center: `getService`, `preWarm`, RBAC-Registrierung für Service-Objekte | | `platform-core/modules/serviceCenter/context.py` | `ServiceCenterContext` pro Request | | `platform-core/modules/serviceCenter/registry.py` | Service-Registry (CORE / IMPORTABLE) | | `platform-core/modules/serviceCenter/resolver.py` | Auflösung von Service-Instanzen inkl. Cache | | `platform-core/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py` | `ServicesBag` -- kanonischer Service-Bag fuer Workflows/Agent/Processing | | `platform-core/modules/routes/*.py` | HTTP-Endpunkte, Aufruf in Richtung Service Center / Features (inkl. `routeI18n.py` für DB-backed i18n mit AI-Übersetzung) | | `platform-core/modules/interfaces/*.py` | Stabile Verträge und DB-Zugriffe (keine direkte Vendor-Logik in Services) | | `platform-core/modules/connectors/*.py` | Vendor-spezifische Adapter (Auth, Transport, Mapping) | | `platform-core/modules/security/*` | RBAC-Auswertung, RBAC-Katalog, Root-Access | | `platform-core/modules/auth/*` | CSRF, Token-Refresh-Middleware, JWT, OAuth | | `platform-core/modules/shared/*` | Querschnitt: Konfiguration, Audit-Logging, Events, Utilities | | `platform-core/modules/system/registry.py` | Feature-Discovery, Router-Laden, Katalog-Registrierung beim App-Start | | `platform-core/modules/workflows/workflowManager.py` | Zentrale Workflow-Steuerung (Workspace/Chat Workflows) | | `platform-core/modules/workflowAutomation/engine/executionEngine.py` | Graph-Execution-Engine: topoSort, Transit-Routing, `_normalizeToSchema` nach Execute, `flow.merge`-Wait, Resume-Schema-Validierung | | `platform-core/modules/workflowAutomation/scheduler/mainScheduler.py` | Konsolidierter Workflow-Scheduler | | `platform-core/modules/interfaces/interfaceBootstrap.py` | System-Bootstrap (Templates, Billing, Stripe) | | `platform-core/modules/interfaces/interfaceVoiceObjects.py` | Fassade Google STT/TTS, Billing-Callback Streaming | | `platform-core/modules/connectors/connectorVoiceGoogle.py` | Google Speech v1 + Translation + TTS-Client | | `platform-core/modules/routes/routeVoiceGoogle.py` | `/voice-google/*` inkl. STT-Streaming-WebSocket | ## Google Voice (STT / TTS) Siehe Kanon-Seite **[voice-google.md](voice-google.md)** (Batch- vs Streaming-Pfad, WS-Protokoll `open`/`end_of_single_utterance`, Parameter `model` / `lightweight` / `audioFormat`, Zuordnung CommCoach / Teamsbot / Workspace / Agent-Tools). ## i18n (Mehrsprachigkeit) Das Gateway nutzt ein DB-basiertes Sprachsystem (`UiLanguageSet`). Alle UI-sichtbaren Texte (HTTPException-Details, Model-Labels, API-Messages) werden ueber `t()` getaggt und per AI uebersetzt. | Komponente | Datei | Zweck | |------------|-------|-------| | `i18nRegistry.py` | `modules/shared/` | `t()`, `resolveText()`, `@i18nModel` Decorator, `_REGISTRY`, `_CACHE`, `_setLanguage`, Boot-Sync | | `attributeUtils.py` | `modules/shared/` | `getModelLabels()` / `getModelLabel()` lesen aus `i18nRegistry.MODEL_LABELS`, nutzen `resolveText()` | | `routeI18n.py` | `modules/routes/` | Admin-API: Sprachset-CRUD, AI-Uebersetzung, xx-Sync, TextMultilingual-Batch-Uebersetzung | | `app.py` | `platform-core/` | Boot-Hooks (`_syncRegistryToDb`, `_loadCache`), Request-Middleware (`_setLanguage`) | ### Architektur-Regeln 1. **`t()` NUR mit String-Literalen:** `t("Speichern")` ist korrekt, `t(variable)` ist verboten. Jeder i18n-Key muss als Literal im Code stehen, damit er beim Import registriert wird. 2. **Backend uebersetzt, Frontend rendert:** Das Backend liefert uebersetzte Strings via API. Das Frontend ruft `t()` nur fuer eigene statische UI-Texte auf, nie fuer Werte die vom Backend kommen. 3. **Fallback `[key]`:** Wenn eine Uebersetzung fehlt, gibt `t()` `[key]` zurueck (z.B. `[Speichern]`), damit fehlende Uebersetzungen im UI sofort sichtbar sind. 4. **Keine lokalen Resolve-Helfer:** Alle Label-Aufloesung laeuft ueber `resolveText()` (zentral in `i18nRegistry.py`). Keine `_resolveLabel()`, `_resolveTextMultilingual()`, `_storeLabelText()` etc. in Route-Dateien. ### Zentrale Funktionen **`t(key: str) -> str`** — Tag + Translate: - Bei Import: registriert Key in `_REGISTRY` - Bei Runtime: liefert `_CACHE[lang][key]`, Fallback `[key]` - Sprache kommt aus `_CURRENT_LANGUAGE` ContextVar (gesetzt durch Middleware) **`resolveText(value: Any, lang: Optional[str] = None) -> str`** — Universelle Label-Aufloesung: - `str` → `t(value)` (behandelt als i18n-Key) - `dict` → Nutzersprache aus Kontext (oder `lang` wenn gesetzt), dann `xx`, dann erster nicht-leerer Wert (keine eckigen Klammern — das ist Inhalt, kein fehlender UI-Key) - `TextMultilingual` → `model_dump()` dann dict-Logik - `None` → `""` ### Boot-Reihenfolge 1. Module laden → `@i18nModel` Decorators + `t()`-Aufrufe registrieren Keys in `_REGISTRY` 2. `_syncRegistryToDb()`: - Scannt Route-Dateien nach `routeApiMsg("…")` (api.* Keys) - `_registerNavLabels()`: Navigation-Labels aus `NAVIGATION_SECTIONS` (nav.* Keys) - `_registerFeatureUiLabels()`: `FEATURE_LABEL` und `UI_OBJECTS` Labels (nav.* Keys) - `_registerRbacLabels()`: `DATA_OBJECTS`, `RESOURCE_OBJECTS`, `TEMPLATE_ROLES`, `QUICK_ACTIONS` Labels (rbac.data, rbac.resource, rbac.role, rbac.quickaction Keys) - Merged Gateway-Keys ins xx-Basisset (UI-Keys bleiben erhalten) 3. `_loadCache()`: Laedt alle `UiLanguageSets` in den In-Memory-Cache (`_CACHE`) ### Request-Flow Middleware setzt `_setLanguage(lang)` aus `Accept-Language` Header → `t("Key")` liefert Uebersetzung aus `_CACHE[lang]` (O(1) Dict-Lookup, kein DB-Call). `resolveText()` nutzt denselben Mechanismus. ### TextMultilingual Felder vom Typ `TextMultilingual` speichern Benutzertexte mehrsprachig. `xx` ist das Pflichtfeld (Quelltext/Deutsch), weitere Sprachen werden dynamisch als Extra-Felder gespeichert. Bei neuer Sprache werden alle TextMultilingual-Felder per Batch-Job automatisch uebersetzt (`routeI18n._translateTextMultilingualFields`). On-Demand: `POST /api/i18n/translate-field`. ### Context-Namensraeume `ui` (Frontend), `api.` (HTTPExceptions), `table.` (Model-Labels), `table..` (Feld-Labels), `nav` (Navigation/Feature-Labels), `rbac.data` / `rbac.resource` / `rbac.role` / `rbac.quickaction` (RBAC-Katalog) **Entries-Identitaet:** Ein Entry wird durch `(key, context)` eindeutig identifiziert — derselbe Text kann mit verschiedenen Contexts existieren. ### BackgroundJob-Progress-Messages Hintergrundjobs laufen ausserhalb des Request-Kontexts und haben deshalb keinen `_CURRENT_LANGUAGE`-Wert. Walker schreiben deshalb einen **strukturierten i18n-Payload** in die DB und der Route-Handler uebersetzt server-side beim Read -- der Frontend ruft `t()` nie auf Backend-supplied Werten auf (Regel #2). **1. Walker schreibt strukturiert** (`platform-core/modules/serviceCenter/.../subConnectorSync*.py`): ```python progressCb( 0, messageKey="{n} Dateien verarbeitet, {indexed} indexiert", messageParams={"n": processed, "indexed": result.indexed}, ) ``` `messageKey` ist ein **String-Literal** (Pflicht, damit es scanbar bleibt). `JobProgressCallback` speichert `{key, params}` in `BackgroundJob.progressMessageData` (JSONB) und einen deutschen Best-Effort-Fallback in `progressMessage` (fuer Logs/Audit/Legacy). **2. Key-Registrierung** -- `progressCb(..., messageKey="…")` durchlaeuft NICHT `t()`, deshalb braucht jeder Key ein **string-literales** `t("…")` in der Feature-Registrierungsdatei (siehe `serviceKnowledge/_progressMessages.py`, `features/trustee/mainTrustee.py`). KEINE Schleifen ueber Listen mit `t(variable)` -- jede Zeile muss `t("LITERAL")` sein, sonst nimmt der Boot-Scan den Key nicht in `UiLanguageSet` auf. **3. Route uebersetzt server-side** (`routeJobs._serialiseJob`, `routeRagInventory`): ```python from modules.shared.i18nRegistry import resolveJobMessage out["progressMessage"] = ( resolveJobMessage(j.get("progressMessageData")) or j.get("progressMessage", "") ) ``` `resolveJobMessage(messageData)` ruft intern `t(key)` mit der Request-Sprache und substituiert die Params. Das ist analog zu `resolveText(value)` -- es ist der **einzige** zulaessige `t(variable)`-Pfad, weil er innerhalb der i18n-Infrastruktur liegt. **4. Frontend rendert 1:1** -- `progressMessage` ist bereits uebersetzt, die Render-Stellen lesen das Feld direkt: ```tsx {conn.runningJobs[0].progressMessage || t('Synchronisierung läuft...')} ``` Das `t('Synchronisierung läuft...')` ist ein lokales UI-Fallback (string-literal, ok), das `progressMessage` aus dem Backend geht nicht durch `t()`. ## Feature: Trustee -- Daten-Tabellen-Endpunkte Alle 13 Trustee-Tabellen sind ueber paginierte, RBAC-gefilterte GET-Endpunkte abrufbar. Die sechs CRUD-Modelle (`TrusteeOrganisation`, `TrusteeRole`, `TrusteeAccess`, `TrusteeContract`, `TrusteeDocument`, `TrusteePosition`) haben weiterhin die etablierten REST-Routen; sieben weitere (zuvor nur als JSON-Export oder Aggregat-Endpunkt verfuegbar) wurden ergaenzt: | Endpunkt | Modell | Zweck | |----------|--------|-------| | `GET /api/trustee/{instanceId}/data/accounts` | `TrusteeDataAccount` | Synchronisierter Kontenplan (read-only) | | `GET /api/trustee/{instanceId}/data/journal-entries` | `TrusteeDataJournalEntry` | Synchronisierte Buchungskoepfe (read-only) | | `GET /api/trustee/{instanceId}/data/journal-lines` | `TrusteeDataJournalLine` | Synchronisierte Buchungszeilen (read-only) | | `GET /api/trustee/{instanceId}/data/contacts` | `TrusteeDataContact` | Synchronisierte Kontakte (read-only) | | `GET /api/trustee/{instanceId}/data/account-balances` | `TrusteeDataAccountBalance` | Synchronisierte Saldenliste (read-only) | | `GET /api/trustee/{instanceId}/accounting/configs` | `TrusteeAccountingConfig` | Connector-Konfiguration (read-only; Secrets maskiert) | | `GET /api/trustee/{instanceId}/accounting/syncs` | `TrusteeAccountingSync` | Audit-Eintraege der Sync-Laeufe (read-only) | Alle sieben Endpunkte teilen sich den Helper `_paginatedReadEndpoint` (`routeFeatureTrustee.py`), der das Pattern aus `get_documents` / `get_positions` reproduziert: Unified Filter API (`mode=filterValues|ids`), `_validateInstanceAccess` fuer Instanz-Gating und `getRecordsetPaginatedWithRBAC` mit `featureCode` + `data.feature.trustee.` fuer datenbasierten RBAC. FK-Felder werden automatisch via `enrichRowsWithFkLabels` aufgeloest (siehe [FK Label Resolution](fk-label-resolution.md)). UI-seitig sind diese Endpunkte unter `/mandates/{m}/trustee/{i}/data-tables[?tab=]` (View `TrusteeDataTablesView`) zugaenglich; UI-Sichtbarkeit der Seite haengt am Permission-Eintrag `ui.feature.trustee.data-tables` (Template-Rollen `trustee-viewer`, `trustee-user`, `trustee-accountant`, Admin via Wildcard). ## Typed Action Architecture (4-Schichten) Seit Phase 1-5 (April 2026) folgt jede Workflow-Aktion einem strikten 4-Schichten-Modell. Quellen: `wiki/c-work/3-validate/2026-04-typed-action-architecture.md`, Tests in `platform-core/tests/unit/workflows/`, `platform-core/tests/integration/trustee/`. | Schicht | Modul / Verantwortung | Type-Anker | |---------|-----------------------|------------| | **1. Editor (Frontend)** | `ui-nyla/src/components/FlowEditor/nodes/shared/` -- `RequiredAttributePicker`, `DataPicker` (strict-mode + Object-Drill-Down mit `*`-Wildcard), `paramValidation.ts` | Action-Signaturen aus dem Catalog (`/api/workflow-automation/catalog`) | | **2. Action / Method** | `platform-core/modules/workflows/methods/method/actions/*.py` (`extractFromFiles`, `processDocuments`, `syncToAccounting`, ...) | Typisierte Pydantic-Eingabe + `ActionResult`-Output, `FeatureInstanceRef` als Discriminator-Envelope | | **3. Adapter** | `platform-core/modules/nodeCatalog/nodeDefinitions/registerNodeWithMethod` -- leitet Node-Definitions aus Action-Signaturen ab, liefert Snapshot fuer den Editor (`adapter_validator`) | Snapshot-Test `test_staticNodesHaveNoDriftAgainstLiveMethods` (`_KNOWN_ADAPTER_DRIFTS = frozenset()`) | | **4. Runtime** | `platform-core/modules/workflowAutomation/engine/executionEngine.py` -- `executeGraph` mit `materializeFeatureInstanceRefs` + `materializeConnectionRefs`, `_unwrapTypedRef` fuer Action-Calls | Migrations-Helper in `workflowAutomation/engine/featureInstanceRefMigration.py` + DB-CLI `scripts/script_migrate_feature_instance_refs.py` | Konsequenzen: - **Single source of truth** ist die Action-Signatur. Editor, Adapter, Runtime und AI-Agent (`actionToolAdapter`) lesen alle dieselbe Catalog-Sicht. - **Pick-not-push** auf Schicht 4: Runtime resolviert `connectionReference` und unwrappt typisierte Envelopes (`FeatureInstanceRef`, `DataRef`) automatisch -- Legacy-Aktionen bleiben funktionsfaehig. - **Save-with-errors** ist explizit erlaubt (AC-9-Patch im `CanvasHeader`); Run wird mit `executeBlockedReason` blockiert, der Save-Toast meldet Pflicht-Fehler-Anzahl. - **DB-Hygiene** ist optional via `script_migrate_feature_instance_refs.py` (`--dry-run` standard) -- ohne Skript laufen Workflows korrekt, der Editor zeigt aber bis zum naechsten Save noch das Legacy-Format. ## Layer-Hierarchie (strikte Importrichtung) ``` shared (L0) → 0 ausgehende Imports datamodels (L1) → nur shared connectors (L2) → shared, datamodels nodeCatalog (L2) → shared, datamodels dbHelpers (L3) → shared, datamodels, connectors aicore (L3) → shared, datamodels, connectors interfaces (L4) → L0-L3 + security, system, nodeCatalog system (L4) → L0-L3 + nodeCatalog security (L4) → L0-L3 auth (L4) → L0-L4 serviceCenter (L5a) → L0-L4 workflows (L5a) → L0-L4 features (L5a) → L0-L4 + serviceCenter, workflows workflowAutomation (L5b) → L0-L5a (Orchestrator, darf von L5a importieren) routes (L6) → L0-L5b demoConfigs (L6) → L0-L5b app (L7) → alle (Composition Root) ``` Jede Schicht importiert NUR von niedrigeren Schichten. `workflowAutomation` (L5b) ist der Orchestrator ueber `workflows`/`serviceCenter`/`features` — Imports VON L5a NACH L5b sind VERBOTEN (keine Rueckkanten). Alle frueheren Violations (interfaces→WA, system→WA, serviceCenter→WA, workflows↔serviceCenter bidirektional) sind seit 2026-06-08 geloest. Seit 2026-06-09 sind auch alle **Intra-Modul-Zyklen** (bidirektionale Imports zwischen Dateien innerhalb desselben Modul-Ordners) bereinigt — u.a. via Protocol-Typen in `serviceCenter/core/types.py`, Logik-Extraktion (`flagResolution`, `_valueKindResolver`, `_runNotifications`) und Lifecycle-Hook-Patterns. ## Regeln / Invarianten - **Schichten:** Connectors sind anbieterspezifisch und ersetzbar; **Services hängen von Interfaces ab, nicht direkt von Connectors**. Geschäftslogik und Guardrails liegen in den Services. - **Kein Aufwaerts-Import:** Interfaces importieren NICHT von features, routes oder serviceCenter. Feature-Interaktion laeuft ueber Lifecycle Hooks (siehe oben). - **Datenbank:** Persistenz und die genannten Domänen-Reads/Writes laufen über die **Interface**-Schicht, nicht ad hoc aus Routen heraus. - **Service Center:** Aufrufe laufen über die zentrale Auflösung (`getService` + Kontext); Workflows und Routen konsumieren dieselbe Landschaft. - **Sicherheit & Governance:** RBAC und Geheimnisse werden zentral geführt; Service-Aufrufe und Workflow-Schritte sollen nachvollziehbar sein (Audit); Kontingente und Limits pro Mandant/Service sind vorgesehen. - **HTTP-Einstieg:** Globale Middleware (CSRF, Token-Refresh unter `modules/auth/`); RBAC-Auswertung und Katalog unter `modules/security/`. - **Code-Konventionen (Projekt):** Interne Hilfsfunktionen mit Präfix **`_`** (duerfen NICHT extern importiert werden); Bezeichner in **camelCase** für Variablen und Funktionen.