Changelog

Product changelog

Every Simmetric Chat release, from the newest version to the oldest.

Unreleased

Added

  • Das Widget-Ownership-Modell (2026-10-05 "wown", quick 261005-scn). Widgets haben jetzt Eigentümer (ihren Ersteller) und Benutzer (pro-Widget-Zuteilungen): - die widget_users-Verbindung (eine additive Migration) + die users.widgetQuotaLimit-Obergrenze pro Eigentümer; NULL/fail-closed 0 — unangetastete Installationen sehen null Verhaltensänderung (das maxSponsoredUsers-Gitteridiom der Phase-206). - Das Widget-CRUD für nicht-admin-EIGENTÜMER wieder scharf geschalten: POST /api/widgets fährt über die neue widget:manage-Berechtigung + die vom Admin vergebene Obergrenze (Überschreitung → 409 { error, quota: "widgets" }); die Lizenz-Gates auf Install-Ebene (widget_enabled / max_widgets) fahren unverändert. GET list/detail, PUT settings + Workspace-Bindungen erreichen creator | widget-user | admin (fremde IDs bleiben nicht-enummerierende 404); DELETE bleibt creator | admin. - Die neue Admin-Sektion im Widget-Bereich (/widgets?tab=quotas, der Quota-Tab): die Zeilen pro Benutzer — user | owned | max-widgets — mit assign / clear (null)-Schreibungen, einer paginierten Suche, audit-protokolliert (user.widget_quota.updated). - Der neue "Benutzer"-Tab pro Widget (WidgetDetail, im Bearbeitungsmodus): der EIGENTÜMER wählt die Widget-Benutzer (eine replace-set-PUT, die Berechtigungsverzeichnis-Sonde /widgets/owner-user-search — eine Identitätsauswahl-Oberfläche, keine E-Mails); die begünstigten Benutzer bekommen das read-only-Arm. - Tests: die Server-Matrix 30-pin (widgetOwnership.test.ts), die Frontend-Arme (WidgetUsersTab/WidgetQuotasTab), die e2e-Reise (widget-ownership.spec.ts).

