Dokumentation: Seiten auswählen

Admin-Dashboard (Modell / Budget / Usage)

Steuer- & Sichtfläche für die LLM-Kostengovernance aus ADR-0017. Entscheidung: ADR-0024. Gleichwertig als UI + API + MCP (AI-Parität, ADR-0018) — die Logik liegt einmalig in lib/llm/* (Single Source).

Was man kann

  • Modell je Aufgabe wählen (classification, embedding, standardReport, premiumReport, pdfExtraction, toolUse, bulkParse) — Override schlägt ENV/Default, sofort wirksam (Cache-Invalidierung).
  • Budget-Cap (USD Tag/Monat) setzen — die Engine bremst fail-open bei Überschreitung (ADR-0017).
  • Ist-Kosten sehen: heute / Monat + je Task+Modell (Calls/Tokens/$).

Benutzerverwaltung & Impersonation

Eigener Admin-Bereich (ab Dashboard-Nav verlinkt), proxy.ts-gated (platform_admin + 2FA) und zusätzlich server-seitig geprüft.

  • /admin/users — Nutzerliste mit Suche (E-Mail/Name) + Rollen-Filter; pro Zeile Auge-Button (Session-Takeover, ADR-0026 v2 — wechselt in die Sicht des Nutzers mit dessen Rolle/Rechten; orange Leiste oben zeigt den Modus, dort auch „Beenden“), Kontext (read-only Inspektion via ?inspect=, auditiert) und Detail. Unten: Admin-Audit + Impersonation-Audit.
  • /admin/users/[id] — Plattform-Rolle ändern (user / support / platform_admin) und Konto DSGVO-konform löschen (gleiche Erasure-Kaskade wie die Selbst-Löschung).
  • /admin/impersonate — nur noch Redirect auf /admin/users (Konsole dort integriert); die Headless-API GET/POST /api/v1/admin/impersonate bleibt unverändert.

Sicherheit (fail-closed): nur verifizierte platform_admin, nicht aus einer Impersonation heraus; kein Self-Target; kein Degradieren des letzten Admins (Lockout-Schutz); kein Löschen eines anderen Admins; Org-Owner-Guard vor der Löschung; striktes Audit vor der Löschung. Rollen-/Lösch-Eingriffe landen in admin_audit_log, Impersonation weiter in impersonation_log. Rollen-Änderungen wirken für den Ziel-Nutzer erst ab dessen nächstem Login/Token-Refresh (JWT-Sessions).

Seiten im Admin-Bereich

Alle Seiten verlangen die Plattformrolle platform_admin und verifizierte TOTP-2FA; mutierende Aktionen sind während einer Impersonation gesperrt. Stand: 07.10.2026, abgeglichen mit app/admin/** und components/AdminNav.tsx.

RouteZweck
/adminÜbersicht: Modell je Aufgabe, Budget-Caps, LLM-Ist-Kosten
/admin/users, /admin/users/[id]Benutzerverwaltung, Rollen, Löschung, Impersonation (siehe oben)
/admin/anfragenZugangs-/Pilotanfragen aus dem Formular: Status-Triage (neu, qualifiziert, nicht_qualifiziert, spam), Folgevertrag-Markierung, CSV-Export
/admin/data-sourcesDatenquellen, aktive Releases, Drift-Überwachung
/admin/cron, /admin/cron/[name], /admin/cron/[name]/runs/[id]Cron-Jobs, Laufhistorie, einzelner Lauf (siehe „Cron-Jobs“)
/admin/metricsWerkzeug-KPIs und interner Prüfmodus
/admin/kostenTag-90-Gate: Kosten pro Tool-Abschluss, qualifiziertem Lead und aktivem Source Release
/admin/impersonatenur Redirect nach /admin/users

Nur ein Mensch setzt in /admin/anfragen den Status qualifiziert; es gibt keinen automatischen Übergang. Dieser Status ist der Nenner „qualifizierter Lead“ in /admin/kosten.

Zugänge

ZugangAuthFür
UI /adminAuth.js platform_admin + 2FA (ADR-0006, Gate in proxy.ts)Menschen
API /api/v1/admin/*API-Key mit admin-ScopeSkripte/KI
MCP get_usage/set_model/set_budgetdito (LANDNUTZEN_API_KEY)KI-Ops

Kein/ungültiger Key → 401, gültiger Key ohne Scope → 403.

Datenquellen und Drift-Überwachung

/admin/data-sources trennt weiterhin den letzten technischen Lauf vom Betriebsnachweis des tatsächlich aktiven Releases. Zusätzlich zeigt die Seite für jeden qualifizierten Active Pointer den Zustand fresh, due, stale, failed oder unknown, seine individuellen Fristen, Coverage-/Quality-Befunde, den letzten Zustandswechsel und die Alarmzustellung.

Der stündliche Cron monitor-data-source-drift verwendet das bestehende CRON_SECRET und das cron_runs-Ledger. Seit 25.09.2026 verschickt er Zustandswechsel als Tagesdigest: höchstens eine Mail pro Wiener Kalendertag, frühestens ab 07:00, mit allen Wechseln seit dem letzten Digest, per world4you-SMTP ausschließlich an office@ostheimer.at (ADR-0048, Nachtrag). Alarmiert werden nur neue, geänderte, verschärfte oder behobene Zustände; unveränderter Drift verursacht keine wiederholten E-Mails. Noch nicht fällige Wechsel zeigt die Alarmzustellung als „Alarm ausstehend (nächster Tagesdigest)“. Fehlgeschlagene oder nicht konfigurierte Zustellung bleibt im Dashboard sichtbar und wird beim nächsten Lauf wiederholt. Bekannte Teilabdeckung und bereits freigegebene Qualitätswarnungen sind keine neuen Vorfälle. Der Monitor verändert keinen Release und keinen Active Pointer.

/admin/cron/monitor-data-source-drift zeigt die Laufhistorie mit Beobachtungen, Übergängen, Zustellstatus, Fingerprint und – beim versendenden Lauf – dem Digest-Tag alertDigestDay.

Cron-Jobs

/admin/cron listet alle Einträge aus vercel.json#crons zusammen mit dem Katalog lib/cron/catalog.ts, dem letzten Lauf aus dem Ledger cron_runs und einem 30-Tage-Aggregat; Stand 07.10.2026 sind es 28 Crons, Katalog und vercel.json stimmen überein. Ein manueller Trigger (POST /api/admin/cron/<name>/trigger, nur per Sitzung eines Plattform-Admins) ruft die Cron-Route mit dem CRON_SECRET auf und schreibt triggered_by='admin'. /admin/cron/<name> zeigt die Laufhistorie eines Eintrags.

/admin/cron/<name>/runs/<id> zeigt einen einzelnen Lauf mit vollständigem Ergebnis-JSON. Seite und URL sind zugleich der Nachweis (evidence_ref) der Snapshot-Renewals auf Vercel (ADR-0080): renew-altlasten-snapshot (täglich 03:37 UTC), renew-hochwasser-snapshot (täglich 02:07 UTC) und renew-brownfield-snapshot (dienstags 03:19 UTC) ersetzen damit die frühere Actions-Run-URL; ohne cron_runs-Zeile veröffentlicht kein Lauf. Bei Reviewpflicht der Altlasten steht hier dieselbe Aktivierungsanleitung wie in der Review-Mail. cron-prune (sonntags 02:00 UTC) löscht Läufe, die älter als 90 Tage sind und nicht zu den letzten 30 Läufen ihres Crons gehören.

Kosten pro Tool-Abschluss (/admin/kosten)

Die Seite macht das Tag-90-Gate „Kosten sichtbar“ prüfbar (Vertrag docs/contracts/admin-cost-overview-v1.md, Headless-Gegenstück GET /api/v1/admin/cost-overview). Sie zeigt Kosten pro qualifiziertem Tool-Abschluss (PV-Ertragsrechner und seit 04.10.2026 Standort-Check), pro qualifiziertem Lead (/admin/anfragen) und pro aktivem Source Release, getrennt nach gemessenen direkten Kosten und einem optional konfigurierten Infrastrukturwert. Nicht erfasste oder nicht aussagekräftige Nenner stehen als eigener Status, nicht als Null. Der Abschnitt „Eigene Prüfläufe“ weist interne Läufe als „davon intern“ aus; sie zählen nie im Nenner (ADR-0079). Die Infrastrukturkosten sind nie live abgerufen, sondern ein optionaler Konfigurationswert mit Quelle und Datum (docs/API-ECONOMICS.md).

Betriebsmails

Alle automatischen Betriebsmails laufen über denselben Pfad wie die Login-Mails (lib/mail/send.ts, world4you-SMTP über EMAIL_SERVER, Absender EMAIL_FROM, ADR-0038) und gehen ausschließlich an office@ostheimer.at:

MailAuslöserTakt
Neue PilotanfragePOST /api/v1/access-requests (ohne Personendaten)sofort
Cron failed/stuckrunCron, auch Renewal-Crons (renew-*)sofort
Altlasten-Review bzw. Entwarnungrenew-altlasten-snapshot bei Reviewpflicht (Frist, Grund, Aktivierungsanleitung, Anhänge) und wenn sie später entfällt; ersetzt das frühere GitHub-Issue (ADR-0080)sofort
Datenquellen-Tagesdigestmonitor-data-source-drifthöchstens 1× pro Tag ab 07:00 Wien

Ohne EMAIL_SERVER wird nichts versendet (nur eine Log-Warnung); einen Resend-Fallback gibt es für Betriebsmails nicht. Zustellstatistiken wie bei Resend fehlen – Beleg für einen Versand sind die Vercel-Logs (Betriebsmail „…“ an office@ostheimer.at versendet) und der Posteingang.

Werkzeug-KPIs

/admin/metrics zeigt die kennungsfreien First-Party-Aggregate aus ADR-0042:

  • Aktivierungsfunnel vom ersten bewussten Formularkontakt bis zum ersten Ergebnis oder ehrlichen technischen Endzustand;
  • qualifizierte Serverläufe, Erfolgs- und Fehlerquoten;
  • p50/p95 als obere Grenzen fester Laufzeitklassen sowie die ausdrücklich als Näherung markierte Erstlauf-/Wiederverwendungs-Sicht je Serverinstanz;
  • Providerstatus, grobes Bundesland und direkt zurechenbare Providerkosten;
  • Stichprobe, erster Messtag und Reife der festen 14-Tage-Baseline einschließlich Funnel-Integrität und vollständiger Kostenzuordnung;
  • den separat ausgewiesenen, während D+1 bis D+14 fortgeschriebenen und ab D+15 eingefrorenen Ausgangswert mit denselben Funnel-, Fehler-, Provider-, Runtime-, Laufzeit- und Kostendimensionen wie die laufende Auswertung;
  • den Schalter „Interner Prüfmodus“ und die Tabelle „Eigene Prüfläufe“ (Insgesamt gemessen, davon intern, Öffentlich), siehe unten.

Erfasst sind der PV-Ertragsrechner (pv-yield), der Standort-Check (site-check) und das Immobilienmarkt-Signal (market-signal). Der Standort-Check zählt seit dem Deploy vom 04.10.2026 als qualifizierter Tool-Abschluss und misst seit Migration 0065 (#650) Laufzeit und Bundesland je Prüfung; bis 04.10.2026 war er nicht instrumentiert. Sein Ausgangswert ist am 06.10.2026 neu verankert (Fortschreibung 07.10. bis 20.10.2026, unveränderlich ab 21.10.2026). Das Marktsignal hat keine Eingabe und geht nicht in den Abschluss-Nenner ein.

Interner Prüfmodus (ADR-0079)

Vor eigenen Tests in Produktion auf /admin/metrics „Prüfmodus einschalten“ (Plattform-Admin mit bestätigter 2FA, keine Impersonation). Der Browser erhält das Cookie ln_qa (Ablaufzeit und Signatur, keine Kennung, höchstens zwölf Stunden). Die Werkzeuge schicken dessen Wert als Header X-LandNutzen-Internal-Run mit; ihre Läufe zählen dann nur in tool_metrics_internal_daily (Migration 0066), nicht in der Tag-90-Kennzahl und nicht im 14-Tage-Ausgangswert. Die Tabelle hält dieselbe 30-Tage-Löschfrist wie die Tageswerte. Danach „Prüfmodus ausschalten“. Gilt nur im Browser, in dem er eingeschaltet wurde.

Skripte und Playwright setzen denselben Header mit dem Wert aus METRICS_INTERNAL_RUN_TOKEN — über scripts/lib/internal-run-header.mjs (internalRunHeaders(url) für fetch, markInternalRunsInPlaywright(context) für Playwright), damit das Token nur an LandNutzen-Hosts geht. Nie über Playwrights extraHTTPHeaders: Die gingen auch an Kartendienste und Geocoder. Unmarkierte Läufe zählen öffentlich.

Es existieren keine Rohereignisse. Adresse, Koordinate, Eingabe, Ergebnis, URL, Referrer und Besucherkennungen sind weder in der Tabelle noch in der Admin-Antwort enthalten. Der tägliche Cleanup behält regulär den aktuellen und die 29 vorhergehenden Kalendertage; sein Laufstatus ist im Cron-Dashboard sichtbar. Davon getrennt bleiben der Baseline-Anker aus Tool-ID und ersten Funnel-/Server-Messtagen sowie die während D+1 bis D+14 fortgeschriebenen Dimensionsaggregate dauerhaft als Vergleichsreferenz erhalten. Mit Ablauf von D+14 ist der Snapshot vollständig und bleibt ab D+15 unverändert. Er enthält keinen Kalendertag und keine Region. Die UI nutzt Auth.js mit platform_admin und dem bestehenden 2FA-Gate.

Endpunkte

MethodePfadZweck
GET/api/v1/admin/usageUsage je Task+Modell + Spend + Budget
GET/api/v1/admin/modeleffektives Modell je Task
PUT/api/v1/admin/model{ task, model } setzen
GET/api/v1/admin/budgetBudget + Ist
PUT/api/v1/admin/budget{ dailyUsd, monthlyUsd } (null = kein Limit)
GET/api/v1/admin/metrics?days=14&tool=pv-yieldaggregierte Werkzeug-KPIs; Fenster 1/7/14/30 Tage
GET/api/v1/admin/cost-overview?days=28Kosten pro Tool-Abschluss, qualifiziertem Lead und aktivem Source Release; Fenster 7/14/28 Tage; internalRuns für „davon intern“
GET/api/v1/admin/data-sourcesQuellenkatalog, aktiver Release, pointergebundene Drift-Policy, Befunde und Alarmzustellung
GET/POST/api/v1/admin/impersonateImpersonation-Audit-Log lesen bzw. read-only Kontext-Inspektion mit Audit

MET-01-Baseline abschließen

Der revisionsfähige Abschlussprüfer liest ausschließlich die bestehenden Aggregate und gibt weder Tageswerte noch Regionen oder Besucherkennungen aus:

npm run qa:metrics:baseline -- --markdown

Vor Ablauf von D+14 endet der Befehl bewusst mit einem Fehlerstatus. Für einen reinen Zwischenstands-Readback während der laufenden Messung muss dieser Zustand ausdrücklich zugelassen werden:

npm run qa:metrics:baseline -- --markdown --allow-pending

Das Tag-30-Gate darf erst geschlossen werden, wenn der Prüfer abschlussbereit, 14/14 vollständig abgeschlossene Messtage, vorhandene Funnel- und Serverdaten, Funnel-Integrität und vollständige Kostenzuordnung meldet. Der ausgegebene SHA-256 bindet den tag- und regionsfreien Snapshot an den Nachweis.

Admin-Key erzeugen

node scripts/create-admin-key.mjs "Label"

Speichert nur den Hash (SHA-256(key+secret)); der Klartext wird einmalig ausgegeben (nicht wiederherstellbar). Scope {report, admin}. Verwendung:

curl -H "Authorization: Bearer ln_…" \
  https://www.landnutzen.at/api/v1/admin/usage

Sicherheit

Admin-Key ist mächtig: nur Hash gespeichert, Scope-getrennt, Rate-Limit/Audit wie jeder Key (ADR-0019/0023), Rotation manuell (neuen Key erzeugen, alten via revoked_at sperren). UI zusätzlich durch 2FA-Gate geschützt.