Catálogo externo — publicar a qualidade no OpenMetadata, no DataHub, no Collibra e no Microsoft Purview (CATSYNC v1/v2/v3, cartão #398)
O BeryX publica, no fim de cada scan, o veredito, o score e o resultado de cada regra como testes de Data Quality no OpenMetadata do cliente. Quem vive no catálogo passa a ver a qualidade medida pelo BeryX na aba Data Quality / Profiler da própria tabela — sem abrir outra ferramenta.
Desde o CATSYNC v2 o mesmo push também publica no DataHub (assertions) — ver DataHub — e desde o CATSYNC v3 no Collibra (assets «Data Quality Metric» pela Import API) — ver Collibra. No Microsoft Purview (cartão #398) o veredito vai para atributos da própria tabela no Data Map — ver Microsoft Purview. Os catálogos partilham tudo exceto o cliente HTTP: mapeamento por conexão, conteúdo publicado, limites, retry, best-effort e API.
Read-only nas fontes mantém-se: o BeryX escreve só no catálogo que o admin ligou (opt-in por conexão) e só entidades próprias — no OpenMetadata, entidades de Data Quality com prefixo
datavault(definições, test cases e resultados); no DataHub, assertionsdatavault-*; no Collibra, assets «Data Quality Metric»DataVault:*; no Purview, os atributos personalizadosdatavault_*da entidade da tabela. Nunca altera tabelas, owners, tags, glossário nem lineage.
No sentido inverso, o BeryX também lê a curadoria do catálogo (domínios, owners, tags de sensibilidade, descrições e o glossário) — ver Ler do catálogo e Glossário importado. Essa leitura nunca escreve no catálogo.
O que aparece no OpenMetadata
Por cada dataset do scan com FQN mapeado e tabela existente no catálogo:
| Test case | Nível | Estado | Resultado |
|---|---|---|---|
datavault_quality |
tabela | Success se o veredito é pass/warn, Failed se fail, Aborted sem regras |
«Veredito BeryX AVISO · score 82.5 · 2 regra(s) falhada(s)» + valores verdict, score, failed_rules |
datavault_<tipo>_<id> |
coluna (ou tabela) | Failed / Success |
severidade, valor observado e limiar |
- O veredito é o mesmo do gate de CI (
GET /datasets/{id}/verdict):fail= regra crítica falhada ou contrato ODCS inválido;warn= só warnings.warnpublicaSuccessno test case do veredito (o catálogo não tem estado de aviso) — as regras em aviso aparecem comoFailednos seus test cases. - Regras que voltam a passar publicam
Success: o histórico no OpenMetadata mostra quando o problema apareceu e quando foi resolvido. - Limites por push: 200 datasets, 50 regras por dataset (falhadas primeiro).
- Só metadados e agregados (nomes de regras/colunas, contagens, min/max, score). Nunca linhas nem amostras.
Setup (10 minutos)
1. Criar o bot no OpenMetadata
- Settings → Bots → Add bot — ex.:
datavault-bot. - Atribui-lhe um papel que permita criar e editar
testDefinition,testCase(e respetivos resultados) e lertable— ex.: um papelDataVaultPublishercom uma política só com essas regras. Não precisa de mais nada: o BeryX não toca noutras entidades. - Copia o JWT token do bot.
2. Ligar o servidor no BeryX
Conexões → cartão da fonte → Catálogo externo → Ligar catálogo:
- URL do servidor:
https://openmetadata.empresa.pt(aceita também…/apiou…/api/v1). - Token do bot: o JWT do passo 1. É cifrado (Fernet) antes de gravar e nunca volta a ser mostrado nem aparece em logs.
- «Ligar e testar» confirma URL e token (
GET /api/v1/users/loggedInUser).
O URL passa pela proteção anti-SSRF: endereços privados/internos são
recusados. Um OpenMetadata na rede interna do cliente (ex.: BeryX
self-hosted no mesmo perímetro) exige o host em
DATAVAULT_CONNECTOR_ALLOWED_HOSTS.
3. Mapear os datasets para FQNs
No OpenMetadata o FQN de uma tabela é serviço.base.schema.tabela. Na
conexão indica o prefixo FQN — o serviço e a base desta fonte — e o
BeryX acrescenta schema.tabela:
| Fonte no BeryX | Prefixo FQN | Dataset | FQN publicado |
|---|---|---|---|
PostgreSQL analytics |
postgres_prod.analytics |
crm.clientes |
postgres_prod.analytics.crm.clientes |
Snowflake DWH |
snowflake_prod.DWH |
SALES.ORDERS |
snowflake_prod.DWH.SALES.ORDERS |
| CSV / SQLite (sem schema) | files.default.default |
vendas |
files.default.default.vendas |
Nomes com . são citados como no OpenMetadata ("v1.2").
Exceções (opcional), uma por linha, dataset = FQN:
crm.clientes = dwh_prod.core.crm.customers
staging.tmp_import =
Sem FQN depois do =, o dataset não é publicado.
4. Validar
«Publicar agora» reenvia o último scan concluído da conexão e mostra o estado:
- publicado — todos os datasets mapeados foram publicados;
- publicado em parte — alguns datasets sem FQN, sem tabela no catálogo (FQN errado?) ou com erro; a mensagem lista os primeiros;
- falhou — nada publicado (token revogado, servidor em baixo, nenhum FQN encontrado).
Daí em diante, cada scan concluído publica automaticamente (desliga com «Publicar automaticamente no fim de cada scan»).
Comportamento em falha
- Best-effort: o push corre no worker depois de o scan ficar
completed. Nenhum erro do catálogo falha, atrasa o estado ou repete o scan. - Retry leve: erros de rede,
429e5xxsão repetidos 2 vezes (0,5 s e 1,5 s).4xxsão definitivos. Após 3 datasets seguidos com erro e nenhum sucesso, o push para (catálogo em baixo / token revogado) em vez de martelar o servidor. - Erros ficam no estado da conexão e no log
catalog_push_*— sempre sem o token (as mensagens do servidor passam porscrub). - Compatível com OpenMetadata ≥ 1.5 (
POST /dataQuality/testCases/testCaseResults/{fqn}) e com versões 1.x anteriores (PUT /dataQuality/testCases/{fqn}/testCaseResult, usado automaticamente quando o endpoint novo devolve 404).
API
| Método | Caminho | Quem |
|---|---|---|
GET |
/catalog-integrations |
membros |
POST |
/catalog-integrations {name, kind: "openmetadata" | "datahub" | "collibra" | "purview", api_url, token} (+ username, community, domain no Collibra; + tenant_id, client_id no Purview, com o client secret em token) |
admin |
POST |
/catalog-integrations/{id}/test |
admin |
DELETE |
/catalog-integrations/{id} (apaga também os mapeamentos) |
admin |
GET |
/catalog-syncs (estado de todas as conexões) |
membros |
GET |
/connections/{id}/catalog-sync |
membros |
PUT |
/connections/{id}/catalog-sync {integration_id, fqn_prefix, overrides, enabled} |
admin |
DELETE |
/connections/{id}/catalog-sync |
admin |
POST |
/connections/{id}/catalog-sync/push |
admin |
Todas as alterações ficam no trilho de auditoria (catalog_integration_*,
catalog_sync_*).
DataHub (CATSYNC v2)
Mesmo desenho, pelo endpoint oficial do REST emitter do DataHub (GMS). O
BeryX escreve só entidades assertion próprias
(urn:li:assertion:datavault-<hash>, determinístico por dataset + teste —
cada scan atualiza a mesma assertion) com dois aspetos:
| Aspeto | Conteúdo |
|---|---|
assertionInfo |
type: DATASET, datasetAssertion com o URN do dataset, scope DATASET_COLUMN (+ fields = schemaField da coluna) ou DATASET_ROWS, operator: _NATIVE_, nativeType datavault_<tipo> / datavault_quality_verdict; source: EXTERNAL; descrição |
assertionRunEvent |
resultado deste scan: SUCCESS / FAILURE / ERROR (sem regras), nativeResults com a mensagem + verdict, score, failed_rules ou severity, rule_type |
Os testes publicados são os mesmos do OpenMetadata (veredito + uma por regra, mesmas regras de estado). As assertions aparecem na aba Quality / Validation do dataset.
Não escrevemos o aspeto
testResultsdo dataset: pertence às Metadata Tests do próprio DataHub e seria sobrescrito. Nenhum aspeto do dataset (schema, owners, tags…) é tocado.
Pedidos (todos com Authorization: Bearer <PAT>):
POST /api/graphql { me } — «Testar ligação»
GET /aspects/{urn}?aspect=datasetKey&version=0 — dataset existe?
POST /aspects?action=ingestProposal assertionInfo + assertionRunEvent
Setup
- Token: com a autenticação do metadata service ativa, cria um utilizador de serviço com permissão de editar assertions (política Edit Assertions nos datasets) e gera um Personal Access Token em Settings → Access Tokens.
- Ligar: Conexões → Catálogo externo → Ligar outro catálogo (ou
«Ligar um catálogo»), escolhe DataHub e indica o URL do GMS — ex.:
https://datahub.empresa.pt/api/gms(proxy do frontend),https://datahub-gms.empresa.pt:8080ouhttps://<tenant>.acryl.io/gms(aceita também o URL terminado em/api/graphql). Mesma proteção anti-SSRF e cifra Fernet do token. - Mapear: no DataHub a tabela é o URN
urn:li:dataset:(urn:li:dataPlatform:<plataforma>,<nome>,<ENV>). O prefixo éplataforma[@ENV].base— o BeryX acrescentaschema.tabelaao nome (ENV por omissãoPROD):
| Prefixo | Dataset | URN publicado |
|---|---|---|
postgres.analytics |
crm.clientes |
urn:li:dataset:(urn:li:dataPlatform:postgres,analytics.crm.clientes,PROD) |
snowflake@DEV.dwh |
sales.orders |
urn:li:dataset:(urn:li:dataPlatform:snowflake,dwh.sales.orders,DEV) |
postgres.prod-instance.analytics (platform instance no nome) |
crm.clientes |
…postgres,prod-instance.analytics.crm.clientes,PROD) |
Exceções: dataset = urn:li:dataset:(…) (usado tal e qual) ou
dataset = plataforma[@ENV].nome; vazio = não publicar. Prefixo/URN com
sintaxe inválida é recusado ao guardar (400).
Compatibilidade: DataHub ≥ 0.12 (campos source/description do
assertionInfo) e DataHub Cloud.
Collibra (CATSYNC v3)
Estado: validado com transporte simulado (a API do Collibra substituída por um servidor falso nos testes, com o CSV e o template verificados por snapshot). A validação num tenant Collibra de um cliente está pendente — até lá, confirma os três pressupostos de Por confirmar no tenant.
Pela Import REST API v2 do Collibra, como no exemplo oficial
«data-quality»
(«Import Data Quality Metrics from an external system»): um CSV + um template
JSON num único POST /rest/2.0/import/csv-job, seguido de polling do job.
Ao contrário do OpenMetadata e do DataHub, que recebem um pedido por teste, o
Collibra recebe um job de import por push com todos os testes do scan.
O que aparece no Collibra
Um asset do tipo OOTB Data Quality Metric por teste (veredito + uma regra por linha, as mesmas do OpenMetadata), no domínio Rulebook configurado:
| Campo do asset | Veredito (datavault_quality) |
Regra (datavault_<tipo>_<id>) |
|---|---|---|
| Nome completo | DataVault:<id do dataset>:datavault_quality |
DataVault:<id do dataset>:datavault_<tipo>_<id8> |
| Display name | clientes · veredito BeryX |
clientes · not_null (email) |
| Passing Fraction | score 0-100 | % de linhas que passam (ou 100/0, ver abaixo) |
| Threshold | — | % mínima para passar = 100 − limiar do BeryX |
| Loaded / Rows Passed / Rows Failed | — | contagens da regra, quando existem |
| Result | true se pass/warn, false se fail |
true se passou |
| Description | resumo + link para o dataset no BeryX + data do scan | idem |
| Relação | «complies to / applies to» (…7018) com a Table |
com a Column (ou a Table, se a regra é de tabela) |
- O nome completo é estável: cada scan atualiza o mesmo asset (upsert por nome + domínio da Import API) — sem estado extra no BeryX.
- Contagens: só agregados que o motor de regras já guarda —
not_null(linhas e nulos),unique(linhas e repetidas) e as regras com valores verificados/violações (pattern,membership,schema_conformity…). Regras sem contagem (frescura, volume, erro de avaliação) publicam uma métrica binária: Passing Fraction 100 se passou, 0 se falhou, sem linhas nem limiar. Nunca vão valores nem exemplos de linhas. - Uma célula vazia no CSV deixa o atributo por enviar (ex.: Threshold do veredito).
Pedidos
Todos com Authorization: Basic <utilizador:password> ou Bearer <JWT>:
GET /rest/2.0/users/current — «Testar ligação»
GET /rest/2.0/assets?name=<nome completo>&nameMatchMode=EXACT — a Table existe?
GET /rest/2.0/relations?targetId=<table>&relationTypeId=…7042 — colunas da tabela
POST /rest/2.0/import/csv-job multipart: file (CSV), template, separator «;», headerRow, continueOnError
GET /rest/2.0/jobs/{jobId} — polling a cada 2 s, teto de 30 s
O template declara a community e o domínio (tipo Rulebook) de destino — o import cria-os se não existirem, como no exemplo oficial — e um asset por linha do CSV identificado por nome + domínio, ligado ao asset alvo pelo id.
Setup
- Utilizador: um utilizador de serviço com permissão para importar e criar assets do tipo Data Quality Metric no domínio de destino (e para criar a community/domínio, se ainda não existirem), e para ver as Tables e Columns das fontes. Autenticação Basic (utilizador + password) ou um JWT do Collibra (deixar o utilizador vazio).
- Ligar: Conexões → Catálogo externo → Ligar um catálogo → Collibra:
- URL do Collibra:
https://empresa.collibra.com(aceita também…/rest/2.0). Mesma proteção anti-SSRF e mesma cifra Fernet (password, utilizador e JWT ficam cifrados; nunca voltam a ser mostrados nem aparecem em logs ou mensagens de erro). - Community de destino e Domínio de destino (Rulebook): onde ficam
as métricas — ex.:
Data Quality/DataVault. Recomendado um domínio Rulebook só para o BeryX. - «Ligar e testar» confirma URL e credenciais (
GET /users/current).
- URL do Collibra:
- Mapear: no Collibra a tabela é a Table com o nome completo da
convenção do Data Catalog, com
>como separador. O prefixo é tudo antes de>schema>tabela:
| Prefixo | Dataset | Table procurada |
|---|---|---|
postgres_prod>analytics |
crm.clientes |
postgres_prod>analytics>crm>clientes |
SQLite>erp |
clientes (sem schema) |
SQLite>erp>clientes |
Exceções: dataset = <nome completo da Table> (usado tal e qual — serve para
Tables registadas à mão com outros nomes); vazio = não publicar. As colunas são
as Columns ligadas à Table pela relação OOTB «Column is part of Table»
(…7042), comparadas pelo último segmento do nome (sem distinguir maiúsculas).
Comportamento em falha
- Table inexistente (ou nome ambíguo — várias Tables com o mesmo nome completo): o dataset fica em «sem tabela no catálogo» e a mensagem diz porquê; nunca exceção.
- Column inexistente: a métrica liga-se à Table e a mensagem do push lista as colunas em falta («N métrica(s) ligada(s) à tabela por falta do asset Column»).
- Job:
COMPLETED/SUCCESS→ publicado;COMPLETED_WITH_ERROR(continueOnError: os comandos válidos entram) → publicado em parte, com a mensagem do Collibra;ERROR/CANCELED/FAILURE→ falhou (nada do lote conta como publicado). Um job ainda a correr ao fim de 30 s fica «submetido» (não é erro): o resultado fica nas Activities do Collibra. - Como nos outros catálogos, nenhum erro do Collibra falha, atrasa o estado ou
repete o scan. O push corre no fim do scan (sem fila própria); o erro fica no
estado da conexão e no log
catalog_push_*com ocorrelation_iddo scan e o id do job — sempre sem credenciais. - Ler do catálogo (cartão #140) ainda não é suportado no Collibra: a
opção «Importar do catálogo ao sincronizar» fica escondida e
POST /connections/{id}/catalog-import/planresponde 400.
Por confirmar no tenant
O cliente HTTP segue o exemplo oficial e a especificação OpenAPI publicada no
mesmo repositório (import-api-java-client/schemas/). Três pontos só um tenant
real confirma:
- O atributo OOTB Description está atribuído ao tipo Data Quality Metric
(os outros seis atributos são os do exemplo oficial). Se não estiver, o job
termina
COMPLETED_WITH_ERROR(publicado em parte) até a atribuição existir. - Os nomes completos das Tables seguem a convenção
sistema>base>schema>tabelada ingestão do Data Catalog — senão, usar exceções com o nome completo real. - Uma célula vazia no CSV não apaga um valor já existente no atributo.
Microsoft Purview (cartão #398)
Estado: validado com transporte simulado. O Data Map e o login do Entra ID são substituídos por um
httpx.MockTransportque aplica a semântica de substituição docreateOrUpdatee recusa pedidos sem os atributos obrigatórios; o corpo do pedido tem snapshot embackend/tests/snapshots/purview/. Sem tenant Purview nesta sessão, o spike confirmou oscustomAttributesna especificação oficial da API do Data Map (2023-09-01, repositórioAzure/azure-rest-api-specs): o modeloAtlasEntitytemcustomAttributese o exemploEntity_AddCustomAttributeusa-os emPOST /atlas/v2/entity. A validação num tenant de um cliente está pendente — ver Por confirmar no tenant.
Pela API do Data Map (compatível com Apache Atlas v2), o BeryX escreve
quatro atributos personalizados (customAttributes) na entidade da tabela
(ATTRIBUTE_KEYS em backend/app/infra/catalog/purview.py):
| Atributo | Valor |
|---|---|
datavault_score |
score 0-100 do último scan, com duas casas (72.50); n/a sem score |
datavault_verdict |
pass, warn, fail ou unknown (o mesmo veredito do gate de CI) |
datavault_last_scan |
fim do scan em UTC, ISO 8601 (2026-10-11T11:00:00Z) |
datavault_url |
página do dataset no BeryX (app_base_url/datasets/{id}) |
Só o veredito sobe. As regras, uma a uma, não vão para o Purview nesta fase: o
push manda-lhe só o caso do veredito (CatalogSpec.publishes_rules=False), e o
estado da conexão conta um teste por dataset.
Pedidos
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token client credentials, scope https://purview.azure.net/.default
GET /datamap/api/atlas/v2/types/entitydef/name/DataSet testar a ligação
GET /datamap/api/atlas/v2/entity/uniqueAttribute/type/{typeName}?attr:qualifiedName=…&ignoreRelationships=true&minExtInfo=true
POST /datamap/api/atlas/v2/entity?api-version=2023-09-01 createOrUpdate
O createOrUpdate é uma atualização completa da entidade. Para não apagar nada
do cliente, o pedido reenvia o typeName, o guid, os attributes, os
contacts e os labels tal como o GET os devolveu e junta os quatro atributos
do BeryX aos customAttributes que já lá estavam (update_payload).
Classificações, relações e business attributes não vão no pedido. Quando os
quatro valores já são os do scan, não há escrita. O pedido não leva controlo de
concorrência: uma edição feita no Purview entre o GET e o POST do mesmo push é
reescrita com os valores lidos no GET.
O login no Entra ID tem um cliente HTTP próprio, com o mesmo guard anti-SSRF e
IP fixado que o endpoint do Purview (CatalogSpec.auth_url). O token de acesso
é pedido uma vez por push e fica só em memória; o client secret e o token
nunca aparecem em mensagens nem em logs.
Setup
Service principal: registar uma app no Entra ID, criar-lhe um client secret e, no Data Map do Purview, dar-lhe a role Data Curator na coleção onde estão as tabelas (a role que lê e altera assets).
Ligar: Conexões → Catálogo externo → Ligar um catálogo → Microsoft Purview:
- Endpoint da conta Purview:
https://<conta>.purview.azure.com(aceita também…/datamap/api); - Tenant (Directory ID) do Entra ID: o GUID do diretório ou um domínio
verificado (
empresa.onmicrosoft.com); - Client ID da app registada: o GUID da app;
- Client secret: cifrado com Fernet, nunca volta a ser mostrado.
O tenant e o client ID não são segredos e ficam em claro em
catalog_integrations.settings_json(sem migration: a coluna existe desde o Collibra). Ligar e testar pede um token ao Entra ID e lê a definição do tipoDataSetno Data Map: confirma as credenciais e a leitura. A permissão de escrita só se confirma no primeiro push.- Endpoint da conta Purview:
Mapear: o prefixo é o
qualifiedNameda base no Purview; o BeryX acrescenta/schema/tabelae procura a entidade pelo tipo e peloqualifiedName. O tipo (typeName) deduz-se do host onde a especificação o mostra; nas outras fontes escreve-se «tipo:qualifiedName»:Fonte Prefixo typeNameAzure SQL Database mssql://<servidor>.database.windows.net/<base>azure_sql_table, deduzidoAzure SQL Managed Instance mssql://<host>.database.windows.net:3342/<base>azure_sql_mi_table, deduzido da porta 3342Synapse, pool dedicado do workspace mssql://<workspace>.sql.azuresynapse.net/<pool>azure_synapse_dedicated_sql_table, deduzidoPool dedicado autónomo (ex-SQL DW) azure_sql_dw_table:mssql://<servidor>.database.windows.net/<dw>explícito: o host é igual ao do Azure SQL Database Synapse serverless, SQL Server, Fabric e outros <typeName>:<qualifiedName>explícito No Fabric o
qualifiedNamede uma tabela de Lakehouse acaba em…/tables/dbo%252Fminha_tabela(o schema e a tabela vão juntos, com a barra codificada), e por isso não se forma com prefixo +/schema/tabela: usa-se uma exceção por dataset com oqualifiedNamecompleto copiado do Purview. As exceções aceitam a mesma forma[tipo:]qualifiedName. Em produção, o conector Fabric não vem na imagem gerida (ver as notas honestas em CONECTORES.md); as fontes Microsoft que chegam ao Purview por este caminho são o Azure SQL e o Synapse, pelo conectormssql.
Comportamento em falha
- Entidade não encontrada (404 do GET, ou resposta sem entidade): o dataset fica em «sem tabela no catálogo» com a referência tentada («tipo:qualifiedName»). Um tipo errado aparece aqui.
- 401 do Entra ID (secret errado ou expirado) ou do Data Map (token
recusado): o push fica falhou com «Entra ID devolveu 401: …» ou
«Microsoft Purview devolveu 401: …», sem segredo nem token. O log
catalog_push_dataset_failedleva ocorrelation_iddo scan; três falhas seguidas sem nenhum sucesso param o push. - 429 e 5xx: duas tentativas extra com espera curta, como nos outros catálogos.
- Como nos outros catálogos, nenhum erro do Purview falha o scan.
- Ler do catálogo (cartão #140) ainda não é suportado no Purview: a
opção fica escondida e
POST /connections/{id}/catalog-import/planresponde
Fora desta fase
- Classificação «BeryX verdict»: exige criar a definição de tipo da classificação e aplicá-la à entidade; fica para um cartão seguinte.
- Data Quality API do Unified Catalog: segundo a página da Microsoft (learn.microsoft.com/rest/api/purview/unified-catalog-data-quality), está em pré-visualização pública desde dezembro de 2025 e só cobre as funcionalidades GA. Publicar lá as regras fica para quando sair da pré-visualização.
- Clouds soberanas (Azure Government, Azure China): o login usa sempre
login.microsoftonline.come o scopehttps://purview.azure.net/.default.
Por confirmar no tenant (Purview)
- Entidade inexistente: o Apache Atlas responde 404 com
ATLAS-404-00-009; a especificação do Purview só documenta o corpo de erro genérico. O cliente trata o 404 e um 200 sem entidade como «não encontrada». - Reenvio dos atributos lidos: o
createOrUpdateaceita osattributes,contactselabelstal como o GET os devolve, sem recusar atributos só de leitura e sem duplicar contactos ou labels. - Tipos deduzidos:
azure_sql_table,azure_sql_mi_tableeazure_sql_dw_tablevêm dos exemplos da especificação (Discovery_Query_*);azure_synapse_dedicated_sql_tablevem do acelerador oficial de linhagem da Microsoft (Purview-ADB-Lineage-Solution-Accelerator), não do scanner. - Limites dos
customAttributes: no Apache Atlas, chave até 50 caracteres ([a-zA-Z0-9_-]) e valor até 500, por omissão; a documentação do Purview não publica limites. O BeryX corta cada valor a 500 caracteres.
Ler do catálogo (cartão #140)
Clientes com OpenMetadata ou DataHub (o Collibra e o Purview ainda não — ver acima) já curaram owners, domínios, tags (PII,
Sensitive…) e glossário. Em vez de os recriar à mão, o BeryX lê essa
curadoria pela mesma integração e pelo mesmo mapeamento dataset → FQN/URN da
conexão (prefixo + exceções). O catálogo nunca é escrito neste caminho:
| Catálogo | Pedidos (só leitura) |
|---|---|
| OpenMetadata | GET /api/v1/tables/name/{fqn}?fields=owners,domain,tags,columns · GET /api/v1/tables?database=… (listar) · GET /api/v1/glossaryTerms (glossário) |
| DataHub | POST /api/graphql só com query — dataset(urn){ownership, domain, tags, glossaryTerms, schemaMetadata…}, search (listar) e search sobre GLOSSARY_TERM (glossário) |
O cliente de leitura (ReadOnlyJsonHttp) recusa, antes de sair para a
rede, qualquer método que não seja GET/HEAD e, no DataHub, qualquer POST
que não seja uma query GraphQL (uma mutation é recusada). No OpenMetadata
os termos de glossário vêm nas tags com source=Glossary.
O que é importado
| Do catálogo | No BeryX | Regras |
|---|---|---|
| Domínio da tabela | Domínio (criado se não existir) + associação do dataset | Upsert pelo nome, sem distinguir maiúsculas nem espaços a mais. Nunca remove associações. |
| Owners (utilizadores) | Owner do domínio | Só se o domínio ainda não tiver owner e o email/username do catálogo corresponder a uma conta ativa do workspace. Sem correspondência → aviso (sem criar contas nem convites). Equipas/grupos são ignorados. |
| Tags das colunas | Nível de sensibilidade com origem catálogo |
Ver tabela abaixo. Nunca sobrepõe uma classificação manual. |
| Descrição da tabela | Dataset.description (docs_source = catalog) |
Só quando a descrição do BeryX está vazia. |
| Termos de glossário | Termos do glossário só de leitura, ligados às tabelas e colunas | Ver Glossário importado. Nunca toca num termo criado no BeryX. |
Tags → nível de sensibilidade (último segmento da tag, sem maiúsculas; o nível mais alto ganha quando há várias):
| Tag no catálogo | Nível |
|---|---|
PII, qualquer PII.* (PII.Sensitive, PII.NonSensitive) |
Dados pessoais |
Sensitive, Confidential |
Confidencial |
Internal |
Interno |
Public |
Público |
Precedência: manual > catálogo > automático. O import cria a
classificação se a coluna não tiver nenhuma, atualiza uma que já veio do
catálogo, e só sobe uma automática (nunca desclassifica PII detetado). A
deteção de PII dos scans continua a poder subir uma classificação do catálogo
para «Dados pessoais». Cada mudança fica no rasto da coluna (origem
catalog, nota com a tag). Tags só ao nível da tabela não classificam
colunas — ficam como aviso no plano.
Plano e aplicação
- Conexões → Catálogo externo → Ler do catálogo → Pré-visualizar import:
o plano lista o que muda (
+criar,~alterar), os avisos (owners sem conta, curadoria manual mantida, tabelas com erro) e as contagens. - Aplicar import volta a ler o catálogo e aplica numa transação. Um segundo apply dá 0 alterações (idempotente).
- Opção «Importar do catálogo ao sincronizar»: o import corre no fim de cada scan concluído, depois do push dos verdicts, numa transação própria — best-effort, nunca falha o scan.
Em falha: cada tabela é lida à parte — uma tabela com erro fica em aviso e o lote continua; tabela inexistente no catálogo (404) conta como «sem metadados». Três falhas seguidas sem nenhuma leitura bem-sucedida param a leitura (catálogo em baixo ou token revogado).
Privacidade: emails/usernames dos owners do catálogo são dados pessoais — só servem para procurar a conta do workspace; não vão para logs, nem para a auditoria, nem para a resposta do plano (que mostra o nome da conta do BeryX). O token nunca aparece em mensagens.
API
| Método | Caminho | Quem | Efeito |
|---|---|---|---|
POST |
/connections/{id}/catalog-import/plan |
admin | Dry-run: {changes, warnings, errors, summary, stats, text} |
POST |
/connections/{id}/catalog-import/apply |
admin | Aplica; auditoria catalog_import_applied (só contagens) |
PUT |
/connections/{id}/catalog-sync |
admin | Campo import_on_sync (omitido = mantém) |
O estado da conexão (GET /connections/{id}/catalog-sync) inclui
import_on_sync, last_import_at e last_import_stats
(tables, read, not_found, failed, skipped, glossary_terms,
glossary_listed, changes…).
Permissões no catálogo: o bot/token só precisa de leitura de tabelas (OpenMetadata) ou de ver datasets (DataHub) para o import — as permissões de escrita de testes/assertions só são precisas para publicar.
Glossário importado (cartão #191)
Quem já tem glossário no OpenMetadata, no DataHub ou no dbt Semantic Layer não
o refaz no BeryX: os termos entram no glossário do BeryX como termos
importados (source = external), só de leitura, com a página do termo no
catálogo do cliente. O BeryX lê o glossário do cliente e não o substitui; nada
é escrito no catálogo (o mesmo ReadOnlyJsonHttp da leitura da curadoria).
O código vive em backend/app/domain/catalog/glossary_import.py.
De onde vêm
| Origem | Termos | Ligações a tabelas e colunas | Quando |
|---|---|---|---|
| OpenMetadata | GET /api/v1/glossaryTerms (todos os glossários, paginado, até 1000 termos e 50 páginas): nome (displayName), descrição, sinónimos e estado (entityStatus a partir da 1.10, status até à 1.9) |
As tags com source=Glossary da tabela e de cada coluna, pelo FQN do mapeamento da conexão |
No import da curadoria — a pedido ou no fim de cada scan, depois do push dos verdicts («Importar do catálogo ao sincronizar») |
| DataHub | search sobre GLOSSARY_TERM (até 1000 termos e 50 páginas): nome, descrição e o aspeto deprecation (o DataHub não tem sinónimos nem aprovação de termos) |
O aspeto glossaryTerms do dataset e de cada campo (schemaMetadata e editableSchemaMetadata), pelo URN |
Idem |
| dbt Semantic Layer | Cada métrica guardada em dbt_metric_definitions (#109): rótulo (ou nome), descrição e fórmula canónica |
As colunas da measure no dataset do modelo dbt (pelo nome do modelo, como no grafo); uma expressão que não é só uma coluna liga o dataset inteiro | No fim de cada import do manifest com apply, depois de gravar as descrições das colunas desse import; o sync do dbt-mcp não traz métricas, mas atualiza as definições com as descrições novas. Contagens na auditoria dbt_manifest_imported |
Sem descrição na métrica dbt, a definição usa a descrição dbt da coluna da measure (a do manifest, #106), já a deste import. Um termo citado por uma tabela mas fora da listagem (teto, ou listagem que falhou) entra na mesma com o que a leitura da tabela trouxe (nome e descrição da etiqueta no OpenMetadata; nome e descrição no DataHub).
Regras
- Termos locais ganham sempre: um termo da origem com o nome de um termo criado no BeryX (sem distinguir maiúsculas) não entra; o plano mostra um aviso com os nomes. Dois termos de origens diferentes com o mesmo nome: fica o primeiro, com aviso. O mesmo vale quando a origem renomeia um termo para um nome que outro termo já tem: o termo guarda o nome que tinha. O nome que um termo renomeado deixa só fica livre na sincronização seguinte.
- Atualizado a cada sincronização: nome, definição, sinónimos, URL e
estado. Estado no OpenMetadata (
entityStatusoustatus) → no BeryX:Approved(ou sem estado) → aprovado;Draft,In RevieweUnprocessed→ rascunho;DeprecatedeArchived→ depreciado. Um termoRejectednão entra, nem pela etiqueta de uma tabela, e um termo já importado que a origem rejeita passa a depreciado. Termos apagados ficam de fora. DataHub:deprecation.deprecated→ depreciado, senão aprovado. dbt: aprovado. Uma métrica dbt que desaparece de todos os projetos deixa o termo depreciado. Só os aprovados saem no contrato ODCS. - Só de leitura no BeryX:
PATCH /glossary/terms/{id}num termo importado responde 409 — muda-se na origem e a sincronização traz a alteração. Odatavault.yaml(GitOps) também não lhe toca: o export não o leva, um termo do ficheiro com o mesmo nome fica de fora, com aviso, e o prune não o apaga. Ligar colunas a um termo importado continua possível, como em qualquer termo. - Ligações: nascem aprovadas (a curadoria é do cliente), com origem
catalog(OpenMetadata, DataHub) oudbt. Uma ligação que já existe, em qualquer estado, fica como está: uma ligação rejeitada no BeryX não reabre. Uma ligação da origem que a origem deixou de ter sai — no catálogo, só nas tabelas lidas nessa corrida; no dbt, em todo o workspace. A criação e a remoção de ligações ficam no histórico dos metadados da coluna. - Plano: os termos e as ligações aparecem no plano do import como
termo do glossárioetermo ← coluna(+,~,-), com as contagensterms_created,terms_updated,term_links_createdeterm_links_removednosummarye na auditoriacatalog_import_applied. O segundo apply dá 0 alterações. A listagem do glossário a falhar fica em aviso e o import continua.
Para onde vão
- Contrato ODCS: um termo importado e aprovado, com ligação aprovada, sai
como
businessName/tags(como qualquer termo, #169) e também emauthoritativeDefinitions—{type: businessDefinition, url: <página do termo no catálogo do cliente>}na coluna, ou no objeto do schema quando a ligação é à tabela. Os termos do dbt não têm página e não levamauthoritativeDefinitions. O diff de versões do contrato compara também este campo. - Agentes: a ferramenta
search_glossarydo assistente e o recursoglossary://termsdo servidor MCP devolvemsource,source_systemesource_url— o agente sabe que o termo é do glossário do cliente e onde está. Nasearch_glossaryos termos depreciados ficam de fora, como os locais. - API:
GET /glossary/termseGET /glossary/terms/{id}devolvemsource(local|external),source_system(openmetadata|datahub|dbt),source_urlesynced_at.
URL do termo: no OpenMetadata, <URL do servidor>/glossary/<FQN>; no DataHub,
<UI>/glossaryTerm/<URN>, com a UI deduzida do URL do GMS (…/api/gms e
…/gms perdem o sufixo). Um GMS num host diferente do frontend
(datahub-gms.empresa.pt:8080) dá um link para esse host — nesse caso, o link
não abre a UI.
Permissões no catálogo: só leitura de termos de glossário (OpenMetadata:
glossaryTerm; DataHub: ver termos), além do que o import já pedia.