31 KiB
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,featureInstanceIdworkflow_id,workflow,feature_coderequireNeutralization(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 --
ServicesBaginserviceCenter/services/serviceAgent/mainServiceAgent.py, exportiert viaserviceCenter/__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):
- KEIN
ServiceHub-- Die alteServiceHub-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. - KEIN
_WorkflowAutomationServiceHub-- Die alte parallele Service-Bag-Klasse inworkflowAutomation/wurde eliminiert und durchServicesBagersetzt. - KEIN
_ServicesAdapter-- Der alte Name fuerServicesBag. Umbenannt 2026-06-08. - 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()). - KEIN defensiver
getattrfuer garantierte Attribute (z.B.getattr(services, "chat", None)ist verboten wennchatgarantiert existiert). - 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/<featureCode>/. Jedes Feature hat ein main<Feature>.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<Feature>.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)
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
# 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
- Kein Aufwaerts-Import: Die Core-Schicht (interfaces) importiert NIEMALS direkt aus
features/. Alle Feature-spezifische Logik wird ueber Hooks und die Registry aufgerufen. - Feature raeumt selbst auf: Jedes Feature ist verantwortlich fuer seine eigenen Daten (eigene DB, eigene Models). Die Core-Schicht delegiert Lifecycle-Events.
- Fail-safe: Hook-Fehler werden geloggt, brechen aber nicht den Boot oder die Mandate-Deletion ab.
- 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 (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
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.- 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. - Fallback
[key]: Wenn eine Uebersetzung fehlt, gibtt()[key]zurueck (z.B.[Speichern]), damit fehlende Uebersetzungen im UI sofort sichtbar sind. - Keine lokalen Resolve-Helfer: Alle Label-Aufloesung laeuft ueber
resolveText()(zentral ini18nRegistry.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_LANGUAGEContextVar (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 (oderlangwenn gesetzt), dannxx, dann erster nicht-leerer Wert (keine eckigen Klammern — das ist Inhalt, kein fehlender UI-Key)TextMultilingual→model_dump()dann dict-LogikNone→""
Boot-Reihenfolge
- Module laden →
@i18nModelDecorators +t()-Aufrufe registrieren Keys in_REGISTRY _syncRegistryToDb():- Scannt Route-Dateien nach
routeApiMsg("…")(api.* Keys) _registerNavLabels(): Navigation-Labels ausNAVIGATION_SECTIONS(nav.* Keys)_registerFeatureUiLabels():FEATURE_LABELundUI_OBJECTSLabels (nav.* Keys)_registerRbacLabels():DATA_OBJECTS,RESOURCE_OBJECTS,TEMPLATE_ROLES,QUICK_ACTIONSLabels (rbac.data, rbac.resource, rbac.role, rbac.quickaction Keys)- Merged Gateway-Keys ins xx-Basisset (UI-Keys bleiben erhalten)
- Scannt Route-Dateien nach
_loadCache(): Laedt alleUiLanguageSetsin 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.<routeModuleName> (HTTPExceptions), table.<ClassName> (Model-Labels), table.<ClassName>.<fieldName> (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):
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):
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:
<span>{conn.runningJobs[0].progressMessage || t('Synchronisierung läuft...')}</span>
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.<Model> fuer datenbasierten RBAC. FK-Felder werden automatisch via enrichRowsWithFkLabels aufgeloest (siehe FK Label Resolution).
UI-seitig sind diese Endpunkte unter /mandates/{m}/trustee/{i}/data-tables[?tab=<key>] (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<Feature>/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
connectionReferenceund unwrappt typisierte Envelopes (FeatureInstanceRef,DataRef) automatisch -- Legacy-Aktionen bleiben funktionsfaehig. - Save-with-errors ist explizit erlaubt (AC-9-Patch im
CanvasHeader); Run wird mitexecuteBlockedReasonblockiert, der Save-Toast meldet Pflicht-Fehler-Anzahl. - DB-Hygiene ist optional via
script_migrate_feature_instance_refs.py(--dry-runstandard) -- 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 untermodules/security/. - Code-Konventionen (Projekt): Interne Hilfsfunktionen mit Präfix
_(duerfen NICHT extern importiert werden); Bezeichner in camelCase für Variablen und Funktionen.