Fixed

  • Das Teilen eines Workspaces stellt die Rolle auf Editor, nicht Betrachter (2026-10-05 "viewer-default"). Das gemeldete Symptom — "als begünstigter Benutzer kann ich keine neuen Chats anlegen und nicht in bestehende Chats des freigegebenen Workspace schreiben" — wurde (via eine neue 4-pin-Integrations-Sonde, granteeChatWriteAccess.test.ts) auf die GRANT-ROLLE zurückgeführt, nicht auf die Gates: mit WORKSPACE_ROLE_ENFORCEMENT=true durchläuft eine Editor-Zeile WorkspaceAccess GET /chats (200) UND POST /chat (200) serverseitig; eine VIEWER-Zeile durchläuft den Lese-Arm und wird auf dem Schreib-Arm BY DESIGN mit 403 bewertet, und ein Zuteilungsempfänger ohne Zeile bekommt die nicht-enummerierende 404 (SC-4). BEIDE Zuteilungsoberflächen (der Teilen-Dialog des Eigentümers WorkspaceAccessDialog und das Settings-Panel SettingsWorkspaceAccess) wählten bei den Einzel- UND Massen-Role-Selects viewer vor — ein Admin, der freigab, ohne die Auswahl zu wechseln, produzierte read-only-Zuteilungen. Auch der UI-Default widersprach der Absicht auf Schema-Ebene: der Migrationskommentar Phase 189 D-12 pinnt den Legacy-Zeilen-Füllbestand auf Editor ("NOT the spec's viewer suggestion"). Beide Oberflächen stellen Editor auf Standard (einzel + Masse); die Rollen-KABEL-Epins (ein Test, der ausdrücklich viewer wählt und dann verifiziert, dass viewer durchgereicht wird) bleiben unangetastet. Notiz: Benutzer, die VOR dieser Korrektur begünstigt wurden, behalten ihre persistierte Rolle — ein Re-Grant (Rollen-Upsert) bewertet neu.
  • "Fasse die angehängte Datei zusammen" logt nicht mehr mit dem vorgefertigten Nichts-gefunden-Satz (die Lücke Angehängtes-vor-Ingest geschlossen, 2026-10-05). Eine Datei im Chat anlegen lädt sie über die ASYNC-Ingest-Pipeline hoch; die Nachricht fragt einen Beat später, das Dokument hat noch NULL Chunks, also injizierte die per-Route-Auflösung attachedDocumentId (beide Chat-Arme in chat.ts) NICHTS: der Agent fiel auf rag_search zurück, das frische Dokument war noch nicht im Vektorspeicher, und buildSystemPrompts Regel 3 antwortete mit dem vorgefertigten Satz "Non ho trovato informazioni su questo argomento nei documenti del workspace. Verifica che i file pertinenti siano stati caricati e indicizzati." — eine falsche Behauptung (der Workspace kann voll sein; nur der frische Anhang ist nicht bereit). Beide Auflösungsstellen entdecken jetzt den Null-Chunk-Arm und injizieren eine wahrheitsgemäße Meldung (buildAttachmentNoticeChunk(new utils/attachmentNotice.ts)): der non-empty-ragContext-Arm des Orchestrators streicht rag_search, sodass das Modell aus der Meldung antwortet — "noch in Bearbeitung, versuchen Sie es gleich wieder" (pending/processing) oder "Bearbeitung fehlgeschlagen/abgebrochen, neu hochladen" — und die Meldung verbietet ausdrücklich den vorgefertigten Satz. Status- und fortschrittsbewusst (der Collector-Fortschritt 0-100 erscheint nur, wenn > 0); die DLP-Rag-Kontext-Untersuchung fährt auf der Meldung unverändert. 5 Unit-Epins auf dem Helfer (die Arme pending/processing/failed/cancelled + die Anti-Regel-3-Anweisung). Frontend-Folge — der Ingest-Warter des Anhang-Chips: der Chip fragt alle 1,5s den neuen Endpoint GET /api/documents/:id/attachment-status (dreifacher IDOR-Check der Metadaten, die Projektion { name, status, progress }, ohne Chunks) ab, zeigt einen Drehkopf + %-Fortschritt während des Ingests, HÄLT das Sende-Gate (canSend + der Enter-Weg über attachmentPending) bis "completed", und lässt den Anhang mit einem Toast bei failed/cancelled fallen (404 → stilles Fallenlassen; vorübergehende Netzfehler fragen weiter — ein Server-Hupper darfnicht einen gesunden Ingest auslöschen). Das Gate ist clientseitige Höflichkeit: die Server-Meldung bleibt der ehrliche Rückwall für alte Tabs. 4 Endpoint-Epins in documentIdor.test.ts (die access-user-Projektion + die Admin-Arme D-04); 2 ChatInputArea-Epins (pending deaktiviert Senden + der Fortschritts-Chip; completed reaktiviert).
  • Settings → "Arbeitsbereich-Zugriff": die Zuteilungszeilen zeigen wieder den Namen des Benutzers (Q6-fast settings parity, 2026-10-05). Der Arbeitsbereich-Zugrifftsdialog hatte exakt diesen Defekt korrigiert (die Kette Benutzername → ID) aber das Settings-Panel behielt die VOR-Q6-Kette user ? displayName(user) : userId — und der D-15-Draht sendet nie ein eingebettetes user-Objekt (T-189-11), sodass jede aufgeklappte Panel-Zeile auf die blanke UUID fiel. Das Panel spiegelt jetzt die Dialog-Kette (eingebetteter Benutzer Legacy-Form → grant.username → ID) und seine Suite pinnt die echte Drahtform: das statische Mock ließ das nie-gesendete user-Feld weg, ein neuer Test verifiziert, dass der Benutzername gerendert wird und die UUID nie (+1 Test).
  • Der Einstiegspunkt des geteilten Workspaces: die Zeilen der Workspaces-Seite ÖFFNEN jetzt (2026-10-05 "open-row"). Ein Workspace-Ebene-Zuteilungsempfänger (Viewer/Editor OHNE Projekzugriff) konnte den freigegebenen Workspace in der Chat-UI nie erreichen: die Seitenleisten-Selektoren sind Projekte zuerst — die Projekt-Auswahl fährt über GET /api/projects (listet nur die ersteller-eigenen / ProjectAccess-Projekte, kein Workspace-Zuteilungsarm) und die Workspace-Auswahl ist disabled ohne ein gewähltes Projekt, gefiltert auf projectId === selectedProjectId — während die Workspaces-Seite den geteilten Workspace auflistete (der OR-Filter von GET /api/workspaces schließt den Zuteilungsarm accessGrants.some(userId) ein) aber ihre Zeilen nur Verwaltung waren, ohne Weg hinein. WorkspaceRow bekommt eine "Open"-Aktion (voran in der Aktionsspalte, für JEDE gelistete Zeile gerendert — die Listenzugehörigkeit impliziert mindestens Viewer nach dem OR-Filter des Servers; das Client-Gate ist nur UX, die Chat-Routen barriere serverseitig pro Anfrage) die den Workspace über ChatContext montiert (setWorkspaceId(id) + navigate("/") — persistiert lastWorkspaceId, sodass der Boot-Wiederaufbau beim Reload in den Kontext wieder eintritt ohne die Selektoren). Ein Spiegeleffekt in App hält die Seitenleisten-Auswahlisten kohärent, wenn der aktive Workspace außerhalb der Selektoren wechselt — NUR wenn das Elternprojekt in der Projektliste des Benutzers liegt; die Chat-Oberfläche wird allein vom currentWorkspaceId gesteuert. Die Seitenleisten-Selektoren bekommen einen statischen "Mit mir geteilt"-Eintrag (Sentinel __shared__ im PROJECT-Dropdown, nur gerendert wenn der Teilmengensatz nicht leer ist): mit einem gewählten Projekt bleibt die Workspace-Liste auf dieses Projekt beschränkt wie zuvor; der Sentinel gewählt (oder eine leere Auswahl beim Boot) lässt das Workspace-Dropdown die projekt-invisible Menge listen (alle OR-filter-erreichbaren Workspaces, deren Elternprojekt in GET /api/projects fehlt) — die Teileliste des nur-Workspace-Empfängers, zuvor unerreichbar, weil das Kontrollelement erst ein Projekt brauchte UND Radix Select eine konkrete Projekt-ID nicht leeren kann, sobald gewählt (one-way Sackgasse; der benannte Eintrag ist der immer-klickbare Rückweg). Der Platzhalter wird inhaltsgesteuert über den geteilten workspaceSelectPlaceholderKey-Helfer in sidebar/navModel.tsx (ein veralteter Leer-Sentinel aus einer widerrufenen Freigabe degradiert zur generischen Formulierung statt einem aktivierten-leeren geteilten Bereich; workspaceSelectDisabled gates die AppNavOverlay-Monts — das Inline-AppSidebarNav rendert nur auf Host-Einträgen). Die i18n-Schlüssel workspace.open + sidebar.selectSharedWorkspace + sidebar.sharedProjects in alle 8 Sprachen ergänzt — Parität grün. Neue Pins: Viewer-Zeilen zeigen Open und nie delete/edit, fehlendes onOpen rendert kein Kontrolelement, der seitige Ride-along-Pin verifiziert den lastWorkspaceId-localStorage-Schreibvorgang über den echten ChatProvider (WorkspacesPage), der geteilte Arm rendert von Host-Einträgen ohne Projekt und verbirgt sich wenn leer (AppSidebarNav), Sentinel-Auswahl zeigt den geteilten Platzhalter während ein veralteter Sentinel degradiert, und die Overlay-Gating-Matrix pinnt aktiviert/deaktiviert je Arm + Platzhalter auf Desktop und Mobil (AppNavOverlay).
  • Der "Zugriff"-Dialog der Workspaces (die Schaltfläche oben in der Seitenleiste) überlüftet seinen eigenen Rand nicht mehr (2026-10-05). Jeder Vollbreite-Block entglitt über den abgerundeten Dialogrand: das ui-kit DialogContent ist ein grid OHNE explizite grid-template-columns, also sassen die Kinder auf EINER impliziten auto-Spur, die zum min-content-Beitrag des größten Items wächst — eine Zuteilungszeile mit einem nicht unterteilbaren 36-stelligen UUID-Benutzernamen trug 503–549px bei gegen das 488px-Inhaltsbox (sm:max-w-[520px]), verbreiterte die Spur und DEHNEGte jedes Vollbreite-Element (Titel, die Selektorzeile, die Kontrollist, die Zuteilungs-<li>s) über den Rand. Der zwei-Schicht-Fix: die ui-kit-Basis pinnt jetzt eine explizite Null-Minimum-Spalte (grid-cols-[minmax(0,1fr)] — die ganze Dialog-Familie schrumpft die Elemente auf die gedeckelte Inhaltsbox statt die Spur hinauszuschieben, app-weit) und die Dialog-Website fügt break-words zum interpolierten Titel + flex-wrap zu den Zuteilungs-/Bulk-Zeilen und jeder Zuteilungs-<li> hinzu (die nowrap-Buttons brechen unter ~360px Viewports auf ihre eigene Zeile). Playwright-verifiziert: null Überlauf 380→1280px (die Zuteilungsnamen kürzen mit Ellipse wie entworfen, nichts kreuzt den Rand); das neue WorkspaceAccessDialog.test.tsx pinnt beide Schichten — 174 Suiten / 1487 Frontend-Tests grün (+1 Suite, +4 Tests).
  • Die "Zugriff"-Zuteilungszeilen zeigen den Namen des Benutzers WIEDER (Q6-Fast-Follow-up, 2026-10-05). Die D-15-Serverantwort trägt {userId, username} aber KEIN eingebettetes Benutzerobjekt (mit T-189-11 gestrichen, um nicht E-Mail-/Rollen-Metadaten zu leaken), während der Dialog den Namen NUR aus diesem toten optionalen user-Feld rendere — jede zugeordnete-Benutzer-Zeile fiel auf die blanke UUID-ID zurück. Zuteilungszeilen lösen jetzt eingebetteter Benutzer (Legacy-Form) → username → ID auf; WorkspaceAccessGrant bekommt das username-Feld, das der Draht wirklich sendet. Neuer Pin: Benutzername gerendert / ID nie geflüchtet / Legacy-Zeilen mit null-Username behalten den ID-Fallback (+1 Test).
  • Settings-Deep-Link restauriert (2026-10-05). Ein voller Seitenlad von /settings (Lesezeichen, Seitenleisteneintrag, veraltete Navigationselemente) öffnete seit der R-5-"Settings als Dialog"-Revision den Einstellungsdialog nicht mehr: beim ersten Commit waren die Auth-Abfragen noch in der Luft (user = null) und der Close-on-Logout-Effekt — nach dem Öffner im selben Komponenten deklariert — schloss ihn im selben Batch wieder (open-then-cancel), und der nach dem Laden normalisierte Pfadname war bereits normalisiert, sodass nichts wiederöffnete. Weiche Navigation (Benutzermenü) war unbeeinträchtigt. Der Öffner schlägt jetzt nur auf einer GELADENEN Session zu und die Normalisierung bewahrt die Abfragezeichenkette, sodass ?tab=-Deep-Links das Dialog-Mount überleben; ?tab=-Lesezeichen TRETTEN auch jetzt in die Zielsektion (dasselbe Arm eine Schienenklick gibt: die Gruppenseite öffnet und der Eintrag markiert data-active). e2e/settings-left-menu-nav.spec.ts ist wieder grün.
  • Auch als Nicht-Verifiziert: POST/DELETE /api/system/settings/branding/icon sind die Routen des ENTERPRISE-Plugins (simmetric-enterprise/brandingRoutes, Phase 145, mountProtected + adminLicense("white_label")) — die Community-Build antwortet 404 und die UI degradiert mit dem dokumentierten Upgrade-Toast by Design (die D-12-Grep-Wache whiteLabelExtractionGuard); die EE-Batterie 37 Suiten / 737 Tests grün, die Community-Wache grün.
  • Das Settings-Panel loggt nicht-admin-Benutzer, die es öffnen, nicht mehr aus. Der Settings-Router war End-to-End mit requireAdmin (die admin:settings-Berechtigung) gegattert während das Per-Rollen-Sichtbarkeits-Gitter (SETTINGS_TAB_PERMISSIONS / RoleSectionVisibility) weiterhin Sektionen — Profil, Aussehen, LLM-Anbieter, Erweitert — an gewöhnliche Benutzer rendere. Das Öffnen des Panels (oder einer Sektion) ließ die Hülle von SettingsPage GET /api/system/settings anfragen, 403 empfangen, und die Seite vermischte 403 mit 401: sie zeigte "Session expired. Please log in again.", löschte dann das Token und leitete 3 Sekunden später zu /login weiter — eine völlig gültige Session für einen bloßen Berechtigungssprung zerstörend. Zwei-teiliger Fix (2026-10-04 "settings-panel logout"): - Server: GET /api/system/settings ist jetzt Berechtigungsbewusst. Admins (Träger von admin:settings) behalten den vollen Konsolen-Payload byte-identisch wie zuvor; jeder andere autorisierte Benutzer erhält nur die NON_ADMIN_SETTINGS_READ_KEYS-Teilmenge (geteilt) — die nicht-sensiblen Schlüssel, die von den nicht-admin-erreichbaren Oberflächen konsumiert werden (BRANDING_* für den Aussehen-Tab / den App-Kopf, ALLOW_NON_ADMIN_UPLOAD für die Upload-Seite) — jeder Eintrag readOnly: true (Anti-Mismatch-Regel: rende nie ein Formular, das der Server mit 403 abweisen würde). Geheimnisse (SMTP_PASS, LLM_API_KEY, JWT_SECRET, DATABASE_URL, VECTOR_DB_API_KEY, ...) können nie zu einer nicht-admin-Antwort gelangen. Die org-gerechte Sicht (?organizationId=) und alle Schreibrouten (PUT /, POST /mail-test) bleiben admin-einzig (fail-closed). - Frontend: nur ein 401 (Authentifizierungsausfall) zerstört die Session — im 3-Sekunden-Weiterleitungsarm von SettingsPage und im useMe-Fehlerarm von App.tsx. Ein 403 rendert jetzt ein unterscheidbares "Sie haben keine Berechtigung, diese Einstellungen zu sehen."-Banner (settings.errorNoPermission, alle 8 Sprachen) und die Session überlebt.
  • Collector-Ingestion: Dateien mit textartiger Erweiterung (.txt .md .csv) aber PDF-Magic-Bytes hochgeladen, werden nicht mehr als rohes UTF-8 indiziert (der AI-ACT.txt-Zwischenfall 2026-10-01). Eine in .txt umbenannte PDF, als Text gelesen, fütterte den Vektorindex nur mit den unkomprimierten PDF-Innereien (Info-Dictionary, XMP-Metadaten, Objekt-Dictionaries, Structure-Elemente) — 3321 Chunks, null Normativtext — während der Korpus in den FlateDecode-komprimierten Streams zu U+FFFD-Müll degradierte. parseFile schnüffelt jetzt den %PDF--Header (die ersten 1024 Bytes, das ISO-32000-Abfallfenster) bei textdeklarierten Dateien und route zum PDF-Parser; die Chunk-Metadaten bekommen einen routedAsPdf-Stempel und ein Fehl-Positiv-Schnüffeln (ein Textfile, das nur früh "%PDF-" erwähnt) fällt auf den Text-Parser zurück statt den Ingest scheitern zu lassen.
