Changelog

Product changelog

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

Unreleased

Added

  • O modelo de posse de widgets (2026-10-05 "wown", quick 261005-scn). Os widgets passam a ter proprietários (o seu criador) e utilizadores (autorizações por widget): - a junção widget_users (migração aditiva) + o teto users.widgetQuotaLimit por proprietário; NULL/fail-closed 0 — instalações intocadas veem zero mudança de comportamento (o idioma do retículo maxSponsoredUsers da Phase-206). - O CRUD de widgets rearmado para PROPRIETÁRIOS não administradores: POST /api/widgets passa pela nova permissão widget:manage + o teto atribuído pela administração (ultrapassar → 409 { error, quota: "widgets" }); as barreiras de licença ao nível da instalação (widget_enabled / max_widgets) continuam inalteradas. GET lista/detalhe, PUT definições + as ligações de workspace alcançam creator | widget-user | admin (os IDs alheios continuam 404 não enumerantes); DELETE continua creator | admin. - A nova secção de administração na zona de widgets (/widgets?tab=quotas, o separador de quotas): linhas por utilizador — user | owned | max-widgets — com escritas assign / clear (null), busca paginada, registada em auditoria (user.widget_quota.updated). - O novo separador "Utilizadores" por widget (WidgetDetail, em modo de edição): o PROPRIETÁRIO seleciona os utilizadores do widget (uma PUT de substituição de conjunto, o apuramento do diretório de autorizações /widgets/owner-user-search — uma superfície de escolha de identidade, sem emails); os utilizadores autorizados obtêm o ramo de só-leitura. - Testes: a matriz de servidor de 30 afixas (widgetOwnership.test.ts), os ramos de frontend (WidgetUsersTab/WidgetQuotasTab), o percurso e2e (widget-ownership.spec.ts).

