BeryX — Capacidades por critério de avaliação
Documento público. Organiza o que o BeryX faz pelos critérios com que os analistas avaliam ferramentas de qualidade de dados (profiling, monitorização, parsing e normalização, matching, limpeza, enriquecimento, gestão de metadados, orquestração e experiência para utilizadores de negócio). É a fonte técnica da página
/rfpdo site: cada secção diz o que existe, onde está no código e que testes o provam — e, quando é parcial, o que falta.Segurança e conformidade estão em SEGURANCA.md. A cobertura por dimensão de qualidade (DAMA, ISO 8000, ISO/IEC 25012) e por área DAMA-DMBOK está em COBERTURA_QUALIDADE.md.
Profiling
Descoberta automática e perfil de cada tabela em todas as fontes ligadas: contagens, nulos, distintos, mínimo/máximo/média/desvio, cardinalidade, distribuições e amostras limitadas.
- Mais de 20 tipos de fonte: warehouses (BigQuery, Snowflake, Databricks, Amazon Redshift, Fabric, Azure Synapse), bases de dados operacionais (PostgreSQL, MySQL, Oracle Database, SQL Server e Azure SQL — é nestes dois que correm os ERPs Primavera, PHC e Sage), MongoDB (schema inferido por amostra), aplicações de negócio (SAP S/4HANA e SAP Gateway por OData, Salesforce, Dynamics 365 e outros serviços OData v2 e v4), ficheiros, folhas Google (cada separador é um dataset), armazenamento de objetos (GCS, S3, Azure Blob e ADLS Gen2), SFTP, streaming (Kafka, Pub/Sub) e fontes documentais — lista e setup no guia de conectores.
- Ficheiros Excel (
.xlsx,.xlsm) numa pasta, num bucket ou num SFTP: cada folha é um dataset, ocultas incluídas, com o valor em cache das fórmulas e o aviso das que foram gravadas sem calcular. O.xlsantigo fica fora. - Profiling incremental: em tabelas grandes com coluna temporal, o re-scan lê só a janela nova e combina-a com o perfil anterior (com um perfil completo periódico).
- Documentos (SharePoint, Google Drive) têm um perfil próprio de qualidade documental.
Testes: backend/tests/test_incremental_profiling.py, backend/tests/test_profile_distributions.py.
Monitorização
Monitores automáticos por omissão em cada tabela da conexão (até 500 por conexão em cada scan), sem configuração e a partir dos metadados de cada fonte (nas bases de dados, só do catálogo): atualidade (aprende a cadência de atualização), volume (z-score modificado sobre o histórico) e mudanças de schema, conforme o que cada fonte expõe (método).
Deteção de drift e anomalias: colunas adicionadas/removidas, mudança de tipo, picos de nulos, quedas de volume, drift de distribuição por coluna (quantis e categorias já perfilados, método), anomalias sazonais e por segmento.
Metodologia publicada: fórmulas, pesos, limiares e limitações do score, das anomalias, dos duplicados, do trust score e da amostragem em METODOS.md, com uma secção «Como calculamos» na app.
Motor de regras com 16 tipos de regra: nulos (
not_null), unicidade (unique), padrões (pattern), intervalos (range), atualidade (freshness), volume mínimo (row_count_min), validade por dígito de controlo (valid_identifier, registo de identificadores por país) — NIF/NIPC, NISS, NIB e Cartão de Cidadão; DNI, NIE e NIF de empresa de Espanha; CPF, CNPJ (também o alfanumérico) e PIS/PASEP do Brasil; AVS/AHV e UID da Suíça; NHS number e VAT GB do Reino Unido; IBAN com a estrutura e o comprimento do país (registo SWIFT e Angola); Luhn. Sem dígito de controlo publicado, só a estrutura: SSN, EIN e ITIN dos EUA, NINO e Companies House do Reino Unido, NIF e BI de Angola —, listas de referência (in_reference_set: as ISO incluídas ou as do registo de dados de referência do workspace, com versões, mapeamentos de códigos entre sistemas e uma verificação de coerência entre datasets), condições entre colunas da mesma linha numa linguagem de expressões fechada — «data_fim >= data_inicio», «total = soma(subtotal, iva)», «se pais = 'PT' então válido(nif, 'nif')» — (expression, com o dígito de controlo de NIF, NISS, CPF, CNPJ, DNI, NIE, NHS number, AVS/AHV e UID verificado na própria fonte), SQL à medida só de leitura — um SELECT das linhas violadoras, com zero ou até N linhas esperadas — (custom_sql), integridade referencial (referential: chaves órfãs face a outro dataset, por pushdown na mesma ligação ou anti-join em memória entre ligações, proposta pelo DETGEN para cada aresta do lineage), exatidão contra fontes autoritativas (accuracy, abaixo) e, nos streams, idade do evento (max_event_age), conformidade com o Schema Registry (schema_conformity), campos obrigatórios (required_fields) e mensagens ilegíveis (max_malformed_pct). As regras de expressão e de SQL correm na fonte, sobre a tabela inteira, e só nas fontes SQL; em ficheiros, APIs e streams ficam «não avaliáveis». O SQL do cliente passa por uma guarda — um só SELECT, sem escrita, só funções de uma lista e só tabelas da ligação — e corre numa transação sempre desfeita, com só-leitura e tempo máximo na sessão onde o motor os tem (DBA.md). Checks em Python à medida continuam fora. As regras chegam por packs por indústria (e-commerce, financeiro, saúde, SaaS), por sugestões a partir do perfil e do histórico de aceitação do workspace — só entram quando um administrador as aceita — ou pelo import de dbt (também os testes singulares, que viram regras de SQL à medida), Great Expectations, SodaCL e ODCS (o que converte). As suites legadas do Informatica Data Quality e do Collibra DQ entram pelo mesmo import, a partir dos exports de regras, com um relatório de cobertura da migração regra a regra (como migrar).Exatidão contra fontes autoritativas (regra
accuracy, cartão #266). Um NIF com o dígito de controlo certo pode não existir: a regra pergunta a uma fonte se o valor existe. Número de IVA no VIES da Comissão Europeia (Estados-Membros e Irlanda do Norte), código postal CP7 e localidade na GeoAPI.pt (dados dos CTT), telefone no plano de numeração do país com a bibliotecaphonenumbers(E.164, tipo e país, sem rede), email por sintaxe e registo MX do domínio (DNS sobre HTTPS, sem enviar nada à caixa) e países e moedas nas listas ISO publicadas, descarregadas com atualização periódica. É opt-in: um admin autoriza cada fornecedor emPUT /api/v1/workspace/accuracye, sem autorização, nenhum valor sai e a regra fica «não avaliada». Os pedidos são só GET pelo cliente HTTP só-leitura dos conectores, com a guarda anti-SSRF; as respostas ficam em cache no worker, separadas por workspace, cada fornecedor tem um intervalo mínimo entre pedidos e cada scan tem um teto de consultas (DATAVAULT_ACCURACY_MAX_LOOKUPS_PER_SCAN). O que falha abre um issue de exatidão com exemplos mascarados. Os números de IVA do Reino Unido e da Suíça ficam por verificar: os serviços oficiais (HMRC e UID) exigem POST (backend/app/domain/quality/accuracy.py,backend/app/domain/scans/accuracy.py).Regras portuguesas só com dados de forma portuguesa. O que é próprio de Portugal deixa de se aplicar a dados com outra forma (
backend/app/domain/quality/pt_shape.py):- a lista de CP4 exige o CP7 em maioria na amostra (NNNN-NNN, também com espaço ou sem separador). Uma coluna que só guarde o CP4 («1000») deixa de receber essa regra, porque não se distingue de um código suíço. Um código postal japonês escrito sem hífen («1000001») continua a passar por português;
- o NIF e o código postal do pack de e-commerce em colunas
vat_number,tax_id,zipoupostal_codeexigem a mesma forma na amostra do último scan. Um VAT britânico, um EIN americano ou um UID suíço não têm a forma do NIF; - na correspondência de telefones, um número sem indicativo casa com o mesmo número com
indicativo estrangeiro («923 456 789» com «+244 923 456 789»,
backend/app/domain/quality/matching.py). Sem indicativos estrangeiros na chave, o +351 aplica-se aos 9 dígitos. Com o país dos dadosPT(a omissão), o caso espelho também casa: um telemóvel português «912 345 678» liga-se a «+244 912 345 678» quando é esse o único número da chave com os mesmos 9 dígitos. Com outro país num dos lados (ver o país dos dados, abaixo), o número sem indicativo desse lado leva o indicativo do país (+34 em Espanha) e nunca o +351; - na deteção de dados pessoais, um telefone de outro país conta como telefone: com
+, com o DDD brasileiro («(11) 91234-5678») ou, numa coluna com nome de telefone, só pelos dígitos. O nome conta quando tem uma palavra de telefone (telefone_alt,phone_number,tlm) ou quando é sócelular,numero_celular,contato,contactoouwhatsapp;canal_contacto,modelo_celulareis_mobilenão contam. Datas, decimais, o CP7 e o CEP nunca contam como telefone. Deixa de passar por morada sem sair do PII (backend/app/domain/compliance/pii_map.py). Sem nome de telefone, também conta o número nacional escrito com separadores e com os grupos de um telefone de PT, Espanha, Brasil, Angola, EUA, Suíça ou Reino Unido («020 7946 0958», «612 34 56 78»), quando é 80% da amostra. Numa coluna de identificador (sku,order_ref) não conta (#552).
Nas datas NN/NN/AAAA, a correção só lê o mês primeiro quando algum valor tem o 2.º campo acima de 12. Sem prova da ordem não há
UPDATE, só o aviso.País dos dados (#513). O workspace tem um país (ISO 3166-1 alfa-2,
PTpor omissão) e a conexão e o dataset podem sobrepô-lo. O scan usa o do nível mais baixo que o tenha (backend/app/domain/catalog/data_country_service.py). Não é a residência dos dados. Com um país que não sejaPT(backend/app/domain/quality/data_country.py):- o tipo semântico deixa de dar
phone_ptepostal_code_pt, e o DETGEN não propõe a lista de CP4 dos CTT nem o padrão do telefone português (backend/app/domain/quality/semantic_types.py,backend/app/domain/rules/generation.py); - os telefones passam a ser o tipo
phonecom a região do país, quando a maioria da amostra são números atribuídos no plano de numeração desse país. O plano vem dophonenumbers, sem rede (backend/app/domain/quality/phones.py, #516). Um valor com parte decimal nunca conta. Sem pista de telefone no nome da coluna, só contam os números escritos com «+», «00» ou separadores, e com os grupos de um telefone desse plano: o processo «2024/12345» não conta. Uma coluna de tipo numérico não éphone(backend/app/domain/quality/semantic_types.py); - a standardização não propõe o +351 nem o CP7, as moradas não se leem como portuguesas e não
há enriquecimento por distrito e concelho
(
backend/app/domain/insights/standardization.py); - a standardização de telefones propõe o E.164 do país, por exemplo +34 num dataset espanhol.
Um número escrito com «+» ou «00» só se converte com o indicativo do país. Os números de
outro país ficam como estão, para revisão manual, e contam na tolerância da regra que
protege a coluna. Em Portugal, o mesmo vale para o +351
(
backend/app/domain/insights/phone_standardization.py); - na correspondência, cada telefone passa a E.164 no plano do país do seu lado. «923 456 789» de um dataset angolano casa com «+244 923 456 789» e não com «+351 923 456 789». Os números brasileiros com o 9.º dígito casam com «+55…», também com o 0 de tronco ou o código de operadora. No Reino Unido, na Suíça e nos EUA o formato nacional casa com o internacional;
- NIF, NISS, CC e IBAN continuam a reconhecer-se, porque o dígito de controlo prova o identificador. O mapa de PII continua a apanhar um telefone numa coluna com nome de telefone, pelas amostras.
Numa fonte SQL, uma tabela com uma coluna de país (
pais,country…) com códigos ISO de dois países ou mais, um delesPT, recebe para a coluna de NIF ou NISS a propostase pais = 'PT' então válido(nif, 'nif')quando a amostra mistura valores com e sem o dígito português (pelo menos 30 % com ele): o dígito de controlo só se pede às linhas portuguesas, na fonte.- o tipo semântico deixa de dar
SLO por dataset e incidentes por linhagem (um problema a montante agrupa os afetados a jusante).
Alertas in-app, email, Slack, Teams e webhook, com escalas de piquete e escalonamento, e SMS ou WhatsApp pela conta Twilio do cliente só para incidentes críticos e violações de SLA (mensagem curta com o link, horário silencioso e teto por número). Cada canal tem um âmbito opcional (domínios, conexões, datasets, tipos de problema, monitores automáticos), para cada equipa receber só os seus, com pré-visualização dos últimos 30 dias.
Varredura de engenharia no Databricks e no Snowflake (cartão #55). Opt-in por conexão e só de leitura: no Databricks só pedidos
GETàs APIs oficiais (notebooks, jobs, pipelines DLT e Lakeflow, Model Registry); no Snowflake sóSHOWeSELECTde catálogo (views, materialized views, procedures, tasks, streams e warehouses). Abre um problema «Engenharia» por heurística, com os ativos afetados: credenciais escritas no código (o tipo e a linha, nunca o valor),SELECT *, escrita na Gold a partir da Bronze, jobs agendados sem retry, timeout ou alerta de falha, jobs e pipelines em falha, notebooks que escrevem tabelas sem job, tasks suspensas há mais de 30 dias, streams stale, modelos ML sem re-treino há mais de 90 dias e warehouses sem auto-suspend. O código não fica guardado. O resumo por IA é opcional e só leva metadados dos achados (metadata_only). Na app, ativa-se e pede-se no cartão da conexão em Conexões, e os achados abrem-se em Problemas com o filtro de engenharia (guia).
Testes: backend/tests/test_auto_monitors.py, backend/tests/test_schema_changes.py,
backend/tests/test_anomalies.py, backend/tests/test_quality_rules.py,
backend/tests/test_slo.py, backend/tests/test_oncall_escalation.py,
backend/tests/test_alert_routing.py, backend/tests/test_engineering_scan.py,
frontend/src/components/EngineeringScanPanel.dom.test.tsx.
Parsing e normalização
Tudo é proposta. O BeryX mede a amostra do perfil e gera o SQL no dialeto da conexão e o PySpark. Quem aplica é o cliente, e nada é escrito na fonte.
Tipos semânticos detetados automaticamente por coluna, a partir dos valores e do nome: email, URL, UUID, telefone, NIF, NISS, IBAN, Cartão de Cidadão, código postal, país, moeda, datas, percentagens, montantes, nomes e moradas (
backend/app/domain/quality/semantic_types.py). Os tipos com formato fixo dão uma regra de padrão ou de dígito de controlo sugerida (backend/app/domain/rules/generation.py).Standardização de formatos: telefones, códigos postais, NIF, IBAN e datas guardados em formatos mistos passam à forma canónica. O passo 1 conta as linhas por máscara e o passo 2 é um
UPDATEproposto. Ao aceitar, o BeryX propõe a regrapatternque impede os formatos antigos de voltar (backend/app/domain/insights/standardization.py).Decomposição de campos compostos por regras de locale (pt, es, en), sem serviços externos (
backend/app/domain/quality/parsing.py):- moradas → rua, número, andar, código postal, localidade e país;
- nomes completos → título, primeiro nome, apelidos (com «de/da/del/y») e último apelido, também na forma «Apelido, Nome»;
- telefones → indicativo, número nacional, extensão e E.164;
- NIF ou DNI/NIE/CIF junto com o nome → identificador validado pelo dígito de controlo e nome;
- datas em texto livre (meses e dias da semana em português, espanhol e inglês) → data ISO;
- montantes com moeda → valor decimal e código ISO da moeda.
O locale e a ordem dia/mês leem-se dos próprios valores. Uma coluna com um tipo semântico atómico (NIF, IBAN, email, código postal, data ISO…) nunca é tratada como composta, e sem pista no nome um telefone só se reconhece com «+», «00» ou separadores (
ATOMIC_SEMANTIC_KINDSembackend/app/domain/quality/parsing.py). Para cada coluna, o relatório dá a taxa de sucesso na amostra, a cobertura de cada componente e as máscaras dos valores que não se decompõem, nunca os valores. Num corpus de teste com formatos mistos, decompõem-se 30 de 30 moradas PT/ES, 30 de 30 nomes, 29 de 30 telefones e 30 de 30 datas.Proposta de normalização (sugestão
parse_standardize): umSELECTsó de leitura com as colunas derivadas e a contagem das linhas reconhecidas, mais o PySpark equivalente. As colunas derivadas só se preenchem nas linhas que a contagem dá como reconhecidas e ficam a NULL nas outras. Nunca háUPDATE(backend/app/domain/insights/parsing_sql.py). Uma coluna que já tem standardização não recebe a decomposição. Como qualquer sugestão aceite, abre um pull request no repositório do cliente (limpeza e remediação).Na app: o painel «Parsing» no detalhe do dataset mostra os componentes detetados, a cobertura e os formatos que falham. O botão «Propor normalização» mostra o SQL e o PySpark da sugestão. A API é
GET /api/v1/datasets/{id}/parsing.Deteção de variantes do mesmo valor numa coluna categórica (maiúsculas, espaços), com proposta de normalização em SQL e PySpark.
Contratos de dados ODCS com tipos e formatos, validáveis em CI (gate de qualidade em CI).
Limites: o SQL usa só funções que existem em PostgreSQL, BigQuery, Snowflake, Databricks e
MySQL. SQL Server/Fabric e SQLite não têm REGEXP_REPLACE e recebem só o PySpark. A coluna
data_iso sai em texto e só se preenche com dias que existem no calendário. No SQL, o NIF e o
DNI/NIE reconhecem-se pela forma; o dígito de controlo só se valida no relatório da amostra. Nos
telefones, os indicativos reconhecidos são os de uma lista fixa (Portugal, Espanha, Reino Unido,
EUA, França, Alemanha, Itália, Suíça, Luxemburgo, Bélgica, Países Baixos, Irlanda, Brasil,
Angola, Moçambique e Cabo Verde), e os outros não se decompõem.
Testes: backend/tests/test_parsing_composite.py (corpus e componentes),
backend/tests/test_parsing_sql.py (SQL compilado em PostgreSQL e BigQuery, colunas derivadas
a NULL fora das linhas reconhecidas, PySpark e API), backend/tests/test_postgres_only.py (o SQL
executado em PostgreSQL no job backend-postgres), backend/tests/test_suggestions_standardization.py,
backend/tests/test_suggestions.py, backend/tests/test_quality_rules.py.
Matching e deduplicação
Tudo é proposta. O BeryX pontua e agrupa os registos, a decisão de quem revê fica registada no BeryX e a consolidação sai em SQL e PySpark só de leitura. Nada é escrito na fonte.
- Deteção de duplicados exatos (hash) e aproximados (semelhança textual com
rapidfuzz) dentro de um dataset, agrupados em clusters que se reveem, fundem ou ignoram na UI, com proposta de consolidação em SQL (ROW_NUMBERde deduplicação) e PySpark. - Correspondência entre dois datasets, também de fontes diferentes (ex.: clientes no CRM e no
ERP — resolução de entidades): chaves exatas, normalizadas (email, telefone, NIF, nome, código
postal) ou aproximadas, com pares, linhas sem par, ambíguas e cobertura, na página
«Correspondências» (
backend/app/domain/quality/matching.py, método). Para cada par, o registo que ficaria (mais completo, depois mais recente, depois o lado A), com o motivo e umSELECTde diagnóstico. - Regras de correspondência configuráveis por campo, guardadas por par de datasets
(
backend/app/domain/quality/matching_rules.py): para cada campo, o comparador, o normalizador, o peso e a semelhança mínima. Os comparadores sãoexact,normalized,jaro_winkler,token(token_sort_ratio),numericcom tolerância edatecom janela em dias (backend/app/domain/quality/matching_comparators.py). Nos normalizadores, «Nome de empresa» (company_name) tira a forma jurídica do fim do nome (Lda, Unipessoal, S.A., S.L., Ltda, EIRELI, Inc, LLC, Ltd, PLC, AG, GmbH, Sàrl) e «Nome de pessoa» (person_name) ignora as partículas («de», «da», «dos», «del», «van») e aceita um nome sem alguns apelidos com 0,9 (backend/app/domain/quality/entity_names.py). O blocking é por chave, por prefixo ou por um código fonético do português, e há dois limiares: correspondência (0,85 por omissão) e revisão (0,70). Na UI, as colunas de cada lado escolhem-se do schema dos dois datasets e os campos (até 12) e as regras de blocking (até 4) acrescentam-se e removem-se. Sem campos escolhidos, a regra nasce das chaves sugeridas. - Clusters revistos na UI: cada linha liga-se ao seu melhor candidato e as ligações formam clusters entre os dois datasets. Um cluster fica em «correspondência» quando a ligação mais fraca passa o limiar de correspondência, e em «revisão» quando fica entre os dois limiares. Na secção «Correspondências: regras e clusters» da vista «Entre datasets» de Duplicados, cada cluster mostra a semelhança de cada campo e marca-se como fundido ou ignorado. A decisão liga-se às linhas do cluster, e não à execução: vale nas execuções seguintes enquanto o cluster tiver as mesmas linhas, e fica no trilho de auditoria.
- Regras de sobrevivência por campo (
backend/app/domain/quality/survivorship.py): fonte prioritária, mais recente (pela coluna de atualização de cada dataset, que a sugestão deteta pelo nome; sem as duas, a regra é recusada), mais completo ou mais frequente, com os empates resolvidos pela ordem das fontes e depois pela das linhas. O registo consolidado é só proposto, e cada campo diz de que fonte e de que linha vem o valor e por que regra. Exporta-se numSELECTsó de leitura ou em PySpark sem escrita, e pode seguir como sugestão que, aceite, abre um pull request no repositório do cliente (limpeza e remediação). - Métricas por conjunto de regras: pares candidatos e pontuados, clusters em cada faixa, taxa de correspondência, decisões de revisão (fundidos, ignorados, por rever) e a precisão observada nessas decisões.
- Benchmark rotulado (sintético, português): com as regras sugeridas para CRM × ERP e os limiares por omissão, precisão 1,0 e recall 0,85 em 10 000 linhas, e precisão 1,0 e recall 0,83 em 2 000 (método).
As execuções das regras correm fora do scan, na fila do matching, sobre uma amostra determinística de até 50 000 linhas de cada dataset. Com colunas de dados pessoais nas regras, a execução pede confirmação, como na correspondência por chaves. Os clusters guardam os scores e um hash de cada linha. Os valores de exemplo ficam truncados, com os dados pessoais mascarados, e a retenção apaga-os.
Limites: o matching corre no BeryX sobre essa amostra, sem pushdown para a fonte. Cada conjunto de regras compara dois datasets. Os limiares não se afinam sozinhos a partir das decisões: a precisão observada fica à vista e quem gere as regras decide se os muda. Um bloco com mais de 200 linhas num dos lados não gera candidatos e fica contado. O código fonético é uma redução própria da ortografia portuguesa, e não um algoritmo publicado. No SQL exportado, o JOIN usa só os campos de igualdade e cada linha é um par A × B, com a mesma escolha que a app faz para esse par; os pares que dependem de campos de semelhança, e a escolha entre mais de dois registos de um cluster, estão nos clusters da app.
Por desenho, não somos um hub MDM: não mantemos um registo-mestre persistido nem escrevemos o golden record nas fontes (o que não fazemos).
Testes: backend/tests/test_duplicates.py, backend/tests/test_duplicate_constellation.py,
backend/tests/test_matching.py, backend/tests/test_record_matching.py,
backend/tests/test_matching_bench.py, backend/tests/test_matching_rules.py,
backend/tests/test_matching_rules_api.py.
Limpeza e remediação
Por desenho: propomos, não escrevemos.
O BeryX propõe a correção — SQL e PySpark gerados, com poupança estimada e risco analisado (seguro vs breaking) — e o cliente aplica. Não pedimos credenciais de escrita e nunca escrevemos na fonte.
- Remediação como pull request: a correção abre um PR no repositório GitHub/GitLab do cliente (modelos SQL/PySpark e testes dbt), para revisão pelo processo que já existe.
- Fluxo governado: proposto → aceite → aplicado → verificado, com trilho de auditoria e verificação automática no scan seguinte.
Testes: backend/tests/test_git_remediation.py, backend/tests/test_remediation_lifecycle.py.
Enriquecimento
Tudo é proposta. O BeryX junta a cada valor de uma coluna o que uma fonte de referência diz dele, com a origem, e gera o SQL no dialeto da conexão e o PySpark. Quem aplica é o cliente, por pull request ou SQL depois de aprovar a sugestão, e nada é escrito na fonte.
- Seis tipos de enriquecimento, escolhidos pelo tipo semântico da coluna no último perfil e,
no código postal espanhol, pelo nome da coluna (
backend/app/domain/quality/enrichment.py):- código postal português (CP7) → localidade, concelho e distrito, na GeoAPI.pt (dados dos CTT e do INE);
- código postal espanhol → localidade e comunidade autónoma, no Nominatim público do OpenStreetMap, que recebe só o código postal;
- NIF de empresa → nome e morada registados no VIES da Comissão Europeia. Só se consulta o NIF de pessoa coletiva (1.º dígito 5, 6 ou 9). O NIF de uma pessoa singular fica fora do âmbito e não sai;
- morada → latitude e longitude, numa instância Nominatim do próprio cliente. As moradas não vão para o Nominatim público, cuja política de uso pede que não se lhe enviem dados pessoais;
- país → código ISO 3166-1 alfa-2, e moeda → código ISO 4217, pelas listas ISO versionadas no BeryX, sem consulta externa.
- Proveniência: cada valor proposto leva a fonte, a data da consulta e a confiança. Cada
proposta dá a cobertura na amostra, ou seja, as linhas com valor proposto entre as linhas com
valor na coluna. Um valor que a fonte não conhece, que passa o teto de consultas ou cuja fonte
não respondeu fica sem proposta, com o motivo. No VIES, um campo publicado como
---conta como sem valor. - Proposta (sugestão
enrichment,backend/app/domain/quality/enrichment_service.py): o passo 1 conta, só com leitura, as linhas a enriquecer, e pode ensaiar-se na fonte (ensaio de uma correção). O passo 2 é umUPDATEproposto numa coluna à escolha, que só preenche as linhas onde ela está vazia, ou, sem coluna escolhida, umSELECTque junta os campos propostos. A tabela de referência do SQL só leva valores que a fonte confirmou: códigos postais, NIF de empresas e códigos ISO. Na geocodificação, o SQL não leva moradas: o passo 2 junta uma tabela de coordenadas que o cliente carrega. Como qualquer sugestão aceite, pode seguir por pull request para o repositório do cliente (limpeza e remediação). Os scans não apagam uma proposta por aprovar: só uma nova proposta da mesma coluna e do mesmo tipo a atualiza. Depois de aplicada, a sugestão não passa sozinha a verificada, porque o scan não mede o enriquecimento: marca-se como verificada à mão, no cartão da sugestão. - Opt-in por fornecedor: nenhum valor sai antes de um administrador autorizar o fornecedor
no workspace, em «Regras → Fornecedores de enriquecimento» ou em
PUT /api/v1/workspace/enrichment. A autorização regista quem consentiu e quando. Sem autorização, a amostra nem se lê. É independente da autorização da regraaccuracy. O que sai para cada fornecedor está em SEGURANCA.md. - Geocodificação só na instância do cliente (
nominatim_self_hosted): para a autorizar, o administrador indica o URL da instância (nominatim_url) e confirma que as moradas podem ser dados pessoais (confirm_personal_data). Mudar o URL pede outra vez essa confirmação, e retirar a autorização apaga o URL. Sem instância configurada e autorizada, nenhuma morada sai. O URL passa pela guarda anti-SSRF ao gravar e em cada consulta. Uma instância atrás de autenticação não está suportada. - API:
GET /api/v1/datasets/{id}/enrichmentlista as colunas candidatas, com o tipo e o fornecedor de cada uma;POST /api/v1/datasets/{id}/enrichment/proposalslê uma amostra só dessa coluna, consulta o fornecedor e grava a sugestão (backend/app/api/v1/enrichment.py).
Limites: a proposta corre sobre uma amostra da coluna, e os valores que não aparecem nela ficam por propor. Cada proposta tem um teto de consultas externas, e o Nominatim público responde a 1 pedido por segundo. No código postal espanhol, o Nominatim público devolve um só resultado, por isso um código partilhado por várias localidades dá só uma delas. A GeoAPI.pt e o Nominatim público são serviços gratuitos sem nível de serviço contratado. Os valores do Nominatim público vêm do OpenStreetMap, com a licença ODbL, que pede atribuição a quem os publica. Fora da lista acima, o BeryX não acrescenta dados de terceiros aos registos: não há serviço postal pago, base comercial de empresas nem geocodificação fora da instância Nominatim do cliente.
Testes: backend/tests/test_enrichment.py.
Gestão de metadados
Catálogo automático de todas as fontes, com domínios/data products, dono e membros.
Linhagem: chaves estrangeiras, SQL das views (ao nível da coluna),
manifest.jsondo dbt, eventos OpenLineage de terceiros — OpenLineage — e a linhagem do Unity Catalog e a Data Lineage API do Dataplex, lidas no import do catálogo (cartões #691 e #692).Explorador de linhagem (página
/linhagem,GET /lineage/graph, cartão #714): o grafo tabela → tabela do workspace e, ao expandir um dataset, coluna → coluna, com os dashboards e modelos como nós finais. Os nós pintam-se pela confiança (o trust score do catálogo, «Confiança B · 74», calculado para até 2.000 datasets, os mais ligados), pelo score de qualidade, pelos problemas abertos, pela sensibilidade ou pela certificação. Filtra por conexão e pelos domínios que quem pede lê, expande a montante e a jusante salto a salto, mostra o caminho mais curto entre dois datasets e exporta a vista em PNG, SVG ou JSON. A mesma vista existe em lista hierárquica, e a linhagem das colunas lê-se também em texto, no painel do dataset e na lista. O grafo usa-se por teclado e o layout corre num Web Worker. Um dataset invisível para quem pede só aparece como «Dataset restrito», ligado a um visível (app/domain/lineage/explorer.py).Publicação no catálogo do cliente: veredictos de qualidade no OpenMetadata e no DataHub, e assets «Data Quality Metric» no Collibra (Import API v2). No Microsoft Purview, o veredito, o score, a data do scan e o link para o BeryX vão para atributos da tabela no Data Map. No Unity Catalog da Databricks, o trust score, o veredito, as regras falhadas, os issues por resolver e a certificação vão para tags
beryx_*da tabela; o owner, as tags, os comentários das colunas e a linhagem são lidos para o catálogo leve. No Dataplex Universal Catalog (cartão #692), a entry da tabela recebe três aspects da BeryX: veredito e trust score, resultado das regras e issues abertas. O Dataplex entra sem chave JSON (a BeryX impersona uma service account do cliente) e dá também a leitura: descrições, glossário e linhagem. No Alation (cartão #693), a tabela e as colunas recebem campos de qualidadeberyx_*pela Data Quality API: veredito e score, trust score, issues abertas, certificação e uma entrada por regra, cada uma GOOD, WARNING ou ALERT. Com o veredito FALHA, a tabela leva também uma trust flag Warning do utilizador da integração, que sai quando o veredito muda. A BeryX não escreve owners, termos nem descrições no Alation. No Atlan (cartão #695), a tabela recebe os atributos do custom metadata «BeryX», que um admin do Atlan cria: veredito, score e resumo, as regras, trust score e grau, issues abertas, certificação, link e data do scan, cada um no tipo que o atributo tem. Com o veredito FALHA, a tabela leva também um announcement da BeryX, só se não tiver outro, que sai quando o veredito muda. A BeryX não escreve owners, termos, tags nem a certificação do Atlan. No Informatica CDGC (cartão #694), cada regra e o veredito sobem como Data Quality Rule OccurrencesBERYX-…ligadas à tabela, com o score 0-100, as linhas lidas e as falhadas, num import em lote por push. O login usa o utilizador e a password do IDMC, presos aos dois hosts do pod. A BeryX não escreve owners nem termos no CDGC. O Collibra, o Purview, o Unity Catalog, o Dataplex, o Alation, o Atlan e o Informatica CDGC estão validados com transporte simulado; falta a validação num tenant ou projeto do cliente — catálogo externo.Classificação de sensibilidade por coluna (dados pessoais) e README por dataset gerado e aprovado por um humano.
Descrições sugeridas por IA (cartão #687) para o dataset e as colunas sem descrição, a partir dos metadados e do perfil agregado (tipo, chaves, % de nulos, distintos e sensibilidade, nunca valores das linhas), pela tarefa
catalog_docsdo routing do LLM. Sem modelo disponível saem de templates, em pt, en ou es. Todo o texto sugerido passa pela guarda de saída antes de se gravar, e uma edição com dados pessoais é recusada. Cada sugestão fica marcada «sugerida por IA» até uma pessoa a aceitar, tal como veio ou editada, ou a recusar, e só a aceite entra no catálogo, com histórico e auditoria. Decide o owner de um domínio do dataset ou um admin, no próprio dataset, e um admin em massa na fila de revisão (GET /description-suggestions). A ação «Sugerir descrições» das operações em massa gera para todos os datasets da seleção, e o rollback apaga as sugestões que ninguém decidiu. Datasets com docs do dbt ou do catálogo externo ficam de fora, porque o catálogo de registo do cliente continua a ganhar (backend/app/domain/catalog/descriptions.py, catálogo externo).Catálogo pelo grafo (cartão #715, ecrã «Sugestões do grafo», só admins): um motor sem LLM lê a linhagem ao nível da tabela e da coluna, as chaves declaradas (também as do inventário), o perfil e o co-uso em consultas (pares de tabelas lidas pela mesma query no Snowflake e no BigQuery, só contagens). Propõe a descrição e o termo do glossário numa coluna copiada 1:1 a jusante, a classificação de uma coluna copiada ou derivada (um
COUNT(email)não herdapii), chaves estrangeiras por declarar (pista no nome, tipo e perfil compatíveis), o domínio (a mesma regra das sugestões de domínio, e um «Ignorar» de lá conta como rejeição), o owner pela maioria estrita dos vizinhos e, só quando se pede, data products por área. Cada sugestão traz a confiança e o caminho no grafo que a justifica, em lista. Nada se escreve sem uma pessoa aceitar: aceitar aplica pelo serviço de sempre (o termo passa pelo fluxo de aprovação das ligações, se estiver ligado) e cada decisão fica na auditoria. Uma sugestão rejeitada só volta se mudar o que propõe (o texto da descrição a montante, por exemplo) ou, no domínio, os vizinhos que votaram nele. As que estão por decidir chegam aos admins na caixa «As minhas tarefas». Um tipo só corre por omissão com precisão ≥ 90 % num conjunto de teste com respostas conhecidas, pelo menos 5 casos e 3 armadilhas (backend/app/domain/catalog/graph_engine.py,backend/tests/graph_golden.py).Qualidade do catálogo e dos metadados (KPIs de governação), por workspace e por domínio: % de datasets com owner, descrição, classificação, domínio, contrato e regras; descrições desatualizadas (o schema mudou depois), owners inativos ou que saíram, datasets sem uso e sem owner e docs de colunas que já não existem. Cada problema traz a sugestão pelo canal que o resolve (README gerado, import do catálogo externo), com metas, tendência diária e export CSV (
GET /catalog/metadata-quality,backend/app/domain/catalog/metadata_quality.py).Pesquisa do catálogo com facetas e ranking (cartão #681, vista «Pesquisa» dos Datasets,
GET /catalog/search): os datasets que casam com o texto, com as regras da pesquisa transversal, ou todos sem texto, com oito facetas e as contagens de cada valor: domínio, owner efetivo, termo do glossário, sensibilidade (a mais alta das colunas), grade do trust score, nível de uso, certificação e os modelos e agentes de IA que consomem o dataset (cartão #685). Dentro de uma faceta os valores somam-se e entre facetas cortam-se, e cada faceta conta com os filtros das outras. Só entram os datasets que quem pesquisa vê, e os domínios restritos sem concessão não aparecem na faceta. A ordem dá pontos, até 100: texto até 50 (nome igual 50, palavras no nome 40, schema ou descrição 25, coluna ou termo 20, tag 15), trust score até 20, uso até 20 (escala logarítmica, 1000 queries/dia dão 20) e certificado 10. Desempata pelo texto, pelo trust, pelo uso e pelo nome, e cada resultado mostra os fatores que o puseram ali («coluna cliente_id (+20) · certificado (+10)»). O texto, os filtros e a página ficam no URL, que se copia para partilhar a pesquisa. Os filtros e as facetas contam com todos os datasets visíveis que casam. Só o trust score, calculado na leitura, fica limitado aos primeiros 2000 que passam os outros filtros, pela ordem sem o trust. Acima disso, a resposta diz-o (capped,trust_scored): a faceta e o filtro do trust só contam com esses, os restantes vêm no fim, sem os pontos do trust, e a contagem e o estado vazio avisam (backend/app/domain/catalog/faceted_search.py,frontend/src/components/CatalogSearch.tsx).Eventos de metadados nos webhooks e pesquisa do catálogo no MCP (cartão #688): um canal pede, em
event_kinds, os eventos de dataset criado ou removido, schema mudado, owner ou steward mudado, termo do glossário aprovado e certificação mudada. Cada evento grava-se na transação da mudança, e o worker entrega-o assinado, comIdempotency-Key, com o âmbito do canal e com o de uma equipa. Uma descoberta ou uma conexão apagada dá no máximo 100 eventos, e o último diz quantos ficaram de fora. A tool MCPsearch_cataloglêGET /catalog/searchcom as credenciais de quem chama e não leva o owner (backend/app/domain/catalog/metadata_events.py,backend/app/domain/alerts/metadata_dispatch.py,backend/app/mcp_catalog.py; ver WEBHOOKS e MCP_SERVER).Glossário de negócio (plano Team ou superior): termos como «Receita líquida», com definição, sinónimos, domínio e owner, ligados aos datasets e às colunas que os implementam. Aprovar um termo ou uma ligação é de um admin ou do owner do domínio do termo, e fica na auditoria. O termo herda a qualidade das colunas aprovadas: o score dos datasets e os issues por resolver de cada coluna, com a explicação («está a 51.7 por causa de 2 issue(s)…»). O sugeridor propõe colunas pelo nome, pelos sinónimos, pelo tipo semântico e pela descrição dbt, sem chave de IA; com IA, a sugestão fica marcada como gerada por IA. Uma sugestão nunca nasce aprovada. Os termos aprovados entram no contrato ODCS (
businessNameetags), nodatavault.yaml, na pesquisa do catálogo, no assistente (search_glossary) e no MCP (glossary://terms) (backend/app/domain/catalog/glossary.py,backend/app/api/v1/glossary.py). Na app, a página Glossário pesquisa, cria, edita e aprova termos e revê as sugestões, e a tabela de colunas do dataset mostra os termos de cada coluna (aceite com borda sólida, proposta tracejada), com «+ termo» e o aviso das ligações por rever (frontend/src/pages/Glossario.tsx,frontend/src/components/DatasetTermChips.tsx).Hierarquia, categorias e relações do glossário (cartão #677): um termo tem um termo mais largo (o pai) e uma categoria («Financeiro»), e relaciona-se com outros como sinónimo, «contém» (do todo para a parte) ou relacionado. A hierarquia não aceita ciclos nem mais de 6 níveis, e «contém» também não fecha ciclos. Num termo fora de rascunho, muda a estrutura quem o aprova, em todos os termos que a mudança toca: as duas pontas de uma relação, e o pai antigo e o novo. Um termo importado do catálogo do cliente é só de leitura, também como a outra ponta. Com o fluxo dos termos em modo retido, a estrutura de um termo aprovado não muda (409), porque o fluxo ainda não a leva (
PUT /glossary/terms/{id}/hierarchy,POST /glossary/terms/{id}/relations,backend/app/domain/catalog/glossary_hierarchy.py). O dicionário para o catálogo do cliente leva só os termos aprovados e mantém a hierarquia (é um ficheiro: o BeryX publica, não substitui o catálogo, #407). Um antepassado por aprovar corta o caminho publicado: o termo fica por baixo do último aprovado da cadeia. No OpenMetadata, vai umCreateGlossaryTermpor termo, os pais antes dos filhos, com oparente osrelatedTermspelo FQN, e as colunas citam o termo pelo FQN com o caminho. No DataHub, cada categoria e cada termo com filhos é umglossaryNode, e o termo leva oparentNodee as relações emglossaryRelatedTerms(backend/app/domain/catalog/dictionary.py). Na app, o termo mostra o caminho, os termos mais estreitos e as relações, e edita-os (frontend/src/components/GlossaryTermStructure.tsx).Papéis, subdomínios e re-atestação do owner dos domínios (cartão #677): um domínio tem um domínio pai (sem ciclos, no máximo 5 níveis; muda-o um admin), stewards e custodians além do owner, e uma matriz RACI por atividade (definição, qualidade, acesso, classificação e ciclo de vida) com um só «A» por atividade. Os papéis não dão permissões. Os papéis e a RACI muda-os quem gere o domínio. O owner atesta de 180 em 180 dias que continua a responder pelo domínio, e outra vez logo que o owner muda (na app, na API, por GitOps ou pelo import do catálogo do cliente) ou que o IdP desativa a conta ou tira de uma equipa quem tem um papel no domínio (SCIM, apagar o grupo no SCIM, ou os grupos do IdP no login), ou que um admin desativa essa conta na app (
PATCH /auth/users/{id}, motivo «conta desativada»). Só o owner atesta: um admin ou um token levam 403. A atestação vencida, ou a 14 dias do prazo, aparece na caixa do owner (ownership_attestation) e sai de lá quando ele atesta. A secção do pacote do conselho (GET /ownership-attestations/board-pack, também em Markdown) dá as contagens por estado e a lista das vencidas, com o domínio, o owner, o prazo, os dias de atraso e o motivo. A sugestão de owner pontua as pessoas pelos datasets do domínio de que são owner ou steward, com mais peso para os mais consultados nos últimos 30 dias, e pelos datasets a montante na linhagem de que são owner. Só contam os datasets que quem pede vê. A resposta não traz nomes de datasets, só contagens por tipo de evidência (GET /domains/{id}/stewardship,backend/app/domain/catalog/stewardship.py,backend/app/domain/governance/ownership_tasks.py). Na app, o domínio tem o painel «Papéis e ownership» (frontend/src/components/DomainStewardshipPanel.tsx). O pacote do conselho da app (em Governação) ainda não traz esta secção: lê-se pela API, como a dos agentes.Dono fantasma (cartão #759): o estado de quem responde vale para todos os objetos governados com uma pessoa responsável: o dataset, o domínio, o termo local do glossário, a definição de métrica (o owner e a autoridade de definição), o elemento de dados crítico, a política e o agente ou modelo de IA (o owner e o owner do prompt). Ficam de fora os retirados, os termos descontinuados e os termos externos. Os estados são os do
owner_inactivedos KPIs de metadados (saiu do workspace, conta desativada, sem sessão há 90 dias) e um sinal novo, «silencioso»: conta ativa e com sessão, sem atestações, aprovações nem decisões no trilho de auditoria há 180 dias, a cadência da atestação do owner de um domínio. Para o owner de uma política, a janela é o período de revisão mais longo das suas, se passa dos 180 dias (365 por omissão): quem cumpre a cadência que o produto lhe pede não é silencioso. Uma conta mais nova do que a janela também não. Com o responsável inativo, cada admin recebe na caixa a tarefa «Reatribuir o responsável» (owner_reassignment), por objeto e papel, com o aviso no sino e por email. A tarefa não tem prazo e abre a página do objeto. Fecha sozinha quando o responsável muda ou a conta volta a estar ativa. Acima de 500, o teto corta pelo pior estado: entram primeiro as contas desativadas, depois quem saiu e por fim quem está sem sessão. O silencioso não pede reatribuição: aparece só no relatórioGET /governance/ghost-owners(só admins). O relatório lista os objetos, pior estado primeiro, com o estado, a última sessão e a última decisão de quem responde. As contagens por estado são de todos os objetos, etruncateddiz que a lista passou do teto de 500 (backend/app/domain/governance/ghost_owners.py).Carta do papel e «Os meus papéis» (cartão #762): cinco papéis têm carta, o owner, o steward e o custodian de um domínio, a autoridade de definição de uma métrica registada e o dono de um agente de IA. Cada carta diz o que o papel é e «a decisão que tomas sozinho», ligada a uma atividade em que o papel é o «A»: nos papéis do domínio, as da matriz RACI desse domínio; a autoridade é o «A» da definição da métrica, porque assina cada versão, e o dono do agente o da passagem, porque só o dono novo a aceita. Há cartas por omissão (o steward e o custodian sem decisão própria, porque a RACI por omissão não lhes dá nenhum «A»), e um admin muda-as em
PUT /role-charters. Uma decisão só se liga a uma atividade em que o papel é o «A» na RACI por omissão ou na de algum domínio. Atestar o ownership de um domínio confirma a versão da carta do owner que está à vista. Sem ela, a atestação dá 422, e com uma versão que já mudou dá 409. A versão confirmada fica no domínio. Em «As minhas tarefas», o separador «Os meus papéis» (GET /tasks/my-roles) lista os objetos de que a pessoa responde, até 500, com a carta, as atividades em que é o «A», o papel que decide quando a RACI de um domínio dá a decisão da carta a outro e, no owner, a atestação e se confirmou a carta em vigor. Os objetos de domínios e datasets que a pessoa não vê ficam de fora, e um token não tem papéis (backend/app/domain/governance/role_charters.py,frontend/src/components/TarefasPapeis.tsx).Fluxos de aprovação (plano Team ou superior): o admin configura, por workspace, o fluxo dos termos do glossário, o das ligações termo → coluna, o da certificação (ver «Certificação de datasets e data products»), o das exceções às políticas (ver «Políticas e normas») e o dos pedidos de acesso (ver «Pedidos de acesso a domínios, com prazo e recertificação»). Cada etapa tem aprovadores (um papel: admins, qualquer membro ou quem gere o domínio do termo; uma equipa; ou uma pessoa), uma regra (um aprova, todos aprovam ou a maioria aprova), um SLA opcional em horas e para quem escala (por omissão, os admins). Vencido o SLA, o worker escala o pedido uma vez por etapa e quem recebe o escalonamento decide a etapa sozinho. Quem sai de uma etapa (conta desativada, apagada ou passada a Leitor) deixa de contar para a regra, e, se ninguém sobrar por votar, os admins decidem a etapa (ou o pedido espera, se o fluxo o disser). Quem se ausenta delega as aprovações noutra pessoa por um período, na Governação, e o voto do delegado conta pela pessoa ausente. As tarefas de cada pessoa aparecem em «As minhas tarefas» (
/tarefas), ao lado das outras, com um aviso no sino e por email quando chegam e quando o SLA da etapa vence. Com o fluxo das ligações ligado, entram nele as ligações propostas à mão, as do «Sugerir ligações» e as que já estavam propostas quando o fluxo foi ligado, e o termo mostra a etapa de cada uma. O histórico de cada pedido (quem decidiu, em que etapa, em nome de quem, com que comentário) fica na auditoria e no termo: o do termo numa secção própria, o de cada ligação na tabela «Onde é usado». Sem fluxo configurado, vale o de antes: um admin ou quem gere o domínio aprova (uma exceção, decide-a um admin). Cada pedido, e cada pedido de ação do assistente (opropose_actiondo MCP incluído), traz a pré-visualização do que muda se for aprovado, calculada sem IA (cartão #699): o diff campo a campo, os ativos e consumidores a jusante (agentes incluídos), se reinicia o histórico de uma regra ou detetor e o custo estimado por scan e de IA. Uma parte ou uma linha do diff que não se calcula aparece como «Impacto desconhecido», com o porquê (por exemplo, a definição de um termo aprovado fora do fluxo, que o histórico só guarda até 500 carateres, ou um objeto que mudou depois do pedido). Numa edição retida, o diff é a proposta face ao termo. «As minhas tarefas» mostra-a em «O que muda?» (backend/app/domain/governance/approvals.py,backend/app/domain/governance/approval_config.py,backend/app/domain/governance/request_preview.py,frontend/src/components/RequestPreview.tsx,backend/app/api/v1/approvals.py,frontend/src/pages/Tarefas.tsx,frontend/src/components/FluxosAprovacao.tsx,frontend/src/components/ApprovalHistory.tsx,frontend/src/components/DelegacaoAusencia.tsx).As garantias que uma auditoria pede:
- Veto. Cada etapa tem um limiar de rejeição: por omissão, uma rejeição faz cair a etapa e
o pedido. Com um limiar maior, a etapa só cai com esse número de rejeições, ou quando os votos
que faltam já não chegam à regra (
approvals._outcome). - Quem pede não decide. Quem pediu (quem submeteu o pedido e o autor do termo, da ligação,
da exceção ou do pedido de acesso; numa edição retida, só quem editou) nunca decide o próprio
pedido: não conta como aprovador da etapa, nem por delegação, escalonamento, fallback ou
override. Se não sobrar outro aprovador, ou se os aprovadores da etapa não resolverem ninguém,
o fluxo diz o que acontece: decidem os admins do workspace, menos quem pediu, ou o pedido
espera (
approval_config.barred_voters,approval_config.step_approvers,approvals._enter_step). - Agentes não aprovam. Um token de API (com ou sem ficha de agente), uma chamada de uma
ferramenta MCP, o Microsoft 365 Copilot e o steward automático recebem 403 ao decidir. O MCP
não tem nenhuma ferramenta que decida (
backend/app/api/v1/approvals.py,approval_guard.is_agent). - Proposta com base. O pedido guarda o que se aprova, o valor de cada campo do objeto quando
abriu e o SHA-256 dos dois. Antes de cada decisão, o objeto compara-se com essa base: se
mudou, o pedido passa a «em conflito» e nada se aplica por cima
(
backend/app/domain/governance/approval_guard.py). - Decisões só de acréscimo. Aprovar, rejeitar e o override de um admin acrescentam uma linha
ao histórico, que nunca se edita nem se apaga. O RGPD só retira a pessoa. O override decide o
pedido por cima das etapas que faltam, pede sempre um motivo, e o motivo fica no histórico e na
Auditoria (
backend/app/domain/governance/approval_outcomes.py). - Edição retida. Com o modo retido no fluxo dos termos, a edição de um termo aprovado não se
aplica logo: fica à espera do fluxo, o termo mostra a versão de antes e quem a propôs não a
decide. Aprovada, entra no termo com o histórico de cada campo (
glossary.update_term). - Expiração. Um fluxo com prazo em dias fecha o pedido como «expirado» quando ninguém o
decide a tempo. O pedido expirado ou em conflito volta a quem pediu, como tarefa em «As minhas
tarefas», até voltar a pedir (
backend/app/domain/governance/approval_returns.py).
- Veto. Cada etapa tem um limiar de rejeição: por omissão, uma rejeição faz cair a etapa e
o pedido. Com um limiar maior, a etapa só cai com esse número de rejeições, ou quando os votos
que faltam já não chegam à regra (
Tarefas geradas pela medição e ações em lote («As minhas tarefas»,
/tarefas): além do que espera uma decisão, a caixa mostra o que a medição sabe que falta, ao owner do dataset (o próprio ou o herdado do domínio) ou, sem owner, ao steward do dataset. São quatro tipos: os checks de cobertura dos KPIs que falham (owner, descrição, classificação), o issue crítico de uma regra sem pessoa nem equipa atribuída, o elemento de dados crítico sem responsável e a certificação a menos de 14 dias do fim. Num dataset sem owner nem steward, o owner em falta vai aos admins, e a descrição e a classificação não vão a ninguém até haver um dos dois. Não se gravam: cada leitura recalcula-as, por isso fecham sozinhas quando o dado é corrigido. As dos metadados não contam no menu nem avisam no sino ou por email, porque são o backlog da cobertura, sem prazo. A lista põe primeiro as vencidas e, a seguir, as críticas (um dataset com CDE, um issue crítico), pelo prazo. Aprovar, rejeitar com comentário e reatribuir fazem-se em lote, e a permissão verifica-se item a item: só entram as tarefas da caixa de quem pede, decididas pelo serviço da rota de origem, e o resto fica por fazer, com o motivo. Um agente não aprova nem rejeita em lote, como na rota da decisão, e um Leitor, mesmo com uma concessãoeditor, decide na origem. O separador «Criados por mim» lista os pedidos por decidir que a pessoa abriu, e o pedido de acesso e a mudança de uma atividade do RoPA (só admins) retiram-se dali. Com uma delegação em ausência ativa, o delegado recebe também as tarefas geradas da pessoa ausente, com o nome dela. As tarefas que vencem chegam também ao Slack e ao Teams, pelo outbox de colaboração: em cada tick dos avisos, um eventotasks.overduepor domínio, com o nome do domínio, quantas venceram, quantas são críticas e o link para a caixa filtrada por esse domínio, sem títulos nem pessoas. Só o recebem os canais com essa subscrição, e a política da página «Presença» lê-se antes de cada envio: a pausa salta-o e «só links» tira o nome do domínio (backend/app/domain/governance/generated_tasks.py,backend/app/domain/governance/task_actions.py,backend/app/domain/governance/task_channels.py,backend/app/api/v1/tasks.py).Certificação de datasets e data products: o selo tem cinco estados (pedido, certificado, em revisão, revogado ou recusado). Cada certificado guarda quem certificou, a data, a validade (180 dias por omissão, até 730) e a evidência dos critérios. Os critérios são automáticos: dono (o do dataset ou o herdado do domínio), descrição, contrato ODCS ativo e não quebrado, regras críticas a passar e score de pelo menos 80. Num data product valem para todos os datasets dele, e conta o pior score. O pedido só abre com os critérios todos cumpridos e segue o fluxo de aprovação das certificações. Sem fluxo configurado, aprova um admin ou quem gere o domínio. A aprovação volta a conferir os critérios e só uma pessoa aprova: um token de API ou um agente pode pedir, mas não certifica. Quando a validade acaba, o worker põe o certificado em revisão e abre o pedido de recertificação, que aparece em «As minhas tarefas» e abre no dataset ou no domínio. No fim de cada scan, um certificado cujo contrato quebrou, cujo veredito passou a fail ou cujo score desceu do mínimo também vai a revisão. Aprovada, a recertificação dá validade nova; recusada, o certificado fica revogado. Um admin ou quem gere o domínio revoga, com motivo. O selo aparece na API (
GET /certifications,GET /datasets/{id}/certification,GET /domains/{id}/certification), no MCP (get_certification, só leitura) e no catálogo do cliente: o casodatavault_qualitypublicado no OpenMetadata, no DataHub e no Collibra leva o estado, a validade e o selo no resumo. O Purview recebe só o veredito; o Unity Catalog, o estado na tagberyx_certificatione a validade emberyx_certified_until. Na app, o selo, os critérios, o pedido e a revogação aparecem na página do dataset e na do domínio. A lista Datasets do catálogo e a dos domínios mostram o selo ao lado do nome quando o objeto está certificado ou em revisão. Quem não lê um domínio restrito vê o nome dele, mas não o estado da certificação do data product: a lista, as tarefas e o histórico deixam-no de fora. Apagar o domínio ou a conexão apaga a certificação e fecha o pedido aberto (backend/app/domain/governance/certification.py,backend/app/api/v1/certifications.py,backend/app/domain/integrations/catalog_sync.py,frontend/src/components/CertificacaoPanel.tsx,frontend/src/components/CertificationBadge.tsx).Pedidos de acesso a domínios, com prazo e recertificação (página
/acessos). Cada passo fica na auditoria, sem o texto do motivo (backend/app/domain/access/requests.py,backend/app/domain/access/recertification.py,backend/app/domain/access/access_policy.py,backend/app/api/v1/access_requests.py,frontend/src/pages/Acessos.tsx).- Pedido. Quem é membro de uma equipa (mesmo com o papel Leitor) ou um admin pede acesso dessa equipa a um domínio, de leitura ou de escrita, à versão mascarada ou aos valores sensíveis, com a finalidade (BI, agente, RAG ou treino), o motivo, o prazo em dias e, opcional, o ticket externo. Uma equipa com uma concessão sem prazo não pede: a equipa owner do domínio muda-a na matriz.
- Aprovação. O pedido segue o fluxo de aprovação dos pedidos de acesso, que o admin configura como os outros. Sem fluxo configurado, aprova um admin ou a equipa owner do domínio, que é quem gere as concessões. O responsável indicado do domínio gere o conteúdo e não decide acessos: o fluxo dos pedidos de acesso não aceita o papel «quem gere o domínio». Um Leitor, mesmo na equipa owner, vê os pedidos do domínio mas não decide acessos, como nos outros fluxos. Um pedido aos valores sensíveis leva sempre mais uma etapa no fim, «Dados pessoais», decidida pelos aprovadores que a política indica (por omissão, os admins), e tem um prazo máximo mais curto (30 dias por omissão, contra 180). Ligada na política, a aprovação automática aprova logo um pedido de leitura, mascarado e com prazo curto. Um pedido aos valores sensíveis nunca é aprovado assim. O aprovador vê no pedido a decisão de confiança dos datasets do domínio para essa finalidade.
- Prazo. Aprovado, o pedido cria a concessão da equipa com data de fim. Passado o prazo, a concessão deixa de contar no acesso e na clearance de dados pessoais, e o worker apaga-a.
- Na fonte. As fontes do cliente são só de leitura. Por isso, a concessão que um pedido aprovou abre à equipa owner do domínio e aos admins a tarefa de a executar na fonte, em «As minhas tarefas». Quando sai, abre a de a retirar: no fim do prazo, no «Revogar acesso», na recertificação, ao apagá-la na matriz ou ao apagar a equipa. O pedido fecha então como expirado ou revogado.
- Recertificação. Uma campanha, que um admin abre à mão ou que abre sozinha de N em N dias, dá à equipa owner de cada domínio e aos admins uma tarefa por concessão em vigor: confirmar ou revogar. No fim da campanha, o que ninguém confirmou é revogado, com a tarefa de o retirar na fonte.
Owner e steward por dataset: cada dataset pode ter owner e steward próprios. Sem owner próprio, herda o do primeiro domínio que tem owner. Esta regra é a mesma no detalhe, no dicionário, na pesquisa e nos KPIs de metadados (
effective_ownerembackend/app/domain/catalog/metadata.py). O steward não se herda. Muda o owner e o steward de um dataset quem pode escrever nesse dataset; o owner de um domínio só o muda um admin. O owner do dataset não dá permissões.Tags por dataset e por coluna: o workspace escolhe uma política de tags. Em modo livre aceita qualquer tag; em modo controlado, só se acrescentam as da lista (as que o alvo já tinha ficam), e só um admin a muda. Editar as tags substitui as manuais e deixa as do catálogo externo. Mudar o owner, o steward ou as tags fica no trilho de auditoria (
PATCH /datasets/{id}/owners,PUT /datasets/{id}/tags,PUT /catalog/tag-policy). Na app, o detalhe do dataset tem o painel «Owner, steward e tags», com o histórico e o diff entre datas, e Datasets › Metadados tem a política de tags.Dicionário de dados exportável por dataset, domínio ou workspace, em CSV, JSON, Markdown, OpenMetadata e DataHub. O dicionário leva descrições, tipos, sensibilidade, termos aprovados, tags, owner, steward, regras ativas e o SLA dos domínios. Os termos só saem nos planos com glossário. O ficheiro OpenMetadata traz um corpo de
PUT /api/v1/tables(CreateTableRequest) por tabela e, à parte, o que esse pedido não aceita: o owner e o steward por email (o pedido exige o id do utilizador no OpenMetadata), os domínios, o SLA e as regras. As tags e os termos vão na classificação e no glossárioDataVault, que têm de existir no OpenMetadata. O ficheiro DataHub traz MCPsUPSERTpara odatahub ingest, com o owner comoBUSINESS_OWNERe o steward comoDATA_STEWARD. O FQN ou URN vem do mapeamento da sincronização do catálogo, se a conexão o tiver. É um ficheiro: o BeryX não envia descrições nem tags para o catálogo do cliente. No CSV, as células que começam por=,+,-ou@saem neutralizadas. Cada pedido leva até 2 000 datasets; com mais, a resposta trazX-Dictionary-Truncated: true(GET /datasets/{id}/dictionary,GET /domains/{id}/dictionary,GET /catalog/dictionary,backend/app/domain/catalog/dictionary.py).Histórico de metadados: cada alteração fica registada com o quê, quem, quando, o antes e o depois. Entram as mudanças de owner, steward e tags, a sensibilidade posta à mão, as ligações de termos a colunas criadas ou decididas pela API e as edições dos campos de um termo. O histórico devolve as 500 alterações mais recentes da janela, com
X-History-Truncated: truequando há mais; para recuar, passa-se embeforeo id da mais antiga devolvida. O diff entre duas datas conta com todas as alterações da janela e dá, por campo, o valor no início e no fim (GET /datasets/{id}/metadata/history,GET /datasets/{id}/metadata/diff,GET /catalog/metadata-history,GET /catalog/metadata-diff). Entra também no export do workspace. O histórico começa com esta versão: os eventos anteriores de auditoria e de sensibilidade não foram convertidos.Pesquisa transversal (
GET /search?q=): datasets, colunas, termos do glossário, tags, owners ou stewards e domínios (data products), estes pelo nome e pela descrição. Acentos e maiúsculas não contam («regiao» encontra «Região»). As colunas vivem numa tabela própria,dataset_columns, com uma linha por coluna. O perfil, o inventário e o import dbt atualizam-na em cada escrita (backend/app/infra/db/catalog_index.py). No Postgres a pesquisa usa um índice guardado: GIN sobre umtsvectorcom pesos (nome, schema, descrição) e trigramas dopg_trgm, que aceitam uma letra em falta ou a mais numa palavra de 4 ou mais letras («custmer» encontracustomer_id). Uma letra errada ou duas letras trocadas de lugar nem sempre passam: «pagamneto» encontrapagamento, «emial» não encontraemail. Com 50 000 colunas responde abaixo de 300 ms (testepg_only). No SQLite casa por LIKE, sem tolerância a erros de escrita. Os datasets dos domínios restritos a quem pesquisa não aparecem em nenhuma das espécies (backend/app/domain/catalog/metadata_search.py). O domínio restrito aparece pelo nome e pela descrição, como noGET /domains. Nos datasets conta também a popularidade (cartão #686): as queries/dia do nível de uso somam ao score até 0,3 em escala logarítmica (1000/dia dão o máximo; o nome igual vale 1), e no SQLite desempatam antes do nome.Uso a partir de Postgres, SQL Server e Redshift (cartão #686): a recolha da telemetria (
POST /dashboard/finops/collect) dá também nível de uso às tabelas destas fontes, só com estatísticas, sem ler dados nem texto de queries (backend/app/infra/connectors/usage_stats.py). No Postgres lê os scans dopg_stat_user_tables, sem privilégios além de ligar à base. No SQL Server lê os seeks, scans e lookups dosys.dm_db_index_usage_stats, com VIEW SERVER STATE. No Redshift lê os passosscandoSYS_QUERY_DETAIL, sem as queries do próprio BeryX, com SYSLOG ACCESS UNRESTRICTED. A vista guarda uns 7 dias, por isso a média divide pelos dias que ela mostra e não por 30 (usage_log_keeps_window). Postgres e SQL Server só têm contadores acumulados, que não dizem desde quando contam (o Postgres guarda-os num reinício limpo, com ostats_resetvazio). Por isso a média sai da diferença entre duas recolhas, com pelo menos 7 dias entre elas, e sem as leituras dos scans do próprio BeryX, medidas no mesmo contador antes e depois de cada scan (uma amostraTABLESAMPLEnão o sobe, umcount(*)paralelo sobe-o uma vez por processo). As leituras do BeryX fora dos scans contam como de outros, e as de outros durante um scan, nas tabelas dele, como do BeryX (backend/app/domain/scans/service.py). A primeira recolha e um reset não dão nível novo nem mexem no aviso aberto. Uma tabela só fica «sem uso» com 80 dias seguidos sem leituras de outros. Com uma réplica de leitura (ou a intenção read-only do SQL Server), o BeryX liga só à secundária, nunca ao primário: os contadores contam só as leituras feitas nela, a recolha di-lo e nenhuma tabela fica «sem uso» por eles (backend/app/domain/finops/read_counters.py). Sem o privilégio ou com otrack_countsdo Postgres desligado, a conexão sai como indisponível, com o que falta, e os níveis anteriores ficam (backend/app/domain/finops/usage_priority.py). O painel Custo × qualidade mostra, por conexão, o que falta, o que a réplica não vê e quantas tabelas ainda esperam nível, com os textos da API na língua de quem recolhe (frontend/src/pages/DashboardExecutivo.tsx).Pesquisa global na app (cartão #680): em qualquer página, ⌘K (Ctrl+K) ou o botão Pesquisar do cabeçalho abrem uma janela sobre o
GET /searchcom até 5 resultados por tipo (datasets, domínios, termos e colunas) e as páginas da app que a conta vê, escolhidos com as setas e abertos com Enter. A página/pesquisamostra até 20 por tipo, com filtro por tipo e o que casou sublinhado (frontend/src/components/CommandPalette.tsx,frontend/src/pages/Pesquisa.tsx). As tags e os owners não têm destino na app e ficam na API.Dashboards e relatórios BI como ativos do catálogo (cartão #683): os dashboards do Power BI, do Tableau e do Looker (sync de BI) e os exposures dbt do tipo dashboard têm página própria (
/dashboards/:id,GET /catalog/dashboards/{id}) e listagem (GET /catalog/dashboards), e aparecem na pesquisa global como tipo «Dashboards e relatórios BI», pelo nome, pelo rótulo e pela descrição da curadoria. A página mostra os datasets de origem com o trust de cada um, e o trust do dashboard é o da fonte mais fraca. Mostra também o uso que a ferramenta reportou no último sync (última visualização, total, «sem visualizações há N dias»). Editores e admins escrevem a curadoria (PATCH /catalog/dashboards/{id}): owner, descrição, tags com a política de tags e termos do glossário. Cada mudança fica na auditoria (dashboard_curated, só ids e nomes dos campos), e a curadoria sobrevive a um dashboard que sai de um sync e volta (backend/app/domain/catalog/dashboards.py). Um dashboard só com fontes em domínios restritos a quem pede não aparece. Ainda não sobem ao catálogo externo, nem entram na pesquisa com facetas (ver catálogo externo).Modelos e agentes de IA como ativos do catálogo (cartão #685): o registo de agentes e modelos do #674 (
GET /compliance/ai-agents) passa a ser também catálogo, com o mesmo objeto e sem cópia. Cada modelo ou agente tem página própria (/modelos-ia/:id,GET /catalog/ai-agents/{id}) e listagem (GET /catalog/ai-agents, com filtrokind). A página mostra o owner (a pessoa responsável ou, sem ela, o texto da ficha), o modelo, o sistema de IA do AI Act ligado, o estado e o nível de risco. Mostra também a linhagem para os datasets de treino (papéis treino, validação e teste) e para os de consumo em produção (inferência), com o trust de cada um. O trust do modelo ou agente é o do dataset mais fraco, e cada etapa tem o seu. Os datasets são os da ficha, os mesmos da ficha do Anexo IV, e só um admin os muda no registo. Na pesquisa global aparecem como tipo «Modelos e agentes de IA», pelo nome, pelo modelo e pela finalidade. Na vista «Pesquisa» dos Datasets, a faceta «Modelos e agentes de IA» filtra os datasets que cada um consome, e cada resultado diz por quem é usado (backend/app/domain/catalog/ai_assets.py,frontend/src/pages/ModeloIaDetalhe.tsx). Um modelo ou agente só com datasets em domínios restritos a quem pede não aparece. Num que também consome outros, só se veem os datasets visíveis. Não sobem ao catálogo externo (ver catálogo externo).Pipelines como ativos do catálogo (cartão #684): os conectores do Fivetran e do Airbyte (o retrato do último sync da integração), os jobs OpenLineage do cliente (Airflow, Spark, dbt, Dagster, pela ferramenta do
producer), os jobs do dbt Cloud e as DAGs do Airflow reportadas peloberyx-airflow(o pipeline é a DAG; o estado é o do run mais recente, falhado se alguma task falhou) têm página própria (/pipelines/:id,GET /catalog/pipelines/{id}) e listagem (GET /catalog/pipelines), e aparecem na pesquisa global como tipo «Pipelines», pelo nome, pela ferramenta e pela descrição e tags da curadoria. A página mostra o estado do último run, a cadência declarada no integrador, os datasets que o pipeline lê e escreve (no dbt Cloud, os modelos cujo último run foi deste job), os issues abertos dos datasets que escreve (os de sync deste pipeline primeiro) e o impacto a jusante: os datasets depois dos que escreve, até 5 saltos e os primeiros 50, os consumidores afetados (os consumidores travados pela decisão de confiança à parte) e o alcance total. Editores e admins escrevem a curadoria (PATCH /catalog/pipelines/{id}): owner, descrição, agenda declarada e tags com a política de tags. Cada mudança fica na auditoria (pipeline_curated, só ids e nomes dos campos), e a curadoria sobrevive a um pipeline que sai de um sync e volta (backend/app/domain/catalog/pipelines.py). Um pipeline que só toca datasets de domínios restritos a quem pede não aparece; os restritos a jusante ficam «Dataset restrito». O que falta: o Azure Data Factory não tem leitor próprio (só entra pelo OpenLineage, se o cliente o emitir), a mesma DAG do Airflow reportada peloberyx-airflowe pelo OpenLineage aparece duas vezes, e os pipelines não sobem ao catálogo externo nem entram na pesquisa com facetas (ver catálogo externo).Montra dos data products (cartão #689): a página
/produtos(GET /catalog/data-products) lista os domínios publicados por nome, com a descrição, o owner, a versão e a data da última publicação. Os descontinuados só aparecem a pedido e os rascunhos nunca. O data product é o domínio, sem entidade à parte. A quem lê o domínio, cada data product mostra também o trust (o do dataset visível mais fraco, como nos dashboards e nos modelos de IA), em quantos datasets há um contrato ODCS aceite, com o bundle para descarregar (GET /domains/{id}/contract), e o selo de certificação. Num domínio restrito sem concessão, ou retido pela ficha de um agente, só se veem o nome, a descrição e o owner. O botão «Pedir acesso» aparece sem leitura e, só com leitura, para pedir escrita. Abre o formulário de Acessos com o domínio escolhido (/acessos?dominio=) e, no pedido de escrita, com o tipo «Escrita» (&papel=editor). Quem não está em nenhuma equipa não pode pedir, porque o pedido é sempre de uma equipa, e a página indica-lhe o owner do data product. A montra só lê, com um número fixo de queries por página (backend/app/domain/catalog/marketplace.py,frontend/src/pages/Produtos.tsx). Não publica no catálogo externo (ver catálogo externo).Inventário só de metadados (cartão #682): por conexão, agendado (de 1 em 1 hora a uma vez por semana), traz tudo o que a conexão vê sem perfilar nem ler linhas: esquemas, tabelas, vistas e colunas, com tipo, comentários, chaves primárias e estrangeiras, partições e clustering (BigQuery) e a contagem estimada quando o motor já a guarda (Postgres, MySQL, BigQuery). As fontes SQL leem o catálogo de cada esquema de uma vez. Salesforce, OData e SAP leem o
describeou o$metadata. Nas fontes em que as colunas só se inferem de linhas (ficheiros, folhas, tópicos, coleções, APIs REST, Dynamics), o inventário fica pelos nomes, com um objeto por ficheiro e sem abrir os livros Excel. As fontes documentais não têm inventário (inventory_modeembackend/app/infra/connectors/inventory.py). O inventário lista todos os ficheiros, tópicos, coleções e entity sets, sem o teto da descoberta (200 por omissão,inventory_tables). Cada consulta tem o teto de tempo do conector e, no BigQuery, omaximum_bytes_billed. A volta tem um prazo de 30 minutos e um teto de 50 000 objetos, e uma volta que para a meio não conta ausências como remoções. Os tenants suspensos ficam fora do agendamento, e cada conexão corre uma volta de cada vez. A 1.ª volta completa é a linha de base. Daí em diante, cada tabela nova, removida ou com o schema mudado fica num evento, com o diff das colunas e se parte (backend/app/domain/catalog/inventory.py). Os eventos e os objetos removidos ficam 90 dias. Os objetos que ainda não são datasets aparecem noGET /searchna espécieinventory. Fica fora dos datasets monitorizados e do limite do plano: um objeto passa a dataset pela descoberta de sempre. API:GET /connections/{id}/inventory,GET /connections/{id}/inventory/objects,PUT /connections/{id}/inventoryePOST /connections/{id}/inventory/run(admin). Para quem já tem um catálogo de registo, o BeryX continua a publicar lá (CATALOGO_EXTERNO.md).Exportação única para um catálogo vazio (cartão #690, a exceção ao #407): quem adota um OpenMetadata ou um DataHub pode levar para lá, uma vez, os datasets das conexões do workspace (até 2000), com as colunas e o tipo, as descrições do dataset e das colunas, as tags, os termos aprovados do glossário com as ligações aprovadas e o owner efetivo. O owner só segue quando o catálogo tem um utilizador com o mesmo email. Só escreve num destino (tipo e URL) sem tabelas nem termos que um run para esse destino não tenha reclamado, e o worker volta a verificar isso antes da primeira escrita. Um admin pede-a e confirma com o nome do workspace, e o pedido fica na auditoria. Repetir faz upsert do que já criou e não duplica, também depois de uma tabela a meio ou de um worker que morreu: o run grava as referências do plano antes de escrever. Um run parado deixa de contar como ativo ao fim do prazo (2 h na fila, 30 min sem escrever). O relatório conta o que ficou criado e dá o mapeamento de cada conexão para o CATSYNC (o prefixo e as exceções dos datasets que ele não mapeia, como os sem schema numa conexão mista), sem emails (
backend/app/domain/catalog/catalog_bootstrap.py). Fora disto, o #407 fica igual: o BeryX não escreve owners, tags nem termos no catálogo do cliente. API:POST /catalog-integrations/{id}/bootstrap/plan(só lê o destino),POST /catalog-integrations/{id}/bootstrapeGET /catalog-integrations/{id}/bootstrap(admin).
O que falta: o owner e as tags, o dicionário e o histórico ainda não têm página
na app e usam-se pela API. O inventário só de metadados também ainda não tem ecrã: liga-se e
lê-se pela API, e a pesquisa global da app não mostra a espécie inventory. A exportação única
para um catálogo vazio também só existe na API. Ficam fora do histórico a criação de termos, as ligações sugeridas
automaticamente, o que o datavault.yaml aplica e a classificação automática da
sensibilidade. Com o catálogo externo ligado, contam-se os termos de glossário lidos no último
import, mas esses termos não entram no glossário do BeryX. O import também não escreve
tags do dataset.
Testes: backend/tests/test_column_lineage.py, backend/tests/test_dbt_manifest.py,
backend/tests/test_openlineage_consume.py, backend/tests/test_catalog_sync.py,
backend/tests/test_column_sensitivity.py, backend/tests/test_dataset_readme.py,
backend/tests/test_metadata_quality.py, backend/tests/test_glossary.py,
backend/tests/test_metadata_management.py, backend/tests/test_catalog_search_index.py,
backend/tests/test_catalog_inventory.py, backend/tests/test_catalog_bootstrap.py,
frontend/src/pages/Glossario.dom.test.tsx, frontend/src/components/DatasetTermChips.dom.test.tsx,
frontend/src/components/CommandPalette.dom.test.tsx, frontend/src/pages/Pesquisa.dom.test.tsx,
frontend/e2e/f12-pesquisa-global.spec.ts.
Que catálogo para que cliente
A BeryX não substitui o catálogo de registo do cliente (#407). Quem já tem um catálogo fica com ele e a BeryX publica lá a qualidade; o catálogo leve da BeryX serve quem não tem nenhum. A recomendação segue o que o cliente já usa:
- Microsoft (Azure, Fabric): Microsoft Purview.
- Databricks: Unity Catalog.
- Google Cloud (BigQuery): Dataplex Universal Catalog.
- Sem catálogo: o catálogo leve da BeryX. Quando o cliente precisar de 80 conectores ou mais, o OpenMetadata, alojado por ele ou no Collate.
- Grande empresa regulada: o catálogo que já comprou (Collibra, Alation ou Informatica CDGC).
- Atlan: Atlan.
- DataHub, open source ou DataHub Cloud: DataHub.
A tabela diz o que a BeryX publica e lê em cada um e o que já foi validado. As linhas são as do
registo dos publicadores (CATALOG_KINDS em backend/app/infra/catalog/client.py), e o
backend/tests/test_docs_drift.py falha quando os dois deixam de bater. «Transporte simulado»: os
testes substituem a API do catálogo por um servidor falso (httpx.MockTransport), e falta a
validação num catálogo de um cliente. O detalhe de cada um está no
catálogo externo.
| Catálogo | Para quem | A BeryX publica | A BeryX lê | Estado |
|---|---|---|---|---|
| Microsoft Purview | Microsoft | O veredito, o score, a data do scan e o link, em atributos datavault_* da tabela, sem as regras |
— | Transporte simulado; por confirmar no tenant |
| Unity Catalog | Databricks | O trust score, o veredito, as regras falhadas, as issues por resolver e a certificação, em tags beryx_* da tabela |
O owner do dataset, as tags, os comentários e a linhagem | Transporte simulado; falta um workspace do cliente |
| Dataplex Universal Catalog | Google Cloud | Três aspects beryx-* na entry da tabela: o veredito e o trust score, uma entrada por regra e as issues abertas |
As descrições, o glossário e a linhagem | Transporte simulado; por confirmar no projeto |
| Catálogo leve da BeryX | Sem catálogo | — (fica na BeryX) | Os esquemas, as tabelas, as vistas e as colunas das conexões (inventário, cartão #682) | Na BeryX; o inventário ainda só se usa pela API |
| OpenMetadata | Sem catálogo, a precisar de 80 conectores ou mais | O veredito, o score e uma entrada por regra, como test cases | Os domínios, os owners, a sensibilidade das colunas, as descrições e o glossário | Transporte simulado; nunca experimentado no Collate |
| Collibra | Grande empresa regulada | O veredito e uma entrada por regra, como assets «Data Quality Metric» | — | Transporte simulado; por confirmar no tenant |
| Alation | Grande empresa regulada | Campos beryx_*: o veredito, o trust score, as issues abertas, a certificação e uma entrada por regra; com o veredito FALHA, uma trust flag Warning |
— | Transporte simulado; por confirmar no tenant |
| Informatica CDGC | Grande empresa regulada | O veredito com o score 0-100 e uma entrada por regra, com as linhas lidas e as falhadas, como Data Quality Rule Occurrences BERYX-… ligadas à tabela |
— | Transporte simulado; por confirmar no tenant |
| Atlan | Atlan | O custom metadata «BeryX»: o veredito, o trust score, as issues abertas, a certificação e uma entrada por regra; com o veredito FALHA, um announcement | — | Transporte simulado; por confirmar no tenant |
| DataHub | DataHub | O veredito, o score e uma entrada por regra, como assertions | Os domínios, os owners, a sensibilidade das colunas, as descrições e o glossário | Transporte simulado |
Elementos de dados críticos e dossiê regulatório
- Elementos de dados críticos (CDE): um dataset ou uma coluna marcado para o BCBS 239, a Solvência II, o DORA ou outra regulação, com referência («Princípio 3», «art. 19.º», a linha do relatório ao supervisor), owner e racional. Declaram-no um admin ou o owner de um domínio do dataset.
- Dossiê regulatório, por regulação e requisito: princípios 3 a 6 do BCBS 239; art. 82.º da
Diretiva 2009/138/CE e art. 19.º do Regulamento Delegado (UE) 2015/35, para a Solvência II; art.
8.º, n.os 1 e 4, art. 9.º, n.º 3, alínea d), e art. 12.º, n.º 7, do Regulamento (UE) 2022/2554,
para o DORA. Para cada elemento traz a definição (os termos do glossário com a ligação aprovada),
os controlos de qualidade por dimensão exigida com a tolerância de cada regra e o último
resultado, a tendência diária dos resultados na janela, os problemas abertos, o cumprimento do
SLA do domínio, a linhagem a um salto (ao nível da coluna) e a fonte autoritativa (as origens a
montante, salto a salto, até 10), o contrato ODCS e as lacunas: sem regra, sem resultado nos
últimos 30 dias, sem owner, sem definição, sem SLA. A exatidão conta com a regra
accuracye com a última auditoria de exatidão por amostragem concluída na janela; a integridade com a regrareferential. O KPI «cobertura de CDEs» conta as dimensões exigidas com controlo e resultado. - Página Elementos críticos (Governação → Elementos críticos): o dossiê filtrado por
regulação e por domínio, com cada elemento e o estado de cada requisito e dimensão. Uma dimensão
sem regras aparece «Não monitorizado», nunca a verde
(
frontend/src/pages/ElementosCriticos.tsx). - Exporta-se em Markdown, CSV e PDF, e partilha-se pelo link de leitura dos relatórios. É
determinístico, sem IA generativa e só com metadados. Nenhum estado afirma conformidade: o dossiê
aponta lacunas e não certifica (
GET /api/v1/cde/dossier,backend/app/domain/compliance/regulatory_dossier.py,backend/app/domain/compliance/cde_evidence.py). - Ciclo de vida (cartão #703): um elemento marcado nasce candidato. Submetido com a
justificação, que é obrigatória, entra no fluxo de aprovação
cde(o configurado ou, sem ele, um admin ou quem gere um domínio do dataset aprova). Quem o marcou e quem o submeteu não o aprovam. Aprovado, passa a certificado enquanto o dataset tiver a certificação em vigor e volta a aprovado quando ela cai. Desmarcar um aprovado também é um pedido no fluxo, com o motivo, e só depois de aprovado fica retirado. Rejeitado, expirado ou em conflito, volta a candidato. A API (GET /api/v1/cde) e o dossiê mostram o estado; o dossiê não traz retirados (backend/app/domain/compliance/cde_lifecycle.py). Um retirado também deixa de classificar o dataset para os agentes de IA e sai da ficha do Anexo IV; um candidato ainda conta (backend/app/domain/compliance/ai_agents.py,backend/app/domain/compliance/annex_iv.py). Na pré-visualização do pedido (#699), quem decide vê o elemento, a justificação ou o motivo, e o que o dataset passa a herdar ou deixa de herdar, que fica de fora se outro elemento aprovado do mesmo dataset já o dá (backend/app/domain/governance/request_preview.py). - O que um elemento aprovado herda: cada issue de aviso ou crítico do dataset que dá alerta
(de um scan, de um monitor, de um SLO, do dbt, de um pipeline, de BI ou de custos) leva o prazo
de resolução da política dos CDEs (24 horas por omissão, configurável em
PUT /api/v1/cde/policy) e o alerta sai crítico, marcadocde. Se o SLA de resolução da severidade (#396) der um prazo mais curto, o issue fica com esse. Um issue informativo não dá alerta e fica sem esse prazo (backend/app/domain/compliance/cde_lifecycle.py). Um canal de notificação com o âmbito «Só elementos de dados críticos» (cde_only) recebe só esses alertas: é a audiência própria dos CDEs (backend/app/domain/alerts/routing.py). O âmbito vai também nodatavault.yamle na IaC (backend/app/domain/gitops/workspace_config.py). Enquanto o dataset não está certificado, o owner do elemento tem a tarefa «Certificar o dataset de um CDE» em «As minhas tarefas», com prazo de 30 dias desde a aprovação, configurável (backend/app/domain/compliance/cde_tasks.py).
O que falta: o chip «CDE» no dataset, o KPI no painel do domínio, marcar, submeter ou desmarcar um
elemento pela interface (hoje só pela API) e a marca «fora de SLA» pelo prazo do CDE (o tick do
SLA mede pela política da severidade; o prazo do CDE fica no due_date do issue).
Testes: backend/tests/test_regulatory_dossier.py, backend/tests/test_cde.py,
backend/tests/test_cde_lifecycle.py, frontend/src/pages/ElementosCriticos.dom.test.tsx.
Privacidade do cliente: registo de tratamentos, conservação e titulares
Registo de atividades de tratamento (RGPD, art. 30.º) do cliente, uma atividade por domínio. O catálogo pré-preenche-o em cada leitura: as categorias de dados vêm do mapa de PII com a classificação revista por cima (a categoria especial do art. 9.º fica marcada), a finalidade vem da descrição do domínio e o prazo de conservação, se a atividade não o declarar, vem da regra
pii_retention_max_ageda política de dados. O admin declara as finalidades (um catálogo partilhado com a decisão de confiança por finalidade), a base legal, os titulares, os destinatários, as transferências, o prazo, as medidas e o responsável. Os datasets com dados pessoais fora de domínios aparecem à parte. Exporta-se em CSV e JSON, com a postura de evidência mínima: a BeryX guarda contagens e diagnósticos e as linhas ficam no armazém do cliente (GET /api/v1/workspace/ropa,GET /api/v1/workspace/ropa/export,backend/app/domain/privacy/ropa.py).Alertas de conservação: os datasets com dados pessoais cujo registo mais antigo, pela última atividade, passa o prazo mais curto das atividades dos seus domínios (ou o da política). É a mesma medida da regra de conservação: um perfil feito por amostra ou à janela de partições deixa o dataset «por medir», com o motivo, e a resposta conta os datasets sem prazo declarado (
GET /api/v1/workspace/retention-alerts).Pedido de acesso com descoberta nas fontes: o valor do titular (email, NIF, telefone, documento, IBAN) procura-se só nas colunas de identificador classificadas do tipo que casa com a forma do valor, com uma contagem na fonte por coluna. O valor vai como parâmetro e nunca no texto do SQL, na sessão só-de-leitura das regras SQL. A resposta diz onde há dados (dataset, coluna, quantas linhas) e nunca os valores; o trilho de auditoria guarda as contagens e os tipos procurados, sem o valor. Com o pedido do registo, traz o prazo dele, de um mês na UE e no Reino Unido (
POST /api/v1/workspace/subject-data/discover,backend/app/domain/privacy/subject_discovery.py).Página «Privacidade»: junta o registo, os alertas e os pedidos. Cada pedido aberto tem os passos que o estado deixa dar: concluir com o resultado, acusar a receção, prorrogar uma vez dentro do prazo inicial onde a lei o prevê, e pausar com o motivo ou retomar. Um passo recusado mostra a causa junto ao pedido (
PATCH /api/v1/workspace/subject-requests/{id},backend/app/domain/privacy/subject_requests.py).Deriva do RoPA (cartão #704): onde o catálogo se afasta do registo, com a idade de cada desvio. Dois desvios, só com metadados do catálogo do workspace:
- uma coluna com dados pessoais (o mapa de PII do profiling com a classificação revista por cima) num dataset fora de qualquer domínio, e por isso de qualquer atividade. Associa-se o dataset a uma atividade (entra no domínio) ou justifica-se;
- um destino que a linhagem mostra e a atividade não declara. A partir dos datasets com dados
pessoais do domínio, a travessia segue as arestas de linhagem pelos datasets do mesmo domínio;
o primeiro dataset fora dele é um destino, tal como os exposures (dbt, dashboards de BI) e os
consumidores agentes da Trust API (consumos dos últimos 30 dias) de um dataset dentro. Declara-se
o destino como destinatário (fica na atividade, em
declared_recipients) ou justifica-se.
A comparação corre no fim de cada scan, de 15 em 15 minutos no worker (para a linhagem importada e a classificação feita à mão) e com «Verificar agora». Abre o desvio que apareceu, fecha o que desapareceu e reabre, com a idade a zero, o que voltou; um justificado fica justificado enquanto existir. Cada desvio aberto é uma tarefa dos admins em «As minhas tarefas» (o RoPA é só de admins), e o destino não declarado também é um alerta no sino. Nada sai do workspace: nem as fontes do cliente nem um LLM são chamados, e os canais (Slack, Teams, webhooks) não recebem nada. O export do registo traz os desvios abertos (
GET /api/v1/workspace/ropa/drift,POST /api/v1/workspace/ropa/drift/check,POST /api/v1/workspace/ropa/drift/{id}/justify|associate|declare,backend/app/domain/privacy/ropa_drift.py,backend/app/domain/privacy/ropa_tasks.py).Finalidade e base legal pelo motor de fluxos (cartão #704): mudar a finalidade ou a base legal já declaradas de uma atividade abre um pedido
processing_activitycom a mudança retida e só o fecho aprovado a aplica. Os outros campos gravam-se logo, e declarar pela primeira vez (o campo estava vazio) também. Sem fluxo configurado, decide outro admin; quem pediu nunca decide. A atividade mostra a mudança à espera, e qualquer admin a retira com «Retirar pedido», na atividade ou em «Criados por mim»: o pedido fica cancelado e a atividade como estava. Apagar o domínio também fecha o pedido (POST /api/v1/workspace/ropa/activities/{domain_id}/pending-change/cancel,backend/app/domain/privacy/ropa_approvals.py).
O que falta: a descoberta corre no pedido, para o valor não ir para a fila, por isso tem tetos (40 colunas, 15 s por contagem, 90 s no total) e diz o que ficou de fora. As fontes sem contagem na fonte (as APIs, como o Salesforce e o SAP, o MongoDB, os streams e os documentos) ficam «não suportadas». Num ficheiro acima do teto de leitura, a contagem cobre só as linhas que o perfil lê; numa tabela SQL perfilada à janela de partições, só essa janela. Essas colunas vêm à parte, com as linhas lidas, e a página dá o resultado como incompleto mesmo sem correspondências. Um email procura-se como foi escrito e em minúsculas: guardado com outras maiúsculas, não se encontra. Na deriva, os destinatários escritos em texto não contam (só os destinos do catálogo declarados), os nós externos do OpenLineage (S3, Kafka) e os consumidores que não são agentes não são destinos, e a travessia para no primeiro dataset fora da atividade. O pedido de mudança de finalidade ou de base legal guarda os aprovadores de quando abriu, e configurar o fluxo depois não lhe mexe. Num workspace com um só admin e sem fluxo configurado, ninguém o decide: decide-o um admin que entre depois, ou quem pediu retira-o e volta a propor, já com o fluxo configurado com outra pessoa.
Testes: backend/tests/test_privacy_ropa.py, backend/tests/test_ropa_drift.py,
frontend/src/pages/Privacidade.dom.test.tsx.
Registo de definições de métricas
- Uma só definição por métrica (página Negócio, secção «Definições de métricas»;
/api/v1/metric-definitions,backend/app/domain/governance/metric_registry.py): nome, definição em linguagem de negócio, fórmula de referência (por exemplo,sum(amount - discount)), filtros, grão, dono, autoridade de definição (uma equipa ou uma pessoa), termos do glossário e datasets de origem. O nome normalizado é único no workspace:Receita Líquidaereceita-liquidasão a mesma métrica. O nome novo de uma mudança à espera também já tem dono até o pedido fechar: criar ou renomear com ele dá conflito, e o import do dbt conta-o como existente (_reserved_keys). - Versões e aprovação: um rascunho edita-se livremente. Submetê-lo abre um pedido
metric_definitionno motor de fluxos, e o fecho aprovado grava a versão 1. Numa definição aprovada nada se grava diretamente: a mudança fica retida no pedido, e só o fecho aprovado a aplica, com a versão seguinte, o pedido que a aprovou e quem o fechou. Assina o fluxo configurado para o tipo em Aprovações (sem ele, um admin) e, no fim, a autoridade de definição; sem fluxo configurado e com autoridade, assina só ela. Quem pede nunca decide, e quem pediu ou um admin retira um pedido aberto. Umbefore_flushrecusa mudar um campo de uma definição aprovada sem a versão nova na mesma transação (refuse_unapproved_changes). Se, antes do fecho, o nome novo passar a ser de outra definição, ou a equipa ou a conta da mudança deixar de valer (apagada ou desativada), o pedido fica em conflito e nada se aplica (stale_changes). O histórico lê-se emGET /api/v1/metric-definitions/{id}/versions, com as assinaturas de cada versão. - Importar do dbt:
POST /api/v1/metric-definitions/import-dbt(só admins) cria rascunhos das métricas do semantic layer já importadas que ainda não estão no registo. Quando os projetos divergem, usa a variante mais comum. Uma definição que já existe não muda, mesmo renomeada depois do import (reconhece-se pela métrica dbt de origem). - Metric drift contra a definição registada
(
backend/app/domain/lineage/metric_registry_drift.py,backend/app/domain/lineage/metric_conformance.py): cada implementação compara-se com a definição aprovada. As implementações são a métrica dbt com o mesmo nome ou label (ou a que deu origem à definição) e as métricas de negócio ligadas. Comparam-se a fórmula (average=avg,count_distinct(x)=count(distinct x)), os filtros, o grão (nas métricas de negócio) e a origem. Quando divergem, abre um issuemetric_driftpor definição, com a evidência por implementação: o aspeto, o valor registado e o implementado. O issue atualiza-se no import do manifest, no fecho de cada versão e quando uma métrica de negócio ligada é criada ou apagada (também pela cascata de uma conexão apagada). Resolve-se quando todas voltam a seguir a definição e fica silenciado se for ignorado. O detalhe da definição mostra a mesma comparação; as métricas dbt só aparecem lá em modo aberto ou a admins, como o relatório do metric drift. Uma definição importada do dbt dá por conforme a métrica de origem pela forma canónica do import só enquanto a fórmula e os filtros registados forem os dela (canonical_holds); a origem compara-se sempre. - Quem lê a evidência: o issue não tem dataset e lê-o todo o workspace, por isso só nomeia
o que um membro sem concessões vê (
every_reader). Uma métrica de negócio com uma fonte num domínio restrito, ou uma métrica dbt em modo restrito, entra só na contagem, e um dataset de origem que esse membro não vê aparece como «Dataset restrito». No detalhe da definição vale o acesso de quem pede. Quem não vê um dataset também não o liga como origem (o erro é o de um id que não existe) nem o tira ao editar a lista. - Métricas de negócio ligadas: uma métrica de negócio pode seguir uma definição aprovada
(
definition_id). O nome e a descrição passam a ser os da definição, também depois de cada versão, e a criação recusa uma agregação ou um grão que não batam com a fórmula e o grão da definição.
Testes: backend/tests/test_metric_registry.py,
frontend/src/components/RegistoMetricas.dom.test.tsx.
Políticas e normas
- Políticas e normas com ciclo de vida: cada política tem tipo (qualidade, privacidade,
segurança, ciclo de vida e retenção, ética da IA ou definições), finalidade, texto, dono,
versão e âmbito (domínios e camadas medallion). Quem a edita submete-a para revisão, e só com
dono; um admin aprova-a ou devolve-a a rascunho e retira-a em qualquer estado. Em revisão não
se edita, e editar uma aprovada abre a versão seguinte em rascunho. Edita-a o dono ou um
admin, e, sem dono, quem escreve. Cada mudança fica na auditoria
(
backend/app/domain/governance/policies.py). - Revisão periódica: ao aprovar, a próxima revisão fica marcada (por omissão a 365 dias,
entre 30 e 1095). Com a antecedência configurada (por omissão, 30 dias), o worker abre a
tarefa de revisão ao dono, com aviso no sino e por email. O dono vê-a em «As minhas tarefas»
(
/tarefas) e fecha-a com «Marcar revisão feita»; a revisão seguinte conta a partir daí e volta a avisar (backend/app/domain/governance/policy_tasks.py). - Controlos ligados e cumprimento calculado: a política liga até 50 controlos que já correm:
regras da política de dados por camada (POLICYCHK), regras de qualidade e o contrato de dados
de um dataset. Ligar controlos não abre versão nova. O cumprimento calcula-se a cada leitura,
controlo a controlo e dataset a dataset: a regra da política de dados efetiva de cada dataset
do âmbito (a mesma do veredito), o último resultado da regra de qualidade e o estado do
contrato, lido como no veredito e na certificação. Um contrato sem versão ativa, ou com uma
coluna prometida que o dataset já não tem, falha; só com avisos, cumpre. Uma regra desligada,
que nunca correu ou cujo último resultado ficou por avaliar (como em Regras) fica «não medida»
e não entra na percentagem, que é (cumpre + com exceção) / (cumpre + com exceção + falha), da
política e de cada controlo. Num dataset que nenhum scan analisou, as regras da política de
dados que leem o score, o perfil ou a PII detetada também ficam «não medidas», e uma violação
que já se vê conta. As regras
require_owner,require_active_contracterequire_readmecontam sempre. Cada leitura avalia até 500 datasets do âmbito (backend/app/domain/governance/policy_compliance.py). - Exceções que expiram: quem escreve pede uma exceção a um controlo ligado, para um dataset
(ou todos os do controlo) e uma coluna (ou o dataset inteiro), com justificação (10 a 2000
caracteres) e data de fim obrigatória, no máximo a 365 dias. Numa regra de qualidade ou num
contrato, o dataset é o do controlo. Com o fluxo de aprovação das exceções ligado, o pedido
entra no motor de fluxos e decide-se em «As minhas tarefas»; sem ele, decide um admin. Nos
dois casos, quem a pediu nunca a decide, mesmo que outra pessoa a submeta ao fluxo: no fluxo
não conta como aprovador da etapa, nem por delegação, escalonamento ou fallback, e, se era o
único, decidem os outros admins (
backend/app/domain/governance/approvals.py,backend/app/domain/governance/approval_config.py). Aprovada e por expirar, a exceção faz contar a falha que cobre como «com exceção»; numa falha em várias colunas, só cobre se todas tiverem exceção (num contrato partido, as colunas que já não existem). Sete dias antes do fim, o worker põe-na nas tarefas de quem a pediu e do dono da política, com aviso no sino e por email. Passada a data de fim, a verificação volta a contar como incumprimento logo na leitura seguinte, sem esperar pelo worker, que a marca expirada. Renova-se com um pedido novo (backend/app/domain/governance/policy_waivers.py). - API e MCP:
/api/v1/policies(backend/app/api/v1/policies.py; com domínios restritos no workspace, só admins). O MCP lê as políticas e as exceções comget_policyelist_waivers, só de leitura (backend/app/mcp_policies.py). Na app, as políticas gerem-se na Governação, e as revisões e as exceções perto do fim aparecem em «As minhas tarefas» (/tarefas,frontend/src/pages/Tarefas.tsx), onde um pedido de exceção abre a política.
O que falta: o cumprimento calcula-se na leitura e não guarda histórico, e os avisos de revisão e de fim de uma exceção são pessoais (sino e email): não vão aos canais de notificação do workspace.
Testes: backend/tests/test_policies.py, backend/tests/test_mcp_policies.py.
Maturidade da gestão de dados pela evidência
- Modelo por capacidade: as 11 áreas de conhecimento do DAMA-DMBOK2 (governação,
arquitetura, modelação, armazenamento e operações, segurança, integração e
interoperabilidade, documentos e conteúdos, dados de referência e mestres, data warehousing e
BI, metadados e qualidade), cada uma com um nível de 0 (inexistente) a 5 (otimizado)
(
backend/app/domain/governance/maturity.py). O mapeamento DCAM/CDMC do EDM Council fica de fora até haver licença. - Pontuada pela evidência: cada capacidade com evidência no produto pontua-se com
indicadores que o BeryX já mede: cobertura de owners, documentação, classificação,
contratos e regras (os KPIs do painel de governação), datasets com linhagem, CDE aprovados e
com responsável, problemas resolvidos dentro do prazo e cumprimento do SLO nos últimos 30
dias, e políticas aprovadas com a revisão em dia. Cada percentagem passa a nível por faixas
(0 % → 0; abaixo de 25 % → 1; 25–49 % → 2; 50–74 % → 3; 75–89 % → 4; 90 % ou mais → 5) e a
capacidade fica com a média dos níveis medidos. A nota calcula-se a cada leitura, por isso
muda sozinha quando a evidência muda, e cada nota traz os indicadores que a sustentam, com
valor, amostra, nível e a página onde se vê
(
backend/app/domain/governance/maturity_service.py). - Respostas manuais onde não há evidência: um admin dá um nível manual (com justificação obrigatória) e um alvo (1 a 5; por omissão, 3) a cada capacidade. A resposta só conta enquanto a capacidade não tem nenhum indicador medido: a evidência ganha sempre. Cada mudança fica na auditoria, sem o texto da justificação.
- Histórico e mapa de calor: o worker escreve um fecho por trimestre e por workspace, reescrito uma vez por dia enquanto o trimestre corre. O mapa de calor cruza capacidades com os últimos 8 trimestres e o corrente, contra o alvo. Com domínios restritos, o histórico do workspace inteiro só aparece a quem vê todos os domínios, e a avaliação do workspace só a admins.
- Pacote do conselho:
GET /api/v1/governance/maturity/board-pack.mddá a avaliação em Markdown, com o resumo, o mapa de calor, a evidência ou a justificação de cada capacidade e o método, sem nomes de pessoas, datasets nem colunas. Na app, o painel fica na Governação (frontend/src/components/MaturidadeDados.tsx).
O que falta: as lacunas ainda não viram tarefas na caixa. O pacote do conselho (secção seguinte) leva o resumo e a nota de cada capacidade contra o alvo; o mapa de calor e a evidência ficam só neste Markdown.
Testes: backend/tests/test_maturity.py.
Pacote do conselho, registo de decisões e atas
- Pacote do conselho por período: um admin gera o pacote de um período (até 366 dias, sem
futuro). Os números calculam-se no pedido, sem IA, e ficam
gravados à data de referência com o SHA-256 do JSON canónico. Cada leitura recalcula o hash e
diz se ainda bate (
hash_valid) (backend/app/domain/governance/board_pack.py,backend/app/domain/governance/board_service.py). - KPIs: cobertura dos CDE e o estado de cada um, pelo pior das dimensões exigidas (os
mesmos do dossiê regulatório); problemas abertos, críticos, com o SLA ultrapassado, abertos e
resolvidos no período, e o cumprimento dos prazos do painel de governação; exceções em vigor,
pendentes, a expirar em 30 dias e a idade média e máxima; cobertura do catálogo (owner,
documentação, classificação); pedidos de acesso decididos em que cada etapa do fluxo com SLA
fechou dentro do prazo, refeito do início da etapa e do SLA copiado no pedido (a aprovação
automática conta no prazo; sem SLA, fica fora da conta), e os pendentes fora do prazo. A
maturidade é a avaliação da secção anterior à data de referência, com os alvos gravados por
capacidade: a nota global de 0 a 5, as capacidades avaliadas, no alvo e com lacuna, e a nota, o
alvo e o estado de cada capacidade, só pelo código. Ao LLM segue só o resumo; na app, as
capacidades com lacuna aparecem pelo nome (
frontend/src/components/PacoteConselhoDetalhe.tsx). - Top riscos: CDE em falha, problemas críticos abertos, exceções em vigor (e as que expiram) e acessos totais a dados sensíveis concedidos no período, também os que entretanto expiraram ou foram revogados, até 10 de cada.
- Narrativa pela IA, só sobre agregados: a narrativa (síntese, riscos, decisões pedidas)
escreve-se na fila
board_pack.requestedpela fachada LLM, com a guarda de saída. O prompt leva só contagens, percentagens e códigos: nenhum nome de dataset, coluna, política, equipa ou pessoa (board_pack.narrative_input). Sem modelo disponível, o template offline escreve as mesmas secções. - Decisões pedidas e registo de decisões: o pacote regista como pendentes as decisões que
pede ao conselho: aprovar as políticas em revisão, aceitar o risco das exceções pendentes ou
a expirar e fixar a tolerância dos CDE em falha. Uma decisão ainda aberta não se repete no
pacote seguinte, e a que o conselho já aprovou ou rejeitou não volta a ser pedida (a de uma
política volta quando a política muda de versão). Um admin também pede decisões à mão (uma
igual ainda aberta dá conflito) e regista o resultado (aprovada, rejeitada ou adiada) com
fundamentação obrigatória. Aprovada ou rejeitada, a decisão fecha.
Cada decisão fica ligada à política, exceção ou CDE que decide, com o nome e a versão da
política no momento da decisão, e cada passo fica na auditoria. Registar a decisão não muda a
política nem a exceção, que seguem o seu ciclo
(
backend/app/domain/governance/board_decisions.py). - Atas: a ata da reunião fica no pacote e não se aprova enquanto a narrativa está a ser
escrita. Aprovada, fecha com o seu SHA-256 e deixa de se editar, e o pacote fecha com ela: a narrativa já não se
reescreve e nenhuma decisão se pede nem se toma nele (409
board_pack_locked); a que ficou em aberto continua no registo. Cada pedido de outra narrativa fica na auditoria (board_narrative_requested), com o SHA-256 da narrativa substituída (backend/app/domain/governance/board_service.py). - Na app: em Governação, só para admins, gera-se o pacote, leem-se os KPIs, os top riscos e
a narrativa (com o rótulo de IA e o modelo, ou o aviso do modo offline), decide-se o que o
pacote pede e escreve-se e aprova-se a ata (
frontend/src/components/PacoteConselho.tsx). Os erros da API saem na língua do pedido (pt, en, es). - API:
/api/v1/governance/board(backend/app/api/v1/board.py), só para admins.
O que falta: o pacote não tem exportação em PDF ou PPTX e não se agenda (gera-se a pedido). Registar uma decisão não aplica nada na política ou na exceção.
Testes: backend/tests/test_board_pack.py, frontend/src/components/PacoteConselho.dom.test.tsx.
Controlos-chave do CDMC: estado e evidência
- Mapa dos 14 controlos-chave: os 14 controlos-chave do CDMC (EDM Council) ligam-se às
verificações que a BeryX já mede: políticas e o cumprimento calculado, owner, certificação,
registo de tratamentos, regra de conservação da política de dados, catálogo, classificação,
domínios restritos e revisões de acessos, regras e resultados, tamanho e custo de compute, e
linhagem. Só entram os datasets sensíveis, com colunas
confidentialoupii(backend/app/domain/compliance/cdmc.py). - Estado calculado da evidência: uma verificação fica
metsó com evidência escrita (o que se observou e de onde veio). Um controlo ficaevidencednum dataset só com todas as verificaçõesmet, e no workspace só com todos os datasets sensíveisevidenced. Com datasets sensíveis fora do teto de 500, que ficam por avaliar, nenhum controlo ficaevidencedno workspace. Um workspace sem datasets sensíveis ficano_sensitive_data, que não é verde. Um scan ou um resultado com mais de 30 dias e uma revisão de acessos com mais de 365 dias já não contam. Os controlos das políticas só contam com todos medidos no dataset: uma exceção aprovada conta, mas fica dita à parte. - Lacunas assinaladas: cada verificação que falha diz o código da lacuna e quantos datasets a
têm. O que a BeryX não mede fica
product_gape nunca conta: a localização da fonte, a cifra em repouso na fonte, a DPIA e o custo do movimento dos dados. Por isso os controlos 4, 9, 10 e 13 nunca ficam verdes. - Evidência exportável:
GET /api/v1/compliance/cdmcem JSON,.mde.csv(uma linha por dataset × controlo × verificação, com a evidência e a lacuna), calculado na leitura, sem LLM e sem tocar nas fontes. Cada pessoa vê os datasets sensíveis a que tem acesso (backend/app/domain/compliance/cdmc_service.py). Na app, o painel fica na Governação (frontend/src/components/ControlosCdmc.tsx).
O que falta: a BeryX não está certificada no CDMC, e o mapa não é uma declaração de conformidade.
A licença do enquadramento e o custo da avaliação são decisões do owner. O mapeamento, as lacunas
do produto e o caminho para certificar estão em docs/compliance/cdmc.md.
Testes: backend/tests/test_cdmc.py.
Agentes e modelos de IA: registo, ficha do Anexo IV e acesso pelo MCP
- Registo de agentes e modelos ligados aos datasets que consomem. A ficha de cada um diz o
modelo, a finalidade, o nível de risco do AI Act (ou o sistema de IA do art. 10.º a que
pertence), as ferramentas MCP e os domínios permitidos e o responsável. Estados: proposto,
aprovado, suspenso e retirado. Aprovar exige finalidade, e mudar a finalidade, o modelo, as
ferramentas ou os domínios de um agente aprovado devolve-o a proposto
(
GET/POST /api/v1/compliance/ai-agents,backend/app/domain/compliance/ai_agents.py). - Identidade por agente nos tokens do MCP: um token
dvk_*criado comagent_idé a identidade desse agente, e o servidor MCP manda o nome da ferramenta em cada chamada (X-Beryx-MCP-Tool;read_resourcenas leituras de resources). Em cada pedido, a API aplica a ficha: o kill-switch global (PUT /api/v1/compliance/ai-agents/kill-switch) e o do agente (suspenso ou retirado) param tudo; com ferramentas na ficha, uma chamada fora da lista é recusada; a ferramenta do cabeçalho só vale numa rota que ela chama, porque o cabeçalho é de quem chama e o token também serve a API REST (backend/app/domain/compliance/ai_agent_tools.py, cartão #741); um dataset classificado, com colunas PII ou confidenciais ou marcado como CDE, só o lê um agente aprovado, com finalidade declarada e com o go-live completo, se o dataset lhe estiver ligado e por uma ferramenta que a matriz de direitos deixa chegar a dados classificados; e, com domínios na ficha, só os datasets desses domínios. Um id que a ficha fecha, no path ou na query, dá 403 com o motivo, e um domínio que tenha um desses datasets também,/domains/{domain_id}incluído. As listagens sem filtro deixam esses datasets de fora e, dos domínios que os têm, o relatório, a certificação e a pontuação do domínio: a lista de domínios mostra-lhes o nome, como a de um domínio restrito. Enquanto a ficha lhe retiver datasets ou domínios, fecham-se-lhe as vistas do workspace inteiro (painéis, grafo do conhecimento, qualidade dos metadados) e as leituras que não filtram por dataset (o progresso de um scan, o sistema de IA do art. 10.º, as integrações, as listas de referência); ficam abertos os pedidos de ação do próprio token (backend/app/api/agent_access.py). No gate RAG, um corpus que a ficha fecha é «não encontrado». Um token de agente não leva o scopeconfig, que aplica odatavault.yaml. - Go-live e operação de cada agente (cartão #725,
backend/app/domain/compliance/ai_agent_golive.py):- Donos: o dono do agente e o dono do prompt são pessoas do workspace
(
PUT /api/v1/compliance/ai-agents/{agent_id}/owners). Trocar uma pessoa por outra abre uma passagem com um runbook de quatro passos: rever a matriz de direitos, o registo de risco, os tokens do agente e os ensaios. Só o novo dono marca os passos e aceita a passagem (POST /api/v1/compliance/ai-agents/handovers/{handover_id}/accept). Enquanto a passagem está aberta, o agente não lê dados classificados. - Matriz de direitos (
PUT /api/v1/compliance/ai-agents/{agent_id}/rights): por ferramenta MCP, se chega a dados classificados, os domínios (dentro dos da ficha), o teto de pedidos por hora (acima dele, 429agent_tool_limit) e os tipos de ação que umpropose_actionpode propor (agent_action_not_allowed). Com matriz, as ferramentas da ficha são as dela, e mudá-la num agente aprovado devolve-o a proposto. - Checklist de go-live (
GET /api/v1/compliance/ai-agents/{agent_id}/golive): donos sem passagem aberta, matriz definida, datasets ligados todos certificados, classificação desses datasets verificada por uma pessoa ou pelo catálogo (nenhuma coluna só com a classificação automática ou por classificar), os três ensaios de falha passados dentro do intervalo da ficha (90 dias por omissão), avaliação de impacto registada e aprovação registada por uma pessoa. Aprovar exige os outros seis itens (409ai_agent_golive_incomplete). Sem os sete, o agente não lê dados classificados. Com os sete, lê só os classificados ligados, os que a checklist verificou: um classificado que não lhe está ligado dá 403agent_classified_deniede fica fora das listagens. A checklist lê-se em cada pedido do agente: um certificado que cai, uma coluna nova por classificar ou um ensaio que vence fecham-nos no pedido seguinte. Mudar a ficha apaga a avaliação de impacto. Ligar outro dataset a um agente aprovado conta como mudança da ficha: o agente volta a proposto e o dataset entra na checklist. - Kill switch por agente (
POST /api/v1/compliance/ai-agents/{agent_id}/kill): suspende o agente e revoga todos os tokens dele, incluindo os que estão na graça de uma rotação. O pedido seguinte de qualquer um deles dá 401. Um stream aberto (/events/*) verifica o token a cada 15 s, antes da mensagem seguinte, e fecha. Como o sinal de vida sai de 15 em 15 s, o fecho pode levar até 30 s. Fica emadmin_events(ai_agent_killed, com quem, o estado anterior e quantos tokens revogou). Reativar o agente não devolve os tokens revogados. - Registo de risco (
GET /api/v1/compliance/ai-agents/risks,backend/app/domain/compliance/ai_agent_risks.py): por agente, cada risco tem dono, probabilidade e impacto de 1 a 5, a pontuação (o produto) e o nível (baixo, médio, alto, crítico), a mitigação, o estado e a data da próxima revisão. O dono do risco ou um admin regista a revisão (POST /api/v1/compliance/ai-agents/risks/{risk_id}/review), e a data seguinte conta a partir daí. Um risco por fechar com a data passada está em atraso. - Ensaios de falha (
POST /api/v1/compliance/ai-agents/{agent_id}/drills,backend/app/domain/compliance/ai_agent_drills.py): três cenários contra as guardas reais, com valores sintéticos, sem ler a fonte nem chamar um LLM. Na fuga de PII, um email, um NIF e um IBAN passam pela guarda de saída dos LLM e os datasets classificados do workspace são pedidos pelo agente sem o go-live, e os que não lhe estão ligados também com o go-live completo. No dado envenenado, cinco pedidos dirigidos a um modelo passam pelo detetor de injeção. Na ação proibida, entram uma ferramenta fora da matriz, o teto por hora, um tokenconfige um voto de aprovação por um token. Cada verificação fica gravada como passou, falhou ou não se aplica, e o ensaio falha se a guarda que exercita falhar (por exemplo, com a guarda de saída desligada). Cada cenário tem de voltar a passar dentro do intervalo, senão o item dos testes de guarda cai. - Pacote do conselho (
GET /api/v1/compliance/ai-agents/board-pack, também em Markdown comboard-pack.md): o registo de risco (por nível, em atraso, sem dono e a lista do maior para o menor) e os ensaios dos últimos 90 dias por cenário e desfecho, com os agentes a quem falta um cenário, falhou ou venceu. Só contagens, nomes de agentes, títulos e datas.
- Donos: o dono do agente e o dono do prompt são pessoas do workspace
(
- Registo de uso: cada pedido de um agente fica registado, aceite ou recusado, com o agente, a
ferramenta, a rota, os datasets, se eram classificados, o motivo da recusa e o
X-Request-ID. Numa listagem sem dataset no pedido, «classificado» diz se o que o agente vê inclui datasets classificados. Só ids e nomes, nunca o que foi lido (GET /api/v1/compliance/ai-agents/usage, filtros por agente, dataset, desfecho e data). - Ficha de dados do Anexo IV (ponto 2, alínea d)) por agente ou modelo, gerada a partir da
proveniência (fonte, tabela, camada e datasets a montante), do perfil (linhas, colunas e colunas
com valores em falta), da qualidade, da classificação e dos sinais de enviesamento do
art. 10.º, com as lacunas escritas. Exporta-se em JSON e Markdown
(
GET /api/v1/compliance/ai-agents/{agent_id}/annex-iv.md,backend/app/domain/compliance/annex_iv.py). - IA sombra: os consumidores do tipo agente que registam consumos pela Trust API sem registo
aparecem no registo como «não autorizados»; registá-los com
data_consumer_idtira-os dessa lista. - Pré-verificação de um SQL antes de o correr (cartão #701): o agente manda a consulta e a
finalidade e recebe a decisão de confiança de cada tabela que ela lê (joins, CTEs, subconsultas,
nomes citados), com a pior a mandar no resumo. Uma tabela que não corresponde a um dataset que o
agente vê é
unknown, nuncatrusted, e uma tabela fora do âmbito do token responde como uma que não existe. O SQL só se lê com o sqlglot, nunca corre (POST /api/v1/trust/check-sql, tool MCPcheck_sql_trust,backend/app/domain/quality/trust_sql.py). - Números que se defendem (cartão #726): o agente manda o número que vai responder e como o
mediu (dataset, agregação, coluna, filtros e período) ou o SQL que o gerou. A BeryX recalcula-o na
fonte, só por leitura, com o timeout e os tetos de custo da conexão, compara-o com uma tolerância
e devolve
verified,divergent(com o valor da fonte e a evidência) ouunverifiable(com o motivo). Um número que bate com uma fonteuntrustedou sem scan ficaunverifiable. Das colunas sensíveis, a métrica só faz contagens. O SQL do agente passa a guarda das regras SQL e não as lê, nem com outro nome (um*, uma lista de aliases, mesmo no alias da própria tabela, uma UNION). Cada verificação fica no registo de saídas, por agente, e na auditoria (POST /api/v1/ai-outputs/verify,GET /api/v1/compliance/ai-agents/{agent_id}/outputs, tool MCPverify_claim,backend/app/domain/quality/claim_check.py). - A definição que vale, no MCP (cartão #760): o resource
metrics://definitionsdá a um agente as definições aprovadas do registo de métricas, com a fórmula, os filtros, o grão, a versão aprovada, o dono e a autoridade de definição (só o nome) e os datasets de origem que quem chama vê (backend/app/mcp_metrics.py). Comdefinition_id, overify_claimrecalcula o número com a fórmula e os filtros dessa versão, sobre um dataset de origem da definição, em vez da agregação que o agente manda; a consulta passa a guarda do SQL do agente, e uma mudança à espera de aprovação só conta depois de aprovada (backend/app/domain/quality/claim_definition.py). - O agente que perguntou e não leu fica registado (cartão #730):
POST /trust/consumptionscomconsumed: falsegrava uma abstenção, com a decisão que o agente viu. Conta à parte dos consumos (abstentions_7dem «Consumidores» do dataset e emGET /consumers), não avisa os canais nem o sino como uma leitura emuntrusted, e no impacto de um incidente o agente que não lê nenhum dataset do alcance sai travado («1 agente travado pela decisão de confiança») em vez de afetado. O SDK regista assim os datasets que atingem ofail_on(trust.checke oTrustGatequando recusa) e a tool MCPget_trust_decisioncomconsumer_nameregista-a numa decisãountrusted(backend/app/domain/lineage/data_consumers.py). - Os agentes nunca aprovam nem certificam: um token só escreve nas operações que a API lhe
abre, e a única ação de um agente é propor (
propose_action), que uma pessoa decide na app. - Conteúdo de origem marcado nos resultados do MCP: cada ferramenta e cada resource dizem no
_meta(beryxtech.com/provenance) que campos trazem texto das fontes e das pessoas do workspace e quais têm a forma de um pedido a um modelo, sem apagar nem alterar o texto. As instruções do servidor lembram o agente de que esses campos são dados, nunca ordens (backend/app/mcp_provenance.py, servidor MCP).
O que falta: com domínios na ficha, a lista de domínios (GET /api/v1/domains) continua a mostrar
os nomes dos de fora, sem os datasets nem a pontuação. Uma lista de referência importada de uma
coluna guarda só o nome «dataset.coluna», sem o id do dataset, por isso fecha-se inteira ao agente
com datasets retidos. A lista de ferramentas vale para os pedidos
que chegam pelo servidor MCP: quem tem o token pode chamar a API com outro cabeçalho. A guarda da
classificação e dos domínios não usa esse cabeçalho, mas a matriz de direitos sim: sem o cabeçalho
de uma ferramenta da matriz, um pedido não chega a dados classificados. A aprovação é direta, por um
admin; o fluxo de aprovação por etapas ainda não cobre agentes. Os ensaios de falha correm quando um
admin os pede: nenhum agendador os corre sozinho, e um ensaio vencido fecha os dados classificados
até voltar a passar. O pacote do conselho da app (em Governação) ainda não traz a secção
dos agentes: lê-se pela API. Não há página na app: o registo gere-se pela API.
Testes: backend/tests/test_ai_agents.py, backend/tests/test_ai_agent_golive.py,
backend/tests/test_mcp_server.py,
backend/tests/test_mcp_provenance.py, backend/tests/test_trust_sql.py.
Orquestração e fluxo de trabalho
- As minhas tarefas (página
/tarefas,GET /api/v1/tasks): a caixa de cada owner e steward com o que espera uma decisão dessa pessoa — termos do glossário em rascunho e ligações propostas (quem gere o domínio do termo, ou um admin), pedidos dos fluxos de aprovação (quem decide a etapa atual: o aprovador, o delegado de alguém ausente, quem recebeu o escalonamento ou, sem ninguém por votar, um admin), propostas de contrato a que o produtor respondeu (admins), pedidos de ações da IA (o papel que a política exige), issues atribuídos e, desde o cartão #669, a revisão periódica das políticas de que a pessoa é dono e as exceções perto do fim (quem a pediu e o dono da política). Cada tarefa só aparece a quem a pode decidir, com a regra da rota de origem (um issue ou uma ligação pede permissão de escrita no dataset, e um pedido de aprovação deixa de fora as ligações a datasets que a pessoa não vê), com prazo e o SLA vencido primeiro; filtra-se por domínio e por tipo. Com o fluxo dos termos ligado, um termo em rascunho só chega à caixa depois de submetido, como pedido de aprovação. Com o das ligações, uma ligação sem pedido abre o glossário, onde «Aceitar» a põe no fluxo. Aprovar ou rejeitar na caixa chama a rota do ecrã de origem (mesmo serviço e mesma auditoria): num pedido de aprovação é o voto na etapa, com o motivo opcional da rejeição; um lote de ações da IA só abre em Equipa → Ações da IA, onde se vê o que executa. A contagem fica no menu e num resumo na página inicial. Um aviso no sino e um email por pessoa saem quando a tarefa chega e quando o prazo vence (app/domain/governance/tasks.py). Num pedido de aprovação, o prazo é o SLA da etapa, cada etapa avisa quem a decide e quem recebe o escalonamento também é avisado. A chegada de um issue que uma pessoa atribuiu só vai ao sino, porque essa atribuição já manda o email; a de um issue que o sistema atribui, como o de um SLO violado, segue por email. Um agente lê a caixa pela tool MCPlist_my_tasks(backend/app/mcp_tasks.py, só de leitura): com as credenciais de uma pessoa vem a caixa dela e com um token vem vazia. - Orquestradores: webhook do dbt Cloud (scan no fim de cada job) e pacote Airflow com um gate de qualidade numa DAG — orquestradores.
- Integradores: saúde dos pipelines Fivetran e Airbyte (só leitura, APIs oficiais) — sync falhado ou atrasado por dataset de destino, latência e reconciliação origem↔destino e o sync no diagnóstico de causa-raiz — integradores.
- Ferramentas de BI: Power BI, Tableau e Looker (só leitura, APIs oficiais) — refreshes falhados ou atrasados como issues, dashboards na linhagem e no impacto das issues com o dono, e aviso de dashboards sem visualizações — ferramentas de BI.
- CI/CD: gate de regras e contratos no pipeline, data-diff entre base e candidato — gate de qualidade em CI.
- Agentes: servidor MCP para Claude e outros clientes MCP — servidor MCP.
- Validação de registos no ponto de entrada: o integrador ou a pipeline mandam até 5 000
registos a
POST /api/v1/datasets/{id}/validateantes de os escrever e recebem o veredito de cada um com as regras ativas do dataset (not_null,pattern,range,required_fields,valid_identifiercom os identificadores do registo por país,in_reference_setcom as listas ISO e as do workspace), o código do motivo de cada falha e o veredito do lote. As regras do dataset inteiro (unique,freshness…) vêm marcadas como não aplicáveis. Nada é guardado nem enviado a um LLM e a fonte não é lida: rejeitar ou pôr em quarentena fica do lado do cliente. Tokendvk_*ou SDK (dv.validate.records) — validação de registos. - IDE: extensão VS Code com a qualidade inline nos ficheiros dbt/SQL — score, issues, contrato e PII por tabela, perfil por coluna no hover, alertas de política e data-diff do branch — extensão VS Code.
- Configuração como código: export/import determinístico da configuração do workspace em
YAML (
datavault.yaml), para rever em code review e promover entre ambientes. O ficheiro leva as regras (rules), os domínios (domains), a política de dados (data_policy), a política de IA (llm_policy), os canais de alerta (channels), os relatórios agendados (report_schedules), o glossário de negócio com as ligações aprovadas (glossary) e, por conexão, a agenda de scans e a ativação dos monitores automáticos (scan_schedules,auto_monitors) e o ambiente da fonte (connection_environments) — sem segredos nem ids. O import mostra primeiro o plano (o diff do que muda) e aplica tudo ou nada. O cliente corre-o a partir do próprio CI com o SDK (beryx config planno PR,beryx config applyno merge, sobre/api/v1/workspace/config) e um token «Configuração como código»; as mudanças às políticas de IA e de dados e ao ambiente das conexões ficam com um admin. O operador também o corre combackend/scripts/workspace_config.py, que corre junto do backend (no pipeline). Pela API, regras, domínios, canais e relatórios agendados gerem-se recurso a recurso em/api/v1/iaccom o mesmo token — é o que usa o provider Terraform (integrations/terraform). Monitores definidos noutras ferramentas (ex.:montecarlo.yml) não se importam: as regras entram pelos formatos do import — dbt, Great Expectations, SodaCL e ODCS (o que converte), mais os exports das suites legadas do Informatica Data Quality e do Collibra DQ — e saem em dbt, SodaCL e GX: testes dbt, checks SodaCL ou uma suite do Great Expectations (GET /api/v1/datasets/{id}/rules/export?format=dbt|sodacl|gx; o workspace inteiro num só ficheiro SodaCL porGET /api/v1/rules/export). No SodaCL e no GX só sai o que o import lê de volta como a mesma regra; o que fica de fora (regras de streams, dígito de controlo, regras SQL fora do dbt, tolerâncias acima de zero) vem no ficheiro com o motivo, no cabeçalho do SodaCL e emmeta.beryx.unsupportedno GX (o que sai). - Fluxo de problemas com estados, responsáveis, escalas e agendamento de scans e relatórios.
Testes: backend/tests/test_orchestrators.py, backend/tests/test_data_diff.py,
backend/tests/test_mcp_server.py, backend/tests/test_workspace_config_gitops.py,
backend/tests/test_iac_api.py, backend/tests/test_ide.py,
backend/tests/test_record_validation_api.py, integrations/vscode/test/.
Experiência para utilizadores de negócio
- Relatórios por persona (engenharia, gestão, executivo) com impacto estimado em euros, em PDF, agendáveis e partilháveis por link de leitura.
- Painel de governação para o CDO: a eficácia do programa num só sítio — cobertura (owner,
documentação, classificação, contratos, regras), cycle time e MTTR dos críticos, cumprimento do
SLO/SLA e dos prazos, adoção de sugestões e de regras «nunca mais», score por dimensão,
incidentes críticos e uso —, cada KPI com meta por workspace ou organização, semáforo, tendência
mensal e drill-down por domínio; export do scorecard executivo (CSV) e da evidência para o pack
de procurement (Markdown) (
GET /governance,backend/app/domain/governance/kpis.py). - Insights de negócio (página Negócio): métricas definidas pelo utilizador sobre os datasets,
em linguagem de negócio (
GET /business-metrics,backend/app/domain/insights/business_insights.py).- Definição: soma, contagem, contagem de distintos ou média; em euros, clientes, contagem ou número; por dia ou por semana. A coluna de data é do tipo data ou data e hora (no SQLite, também texto) e a soma e a média pedem uma coluna numérica. A criação recusa uma coluna classificada como dados pessoais ou confidenciais quando a métrica mostraria os valores dela (segmento, data, valor de uma soma ou média); a contagem de distintos aceita-a.
- Cálculo: em cada scan, só em PostgreSQL, MySQL, SQL Server, Fabric, Snowflake, BigQuery, Databricks e SQLite (a criação recusa as outras fontes), com uma consulta agregada só de leitura: sem linhas, só o agregado de cada período e segmento. Uma coluna classificada depois deixa de sair: o segmento sai da leitura e do scan seguinte, e uma data ou um valor param a origem.
- Insight: a métrica, o período, a variação face ao período anterior e ao habitual (o mesmo dia da semana ou as 8 semanas anteriores) e a evidência: os datasets, a consulta e os problemas abertos nos dados. Na soma e na contagem, a variação decompõe-se por origem e segmento (a média e a contagem de distintos têm uma só origem e a criação recusa-lhes o segmento, porque as partes não somam a variação do total), e a parte que está em origens com problemas abertos nos dados (frescura, volume, estrutura, regras) separa «rever os dados» de uma variação sem problemas nos dados, sem afirmar causas. Um período que alguma origem ainda não calculou fica «por atualizar», nunca a zero. Com a métrica em euros ou clientes (fora da média), mostra também uma estimativa do valor em linhas com regras a falhar no último scan.
- Resumo: escrito por templates em português, inglês e espanhol, sem LLM, com as origens de cada métrica e o link para a página; segue por email, Slack ou Teams com frequência semanal ou diária.
- Visões por equipa (
GET /team-lens,backend/app/domain/access/team_lens.py): os dashboards de engenharia, gestão e executivo, o calendário de qualidade, o scorecard por dimensão e o SLO (com os CSV) aceitam?team=(global,mineou o id da equipa). O âmbito de uma equipa são os datasets dos domínios onde tem uma concessão em vigor e os que têm como owner um membro ou o gestor da equipa; semteam, quem vê o global recebe o global e os outros as suas equipas.- Política do admin (
PUT /team-lens/policy, auditada emteam_lens.policy_updated):open(por omissão, todos veem tudo),aggregates(o detalhe só das suas equipas, os KPIs das outras) ouisolated(as outras equipas saem doAccessScope: os datasets e domínios só delas dão 404 em toda a API, incluindo «As minhas tarefas»). A visão global e a comparação entre equipas são do admin e dos papéis emglobal_roles. Uma política gravada que não se reconhece valeisolated, semglobal_roles(team_lens.policy_of). - KPIs da lente (
GET /team-lens/kpis): score, problemas abertos, MTTR a 30 dias, correções aplicadas e cobertura de regras;GET /team-lens/comparisonpõe as equipas lado a lado, só para quem vê o global (backend/app/domain/scans/team_kpis.py). - Gestor da equipa:
PUT /teams/{team_id}/manager(admin, auditado emteam_manager_set); conta para a lente e para o modo isolado como um membro. - Relatório do workspace por equipa:
POST /reportscomteam_id(só noworkspace_overview) monta o contexto só com os datasets, problemas, regras, duplicados e sinais do âmbito da equipa, sem o custo LLM do workspace (backend/app/domain/insights/overview.py). Oteam_idtem de ser o id de uma equipa do workspace com datasets no âmbito (422 senão). Os datasets ficam gravados no pedido (AIReport.team_scope_json): o contexto e os gráficos do export usam só esses, o texto gravado de problemas e sinais que nomeia um dataset de fora sai «Dataset restrito», e lê o relatório quem temreadem todos eles, também depois de uma concessão retirada. O relatório de equipa não entra na base de conhecimento, onde ficaria sem dataset e legível a todo o workspace (backend/app/domain/knowledge/service.py,ingest_report).GET /issues,GET /suggestions,GET /reports,GET /taskseGET /tasks/countaceitam o mesmo?team=. - Scans e amostras:
GET /scans,GET /scans/{scan_id},GET /scans/{scan_id}/live,GET /duplicateseGET /rules/{rule_id}/failing-rows.sqlaceitam o mesmo?team=. Um scan está na lente quando leu um dataset da equipa (team_lens.scan_in_lens, a regra do worker); fora dela, o 404 de sempre. - Envio à equipa: o digest semanal dos canais de equipa leva os KPIs da equipa e segue
também para o gestor, quando ele lê o que a equipa lê
(
backend/app/domain/alerts/team_channels.py). O relatório de equipa concluído segue por email aos membros e ao gestor comreadem todos os datasets gravados no pedido (backend/app/domain/insights/team_reports.py). - Na app, o seletor «Lente de equipa» (
frontend/src/components/TeamLensSelect.tsx) está nos três dashboards, em Problemas, Sugestões, Relatórios IA, SLO e «As minhas tarefas». A escolha fica no URL (?equipa=) e no browser (frontend/src/lib/teamLensStore.ts). A contagem de tarefas do menu e o resumo da página inicial seguem a escolha guardada. No modal «Relatório do workspace» escolhe-se o âmbito: todo o workspace, só para quem vê o global, ou uma equipa.
- Política do admin (
- Assistente em linguagem natural sobre os dados de qualidade do workspace.
- Scan ao vivo: o primeiro scan mostra-se passo a passo; primeiro resultado em minutos.
- Interface em português, inglês e espanhol; badge público de qualidade por dataset.
Testes: backend/tests/test_reports_v2.py, backend/tests/test_report_pdf.py,
backend/tests/test_assistant_chat.py, backend/tests/test_public_badge.py,
backend/tests/test_governance.py, backend/tests/test_business_metrics.py,
backend/tests/test_team_lens.py, backend/tests/test_team_lens_properties.py,
backend/tests/test_team_lens_views.py, backend/tests/test_team_report_scope.py,
backend/tests/test_team_digest_delivery.py.