405 lines
19 KiB
Markdown
405 lines
19 KiB
Markdown
<!-- status: canonical -->
|
|
<!-- lastReviewed: 2026-04-21 -->
|
|
|
|
# Coding-Konventionen
|
|
|
|
## Naming
|
|
|
|
- Alle internen Funktionen beginnen mit `_` Prefix (nicht exportierbar)
|
|
- **camelCase** fuer Variablen und Funktionsnamen (kein snake_case)
|
|
- **PascalCase** fuer Klassen und Pydantic-Models
|
|
- Dateien: `camelCase` fuer Module (z.B. `mainServiceAi.py`, `routeBilling.py`)
|
|
|
|
## i18n Grundprinzip: Uebersetzen an der Quelle
|
|
|
|
Jeder Text wird **an der Quelle** uebersetzt. Backend-Strings werden im Backend uebersetzt, Frontend-Strings im Frontend. Der Empfaenger rendert direkt — keine doppelte Uebersetzung, keine Checks.
|
|
|
|
**Backend liefert IMMER in der korrekten Sprache.** Das Frontend muss nichts pruefen oder nochmals uebersetzen.
|
|
|
|
| Text-Quelle | Wer uebersetzt | Empfaenger |
|
|
|---|---|---|
|
|
| Frontend-Komponente (Button, Label, h1) | Frontend: `t('Speichern')` | rendert direkt |
|
|
| Backend statische Struktur (Navigation, Katalog) | Backend: `t()` registriert Key, `resolveText()` liefert zur Request-Zeit | Frontend: `item.uiLabel` direkt rendern |
|
|
| Backend API-Response (Fehlermeldung, Erfolg) | Backend: `t("Zugriff verweigert")` | Frontend: `error.detail` direkt rendern |
|
|
| Backend DB-Wert (TextMultilingual) | Backend: `resolveText(role.description)` | Frontend: `role.description` direkt rendern |
|
|
|
|
**Kernregel:** Strings vom Backend sind **immer bereits uebersetzt**. Das Frontend darf `t()` **nie** auf Backend-Werte anwenden — weder `t(item.label)` noch `t(variable)`. Keine `typeof === 'object'` Checks, keine Fallback-Ketten. Nur eigene Frontend-Literale wie `t('Speichern')`.
|
|
|
|
**Redundanz vermeiden:** Derselbe Text darf nicht an zwei Stellen mit `t()` getaggt werden.
|
|
|
|
## Frontend (React/TypeScript)
|
|
|
|
- Keine Browser-Dialoge (`alert`, `confirm`, `prompt`) -- stattdessen `useConfirm()` / `usePrompt()` Hooks
|
|
- CSS Modules fuer Styling
|
|
- Hooks-Pattern fuer State und API-Zugriffe (`useApiRequest`, `useBilling`, etc.)
|
|
- Fehler propagieren -- keine stillen Fallbacks bei kritischen Pfaden
|
|
|
|
### i18n-Pflicht: `t()` fuer alle UI-Texte
|
|
|
|
Jeder sichtbare Text im UI (Labels, Buttons, Placeholders, Tooltips, Fehlermeldungen) **muss** mit `t()` getaggt werden. Hardcodierte deutsche Strings im JSX sind nicht erlaubt.
|
|
|
|
```tsx
|
|
import { useLanguage } from '../../providers/language/LanguageContext';
|
|
|
|
const { t } = useLanguage();
|
|
|
|
// Einfacher Text
|
|
t('Speichern')
|
|
|
|
// Mit Variablen-Interpolation
|
|
t('{count} Eintraege gefunden', { count: String(total) })
|
|
|
|
// Gleicher Text, anderer Kontext → Klammer als Kontext-Hinweis
|
|
t('Offen (Status)') // vs. t('Offen (Zustand)')
|
|
```
|
|
|
|
- **Key = deutscher Klartext** (kein Dot-Notation-Schema)
|
|
- **`t()` NUR mit String-Literalen** — `t(variable)` ist verboten. Backend-Werte (Navigation-Labels, Feature-Labels, Role-Descriptions etc.) sind bereits uebersetzt und werden direkt gerendert:
|
|
|
|
```tsx
|
|
// FALSCH - Backend-Wert nochmal durch t()
|
|
label: t(item.uiLabel) // Backend hat schon uebersetzt!
|
|
<h1>{t(plan.title)}</h1> // Backend hat schon uebersetzt!
|
|
|
|
// RICHTIG - Backend-Wert direkt rendern
|
|
label: item.uiLabel
|
|
<h1>{plan.title}</h1>
|
|
```
|
|
|
|
- Fuer statische Frontend-Maps (Wochentage, Monatsnamen, Status-Labels) wird `t()` per `switch`-Funktion mit Literalen aufgerufen, nicht ueber Map-Lookup:
|
|
|
|
```tsx
|
|
// FALSCH - t() mit Variable
|
|
const labels: Record<string, string> = { budget: 'Budget-Vergleich', kpi: 'KPI-Dashboard' };
|
|
{t(labels[tabId])}
|
|
|
|
// RICHTIG - t() mit String-Literal per switch
|
|
function _tabLabel(tabId: string, t: (k: string) => string): string {
|
|
switch (tabId) {
|
|
case 'budget': return t('Budget-Vergleich');
|
|
case 'kpi': return t('KPI-Dashboard');
|
|
default: return tabId;
|
|
}
|
|
}
|
|
```
|
|
|
|
- Kein Plural-Framework -- separate Keys verwenden: `t('1 Eintrag')` vs. `t('{count} Eintraege', { count })`
|
|
- Fehlende Keys erscheinen als `[Text]` (eckige Klammern = unuebersetzt)
|
|
- Admin synchronisiert Sprachsets ueber Administration → System → UI-Sprachen → "Alle aktualisieren"
|
|
|
|
#### Platzhalter-Namen sind Code, nicht Text
|
|
|
|
Die Token in `{...}` (z. B. `{konten}`, `{count}`) sind interne Variablen-Namen
|
|
und werden zur Laufzeit durch das Param-Objekt ersetzt. **Sie duerfen nie
|
|
uebersetzt werden** — auch wenn sie wie deutsche oder englische Woerter aussehen.
|
|
|
|
```tsx
|
|
// Quelle (immer Deutsch, weil Source-Sprache):
|
|
t('{konten} Konten, {buchungen} Buchungen', { konten: '215', buchungen: '72' })
|
|
|
|
// Korrekte EN-Uebersetzung in der DB:
|
|
'{konten} accounts, {buchungen} entries'
|
|
// ^^^^^^^ ^^^^^^^^^^^ <-- Token bleiben Deutsch!
|
|
|
|
// FALSCH (wuerde zu rohem '{accounts}' im UI fuehren):
|
|
'{accounts} accounts, {entries} entries'
|
|
```
|
|
|
|
Der AI-Uebersetzer verletzt diese Regel ab und zu, deshalb laeuft in
|
|
`routeI18n._enforcePlaceholdersOnBatch` und `i18nRegistry._loadCache` ein
|
|
deterministischer Guard, der bei gleicher Token-Anzahl die Source-Namen
|
|
positionsweise wiederherstellt. Bei abweichender Anzahl bleibt der Wert
|
|
unangetastet — das muss der Reviewer manuell pruefen.
|
|
|
|
## Backend (FastAPI/Python)
|
|
|
|
- **Pydantic-Models** als einzige Quelle fuer UI-Feld-Definitionen
|
|
- **`PowerOnModel`** als Basis mit System-Audit-Feldern (`sysCreatedAt`, `sysCreatedBy`, `sysModifiedAt`, `sysModifiedBy`)
|
|
- Fehler propagieren -- Exceptions explizit werfen, nicht schlucken
|
|
- Config ueber `APP_CONFIG` (aus `modules/shared/configuration.py`)
|
|
|
|
### Async-Routen / Background-Jobs: niemals den Event-Loop blockieren
|
|
|
|
Alle FastAPI-`async def`-Routen und alle Background-Job-Handler
|
|
(`registerJobHandler`) laufen auf dem **gemeinsamen** Event-Loop des Workers.
|
|
Synchroner, blockierender Code (psycopg2, requests, time.sleep, ...) friert
|
|
damit den ganzen Worker ein -- inklusive Health-Checks. Eine grosse Schleife
|
|
mit `db.recordCreate(...)` reicht, um die Instanz fuer Minuten unerreichbar
|
|
zu machen.
|
|
|
|
**Regeln:**
|
|
|
|
- HTTP / IO mit nativem `await` aufrufen (httpx, aiohttp, asyncpg, ...).
|
|
- Sync-Code (psycopg2, lange CPU-Schleifen) **immer** in
|
|
`await asyncio.to_thread(_syncFn, ...)` verpacken.
|
|
- Bei grossen Datenmengen (>100 Zeilen) **niemals** `recordCreate` /
|
|
`recordDelete` in der Schleife aufrufen. Stattdessen die Bulk-Methoden
|
|
des Connectors verwenden:
|
|
- `db.recordCreateBulk(model, rows)` -> ein `execute_values` + ein COMMIT
|
|
- `db.recordDeleteWhere(model, recordFilter)` -> ein `DELETE WHERE ...`
|
|
- Bei laengeren Phasen Heartbeat-Logs alle 500-1000 Items, damit Hangs
|
|
im Log sichtbar sind.
|
|
|
|
Referenz-Implementation: `modules/features/trustee/accounting/accountingDataSync.py`
|
|
(jede Phase: `await connector.fetch(...)` -> `await asyncio.to_thread(self._persistXxx, ...)`).
|
|
|
|
### Datum/Zeit: UTC fuer Storage, Request-TZ fuer User-sichtbare Werte
|
|
|
|
Backend-Code, der **gespeicherte** Zeitstempel produziert (DB-Felder, Audit-Logs, Token-Expiry), nutzt **UTC** via `getUtcTimestamp()`, `getUtcNow()` oder `getIsoTimestamp()` aus `modules/shared/timeUtils.py`.
|
|
|
|
Backend-Code, der **user-sichtbare** "jetzt"-Werte produziert (AI-Agent-System-Prompt, gerenderte Display-Strings, formatierte Logs an den Endnutzer), nutzt `getRequestNow()` / `getRequestTimezone()`. Diese lesen die Browser-Zeitzone, die das Frontend per `X-User-Timezone`-Header schickt (Axios-Interceptor in `ui-nyla/src/api.ts`) und die `_requestContextMiddleware` (`app.py`) in eine `ContextVar` schreibt — analog zum `_setLanguage`-Pattern.
|
|
|
|
```python
|
|
from modules.shared.timeUtils import getUtcTimestamp, getRequestNow
|
|
|
|
createdAt = getUtcTimestamp() # DB-Speicherung -> UTC float
|
|
nowForUser = getRequestNow() # Anzeige/Prompt -> tz-aware datetime in User-TZ
|
|
```
|
|
|
|
**Verboten:** Hardcoded-Zeitzonen wie `ZoneInfo("Europe/Zurich")` als Default fuer User-Anzeige. Stattdessen die Request-TZ benutzen; ohne HTTP-Kontext (z.B. Scheduler) faellt `getRequestNow()` automatisch auf UTC zurueck.
|
|
|
|
**Frontend:** User-sichtbare Zeitstempel werden mit `formatUnixTimestamp()` aus `ui-nyla/src/utils/time.ts` formatiert; ohne expliziten `timeZone`-Override nimmt `toLocaleString` die Browser-TZ. Backend-Felder bleiben UTC-Floats (`number`).
|
|
|
|
### Debug-File-Dumps: nur DEV, niemals INT/PROD
|
|
|
|
Code, der zu Debug-Zwecken raw payloads / Prompts / Sync-Daten auf die Disk
|
|
schreibt, **muss** ein dediziertes Env-Flag pruefen. Pattern:
|
|
|
|
- `APP_DEBUG_<FEATURE>_ENABLED` (`True` nur in `env_dev.env`)
|
|
- `APP_DEBUG_<FEATURE>_DIR` (gateway-relativer Pfad ist OK; absoluter Pfad
|
|
nur fuer DEV-Workstations)
|
|
|
|
Beispiele: `APP_DEBUG_CHAT_WORKFLOW_*`, `APP_DEBUG_ACCOUNTING_SYNC_*`. Hardcoded
|
|
Windows-Pfade wie `D:/...` sind verboten (laufen auf Linux als
|
|
Relativ-Pfad an und schreiben unkontrolliert ins Dateisystem).
|
|
|
|
### i18n-Pflicht: `t()` fuer alle UI-sichtbaren Gateway-Texte
|
|
|
|
Jeder Text der im Frontend angezeigt wird (HTTPException-Details, API-Response-Messages, Erfolgs-/Fehlermeldungen) **muss** mit `t()` getaggt werden (nur **String-Literale** in `t()`).
|
|
|
|
**Dynamische Werte** (Feld aus DB, Katalog, verschachteltes Dict): `resolveText(wert)` aus `modules.shared.i18nRegistry` verwenden — nicht `t(variable)`. `resolveText` akzeptiert optional `lang="fr"` fuer Kontexte ohne Request-Middleware (z.B. Scheduler mit expliziter Nutzersprache).
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import t
|
|
|
|
# Fehlermeldung (context automatisch = "api")
|
|
raise HTTPException(status_code=403, detail=t("Zugriff verweigert", "api.routeSecurity",
|
|
"Fehlermeldung bei fehlendem Zugriff"))
|
|
|
|
# Erfolgsmeldung
|
|
return {"message": t("Datei erfolgreich hochgeladen", "api.routeFiles",
|
|
"Bestaetigung nach Datei-Upload")}
|
|
```
|
|
|
|
**Nicht** mit `t()` taggen: Log-Eintraege, AI-Prompts, interne technische Fehlermeldungen.
|
|
|
|
### Statische Dicts mit i18n-Keys: t() registriert, resolveText() liefert
|
|
|
|
Statische Dicts (Navigation, Kataloge) haben **zwei Schritte**:
|
|
|
|
1. **Import-Zeit:** `t()` im Dict **registriert** den Key (und gibt den deutschen String zurueck, der im Dict gespeichert wird)
|
|
2. **Request-Zeit:** `resolveText()` in der Route nimmt den deutschen String und liefert die Uebersetzung fuer die aktuelle Sprache
|
|
|
|
```python
|
|
# mainSystem.py — t() registriert Keys bei Import-Zeit
|
|
from modules.shared.i18nRegistry import t
|
|
|
|
NAVIGATION_SECTIONS = [
|
|
{"title": t("Meine Sicht"), "items": [
|
|
{"label": t("Uebersicht"), "objectKey": "ui.system.home", ...},
|
|
]},
|
|
]
|
|
```
|
|
|
|
```python
|
|
# routeSystem.py — resolveText() uebersetzt zur Request-Zeit
|
|
from modules.shared.i18nRegistry import resolveText
|
|
|
|
def _formatBlockItem(item):
|
|
return {"uiLabel": resolveText(item["label"]), ...}
|
|
```
|
|
|
|
**Warum zwei Schritte?** `t()` wird bei Import-Zeit ausgewertet — der Rueckgabewert ist immer deutsch (Default-Sprache). Der Wert im Dict ist daher ein fester deutscher String. `resolveText()` nimmt diesen deutschen String zur Request-Zeit und liefert die korrekte Uebersetzung.
|
|
|
|
**Warum Modul-Ebene?** `t()` registriert Keys nur beim ersten Aufruf. Wenn `t()` nur in einer Funktion steht, wird der Key erst beim ersten Request registriert — **nach** dem Boot-Sync. Der Key fehlt dann im `xx`-Set und kann nicht uebersetzt werden. Im UI erscheint er als `[Schluessel]` (eckige Klammern = Cache-Miss).
|
|
|
|
**Pflicht-Hook fuer dynamisch geladene Quellen:** Plugin-/Lazy-Module (z.B. Connector-Plugins, dynamisch entdeckte Klassen) deren `t()`-Aufrufe in einer Methode stecken (nicht auf Modul-Ebene), brauchen einen eigenen `_register…Labels()`-Hook in `_syncRegistryToDb()` (`modules/shared/i18nRegistry.py`). Beispiel: `_registerAccountingConnectorLabels()` importiert die Accounting-Connector-Registry und ruft `getRequiredConfigFields()` einmal beim Boot, damit die Field-Labels im `xx`-Set landen. Analog zu `_registerNodeLabels`, `_registerRbacLabels`, etc.
|
|
|
|
### TextMultilingual-Felder: Backend loest auf via resolveText()
|
|
|
|
`TextMultilingual`-Felder (User-Content in mehreren Sprachen, z.B. `Role.description`) werden im Backend via `resolveText()` aufgeloest bevor sie ans Frontend geliefert werden. Das Frontend erhaelt immer einen **String**, nie ein Dict.
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import resolveText
|
|
|
|
# In Route-Handlern:
|
|
"description": resolveText(role.description),
|
|
"label": resolveText(featureDef.get("label")) or featureCode,
|
|
```
|
|
|
|
**Keine lokalen Resolve-Helfer:** `_resolveTextMultilingual()`, `_resolveLabel()`, `_storeLabelText()`, `_featureLabelPlain()`, `_pickInvocationTitleLabel()` etc. sind verboten. Immer `resolveText()` aus `i18nRegistry` verwenden.
|
|
|
|
### Frontend: Keine Dict-Fallbacks fuer Labels
|
|
|
|
Das Frontend darf **keine** `label?.de || label?.en || Object.values(...)` Ketten verwenden. Wenn das Backend korrekt arbeitet, sind alle Labels Strings. Defensive `typeof === 'object'` Checks verdecken Backend-Fehler und sind verboten.
|
|
|
|
```tsx
|
|
// FALSCH - verdeckt Backend-Fehler
|
|
const name = typeof m.name === 'object' ? m.name.de || m.name.en : m.name;
|
|
|
|
// RICHTIG - Backend liefert String
|
|
const name = m.label || m.name || m.id;
|
|
```
|
|
|
|
Fuer Route-Module gibt es den Shorthand `apiRouteContext`:
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import apiRouteContext
|
|
routeApiMsg = apiRouteContext("routeBilling")
|
|
|
|
raise HTTPException(status_code=403, detail=routeApiMsg("Zugriff verweigert"))
|
|
```
|
|
|
|
### Pydantic-Models: `@i18nModel` Decorator Pflicht
|
|
|
|
Jedes Pydantic-Model das im UI angezeigt wird (Tabellen, Formulare) **muss** den `@i18nModel` Decorator haben. Feld-Labels werden in `json_schema_extra["label"]` definiert.
|
|
|
|
```python
|
|
from modules.shared.i18nRegistry import i18nModel
|
|
|
|
@i18nModel("Benutzer")
|
|
class User(PowerOnModel):
|
|
name: str = Field(
|
|
description="Full name of the user",
|
|
json_schema_extra={"label": "Name", "frontend_type": "text"}
|
|
)
|
|
email: str = Field(
|
|
description="Email address for login and notifications",
|
|
json_schema_extra={"label": "E-Mail-Adresse", "frontend_type": "text"}
|
|
)
|
|
```
|
|
|
|
- `@i18nModel("Deutscher Modelname")` -- AI-Kontext kommt automatisch aus dem Class-Docstring
|
|
- `json_schema_extra={"label": "Deutscher Feldname"}` -- Pflicht fuer jedes UI-sichtbare Feld
|
|
- `Field(description=...)` -- wird als AI-Kontext fuer die Uebersetzung verwendet
|
|
- **Kein** `registerModelLabels()` mehr verwenden (entfernt)
|
|
|
|
### Feature-Module: Labels als deutsche Basis-Strings
|
|
|
|
`DATA_OBJECTS`, `RESOURCE_OBJECTS` und `UI_OBJECTS` Labels verwenden **deutsche Klartext-Strings** als Basis-Key. Labels werden beim Boot automatisch ueber `_registerRbacLabels()` registriert und erscheinen im xx-Basisset. Bei Runtime werden sie via `resolveText()` aufgeloest.
|
|
|
|
```python
|
|
DATA_OBJECTS = [
|
|
{
|
|
"objectKey": "data.uam.UserInDB",
|
|
"label": "Benutzer",
|
|
"meta": {"table": "UserInDB", "namespace": "uam"}
|
|
},
|
|
]
|
|
```
|
|
|
|
Legacy `{en, de, fr}`-Dicts werden via `_extractRegistrySourceText()` auf den `xx`-Key reduziert. Neue Eintraege: immer **String**, kein Dict.
|
|
|
|
## Projektstruktur Gateway
|
|
|
|
```
|
|
platform-core/
|
|
app.py # FastAPI-App, Middleware, Startup
|
|
config.ini # Statische Konfiguration
|
|
modules/
|
|
auth/ # JWT, OAuth, CSRF, Authentication
|
|
datamodels/ # Pydantic-Models (zentrale Quelle)
|
|
features/ # Feature-Module (workspace, automation, ...)
|
|
<name>/
|
|
main<Name>.py # FEATURE_CODE, Registrierung
|
|
routeFeature<Name>.py # HTTP-Endpunkte
|
|
interfaceFeature<Name>.py # DB-Interface
|
|
datamodelFeature<Name>.py # Feature-spezifische Models
|
|
interfaces/ # DB-Interfaces (CRUD, Queries)
|
|
routes/ # Core-Routes (billing, admin, GDPR, ...)
|
|
security/ # RBAC-Engine
|
|
serviceCenter/
|
|
core/ # serviceSecurity, serviceUtils, serviceStreaming
|
|
services/ # serviceAi, serviceChat, serviceAgent, ...
|
|
registry.py # Service-Registrierung und Dependencies
|
|
shared/ # configuration.py, Utilities
|
|
system/ # registry.py (Feature-Discovery)
|
|
workflows/
|
|
methods/ # Unified Action Library
|
|
processing/ # WorkflowProcessor, Modes
|
|
automation/ # v1 Runtime
|
|
automation2/ # v2 Engine
|
|
connectors/ # Externe Systeme (DB, SharePoint, Jira, ...)
|
|
aicore/ # AI-Provider-Plugins, Model-Selector
|
|
```
|
|
|
|
## Feature Data Sub-Agent: Ontologie statt freier Domain-Hints (ab 2026-05)
|
|
|
|
Wenn ein Feature dem AI-Agent strukturierten Zugriff auf seine Daten gibt (typisch ueber `queryFeatureInstance` -> Feature Data Sub-Agent), exportiert es eine **`OntologyDescriptor`** statt eines freien Hint-Strings.
|
|
|
|
**Alt (deprecated, nur noch als Fallback):**
|
|
|
|
```python
|
|
def getAgentDomainHints() -> str:
|
|
return "Bankkonten = accountNumber LIKE '102%'. closingBalance ist already-aggregated, niemals SUM-en. ..."
|
|
```
|
|
|
|
**Neu (Standard ab Mai 2026):**
|
|
|
|
```python
|
|
from modules.serviceCenter.services.serviceAgent.datamodelOntology import (
|
|
OntologyDescriptor, Entity, Constraint, ConstraintRule,
|
|
CanonicalQueryPattern, SemanticType,
|
|
)
|
|
|
|
_ONTOLOGY = OntologyDescriptor(
|
|
featureCode="myfeature",
|
|
entities=[
|
|
Entity(
|
|
name="BankAccount",
|
|
pythonClass="MyFeatureAccount",
|
|
semanticType=SemanticType.ACCOUNT,
|
|
parentEntity="Account",
|
|
description="Account with accountNumber LIKE '102%'.",
|
|
),
|
|
],
|
|
constraints=[
|
|
Constraint(
|
|
appliesTo="MyFeatureAccountBalance.closingBalance",
|
|
rule=ConstraintRule.NEVER_AGGREGATE,
|
|
message="closingBalance is per-period already; query with periodYear+periodMonth, never SUM/AVG it.",
|
|
),
|
|
],
|
|
canonicalPatterns=[
|
|
CanonicalQueryPattern(
|
|
intent="BANK_BALANCE_AT_DATE",
|
|
description="Saldo eines Bankkontos per Jahresende.",
|
|
pattern={
|
|
"tool": "queryTable",
|
|
"tableName": "MyFeatureAccountBalance",
|
|
"filters": [
|
|
{"field": "accountNumber", "op": "=", "value": "<accountNumber>"},
|
|
{"field": "periodYear", "op": "=", "value": "<year>"},
|
|
{"field": "periodMonth", "op": "=", "value": 0},
|
|
],
|
|
"fields": ["closingBalance", "currency"],
|
|
},
|
|
),
|
|
],
|
|
)
|
|
|
|
def getAgentOntology() -> OntologyDescriptor:
|
|
return _ONTOLOGY
|
|
```
|
|
|
|
**Warum:** Die Ontologie ist die Single Source of Truth fuer Prompt **und** Validator. Aenderungen an einer `Constraint` wirken sowohl auf den AI-Steering-Block als auch auf die deterministische Pre-Execute-Validierung -- es gibt keine Drift zwischen Prompt-Text und Tool-Reject-Logik mehr. Mehr Hintergrund: `b-reference/platform-core/ai-agent.md` Abschnitt "FeatureDataAgent: Query-Repair-Loop + Ontologie" und `b-reference/platform-core/features/trustee.md` als Referenz-Pilot.
|
|
|
|
**Backward-Compatibility:** Features, die `getAgentOntology()` (noch) nicht haben, behalten ihren bestehenden `getAgentDomainHints()`-Pfad ohne Code-Change. Der Sub-Agent nutzt automatisch den passenden Block.
|
|
|
|
## Anti-Patterns
|
|
|
|
- Keine impliziten Type-Conversions in API-Responses
|
|
- Keine DB-Queries in Route-Handlern (immer ueber Interfaces)
|
|
- Kein direkter `os.environ`-Zugriff -- immer `APP_CONFIG`
|
|
- Keine hartkodierten Secrets -- verschluesselt in Env-Dateien
|
|
- Neue Features: kein `getAgentDomainHints() -> str` mehr. Wenn der AI-Agent auf die Feature-Daten zugreifen soll, `getAgentOntology() -> OntologyDescriptor` exportieren (siehe Abschnitt oben).
|