Fixed

  • Partilhar um workspace define o papel editor por omissão, não espectador (2026-10-05 "viewer-default"). O sintoma relatado — "como utilizador com autorização, não consigo criar novos chats nem escrever nos chats existentes do workspace partilhado" — remontou-se (por uma nova prova de integração de 4 afixas, granteeChatWriteAccess.test.ts) ao PAPEL da autorização, e não às barreiras: com WORKSPACE_ROLE_ENFORCEMENT=true, uma linha WorkspaceAccess de editor passa GET /chats (200) E POST /chat (200) no servidor; uma linha de ESPECTADOR passa o ramo de leitura e avalia-se 403 no ramo de escrita POR DESENHO, e um autorizado sem linha recebe o 404 não enumerante (SC-4). AMBAS as superfícies de autorização (o diálogo de partilha do proprietário WorkspaceAccessDialog e o painel de Settings SettingsWorkspaceAccess) pré-selecionavam espectador nos seletores de papel individuais E de massa — um administrador que autorizava sem mexer no menu produzia autorizações de só-leitura. A omissão da interface também contradizia a intenção ao nível do esquema: o comentário de migração da Phase 189 D-12 apoia o enchimento das linhas antigas em editor ("NOT the spec's viewer suggestion"). Ambas as superfícies definem agora editor por omissão (individual + massiva); os afixes do cablagem dos papéis (um teste que escolhe explicitamente espectador e depois prova que espectador atravessa até ao fim) ficam intactos. Nota: os utilizadores autorizados ANTES desta correção conservam o seu papel persistido — uma nova autorização (upsert do papel) volta a avaliar.
  • "Resume o ficheiro anexado" deixa de mentir com a frase feita de sem-resultados (a lacuna anexo-antes-da-ingestão fechada, 2026-10-05). Anexar um ficheiro no chat carrega-o pela pipeline de ingestão ASSÍNCRONA; a mensagem pergunta um tempo depois, o documento ainda tem ZERO blocos, por isso a resolução por rota de attachedDocumentId (ambos os ramos do chat em chat.ts) não injetava NADA: o agente caía no rag_search, o documento recente ainda não estava no armazém de vetores, e a regra 3 do buildSystemPrompt respondia com a frase feita "Non ho trovato informazioni su questo argomento nei documenti del workspace. Verifica che i file pertinenti siano stati caricati e indicizzati." — uma afirmação falsa (o workspace pode estar cheio; só o anexo recente não está pronto). Os dois sítios de resolução agora detetam o ramo de zero blocos e injetam um aviso veraz (buildAttachmentNoticeChunk(new utils/attachmentNotice.ts)): o ramo non-empty-ragContext do orquestrador tira o rag_search, de modo que o modelo responde a partir do aviso — "ainda a ser processado, tente de novo dentro de um momento" (pendente/em curso) ou "processamento falhou/cancelado, torne a carregar" — e o aviso proíbe explicitamente a frase feita. Consciente do estado e do avanço (o progresso 0-100 do collector aparece só quando > 0); a varredura DLP do contexto RAG monta no aviso sem mudanças. 5 afixas de unidade no auxiliar (os ramos pendente/em curso/falhado/cancelado + a instrução anti-regra-3). Continuação de frontend — o esperante de ingestão da placa de anexo: a placa consulta a cada 1,5s o novo ponto final GET /api/documents/:id/attachment-status (triple teste de IDOR dos metadados, a projeção { name, status, progress }, sem blocos), mostra uma roda + o progresso % durante a ingestão, SEGURA a barreira de envio (canSend + a via de Enter por attachmentPending) até "completed", e deixa cair o anexo com um aviso sobre failed/cancelled (404 → queda silenciosa; os erros de rede transitórios continuam a consultar — um soluço do servidor não deve destruir uma ingestão saudável). A barreira é uma cortesia do cliente: o aviso do servidor fica como baluarte veraz de abas envelhecidas. 4 afixes de ponto final em documentIdor.test.ts (a projeção access-user + os ramos D-04 da administração); 2 afixes da ChatInputArea (pendente desativa o envio + a placa de progresso; concluído reativa).
  • Settings → "Acesso aos workspaces": as linhas de autorização mostram de novo o nome do utilizador (Q6-fast settings parity, 2026-10-05). O diálogo de acesso aos espacios de trabalho tinha corrigido este autêntico defeito (a cadeia nome-de-utilizador → ID), mas o painel de Settings conservava a cadeia PRÉ-Q6 user ? displayName(user) : userId — e o fio D-15 nunca envia um objeto user embutido (T-189-11), por isso cada linha expandida do painel caía no UUID nu. O painel espelha agora a cadeia do diálogo (o utilizador embutido forma antiga → grant.username → ID) e a sua suorta apoia a forma real em fio: o simulador estático retirou o campo user nunca enviado, um teste novo prova que o nome de utilizador rende-se e o UUID nunca (+1 teste).
  • O ponto de entrada do workspace partilhado: as linhas da página de Workspaces AGORA ABREM (2026-10-05 "open-row"). Um autorizado a nível de workspace (leitor/editor SEM acesso a projectos) nunca conseguia alcançar o workspace partilhado na interface do chat: os seletores da barra lateral são projecto-primeiro — o menu de projectos por GET /api/projects (enumera apenas os projectos do criador / ProjectAccess, sem ramo de autorização de workspace) e o menu de workspaces disabled até se eleger um projecto, filtrado por projectId === selectedProjectId — enquanto a página de Workspaces enumerava o workspace partilhado (o filtro OR de GET /api/workspaces inclui o ramo de autorização accessGrants.some(userId)) mas as suas linhas eram só de gestão, sem forma de entrar. WorkspaceRow ganha uma ação "Open" (primeira na coluna de ações, desenhada para CADA linha enumerada — a pertença à lista implica pelo menos leitor pelo filtro OR do servidor; a barreira do cliente é só UX, as rotas do chat repõem a barreira server-side por pedido) que monta o workspace via ChatContext (setWorkspaceId(id) + navigate("/") — persiste lastWorkspaceId, por isso a restauração ao arrancar volve ao contexto ao recarregar sem os seletores). Um efeito espelho em App mantém os menus da barra lateral coerentes quando o workspace activo muda fora dos seletores — SOMENTE quando o projecto-pai está na lista de projectos do utilizador; a superfície de chat é dirigida pelo único currentWorkspaceId. Os seletores da barra lateral ganham uma entrada estática "Partilhados comigo" (sentinela __shared__ no menu de PROJECTOS, desenhada só quando o conjunto de partilhas não é vazio): com um projecto eleito a lista de workspaces continua limitada àquele projecto como antes; eleger a sentinela (ou uma escolha vazia ao arranque) faz o menu de workspaces enumerar o conjunto invisível-por-projecto (todos os workspaces alcançáveis pelo filtro OR cujo projecto-pai falta em GET /api/projects) — a lista de partilhas do autorizado só-workspace, antes inalcançável porque o controlo exigia primeiro um projecto E o Radix Select não pode esvaziar um ID concreto de projecto depois de eleito (beco sem saída unidireccional; a entrada nomeada é o caminho sempre clicável de regresso). O texto de espera é dirigido pelo conteúdo via o auxiliar partilhado workspaceSelectPlaceholderKey em sidebar/navModel.tsx (uma sentinela esvaziada caducada de uma autorização revogada degrada para a formulação genérica em vez de um alcance partilhado activado-e-vazio; workspaceSelectDisabled barra as montagens de AppNavOverlay — o AppSidebarNav inline rende-se só nos itens anfitriões). As chaves i18n workspace.open + sidebar.selectSharedWorkspace + sidebar.sharedProjects acrescentadas aos 8 idiomas — a paridade verde. Afixes novos: as linhas de leitor mostram Open e nunca delete/edit, um onOpen ausente não rende controlo, o afix de página de boleia prova a escrita lastWorkspaceId no localStorage via o ChatProvider real (WorkspacesPage), o ramo partilhado rende-se dos itens anfitriões sem projecto e esconde-se quando vazio (AppSidebarNav), eleger a sentinela mostra a espera partilhada enquanto uma sentinela caducada degrada, e a matriz de barreiras do overlay apoia activado/desactivado por ramo + texto de espera em sobremesa e móbil (AppNavOverlay).
  • O diálogo "Accesso" (o botão do topo da barra lateral) já não transborda a própria moldura (2026-10-05). Cada bloco de largura total escapava para além da borda arredondada do diálogo: o DialogContent do kit de interface é uma grelha grid SEM grid-template-columns explícito, por isso as crianças sentavam-se sobre UMA trilho implícita auto que cresce até ao contributo min-content do item mais largo — uma linha de autorização com um nome de utilizador UUID indivisível de 36 caracteres somava 503–549px contra a caixa de conteúdo de 488px (sm:max-w-[520px]), alargava a trilho e ESTIRAVA cada item de largura total (o título, a fila de seletores, a lista de caixas, as <li> de autorização) para além da borda. Correção de dois pisos: a base do kit de interface apoia agora uma coluna explícita de mínimo-zero (grid-cols-[minmax(0,1fr)] — toda a família de Diálogos restringe os itens à caixa de conteúdo limitada em vez de empurrar a trilho, para toda a aplicação) e o sítio do diálogo acrescenta break-words ao título interpolado + flex-wrap às filas de autorização/massivas e a cada <li> de autorização (os botões nowrap quebram para a própria linha sob ~360px de vista). Verificado com Playwright: nenhum transbordo 380→1280px (os nomes de autorização truncam com elipses como desenhado, nada cruza a borda); o novo WorkspaceAccessDialog.test.tsx apoia ambos os pisos — 174 suítes / 1487 testes de frontend verdes (+1 suíte, +4 testes).
  • As linhas de autorização de "Accesso" mostram o NOME do utilizador de novo (o seguimento rápido do Q6, 2026-10-05). A resposta do servidor D-15 leva {userId, username} mas NÃO um objecto de utilizador embutido (retirado com T-189-11 para deixar de vazar emails/papéis), enquanto o diálogo desenhava o nome APENAS a partir daquele campo opcional user morto — cada linha de utilizador atribuído caía no ID UUID. As linhas de autorização resolvem agora o utilizador embutido (forma antiga) → username → ID; WorkspaceAccessGrant ganha o campo username que o fio realmente envia. Afixe novo: o nome de utilizador desenhado / o ID nunca vazado / as linhas antigas com username nulo conservam a cadeia de recurso ao ID (+1 teste).
  • O link fundo de Settings restaurado (2026-10-05). Um carregamento completo de /settings (marcador, entrada da barra lateral, entradas de navegação antigas) deixou de abrir o diálogo de ajustes desde a revisão R-5 "adjusts as diálogo": ao primeiro commit as consultas de autenticação ainda estavam em voo (user = null) e o efeito close-on-logout — declarado depois do abridor no mesmo componente — voltava a fechá-lo no mesmo lote (abrir-depois-cancelar), e o caminho após o carregamento já estava normalizado por isso nada reabria. A navegação suave (o menu do utilizador) não estava tocada. O abridor agora dispara só sobre uma sessão CARREGADA e a normalização conserva a cadeia de consulta, por isso os links fundos ?tab= sobrevivem ao montagem do diálogo; os marcadores ?tab= TAMBÉM ENT RAM na secção visada (o mesmo ramo que um clique numa entrada: a página do grupo abre-se e a entrada marca data-active). e2e/settings-left-menu-nav.spec.ts volta a estar verde.
  • Verificado também como não-um-bug: POST/DELETE /api/system/settings/branding/icon são as rotas do plugin ENTERPRISE (simmetric-enterprise/brandingRoutes, Phase 145, mountProtected + adminLicense("white_label")) — a montagem de comunidade responde 404 e a interface degrada com o aviso documentado de melhoria por desenho (a guarda de grep D-12 whiteLabelExtractionGuard); a bateria EE 37 suítes / 737 testes verdes, a guarda de comunidade verde.
  • O painel de Settings já não desloca o acesso aos utilizadores não administradores que o abrem. O encaminhador dos ajustes estava barrado de dica a dica com requireAdmin (a permissão admin:settings) enquanto a grelha de visibilidade por papel (SETTINGS_TAB_PERMISSIONS / RoleSectionVisibility) continuava a desenhar secções — Perfil, Aparência, Fornecedores LLM, Avançado — aos utilizadores ordinários. Abrir o painel (ou uma secção) fazia a concha de SettingsPage ir buscar GET /api/system/settings, receber 403, e a página confundia 403 com 401: mostrava "Session expired. Please log in again.", apagava o token e encaminhava ao /login 3 segundos depois — destruindo uma sessão inteiramente válida por um vão simples de permissão. Correção em duas partes (2026-10-04 "settings-panel logout"): - Servidor: GET /api/system/settings agora é consciente da permissão. Os administradores (portadores de admin:settings) conservam a carga da consola completa idêntica ao bit; cada outro utilizador autenticado recebe apenas o subconjunto NON_ADMIN_SETTINGS_READ_KEYS (partilhado) — as chaves não sensíveis consumidas pelas superfícies alcançáveis dos não administradores (BRANDING_* para o separador Aparência / o cabeçalho da aplicação, ALLOW_NON_ADMIN_UPLOAD para a página de carregamentos) — cada entrada readOnly: true (regra anti-desfasamento: nunca desenhar um formulário que o servidor rejeitaria com 403). Os segredos (SMTP_PASS, LLM_API_KEY, JWT_SECRET, DATABASE_URL, VECTOR_DB_API_KEY, ...) nunca podem alcançar uma resposta não administrativa. A vista da organização (?organizationId=) e todas as rotas de escrita (PUT /, POST /mail-test) continham-se à administração apenas (fail-closed). - Frontend: só um 401 (fallo de autenticação) destrói a sessão — no ramo de redirecionamento de 3 segundos da SettingsPage e no ramo de erro do useMe de App.tsx. Um 403 desenha agora uma faixa distinta "Não tem permissão para ver estes ajustes." (settings.errorNoPermission, os 8 idiomas) e a sessão sobrevive.
  • A ingestão do Collector: os ficheiros carregados com uma extensão tipo texto (.txt .md .csv) mas com bytes mágicos PDF deixam de se indexar como UTF-8 cru (o incidente AI-ACT.txt de 2026-10-01). Um PDF renomeado como texto alimentava o índice de vetores apenas com as entranhas PDF não comprimidas (o dicionário de Informação, os metadados XMP, os dicionários de objectos, os elementos de estrutura) — 3321 blocos, nenhum de texto normativo — enquanto o corpo dentro dos fluxos comprimidos FlateDecode degradava para lixo U+FFFD. parseFile agora farejia o cabeçalho %PDF- (os primeiros 1024 bytes, a janela a partir do lixo de ISO 32000) nos ficheiros declarados texto e dirige-os ao analisador de PDF; os metadados dos blocos ganham um selo routedAsPdf e um falso positivo do farejamento (um ficheiro de texto que somente menciona "%PDF-" cedo) volta ao analisador de texto em vez de fazer falhar a ingestão.
