Product changelog
Every Simmetric Chat release, from the newest version to the oldest.
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) + dieusers.widgetQuotaLimit-Obergrenze pro Eigentümer; NULL/fail-closed 0 — unangetastete Installationen sehen null Verhaltensänderung (dasmaxSponsoredUsers-Gitteridiom der Phase-206). - Das Widget-CRUD für nicht-admin-EIGENTÜMER wieder scharf geschalten:POST /api/widgetsfährt über die neuewidget: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: mitWORKSPACE_ROLE_ENFORCEMENT=truedurchlä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ümersWorkspaceAccessDialogund das Settings-PanelSettingsWorkspaceAccess) 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 aufrag_searchzurü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 streichtrag_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 EndpointGET /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 überattachmentPending) bis "completed", und lässt den Anhang mit einem Toast beifailed/cancelledfallen (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 eingebettetesuser-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-gesendeteuser-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 istdisabledohne ein gewähltes Projekt, gefiltert aufprojectId === selectedProjectId— während die Workspaces-Seite den geteilten Workspace auflistete (der OR-Filter vonGET /api/workspacesschließt den ZuteilungsarmaccessGrants.some(userId)ein) aber ihre Zeilen nur Verwaltung waren, ohne Weg hinein.WorkspaceRowbekommt 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("/") — persistiertlastWorkspaceId, 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 vomcurrentWorkspaceIdgesteuert. 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 inGET /api/projectsfehlt) — 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 geteiltenworkspaceSelectPlaceholderKey-Helfer insidebar/navModel.tsx(ein veralteter Leer-Sentinel aus einer widerrufenen Freigabe degradiert zur generischen Formulierung statt einem aktivierten-leeren geteilten Bereich;workspaceSelectDisabledgates dieAppNavOverlay-Monts — das Inline-AppSidebarNavrendert nur auf Host-Einträgen). Die i18n-Schlüsselworkspace.open+sidebar.selectSharedWorkspace+sidebar.sharedProjectsin alle 8 Sprachen ergänzt — Parität grün. Neue Pins: Viewer-Zeilen zeigen Open und nie delete/edit, fehlendesonOpenrendert kein Kontrolelement, der seitige Ride-along-Pin verifiziert denlastWorkspaceId-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
DialogContentist eingridOHNE explizitegrid-template-columns, also sassen die Kinder auf EINER implizitenauto-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ügtbreak-wordszum interpolierten Titel +flex-wrapzu 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 neueWorkspaceAccessDialog.test.tsxpinnt 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 optionalenuser-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;WorkspaceAccessGrantbekommt dasusername-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 markiertdata-active).e2e/settings-left-menu-nav.spec.tsist wieder grün. - Auch als Nicht-Verifiziert:
POST/DELETE /api/system/settings/branding/iconsind 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-WachewhiteLabelExtractionGuard); 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(dieadmin: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 vonSettingsPageGET /api/system/settingsanfragen, 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/settingsist jetzt Berechtigungsbewusst. Admins (Träger vonadmin:settings) behalten den vollen Konsolen-Payload byte-identisch wie zuvor; jeder andere autorisierte Benutzer erhält nur dieNON_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_UPLOADfür die Upload-Seite) — jeder EintragreadOnly: 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 vonSettingsPageund imuseMe-Fehlerarm vonApp.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.parseFileschnü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 einenroutedAsPdf-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.
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).isEnabledbleibt auf der Karten-Switch, TelegrampollModeauf den Webhook-Schaltflächen, und Platform/botToken/Workspace bleiben per Schema unveränderlich. Der PUT-Arm bekommt zudem das Audit-Ereignischat_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üsselINSTANCE_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 UmgebungsvariableLICENSE_KEY→ Community. Die Massen-PUT /api/system/settingsverweigert 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 vomSETTINGS_SECTION_MODIFY_PERMISSIONS-Fallback ab — gewährt nie,admin:settingsendgültig),settingsModifyinGET /api/roles/me/menu-sections, Server-Gate auf Abschnittsmutationen, die403 { error, section }zurückgeben (/api/providers-Mutationen →llm;POST /api/agency/users→team) und eine Ändern-Spalte im Rollen-Sichtbarkeitseditor (PUT /api/roles/:id/visibilityakzeptiertcanModifypro 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-Buttonwhitespace-nowrap shrink-0BY 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 jetztw-[22rem] max-w-[calc(100vw_-_2rem)](breiter + viewport-gedeckelt), die Button-Zeilenflex-wrap, die beiden langen Aktions-etiketten überschreiben das nowrap (whitespace-normal text-left), und die interpolierten Titel/Texte tragenbreak-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-Idiomcalc(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 vonChat.modelundWorkspaceAgentConfig.modelvon"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 schreibenexplicit: 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 additiveschat-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 (vectorStoresensureTableerstellt 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ürconnectors_enablederhö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_KEYgeschrieben), liefert jetzt eine geführte Fehlermeldung, die die Ursache benennt, die Wiederherstellungshebel (LEGACY_PREVIOUS_ENCRYPTION_KEYS/ eine frühereENCRYPTION_KEY, dann Neustart), die Anzahl der getesteten Schlüssel und das Handbuchdocs/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 Selbsttestscripts/repo-boundary-check.test.cjs+ der CI-Jobrepo-boundary-checkinbuild.needsverdrahtet
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 aufMODULE_NOT_FOUNDim 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/registercommittet 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 denroleInOrgdes 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 jetztuseMe()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-.envmit einem unausgefüllten.env.example-Vorlagen-Platzhalter (<sostituire-con-valore-generato>) alsAPI_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 mitNODE_ENV=productionlä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 denX-Collector-Secret-Header in konstanter Zeit (crypto.timingSafeEqualmit 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/loginvalidiert now seinen Körper auf der Route-Ebene mit dem geteiltenloginSchema(safeParse) — ein ungültiger oder leerer Körper gibt400 { error, details }wie jede andere Auth-Route zurück, statt eines401mit einer rohen Zod-Meldung. Valide-aber-falsche Anmeldedaten geben weiterhin401 "Invalid credentials". - Authentifizierung:
loginläuft jetzt ein Pseudobrutus-bcrypt.comparegegen 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 dasselbe401 "Invalid credentials". - Authentifizierung:
POST /api/auth/admin-reset-passwordvalidiert now seinen Körper mit dem neuen geteiltenadminResetPasswordSchema(userId-UUID +newPasswordvon 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/registerdupiziert die Auth-Logik nicht mehr inline — er benutzt die statisch importiertenverifyToken/isTokenRevoked/isAdmin/getCachedUserWithRoles(kein dynamischesawait 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 denX-Api-Key-Header in konstanter Zeit (crypto.timingSafeEqualmit einer Längenwache) und scheitert geschlossen, wenn der erwartete Schlüssel nicht gesetzt ist, anstatt des früheren Klartextvergleichs mit der fail-openexpected &&-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-qemuentfernt, jeder Matrixeintrig veröffentlichtlinux/amd64, und die Build-Matrix un-serialisiert zumax-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 indocs/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 Migrationadd_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.
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), undeu_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 beimigrate deploymit derON 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-forgetdlp.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ägtsource: "chat" | "widget"in seinen Metadaten — der geteilte SSE-Stream-Kern zieht die Marke aus demX-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) durchPUT /api/system/settingsaus, 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.exampledokumentiert 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 einemenvOverridden-Badge in der Settings-UI angezeigt. - Konfiguration: das Stammrepositorie-
.envist 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 composeenv_fileweist 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; roheprocess.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 durchPUT /api/system/settingsverwaltet. 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.prefixstarb: 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 (dasollama/ollama-Image liefert kein curl/wget/node — der Container meldete für immer "unhealthy") durch eineollama ls-Sonde; der qdrant-Healthcheck ersetzte diewget-Sonde (das Image liefert kein wget/busybox) durch einen Bash-/dev/tcp-Sokkel-Check — beide Dienste melden jetzthealthy. Derdocker/entrypoint.shdes Einzelcontainer versorgt jetztprovision-encryption-key.sh(Parität mit dem gesplitteren Bild): produktionsunabhängige Boots auto-provisionierenENCRYPTION_KEY+API_KEY_HMAC_SECRETstatt 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_matchleitet 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 leeresmatchTypes-Arraytragen, während die Zeile trotzdem ein Treffertyp-Badge zeigte. Der Treffertext wird nach dem DLP-Filter-Plugin-Präzedenz gespeichert (dlp.input_match/ nicht-Streamingdlp.output_matchtragen 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//assignheilen 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/:idbehandelt 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.exampleunddocs/DEPLOYMENT.mdaktualisiert, die Variable als Erforderlich-in-Produktion zu markieren. Siehedocs/ENCRYPTION_KEY_ROTATION.mdfür die Docker-Notizen. - Wissensbasis-OCR-Seiten scheitern nicht mehr, wenn der Ollama-Visionstream vom Modellmotor ohne die Endmarker
donenach vollständiger Transkription geschlossen wird (beigebracht mit dem glm-ocr-Eigenbau-Modul):ocrPagerettet die gesammelte nicht-eere Ausgabe als Seiten ergebnis mit einertruncated-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 alsfailedPagesin 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/sharedNEUE 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 denfile:-Paket-Snapshot innode_modules/.pnpmauf und Dateien, die nach dem letztenpnpm installerzeugt werden, erreichen diesen Snapshot nie (Hardlinks aktualisieren nur bereits existierende Dateien), sodass derschemas/index.jsdes 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 mitpnpm installauffrischen, dannpnpm build), und das Luftlücke-Installationshandbuch indocs/ENTERPRISE_PLUGIN.mdbekommt die passende Störungsbehebung-Unterabschnitt (Symptom, Ursache, Fix, Verstandkontrolle), sodass auch Betreibern ohne das private Repository die Abhilfe finden.
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).LICENSEwechselte 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 viascripts/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 heraufgepumpt0.17.0→1.4.0(synchronisiert mit dem letzten git-Tagv1.4). Per-Paket-Versionen bleiben bei0.22.0/0.23.0(private Pakete, in keinem Registry veröffentlicht). - Das
license-Feld inpackage.json+ allenpackages/*/package.jsonaufAGPL-3.0-or-latergesetzt. - 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.mdmit den CLA-Unterzeichnungs-Anweisungen aktualisiert.
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+withDistributedLockaus 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 BruchmigrationDROP COLUMN hashedKey+ADD COLUMN key_hash; die UmgebungsvariableAPI_KEY_HMAC_SECRET; das Betreiber-Migrationshandbuchdocs/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) bleibensetInterval; 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-
.envgesetzt werden (base64, 32 Bytes) oder der Server verweigert den Boot. Siehedocs/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. Siehedocs/API_KEY_MIGRATION.md.
Infrastruktur
- Neue Abhängigkeit:
pg-bossv12.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.
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).
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).
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-NahtloadEnterprisePlugin(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_FLAGSentfernt (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; dasGET /api/enterprise/modules-Manifest; dasReact.lazy-bedingte Laden von 4 Enterprise-Panels. - EPA-08/09/10:
priority_supportentfernt;custom_agents= eine numerische Limit (Community 3, Enterprise Unendlich);licensePayloadSchemabyte-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).
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.
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.
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).
Festigung
Ausgeliefert: 4 Phasen (116–119).
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.