platform.audit ist der unveränderliche Audit-Trail des Tenants. Jede sicherheits- oder abrechnungsrelevante Aktion hinterlässt einen Eintrag: wer hat wann was mit welcher Ressource gemacht.
Schreiben
In App-Code gibt es den fail-soften Wrapper recordTenantAudit: ein fehlgeschlagener Audit-Write wird geloggt (PII-frei), bricht aber nie die Nutzer-Aktion ab. Darunter liegt createAuditService(db).log:
import { recordTenantAudit } from "@/lib/audit";
await recordTenantAudit(ctx, {
action: "homestaging.generation.created", // Namenskonvention, siehe unten
resourceType: "hs_generation", // optional
resourceId: generation.id, // optional
metadata: { style: "scandinavian" }, // optional, NIE PII oder Geheimnisse
});
// userId, ipAddress, userAgent kommen automatisch aus ctx.Das Log ist append-only: die Tabelle hat unter RLS eine reine Lese-Policy, der Insert läuft SDK-intern mit erhöhter Rolle. App- und Modul-Code nennt nie selbst eine erhöhte Rolle (der Secrets-Validator erzwingt das).
Namenskonvention
- Modul-Actions:
<modul>.<entity>.<verb>in Vergangenheitsform, z.B.credentials.template.created,credentials.policy.updated,homestaging.generation.created. Definiere die Strings als Konstanten an einer Stelle im Modul, damit Log und Abfragen nie auseinanderlaufen. - Plattform-Actions (Bestand) nutzen
snake_casewiemember_invited,share_viewed. Für neue Modul-Events gilt die Punkt-Konvention. - Action-Strings sind API: einmal geschrieben, werden sie nie umbenannt (Abfragen und Auswertungen filtern exakt auf den String).
Metadaten-Regeln
- Nie PII: keine E-Mail-Adressen, Namen, Nutzereingaben oder Datei-Inhalte in
metadata. - Nie Klartext-Geheimnisse: keine Passwörter, Tokens oder Keys, auch nicht "nur zum Debuggen".
- Gut geeignet: IDs, Enum-Werte, Zähler, Flags. Der Eintrag soll die Frage "was ist passiert" beantworten, nicht den Inhalt duplizieren.
resourceType/resourceIdreferenzieren die betroffene Ressource; wer eine Kategorie braucht, legt sie inmetadataab.
Lesen
query(ctx, input) liefert den Trail des eigenen Tenants, RLS-scoped, neueste zuerst, mit Keyset-Cursor:
import { queryTenantAudit } from "@/lib/audit";
const { entries, nextCursor } = await queryTenantAudit(ctx, {
action: "credentials.secret.revealed", // optional: exakter Action-String
userId, // optional: handelnde Person
resourceType: "credential", // optional
fromDate, toDate, // optional: Zeitfenster
limit: 50, // max 100
});Wo das Log sichtbar ist
Tenant-Admins sehen den Trail in der Plattform unter Einstellungen > Aktivität. Deine Einträge sind dort unmittelbar sichtbar; schreibe action-Strings und Metadaten so, dass sie einer Admin-Prüfung standhalten.
Weiterführend
Audit beantwortet "wer hat was getan" für den Tenant selbst. Sollen externe Systeme auf Ereignisse reagieren, emittierst du zusätzlich ein Domain-Event: Webhooks und Events.