v0.28.1
2026-10-02

Added

  • Conectors: os conectors existentes agora podem ser MODIFICADOS na interface de administração (Q1-rápido) — o ramo já existente PUT /api/connectors/:id finalmente emergiu: o menu de transbordo do cartão ganha Editar (a primeira entrada), que abre um diálogo pré-preenchido sobre a superfície mutável (o nome, a ligação do arquivo de conhecimento, as mensagens de boas-vindas/recurso, a localização de recurso, o limite de cadência + o limite de sessões diárias — vazio põe null, 0 = o valor sentinela ILIMITADO do router). isEnabled fica no interruptor do cartão, o pollMode do Telegram nos botões do webhook, e plataforma/botToken/workspace continuam imutáveis por esquema. O ramo PUT também ganha o evento de auditoria chat_connector.updated (a paridade com o ramo DELETE, D-21).
  • Phase 221 (INSTLIC) — a fenda de colagem da licença de instância (a melhoria enterprise sem .env): uma nova superfície de administração GET/PUT/DELETE /api/license/instance (requireAdmin + o middleware de inquilino; o empilhar por rota deixa GET /info e /diagnose intactas) mais o painel Configurações → Segurança → Licença. A colagem verifica primeiro por sonda um JWT RS256 de nível ENTERPRISE ainda não reclamado (uma chave recusada nunca persiste: enumeração fechada { erro, razão } — inválida / expirada / plugin_limitada / nível_da_comunidade), guarda APENAS o cifrado AES-256-GCM na SystemConfig (a chave INSTANCE_LICENSE — a disciplina da costura de escrita do SMTP_PASS), invalida o cache Redis D-07, e aplica-se à instância EM CURSO pelo MESMO resolutor que o passo de arranque corre (setCachedLicense — as passagens de limites injetadas pelo carregador sobrevivem; as bandeiras booleanas vivem imediatamente; limpar a colagem devolve o estado do próximo arranque com paridade de arranque). Precedência, arranque e runtime idênticos: a fenda → a linha do plugin verificada → a variável do ambiente LICENSE_KEY → Community. A escrita em massa pela PUT /api/system/settings recusa a chave (a doutrina da rota de escrita dedicada pela retenção de conversas) e a enumeração dos ajustes nunca expõe o cifrado.
  • Phase 219 (SETP-01/02) — bandeiras de verer + modificar por papel para cada secção de Configurações: RoleSectionVisibility.canModify aditivo (subtrai-se do apoiamento SETTINGS_SECTION_MODIFY_PERMISSIONS — nunca concede, admin:settings terminal), settingsModify em GET /api/roles/me/menu-sections, a barreira do servidor nas mutações de secção que devolve 403 { erro, secção } (as mutações /api/providers → llm; POST /api/agency/users → team) e uma coluna Modificar no editor de visibilidade dos papéis (PUT /api/roles/:id/visibility aceita canModify por entrada — deixar-como-está quando ausente).

Fixed

  • Os diálogos de confirmação de eliminação da barra lateral já não transbordam (Q5-rápido): as confirmações de eliminação de conversa/pasta eram caixas w-80 cujo botão do kit de interface é whitespace-nowrap shrink-0 POR DESENHO — as etiquetas longas localizadas das ações de pasta ("Mantieni N chat…" / "Elimina N chat permanentemente") escapavam horizontalmente da caixa, e os nomes de pasta interpolados longos transbordavam o título. As caixas agora são w-[22rem] max-w-[calc(100vw_-_2rem)] (mais largas + limitadas ao visor), as filas dos botões flex-wrap, as duas etiquetas longas de ação sobrescrevem o nowrap (whitespace-normal text-left), e os títulos/textos interpolados levam break-words. As três chaves i18n em falta do diálogo de eliminação de conversa (sidebar.deleteChat / .deleteChatConfirm / .deleteChatButton) entregadas nos 8 idiomas — a interface italiana já não mostra os apoios em linha ingleses ali. O seguimento do Q5: o primeiro tecto nunca compilava — calc(100vw-2rem) dentro de um valor arbitrário é CSS INVÁLIDO (o Tailwind exige a linguagem do sublinhado calc(100vw_-_2rem) para os espaços à volta dos operadores) e a regra nunca se emitia silenciosamente, deixando a caixa de 352px não limitada nas vistas estreitas. O utilitário corrigido entrega-se no CSS construído (max-width:calc(100vw - 2rem) verificado no pacote dist).
  • Resolução do modelo: os turnos de conversa já não aterram no valor por omissão fixado em código qwen2.5:3b quando não existe um pino explícito (Q4-rápido — a causa raiz do incidente das quatro conversas: conversas novas serviam um pequeno modelo local em lugar do valor por omissão do espaço de trabalho, 34-98s por turno). Duas correções: (1) os valores por omissão do esquema de Chat.model e WorkspaceAgentConfig.model mudaram de "qwen2.5:3b" para o valor sentinela "" (migração aditiva — ALTER COLUMN … SET DEFAULT, linhas existentes intactas): a cadeia falsa (model || chat.model || undefined, orchestrator:388/1059) agora cai na cadeia de configuração do agente do espaço de trabalho exactamente como a sentinela irmã "default"; (2) a preferência do modelo por espaço de trabalho no localStorage agora é SÓ-EXPLÍCITA — as escolhas da paleta escrevem explicit: true, a nova perseverança automática do ressemeamento desapareceu (um valor por omissão resolvido pela cadeia não é uma escolha do utilizador e não deve voltar-se pegajoso) e os valores antigas sem marca limpam-se uma vez e nunca mais se usam como candidatos. Uma preferência caducada (p. ex. qwen2.5:3b, ainda válida-mas-lenta no registo) já não pode fazer sombra ao valor por omissão do espaço de trabalho em cada conversa nova.
  • Conversa: "uma mensagem abre N conversas" — o cliente adoptava o chatId do servidor apenas no evento SSE done (até ~90s mais tarde nos turnos ricos em ferramentas), de modo que uma nova tentativa do utilizador nessa janela reenviava sem chatId e o servidor criava uma conversa NOVA por tentativa, enquanto o turno em voo terminava em segundo plano como linha órfã (Q3-rápido). A rota do fluxo agora emite um evento aditivo conversation que leva o chatId AO INÍCIO do turno (antes de qualquer símbolo); o cliente adopta-o de imediato (protegido + idempotente — um envio que já leva um chatId nunca volta a adoptar) e refresca a barra lateral no ato. As novas tentativas agora aterram na MESMA conversa. Idêntico ao bit para os clientes pré-Q3 (os eventos SSE desconhecidos ignoram-se).
  • A memória: um casal (utilizador, espaço de trabalho) com ZERO memórias armazenadas agora salta todo o gancho de rememoramento pré-LLM (Q2-rápido) — uma contagem indexada de PostgreSQL (@@index([userId, workspaceId])) barra o gancho antes de toda a obra: sem incorporar de consulta, sem ida-e-volta do collector, e sem mais nenhum por-turno [vector-store] Tabela "ws_user_memory_…" não encontrada de uma tabela LanceDB legitimamente inexistente até à PRIMEIRA escrita de memória (o ensureTable do vectorStore cria na primeira ingestão; o ramo de busca cai aberto a []). Um soluço da base na contagem cai aberto ao rememoramento pré-barreira (o contrato aos melhores esforços do gancho — nunca bloquear a conversa).
  • shared: a guarda de regressão FEATURE_FLAGS ficou para trás pela muda dos conectors à EE (quick 260930-l4q) — o pino subiu de 12 → 13 por connectors_enabled.

