feature/qa-05-abnahme-compliance-pruefung-core
175
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d63fcbb49e |
RBAC-02: policy-enforcement-schicht-zentral
internal/policy: deklarativer, DB-gehaltener Regelsatz (policy_rules) statt hartcodierter Go-Entscheidungslogik — Store.IsAllowed schaut ausschliesslich in die Datenbank, kein Go-Fallback. Ein Regelwechsel (Grant/Revoke) wirkt sich sofort aus, ohne Codeaenderung/Deploy (Akzeptanzkriterium 3). Jede Aenderung wird atomar mit einem versionierten Historieneintrag in policy_rule_changes festgehalten (grant/revoke, Akteur, Version). Enforcer.Authorize ist Default-Deny: existiert keine Regel fuer role+ permission, ist der Zugriff verboten (Akzeptanzkriterium 2), fuer sich genommen ohne Anwendungslogik testbar. Guard/GuardTenantScoped sind die zentrale Enforcement-Funktion (Akzeptanzkriterium 1): die uebergebene Query-Funktion wird NUR bei erfolgreicher Autorisierung aufgerufen — es gibt keinen Weg, Daten ohne vorherige Authorize-Entscheidung zu erhalten. GuardTenantScoped erzwingt zusaetzlich per Funktionssignatur, dass tenantSlug TEIL der Query-Funktion ist (Akzeptanzkriterium 3) — ein nachgelagerter Post-Filter (der archivmail-Fehler aus "Bekannte Fehler vermeiden": Tenant-Filter nach statt in der Query) ist mit dieser Signatur strukturell nicht moeglich, da die Repository-Implementierung tenantSlug selbst fuer ihre eigene WHERE-Klausel entgegennimmt. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Kein Datenzugriffs-Pfad umgeht die zentrale Enforcement-Schicht — TestGuard_NeverCallsQueryWithoutAuthorization: query-Funktion wird nachweislich NICHT aufgerufen ohne vorherige Regel, erst nach Grant. PASS. 2. Anfrage ohne passende Policy wird zuverlaessig abgewiesen (Default-Deny) — TestAuthorize_DefaultDeny: keine Regel konfiguriert -> ErrDenied, nicht automatisch erlaubt. PASS. 3. Policy-Regelsatz versioniert, Regelwechsel ohne Codeaenderung nachvollziehbar — TestGrantRevoke_ChangesBehaviorWithoutCodeChange: Verhalten aendert sich durch reinen Datenbank-Grant/Revoke, Historie zeigt beide Versionen korrekt. PASS. Zusaetzlich: TestGuardTenantScoped_IsolatesDataBetweenTenants belegt das Tenant-Scoping-Muster aus Akzeptanzkriterium 3 konkret anhand zweier Tenants. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
f7863fd4d2 |
API-05: verteilte-jwt-verifikation-rechte-feature-flag-cache-kontrakt
internal/moduletrust: asymmetrische JWT-Signatur (Ed25519) mit JWKS- Verteilung, wie im Entscheidungsverlauf "Vertrauensstellung Core<->Module" (nexarch-state.json) festgelegt. Getrennt von IAM-02s HS256-Session-Cookie (Browser-Login bleibt unangetastet) — dies ist der Modul-zu-Core- Vertrauensmechanismus. KeyManager haelt ALLE noch gueltigen Schluesselpaare (nicht nur das aktuell signierende); Rotate() erzeugt einen neuen Schluessel, alte bleiben in PublicKeySet() erhalten — bereits ausgestellte Tokens bleiben dadurch nach einer Rotation weiterhin verifizierbar (Akzeptanzkriterium 3, keine Ausfallzeit). ServeJWKS/ParseJWKS sind der Verteilungsmechanismus. StaleCache[T] ist der generische Rechte-/Feature-Flag-Cache-Kontrakt (Akzeptanzkriterium 2), mit zwei explizit benannten und begruendeten Verhalten: Get() ist FAIL-OPEN (nutzt bei Core-Ausfall einen vorhandenen, abgelaufenen Stand weiter — ein bereits authentifiziertes Modul soll nicht hart blockieren), RequireFresh() ist FAIL-CLOSED (nie zwischengespeichert, schlaegt bei Core-Ausfall klar fehl — fuer sicherheitskritische Aktionen wie einen neuen Login). LIC-02s internal/flag.Service implementiert bereits denselben Kontrakt fuer Feature-Flags; StaleCache verallgemeinert dasselbe Muster fuer JWT-Schluessel, damit beide Faelle derselben dokumentierten Policy folgen statt zwei unterschiedlichen Ad-hoc-Loesungen. Verifier.Verify ruft KeyFetchFunc nur bei abgelaufener TTL auf, nicht pro Aufruf (Akzeptanzkriterium 1) — Signaturpruefung selbst ist immer lokal. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Core simuliert abgeschaltet, andere Module bleiben fuer bereits authentifizierte Nutzer funktionsfaehig bis TTL/Fail-Open greift — TestVerify_FailsOpenWhenCoreUnreachableButStaleKeysExist: Verify() funktioniert weiter mit letztbekanntem Schluesselstand. PASS. 2. Neue sicherheitskritische Aktion schlaegt bei Core-Ausfall klar fehl, statt andere Funktionen mitzureissen — TestRequireFreshKeys_FailsClosedWhenCoreUnreachable: Fehler trotz vorhandenem (aelterem) Cache-Stand. PASS. 3. Schluesselrotation ohne Downtime in einem simulierten zweiten Modul — TestRotate_NoDowntimeForAlreadyIssuedTokens: vor UND nach Rotation ausgestellte Tokens beide weiterhin gueltig fuer Modul B. PASS. Zusaetzlich: TestVerify_DoesNotFetchPerCall belegt Akzeptanzkriterium 1 direkt (10 Verify-Aufrufe, genau 1 Fetch). PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
27f9866264 |
API-01: rest-api-grundgeruest-versionierung
internal/apiserver: Server.Handle(version, pattern, h) registriert Routen
unter /api/{version}/... (Akzeptanzkriterium 1) — verschiedene Versionen
sind unabhaengige Pfade im ServeMux, eine neue Version beeintraechtigt
bestehende nicht. HandleV1 ist die Kurzform fuer die aktuelle Hauptversion.
Einheitliches Fehlerschema {"error":{"code","message"}} ueber WriteError
(Akzeptanzkriterium 2) — bewusst NICHT auth.RequireAuth aus IAM-02
wiederverwendet, da dessen Klartext-Fehlerantworten nicht zum einheitlichen
JSON-Schema passen wuerden; stattdessen authAndTenantContext nutzt
auth.TokenIssuer.Verify direkt (dieselbe Kryptographie, keine Duplikation)
und antwortet im API-01-Schema, auch bei 401.
authAndTenantContext ist die Middleware, die JEDEM ueber Handle registrierten
Endpunkt Tenant-/Benutzerkontext bereitstellt (Akzeptanzkriterium 3, ueber
apiserver.FromContext abrufbar) — kein Handler prueft Auth selbst.
loggingMiddleware protokolliert jede Anfrage strukturiert.
Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS):
1. Fehlerhafte Anfrage liefert ueber mehrere Endpunkte hinweg dasselbe
Fehlerschema (Stichprobe) — TestErrorFormat_ConsistentAcrossEndpoints:
zwei unabhaengige Endpunkte, beide liefern 401 im identischen
{"error":{"code","message"}}-Schema. PASS.
2. Middleware-Kette nachweislich von jedem Endpunkt durchlaufen —
TestMiddleware_SetsRequestContextForEveryEndpoint: zwei Endpunkte lesen
RequestContext, beide erhalten korrekten UserID/TenantSlug aus dem Token. PASS.
3. Versionswechsel (fiktive v2-Route) ohne v1 zu beeintraechtigen —
TestVersioning_V2DoesNotAffectV1: v1 vor und nach Anlage von v2 liefert
unveraendert dieselbe Antwort. PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
b23cd1961f |
API-02: modul-registry-aktivierungspruefung
internal/moduleregistry: Registry.Register traegt Fachmodule mit Name, Version und benoetigten Feature-Flags ein (Akzeptanzkriterium 1), fehlende Pflichtangaben werden abgewiesen. IsActive kombiniert Registrierung + LIC-02 Feature-Flag-Auswertung (ALLE benoetigten Flags muessen fuer den Tenant aktiv sein) — ein nicht registriertes Modul ist nie aktiv. List liefert alle Module fuer Statusseite/Lizenzoberflaeche (Akzeptanzkriterium 3). RequireActiveModule ist die zentrale Durchsetzungs-Middleware (Casbin- Prinzip): weist Anfragen an ein deaktiviertes Modul ab, BEVOR der Modul-Handler ueberhaupt aufgerufen wird (Akzeptanzkriterium 2) — Pruefung per Test belegt, dass der Handler bei Deaktivierung nachweislich nicht erreicht wird. Service-Credentials (Akzeptanzkriterium 4): Registry.Provision stellt pro Modul-Instanz Client-ID + Secret aus, gespeichert wird nur der SHA-256-Hash des Secrets. Registry.Authenticate vergleicht timing-safe (dieselbe subtle.ConstantTimeCompare-Referenzimplementierung wie AUD-02). RequireServiceCredential-Middleware liest X-Client-Id/X-Client-Secret und weist Aufrufe ohne gueltiges Credential mit 401 ab, bevor der Core-seitige Endpunkt (z.B. Audit-Nachlieferung, Nutzungsmeldung) erreicht wird. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Anfrage an deaktiviertes Modul nachweislich vor Modul-Logik abgewiesen — TestRequireActiveModule_BlocksBeforeHandler: handlerReached bleibt false bei 403, wird true erst nach Aktivierung bei 200. PASS. 2. Registrierung mit fehlenden Pflichtangaben abgewiesen — TestRegister_RejectsMissingFields (leerer Name, leere Version). PASS. 3. Registry-Abfrage liefert konsistente Daten nach Aktivierung/Deaktivierung — TestIsActive_ReflectsFlagStateConsistently: aus/an/aus-Zyklus, IsActive folgt dem Flag-Zustand korrekt. PASS. 4. Aufruf mit ungueltigem/fehlendem Service-Credential abgewiesen, mit gueltigem angenommen — TestRequireServiceCredential_RejectsInvalidAcceptsValid und TestProvisionAndAuthenticate (falsches Secret, unbekannte Client-ID, korrektes Credential). PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
b6184b67aa |
LIC-05: speicherverbrauch-metrik-je-tenant
internal/usage/storage.go: ReportStorageWrite/ReportStorageDelete sind duenne Spezialisierungen von LIC-03s bereits atomarem Store.Increment auf die feste Metrik "storage_bytes" — Loeschung nutzt einfach ein negatives Delta desselben UPSERT-Mechanismus, kein zweiter Zaehl-Codepfad. Damit uebernehmen Akzeptanzkriterium 2 (atomar, race-frei) und die zugehoerigen LIC-03-Garantien direkt, ohne Duplikat. CurrentStorageUsage ist ein einfaches Store.Get auf dieselbe Metrik — LIC-03 kann denselben Wert ueber Store.Get(tenant, StorageBytesMetric) abfragen (Akzeptanzkriterium 3, per Test TestCurrentStorageUsage_MatchesGenericStoreGet belegt: kein zweiter, abweichender Zaehlmechanismus). Die Objekt-Storage-Treiber der Module (DMS FDN-03, Mail ARC-01), die diese Funktionen bei jedem Schreib-/Loeschvorgang aufrufen wuerden, existieren als Code noch nicht (nur geplant in dms-kanban/mail-kanban) — diese Kachel implementiert nur die Core-seitige Zaehl-Schnittstelle, analog zum AUD-05/RetentionRegistrar-Muster. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Paralleler Schreib-Test (viele gleichzeitige Uploads) ergibt korrekten Endstand ohne verlorene Updates — TestReportStorageWrite_ConcurrentUploadsSumCorrectly: 10 parallele Schreibvorgaenge unterschiedlicher Groesse, Endstand exakt gleich der in Go unabhaengig berechneten Summe. PASS. 2. Loeschvorgang dekrementiert korrekt — TestReportStorageDelete_Decrements. PASS. 3. Abfrage liefert konsistenten Wert mit unabhaengiger Kontrollzaehlung — TestCurrentStorageUsage_MatchesIndependentTally (gemischte Schreib-/ Loeschfolge, in Go parallel mitgezaehlt) und TestCurrentStorageUsage_MatchesGenericStoreGet. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d447869246 |
LIC-03: nutzungszaehler-quotas
internal/usage: Store.Increment aktualisiert Zaehlerstaende ueber ein einziges atomares SQL-UPSERT (value = value + delta) statt Read-Modify-Write in Go — haelt Zaehlerstaende bei parallelen Schreibzugriffen konsistent (Akzeptanzkriterium 1), ganz ohne Anwendungs-Lock. Quotas sind Konfiguration (usage_quotas-Tabelle je Tenant+Metrik), kein Hardcode. Check/Enforce leiten aus Zaehlerstand + Quota eine definierte Reaktion ab (StatusOK/Warning bei 80%/Exceeded, Akzeptanzkriterium 2) — Enforce ruft eine uebergebene Reaction-Funktion auf, wenn der Status nicht OK ist; die konkrete Sperr-/Benachrichtigungslogik bleibt beim Aufrufer (z.B. TEN-02 vor Benutzeranlage), Enforce garantiert nur zuverlaessiges Ausloesen. Fehlende Quota-Konfiguration bedeutet unbegrenzt (StatusOK), kein Fehler. RunPeriodicAggregation ist das Aggregations-Grundgerüst (Akzeptanzkriterium 1: "periodisch aggregiert") — dieselbe In-Prozess-Worker-Goroutine-Konvention wie internal/tenant.Lifecycle.RunSweeper. Die konkrete Aggregationsquelle (Zeilen zaehlen in Modul-Tabellen) haengt vom jeweiligen Modul ab und ist nicht Teil dieser Kachel. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Quota-Ueberschreitung automatisiert erkannt, definierte Reaktion ausgeloest — TestEnforce_TriggersReactionOnExceeded: Reaction-Callback wird mit StatusExceeded aufgerufen. PASS. 2. Aggregationsjob liefert bei parallelen Schreibzugriffen konsistente Zaehlerstaende — TestIncrement_ConsistentUnderConcurrentWrites: 50 nebenlaeufige Increments, Endstand exakt 50 (kein Lost Update). PASS. 3. Zaehlerstand eines Tenants beeinflusst nicht den eines anderen — TestIncrement_IsolatedBetweenTenants. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
6f532d8350 |
CFG-02: benachrichtigungs-dispatcher-core-service-fuer-module
internal/notify: Dispatcher.Enqueue ist die EINE schmale Schnittstelle, ueber die Module Benachrichtigungen ausloesen (Akzeptanzkriterium 1) — kein Modul baut eigenen Versandcode. Warteschlange ist die Postgres-Tabelle notification_jobs (Projekt-Konvention statt Redis/AMQP), existiert ausschliesslich in der Datenbank, nicht im Prozessspeicher. Dispatcher.ProcessDue holt faellige Jobs per FOR UPDATE SKIP LOCKED (dieselbe Konvention wie internal/tenant.Lifecycle.ProcessDueDeletions) — serialisiert konkurrierende Worker/Module, verhindert doppelte Zustellung. Fehlschlag erhoeht attempts und plant next_attempt_at mit linearem Backoff; nach max_attempts wird der Job kontrolliert auf status=failed gesetzt statt endlos wiederholt zu werden (Akzeptanzkriterium 2). Sender ist eine schmale Schnittstelle fuer die eigentlichen Kanaele (E-Mail/In-App = CFG-03, nicht Teil dieser Kachel) — der Dispatcher kennt nur "zustellen oder nicht", keine Kanal-Details. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Neustart waehrend offener Zustellung verliert keine Nachricht — TestQueue_SurvivesRestartWithoutMessageLoss: Enqueue durch eine Dispatcher-Instanz, Verarbeitung durch eine komplett neue (simulierter Neustart), Nachricht wird trotzdem zugestellt. PASS. 2. Wiederholungslogik greift bei simuliertem Fehler und bricht kontrolliert ab — TestProcessDue_RetriesThenGivesUpAfterMaxAttempts: 3 Versuche bei max_attempts=3, danach status=failed, keine weitere Verarbeitung. PASS. 3. Zwei Module loesen gleichzeitig aus, beide korrekt zugestellt — TestProcessDue_ConcurrentDispatchBothDelivered: zwei parallele ProcessDue-Aufrufe, beide Nachrichten je genau einmal zugestellt, keine Doppelzustellung. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
c9e8edbbf0 |
AUD-03: audit-log-export-filter-api
internal/audit/export.go: StreamCSV/StreamJSON filtern nach Tenant, Akteur, Aktion und Zeitraum (Akzeptanzkriterium 1) und schreiben Zeile fuer Zeile ueber rows.Next() DIREKT auf den uebergebenen io.Writer — zu keinem Zeitpunkt wird das komplette Ergebnis im Speicher aufgebaut (Akzeptanz- kriterium 3). JSON-Export als JSON Lines statt einem grossen Array, um Streaming ohne Sonderbehandlung von Klammern/Kommas zu ermoeglichen. ExportHandler (Akzeptanzkriterium 2) schreibt direkt auf http.ResponseWriter — derselbe Streaming-Pfad wie in Tests, kein Zwischenpuffer nur fuer HTTP. Authorize ist eine schmale Schnittstelle (Vorbild: AUD-05 RetentionRegistrar- Muster), da die eigentliche Rollenpruefung RBAC-02 (Policy-Enforcement) ist und nicht Teil dieser Kachel — der Handler kennt nur "darf dieser Aufrufer exportieren", nicht wie das entschieden wird. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Export mit hoher Eintragszahl ohne uebermaessigen Speicherverbrauch — TestExport_StreamsLargeResultWithoutExcessiveMemory: 20.000 Eintraege, Heap-Wachstum waehrend Export nur ~1.8KB (Schwelle 3MB). PASS. 2. Filterkombinationen automatisiert gegen erwartete Ergebnismengen — TestExport_FilterCombinations (Tenant/Actor/Action einzeln und kombiniert) und TestExport_TimeRangeFilter (innerhalb/ausserhalb Zeitraum). PASS. 3. Zugriff ohne passende Berechtigung abgewiesen — TestExportHandler_RejectsWithoutAuthorization: fehlender/falscher caller -> 403, berechtigter caller -> 200. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
3376cf99dc |
AUD-05: audit-log-registrierung bei archive-retention-engine
internal/audit/retention.go: RegisterWithArchive meldet audit_log_entry als Objekttyp bei Archives Retention-Engine an (Default-Frist 10 Jahre, GoBD- Buchungsbeleg-Frist, tenant-ueberschreibbar). RetentionRegistrar ist der RET-05-Modul-Adapter-Vertrag, wie Core ihn konsumiert — die eigentliche Implementierung lebt im Archive-Modul. WICHTIGER HINWEIS: Archive (RET-01 Retention-Objektmodell, RET-02 Fristen- Engine, RET-05 Modul-Adapter) existiert zum Zeitpunkt dieser Kachel NICHT als Code — nur als Planung in archive-kanban/. Diese Kachel implementiert ausschliesslich die Core-Seite (Registrierungsaufruf gegen die Schnittstelle) und testet sie gegen einen lokalen Fake, der den RET-05-Vertrag simuliert. Das ist KEIN Ersatz fuer eine echte Integrationspruefung gegen Archive. Core implementiert bewusst keine eigene Loeschlogik fuer Audit-Eintraege (Akzeptanzkriterium 3) — es gibt in diesem Paket keinen Delete-Codepfad ausser dem durch AUD-02 technisch unterbundenen. Pruefungen: 1. Registrierung bei Archive erfolgreich getestet, Objekttyp taucht in Archives Retention-Konfiguration auf — NICHT durchfuehrbar, da Archive nicht existiert. Stattdessen TestRegisterWithArchive_UsesCorrectObjectTypeAndRetention gegen Fake: bestaetigt korrekten Aufruf mit objectType=audit_log_entry, 10 Jahre, tenantOverridable=true. Ausgefuehrt, PASS — aber die eigentliche Pruefung bleibt OFFEN bis Archive RET-05 existiert. 2. Audit-Eintrag mit abgelaufener Frist wird von Archive korrekt als loeschfaellig markiert, Core greift nicht ein — NICHT durchfuehrbar ohne Archive RET-02. Offen. 3. Legal Hold aus Archive verhindert Loeschung trotz abgelaufener Frist — NICHT durchfuehrbar ohne Archive RET-01/02. Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
1bfb2efd94 |
AUD-02: unveraenderliches-protokoll-append-only
Append-only per Trigger (nicht nur GRANT/REVOKE): audit_events_prevent_mutation() wirft bei jedem UPDATE/DELETE auf audit_events eine Exception, unabhaengig von der verbindenden Rolle (Akzeptanzkriterium 1). internal/audit/four_eyes.go: Vier-Augen-Prinzip fuer sicherheitskritische Entscheidungen (Loeschbestaetigung, Rechtevergabe), 1:1 nach archivdms- Vorbild. Request erzeugt einen Klartext-Code (wird ausserhalb des Systems an eine ZWEITE Person uebermittelt) und speichert nur dessen SHA-256-Hash. Confirm sperrt die Zeile mit FOR UPDATE (Akzeptanzkriterium 2 — serialisiert zwei gleichzeitige Bestaetigungsversuche, verhindert doppelte Ausfuehrung), weist eine Bestaetigung durch dieselbe Person wie die anfordernde ab (ErrSameActor, echtes Vier-Augen-Prinzip statt nur Code-Pruefung), und vergleicht den Code timing-safe (Akzeptanzkriterium 3). internal/audit/timingsafe.go: timingSafeEqual als projektweite Referenz- implementierung (subtle.ConstantTimeCompare) fuer sicherheitsrelevante Vergleiche — andere Module (z.B. Archive CMP-06 Freigabelinks) uebernehmen dasselbe Muster laut IAM-02-Konvention. Nebenbei behoben: AUD-01s eigener Test nutzte einen festen Tenant-Slug mit DELETE-basiertem Cleanup — seit dem neuen Append-only-Trigger schlaegt dieses Cleanup lautlos fehl, wodurch Zeilen sich ueber Testlaeufe hinweg summierten und die Zaehl-Assertion brach. Auf eindeutigen Slug pro Lauf umgestellt (direkte, notwendige Folge dieser Kachel, keine Umgestaltung von AUD-01 selbst). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Direkter UPDATE/DELETE-Versuch von der Datenbank abgewiesen — TestAppendOnly_RejectsUpdateAndDelete: beide Operationen scheitern, Eintrag bleibt unveraendert erhalten. PASS. 2. Vier-Augen-Prinzip mit FOR-UPDATE-Lock race-frei unter parallelen Anfragen — TestFourEyes_ConcurrentConfirmIsRaceFree: zwei gleichzeitige Bestaetigungsversuche fuer denselben Vorgang, genau einer erfolgreich, der andere ErrAlreadyDecided. PASS. 3. Timing-safe Vergleich per Laufzeitmessung stichprobenartig verifiziert — TestTimingSafeEqual_NoEarlyExitTiming: Mismatch am Anfang (603µs) vs. am Ende (574µs) ueber 20000 Iterationen, kein Hinweis auf Short-Circuit- Vergleich (Ratio innerhalb Faktor 3 Toleranz). PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
ed67887385 |
RBAC-01: rollenmodell-grundrechte
internal/rbac: drei Grundrollen (superadmin, tenant_admin, user) mit Hierarchie ueber eine einfache Eltern-Map (tenant_admin erbt von user, superadmin erbt von tenant_admin) — EffectivePermissions loest die volle vererbte Rechtemenge auf (Akzeptanzkriterium 2). Policy-Modell bewusst als reine Go-Datenstruktur getrennt von der Durchsetzung (RBAC-02), nach Casbin-Prinzip. Store verwaltet Rollenzuweisungen innerhalb EINER Tenant-Datenbank (Modell C, analog internal/user.TenantUserStore) — nur 'user' und 'tenant_admin' sind hier zuweisbar (assignableRoles-Matrix). Ein Zuweisungsversuch fuer 'superadmin' wird abgewiesen, da diese Rolle mandantenuebergreifend ist und bereits durch die Existenz eines Kontos in IAM-01s SuperadminStore repraesentiert wird — keine doppelte Modellierung. Jede Zuweisung schreibt zusaetzlich einen Historieneintrag (role_assignment_history) mit grantedBy/grantedAt, atomar in derselben Transaktion (Akzeptanzkriterium 3). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Zuweisung ausserhalb der erlaubten Matrix abgewiesen — TestStore_RejectsSuperadminOutsideAllowedMatrix und TestStore_RejectsUnknownRole: beide ErrRoleNotAssignableInTenantScope, kein Datensatz hinterlassen. PASS. 2. Rollenhierarchie liefert erwartete effektive Rechtemenge — TestEffectivePermissions_Inheritance: tenant_admin hat geerbte user-Rechte + eigene, aber nicht platform.manage_tenants; superadmin hat die volle Kette. PASS. 3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Zusaetzlich automatisiert getestet (Akzeptanzkriterium 3): TestStore_HistoryTracksWhoAndWhen — zwei aufeinanderfolgende Zuweisungen, Historie liefert beide mit korrektem grantedBy in chronologischer Reihenfolge. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
383da92ca8 |
TEN-03: tenant-einstellungen-branding
internal/tenantsettings: pro-Tenant-Einstellungen (Anzeigename, Logo, Farbschema, Zeitzone, Sprache) in der Registry-DB, alle Spalten nullable — fehlender Wert bedeutet immer "Systemvoreinstellung verwenden" (Defaults(): color_scheme=system, timezone=UTC, language=de), niemals ein Fehler (Akzeptanzkriterium 2). Store.Update schreibt aktuellen Stand + Historieneintrag atomar in einer Transaktion mit FOR-UPDATE-Lock auf der aktuellen Zeile (Akzeptanzkriterium 3, Race-sicher bei nebenlaeufigen Updates desselben Tenants). Patch-Typ mit *string-Feldern erlaubt Teil-Updates ohne unbeteiligte Felder zu beruehren. Schlanker Handler (Get/Update ueber tenant_id-Query-Parameter) als Vorbereitung der REST-Schnittstelle — echte Auth/Versionierung kommt erst mit API-01/IAM-02/RBAC-01. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Aenderung eines Tenants wirkt sich nicht auf einen anderen aus — TestUpdate_IsolatedBetweenTenants: Tenant A geaendert, Tenant B bleibt nachweislich bei Defaults(). PASS. 2. Fehlende Werte liefern Defaults statt Fehler — TestGet_UnsetTenantReturnsDefaults (Tenant ganz ohne Datensatz) und TestUpdate_PartialPatchKeepsOtherFieldsAtDefault (nur ein Feld gesetzt, Rest bleibt Default). PASS. 3. API-Schema von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Zusaetzlich automatisiert getestet (Akzeptanzkriterium 3): TestUpdate_HistoryTracksVersions — 3 aufeinanderfolgende Aenderungen, Historie liefert alle 3 in korrekter Reihenfolge, nicht angefasste Felder bleiben aus dem vorherigen Update erhalten. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
4180a26c6e |
CFG-01: zentraler-konfigurationsdienst
internal/cfgservice: Store (Schreiben/Historie) + Service (Lesen mit Vorrangregel + TTL-Cache, Default 5s, analog internal/flag). Genannt "cfgservice" statt "config", da internal/config bereits die Bootstrap- Konfiguration des Core-Prozesses selbst belegt. Store.Set schreibt aktuellen Stand (config_values) und Historieneintrag (config_value_history) atomar in einer Transaktion — eine Aenderung ohne Versionshistorie ist strukturell ausgeschlossen (Akzeptanzkriterium 2). Version wird pro (key, scope) monoton hochgezaehlt. Service.Resolve wendet die Vorrangregel an: Tenant-spezifischer Override (scope = Tenant-Slug) hat Vorrang vor globalem Default (scope = 'global'), faellt sauber zurueck wenn kein Override existiert (Akzeptanzkriterium 1). Invalidate erzwingt sofortiges Neuladen fuer den Schreiber, andere Instanzen sehen Aenderungen spaetestens nach der TTL. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Vorrangregel automatisiert getestet — TestService_TenantOverrideTakesPrecedenceOverGlobal: Tenant mit Override bekommt Tenant-Wert, Tenant ohne Override bekommt Global-Default. PASS. 2. Cache-Invalidierung nach Aenderung innerhalb dokumentierter Zeit gemessen — TestService_CacheInvalidationTiming: wirksam nach 154ms bei TTL=150ms (innerhalb Ziel+Toleranz), vorher nachweislich noch alter Stand. PASS. 3. Versionierungshistorie ueber mehrere Aenderungen nachvollzogen — TestStore_HistoryTracksAllChanges: 3 aufeinanderfolgende Aenderungen, Historie liefert alle 3 in korrekter Reihenfolge mit korrekten Versionsnummern. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
b12d53f469 |
AUD-01: zentrales-audit-log-modell
internal/audit: eigenes, strukturiertes Audit-Datenmodell (Akteur, Aktion, Zielobjekt, Zeitpunkt, Tenant) in der Registry-DB, getrennt von jedem allgemeinen Anwendungs-Log (eigenes Paket, eigene Tabelle audit_events, kein Logging-Framework). Log.Record ist der EINE zentrale Schreibpfad — es gibt keine zweite Schreibmoeglichkeit, ueber die ein Handler die Validierung umgehen koennte. Fehlender Tenant-Bezug wird zweifach verhindert (Akzeptanzkriterium 2): Log.Record weist leeren TenantSlug direkt ab (ErrMissingTenant), zusaetzlich erzwingt eine CHECK-Constraint in der Migration dasselbe auf Datenbankebene, selbst wenn Log.Record umgangen wuerde. Mandantenuebergreifende Ereignisse (z.B. Superadmin-Aktionen) nutzen den reservierten Wert audit.SystemTenant statt NULL oder leerem String — es gibt keinen Weg, ganz ohne Tenant-Bezug zu schreiben. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Automatisierter Test belegt genau einen Audit-Eintrag pro sicherheitsrelevantem Vorgang — TestRecord_PersistsExactlyOneEventPerSecurityIncident (simulierter fehlgeschlagener Login), Feldinhalte verifiziert. PASS. 2. Fehlender Tenant-Bezug durch Constraint/Test verhindert — TestRecord_RejectsMissingTenant (App-Ebene) UND TestConstraint_RejectsMissingTenantAtDatabaseLevel (direkter INSERT unter Umgehung von Log.Record, durch CHECK-Constraint abgewiesen). PASS. 3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
4a30345e07 |
LIC-02: feature-flag-service-je-tenant
internal/flag: Store (Verwaltung) + Service (Auswertung mit TTL-Cache, Default 5s) — Unleash-Prinzip Flag-Verwaltung vs. Flag-Auswertung getrennt, als Kernfunktion des Core-Dienstes selbst statt separater Infrastruktur. evaluate() wendet drei Strategien in fester Reihenfolge an: global an/aus, Tenant-Zielgruppe, deterministischer Prozentsatz-Rollout (FNV-Hash aus Tenant+Key, stabil pro Tenant). IsEnabled liefert IMMER nur bool (kein Fehlerwert) — ein nicht erreichbarer Flag-Dienst kann damit keinen Aufrufer zum Absturz bringen: bei DB-Fehler wird der zuletzt bekannte Cache-Stand verwendet, ohne jeglichen Stand faellt der Dienst sicher auf false zurueck. Service.Invalidate erzwingt sofortiges Neuladen fuer den Schreiber selbst, andere Instanzen sehen Aenderungen spaetestens nach der TTL (Akzeptanzkriterium 3, kein Neustart noetig). Bugfix waehrend Tests: Store.Set uebergab ein nil-TargetTenantSlugs-Slice als SQL NULL statt leerem Array (NOT-NULL-Verletzung) — auf leeres Slice normalisiert. Akzeptanzkriterium 4 (Deaktivierung loescht keine Daten): dieses Paket besitzt ausschliesslich die eigene feature_flags-Zeile, hat keinerlei Code-Pfad, der Modul-Geschaeftsdaten anfassen koennte — Loeschung bleibt strukturell der Archive-Retention-Engine vorbehalten. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Cache-Invalidierungszeit automatisiert gemessen — TestService_CacheInvalidationTiming: Aenderung wirksam nach 153ms bei TTL=150ms (innerhalb Ziel+Toleranz), vorher nachweislich noch alter Stand. PASS. 2. Zielgruppen-Strategie liefert erwartete Auswertung — TestService_TargetTenantStrategy / TestEvaluate_TargetTenantStrategy. PASS. 3. Ausfall des Flag-Dienstes fuehrt zu dokumentiertem Fallback, kein Absturz — TestService_FallsBackOnStoreFailure (mit recover()-Absicherung): Fallback auf Cache-Stand bzw. sicheres false bei komplett unerreichbarer DB, geloggt. PASS. 4. Modul-Deaktivierung/Reaktivierung ohne Datenverlust — architektonisch durch fehlenden Code-Pfad sichergestellt (siehe oben), zusaetzlich durch TestService_InvalidateForcesImmediateRefresh (Toggle aus/an bleibt konsistent nachvollziehbar) mitabgedeckt. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
d63db93a4c |
LIC-01: lizenzmodell-lizenzschluessel-pruefung
internal/license: Ed25519-signierte Lizenzschluessel (stdlib crypto/ed25519, keine neue Abhaengigkeit). Issuer haelt den privaten Schluessel (lebt beim Lizenzgeber), Validator nur den oeffentlichen (lebt im Core-Prozess) — klare Trennung Ausstellung/Pruefung nach Unleash-Vorbild (Flag-Verwaltung vs. Flag-Auswertung). Store.Install prueft NUR die Signatur und persistiert den Lizenzumfang (Plan, Modul-Liste, Laufzeit) in tenant_licenses (Registry-DB, 1:1 zu tenants). Eine bereits abgelaufene, aber korrekt signierte Lizenz laesst sich trotzdem einspielen — der Ablauf wird erst bei Store.RequireActive bewertet (liefert ErrLicenseExpired statt Panic/Absturz), waehrend Store.Status den Umfang unabhaengig vom Ablauf weiterhin liefert. Neu: scripts/run-checks.sh buendelt reset-test-env.sh + go build/vet/test (-p 1) zu einem Ein-Kommando-Check fuer den Testhost. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Manipulierter Lizenzschluessel zuverlaessig erkannt — TestParse_RejectsTamperedKey, TestParse_RejectsWrongKeyPair, TestStore_InstallRejectsInvalidSignature. PASS. 2. Ablauf loest definierten eingeschraenkten Zustand aus, kein harter Systemausfall — TestStore_RequireActive_DetectsExpiry (inkl. recover()- Absicherung im Test, dass kein Panic auftritt), ErrLicenseExpired statt Absturz; Status bleibt trotzdem abfragbar. PASS. 3. Signaturpruefung von zweiter Person gegen Dokumentation nachvollzogen — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
b08f6a49fc |
TEN-07: migrations-orchestrierung-tenant-datenbanken
Orchestrator.RolloutAll wendet eine geordnete Liste von Migrationen auf JEDE registrierte Tenant-Datenbank an (LoadMigrations liest *.up.sql aus einem Verzeichnis nach der bestehenden 000N_name-Namenskonvention). Jeder Tenant laeuft unabhaengig in eigener Verbindung — ein Fehlschlag bei einem Mandanten bricht nur dessen eigenen Rollout ab (spaetere Migrationen bauen typischerweise auf frueheren auf) und blockiert die uebrigen Tenants nicht. schema_migrations-Tabelle pro Tenant-Datenbank (version PK, applied_at, success, error) haelt den Stand pro Version einzeln nachvollziehbar fest. Bereits erfolgreiche Versionen werden bei einem erneuten Rollout uebersprungen (isAlreadySuccessful-Check vor jeder Anwendung), fehlgeschlagene werden beim naechsten Versuch automatisch erneut probiert (kein manuelles Zuruecksetzen noetig) — ON CONFLICT DO UPDATE haelt jeweils nur den letzten Versuch fest. Bewusst ohne Abhaengigkeit von TEN-06 (Router): Migrations-Rollouts sind seltene Batch-Vorgaenge, ein kurzlebiger Pool pro Tenant und Lauf reicht. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS, go test ./... mit -p 1 noetig da mehrere Pakete die geteilte Registry-Tabelle auf derselben Postgres-Instanz nutzen — siehe scripts/reset-test-env.sh): 1. Rollout gegen 3 Test-Tenants, einer absichtlich inkompatibel (Tabellen- Konflikt bei Migration 2) — TestRolloutAll_IsolatesFailurePerTenant: die anderen beiden erhalten beide Migrationen, der inkompatible bekommt Migration 1 trotzdem, scheitert nur an Migration 2, faellt nicht die anderen um. PASS. 2. Migrationsstand-Abfrage liefert korrekten Stand pro Tenant — TestStatus_ReflectsPerTenantState: Version 1 success=true, Version 2 success=false mit Fehlertext. PASS. 3. Wiederholter Rollout fuer fehlgeschlagene Migration moeglich, ohne bereits erfolgreiche erneut anzuwenden — TestRolloutAll_RetryDoesNotReapplySuccessful: Migration 1 nutzt bewusst kein IF NOT EXISTS, ein Reapply haette den zweiten Lauf scheitern lassen; zweiter Lauf ist fehlerfrei und wendet nur die zuvor fehlgeschlagene Version an. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
45bc10719a |
TEN-04: tenant-lifecycle-suspendieren-reaktivieren-loeschen
Zustandsautomat active/suspended/pending_deletion/deleted als First-Class- Konzept (previous_status + deletion_scheduled_at in der Registry). Alle Uebergaenge in Registry.transition als atomarer Check-and-Set (UPDATE ... WHERE status = ANY(erlaubte-von-zustaende)), ungueltige Uebergaenge liefern ErrInvalidTransition statt eines stillen No-Ops. ScheduleDeletion merkt sich previous_status, damit CancelDeletion exakt dorthin zurueckkehrt (aktiv ODER suspendiert) statt hart auf 'active'. Lifecycle.ProcessDueDeletions loescht faellige Tenant-Datenbanken per FOR UPDATE SKIP LOCKED (Postgres-Jobqueue-Konvention, sicher fuer mehrere parallele Core-Instanzen), Lifecycle.RunSweeper triggert das periodisch per In-Prozess-Goroutine. Lifecycle.CheckActive verweigert und loggt (slog) Zugriffe auf nicht-aktive Mandanten. Bugfix nebenbei: Registry.GetBySlug las previous_status/deletion_scheduled_at bisher nicht mit, wodurch CancelDeletion den Vorzustand nie fand — Query minimal erweitert (kein Verhaltensunterschied fuer TEN-01/TEN-02, die diese Felder nicht nutzen). Neu: scripts/reset-test-env.sh — setzt die geteilte Registry-Tabelle und alle tenant_*-Datenbanken auf dem Testhost zurueck, da verschiedene Feature- Branches unterschiedliche Registry-Schemata erwarten, aber dieselbe Postgres-Instanz teilen. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Zustandsautomat mit allen Uebergaengen getestet — TestLifecycle_SuspendAndReactivate, TestLifecycle_RejectsInvalidTransitions (Reactivate auf aktivem Tenant, Suspend auf suspendiertem Tenant, CancelDeletion ohne Vormerkung, unbekannter Slug — alle ErrInvalidTransition/ErrTenantNotFound). PASS. 2. Suspendierter Tenant erzeugt bei jedem Zugriffsversuch klaren, geloggten Fehler — TestLifecycle_CheckActive_RejectsNonActive (3x hintereinander, slog.Warn nachweislich pro Aufruf). PASS. 3. Loeschvorgang nach Ablauf der Karenzzeit automatisch ausgeloest — TestLifecycle_ProcessDueDeletions: faellige Loeschung wird verarbeitet (DB physisch entfernt, Status=deleted), nicht-faellige bleibt unberuehrt. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
2026514404 |
TEN-06: connection-routing-pooling-pro-mandant
Router loest Tenant-Slug (aus JWT-Claim, API-05 vorausgesetzt) ueber die TEN-01-Registry in eine wiederverwendete Postgres-Verbindung auf. LRU-Cache (container/list) begrenzt die Zahl gleichzeitig offener Tenant-Pools auf maxOpen — bei Ueberschreitung wird der am laengsten ungenutzte Pool geschlossen, bevor ein neuer aufgemacht wird. Fehlender/unbekannter Tenant-Kontext liefert explizite Fehler (ErrMissingTenantContext / ErrUnknownTenant) statt stillschweigend zu routen. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Lasttest mit 6 simulierten Mandanten gegen maxOpen=2 — TestRouter_BoundsOpenConnectionsUnderLoad: OpenCount() bleibt nach jedem Resolve <= maxOpen, Verbindungszahl waechst nicht linear mit der Mandantenzahl. PASS. 2. Anfrage ohne/mit unbekanntem Tenant-Kontext abgewiesen — TestRouter_RejectsMissingOrUnknownTenant. PASS. 3. Verbindungswiederverwendung gemessen — TestRouter_ReusesConnectionForSameTenant: zweiter Resolve-Aufruf liefert exakt dieselbe *pgxpool.Pool-Instanz. PASS. Router ist eigenstaendig nutzbar/getestet, aber noch nicht in cmd/core/main.go verdrahtet — der JWT-Claim mit Tenant-Kontext (API-05) und das HTTP-Routing, das den Slug pro Request extrahiert, sind nicht Teil dieser Kachel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
3d20d86a4f |
IAM-02: login-session-jwt-grundgeruest
internal/auth: Login/Logout ueber httpOnly/Secure/SameSite=Strict-Cookie mit HS256-JWT (30min TTL), bcrypt-Passwort-Hashing (Cost 12, explizit begruendet und benchmarkt statt DefaultCost uebernommen), RequireAuth-Middleware fuer geschuetzte Routen. LoginService ist strukturell auf einen Tenant gescopt (nutzt user.TenantUserStore, dessen Pool = eine Tenant-DB — derselbe Mechanismus wie in TEN-01/TEN-02), liefert bei falscher E-Mail und falschem Passwort denselben Fehler (User-Enumeration-Schutz) inkl. Dummy-bcrypt- Vergleich gegen Timing-Seitenkanal bei unbekannter E-Mail. user.TenantUserStore erweitert um SetPasswordHash/GetByEmailForAuth (password_hash bleibt ausserhalb des regulaeren User-Typs/JSON-Pfads). Migration 0002 fuegt password_hash-Spalte hinzu (Default '', da IAM-01 User ohne Passwort anlegt). Login-Handler ist wie IAM-01/TEN-02 aus denselben Gruenden (Tenant- Connection-Routing = TEN-06, noch nicht gebaut) nicht in cmd/core/main.go verdrahtet — Package ist eigenstaendig nutzbar/getestet. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Login-Query tenant-gescopt — TestLoginService_NoCrossTenantLogin: gleiche E-Mail in zwei Tenant-DBs mit unterschiedlichem Passwort, Login gegen Tenant A mit Tenant-B-Passwort schlaegt fehl. PASS. 2. Session-Fixation/Token-Manipulation — TestTokenVerify_RejectsManipulatedPayload und TestTokenVerify_RejectsWrongSecret: manipuliertes/falsch signiertes Token wird abgelehnt. PASS. 3. Abgelaufenes Token erzwingt Neuanmeldung — TestTokenVerify_RejectsExpiredToken und TestRequireAuth_BlocksWithoutValidCookie. PASS. 4. Login-Latenz mit Kostenfaktor 12 gemessen: 294ms (Ziel < 400ms) — TestBcryptCostAgainstLatencyTarget. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
5915a4e7b1 |
TEN-02: tenant-onboarding-api-self-service
OnboardingService.Onboard: ein API-Aufruf legt Tenant (via bestehendem Provisioner) UND ersten Administrator-Account (via IAM-01 TenantUserStore) an. CREATE DATABASE erlaubt keine echte cross-database Transaktion, daher Saga-Kompensation: schlaegt die Admin-Anlage nach erfolgreichem Provisioning fehl, wird der Tenant per neuem Provisioner.Deprovision wieder vollstaendig entfernt (Registry.Delete + DB-Drop). Provisioner.Provision mappt Duplikat-Faelle (42P04 duplicate_database und den bei echt parallelen CREATE DATABASE moeglichen 23505-Unique-Konflikt auf pg_database) jetzt auf ErrTenantExists statt einer rohen Postgres-Fehlermeldung. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Onboarding-Vorgang zweimal parallel mit gleichem Slug ausgeloest — TestOnboarding_RejectsDuplicateSlugConcurrently: genau ein Erfolg, kein Doppel-Tenant, zweiter Aufruf bekommt ErrTenantExists. PASS. 2. Fehleingaben (leere Pflichtfelder, ungueltige E-Mail, ungueltiger Slug) — TestOnboarding_ValidationErrors deckt alle vier Faelle mit klaren Fehlern ab, bevor irgendein DB-Zugriff stattfindet. PASS. 3. Erfolgreicher Durchlauf von zweiter Person end-to-end nachvollzogen — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
e4793303fc |
IAM-01: benutzer-datenmodell-crud
Benutzer-Datenmodell + CRUD fuer Tenant-User (tenant-scoped, keine tenant_id-Spalte noetig, Tenant ergibt sich aus der DB-Verbindung, Modell C) und getrennt dafuer SuperadminStore fuer mandantenuebergreifende Konten in der Registry-DB — First-Class-Typ statt tenant_id-NULL-Sonderfall im Tenant-User-Code (bekannter archivdms-Fehler vermieden). E-Mail-Eindeutigkeit: tenant-scoped fuer normale Benutzer (UNIQUE-Constraint gilt nur innerhalb der jeweiligen Tenant-DB), global fuer Superadmins (eine Registry-DB, ein UNIQUE-Constraint). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. CRUD automatisiert getestet inkl. Negativfaellen — TestTenantUserStore_CRUD deckt doppelte E-Mail (ErrEmailTaken) und unbekannte ID ab. PASS. 2. Superadmin-Anlage ohne Tenant-Kontext — TestSuperadminStore_CreateWithoutTenantContext: SuperadminStore.Create hat syntaktisch keinen Tenant-Parameter, kein if-Zweig fuer "kein Tenant" im Code. PASS. 3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Tenant-User-Handler ist im Code vorhanden, aber in cmd/core/main.go noch nicht geroutet — braucht Connection-Routing pro Mandant (TEN-06), das nicht Teil dieser Kachel ist. Nur der Superadmin-Endpunkt ist verdrahtet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
8da9c67d08 |
TEN-01: tenant-registry-datenbank-provisioning
Registry-DB (nur Tenant-Metadaten), Provisioning-Routine legt pro Mandant
eine physisch isolierte Postgres-DB an und registriert sie transaktional
(Rollback der DB bei fehlgeschlagener Registrierung). Schlanker HTTP-Handler
als Schnittstellen-Vorbereitung fuer API-01/TEN-02, kein eigenes REST-Grundgerüst.
Pruefungen:
1. Migration up/down geschrieben (0001_tenant_registry.{up,down}.sql) — nicht
gegen echte DB ausgefuehrt, da auf dieser Maschine kein Go/Postgres-Test-
Setup verfuegbar ist. Offen zur Ausfuehrung.
2. Integrationstest TestProvision_CreatesIsolatedDatabases geschrieben (zwei
Mandanten, prueft unterschiedliche db_name und current_database()) —
ebenfalls nicht ausgefuehrt, guarded per TEST_ADMIN_DSN env var. Offen.
3. Slug-Validierung (unit test TestValidateSlug) deckt SQL-Injection-Versuch
im Datenbanknamen ab — ebenfalls nicht lokal ausgefuehrt, da kein Go
Compiler auf dieser Maschine vorhanden ist. Offen.
Alle drei Pruefungen sind vorbereitet, aber NICHT durchgefuehrt worden —
zaehlen laut Vorgabe als offen bis auf einer Maschine mit Go+Postgres verifiziert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
c895a67c4b | core: initial Go module skeleton (config, db pool, tenant registry migration) | ||
|
|
72261cc69f | first commit |