diff --git a/nexus-dev/skills/nexus-stack/SKILL.md b/nexus-dev/skills/nexus-stack/SKILL.md index d143009..fe13f76 100644 --- a/nexus-dev/skills/nexus-stack/SKILL.md +++ b/nexus-dev/skills/nexus-stack/SKILL.md @@ -35,7 +35,7 @@ Regeln: `configure_logging` vor allem anderen. Neue Router über `include_router ## Settings (`src/nexus/config.py`) -Eine `Settings`-Klasse (pydantic-settings, Prefix `NEXUS_`), Zugriff via `get_settings()` (lru_cache). Tests leeren den Cache. `Settings.__repr__` redacted Secrets — bei neuen Secret-Feldern (`*_secret`, `*_password`, DSN) beibehalten. Werte kommen prod aus `/etc/nexus/env`. Reale Felder u.a.: `host` (Default `127.0.0.1`), `public_url`, `database_url`, `oidc_*`, `session_secret`, `llm_policy_path`, `anthropic_api_key`. **Fail-closed by default:** fehlende Konfiguration ⇒ Feature aus (None), nie ein unsicherer Default. +Eine `Settings`-Klasse (pydantic-settings, Prefix `NEXUS_`), Zugriff via `get_settings()` (lru_cache). Tests leeren den Cache. `Settings.__repr__` redacted Secrets — bei neuen Secret-Feldern (`*_secret`, `*_password`, DSN) beibehalten. Werte kommen prod aus `/etc/nexus/env`. Reale Felder u.a.: `host` (Default `127.0.0.1`), `public_url`, `database_url`, `oidc_*`, `session_secret`, `llm_policy_path`, `anthropic_api_key`. **Fail-closed gilt für die nullable Secrets/DSN:** ohne `database_url`/`oidc_discovery_url`/`oidc_client_secret`/`session_secret`/`anthropic_api_key` ist das jeweilige Feature aus. Achtung: NICHT alle Felder sind so — `oidc_client_id` hat den Default `nexus-web`, `public_url` einen http-localhost-Default, und ohne `session_secret` läuft die SessionMiddleware mit einem EPHEMEREN Secret. Den OIDC-Login schaltet erst `build_oauth` fail-closed ab (None ⇒ `/auth/login` 503), wenn Discovery/Secret/Session-Secret fehlen oder in Prod `public_url` nicht https ist. ## Lese-Pfad: `nexus/visibility.py` ist der EINZIGE Query-Weg (SICHT-1/2/3) @@ -52,7 +52,7 @@ if entry is None: raise HTTPException(404, "not found") # nicht-sicht - Sichtbarkeit wird IN der Query durchgesetzt (kein Python-Nachfilter); `viewer` ist Pflichtparameter. Share-Zeilen wirken nur bei `visibility=='geteilt'`. - SICHT-2: nicht-sichtbar liefert `None` ⇒ überall dieselbe 404 (gleicher Text), nie ein abweichender Code/Zählwert. - Kein Admin-Bypass auf Inhalte. -- **Erzwungen:** `tests/test_architecture.py` scannt `src/` und verbietet `select(Entry…)`, `.get(Entry…)`, `aliased(…)`, `models.X` und Raw-SQL auf die geschützten Tabellen außerhalb von `visibility.py`/`models.py`. Wer eine neue sichtbarkeitsrelevante Query braucht: Helper IN `visibility.py` ergänzen, nicht den Test aufweichen. Jede neue Lese-Route kommt zusätzlich in die **SICHT-4-Suite** (`tests/test_sicht4.py`). +- **Erzwungen:** `tests/test_architecture.py` scannt `src/` und verbietet die geschützten Modelle (`Entry`, auch als `models.Entry`) INNERHALB der Query-Konstrukte `select(…)`, `.get(…)`, `.query(…)`, `aliased(…)` sowie Raw-SQL (`text(…)`) auf die geschützten Tabellen — alles außerhalb von `visibility.py`/`models.py`. Ein bloßer `models.Entry`-Bezug (z. B. Typannotation) ist erlaubt; der verbotene Pfad ist die Query. Wer eine neue sichtbarkeitsrelevante Query braucht: Helper IN `visibility.py` ergänzen, nicht den Test aufweichen. Jede neue Lese-Route kommt zusätzlich in die **SICHT-4-Suite** (`tests/test_sicht4.py`). ## LLM: `router.run(...)` ist die EINZIGE Modell-Schnittstelle (PRIV-1…6) @@ -64,7 +64,7 @@ with LLMRouter() as r: ``` Hartverdrahtete Garantien (nicht umbauen ohne ADR): -- **Reihenfolge in `run`:** PRIV-6-Sensitivitäts-Pre-Check → Policy-Decision → forbidden? → Endpoint-Auflösung → **Hard Guard** → Health-Check (lokal) → Call. Jeder Fehlerpfad endet in `blocked` + Audit; **kein** externer Fallback, auch nicht bei Timeout. +- **Reihenfolge in `run`:** PRIV-6-Sensitivitäts-Pre-Check → Policy-Decision → forbidden? → Endpoint-Auflösung → **Hard Guard** → Health-Check (lokal) → Call. Jeder Fehlerpfad endet in `blocked`; **kein** externer Fallback, auch nicht bei Timeout. Das begleitende Audit hängt am `audit_sink`: per Default schreibt der Router via Session, und ohne erreichbare/ konfigurierte DB (`audit_session`/`NEXUS_DATABASE_URL`) kann der Audit-Write fehlschlagen — der Router gibt dann zwar `blocked` zurück, das Event ist aber nicht garantiert persistiert. Wer das Audit verlässlich braucht (K-108 Queue), injiziert eine Session/Sink. - **Hard Guard:** `privat`/`familie` an `kind=="external"` ⇒ `blocked` — zweite Verteidigungslinie vor der Policy. - **Allowlist:** `EndpointConfig` validiert `kind=="local"` gegen `LOCAL_ENDPOINT_ALLOWLIST` (gepinnte luki-ai host:port) — eine als „local" getarnte externe URL wird beim Policy-Load abgewiesen. Endpoints sind Konfiguration (`config/llm_policy.default.json`), nie Code. - Policy-Änderung nur über `write_policy_as_admin` (admin-only, auditiert).