Fixed

  • Backup/restauração: um ensaio em vazio ou uma restauro que falha com "Unable to decrypt (no key in chain matched)" (credenciais de destino escritas sob uma ENCRYPTION_KEY anterior) agora devolve um erro guiado que nomeia a causa, as alavancas de recuperação (LEGACY_PREVIOUS_ENCRYPTION_KEYS / uma ENCRYPTION_KEY anterior, depois reiniciar), o número de chaves tentadas, e o caderno de rota docs/ENCRYPTION_KEY_ROTATION.md — que ganhou uma secção de recuperação Docker "o ensaio do restauração falha". Só a longitude da cadeia de chaves se divulga, nunca o material das chaves.
  • Phase 220 (SPLIT-01/02) — a coerência da cisão do repositório: docs/REPO_BOUNDARIES.md (a matriz de auditoria módulo→repositório para 4 repositórios com a doutrina de classificação de 7 critérios e as exceções por-desenho) + a barreira das fronteiras do repositório: scripts/repo-boundary-check.cjs (uma barreira de grep com lista de permitido — o núcleo não pode referir pacotes de plugim privado fora das duas costuras de carregador e os sítios de contrato documentados) + o autoteste scripts/repo-boundary-check.test.cjs + a posição CI repo-boundary-check cabeada em build.needs
v0.25.0
2026-09-09

Added

  • UI: a revisão completa da concha (R-2 → R-5). O estado vazio da conversa mostra uma marca de palavra monocromática elegante (a animação de falha ciano/magenta fica agora confinada ao tema hacker); a barra lateral torna-se um trilho minimalista ao estilo Claude (conversa nova + lista de conversas + menu do fundo) com a navegação completa numa sobreposição cmdk acolhedora ao teclado (Cmd/Ctrl+/) que mantém a filtragem RBAC exata; o menu do utilizador mudou-se da barra superior para um diálogo dedicado (conta, idioma, tema, ajustes, sair) com uma gaveta à esquerda no móbil; Configurações abre-se como um grande diálogo mestre-detalhe (menu esquerdo + detalhe de área completa com comutador de trilho e migalhas de pão) em vez de uma página encaminhada — a área principal é agora a conversa sozinha em cada página. Os temas (claro "Pérola", escuro, hacker, sistema) receberam uma passada de coerência com correções de contraste WCAG-AA para o texto atenuado e os acentos da conversa, guardas prefers-reduced-motion para todas as sobreposições, e marca branda consciente da folha branca sobre a marca de palavra.
  • Docker: o serviço de metabusca searXNG opcional acrescentado à pilha compose (infraestrutura pura — nada depende dele ainda; a etapa da versão afixada, um exame de saúde à base de wget, configurações auto-fornecidas pelo ponto de entrada da imagem).
  • Docker: o plugim privado @simmetric-chat/saas agora monta-se no servidor compose como o plugim enterprise (um montagem por vínculo do repositório inteiro irmão; o saasLoader no-ops em MODULE_NOT_FOUND no modo comunidade).

Fixed

  • Semear: um desastre de arranque durante a sementeira de utilizadores podia apagar os papéis e barrar a administração; as sementeiras agora curam-se a si mesmas — a autorização idempotente reaplica o papel da administração ao utilizador admin sobrevivente em cada arranque (levado ao registo nos itens adiados de STATE.md).
  • Posse: POST /api/auth/registro agora consignida a criação do utilizador, a autorização do papel por omissão e a adesão da organização por omissão numa única transação, e cada via de sementeira/arranque corre de novo o auxiliar de adesão idempotente na sua ramagem "o utilizador já existe" — um desastre a meio do arranque/pedido já não pode deixar um utilizador sem adesão (a revisão da Phase 182 WR-01); a ressurreição-lápide agora aplica o roleInOrg de quem chama, por isso a remoção degrada privilégios (WR-02).
  • As montagens Enterprise: a secção Configurações → Avançado → Cópia de segurança (e as rotas /sso e /logs) já não ficam atrás da cartão "requer Enterprise" depois de uma entrada pela aplicação ou uma entrada por SSO. A consulta do manifesto dos módulos enterprise (GET /api/enterprise/modules) agora está barrada pela autenticação e reativa pela sessão: antes ia ser buscada uma vez, não autenticada, ao arranque da aplicação e guardada em esconderijo para a sessão toda (retry: false + staleTime: Infinity), por isso no visor de entrada dava 401 e as funções Enterprise ficavam ocultas até uma recarga completa COM o sinal guardado. O gancho agora subscreve useMe() e re-consulta o manifesto quando aparece uma sessão (a entrada por palavra-passe, a entrega SSO ?token=), e nunca apura o ponto final sem sessão de todo.
  • Docker: o auxiliar de abastecimento de segredos de produção (docker/provision-encryption-key.sh) já não mete o servidor em ciclos de queda quando a raiz .env se desdobra com um marcador de lugar do gabarito .env.example não preenchido (<sostituire-con-valore-generato>) como o valor de API_KEY_HMAC_SECRET/ENCRYPTION_KEY — um marcador de lugar reconhece-se agora como "não estabelecido" (advertência sonora) e o abastecimento cai às vias restauração/geração. Qualquer outro valor não-vazio inválido continua a falhar sonoro na barreira do arranque.
  • Segurança: o encaminhador de auxiliares para os testes E2E (/api/__tests__, um servidor eco MCP de inicia/fim sem autenticação) já não se monta quando o servidor corre com NODE_ENV=produção — os arranques de produção respondem 404 aí, aplicando o contrato documentado dev/apenas-de-teste do encaminhador (nenhum ponto final de incubadura de processos sem autenticação em produção).
  • Segurança: o controlo da retroligação do collector de importação do arquivo (PUT /api/archives/import/:jobId/callback) agora compara a cabeça X-Collector-Secret em tempo constante (crypto.timingSafeEqual com uma guarda de comprimento), alinhando a disciplina de tempo seguro já usada pela retroligação do estado dos documentos e pela barreira de segredo do collector — fechando a última comparação de segredo em claro no servidor.
  • Autenticação: POST /api/auth/login agora valida o seu corpo ao nível da rota com o esquema partilhado loginSchema (safeParse) — um corpo inválido ou vazio devolve 400 { erro, detalhes } coerente com cada outra rota de autenticação, em vez de um 401 com uma mensagem Zod nua. Credenciais válias-mas-falsas continuam a devolver 401 "Credenciais inválidas".
  • Autenticação: a entrada agora corre um bcrypt.compare manequim contra um hash descartável fixo quando o utilizador não se encontra, por isso o tempo da resposta já não distingue nomes de utilizador existentes dos inexistentes (a irrobulização da enumeração). Ambas as vias continuam a devolver o mesmo 401 "Credenciais inválidas".
  • Autenticação: POST /api/auth/admin-reset-password agora valida o seu corpo com o novo esquema partilhado adminResetPasswordSchema (userId UUID + novaPassword de 8-128 caracteres, safeParse → 400 { erro, detalhes }) — a última rota de autenticação deixa a validação do corpo desestruturada ad-hoc.
  • Autenticação: a ramagem de registo fechado do POST /api/auth/registro já não duplica a lógica de autenticação em linha — usa verifyToken / isTokenRevoked / isAdmin / getCachedUserWithRoles importados estáticamente (sem um await import dinâmico, sem uma reviva verificação de cabeça morta, a busca do utilizador em esconderijo) preservando a cada forma de resposta e a ordem revocação-antes-de-busca.
  • Segurança: o ponto final de quebrar a cache do widget (POST /api/config/:widgetId/cache-bust) agora compara a cabeça X-Api-Key em tempo constante (crypto.timingSafeEqual com uma guarda de comprimento) e falha fechado se a chave esperada não está estabelecida, em vez da comparação anterior em claro com a forma falha-aberta esperado && — o controlo de chave do widget agora corresponde à disciplina de tempo seguro usada pelas barreiras de segredo do servidor e do collector.
  • A tubagem da publicação: as cinco imagens de Docker agora constroem-se só amd64. As construções linux/arm64 emuladas por QEMU pendiam até ~5h (o widget primeiro, depois a compilação de fontes pgvector tudo-em-um) e esgotavam o disco do corredor nos minutos pagados dum repositório privado; setup-qemu removido, cada entrada da matriz publica linux/amd64, e a matriz de construção desserializa-se a max-parallel: 2. Os utilizadores de arm64 podem auto-constuir localmente (os Dockerfile são seguros pela compilação cruzada) ou anexar um corredor arm64 auto-hospedado — veja a nota arm64 de docs/DEPLOYMENT.md.

