From dc0f458f384687376c7d86795709585f9d92b9f3 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sat, 28 Mar 2026 18:12:32 +0100
Subject: [PATCH] BREAKING CHANGE API and persisted records use PowerOnModel
system fields: - sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy
Removed legacy JSON/DB field names: - _createdAt, _createdBy, _modifiedAt,
_modifiedBy Frontend (frontend_nyla) and gateway call sites were updated
accordingly. Database: - Bootstrap runs idempotent backfill
(_migrateSystemFieldColumns) from old underscore columns and selected
business duplicates into sys* where sys* IS NULL. - Re-run app bootstrap
against each PostgreSQL database after deploy. - Optional: DROP INDEX IF
EXISTS "idx_invitation_createdby" if an old index remains; new index:
idx_invitation_syscreatedby on Invitation(sysCreatedBy). Tests: - RBAC
integration tests aligned with current GROUP mandate filter and
UserMandate-based UserConnection GROUP clause; buildRbacWhereClause(...,
mandateId=...) must be passed explicitly (same as production request
context).
---
concepts/Plan-SystemFields-Architecture.md | 573 +++++++++++++++++++++
1 file changed, 573 insertions(+)
create mode 100644 concepts/Plan-SystemFields-Architecture.md
diff --git a/concepts/Plan-SystemFields-Architecture.md b/concepts/Plan-SystemFields-Architecture.md
new file mode 100644
index 0000000..508127b
--- /dev/null
+++ b/concepts/Plan-SystemFields-Architecture.md
@@ -0,0 +1,573 @@
+# Plan: System-Felder Architektur (inkl. Review-Findings)
+
+## Entscheidung: Echte Pydantic-Felder (KEIN extra='allow')
+
+Pydantic v2 erlaubt keine `_`-prefixed Felder als regulaere model_fields.
+Deshalb: **Felder OHNE Underscore-Prefix** in einer Base-Class, DB-Spalten werden umbenannt.
+
+Feldnamen: `sysCreatedAt`, `sysCreatedBy`, `sysModifiedAt`, `sysModifiedBy`
+
+Vorteile gegenueber extra='allow':
+- Felder sind in model_fields (type-safe, sichtbar fuer getModelAttributeDefinitions)
+- model_dump() inkludiert sie natuerlich
+- Kein extra='allow' noetig - wird global entfernt
+- Kein Spezial-Code in attributeUtils noetig
+
+---
+
+## Deploy-Reihenfolge (Boot-Sequenz)
+
+1. Deploy neuer Code
+2. Boot: `_ensureTableExists` fuegt neue `sysCreatedAt` etc. Spalten automatisch hinzu (da jetzt in model_fields)
+3. Boot: Bootstrap `_migrateSystemFieldColumns` kopiert Daten von alten Spalten in neue
+4. Applikation laeuft — referenziert nur noch `sysCreatedAt` etc.
+
+Alte `_createdAt`-Spalten bleiben in DB bestehen (kein DROP COLUMN), werden aber ignoriert.
+
+---
+
+## Ziel-Architektur (Datenfluss)
+
+```
+PostgreSQL (sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy)
+ |
+connectorDbPostgre.py (_saveRecord setzt die 4 Felder automatisch)
+ |
+PowerOnModel (echte Felder: sysCreatedAt etc., readonly, visible=false)
+ |
+Alle persistenten Models erben von PowerOnModel
+ |
+interfaceDbApp.py (KEIN _-Filter mehr, Konstruktor akzeptiert alle Felder)
+ |
+Route Endpoints (model_dump() inkludiert system fields)
+ |
+Frontend (API liefert sysCreatedAt etc., FormGenerator zeigt sie nur wenn visible=true)
+```
+
+---
+
+## Schritt 1: PowerOnModel Basisklasse erstellen
+
+Neue Datei: `gateway/modules/datamodels/datamodelBase.py`
+
+```python
+from typing import Optional
+from pydantic import BaseModel, Field
+from modules.shared.attributeUtils import registerModelLabels
+
+class PowerOnModel(BaseModel):
+ sysCreatedAt: Optional[float] = Field(
+ default=None,
+ description="Record creation timestamp (UTC, set by system)",
+ json_schema_extra={
+ "frontend_type": "timestamp",
+ "frontend_readonly": True,
+ "frontend_required": False,
+ "frontend_visible": False,
+ "system": True,
+ }
+ )
+ sysCreatedBy: Optional[str] = Field(
+ default=None,
+ description="User ID who created this record (set by system)",
+ json_schema_extra={
+ "frontend_type": "text",
+ "frontend_readonly": True,
+ "frontend_required": False,
+ "frontend_visible": False,
+ "system": True,
+ }
+ )
+ sysModifiedAt: Optional[float] = Field(
+ default=None,
+ description="Record last modification timestamp (UTC, set by system)",
+ json_schema_extra={
+ "frontend_type": "timestamp",
+ "frontend_readonly": True,
+ "frontend_required": False,
+ "frontend_visible": False,
+ "system": True,
+ }
+ )
+ sysModifiedBy: Optional[str] = Field(
+ default=None,
+ description="User ID who last modified this record (set by system)",
+ json_schema_extra={
+ "frontend_type": "text",
+ "frontend_readonly": True,
+ "frontend_required": False,
+ "frontend_visible": False,
+ "system": True,
+ }
+ )
+
+registerModelLabels(
+ "PowerOnModel",
+ {"en": "Base Record", "de": "Basisdatensatz"},
+ {
+ "sysCreatedAt": {"en": "Created At", "de": "Erstellt am", "fr": "Cree le"},
+ "sysCreatedBy": {"en": "Created By", "de": "Erstellt von", "fr": "Cree par"},
+ "sysModifiedAt": {"en": "Modified At", "de": "Geaendert am", "fr": "Modifie le"},
+ "sysModifiedBy": {"en": "Modified By", "de": "Geaendert von", "fr": "Modifie par"},
+ },
+)
+```
+
+### Label-Vererbung in attributeUtils.py (Review #6)
+
+`getModelAttributeDefinitions` liest Labels anhand des Model-Namens (z.B. "Prompt").
+Die Labels der Basis-Klasse "PowerOnModel" werden NICHT automatisch vererbt.
+
+**Fix:** `getModelAttributeDefinitions` anpassen: wenn ein Label fuer ein Feld
+nicht im aktuellen Model gefunden wird, in der MRO (Method Resolution Order)
+der Elternklassen suchen. Pseudocode:
+
+```python
+labels = getModelLabels(model_name, userLanguage)
+if not labels.get(fieldName):
+ for parent in modelClass.__mro__:
+ parentLabels = getModelLabels(parent.__name__, userLanguage)
+ if parentLabels.get(fieldName):
+ labels[fieldName] = parentLabels[fieldName]
+ break
+```
+
+---
+
+## Schritt 2: connectorDbPostgre.py anpassen
+
+DB-Spaltennamen von `_createdAt` auf `sysCreatedAt` etc. aendern:
+
+### 2a: _create_table_from_model (Zeile ~598)
+Vorher: `"_createdAt" DOUBLE PRECISION` etc. hardcoded append
+Nachher: ENTFERNEN — Spalten kommen jetzt automatisch aus model_fields via `_get_model_fields()`
+
+### 2b: _save_record columns-Liste (Zeile ~629)
+Vorher: `columns = [...] + ["_createdAt", "_createdBy", "_modifiedAt", "_modifiedBy"]`
+Nachher: `columns = ["id"] + [field for field in fields.keys() if field != "id"]`
+(system fields sind jetzt in model_fields, kein Append noetig)
+
+### 2c: KRITISCH — _save_record ON CONFLICT Klausel (Zeile ~689-694) (Review #2)
+Vorher:
+```python
+updates = ", ".join(
+ [f'"{col}" = EXCLUDED."{col}"'
+ for col in columns[1:]
+ if col not in ["_createdAt", "_createdBy"]]
+)
+```
+Nachher:
+```python
+updates = ", ".join(
+ [f'"{col}" = EXCLUDED."{col}"'
+ for col in columns[1:]
+ if col not in ["sysCreatedAt", "sysCreatedBy"]]
+)
+```
+Zweck: bei UPSERT werden sysCreatedAt/sysCreatedBy NICHT ueberschrieben.
+
+### 2d: _save_record Timestamp-String-Parsing (Zeile ~651) (Review #3)
+Vorher: `if col in ["_createdAt", "_modifiedAt"] and value is not None:`
+Nachher: `if col in ["sysCreatedAt", "sysModifiedAt"] and value is not None:`
+
+### 2e: KRITISCH — _saveRecord Metadata-Logik (Zeile ~746) (Review #11)
+Vorher:
+```python
+if "_createdAt" not in record:
+ record["_createdAt"] = currentTime
+```
+Problem: da sysCreatedAt ein model_field mit default=None ist, ist es
+bei model_dump() IMMER im Dict (als None). `"sysCreatedAt" not in record`
+waere deshalb NIE True.
+
+Nachher:
+```python
+if not record.get("sysCreatedAt"):
+ record["sysCreatedAt"] = currentTime
+ if effective_user_id:
+ record["sysCreatedBy"] = effective_user_id
+elif not record.get("sysCreatedBy"):
+ if effective_user_id:
+ record["sysCreatedBy"] = effective_user_id
+record["sysModifiedAt"] = currentTime
+if effective_user_id:
+ record["sysModifiedBy"] = effective_user_id
+```
+
+### 2f: _ensureTableExists (Zeile ~521)
+Vorher: `desired_columns |= {"_createdAt", "_modifiedAt", "_createdBy", "_modifiedBy"}`
+Nachher: ENTFERNEN (kommen aus model_fields)
+
+### 2g: _buildPaginationClauses (Zeile ~995)
+Vorher: `fields["_createdAt"] = "DOUBLE PRECISION"` etc.
+Nachher: ENTFERNEN (kommen aus model_fields)
+
+### 2h: getDistinctColumnValues (Zeile ~1193)
+Vorher: `fields["_createdAt"] = "DOUBLE PRECISION"` etc.
+Nachher: ENTFERNEN (kommen aus model_fields)
+
+### 2i: _system Tabelle (Review #7)
+Die interne `_system`-Tabelle hat hardcoded `_createdAt`/`_modifiedAt` in
+CREATE TABLE (Zeile ~326-333, ~447-454). `SystemTable` erbt von `BaseModel`.
+
+Empfehlung: `SystemTable` auf `PowerOnModel` umstellen, hardcoded
+`_createdAt`/`_modifiedAt` auf `sysCreatedAt`/`sysModifiedAt` aendern.
+Bootstrap-Migration muss auch `_system` beruecksichtigen.
+
+---
+
+## Schritt 3: Alle persistenten Models auf PowerOnModel umstellen
+
+### Kern-Datamodels (gateway/modules/datamodels/):
+
+- datamodelUam.py: User, Mandate, UserConnection, UserVoicePreferences, UserInDB
+- datamodelRbac.py: Role, AccessRule
+- datamodelMembership.py: UserMandate, FeatureAccess, UserMandateRole, FeatureAccessRole
+- datamodelFeatures.py: Feature, FeatureInstance
+- datamodelUtils.py: Prompt (ENTFERNE extra='allow')
+- datamodelFiles.py: FileItem (ENTFERNE extra='allow'), FileData
+- datamodelFileFolder.py: FileFolder
+- datamodelInvitation.py: Invitation
+- datamodelFeatureDataSource.py: FeatureDataSource
+- datamodelDataSource.py: DataSource
+- datamodelMessaging.py: MessagingSubscription
+- datamodelBilling.py: BillingAccount, BillingTransaction
+- datamodelSubscription.py: MandateSubscription
+- datamodelNotification.py: UserNotification
+- datamodelSecurity.py: Token, AuthEvent
+- datamodelKnowledge.py: persistente Knowledge-Models
+- datamodelChat.py: persistente Chat-Models
+
+### Feature-Datamodels (gateway/modules/features/):
+
+- commcoach/datamodelCommcoach.py: CoachingContext, CoachingSession, CoachingMessage, CoachingTask, CoachingScore, CoachingUserProfile, CoachingPersona, CoachingBadge
+- workspace/datamodelFeatureWorkspace.py: WorkspaceUserSettings
+- neutralization/datamodelFeatureNeutralizer.py: DataNeutraliserConfig
+- automation2/datamodelFeatureAutomation2.py: Automation2Workflow, Automation2WorkflowRun, Automation2HumanTask
+- trustee/datamodelFeatureTrustee.py: TrusteeOrganisation, TrusteeRole, TrusteeAccess, TrusteeContract, TrusteeDocument, TrusteePosition (ENTFERNE extra='allow'), TrusteeDataAccount, TrusteeDataJournalEntry, TrusteeDataJournalLine, TrusteeDataContact, TrusteeDataAccountBalance, TrusteeAccountingConfig, TrusteeAccountingSync
+- teamsbot/datamodelTeamsbot.py: TeamsbotSession, TeamsbotTranscript, TeamsbotBotResponse, TeamsbotSystemBot, TeamsbotUserAccount, TeamsbotUserSettings, TeamsbotConfig
+- automation/datamodelFeatureAutomation.py: AutomationDefinition, AutomationTemplate
+- realEstate/datamodelFeatureRealEstate.py: Land, Kanton, Gemeinde, Parzelle, Projekt, Dokument, Kontext
+
+### Connector-intern:
+- connectorDbPostgre.py: SystemTable (Review #7)
+
+### NICHT umstellen (reine DTOs):
+- PaginationParams, PaginatedResponse, AttributeDefinition, FilePreview, TextMultilingual
+- CreateContextRequest, UpdateContextRequest, etc. (Request/Response DTOs)
+- DataNeutralizerAttributes (DTO)
+- GeoPunkt, GeoPolylinie (embedded value objects)
+- Enums
+
+---
+
+## Schritt 4: Business-Feld-Duplikate entfernen
+
+Felder die durch die neuen System-Felder ersetzt werden:
+
+### datamodelFileFolder.py:
+- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt
+
+### datamodelFiles.py:
+- ENTFERNE: `creationDate: float` -> ersetzt durch sysCreatedAt
+
+### datamodelInvitation.py:
+- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt
+- ENTFERNE: `createdBy: str` -> ersetzt durch sysCreatedBy
+
+### datamodelFeatureDataSource.py:
+- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt
+
+### datamodelDataSource.py (Review #5):
+- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt
+
+### datamodelNotification.py (Review #5):
+- ENTFERNE: `createdAt: float` auf UserNotification -> ersetzt durch sysCreatedAt
+
+### datamodelSecurity.py (Review #5):
+- ENTFERNE: `createdAt: Optional[float]` auf Token -> ersetzt durch sysCreatedAt
+
+### datamodelMessaging.py:
+- ENTFERNE: `createdBy: str` -> ersetzt durch sysCreatedBy
+- ENTFERNE: `modifiedBy: str` -> ersetzt durch sysModifiedBy
+
+### commcoach/datamodelCommcoach.py:
+- ENTFERNE: `createdAt` auf 8 Models -> ersetzt durch sysCreatedAt
+- ENTFERNE: `updatedAt` auf mehreren Models -> ersetzt durch sysModifiedAt
+
+### teamsbot/datamodelTeamsbot.py:
+- ENTFERNE: `creationDate` auf 6 Models -> ersetzt durch sysCreatedAt
+- ENTFERNE: `lastModified` auf 4 Models -> ersetzt durch sysModifiedAt
+
+---
+
+## Schritt 5: _-Filter und Passthrough-Logik bereinigen
+
+### 5a: 44x _-Filter in interfaceDbApp.py entfernen
+
+Vorher (44 Stellen):
+```python
+cleanedRecord = {k: v for k, v in record.items() if not k.startswith("_")}
+return UserMandate(**cleanedRecord)
+```
+
+Nachher:
+```python
+return UserMandate(**record)
+```
+
+Da alle Models jetzt von PowerOnModel erben und die system fields als echte
+model_fields haben, akzeptiert der Konstruktor sie direkt.
+
+### 5b: Muster B — User.model_validate statt Denylist (Review #1)
+
+Die 2 Stellen mit Zusatzausschluss `hashedPassword`/`resetToken`/`resetTokenExpires`
+(Zeile ~652 und ~2560-2567) ersetzen durch:
+
+```python
+user = User.model_validate(userRecord)
+if not hasattr(user, 'roleLabels') or user.roleLabels is None:
+ # roleLabels Defaulting wie bisher
+ pass
+```
+
+Pydantic v2 `extra='ignore'` verwirft unbekannte Keys automatisch.
+Die Sicherheit steckt im Schema von `User` (Allowlist), nicht in
+einer parallelen Ausschlussliste (Denylist).
+
+### 5c: startswith("_") Passthrough entfernen (Review #4)
+
+In `interfaceDbApp.py` Zeile ~192 und `interfaceDbManagement.py` Zeile ~180:
+
+```python
+if fieldName.startswith("_"):
+ simpleFields[fieldName] = value
+```
+
+Diese Sonderbehandlung wird OBSOLET: `sysCreatedAt` ist ein regulaeres
+model_field und landet automatisch in simpleFields. Die `startswith("_")`-
+Branch kann ENTFERNT werden.
+
+### 5d: Gleiche Bereinigung in:
+- gateway/modules/interfaces/interfaceFeatures.py (9 Stellen, nur Muster A)
+- gateway/modules/interfaces/interfaceDbManagement.py
+
+### Inventar Muster A (Ziel-Model pro Block, interfaceDbApp.py):
+
+| Ziel-Model | Typische Funktionen / Bereich | Zeilen (ca.) |
+|------------|------------------------------|--------------|
+| `User` | getUsers, Listen, einzelner User | ~531, ~563, ~589, ~880, ~920, ~981, ~1044 |
+| `Mandate` | Mandanten-Filter fuer API | ~1332, ~1381 |
+| `UserMandate` | Membership | ~1797, ~1820, ~1872, ~2002, ~2596 |
+| `UserMandateRole` | Junction | ~2026, ~2123, ~2131, ~2617 |
+| `FeatureAccess` | Feature-Zugriff | ~2196, ~2219, ~2243, ~2292 |
+| `Invitation` | Einladungen | ~2430, ~2450, ~2471, ~2492, ~2513, ~2534 |
+| `FeatureInstance` | Instanz laden / nach Mandant | ~2637, ~2682 |
+| `Feature` | getFeatureByCode | ~2657 |
+| `UserNotification` | Benachrichtigungen | ~2706, ~2737 |
+| `AccessRule` | Regeln laden | ~2765, ~2786, ~3366 |
+| `Role` | Rollen nach Instanz/Code/Mandant/alle | ~2807, ~2832, ~3550, ~3571 |
+| `Token` | Tokens nach Connection/User | ~3031, ~3052 |
+
+### Sonderfall Token:
+Zeile ~3006: `Token(**tokens[0])` ohne vorherigen _-Filter — nach
+Umbenennung konsistent, aber an andere Token-Pfade angleichen.
+
+### Sonderfall Invitation + recordFilter:
+`getInvitationsByCreator` nutzt `recordFilter={"createdBy": creatorId}` (~2489).
+Nach Entfernung des Business-Felds auf `recordFilter={"sysCreatedBy": creatorId}` umstellen.
+
+### Sonderfall UserNotification + Sortierung:
+`getNotificationsByUser` sortiert mit `x.createdAt` (~2740).
+Nach Entfernung auf `x.sysCreatedAt` aendern.
+
+---
+
+## Schritt 6: extra='allow' global entfernen
+
+Entfernen aus:
+- datamodelUtils.py: Prompt (`ConfigDict(extra='allow')`)
+- datamodelFiles.py: FileItem (`ConfigDict(extra='allow')`)
+- features/trustee/datamodelFeatureTrustee.py: TrusteePosition (`model_config={"extra":"allow"}`)
+
+Keine `model_config` mit `extra='allow'` mehr in der gesamten Applikation.
+
+---
+
+## Schritt 7: Bootstrap-Migration (interfaceBootstrap.py)
+
+Neue Funktion `_migrateSystemFieldColumns(db)` in interfaceBootstrap.py,
+aufgerufen frueh in `initBootstrap()` (NACH initRootMandate, VOR allem anderen):
+
+```python
+def _migrateSystemFieldColumns(db: DatabaseConnector) -> None:
+ """Migrate old _createdAt/_createdBy/_modifiedAt/_modifiedBy columns
+ to new sysCreatedAt/sysCreatedBy/sysModifiedAt/sysModifiedBy columns.
+ Also migrates business field duplicates (createdAt, creationDate, etc.)
+ Idempotent: safe to run on every boot."""
+
+ COLUMN_RENAMES = {
+ "_createdAt": "sysCreatedAt",
+ "_createdBy": "sysCreatedBy",
+ "_modifiedAt": "sysModifiedAt",
+ "_modifiedBy": "sysModifiedBy",
+ }
+
+ BUSINESS_FIELD_MIGRATIONS = {
+ "FileFolder": {"createdAt": "sysCreatedAt"},
+ "FileItem": {"creationDate": "sysCreatedAt"},
+ "Invitation": {"createdAt": "sysCreatedAt", "createdBy": "sysCreatedBy"},
+ "FeatureDataSource": {"createdAt": "sysCreatedAt"},
+ "DataSource": {"createdAt": "sysCreatedAt"},
+ "UserNotification": {"createdAt": "sysCreatedAt"},
+ "Token": {"createdAt": "sysCreatedAt"},
+ "MessagingSubscription": {"createdBy": "sysCreatedBy", "modifiedBy": "sysModifiedBy"},
+ "CoachingContext": {"createdAt": "sysCreatedAt"},
+ "CoachingSession": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"},
+ "CoachingMessage": {"createdAt": "sysCreatedAt"},
+ "CoachingTask": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"},
+ "CoachingScore": {"createdAt": "sysCreatedAt"},
+ "CoachingUserProfile": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"},
+ "CoachingPersona": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"},
+ "CoachingBadge": {"createdAt": "sysCreatedAt"},
+ "TeamsbotSession": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"},
+ "TeamsbotTranscript": {"creationDate": "sysCreatedAt"},
+ "TeamsbotBotResponse": {"creationDate": "sysCreatedAt"},
+ "TeamsbotSystemBot": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"},
+ "TeamsbotUserAccount": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"},
+ "TeamsbotUserSettings": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"},
+ "_system": {"_createdAt": "sysCreatedAt", "_modifiedAt": "sysModifiedAt"},
+ }
+
+ # Fuer jede Tabelle in der DB:
+ # 1. Alte _-Spalten -> neue sys-Spalten kopieren (COLUMN_RENAMES)
+ # 2. Business-Felder -> sys-Felder kopieren (BUSINESS_FIELD_MIGRATIONS)
+ # 3. Nur wo sysX IS NULL (idempotent)
+ # 4. Neue Spalten werden automatisch von _ensureTableExists erstellt
+```
+
+---
+
+## Schritt 8: Gateway Calling References anpassen
+
+Alle Dateien, die `_createdAt`/`_createdBy`/`_modifiedAt`/`_modifiedBy` referenzieren:
+
+### Interfaces:
+- gateway/modules/interfaces/interfaceDbApp.py (44x _-Filter + direkte Referenzen)
+- gateway/modules/interfaces/interfaceDbManagement.py
+- gateway/modules/interfaces/interfaceBootstrap.py
+- gateway/modules/interfaces/interfaceRbac.py
+- gateway/modules/interfaces/interfaceDbChat.py
+- gateway/modules/interfaces/interfaceDbBilling.py
+
+### Routes:
+- gateway/modules/routes/routeDataFiles.py
+- gateway/modules/routes/routeBilling.py
+- gateway/modules/routes/routeAdminAutomationLogs.py
+- gateway/modules/routes/routeAdminRbacRules.py
+- gateway/modules/routes/routeAdminAutomationEvents.py
+- gateway/modules/routes/routeSecurityLocal.py
+- gateway/modules/routes/routeSecurityGoogle.py
+
+### Features:
+- gateway/modules/features/trustee/interfaceFeatureTrustee.py
+- gateway/modules/features/chatbot/interfaceFeatureChatbot.py
+- gateway/modules/features/automation2/routeFeatureAutomation2.py
+- gateway/modules/features/automation/routeFeatureAutomation.py
+- gateway/modules/features/automation/interfaceFeatureAutomation.py
+- gateway/modules/features/workspace/mainWorkspace.py
+- gateway/modules/features/teamsbot/routeFeatureTeamsbot.py
+- gateway/modules/features/teamsbot/interfaceFeatureTeamsbot.py
+
+### Sonstige Gateway:
+- gateway/modules/connectors/connectorDbPostgre.py
+- gateway/modules/auth/tokenManager.py
+- gateway/modules/serviceCenter/services/serviceChat/mainServiceChat.py
+- gateway/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py
+- gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py
+- gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py
+- gateway/modules/workflows/automation/mainWorkflow.py
+- gateway/modules/security/rbac.py
+- gateway/modules/shared/gdprDeletion.py
+- gateway/modules/migration/migrateRootUsers.py
+- gateway/scripts/script_db_export_migration.py
+- gateway/tests/integration/rbac/test_rbac_database.py
+
+### Business-Feld Referenzen (creationDate, .createdAt, .createdBy, etc.):
+- gateway/modules/features/teamsbot/datamodelTeamsbot.py
+- gateway/modules/features/teamsbot/interfaceFeatureTeamsbot.py
+- gateway/modules/features/teamsbot/routeFeatureTeamsbot.py
+- gateway/modules/features/commcoach/datamodelCommcoach.py
+- gateway/modules/datamodels/datamodelFiles.py
+- gateway/modules/datamodels/datamodelMessaging.py
+- gateway/modules/datamodels/datamodelDataSource.py
+- gateway/modules/datamodels/datamodelNotification.py
+- gateway/modules/datamodels/datamodelSecurity.py
+- gateway/modules/interfaces/interfaceDbManagement.py
+- gateway/modules/routes/routeSecurityLocal.py
+- gateway/modules/routes/routeSecurityGoogle.py
+- gateway/modules/serviceCenter/services/serviceChat/mainServiceChat.py
+- gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py
+- gateway/modules/auth/tokenManager.py
+
+---
+
+## Schritt 9: Frontend Calling References anpassen
+
+Alle Dateien die `_createdAt`/`_createdBy`/`_modifiedAt`/`_modifiedBy` oder
+Business-Duplikate referenzieren:
+
+### API Types:
+- frontend_nyla/src/api/automationApi.ts
+ `_createdAt`, `_createdBy`, `_createdByUserName` -> `sysCreatedAt`, `sysCreatedBy`
+- frontend_nyla/src/api/automation2Api.ts
+ `createdAt` Kommentar -> `sysCreatedAt`
+
+### Type Definitions:
+- frontend_nyla/src/types/mandate.ts
+ `record._createdBy` -> `record.sysCreatedBy` (RBAC owner check)
+
+### Hooks:
+- frontend_nyla/src/hooks/useAutomations.ts
+ `Omit<..., '_createdAt' | '_createdBy'>` -> `Omit<..., 'sysCreatedAt' | 'sysCreatedBy'>`
+
+### Pages (excludedFields/hiddenColumns Listen):
+- frontend_nyla/src/pages/basedata/PromptsPage.tsx
+- frontend_nyla/src/pages/basedata/FilesPage.tsx
+- frontend_nyla/src/pages/basedata/ConnectionsPage.tsx
+- frontend_nyla/src/pages/views/trustee/TrusteePositionsView.tsx
+- frontend_nyla/src/pages/views/trustee/TrusteePositionDocumentsView.tsx
+- frontend_nyla/src/pages/views/trustee/TrusteeDocumentsView.tsx
+- frontend_nyla/src/pages/views/realestate/RealEstateProjectsView.tsx
+- frontend_nyla/src/pages/views/realestate/RealEstateParcelsView.tsx
+- frontend_nyla/src/pages/views/automation/AutomationDefinitionsView.tsx
+- frontend_nyla/src/pages/views/automation/AutomationTemplatesView.tsx
+- frontend_nyla/src/pages/views/workspace/NeutralizationPanel.tsx
+ `m.createdAt || m._createdAt` -> `m.sysCreatedAt`
+
+### Hinweis zu den excludedFields-Listen:
+Da die System-Felder jetzt mit `visible=false` in den Attributen kommen,
+werden viele dieser manuellen Ausschluss-Listen OBSOLET. Der FormGenerator
+filtert sie automatisch raus. Die Listen koennen vereinfacht werden.
+
+---
+
+## Zusammenfassung
+
+| Bereich | Dateien | Aenderungsart |
+|---------|---------|---------------|
+| Neue Datei | datamodelBase.py | 1 neue Datei (PowerOnModel + Labels) |
+| Connector | connectorDbPostgre.py | ~15 Stellen: Spaltennamen + Logik-Fix |
+| attributeUtils | attributeUtils.py | Label-Vererbung via MRO |
+| Kern-Datamodels | ~17 Dateien | BaseModel -> PowerOnModel |
+| Feature-Datamodels | 8 Dateien | BaseModel -> PowerOnModel |
+| Business-Felder | ~13 Models | Felder entfernen |
+| extra='allow' | 3 Models | ConfigDict entfernen |
+| _-Filter | interfaceDbApp.py + 2 weitere | ~50 Stellen vereinfachen |
+| Muster B | interfaceDbApp.py (2 Stellen) | Denylist -> User.model_validate |
+| Passthrough | interfaceDbApp.py + interfaceDbManagement.py | startswith("_") entfernen |
+| Gateway Referenzen | ~30 Dateien | _createdAt -> sysCreatedAt |
+| Frontend Referenzen | ~13 Dateien | _createdAt -> sysCreatedAt |
+| Bootstrap | interfaceBootstrap.py | Migration-Funktion |
+| registerModelLabels | ~10 Dateien | Labels fuer entfernte Felder bereinigen |
+| _system Tabelle | connectorDbPostgre.py | Hardcoded Spalten umbenennen |