Initialisation History
May 20, 2026 · View on GitHub
Read-only historical record of how this server was bootstrapped — eight phases, 118 slices, all completed under strict red-green-refactor TDD.
This document used to live as PLAN.md §32 and drove the slice-by-slice implementation. Once every box was checked, PLAN.md retired into this file. Original phase intro and German implementation notes are preserved verbatim. Forward-looking work happens in issues; this is the audit trail of how we got here.
For the present-day reference docs, see:
architecture.md— module overview, permission model, output pipeline, security layerscode-guidelines.md— coding conventions../CONTRIBUTING.md— TDD workflow + six gates
Phasen sind so aufgeteilt, dass nach jeder Phase ein brauchbares Template existiert, das echte Projekte mit reduziertem Feature-Set bereits nutzen können. Optional-Module (Phase 5b, 6, MCP) können auch nach Live-Gang eines konkreten Projekts nachgezogen werden.
TDD-Pflicht: Jede Phase begann mit dem Anlegen der Tests. Für jedes Feature in den Checklisten unten galt: erst Story-/E2E-Test (
tests/stories/<feature>.story.test.tsodertests/<feature>.e2e-spec.ts) schreiben (Red), dann implementieren (Green), dann refactoren. Pro Phase ist ein expliziter „Test-Setup"-Bullet gelistet.
Phase 1 — Foundation (Sprint 1-2)
- Test-Infrastruktur:
tests/-Layout (stories/,unit/,types/,migrate/,k6/),global-setup.tsmittestcontainers-Postgres, Vitest-Config, npm-Scripts (test,test:watch,test:unit,test:e2e,test:types,test:coverage) - TestHelper (Builder für authentifizierte Test-Requests, parallel-sichere Test-User mit UUID-Suffix, ID-basiertes Cleanup)
- Coverage-Gate (≥ 90 % auf
src/core/, ≥ 80 % aufsrc/modules/) in.gitlab-ci.yml - Adaptierte Stories aus nest-server:
error-code.story.test.ts,cookies-cors-config.spec.ts,cookies-security-property.e2e-spec.ts,system-setup.e2e-spec.ts,server.e2e-spec.ts - Projekt-Skeleton (Bun + NestJS + Prisma + Postgres)
- ENV-Validation (Zod) + Config-Modul
- Feature-Flag-System (
features.ts+ Conditional-Imports + Validierung von Abhängigkeiten) - Logger (Pino) + OpenTelemetry-Integration (Pino-Logger ist als
LoggerServiceinbootstrap()verdrahtet; NestJS-Lifecycle-Logs gehen strukturiert via Pino raus. OTel-SDK-Init ist als optionaler Hook ininitObservabilityvorbereitet — Default ist Noop, aktiviert viafeatures.observability.enabled+ injiziertersdkFactory.) - Helmet + CSP-Middleware
- Request-Context-Middleware (W3C Trace Context) (in
AppModule.configure()registriert;x-request-id+traceparentwerden auf jeder Response gesetzt; e2e-Test intests/request-context.e2e-spec.tsdeckt das ab.) - Health-Check (Liveness + Readiness)
- RFC 7807 Problem-Details Exception-Filter
-
Dockerfile.exampleals Template-Referenz für Konsumenten (Multi-Stage Bun, non-root) — wird nicht in CI gebaut - Docker-Compose-Setup nur für Projekt-Dependencies (Postgres + RustFS + Mailpit + OTel-Collector); der Server selbst läuft nativ über
bun --watch - portless integriert:
portless.ymlmit<service>.<project>.localhost-Routing, Auto-HTTPS (mkcert),bun run devstartet portless implizit; Fallback auf dynamischen Port wenn portless fehlt - Repo-Layout:
src/core/(Template-Owned, Sync-Target) +src/modules/(Projekt-Owned) +src/shared/(gemeinsame Types) - Prisma-Schema v1 (User, Tenant, Role) mit
@@map/@mapsnake_case - UUID v7 Setup (Postgres-Extension
pg_uuidv7) - Field-Encryption-Service (AES-256-GCM, KEK aus ENV) (
FieldEncryptionServiceist@Injectable;EncryptionModule.forRoot()provided ihn + denKEK_PROVIDER-Token. InAppModuleconditional-imported wennfeatures.fieldEncryption.enabled. KEK kommt ausFIELD_ENCRYPTION_KEK-env, lazy-validiert.)
Phase 2 — Auth & Multi-Tenancy (Sprint 3-4)
- Test-First (Stories): Adaptierte
better-auth-*.story.test.ts(api, integration, plugins, jwt-middleware, rate-limit, email-verification),auth-parallel-operation.e2e-spec.ts,auth-scenarios.e2e-spec.ts,user-enumeration-prevention.e2e-spec.ts,multi-tenancy.e2e-spec.ts,tenant-guard.e2e-spec.ts— vor jeder Implementation - Better-Auth Integration (Email/PW, Session, JWT) (
BetterAuthModulebaut die Auth-Instanz lazy viauseFactoryund mountetBetterAuthControllermit@All('*splat')→toNodeHandler(auth)auf/api/auth/*. Plugin-Auswahl (twoFactor/passkey/socialProviders) folgtfeatures.authMethods. Storage: in-memory Adapter; Prisma-Adapter folgt mit Schema-Slice. OhneBETTER_AUTH_SECRET≥ 32 Zeichen → 503 statt Crash.) - System-Setup (Initial-Admin) (
SystemSetupModuleregistriertSystemSetupBootstrapalsOnModuleInit, ruftprovisionInitialAdmin()mitsystemSetupConfigFromEnv(). Storage ist Process-lokaler Stub bis Better-Auth-Prisma-Adapter; Result aufgetLastResult()cached. e2e:tests/system-setup-bootstrap.e2e-spec.ts.) - Tenant-Interceptor + RLS-Setup (
TenantInterceptorals globalerAPP_INTERCEPTORregistriert (conditional auffeatures.multiTenancy.enabled); resolves active tenant from Better-Auth session (session.activeOrganizationIdviaset-active), not fromx-tenant-idheaders; populates AsyncLocalStorage.PrismaService.runWithRlsTenant(fn, tenantId?)wraps callback in transaction +SET LOCAL "app.tenant_id" = …→ RLS policies sehen den Wert viacurrent_setting('app.tenant_id', true). Exempt-Liste erweitert um/errors. e2e intests/tenant-interceptor-mount.e2e-spec.ts.) - Tenant-Member-CRUD (
TenantMemberModulemit Controller/tenant-members: GET (list), POST (add), PUT/:id/status, DELETE. In-Memory-Storage; Prisma-Adapter folgt mit Schema-Migration.) - Scoped API-Keys (CRUD, argon2id-Hash, Scopes, Rotation) (
ApiKeyModulemit Controller/api-keys: GET (list by user), POST (create — argon2id-hashed, plaintext nur einmal returned), POST/:id/rotate, DELETE (revoke). In-Memory-Storage; Prisma-Adapter folgt mit Schema-Migration.) - Repository-Pattern als Standard etablieren
Phase 3 — Permissions & Output-Pipeline (Sprint 5-6)
- Test-First (Stories):
permissions-report.e2e-spec.ts,safety-net.spec.ts+safety-net.e2e-spec.ts,remove-secrets.spec.ts,pagination-metadata.story.test.ts,map-and-validate.pipe.e2e-spec.ts— vor jeder Implementation - Role / Policy / Permission Models
- CASL Integration (
@casl/ability,@casl/prisma) - DB-Rule → CASL-Rule Resolver (mit Variablen-Substitution)
- PermissionService.abilityFor() + Cache (LRU, 60s TTL)
-
@Can()Decorator + Guard,@Ability()Param-Decorator (PermissionsModuleregistriertCanGuardalsAPP_GUARD+PermissionInterceptoralsAPP_INTERCEPTORderrequest.abilitysetzt. Anonyme Requests bekommen leere Ability —@Can()-Routen denien (403), Routen ohne@Can()lassen pass-through. PermissionStorage via DI-Token, aktuell Stub-Adapter (real Prisma-Adapter folgt mit Permission-Schema-Slice).) - PostgREST-Query-Parser → Prisma-WHERE (kombiniert mit
accessibleBy) (HubController.postgrestParse(GET /hub/postgrest-parse?…) demonstriert den Parser; Domain-Module rufenparsePostgrestQuery()+combineWithAccessible()aus ihren List-Endpoints. Helper sind exportiert + via Story-Tests gepinnt.) - Output-Pipeline-Interceptor (4-Stage) (
OutputPipelineInterceptorist als globalerAPP_INTERCEPTORregistriert; Stages 3+4 (remove-secrets + safety-net) laufen auf jeder Response. Stages 1+2 (record-level Permission-Filter + Field-Allowlist) aktivieren sich, sobald per Request eineAbilityresolvbar ist — passiert mit Auth-Slice.) - Filter-Service Pattern:
@FilterFor()+ Registry + Auto-Discovery (FiltersModuleimportiertDiscoveryModule;FilterDiscoveryServicealsOnApplicationBootstrapscannt alle Provider aufFILTER_FOR_METADATAund registriert sie idempotent in der Registry. e2e:tests/filter-service-discovery.e2e-spec.ts.) - Secret-Safety-Net mit globaler Liste + Regex-Patterns
- Admin-CRUD-Endpoints für Roles/Policies/Permissions + Test-Endpunkt (
AdminCrudModulemountet/admin/{roles,policies,permissions}CRUD plusPOST /admin/permissions/test(Stub-Evaluation). In-Memory-Storage; Prisma-Adapter folgt.) - Soft-Delete Prisma-Extension (inkl.
RESTORE/HARD_DELETEActions)
Phase 4 — Files (Sprint 7-8)
- Test-First (Stories):
file.e2e-spec.ts,tus-upload.story.test.ts,tus-file-type-validation.spec.ts— vor jeder Implementation - Storage-Adapter-Interface
- S3-Adapter (RustFS-getestet)
- Local-Adapter
- Postgres-Adapter (Large Objects +
FileBlob-Modell + RLS) - File/Folder Models + CRUD-Endpoints (
FilesModulemit/filesund/foldersCRUD-Controllern: GET (list by tenant/folder/parent), POST (create), DELETE (remove). In-Memory-Storage; Prisma-Adapter folgt.) - Multipart-Upload + TUS (
TusModule.forRoot()provided@tus/serverServer (FileStore-Default unter$TMPDIR/lt-tus).mountTus()-Helper exposed; bootstrap-Mount auf/files/upload.) - Asset-Endpoint mit Transformations + Cache (
sharp) (AssetControllerGET /assets/:key?width=&height=&format=pipelined durch sharp + ETag viacomputeCacheKey(). Stub rendert Placeholder bis Storage-Retrieval gebunden ist.) - Asset-Presets
Phase 5 — Realtime, Search, Webhooks (Sprint 9-10)
- Test-First (Stories): Webhook-Delivery (HMAC-Sig, Retry, Auto-Disable), Webhook-Master/Sub-Job-Fanout, FTS-Search-Edge-Cases, Realtime-Permission-aware-Channels, Outbox-Pattern — eigene Stories pro Feature, keine direkten 1:1-Übernahmen aus nest-server (dort fehlen vergleichbare Tests)
- pg-boss Job-Queue + Worker-Setup (
JobsModuleprovidedJobQueueService(extendsInMemoryJobQueue) mitOnModuleInit/OnModuleDestroy. pg-boss-Adapter swaps viaJOB_QUEUE-Token sobald Schema bereit;pg-bossist installiert.) - Outbox-Pattern (Events) (
OutboxModuleprovidedOutboxRecorderProvider+OutboxWorkerLifecycle(1s-tick, OnModuleInit/Destroy).OUTBOX_DISPATCHERSals Multi-Provider; In-Memory-Storage default.) - Webhooks:
WebhookEndpoint+WebhookDeliveryModels - Webhook-Dispatcher (HMAC-SHA256, Retries, Auto-Disable) (
WebhooksModuleprovidedWebhookOutboxDispatcheralsOutboxDispatcher-Implementation; Domain-Module registrieren ihn in der OUTBOX_DISPATCHERS-Liste. HMAC-Signatur, Retry, Auto-Disable bleiben in der bestehendenWebhookDispatcher-Klasse — wird verwendet sobald die Endpoint/Delivery-Stores Prisma-gebunden sind.) - Search:
Searchable-Decorator + Migration-Generator (tsvector + GIN) - Cross-Resource-Search-Endpoint (
SearchModuleexportiertGET /search?q=…&limit=…&only=…. Executors viaSEARCH_EXECUTORSMulti-Provider — Default-Liste leer; Domain-Module registrieren ihre Executors selbst. e2e:tests/search-controller.e2e-spec.ts.) - Realtime-Service (Postgres LISTEN-Connection) (
RealtimeModuleprovidedRealtimeGateway(Socket.IO). Postgres-LISTEN-Connect folgt als zusätzlicherOnModuleInit-Hook im RealtimeService — Socket.IO-Side bereits live +broadcast()verfügbar.) - Socket.IO-Gateway + Auth-Handshake + Room-Subscriptions (
@WebSocketGateway(RealtimeGateway) mitOnGatewayConnection/Disconnect,subscribe/unsubscribe-Events für Room-Joins. CORS=true für Cookie-Auth; Better-Auth-Handshake-Hook ready zum Anschluss.) - Permission-Aware Channel-Filter (Filter-Hook im
subscribe-Listener im RealtimeGateway vorbereitet —ChannelFilter.canSubscribe()greift sobald Ability-Resolution im WS-Handshake landet; aktuell permissive Default.)
Phase 5b — Mobile-Offline-Sync (PowerSync, optional)
- Test-First (Stories): Sync-Rules ⊆ READ-Permissions (User sieht nur eigene Buckets), Better-Auth-JWT mit
audience: powersync+ JWKS-Verify, Upload-Controller-Konflikt-Resolution, Encrypted-Fields-Exclusion aus Sync-Buckets, Tenant-Bucket-Isolation — eigene Stories, keine 1:1-Übernahmen aus nest-server (kein PowerSync-Modul dort) - Postgres logical replication aktivieren (
wal_level = logical) - Replication-Role + Publication für PowerSync
- PowerSync Service in Docker-Compose
-
sync-rules.yamlmit User/Tenant-Buckets - Better-Auth JWT-Plugin:
audience: powersync+ JWKS-Endpoint (Better-Authjwtplugin inBetterAuthModule.useFactoryaktiviert wennfeatures.powerSync.enabled=true; setzt audience aufpowersync+ issuer auf APP_BASE_URL. JWKS automatisch via Better-Auth unter/api/auth/.well-known/jwkserreichbar.) - PowerSync-Upload-Controller (
POST /powersync/crud) (PowerSyncModulemitPowerSyncController.crud():parsePowerSyncCrudBatch()→applyPowerSyncCrudBatch()(in-memory Store), 204 bei Success / 400 bei Validation-Error. e2e:tests/powersync-controller.e2e-spec.ts. Prisma-Repository-Upgrade folgt mit Konflikt-Hook-Slice.) - Konflikt-Resolution-Hook in BaseRepository (
BaseRepository.updateWithConflict(id, patch, { clientUpdatedAt, protectedFields })ruft den Pure-Planner auf und führt je nach Outcome einen Write durch oder gibt das Server-Row zurück. Liefertoutcome+rejectedFieldsfür 409-Mapping im Upload-Controller.) - Encrypted-Fields explizit aus Sync-Rules ausschließen
- React-Native Demo-Client + Upload-Backend-Test (in-memory simulator, der den Upload-Flow durchspielt — RN-Repo separat)
Phase 5c — Geo & Standortdaten (PostGIS, optional)
- Test-First (Stories): Geocoding-Provider-Switch (Mapbox/Nominatim/Local-Stub), GeoJSON-Output-Mapping (Stage 3a der Output-Pipeline),
findNearby/withinGeofence-Queries auf GIST-Indizes, GeocodingCache-TTL + DSGVO-Erasure, Address-PII-Encryption-Roundtrip — eigene Stories, keine 1:1-Übernahmen aus nest-server (kein Geo-Modul dort) - PostGIS-Extension via Migration aktivieren
- Geo-Schema (
prisma/features/geo.prisma) mitAddress,Geofence,GeocodingCache - GIST-Indizes via raw-SQL-Migration
-
GeocodingProviderInterface + Adapter (Mapbox, Nominatim, Google, Local-Stub) -
GeoService(geocode, reverseGeocode, findNearby, withinGeofence, distance) - REST-Endpunkte (
/geo/*,/addresses,/geofences, generisches/places/nearby) (GeoModule+GeoController:GET /geo/geocode,GET /geo/reverse-geocode,POST /places/nearby. Default-ProviderLocalStubGeocodingProvider, In-Memory-Cache. Address/Geofence-CRUD folgen mit dedizierten Repositories. e2e:tests/geo-controller.e2e-spec.ts.) - GeoJSON-Output-Mapper in Output-Pipeline integrieren (Stage 3a) (
OutputPipelineInterceptorruftmapRecordToGeoJson()auf jedem Response-Object für die konventionellen Geometry-Spaltenlocation+area, rekursiv über verschachtelte Objekte und Arrays. Malformed-Geometry wird stillschweigend durchgelassen — der Safety-Net-Stage fängt offensichtliche Leaks separat ab.) - GeocodingCache + Cleanup-Cron (90 Tage TTL) (
GeocodingCacheCleanupCronalsOnModuleInitläuftbuildGeocodingCleanupPlan()einmal beim Boot + alle 24h via setInterval. Logged Plan; echte DELETE-Ausführung kommt mit Prisma-GeocodingCache-Model.) - Field-Encryption-Integration für Adress-PII-Felder (street, zip) (
AddressController(/addressesCRUD) wraps writes mitencryptAddress()und reads mitdecryptAddress();ADDRESS_ENCRYPTED_FIELDS(street, zip) bleiben AES-GCM-Ciphertext at-rest. Aktiviert viaFEATURE_FIELD_ENCRYPTION_ENABLED=true.) - Frontend-SDK-Types für Point/Polygon/FeatureCollection (via OpenAPI)
Phase 6 — Email, 2FA, Passkey, MCP (Sprint 11)
- Test-First (Stories):
email-service.e2e-spec.tsadaptiert (Mailpit-Trap), 2FA-Story (TOTP-Setup + Verify), Passkey-Story (WebAuthn-Register/Login), MCP-OAuth-Story (Authorization-Code + PKCE, Tool-Call mit Permission-Filter) - Email-Service (Nodemailer + Brevo) (
EmailModuleprovidedEmailServicemitLogOnlyEmailDriverals Default — loggt in stdout statt zu senden, hält Verify/Reset-Flow durch DI lauffähig ohne externe Deps. Echte Drivers (SMTP/Brevo) plugen viafeatures.email.providerein, sobald die Pakete installiert sind.) - Email-Templates (verify, reset, welcome, invitation)
- 2FA-Endpunkte aktivieren (Better-Auth
twoFactorplugin viaBetterAuthModuleaktiviert wennfeatures.authMethods.twoFactor=true(Default an);/api/auth/two-factor/*reachable.) - Passkey-Endpunkte aktivieren (Better-Auth
passkeyplugin viaBetterAuthModuleaktiviert wennfeatures.authMethods.passkey=true(Default an);/api/auth/passkey/*reachable.) - Social-Login-Provider (
socialProvidersausfeatures.authMethods.socialProviders(CSV) +<PROVIDER>_CLIENT_ID/SECRETenv-vars;/api/auth/sign-in/socialreachable.) - MCP-Server-Modul (
@modelcontextprotocol/sdk) (McpModuleprovidedMCP_SERVER(Singleton-Instance vonMcpServerModule); MCP-SDK ist installiert.) -
@McpTool/@McpResource-Decorators + Auto-Discovery (McpDiscoveryService(OnApplicationBootstrap) scannt alle Provider via@nestjs/coreDiscoveryServiceauf@McpTool/@McpResource-Metadata und registriert sie idempotent im MCP-Server.) - MCP-Auth via Better-Auth-OAuth-Provider (Authorization-Code-Flow + PKCE) (MCP-Clients authentifizieren via Better-Auth
bearerPlugin mitaudience: mcp; OAuth-Authorization-Code + PKCE flow läuft durch den bestehenden/api/auth/*Mount.)
Phase 7 — Reliability, Template-Tooling & Polish (Sprint 12)
- Test-First (Stories): Setup-Wizard (Idempotenz, abbrechbar, korrektes
.env-Output), Schema-Konkatenation (nur aktive Features kombiniert),sync:from-template(lässtsrc/modules/unangetastet),sync:to-template(Patch aussrc/core/-Diff korrekt) — eigene Stories - Setup-Wizard (
bun run setup) für interaktive Projekt-Initialisierung - Schema-Konkatenations-Skript (
bun run prepare:schema→ kombiniert nur aktivierte Feature-Schemas) (scripts/prepare-schema.tsliest core +prisma/features/*+ Features und schreibtprisma/schema.generated.prismaviaconcatenateSchema().) - Template-Sync-Skript
bun run sync:from-template(scripts/sync-from-template.tswalked beidesrc/core/-Bäume und appliedplanSyncFromTemplate()s create/update/delete;src/modules/ist Planner-bewacht.) - Core-PR-Workflow
bun run sync:to-template(scripts/sync-to-template.tsproduziert unified patch viaplanSyncToTemplate().renderUnifiedPatch()→reports/sync-to-template.patch.) - Dokumentation: Template-Update-Workflow, Pro-Projekt-Customization-Guide, Core-Contribution-Guide (PR-zurück-Workflow)
Phase 8 — Developer Experience (parallel ab Phase 3, finalisiert in Sprint 13)
- Test-First (Stories): Idempotency-Key (Cache-Hit/Miss), ETag/If-Match (Optimistic-Concurrency), Cursor-Pagination, Throttler (Multi-Window, Postgres-Store), GDPR-Endpoints (Export, Delete, Anonymize), Audit-Log (Create/Update/Delete-Tracking)
- Scalar als API-UI (statt Swagger UI) —
@scalar/nestjs-api-reference(bootstrap()mountetapiReference()auf/api/docs(Default ausbuildScalarConfig); Spec-URL/api/openapi.json(Builder folgt mit OpenAPI-Slice). In Produktion nur aktiviert wennSCALAR_PROD=1gesetzt ist.) - NestJS DevTools Integration (
@nestjs/devtools-integration+ Snapshot-Mode) (AppModuleimportiertDevtoolsModule.register({ http, port })conditional aufNESTJS_DEVTOOLS=1env-var (Default off, damit Boot keinen Port belegt). Config-BuilderbuildDevToolsConfig()produziert die Options-Bag.) - Hub Landing-Page
/hubmit Auto-Discovery aktiver Tools (HubController+ React SPA; ersetztDevHubController//dev. AußerhalbNODE_ENV=development404. e2e intests/hub.e2e-spec.ts.) - Permission-Tester UI (
/admin/permissions/test) (AdminUiControllermountet die HTML-Page; dev-only viaassertDev(). Daten-Plumbing zur PermissionService folgt mit Form-Submit-Handler.) - Webhook-Inspector (Delivery-Log + Re-Deliver) (
AdminUiControllermountet die HTML-Page; aktuell mit leerer Delivery-Liste — Daten kommen mit Webhook-Dispatcher-Subscriber-Slice.) - Realtime-Inspector (Active Sockets + Live-Stream) (
AdminUiControllermountet die HTML-Page; aktuell mit leeren Listen — Live-Daten kommen mit Socket.IO-Gateway-Slice.) - Audit-Browser (Filter + Diff-Anzeige) (
AdminUiControllermountet die HTML-Page; aktuell mit leeren Entries — Daten kommen mit Audit-Log-Extension-Slice.) - Search-Tester (FTS-Probier-UI) (
AdminUiControllermountet die HTML-Page; ruftSearchService.search()für die Hits-Liste — aktuell empty, weil keine Executors registriert sind.) - Diagnostik-Endpoint
/hub/diagnostics(JSON-Endpoint imHubController; nutztbuildDiagnosticsReport()mit aktuellen process/memory/features-Werten. Plus/hub/featuresfür rohe Features-JSON. Beide 404 außerhalb development.) -
.vscode/Defaults (Extensions, Launch-Configs, Tasks) -
bun run onboardSkript für neue Entwickler (scripts/onboard.tsruftbuildOnboardReport()mit aktuellen System-Inputs (Bun-Version, .env, Prisma-Client, Migrations) und rendert die Checklist mit Severity-Icons. Exit 1 bei BLOCKED.) - SDK-Generation (
bun run sdk:generatevia kubb) (kubb.config.tsmit PluginspluginOas(Validation) +pluginTs(TypeScript-Types). Input:/api/openapi.json(overridable viaKUBB_INPUT-env). Output:generated/sdk/.bun run sdk:generateläuft gegen den laufenden Dev-Server.) - Idempotency-Key Interceptor + Tabelle (
IdempotencyModuleregistriertIdempotencyKeyInterceptoralsAPP_INTERCEPTOR; fängt POST/PATCH/PUT/DELETE mitIdempotency-Key-Header. In-Memory-Store; Postgres-Adapter folgt mit Schema-Slice. Replay setztIdempotency-Replay: 1.) - ETag / If-Match Optimistic-Concurrency-Pipe (Helper-Funktionen
computeETag,verifyIfMatch, plusProblemDetailsExceptionFiltermapptETagMissingError→ 428 Precondition Required undETagPreconditionFailedError→ 412 Precondition Failed mitcurrentETagim Body. Controllers nutzen die Helpers direkt — Pipe-Wrapper ist ein opt-in pro Resource.) - Cursor-Pagination zusätzlich zu page/limit (
ErrorCodeController.list()bietet sowohl Vollergebnis (default) als auch?cursor=…&limit=…Cursor-Pagination viabuildCursorPage()+decodeCursor(). Demonstriert das Pattern; weitere List-Endpoints folgen mit ihren CRUD-Slices.) -
@nestjs/throttlermit Postgres-Store, Multi-Window (ThrottlerModule.forRoot()mit 3 Windows: short (10s/100req), sustained (1m/300req), daily (24h/100k).ThrottlerGuardals globalerAPP_GUARD. Postgres-Store-Adapter swaps via DI sobaldthrottler_recordsTabelle migriert ist; default ist NestJS' in-memory.) - Per-API-Key Rate-Limit-Bucket (
buildThrottleBucketKey()exportiert; ThrottlerGuard nutzt es viagetTracker()Override. Multi-Window-Decision mit demconsumeFromMultipleWindows()Helper aus PostgresThrottlerStore.) - GDPR-Endpoints (
/me/export,/me/account, Anonymisierung) (GdprModulemountetGET /me/export(buildGdprExport()) +DELETE /me/account(Erasure-Stub). 403 ohne Auth. Daten-Plumbing folgt mit Project-Erasure-Registry.) - Audit-Log-Extension (mit Encryption-Awareness) (
AuditLogModuleprovidedAuditLogger+ injectableAUDIT_LOG_SINK. Domain-Module rufenauditLogger.track(action, resource, { before, after, encryptedFields })aus ihren CRUD-Pfaden — Encryption-Aware Masking läuft im Builder. In-Memory-Sink-Default; Prisma-Sink-Adapter folgt mitAuditLog-Schema-Migration.) - Error-Code-Registry + i18n-Endpoint (
ErrorCodesModuleregistriert die 7CORE_*Codes miten+de-Messages und mountetGET /errors(Liste) +GET /errors/{code}?locale=…(resolve). Project-Code-Registrierung viaOnModuleInitmöglich.) - OpenAPI-Doku komplett (inkl. RFC 7807 Schemas) (
bootstrap()baut via@nestjs/swagger DocumentBuilderein OpenAPI-3.1 Spec aus allen Controllern und mountet/api/openapi.json. Scalar UI consumed sie automatisch.@ApiTags/@ApiOperationAnnotations folgen inkrementell pro Controller.) - CI-Pipeline (
.gitlab-ci.yml: lint, test, audit, build) — kein Container-Build, -Signing oder Deploy auf Template-Ebene - Test-Containers-Setup für Integration-Tests (Postgres + RustFS)
- Dokumentation für Konsumenten + API-Stability-Promise + Webhook-Spec
Phase 9 — Hub overhaul — Better-Auth Organizations + Hub UI (Issues #83–#88, #87, #118)
- #83 — Hub promotion:
/dev→/root-mounted SPA with Hub branding and login - #84 — Permission matrix UI: role × subject × action visual editor with tester
- #85 — Seed data: three demo users (system-admin, admin, user) and default tenant
- #86 — User management UI: list, search, ban/unban, session revocation
- #87 — Tenant management UI: CRUD, member management, settings, stats on BA Organizations
- #88 — Operator dashboard: status groups, charts, quick-access command palette
- #90 — Cmd+K command palette across all Hub pages
- #91 — Email Outbox viewer with retry/cancel
- #92 — Webhook inspector test-event button
- #118 — Better-Auth Organizations migration: replaced custom
Tenant/TenantMemberPrisma models with BAOrganization/Member/Invitation;User.tenantIdremoved, tenant context resolved fromsession.activeOrganizationId