Changed

  • Ferramenta: uma barreira de código-morto agora corre em CI (knip) e falha por ficheiros, dependências ou exportações não usados fora das listas de permitido documentadas; uma varredura de todo o repositório removeu ~3400 linhas de código morto, a geração da chave API do widget agora vive atrás do fluxo padrão da administração, e os dois scripts i18n reportam as chaves de tradução definidas-mas-nunca-usadas como um indicia de limpeza não bloqueante.
  • Widget: o afixar do modelo de resposta por widget — a configuração da administração de cada widget agora pode selecionar o fornecedor LLM + o modelo que serve as respostas de conversa desse widget (responseProviderId/responseModel, migração aditiva add_widget_response_model). O afixar resolve-se do lado do servidor desde a linha da base do widget em cada pedido de conversa (nunca fornecido do lado do cliente — o proxy do widget e a interface do visitante ficam intactos), tem precedência sobre o valor por omissão do espaço de trabalho, e os widgets não estabelecidos conservam a cadeia de resolução espaço-de-trabalho/global existente. As conversas do widget agora registram o modelo que realmente as serve.
v0.22.0
2026-08-30

Added

  • RAG: a filtragem de metadados para rag_search — o agente agora pode apertar o alcance da recuperação por tipos de documento (documentTypes: pdf/md/txt/csv/docx/xlsx) e datas de ingestão (dateFrom/dateTo, ISO; um limite superior de só-data inclui o dia inteiro), corrigindo o modo de falha "os documentos de 2023 para as perguntas de 2025". Os filtros atravessam a busca híbrida entrando em ambas as pernas de recuperação: a perna de vetores chega ao collector como verdadeiros pré-filtros sobre metadados de ingestão estampados (os predicados JSONB pgvector + as cláusulas must da carga da Qdrant; LanceDB e Chroma ignoram as chaves novas com uma advertência levada a registro), e a perna FTS ganha predicados SQL parametrizados sobre a tabela de documentos. Um baluarte pós-recuperação do lado do servidor contra a tabela autoritativa de documentos garante a correção filtrada em cada fornecedor. Os documentos ingeridos a partir de agora são sempre filtráveis; os vetores antigos (antes do estampamento) excluem-se enquanto um filtro é activo e voltam a ser filtráveis pela re-inserção da administração — nenhuma mudança de esquema, nenhuma re-inserção dos documentos existentes, e um comportamento sem filtros sem mudanças.
  • DLP: quatro novos padrões embutidos para os identificadores europeus (sobretudo italianos) mais comuns, semeados idempotentemente por uma migração só-de-dados e espelhados no conjunto de recurso sem-base — it_vat_iva (Partita IVA; ancorada à etiqueta, por isso um número nu de 11 algarismos nunca casa), it_codice_fiscale (o código fiscal de 16 caracteres; só-maiúsculas, palavras de prosa como "foschi" nunca casam), iban (o prefixo do país + comprimento ≥ 15; as formas compacta agrupada-espacios, tempo linear/à prova de ReDoS), e eu_phone (telefone IT/EU; semeado DESATIVADO para revisão da administração, visto que qualquer regex telefónica estruturada marca sequências de algarismos de ordem/referência). Os embutidos são agora 10 no total; as implantações existentes recebem as novas linhas ao migrate deploy com a segurança de reaparição ON CONFLICT DO NOTHING.
  • Conversa: os administradores agora podem revelar ou esconder de novo os textos censurados pelo DLP na conversa inteira de uma vez, via um novo item "Mostrar textos DLP" / "Esconder textos DLP" no menu mais-ações (+) da entrada da conversa (os ícones Eye/EyeOff, mostrados apenas aos administradores pois só eles podem desdobrar um aviso DLP). A preferência é à escala da sessão, oculta por omissão e aplica a cada aviso, incluindo as mensagens recém-chegadas; o botão olho por-aviso continua a funcionar para ajustes manuais por-mensagem entre os comutadores globais.
  • A configuração dos padrões DLP: os seis padrões DLP agora portam-se pela base (a tabela dlp_patterns, semeada idempotentemente pela migração para as implantações existentes receberem os embutidos) em vez de fixados em código. Os administradores gerem-nos em Configurações → Avançado → Padrões DLP: ativar/desativar qualquer padrão por linha, renomear os embutidos (o seu regex está congelado — as tentativas de modificação devolvem 400), e acrescentar até 50 padrões personalizados (nome snake_case, fonte regex com validação de compilação ao salvar, bandeiras, substituição) com uma pré-visualização de teste em directo que muestra os segmentos casados e o texto censurado. As varreduras que podem carregar o conjunto da base — a entrada/saída do plugim de filtro e a descarga final de fim-de-resposta — usam os padrões configurados por um cache TTL de 5 minutos com a guarda em esconderijo dos regex compilados por padrão, caindo ao conjunto embutido quando a base é inalcançável; a descarga progressiva token-por-token do fluxo conserva os padrões embutidos (a limitação v1 documentada). O painel inteiro localiza-se nos 8 idiomas.
  • O bypass de papéis DLP: um novo ajuste do sistema DLP_BYPASS_ROLES (um arrey JSON de nomes de papéis, por omissão []) deixa os administradores isentarem os papéis selecionados de TODA a varredura e censura DLP — a entrada/saída do plugim de filtro e o núcleo de descarga progressiva do fluxo (incluindo a varredura do contexto do RAG e a censura do pensar, tanto para a conversa JWT como para a conta de serviço do widget) honram todos a mesma barreira. Cada corrida com bypass dispara um evento de auditoria fire-and-forget dlp.bypassed registrando QUEM passou (os nomes de papéis cruzados) e a superfície de origem — nunca o conteúdo varrido. A interface de administração: uma seleção múltipla de papéis com uma advertência de exposição em Configurações → Avançado → DLP, e uma etiqueta de ação "Com bypass" no painel do Histórico de Correspondências DLP, localizados nos 8 idiomas.
  • O marco e o filtro da fonte de auditoria DLP: cada registro do evento dlp.* leva source: "conversa" | "widget" nos seus metadados — o núcleo partilhado do fluxo SSE tira a marca da cabeça X-Widget-Id do widget por isso conversas de widget censuram-se como as de conversa mas agora discerníveis no painel do Histórico de Correspondências DLP (Configurações → Avançado). O painel ganha filtros do lado do cliente por fonte (Tudo/Conversa/Widget), o ID da conversa e o utilizador.
  • A interface da administração para o ceifador dos rascunhos de carregamento: uma nova secção em Configurações → Manutenção expõe o comutador activado (upload_draft_reaper_enabled) e a expressão cron de cadência (upload_draft_reaper_cron) via PUT /api/system/settings, com um pré-tocante do lado do cliente da forma cron de cinco campos (o servidor conserva o seu comportamento advertir-e-cair para os valores inválidos), avisos de êxito/falha localizados e descrições nos 8 idiomas. .env.example documenta as chaves do ceifador como passagens por cima de variáveis de ambiente (upload_draft_reaper_enabled, upload_draft_reaper_cron, upload_draft_retention_days).
  • Configuração: a configuração do ambiente agora define-se uma vez em um esquema Zod partilhado (@simmetric-chat/shared), a precedência efectiva documenta-se e afixa-se por testes como Base de dados > Ambiente > por omissão (as mudanças da interface de Configurações ganham de imediato; uma matriz de 8 casos), e as chaves que se ignorariam porque uma variável de ambiente as passa por cima mostram-se com um distintivo envOverridden na interface de Configurações.
  • Configuração: o .env da raiz do repositório agora é a única configuração de runtime para o servidor, o collector e o widget — um novo carregador de zero dependências (loadRootEnv()) funde-o sob as passagens por cima .env por pacote (antigas), com uma advertência de obsolescência uma-por-arranque enumerando os nomes das chaves tocadas (nunca os valores); Docker e Tauri alinhados (o env_file do compose aponta ao ficheiro raiz, nenhuma imagem cose segredos).
  • Configuração: os três ficheiros .env.example dos pacotes regeneram-se dos seus esquemas Zod (83/15/6 chaves, agrupadas, valores por omissão sincronizados) com armadilhas de Jest que falham quando uma chave futura do esquema fica por-documentar; as leituras crúas de process.env conservadas fora do esquema (HF/eccentricidades de openai, as chaves de ciframento/HMAC, LOG_LEVEL, as barreiras de teste) apoiam-se por 44 sondas de guarda comportamentais.
  • UX: eliminar um espaço de trabalho agora pede um diálogo de confirmação padrão (era um mini-diálogo de confirmação em linha), os espacios de trabalho suportam a eliminação em massa multi-seleção com um diálogo de confirmação e um aviso de resultados contando as linhas saltadas (as linhas sem permissão saltam-se, nunca uma falha silenciosa), a pré-visualização OCR lado a lado conserva o texto extraído atado à mesma porção de página que o painel de imagem, e o detalhe da corrida de síntese mostra o botão de aprovação desativado com a razão explícita nas corridas que ainda não se podem aprovar.
  • UX: as caixas de seleção de espacios de trabalho e a eliminação em massa localizam-se nos 8 idiomas.