v0.28.1
2026-10-02

Added

  • Connectoren: bestehende Connectoren können jetzt aus der Admin-UI BEARBEITET werden (Q1-quick) — der bestehende PUT /api/connectors/:id-Arm kommt endlich an die Oberfläche: das Overflow-Menü der Karte bekommt Bearbeiten (erster Eintrag), der einen vorausgefüllten Dialog über der veränderlichen Oberfläche öffnet (Name, Wissensarchiv-Bindung, Willkommens-/Fallback-Nachrichten, Fallback-Locale, Rate limit + Tages-Sitzungslimit — leer stellt null ein, 0 = die UNLIMITED-Sentinel des Routers). isEnabled bleibt auf der Karten-Switch, Telegram pollMode auf den Webhook-Schaltflächen, und Platform/botToken/Workspace bleiben per Schema unveränderlich. Der PUT-Arm bekommt zudem das Audit-Ereignis chat_connector.updated (auf Augenhöhe mit dem DELETE-Arm, D-21).
  • Phase 221 (INSTLIC) — die Instanz-Lizenz-Paste-Slot (Enterprise-Aufrüstung ohne .env): eine neue Admin-Oberfläche GET/PUT/DELETE /api/license/instance (requireAdmin + Tenant-Middleware; das per-Route-Stacking hält GET /info und /diagnose unangetastet) plus das Settings → Security → Lizenz-Panel. Der Paste verifiziert sondefirst einen nicht beanspruchten RS256-JWT vom ENTERPRISE-Tier (ein abgelehnter Schlüssel persistiert nie: geschlossene Aufzählung { error, reason } — invalid / expired / plugin_bound / community_tier), speichert NUR AES-256-GCM-Chiffrat in SystemConfig (Schlüssel INSTANCE_LICENSE — die SMTP_PASS-Schreibnaht-Disziplin), invalidiert den Redis-Konfig-Cache D-07, und trifft die LAUFENDE Instanz über den GLEICHEN Resolver, den der Boot-Schritt fährt (setCachedLicense — die loader-injizierten Limit-Durchgriffe überleben; boolesche Flaggen gehen sofort live; das Löschen des Paste kehrt zum nächsten Boot-Zustand mit Boot-Parität). Präzedenz, Boot und Laufzeit identisch: Slot → geprüfte Plugin-Zeile → die Umgebungsvariable LICENSE_KEY → Community. Die Massen-PUT /api/system/settings verweigert den Schlüssel (die Dedizierte-Schreibroute-Doktrin für die Chat-Aufbewahrung) und die Einstellungsliste legt das Chiffrat nie offen.
  • Phase 219 (SETP-01/02) — Anzeigen + Ändern-Flag pro Rolle für jede Settings-Sektion: additives RoleSectionVisibility.canModify (zieht vom SETTINGS_SECTION_MODIFY_PERMISSIONS-Fallback ab — gewährt nie, admin:settings endgültig), settingsModify in GET /api/roles/me/menu-sections, Server-Gate auf Abschnittsmutationen, die 403 { error, section } zurückgeben (/api/providers-Mutationen → llm; POST /api/agency/users → team) und eine Ändern-Spalte im Rollen-Sichtbarkeitseditor (PUT /api/roles/:id/visibility akzeptiert canModify pro Eintrag — unverändert lassen wenn fehlend).

Fixed

  • Die Sidebar-Löschbestätigungs-Dialoge überlüften nicht mehr (Q5-quick): die Chat-/Ordner-Löschbestätigungen waren w-80-Boxen, deren ui-kit-Button whitespace-nowrap shrink-0 BY DESIGN ist — die langen lokalisierten Ordner-aktionsetiketten ("Mantieni N chat…" / "Elimina N chat permanentemente") entglitten horizontales aus der Box, und lange interpolierte Ordnernamen überlüften den Titel. Die Boxen sind jetzt w-[22rem] max-w-[calc(100vw_-_2rem)] (breiter + viewport-gedeckelt), die Button-Zeilen flex-wrap, die beiden langen Aktions-etiketten überschreiben das nowrap (whitespace-normal text-left), und die interpolierten Titel/Texte tragen break-words. Die drei fehlenden i18n-Schlüssel des Chat-Löschdialogs (sidebar.deleteChat / .deleteChatConfirm / .deleteChatButton) in alle 8 Sprachen ausgegeben — die italienische UI zeigt dort keine Englisch-Inline-Fallbacks mehr. Q5-Folge: die erste Deckelung kompilierte nie — calc(100vw-2rem) in einem beliebigen Wert ist UNGÜLTIGES CSS (Tailwind verlangt das Unterstrich-Idiom calc(100vw_-_2rem) für Leerzeichen um Operatoren) und die Regel wurde nie stillschweigend emittiert, so dass die 352px-Box auf schmalen Viewports ungedeckelt blieb. Die korrigierte Utility geht in das gebaute CSS (max-width:calc(100vw - 2rem) im dist-Bundle verifiziert).
  • Modellauflösung: Chat-Runden landen nicht mehr auf dem hardcodierten qwen2.5:3b-Standard, wenn es keinen ausdrückliche Pin gibt (Q4-quick — die Wurzelursache des Vier-Chats-Vorfalls: frische Chats bedienten ein kleines lokales Modell statt des Workspace-Standardwerts, 34-98s pro Runde). Zwei Korrekturen: (1) die Schema-Standardwerte von Chat.model und WorkspaceAgentConfig.model von "qwen2.5:3b" auf den ""-Sentinel geändert (additive Migration — ALTER COLUMN … SET DEFAULT, bestehende Zeilen unangetastet): die falsy Kette (model || chat.model || undefined, orchestrator:388/1059) fällt jetzt durch zur Workspace-Agent-Config-Kette genau wie der "default"-Schwester-Sentinel; (2) die per-Workspace-localStorage-Modell-Präferenz ist jetzt NUR-EXPLIZIT — die Palette-Auswahlen schreiben explicit: true, das Retseeding der automatischen Neupersistenz ist verschwunden (ein kettenaufgelöster Standard ist keine Benutzerwahl und darf nicht klebrig werden) und Werte-legacy-Marker werden einmal aufgeräumt und nie wieder als Kandidaten benutzt. Ein veraltete Präferenz (z. B. qwen2.5:3b, noch gültig-allerdings-langsam im Register) darf den Workspace-Standard nicht mehr auf jedem neuen Chat überlüften.
  • Chat: "Eine Nachricht öffnet N Chats" — der Client übernahm die Server-Chat-ID nur beim SSE done-Ereignis (bis zu ~90s später bei toolreichen Runden), sodass ein Benutzer-Neuversuche in diesem Fenster chatId-los wieder sendete und der Server einen NEUEN Chat pro Neuversuche anlegte während die in-flight Runde im Hintergrund als Waisen-Zeile beendete (Q3-quick). Die Stream-Route emittiert jetzt ein additives chat-Ereignis, das die Chat-ID beim RUNDENSTART trägt (vor jedem Token); der Client übernimmt sie sofort (geschützt + idempotent — ein Senden, das bereits eine Chat-ID trägt, übernimmt nie wieder) und aktualisiert sofort die Sidebar. Neuversuche landen jetzt im GLEICHEN Chat. Byte-identisch für vork-Clienten (unbekannte SSE-Ereignisse werden ignoriert).
  • Erinnerungsabruf: eine (Benutzer, Workspace)-Paar mit NUL gespeicherten Erinnerungen überspringt jetzt den gesamten pr-LLM-Abruf-Hook (Q2-quick) — eine indizierte PostgreSQL-Zählung (@@index([userId, workspaceId])) gate den Hook vor jeder Arbeit: keine Abfrage-Einbettung, keine Collector-Rundreise, keine weiteren per-Runde [vector-store] Tabelle "ws_user_memory_…" nicht gefunden-Warnung von einer LanceDB-Tabelle, die legitimerweise nicht existiert, bis zur ERSTEN Erinnerungsschreibung (vectorStores ensureTable erstellt beim ersten Ingest; der Sucharm fällt offen auf [] —). Ein DB-Zwischenfall im Zählinnens fällt offen zur Vorgate-Erinnerung (der best-effort-Vertrag des Hooks — nie den Chat blockieren).
  • shared: die FEATURE_FLAGS-Regressionswache wurde durch die Connector-EE-Verschiebung auf der Strecke gelassen (quick 260930-l4q) — der Pin von 12 → 13 für connectors_enabled erhöht.

