Infrastructure (Phase 1): - AuditLog schema: add source, entityName, summary fields + index - createAuditEntry() helper: auto-diff, auto-summary, fire-and-forget - auditLog query router: list, getByEntity, getTimeline, getActivitySummary Audit Coverage (Phase 2 — 14 routers, 50+ mutations): - vacation: create, approve, reject, cancel, batch ops (8 mutations) - user: create, updateRole, setPermissions, resetPermissions (5 mutations) - entitlement: set, bulkSet (3 mutations) - client: create, update, delete, batchUpdateSortOrder - org-unit: create, update, deactivate - country: create, update, createCity, updateCity, deleteCity - management-level: createGroup, updateGroup, createLevel, updateLevel, deleteLevel - settings: updateSystemSettings (sensitive fields sanitized), testSmtp - blueprint: create, update, updateRolePresets, delete, batchDelete, setGlobal - rate-card: create, update, deactivate, addLine, updateLine, deleteLine, replaceLines - calculation-rules: create, update, delete - effort-rule: create, update, delete - experience-multiplier: create, update, delete - utilization-category: create, update Admin UI (Phase 3): - /admin/activity-log page with global searchable timeline - Filters: entity type, action, user, date range, text search - Expandable before/after diff view per entry - Summary cards showing top entity types by change count - EntityHistory reusable component for entity detail pages - Sidebar nav link with clock icon AI Assistant (Phase 4): - query_change_history tool: "Who changed project X?" - get_entity_timeline tool: "What happened to resource Y?" Regression: 283 engine + 37 staffing tests pass. TypeScript clean. Co-Authored-By: claude-flow <ruv@ruv.net>
11 KiB
Activity History System — Detailed Plan
Anforderungsanalyse
Ziel: Ein lueckenloses Aenderungsprotokoll, das jede Mutation im System erfasst und ueber UI und AI Assistant abfragbar macht. Nutzer sollen fragen koennen: "Wer hat die Buchung von Person X geaendert?" oder "Was ist in den letzten Tagen bei Projekt Y passiert?"
Ist-Zustand:
- AuditLog-Modell existiert (entityType, entityId, action, userId, changes JSONB, createdAt)
- 10 von 36 Routern loggen Aenderungen (44% Abdeckung)
- userId wird nur in ~60% der Faelle erfasst
- Kein Query-Endpoint (write-only)
- Keine UI zum Anzeigen der Historie
- AI Assistant kann keine Historie abfragen
- Inkonsistente before/after Snapshots
Soll-Zustand:
- 100% Mutation-Abdeckung ueber alle Router
- Konsistente before/after Snapshots mit User-Attribution
- Query-API mit Filtern (entityType, entityId, userId, dateRange, action)
- Admin-UI:
/admin/activity-logmit suchbarer, filterbarer Timeline - Entity-Detail-Seiten: "History"-Tab/-Drawer auf Project/Resource/Allocation
- AI Assistant Tool:
query_change_historyfuer natuerlichsprachliche Abfragen - Change-Source Tracking: UI vs API vs AI vs Import
Architektur-Entscheidungen
1. Audit Middleware statt manuelle Calls
Entscheidung: tRPC Middleware die automatisch vor/nach jeder Mutation auditiert
Grund: Eliminiert vergessene auditLog.create() Calls, garantiert 100% Abdeckung
Umsetzung: Middleware auf protectedProcedure die:
- Vor der Mutation: Entity-Snapshot speichert (before)
- Nach der Mutation: Neuen Snapshot speichert (after)
- Diff berechnet und AuditLog-Entry erstellt
2. Standardisiertes Changes-Format
interface AuditChanges {
before?: Record<string, unknown>; // Snapshot vor der Aenderung
after?: Record<string, unknown>; // Snapshot nach der Aenderung
diff?: Record<string, { old: unknown; new: unknown }>; // Nur geaenderte Felder
metadata?: {
source: "ui" | "api" | "ai" | "import" | "cron"; // Wer hat die Aenderung ausgeloest
reason?: string; // Optionaler Kommentar
ip?: string; // Request IP (optional)
batchId?: string; // Fuer Bulk-Operationen
};
}
3. Schema-Erweiterungen
model AuditLog {
// Existierende Felder behalten
id String @id @default(cuid())
entityType String
entityId String
action AuditAction
userId String?
user User? @relation(fields: [userId], references: [id])
changes Json @db.JsonB
createdAt DateTime @default(now())
// NEU: Zusaetzliche Felder
source String? // "ui" | "api" | "ai" | "import" | "cron"
entityName String? // Menschenlesbarer Name (z.B. "Porsche Taycan Project")
summary String? // Einzeiler: "Changed status from DRAFT to ACTIVE"
@@index([entityType, entityId])
@@index([userId])
@@index([createdAt])
@@index([entityType, createdAt]) // NEU: Fuer sortierte Timeline-Queries
}
Betroffene Pakete & Dateien
| Paket | Dateien | Art der Aenderung |
|---|---|---|
packages/db |
prisma/schema.prisma |
edit — AuditLog um source, entityName, summary erweitern |
packages/api |
src/lib/audit.ts |
create — createAuditEntry() Helper + auditMiddleware |
packages/api |
src/router/audit-log.ts |
create — Query-Router (list, getByEntity, getTimeline) |
packages/api |
src/router/index.ts |
edit — auditLog Router registrieren |
packages/api |
src/router/assistant-tools.ts |
edit — query_change_history Tool hinzufuegen |
packages/api |
26 Router-Dateien | edit — fehlende audit Calls nachruesten |
apps/web |
src/app/(app)/admin/activity-log/page.tsx |
create — Activity Log Seite |
apps/web |
src/components/admin/ActivityLogClient.tsx |
create — Suchbare Timeline |
apps/web |
src/components/ui/EntityHistory.tsx |
create — Wiederverwendbare History-Komponente |
apps/web |
src/components/layout/AppShell.tsx |
edit — Nav-Link fuer Activity Log |
Task-Liste (atomare Schritte)
Phase 1: Infrastruktur (Basis)
-
Task 1: Schema erweitern →
packages/db/prisma/schema.prismasource String?,entityName String?,summary String?hinzufuegen- Index
@@index([entityType, createdAt])hinzufuegen prisma db push+prisma generate
-
Task 2: Audit Helper erstellen →
packages/api/src/lib/audit.tscreateAuditEntry(db, params)— standardisierter Audit-Entry-Creator- Params:
{ entityType, entityId, entityName, action, userId, before?, after?, source?, summary? } - Automatische Diff-Berechnung wenn before + after vorhanden
- Automatische Summary-Generierung aus Diff (z.B. "Updated name, status, budgetCents")
computeDiff(before, after)— gibt nur geaenderte Felder zurueck
-
Task 3: Query Router erstellen →
packages/api/src/router/audit-log.tslistquery (controllerProcedure): paginiert, filterbar nach entityType, entityId, userId, action, dateRange, sourcegetByEntityquery: alle Entries fuer eine Entity, chronologischgetTimelinequery: globale Timeline aller Aenderungen, gruppierbar nach TaggetActivitySummaryquery: Zusammenfassung (counts pro entityType, pro action, pro User) fuer einen Zeitraum- Registrieren in
router/index.ts
Phase 2: Audit-Abdeckung erweitern
-
Task 4: Kritische Router nachruesteen (Parallel-fähig, 4 Agents)
- Agent A:
vacation.ts(8 Mutations),entitlement.ts(2),user.ts(9) - Agent B:
client.ts(5),org-unit.ts(3),country.ts(5),management-level.ts(5) - Agent C:
rate-card.ts(7),blueprint.ts(6),settings.ts(3),calculation-rules.ts(3) - Agent D:
webhook.ts(4),comment.ts(3),notification.ts(nur create/task),dispo.ts(4) - Jeder Agent:
import { createAuditEntry } from "../lib/audit.js"verwenden - userId immer aus
ctx.dbUser?.idnehmen
- Agent A:
-
Task 5: Bestehende Audit-Calls standardisieren
- Alle 37 existierenden
auditLog.createCalls aufcreateAuditEntry()Helper umstellen - userId konsistent aus Context nehmen
- before/after Snapshots wo fehlend ergaenzen
source: "ui"als Default setzen
- Alle 37 existierenden
Phase 3: UI
-
Task 6: Activity Log Admin-Seite →
ActivityLogClient.tsx- Globale, suchbare Timeline aller Aenderungen
- Filter: Entity-Typ (Project/Resource/Allocation/...), User, Action, Datum
- Jeder Eintrag zeigt: Zeitstempel, User (Avatar + Name), Entity (verlinkt), Action-Badge, Summary
- Expandierbares Detail: before/after Diff-View (JSON oder tabellarisch)
- Pagination (50 pro Seite)
- Sidebar Nav-Link unter Admin: "Activity Log"
-
Task 7: Entity History Komponente →
EntityHistory.tsx- Wiederverwendbar fuer Project/Resource/Allocation Detail-Seiten
- Props:
entityType: string, entityId: string - Chronologische Liste der Aenderungen fuer diese Entity
- Kompakte Darstellung: User, Action, Summary, Zeitstempel
- Optional: als Tab oder Drawer auf Detail-Seiten einbinden
-
Task 8: History-Tab auf Detail-Seiten integrieren
/projects/[id]→ "History" Tab mit<EntityHistory entityType="project" entityId={id} />/resources/[id]→ "History" Tab- Optional spaeter: Allocation Detail, Estimate Detail
Phase 4: AI Assistant Integration
- Task 9: AI Tool erstellen →
assistant-tools.tsquery_change_historyTool:- Input:
{ entityType?, entityId?, userId?, search?, daysBack?, limit? } - Ruft
auditLog.listmit Filtern auf - Formatiert Ergebnis menschenlesbar:
[2026-03-22 14:30] admin@planarchy.dev UPDATED Project "Porsche Taycan" → Changed status from DRAFT to ACTIVE → Changed budgetCents from 500000 to 750000
- Input:
get_entity_timelineTool:- Input:
{ entityType, entityId, limit? } - Gibt chronologische History fuer eine Entity zurueck
- Input:
- Beide Tools mit Permission
VIEW_PROJECTSoderVIEW_RESOURCESje nach entityType
Abhaengigkeiten
Task 1 (Schema) ──► Task 2 (Helper) ──► Task 3 (Query Router)
└──► Task 4a-d (Parallel: 26 Router)
└──► Task 5 (Bestehende Calls)
Task 3 ──► Task 6 (UI: Activity Log)
──► Task 7 (UI: Entity History)
──► Task 9 (AI Tools)
Task 7 ──► Task 8 (Integration in Detail-Seiten)
- Tasks 4a-d koennen parallel ausgefuehrt werden (unterschiedliche Dateien)
- Tasks 6, 7, 9 koennen parallel nach Task 3
- Task 8 benoetigt Task 7
Akzeptanzkriterien
pnpm --filter @planarchy/web exec tsc --noEmit— keine neuen Errorspnpm test:unit— alle Tests gruen- 100% Mutation-Abdeckung: Jede Mutation in jedem Router erzeugt einen AuditLog-Entry
- Konsistente userId: Jeder Entry hat den ausfuehrenden User
- before/after: UPDATE-Actions haben immer before + after Snapshots
- Query-API:
trpc.auditLog.listliefert paginierte, filterbare Ergebnisse - Admin UI:
/admin/activity-logzeigt globale Timeline mit Filtern - Entity History: Project/Resource Detail-Seiten zeigen Aenderungs-Historie
- AI Assistant: "Wer hat die Buchung von Person X geaendert?" wird korrekt beantwortet
- AI Assistant: "Was ist bei Projekt Y in den letzten Tagen passiert?" liefert Ergebnis
Risiken & offene Fragen
Risiken
- Performance: Audit-Middleware auf jeder Mutation koennte Latenz erhoehen → Mitigation: Audit-Writes fire-and-forget (non-blocking), oder nach Response
- Storage: JSONB Snapshots koennen gross werden
→ Mitigation: Nur geaenderte Felder in
diffspeichern, nicht volle Snapshots - Migration: 37 bestehende Calls umstellen birgt Regressions-Risiko → Mitigation: Schrittweise, mit Tests pro Router
Offene Fragen
- Retention: Wie lange sollen Audit-Logs aufbewahrt werden? (Vorschlag: 2 Jahre)
- Granularitaet: Sollen READ-Zugriffe geloggt werden? (Vorschlag: Nein, nur Mutations)
- DSGVO: Muessen Audit-Logs bei User-Loeschung anonymisiert werden?
- Notifications: Sollen bestimmte Aenderungen (z.B. Projekt-Status) automatisch Notifications ausloesen?
- Middleware vs Manual: Soll der Audit-Helper manuell oder als tRPC-Middleware eingebaut werden? → Empfehlung: Manuell mit Helper-Funktion, da Middleware die Entity-Snapshots nicht automatisch kennt
Geschaetzter Aufwand
| Phase | Aufwand | Parallelisierbar |
|---|---|---|
| Phase 1: Infrastruktur | 1 Tag | Nein (sequenziell) |
| Phase 2: Audit-Abdeckung | 1 Tag | Ja (4 Agents parallel) |
| Phase 3: UI | 1 Tag | Ja (2 Agents parallel) |
| Phase 4: AI Integration | 0.5 Tag | Ja (mit Phase 3) |
| Gesamt | ~3.5 Tage |