Changed

  • O ceifador dos rascunhos de carregamento é totalmente configurável via os ajustes do sistema: um comutador activado/desativado (upload_draft_reaper_enabled, omissão "verdadeiro") e uma passagem por cima da cadência (upload_draft_reaper_cron, omissão diária 03:00 UTC), ambos geridos via PUT /api/system/settings. Os valores cron inválidos levam uma advertência ao registro e caem à cadência por omissão em vez de falhar o arranque; o comportamento por omissão não muda.

Fixed

  • Corrigido um ciclo de desastre de arranque em produção Docker onde o arranque do servidor morria com um fallo da restrição única em api_keys.prefix: a semeadora da chave API do widget agora deriva o prefixo de exposição do resumo HMAC e tolera as colisões de prefixo (verificar de novo pelo resumo da chave, uma advertência em vez de desastre), e a geração da chave API torna a tentar com uma chave nova numa colisão de prefixo (no máximo 3 tentativas).
  • As citações da conversa agora espelham as fontes em que uma resposta verdadeiramente se funda em vez de tudo o que as ferramentas de busca apanharam: depois da desduplicação das citações, um filtro de fundação limita as citações por documento à cima-2 por pontuação de relevância e deixa cair as citações de bloco cujo texto não partilha sobreposição léxica significativa com a resposta final (uma barreira conservadora — as páginas wiki/arquivo, os resultados teia/memória, as citações sem texto de bloco e as respostas muito curtas sempre se conservam). As respostas "sem mais informação" já não mostram "Fonti (5)" de um só documento a ~1,5% de relevância; a isenção "Sem evidência de apoio" julga-se do mesmo modo ao conjunto filtrado (tanto a via de fluxo como a de não-fluxo). Apenas a tubagem das citações — nenhuma mudança de interface, i18n ou esquema.
  • Docker: o exame de saúde do serviço ollama substituiu a sonda curl (a imagem ollama/ollama não inclui curl/wget/nó — o containter reportava "unhealthy" por sempre) por uma sonda ollama ls; o exame de saúde qdrant substituiu a sonda wget (a imagem não inclui wget/busybox) por um tocamento de soquete bash /dev/tcp — ambos os serviços agora reportam saudável. O docker/entrypoint.sh do containter único agora processa provision-encryption-key.sh (a paridade pela imagem separada): os arranques de produção autónomos auto-fornecem ENCRYPTION_KEY + API_KEY_HMAC_SECRET em vez de falhar a barreira. Os cinco Dockerfile limpam-se no lint sob hadolint (a política documentada de dependências sem afixar + as renúncias de ENV de marcador de lugar).
  • Os eventos da cronologia da auditoria DLP já não mostram "sem detalhes da correspondência" quando o conteúdo casado chegou num pedaço cedo do fluxo: o evento de fluxo dlp.output_match agora deriva os seus tipos de casamento E a sua lista de textos casados das correspondências acumuladas por toda a corrida (todas as descargas progressivas mais a varredura final da cauda) em vez da varredura de somente-a-última-pedaço — linhas históricas podiam guardar um arrajão matchTypes vazio enquanto a linha ainda mostra uma placa de tipo de casamento. O texto casado guarda-se pelo precedente do plugim de filtro DLP (dlp.input_match / dlp.output_match sem-fluxo já o levam); o painel de auditoria é só-de-administração e bloqueado pela Enterprise. A vista desdobrada do painel agora desenha a lista dos tipos detetados com um indicio "só-de-tipo" para os eventos que registram tipos de casamento sem casamentos (localizado nos 8 idiomas), conservando o recurso "sem detalhes" apenas para os metadados verdadeiramente vazios.
  • Os ficheiros carregados ficam disponíveis para tornar a tentar: as retroligações do estado terminal do collector já não apagam os ficheiros de rascunho arrumados de storage/uploads/drafts/ (a retroligação de qualquer documento irmão podia apagar o ficheiro de que cada novíssima tentativa depende), e /tocar-de-novo//atribuir curam-s solos restaurando o ficheiro de rascunho arrumado da cópia OCR persistente (storage/ocr-sources/) quando presente em vez de falhar com "já não existe no disco".
  • Eliminar um rascunho de carregamento falho e caducado já não devolve bloqueado (409) — DELETE /api/uploads/:id agora trata os estados da perna terminal (completada ou falhada) como apagáveis e recusa só os rascunhos com uma perna verdadeiramente em voo (activada não-terminal), e a eliminação em massa do frontend usa o mesmo predicado em-voo que as placas do filtro, por isso o que a interface oferece apagar é exactamente o que o servidor aceita.
  • A novíssima tentativa RAG/KB de um carregamento cujo ficheiro de rascunho arrumado falta agora devolve um erro claro de vir-a-carregar (400) em vez de um aviso falso de êxito, as pernas do collector falhadas já não apagam o ficheiro de rascunho arrumado de storage/uploads/drafts/ (as novíssimas tentativas continuam possíveis — o ceifador 24h e a rota DELETE ficam como os únicos apagadores de ficheiros de rascunho), e os avisos das novas tentativas agora espelham o desenlace por-perna: uma perna recusada sobre HTTP 200 mostra um aviso de erro localizado e o recenseamento da tentativa em massa conta-a como falhada.
  • As implantações Docker de produção auto-fornecem uma ENCRYPTION_KEY persistente (um volume de almoxarifado do servidor, /app/storage/.encryption-key) em vez de meter-se num ciclo de desastre sobre o valor por omissão duro. O ponto de entrada do servidor gera a chave uma vez antes de qualquer passo Prisma, restaura-a a cada reinício/remontagem, prefere um valor fornecido pelo operador e falha sonora sobre uma chave persistida corrompida. .env.example e docs/DEPLOYMENT.md actualizados para marcar a variável Obrigatória-em-produção. Veja docs/ENCRYPTION_KEY_ROTATION.md para as notas Docker.
  • As páginas OCR da Base de Conhecimento já não falham quando o fluxo de visão Ollama fecha-se pelo motor de modelos sem a marca final done depois da transcrição completa (observado no motor personalizada glm-ocr): ocrPage agora salva a saída acumulada não-vazia como o resultado da página com uma bandeira truncated e um registro de advertência, em vez de atirar fora uma transcrição real atrás de "Não recebeu done ou resposta de sucesso no fluxo.". Um fluxo sem done e sem conteúdo ainda faz falhar a página.
  • Os sumários dos trabalhos OCR são verazes sobre a saúde das páginas: as páginas que terminam com uma marca [FAILED: contam-se e guardam-se como failedPages no JSON do resultado do trabalho, e os registros da conclusão emitem uma advertência "[ocr] Trabalho completado com páginas falhadas", por isso um trabalho COMPLETADO com páginas falhadas já não é indistinguível de um êxito limpo (zero tokens + uma pontuação de qualidade ao pavimento por sós eram ambíguos).
  • Documentado e corrigido o modo de falha para as implantações Enterprise: quando @simmetric-chat/shared ganha ficheiros de fonte NOVOS, uma fotografia fora de data da dependência file: enterprise pode fazer cair o servidor ao arrancar com "Não pode encontrar o módulo './env.schema'" — o pnpm tira a fotografia do pacote file: a node_modules/.pnpm e os ficheiros criados depois do último pnpm install nunca chegam a essa fotografia (os vínculos duros atualizam apenas os ficheiros já existentes), por isso o schemas/index.js da fotografia precisa um módulo que não existe do lado enterprise, e a política falha-sonora do carregador transforma o fallo do registro do plugim num ciclo de desastre de arranque. A correção viaja como documentação: o caderno de rota da remontagem do repositório enterprise agora cobre a distinção reinstalar-não-remontar (refresca a fotografia com pnpm install, depois pnpm build), e o caderno de rota da instalação air-gap em docs/ENTERPRISE_PLUGIN.md ganha a subsecção de solução de problemas correspondente (o sintoma, a causa, a correção, o tocamento de cordura) para que os operadores sem o repositório privado ainda encontrem o remédio.
v0.21.0
2026-08-28

Varredura das dívidas & higiene da publicação (era: v1.5.0)

Added

  • docs/LICENSE_DECISION.md — a análise do modelo de licença para a distribuição dupla (comunidade + enterprise). Decisão: AGPL-3.0 (comunidade) + propriedade comercial (enterprise).
  • LICENSE substituído Apache-2.0 → GNU AGPL-3.0 (o repositório de comunidade).
  • LICENSE_EE.md — a licença comercial proprietária do plugim enterprise (@simmetric-chat/enterprise).
  • NOTICE — a explicação da dupla licença na raiz do repositório.
  • CLA.md — o Acordo da Licença do Colaborador (v1.0) — o copyright + a concessão da patente, consciente da dupla licença. Requerido para todas as contribuições externas.
  • As cabesas do copyright AGPL-3.0 acrescentadas a todos os 981 ficheiros de fonte através de 5 pacotes (.ts/.tsx). Aplicadas via scripts/add-headers.cjs (idempotente).
  • docs/ENTERPRISE_LICENSE_TERMS.md — os termos comerciais/nível de serviço à altura do operador para clientes enterprise.
  • .github/workflows/release.yml — GitHub Release + a publicação das imagens GHCR ao empurrar a marca.
  • CHANGELOG.md (este ficheiro) — o formato Keep-a-Changelog, semeado da história dos marcos.
  • .planning/milestones/v1.5-debt-sweep-ROADMAP.md — o âmbito do marco da avaliação das dívidas (o porte E2E para a frente, as sessões de depuração, a triagem das tarefas rápidas, a disciplina da sincronização dos selos de versão).

Changed

  • A versão package.json empurrou-se 0.17.0 → 1.4.0 (sincronizada com a última marca git v1.4). As versões por-pacote ficam a 0.22.0/0.23.0 (pacotes privados, não publicados em nenhum registo).
  • O campo license de package.json + todos os packages/*/package.json estabelecido em AGPL-3.0-or-later.
  • A placa da licença do README.md atualizada Apache-2.0 → AGPL-3.0; a secção da licença atualizada para descrever o modelo da dupla licença; a secção Contribuição acrescentada com o requisito do CLA.
  • CONTRIBUTING.md atualizado com as instruções de assinatura do CLA.
v0.20.1
2026-08-27

Escala horizontal (a consumação da camada Redis) (era: v1.4)

Objetivo: fechar os 4 itens adiados da ESCALA (CSW-19 + ESCALA-02/03/04) e provar o redimensionamento horizontal com teste de integração multi-instância real — ativando uma implantação de servidor multi-instância segura atrás de um equilibrador de carga.

Entregue: 7 fases (161–167), 13 planos, 29 tarefas. Todos os 22/22 requisitos satisfeitos. Marca git: v1.4.

Added

  • ESCALA-01 (CSW-19): os 3 agendadores restantes embrulhados com fechadura → depois os 8 migrados às tarefas cron pg-boss; setInterval + isRunning + withDistributedLock removidos de todos os 8 agendadores migrados.
  • ESCALA-02: o recurso scryptSync de ENCRYPTION_KEY removido em produção — um arranque falha-sonoro (logger.error + process.exit(1)) + um lançamento do serviço de defesa-in-profundidade; dev/teste conserva scrypt; o caderno da rotação atualizado.
  • ESCALA-03: a verificação da chave API dum ciclo bcrypt → uma busca de resumo O(1) findUnique({key_hash}) à HMAC apoiada; uma migração quebradíça DROP COLUMN hashedKey + ADD COLUMN key_hash; a variável de ambiente API_KEY_HMAC_SECRET; o caderno de rota da migração ao operador docs/API_KEY_MIGRATION.md.
  • ESCALA-04: 8 agendadores setInterval migrados a pg-boss (uma fila de tarefas carregada pelo PostgreSQL, amiga do air gap); 2 sondas de 10s sensíveis à latência (OCR + síntese) ficam como setInterval; o singleton pg-boss (jobQueue.ts) com degradação graciosa.
  • A verificação do fan-out SSE: o adiamento entre-instâncias fechado — 4 testes entre-instâncias com base em simulações provando o retransmitir SSE Redis pub/sub (A publica → B retransmite, o salto da origem, o desnudar, a degradação).
  • A doc ao operador: docs/SCALING.md (377 linhas) — a topologia da implantação multi-instância, o requisito REDIS_URL, a dependência PostgreSQL do pg-boss, os referos cruzados ESCALA-02/03, as sondas de 10s, a degradação graciosa.

Mudanças que partem — ação do operador requerida

  • `ENCRYPTION_KEY` deve estabelecer-se no .env de produção (base64, 32 bytes) ou o servidor recusa-se a arrancar. Veja docs/ENCRYPTION_KEY_ROTATION.md.
  • As chaves API mudaram de bcrypt (apiKeyHash) → HMAC-SHA256 (key_hash). Os operadores devem re-emitir as chaves API do widget/collector. Veja docs/API_KEY_MIGRATION.md.

Infra-estrutura

  • Nova dependência: pg-boss v12.28.0 (servidor). Nova variável de ambiente: API_KEY_HMAC_SECRET. A ordem do arranque mudou — a inicialização pg-boss é agora o 7.º passo de init assíncrono com um desarmamento ao desligar.
v0.20.0
2026-08-26

A varredura das PREOCUPAÇÕES (era: v1.3)

Objetivo: varrer a auditoria PREOCUPAÇÕES.md de 2026-08-26 — corrigir todas as vitórias fáceis diretamente, atacar os itens de esforço médio em fases focadas, e avaliar (adiar ou documentar) os pontos bloqueantes/de grande esforço.

Entregue: 7 fases (154–160), 14 planos. Um marco de endurecimento brownfield; nenhuma função nova visível ao utilizador.

Added

  • a correção do gitignore da doc, o mapa de ficheiros de chat.ts, as viradas das regras de lint.
  • as vitórias fáceis do endurecimento de código (o teto take:10 do apiKeyMiddleware, etc.).
  • a redução da dívida de lint do frontend (3 planos, as viradas aviso→erro).
  • a advertência do arranque de produção da ENCRYPTION_KEY (CSW-11) + a geração em lote de título+etiquetas+seguintes.
  • as cisões de módulos (a extração do analisador dos fornecedores, os registros de capacidades).
  • a cobertura de testes (chatImportService, chatExportService, apiKeyService, outros 5 serviços; o collector + o widget cabeados ao config raiz do jest).
  • avaliado e documentado (os direitos JWT da licença do .env, o caderno de rota da rotação da chave de assinatura da licença, a correção da aserção caducada de AGENTS.md).
v0.19.1
2026-08-25

Os refinamentos (era: v1.2)

Objetivo: a marca, o descredito da IA, a reimplementação em TypeScript do algoritmo de grafo da wiki, as correções da busca RAG, o endurecimento do MCP, o primeiro arranque do Assistente de Configuração.

Entregue: 6 fases (149–153 + 153.1).

Added

  • BRAND-01/02: o favicon SVG do monograma S, o componente Monograma reutilizável, o descredito da IA localizado e atenuado sob cada mensagem do assistente (7 idiomas).
  • MCP-01/02/03: o endurecimento do MCP — o mapa de transporte SSE por-sessão, a barreira de auth Portadora MCP_API_KEY, a IDOR de list_workspaces fechada, o getMCPToolsForWorkspace cabeado em resolveSkillsForChat.
  • RAG-01/02/03: a correção da localização do FTS (a tokenização multi-idioma para it/ru/de/es/fr/zh), a desduplicação dos resultados de recurso rag_search↔wiki_query, o prompt de oferta da LLM para rag_search vs wiki_query.
  • WIKI-01: a reimplementação em TypeScript do algoritmo Graphify da wiki (a deteção da comunidade + a geração markdown da wiki, uma reimplementação limpa da Apache-2.0, ~280 LOC Python → TS).
  • WIZ-01/02: a interface do primeiro arranque do Assistente de Configuração (multi-passo: a conta da administração + a configuração do fornecedor + a base de vetores, o consumo de /api/sistema/inicializa); SEED_BOOTSTRAP_ADMIN desativado enquanto o Assistente activa-se.
  • .1: a limpeza da dívida técnica da v1.2 (a virada aviso→erro do Compiler de React).
v0.19.0
2026-08-19

Arquitetura de plugim Enterprise (era: v1.1)

Objetivo: refazer o modelo de licenciamento de "bandeiras de funções em execução na fonte pública" a um "plugim enterprise privado separado": as funções enterprise (SSO, log de auditoria, folha-branca, cópia de segurança) vivem em um pacote privado entregado apenas aos clientes pagantes; o repositório de comunidade não contém nenhum código de funções enterprise. As funções de mercancia tornam-se sempre-LIGADAS na comunidade.

Entregue: 9 fases (140–148), 19 planos, 26 tarefas.

Added

  • EPA-01: os tipos PluginContext/EnterprisePlugin em shared; a costura do carregador loadEnterprisePlugin(app) / shutdownEnterprisePlugin() com require.resolve→require em dois passos (MODULE_NOT_FOUND = no-op de comunidade, outros erros = process.exit(1) falha-sonoro).
  • EPA-02: 9 bandeiras de mercancia removidas das FEATURE_FLAGS (20→10): web_search, webhooks, push_notifications, memória, exportação_de_líderes, widget_analytics, auto_título, taxe_de_síntese. As rotas de mercancia sempre-LIGADAS.
  • EPA-03: SSO (SAML + OIDC + SCIM 2.0) extraído ao plugim enterprise.
  • EPA-04: o log de auditoria (a tabela event_log imutável + um papel da base INSERT-só) extraído ao plugim enterprise.
  • EPA-05: a marca de folha branca (o gancho de IoC do validador de chave de configuração) extraído ao plugim enterprise.
  • EPA-06: a Cópia de Segurança (~1400 LOC: backupService, o registo dos fornededores, o agendador Bree, a restauração, as rotes, a interface) extraído ao plugim enterprise.
  • EPA-07: o resolvedor overrideFeatureLimit com revogação reativa; omanifesto GET /api/enterprise/modules; o carregamento condicional React.lazy de 4 painéis enterprise.
  • EPA-08/09/10: priority_support removido; custom_agents = um limite numérico (comunidade 3, enterprise Infinito); licensePayloadSchema idêntico ao bit (RS256).
  • EPA-12: o alinhamento docs/environmento (docs/ENTERPRISE_PLUGIN.md, o montar de volume docker-compose, a cisão .env.example), a instalação CI de buranco de ar (uma barreira de grep no serviço de licença para as primitivas HTTP).
v0.18.0
2026-08-12

O candidato da publicação pública (simmetric-chat) (era: v1.0)

Objetivo: preparar e publicar o projeto como um repositório público limpo, renomeado a simmetric-chat, sem segredos/dados pessoais/caminhos locais, com a verificação das licenças das dependências, o gerador de chaves enterprise (separado, não publicado), e a documentação completa.

Entregue: 8 fases (132–139), 12 planos, 16 tarefas. O repositório público publicado, o renomeação completa, a auditoria das licenças, o gerador de chaves enterprise (HS256 → migrado ao RS256 depois da v1.0), as documentações canónicas, o CI das Ações do GitHub, um repositório fresco.

v0.20
2026-08-11

A interface do widget, a i18n e a fiabilidade

Entregue: 7 fases (125–131), 32 planos, 75 tarefas, os 16/16 requisitos satisfeitos.

Added

  • WID-ADMIN: a subpage com abas da criação/edição do widget (/widgets/:id + /widgets/new), o painel da pré-visão ao vivo, a guarda de sujidade, a aba dos potenciais.
  • WID-I18N: o transporte por blocos de JSON (o assassinato do bug %20), a cadeia de recurso visitante→por omissão do widget→en, a localização da concha + do conteúdo a 7 idiomas, o fiado da localização widget→proxy→servidor→orquesterador.
  • WID-QUEST: as perguntas sugeridas de três estados (Predeterminadas/Nenhuma/Personalizadas), grupos a 7 idiomas, a correção do transbordo line-clamp.
  • WID-CREDITS: os créditos do rodapé + a placa da IA, a remoção barrada da folha-branca, o relé do pop-up de incorporação real verificados.
  • WID-BUGS: os botões de entrada de 44px, a ida-e-volta do FAB do anfitrião, o contrato de ícones só-SVG, o cartel de contato, o laço da wiki, a persistência dos potenciais, a i18n do RAG degradado.
v0.19
2026-08-08

Diagnóstico e Dívidas

Entregue: 5 fases (120–124). O diagnóstico da licença (os registros do arranque, um ponto final de diagnóstico só-para-administração, o script pnpm license:verificação), a dívida de testes (a deriva ESM, a LoginPage QueryClientProvider, a espécie da forma das simulações), a avaliação da dívida técnica (a ponteia da camada de escala Redis — adiada à v1.4).

v0.18
2026-08-04

Estabilização

Entregue: 4 fases (116–119).

v0.17
2026-08-02

A empresa e o Acabamento

Entregue: 8 fases (108–115).

Os marcos anteriores (v0.1–v0.16) documentam-se em .planning/PROJECT.md (a secção da História de Marcos). A história completa a nível de fases vive em .planning/MILESTONES.md.