Fixed

  • Backup/Wiederherstellung: ein Probelauf oder eine Wiederherstellung, die mit "Unable to decrypt (no key in chain matched)" fehlschlägt (Ziel-Anmeldedaten unter einer früheren ENCRYPTION_KEY geschrieben), liefert jetzt eine geführte Fehlermeldung, die die Ursache benennt, die Wiederherstellungshebel (LEGACY_PREVIOUS_ENCRYPTION_KEYS / eine frühere ENCRYPTION_KEY, dann Neustart), die Anzahl der getesteten Schlüssel und das Handbuch docs/ENCRYPTION_KEY_ROTATION.md — das eine Docker-Abschnitt "Backup restore dry-run fails" erhielt. Nur die Schlüsselleitungslänge wird offengelegt, nie Schlüsselmaterial.
  • Phase 220 (SPLIT-01/02) — Repository-Split-Kohärenz: docs/REPO_BOUNDARIES.md (die Modul→Repository-Audit-Matrix für 4 Repos mit der 7-Kriterien-Einstufungslehre und den gewollten Ausnahmen) + die Repository-Grenze-Schranke: scripts/repo-boundary-check.cjs (eine Allowlist-erzwungene Grep-Schranke — der Kern darf die privaten Plugin-Pakete außerhalb der zwei Lader-Nähte und der dokumentierten Vertragsstellen nicht referenzieren) + der Selbsttest scripts/repo-boundary-check.test.cjs + der CI-Job repo-boundary-check in build.needs verdrahtet
v0.25.0
2026-09-09

Added

  • UI: vollständige Schalenrevision (R-2 → R-5). Der Chat-Leerzustand zeigt ein elegentes monochromes Wörtermal (die Glitch-cyan/magenta-Animation ist jetzt aufs Hacker-Thema beschränkt); die Seitenleiste wird eine minimale Claude-artige Schiene (neuer Chat + Chatliste + Endmenü) mit der vollen Navigation in einem tastaturfreundlichen cmdk-Overlay (Cmd/Ctrl+/), das die exakte RBAC-Filterung hält; das Benutzermenü zog aus der Oberleiste in ein eigenes Dialog (Konto, Sprache, Theme, Einstellungen, Abmelden) mit einem linksseitigen Drawer auf dem Handy; Settings öffnet als großes Master-Detail-Dialog (linkes Menü + Flächen-Detail mit Schienen-Umschalter und Brotkrumen) statt einer gerouteten Seite — die Hauptfläche ist jetzt allein der Chat auf jeder Seite. Die Themen (hell "Pearl", dunkel, hacker, system) erhielten einen Kohärenzdurchgang mit WCAG-AA-Kontrastkorrekturen für gedämpften Text und Chat-Akzenten, prefers-reduced-motion-Sicherungen für alle Overlays und weißlabel-bewusstes Branding auf dem Wörtermal.
  • Docker: der optionale searXNG-Metasuch-Dienst in den Compose-Stack ergänzt (reine Infrastruktur — nichts hängt bisher daran; gepinnte Release-Marke, wget-basierter Healthcheck, Settings vom Image-Eingangspunkt auto-provisioniert).
  • Docker: das private @simmetric-chat/saas-Plugin mountet sich jetzt wie das Enterprise-Plugin in den Compose-Server (eine Bind-Mount des ganzen Geschwister-Repos; der saasLoader no-ops auf MODULE_NOT_FOUND im Community-Modus).

Fixed

  • Seed: ein Boot-Crash während der Benutzer-Seedung konnte die Rollen auslöschen und den Admin ausperren; Seedungen heilen jetzt selbst — das idempotente Zuteilen legt die Admin-Rolle beim überlebenden admin-Benutzer bei jedem Boot wieder an (im Protokoll der verzögerten STATE.md-Elemente).
  • Tenancy: POST /api/auth/register committet jetzt die Benutzerschaffung, die Standardrollen-Zuteilung und die Standard-Org-Mitgliedschaft in einer einzigen Transaktion, und jeder Seed-/Boot-Pfad wiederholt den idempotenten Mitgliedschafts-Helfer in seinem "Benutzer existiert"-Zweig — ein Crash mitten im Boot/Anfrage kann keinen benutzer ohne Mitgliedschaft hinterlassen (die Phase-182-Prüfung WR-01); das Grabstein-Wiederaufwecken trägt jetzt den roleInOrg des Aufrufers, sodass Löschen Rechte zurückzustutzt (WR-02).
  • Enterprise-Builds: die Settings → Erweitert → Backup-Sektion (und die /sso- und /logs-Routen) bleiben nach einem App-Login oder einem SSO-Anmelden nicht mehr hinter der "requires-Enterprise"-Karte gesperrt. Die Enterprise-Module-Manifest-Abfrage (GET /api/enterprise/modules) ist jetzt auth-gated und session-reaktiv: vorher wurde sie einmal, unauthentifiziert, beim App-Boot geladen und für die ganze Session gecacht (retry: false + staleTime: Infinity), sodass auf dem Anmeldebildschirm sie 401 wurde und die Enterprise-Funktionen bis zu einem vollen Seite-Neuladen MIT dem gesicherten Token verborgen blieben. Der Hook abonniert jetzt useMe() und wiederholt das Manifest, wenn eine Session erscheine (Passwort-Login, der SSO-?token=-Handoff), und sondiert den Endpoint überhaupt nie ohne Session.
  • Docker: der Produktions-Geheimprovisions-Helfer (docker/provision-encryption-key.sh) schlingt den Server nicht mehr in Crash-Schleifen, wenn das Root-.env mit einem unausgefüllten .env.example-Vorlagen-Platzhalter (<sostituire-con-valore-generato>) als API_KEY_HMAC_SECRET/ENCRYPTION_KEY-Wert ausgerollt wird — ein Platzhalter wird jetzt als "nicht gesetzt" erkannt (lautsprechende Warnung) und die Provision fällt zu den Wiederherstellen/Erzeugen-Pfaden durch. Jede andere nichteere ungültige Wert scheitert weiter laut am Boot-Gate.
  • Sicherheit: der E2E-Helfer-Router (/api/__tests__, ein nicht authentifizierter Start/Stopp-Echo-MCP-Server) wird nicht mehr montiert, wenn der Server mit NODE_ENV=production läuft — Produktions-Boots antworten jetzt dort 404, der dokumentierte dev/test-only-Vertrag des Routers gilt (kein unauthentifizierter Prozess-Abspalt-Endpoint in der Produktion).
  • Sicherheit: die Archiv-Import-Collector-Geheimrückrufe-Kontrolle (PUT /api/archives/import/:jobId/callback) vergleicht jetzt den X-Collector-Secret-Header in konstanter Zeit (crypto.timingSafeEqual mit einer Längesicherung), die timing-sichere Disziplin spiegelnd, die schon von den Dokumentenstatus-Rückrufen und der Geheimbarriere des Collectors benutzt wird — die letzte Klartext-Geheimvergleiche am Server schließend.
  • Authentifizierung: POST /api/auth/login validiert now seinen Körper auf der Route-Ebene mit dem geteilten loginSchema (safeParse) — ein ungültiger oder leerer Körper gibt 400 { error, details } wie jede andere Auth-Route zurück, statt eines 401 mit einer rohen Zod-Meldung. Valide-aber-falsche Anmeldedaten geben weiterhin 401 "Invalid credentials".
  • Authentifizierung: login läuft jetzt ein Pseudobrutus-bcrypt.compare gegen einen fixen Wegwerf-Hash, wenn der Benutzer nicht gefunden wird, sodass die Antwortzeit bestehende von nicht-bestehenden Benutzernamen nicht mehr unterscheidet (Aufzählungsverfestigung). Beide Pfade geben weiterhin dasselbe 401 "Invalid credentials".
  • Authentifizierung: POST /api/auth/admin-reset-password validiert now seinen Körper mit dem neuen geteilten adminResetPasswordSchema (userId-UUID + newPassword von 8-128 Zeichen, safeParse → 400 { error, details }) — die letzte Auth-Route verlässt die ad-hoc destrukturierte Körper-Validierung.
  • Authentifizierung: der geschlossene-Registrierung-Zweig von POST /api/auth/register dupiziert die Auth-Logik nicht mehr inline — er benutzt die statisch importierten verifyToken / isTokenRevoked / isAdmin / getCachedUserWithRoles (kein dynamisches await import, kein toter Header-Nach-Check, der gechachte Benutzer-Nachschlag) und jeden Antwortformular und die Widerruf-vor-Nachschlage-Reihenfolge bewahrend.
  • Sicherheit: der Widget-Cache-Durchspül-Endpoint (POST /api/config/:widgetId/cache-bust) vergleicht jetzt den X-Api-Key-Header in konstanter Zeit (crypto.timingSafeEqual mit einer Längenwache) und scheitert geschlossen, wenn der erwartete Schlüssel nicht gesetzt ist, anstatt des früheren Klartextvergleichs mit der fail-open expected &&-Form — die Schlüsselkontrolle des Widgets entspricht jetzt der timing-sicheren Disziplin der Server- und Collector-Geheimbarrieren.
  • Release-Pipeline: alle fünf Docker-Images werden jetzt nur amd64 gebaut. Die QEMU-emulierten linux/arm64-Builds hingen bis zu ~5h (das Widget zuerst, dann die All-in-One-Quellkompilation pgvector) und erschöpften den Läufer-Festplatte auf privater Bezahleminuten; setup-qemu entfernt, jeder Matrixeintrig veröffentlicht linux/amd64, und die Build-Matrix un-serialisiert zu max-parallel: 2. arm64-Benutzer können lokal selbstbauen (die Dockerfiles sind kreuzbuild-sicher) oder einen selbst gehosteten arm64-Läufer anhängen — siehe die arm64-Notiz in docs/DEPLOYMENT.md.

