# Changelog (c-work) Eine Zeile pro Change, NEUESTE EINTRAEGE ZUOBERST. Begruendungen gehoeren ins zugehoerige `c-work//.md` oder die PR-Beschreibung. Format: `- YYYY-MM-DD | | | [(c-work: )] [(PR: #123)]` type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway` `frontend-nyla` `private-llm` `teams-bot` `wiki` `infra` `*` Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. ## 2026-04-29 - 2026-04-29 | fix | gateway, frontend-nyla, wiki | **Infomaniak-Connector: kDrive findet jetzt auch Drives, in denen der User nur `role: user` ist (statt `account_admin`).** Live-Beweis vom User mit Files in `https://ksuite.infomaniak.com/1696919/kdrive/app/drive/2980592/files/11`: das Drive haengt korrekt an account_id 1696919, aber `/2/drive?account_id=1696919` antwortet trotzdem `200 [] -- empty`, weil dieser Endpoint zur Drive-Manager-Admin-Sicht gehoert (filtered auf `account_admin: true`) und nicht zur Endbenutzer-Sicht. Direkt-Aufrufe wie `/2/drive/2980592/files` funktionieren fuer denselben User mit `role: user` einwandfrei (PDF "Start_with_kDrive.pdf", Ordner "Nyla-Analysen" und "Onboarding" alle sichtbar). Root-Endpoint gefunden: `GET /2/drive/init?with=drives` -- enumeriert ALLE Drives die der User sehen kann, unabhaengig von der Admin-Rolle, braucht NUR den `drive`-Scope (verifiziert mit PAT der `accounts`-Scope explizit nicht hat). Implementierung: (a) Neuer Helper `listAccessibleDrives(token) -> List[dict]` im `connectorInfomaniak.py` -- ein einzelner `/2/drive/init?with=drives`-Call, raised `InfomaniakIdentityError` mit Scope-Message wenn `drive`-Scope fehlt. (b) `KdriveAdapter` haelt jetzt `_drives: Optional[List[Dict]]` (statt `_accountId`/`_accountIds`); `_listDrives()` mappt direkt aus dem gecachten Init-Response. (c) `submit_infomaniak_token` validiert in zwei Schritten: `listAccessibleDrives` (`drive`-Scope) und `resolveOwnerIdentity` (`workspace:calendar`/`workspace:contact`-Scope). (d) Der zwischenzeitliche `resolveAccessibleAccountIds()` + `accounts`-Scope-Workaround wieder ENTFERNT (war eine Sackgasse: `/1/accounts` listet Manager-Organizations, nicht Drive-Accounts). (e) `_probeDriveScope()`-httpx-Helper im Submit-Route entfernt; `httpx`-Import + `INFOMANIAK_API_BASE`-Konstante raus. Frontend: PAT-Setup-Modal wieder auf 4 Pflicht-Scopes zurueck (`accounts` raus). Doku: Status-Tabelle, Validation-Section und "How the kDrive adapter discovers your drives" komplett auf den `init`-Endpoint umgeschrieben. **Bestehende Connections sollten ohne User-Aktion sofort funktionieren -- es ist kein neuer Scope und keine Token-Rotation noetig, der Adapter wechselt nur den Discovery-Endpoint.** (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-29 | fix | gateway | **Infomaniak-Connector: Calendar-Events und Contacts-Download auf die echten PAT-faehigen Pfade gezogen.** Live-Tests gegen die Vendor-API zeigten zwei Pfad-Mismatches im gestrigen Build: (1) Calendar-Events: der nested Route `/api/pim/calendar/{id}/event` 302t zur OAuth-Login-Seite (also nicht PAT-faehig); korrekter Endpoint ist `/api/pim/event?calendar_id={id}&from=YYYY-MM-DD HH:MM:SS&to=...` mit Pflicht-Range max 3 Monate (Vendor-Constraint `range_must_be_lower_than_3_months`) -- Adapter waehlt jetzt fix 90-Tage-Window (heute -30 / +60), URL-Encoding via `urllib.parse.quote`. Event-Detail und `.ics`-Export laufen ueber `/api/pim/event/{eventId}` und `.../export` (also ohne calendar-Praefix). (2) Contacts-Download: `/addressbook/{book}/contact/{id}` antwortet mit `500 unexpected_error` und `.../export` 302t zu OAuth -- beide Detail-Endpoints sind also nicht PAT-faehig. Listing dagegen funktioniert, liefert aber per Default nur Stammdaten -- ohne `with=emails,phones,addresses,details` kommen Email/Phone/Adresse leer. Loesung: ContactAdapter holt das Listing mit dem `with`-Param und rendert die `.vcf` selbst via `_renderInfomaniakVcard()` (vCard 3.0, escaped, mit N/FN/ORG/EMAIL/TEL/ADR/URL/NOTE) -- konsistent mit MSFT/Google-Adapter, die ihre `.vcf`s ebenfalls selbst synthetisieren. Plus: Helper `_safeFileName` aus dem Calendar-Adapter zu modullokal hochgezogen, von beiden Adaptern genutzt; ungenutzte `import re`/`import json`-Inline-Imports raus. **kDrive-Adapter ist im Live-Test korrekt:** `/2/drive?account_id=1696919` antwortet 200 mit leerem Array fuer den Test-Account (kein kDrive-Produkt aktiviert). (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) ## 2026-04-28 - 2026-04-28 | refactor | gateway | **Infomaniak-Connector: `account_id` ist Adapter-State, nicht Connection-State.** Symptom im Log: `Infomaniak GET https://api.infomaniak.com/2/drive?account_id=pat-XXXXXXXX -> 422 validation_rule_integer "The account id must be an integer."`. Root cause: der `KdriveAdapter` las `connection.externalId` als `account_id`-Quelle, und bei kaputten Submits konnte dort der Token-Fingerprint ("pat-59ee48d9") statt der `account_id` stehen. Saubere Loesung statt Fingerprint-Migration: (a) Neuer modullokaler Helper `resolveOwnerIdentity(token) -> InfomaniakOwnerIdentity` im `connectorInfomaniak.py` -- versucht PIM Calendar (`workspace:calendar`-Scope), faellt auf PIM Contacts (`workspace:contact`-Scope) zurueck, raised `InfomaniakIdentityError` wenn keine Owner-Records gefunden. (b) `KdriveAdapter` hat keinen `accountId`-Konstruktor-Parameter mehr, sondern resolvt zur Laufzeit ueber `_ensureAccountId()` (gecached auf der Adapter-Instanz). Damit ist der Adapter self-contained und liest **nichts** mehr aus der Connection -- `externalId` ist reiner UI-State. (c) `submit_infomaniak_token` ruft denselben `resolveOwnerIdentity()` als Pre-Flight-Check, dann `/2/drive?account_id={resolved}` als sauberer 200-Probe. Der frueher noetige `_probeScope`-422-Tolerance-Hack ist entfallen. (d) `getServiceAdapter` hat keine kDrive-Sonderbehandlung mehr; alle drei Adapter werden uniform mit `accessToken` konstruiert. (e) `_PIM_PREFIX` ist auf Modul-Ebene definiert; CalendarAdapter und ContactAdapter haben keine Klassen-Konstanten mehr. **Konsequenz: jede existierende Infomaniak-Connection arbeitet ohne User-Aktion sofort wieder, auch wenn `externalId` historisch einen Fingerprint enthaelt -- der Adapter zieht die `account_id` deterministisch aus der API.** Setup-Guide um Architektur-Abschnitt erweitert. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-28 | feat | gateway, frontend-nyla | **MSFT- und Google-Connector: CalendarAdapter + ContactsAdapter neu, plus Reconnect-Button.** Nachdem Infomaniak heute Calendar/Contacts kann, ziehen MSFT und Google nach. Backend: (a) `oauthProviderConfig.googleDataScopes` um `calendar.readonly` + `contacts.readonly` erweitert, `msftDataScopes` um `Calendars.Read` + `Contacts.Read`. (b) `connectorMsft.CalendarAdapter` (Graph: `me/calendars`, `me/calendars/{id}/events?$top&$orderby=start/dateTime desc`, Pagination via `@odata.nextLink`, `.ics`-Download als selbstgebautes RFC-5545 VCALENDAR/VEVENT da Graph keinen `$value`-Endpoint fuer Events kennt; `$search` fuer query). (c) `connectorMsft.ContactsAdapter` (Graph: `me/contactFolders` + virtueller `default`-Ordner fuer `me/contacts`, `me/contactFolders/{id}/contacts?$orderby=displayName`, `.vcf` als vCard-3.0 selbstgebaut mit N/FN/ORG/TITLE/EMAIL/TEL/ADR/NOTE). Helper `_eventToIcs`, `_contactToVcard`, `_safeFileName`, `_personLabel` plus `_icsEscape`/`_icsDateTime`. (d) `connectorGoogle.CalendarAdapter` (Calendar v3: `users/me/calendarList`, `calendars/{id}/events?singleEvents=true&orderBy=startTime`, `.ics` aus Event-Detail). (e) `connectorGoogle.ContactsAdapter` (People API: `contactGroups` + virtueller `all`-Folder ueber `people/me/connections`, fuer Gruppen `people:batchGet?resourceNames=...`, Search via `people:searchContacts`, `.vcf` aus `personFields=names,emailAddresses,phoneNumbers,organizations,addresses,biographies`). (f) `_SERVICE_MAP` von beiden Connectoren um `"calendar"`/`"contact"` ergaenzt; `routeFeatureWorkspace` und `routeFeatureGraphicalEditor` Service-Label-/Icon-Maps um `kdrive`, `calendar`, `contact` ergaenzt, damit die UDB sinnvolle Anzeigen macht. (g) **Reconnect-Flow**: `POST /api/connections/{id}/connect` akzeptiert optionalen Body `{"reauth": true}` und haengt `&reauth=1` an die Auth-URL. `routeSecurityMsft.auth_connect` setzt bei `reauth=1` `prompt=consent` (sonst select_account/login), `routeSecurityGoogle.auth_connect` droppt bei `reauth=1` `include_granted_scopes=true` damit Google strikt fuer die aktuelle Scope-Liste neu signiert (sonst werden neue Scopes still uebersprungen). Frontend: `connectionApi.connectService(id, reauth?)` -> POST mit Body, `useConnections.connectWithPopup(id, reauth?)` reicht durch, `ConnectionsPage` zeigt fuer aktive MSFT/Google/ClickUp-Connections einen `FaSyncAlt`-Button "Erneut verbinden (neue Scopes erteilen)" mit eigenem Loading-Set. Bestehende Connections muessen einmal reconnected werden, damit Calendar/Contacts in der UDB auftauchen. (c-work: `c-work/2-build/2026-04-msft-google-calendar-contacts.md`) - 2026-04-28 | fix | gateway | **OpenAI-Connector: `temperature` fuer GPT-5.x / o-Serie aus dem Payload nehmen.** Symptom im Log: jede AI-Anfrage failt mit HTTP 400 `Unsupported value: 'temperature' does not support 0.2 with this model. Only the default (1) value is supported.`, der Failover spricht 14 Modelle durch und schreibt `Recorded failure for gpt-5.5, cooldown 60.0s`. Root cause: die GPT-5-Familie (gpt-5, gpt-5.4*, gpt-5.5*) und die o-Serie (o1/o3/o4) sind Reasoning-Modelle; OpenAI akzeptiert dort -- analog zur `max_tokens` -> `max_completion_tokens`-Restriction -- nur den Default (`1`). Wir senden aber unverhandelt `temperature=0.2` aus jedem `AiModel`-Eintrag. Fix: Helper `_supportsCustomTemperature(modelName)` und in `callAiBasic`/`callAiBasicStream`/`callAiImage` den Key nur conditional ins Payload aufnehmen (Modellnamen mit Praefix `gpt-5`, `o1`, `o3`, `o4` lassen ihn weg). Der vom User im UI gesetzte Override ueber `AiCallOptions.temperature` wird auf den nicht unterstuetzten Modellen still verworfen statt einen 400 zu erzwingen. Tests: `tests/unit/aicore/test_aicorePluginOpenai_temperature.py` (18 Tests, parametrisiert ueber Legacy-Modelle vs. Reasoning-Familie). Suite gesamt: 530 passed. - 2026-04-28 | docs | wiki | **Infomaniak-Connector: Mail-Adapter formal als "blocked by vendor" markiert.** Erschoepfende Pfad-Tests am 2026-04-28 zeigen: alle 7 plausiblen Mail-Endpoints sind heute nicht PAT-faehig. `api.infomaniak.com/{1,2}/mail` -> 404 nginx (existiert nicht); `mail.infomaniak.com/api/mail[?account_id=...]`, `/api/pim/mail`, `/api/pim/mailbox`, `/api/pim/folder` -> 302 zu `login.infomaniak.com/authorize` (nur OAuth-Web-Session, Bearer-PAT wird abgelehnt); `mail.infomaniak.com/api/mail/?account_id=...` -> 301 zu `http://mail.infomaniak.com:5000` (interner Cyrus-IMAP-Port, von aussen nicht erreichbar). Der `workspace:mail`-Scope bleibt im PAT-Standard-Setup, damit der MailAdapter spaeter ohne Token-Rotation freigeschaltet werden kann; Setup-Guide und c-work-Doku zementieren den Befund. Kein Code-Change. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-28 | feat | gateway, frontend-nyla | **Infomaniak-Connector: ContactAdapter neu.** Nachfolge-Tests gegen die Contacts-PIM-API zeigten, dass `https://contacts.infomaniak.com/api/pim/addressbook` (Singular!) mit dem PAT-Scope `workspace:contact` und Bearer-Auth `200` + JSON liefert -- gleiche Antwort-Struktur wie Calendar (`addressbooks[].id/name/account_id/...`). Die Plural- und `/contact*`-Pfade (`/api/pim/contacts`, `/api/pim/contact/addressbook`) sind weiterhin OAuth-only bzw. 404. Neuer `ContactAdapter` 1:1 analog `CalendarAdapter`: `browse("/")` -> Adressbuecher, `browse("/{bookId}")` -> Kontakte (Display-Name, Email, Phone, Organization in Metadata), `download("/{bookId}/{contactId}")` -> `.vcf` via `/contact/{id}/export` mit JSON-Fallback, `search()` als kostenguenstiger Client-Filter. Geteilte Organisations-Adressbuecher (`name=""`, `is_dynamic_organisation_member_directory=true`) bekommen "Organisation" als Anzeigename, sonst waere der Tree-Knoten leer. Neue Konstante `_CONTACTS_BASE`, `ContactAdapter` in `_SERVICE_MAP` registriert. Frontend: `SourcesTab` kennt `contact`-Icon (👤), -Color und Mapping; PAT-Modal nennt Contacts als heute aktiv (Mail bleibt "in Vorbereitung"). Setup-Guide: Status-Tabelle, UDB-Verifikations-Liste und Validation-Beschreibung aktualisiert. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-28 | feat | gateway, frontend-nyla | **Infomaniak-Connector: kDrive PAT-Fix + Calendar-Adapter neu.** Nach den ersten echten PAT-Test-Calls aufgeraeumt: (a) `/2/drive` braucht ein `account_id`-Query-Arg, sonst `422 account_id required`. Fix: `account_id` einmalig beim Token-Submit aus dem Calendar-PIM-Endpoint (`https://calendar.infomaniak.com/api/pim/calendar` -- liefert `account_id`, `user_id`, Anzeigename) ziehen, in `UserConnection.externalId` persistieren und ueber `InfomaniakConnector.getServiceAdapter` als Konstruktor-Parameter in den `KdriveAdapter` injizieren. `/1/profile` (haette `user_info`-Scope verlangt) und `/1/mail` (existiert nicht: 404 nginx) raus aus den Probes. (b) `_probeScope` toleriert jetzt 4xx ausser 401/403 (z.B. 422 `validation_failed`) als "Scope ist da, Endpoint braucht nur weitere Args". (c) Neuer `CalendarAdapter` (`browse` -> Calendars/Events ueber `calendar.infomaniak.com/api/pim/calendar`, `download` -> `.ics` via `/event/{id}/export`, JSON-Fallback). `_infomaniakGet` und `_infomaniakDownload` akzeptieren ein optionales `baseUrl`, sodass Calendar gegen `calendar.infomaniak.com` und kDrive gegen `api.infomaniak.com` laufen koennen. (d) `MailAdapter` aus `_SERVICE_MAP` entfernt: `mail.infomaniak.com/api/mail` redirected mit 302 zu `login.infomaniak.com/authorize`, akzeptiert also keine PATs; gleiches gilt fuer `contacts.infomaniak.com/api/pim/contact`. Beide Scopes werden weiterhin in `grantedScopes` gespeichert, damit kuenftige Adapter ohne Token-Rotation aufgeschaltet werden koennen. (e) Frontend: `SourcesTab` kennt `calendar`-Icon, -Color und `_SERVICE_TO_SOURCE_TYPE`-Mapping; PAT-Modal in `ConnectionsPage` zeigt Calendar als zweiten aktiven Service, Mail/Contact als "in Vorbereitung, Scope schon mitnehmen". (f) Setup-Guide aktualisiert (Status-Tabelle + Validation-Beschreibung). (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-28 | refactor | gateway, frontend-nyla | **Infomaniak-Connector: OAuth -> Personal Access Token.** Infomaniaks `login.infomaniak.com/authorize` akzeptiert nur Identity-Scopes (`openid`, `profile`, `email`, `phone`); Versuche mit `kdrive`/`mail`-Scopes quittieren mit `error=invalid_scope`. Datenzugriff geht ausschliesslich ueber manuell im Manager erstellte PATs. Umbau: (a) Backend `routeSecurityInfomaniak` komplett neu -- ein Endpoint `POST /api/infomaniak/connections/{id}/token`, validiert PAT via `GET https://api.infomaniak.com/1/profile`, persistiert Bearer mit 10-Jahres-Horizont (analog ClickUp), entfernt OAuth-Connect/Callback-Pfade. (b) `tokenManager.refreshInfomaniakToken` + `tokenRefreshService._refresh_infomaniak_token` entfernt, AuthAuthority.INFOMANIAK aus den Background-Refresh-Filtern raus -- PATs sind langlebig. (c) `oauthProviderConfig.infomaniakDataScopes` + `infomaniakDataScopesForRefresh` entfernt. (d) `routeDataConnections.connect_service` antwortet bei Infomaniak jetzt mit 400 + Hinweis auf den PAT-Endpoint. (e) Env-Cleanup: `Service_INFOMANIAK_DATA_CLIENT_ID/SECRET` und `Service_INFOMANIAK_OAUTH_REDIRECT_URI` aus `.env` + `env_dev/int/prod[_forgejo].env` raus. (f) Frontend: `useConnections.createInfomaniakConnectionAndAuth` (OAuth-Popup) ersetzt durch `createInfomaniakConnection` + `submitInfomaniakToken`; `ConnectionsPage` zeigt PAT-Modal mit Schritt-Anleitung + Deeplink zu `manager.infomaniak.com/v3/ng/accounts/token/list`; Cancel rollt PENDING-Connection per DELETE zurueck. (g) Doku: `wiki/d-guides/infomaniak-oauth-setup.md` geloescht, `wiki/d-guides/infomaniak-token-setup.md` neu. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`) - 2026-04-28 | refactor | gateway | **Cleanup der zwei tieferliegenden Defensive-Programming-Schichten, die den Trustee-Bug (vorheriger Eintrag) ueberhaupt erst durchgelassen haben.** - **(1) DB-Connector: fail-loud statt swallow.** `connectorDbPostgre.getRecord/getRecordset/getRecordsetPaginated/getDistinctColumnValues/_loadTable/semanticSearch` haben bisher jede Exception per `except Exception → log → return []` (bzw. `None`/leeres Pagination-Resultat) verschluckt. Folge: jeder echte DB-Fehler (Postgres-Adapt, UndefinedTable, UndefinedColumn, OperationalError, etc.) wurde fuer den Caller ununterscheidbar von "0 Rows" -- darauf basierten misleading downstream Errors wie "No active accounting configuration found". Neu: typisierte Exception `DatabaseQueryError(table, message, original)` plus zentrales `_rollbackQuietly(connection)` (Postgres setzt die Connection in Error-State nach jedem fehlgeschlagenen Statement). Empty Result Sets liefern weiterhin `[]`/`None`/`{items: [], totalItems: 0, totalPages: 0}` (= Normalpfad ueber `cursor.fetchall()/fetchone()`), aber jede Exception innerhalb des Query-Pfads wird hochgereicht. Tests: `tests/unit/connectors/test_connectorDbPostgre_failLoud.py` (9 Tests). - **(2) Action-Parameter: zentrale Validierung statt impliziter Kontrakt.** Workflow-Actions haben `parameters: Dict[str, Any]` ohne Schema-Enforcement bekommen; die Aktionsimplementationen mussten ad-hoc `isinstance`-Branches haben oder mit Postgres-Errors abstuerzen (siehe Trustee-Bug). Neues Modul `gateway/modules/workflows/processing/shared/parameterValidation.py` mit `InvalidActionParameterError(ValueError)` + `validateAndCoerceParameters(actionDef, parameters)`. Zentral aufgerufen in `ActionExecutor.executeAction` -- gilt fuer alle Aufrufpfade (Agent, Workflow-Graph, REST). Logik: 1. Required-Parameter erzwungen (typisierter Fehler statt opaque downstream). 2. Ref-Schemas (`FeatureInstanceRef`/`ConnectionRef`/...) -- Dict mit `id` wird auf String-UUID kollabiert; Pass-Through fuer bereits-Strings; Dict ohne `id` raisst kontrolliert. 3. Primitive-Coercion (`bool`/`int`/`float`) aus haeufigen String-Formen ("true"/"12"/"3.14"). Unbekannte Extra-Keys (`parentOperationId`, `expectedDocumentFormats`, ...) bleiben unangetastet. Die in der vorherigen Iteration in `actionToolAdapter` eingebaute Ref-Normalisierung wurde komplett entfernt (Single Source of Truth in `parameterValidation`). Tests: `tests/unit/workflows/test_parameterValidation.py` (19 Tests). - Gesamttest-Suite: 512 passed (vorher 503, plus 9 neue DB-Tests; +19 neue Validation-Tests; -7 obsolete Adapter-Tests, da Logik umgezogen). - 2026-04-28 | fix | gateway | Agent-Tool-Calls auf Workflow-Actions mit `*Ref`-Parametern (z.B. `trustee_refreshAccountingData(featureInstanceId=...)`) brachen mit irrefuehrendem "No active accounting configuration found" ab. Root cause: das Tool-Schema (Phase-3 Typed Action Architecture) exponiert `FeatureInstanceRef`/`ConnectionRef` absichtlich als typisiertes Objekt mit `id`+Diskriminator (`featureCode`/`authority`), damit der LLM bei mehreren Instanzen die richtige picken kann -- aber die Action-Implementierungen verwenden den Wert direkt als String-UUID in `recordFilter={"featureInstanceId": , ...}`. Der LLM uebergibt korrekt das Dict `{id, featureCode, label, mandateId}`, Postgres-Adapter scheitert mit `can't adapt type 'dict'`, der DB-Connector swallowed den Fehler (soft-fail mit `[]`), und die Action interpretiert das leere Resultset als "Konfiguration fehlt". Initial-Fix in `actionToolAdapter` (Ref-Dict -> id-String). **Diese Adapter-Normalisierung wurde im Folgeschritt zur zentralen `parameterValidation` migriert (siehe naechster Eintrag).** ## 2026-04-27 - 2026-04-27 | fix | gateway | Trustee-Subagent gab fuer "Banksaldo per 31.12.2025" CHF 11'861'162.50 zurueck statt der echten 48'507.41 (Konto 1020) und identifizierte 5400 (Materialaufwand) + 3434 (Erloeskorrekturen) als "Bankkonten". Root cause war doppelt: (1) `closingBalance` ist bereits ein Saldo pro Periode -- der Agent rief `aggregateTable(SUM, closingBalance, GROUP BY accountNumber)` ohne periodYear/periodMonth-Filter und summierte 7 Jahre x 13 Perioden auf (~90x echter Saldo); (2) er hatte kein Wissen darueber, dass Schweizer KMU-Bankkonten dem Praefix `102x` folgen und `periodMonth=0` das Jahres-Total bedeutet. Fix in zwei Teilen: (a) generische Regel in `featureDataAgent._buildSchemaContext` System-Prompt -- "NEVER apply SUM/AVG to columns that already represent a balance, closing/opening total or aggregate" mit konkreten Beispielen `closingBalance/openingBalance/debitTotal/creditTotal`; (b) Pro-Feature-Hook `getAgentDomainHints() -> str` in `mainXxx.py`: wenn die Funktion existiert, wird ihr Rueckgabetext ans Ende des Subagent-Prompts angehaengt. Trustee liefert jetzt einen kompakten Domain-Guide mit KMU-Kontoplan-Praefixen (1xxx/2xxx/3xxx/4xxx, 100x/102x), Periodenkonvention (`periodMonth=0` = Jahr, 1-12 = Monat), drei kanonischen Query-Patterns (Banksaldo, Konto-Saldo, Buchungen-im-Monat) und einer Anti-Pattern-Liste (kein SUM auf closingBalance, debitTotal/creditTotal sind keine Salden). Loader in `_loadFeatureDomainHints` nutzt `loadFeatureMainModules()` und ist tolerant gegen fehlende Hooks/Module. Unit-Tests in `tests/unit/services/test_featureDataAgent_schema.py` (4 neue: Generische Regel, Trustee-Hints angehaengt, kein Hints-Block fuer Features ohne Hook, Anti-Pattern-Erwaehnungen) ## 2026-04-26 - 2026-04-26 | fix | gateway | Feature-Data-Subagent (`queryFeatureInstance`) Schema-Prompt war zu duenn: Agent erhielt nur Tabellennamen + flache Spalten-Liste, ohne Typen / Beschreibungen / FK-Beziehungen. Folge: bei Trustee-Saldo-Queries summierte er `TrusteeDataJournalLine.debitAmount/creditAmount` ohne Datumsfilter (JournalLine hat gar kein `bookingDate`!) statt die Periode-bezogenen `TrusteeDataAccountBalance.closingBalance` zu nutzen; ISO-Date-Strings wurden gegen Float-Unix-Timestamps gefiltert (`bookingDate <= '2025-12-31'`). Fix in `featureDataAgent._buildSchemaContext`: pro Tabelle wird jetzt der zugehoerige Pydantic-Klasse via `MODEL_REGISTRY` resolved und pro selektiertem Feld eine angereicherte Zeile gerendert -- Python-Typ aus `field.annotation`, deutsches Label aus `json_schema_extra.label`, Description aus `Field(description=...)`, FK-Target aus `fk_target`. Dazu drei neue Regeln im System-Prompt: (a) Float-Felder mit "unix timestamp" in der Description sind Sekunden-seit-Epoch (Beispiel `'2025-12-31' -> 1735603200.0`), (b) Tools koennen nicht JOINen -- FK-Tabellen separat abfragen, (c) Periode-aggregierte Tabellen (Opening/Closing-Balances) bevorzugt vor SUM ueber Rohdaten. Fallback auf flache Feldliste wenn die Tabelle nicht im Pydantic-Registry ist. Unit-Tests in `tests/unit/services/test_featureDataAgent_schema.py` - 2026-04-26 | feat | gateway+frontend-nyla | Infomaniak-Connector (kDrive + Mail) als neuer ProviderConnector analog Google/MSFT/ClickUp. Backend: `AuthAuthority.INFOMANIAK`, `providerInfomaniak/connectorInfomaniak.py` mit `KdriveAdapter`+`MailAdapter` (httpx, OAuth-Bearer, Path-Konvention `/{driveId}/{fileId}` bzw. `/{mailboxId}/{folderId}/{uid}`), `routeSecurityInfomaniak.py` mit `_FLOW_CONNECT`-only (kein Login -- pure DATA_CONNECTION), Token-Refresh via `tokenManager.refreshInfomaniakToken` + `tokenRefreshService._refresh_infomaniak_token`, `connectorResolver`-Registry-Eintrag, Authority-Map/Labels/Dispatch in `routeDataConnections`, Router-Mount in `app.py`. Refresh-Token-Persistierung holt bei fehlendem `refresh_token`-Response aus dem letzten gespeicherten Token (analog Google). Frontend: `connectionApi.ts` Authority-Typ erweitert, `useConnections.createInfomaniakConnectionAndAuth` + `infomaniak_connection_success/error`-Event-Listener, `ConnectionsPage` mit Infomaniak-Button (FaCloud), `SourcesTab` Icons/Colors/Service-Mapping fuer `infomaniak`/`kdrive`/`mail` -- inkl. Fix der bisher fehlenden ClickUp-Eintraege in `_SERVICE_ICONS` + `_SERVICE_TO_SOURCE_TYPE`. Setup-Guide unter `wiki/d-guides/infomaniak-oauth-setup.md` (c-work: c-work/2-build/2026-04-infomaniak-connector.md) - 2026-04-26 | fix | gateway | Feature-Data-Subagent (`queryFeatureInstance`) hat seine Loop hardcoded auf 8 Runden begrenzt, unabhaengig vom Workspace `maxAgentRounds`. Im int-System brachen Trustee-Saldo-Queries deshalb mit `Maximum rounds reached. Progress after 8 rounds` ab, obwohl der Parent-Agent z.B. mit 25 Runden konfiguriert war. Fix: `agentLoop._executeToolCalls` propagiert jetzt `parentMaxRounds` + `parentMaxCostCHF` ueber den Tool-Context; `_featureSubAgentTools._queryFeatureInstance` liest sie aus und reicht sie an `runFeatureDataAgent(maxRounds=, maxCostCHF=)` weiter. Default fuer Direktaufrufer/Tests bleibt 8. Cost-Cap skaliert linear (`_MAX_COST_CHF_PER_ROUND = 0.02 * maxRounds`), damit nicht der 0.15-CHF-Guard die Loop vor Erreichen der konfigurierten Runden abschiesst (25 Runden -> 0.50 CHF). Subagent-Start loggt jetzt effektive `maxRounds`/`maxCostCHF` zur Diagnose - 2026-04-26 | feat | gateway+frontend-nyla | Database-Health Orphan-Cleanup: neue Checkbox `Ohne FK-Referenzen zu UserInDB.id` (default ON). Deleted-User-Reste in Audit/Billing/Membership-Tabellen sammeln sich natuerlich an wenn ein User geloescht wird und gehoeren in den separaten User-Purge-Workflow, nicht in die generische FK-Bereinigung. Backend: `_isUserIdFk(targetTable, targetColumn)`-Helper (case-insensitive auf Tabellenname); `_cleanAllOrphans(force, excludeUserFks)` ueberspringt entsprechende Relationen; `/orphans?excludeUserFks=true` filtert Scan-Resultate; `OrphanCleanAllRequest.excludeUserFks` filtert clean-all. Frontend: Checkbox neben `Nur Probleme`, default checked, mit Tooltip; URL-Param + clean-all Body-Field synchron; `Alle bereinigen`-Counter zeigt jetzt nur Non-User-FK-Orphans - 2026-04-26 | fix | gateway | aicorePluginOpenai: `max_tokens` durch `max_completion_tokens` ersetzt in `callAiBasic` und `callAiBasicStream`. Hintergrund: OpenAI lehnt `max_tokens` fuer gpt-5.x / o-series Modelle mit HTTP 400 `unsupported_parameter` ab (`Use 'max_completion_tokens' instead`). Im Log `log_app_20260426.log` (L741-764) sichtbar: `gpt-5.4-nano` failover scheiterte sofort, ModelSelector wechselte auf `claude-opus-4-6`. Per OpenAI API-Reference akzeptieren ALLE aktuellen Chat-Completions-Modelle (legacy gpt-4o/gpt-4.1, gpt-5.x, o1/o3/o4) `max_completion_tokens`, daher universeller Wechsel statt Modell-spezifischer Verzweigung - 2026-04-26 | feat | gateway | PDF-Renderer Emoji-Support: Noto Emoji (monochrome, OFL) als Fallback-Font registriert. Bisher rendern WinAnsi-Core-Fonts (Helvetica/Courier) Emoji-Codepoints (U+2600+, U+1F300+) als fehlende Glyphen-Quadrate. Neu unter `gateway/assets/fonts/NotoEmoji-Regular.ttf` (~419 KB, 887 Codepoints) + `_pdfFontFallback.py` Helper: registriert die TTF einmalig bei reportlab, scannt deren cmap, und `wrapEmojiSpansInXml` umschliesst zusammenhaengende Emoji-Runs (codepoint >= U+2000 ∧ in cmap) mit `` — nestet sauber in ``/``/``. `rendererPdf._markdownInlineToReportlabXml` wendet das am Ende an, also greift es ueberall wo Paragraph-Markup gebaut wird (Headings, Paragraphs, Bullet-Lists, Table-Cells, extracted_text). `Preformatted` (Code-Blocks) ist Single-Font-only und bleibt unveraendert — Emojis in Code-Bloecken sind selten, Box-Drawing wird wie bisher zu ASCII normalisiert. Smoke-Test in `test_renderer_pdf_smoke.py` - 2026-04-26 | fix | gateway | FK Orphan-Scanner loeschte korrekte Trustee-Workflows: `AutoWorkflow.templateSourceId` enthaelt teils Sentinel-IDs (z.B. `"trustee-receipt-import"` aus `featureModule.getTemplateWorkflows()`), die absichtlich keine DB-Zeile haben — wurden faelschlich als Orphans markiert und mit `force=true` (oder unter 50%-Schwelle) geloescht. Neuer `softFk: True` Flag in `fk_target`: `fkRegistry.FkRelationship` traegt das Flag, `databaseHealth._scanOrphans`/`_cleanOrphans`/`_listOrphans` ueberspringen soft FKs komplett (kein Display, kein Cleanup). Label-Resolution unveraendert. `templateSourceId` als `softFk: True` markiert. Wiki `b-reference/platform/database-architecture.md` aktualisiert - 2026-04-26 | refactor | gateway+frontend-nyla | Letzte 4 hardcoded Cell-Label-Stellen entfernt: (1) `RoleView.scopeType` (select mit `frontend_options` System-Template/Template/Mandant) + `RoleView.userCount` -> `AdminMandateRolesPage` zieht Attribute jetzt von `RoleView`, kein lokaler `scopeType`-Formatter mehr; (2) `Invitation.expiredFlag` als Pydantic `@computed_field` (live aus `expiresAt`+`time.time()`) mit `frontend_format_labels=["Ja","-","Nein"]`; (3) `Invitation.emailSent` -> `emailSentFlag` umbenannt + neues `emailSentAt`-Feld (Persistenz im DB-Record), `routeInvitations.create_invitation` setzt beide nach erfolgreichem Mailversand; (4) `TrusteePositionView` mit `syncStatus` (select Ausstehend/Synchronisiert/Fehler/Abgebrochen) + `syncErrorMessage` -> `routeFeatureTrustee.get_positions` enriched Rows aus `TrusteeAccountingSync`, `useTrustee` lookt Attribute via neuem `attributesEntityName`-Override, `TrusteePositionsView` hat eigenen Sync-State + Custom-Renderer geloescht. `attributeUtils.getModelAttributeDefinitions` und `i18nRegistry.@i18nModel` verarbeiten jetzt auch `model_computed_fields` (Labels + `frontend_format_labels` werden registriert) - 2026-04-26 | refactor | gateway+frontend-nyla | Hardcoded Cell-Labels aus FormGeneratorTable-Pages entfernt: Boolean-Formatter ("Ja"/"Nein", "OK"/"Fehler") und Enum-Maps (`_STATUS_LABELS`, `scopeLabels`) aus `GraphicalEditorWorkflowsPage`, `GraphicalEditorTemplatesPage`, `AutomationsDashboardPage`, `ComplianceAuditPage` ersatzlos geloescht. Stattdessen Pydantic-Modelle (`AutoWorkflow.active|sharedReadOnly|isTemplate|notifyOnFailure|templateScope`, `AutoRun.status`, `AutoStep.status`, `AutoTask.status`, `AutoVersion.status`, `Automation2WorkflowView.isRunning`, `AuditLogEntry.success`) mit `frontend_format_labels`/`frontend_options` ausgestattet. `resolveColumnTypes` merged jetzt auch `label` und `options` aus dem Backend; `FormGeneratorTable` rendert Cells UND Filter-Dropdowns ueber `column.options` automatisch — Pages duerfen Labels nicht mehr im Frontend hardcoden - 2026-04-26 | feat | frontend-nyla | FormGeneratorTable + columnTypeResolver: `ColumnConfig.options` (aus `frontend_options` der Pydantic-Felder) ist jetzt erste Klasse; Cell-Renderer und Filter-Liste resolven Value -> Label automatisch; `column.label` ist optional und wird vom Backend-Attribut gefuellt - 2026-04-26 | fix | gateway+frontend-nyla | GraphicalEditor Workflows-Tabelle: `createdAt`-Alias aus `routeFeatureGraphicalEditor.get_workflows` entfernt — Frontend nutzt nun das kanonische `sysCreatedAt`. `GraphicalEditorWorkflowsPage` + `GraphicalEditorTemplatesPage` holen Attribute jetzt von `Automation2WorkflowView` (mit `frontend_type=timestamp` fuer `sysCreatedAt`/`lastStartedAt` und `frontend_type=number` fuer `runCount`); Spalten haben explizit `sortable`/`filterable` gesetzt — fehlende Sort-Icons und Zahl-statt-PeriodPicker behoben. `Automation2Workflow`-TS-Interface auf `sysCreatedAt` umgestellt - 2026-04-26 | refactor | frontend-nyla | RealEstate Parcels+Projects + GraphicalEditor Workflows+Templates: `apiEndpoint` auf den jeweiligen Listenroute gesetzt — Backend-Routen unterstuetzen `mode=filterValues&column=X` und `mode=ids` ueber `handleFilterValuesInMemory`/`handleIdsInMemory`; FormGeneratorTable holt Filter-Werte jetzt sauber vom Backend (kein Local-Mode mehr noetig) - 2026-04-26 | feat | gateway | routeFeatureGraphicalEditor: `/workflows` und `/templates` Endpunkte unterstuetzen jetzt `mode=filterValues&column=X` und `mode=ids` (FormGeneratorTable Backend-Pattern) ueber `handleFilterValuesInMemory`/`handleIdsInMemory` aus routeHelpers - 2026-04-26 | fix | frontend-nyla | AdminLanguagesPage: `hookData.fetchFilterValues` implementiert (offizielles Pattern fuer In-Memory-Tabellen ohne Backend-Endpunkt) — distinct Filter-Werte aus `displayRows` mit Cross-Filter-Support; ersetzt das zuvor versuchte FormGeneratorTable-Local-Mode - 2026-04-26 | revert | frontend-nyla | FormGeneratorTable: Local-Mode-Fallback in `getUniqueValuesForColumn` und das Entfernen der console.warn rueckgaengig gemacht — silent Fallbacks verstossen gegen das Prinzip "klare Datenstrukturen + Modelle im Backend"; Tabellen muessen stattdessen `apiEndpoint` (Backend) oder `hookData.fetchFilterValues` (explizit) setzen - 2026-04-26 | fix | frontend-nyla | AutomationsDashboardPage Workflows-Tab: Spalten `isRunning` und `runCount` als `sortable` + `filterable` markiert (Backend unterstuetzt JOIN-basierte Sortierung/Filterung dieser computed fields) - 2026-04-26 | fix | gateway | Automation2 ExecutionEngine: `AutoRun.startedAt` wird jetzt in `createRun` gesetzt; `AutoRun.completedAt` wird in `updateRun` automatisch gesetzt sobald Status terminal wird (completed/failed/stopped/cancelled); routeWorkflowDashboard.stopRun setzt `completedAt` ebenfalls. Bisher wurden diese Felder nie befuellt — daher waren `started`/`completed` Spalten in der Runs-Tabelle leer - 2026-04-26 | fix | frontend-nyla | PeriodPicker in FormGeneratorTable: Preset-Kind (`thisMonth`, `thisQuarter` etc.) wird im Filter-Wert mitgespeichert, damit es beim Round-Trip erhalten bleibt und `isValueAllowed` nicht faelschlicherweise auf `ytd` zurueckfaellt - 2026-04-26 | fix | gateway | routeAudit: 500-Fehler bei Datumsfilter behoben — `PaginationParams(pageSize=999999)` verletzte `le=1000`-Constraint; nutzt jetzt `model_construct` + `SortField`-Konvertierung - 2026-04-26 | refactor | gateway | 5 Pattern-Inkonsistenzen aus FormGeneratorTable-Audit behoben: routeDataUsers stiller Fallback entfernt; routeFeatureRealEstate Projekte+Parzellen nutzen jetzt `applyFiltersAndSort` statt nur Sorting; routeAdminRbacRules custom filter/sort durch shared Helper ersetzt + `enrichRowsWithFkLabels` ergaenzt - 2026-04-26 | fix | frontend-nyla | ComplianceAuditPage: Fallback-Formatter fuer `instanceLabel` und `username` zeigen jetzt `NA(uuid)` statt abgeschnittener UUID ohne Kontext - 2026-04-26 | fix | gateway | aicoreModelRegistry: Race-Condition in `refreshModels` behoben — Lock verhindert konkurrierende Refreshes; harmlose Duplikate (gleicher Name+Connector) werden toleriert statt als Fehler geworfen - 2026-04-26 | fix | gateway | routeDataFiles: `mode=filterValues` nutzt jetzt `enrichRowsWithFkLabels` + `handleFilterValuesInMemory` statt direktem `getDistinctColumnValues` — FK-Spalten (mandateId, featureInstanceId) zeigen wieder Labels statt UUIDs - 2026-04-26 | fix | gateway | routeFeatureTrustee: 3x `mode=filterValues` (Documents, Positions, generisch) von `getDistinctColumnValuesWithRBAC` auf `enrichRowsWithFkLabels` + `handleFilterValuesInMemory` umgestellt — FK-Spalten (organisationId, roleId, userId, contractId etc.) zeigen Labels statt UUIDs; generischer Endpunkt nutzt zusaetzlich `_buildFeatureInternalResolvers` fuer Feature-interne FKs - 2026-04-26 | fix | gateway | routeAudit: `_enrichUserAndInstanceLabels` setzt jetzt `NA(uuid)` als Fallback statt `None` fuer nicht aufloesbare FeatureInstance/User-IDs — Filter-Dropdown fuer Feature-Instanz war leer weil alle Labels `None` waren - 2026-04-26 | fix | gateway | Zwei Filter-Bugs: (1) `applyFiltersAndSort` in routeHelpers: `value is None` filtert jetzt auf leere Felder statt den Filter zu ueberspringen ("Leer"-Option funktioniert); (2) `routeAudit._applySortFilterSearch` durch Delegation an shared `applyFiltersAndSort` ersetzt — Datumsbereich-Filter (`between`-Operator) und Null-Filter funktionieren jetzt konsistent - 2026-04-26 | fix | gateway | Stille `except Exception`-Fallbacks in `mode=filterValues` entfernt: `routeFeatureTrustee` (_handleDocumentMode, _handlePositionMode, _paginatedReadEndpoint), `routeDataFiles`, `routeDataMandates` — Fehler bubblen jetzt hoch statt stillschweigend auf teuren In-Memory-Pfad auszuweichen - 2026-04-26 | fix | gateway | Filter-Dropdown-UUID-Bug: `enrichRowsWithFkLabels` fehlte im `mode=filterValues`-Pfad bei 8 Routen (routeDataConnections, routeInvitations, routeAdminFeatures, routeSubscription, routeFeatureRealEstate x2); Wiki `fk-label-resolution.md` mit Filter-Enrichment-Regel fuer AI-Agent ergaenzt - 2026-04-26 | refactor | gateway | FK-Metadaten konsolidiert: alle Datamodels nutzen `fk_target` mit Pflicht-Keys `db`/`table`/`labelField`; `fk_model`/`fk_label_field` entfernt; `_BUILTIN_FK_RESOLVERS["UserInDB"]`; `_buildLabelResolversFromModel` skip ohne `labelField`; `attributeUtils` setzt `displayField` nur bei gesetztem `labelField`; `validateFkTargets()` Startup-Validierung in `fkRegistry.py` + `app.py lifespan`; Wiki `fk-label-resolution.md` + `database-architecture.md` aktualisiert - 2026-04-26 | fix | gateway | `connectorDbPostgre._ensureTableExists`: TEXT->DOUBLE PRECISION Spalten-Migration schlug fehl fuer ISO-Datetime-Strings — Regex `\\d{{4}}` korrigiert zu `\\d{4}` (doppelte Klammern waren kein f-string-Escaping sondern literal), `::timestamp` auf `::timestamptz` (Timezone-Offset korrekt parsen), SAVEPOINT pro ALTER (eine fehlgeschlagene Migration killt nicht mehr die gesamte Transaktion) — betraf MandateSubscription (6 Spalten) und BackgroundJob (3 Spalten) - 2026-04-26 | refactor | frontend-nyla | FlowEditor Form-Field-Type-Zentralisierung: `FORM_FIELD_TYPES` + `FORM_FIELD_TYPE_LABELS` in `attributeTypeMapper.ts`; `FormStartNodeConfig`, `FormNodeConfig`, `FieldBuilderEditor` beziehen Feldtypen aus zentraler Library statt hardcoded Listen; ClickUp-spezifische Typen (`clickup_status`, `clickup_tasks`) und zugehoerige UI (Connection-Picker, Status-Hinweis) entfernt; shared `FormField`-Typ auf `AttributeType` + generisches `options` umgestellt; `TriggerFormFieldRow` eliminiert; `clickupFormSync.ts` geloescht (dead code, nirgends importiert) - 2026-04-26 | refactor | gateway+frontend-nyla | Column-Type-Refactoring Schritte 1-10 abgeschlossen: 6 Pydantic View-Modelle (`UserMandateView`, `FeatureAccessView`, `BillingTransactionView`, `MandateSubscriptionView`, `UiLanguageSetView`, `DataNeutralizerAttributesView`) in `datamodelViews.py`; `createdAt`/`createdBy` Aliase in Invitation- und Billing-DTOs auf `sysCreatedAt`/`sysCreatedBy` standardisiert; `_COL_MAP`-Remapping in `interfaceDbBilling` entfernt; 7 Admin/Billing/Compliance-Seiten beziehen Spaltentypen via `resolveColumnTypes` + `fetchAttributes('')` statt hardcoded `type:` — tsc + Grep-Completeness-Check bestanden - 2026-04-26 | refactor | frontend-nyla | Schritt 8: Vollstaendigkeits-Grep — verbleibende hardcoded `type:` in ComplianceAuditPage entfernt (username/instanceLabel/ipAddress im Modell); alle anderen type:-Werte berechtigt dokumentiert (enriched View-Spalten, synthetische Zaehler, Alias-Keys) - 2026-04-26 | refactor | frontend-nyla | Schritt 7: weitere FormGenerator-Tabellen (AdminUsers, Connections, Files/Prompts, Mandate-Hook, RealEstate*, Trustee*) bauen Spaltentypen nur noch via `resolveColumnTypes` statt `type: attr.type` im Column-Map - 2026-04-26 | feat | gateway | `attributeUtils.getModelClasses`: Feature-`datamodel*.py` unter `modules/features/**` rekursiv importieren (Trustee, Teamsbot, GraphicalEditor, …) fuer `/api/attributes/{entityType}` - 2026-04-26 | refactor | frontend-nyla | Workflow-Seiten (GraphicalEditorWorkflowsPage, AutomationsDashboardPage) beziehen Spaltentypen via `resolveColumnTypes` + `fetchAttributes` vom Backend statt hardcoded `type:` im Frontend; neuer Shared-Utility `columnTypeResolver.ts` - 2026-04-26 | feat | frontend-nyla | `attributesApi.AttributeDefinition.type` nutzt `AttributeType` aus `attributeTypeMapper`; Mapper um Backend-Typ `object` (JSON/Dict) ergaenzt - 2026-04-26 | feat | gateway | Pydantic CHECK-Cleanup: fehlendes `frontend_type: "timestamp"` bei float-Zeitfeldern (UAM resetTokenExpires, DataSource, Security Token, Chat ChatLog/publishedAt/ActionItem/TaskItem/TaskHandover, Knowledge extractedAt); Redmine `*OnTs` von `number` auf `timestamp`; Redmine DTOs (`RedmineSyncResultDto`, `RedmineSyncStatusDto`, `RedmineConfigDto`); `UsageStatistics.periodStart` mit `frontend_type: "date"`; alle `frontend_type: "datetime"` auf `"timestamp"` (Audit, AuthEvent, GraphicalEditor Auto*, Messaging sentAt) — konsistent mit `attributeTypeMapper.isDateTimeType` - 2026-04-26 | refactor | gateway | Boot-Optimierung: Chatbot-Duplikat-Prewarm entfernt (`routeFeatureChatbot`), Stripe-Bootstrap parallelisiert via ThreadPoolExecutor (`stripeBootstrap`) — erwartete Bootzeit-Reduktion ~8s - 2026-04-26 | fix | gateway | `interfaceRbac`: Pagination-Dict-Filter (`getRecordsetPaginatedWithRBAC` / `getDistinctColumnValuesWithRBAC`) nutzen `_rbacAppendPaginationDictFilter` — numerische `gt`/`gte`/`lt`/`lte`/`between` mit `::double precision`, ISO-Datum + numerische Spalte als Unix-Bounds wie Connector - 2026-04-26 | feat | frontend-nyla | `FormGeneratorTable`: Datum-Filter nutzt PeriodPicker (Presets + Kalender) statt primitiver ``; PeriodPicker rendert ausserhalb `filterDropdownOptions` (Popover nicht durch `overflow-y: auto` geclippt) - 2026-04-26 | fix | gateway | `TrusteeDataJournalEntry.bookingDate`: `Optional[str]` -> `Optional[float]` (unix timestamp); Konvertierung in `_persistJournal` via `_isoDateToTimestamp` (ValueError bei ungueltigem Datum, kein Fallback); `_aggregateLocalMovements` liest float; FK-Label-Resolver formatiert float als ISO; Demo-Daten konvertiert; DB-Migration TEXT->DOUBLE PRECISION in `_ensureTableExists` - 2026-04-26 | fix | gateway | `datamodelFeatureTrustee`: `lastSyncAt`/`chartCachedAt`/`syncedAt` bekommt `frontend_type: "timestamp"`, `lastSyncDateFrom`/`lastSyncDateTo` `frontend_type: "date"` — damit Frontend Date-Filter statt Text anzeigt - 2026-04-26 | fix | frontend-nyla | `FormGeneratorTable`: Filter-Dropdown per `useLayoutEffect` als `position: fixed` in den Viewport geklemmt; Audit-Timestamp-Spalten (`sysCreatedAt` etc.) bei numerischem `type` als Datums-UI + kein distinct-Fetch - 2026-04-26 | feat | frontend-nyla | `FormGeneratorTable`: typbezogene Spaltenfilter — Zahlen (`integer`/`int`/`number`/`float`) mit Operator (=, >, >=, <, <=, Zwischen) und `Anwenden`; Datum-Filter mit CSS-Panel; kein `filterValues`-Fetch mehr fuer bool/date/number-Spalten - 2026-04-26 | fix | gateway | `routeHelpers._matchesBetween`: numerische `from`/`to` nach fehlgeschlagenem Datums-Parse (korrekte BETWEEN-Logik fuer Zahlenspalten); `connectorDbPostgre`: `gt`/`gte`/`lt`/`lte` und `between` auf INTEGER/DOUBLE PRECISION mit `::double precision` statt lexikographischem TEXT-Vergleich ## 2026-04-25 - 2026-04-25 | feat | * | Phase 4 FK: `frontend_fk_*` und FormGenerator-`fkSource`/Client-Cache entfernt; `fk_label_field` + `displayField` only; `_resolveRoleLabels`; `getRecordsetPaginated` + `getRecordsetPaginatedWithRBAC` + FK-Sort-Pfad mit `_enrichRowsWithFkLabels`; `attributeUtils` + betroffene Datamodels + Pages auf reines Backend-Enrichment - 2026-04-25 | fix | gateway | Trustee Account Balances: echte Schlusssalden aus Buchhaltungssystem importieren (RMA via `/gl/saldo`; Bexio via Journal-Aggregation; Abacus via OData-Aggregation); korrigierte kumulative Fallback-Berechnung in `_persistBalances`; neues `AccountingPeriodBalance`-Modell + `getAccountBalances`-Methode in `BaseAccountingConnector`; Bug "Banksaldo per Stichtag falsch" (BuHa SoHa Konto 1020) geloest (c-work: c-work/4-done/2026-04-trustee-account-balances-import.md) - 2026-04-25 | test | gateway | Unit-Tests fuer Trustee-Balance-Import: RMA-Connector (BuHa-SoHa-Szenario + ER-Reset), Bexio-Connector (kumulative Aggregation + Carry-Over), Abacus-Connector (OData-Aggregation), AccountingDataSync (Connector-Path + Local-Fallback) - 2026-04-25 | feat | gateway | FK-Resolution Phase 2 (A1+A2): Neue zentrale `_enrichRowsWithFkLabels()` in `routeHelpers.py` — bulk-resolved FK-Labels als `{field}Label`-Spalten pro Row; `_resolveMandateLabels`/`_resolveInstanceLabels`/`_resolveUserLabels` liefern `None` statt ID bei fehlender Aufloesung; `routeWorkflowDashboard`, `routeAudit`, `routeBilling` (Transactions + Billing-Aggregation), `routeSubscription` auf zentrale Funktion migriert (`or mid[:8]` / `or uid[:8]` / `or iid`-Fallbacks entfernt) - 2026-04-25 | feat | gateway | FK-Resolution Phase 2 (B2): `_enrichedFilterValues` in `routeWorkflowDashboard` liefert `{value, label}` Objekte fuer FK-Spalten (mandateId, featureInstanceId) — Frontend zeigt Labels im Filter-Dropdown ohne separate `fkSource`-Aufloesung; Leerwerte (`null`) fuer "(Leer)"-Filter inkludiert - 2026-04-25 | fix | gateway+frontend | FK-Resolution Korrektur: `routeWorkflowDashboard` runs/workflows-Enrichment benennt `mandateIdLabel` → `mandateLabel` um (Frontend-Interface-Kompatibilitaet); `AutomationsDashboardPage` Spalten mandateId/featureInstanceId nutzen `displayField: 'mandateLabel'/'instanceLabel'` - 2026-04-25 | feat | gateway | FK-Resolution Phase 2 (B1): `getDistinctColumnValues` + `getDistinctColumnValuesWithRBAC` + `_extractDistinctValues` + `_distinctColumnValues` liefern `null` als letzten Eintrag wenn NULL/Leer-Zeilen existieren — Frontend kann "(Leer)"-Filter anbieten - 2026-04-25 | feat | frontend-nyla | FK-Resolution Phase 3 (C1+C2): `FormGeneratorTable.ColumnConfig.displayField` — neues Pattern: Cell rendert `row[displayField]` statt `row[key]`, CSV nutzt `displayField`; `fkSource`/`fkDisplayField` als `@deprecated` markiert (Legacy-Pfad funktioniert weiterhin) - 2026-04-25 | feat | frontend-nyla | FK-Resolution Phase 3 (B3): `FilterValuesList` akzeptiert `string | null | {value, label}` Eintraege; `FilterValue`-Typ eingefuehrt; `_normalizeFilterValue` normalisiert alle 3 Formate; Backend-`null`-Eintraege werden als "(Leer)"-Option gerendert - 2026-04-25 | fix | gateway | Fallback-Cleanup Phase 1 (D1+D2): Pagination-Parsing in `routeWorkflowDashboard` (runs/workflows) und `routeDataMandates` wirft 400 bei kaputtem JSON statt silent default; `runsByStatus`/Run-Enrichment in `/metrics` + `/workflows` propagieren DB-Fehler statt `logger.warning`+200; `delete_system_workflow` Callback-Trigger meldet Listener-Bugs (500 statt `except: pass`); `routeBilling._isAdminOfMandate`/`_isMemberOfMandate` und `routeSubscription._assertMandateAdmin` fail-loud (kein "DB-Down → 403"-Mask mehr); Stripe `Subscription.retrieve` im Checkout-Webhook re-raised statt silent skip - 2026-04-25 | fix | gateway | Fallback-Cleanup Phase 1 (D2): `routeInvitations` Rollen-Zuweisung — `addRoleToFeatureAccess`/`addRoleToUserMandate` sind bereits idempotent, daher `try/except: pass # Role might already be assigned` entfernt → echte FK-/DB-Fehler beim Einladungs-Akzept werden jetzt sichtbar - 2026-04-25 | fix | frontend-nyla | Fallback-Cleanup Phase 1 (D3+D4): `AutomationsDashboardPage._handleExecute` zeigt "Workflow gestartet" nur noch, wenn die 1s-Beobachtungs-Phase weder Erfolg noch Fehler beobachtet hat (kein Doppel-Toast "gestartet" + "fehlgeschlagen" mehr); `_loadMetrics` toast-t Backend-Fehler statt nur `console.error`; `Automation2FlowEditor.handleWorkflowRename` zeigt Fehler-Toast statt unsichtbarem `console.error` - 2026-04-25 | feat | frontend-nyla | `FormGeneratorTable`: Leerwert-Filter `(Leer)` in allen Filter-Dropdowns — filtert auf `IS NULL OR = ''` (Backend unterstützt bereits `null` in Pagination-Filtern); Filter-Icon/Clear-Button erkennen `null`-Filter korrekt via `key in filters` - 2026-04-25 | fix | frontend-nyla | NodeConfigPanel/RequiredAttributePicker/FeatureInstancePicker: Texte (Type-Badges, Bound-Refs, Vorschlag-Labels, Beschreibungen) verlassen den 280px-Panel-Frame nicht mehr — Header-Layout `label flex:1 1 100 %` lässt Badge umbrechen; `box-sizing: border-box`, `overflow-x: hidden`, `overflow-wrap: anywhere` als Safety-Net auf `.nodeConfigPanel`; Bound-Chip/Vorschlag-Button mit `whitespace: normal` + `word-break` - 2026-04-25 | fix | frontend-nyla | KeepAlive-Wrapper (`GraphicalEditor`, `Workspace`, `Commcoach`): Persistenz strikt pro `(mandateId, instanceId)` — `key={mandate:instance}` an die gehaltene Page; Wechsel der Mandanten-/Instanz-Tupel unmountet den alten Editor (kein Cross-Tenant-Save mehr, "not found"-Bug behoben); Unit-Test `GraphicalEditorKeepAlive.test.tsx` - 2026-04-25 | fix | frontend-nyla | `DataPicker`: per `createPortal` nach `document.body` (entkoppelt von `.nodeConfigPanel button`-Primary-Override); neues List-Row-Layout/Theme (`dataPickerNodeHeader` neutral), Header-Badge/Filter/Close-Styles, höheres z-index - 2026-04-25 | fix | frontend-nyla | Flow-Editor `CanvasHeader`: Zwei-Spalten-Layout (Kontext: fester Workflow-Dropdown + Titel mit Ellipsis | Aktionspanel); Run-Button `min-width`; Version-Zeile getrennt; `retryButton`-Margin im Toolbar-Panel neutralisiert - 2026-04-25 | fix | gateway | Trustee-Template `trustee-receipt-import`: `documentList` als DataRef `extract→process→sync` (Pick-not-Push), nicht leere Listen; Unit-Test `test_trustee_template_workflows.py` - 2026-04-25 | feat | gateway | Trustee + Redmine Nodes auf typisierten `FeatureInstanceRef[]`-Param + `frontendType: featureInstance` migriert (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | feat | gateway | Neuer Endpoint `GET /api/workflows/{instanceId}/options/feature.instance?featureCode=…` fuer Mandanten-gefilterte FeatureInstance-Auswahl (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | feat | frontend-nyla | `FeatureInstancePicker` (0/1/N) als Renderer fuer `frontendType: featureInstance`; Sysadmin-Toggle "Schema-Details" im CanvasHeader (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | fix | frontend-nyla | NodeConfigPanel-Banner zeigt `param.name` statt der ausschweifenden Description (Tooltip enthaelt vollen Text); hidden-Pflicht-Params werden zentral in `findRequiredErrors` gefiltert (kein Phantom-Pflichtfeld mehr) (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | fix | frontend-nyla | DataPicker-Modal auf CSS-Variablen umgestellt; Hover-Safety-Net `dataPickerLeaf:hover *` haelt Type-Hints auf blauem Hintergrund lesbar (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | docs | wiki | Audit `2026-04-node-typization-audit.md` archiviert; Folge-Track-Doc `2026-04-feature-instance-ref-adapter-migration.md` direkt in `4-done/` als erledigt - 2026-04-25 | docs | wiki | Changelog-Konvention im `_CHANGELOG.md` eingefuehrt; in `README.md` + `doc-sync.mdc` referenziert - 2026-04-26 | fix | gateway+frontend | Automation Workflow-Tab: `Automation2WorkflowView` erstellt damit sysCreatedAt (timestamp) und lastStartedAt korrekt als PeriodPicker-Spalten erkannt werden; lastStartedAt nutzt jetzt AutoRun.startedAt statt sysCreatedAt; computed-field Filter/Sort via applyFiltersAndSort in-memory; Runs-Tab auf startedAt/completedAt umgestellt statt System-Audit-Felder - 2026-04-26 | perf | gateway | routeWorkflowDashboard get_system_workflows: N+1 AutoRun-Abfragen ersetzt durch LEFT JOIN + Subquery-Aggregation (eine Daten- + eine Count-Query); FK-Sort-Pfad nutzt eine gebündelte Run-Stats-Query; lastStartedAt/runCount/isRunning-Filter im Join-Pfad in SQL