Changed

  • Werkzeug: eine Tätigkeitscode-Schranke läuft jetzt in CI (knip) und scheitert an unbenützten Dateien, Abhängigkeiten oder Exporten außerhalb der dokumentierten Allowlists; eine repo-breite Fegung entfernte ~3400 Zeilen Inaktiven-Code, die Widget-API-Schlüsselerzeugung lebt jetzt hinter dem Standard-Admin-Fluss, und beide i18n-Skripte berichten definierte-aber-nie-benutzte Übersetzungsschlüssel als nicht-blockierende Aufräumhinweis.
  • Widget: der Antwortmodell-Pin per Widget — die Admin-Konfiguration jedes Widgets kann jetzt den LLM-Anbieter + das Modell wählen, der/das die Chat-Antworten dieses Widgets bedient (responseProviderId/responseModel, die additive Migration add_widget_response_model). Der Pin löst serverseitig aus der Datenbankzeile des Widgets bei jeder Chat-Anfrage auf (nie klient-seitig geliefert — der Widget-Proxy und die Besucher-UI bleiben unangetastet), hat Vorfahrt vor dem Workspace-Standard, und nicht gesetzte Widgets behalten die bestehende Workspace-/globale Auflösungskette. Widget-Chats erfassen jetzt das Modell, das sie wirklich bedient.
v0.22.0
2026-08-30

Added

  • RAG: die Metadatenfilterung für rag_search — der Agent kann den Abruf jetzt auf Dokumentarten (documentTypes: pdf/md/txt/csv/docx/xlsx) und Ingest-Daten (dateFrom/dateTo, ISO; eine datums-einseitige Obergrenze schließt den ganzen Tag ein) beschränken, der "2023er-Dokumente für 2025er-Fragen"-Fehlform behebend. Die Filter laufen durch die hybride Suche in beide Abrufbeine: das Vektorbein erreicht den Collector als wahre Prefilter auf gestempelten Ingest-Metadaten (pgvector-JSONB-Prädikate + Qdrant-Nutzlast-must-Klauseln; LanceDB und Chroma ignorieren die neuen Schlüssel mit einer geloggten Warnung), und das FTS-Bein bekommt parametrisierte SQL-Prädikate auf der Dokumenttabelle. Ein serverseitiges Nach-Abruf-Rückstopp gegen die maßgebliche Dokumenttabelle garantiert gefilterte Korrektheit auf jedem Anbieter. Ab jetzt ingestierte Dokumente sind immer filterbar; Legacy-Vektoren (vor dem Stempeln) sind ausgeschlossen, während ein Filter aktiv ist und gewinnen Filterbarkeit via Admin-Re-Embed wieder — kein Schema-Wechsel, kein Re-Embedding bestehender Dokumente, und Verhalten ohne Filter unverändert.
  • DLP: vier neue eingebaute Muster für übliche europäische (v. a. italienische) Kennwörter, idempotent durch eine datums-einse Migration gesät und in den DB-abgesenkten Fallback gespiegelt — it_vat_iva (Partita IVA; etiketankert, sodass eine blanke 11-stellige Zahl nie matcht), it_codice_fiscale (16-zeichen codice fiscale; nur Großbuchstaben, Prosa-Wörter wie "foschi" können nie matchen), iban (Länderpräfix + Länge ≥ 15; die kompakte und raumgruppierte Formen, linearzeit/ReDoS-sicher), und eu_phone (Telefon IT/EU; als DEAKTIVIERT gesät für Admin-Überprüfung, denn jede regelmäßige Telefon-Regex markiert Order-/Referenzziffernläufe). Die Eingebauten sind je jetzt 10 gesamt; bestehende Implementierungen bekommen die neuen Zeilen bei migrate deploy mit der ON CONFLICT DO NOTHING-Nachspiel-Sicherheit.
  • Chat: Admins können jetzt die DLP-gesäuberten Übereinstimmungstexte über die ganze Konversation auf einmal offenlegen oder wieder verbergen, via einen neuen "Show DLP texts" / "Hide DLP texts"-Eintrag im (+)-Mehr-Aktionen-Menü des Chat-Eingangs (das Eye/EyeOff-Icon, nur Admins gezeigt, da nur sie einen DLP-Hinweis aufklappen können). Die Präferenz ist session-gerecht, Standard hidden, und trifft auf jeden Hinweis, auch neu angekommene Nachrichten; der Augenknopf pro-Hinweis funktioniert weiterhin für manuelle per-Nachrichten-Übersteuerungen zwischen den globalen Umschalter.
  • DLP-Musterkonfiguration: die sechs DLP-Muster sind jetzt datenbankgesichert (die dlp_patterns-Tabelle, durch die Migration idempotent gesät, sodass bestehende Implementierungen die Eingebauten bekommen) statt hartcodiert. Admins verwalten sie in den Settings → Erweitert → DLP Muster: jedes Muster ein/aus per Zeile, Eingebaute umbenennen (ihr Regex ist eingefroren — Änderungsversuche geben 400), und bis zu 50 benutzerdefinierte Muster ergänzen (snake_case-Name, Regex-Quelle mit Kompilationsvalidierung beim Speichern, Flaggen, Ersatz) mit einer Live-Testvorschau, die gematchte Abschnitte und den gesäuberten Text anzeigt. Scans, die den Datenbanksatz laden können — die Filter-Plugin-Einleit/Outlet und die Endend-der-Antwort-Spülung — benutzen die konfigurierten Muster über einen 5-minütigen TTL-Cache mit per-Muster kompilierte Regex-Caching, zurückfallend auf den eingebaite Satz, wenn die Datenbank unerreichbar ist; die token-je-token-Streaming-Spülung behält die eingebauten Muster (die dokumentierte v1-Beschränkung). Das ganze Panel ist in allem 8 Sprachen lokalisiert.
  • DLP-Rollen-Bypass: eine neue Systemeinstellung DLP_BYPASS_ROLES (ein JSON-Array von Rollennamen, Standard []) lasst Admins gewählte Rollen von ALLEM DLP-Scanning und der Säuberung ausnehmen — die Filter-Plugin-Einleit/Outlet und der progressive-Spülkern der Streaming (einschließlich der RAG-Kontext-Untersuchung und der Denkbar-Säuberung, sowohl für den JWT-Chat als auch für den Widget-Service-Konto) folgen alle demselben Gate. Jeder bypassed-Lauf entfesselt ein fire-and-forget dlp.bypassed-Auditereignis, das protokolliert, WER vorbei ist (die geschnittenen Rollennamen) und die Ursprungs-Oberfläche — nie der gescannte Inhalt. Admin-UI: eine Rollen-Mehrfachauswahl mit einer Expositionswarnung unter Settings → Erweitert → DLP, und eine "Bypassed"-Aktionsbeschriftung im DLP-Trefferhistorie-Panel, in allen 8 Sprachen lokalisiert.
  • DLP-Quellenmarkierung und -filterung: jedes dlp.*-Ereignisprotokoll trägt source: "chat" | "widget" in seinen Metadaten — der geteilte SSE-Stream-Kern zieht die Marke aus dem X-Widget-Id-Header des Widgets, sodass Widget-Umterhaltungen wie Chat-Unterhaltungen gesäubert werden, aber jetzt im DLP-Trefferhistorie-Panel (Settings → Erweitert) unterscheidbar sind. Das Panel bekommt clientseitige Filter für die Quelle (Alle/Chat/Widget), Chat-ID und Benutzer.
  • Admin-Settings-UI für den Upload-Verwerkungs-Reaper: eine neue Sektion in Settings → Wartung stellt den aktivierten Umschalter (upload_draft_reaper_enabled) und den Cadenz-Cron-Ausdruck (upload_draft_reaper_cron) durch PUT /api/system/settings aus, mit einem clientseitigen Fünf-Feld-Cron-Form-Precheck (der Server behält sein Warn-und-Fallback-Verhalten für ungültige Werte), lokalisierte Erfolgs/Fehler-Toasts und Beschreibungen in allen 8 Sprachen. .env.example dokumentiert die Reaper-Schlüssel als Umgebungsvariable-Übersteuerungen (upload_draft_reaper_enabled, upload_draft_reaper_cron, upload_draft_retention_days).
  • Konfiguration: die Umgebungskonfiguration ist jetzt einmal in einem geteilten Zod-Schema definiert (@simmetric-chat/shared), die wirksame Vorrangsordnung dokumentiert und test-gepinnt als DB > ENV > Standard (Edits der Settings-UI gewinnen sofort; eine 8-Fall-Matrix) und Schlüssel, die ignoriert werden, weil eine Umgebungsvariable sie umschließt, mit einem envOverridden-Badge in der Settings-UI angezeigt.
  • Konfiguration: das Stammrepositorie-.env ist jetzt die einzige Laufzeitkonfiguration für den Server, den Collector und das Widget — ein neues null-Abhängigkeits-Ladewerkzeug (loadRootEnv()) schmückt es unter den per-Paket-.env-Übersteuerungen (Legacy), mit einer Ein-Pro-Boot-Deprecation-Warnung, die die betroffenen Schlüsselnamen auflistet (nie Werte); Docker und Tauri ausgerichtet (das compose env_file weist auf die Stammdatei, kein Bild bärt Geheimnisse).
  • Konfiguration: alle drei env.Beispiel-Paketdateien sind aus den Zod-Schemata wiederzeugt (83/15/6 Schlüssel, gruppiert, Standardwerte synchronisiert) mit Jest-Pin-Wachern, die scheitern, wenn eine künftige Schema-Schlüssel unerklärt bleibt; rohe process.env-Lesevorgänge außerhalb des Schemas (HF/openai-Eigenheiten, Verschlüsselung/HMAC-Schlüssel, LOG_LEVEL, Test-Gates) sind durch 44 Verhaltenswachschüche gepinnt.
  • UX: einen Workspace löschen fragt jetzt einen Standardbestätigungs-Dialog (war ein Inline-Mini-Bestätigung), Workspaces unterstützen mehrfachauswahl Bulk-Löschen mit einem Bestätigungsdialog und einem Ergebnistoast, der überprungene Zeilen zählt (keine-Berechtigungs-Zeilen werden überprungen, nie stillschweigender Misserfolg), die OCR-Vorschau Seite-an-Seite hält den extrahierten Text an denselben Seitenzuschnitt wie die Bildscheibe gebunden, und der Laufdetail der Synthese zeigt den Genehmigungs-Knopf deaktiviert mit dem ausdrücklichen Grund auf Läufen, die noch nicht genehmigt werden können.
  • UX: Workspace-Auswahls-Kontrollkästchen und Bulk-Löschen sind in allen 8 Sprachen lokalisiert.

Changed

  • Der Upload-Verk-Reaper ist über die Systemeinstellungen voll konfigurierbar: ein An-/Aus-Umschalter (upload_draft_reaper_enabled, Standard "true") und eine Cadenz-Übersteuerung (upload_draft_reaper_cron, Standard täglich 03:00 UTC), beide durch PUT /api/system/settings verwaltet. Ungültige Cron-Werte loggen eine Warnung und falzen auf die Standard-Cadenz zurück, ohne Boot auszulassen; Standardverhalten unverändert.

Fixed

  • Eine Docker-Produktions-Bootsturz-Schleife korrigiert, wobei Serverstart mit einem Unique-Beschränkungsfehler auf api_keys.prefix starb: der Widget-API-Schlüsselsämann leitet jetzt den Anzeigepräfix vom HMAC verdauungsmechanismus ab und toleriert Präfixzusammenstöße (Nachprüfung per Schlüssel-Hash, Warn statt Absturz) und die API-Schlüsselerzeugung wiederholte mit einem frischen Schlüssel bei einem Präfixzusammenstoß (höchstens 3 Versuch).
  • Chat-Zitierungen spiegeln jetzt die Quellen wider, auf die eine Antwort wirklich fundiert, statt alles, was die Suchwerkzeuge abholten: nach der Entduplizierung der Zitationen kappet ein Grundungsfilter die Zitationen pro Dokument auf die Spitze-2-Relevanzpunktzahl und losst Zitat-Chunks, deren Text keine bedeutsame lexikale Überlappung mit der Endantwort teilt (konservative Schranke — Wiki-/Archivseiten, Web-/Erinnerungsergebnisse, Zitationen ohne Chunks und sehr kurze Antworten bleiben immer). "Keine weiteren Infos"-Antworten zeigen kein "Fonti (5)" von einem Dokument bei ~1,5% Relevanz; die "Keine Stützungsbeleg"-Disclaimer wird ebenfalls auf dem gefilterten Satz gewertet (sowohl Streaming als auch Nicht-Streaming). Nur die Zitation-Pipeline — kein UI, i18n oder Schema-Wechsel.
  • Docker: der ollama-Dienst-Healthcheck ersetzte die curl-Sonde (das ollama/ollama-Image liefert kein curl/wget/node — der Container meldete für immer "unhealthy") durch eine ollama ls-Sonde; der qdrant-Healthcheck ersetzte die wget-Sonde (das Image liefert kein wget/busybox) durch einen Bash-/dev/tcp-Sokkel-Check — beide Dienste melden jetzt healthy. Der docker/entrypoint.sh des Einzelcontainer versorgt jetzt provision-encryption-key.sh (Parität mit dem gesplitteren Bild): produktionsunabhängige Boots auto-provisionieren ENCRYPTION_KEY + API_KEY_HMAC_SECRET statt am Gate zu scheitern. Alle fünf Dockerfile linten sauber unter hadolint (die dokumentierte nicht-gepinnte-deps-Politik + Platzhalter-ENV-Nachgiebigkeiten).
  • DLP-Audit-Chronologie-Ereignisse zeigen nicht mehr "keine Trefferdetails", wenn der Trefferinhalt in einem frühen Streamabschnitt kam: das Streaming-Ereignis dlp.output_match leitet jetzt seine Treffertypen UND die Treffer-Textliste aus den über die ganze Lauf gesammelten Übereinstimmungen ab (alle fortschreitenden Spülungen plus die endgültige Schwanz-Untersuchung) statt der alleinigen letzten Abschnittsuntersuchung — historische Zeilen konnten ein leeres matchTypes-Array tragen, während die Zeile trotzdem ein Treffertyp-Badge zeigte. Der Treffertext wird nach dem DLP-Filter-Plugin-Präzedenz gespeichert (dlp.input_match / nicht-Streaming dlp.output_match tragen ihn schon); das Audit-Panel ist admin-einzig und Enterprise-gebarrt. Die aufklappende Ansicht des Audit-Panels rendert jetzt die entdeckten Typliste mit einem "nur-Typ"-Hinweis für Ereignisse, die Treffertypen ohne Treffer aufzeichnen (in allen 8 Sprachen lokalisiert), und hält die "keine-Details"-Notiz nur für wirklich leere Metadaten.
  • Hochgeladene Dateien bleiben für Retry verfügbar: Collector-Terminalstatus-Rückrufe löschen die staged Verkürzungdateien aus storage/uploads/drafts/ nicht mehr (der Rückruf jedes Bruderdokuments konnte die Datei auslöschen, von der jede Neuversuche abhängt), und /retake//assign heilen sich selbst, indem das staged-Datei aus der persistenten OCR-Kopie (storage/ocr-sources/) wiederhergestellt wird, anstatt mit "auf dem Laufwerk nicht mehr vorhanden" zu scheitern.
  • Ein gescheiterten veraltet Verkürzungsentwurf löschen gibt keinen blockierten (409) mehr zurück — DELETE /api/uploads/:id behandelt nun Endzustandsbeine (vollendet oder gescheitert) als löschbarkeit und weist nur Entwürfe mit einem wirklich in-the-air (nicht-terminal aktiv) Bein aus, und die Frontend-Massenlöschung benutzt dasselbe In-the-air-Prädikat wie die Filterchips, sodass das, was die UI zum Löschen anbietet, genau das ist, was der Server akzeptiert.
  • RAG/KB-Das Wiederholung eines Uploads, dessen Verkürzungsentwurf fehlt, liefert jetzt einen klaren Neuladefehler (400) statt eines falschen Erfolgs-Toast, misslungene Collector-Beine löschen die staged-Verkürzungdatei aus storage/uploads/drafts/ nicht mehr (künftig Neuversuche bleiben möglich — der 24h-Reaper und die DELETE-Route bleiben die einzigen Verkürzungdateilöscher), und Retries-Toast spiegeln jetzt den pro-Bein-Verlauf: ein abgewiesenes Bein bei HTTP 200 zeigt einen lokalisierten Fehler-Toast und Massen-Retry zählt es als gescheitert.
  • Docker-Produktionsausrollungen auto-provisionieren einen persistenten ENCRYPTION_KEY (Server-Speichervolume, /app/storage/.encryption-key) anstatt auf der harten Standard auf Crash-Schleife zu fallen. Der Server-Eingangspunkt erzeugt den Schlüssel einmal vor jedem Prisma-Schritt, stellt ihn bei jedem Neustart/Neuaufbau wieder her, zieht einen vom Bediener gelieferten Wert vor und scheitert laut bei einem korrodierten persistierten Schlüssel. .env.example und docs/DEPLOYMENT.md aktualisiert, die Variable als Erforderlich-in-Produktion zu markieren. Siehe docs/ENCRYPTION_KEY_ROTATION.md für die Docker-Notizen.
  • Wissensbasis-OCR-Seiten scheitern nicht mehr, wenn der Ollama-Visionstream vom Modellmotor ohne die Endmarker done nach vollständiger Transkription geschlossen wird (beigebracht mit dem glm-ocr-Eigenbau-Modul): ocrPage rettet die gesammelte nicht-eere Ausgabe als Seiten ergebnis mit einer truncated-Flagge und einer Warnlog, statt eine echte Transkription hinter "Did not receive done or success response in stream." zu verfehlen. Ein Done-leser-Stream ohne Inhalt lässt die Seite immer noch scheitern.
  • OCR-Auftragszusammenfassungen sind wahrhaftig über die Seiten Gesundheit: Seiten, die mit einem [FAILED:-Marker enden, werden gezählt und als failedPages in das Ergebnis-JSON des Auftrags gespeichert, und die Vollendungslogs geben eine "[ocr] Auftrag mit gescheiterten Seiten vollendet" Warnung aus, sodass ein VOLLSTÄNDIERTER Auftrag mit gescheiterten Seiten nicht länger ununterscheidbar von einem sauberen Erfolg ist (null Token + Mindestqualität allein waren mehrdeutig).
  • Dokumentiert und korrigierter Ausfallmodus für Enterprise-Implementierungen: wenn @simmetric-chat/shared NEUE Quelldateien erhält, kann ein aus der Zeit gefallener Enterprise-file:-Abhängigkeitsnapshot den Server am Boot mit "Cannot find module './env.schema'" abstürzen lassen — pnpm nimmt den file:-Paket-Snapshot in node_modules/.pnpm auf und Dateien, die nach dem letzten pnpm install erzeugt werden, erreichen diesen Snapshot nie (Hardlinks aktualisieren nur bereits existierende Dateien), sodass der schemas/index.js des Snapshots ein Modul verlangt, das auf der Enterprise-Seite nicht existiert, und die fail-loud-Regel des Laders die Plugin-Registrierungsausfallung in eine Bootsturzschleife umwandelt. Der Fix kommt als Dokumentation an: das Wiederaufbauhandbuch des Enterprise-Repos umfasst jetzt den Reinstall-not-Rebuild-Unterschied (den Snapshot mit pnpm install auffrischen, dann pnpm build), und das Luftlücke-Installationshandbuch in docs/ENTERPRISE_PLUGIN.md bekommt die passende Störungsbehebung-Unterabschnitt (Symptom, Ursache, Fix, Verstandkontrolle), sodass auch Betreibern ohne das private Repository die Abhilfe finden.
v0.21.0
2026-08-28

Schuldsweep & Veröffentlichungshygiene (war: v1.5.0)

Added

  • docs/LICENSE_DECISION.md — die Lizenzmodellenanalyse für die doppelte Distribution (Community + Enterprise). Entscheidung: AGPL-3.0 (Community) + propetärer Handel (Enterprise).
  • LICENSE wechselte Apache-2.0 → GNU AGPL-3.0 (Community-Repo).
  • LICENSE_EE.md — die propetetische kommerzielle Lizenz für das Enterprise-Plugin (@simmetric-chat/enterprise).
  • NOTICE — die Doppel-Lizenz-Erklärung am Stamm des Repos.
  • CLA.md — der Beitragslizenzvertrag (v1.0) — das Urheberrecht + Patenteinräumung, doppel-lizenzbewusst. Für alle externen Beiträge erforderlich.
  • AGPL-3.0-Urheberrecht-Header zu allen 981 Quelldateien über 5 Pakete (.ts/.tsx). Angewandt via scripts/add-headers.cjs (idempotent).
  • docs/ENTERPRISE_LICENSE_TERMS.md — die anbieterorientierten Handels-/SLA-Bedingungen für Enterprise-Kunden.
  • .github/workflows/release.yml — GitHub-Release + GHCR-Bildveröffentlichung beim Tag-Push.
  • CHANGELOG.md (dieses Datei) — Keep-a-Changelog-Format, aus der Meilensteingeschichte gesät.
  • .planning/milestones/v1.5-debt-sweep-ROADMAP.md — der Umfang des Schuldbewertungs-Meilenstein (E2E-Übertrag, Fehlersuchungs-Sessions, Schnelllauf-Triage, Versionsstempel-Synchronisationsdisziplin).

Changed

  • Die package.json-Version heraufgepumpt 0.17.0 → 1.4.0 (synchronisiert mit dem letzten git-Tag v1.4). Per-Paket-Versionen bleiben bei 0.22.0/0.23.0 (private Pakete, in keinem Registry veröffentlicht).
  • Das license-Feld in package.json + allen packages/*/package.json auf AGPL-3.0-or-later gesetzt.
  • Das README.md-Lizenz-Badge aktualisiert von Apache-2.0 → AGPL-3.0; die Lizenz-Sektion aktualisiert, das doppelte Lizenzmodell zu beschreiben; die Mitwirkung-Sektion mit der CLA-Anforderung ergänzt.
  • CONTRIBUTING.md mit den CLA-Unterzeichnungs-Anweisungen aktualisiert.
v0.20.1
2026-08-27

Horizontale Skalierung (die Redis-Schicht-Vollendung) (war: v1.4)

Ziel: die 4 verzögerten SCALE-Elemente schließen (CSW-19 + SCALE-02/03/04) und horizontale Skalierung mit einem echten Multi-Instanz-Integrationsversuch nachweisen — die sichere Multi-Instanz-Server-Ausrollung hinter einem Lastausgleicher aktivieren.

Ausgeliefert: 7 Phasen (161–167), 13 Pläne, 29 Aufgaben. Alle 22/22 Anforderungen erfüllt. Git-Tag: v1.4.

Added

  • SCALE-01 (CSW-19): die 3 übrigen Scheduler Sperre-Wrapped → dann alle 8 zu pg-boss-cron-Jobs migriert; setInterval + isRunning + withDistributedLock aus allen 8 migrierten Schedulern entfernt.
  • SCALE-02: der scryptSync-ENCRYPTION_KEY-Fallback in der Produktion entfernt — Boot scheitert laut (logger.error + process.exit(1)) + Tiefenverteidigung-Dienstwurf; dev/test bewahrt scrypt; das Rotationshandbuch aktualisiert.
  • SCALE-03: die bcrypt-Schleifen-API-Schlüsselverifizierung → keyed-HMAC-O(1)-findUnique({key_hash})-Verdauungslookup; die Bruchmigration DROP COLUMN hashedKey + ADD COLUMN key_hash; die Umgebungsvariable API_KEY_HMAC_SECRET; das Betreiber-Migrationshandbuch docs/API_KEY_MIGRATION.md.
  • SCALE-04: 8 setInterval-Scheduler zu pg-boss migriert (eine PostgreSQL-getragene Jobqueue, Luftlücke-freundlich); 2 Latenz-sensitive 10s-Abfrager (OCR + Synthese) bleiben setInterval; pg-boss-Singleton (jobQueue.ts) mit anstandsloser Rückbildung.
  • Die SSE-Verteilervisite: die Instanzübergreifende Aussetzung geschlossen — 4 Mock-basierte Instanzübergreifende Tests, die das Redis-pub/sub-SSE-Relaiss beweisen (A veröffentlicht → B leitet weiter, der Ursprungsskip, der Abbau, die Rückbildung).
  • Betreiber-Dokumentation: docs/SCALING.md (377 Zeilen) — die Multi-Instanz-Ausrollungstopologie, die REDIS_URL-Anforderung, die pg-boss-PostgreSQL-Abhängigkeit, die SCALE-02/03-Kenner, die 10s-Abfrager, die anstandslose Rückbildung.

Läuferänderungen — Betreibernaht erforderlich

  • `ENCRYPTION_KEY` muss im Produktions-.env gesetzt werden (base64, 32 Bytes) oder der Server verweigert den Boot. Siehe docs/ENCRYPTION_KEY_ROTATION.md.
  • API-Schlüssel wechselten von bcrypt (apiKeyHash) → HMAC-SHA256 (key_hash). Betreiber müssen Widget-/Collector-API-Schlüssel neu ausstellen. Siehe docs/API_KEY_MIGRATION.md.

Infrastruktur

  • Neue Abhängigkeit: pg-boss v12.28.0 (Server). Neue Umgebungsvariable: API_KEY_HMAC_SECRET. Boot-Reihenfolge geändert — die pg-boss-Init ist jetzt der 7. asynchrone Init-Schritt mit einem Abscluss-Teardown.
v0.20.0
2026-08-26

Concerns-Sweep (war: v1.3)

Ziel: das 2026-08-26 CONCERNS.md-Audit fegen — alle leichten Gewinne direkt korrigieren, die Mittel-Aufwand-Elemente in fokussierten Phasen anpacken und (verzögern oder dokumentieren) die Block-/große-Aufwands-Punkte bewerten.

Ausgeliefert: 7 Phasen (154–160), 14 Pläne. Ein Brownfield-Verfestigungsmeilenstein; keine neuen benutzergerichtete Funktionen.

Added

  • Docs-Gitignore-Verkorrichtigung, chat.ts-Dateikarte, Lint-Regel-Umschalter.
  • Code-Verfestigung, leichte Gewinne (apiKeyMiddleware Take:10 Deckel, usw.).
  • Frontend-Lint-Schuld (3 Pläne, Warnung→Fehler-Umschalter).
  • ENCRYPTION_KEY-Produktions-Bootwarnung (CSW-11) + gestapelt Titel+Tags+FollowUps-Erzeugung.
  • Modulsplits (die Anbieter-Parser-Extraktion, die Fächtigkeits-Register).
  • Test-Abdeckung (chatImportService, chatExportService, apiKeyService, 5 übrige Dienste; Collector + Widget in der根 jest-Konfiguration verdrahtet).
  • Bewerten & dokumentieren (die .env-Lizenz-JWT-Einräume, das Lizenz-Signierschlüssel-Rotationshandbuch, die veraltete AGENTS.md-Behauptverkorrichtigung).
v0.19.1
2026-08-25

Verfestigungen (war: v1.2)

Ziel: Branding, KI-Disclaimer, TypeScript-Reimplementierung des Wiki-Graphenalgorithmus, RAG-Suchverbesserungen, MCP-Verfestigung, Setup-Assistent Erstartern.

Ausgeliefert: 6 Phasen (149–153 + 153.1).

Added

  • BRAND-01/02: SVG-S-Monogramm-Favicon, wiederverwendbare Monogramm-Komponente, gedämpfte lokalisierte KI-Disclaimer unter jeder Assistent-Nachricht (7 Sprachen).
  • MCP-01/02/03: MCP-Verfestigung — pro-Session-SSE-Transport-Map, MCP_API_KEY-Bearer-Auth-Schranke, list_workspaces-IDOR geschlossen, getMCPToolsForWorkspace in resolveSkillsForChat verkabelt.
  • RAG-01/02/03: FTS-Ortsverkorrichtigung (Mehrsprachige Tokenisierung für it/ru/de/es/fr/zh), Entdopplung der Rückfallergebnisse rag_search↔wiki_query, LLM-Angebotsprompt für rag_search vs wiki_query.
  • WIKI-01: TypeScript-Reimplementierung des Graphify-Wiki-Algorithmus (Community-Erkennung + Wiki-Markdown-Erzeugung, eine Apache-2.0-sauber Reimplementierung, ~280 LOC Python → TS).
  • WIZ-01/02: Setup-Assistent Erstartern-UI (Mehrstufig: Admin-Konto + Anbieterkonfiguration + Vektordatenbank; der Verbrauch von /api/system/initialize); SEED_BOOTSTRAP_ADMIN deaktiviert bei aktiver Assistent.
  • .1: die v1.2-Tech-Schuld, aufgeräumt (React Compiler Warn→Fehler-Umschalter).
v0.19.0
2026-08-19

Die Enterprise-Plugin-Architektur (war: v1.1)

Ziel: das Lizenzierungsmodell von "Laufzeit-Fähigkeitsflaggen im öffentlichen Quellcode" auf ein "eigenständiges privates Enterprise-Plugin" umgestalten: Enterprise-Funktionalitäten (SSO, Audit-Log, White-Label, Backup) wohnen in einem privaten Paket, das nur an zahlenden Kunden ausgeliefert wird; das Community-Repo enthält keinen Enterprise-Funktionscode. Ware-Fähigkeiten werden in der Community immer-EIN.

Ausgeliefert: 9 Phasen (140–148), 19 Pläne, 26 Aufgaben.

Added

  • EPA-01: PluginContext/EnterprisePlugin-Typen in shared; die Loader-Naht loadEnterprisePlugin(app) / shutdownEnterprisePlugin() mit zwei-Schritt-require.resolve→require (MODULE_NOT_FOUND = Community-no-op, andere Fehler = process.exit(1) fail-loud).
  • EPA-02: 9 Ware-Flaggen aus FEATURE_FLAGS entfernt (20→10): web_search, webhooks, push_notifications, memory, lead_export, widget_analytics, auto_title, synthesis_rate_limit. Ware-Routen immer-EIN.
  • EPA-03: SSO (SAML + OIDC + SCIM 2.0) in das Enterprise-Plugin extrahiert.
  • EPA-04: Audit-Log (eine unveränderbare event_log-Tabelle + eine INSERT-only-Datenbank-Rolle) in das Enterprise-Plugin extrahiert.
  • EPA-05: White-Label-Branding (ein Konfigurationsschlüssel-Validator-IoC-Haken) in das Enterprise-Plugin extrahiert.
  • EPA-06: Backup (~1400 LOC: backupService, ein Anbieter-Register, ein Bree-Scheduler, Wiederherstellung, Routen, UI) in das Enterprise-Plugin extrahiert.
  • EPA-07: der overrideFeatureLimit-Resolver mit reaktiver Widerruf; das GET /api/enterprise/modules-Manifest; das React.lazy-bedingte Laden von 4 Enterprise-Panels.
  • EPA-08/09/10: priority_support entfernt; custom_agents = eine numerische Limit (Community 3, Enterprise Unendlich); licensePayloadSchema byte-identisch (RS256).
  • EPA-12: die Docs/Env-Ausrichtung (docs/ENTERPRISE_PLUGIN.md, das docker-compose-Volume-Mount, der .env.example-Split), das Luftlücke-CI-Profil (eine Grep-Schranke am Lizenzdienst für HTTP-Primitive).
v0.18.0
2026-08-12

Der öffentliche Release-Kandidat (simmetric-chat) (war: v1.0)

Ziel: das Projekt als ein sauberes öffentliches Repo vorbereiten und veröffentlichen, umbenannt zu simmetric-chat, ohne Geheimnisse/personelle Daten/lokale Pfade, mit dem Abhängigkeits- Lizenzeintrag, dem Enterprise-Keygen-Programm (getrennt, nicht veröffentlicht) und vollständiger Dokumentation.

Ausgeliefert: 8 Phasen (132–139), 12 Pläne, 16 Aufgaben. Öffentliches Repo veröffentlicht, Umbenennung abgeschlossen, Lizenz-Verifizierung, Enterprise-Keygen (HS256 → nach v1.0 zu RS256 gemigriert), kanonische Dokumente, CI GitHub Actions, frisches Repo.

v0.20
2026-08-11

Widget UX, i18n & Zuverlässigkeit

Ausgeliefert: 7 Phasen (125–131), 32 Pläne, 75 Aufgaben, 16/16 Anforderungen erfüllt.

Added

  • WID-ADMIN: die tabbed Unterseite Widget bearbeiten/schaffen (/widgets/:id + /widgets/new), die Live-Vorschau-Pane, die Schmutzwache, die Leads-Tab.
  • WID-I18N: der JSON-Block-Transport (tötet den %20-Bug), die Besucher→Widget-Standard→en-Fallbackkette, die 7-Sprachen-Chrome- + Inhaltslokalisation, das Orts-Gewinde Widget→Proxy→Server→Orchestrator.
  • WID-QUEST: Tri-state vorgeschlagene Fragen (Standard/Keine/Angepasste), 7-Sprachgruppen, der Behebung der Overflow line-clamp.
  • WID-CREDITS: Fußzeilenkredite + KI-Badge, die White-Label-gebarrte Entfernung, das verifizierte real-embed Popup-Relaiss.
  • WID-BUGS: 44px-Eingabeknöpfe, der Host-FAB-Lauf zurück, der nur-SVG-Icon-Vertrag, Kontaktaufforderung, Wiki-Schleife, Lead-Persistenz, RAG-degradierte i18n.
v0.19
2026-08-08

Diagnostik & Schulden

Ausgeliefert: 5 Phasen (120–124). Lizenzdiagnostik (Boot-Protokolle, admin-einziges Diagnose-Endpoint, pnpm license:check-Skript), Testschulden (ESM-Verdriftung, LoginPage QueryClientProvider, das Fixtures-Formulars-Spec), Tech-Schuldenbewertung (der Redis-Skalierschicht-Spike — auf v1.4 vertagt).

v0.18
2026-08-04

Festigung

Ausgeliefert: 4 Phasen (116–119).

v0.17
2026-08-02

Enterprise & Feinschliff

Ausgeliefert: 8 Phasen (108–115).

Frühere Meilensteine (v0.1–v0.16) sind in .planning/PROJECT.md (die Milestone-Historie-Sektion) dokumentiert. Die volle Phasen-Level-Geschichte lebt in .planning/MILESTONES.md.